bolt1

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

commit 03232f36d797c377d8cd3ebbf9370c0a7e7ae0e8
parent 36fceee777eeeff95f642d81d0b64f204017e13e
Author: Jared Tobin <jared@jtobin.io>
Date:   Sat, 10 Oct 2026 13:22:19 +0400

test: cover every BOLT #1 appendix vector

Appendix A (BigSize, including short and no reads), Appendix B (the n1
and n2 namespaces, built from the public primitives), Appendix C
(init extensions) and Appendix D (per-width signed integers), plus
negative tests for each rejection rule and round-trip properties for
the codecs and every message.

Diffstat:
Atest/Main.hs | 548+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 548 insertions(+), 0 deletions(-)

diff --git a/test/Main.hs b/test/Main.hs @@ -0,0 +1,548 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Main where + +import qualified Data.ByteString as BS +import qualified Data.ByteString.Base16 as B16 +import qualified Data.ByteString.Char8 as B8 +import Data.Int (Int64) +import qualified Data.List as L +import Data.Maybe (fromMaybe, isJust, isNothing) +import Data.Word (Word16, Word32, Word64) +import Lightning.Protocol.BOLT1 +import qualified Lightning.Protocol.BOLT9 as BOLT9 +import Test.Tasty +import Test.Tasty.HUnit +import Test.Tasty.QuickCheck + +main :: IO () +main = defaultMain $ testGroup "ppad-bolt1" [ + appendix_a + , appendix_b + , appendix_c + , appendix_d + , primitives + , tlv + , messages + , properties + ] + +-- helpers -------------------------------------------------------------------- + +-- run an assertion on decoded hex (spaces and a 0x prefix are allowed), +-- failing the test if the literal isn't valid hex +with_hex :: String -> (BS.ByteString -> Assertion) -> Assertion +with_hex s k = case B16.decode (B8.pack (strip s)) of + Nothing -> assertFailure ("invalid hex literal: " ++ s) + Just bs -> k bs + where + strip = filter (/= ' ') . drop0x + drop0x ('0':'x':r) = r + drop0x r = r + +is_left :: Either a b -> Bool +is_left = either (const True) (const False) + +empty_fv :: BOLT9.FeatureVector +empty_fv = BOLT9.parse "" + +-- Appendix A: BigSize -------------------------------------------------------- + +appendix_a :: TestTree +appendix_a = testGroup "Appendix A (BigSize)" [ + testGroup "decoding" (map dec ok_vectors) + , testGroup "encoding" (map enc ok_vectors) + , testGroup "decoding failures" (map bad bad_vectors) + ] + where + dec (v, h) = testCase ("decode " ++ h) $ with_hex h $ \bs -> + decode_bigsize bs @?= Just (v, "") + enc (v, h) = testCase ("encode " ++ show v) $ with_hex h $ \bs -> + encode_bigsize v @?= bs + bad h = testCase ("reject " ++ show h) $ with_hex h $ \bs -> + decode_bigsize bs @?= Nothing + + ok_vectors :: [(Word64, String)] + ok_vectors = [ + (0, "00"), (252, "fc"), (253, "fd00fd"), (65535, "fdffff") + , (65536, "fe00010000"), (4294967295, "feffffffff") + , (4294967296, "ff0000000100000000") + , (18446744073709551615, "ffffffffffffffffff") + ] + + -- non-canonical encodings, short reads and no reads + bad_vectors = [ + "fd00fc", "fe0000ffff", "ff00000000ffffffff" + , "fd00", "feffff", "ffffffffff" + , "", "fd", "fe", "ff" + ] + +-- Appendix B: TLV ------------------------------------------------------------ + +-- the n1 and n2 test namespaces, decoded with bolt1's primitives + +data N1 = N1Tlv1 !Word64 + | N1Tlv2 !ShortChannelId + | N1Tlv3 !Point !Word64 !Word64 + | N1Tlv4 !Word16 + deriving (Eq, Show) + +decode_n1 :: BS.ByteString -> Either String [N1] +decode_n1 bs = do + s <- either (Left . show) Right (decode_tlv_stream (`elem` n1_types) bs) + sequence [ one r | r <- un_tlv_stream s, tlv_type r `elem` n1_types ] + where + n1_types = [1, 2, 3, 254] + one (TlvRecord 1 v) = maybe (Left "tlv1") (Right . N1Tlv1) + (decode_tu64 v) + one (TlvRecord 2 v) = case decode_short_channel_id v of + Just (c, "") -> Right (N1Tlv2 c) + _ -> Left "tlv2" + one (TlvRecord 3 v) = maybe (Left "tlv3") Right $ do + (p, r0) <- decode_point v + (a, r1) <- decode_u64 r0 + (b, r2) <- decode_u64 r1 + if BS.null r2 then pure (N1Tlv3 p a b) else Nothing + one (TlvRecord 254 v) = case decode_u16 v of + Just (c, "") -> Right (N1Tlv4 c) + _ -> Left "tlv4" + one _ = Left "unreachable" + +decode_n2 :: BS.ByteString -> Either String [(Word64, Word64)] +decode_n2 bs = do + s <- either (Left . show) Right (decode_tlv_stream (`elem` [0, 11]) bs) + sequence [ one r | r <- un_tlv_stream s, tlv_type r `elem` [0, 11] ] + where + one (TlvRecord 0 v) = maybe (Left "tlv1") (Right . (,) 0) (decode_tu64 v) + one (TlvRecord 11 v) = + maybe (Left "tlv2") (Right . (,) 11 . fromIntegral) (decode_tu32 v) + one _ = Left "unreachable" + +appendix_b :: TestTree +appendix_b = testGroup "Appendix B (TLV)" [ + testGroup "failures in any namespace" [ + testCase h $ with_hex h $ \bs -> do + assertBool "n1" (is_left (decode_n1 bs)) + assertBool "n2" (is_left (decode_n2 bs)) + | h <- any_fail ] + , testGroup "n1 failures" [ + testCase h $ with_hex h $ \bs -> assertBool "n1" (is_left (decode_n1 bs)) + | h <- n1_fail ] + , testGroup "n2 failures" [ + testCase h $ with_hex h $ \bs -> assertBool "n2" (is_left (decode_n2 bs)) + | h <- n2_fail ] + , testGroup "ignored successes" [ + testCase h $ with_hex h $ \bs -> do + decode_n1 bs @?= Right [] + decode_n2 bs @?= Right [] + | h <- any_ok ] + , testGroup "n1 successes" [ + testCase h $ with_hex h $ \bs -> decode_n1 bs @?= Right [v] + | (h, v) <- n1_ok ] + , testCase "n1 tlv3" $ with_hex n1_tlv3 $ \bs -> + with_hex node_id $ \pk -> case point pk of + Nothing -> assertFailure "bad node_id" + Just p -> decode_n1 bs @?= Right [N1Tlv3 p 1 2] + , testCase "unknown even types fail when no types are known" $ + mapM_ (\h -> with_hex h $ \bs -> + assertBool h (is_left (decode_tlv_stream (const False) bs))) + ["12 00", "fd0102 00", "fe01000002 00", "ff0100000000000002 00"] + , testCase "appending a higher-numbered valid stream succeeds" $ + with_hex "01 01 01" $ \a -> with_hex "fd00fe 02 0226" $ \b -> + decode_n1 (a <> b) @?= Right [N1Tlv1 1, N1Tlv4 550] + , testCase "appending an invalid stream fails" $ + with_hex "01 01 01" $ \a -> mapM_ (\h -> with_hex h $ \b -> + assertBool h (is_left (decode_n1 (a <> b)))) any_fail + ] + where + any_fail = [ + "0xfd", "0xfd01", "0xfd0001 00", "0xfd0101", "0x0f fd", "0x0f fd26" + , "0x0f fd2602", "0x0f fd0001 00", "0x0f fd0201 " ++ replicate 1024 '0' + , "0x12 00", "0xfd0102 00", "0xfe01000002 00" + , "0xff0100000000000002 00" + ] + n1_fail = [ + "0x01 09 ffffffffffffffffff", "0x01 01 00", "0x01 02 0001" + , "0x01 03 000100", "0x01 04 00010000", "0x01 05 0001000000" + , "0x01 06 000100000000", "0x01 07 00010000000000" + , "0x01 08 0001000000000000", "0x02 07 01010101010101" + , "0x02 09 010101010101010101" + , "0x03 21 " ++ node_id + , "0x03 29 " ++ node_id ++ "0000000000000001" + , "0x03 30 " ++ node_id ++ "000000000000000100000000000001" + , "0x03 31 04" ++ drop 2 node_id + ++ "00000000000000010000000000000002" + , "0x03 32 " ++ node_id ++ "0000000000000001000000000000000001" + , "0xfd00fe 00", "0xfd00fe 01 01", "0xfd00fe 03 010101", "0x00 00" + , "0x02 08 0000000000000226 01 01 2a" + , "0x02 08 0000000000000231 02 08 0000000000000451" + , "0x1f 00 0f 01 2a", "0x1f 00 1f 01 2a" + ] + n2_fail = ["0xffffffffffffffffff 00 00 00"] + any_ok = [ + "0x", "0x21 00", "0xfd0201 00", "0xfd00fd 00", "0xfd00ff 00" + , "0xfe02000001 00", "0xff0200000000000001 00" + ] + n1_ok = [ + ("0x01 00", N1Tlv1 0), ("0x01 01 01", N1Tlv1 1) + , ("0x01 02 0100", N1Tlv1 256), ("0x01 03 010000", N1Tlv1 65536) + , ("0x01 04 01000000", N1Tlv1 16777216) + , ("0x01 05 0100000000", N1Tlv1 4294967296) + , ("0x01 06 010000000000", N1Tlv1 1099511627776) + , ("0x01 07 01000000000000", N1Tlv1 281474976710656) + , ("0x01 08 0100000000000000", N1Tlv1 72057594037927936) + , ("0x02 08 0000000000000226", N1Tlv2 (ShortChannelId 0x226)) + , ("0xfd00fe 02 0226", N1Tlv4 550) + ] + node_id = + "023da092f6980e58d2c037173180e9a465476026ee50f96695963e8efe436f54eb" + n1_tlv3 = "0x03 31 " ++ node_id ++ "00000000000000010000000000000002" + +-- Appendix C: message extension ---------------------------------------------- + +appendix_c :: TestTree +appendix_c = testGroup "Appendix C (message extension)" [ + testCase "no extension" $ with_hex "001000000000" $ \bs -> + case decode_message bs of + Right (MsgInit i) -> do + init_networks i @?= Nothing + init_tlvs i @?= empty_tlv_stream + other -> assertFailure (show other) + , testCase "two unknown odd records are kept" $ + with_hex "001000000000c9012acb0104" $ \bs -> + case decode_message bs of + Right m@(MsgInit i) -> do + map tlv_type (un_tlv_stream (init_tlvs i)) @?= [0xc9, 0xcb] + encode_message m @?= Right bs + other -> assertFailure (show other) + , testCase "truncated extension" $ with_hex "00100000000001" $ \bs -> + decode_message bs @?= Left (DecodeTlvError TlvTruncated) + , testCase "unknown even record" $ with_hex "001000000000ca012a" $ \bs -> + decode_message bs @?= Left (DecodeTlvError (TlvUnknownEvenType 0xca)) + , testCase "duplicate record" $ with_hex "001000000000c90101c90102" $ \bs -> + decode_message bs @?= Left (DecodeTlvError TlvNotStrictlyIncreasing) + ] + +-- Appendix D: signed integers ------------------------------------------------ + +appendix_d :: TestTree +appendix_d = testGroup "Appendix D (signed integers)" [ + testCase (show v) $ with_hex h $ \bs -> case BS.length bs of + 1 -> do + encode_s8 (fromIntegral v) @?= bs + decode_s8 bs @?= Just (fromIntegral v, "") + 2 -> do + encode_s16 (fromIntegral v) @?= bs + decode_s16 bs @?= Just (fromIntegral v, "") + 4 -> do + encode_s32 (fromIntegral v) @?= bs + decode_s32 bs @?= Just (fromIntegral v, "") + 8 -> do + encode_s64 v @?= bs + decode_s64 bs @?= Just (v, "") + _ -> assertFailure "bad vector width" + | (v, h) <- vectors ] + where + vectors :: [(Int64, String)] + vectors = [ + (0, "00"), (42, "2a"), (-42, "d6"), (127, "7f"), (-128, "80") + , (128, "0080"), (-129, "ff7f"), (15000, "3a98"), (-15000, "c568") + , (32767, "7fff"), (-32768, "8000"), (32768, "00008000") + , (-32769, "ffff7fff"), (21000000, "01406f40") + , (-21000000, "febf90c0"), (2147483647, "7fffffff") + , (-2147483648, "80000000"), (2147483648, "0000000080000000") + , (-2147483649, "ffffffff7fffffff"), (500000000000, "000000746a528800") + , (-500000000000, "ffffff8b95ad7800") + , (9223372036854775807, "7fffffffffffffff") + , (-9223372036854775808, "8000000000000000") + ] + +-- primitives ----------------------------------------------------------------- + +primitives :: TestTree +primitives = testGroup "Primitives" [ + testCase "u16/u32/u64 known answers" $ do + encode_u16 0x0102 @?= "\x01\x02" + encode_u32 0x01020304 @?= "\x01\x02\x03\x04" + encode_u64 0x0102030405060708 @?= "\x01\x02\x03\x04\x05\x06\x07\x08" + decode_u32 "\x01\x02\x03\x04rest" @?= Just (0x01020304, "rest") + , testCase "short reads fail" $ do + decode_u16 "\x01" @?= Nothing + decode_u32 "\x01\x02\x03" @?= Nothing + decode_u64 "\x01\x02\x03\x04\x05\x06\x07" @?= Nothing + decode_s8 "" @?= Nothing + , testCase "truncated integers" $ do + encode_tu64 0 @?= "" + encode_tu64 1 @?= "\x01" + encode_tu32 0x010000 @?= "\x01\x00\x00" + decode_tu16 "" @?= Just 0 + decode_tu16 "\x00\x01" @?= Nothing + decode_tu16 "\x01\x00\x00" @?= Nothing + decode_tu32 "\x01\x00\x00\x00\x00" @?= Nothing + decode_tu64 "\x01\x00\x00\x00\x00\x00\x00\x00\x00" @?= Nothing + , testCase "length-prefixed bytes" $ do + encode_u16_prefixed "abc" @?= Just "\x00\x03\&abc" + encode_u16_prefixed (BS.replicate 65536 0) @?= Nothing + decode_u16_prefixed "\x00\x03\&abcde" @?= Just ("abc", "de") + decode_u16_prefixed "\x00\x03\&ab" @?= Nothing + , testCase "fixed-size types check their lengths" $ do + assertBool "chain_hash" (isNothing (chain_hash (BS.replicate 31 0))) + assertBool "channel_id" (isNothing (channel_id (BS.replicate 33 0))) + assertBool "signature" (isNothing (signature (BS.replicate 65 0))) + assertBool "payment_hash" (isNothing (payment_hash "")) + assertBool "preimage" (isNothing (payment_preimage "")) + assertBool "secret" (isNothing (per_commitment_secret "")) + , testCase "point prefix" $ do + assertBool "02" (isJust (point (BS.cons 0x02 (BS.replicate 32 1)))) + assertBool "03" (isJust (point (BS.cons 0x03 (BS.replicate 32 1)))) + assertBool "04" (isNothing (point (BS.cons 0x04 (BS.replicate 32 1)))) + assertBool "len" (isNothing (point (BS.cons 0x02 (BS.replicate 31 1)))) + , testCase "short_channel_id" $ case short_channel_id 539268 845 1 of + Nothing -> assertFailure "valid scid rejected" + Just c -> do + scid_block_height c @?= 539268 + scid_tx_index c @?= 845 + scid_output_index c @?= 1 + encode_short_channel_id c @?= "\x08\x3a\x84\x00\x03\x4d\x00\x01" + , testCase "short_channel_id rejects 25-bit components" $ do + short_channel_id 0x1000000 0 0 @?= Nothing + short_channel_id 0 0x1000000 0 @?= Nothing + , testCase "amount bounds" $ do + fmap un_satoshi (satoshi 0x000775f05a074000) @?= Just 0x000775f05a074000 + satoshi 0x000775f05a074001 @?= Nothing + fmap un_milli_satoshi (milli_satoshi 0x1d24b2dfac520000) + @?= Just 0x1d24b2dfac520000 + milli_satoshi 0x1d24b2dfac520001 @?= Nothing + sat_to_msat max_satoshi @?= max_milli_satoshi + decode_satoshi (encode_u64 0x000775f05a074001) @?= Nothing + decode_milli_satoshi (encode_u64 maxBound) @?= Nothing + , testCase "checked arithmetic" $ do + add_sat max_satoshi max_satoshi @?= Nothing + (do a <- satoshi 1; b <- satoshi 2; sub_sat a b) @?= Nothing + (do a <- satoshi 2; b <- satoshi 1; sub_sat a b) @?= satoshi 1 + (do a <- milli_satoshi 1; b <- milli_satoshi 2; sub_msat a b) + @?= Nothing + add_msat max_milli_satoshi max_milli_satoshi @?= Nothing + , testCase "secrets don't show" $ + case payment_preimage (BS.replicate 32 7) of + Nothing -> assertFailure "valid preimage rejected" + Just p -> do + show p @?= "PaymentPreimage <redacted>" + show (Just p) @?= "Just (PaymentPreimage <redacted>)" + , testCase "secret equality" $ do + let a = payment_preimage (BS.replicate 32 7) + b = payment_preimage (BS.replicate 31 7 <> "\x08") + assertBool "equal" (a == a) + assertBool "unequal" (a /= b) + ] + +-- TLV ------------------------------------------------------------------------ + +tlv :: TestTree +tlv = testGroup "TLV" [ + testCase "tlv_stream sorts" $ + fmap (map tlv_type . un_tlv_stream) + (tlv_stream [TlvRecord 5 "", TlvRecord 1 "", TlvRecord 3 ""]) + @?= Just [1, 3, 5] + , testCase "tlv_stream rejects duplicates" $ + tlv_stream [TlvRecord 1 "a", TlvRecord 1 "b"] @?= Nothing + , testCase "lookup_tlv" $ do + (lookup_tlv 3 =<< tlv_stream [TlvRecord 1 "a", TlvRecord 3 "b"]) + @?= Just "b" + (lookup_tlv 2 =<< tlv_stream [TlvRecord 1 "a", TlvRecord 3 "b"]) + @?= Nothing + , testCase "huge lengths are rejected, not wrapped" $ + mapM_ (\h -> with_hex h $ \bs -> + decode_tlv_stream (const True) bs @?= Left TlvTruncated) + [ "01ffffffffffffffffff", "01ff800000000000000067" + , "01ffffffffffffffffff6701aa" ] + , testCase "truncation vs non-minimal" $ do + decode_tlv_stream (const True) "\xfd" @?= Left TlvTruncated + decode_tlv_stream (const True) "\xfd\x00\x01\x00" + @?= Left TlvNonMinimalBigSize + , testCase "unknown odd records are kept" $ + fmap (map tlv_type . un_tlv_stream) + (decode_tlv_stream (const False) "\x01\x00\x03\x01\x2a") + @?= Right [1, 3] + ] + +-- messages ------------------------------------------------------------------- + +messages :: TestTree +messages = testGroup "Messages" [ + testCase "pong known answer" $ + encode_message (MsgPong (Pong "\x00\x00" empty_tlv_stream)) + @?= Right "\x00\x13\x00\x02\x00\x00" + , testCase "ping known answer" $ + encode_message (MsgPing (Ping 4 "" empty_tlv_stream)) + @?= Right "\x00\x12\x00\x04\x00\x00" + , testCase "error known answer" $ + encode_message (MsgError (Error all_channels "x" empty_tlv_stream)) + @?= Right ("\x00\x11" <> BS.replicate 32 0 <> "\x00\x01x") + , testCase "init with networks and remote_addr" $ + case chain_hash (BS.replicate 32 0x6f) of + Nothing -> assertFailure "valid chain hash rejected" + Just ch -> do + let addr = "\x01\x7f\x00\x00\x01\x26\x07" + i = Init empty_fv (BOLT9.parse "\x02") (Just [ch]) + (Just addr) empty_tlv_stream + wire = "\x00\x10\x00\x00\x00\x01\x02" + <> "\x01\x20" <> un_chain_hash ch <> "\x03\x07" <> addr + encode_message (MsgInit i) @?= Right wire + decode_message wire @?= Right (MsgInit i) + , testCase "init networks must be a multiple of 32 bytes" $ + decode_init "\x00\x00\x00\x00\x01\x01\x00" + @?= Left (DecodeInvalidTlvValue 1) + , testCase "init rejects tlvs clashing with typed fields" $ + case tlv_stream [TlvRecord 3 "x"] of + Nothing -> assertFailure "valid stream rejected" + Just s -> encode_init (Init empty_fv empty_fv Nothing (Just "y") s) + @?= Left EncodeInvalidTlvs + , testCase "unknown message types" $ do + decode_message "\x00\x20" @?= Left (DecodeUnknownEvenType 32) + decode_message "\x80\x01" @?= Left (DecodeUnknownOddType 32769) + decode_message "\x00" @?= Left DecodeInsufficientBytes + , testCase "truncated payloads" $ do + decode_init "\x00" @?= Left DecodeInsufficientBytes + decode_init "\x00\x01" @?= Left DecodeInsufficientBytes + decode_error (BS.replicate 31 0) @?= Left DecodeInsufficientBytes + decode_warning (BS.replicate 33 0) @?= Left DecodeInsufficientBytes + decode_ping "\x00\x04\x00\x02\x00" @?= Left DecodeInsufficientBytes + decode_pong "\x00\x02\x00" @?= Left DecodeInsufficientBytes + decode_peer_storage "\x00\x01" @?= Left DecodeInsufficientBytes + decode_peer_storage_retrieval "\x00" @?= Left DecodeInsufficientBytes + , testCase "invalid extensions" $ do + decode_pong "\x00\x00\x02\x00" @?= Left + (DecodeTlvError (TlvUnknownEvenType 2)) + decode_ping "\x00\x00\x00\x00\x01" @?= Left (DecodeTlvError TlvTruncated) + , testCase "message size limit" $ do + encode_envelope 7 (BS.replicate 65533 0) @?= + Right (encode_u16 7 <> BS.replicate 65533 0) + encode_envelope 7 (BS.replicate 65534 0) @?= Left EncodeMessageTooLarge + encode_message (MsgPeerStorage (PeerStorage (BS.replicate 65532 0) + empty_tlv_stream)) @?= Left EncodeMessageTooLarge + encode_pong (Pong (BS.replicate 65536 0) empty_tlv_stream) + @?= Left EncodeLengthOverflow + , testCase "ping_response" $ do + ping_response (Ping 3 "ab" empty_tlv_stream) + @?= Just (Pong "\x00\x00\x00" empty_tlv_stream) + ping_response (Ping 65531 "" empty_tlv_stream) @?= + Just (Pong (BS.replicate 65531 0) empty_tlv_stream) + ping_response (Ping 65532 "" empty_tlv_stream) @?= Nothing + ] + +-- properties ----------------------------------------------------------------- + +newtype Bytes = Bytes BS.ByteString + deriving Show + +instance Arbitrary Bytes where + arbitrary = Bytes . BS.pack <$> (choose (0, 64) >>= vector) + +-- streams of unknown odd records +newtype OddTlvs = OddTlvs TlvStream + deriving Show + +instance Arbitrary OddTlvs where + arbitrary = do + ts <- L.nub . map (\t -> 2 * (t `div` 2) + 1) + <$> listOf (arbitrary :: Gen Word64) + vs <- vectorOf (length ts) arbitrary + pure . OddTlvs . fromMaybe empty_tlv_stream $ + tlv_stream [ TlvRecord t v | (t, Bytes v) <- zip ts vs ] + +newtype Bytes32 = Bytes32 BS.ByteString + deriving Show + +instance Arbitrary Bytes32 where + arbitrary = Bytes32 . BS.pack <$> vector 32 + +newtype Msg = Msg Message + deriving Show + +instance Arbitrary Msg where + arbitrary = Msg <$> oneof [ + do Bytes a <- arbitrary + Bytes b <- arbitrary + nets <- oneof [pure Nothing, Just <$> listOf chain] + addr <- oneof [pure Nothing, (\(Bytes x) -> Just x) <$> arbitrary] + OddTlvs s <- arbitrary + let s' = filter_tlv_stream (\t -> t /= 1 && t /= 3) s + pure (MsgInit (Init (BOLT9.parse a) (BOLT9.parse b) nets addr s')) + , do c <- cid + Bytes d <- arbitrary + OddTlvs s <- arbitrary + pure (MsgError (Error c d s)) + , do c <- cid + Bytes d <- arbitrary + OddTlvs s <- arbitrary + pure (MsgWarning (Warning c d s)) + , do n <- arbitrary + Bytes d <- arbitrary + OddTlvs s <- arbitrary + pure (MsgPing (Ping n d s)) + , do Bytes d <- arbitrary + OddTlvs s <- arbitrary + pure (MsgPong (Pong d s)) + , do Bytes d <- arbitrary + OddTlvs s <- arbitrary + pure (MsgPeerStorage (PeerStorage d s)) + , do Bytes d <- arbitrary + OddTlvs s <- arbitrary + pure (MsgPeerStorageRetrieval (PeerStorageRetrieval d s)) + ] + where + chain = do + Bytes32 b <- arbitrary + maybe chain pure (chain_hash b) + cid = do + Bytes32 b <- arbitrary + pure (fromMaybe all_channels (channel_id b)) + +properties :: TestTree +properties = testGroup "Properties" [ + testProperty "bigsize round-trips" $ \w -> + decode_bigsize (encode_bigsize w) === Just (w, "") + , testProperty "tu16 round-trips" $ \w -> + decode_tu16 (encode_tu16 w) === Just w + , testProperty "tu32 round-trips" $ \w -> + decode_tu32 (encode_tu32 w) === Just w + , testProperty "tu64 round-trips" $ \w -> + decode_tu64 (encode_tu64 w) === Just w + , testProperty "u16 round-trips" $ \w -> + decode_u16 (encode_u16 w) === Just (w, "") + , testProperty "u32 round-trips" $ \w -> + decode_u32 (encode_u32 w) === Just (w, "") + , testProperty "u64 round-trips" $ \w -> + decode_u64 (encode_u64 w) === Just (w, "") + , testProperty "s64 round-trips" $ \w -> + decode_s64 (encode_s64 w) === Just (w, "") + , testProperty "scid components round-trip" $ \h t o -> + let h' = h `mod` 0x1000000 :: Word32 + t' = t `mod` 0x1000000 :: Word32 + in fmap (\c -> (scid_block_height c, scid_tx_index c, + scid_output_index c)) (short_channel_id h' t' o) + === Just (h', t', o) + , testProperty "sat -> msat -> sat is the identity" $ + forAll (choose (0, 0x000775f05a074000)) $ \w -> + fmap (msat_to_sat . sat_to_msat) (satoshi w) === satoshi w + , testProperty "add then sub is the identity" $ + forAll (choose (0, 0x000775f05a074000)) $ \a -> + forAll (choose (0, 0x000775f05a074000 - a)) $ \b -> + (do x <- satoshi a + y <- satoshi b + z <- add_sat x y + sub_sat z y) === satoshi a + , testProperty "tlv streams round-trip" $ \(OddTlvs s) -> + decode_tlv_stream (const False) (encode_tlv_stream s) === Right s + , testProperty "messages round-trip" $ \(Msg m) -> + case encode_message m of + Left e -> counterexample (show e) False + Right bs -> decode_message bs === Right m + , testProperty "re-encoding reproduces the wire bytes" $ \(Msg m) -> + case encode_message m of + Left e -> counterexample (show e) False + Right bs -> case decode_message bs of + Left e -> counterexample (show e) False + Right m' -> encode_message m' === Right bs + ]