haskoin-store-data 2.0.1 → 2.0.2
raw patch · 4 files changed
+68/−18 lines, 4 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Haskoin.Store.Data: toTransactionWithId :: Network -> Ctx -> TxHash -> TxData -> Transaction
Files
- CHANGELOG.md +12/−1
- haskoin-store-data.cabal +1/−1
- src/Haskoin/Store/Data.hs +42/−16
- test/Haskoin/Store/DataSpec.hs +13/−0
CHANGELOG.md view
@@ -4,7 +4,18 @@ 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) -## [Unreleased]+## [2.0.2] - 2026-11-14++### Added+- **`toTransactionWithId`**, `toTransaction` for a transaction whose id is+ known, as it is for one read by its id. It serializes nothing:+ serializing and hashing the transaction took a quarter of the time of+ building its API form.++### Changed+- `toTransaction` counts the size and the weight from the lengths of the+ parts of the transaction, instead of serializing it with and without its+ witness. It still serializes it once without the witness, for its id. ## [2.0.1] - 2026-11-12
haskoin-store-data.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: haskoin-store-data-version: 2.0.1+version: 2.0.2 synopsis: Data types and API client of Haskoin Store description: The types of the data that Haskoin Store serves (blocks, transactions, balances, unspent outputs, extended public keys, health checks, events and
src/Haskoin/Store/Data.hs view
@@ -49,6 +49,7 @@ transactionData, fromTransaction, toTransaction,+ toTransactionWithId, StoreInput (..), isCoinbase, StoreOutput (..),@@ -1135,9 +1136,20 @@ -- | Build the API form of a stored transaction. The network decides which -- output scripts have an address. A SegWit-shaped script has none on Bitcoin--- Cash, for example.+-- Cash, for example. The id is the hash of the transaction without its+-- witness; where it is known, as it is for a transaction read by its id,+-- 'toTransactionWithId' saves serializing and hashing the transaction. toTransaction :: Network -> Ctx -> TxData -> Transaction-toTransaction net ctx t =+toTransaction net ctx t = toTransactionWithId net ctx (txHash t.tx) t++-- | 'toTransaction' for a transaction whose id is known, given here. It+-- serializes nothing: the size and the weight are counted from the lengths+-- of the parts of the transaction ('txSizes'). Serializing and hashing a+-- transaction took a quarter of the time of building its API form, for+-- bytes that the store had just read. The id is taken as given, so it has+-- to be the id of the transaction.+toTransactionWithId :: Network -> Ctx -> TxHash -> TxData -> Transaction+toTransactionWithId net ctx txid t = Transaction { version = t.tx.version, locktime = t.tx.locktime,@@ -1145,34 +1157,48 @@ deleted = t.deleted, rbf = t.rbf, timestamp = t.timestamp,- txid = TxHash (doubleSHA256 witless),+ txid = txid, inputs = ins, outputs = outs,- size = fromIntegral (B.length full),- weight = fromIntegral (B.length witless * 3 + B.length full),+ size = fromIntegral full,+ weight = fromIntegral (witless * 3 + full), fee = if any isCoinbase ins then 0 else inv `minus` outv } where- -- Serialize once with the witness and once without it, and only once- -- if there is no witness, when both are the same bytes. The id of the- -- transaction is the hash of the bytes without witness, as 'txHash'- -- computes it.- full, witless :: ByteString- full = runPutS (serialize t.tx)- witless- | hasWitness t.tx =- let Tx {..} = t.tx- in runPutS (serialize Tx {witness = [], ..})- | otherwise = full+ (full, witless) = txSizes t.tx ins = zipWith3 f [0 ..] t.tx.inputs (t.tx.witness <> repeat []) inv = sum (map (.value) ins) outs = zipWith g [0 ..] t.tx.outputs outv = sum $ map (.value) outs f n i w = toInput net ctx i (IntMap.lookup n t.prevs) w g n o = toOutput net ctx o $ IntMap.lookup n t.spenders++-- | The length of a transaction serialized, and its length without the+-- witness, counted from the lengths of its parts as 'serialize' writes+-- them: the witness, with its marker and flag, only if 'hasWitness', and+-- one stack for each input.+txSizes :: Tx -> (Int, Int)+txSizes tx = (if hasWitness tx then witless + 2 + witnesses else witless, witless)+ where+ witless =+ 8+ + list (length tx.inputs)+ + sum [40 + bytes (B.length i.script) | i <- tx.inputs]+ + list (length tx.outputs)+ + sum [8 + bytes (B.length o.script) | o <- tx.outputs]+ witnesses = sum [stack w | w <- take (length tx.inputs) (tx.witness <> repeat [])]+ stack w = list (length w) + sum (map (bytes . B.length) w)+ bytes n = list n + n+ -- the length of a count as a variable integer+ list :: Int -> Int+ list n+ | n < 0xfd = 1+ | n <= 0xffff = 3+ | n <= 0xffffffff = 5+ | otherwise = 9 -- | Inverse of 'toTransaction'. fromTransaction :: Transaction -> TxData
test/Haskoin/Store/DataSpec.hs view
@@ -262,6 +262,19 @@ fromIntegral (len witless * 3 + len td.tx), ws )+ it "gives the same with the id given" $+ forAll (arbitraryTxData ctx) $ \td ->+ toTransactionWithId btc ctx (txHash td.tx) td `shouldBe` toTransaction btc ctx td+ it "counts sizes as serializing does, with large counts and pushes" $+ -- counts and lengths of 253 and more take more than a byte+ forAll (arbitraryTxData ctx) $ \td -> forAll (choose (0, 3)) $ \k ->+ let big = B.replicate (0xfc + k * 0x100) 0x51+ Tx {..} = td.tx+ grown = case outputs of+ TxOut v _ : os -> Tx {outputs = TxOut v big : os, ..}+ [] -> Tx {..}+ t = toTransaction btc ctx (td {tx = grown} :: TxData)+ in t.size `shouldBe` fromIntegral (B.length (runPutS (serialize grown))) -- an output has an address only where the network has that kind forM_ [ ("a SegWit output an address on Bitcoin", btc, p2wpkh, "\"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4\""),