bolt2

Lightning peer protocol, per BOLT #2.
git clone git://git.ppad.tech/bolt2.git
Log | Files | Refs | README | LICENSE

commit 779bf5c0d8de70c80aaa2bc8b9ee3430f768c15a
parent 93521021143220e4829968a962b44f1f82922258
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 25 Jan 2026 11:15:52 +0400

Merge impl/bench: criterion and weigh benchmarks for BOLT2 codecs

Adds timing and allocation benchmarks for representative messages from
each category:

Criterion (bench/Main.hs):
- V1: OpenChannel encode/decode
- V2: OpenChannel2, TxSignatures with 5 witnesses
- Close: ClosingSigned
- Normal: UpdateAddHtlc, CommitmentSigned with 10 HTLC sigs

Weigh (bench/Weight.hs):
- Allocation tracking for construction, encoding, and decoding

Diffstat:
Mbench/Main.hs | 243++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mbench/Weight.hs | 282++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mppad-bolt2.cabal | 2++
3 files changed, 525 insertions(+), 2 deletions(-)

diff --git a/bench/Main.hs b/bench/Main.hs @@ -1,10 +1,251 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} +-- | +-- Module: Main +-- Copyright: (c) 2025 Jared Tobin +-- License: MIT +-- Maintainer: Jared Tobin <jared@ppad.tech> +-- +-- Criterion timing benchmarks for BOLT #2 message codecs. + module Main where import Criterion.Main +import qualified Data.ByteString as BS +import Lightning.Protocol.BOLT1 (TlvStream(..)) +import Lightning.Protocol.BOLT2 + +-- Test data construction ------------------------------------------------------ + +-- | 32 zero bytes for channel IDs, chain hashes, etc. +zeroBytes32 :: BS.ByteString +zeroBytes32 = BS.replicate 32 0x00 +{-# NOINLINE zeroBytes32 #-} + +-- | 33-byte compressed public key (02 prefix + 32 zero bytes). +testPoint :: Point +testPoint = case point (BS.cons 0x02 zeroBytes32) of + Just p -> p + Nothing -> error "testPoint: invalid" +{-# NOINLINE testPoint #-} + +-- | 64-byte signature. +testSignature :: Signature +testSignature = case signature (BS.replicate 64 0x01) of + Just s -> s + Nothing -> error "testSignature: invalid" +{-# NOINLINE testSignature #-} + +-- | 32-byte channel ID. +testChannelId :: ChannelId +testChannelId = case channelId zeroBytes32 of + Just c -> c + Nothing -> error "testChannelId: invalid" +{-# NOINLINE testChannelId #-} + +-- | 32-byte chain hash. +testChainHash :: ChainHash +testChainHash = case chainHash zeroBytes32 of + Just h -> h + Nothing -> error "testChainHash: invalid" +{-# NOINLINE testChainHash #-} + +-- | 32-byte txid. +testTxId :: TxId +testTxId = case txId zeroBytes32 of + Just t -> t + Nothing -> error "testTxId: invalid" +{-# NOINLINE testTxId #-} + +-- | 32-byte payment hash. +testPaymentHash :: PaymentHash +testPaymentHash = case paymentHash zeroBytes32 of + Just h -> h + Nothing -> error "testPaymentHash: invalid" +{-# NOINLINE testPaymentHash #-} + +-- | 1366-byte onion packet. +testOnionPacket :: OnionPacket +testOnionPacket = case onionPacket (BS.replicate 1366 0x00) of + Just o -> o + Nothing -> error "testOnionPacket: invalid" +{-# NOINLINE testOnionPacket #-} + +-- | Empty TLV stream. +emptyTlvs :: TlvStream +emptyTlvs = TlvStream [] +{-# NOINLINE emptyTlvs #-} + +-- V1 messages ----------------------------------------------------------------- + +-- | Test OpenChannel message. +testOpenChannel :: OpenChannel +testOpenChannel = OpenChannel + { openChannelChainHash = testChainHash + , openChannelTempChannelId = testChannelId + , openChannelFundingSatoshis = Satoshis 1000000 + , openChannelPushMsat = MilliSatoshis 0 + , openChannelDustLimitSatoshis = Satoshis 546 + , openChannelMaxHtlcValueInFlight = MilliSatoshis 1000000000 + , openChannelChannelReserveSat = Satoshis 10000 + , openChannelHtlcMinimumMsat = MilliSatoshis 1000 + , openChannelFeeratePerKw = 250 + , openChannelToSelfDelay = 144 + , openChannelMaxAcceptedHtlcs = 30 + , openChannelFundingPubkey = testPoint + , openChannelRevocationBasepoint = testPoint + , openChannelPaymentBasepoint = testPoint + , openChannelDelayedPaymentBase = testPoint + , openChannelHtlcBasepoint = testPoint + , openChannelFirstPerCommitPoint = testPoint + , openChannelChannelFlags = 0x00 + , openChannelTlvs = emptyTlvs + } +{-# NOINLINE testOpenChannel #-} + +-- | Encoded OpenChannel for decode benchmarks. +encodedOpenChannel :: BS.ByteString +encodedOpenChannel = encodeOpenChannel testOpenChannel +{-# NOINLINE encodedOpenChannel #-} + +-- V2 messages ----------------------------------------------------------------- + +-- | Test OpenChannel2 message. +testOpenChannel2 :: OpenChannel2 +testOpenChannel2 = OpenChannel2 + { openChannel2ChainHash = testChainHash + , openChannel2TempChannelId = testChannelId + , openChannel2FundingFeeratePerkw = 2500 + , openChannel2CommitFeeratePerkw = 250 + , openChannel2FundingSatoshis = Satoshis 1000000 + , openChannel2DustLimitSatoshis = Satoshis 546 + , openChannel2MaxHtlcValueInFlight = MilliSatoshis 1000000000 + , openChannel2HtlcMinimumMsat = MilliSatoshis 1000 + , openChannel2ToSelfDelay = 144 + , openChannel2MaxAcceptedHtlcs = 30 + , openChannel2Locktime = 0 + , openChannel2FundingPubkey = testPoint + , openChannel2RevocationBasepoint = testPoint + , openChannel2PaymentBasepoint = testPoint + , openChannel2DelayedPaymentBase = testPoint + , openChannel2HtlcBasepoint = testPoint + , openChannel2FirstPerCommitPoint = testPoint + , openChannel2SecondPerCommitPoint = testPoint + , openChannel2ChannelFlags = 0x00 + , openChannel2Tlvs = emptyTlvs + } +{-# NOINLINE testOpenChannel2 #-} + +-- | Encoded OpenChannel2 for decode benchmarks. +encodedOpenChannel2 :: BS.ByteString +encodedOpenChannel2 = encodeOpenChannel2 testOpenChannel2 +{-# NOINLINE encodedOpenChannel2 #-} + +-- | Test witness data (simulated P2WPKH signature + pubkey). +testWitness :: Witness +testWitness = Witness (BS.replicate 107 0xab) +{-# NOINLINE testWitness #-} + +-- | TxSignatures with multiple witnesses. +testTxSignatures :: TxSignatures +testTxSignatures = TxSignatures + { txSignaturesChannelId = testChannelId + , txSignaturesTxid = testTxId + , txSignaturesWitnesses = replicate 5 testWitness + } +{-# NOINLINE testTxSignatures #-} + +-- | Encoded TxSignatures for decode benchmarks. +encodedTxSignatures :: BS.ByteString +encodedTxSignatures = encodeTxSignatures testTxSignatures +{-# NOINLINE encodedTxSignatures #-} + +-- Close messages -------------------------------------------------------------- + +-- | Test ClosingSigned message. +testClosingSigned :: ClosingSigned +testClosingSigned = ClosingSigned + { closingSignedChannelId = testChannelId + , closingSignedFeeSatoshis = Satoshis 1000 + , closingSignedSignature = testSignature + , closingSignedTlvs = emptyTlvs + } +{-# NOINLINE testClosingSigned #-} + +-- | Encoded ClosingSigned for decode benchmarks. +encodedClosingSigned :: BS.ByteString +encodedClosingSigned = encodeClosingSigned testClosingSigned +{-# NOINLINE encodedClosingSigned #-} + +-- Normal operation messages --------------------------------------------------- + +-- | Test UpdateAddHtlc message. +testUpdateAddHtlc :: UpdateAddHtlc +testUpdateAddHtlc = UpdateAddHtlc + { updateAddHtlcChannelId = testChannelId + , updateAddHtlcId = 0 + , updateAddHtlcAmountMsat = MilliSatoshis 10000000 + , updateAddHtlcPaymentHash = testPaymentHash + , updateAddHtlcCltvExpiry = 800000 + , updateAddHtlcOnionPacket = testOnionPacket + , updateAddHtlcTlvs = emptyTlvs + } +{-# NOINLINE testUpdateAddHtlc #-} + +-- | Encoded UpdateAddHtlc for decode benchmarks. +encodedUpdateAddHtlc :: BS.ByteString +encodedUpdateAddHtlc = encodeUpdateAddHtlc testUpdateAddHtlc +{-# NOINLINE encodedUpdateAddHtlc #-} + +-- | Test CommitmentSigned message with HTLC signatures. +testCommitmentSigned :: CommitmentSigned +testCommitmentSigned = CommitmentSigned + { commitmentSignedChannelId = testChannelId + , commitmentSignedSignature = testSignature + , commitmentSignedHtlcSignatures = replicate 10 testSignature + } +{-# NOINLINE testCommitmentSigned #-} + +-- | Encoded CommitmentSigned for decode benchmarks. +encodedCommitmentSigned :: BS.ByteString +encodedCommitmentSigned = encodeCommitmentSigned testCommitmentSigned +{-# NOINLINE encodedCommitmentSigned #-} + +-- Benchmark groups ------------------------------------------------------------ main :: IO () -main = defaultMain [ +main = defaultMain + [ bgroup "v1" + [ bgroup "open_channel" + [ bench "encode" $ nf encodeOpenChannel testOpenChannel + , bench "decode" $ nf decodeOpenChannel encodedOpenChannel + ] + ] + , bgroup "v2" + [ bgroup "open_channel2" + [ bench "encode" $ nf encodeOpenChannel2 testOpenChannel2 + , bench "decode" $ nf decodeOpenChannel2 encodedOpenChannel2 + ] + , bgroup "tx_signatures" + [ bench "encode" $ nf encodeTxSignatures testTxSignatures + , bench "decode" $ nf decodeTxSignatures encodedTxSignatures + ] + ] + , bgroup "close" + [ bgroup "closing_signed" + [ bench "encode" $ nf encodeClosingSigned testClosingSigned + , bench "decode" $ nf decodeClosingSigned encodedClosingSigned + ] + ] + , bgroup "normal" + [ bgroup "update_add_htlc" + [ bench "encode" $ nf encodeUpdateAddHtlc testUpdateAddHtlc + , bench "decode" $ nf decodeUpdateAddHtlc encodedUpdateAddHtlc + ] + , bgroup "commitment_signed" + [ bench "encode" $ nf encodeCommitmentSigned testCommitmentSigned + , bench "decode" $ nf decodeCommitmentSigned encodedCommitmentSigned + ] + ] ] diff --git a/bench/Weight.hs b/bench/Weight.hs @@ -1,10 +1,290 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} +-- | +-- Module: Main +-- Copyright: (c) 2025 Jared Tobin +-- License: MIT +-- Maintainer: Jared Tobin <jared@ppad.tech> +-- +-- Weigh allocation benchmarks for BOLT #2 message codecs. + module Main where +import qualified Data.ByteString as BS +import Lightning.Protocol.BOLT1 (TlvStream(..)) +import Lightning.Protocol.BOLT2 import Weigh +-- Test data construction ------------------------------------------------------ + +-- | 32 zero bytes for channel IDs, chain hashes, etc. +zeroBytes32 :: BS.ByteString +zeroBytes32 = BS.replicate 32 0x00 +{-# NOINLINE zeroBytes32 #-} + +-- | 33-byte compressed public key (02 prefix + 32 zero bytes). +testPoint :: Point +testPoint = case point (BS.cons 0x02 zeroBytes32) of + Just p -> p + Nothing -> error "testPoint: invalid" +{-# NOINLINE testPoint #-} + +-- | 64-byte signature. +testSignature :: Signature +testSignature = case signature (BS.replicate 64 0x01) of + Just s -> s + Nothing -> error "testSignature: invalid" +{-# NOINLINE testSignature #-} + +-- | 32-byte channel ID. +testChannelId :: ChannelId +testChannelId = case channelId zeroBytes32 of + Just c -> c + Nothing -> error "testChannelId: invalid" +{-# NOINLINE testChannelId #-} + +-- | 32-byte chain hash. +testChainHash :: ChainHash +testChainHash = case chainHash zeroBytes32 of + Just h -> h + Nothing -> error "testChainHash: invalid" +{-# NOINLINE testChainHash #-} + +-- | 32-byte txid. +testTxId :: TxId +testTxId = case txId zeroBytes32 of + Just t -> t + Nothing -> error "testTxId: invalid" +{-# NOINLINE testTxId #-} + +-- | 32-byte payment hash. +testPaymentHash :: PaymentHash +testPaymentHash = case paymentHash zeroBytes32 of + Just h -> h + Nothing -> error "testPaymentHash: invalid" +{-# NOINLINE testPaymentHash #-} + +-- | 1366-byte onion packet. +testOnionPacket :: OnionPacket +testOnionPacket = case onionPacket (BS.replicate 1366 0x00) of + Just o -> o + Nothing -> error "testOnionPacket: invalid" +{-# NOINLINE testOnionPacket #-} + +-- | Empty TLV stream. +emptyTlvs :: TlvStream +emptyTlvs = TlvStream [] +{-# NOINLINE emptyTlvs #-} + +-- Message constructors -------------------------------------------------------- + +-- | Construct OpenChannel message. +mkOpenChannel :: ChainHash -> ChannelId -> Point -> TlvStream -> OpenChannel +mkOpenChannel !ch !cid !pt !tlvs = OpenChannel + { openChannelChainHash = ch + , openChannelTempChannelId = cid + , openChannelFundingSatoshis = Satoshis 1000000 + , openChannelPushMsat = MilliSatoshis 0 + , openChannelDustLimitSatoshis = Satoshis 546 + , openChannelMaxHtlcValueInFlight = MilliSatoshis 1000000000 + , openChannelChannelReserveSat = Satoshis 10000 + , openChannelHtlcMinimumMsat = MilliSatoshis 1000 + , openChannelFeeratePerKw = 250 + , openChannelToSelfDelay = 144 + , openChannelMaxAcceptedHtlcs = 30 + , openChannelFundingPubkey = pt + , openChannelRevocationBasepoint = pt + , openChannelPaymentBasepoint = pt + , openChannelDelayedPaymentBase = pt + , openChannelHtlcBasepoint = pt + , openChannelFirstPerCommitPoint = pt + , openChannelChannelFlags = 0x00 + , openChannelTlvs = tlvs + } + +-- | Construct OpenChannel2 message. +mkOpenChannel2 :: ChainHash -> ChannelId -> Point -> TlvStream -> OpenChannel2 +mkOpenChannel2 !ch !cid !pt !tlvs = OpenChannel2 + { openChannel2ChainHash = ch + , openChannel2TempChannelId = cid + , openChannel2FundingFeeratePerkw = 2500 + , openChannel2CommitFeeratePerkw = 250 + , openChannel2FundingSatoshis = Satoshis 1000000 + , openChannel2DustLimitSatoshis = Satoshis 546 + , openChannel2MaxHtlcValueInFlight = MilliSatoshis 1000000000 + , openChannel2HtlcMinimumMsat = MilliSatoshis 1000 + , openChannel2ToSelfDelay = 144 + , openChannel2MaxAcceptedHtlcs = 30 + , openChannel2Locktime = 0 + , openChannel2FundingPubkey = pt + , openChannel2RevocationBasepoint = pt + , openChannel2PaymentBasepoint = pt + , openChannel2DelayedPaymentBase = pt + , openChannel2HtlcBasepoint = pt + , openChannel2FirstPerCommitPoint = pt + , openChannel2SecondPerCommitPoint = pt + , openChannel2ChannelFlags = 0x00 + , openChannel2Tlvs = tlvs + } + +-- | Construct TxSignatures message. +mkTxSignatures :: ChannelId -> TxId -> [Witness] -> TxSignatures +mkTxSignatures !cid !tid !ws = TxSignatures + { txSignaturesChannelId = cid + , txSignaturesTxid = tid + , txSignaturesWitnesses = ws + } + +-- | Construct ClosingSigned message. +mkClosingSigned :: ChannelId -> Signature -> TlvStream -> ClosingSigned +mkClosingSigned !cid !sig !tlvs = ClosingSigned + { closingSignedChannelId = cid + , closingSignedFeeSatoshis = Satoshis 1000 + , closingSignedSignature = sig + , closingSignedTlvs = tlvs + } + +-- | Construct UpdateAddHtlc message. +mkUpdateAddHtlc + :: ChannelId -> PaymentHash -> OnionPacket -> TlvStream -> UpdateAddHtlc +mkUpdateAddHtlc !cid !ph !onion !tlvs = UpdateAddHtlc + { updateAddHtlcChannelId = cid + , updateAddHtlcId = 0 + , updateAddHtlcAmountMsat = MilliSatoshis 10000000 + , updateAddHtlcPaymentHash = ph + , updateAddHtlcCltvExpiry = 800000 + , updateAddHtlcOnionPacket = onion + , updateAddHtlcTlvs = tlvs + } + +-- | Construct CommitmentSigned message. +mkCommitmentSigned :: ChannelId -> Signature -> [Signature] -> CommitmentSigned +mkCommitmentSigned !cid !sig !htlcSigs = CommitmentSigned + { commitmentSignedChannelId = cid + , commitmentSignedSignature = sig + , commitmentSignedHtlcSignatures = htlcSigs + } + +-- Pre-constructed messages ---------------------------------------------------- + +-- | Test OpenChannel message. +testOpenChannel :: OpenChannel +testOpenChannel = + mkOpenChannel testChainHash testChannelId testPoint emptyTlvs +{-# NOINLINE testOpenChannel #-} + +-- | Encoded OpenChannel for decode benchmarks. +encodedOpenChannel :: BS.ByteString +encodedOpenChannel = encodeOpenChannel testOpenChannel +{-# NOINLINE encodedOpenChannel #-} + +-- | Test OpenChannel2 message. +testOpenChannel2 :: OpenChannel2 +testOpenChannel2 = + mkOpenChannel2 testChainHash testChannelId testPoint emptyTlvs +{-# NOINLINE testOpenChannel2 #-} + +-- | Encoded OpenChannel2 for decode benchmarks. +encodedOpenChannel2 :: BS.ByteString +encodedOpenChannel2 = encodeOpenChannel2 testOpenChannel2 +{-# NOINLINE encodedOpenChannel2 #-} + +-- | Test witness data. +testWitness :: Witness +testWitness = Witness (BS.replicate 107 0xab) +{-# NOINLINE testWitness #-} + +-- | Multiple witnesses for TxSignatures. +testWitnesses :: [Witness] +testWitnesses = replicate 5 testWitness +{-# NOINLINE testWitnesses #-} + +-- | Test TxSignatures message. +testTxSignatures :: TxSignatures +testTxSignatures = mkTxSignatures testChannelId testTxId testWitnesses +{-# NOINLINE testTxSignatures #-} + +-- | Encoded TxSignatures for decode benchmarks. +encodedTxSignatures :: BS.ByteString +encodedTxSignatures = encodeTxSignatures testTxSignatures +{-# NOINLINE encodedTxSignatures #-} + +-- | Test ClosingSigned message. +testClosingSigned :: ClosingSigned +testClosingSigned = mkClosingSigned testChannelId testSignature emptyTlvs +{-# NOINLINE testClosingSigned #-} + +-- | Encoded ClosingSigned for decode benchmarks. +encodedClosingSigned :: BS.ByteString +encodedClosingSigned = encodeClosingSigned testClosingSigned +{-# NOINLINE encodedClosingSigned #-} + +-- | Test UpdateAddHtlc message. +testUpdateAddHtlc :: UpdateAddHtlc +testUpdateAddHtlc = + mkUpdateAddHtlc testChannelId testPaymentHash testOnionPacket emptyTlvs +{-# NOINLINE testUpdateAddHtlc #-} + +-- | Encoded UpdateAddHtlc for decode benchmarks. +encodedUpdateAddHtlc :: BS.ByteString +encodedUpdateAddHtlc = encodeUpdateAddHtlc testUpdateAddHtlc +{-# NOINLINE encodedUpdateAddHtlc #-} + +-- | HTLC signatures for CommitmentSigned. +testHtlcSigs :: [Signature] +testHtlcSigs = replicate 10 testSignature +{-# NOINLINE testHtlcSigs #-} + +-- | Test CommitmentSigned message. +testCommitmentSigned :: CommitmentSigned +testCommitmentSigned = + mkCommitmentSigned testChannelId testSignature testHtlcSigs +{-# NOINLINE testCommitmentSigned #-} + +-- | Encoded CommitmentSigned for decode benchmarks. +encodedCommitmentSigned :: BS.ByteString +encodedCommitmentSigned = encodeCommitmentSigned testCommitmentSigned +{-# NOINLINE encodedCommitmentSigned #-} + +-- Weigh benchmarks ------------------------------------------------------------ + main :: IO () main = mainWith $ do - pure () + -- V1 message construction and encoding + wgroup "v1/open_channel" $ do + func "construct" (mkOpenChannel testChainHash testChannelId testPoint) + emptyTlvs + func "encode" encodeOpenChannel testOpenChannel + func "decode" decodeOpenChannel encodedOpenChannel + + -- V2 message construction and encoding + wgroup "v2/open_channel2" $ do + func "construct" (mkOpenChannel2 testChainHash testChannelId testPoint) + emptyTlvs + func "encode" encodeOpenChannel2 testOpenChannel2 + func "decode" decodeOpenChannel2 encodedOpenChannel2 + + wgroup "v2/tx_signatures" $ do + func "construct" (mkTxSignatures testChannelId testTxId) testWitnesses + func "encode" encodeTxSignatures testTxSignatures + func "decode" decodeTxSignatures encodedTxSignatures + + -- Close messages + wgroup "close/closing_signed" $ do + func "construct" (mkClosingSigned testChannelId testSignature) emptyTlvs + func "encode" encodeClosingSigned testClosingSigned + func "decode" decodeClosingSigned encodedClosingSigned + + -- Normal operation (hot paths) + wgroup "normal/update_add_htlc" $ do + func "construct" + (mkUpdateAddHtlc testChannelId testPaymentHash testOnionPacket) emptyTlvs + func "encode" encodeUpdateAddHtlc testUpdateAddHtlc + func "decode" decodeUpdateAddHtlc encodedUpdateAddHtlc + + wgroup "normal/commitment_signed" $ do + func "construct" (mkCommitmentSigned testChannelId testSignature) + testHtlcSigs + func "encode" encodeCommitmentSigned testCommitmentSigned + func "decode" decodeCommitmentSigned encodedCommitmentSigned diff --git a/ppad-bolt2.cabal b/ppad-bolt2.cabal @@ -67,6 +67,7 @@ benchmark bolt2-bench , bytestring , criterion , deepseq + , ppad-bolt1 , ppad-bolt2 benchmark bolt2-weigh @@ -82,5 +83,6 @@ benchmark bolt2-weigh base , bytestring , deepseq + , ppad-bolt1 , ppad-bolt2 , weigh