commit 0c4cf4e90876f7fbcc3c1ed10e12e0a24067f1fe
parent 07341e4e7f4546a61fb5e667660c04e02b6ba42b
Author: Jared Tobin <jared@jtobin.io>
Date: Sun, 25 Jan 2026 15:19:52 +0400
Merge impl/codec: Phase 3 Codec module completion
Phase 3 of IMPL1 implementation plan - complete Codec module.
New functionality:
- encodeShortChannelIdList: encode list of SCIDs as encoded_short_ids
- decodeShortChannelIdList: decode encoded_short_ids to SCID list
The core encode/decode functions for all 9 message types were already
implemented. This phase adds the SCID list helpers needed for query
message payloads.
All 36 tests pass.
Diffstat:
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