bolt7

Routing gossip protocol, per BOLT #7.
git clone git://git.ppad.tech/bolt7.git
Log | Files | Refs | README | LICENSE

commit 6408ff8c1bceb5669220e5a2617bd16e0709a3c9
parent 07341e4e7f4546a61fb5e667660c04e02b6ba42b
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 25 Jan 2026 15:18:08 +0400

Phase 3: Add SCID list encoding helpers

Add encodeShortChannelIdList/decodeShortChannelIdList for working with
encoded_short_ids data in query messages.

- Encoding type 0 (uncompressed) is supported
- Unknown encoding types are rejected
- Functions work with the raw encoded_short_ids payload format

Add tests for SCID list encoding:
- Empty list roundtrip
- Single and multiple SCID roundtrip
- Format verification (encoding type byte)
- Unknown encoding type rejection

All 36 tests pass.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Diffstat:
Mlib/Lightning/Protocol/BOLT7/Codec.hs | 45+++++++++++++++++++++++++++++++++++++++++++++
Mtest/Main.hs | 40++++++++++++++++++++++++++++++++++++++++
2 files changed, 85 insertions(+), 0 deletions(-)

diff --git a/lib/Lightning/Protocol/BOLT7/Codec.hs b/lib/Lightning/Protocol/BOLT7/Codec.hs @@ -41,6 +41,10 @@ module Lightning.Protocol.BOLT7.Codec ( , decodeReplyChannelRange , encodeGossipTimestampFilter , decodeGossipTimestampFilter + + -- * Short channel ID encoding + , encodeShortChannelIdList + , decodeShortChannelIdList ) where import Control.DeepSeq (NFData) @@ -599,3 +603,44 @@ decodeGossipTimestampFilter bs = do , gossipFilterTimestampRange = tsRange } Right (msg, rest) + +-- Short channel ID list encoding ----------------------------------------------- + +-- | Encode a list of short channel IDs as concatenated 8-byte values. +-- +-- This produces encoded_short_ids data with encoding type 0 (uncompressed). +-- The first byte is the encoding type (0), followed by the concatenated SCIDs. +-- +-- Note: This does NOT sort the SCIDs. The caller should ensure they are in +-- ascending order if that's required by the protocol context. +encodeShortChannelIdList :: [ShortChannelId] -> ByteString +encodeShortChannelIdList scids = BS.cons 0 $ + mconcat (map getShortChannelId scids) +{-# INLINE encodeShortChannelIdList #-} + +-- | Decode a list of short channel IDs from encoded_short_ids data. +-- +-- Supports encoding type 0 (uncompressed). Other encoding types will fail. +decodeShortChannelIdList :: ByteString + -> Either DecodeError [ShortChannelId] +decodeShortChannelIdList bs + | BS.null bs = Left DecodeInsufficientBytes + | otherwise = do + let encType = BS.index bs 0 + payload = BS.drop 1 bs + case encType of + 0 -> decodeUncompressedScids payload + _ -> Left DecodeInvalidShortChannelId -- Unsupported encoding type + where + decodeUncompressedScids :: ByteString -> Either DecodeError [ShortChannelId] + decodeUncompressedScids !d + | BS.null d = Right [] + | BS.length d < shortChannelIdLen = Left DecodeInsufficientBytes + | otherwise = do + let (scidBytes, rest) = BS.splitAt shortChannelIdLen d + case shortChannelId scidBytes of + Nothing -> Left DecodeInvalidShortChannelId + Just scid -> do + scids <- decodeUncompressedScids rest + Right (scid : scids) +{-# INLINE decodeShortChannelIdList #-} diff --git a/test/Main.hs b/test/Main.hs @@ -19,6 +19,7 @@ main = defaultMain $ testGroup "ppad-bolt7" [ , channel_update_tests , announcement_signatures_tests , query_tests + , scid_list_tests , error_tests , property_tests ] @@ -327,6 +328,45 @@ query_tests = testGroup "Query Messages" [ ] ] +-- SCID List Tests ------------------------------------------------------------ + +scid_list_tests :: TestTree +scid_list_tests = testGroup "SCID List Encoding" [ + testCase "encode/decode roundtrip empty list" $ do + let encoded = encodeShortChannelIdList [] + case decodeShortChannelIdList encoded of + Right decoded -> decoded @?= [] + Left e -> assertFailure $ "decode failed: " ++ show e + , testCase "encode/decode roundtrip single SCID" $ do + let scids = [mkShortChannelId 539268 845 1] + encoded = encodeShortChannelIdList scids + case decodeShortChannelIdList encoded of + Right decoded -> decoded @?= scids + Left e -> assertFailure $ "decode failed: " ++ show e + , testCase "encode/decode roundtrip multiple SCIDs" $ do + let scids = [ mkShortChannelId 100000 1 0 + , mkShortChannelId 200000 2 1 + , mkShortChannelId 300000 3 2 + ] + encoded = encodeShortChannelIdList scids + case decodeShortChannelIdList encoded of + Right decoded -> decoded @?= scids + Left e -> assertFailure $ "decode failed: " ++ show e + , testCase "encoding has correct format" $ do + let scids = [mkShortChannelId 1 2 3] + encoded = encodeShortChannelIdList scids + -- First byte should be 0 (encoding type) + BS.index encoded 0 @?= 0 + -- Total length: 1 (type) + 8 (SCID) = 9 + BS.length encoded @?= 9 + , testCase "decode rejects unknown encoding type" $ do + -- Encoding type 1 (zlib compressed) is not supported + let badEncoded = BS.cons 1 (getShortChannelId testShortChannelId) + case decodeShortChannelIdList badEncoded of + Left _ -> pure () + Right _ -> assertFailure "should reject encoding type 1" + ] + -- Error Tests ----------------------------------------------------------------- error_tests :: TestTree