commit 26b4cce6da935770559d2c1e0a2550ae71a7cada
parent 6da7d511bf5afe9241caa6d16a16595fe91348c6
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 25 Jan 2026 15:08:32 +0400
Expand weigh benchmark suite with comprehensive allocation tracking
Add NFData instances for all transaction, output, context, and error
types. Mirror the criterion benchmark structure with allocation
tracking for:
- Transaction building (commitment, HTLC timeout/success, closing)
- Script generation (funding, to_local, to_remote, anchor, HTLC)
- Transaction serialization (encode_tx variants)
- Transaction parsing (decode_tx with varying HTLC counts)
- Validation functions
- Secret storage operations (insert_secret, derive_old_secret)
- Output sorting
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat:
| M | bench/Weight.hs | | | 359 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 359 insertions(+), 0 deletions(-)
diff --git a/bench/Weight.hs b/bench/Weight.hs
@@ -5,16 +5,20 @@ module Main where
import Control.DeepSeq (NFData(..))
import qualified Data.ByteString as BS
+import Data.Word (Word32, Word64)
import Lightning.Protocol.BOLT3
import Weigh
-- NFData instances for weigh
+
+-- Monetary types
instance NFData Satoshi where
rnf (Satoshi x) = rnf x
instance NFData MilliSatoshi where
rnf (MilliSatoshi x) = rnf x
+-- Key types
instance NFData Pubkey where
rnf (Pubkey x) = rnf x
@@ -30,6 +34,40 @@ instance NFData RevocationPubkey where
instance NFData RevocationBasepoint where
rnf (RevocationBasepoint x) = rnf x
+instance NFData LocalDelayedPubkey where
+ rnf (LocalDelayedPubkey p) = rnf p
+
+instance NFData RemoteDelayedPubkey where
+ rnf (RemoteDelayedPubkey p) = rnf p
+
+instance NFData LocalHtlcPubkey where
+ rnf (LocalHtlcPubkey p) = rnf p
+
+instance NFData RemoteHtlcPubkey where
+ rnf (RemoteHtlcPubkey p) = rnf p
+
+instance NFData LocalPubkey where
+ rnf (LocalPubkey p) = rnf p
+
+instance NFData RemotePubkey where
+ rnf (RemotePubkey p) = rnf p
+
+instance NFData PaymentBasepoint where
+ rnf (PaymentBasepoint p) = rnf p
+
+instance NFData DelayedPaymentBasepoint where
+ rnf (DelayedPaymentBasepoint p) = rnf p
+
+instance NFData HtlcBasepoint where
+ rnf (HtlcBasepoint p) = rnf p
+
+instance NFData FundingPubkey where
+ rnf (FundingPubkey p) = rnf p
+
+instance NFData PerCommitmentSecret where
+ rnf (PerCommitmentSecret bs) = rnf bs
+
+-- Channel features
instance NFData ChannelFeatures where
rnf (ChannelFeatures x) = rnf x
@@ -39,12 +77,14 @@ instance NFData FeeratePerKw where
instance NFData DustLimit where
rnf (DustLimit x) = rnf x
+-- Hash types
instance NFData PaymentHash where
rnf (PaymentHash x) = rnf x
instance NFData CltvExpiry where
rnf (CltvExpiry x) = rnf x
+-- HTLC types
instance NFData HTLCDirection where
rnf HTLCOffered = ()
rnf HTLCReceived = ()
@@ -52,6 +92,115 @@ instance NFData HTLCDirection where
instance NFData HTLC where
rnf (HTLC d a h c) = rnf d `seq` rnf a `seq` rnf h `seq` rnf c
+-- Transaction types
+instance NFData CommitmentTx where
+ rnf (CommitmentTx v l i s o f) =
+ rnf v `seq` rnf l `seq` rnf i `seq` rnf s `seq` rnf o `seq` rnf f
+
+instance NFData HTLCTx where
+ rnf (HTLCTx v l i s ov os) =
+ rnf v `seq` rnf l `seq` rnf i `seq` rnf s `seq` rnf ov `seq` rnf os
+
+instance NFData ClosingTx where
+ rnf (ClosingTx v l i s o f) =
+ rnf v `seq` rnf l `seq` rnf i `seq` rnf s `seq` rnf o `seq` rnf f
+
+-- Output types
+instance NFData TxOutput where
+ rnf (TxOutput v s t) = rnf v `seq` rnf s `seq` rnf t
+
+instance NFData OutputType where
+ rnf OutputToLocal = ()
+ rnf OutputToRemote = ()
+ rnf OutputLocalAnchor = ()
+ rnf OutputRemoteAnchor = ()
+ rnf (OutputOfferedHTLC e) = rnf e
+ rnf (OutputReceivedHTLC e) = rnf e
+
+-- Primitives
+instance NFData Script where
+ rnf (Script bs) = rnf bs
+
+instance NFData Witness where
+ rnf (Witness items) = rnf items
+
+instance NFData Outpoint where
+ rnf (Outpoint t i) = rnf t `seq` rnf i
+
+instance NFData Sequence where
+ rnf (Sequence x) = rnf x
+
+instance NFData Locktime where
+ rnf (Locktime x) = rnf x
+
+instance NFData TxId where
+ rnf (TxId bs) = rnf bs
+
+instance NFData ToSelfDelay where
+ rnf (ToSelfDelay x) = rnf x
+
+instance NFData CommitmentNumber where
+ rnf (CommitmentNumber x) = rnf x
+
+-- Parsing types
+instance NFData RawTx where
+ rnf (RawTx v i o w l) =
+ rnf v `seq` rnf i `seq` rnf o `seq` rnf w `seq` rnf l
+
+instance NFData RawInput where
+ rnf (RawInput o scr sq) = rnf o `seq` rnf scr `seq` rnf sq
+
+instance NFData RawOutput where
+ rnf (RawOutput v s) = rnf v `seq` rnf s
+
+-- Context types
+instance NFData CommitmentContext where
+ rnf ctx = rnf (cc_funding_outpoint ctx) `seq`
+ rnf (cc_commitment_number ctx) `seq`
+ rnf (cc_htlcs ctx) `seq`
+ rnf (cc_keys ctx)
+
+instance NFData CommitmentKeys where
+ rnf keys = rnf (ck_revocation_pubkey keys) `seq`
+ rnf (ck_local_delayed keys) `seq`
+ rnf (ck_local_htlc keys) `seq`
+ rnf (ck_remote_htlc keys)
+
+instance NFData HTLCContext where
+ rnf ctx = rnf (hc_commitment_txid ctx) `seq`
+ rnf (hc_htlc ctx)
+
+instance NFData ClosingContext where
+ rnf ctx = rnf (clc_funding_outpoint ctx) `seq`
+ rnf (clc_local_amount ctx) `seq`
+ rnf (clc_remote_amount ctx)
+
+-- Error types
+instance NFData DecodeError where
+ rnf (InsufficientBytes e a) = rnf e `seq` rnf a
+ rnf (InvalidMarker w) = rnf w
+ rnf (InvalidFlag w) = rnf w
+ rnf InvalidVarint = ()
+ rnf EmptyInput = ()
+
+instance NFData ValidationError where
+ rnf (InvalidVersion e a) = rnf e `seq` rnf a
+ rnf (InvalidLocktime lt) = rnf lt
+ rnf (InvalidSequence sq) = rnf sq
+ rnf InvalidOutputOrdering = ()
+ rnf (DustLimitViolation i v d) = rnf i `seq` rnf v `seq` rnf d
+ rnf MissingAnchorOutput = ()
+ rnf (InvalidAnchorValue v) = rnf v
+ rnf (InvalidFee e a) = rnf e `seq` rnf a
+ rnf (InvalidHTLCLocktime e a) = rnf e `seq` rnf a
+ rnf (InvalidHTLCSequence e a) = rnf e `seq` rnf a
+ rnf NoOutputs = ()
+ rnf (TooManyOutputs n) = rnf n
+
+-- Secret store (opaque, use generic rnf on the list)
+instance NFData SecretStore where
+ rnf ss = ss `seq` ()
+
main :: IO ()
main = mainWith $ do
setColumns [Case, Allocated, GCs, Max]
@@ -78,6 +227,105 @@ main = mainWith $ do
(is_trimmed dust feerate noAnchors) htlcTrimmed
func "htlc_trim_threshold"
(htlc_trim_threshold dust feerate noAnchors) HTLCOffered
+
+ -- Transaction building allocations
+ func "build_commitment_tx (0 htlcs, no anchors)"
+ build_commitment_tx (mkCommitmentContext htlcs0 noAnchors)
+ func "build_commitment_tx (10 htlcs, no anchors)"
+ build_commitment_tx (mkCommitmentContext htlcs10 noAnchors)
+ func "build_commitment_tx (100 htlcs, no anchors)"
+ build_commitment_tx (mkCommitmentContext htlcs100 noAnchors)
+ func "build_commitment_tx (10 htlcs, anchors)"
+ build_commitment_tx (mkCommitmentContext htlcs10 withAnchors)
+ func "build_htlc_timeout_tx"
+ build_htlc_timeout_tx sampleHtlcContext
+ func "build_htlc_success_tx"
+ build_htlc_success_tx sampleHtlcContext
+ func "build_closing_tx"
+ build_closing_tx sampleClosingContext
+
+ -- Script generation allocations
+ func "funding_script"
+ (funding_script (FundingPubkey samplePubkey1))
+ (FundingPubkey samplePubkey2)
+ func "to_local_script"
+ (to_local_script (RevocationPubkey samplePubkey1)
+ (ToSelfDelay 144))
+ (LocalDelayedPubkey samplePubkey2)
+ func "to_remote_script (no anchors)"
+ (to_remote_script (RemotePubkey samplePubkey1)) noAnchors
+ func "to_remote_script (anchors)"
+ (to_remote_script (RemotePubkey samplePubkey1)) withAnchors
+ func "anchor_script"
+ anchor_script (FundingPubkey samplePubkey1)
+ func "offered_htlc_script"
+ (offered_htlc_script (RevocationPubkey samplePubkey1)
+ (RemoteHtlcPubkey samplePubkey2)
+ (LocalHtlcPubkey samplePubkey3)
+ (PaymentHash $ BS.replicate 32 0))
+ noAnchors
+ func "received_htlc_script"
+ (received_htlc_script (RevocationPubkey samplePubkey1)
+ (RemoteHtlcPubkey samplePubkey2)
+ (LocalHtlcPubkey samplePubkey3)
+ (PaymentHash $ BS.replicate 32 0)
+ (CltvExpiry 500000))
+ noAnchors
+
+ -- Serialization allocations
+ func "encode_tx (0 htlcs)"
+ encode_tx (build_commitment_tx $ mkCommitmentContext htlcs0 noAnchors)
+ func "encode_tx (10 htlcs)"
+ encode_tx (build_commitment_tx $ mkCommitmentContext htlcs10 noAnchors)
+ func "encode_tx (100 htlcs)"
+ encode_tx (build_commitment_tx $ mkCommitmentContext htlcs100 noAnchors)
+ func "encode_htlc_tx"
+ encode_htlc_tx (build_htlc_timeout_tx sampleHtlcContext)
+ func "encode_closing_tx"
+ encode_closing_tx (build_closing_tx sampleClosingContext)
+
+ -- Parsing allocations
+ func "decode_tx (0 htlcs)"
+ decode_tx (encode_tx $ build_commitment_tx $
+ mkCommitmentContext htlcs0 noAnchors)
+ func "decode_tx (10 htlcs)"
+ decode_tx (encode_tx $ build_commitment_tx $
+ mkCommitmentContext htlcs10 noAnchors)
+ func "decode_tx (100 htlcs)"
+ decode_tx (encode_tx $ build_commitment_tx $
+ mkCommitmentContext htlcs100 noAnchors)
+
+ -- Validation allocations
+ func "validate_commitment_tx (valid)"
+ (validate_commitment_tx dust noAnchors)
+ (build_commitment_tx $ mkCommitmentContext htlcs10 noAnchors)
+ func "validate_htlc_tx"
+ validate_htlc_tx (build_htlc_timeout_tx sampleHtlcContext)
+ func "validate_closing_tx"
+ validate_closing_tx (build_closing_tx sampleClosingContext)
+ func "validate_output_ordering"
+ validate_output_ordering (ctx_outputs $ build_commitment_tx $
+ mkCommitmentContext htlcs10 noAnchors)
+
+ -- Secret storage allocations
+ func "insert_secret (first)"
+ (insert_secret (BS.replicate 32 0xFF) 281474976710655)
+ empty_store
+ func "derive_old_secret (recent)"
+ (derive_old_secret 281474976710654)
+ filledStore
+ func "derive_old_secret (old)"
+ (derive_old_secret 281474976710600)
+ filledStore
+
+ -- Output sorting allocations
+ func "sort_outputs (10)"
+ sort_outputs (ctx_outputs $ build_commitment_tx $
+ mkCommitmentContext htlcs10 noAnchors)
+ func "sort_outputs (100)"
+ sort_outputs (ctx_outputs $ build_commitment_tx $
+ mkCommitmentContext htlcs100 noAnchors)
+
where
-- Key derivation test data
basepoint = Point $ BS.pack
@@ -101,6 +349,7 @@ main = mainWith $ do
-- Fee calculation test data
feerate = FeeratePerKw 5000
noAnchors = ChannelFeatures { cf_option_anchors = False }
+ withAnchors = ChannelFeatures { cf_option_anchors = True }
-- Trimming test data
dust = DustLimit (Satoshi 546)
@@ -118,3 +367,113 @@ main = mainWith $ do
, htlc_payment_hash = PaymentHash (BS.replicate 32 0)
, htlc_cltv_expiry = CltvExpiry 500000
}
+
+ -- Sample pubkeys
+ samplePubkey1, samplePubkey2, samplePubkey3 :: Pubkey
+ samplePubkey1 = Pubkey $ BS.pack
+ [0x03, 0x6d, 0x6c, 0xaa, 0xc2, 0x48, 0xaf, 0x96, 0xf6, 0xaf, 0xa7,
+ 0xf9, 0x04, 0xf5, 0x50, 0x25, 0x3a, 0x0f, 0x3e, 0xf3, 0xf5, 0xaa,
+ 0x2f, 0xe6, 0x83, 0x8a, 0x95, 0xb2, 0x16, 0x69, 0x14, 0x68, 0xe2]
+ samplePubkey2 = Pubkey $ BS.pack
+ [0x02, 0x5f, 0x71, 0x17, 0xa7, 0x81, 0x50, 0xfe, 0x2e, 0xf9, 0x7d,
+ 0xb7, 0xcf, 0xc8, 0x3b, 0xd5, 0x7b, 0x2e, 0x2c, 0x0d, 0x0d, 0xd2,
+ 0x5e, 0xaf, 0x46, 0x7a, 0x4a, 0x1c, 0x2a, 0x45, 0xce, 0x14, 0x86]
+ samplePubkey3 = samplePubkey1
+
+ -- Funding outpoint
+ sampleFundingOutpoint :: Outpoint
+ sampleFundingOutpoint = Outpoint (TxId $ BS.replicate 32 0x01) 0
+
+ -- HTLC builder
+ mkHtlc :: HTLCDirection -> Word64 -> Word32 -> HTLC
+ mkHtlc dir amtMsat expiry = HTLC
+ { htlc_direction = dir
+ , htlc_amount_msat = MilliSatoshi amtMsat
+ , htlc_payment_hash = PaymentHash (BS.replicate 32 0x00)
+ , htlc_cltv_expiry = CltvExpiry expiry
+ }
+
+ htlcs0, htlcs10, htlcs100 :: [HTLC]
+ htlcs0 = []
+ htlcs10 = [mkHtlc (if even i then HTLCOffered else HTLCReceived)
+ (5000000 + i * 100000) (500000 + fromIntegral i)
+ | i <- [0..9]]
+ htlcs100 = [mkHtlc (if even i then HTLCOffered else HTLCReceived)
+ (5000000 + i * 10000) (500000 + fromIntegral i)
+ | i <- [0..99]]
+
+ -- CommitmentKeys
+ sampleCommitmentKeys :: CommitmentKeys
+ sampleCommitmentKeys = CommitmentKeys
+ { ck_revocation_pubkey = RevocationPubkey samplePubkey1
+ , ck_local_delayed = LocalDelayedPubkey samplePubkey1
+ , ck_local_htlc = LocalHtlcPubkey samplePubkey1
+ , ck_remote_htlc = RemoteHtlcPubkey samplePubkey2
+ , ck_local_payment = LocalPubkey samplePubkey1
+ , ck_remote_payment = RemotePubkey samplePubkey2
+ , ck_local_funding = FundingPubkey samplePubkey1
+ , ck_remote_funding = FundingPubkey samplePubkey2
+ }
+
+ -- CommitmentContext builder
+ mkCommitmentContext :: [HTLC] -> ChannelFeatures -> CommitmentContext
+ mkCommitmentContext htlcs features = CommitmentContext
+ { cc_funding_outpoint = sampleFundingOutpoint
+ , cc_commitment_number = CommitmentNumber 42
+ , cc_local_payment_bp = PaymentBasepoint $
+ Point $ unPubkey samplePubkey1
+ , cc_remote_payment_bp = PaymentBasepoint $
+ Point $ unPubkey samplePubkey2
+ , cc_to_self_delay = ToSelfDelay 144
+ , cc_dust_limit = DustLimit (Satoshi 546)
+ , cc_feerate = FeeratePerKw 5000
+ , cc_features = features
+ , cc_is_funder = True
+ , cc_to_local_msat = MilliSatoshi 500000000
+ , cc_to_remote_msat = MilliSatoshi 500000000
+ , cc_htlcs = htlcs
+ , cc_keys = sampleCommitmentKeys
+ }
+
+ -- HTLC context
+ sampleHtlcContext :: HTLCContext
+ sampleHtlcContext = HTLCContext
+ { hc_commitment_txid = TxId $ BS.replicate 32 0x01
+ , hc_output_index = 0
+ , hc_htlc = mkHtlc HTLCOffered 5000000 500000
+ , hc_to_self_delay = ToSelfDelay 144
+ , hc_feerate = FeeratePerKw 5000
+ , hc_features = noAnchors
+ , hc_revocation_pubkey = RevocationPubkey samplePubkey1
+ , hc_local_delayed = LocalDelayedPubkey samplePubkey1
+ }
+
+ -- Closing context
+ sampleClosingContext :: ClosingContext
+ sampleClosingContext = ClosingContext
+ { clc_funding_outpoint = sampleFundingOutpoint
+ , clc_local_amount = Satoshi 500000
+ , clc_remote_amount = Satoshi 500000
+ , clc_local_script = Script $
+ BS.pack [0x00, 0x14] <> BS.replicate 20 0x01
+ , clc_remote_script = Script $
+ BS.pack [0x00, 0x14] <> BS.replicate 20 0x02
+ , clc_local_dust_limit = DustLimit (Satoshi 546)
+ , clc_remote_dust_limit = DustLimit (Satoshi 546)
+ , clc_fee = Satoshi 1000
+ , clc_is_funder = True
+ , clc_locktime = Locktime 0
+ , clc_funding_script = funding_script (FundingPubkey samplePubkey1)
+ (FundingPubkey samplePubkey2)
+ }
+
+ -- Secret storage for benchmarks
+ filledStore :: SecretStore
+ filledStore = foldl insertOne empty_store [0..99]
+ where
+ insertOne store i =
+ let idx = 281474976710655 - i
+ sec = BS.replicate 32 (fromIntegral i)
+ in case insert_secret sec idx store of
+ Just s -> s
+ Nothing -> store