bolt5

On-chain transaction handling for Lightning (docs.ppad.tech/bolt5).
git clone git://git.ppad.tech/bolt5.git
Log | Files | Refs | README | LICENSE

commit 897e5397e0818a9014ba6f4ba2e29df8788a0295
parent e1594651e67c02a83079506762b019fb4fe42218
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 18 Apr 2026 21:15:27 +0800

Add criterion and weigh benchmarks

Criterion benchmarks for spend_to_local, spend_revoked_to_local,
spend_anchor_owner, spend_revoked_batch (10/100/483 outputs),
spending_fee, and htlc_timed_out.

Weigh allocation benchmarks for spend_to_local,
spend_anchor_owner, and spend_revoked_batch (10/100/483 outputs).

Both suites include orphan NFData instances for bolt3 and bolt5
types needed by criterion's nf and weigh's func.

Diffstat:
Mbench/Main.hs | 177++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mbench/Weight.hs | 142++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 309 insertions(+), 10 deletions(-)

diff --git a/bench/Main.hs b/bench/Main.hs @@ -1,16 +1,183 @@ {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} module Main where +import Bitcoin.Prim.Tx.Sighash (SighashType(..)) +import Control.DeepSeq (NFData(..)) import Criterion.Main -import qualified Lightning.Protocol.BOLT5 as BOLT5 +import qualified Data.ByteString as BS +import Lightning.Protocol.BOLT3 hiding + (txout_value, txout_script) +import qualified Lightning.Protocol.BOLT5 as B5 + +-- NFData orphan instances for criterion ----------------------------- + +instance NFData Satoshi where + rnf (Satoshi x) = rnf x + +instance NFData MilliSatoshi where + rnf (MilliSatoshi x) = rnf x + +instance NFData Script where + rnf (Script bs) = rnf bs + +instance NFData Pubkey where + rnf (Pubkey x) = rnf x + +instance NFData Point where + rnf (Point x) = rnf x + +instance NFData RevocationPubkey where + rnf (RevocationPubkey x) = rnf x + +instance NFData LocalDelayedPubkey where + rnf (LocalDelayedPubkey p) = rnf p + +instance NFData FundingPubkey where + rnf (FundingPubkey p) = rnf p + +instance NFData FeeratePerKw where + rnf (FeeratePerKw x) = rnf x + +instance NFData ToSelfDelay where + rnf (ToSelfDelay x) = rnf x + +instance NFData PaymentHash where + rnf (PaymentHash x) = rnf x + +instance NFData CltvExpiry where + rnf (CltvExpiry x) = rnf x + +instance NFData HTLCDirection where + rnf HTLCOffered = () + rnf HTLCReceived = () + +instance NFData HTLC where + rnf (HTLC d a h c) = + rnf d `seq` rnf a `seq` rnf h `seq` rnf c + +instance NFData SighashType + +instance NFData B5.OutputResolution where + rnf B5.Resolved = () + rnf (B5.Revoke rk) = rnf rk + rnf _ = () + +instance NFData B5.UnresolvedOutput where + rnf (B5.UnresolvedOutput op v t) = + rnf op `seq` rnf v `seq` rnf t + +instance NFData B5.SpendingTx where + rnf (B5.SpendingTx tx scr val sh) = + rnf tx `seq` rnf scr `seq` rnf val `seq` rnf sh + +instance NFData B5.PenaltyContext where + rnf (B5.PenaltyContext os rk d f) = + rnf os `seq` rnf rk `seq` rnf d `seq` rnf f main :: IO () main = defaultMain [ - placeholder + spend_benchmarks + , classify_benchmarks ] -placeholder :: Benchmark -placeholder = bgroup "placeholder" [ - bench "placeholder" $ nf (const BOLT5.placeholder) () +-- fixtures ----------------------------------------------------------- + +dummyPubkey :: Pubkey +dummyPubkey = case pubkey (BS.pack (0x02 : replicate 32 0x01)) of + Just pk -> pk + Nothing -> error "impossible" + +dummyTxId :: TxId +dummyTxId = case mkTxId (BS.replicate 32 0x00) of + Just tid -> tid + Nothing -> error "impossible" + +dummyOutPoint :: OutPoint +dummyOutPoint = OutPoint dummyTxId 0 + +dummyRevPk :: RevocationPubkey +dummyRevPk = RevocationPubkey dummyPubkey + +dummyDelayedPk :: LocalDelayedPubkey +dummyDelayedPk = LocalDelayedPubkey dummyPubkey + +dummyFundPk :: FundingPubkey +dummyFundPk = FundingPubkey dummyPubkey + +dummyDest :: Script +dummyDest = Script $ BS.pack [0x00, 0x14] <> + BS.replicate 20 0xCC + +dummyFeerate :: FeeratePerKw +dummyFeerate = FeeratePerKw 253 + +dummyDelay :: ToSelfDelay +dummyDelay = ToSelfDelay 144 + +mkRevokedOutputs :: Int -> [B5.UnresolvedOutput] +mkRevokedOutputs n = + [ B5.UnresolvedOutput + (OutPoint dummyTxId (fromIntegral i)) + (Satoshi 10000) + (B5.Revoke dummyRevPk) + | i <- [0..n-1] + ] + +-- benchmarks --------------------------------------------------------- + +spend_benchmarks :: Benchmark +spend_benchmarks = bgroup "spend" [ + bench "spend_to_local" $ + nf (\v -> B5.spend_to_local + dummyOutPoint v dummyRevPk dummyDelay + dummyDelayedPk dummyDest dummyFeerate) + (Satoshi 100000) + + , bench "spend_revoked_to_local" $ + nf (\v -> B5.spend_revoked_to_local + dummyOutPoint v dummyRevPk dummyDelay + dummyDelayedPk dummyDest dummyFeerate) + (Satoshi 100000) + + , bench "spend_anchor_owner" $ + nf (\v -> B5.spend_anchor_owner + dummyOutPoint v dummyFundPk dummyDest) + (Satoshi 330) + + , bench "spend_revoked_batch/10" $ + nf B5.spend_revoked_batch + (B5.PenaltyContext + (mkRevokedOutputs 10) + dummyRevPk dummyDest dummyFeerate) + + , bench "spend_revoked_batch/100" $ + nf B5.spend_revoked_batch + (B5.PenaltyContext + (mkRevokedOutputs 100) + dummyRevPk dummyDest dummyFeerate) + + , bench "spend_revoked_batch/483" $ + nf B5.spend_revoked_batch + (B5.PenaltyContext + (mkRevokedOutputs 483) + dummyRevPk dummyDest dummyFeerate) + ] + +classify_benchmarks :: Benchmark +classify_benchmarks = bgroup "classify" [ + bench "spending_fee" $ + nf (B5.spending_fee dummyFeerate) 538 + + , bench "htlc_timed_out" $ + nf (B5.htlc_timed_out 500001) + (HTLC HTLCOffered (MilliSatoshi 1000000) + dummyPaymentHash (CltvExpiry 500000)) ] + where + dummyPaymentHash = case payment_hash + (BS.replicate 32 0xAA) of + Just ph -> ph + Nothing -> error "impossible" diff --git a/bench/Weight.hs b/bench/Weight.hs @@ -1,14 +1,146 @@ {-# LANGUAGE BangPatterns #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} module Main where -import qualified Lightning.Protocol.BOLT5 as BOLT5 +import Bitcoin.Prim.Tx.Sighash (SighashType(..)) +import Control.DeepSeq (NFData(..)) +import qualified Data.ByteString as BS +import Lightning.Protocol.BOLT3 hiding + (txout_value, txout_script) +import qualified Lightning.Protocol.BOLT5 as B5 import Weigh +-- NFData orphan instances for weigh --------------------------------- + +instance NFData Satoshi where + rnf (Satoshi x) = rnf x + +instance NFData Script where + rnf (Script bs) = rnf bs + +instance NFData Pubkey where + rnf (Pubkey x) = rnf x + +instance NFData Point where + rnf (Point x) = rnf x + +instance NFData RevocationPubkey where + rnf (RevocationPubkey x) = rnf x + +instance NFData LocalDelayedPubkey where + rnf (LocalDelayedPubkey p) = rnf p + +instance NFData FundingPubkey where + rnf (FundingPubkey p) = rnf p + +instance NFData FeeratePerKw where + rnf (FeeratePerKw x) = rnf x + +instance NFData ToSelfDelay where + rnf (ToSelfDelay x) = rnf x + +instance NFData SighashType + +instance NFData B5.OutputResolution where + rnf B5.Resolved = () + rnf (B5.Revoke rk) = rnf rk + rnf _ = () + +instance NFData B5.UnresolvedOutput where + rnf (B5.UnresolvedOutput op v t) = + rnf op `seq` rnf v `seq` rnf t + +instance NFData B5.SpendingTx where + rnf (B5.SpendingTx tx scr val sh) = + rnf tx `seq` rnf scr `seq` rnf val `seq` rnf sh + +instance NFData B5.PenaltyContext where + rnf (B5.PenaltyContext os rk d f) = + rnf os `seq` rnf rk `seq` rnf d `seq` rnf f + -- note that 'weigh' doesn't work properly in a repl main :: IO () -main = mainWith placeholder +main = mainWith $ do + spend_weights + batch_weights + +-- fixtures ----------------------------------------------------------- + +dummyPubkey :: Pubkey +dummyPubkey = case pubkey (BS.pack (0x02 : replicate 32 0x01)) of + Just pk -> pk + Nothing -> error "impossible" + +dummyTxId :: TxId +dummyTxId = case mkTxId (BS.replicate 32 0x00) of + Just tid -> tid + Nothing -> error "impossible" + +dummyOutPoint :: OutPoint +dummyOutPoint = OutPoint dummyTxId 0 + +dummyRevPk :: RevocationPubkey +dummyRevPk = RevocationPubkey dummyPubkey + +dummyDelayedPk :: LocalDelayedPubkey +dummyDelayedPk = LocalDelayedPubkey dummyPubkey + +dummyFundPk :: FundingPubkey +dummyFundPk = FundingPubkey dummyPubkey + +dummyDest :: Script +dummyDest = Script $ BS.pack [0x00, 0x14] <> + BS.replicate 20 0xCC + +dummyFeerate :: FeeratePerKw +dummyFeerate = FeeratePerKw 253 + +dummyDelay :: ToSelfDelay +dummyDelay = ToSelfDelay 144 + +mkRevokedOutputs :: Int -> [B5.UnresolvedOutput] +mkRevokedOutputs n = + [ B5.UnresolvedOutput + (OutPoint dummyTxId (fromIntegral i)) + (Satoshi 10000) + (B5.Revoke dummyRevPk) + | i <- [0..n-1] + ] + +-- weights ------------------------------------------------------------ + +spend_weights :: Weigh () +spend_weights = wgroup "spend" $ do + func "spend_to_local" + (\v -> B5.spend_to_local + dummyOutPoint v dummyRevPk dummyDelay + dummyDelayedPk dummyDest dummyFeerate) + (Satoshi 100000) + + func "spend_anchor_owner" + (\v -> B5.spend_anchor_owner + dummyOutPoint v dummyFundPk dummyDest) + (Satoshi 330) + +batch_weights :: Weigh () +batch_weights = wgroup "batch" $ do + func "spend_revoked_batch/10" + B5.spend_revoked_batch + (B5.PenaltyContext + (mkRevokedOutputs 10) + dummyRevPk dummyDest dummyFeerate) + + func "spend_revoked_batch/100" + B5.spend_revoked_batch + (B5.PenaltyContext + (mkRevokedOutputs 100) + dummyRevPk dummyDest dummyFeerate) -placeholder :: Weigh () -placeholder = wgroup "placeholder" $ - func "placeholder" (const BOLT5.placeholder) () + func "spend_revoked_batch/483" + B5.spend_revoked_batch + (B5.PenaltyContext + (mkRevokedOutputs 483) + dummyRevPk dummyDest dummyFeerate)