packages feed

haskoin-core 1.6.3 → 1.6.4

raw patch · 7 files changed

+145/−11 lines, 7 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Haskoin.Util.Helpers: encodeHexBytes :: ByteString -> ByteString
+ Haskoin.Util.Helpers: encodeHexReversed :: ShortByteString -> Text
+ Haskoin.Util.Helpers: hexEncodingReversed :: ShortByteString -> Encoding

Files

CHANGELOG.md view
@@ -5,6 +5,28 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## [1.6.4] - 2026-11-14++### Changed++- **Hex is written faster.** `encodeHex`, `encodeHexLazy`, `hexBuilder` and+  `hexEncoding` look up the two digits of each byte in a table. On a+  250-byte transaction, that is four times as fast as base16, which+  `encodeHex` used, and on a block of a megabyte, 1.5 times as fast as the+  builder of bytestring, which `hexBuilder` used. Short input inside a+  builder is still written by the builder's own hex, which is as fast+  there and allocates nothing.+- **Hashes of blocks and transactions are written five times as fast in+  JSON**: 78 ns where it took 390, straight from their bytes, without+  serializing and reversing them first. `txHashToHex`, `blockHashToHex`+  and the hashes in a block header gain too.++### Added++- `encodeHexBytes`, the hex of bytes as the bytes of its digits.+- `encodeHexReversed` and `hexEncodingReversed`, the hex of bytes in+  reverse order, as hashes are shown, as text and as JSON.+ ## [1.6.3] - 2026-11-14  ### Changed
README.md view
@@ -115,7 +115,7 @@ ```yaml resolver: nightly-2026-10-28 extra-deps:-  - haskoin-core-1.6.3+  - haskoin-core-1.6.4   - base16-2.0   - secp256k1-haskell-2.0.1 ```
haskoin-core.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack  name:           haskoin-core-version:        1.6.3+version:        1.6.4 synopsis:       Bitcoin & Bitcoin Cash library for Haskell description:    Please see the README on GitHub at <https://github.com/jprupp/haskoin-core#readme> category:       Bitcoin, Finance, Network
src/Haskoin/Block/Common.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-}@@ -152,11 +153,11 @@ -- | Hex of a 256-bit hash in display order, which is the reverse of its byte -- order in memory. Block hashes and Merkle roots are shown this way. reversedHex :: Hash256 -> Text-reversedHex = encodeHex . B.reverse . runPutS . serialize+reversedHex = encodeHexReversed . (.get)  -- | JSON encoding of a 256-bit hash in display order. reversedHexEncoding :: Hash256 -> Encoding-reversedHexEncoding = hexEncoding . L.reverse . runPutL . serialize+reversedHexEncoding = hexEncodingReversed . (.get)  -- | Parse hex in display order into a 256-bit hash. Any input that is not -- exactly 32 bytes of hex is rejected instead of being truncated.
src/Haskoin/Transaction/Common.hs view
@@ -108,7 +108,7 @@  instance ToJSON TxHash where   toJSON = A.String . txHashToHex-  toEncoding = hexEncoding . BL.reverse . runPutL . serialize+  toEncoding (TxHash h) = hexEncodingReversed h.get  -- | Hash of the transaction with every input script and the whole witness -- removed, so that it is the same before and after signing, whether the@@ -122,8 +122,7 @@  -- | Convert transaction hash to hex form, reversing bytes. txHashToHex :: TxHash -> Text-txHashToHex (TxHash h) =-  encodeHex . B.reverse . runPutS $ serialize h+txHashToHex (TxHash h) = encodeHexReversed h.get  -- | Convert transaction hash from hex, reversing bytes. The hex must be -- exactly 32 bytes long, as @uint256::FromHex@ requires in Bitcoin Core.
src/Haskoin/Util/Helpers.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ImportQualifiedPost #-} {-# LANGUAGE LambdaCase #-}@@ -19,7 +20,10 @@     hexEncoding,     hexBuilder,     encodeHex,+    encodeHexBytes,     encodeHexLazy,+    encodeHexReversed,+    hexEncodingReversed,     decodeHex,     decodeHexLazy,     getBits,@@ -82,8 +86,14 @@ import Data.ByteString qualified as B import Data.ByteString.Base16 import Data.ByteString.Builder+import Data.ByteString.Builder.Prim qualified as BP+import Data.ByteString.Builder.Prim.Internal qualified as BPI+import Data.ByteString.Internal qualified as BI import Data.ByteString.Lazy qualified as LB import Data.ByteString.Lazy.Base16 qualified as LB16+import Data.ByteString.Short (ShortByteString)+import Data.ByteString.Short qualified as SBS+import Data.ByteString.Unsafe qualified as BU import Data.Bytes.Get import Data.Bytes.Put import Data.Bytes.Serial@@ -98,6 +108,8 @@ import Data.Text.Lazy qualified as LT import Data.Text.Lazy.Encoding qualified as LT import Data.Word+import Foreign.Ptr (Ptr)+import Foreign.Storable (peekByteOff, pokeByteOff) import Test.Hspec  -- ByteString helpers@@ -118,20 +130,95 @@     f 0 = Nothing     f x = Just (fromInteger x :: Word8, x `shiftR` 8) +-- Hexadecimal+--+-- Hex is written by looking up the two digits of each byte in a table of+-- the 256 pairs. Measured on a 250-byte transaction, that is four times as+-- fast as base16, and twice as fast as the hex of the builder of+-- bytestring, which works a digit at a time; on a block of a megabyte, it+-- is 1.5 times as fast as the builder. Inside a builder, short input is+-- left to the builder's own hex, which costs as much there and writes in+-- place: copying in a table-made string would allocate for each one.++-- | JSON string of the hex of bytes. hexEncoding :: LB.ByteString -> Encoding hexEncoding b =   unsafeToEncoding $     char7 '"' <> hexBuilder b <> char7 '"' +-- | Hex of bytes, in lowercase, to append to other output. hexBuilder :: LB.ByteString -> Builder-hexBuilder = lazyByteStringHex+hexBuilder = foldMap chunk . LB.toChunks+  where+    chunk c+      | B.length c < 256 = byteStringHex c+      | otherwise = byteString (encodeHexBytes c) +-- | Hex of bytes, in lowercase. encodeHex :: ByteString -> Text-encodeHex = extractBase16 . encodeBase16+encodeHex = T.decodeLatin1 . encodeHexBytes +-- | Hex of bytes, in lowercase, as the bytes of its ASCII digits.+encodeHexBytes :: ByteString -> ByteString+encodeHexBytes bs =+  BI.unsafeCreate (2 * n) $ \out ->+    BU.unsafeUseAsCString bs $ \inp ->+      let go !i+            | i >= n = return ()+            | otherwise = do+                w <- peekByteOff inp i+                pokeDigits out (2 * i) w+                go (i + 1)+       in go 0+  where+    n = B.length bs+ -- | Encode as string of human-readable hex characters. encodeHexLazy :: LB.ByteString -> LT.Text-encodeHexLazy = extractBase16 . LB16.encodeBase16+encodeHexLazy = LT.fromChunks . map encodeHex . LB.toChunks++-- | Hex of bytes in reverse order, as hashes are shown, in lowercase.+encodeHexReversed :: ShortByteString -> Text+encodeHexReversed h =+  T.decodeLatin1 . BI.unsafeCreate (2 * n) $ \out ->+    let go !i+          | i >= n = return ()+          | otherwise = pokeDigits out (2 * i) (SBS.index h (n - 1 - i)) >> go (i + 1)+     in go 0+  where+    n = SBS.length h++-- | JSON string of the hex of bytes in reverse order, as hashes are shown.+-- A hash of 32 bytes, the size of the hashes of blocks and transactions,+-- is written in one step, straight from its bytes: a fifth of the time of+-- serializing it, reversing it and writing its hex.+hexEncodingReversed :: ShortByteString -> Encoding+hexEncodingReversed h+  | SBS.length h == 32 = unsafeToEncoding (BP.primFixed quotedHash h)+  | otherwise = hexEncoding (LB.fromStrict (B.reverse (SBS.fromShort h)))+  where+    quotedHash = BPI.fixedPrim 66 $ \x p -> do+      pokeByteOff p 0 (0x22 :: Word8)+      let go !i+            | i >= 32 = return ()+            | otherwise = pokeDigits p (1 + 2 * i) (SBS.index x (31 - i)) >> go (i + 1)+      go 0+      pokeByteOff p 65 (0x22 :: Word8)++-- | Write the two hex digits of a byte at an offset, from the table.+pokeDigits :: Ptr a -> Int -> Word8 -> IO ()+pokeDigits out at w = do+  let k = 2 * fromIntegral w+  pokeByteOff out at (BU.unsafeIndex hexDigits k)+  pokeByteOff out (at + 1) (BU.unsafeIndex hexDigits (k + 1))+{-# INLINE pokeDigits #-}++-- | The two lowercase hex digits of each byte, in order: 512 bytes.+hexDigits :: ByteString+hexDigits = B.pack (concat [[digit (w `shiftR` 4), digit (w .&. 15)] | w <- [0 .. 255]])+  where+    digit x = if x < 10 then 0x30 + x else 0x57 + x+{-# NOINLINE hexDigits #-}  decodeHex :: Text -> Maybe ByteString decodeHex t =
test/Haskoin/UtilSpec.hs view
@@ -15,8 +15,11 @@ import Data.Aeson.Encoding (encodingToLazyByteString) import Data.ByteString (ByteString) import Data.ByteString qualified as B-import Data.ByteString.Builder (toLazyByteString)+import Data.ByteString.Builder (byteStringHex, lazyByteStringHex, toLazyByteString) import Data.ByteString.Lazy qualified as BL+import Data.ByteString.Short qualified as SBS+import Data.Text.Encoding qualified as TE+import Data.Text.Lazy.Encoding qualified as TLE import Data.Bytes.Get import Data.Bytes.Put import Data.Default (def)@@ -101,6 +104,28 @@         encodingToLazyByteString (hexEncoding "\xab\xcd") `shouldBe` "\"abcd\""         toLazyByteString (hexBuilder "\xab\xcd") `shouldBe` "abcd"       prop "decodeHex . encodeHex" $ forAll arbitraryBS fromToHex+      -- the table-based hex against the hex of the builder of bytestring,+      -- byte by byte, on every byte value and on chunks of every size+      -- either side of where the builder's own hex takes over+      it "writes every byte value as the builder of bytestring does" $ do+        let every = B.pack [0 .. 255]+            reference = BL.toStrict (toLazyByteString (byteStringHex every))+        encodeHexBytes every `shouldBe` reference+        encodeHex every `shouldBe` TE.decodeLatin1 reference+      prop "writes what the builder of bytestring writes, whatever the chunks" $+        forAll (listOf (sized (\n -> B.pack <$> vectorOf (n * 7) arbitrary))) $ \chunks -> do+          let lazy = BL.fromChunks chunks+              reference = toLazyByteString (lazyByteStringHex lazy)+          toLazyByteString (hexBuilder lazy) `shouldBe` reference+          encodeHexLazy lazy `shouldBe` TLE.decodeLatin1 reference+          encodingToLazyByteString (hexEncoding lazy) `shouldBe` "\"" <> reference <> "\""+      prop "writes reversed hex as the hex of the reversed bytes" $+        forAll (oneof [pure 32, choose (0, 70)]) $ \n -> forAll (vectorOf n arbitrary) $ \ws -> do+          let bytes = B.pack ws+              reversed = BL.toStrict (toLazyByteString (byteStringHex (B.reverse bytes)))+          encodeHexReversed (SBS.toShort bytes) `shouldBe` TE.decodeLatin1 reversed+          encodingToLazyByteString (hexEncodingReversed (SBS.toShort bytes))+            `shouldBe` BL.fromStrict ("\"" <> reversed <> "\"")       prop "decodeHexLazy . encodeHexLazy" $ forAll arbitraryBS $ \bs -> decodeHexLazy (encodeHexLazy (BL.fromStrict bs)) == Just (BL.fromStrict bs)      describe "getBits" $ do