packages feed

ppad-bolt2-0.0.1: bench/Weight.hs

{-# 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, unsafeTlvStream)
import Lightning.Protocol.BOLT2
import Weigh

-- | Wrapper for encoding functions that return Either.
forceEncode :: Either EncodeError BS.ByteString -> BS.ByteString
forceEncode (Right bs) = bs
forceEncode (Left e)   = error $ "forceEncode: " ++ show e
{-# INLINE forceEncode #-}

-- 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 = unsafeTlvStream []
{-# 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 = case encodeTxSignatures testTxSignatures of
  Right bs -> bs
  Left e   -> error $ "encodedTxSignatures: " ++ show e
{-# 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 = case encodeCommitmentSigned testCommitmentSigned of
  Right bs -> bs
  Left e   -> error $ "encodedCommitmentSigned: " ++ show e
{-# NOINLINE encodedCommitmentSigned #-}

-- | Large HTLC signatures for CommitmentSigned (100).
testHtlcSigsLarge :: [Signature]
testHtlcSigsLarge = replicate 100 testSignature
{-# NOINLINE testHtlcSigsLarge #-}

-- | Test CommitmentSigned message (100 sigs).
testCommitmentSignedLarge :: CommitmentSigned
testCommitmentSignedLarge =
  mkCommitmentSigned testChannelId testSignature testHtlcSigsLarge
{-# NOINLINE testCommitmentSignedLarge #-}

-- | Encoded large CommitmentSigned for decode benchmarks.
encodedCommitmentSignedLarge :: BS.ByteString
encodedCommitmentSignedLarge =
  case encodeCommitmentSigned testCommitmentSignedLarge of
    Right bs -> bs
    Left e   -> error $ "encodedCommitmentSignedLarge: " ++ show e
{-# NOINLINE encodedCommitmentSignedLarge #-}

-- | Max HTLC signatures for CommitmentSigned (483).
testHtlcSigsMax :: [Signature]
testHtlcSigsMax = replicate 483 testSignature
{-# NOINLINE testHtlcSigsMax #-}

-- | Test CommitmentSigned message (483 sigs).
testCommitmentSignedMax :: CommitmentSigned
testCommitmentSignedMax =
  mkCommitmentSigned testChannelId testSignature testHtlcSigsMax
{-# NOINLINE testCommitmentSignedMax #-}

-- | Encoded max CommitmentSigned for decode benchmarks.
encodedCommitmentSignedMax :: BS.ByteString
encodedCommitmentSignedMax =
  case encodeCommitmentSigned testCommitmentSignedMax of
    Right bs -> bs
    Left e   -> error $ "encodedCommitmentSignedMax: " ++ show e
{-# NOINLINE encodedCommitmentSignedMax #-}

-- Weigh benchmarks ------------------------------------------------------------

main :: IO ()
main = mainWith $ do
  -- 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" (forceEncode . 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" (forceEncode . encodeCommitmentSigned) testCommitmentSigned
    func "decode" decodeCommitmentSigned encodedCommitmentSigned

  wgroup "normal/commitment_signed_100" $ do
    func "decode" decodeCommitmentSigned encodedCommitmentSignedLarge

  wgroup "normal/commitment_signed_483" $ do
    func "decode" decodeCommitmentSigned encodedCommitmentSignedMax