packages feed

ppad-bolt7-0.0.1: bench/Main.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}

-- |
-- Module: Main
-- Copyright: (c) 2025 Jared Tobin
-- License: MIT
-- Maintainer: Jared Tobin <jared@ppad.tech>
--
-- Criterion timing benchmarks for BOLT #7 gossip codecs.

module Main where

import Criterion.Main
import qualified Data.ByteString as BS
import Lightning.Protocol.BOLT1 (TlvStream, unsafeTlvStream)
import Lightning.Protocol.BOLT7

-- Test data construction ------------------------------------------------------

-- | 32 zero bytes for chain hashes, etc.
zeroBytes32 :: BS.ByteString
zeroBytes32 = BS.replicate 32 0x00
{-# NOINLINE zeroBytes32 #-}

-- | 8 zero bytes for short channel IDs.
zeroBytes8 :: BS.ByteString
zeroBytes8 = BS.replicate 8 0x00
{-# NOINLINE zeroBytes8 #-}

-- | 64-byte signature.
testSignature :: Signature
testSignature = case signature (BS.replicate 64 0x01) of
  Just s  -> s
  Nothing -> error "testSignature: invalid"
{-# NOINLINE testSignature #-}

-- | 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 #-}

-- | 32-byte chain hash.
testChainHash :: ChainHash
testChainHash = case chainHash zeroBytes32 of
  Just h  -> h
  Nothing -> error "testChainHash: invalid"
{-# NOINLINE testChainHash #-}

-- | 8-byte short channel ID.
testShortChannelId :: ShortChannelId
testShortChannelId = case shortChannelId zeroBytes8 of
  Just s  -> s
  Nothing -> error "testShortChannelId: invalid"
{-# NOINLINE testShortChannelId #-}

-- | 32-byte channel ID.
testChannelId :: ChannelId
testChannelId = case channelId zeroBytes32 of
  Just c  -> c
  Nothing -> error "testChannelId: invalid"
{-# NOINLINE testChannelId #-}

-- | 33-byte node ID (03 prefix).
testNodeId :: NodeId
testNodeId = case nodeId (BS.cons 0x03 zeroBytes32) of
  Just n  -> n
  Nothing -> error "testNodeId: invalid"
{-# NOINLINE testNodeId #-}

-- | Second node ID (02 prefix, lexicographically smaller).
testNodeId2 :: NodeId
testNodeId2 = case nodeId (BS.cons 0x02 zeroBytes32) of
  Just n  -> n
  Nothing -> error "testNodeId2: invalid"
{-# NOINLINE testNodeId2 #-}

-- | RGB color.
testRgbColor :: RgbColor
testRgbColor = case rgbColor (BS.pack [0xff, 0x00, 0x00]) of
  Just c  -> c
  Nothing -> error "testRgbColor: invalid"
{-# NOINLINE testRgbColor #-}

-- | 32-byte alias.
testAlias :: Alias
testAlias = case alias zeroBytes32 of
  Just a  -> a
  Nothing -> error "testAlias: invalid"
{-# NOINLINE testAlias #-}

-- | IPv4 address.
testIPv4 :: IPv4Addr
testIPv4 = case ipv4Addr (BS.pack [127, 0, 0, 1]) of
  Just a  -> a
  Nothing -> error "testIPv4: invalid"
{-# NOINLINE testIPv4 #-}

-- | Empty TLV stream.
emptyTlvs :: TlvStream
emptyTlvs = unsafeTlvStream []
{-# NOINLINE emptyTlvs #-}

-- | Empty feature bits.
emptyFeatures :: FeatureBits
emptyFeatures = featureBits BS.empty
{-# NOINLINE emptyFeatures #-}

-- | List of test SCIDs for list encoding benchmarks.
testScidList :: [ShortChannelId]
testScidList = map mkScid [1..100]
  where
    mkScid n = case shortChannelId (BS.pack [0, 0, 0, n, 0, 0, 0, n]) of
      Just s  -> s
      Nothing -> error "mkScid: invalid"
{-# NOINLINE testScidList #-}

-- | Encoded SCID list for decode benchmarks.
encodedScidList :: BS.ByteString
encodedScidList = encodeShortChannelIdList testScidList
{-# NOINLINE encodedScidList #-}

-- Test messages ---------------------------------------------------------------

-- | Test ChannelAnnouncement message.
testChannelAnnouncement :: ChannelAnnouncement
testChannelAnnouncement = ChannelAnnouncement
  { channelAnnNodeSig1     = testSignature
  , channelAnnNodeSig2     = testSignature
  , channelAnnBitcoinSig1  = testSignature
  , channelAnnBitcoinSig2  = testSignature
  , channelAnnFeatures     = emptyFeatures
  , channelAnnChainHash    = testChainHash
  , channelAnnShortChanId  = testShortChannelId
  , channelAnnNodeId1      = testNodeId2  -- 02... (smaller)
  , channelAnnNodeId2      = testNodeId   -- 03... (larger)
  , channelAnnBitcoinKey1  = testPoint
  , channelAnnBitcoinKey2  = testPoint
  }
{-# NOINLINE testChannelAnnouncement #-}

-- | Encoded ChannelAnnouncement for decode benchmarks.
encodedChannelAnnouncement :: BS.ByteString
encodedChannelAnnouncement = encodeChannelAnnouncement testChannelAnnouncement
{-# NOINLINE encodedChannelAnnouncement #-}

-- | Test NodeAnnouncement message.
testNodeAnnouncement :: NodeAnnouncement
testNodeAnnouncement = NodeAnnouncement
  { nodeAnnSignature = testSignature
  , nodeAnnFeatures  = emptyFeatures
  , nodeAnnTimestamp = 1234567890
  , nodeAnnNodeId    = testNodeId
  , nodeAnnRgbColor  = testRgbColor
  , nodeAnnAlias     = testAlias
  , nodeAnnAddresses = [AddrIPv4 testIPv4 9735]
  }
{-# NOINLINE testNodeAnnouncement #-}

-- | Encoded NodeAnnouncement for decode benchmarks.
encodedNodeAnnouncement :: BS.ByteString
encodedNodeAnnouncement = case encodeNodeAnnouncement testNodeAnnouncement of
  Right bs -> bs
  Left _   -> error "encodedNodeAnnouncement: encode failed"
{-# NOINLINE encodedNodeAnnouncement #-}

-- | Test ChannelUpdate message.
testChannelUpdate :: ChannelUpdate
testChannelUpdate = ChannelUpdate
  { chanUpdateSignature       = testSignature
  , chanUpdateChainHash       = testChainHash
  , chanUpdateShortChanId     = testShortChannelId
  , chanUpdateTimestamp       = 1234567890
  , chanUpdateMsgFlags        = 0x01
  , chanUpdateChanFlags       = 0x00
  , chanUpdateCltvExpDelta    = 144
  , chanUpdateHtlcMinMsat     = 1000
  , chanUpdateFeeBaseMsat     = 1000
  , chanUpdateFeeProportional = 100
  , chanUpdateHtlcMaxMsat     = Just 1000000000
  }
{-# NOINLINE testChannelUpdate #-}

-- | Encoded ChannelUpdate for decode benchmarks.
encodedChannelUpdate :: BS.ByteString
encodedChannelUpdate = encodeChannelUpdate testChannelUpdate
{-# NOINLINE encodedChannelUpdate #-}

-- | Test AnnouncementSignatures message.
testAnnouncementSignatures :: AnnouncementSignatures
testAnnouncementSignatures = AnnouncementSignatures
  { annSigChannelId   = testChannelId
  , annSigShortChanId = testShortChannelId
  , annSigNodeSig     = testSignature
  , annSigBitcoinSig  = testSignature
  }
{-# NOINLINE testAnnouncementSignatures #-}

-- | Encoded AnnouncementSignatures for decode benchmarks.
encodedAnnouncementSignatures :: BS.ByteString
encodedAnnouncementSignatures =
  encodeAnnouncementSignatures testAnnouncementSignatures
{-# NOINLINE encodedAnnouncementSignatures #-}

-- | Test QueryShortChannelIds message.
testQueryShortChannelIds :: QueryShortChannelIds
testQueryShortChannelIds = QueryShortChannelIds
  { queryScidsChainHash = testChainHash
  , queryScidsData      = encodeShortChannelIdList [testShortChannelId]
  , queryScidsTlvs      = emptyTlvs
  }
{-# NOINLINE testQueryShortChannelIds #-}

-- | Encoded QueryShortChannelIds for decode benchmarks.
encodedQueryShortChannelIds :: BS.ByteString
encodedQueryShortChannelIds =
  case encodeQueryShortChannelIds testQueryShortChannelIds of
    Right bs -> bs
    Left _   -> error "encodedQueryShortChannelIds: encode failed"
{-# NOINLINE encodedQueryShortChannelIds #-}

-- | Test ReplyShortChannelIdsEnd message.
testReplyShortChannelIdsEnd :: ReplyShortChannelIdsEnd
testReplyShortChannelIdsEnd = ReplyShortChannelIdsEnd
  { replyScidsChainHash = testChainHash
  , replyScidsFullInfo  = 1
  }
{-# NOINLINE testReplyShortChannelIdsEnd #-}

-- | Encoded ReplyShortChannelIdsEnd for decode benchmarks.
encodedReplyShortChannelIdsEnd :: BS.ByteString
encodedReplyShortChannelIdsEnd =
  encodeReplyShortChannelIdsEnd testReplyShortChannelIdsEnd
{-# NOINLINE encodedReplyShortChannelIdsEnd #-}

-- | Test QueryChannelRange message.
testQueryChannelRange :: QueryChannelRange
testQueryChannelRange = QueryChannelRange
  { queryRangeChainHash  = testChainHash
  , queryRangeFirstBlock = 700000
  , queryRangeNumBlocks  = 10000
  , queryRangeTlvs       = emptyTlvs
  }
{-# NOINLINE testQueryChannelRange #-}

-- | Encoded QueryChannelRange for decode benchmarks.
encodedQueryChannelRange :: BS.ByteString
encodedQueryChannelRange = encodeQueryChannelRange testQueryChannelRange
{-# NOINLINE encodedQueryChannelRange #-}

-- | Test ReplyChannelRange message.
testReplyChannelRange :: ReplyChannelRange
testReplyChannelRange = ReplyChannelRange
  { replyRangeChainHash    = testChainHash
  , replyRangeFirstBlock   = 700000
  , replyRangeNumBlocks    = 10000
  , replyRangeSyncComplete = 1
  , replyRangeData         = encodeShortChannelIdList [testShortChannelId]
  , replyRangeTlvs         = emptyTlvs
  }
{-# NOINLINE testReplyChannelRange #-}

-- | Encoded ReplyChannelRange for decode benchmarks.
encodedReplyChannelRange :: BS.ByteString
encodedReplyChannelRange = case encodeReplyChannelRange testReplyChannelRange of
  Right bs -> bs
  Left _   -> error "encodedReplyChannelRange: encode failed"
{-# NOINLINE encodedReplyChannelRange #-}

-- | Test GossipTimestampFilter message.
testGossipTimestampFilter :: GossipTimestampFilter
testGossipTimestampFilter = GossipTimestampFilter
  { gossipFilterChainHash      = testChainHash
  , gossipFilterFirstTimestamp = 1609459200
  , gossipFilterTimestampRange = 86400
  }
{-# NOINLINE testGossipTimestampFilter #-}

-- | Encoded GossipTimestampFilter for decode benchmarks.
encodedGossipTimestampFilter :: BS.ByteString
encodedGossipTimestampFilter =
  encodeGossipTimestampFilter testGossipTimestampFilter
{-# NOINLINE encodedGossipTimestampFilter #-}

-- Benchmark groups ------------------------------------------------------------

main :: IO ()
main = defaultMain
  [ bgroup "channel_announcement"
      [ bench "encode" $ nf encodeChannelAnnouncement testChannelAnnouncement
      , bench "decode" $ nf decodeChannelAnnouncement encodedChannelAnnouncement
      ]
  , bgroup "node_announcement"
      [ bench "encode" $ nf encodeNodeAnnouncement testNodeAnnouncement
      , bench "decode" $ nf decodeNodeAnnouncement encodedNodeAnnouncement
      ]
  , bgroup "channel_update"
      [ bench "encode" $ nf encodeChannelUpdate testChannelUpdate
      , bench "decode" $ nf decodeChannelUpdate encodedChannelUpdate
      ]
  , bgroup "announcement_signatures"
      [ bench "encode" $
          nf encodeAnnouncementSignatures testAnnouncementSignatures
      , bench "decode" $
          nf decodeAnnouncementSignatures encodedAnnouncementSignatures
      ]
  , bgroup "query_short_channel_ids"
      [ bench "encode" $
          nf encodeQueryShortChannelIds testQueryShortChannelIds
      , bench "decode" $
          nf decodeQueryShortChannelIds encodedQueryShortChannelIds
      ]
  , bgroup "reply_short_channel_ids_end"
      [ bench "encode" $
          nf encodeReplyShortChannelIdsEnd testReplyShortChannelIdsEnd
      , bench "decode" $
          nf decodeReplyShortChannelIdsEnd encodedReplyShortChannelIdsEnd
      ]
  , bgroup "query_channel_range"
      [ bench "encode" $ nf encodeQueryChannelRange testQueryChannelRange
      , bench "decode" $ nf decodeQueryChannelRange encodedQueryChannelRange
      ]
  , bgroup "reply_channel_range"
      [ bench "encode" $ nf encodeReplyChannelRange testReplyChannelRange
      , bench "decode" $ nf decodeReplyChannelRange encodedReplyChannelRange
      ]
  , bgroup "gossip_timestamp_filter"
      [ bench "encode" $
          nf encodeGossipTimestampFilter testGossipTimestampFilter
      , bench "decode" $
          nf decodeGossipTimestampFilter encodedGossipTimestampFilter
      ]
  , bgroup "scid_list"
      [ bench "encode (100)" $ nf encodeShortChannelIdList testScidList
      , bench "decode (100)" $ nf decodeShortChannelIdList encodedScidList
      ]
  , bgroup "hash"
      [ bench "channelAnnouncementHash" $
          nf channelAnnouncementHash encodedChannelAnnouncement
      , bench "nodeAnnouncementHash" $
          nf nodeAnnouncementHash encodedNodeAnnouncement
      , bench "channelUpdateHash" $
          nf channelUpdateHash encodedChannelUpdate
      , bench "channelUpdateChecksum" $
          nf channelUpdateChecksum encodedChannelUpdate
      ]
  , bgroup "validate"
      [ bench "channelAnnouncement" $
          nf validateChannelAnnouncement testChannelAnnouncement
      , bench "nodeAnnouncement" $
          nf validateNodeAnnouncement testNodeAnnouncement
      , bench "channelUpdate" $
          nf validateChannelUpdate testChannelUpdate
      , bench "queryChannelRange" $
          nf validateQueryChannelRange testQueryChannelRange
      , bench "replyChannelRange" $
          nf validateReplyChannelRange testReplyChannelRange
      ]
  ]