bolt1

Base Lightning protocol, per BOLT #1.
git clone git://git.ppad.tech/bolt1.git
Log | Files | Refs | README | LICENSE

commit 9acf4e37bf31a5a6db4a5fc478e4538fd8c50574
parent 2a3373657b7ba5c0b137ca0781657f4110569171
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 25 Jan 2026 15:00:43 +0400

Merge branch 'impl/weigh' - comprehensive allocation benchmarks

Add allocation tracking benchmarks covering:
- Primitive encoders/decoders
- Truncated unsigned encoders (small/full values)
- Minimal signed encoder (1/2/4/8 byte outputs)
- BigSize encoder (1/3/9 byte outputs)
- TLV operations (encode/decode for varying record counts)
- All BOLT#1 message encoders/decoders
- Envelope operations

Diffstat:
Mbench/Weight.hs | 339++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 338 insertions(+), 1 deletion(-)

diff --git a/bench/Weight.hs b/bench/Weight.hs @@ -3,8 +3,345 @@ module Main where +import qualified Data.ByteString as BS +import Data.Word (Word16, Word32, Word64) +import Data.Int (Int8, Int16, Int32, Int64) +import Lightning.Protocol.BOLT1 +import Lightning.Protocol.BOLT1.Codec +import Lightning.Protocol.BOLT1.TLV (encodeTlvRecord) import Weigh +-- Fixtures -------------------------------------------------------------------- + +-- Prevent constant folding with NOINLINE + +{-# NOINLINE w16Val #-} +w16Val :: Word16 +w16Val = 0x1234 + +{-# NOINLINE w32Val #-} +w32Val :: Word32 +w32Val = 0x12345678 + +{-# NOINLINE w64Val #-} +w64Val :: Word64 +w64Val = 0x0102030405060708 + +{-# NOINLINE s8Val #-} +s8Val :: Int8 +s8Val = -42 + +{-# NOINLINE s16Val #-} +s16Val :: Int16 +s16Val = -1000 + +{-# NOINLINE s32Val #-} +s32Val :: Int32 +s32Val = -100000 + +{-# NOINLINE s64Val #-} +s64Val :: Int64 +s64Val = -10000000000 + +{-# NOINLINE tu16Small #-} +tu16Small :: Word16 +tu16Small = 0x7f + +{-# NOINLINE tu16Full #-} +tu16Full :: Word16 +tu16Full = 0xffff + +{-# NOINLINE tu32Small #-} +tu32Small :: Word32 +tu32Small = 0x42 + +{-# NOINLINE tu32Full #-} +tu32Full :: Word32 +tu32Full = 0xffffffff + +{-# NOINLINE tu64Small #-} +tu64Small :: Word64 +tu64Small = 0x10 + +{-# NOINLINE tu64Full #-} +tu64Full :: Word64 +tu64Full = 0xffffffffffffffff + +{-# NOINLINE bigSizeSmall #-} +bigSizeSmall :: Word64 +bigSizeSmall = 0xfc + +{-# NOINLINE bigSizeMedium #-} +bigSizeMedium :: Word64 +bigSizeMedium = 0x1000 + +{-# NOINLINE bigSizeLarge #-} +bigSizeLarge :: Word64 +bigSizeLarge = 0x100000000 + +-- Pre-encoded bytes for decoder benchmarks + +{-# NOINLINE u16Bytes #-} +u16Bytes :: BS.ByteString +u16Bytes = encodeU16 w16Val + +{-# NOINLINE u32Bytes #-} +u32Bytes :: BS.ByteString +u32Bytes = encodeU32 w32Val + +{-# NOINLINE u64Bytes #-} +u64Bytes :: BS.ByteString +u64Bytes = encodeU64 w64Val + +{-# NOINLINE s8Bytes #-} +s8Bytes :: BS.ByteString +s8Bytes = encodeS8 s8Val + +{-# NOINLINE s16Bytes #-} +s16Bytes :: BS.ByteString +s16Bytes = encodeS16 s16Val + +{-# NOINLINE s32Bytes #-} +s32Bytes :: BS.ByteString +s32Bytes = encodeS32 s32Val + +{-# NOINLINE s64Bytes #-} +s64Bytes :: BS.ByteString +s64Bytes = encodeS64 s64Val + +{-# NOINLINE bigSizeBytes #-} +bigSizeBytes :: BS.ByteString +bigSizeBytes = encodeBigSize bigSizeLarge + +-- TLV fixtures + +{-# NOINLINE tlvRecord1 #-} +tlvRecord1 :: TlvRecord +tlvRecord1 = TlvRecord 1 "test-value" + +{-# NOINLINE tlvStream1 #-} +tlvStream1 :: TlvStream +tlvStream1 = unsafeTlvStream [tlvRecord1] + +{-# NOINLINE tlvStream5 #-} +tlvStream5 :: TlvStream +tlvStream5 = unsafeTlvStream + [ TlvRecord 1 "value1" + , TlvRecord 3 "value3" + , TlvRecord 5 "value5" + , TlvRecord 7 "value7" + , TlvRecord 9 "value9" + ] + +{-# NOINLINE tlvStream20 #-} +tlvStream20 :: TlvStream +tlvStream20 = unsafeTlvStream + [ TlvRecord (2 * i + 1) (BS.replicate 10 (fromIntegral i)) + | i <- [0..19] + ] + +{-# NOINLINE tlvStreamBytes1 #-} +tlvStreamBytes1 :: BS.ByteString +tlvStreamBytes1 = encodeTlvStream tlvStream1 + +{-# NOINLINE tlvStreamBytes5 #-} +tlvStreamBytes5 :: BS.ByteString +tlvStreamBytes5 = encodeTlvStream tlvStream5 + +{-# NOINLINE tlvStreamBytes20 #-} +tlvStreamBytes20 :: BS.ByteString +tlvStreamBytes20 = encodeTlvStream tlvStream20 + +-- Message fixtures + +{-# NOINLINE minimalInit #-} +minimalInit :: Init +minimalInit = Init BS.empty BS.empty [] + +{-# NOINLINE initWithFeatures #-} +initWithFeatures :: Init +initWithFeatures = Init "\x00\x08" "\x00\x0a\x8a" [] + +{-# NOINLINE initWithTlvs #-} +initWithTlvs :: Init +initWithTlvs = Init BS.empty "\x00\x01" [InitRemoteAddr "127.0.0.1"] + +{-# NOINLINE errorMsg #-} +errorMsg :: Error +errorMsg = Error allChannels "something bad happened" + +{-# NOINLINE warningMsg #-} +warningMsg :: Warning +warningMsg = Warning allChannels "something concerning" + +{-# NOINLINE pingMinimal #-} +pingMinimal :: Ping +pingMinimal = Ping 4 BS.empty + +{-# NOINLINE pingWithPadding #-} +pingWithPadding :: Ping +pingWithPadding = Ping 4 (BS.replicate 64 0x00) + +{-# NOINLINE pongMsg #-} +pongMsg :: Pong +pongMsg = Pong (BS.replicate 4 0x00) + +{-# NOINLINE peerStorageMsg #-} +peerStorageMsg :: PeerStorage +peerStorageMsg = PeerStorage (BS.replicate 100 0xab) + +{-# NOINLINE peerStorageRetrievalMsg #-} +peerStorageRetrievalMsg :: PeerStorageRetrieval +peerStorageRetrievalMsg = PeerStorageRetrieval (BS.replicate 50 0xcd) + +-- Pre-encoded message bytes for decoder benchmarks + +{-# NOINLINE initMinimalBytes #-} +initMinimalBytes :: BS.ByteString +initMinimalBytes = either (const BS.empty) id (encodeInit minimalInit) + +{-# NOINLINE initWithTlvsBytes #-} +initWithTlvsBytes :: BS.ByteString +initWithTlvsBytes = either (const BS.empty) id (encodeInit initWithTlvs) + +{-# NOINLINE errorBytes #-} +errorBytes :: BS.ByteString +errorBytes = either (const BS.empty) id (encodeError errorMsg) + +{-# NOINLINE warningBytes #-} +warningBytes :: BS.ByteString +warningBytes = either (const BS.empty) id (encodeWarning warningMsg) + +{-# NOINLINE pingMinimalBytes #-} +pingMinimalBytes :: BS.ByteString +pingMinimalBytes = either (const BS.empty) id (encodePing pingMinimal) + +{-# NOINLINE pingWithPaddingBytes #-} +pingWithPaddingBytes :: BS.ByteString +pingWithPaddingBytes = either (const BS.empty) id (encodePing pingWithPadding) + +{-# NOINLINE pongBytes #-} +pongBytes :: BS.ByteString +pongBytes = either (const BS.empty) id (encodePong pongMsg) + +{-# NOINLINE peerStorageBytes #-} +peerStorageBytes :: BS.ByteString +peerStorageBytes = either (const BS.empty) id (encodePeerStorage peerStorageMsg) + +{-# NOINLINE peerStorageRetrievalBytes #-} +peerStorageRetrievalBytes :: BS.ByteString +peerStorageRetrievalBytes = + either (const BS.empty) id (encodePeerStorageRetrieval peerStorageRetrievalMsg) + +-- Envelope fixtures + +{-# NOINLINE initMessage #-} +initMessage :: Message +initMessage = MsgInitVal minimalInit + +{-# NOINLINE pingMessage #-} +pingMessage :: Message +pingMessage = MsgPingVal pingMinimal + +{-# NOINLINE envelopeBytes #-} +envelopeBytes :: BS.ByteString +envelopeBytes = either (const BS.empty) id (encodeEnvelope initMessage Nothing) + +-- Main ------------------------------------------------------------------------ + main :: IO () main = mainWith $ do - pure () + setColumns [Case, Allocated, GCs, Max] + + -- Primitive encoders -------------------------------------------------------- + + wgroup "Primitive Encoders" $ do + func "encodeU16" encodeU16 w16Val + func "encodeU32" encodeU32 w32Val + func "encodeU64" encodeU64 w64Val + func "encodeS8" encodeS8 s8Val + func "encodeS16" encodeS16 s16Val + func "encodeS32" encodeS32 s32Val + func "encodeS64" encodeS64 s64Val + + wgroup "Truncated Unsigned Encoders" $ do + func "encodeTu16/small" encodeTu16 tu16Small + func "encodeTu16/full" encodeTu16 tu16Full + func "encodeTu32/small" encodeTu32 tu32Small + func "encodeTu32/full" encodeTu32 tu32Full + func "encodeTu64/small" encodeTu64 tu64Small + func "encodeTu64/full" encodeTu64 tu64Full + + wgroup "Minimal Signed Encoder" $ do + func "encodeMinSigned/1-byte" encodeMinSigned (0 :: Int64) + func "encodeMinSigned/2-byte" encodeMinSigned (1000 :: Int64) + func "encodeMinSigned/4-byte" encodeMinSigned (100000 :: Int64) + func "encodeMinSigned/8-byte" encodeMinSigned s64Val + + wgroup "BigSize Encoder" $ do + func "encodeBigSize/1-byte" encodeBigSize bigSizeSmall + func "encodeBigSize/3-byte" encodeBigSize bigSizeMedium + func "encodeBigSize/9-byte" encodeBigSize bigSizeLarge + + -- Primitive decoders -------------------------------------------------------- + + wgroup "Primitive Decoders" $ do + func "decodeU16" decodeU16 u16Bytes + func "decodeU32" decodeU32 u32Bytes + func "decodeU64" decodeU64 u64Bytes + func "decodeS8" decodeS8 s8Bytes + func "decodeS16" decodeS16 s16Bytes + func "decodeS32" decodeS32 s32Bytes + func "decodeS64" decodeS64 s64Bytes + func "decodeBigSize" decodeBigSize bigSizeBytes + + -- TLV operations ------------------------------------------------------------ + + wgroup "TLV Encoding" $ do + func "encodeTlvRecord" encodeTlvRecord tlvRecord1 + func "encodeTlvStream/1-record" encodeTlvStream tlvStream1 + func "encodeTlvStream/5-records" encodeTlvStream tlvStream5 + func "encodeTlvStream/20-records" encodeTlvStream tlvStream20 + + wgroup "TLV Decoding" $ do + func "decodeTlvStreamRaw/1-record" decodeTlvStreamRaw tlvStreamBytes1 + func "decodeTlvStreamRaw/5-records" decodeTlvStreamRaw tlvStreamBytes5 + func "decodeTlvStreamRaw/20-records" decodeTlvStreamRaw tlvStreamBytes20 + func "decodeTlvStream/1-record" decodeTlvStream tlvStreamBytes1 + func "decodeTlvStream/5-records" decodeTlvStream tlvStreamBytes5 + + -- Message encoders ---------------------------------------------------------- + + wgroup "Message Encoders" $ do + func "encodeInit/minimal" encodeInit minimalInit + func "encodeInit/with-features" encodeInit initWithFeatures + func "encodeInit/with-tlvs" encodeInit initWithTlvs + func "encodeError" encodeError errorMsg + func "encodeWarning" encodeWarning warningMsg + func "encodePing/minimal" encodePing pingMinimal + func "encodePing/with-padding" encodePing pingWithPadding + func "encodePong" encodePong pongMsg + func "encodePeerStorage" encodePeerStorage peerStorageMsg + func "encodePeerStorageRetrieval" encodePeerStorageRetrieval + peerStorageRetrievalMsg + + -- Message decoders ---------------------------------------------------------- + + wgroup "Message Decoders" $ do + func "decodeInit/minimal" decodeInit initMinimalBytes + func "decodeInit/with-tlvs" decodeInit initWithTlvsBytes + func "decodeError" decodeError errorBytes + func "decodeWarning" decodeWarning warningBytes + func "decodePing/minimal" decodePing pingMinimalBytes + func "decodePing/with-padding" decodePing pingWithPaddingBytes + func "decodePong" decodePong pongBytes + func "decodePeerStorage" decodePeerStorage peerStorageBytes + func "decodePeerStorageRetrieval" decodePeerStorageRetrieval + peerStorageRetrievalBytes + + -- Envelope operations ------------------------------------------------------- + + wgroup "Envelope Operations" $ do + func "encodeEnvelope/init" (flip encodeEnvelope Nothing) initMessage + func "encodeEnvelope/ping" (flip encodeEnvelope Nothing) pingMessage + func "decodeEnvelope" decodeEnvelope envelopeBytes