commit e7ef9d53a92063b1c9129d2f811606795a5440cf
parent 6f295dd9af0ea3a78db14f5020744bef902a55a1
Author: Jared Tobin <jared@jtobin.io>
Date: Sat, 10 Oct 2026 14:16:27 +0400
test: known answers, TLV handling and round-trips
Replaces the suite for the new API.
* A known-answer test per message (31 in all), each against wire bytes
assembled by hand from the spec's field layout, checked through both
the payload codec and encode_message/decode_message.
* Per-message TLV tests: unknown odd records preserved and re-encoded
exactly, unknown even and unordered records rejected, each known type
decoded into its typed field, malformed known values rejected, and
the 2^64-1 record length rejected.
* Splicing records: shared_input_txid and shared_input_signature are
rejected as unknown even types; my_current_funding_locked is kept.
* Negative tests for truncation, unknown message types, invalid points,
amounts above 21M BTC, the stfu initiator byte, u16 length and count
overflows, known types in _tlvs fields and the message size limit.
* Round-trip properties for every message, and a property that any
decodable edit of a valid encoding re-encodes to the same bytes.
Diffstat:
| M | test/Main.hs | | | 2581 | +++++++++++++++++++++++++++++++++++++++---------------------------------------- |
1 file changed, 1279 insertions(+), 1302 deletions(-)
diff --git a/test/Main.hs b/test/Main.hs
@@ -2,1330 +2,1307 @@
module Main where
+import qualified Bitcoin.Prim.Tx as Tx
import qualified Data.ByteString as BS
import qualified Data.ByteString.Base16 as B16
-import Data.Maybe (fromJust)
-import Data.Word (Word8, Word16, Word32, Word64)
-import Lightning.Protocol.BOLT1 (TlvStream, unsafeTlvStream)
+import qualified Data.ByteString.Char8 as B8
+import Data.Int (Int64)
+import Data.Word (Word8, Word64)
+import qualified Lightning.Protocol.BOLT1 as BOLT1
+import Lightning.Protocol.BOLT1 (TlvRecord(..), TlvError(..))
import Lightning.Protocol.BOLT2
+import qualified Lightning.Protocol.BOLT9 as BOLT9
import Test.Tasty
import Test.Tasty.HUnit
-import Test.Tasty.QuickCheck
+import Test.Tasty.QuickCheck hiding (witness)
main :: IO ()
main = defaultMain $ testGroup "ppad-bolt2" [
- v1_establishment_tests
- , v2_establishment_tests
- , close_tests
- , normal_operation_tests
- , reestablish_tests
- , error_tests
- , property_tests
+ known_answers
+ , tlv_handling
+ , decoding_failures
+ , encoding_failures
+ , field_types
+ , properties
]
--- Test data helpers -----------------------------------------------------------
-
--- | Create a valid ChannelId (32 bytes).
-testChannelId :: ChannelId
-testChannelId = fromJust $ channelId (BS.replicate 32 0xab)
-
--- | Create a valid ChainHash (32 bytes).
-testChainHash :: ChainHash
-testChainHash = fromJust $ chainHash (BS.replicate 32 0x01)
-
--- | Create a valid Point (33 bytes).
-testPoint :: Point
-testPoint = fromJust $ point (BS.pack $ 0x02 : replicate 32 0xff)
-
--- | Create a second valid Point (33 bytes).
-testPoint2 :: Point
-testPoint2 = fromJust $ point (BS.pack $ 0x03 : replicate 32 0xee)
-
--- | Create a valid Signature (64 bytes).
-testSignature :: Signature
-testSignature = fromJust $ signature (BS.replicate 64 0xcc)
-
--- | Create a valid TxId (32 bytes).
-testTxId :: TxId
-testTxId = fromJust $ mkTxId (BS.replicate 32 0xdd)
-
--- | Create a valid PaymentHash (32 bytes).
-testPaymentHash :: PaymentHash
-testPaymentHash = fromJust $ paymentHash (BS.replicate 32 0xaa)
-
--- | Create a valid PaymentPreimage (32 bytes).
-testPaymentPreimage :: PaymentPreimage
-testPaymentPreimage = fromJust $ paymentPreimage (BS.replicate 32 0xbb)
-
--- | Create a valid OnionPacket (1366 bytes).
-testOnionPacket :: OnionPacket
-testOnionPacket = fromJust $ onionPacket (BS.replicate 1366 0x00)
-
--- | Create a valid PerCommitmentSecret (32 bytes).
-testSecret :: PerCommitmentSecret
-testSecret = fromJust $
- perCommitmentSecret (BS.replicate 32 0x11)
-
--- | Empty TLV stream for messages.
-emptyTlvs :: TlvStream
-emptyTlvs = unsafeTlvStream []
-
--- V1 Channel Establishment Tests ----------------------------------------------
-
-v1_establishment_tests :: TestTree
-v1_establishment_tests = testGroup "V1 Channel Establishment" [
- testGroup "OpenChannel" [
- testCase "encode/decode roundtrip" $ do
- let msg = OpenChannel
- { openChannelChainHash = testChainHash
- , openChannelTempChannelId = testChannelId
- , openChannelFundingSatoshi = Satoshi 1000000
- , openChannelPushMsat = MilliSatoshi 500000
- , openChannelDustLimitSatoshi = Satoshi 546
- , openChannelMaxHtlcValueInFlight = MilliSatoshi 100000000
- , openChannelChannelReserveSat = Satoshi 10000
- , openChannelHtlcMinimumMsat = MilliSatoshi 1000
- , openChannelFeeratePerKw = 2500
- , openChannelToSelfDelay = 144
- , openChannelMaxAcceptedHtlcs = 483
- , openChannelFundingPubkey = testPoint
- , openChannelRevocationBasepoint = testPoint
- , openChannelPaymentBasepoint = testPoint
- , openChannelDelayedPaymentBase = testPoint
- , openChannelHtlcBasepoint = testPoint
- , openChannelFirstPerCommitPoint = testPoint
- , openChannelChannelFlags = 0x01
- , openChannelTlvs = emptyTlvs
- }
- encoded = encodeOpenChannel msg
- case decodeOpenChannel encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "AcceptChannel" [
- testCase "encode/decode roundtrip" $ do
- let msg = AcceptChannel
- { acceptChannelTempChannelId = testChannelId
- , acceptChannelDustLimitSatoshi = Satoshi 546
- , acceptChannelMaxHtlcValueInFlight = MilliSatoshi 100000000
- , acceptChannelChannelReserveSat = Satoshi 10000
- , acceptChannelHtlcMinimumMsat = MilliSatoshi 1000
- , acceptChannelMinimumDepth = 3
- , acceptChannelToSelfDelay = 144
- , acceptChannelMaxAcceptedHtlcs = 483
- , acceptChannelFundingPubkey = testPoint
- , acceptChannelRevocationBasepoint = testPoint
- , acceptChannelPaymentBasepoint = testPoint
- , acceptChannelDelayedPaymentBase = testPoint
- , acceptChannelHtlcBasepoint = testPoint
- , acceptChannelFirstPerCommitPoint = testPoint
- , acceptChannelTlvs = emptyTlvs
- }
- encoded = encodeAcceptChannel msg
- case decodeAcceptChannel encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "FundingCreated" [
- testCase "encode/decode roundtrip" $ do
- let msg = FundingCreated
- { fundingCreatedTempChannelId = testChannelId
- , fundingCreatedFundingTxid = testTxId
- , fundingCreatedFundingOutIdx = 0
- , fundingCreatedSignature = testSignature
- }
- encoded = encodeFundingCreated msg
- case decodeFundingCreated encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- , testCase "roundtrip with non-zero output index" $ do
- let msg = FundingCreated
- { fundingCreatedTempChannelId = testChannelId
- , fundingCreatedFundingTxid = testTxId
- , fundingCreatedFundingOutIdx = 42
- , fundingCreatedSignature = testSignature
- }
- encoded = encodeFundingCreated msg
- case decodeFundingCreated encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "FundingSigned" [
- testCase "encode/decode roundtrip" $ do
- let msg = FundingSigned
- { fundingSignedChannelId = testChannelId
- , fundingSignedSignature = testSignature
- }
- encoded = encodeFundingSigned msg
- case decodeFundingSigned encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "ChannelReady" [
- testCase "encode/decode roundtrip" $ do
- let msg = ChannelReady
- { channelReadyChannelId = testChannelId
- , channelReadySecondPerCommitPoint = testPoint
- , channelReadyTlvs = emptyTlvs
- }
- encoded = encodeChannelReady msg
- case decodeChannelReady encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
+-- helpers --------------------------------------------------------------------
+
+rep :: Int -> String -> String
+rep n = concat . replicate n
+
+hx :: String -> Maybe BS.ByteString
+hx = B16.decode . B8.pack
+
+-- decode a hex literal, failing the test if it isn't valid hex
+unhex :: String -> IO BS.ByteString
+unhex s = maybe (assertFailure ("invalid hex literal: " ++ s)) pure (hx s)
+
+-- unwrap a fixture, failing the test if it couldn't be constructed
+just :: String -> Maybe a -> IO a
+just what = maybe (assertFailure ("invalid fixture: " ++ what)) pure
+
+empty :: BOLT1.TlvStream
+empty = BOLT1.empty_tlv_stream
+
+-- the unknown TLV records of a message
+message_tlvs :: Message -> BOLT1.TlvStream
+message_tlvs m = case m of
+ MsgOpenChannel a -> open_channel_tlvs a
+ MsgAcceptChannel a -> accept_channel_tlvs a
+ MsgFundingCreated a -> funding_created_tlvs a
+ MsgFundingSigned a -> funding_signed_tlvs a
+ MsgChannelReady a -> channel_ready_tlvs a
+ MsgOpenChannel2 a -> open_channel2_tlvs a
+ MsgAcceptChannel2 a -> accept_channel2_tlvs a
+ MsgTxAddInput a -> tx_add_input_tlvs a
+ MsgTxAddOutput a -> tx_add_output_tlvs a
+ MsgTxRemoveInput a -> tx_remove_input_tlvs a
+ MsgTxRemoveOutput a -> tx_remove_output_tlvs a
+ MsgTxComplete a -> tx_complete_tlvs a
+ MsgTxSignatures a -> tx_signatures_tlvs a
+ MsgTxInitRbf a -> tx_init_rbf_tlvs a
+ MsgTxAckRbf a -> tx_ack_rbf_tlvs a
+ MsgTxAbort a -> tx_abort_tlvs a
+ MsgStfu a -> stfu_tlvs a
+ MsgShutdown a -> shutdown_tlvs a
+ MsgClosingComplete a -> closing_complete_tlvs a
+ MsgClosingSig a -> closing_sig_tlvs a
+ MsgClosingSigned a -> closing_signed_tlvs a
+ MsgUpdateAddHtlc a -> update_add_htlc_tlvs a
+ MsgUpdateFulfillHtlc a -> update_fulfill_htlc_tlvs a
+ MsgUpdateFailHtlc a -> update_fail_htlc_tlvs a
+ MsgUpdateFailMalformedHtlc a -> update_fail_malformed_htlc_tlvs a
+ MsgCommitmentSigned a -> commitment_signed_tlvs a
+ MsgRevokeAndAck a -> revoke_and_ack_tlvs a
+ MsgUpdateFee a -> update_fee_tlvs a
+ MsgChannelReestablish a -> channel_reestablish_tlvs a
+
+-- field values ---------------------------------------------------------------
+
+-- the bitcoin genesis block hash, in internal byte order
+chain_h :: String
+chain_h = "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000"
+
+cid_h, tcid_h, txid_h, secret_h, preimage_h, phash_h :: String
+cid_h = rep 32 "ab"
+tcid_h = rep 32 "cd"
+txid_h = rep 32 "7e"
+secret_h = rep 32 "5e"
+preimage_h = rep 32 "50"
+phash_h = rep 32 "9a"
+
+p1_h, p2_h, p3_h, p4_h, p5_h, p6_h, p7_h :: String
+p1_h = "02" ++ rep 32 "11"
+p2_h = "03" ++ rep 32 "22"
+p3_h = "02" ++ rep 32 "33"
+p4_h = "03" ++ rep 32 "44"
+p5_h = "02" ++ rep 32 "55"
+p6_h = "03" ++ rep 32 "66"
+p7_h = "02" ++ rep 32 "77"
+
+sig1_h, sig2_h, sig3_h :: String
+sig1_h = rep 64 "a1"
+sig2_h = rep 64 "b2"
+sig3_h = rep 64 "c3"
+
+-- version, ephemeral key, hop payloads, hmac
+onion_h :: String
+onion_h = "00" ++ p3_h ++ rep 1300 "44" ++ rep 32 "55"
+
+-- twenty hold times of 100, then 210 truncated hmacs
+attribution_h :: String
+attribution_h = rep 20 "00000064" ++ rep 210 "0badf00d"
+
+p2wpkh_h, p2wsh_h, p2tr_h :: String
+p2wpkh_h = "0014" ++ rep 20 "88"
+p2wsh_h = "0020" ++ rep 32 "99"
+p2tr_h = "5120" ++ rep 32 "ee"
+
+-- the sha256 of the empty string
+onion_hash_h :: String
+onion_hash_h =
+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+
+f_chain :: Maybe BOLT1.ChainHash
+f_chain = BOLT1.chain_hash =<< hx chain_h
+
+f_cid, f_tcid :: Maybe BOLT1.ChannelId
+f_cid = BOLT1.channel_id =<< hx cid_h
+f_tcid = BOLT1.channel_id =<< hx tcid_h
+
+f_point :: String -> Maybe BOLT1.Point
+f_point h = BOLT1.point =<< hx h
+
+f_sig :: String -> Maybe BOLT1.Signature
+f_sig h = BOLT1.signature =<< hx h
+
+f_txid :: Maybe Tx.TxId
+f_txid = Tx.mkTxId =<< hx txid_h
+
+f_secret :: Maybe BOLT1.PerCommitmentSecret
+f_secret = BOLT1.per_commitment_secret =<< hx secret_h
+
+f_script :: String -> Maybe ScriptPubKey
+f_script h = script_pubkey =<< hx h
+
+f_attribution :: Maybe AttributionData
+f_attribution = attribution_data =<< hx attribution_h
+
+anchors :: BOLT9.FeatureVector
+anchors = BOLT9.channel_type_features
+ (BOLT9.ChannelType BOLT9.BasicAnchors False False)
+
+sat :: Word64 -> Maybe BOLT1.Satoshi
+sat = BOLT1.satoshi
+
+msat :: Word64 -> Maybe BOLT1.MilliSatoshi
+msat = BOLT1.milli_satoshi
+
+-- message layouts ------------------------------------------------------------
+
+-- A message's layout: its type and fixed fields (hex, assembled by hand
+-- from the spec) and its known TLV types, each with a valid value and
+-- some malformed ones.
+data Layout = Layout
+ { lay_name :: String
+ , lay_type :: String
+ , lay_fixed :: [String]
+ , lay_known :: [(Word64, String, [String])]
+ }
+
+open_channel_lay :: Layout
+open_channel_lay = Layout "open_channel" "0020" [
+ chain_h
+ , tcid_h
+ , "00000000000f4240" -- funding_satoshis: 1000000
+ , "00000000000003e8" -- push_msat: 1000
+ , "0000000000000222" -- dust_limit_satoshis: 546
+ , "ffffffffffffffff" -- max_htlc_value_in_flight_msat: u64 max
+ , "0000000000002710" -- channel_reserve_satoshis: 10000
+ , "0000000000000001" -- htlc_minimum_msat: 1
+ , "000000fd" -- feerate_per_kw: 253
+ , "0090" -- to_self_delay: 144
+ , "01e3" -- max_accepted_htlcs: 483
+ , p1_h, p2_h, p3_h, p4_h, p5_h, p6_h
+ , "01" -- channel_flags
+ ] [ (0, p2wpkh_h, []), (1, "401000", []) ]
+
+accept_channel_lay :: Layout
+accept_channel_lay = Layout "accept_channel" "0021" [
+ tcid_h
+ , "0000000000000222" -- dust_limit_satoshis: 546
+ , "0000000005f5e100" -- max_htlc_value_in_flight_msat: 100000000
+ , "0000000000002710" -- channel_reserve_satoshis: 10000
+ , "00000000000003e8" -- htlc_minimum_msat: 1000
+ , "00000003" -- minimum_depth: 3
+ , "0090" -- to_self_delay: 144
+ , "01e3" -- max_accepted_htlcs: 483
+ , p1_h, p2_h, p3_h, p4_h, p5_h, p6_h
+ ] [ (0, "", []), (1, "401000", []) ]
+
+funding_created_lay :: Layout
+funding_created_lay = Layout "funding_created" "0022" [
+ tcid_h
+ , txid_h
+ , "0001" -- funding_output_index: 1
+ , sig1_h
+ ] []
+
+funding_signed_lay :: Layout
+funding_signed_lay = Layout "funding_signed" "0023" [ cid_h, sig1_h ] []
+
+channel_ready_lay :: Layout
+channel_ready_lay = Layout "channel_ready" "0024" [ cid_h, p1_h ]
+ [ (1, "083a8400034d0001", ["083a8400034d00", "083a8400034d000100"]) ]
+
+open_channel2_lay :: Layout
+open_channel2_lay = Layout "open_channel2" "0040" [
+ chain_h
+ , tcid_h
+ , "00000fa0" -- funding_feerate_perkw: 4000
+ , "000000fd" -- commitment_feerate_perkw: 253
+ , "0000000000030d40" -- funding_satoshis: 200000
+ , "0000000000000162" -- dust_limit_satoshis: 354
+ , "ffffffffffffffff" -- max_htlc_value_in_flight_msat: u64 max
+ , "0000000000000001" -- htlc_minimum_msat: 1
+ , "0090" -- to_self_delay: 144
+ , "01e3" -- max_accepted_htlcs: 483
+ , "000c3500" -- locktime: 800000
+ , p1_h, p2_h, p3_h, p4_h, p5_h, p6_h, p7_h
+ , "00" -- channel_flags
+ ] [ (0, p2wpkh_h, []), (1, "401000", []), (2, "", ["00", "01"]) ]
+
+accept_channel2_lay :: Layout
+accept_channel2_lay = Layout "accept_channel2" "0041" [
+ tcid_h
+ , "0000000000000000" -- funding_satoshis: 0
+ , "0000000000000162" -- dust_limit_satoshis: 354
+ , "0000000005f5e100" -- max_htlc_value_in_flight_msat: 100000000
+ , "00000000000003e8" -- htlc_minimum_msat: 1000
+ , "00000006" -- minimum_depth: 6
+ , "0090" -- to_self_delay: 144
+ , "001e" -- max_accepted_htlcs: 30
+ , p1_h, p2_h, p3_h, p4_h, p5_h, p6_h, p7_h
+ ] [ (0, p2wsh_h, []), (1, "401000", []), (2, "", ["00"]) ]
+
+tx_add_input_lay :: Layout
+tx_add_input_lay = Layout "tx_add_input" "0042" [
+ cid_h
+ , "0000000000000002" -- serial_id: 2
+ , "0005", "0102030405" -- prevtx
+ , "00000001" -- prevtx_vout: 1
+ , "fffffffd" -- sequence
+ ] []
+
+tx_add_output_lay :: Layout
+tx_add_output_lay = Layout "tx_add_output" "0043" [
+ cid_h
+ , "0000000000000004" -- serial_id: 4
+ , "0000000000030d40" -- sats: 200000
+ , "0022", p2wsh_h -- script
+ ] []
+
+tx_remove_input_lay :: Layout
+tx_remove_input_lay = Layout "tx_remove_input" "0044"
+ [ cid_h, "0000000000000002" ] []
+
+tx_remove_output_lay :: Layout
+tx_remove_output_lay = Layout "tx_remove_output" "0045"
+ [ cid_h, "0000000000000005" ] []
+
+tx_complete_lay :: Layout
+tx_complete_lay = Layout "tx_complete" "0046" [ cid_h ] []
+
+tx_signatures_lay :: Layout
+tx_signatures_lay = Layout "tx_signatures" "0047" [
+ cid_h
+ , txid_h
+ , "0002" -- num_witnesses
+ , "0006", "0202aaaa01bb" -- two stack items
+ , "0003", "0101cc" -- one stack item
+ ] []
+
+tx_init_rbf_lay :: Layout
+tx_init_rbf_lay = Layout "tx_init_rbf" "0048" [
+ cid_h
+ , "000c3500" -- locktime: 800000
+ , "00001046" -- feerate: 4166
+ ] [ (0, "0000000000030d40", ["00030d40", "000000000000030d40"])
+ , (2, "", ["00"]) ]
+
+tx_ack_rbf_lay :: Layout
+tx_ack_rbf_lay = Layout "tx_ack_rbf" "0049" [ cid_h ]
+ [ (0, "fffffffffffe7960", ["ff"]), (2, "", ["2a2a"]) ]
+
+tx_abort_lay :: Layout
+tx_abort_lay = Layout "tx_abort" "004a" [
+ cid_h
+ , "000c", "6661696c656420636865636b" -- "failed check"
+ ] []
+
+stfu_lay :: Layout
+stfu_lay = Layout "stfu" "0002" [ cid_h, "01" ] []
+
+shutdown_lay :: Layout
+shutdown_lay = Layout "shutdown" "0026" [ cid_h, "0016", p2wpkh_h ] []
+
+closing_complete_lay :: Layout
+closing_complete_lay = Layout "closing_complete" "0028" [
+ cid_h
+ , "0016", p2wpkh_h -- closer_scriptpubkey
+ , "0022", p2tr_h -- closee_scriptpubkey
+ , "00000000000001f4" -- fee_satoshis: 500
+ , "00000000" -- locktime: 0
+ ] [ (t, sig1_h, [rep 63 "a1", rep 65 "a1"]) | t <- [1, 2, 3] ]
+
+closing_sig_lay :: Layout
+closing_sig_lay = Layout "closing_sig" "0029" [
+ cid_h
+ , "0016", p2wpkh_h -- closer_scriptpubkey
+ , "0022", p2tr_h -- closee_scriptpubkey
+ , "00000000000001f4" -- fee_satoshis: 500
+ , "000c3500" -- locktime: 800000
+ ] [ (t, sig3_h, [rep 63 "c3", ""]) | t <- [1, 2, 3] ]
+
+closing_signed_lay :: Layout
+closing_signed_lay = Layout "closing_signed" "0027" [
+ cid_h
+ , "00000000000001f4" -- fee_satoshis: 500
+ , sig1_h
+ ] [ (1, "000000000000006400000000000003e8", [
+ "000000000000006400000000000003"
+ , "000000000000006400000000000003e800"
+ , "0000000000000064ffffffffffffffff" -- max fee above 21M BTC
+ ]) ]
+
+update_add_htlc_lay :: Layout
+update_add_htlc_lay = Layout "update_add_htlc" "0080" [
+ cid_h
+ , "0000000000000000" -- id: 0
+ , "00000000000186a0" -- amount_msat: 100000
+ , phash_h
+ , "000c3550" -- cltv_expiry: 800080
+ , onion_h
+ ] [ (0, p2_h, [ rep 32 "22", p2_h ++ "00", "04" ++ rep 32 "22" ]) ]
+
+update_fulfill_htlc_lay :: Layout
+update_fulfill_htlc_lay = Layout "update_fulfill_htlc" "0082" [
+ cid_h
+ , "0000000000000007" -- id: 7
+ , preimage_h
+ ] [ (1, attribution_h, [ drop 2 attribution_h, attribution_h ++ "00" ])
+ , (3, "deadbeef", []) ]
+
+update_fail_htlc_lay :: Layout
+update_fail_htlc_lay = Layout "update_fail_htlc" "0083" [
+ cid_h
+ , "0000000000000007" -- id: 7
+ , "0004", "01020304" -- reason
+ ] [ (1, attribution_h, [ "" ]) ]
+
+update_fail_malformed_htlc_lay :: Layout
+update_fail_malformed_htlc_lay =
+ Layout "update_fail_malformed_htlc" "0087" [
+ cid_h
+ , "0000000000000003" -- id: 3
+ , onion_hash_h
+ , "c005" -- failure_code: BADONION | PERM | 5
+ ] []
+
+commitment_signed_lay :: Layout
+commitment_signed_lay = Layout "commitment_signed" "0084" [
+ cid_h
+ , sig1_h
+ , "0002" -- num_htlcs
+ , sig2_h, sig3_h
+ ] [ (1, txid_h, [ rep 31 "7e", rep 33 "7e" ]) ]
+
+revoke_and_ack_lay :: Layout
+revoke_and_ack_lay = Layout "revoke_and_ack" "0085"
+ [ cid_h, secret_h, p1_h ] []
+
+update_fee_lay :: Layout
+update_fee_lay = Layout "update_fee" "0086"
+ [ cid_h, "000009c4" ] [] -- feerate_per_kw: 2500
+
+channel_reestablish_lay :: Layout
+channel_reestablish_lay = Layout "channel_reestablish" "0088" [
+ cid_h
+ , "0000000000000005" -- next_commitment_number: 5
+ , "0000000000000004" -- next_revocation_number: 4
+ , secret_h
+ , p1_h
+ ] [ (1, txid_h ++ "01", [ txid_h, txid_h ++ "0100" ]) ]
+
+layouts :: [Layout]
+layouts = [
+ open_channel_lay, accept_channel_lay, funding_created_lay
+ , funding_signed_lay, channel_ready_lay, open_channel2_lay
+ , accept_channel2_lay, tx_add_input_lay, tx_add_output_lay
+ , tx_remove_input_lay, tx_remove_output_lay, tx_complete_lay
+ , tx_signatures_lay, tx_init_rbf_lay, tx_ack_rbf_lay, tx_abort_lay
+ , stfu_lay, shutdown_lay, closing_complete_lay, closing_sig_lay
+ , closing_signed_lay, update_add_htlc_lay, update_fulfill_htlc_lay
+ , update_fail_htlc_lay, update_fail_malformed_htlc_lay
+ , commitment_signed_lay, revoke_and_ack_lay, update_fee_lay
+ , channel_reestablish_lay
]
--- V2 Channel Establishment (Interactive-tx) Tests ----------------------------
-
-v2_establishment_tests :: TestTree
-v2_establishment_tests = testGroup "V2 Channel Establishment" [
- testGroup "OpenChannel2" [
- testCase "encode/decode roundtrip" $ do
- let msg = OpenChannel2
- { openChannel2ChainHash = testChainHash
- , openChannel2TempChannelId = testChannelId
- , openChannel2FundingFeeratePerkw = 2500
- , openChannel2CommitFeeratePerkw = 2000
- , openChannel2FundingSatoshi = Satoshi 1000000
- , openChannel2DustLimitSatoshi = Satoshi 546
- , openChannel2MaxHtlcValueInFlight = MilliSatoshi 100000000
- , openChannel2HtlcMinimumMsat = MilliSatoshi 1000
- , openChannel2ToSelfDelay = 144
- , openChannel2MaxAcceptedHtlcs = 483
- , openChannel2Locktime = 0
- , openChannel2FundingPubkey = testPoint
- , openChannel2RevocationBasepoint = testPoint
- , openChannel2PaymentBasepoint = testPoint
- , openChannel2DelayedPaymentBase = testPoint
- , openChannel2HtlcBasepoint = testPoint
- , openChannel2FirstPerCommitPoint = testPoint
- , openChannel2SecondPerCommitPoint = testPoint2
- , openChannel2ChannelFlags = 0x00
- , openChannel2Tlvs = emptyTlvs
- }
- encoded = encodeOpenChannel2 msg
- case decodeOpenChannel2 encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "AcceptChannel2" [
- testCase "encode/decode roundtrip" $ do
- let msg = AcceptChannel2
- { acceptChannel2TempChannelId = testChannelId
- , acceptChannel2FundingSatoshi = Satoshi 500000
- , acceptChannel2DustLimitSatoshi = Satoshi 546
- , acceptChannel2MaxHtlcValueInFlight = MilliSatoshi 100000000
- , acceptChannel2HtlcMinimumMsat = MilliSatoshi 1000
- , acceptChannel2MinimumDepth = 3
- , acceptChannel2ToSelfDelay = 144
- , acceptChannel2MaxAcceptedHtlcs = 483
- , acceptChannel2FundingPubkey = testPoint
- , acceptChannel2RevocationBasepoint = testPoint
- , acceptChannel2PaymentBasepoint = testPoint
- , acceptChannel2DelayedPaymentBase = testPoint
- , acceptChannel2HtlcBasepoint = testPoint
- , acceptChannel2FirstPerCommitPoint = testPoint
- , acceptChannel2SecondPerCommitPoint = testPoint2
- , acceptChannel2Tlvs = emptyTlvs
- }
- encoded = encodeAcceptChannel2 msg
- case decodeAcceptChannel2 encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "TxAddInput" [
- testCase "encode/decode roundtrip" $ do
- let msg = TxAddInput
- { txAddInputChannelId = testChannelId
- , txAddInputSerialId = serialId 12345
- , txAddInputPrevTx = BS.pack [0x01, 0x02, 0x03, 0x04]
- , txAddInputPrevVout = 0
- , txAddInputSequence = 0xfffffffe
- }
- case encodeTxAddInput msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeTxAddInput encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- , testCase "roundtrip with empty prevTx" $ do
- let msg = TxAddInput
- { txAddInputChannelId = testChannelId
- , txAddInputSerialId = serialId 0
- , txAddInputPrevTx = BS.empty
- , txAddInputPrevVout = 0
- , txAddInputSequence = 0
- }
- case encodeTxAddInput msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeTxAddInput encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "TxAddOutput" [
- testCase "encode/decode roundtrip" $ do
- let msg = TxAddOutput
- { txAddOutputChannelId = testChannelId
- , txAddOutputSerialId = serialId 54321
- , txAddOutputSats = Satoshi 100000
- , txAddOutputScript = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xaa)
- }
- case encodeTxAddOutput msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeTxAddOutput encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "TxRemoveInput" [
- testCase "encode/decode roundtrip" $ do
- let msg = TxRemoveInput
- { txRemoveInputChannelId = testChannelId
- , txRemoveInputSerialId = serialId 12345
- }
- encoded = encodeTxRemoveInput msg
- case decodeTxRemoveInput encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "TxRemoveOutput" [
- testCase "encode/decode roundtrip" $ do
- let msg = TxRemoveOutput
- { txRemoveOutputChannelId = testChannelId
- , txRemoveOutputSerialId = serialId 54321
- }
- encoded = encodeTxRemoveOutput msg
- case decodeTxRemoveOutput encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "TxComplete" [
- testCase "encode/decode roundtrip" $ do
- let msg = TxComplete { txCompleteChannelId = testChannelId }
- encoded = encodeTxComplete msg
- case decodeTxComplete encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "TxSignatures" [
- testCase "encode/decode with no witnesses" $ do
- let msg = TxSignatures
- { txSignaturesChannelId = testChannelId
- , txSignaturesTxid = testTxId
- , txSignaturesWitnesses = []
- }
- case encodeTxSignatures msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeTxSignatures encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- , testCase "encode/decode with multiple witnesses" $ do
- let w1 = Witness (BS.pack [0x30, 0x44] <> BS.replicate 68 0xaa)
- w2 = Witness (BS.pack [0x02] <> BS.replicate 32 0xbb)
- msg = TxSignatures
- { txSignaturesChannelId = testChannelId
- , txSignaturesTxid = testTxId
- , txSignaturesWitnesses = [w1, w2]
- }
- case encodeTxSignatures msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeTxSignatures encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "TxInitRbf" [
- testCase "encode/decode roundtrip" $ do
- let msg = TxInitRbf
- { txInitRbfChannelId = testChannelId
- , txInitRbfLocktime = 800000
- , txInitRbfFeerate = 3000
- , txInitRbfTlvs = emptyTlvs
- }
- encoded = encodeTxInitRbf msg
- case decodeTxInitRbf encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "TxAckRbf" [
- testCase "encode/decode roundtrip" $ do
- let msg = TxAckRbf
- { txAckRbfChannelId = testChannelId
- , txAckRbfTlvs = emptyTlvs
- }
- encoded = encodeTxAckRbf msg
- case decodeTxAckRbf encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "TxAbort" [
- testCase "encode/decode roundtrip" $ do
- let msg = TxAbort
- { txAbortChannelId = testChannelId
- , txAbortData = "transaction abort reason"
- }
- case encodeTxAbort msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeTxAbort encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- , testCase "roundtrip with empty data" $ do
- let msg = TxAbort
- { txAbortChannelId = testChannelId
- , txAbortData = BS.empty
- }
- case encodeTxAbort msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeTxAbort encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
+-- a TLV record, in hex (for types below 253 and values below 65536
+-- bytes)
+tlv_h :: Word64 -> String -> String
+tlv_h t v = hex2 t ++ len ++ v
+ where
+ n = fromIntegral (length v `div` 2) :: Word64
+ len | n < 253 = hex2 n
+ | otherwise = "fd" ++ hex2 (n `div` 256) ++ hex2 (n `mod` 256)
+
+-- a byte, in hex
+hex2 :: Word64 -> String
+hex2 w = [digit (w `div` 16), digit (w `mod` 16)]
+ where
+ digit d = maybe '?' id (lookup d (zip [0 ..] "0123456789abcdef"))
+
+-- known answers --------------------------------------------------------------
+
+-- Check a message against wire bytes assembled by hand: its payload
+-- decodes to the expected value and re-encodes to the same bytes, and
+-- likewise for the full message.
+kat
+ :: (Eq a, Show a)
+ => String
+ -> Layout
+ -> [String] -- TLV records, hex
+ -> (BS.ByteString -> Either DecodeError a)
+ -> (a -> Either EncodeError BS.ByteString)
+ -> (a -> Message)
+ -> Maybe a
+ -> TestTree
+kat name lay recs dec enc wrap expected = testCase name $ do
+ payload <- unhex (concat (lay_fixed lay ++ recs))
+ wire <- unhex (lay_type lay ++ concat (lay_fixed lay ++ recs))
+ m <- just name expected
+ dec payload @?= Right m
+ enc m @?= Right payload
+ decode_message wire @?= Right (wrap m)
+ encode_message (wrap m) @?= Right wire
+
+known_answers :: TestTree
+known_answers = testGroup "known answers" [
+ kat "open_channel" open_channel_lay
+ [ tlv_h 0 p2wpkh_h, tlv_h 1 "401000" ]
+ decode_open_channel encode_open_channel MsgOpenChannel $ do
+ ch <- f_chain
+ tcid <- f_tcid
+ fund <- sat 1000000
+ push <- msat 1000
+ dust <- sat 546
+ res <- sat 10000
+ hmin <- msat 1
+ [a, b, c, d, e, f] <- traverse f_point
+ [p1_h, p2_h, p3_h, p4_h, p5_h, p6_h]
+ spk <- f_script p2wpkh_h
+ pure (OpenChannel ch tcid fund push dust maxBound res hmin 253 144
+ 483 a b c d e f 1 (Just spk) (Just anchors) empty)
+
+ , kat "open_channel (no TLVs)" open_channel_lay []
+ decode_open_channel encode_open_channel MsgOpenChannel $ do
+ ch <- f_chain
+ tcid <- f_tcid
+ fund <- sat 1000000
+ push <- msat 1000
+ dust <- sat 546
+ res <- sat 10000
+ hmin <- msat 1
+ [a, b, c, d, e, f] <- traverse f_point
+ [p1_h, p2_h, p3_h, p4_h, p5_h, p6_h]
+ pure (OpenChannel ch tcid fund push dust maxBound res hmin 253 144
+ 483 a b c d e f 1 Nothing Nothing empty)
+
+ , kat "accept_channel" accept_channel_lay
+ [ tlv_h 0 "", tlv_h 1 "401000" ]
+ decode_accept_channel encode_accept_channel MsgAcceptChannel $ do
+ tcid <- f_tcid
+ dust <- sat 546
+ res <- sat 10000
+ hmin <- msat 1000
+ [a, b, c, d, e, f] <- traverse f_point
+ [p1_h, p2_h, p3_h, p4_h, p5_h, p6_h]
+ spk <- script_pubkey ""
+ pure (AcceptChannel tcid dust 100000000 res hmin 3 144 483
+ a b c d e f (Just spk) (Just anchors) empty)
+
+ , kat "funding_created" funding_created_lay []
+ decode_funding_created (Right . encode_funding_created)
+ MsgFundingCreated $ do
+ tcid <- f_tcid
+ t <- f_txid
+ s <- f_sig sig1_h
+ pure (FundingCreated tcid t 1 s empty)
+
+ , kat "funding_signed" funding_signed_lay []
+ decode_funding_signed (Right . encode_funding_signed)
+ MsgFundingSigned $ do
+ cid <- f_cid
+ s <- f_sig sig1_h
+ pure (FundingSigned cid s empty)
+
+ , kat "channel_ready" channel_ready_lay
+ [ tlv_h 1 "083a8400034d0001" ]
+ decode_channel_ready encode_channel_ready MsgChannelReady $ do
+ cid <- f_cid
+ pcp <- f_point p1_h
+ alias <- BOLT1.short_channel_id 539268 845 1
+ pure (ChannelReady cid pcp (Just alias) empty)
+
+ , kat "open_channel2" open_channel2_lay
+ [ tlv_h 0 p2wpkh_h, tlv_h 1 "401000", tlv_h 2 "" ]
+ decode_open_channel2 encode_open_channel2 MsgOpenChannel2 $ do
+ ch <- f_chain
+ tcid <- f_tcid
+ fund <- sat 200000
+ dust <- sat 354
+ hmin <- msat 1
+ [a, b, c, d, e, f, g] <- traverse f_point
+ [p1_h, p2_h, p3_h, p4_h, p5_h, p6_h, p7_h]
+ spk <- f_script p2wpkh_h
+ pure (OpenChannel2 ch tcid 4000 253 fund dust maxBound hmin 144 483
+ 800000 a b c d e f g 0 (Just spk) (Just anchors) True
+ empty)
+
+ , kat "accept_channel2" accept_channel2_lay
+ [ tlv_h 0 p2wsh_h, tlv_h 1 "401000", tlv_h 2 "" ]
+ decode_accept_channel2 encode_accept_channel2 MsgAcceptChannel2 $ do
+ tcid <- f_tcid
+ fund <- sat 0
+ dust <- sat 354
+ hmin <- msat 1000
+ [a, b, c, d, e, f, g] <- traverse f_point
+ [p1_h, p2_h, p3_h, p4_h, p5_h, p6_h, p7_h]
+ spk <- f_script p2wsh_h
+ pure (AcceptChannel2 tcid fund dust 100000000 hmin 6 144 30
+ a b c d e f g (Just spk) (Just anchors) True empty)
+
+ , kat "tx_add_input" tx_add_input_lay []
+ decode_tx_add_input encode_tx_add_input MsgTxAddInput $ do
+ cid <- f_cid
+ pure (TxAddInput cid (SerialId 2) "\x01\x02\x03\x04\x05" 1
+ 0xfffffffd empty)
+
+ , kat "tx_add_output" tx_add_output_lay []
+ decode_tx_add_output (Right . encode_tx_add_output)
+ MsgTxAddOutput $ do
+ cid <- f_cid
+ amt <- sat 200000
+ spk <- f_script p2wsh_h
+ pure (TxAddOutput cid (SerialId 4) amt spk empty)
+
+ , kat "tx_remove_input" tx_remove_input_lay []
+ decode_tx_remove_input (Right . encode_tx_remove_input)
+ MsgTxRemoveInput $ do
+ cid <- f_cid
+ pure (TxRemoveInput cid (SerialId 2) empty)
+
+ , kat "tx_remove_output" tx_remove_output_lay []
+ decode_tx_remove_output (Right . encode_tx_remove_output)
+ MsgTxRemoveOutput $ do
+ cid <- f_cid
+ pure (TxRemoveOutput cid (SerialId 5) empty)
+
+ , kat "tx_complete" tx_complete_lay []
+ decode_tx_complete (Right . encode_tx_complete) MsgTxComplete $ do
+ cid <- f_cid
+ pure (TxComplete cid empty)
+
+ , kat "tx_signatures" tx_signatures_lay []
+ decode_tx_signatures encode_tx_signatures MsgTxSignatures $ do
+ cid <- f_cid
+ t <- f_txid
+ w1 <- witness "\x02\x02\xaa\xaa\x01\xbb"
+ w2 <- witness "\x01\x01\xcc"
+ pure (TxSignatures cid t [w1, w2] empty)
+
+ , kat "tx_init_rbf" tx_init_rbf_lay
+ [ tlv_h 0 "0000000000030d40", tlv_h 2 "" ]
+ decode_tx_init_rbf encode_tx_init_rbf MsgTxInitRbf $ do
+ cid <- f_cid
+ pure (TxInitRbf cid 800000 4166 (Just 200000) True empty)
+
+ , kat "tx_ack_rbf" tx_ack_rbf_lay
+ [ tlv_h 0 "fffffffffffe7960", tlv_h 2 "" ]
+ decode_tx_ack_rbf encode_tx_ack_rbf MsgTxAckRbf $ do
+ cid <- f_cid
+ pure (TxAckRbf cid (Just (-100000)) True empty)
+
+ , kat "tx_abort" tx_abort_lay []
+ decode_tx_abort encode_tx_abort MsgTxAbort $ do
+ cid <- f_cid
+ pure (TxAbort cid "failed check" empty)
+
+ , kat "stfu" stfu_lay []
+ decode_stfu (Right . encode_stfu) MsgStfu $ do
+ cid <- f_cid
+ pure (Stfu cid True empty)
+
+ , kat "shutdown" shutdown_lay []
+ decode_shutdown (Right . encode_shutdown) MsgShutdown $ do
+ cid <- f_cid
+ spk <- f_script p2wpkh_h
+ pure (Shutdown cid spk empty)
+
+ , kat "closing_complete" closing_complete_lay
+ [ tlv_h 1 sig1_h, tlv_h 3 sig2_h ]
+ decode_closing_complete encode_closing_complete
+ MsgClosingComplete $ do
+ cid <- f_cid
+ closer <- f_script p2wpkh_h
+ closee <- f_script p2tr_h
+ fee <- sat 500
+ s1 <- f_sig sig1_h
+ s2 <- f_sig sig2_h
+ pure (ClosingComplete cid closer closee fee 0 (Just s1) Nothing
+ (Just s2) empty)
+
+ , kat "closing_complete (closee output only)" closing_complete_lay
+ [ tlv_h 2 sig1_h ]
+ decode_closing_complete encode_closing_complete
+ MsgClosingComplete $ do
+ cid <- f_cid
+ closer <- f_script p2wpkh_h
+ closee <- f_script p2tr_h
+ fee <- sat 500
+ s1 <- f_sig sig1_h
+ pure (ClosingComplete cid closer closee fee 0 Nothing (Just s1)
+ Nothing empty)
+
+ , kat "closing_sig" closing_sig_lay [ tlv_h 3 sig3_h ]
+ decode_closing_sig encode_closing_sig MsgClosingSig $ do
+ cid <- f_cid
+ closer <- f_script p2wpkh_h
+ closee <- f_script p2tr_h
+ fee <- sat 500
+ s3 <- f_sig sig3_h
+ pure (ClosingSig cid closer closee fee 800000 Nothing Nothing
+ (Just s3) empty)
+
+ , kat "closing_signed" closing_signed_lay
+ [ tlv_h 1 "000000000000006400000000000003e8" ]
+ decode_closing_signed encode_closing_signed MsgClosingSigned $ do
+ cid <- f_cid
+ fee <- sat 500
+ s <- f_sig sig1_h
+ lo <- sat 100
+ hi <- sat 1000
+ pure (ClosingSigned cid fee s (Just (FeeRange lo hi)) empty)
+
+ , kat "update_add_htlc" update_add_htlc_lay [ tlv_h 0 p2_h ]
+ decode_update_add_htlc encode_update_add_htlc MsgUpdateAddHtlc $ do
+ cid <- f_cid
+ amt <- msat 100000
+ ph <- BOLT1.payment_hash =<< hx phash_h
+ onion <- onion_routing_packet =<< hx onion_h
+ pk <- f_point p2_h
+ pure (UpdateAddHtlc cid (HtlcId 0) amt ph 800080 onion (Just pk)
+ empty)
+
+ , kat "update_fulfill_htlc" update_fulfill_htlc_lay
+ [ tlv_h 1 attribution_h, tlv_h 3 "deadbeef" ]
+ decode_update_fulfill_htlc encode_update_fulfill_htlc
+ MsgUpdateFulfillHtlc $ do
+ cid <- f_cid
+ pre <- BOLT1.payment_preimage =<< hx preimage_h
+ att <- f_attribution
+ pure (UpdateFulfillHtlc cid (HtlcId 7) pre (Just att)
+ (Just "\xde\xad\xbe\xef") empty)
+
+ , kat "update_fail_htlc" update_fail_htlc_lay [ tlv_h 1 attribution_h ]
+ decode_update_fail_htlc encode_update_fail_htlc MsgUpdateFailHtlc $ do
+ cid <- f_cid
+ att <- f_attribution
+ pure (UpdateFailHtlc cid (HtlcId 7) "\x01\x02\x03\x04" (Just att)
+ empty)
+
+ , kat "update_fail_malformed_htlc" update_fail_malformed_htlc_lay []
+ decode_update_fail_malformed_htlc
+ (Right . encode_update_fail_malformed_htlc)
+ MsgUpdateFailMalformedHtlc $ do
+ cid <- f_cid
+ oh <- onion_hash =<< hx onion_hash_h
+ pure (UpdateFailMalformedHtlc cid (HtlcId 3) oh 0xc005 empty)
+
+ , kat "commitment_signed" commitment_signed_lay [ tlv_h 1 txid_h ]
+ decode_commitment_signed encode_commitment_signed
+ MsgCommitmentSigned $ do
+ cid <- f_cid
+ sigs <- traverse f_sig [sig1_h, sig2_h, sig3_h]
+ t <- f_txid
+ case sigs of
+ (s : hs) -> pure (CommitmentSigned cid s hs (Just t) empty)
+ [] -> Nothing
+
+ , kat "revoke_and_ack" revoke_and_ack_lay []
+ decode_revoke_and_ack (Right . encode_revoke_and_ack)
+ MsgRevokeAndAck $ do
+ cid <- f_cid
+ sec <- f_secret
+ pcp <- f_point p1_h
+ pure (RevokeAndAck cid sec pcp empty)
+
+ , kat "update_fee" update_fee_lay []
+ decode_update_fee (Right . encode_update_fee) MsgUpdateFee $ do
+ cid <- f_cid
+ pure (UpdateFee cid 2500 empty)
+
+ , kat "channel_reestablish" channel_reestablish_lay
+ [ tlv_h 1 (txid_h ++ "01") ]
+ decode_channel_reestablish encode_channel_reestablish
+ MsgChannelReestablish $ do
+ cid <- f_cid
+ sec <- f_secret
+ pcp <- f_point p1_h
+ t <- f_txid
+ pure (ChannelReestablish cid 5 4 sec pcp (Just (NextFunding t 1))
+ empty)
]
--- Channel Close Tests ---------------------------------------------------------
-
-close_tests :: TestTree
-close_tests = testGroup "Channel Close" [
- testGroup "Stfu" [
- testCase "encode/decode IsInitiator" $ do
- let msg = Stfu
- { stfuChannelId = testChannelId
- , stfuInitiator = IsInitiator
- }
- encoded = encodeStfu msg
- case decodeStfu encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- , testCase "encode/decode NotInitiator" $ do
- let msg = Stfu
- { stfuChannelId = testChannelId
- , stfuInitiator = NotInitiator
- }
- encoded = encodeStfu msg
- case decodeStfu encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "Shutdown" [
- testCase "encode/decode with P2WPKH script" $ do
- let script = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xaa)
- msg = Shutdown
- { shutdownChannelId = testChannelId
- , shutdownScriptPubkey = script
- }
- case encodeShutdown msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeShutdown encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- , testCase "encode/decode with P2WSH script" $ do
- let script = scriptPubKey (BS.pack [0x00, 0x20] <>
- BS.replicate 32 0xbb)
- msg = Shutdown
- { shutdownChannelId = testChannelId
- , shutdownScriptPubkey = script
- }
- case encodeShutdown msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeShutdown encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "ClosingSigned" [
- testCase "encode/decode roundtrip" $ do
- let msg = ClosingSigned
- { closingSignedChannelId = testChannelId
- , closingSignedFeeSatoshi = Satoshi 1000
- , closingSignedSignature = testSignature
- , closingSignedTlvs = emptyTlvs
- }
- encoded = encodeClosingSigned msg
- case decodeClosingSigned encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "ClosingComplete" [
- testCase "encode/decode roundtrip" $ do
- let closerScript = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xcc)
- closeeScript = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xdd)
- msg = ClosingComplete
- { closingCompleteChannelId = testChannelId
- , closingCompleteCloserScript = closerScript
- , closingCompleteCloseeScript = closeeScript
- , closingCompleteFeeSatoshi = Satoshi 500
- , closingCompleteLocktime = 0
- , closingCompleteTlvs = emptyTlvs
- }
- case encodeClosingComplete msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeClosingComplete encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "ClosingSig" [
- testCase "encode/decode roundtrip" $ do
- let closerScript = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xee)
- closeeScript = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xff)
- msg = ClosingSig
- { closingSigChannelId = testChannelId
- , closingSigCloserScript = closerScript
- , closingSigCloseeScript = closeeScript
- , closingSigFeeSatoshi = Satoshi 500
- , closingSigLocktime = 100
- , closingSigTlvs = emptyTlvs
- }
- case encodeClosingSig msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeClosingSig encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
+-- TLV handling ---------------------------------------------------------------
+
+-- decode a message (type, fixed fields and TLV records in hex), check
+-- its unknown records, and check that it re-encodes exactly
+reencodes :: Layout -> [String] -> [TlvRecord] -> Assertion
+reencodes lay recs extra = do
+ wire <- unhex (lay_type lay ++ concat (lay_fixed lay ++ recs))
+ case decode_message wire of
+ Left e -> assertFailure ("decode failed: " ++ show e)
+ Right m -> do
+ BOLT1.un_tlv_stream (message_tlvs m) @?= extra
+ encode_message m @?= Right wire
+
+rejects :: Layout -> [String] -> DecodeError -> Assertion
+rejects lay recs err = do
+ wire <- unhex (lay_type lay ++ concat (lay_fixed lay ++ recs))
+ decode_message wire @?= Left err
+
+-- the smallest even type a message doesn't know
+first_unknown_even :: Layout -> Word64
+first_unknown_even lay = case filter (`notElem` known) [0, 2 ..] of
+ (t:_) -> t
+ [] -> 0
+ where
+ known = [ t | (t, _, _) <- lay_known lay ]
+
+tlv_cases :: Layout -> TestTree
+tlv_cases lay = testGroup (lay_name lay) $ [
+ testCase "unknown odd records are preserved" $
+ reencodes lay (valid ++ [tlv_h 43 "2a", tlv_h 201 ""])
+ [TlvRecord 43 "\x2a", TlvRecord 201 ""]
+ , testCase "unknown even records are rejected" $ do
+ let t = first_unknown_even lay
+ rejects lay [tlv_h t ""] (DecodeTlvError (TlvUnknownEvenType t))
+ rejects lay (valid ++ [tlv_h 42 "00"])
+ (DecodeTlvError (TlvUnknownEvenType 42))
+ , testCase "unordered records are rejected" $
+ rejects lay [tlv_h 45 "", tlv_h 43 ""]
+ (DecodeTlvError TlvNotStrictlyIncreasing)
+ , testCase "a huge record length is rejected" $ do
+ -- the length 2^64 - 1 must not wrap
+ rejects lay ["2bffffffffffffffffff2b"] (DecodeTlvError TlvTruncated)
+ ] ++ [
+ testCase ("type " ++ show t ++ " is typed") $ reencodes lay [tlv_h t v] []
+ | (t, v, _) <- lay_known lay
+ ] ++ [
+ testCase ("malformed type " ++ show t ++ " is rejected") $
+ mapM_ (\b -> rejects lay [tlv_h t b] (DecodeInvalidTlvValue t)) bad
+ | (t, _, bad) <- lay_known lay, not (null bad)
+ ]
+ where
+ valid = [ tlv_h t v | (t, v, _) <- lay_known lay ]
+
+tlv_handling :: TestTree
+tlv_handling = testGroup "TLV handling" $ map tlv_cases layouts ++ [
+ testGroup "splicing records" [
+ testCase "tx_add_input shared_input_txid is rejected" $
+ rejects tx_add_input_lay [tlv_h 0 txid_h]
+ (DecodeTlvError (TlvUnknownEvenType 0))
+ , testCase "tx_signatures shared_input_signature is rejected" $
+ rejects tx_signatures_lay [tlv_h 0 sig1_h]
+ (DecodeTlvError (TlvUnknownEvenType 0))
+ , testCase "channel_reestablish my_current_funding_locked is kept" $ do
+ v <- unhex (txid_h ++ "01")
+ reencodes channel_reestablish_lay
+ [tlv_h 1 (txid_h ++ "00"), tlv_h 5 (txid_h ++ "01")]
+ [TlvRecord 5 v]
]
+ , testCase "a non-minimal type is rejected" $
+ rejects update_fee_lay ["fd002b00"]
+ (DecodeTlvError TlvNonMinimalBigSize)
+ , testCase "a repeated type is rejected" $
+ rejects update_fee_lay [tlv_h 43 "", tlv_h 43 ""]
+ (DecodeTlvError TlvNotStrictlyIncreasing)
+ , testCase "trailing bytes that aren't a TLV record are rejected" $ do
+ rejects update_fee_lay ["fd"] (DecodeTlvError TlvTruncated)
+ rejects update_fee_lay ["2b"] (DecodeTlvError TlvTruncated)
+ rejects update_fee_lay ["2b02aa"] (DecodeTlvError TlvTruncated)
+ , testCase "an empty upfront_shutdown_script is kept" $ do
+ wire <- unhex (concat (lay_fixed open_channel_lay ++ [tlv_h 0 ""]))
+ fmap open_channel_upfront_shutdown_script (decode_open_channel wire)
+ @?= Right (script_pubkey "")
+ , testCase "an oversized upfront_shutdown_script is rejected" $ do
+ fixed <- unhex (concat (lay_fixed open_channel_lay))
+ let big = BS.replicate 65536 0x00
+ rec = "\x00\xfe\x00\x01\x00\x00" <> big
+ decode_open_channel (fixed <> rec) @?= Left (DecodeInvalidTlvValue 0)
+ , testCase "channel_type bytes are kept as received" $ do
+ wire <- unhex (concat (lay_fixed open_channel_lay
+ ++ [tlv_h 1 "00401000"]))
+ case decode_open_channel wire of
+ Left e -> assertFailure (show e)
+ Right m -> do
+ fmap BOLT9.render (open_channel_channel_type m)
+ @?= Just "\x00\x40\x10\x00"
+ (BOLT9.channel_type =<< open_channel_channel_type m)
+ @?= Just (BOLT9.ChannelType BOLT9.BasicAnchors False False)
+ encode_open_channel m @?= Right wire
]
--- Normal Operation Tests ------------------------------------------------------
-
-normal_operation_tests :: TestTree
-normal_operation_tests = testGroup "Normal Operation" [
- testGroup "UpdateAddHtlc" [
- testCase "encode/decode roundtrip" $ do
- let msg = UpdateAddHtlc
- { updateAddHtlcChannelId = testChannelId
- , updateAddHtlcId = htlcId 0
- , updateAddHtlcAmountMsat = MilliSatoshi 10000000
- , updateAddHtlcPaymentHash = testPaymentHash
- , updateAddHtlcCltvExpiry = 800144
- , updateAddHtlcOnionPacket = testOnionPacket
- , updateAddHtlcTlvs = emptyTlvs
- }
- encoded = encodeUpdateAddHtlc msg
- case decodeUpdateAddHtlc encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "UpdateFulfillHtlc" [
- testCase "encode/decode roundtrip" $ do
- let msg = UpdateFulfillHtlc
- { updateFulfillHtlcChannelId = testChannelId
- , updateFulfillHtlcId = htlcId 42
- , updateFulfillHtlcPaymentPreimage = testPaymentPreimage
- , updateFulfillHtlcTlvs = emptyTlvs
- }
- encoded = encodeUpdateFulfillHtlc msg
- case decodeUpdateFulfillHtlc encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "UpdateFailHtlc" [
- testCase "encode/decode roundtrip" $ do
- let msg = UpdateFailHtlc
- { updateFailHtlcChannelId = testChannelId
- , updateFailHtlcId = htlcId 42
- , updateFailHtlcReason = BS.replicate 32 0xaa
- , updateFailHtlcTlvs = emptyTlvs
- }
- case encodeUpdateFailHtlc msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeUpdateFailHtlc encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- , testCase "roundtrip with empty reason" $ do
- let msg = UpdateFailHtlc
- { updateFailHtlcChannelId = testChannelId
- , updateFailHtlcId = htlcId 0
- , updateFailHtlcReason = BS.empty
- , updateFailHtlcTlvs = emptyTlvs
- }
- case encodeUpdateFailHtlc msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeUpdateFailHtlc encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "UpdateFailMalformedHtlc" [
- testCase "encode/decode roundtrip" $ do
- let msg = UpdateFailMalformedHtlc
- { updateFailMalformedHtlcChannelId = testChannelId
- , updateFailMalformedHtlcId = htlcId 42
- , updateFailMalformedHtlcSha256Onion = testPaymentHash
- , updateFailMalformedHtlcFailureCode = 0x8002
- }
- encoded = encodeUpdateFailMalformedHtlc msg
- case decodeUpdateFailMalformedHtlc encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "CommitmentSigned" [
- testCase "encode/decode with no HTLC signatures" $ do
- let msg = CommitmentSigned
- { commitmentSignedChannelId = testChannelId
- , commitmentSignedSignature = testSignature
- , commitmentSignedHtlcSignatures = []
- }
- case encodeCommitmentSigned msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeCommitmentSigned encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- , testCase "encode/decode with HTLC signatures" $ do
- let sig2 = fromJust $ signature (BS.replicate 64 0xdd)
- sig3 = fromJust $ signature (BS.replicate 64 0xee)
- msg = CommitmentSigned
- { commitmentSignedChannelId = testChannelId
- , commitmentSignedSignature = testSignature
- , commitmentSignedHtlcSignatures = [sig2, sig3]
- }
- case encodeCommitmentSigned msg of
- Left e -> assertFailure $ "encode failed: " ++ show e
- Right encoded -> case decodeCommitmentSigned encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "RevokeAndAck" [
- testCase "encode/decode roundtrip" $ do
- let msg = RevokeAndAck
- { revokeAndAckChannelId = testChannelId
- , revokeAndAckPerCommitmentSecret = testSecret
- , revokeAndAckNextPerCommitPoint = testPoint
- }
- encoded = encodeRevokeAndAck msg
- case decodeRevokeAndAck encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
- , testGroup "UpdateFee" [
- testCase "encode/decode roundtrip" $ do
- let msg = UpdateFee
- { updateFeeChannelId = testChannelId
- , updateFeeFeeratePerKw = 5000
- }
- encoded = encodeUpdateFee msg
- case decodeUpdateFee encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- ]
+-- decoding failures ----------------------------------------------------------
+
+decoding_failures :: TestTree
+decoding_failures = testGroup "decoding failures" [
+ testGroup "truncated payloads" [
+ testCase (lay_name lay) $ do
+ wire <- unhex (lay_type lay ++ concat (lay_fixed lay))
+ decode_message (BS.take (BS.length wire - 1) wire)
+ @?= Left DecodeInsufficientBytes
+ decode_message (BS.take 2 wire) @?= Left DecodeInsufficientBytes
+ | lay <- layouts ]
+ , testCase "a message without a type" $ do
+ decode_message "" @?= Left DecodeInsufficientBytes
+ decode_message "\x00" @?= Left DecodeInsufficientBytes
+ , testCase "message types BOLT #2 doesn't define" $ do
+ decode_message "\x00\x50" @?= Left (DecodeUnknownEvenType 80)
+ decode_message "\x00\x51" @?= Left (DecodeUnknownOddType 81)
+ decode_message "\x00\x4d" @?= Left (DecodeUnknownOddType 77)
+ decode_message "\x00\x7f" @?= Left (DecodeUnknownOddType 127)
+ decode_message "\x00\x10\x00\x00\x00\x00"
+ @?= Left (DecodeUnknownEvenType 16)
+ , testCase "an invalid point" $ do
+ let bad = "04" ++ rep 32 "11"
+ wire <- unhex (concat (open_channel_fields 11 bad))
+ decode_open_channel wire @?= Left DecodeInvalidPoint
+ wire' <- unhex (cid_h ++ secret_h ++ bad)
+ decode_revoke_and_ack wire' @?= Left DecodeInvalidPoint
+ , testCase "amounts above 21M BTC" $ do
+ -- funding_satoshis of 21M BTC, then one satoshi more
+ ok <- unhex (concat (open_channel_fields 2 "000775f05a074000"))
+ bad <- unhex (concat (open_channel_fields 2 "000775f05a074001"))
+ assertBool "21M BTC" (either (const False) (const True)
+ (decode_open_channel ok))
+ decode_open_channel bad @?= Left DecodeInvalidAmount
+ htlc <- unhex (cid_h ++ "0000000000000000" ++ "1d24b2dfac520001"
+ ++ phash_h ++ "000c3550" ++ onion_h)
+ decode_update_add_htlc htlc @?= Left DecodeInvalidAmount
+ out <- unhex (cid_h ++ "0000000000000004" ++ "ffffffffffffffff"
+ ++ "0000")
+ decode_tx_add_output out @?= Left DecodeInvalidAmount
+ , testCase "stfu initiator byte" $ do
+ off <- unhex (cid_h ++ "00")
+ fmap stfu_initiator (decode_stfu off) @?= Right False
+ bad <- unhex (cid_h ++ "02")
+ decode_stfu bad @?= Left DecodeInvalidInitiator
+ , testCase "a list longer than its payload" $ do
+ wire <- unhex (cid_h ++ sig1_h ++ "0003" ++ sig2_h ++ sig3_h)
+ decode_commitment_signed wire @?= Left DecodeInsufficientBytes
+ wire' <- unhex (cid_h ++ txid_h ++ "0001" ++ "0004" ++ "0101")
+ decode_tx_signatures wire' @?= Left DecodeInsufficientBytes
+ , testCase "a short onion_routing_packet" $ do
+ wire <- unhex (concat (take 5 (lay_fixed update_add_htlc_lay))
+ ++ drop 2 onion_h)
+ decode_update_add_htlc wire @?= Left DecodeInsufficientBytes
+ ]
+ where
+ -- open_channel's fixed fields with field i replaced
+ open_channel_fields :: Int -> String -> [String]
+ open_channel_fields i v =
+ let fs = lay_fixed open_channel_lay
+ in take i fs ++ [v] ++ drop (i + 1) fs
+
+-- encoding failures ----------------------------------------------------------
+
+encoding_failures :: TestTree
+encoding_failures = testGroup "encoding failures" [
+ testCase "u16 length prefixes" $ do
+ cid <- just "cid" f_cid
+ let big = BS.replicate 65536 0x00
+ max_ok = BS.replicate 65535 0x00
+ encode_tx_add_input (TxAddInput cid (SerialId 0) big 0 0 empty)
+ @?= Left EncodeLengthOverflow
+ assertBool "65535-byte prevtx" $ either (const False) (const True)
+ (encode_tx_add_input (TxAddInput cid (SerialId 0) max_ok 0 0 empty))
+ encode_tx_abort (TxAbort cid big empty) @?= Left EncodeLengthOverflow
+ encode_update_fail_htlc
+ (UpdateFailHtlc cid (HtlcId 0) big Nothing empty)
+ @?= Left EncodeLengthOverflow
+ , testCase "u16 counts" $ do
+ cid <- just "cid" f_cid
+ s <- just "sig" (f_sig sig1_h)
+ t <- just "txid" f_txid
+ w <- just "witness" (witness "\x00")
+ encode_commitment_signed
+ (CommitmentSigned cid s (replicate 65536 s) Nothing empty)
+ @?= Left EncodeLengthOverflow
+ encode_tx_signatures (TxSignatures cid t (replicate 65536 w) empty)
+ @?= Left EncodeLengthOverflow
+ , testCase "_tlvs fields can't hold known types" $ do
+ cid <- just "cid" f_cid
+ pcp <- just "point" (f_point p1_h)
+ s <- just "sig" (f_sig sig1_h)
+ spk <- just "script" (f_script p2wpkh_h)
+ fee <- just "fee" (sat 500)
+ one <- just "tlvs" (BOLT1.tlv_stream [TlvRecord 1 "\x00"])
+ two <- just "tlvs" (BOLT1.tlv_stream [TlvRecord 2 ""])
+ odd' <- just "tlvs" (BOLT1.tlv_stream [TlvRecord 43 ""])
+ encode_channel_ready (ChannelReady cid pcp Nothing one)
+ @?= Left EncodeInvalidTlvs
+ encode_closing_complete
+ (ClosingComplete cid spk spk fee 0 (Just s) Nothing Nothing two)
+ @?= Left EncodeInvalidTlvs
+ encode_tx_ack_rbf (TxAckRbf cid Nothing False two)
+ @?= Left EncodeInvalidTlvs
+ assertBool "unknown odd records encode" $
+ either (const False) (const True)
+ (encode_channel_ready (ChannelReady cid pcp Nothing odd'))
+ , testCase "the message size limit" $ do
+ cid <- just "cid" f_cid
+ -- a 65533-byte payload fits; one more byte doesn't
+ let at n = TxAbort cid (BS.replicate n 0x00) empty
+ fmap BS.length (encode_message (MsgTxAbort (at 65499)))
+ @?= Right 65535
+ encode_message (MsgTxAbort (at 65500)) @?= Left EncodeMessageTooLarge
+ fmap BS.length (encode_tx_abort (at 65500)) @?= Right 65534
]
--- Reestablish Tests -----------------------------------------------------------
-
-reestablish_tests :: TestTree
-reestablish_tests = testGroup "Channel Reestablish" [
- testCase "encode/decode roundtrip" $ do
- let sec = fromJust $ perCommitmentSecret (BS.replicate 32 0x22)
- msg = ChannelReestablish
- { channelReestablishChannelId = testChannelId
- , channelReestablishNextCommitNum = 5
- , channelReestablishNextRevocationNum = 4
- , channelReestablishYourLastCommitSecret = sec
- , channelReestablishMyCurrentCommitPoint = testPoint
- , channelReestablishTlvs = emptyTlvs
- }
- encoded = encodeChannelReestablish msg
- case decodeChannelReestablish encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
- , testCase "roundtrip with zero counters" $ do
- let sec = fromJust $ perCommitmentSecret (BS.replicate 32 0x00)
- msg = ChannelReestablish
- { channelReestablishChannelId = testChannelId
- , channelReestablishNextCommitNum = 1
- , channelReestablishNextRevocationNum = 0
- , channelReestablishYourLastCommitSecret = sec
- , channelReestablishMyCurrentCommitPoint = testPoint
- , channelReestablishTlvs = emptyTlvs
- }
- encoded = encodeChannelReestablish msg
- case decodeChannelReestablish encoded of
- Right (decoded, _) -> decoded @?= msg
- Left e -> assertFailure $ "decode failed: " ++ show e
+-- field types ----------------------------------------------------------------
+
+field_types :: TestTree
+field_types = testGroup "field types" [
+ testCase "script_pubkey" $ do
+ fmap un_script_pubkey (script_pubkey (BS.replicate 65535 0x51))
+ @?= Just (BS.replicate 65535 0x51)
+ script_pubkey (BS.replicate 65536 0x51) @?= Nothing
+ , testCase "witness" $ do
+ fmap un_witness (witness "") @?= Just ""
+ witness (BS.replicate 65536 0x00) @?= Nothing
+ , testCase "onion_routing_packet" $ do
+ let bs = BS.replicate 1366 0x00
+ fmap un_onion_routing_packet (onion_routing_packet bs) @?= Just bs
+ onion_routing_packet (BS.take 1365 bs) @?= Nothing
+ onion_routing_packet (BS.cons 0x00 bs) @?= Nothing
+ , testCase "onion_hash" $ do
+ fmap un_onion_hash (onion_hash (BS.replicate 32 0x01))
+ @?= Just (BS.replicate 32 0x01)
+ onion_hash (BS.replicate 33 0x01) @?= Nothing
+ , testCase "attribution_data" $ do
+ let bs = BS.replicate 920 0x00
+ fmap un_attribution_data (attribution_data bs) @?= Just bs
+ attribution_data (BS.take 919 bs) @?= Nothing
+ , testCase "message types" $ do
+ cid <- just "cid" f_cid
+ message_type (MsgStfu (Stfu cid False empty)) @?= 2
+ message_type (MsgUpdateFee (UpdateFee cid 0 empty)) @?= 134
]
--- Error Condition Tests -------------------------------------------------------
-
-error_tests :: TestTree
-error_tests = testGroup "Error Conditions" [
- testGroup "Insufficient Bytes" [
- testCase "decodeOpenChannel empty" $ do
- case decodeOpenChannel BS.empty of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeOpenChannel too short" $ do
- case decodeOpenChannel (BS.replicate 100 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeAcceptChannel too short" $ do
- case decodeAcceptChannel (BS.replicate 10 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeFundingCreated too short" $ do
- case decodeFundingCreated (BS.replicate 50 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeFundingSigned too short" $ do
- case decodeFundingSigned (BS.replicate 30 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeChannelReady too short" $ do
- case decodeChannelReady (BS.replicate 32 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeStfu too short" $ do
- case decodeStfu (BS.replicate 31 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeShutdown too short" $ do
- case decodeShutdown (BS.replicate 32 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeStfu invalid initiator byte" $ do
- -- channel_id (32 bytes) + initiator (1 byte, value 2)
- let encoded = BS.replicate 32 0xab <> BS.singleton 0x02
- case decodeStfu encoded of
- Left DecodeInvalidInitiator -> pure ()
- other -> assertFailure $
- "expected invalid initiator: " ++ show other
- , testCase "decodeUpdateAddHtlc too short" $ do
- case decodeUpdateAddHtlc (BS.replicate 100 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeCommitmentSigned too short" $ do
- case decodeCommitmentSigned (BS.replicate 90 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeRevokeAndAck too short" $ do
- case decodeRevokeAndAck (BS.replicate 60 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeTxSignatures too short" $ do
- case decodeTxSignatures (BS.replicate 60 0x00) of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- ]
- , testGroup "EncodeError - Length Overflow" [
- testCase "encodeShutdown with oversized script" $ do
- let script = scriptPubKey (BS.replicate 70000 0x00)
- msg = Shutdown
- { shutdownChannelId = testChannelId
- , shutdownScriptPubkey = script
- }
- case encodeShutdown msg of
- Left EncodeLengthOverflow -> pure ()
- other -> assertFailure $ "expected overflow: " ++ show other
- , testCase "encodeClosingComplete with oversized closer script" $ do
- let oversizedScript = scriptPubKey (BS.replicate 70000 0x00)
- normalScript = scriptPubKey (BS.replicate 22 0x00)
- msg = ClosingComplete
- { closingCompleteChannelId = testChannelId
- , closingCompleteCloserScript = oversizedScript
- , closingCompleteCloseeScript = normalScript
- , closingCompleteFeeSatoshi = Satoshi 500
- , closingCompleteLocktime = 0
- , closingCompleteTlvs = emptyTlvs
- }
- case encodeClosingComplete msg of
- Left EncodeLengthOverflow -> pure ()
- other -> assertFailure $ "expected overflow: " ++ show other
- , testCase "encodeClosingComplete with oversized closee script" $ do
- let normalScript = scriptPubKey (BS.replicate 22 0x00)
- oversizedScript = scriptPubKey (BS.replicate 70000 0x00)
- msg = ClosingComplete
- { closingCompleteChannelId = testChannelId
- , closingCompleteCloserScript = normalScript
- , closingCompleteCloseeScript = oversizedScript
- , closingCompleteFeeSatoshi = Satoshi 500
- , closingCompleteLocktime = 0
- , closingCompleteTlvs = emptyTlvs
- }
- case encodeClosingComplete msg of
- Left EncodeLengthOverflow -> pure ()
- other -> assertFailure $ "expected overflow: " ++ show other
- , testCase "encodeClosingSig with oversized script" $ do
- let oversizedScript = scriptPubKey (BS.replicate 70000 0x00)
- normalScript = scriptPubKey (BS.replicate 22 0x00)
- msg = ClosingSig
- { closingSigChannelId = testChannelId
- , closingSigCloserScript = oversizedScript
- , closingSigCloseeScript = normalScript
- , closingSigFeeSatoshi = Satoshi 500
- , closingSigLocktime = 0
- , closingSigTlvs = emptyTlvs
- }
- case encodeClosingSig msg of
- Left EncodeLengthOverflow -> pure ()
- other -> assertFailure $ "expected overflow: " ++ show other
- ]
- , testGroup "Invalid Field Length" [
- testCase "decodeShutdown with invalid script length" $ do
- -- channel_id (32 bytes) + script length (2 bytes) claiming more
- let encoded = BS.replicate 32 0xab <>
- BS.pack [0xff, 0xff] <> -- claims 65535 bytes
- BS.replicate 10 0x00 -- only 10 bytes
- case decodeShutdown encoded of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeTxAddInput with invalid prevTx length" $ do
- -- channel_id (32) + serial_id (8) + len (2) claiming more
- let encoded = BS.replicate 32 0xab <>
- BS.replicate 8 0x00 <>
- BS.pack [0xff, 0xff] <> -- claims 65535 bytes
- BS.replicate 10 0x00 -- only 10 bytes
- case decodeTxAddInput encoded of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- , testCase "decodeUpdateFailHtlc with invalid reason length" $ do
- -- channel_id (32) + htlc_id (8) + len (2) claiming more
- let encoded = BS.replicate 32 0xab <>
- BS.replicate 8 0x00 <>
- BS.pack [0xff, 0xff] <> -- claims 65535 bytes
- BS.replicate 10 0x00 -- only 10 bytes
- case decodeUpdateFailHtlc encoded of
- Left DecodeInsufficientBytes -> pure ()
- other -> assertFailure $ "expected insufficient: " ++ show other
- ]
+-- generators -----------------------------------------------------------------
+
+gen_bytes :: Int -> Gen BS.ByteString
+gen_bytes n = BS.pack <$> vectorOf n arbitrary
+
+gen_var_bytes :: Int -> Gen BS.ByteString
+gen_var_bytes hi = choose (0, hi) >>= gen_bytes
+
+gen_cid :: Gen BOLT1.ChannelId
+gen_cid = gen_bytes 32 `suchThatMap` BOLT1.channel_id
+
+gen_chain :: Gen BOLT1.ChainHash
+gen_chain = gen_bytes 32 `suchThatMap` BOLT1.chain_hash
+
+gen_point :: Gen BOLT1.Point
+gen_point = do
+ p <- elements [0x02, 0x03]
+ (BS.cons p <$> gen_bytes 32) `suchThatMap` BOLT1.point
+
+gen_sig :: Gen BOLT1.Signature
+gen_sig = gen_bytes 64 `suchThatMap` BOLT1.signature
+
+gen_txid :: Gen Tx.TxId
+gen_txid = gen_bytes 32 `suchThatMap` Tx.mkTxId
+
+gen_secret :: Gen BOLT1.PerCommitmentSecret
+gen_secret = gen_bytes 32 `suchThatMap` BOLT1.per_commitment_secret
+
+gen_sat :: Gen BOLT1.Satoshi
+gen_sat = choose (0, 2100000000000000) `suchThatMap` BOLT1.satoshi
+
+gen_msat :: Gen BOLT1.MilliSatoshi
+gen_msat = choose (0, 2100000000000000000) `suchThatMap` BOLT1.milli_satoshi
+
+gen_script :: Gen ScriptPubKey
+gen_script = gen_var_bytes 40 `suchThatMap` script_pubkey
+
+gen_fv :: Gen BOLT9.FeatureVector
+gen_fv = BOLT9.parse <$> gen_var_bytes 8
+
+gen_maybe :: Gen a -> Gen (Maybe a)
+gen_maybe g = oneof [pure Nothing, Just <$> g]
+
+-- unknown odd records, avoiding a message's known types
+gen_tlvs :: [Word64] -> Gen BOLT1.TlvStream
+gen_tlvs known = do
+ ts <- sublistOf (filter (`notElem` known) candidates)
+ rs <- traverse (\t -> TlvRecord t <$> gen_var_bytes 12) ts
+ pure (maybe empty id (BOLT1.tlv_stream rs))
+ where
+ candidates = [1, 3, 5, 7, 43, 201, 253, 65537, 4294967297]
+
+gen_open_channel :: Gen OpenChannel
+gen_open_channel = OpenChannel <$> gen_chain <*> gen_cid <*> gen_sat
+ <*> gen_msat <*> gen_sat <*> arbitrary <*> gen_sat <*> gen_msat
+ <*> arbitrary <*> arbitrary <*> arbitrary <*> gen_point <*> gen_point
+ <*> gen_point <*> gen_point <*> gen_point <*> gen_point <*> arbitrary
+ <*> gen_maybe gen_script <*> gen_maybe gen_fv <*> gen_tlvs [0, 1]
+
+gen_accept_channel :: Gen AcceptChannel
+gen_accept_channel = AcceptChannel <$> gen_cid <*> gen_sat <*> arbitrary
+ <*> gen_sat <*> gen_msat <*> arbitrary <*> arbitrary <*> arbitrary
+ <*> gen_point <*> gen_point <*> gen_point <*> gen_point <*> gen_point
+ <*> gen_point <*> gen_maybe gen_script <*> gen_maybe gen_fv
+ <*> gen_tlvs [0, 1]
+
+gen_funding_created :: Gen FundingCreated
+gen_funding_created = FundingCreated <$> gen_cid <*> gen_txid <*> arbitrary
+ <*> gen_sig <*> gen_tlvs []
+
+gen_funding_signed :: Gen FundingSigned
+gen_funding_signed = FundingSigned <$> gen_cid <*> gen_sig <*> gen_tlvs []
+
+gen_channel_ready :: Gen ChannelReady
+gen_channel_ready = ChannelReady <$> gen_cid <*> gen_point
+ <*> gen_maybe (BOLT1.ShortChannelId <$> arbitrary) <*> gen_tlvs [1]
+
+gen_open_channel2 :: Gen OpenChannel2
+gen_open_channel2 = OpenChannel2 <$> gen_chain <*> gen_cid <*> arbitrary
+ <*> arbitrary <*> gen_sat <*> gen_sat <*> arbitrary <*> gen_msat
+ <*> arbitrary <*> arbitrary <*> arbitrary <*> gen_point <*> gen_point
+ <*> gen_point <*> gen_point <*> gen_point <*> gen_point <*> gen_point
+ <*> arbitrary <*> gen_maybe gen_script <*> gen_maybe gen_fv
+ <*> arbitrary <*> gen_tlvs [0, 1, 2]
+
+gen_accept_channel2 :: Gen AcceptChannel2
+gen_accept_channel2 = AcceptChannel2 <$> gen_cid <*> gen_sat <*> gen_sat
+ <*> arbitrary <*> gen_msat <*> arbitrary <*> arbitrary <*> arbitrary
+ <*> gen_point <*> gen_point <*> gen_point <*> gen_point <*> gen_point
+ <*> gen_point <*> gen_point <*> gen_maybe gen_script
+ <*> gen_maybe gen_fv <*> arbitrary <*> gen_tlvs [0, 1, 2]
+
+gen_tx_add_input :: Gen TxAddInput
+gen_tx_add_input = TxAddInput <$> gen_cid <*> (SerialId <$> arbitrary)
+ <*> gen_var_bytes 64 <*> arbitrary <*> arbitrary <*> gen_tlvs []
+
+gen_tx_add_output :: Gen TxAddOutput
+gen_tx_add_output = TxAddOutput <$> gen_cid <*> (SerialId <$> arbitrary)
+ <*> gen_sat <*> gen_script <*> gen_tlvs []
+
+gen_tx_remove_input :: Gen TxRemoveInput
+gen_tx_remove_input = TxRemoveInput <$> gen_cid <*> (SerialId <$> arbitrary)
+ <*> gen_tlvs []
+
+gen_tx_remove_output :: Gen TxRemoveOutput
+gen_tx_remove_output = TxRemoveOutput <$> gen_cid
+ <*> (SerialId <$> arbitrary) <*> gen_tlvs []
+
+gen_tx_complete :: Gen TxComplete
+gen_tx_complete = TxComplete <$> gen_cid <*> gen_tlvs []
+
+gen_tx_signatures :: Gen TxSignatures
+gen_tx_signatures = TxSignatures <$> gen_cid <*> gen_txid
+ <*> listOf (gen_var_bytes 32 `suchThatMap` witness) <*> gen_tlvs []
+
+gen_tx_init_rbf :: Gen TxInitRbf
+gen_tx_init_rbf = TxInitRbf <$> gen_cid <*> arbitrary <*> arbitrary
+ <*> gen_maybe (arbitrary :: Gen Int64) <*> arbitrary <*> gen_tlvs [0, 2]
+
+gen_tx_ack_rbf :: Gen TxAckRbf
+gen_tx_ack_rbf = TxAckRbf <$> gen_cid <*> gen_maybe arbitrary <*> arbitrary
+ <*> gen_tlvs [0, 2]
+
+gen_tx_abort :: Gen TxAbort
+gen_tx_abort = TxAbort <$> gen_cid <*> gen_var_bytes 64 <*> gen_tlvs []
+
+gen_stfu :: Gen Stfu
+gen_stfu = Stfu <$> gen_cid <*> arbitrary <*> gen_tlvs []
+
+gen_shutdown :: Gen Shutdown
+gen_shutdown = Shutdown <$> gen_cid <*> gen_script <*> gen_tlvs []
+
+gen_closing_complete :: Gen ClosingComplete
+gen_closing_complete = ClosingComplete <$> gen_cid <*> gen_script
+ <*> gen_script <*> gen_sat <*> arbitrary <*> gen_maybe gen_sig
+ <*> gen_maybe gen_sig <*> gen_maybe gen_sig <*> gen_tlvs [1, 2, 3]
+
+gen_closing_sig :: Gen ClosingSig
+gen_closing_sig = ClosingSig <$> gen_cid <*> gen_script <*> gen_script
+ <*> gen_sat <*> arbitrary <*> gen_maybe gen_sig <*> gen_maybe gen_sig
+ <*> gen_maybe gen_sig <*> gen_tlvs [1, 2, 3]
+
+gen_closing_signed :: Gen ClosingSigned
+gen_closing_signed = ClosingSigned <$> gen_cid <*> gen_sat <*> gen_sig
+ <*> gen_maybe (FeeRange <$> gen_sat <*> gen_sat) <*> gen_tlvs [1]
+
+gen_update_add_htlc :: Gen UpdateAddHtlc
+gen_update_add_htlc = UpdateAddHtlc <$> gen_cid <*> (HtlcId <$> arbitrary)
+ <*> gen_msat
+ <*> (gen_bytes 32 `suchThatMap` BOLT1.payment_hash)
+ <*> arbitrary
+ <*> (gen_bytes 1366 `suchThatMap` onion_routing_packet)
+ <*> gen_maybe gen_point <*> gen_tlvs [0]
+
+gen_update_fulfill_htlc :: Gen UpdateFulfillHtlc
+gen_update_fulfill_htlc = UpdateFulfillHtlc <$> gen_cid
+ <*> (HtlcId <$> arbitrary)
+ <*> (gen_bytes 32 `suchThatMap` BOLT1.payment_preimage)
+ <*> gen_maybe (gen_bytes 920 `suchThatMap` attribution_data)
+ <*> gen_maybe (gen_var_bytes 64) <*> gen_tlvs [1, 3]
+
+gen_update_fail_htlc :: Gen UpdateFailHtlc
+gen_update_fail_htlc = UpdateFailHtlc <$> gen_cid <*> (HtlcId <$> arbitrary)
+ <*> gen_var_bytes 300
+ <*> gen_maybe (gen_bytes 920 `suchThatMap` attribution_data)
+ <*> gen_tlvs [1]
+
+gen_update_fail_malformed_htlc :: Gen UpdateFailMalformedHtlc
+gen_update_fail_malformed_htlc = UpdateFailMalformedHtlc <$> gen_cid
+ <*> (HtlcId <$> arbitrary)
+ <*> (gen_bytes 32 `suchThatMap` onion_hash)
+ <*> arbitrary <*> gen_tlvs []
+
+gen_commitment_signed :: Gen CommitmentSigned
+gen_commitment_signed = CommitmentSigned <$> gen_cid <*> gen_sig
+ <*> listOf gen_sig <*> gen_maybe gen_txid <*> gen_tlvs [1]
+
+gen_revoke_and_ack :: Gen RevokeAndAck
+gen_revoke_and_ack = RevokeAndAck <$> gen_cid <*> gen_secret <*> gen_point
+ <*> gen_tlvs []
+
+gen_update_fee :: Gen UpdateFee
+gen_update_fee = UpdateFee <$> gen_cid <*> arbitrary <*> gen_tlvs []
+
+gen_channel_reestablish :: Gen ChannelReestablish
+gen_channel_reestablish = ChannelReestablish <$> gen_cid <*> arbitrary
+ <*> arbitrary <*> gen_secret <*> gen_point
+ <*> gen_maybe (NextFunding <$> gen_txid <*> arbitrary) <*> gen_tlvs [1]
+
+gen_message :: Gen Message
+gen_message = oneof [
+ MsgOpenChannel <$> gen_open_channel
+ , MsgAcceptChannel <$> gen_accept_channel
+ , MsgFundingCreated <$> gen_funding_created
+ , MsgFundingSigned <$> gen_funding_signed
+ , MsgChannelReady <$> gen_channel_ready
+ , MsgOpenChannel2 <$> gen_open_channel2
+ , MsgAcceptChannel2 <$> gen_accept_channel2
+ , MsgTxAddInput <$> gen_tx_add_input
+ , MsgTxAddOutput <$> gen_tx_add_output
+ , MsgTxRemoveInput <$> gen_tx_remove_input
+ , MsgTxRemoveOutput <$> gen_tx_remove_output
+ , MsgTxComplete <$> gen_tx_complete
+ , MsgTxSignatures <$> gen_tx_signatures
+ , MsgTxInitRbf <$> gen_tx_init_rbf
+ , MsgTxAckRbf <$> gen_tx_ack_rbf
+ , MsgTxAbort <$> gen_tx_abort
+ , MsgStfu <$> gen_stfu
+ , MsgShutdown <$> gen_shutdown
+ , MsgClosingComplete <$> gen_closing_complete
+ , MsgClosingSig <$> gen_closing_sig
+ , MsgClosingSigned <$> gen_closing_signed
+ , MsgUpdateAddHtlc <$> gen_update_add_htlc
+ , MsgUpdateFulfillHtlc <$> gen_update_fulfill_htlc
+ , MsgUpdateFailHtlc <$> gen_update_fail_htlc
+ , MsgUpdateFailMalformedHtlc <$> gen_update_fail_malformed_htlc
+ , MsgCommitmentSigned <$> gen_commitment_signed
+ , MsgRevokeAndAck <$> gen_revoke_and_ack
+ , MsgUpdateFee <$> gen_update_fee
+ , MsgChannelReestablish <$> gen_channel_reestablish
]
--- Property Tests --------------------------------------------------------------
-
-property_tests :: TestTree
-property_tests = testGroup "Properties" [
- testProperty "OpenChannel roundtrip" propOpenChannelRoundtrip
- , testProperty "AcceptChannel roundtrip" propAcceptChannelRoundtrip
- , testProperty "FundingCreated roundtrip" propFundingCreatedRoundtrip
- , testProperty "FundingSigned roundtrip" propFundingSignedRoundtrip
- , testProperty "ChannelReady roundtrip" propChannelReadyRoundtrip
- , testProperty "OpenChannel2 roundtrip" propOpenChannel2Roundtrip
- , testProperty "AcceptChannel2 roundtrip" propAcceptChannel2Roundtrip
- , testProperty "TxAddInput roundtrip" propTxAddInputRoundtrip
- , testProperty "TxAddOutput roundtrip" propTxAddOutputRoundtrip
- , testProperty "TxRemoveInput roundtrip" propTxRemoveInputRoundtrip
- , testProperty "TxRemoveOutput roundtrip" propTxRemoveOutputRoundtrip
- , testProperty "TxComplete roundtrip" propTxCompleteRoundtrip
- , testProperty "TxSignatures roundtrip" propTxSignaturesRoundtrip
- , testProperty "TxInitRbf roundtrip" propTxInitRbfRoundtrip
- , testProperty "TxAckRbf roundtrip" propTxAckRbfRoundtrip
- , testProperty "TxAbort roundtrip" propTxAbortRoundtrip
- , testProperty "Stfu roundtrip" propStfuRoundtrip
- , testProperty "Shutdown roundtrip" propShutdownRoundtrip
- , testProperty "ClosingSigned roundtrip" propClosingSignedRoundtrip
- , testProperty "ClosingComplete roundtrip" propClosingCompleteRoundtrip
- , testProperty "ClosingSig roundtrip" propClosingSigRoundtrip
- , testProperty "UpdateAddHtlc roundtrip" propUpdateAddHtlcRoundtrip
- , testProperty "UpdateFulfillHtlc roundtrip" propUpdateFulfillHtlcRoundtrip
- , testProperty "UpdateFailHtlc roundtrip" propUpdateFailHtlcRoundtrip
- , testProperty "UpdateFailMalformedHtlc roundtrip"
- propUpdateFailMalformedHtlcRoundtrip
- , testProperty "CommitmentSigned roundtrip" propCommitmentSignedRoundtrip
- , testProperty "RevokeAndAck roundtrip" propRevokeAndAckRoundtrip
- , testProperty "UpdateFee roundtrip" propUpdateFeeRoundtrip
- , testProperty "ChannelReestablish roundtrip" propChannelReestablishRoundtrip
- , testProperty "HtlcId wrap/unwrap" propHtlcIdRoundtrip
- , testProperty "SerialId wrap/unwrap" propSerialIdRoundtrip
- , testProperty "OnionPacket validates length" propOnionPacketLength
- , testProperty "TxId validates length" propTxIdLength
+-- properties -----------------------------------------------------------------
+
+-- decoding an encoded payload gives back the value
+roundtrip
+ :: (Eq a, Show a)
+ => String
+ -> Gen a
+ -> (a -> Either EncodeError BS.ByteString)
+ -> (BS.ByteString -> Either DecodeError a)
+ -> TestTree
+roundtrip name gen enc dec = testProperty name $ forAll gen $ \m ->
+ case enc m of
+ Left e -> counterexample ("encode failed: " ++ show e) False
+ Right w -> dec w === Right m
+
+-- byte strings derived from a valid encoding
+data Edit = Replace !Int !Word8 | Truncate !Int | Append !BS.ByteString
+ deriving Show
+
+gen_edit :: Gen Edit
+gen_edit = oneof [
+ Replace <$> arbitrary <*> arbitrary
+ , Truncate <$> arbitrary
+ , Append <$> gen_var_bytes 8
]
--- Property: OpenChannel roundtrip
-propOpenChannelRoundtrip :: Property
-propOpenChannelRoundtrip = property $ do
- let msg = OpenChannel
- { openChannelChainHash = testChainHash
- , openChannelTempChannelId = testChannelId
- , openChannelFundingSatoshi = Satoshi 1000000
- , openChannelPushMsat = MilliSatoshi 500000
- , openChannelDustLimitSatoshi = Satoshi 546
- , openChannelMaxHtlcValueInFlight = MilliSatoshi 100000000
- , openChannelChannelReserveSat = Satoshi 10000
- , openChannelHtlcMinimumMsat = MilliSatoshi 1000
- , openChannelFeeratePerKw = 2500
- , openChannelToSelfDelay = 144
- , openChannelMaxAcceptedHtlcs = 483
- , openChannelFundingPubkey = testPoint
- , openChannelRevocationBasepoint = testPoint
- , openChannelPaymentBasepoint = testPoint
- , openChannelDelayedPaymentBase = testPoint
- , openChannelHtlcBasepoint = testPoint
- , openChannelFirstPerCommitPoint = testPoint
- , openChannelChannelFlags = 0x01
- , openChannelTlvs = emptyTlvs
- }
- encoded = encodeOpenChannel msg
- case decodeOpenChannel encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: AcceptChannel roundtrip
-propAcceptChannelRoundtrip :: Property
-propAcceptChannelRoundtrip = property $ do
- let msg = AcceptChannel
- { acceptChannelTempChannelId = testChannelId
- , acceptChannelDustLimitSatoshi = Satoshi 546
- , acceptChannelMaxHtlcValueInFlight = MilliSatoshi 100000000
- , acceptChannelChannelReserveSat = Satoshi 10000
- , acceptChannelHtlcMinimumMsat = MilliSatoshi 1000
- , acceptChannelMinimumDepth = 3
- , acceptChannelToSelfDelay = 144
- , acceptChannelMaxAcceptedHtlcs = 483
- , acceptChannelFundingPubkey = testPoint
- , acceptChannelRevocationBasepoint = testPoint
- , acceptChannelPaymentBasepoint = testPoint
- , acceptChannelDelayedPaymentBase = testPoint
- , acceptChannelHtlcBasepoint = testPoint
- , acceptChannelFirstPerCommitPoint = testPoint
- , acceptChannelTlvs = emptyTlvs
- }
- encoded = encodeAcceptChannel msg
- case decodeAcceptChannel encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: FundingCreated roundtrip
-propFundingCreatedRoundtrip :: Word16 -> Property
-propFundingCreatedRoundtrip outIdx = property $ do
- let msg = FundingCreated
- { fundingCreatedTempChannelId = testChannelId
- , fundingCreatedFundingTxid = testTxId
- , fundingCreatedFundingOutIdx = outIdx
- , fundingCreatedSignature = testSignature
- }
- encoded = encodeFundingCreated msg
- case decodeFundingCreated encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: FundingSigned roundtrip
-propFundingSignedRoundtrip :: Property
-propFundingSignedRoundtrip = property $ do
- let msg = FundingSigned
- { fundingSignedChannelId = testChannelId
- , fundingSignedSignature = testSignature
- }
- encoded = encodeFundingSigned msg
- case decodeFundingSigned encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: ChannelReady roundtrip
-propChannelReadyRoundtrip :: Property
-propChannelReadyRoundtrip = property $ do
- let msg = ChannelReady
- { channelReadyChannelId = testChannelId
- , channelReadySecondPerCommitPoint = testPoint
- , channelReadyTlvs = emptyTlvs
- }
- encoded = encodeChannelReady msg
- case decodeChannelReady encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: OpenChannel2 roundtrip
-propOpenChannel2Roundtrip :: Property
-propOpenChannel2Roundtrip = property $ do
- let msg = OpenChannel2
- { openChannel2ChainHash = testChainHash
- , openChannel2TempChannelId = testChannelId
- , openChannel2FundingFeeratePerkw = 2500
- , openChannel2CommitFeeratePerkw = 2000
- , openChannel2FundingSatoshi = Satoshi 1000000
- , openChannel2DustLimitSatoshi = Satoshi 546
- , openChannel2MaxHtlcValueInFlight = MilliSatoshi 100000000
- , openChannel2HtlcMinimumMsat = MilliSatoshi 1000
- , openChannel2ToSelfDelay = 144
- , openChannel2MaxAcceptedHtlcs = 483
- , openChannel2Locktime = 0
- , openChannel2FundingPubkey = testPoint
- , openChannel2RevocationBasepoint = testPoint
- , openChannel2PaymentBasepoint = testPoint
- , openChannel2DelayedPaymentBase = testPoint
- , openChannel2HtlcBasepoint = testPoint
- , openChannel2FirstPerCommitPoint = testPoint
- , openChannel2SecondPerCommitPoint = testPoint2
- , openChannel2ChannelFlags = 0x00
- , openChannel2Tlvs = emptyTlvs
- }
- encoded = encodeOpenChannel2 msg
- case decodeOpenChannel2 encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: AcceptChannel2 roundtrip
-propAcceptChannel2Roundtrip :: Property
-propAcceptChannel2Roundtrip = property $ do
- let msg = AcceptChannel2
- { acceptChannel2TempChannelId = testChannelId
- , acceptChannel2FundingSatoshi = Satoshi 500000
- , acceptChannel2DustLimitSatoshi = Satoshi 546
- , acceptChannel2MaxHtlcValueInFlight = MilliSatoshi 100000000
- , acceptChannel2HtlcMinimumMsat = MilliSatoshi 1000
- , acceptChannel2MinimumDepth = 3
- , acceptChannel2ToSelfDelay = 144
- , acceptChannel2MaxAcceptedHtlcs = 483
- , acceptChannel2FundingPubkey = testPoint
- , acceptChannel2RevocationBasepoint = testPoint
- , acceptChannel2PaymentBasepoint = testPoint
- , acceptChannel2DelayedPaymentBase = testPoint
- , acceptChannel2HtlcBasepoint = testPoint
- , acceptChannel2FirstPerCommitPoint = testPoint
- , acceptChannel2SecondPerCommitPoint = testPoint2
- , acceptChannel2Tlvs = emptyTlvs
- }
- encoded = encodeAcceptChannel2 msg
- case decodeAcceptChannel2 encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: TxAddInput roundtrip with varying data
-propTxAddInputRoundtrip :: [Word8] -> Word32 -> Word32 -> Property
-propTxAddInputRoundtrip prevTxBytes vout seqNum = property $ do
- let prevTx = BS.pack (take 1000 prevTxBytes) -- limit size
- msg = TxAddInput
- { txAddInputChannelId = testChannelId
- , txAddInputSerialId = serialId 12345
- , txAddInputPrevTx = prevTx
- , txAddInputPrevVout = vout
- , txAddInputSequence = seqNum
- }
- case encodeTxAddInput msg of
- Left _ -> False
- Right encoded -> case decodeTxAddInput encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: TxAddOutput roundtrip
-propTxAddOutputRoundtrip :: Word64 -> [Word8] -> Property
-propTxAddOutputRoundtrip sats scriptBytes = property $ do
- let script = scriptPubKey (BS.pack (take 100 scriptBytes))
- msg = TxAddOutput
- { txAddOutputChannelId = testChannelId
- , txAddOutputSerialId = serialId 54321
- , txAddOutputSats = Satoshi sats
- , txAddOutputScript = script
- }
- case encodeTxAddOutput msg of
- Left _ -> False
- Right encoded -> case decodeTxAddOutput encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: TxRemoveInput roundtrip
-propTxRemoveInputRoundtrip :: Word64 -> Property
-propTxRemoveInputRoundtrip sid = property $ do
- let msg = TxRemoveInput
- { txRemoveInputChannelId = testChannelId
- , txRemoveInputSerialId = serialId sid
- }
- encoded = encodeTxRemoveInput msg
- case decodeTxRemoveInput encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: TxRemoveOutput roundtrip
-propTxRemoveOutputRoundtrip :: Word64 -> Property
-propTxRemoveOutputRoundtrip sid = property $ do
- let msg = TxRemoveOutput
- { txRemoveOutputChannelId = testChannelId
- , txRemoveOutputSerialId = serialId sid
- }
- encoded = encodeTxRemoveOutput msg
- case decodeTxRemoveOutput encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: TxComplete roundtrip
-propTxCompleteRoundtrip :: Property
-propTxCompleteRoundtrip = property $ do
- let msg = TxComplete { txCompleteChannelId = testChannelId }
- encoded = encodeTxComplete msg
- case decodeTxComplete encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: TxSignatures roundtrip with varying witnesses
-propTxSignaturesRoundtrip :: [[Word8]] -> Property
-propTxSignaturesRoundtrip witnessList = property $ do
- let wits = map (Witness . BS.pack . take 200) (take 10 witnessList)
- msg = TxSignatures
- { txSignaturesChannelId = testChannelId
- , txSignaturesTxid = testTxId
- , txSignaturesWitnesses = wits
- }
- case encodeTxSignatures msg of
- Left _ -> False
- Right encoded -> case decodeTxSignatures encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: TxInitRbf roundtrip
-propTxInitRbfRoundtrip :: Word32 -> Word32 -> Property
-propTxInitRbfRoundtrip locktime feerate = property $ do
- let msg = TxInitRbf
- { txInitRbfChannelId = testChannelId
- , txInitRbfLocktime = locktime
- , txInitRbfFeerate = feerate
- , txInitRbfTlvs = emptyTlvs
- }
- encoded = encodeTxInitRbf msg
- case decodeTxInitRbf encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: TxAckRbf roundtrip
-propTxAckRbfRoundtrip :: Property
-propTxAckRbfRoundtrip = property $ do
- let msg = TxAckRbf
- { txAckRbfChannelId = testChannelId
- , txAckRbfTlvs = emptyTlvs
- }
- encoded = encodeTxAckRbf msg
- case decodeTxAckRbf encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: TxAbort roundtrip
-propTxAbortRoundtrip :: [Word8] -> Property
-propTxAbortRoundtrip dataBytes = property $ do
- let abortData = BS.pack (take 1000 dataBytes)
- msg = TxAbort
- { txAbortChannelId = testChannelId
- , txAbortData = abortData
- }
- case encodeTxAbort msg of
- Left _ -> False
- Right encoded -> case decodeTxAbort encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: Stfu roundtrip
-propStfuRoundtrip :: Bool -> Property
-propStfuRoundtrip isInit = property $ do
- let ini = if isInit then IsInitiator else NotInitiator
- msg = Stfu
- { stfuChannelId = testChannelId
- , stfuInitiator = ini
- }
- encoded = encodeStfu msg
- case decodeStfu encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: Shutdown roundtrip
-propShutdownRoundtrip :: [Word8] -> Property
-propShutdownRoundtrip scriptBytes = property $ do
- let script = scriptPubKey (BS.pack (take 100 scriptBytes))
- msg = Shutdown
- { shutdownChannelId = testChannelId
- , shutdownScriptPubkey = script
- }
- case encodeShutdown msg of
- Left _ -> False
- Right encoded -> case decodeShutdown encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: ClosingSigned roundtrip
-propClosingSignedRoundtrip :: Word64 -> Property
-propClosingSignedRoundtrip feeSats = property $ do
- let msg = ClosingSigned
- { closingSignedChannelId = testChannelId
- , closingSignedFeeSatoshi = Satoshi feeSats
- , closingSignedSignature = testSignature
- , closingSignedTlvs = emptyTlvs
- }
- encoded = encodeClosingSigned msg
- case decodeClosingSigned encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: ClosingComplete roundtrip
-propClosingCompleteRoundtrip :: Word64 -> Word32 -> Property
-propClosingCompleteRoundtrip feeSats locktime = property $ do
- let closerScript = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xcc)
- closeeScript = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xdd)
- msg = ClosingComplete
- { closingCompleteChannelId = testChannelId
- , closingCompleteCloserScript = closerScript
- , closingCompleteCloseeScript = closeeScript
- , closingCompleteFeeSatoshi = Satoshi feeSats
- , closingCompleteLocktime = locktime
- , closingCompleteTlvs = emptyTlvs
- }
- case encodeClosingComplete msg of
- Left _ -> False
- Right encoded -> case decodeClosingComplete encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: ClosingSig roundtrip
-propClosingSigRoundtrip :: Word64 -> Word32 -> Property
-propClosingSigRoundtrip feeSats locktime = property $ do
- let closerScript = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xee)
- closeeScript = scriptPubKey (BS.pack [0x00, 0x14] <>
- BS.replicate 20 0xff)
- msg = ClosingSig
- { closingSigChannelId = testChannelId
- , closingSigCloserScript = closerScript
- , closingSigCloseeScript = closeeScript
- , closingSigFeeSatoshi = Satoshi feeSats
- , closingSigLocktime = locktime
- , closingSigTlvs = emptyTlvs
- }
- case encodeClosingSig msg of
- Left _ -> False
- Right encoded -> case decodeClosingSig encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: UpdateAddHtlc roundtrip
-propUpdateAddHtlcRoundtrip :: Word64 -> Word64 -> Word32 -> Property
-propUpdateAddHtlcRoundtrip hid amountMsat cltvExpiry = property $ do
- let msg = UpdateAddHtlc
- { updateAddHtlcChannelId = testChannelId
- , updateAddHtlcId = htlcId hid
- , updateAddHtlcAmountMsat = MilliSatoshi amountMsat
- , updateAddHtlcPaymentHash = testPaymentHash
- , updateAddHtlcCltvExpiry = cltvExpiry
- , updateAddHtlcOnionPacket = testOnionPacket
- , updateAddHtlcTlvs = emptyTlvs
- }
- encoded = encodeUpdateAddHtlc msg
- case decodeUpdateAddHtlc encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: UpdateFulfillHtlc roundtrip
-propUpdateFulfillHtlcRoundtrip :: Word64 -> Property
-propUpdateFulfillHtlcRoundtrip hid = property $ do
- let msg = UpdateFulfillHtlc
- { updateFulfillHtlcChannelId = testChannelId
- , updateFulfillHtlcId = htlcId hid
- , updateFulfillHtlcPaymentPreimage = testPaymentPreimage
- , updateFulfillHtlcTlvs = emptyTlvs
- }
- encoded = encodeUpdateFulfillHtlc msg
- case decodeUpdateFulfillHtlc encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: UpdateFailHtlc roundtrip
-propUpdateFailHtlcRoundtrip :: Word64 -> [Word8] -> Property
-propUpdateFailHtlcRoundtrip hid reasonBytes = property $ do
- let failReason = BS.pack (take 1000 reasonBytes)
- msg = UpdateFailHtlc
- { updateFailHtlcChannelId = testChannelId
- , updateFailHtlcId = htlcId hid
- , updateFailHtlcReason = failReason
- , updateFailHtlcTlvs = emptyTlvs
- }
- case encodeUpdateFailHtlc msg of
- Left _ -> False
- Right encoded -> case decodeUpdateFailHtlc encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: UpdateFailMalformedHtlc roundtrip
-propUpdateFailMalformedHtlcRoundtrip :: Word64 -> Word16 -> Property
-propUpdateFailMalformedHtlcRoundtrip hid failCode = property $ do
- let msg = UpdateFailMalformedHtlc
- { updateFailMalformedHtlcChannelId = testChannelId
- , updateFailMalformedHtlcId = htlcId hid
- , updateFailMalformedHtlcSha256Onion = testPaymentHash
- , updateFailMalformedHtlcFailureCode = failCode
- }
- encoded = encodeUpdateFailMalformedHtlc msg
- case decodeUpdateFailMalformedHtlc encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: CommitmentSigned roundtrip with varying HTLC count
-propCommitmentSignedRoundtrip :: NonNegative Int -> Property
-propCommitmentSignedRoundtrip (NonNegative n) = property $ do
- let numHtlcs = n `mod` 10 -- limit to 10 HTLCs for test speed
- htlcSigs = replicate numHtlcs testSignature
- msg = CommitmentSigned
- { commitmentSignedChannelId = testChannelId
- , commitmentSignedSignature = testSignature
- , commitmentSignedHtlcSignatures = htlcSigs
- }
- case encodeCommitmentSigned msg of
- Left _ -> False
- Right encoded -> case decodeCommitmentSigned encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: RevokeAndAck roundtrip
-propRevokeAndAckRoundtrip :: Property
-propRevokeAndAckRoundtrip = property $ do
- let msg = RevokeAndAck
- { revokeAndAckChannelId = testChannelId
- , revokeAndAckPerCommitmentSecret = testSecret
- , revokeAndAckNextPerCommitPoint = testPoint
- }
- encoded = encodeRevokeAndAck msg
- case decodeRevokeAndAck encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: UpdateFee roundtrip
-propUpdateFeeRoundtrip :: Word32 -> Property
-propUpdateFeeRoundtrip feerate = property $ do
- let msg = UpdateFee
- { updateFeeChannelId = testChannelId
- , updateFeeFeeratePerKw = feerate
- }
- encoded = encodeUpdateFee msg
- case decodeUpdateFee encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: ChannelReestablish roundtrip
-propChannelReestablishRoundtrip :: Word64 -> Word64 -> Property
-propChannelReestablishRoundtrip nextCommit nextRevoke = property $ do
- let sec = fromJust $ perCommitmentSecret (BS.replicate 32 0x22)
- msg = ChannelReestablish
- { channelReestablishChannelId = testChannelId
- , channelReestablishNextCommitNum = nextCommit
- , channelReestablishNextRevocationNum = nextRevoke
- , channelReestablishYourLastCommitSecret = sec
- , channelReestablishMyCurrentCommitPoint = testPoint
- , channelReestablishTlvs = emptyTlvs
- }
- encoded = encodeChannelReestablish msg
- case decodeChannelReestablish encoded of
- Right (decoded, _) -> decoded == msg
- Left _ -> False
-
--- Property: HtlcId wrap/unwrap
-propHtlcIdRoundtrip :: Word64 -> Property
-propHtlcIdRoundtrip w = property $ unHtlcId (htlcId w) == w
-
--- Property: SerialId wrap/unwrap
-propSerialIdRoundtrip :: Word64 -> Property
-propSerialIdRoundtrip w = property $ unSerialId (serialId w) == w
-
--- Property: OnionPacket validates length (1366 only)
-propOnionPacketLength :: NonNegative Int -> Property
-propOnionPacketLength (NonNegative n) = property $
- let len = n `mod` 2000
- bs = BS.replicate len 0x00
- in case onionPacket bs of
- Just _ -> len == onionPacketLen
- Nothing -> len /= onionPacketLen
-
--- Property: TxId validates length (32 only)
-propTxIdLength :: NonNegative Int -> Property
-propTxIdLength (NonNegative n) = property $
- let len = n `mod` 100
- bs = BS.replicate len 0x00
- in case mkTxId bs of
- Just _ -> len == 32
- Nothing -> len /= 32
-
--- Helpers ---------------------------------------------------------------------
-
--- | Decode hex string. Returns Nothing on invalid hex.
-unhex :: BS.ByteString -> Maybe BS.ByteString
-unhex = B16.decode
+apply_edit :: Edit -> BS.ByteString -> BS.ByteString
+apply_edit e bs = case e of
+ Replace i b
+ | BS.null bs -> bs
+ | otherwise ->
+ let j = i `mod` BS.length bs
+ in BS.take j bs <> BS.singleton b <> BS.drop (j + 1) bs
+ Truncate i
+ | BS.null bs -> bs
+ | otherwise -> BS.take (i `mod` BS.length bs) bs
+ Append x -> bs <> x
+
+properties :: TestTree
+properties = testGroup "properties" [
+ testGroup "payloads round-trip" [
+ roundtrip "open_channel" gen_open_channel
+ encode_open_channel decode_open_channel
+ , roundtrip "accept_channel" gen_accept_channel
+ encode_accept_channel decode_accept_channel
+ , roundtrip "funding_created" gen_funding_created
+ (Right . encode_funding_created) decode_funding_created
+ , roundtrip "funding_signed" gen_funding_signed
+ (Right . encode_funding_signed) decode_funding_signed
+ , roundtrip "channel_ready" gen_channel_ready
+ encode_channel_ready decode_channel_ready
+ , roundtrip "open_channel2" gen_open_channel2
+ encode_open_channel2 decode_open_channel2
+ , roundtrip "accept_channel2" gen_accept_channel2
+ encode_accept_channel2 decode_accept_channel2
+ , roundtrip "tx_add_input" gen_tx_add_input
+ encode_tx_add_input decode_tx_add_input
+ , roundtrip "tx_add_output" gen_tx_add_output
+ (Right . encode_tx_add_output) decode_tx_add_output
+ , roundtrip "tx_remove_input" gen_tx_remove_input
+ (Right . encode_tx_remove_input) decode_tx_remove_input
+ , roundtrip "tx_remove_output" gen_tx_remove_output
+ (Right . encode_tx_remove_output) decode_tx_remove_output
+ , roundtrip "tx_complete" gen_tx_complete
+ (Right . encode_tx_complete) decode_tx_complete
+ , roundtrip "tx_signatures" gen_tx_signatures
+ encode_tx_signatures decode_tx_signatures
+ , roundtrip "tx_init_rbf" gen_tx_init_rbf
+ encode_tx_init_rbf decode_tx_init_rbf
+ , roundtrip "tx_ack_rbf" gen_tx_ack_rbf
+ encode_tx_ack_rbf decode_tx_ack_rbf
+ , roundtrip "tx_abort" gen_tx_abort encode_tx_abort decode_tx_abort
+ , roundtrip "stfu" gen_stfu (Right . encode_stfu) decode_stfu
+ , roundtrip "shutdown" gen_shutdown
+ (Right . encode_shutdown) decode_shutdown
+ , roundtrip "closing_complete" gen_closing_complete
+ encode_closing_complete decode_closing_complete
+ , roundtrip "closing_sig" gen_closing_sig
+ encode_closing_sig decode_closing_sig
+ , roundtrip "closing_signed" gen_closing_signed
+ encode_closing_signed decode_closing_signed
+ , roundtrip "update_add_htlc" gen_update_add_htlc
+ encode_update_add_htlc decode_update_add_htlc
+ , roundtrip "update_fulfill_htlc" gen_update_fulfill_htlc
+ encode_update_fulfill_htlc decode_update_fulfill_htlc
+ , roundtrip "update_fail_htlc" gen_update_fail_htlc
+ encode_update_fail_htlc decode_update_fail_htlc
+ , roundtrip "update_fail_malformed_htlc" gen_update_fail_malformed_htlc
+ (Right . encode_update_fail_malformed_htlc)
+ decode_update_fail_malformed_htlc
+ , roundtrip "commitment_signed" gen_commitment_signed
+ encode_commitment_signed decode_commitment_signed
+ , roundtrip "revoke_and_ack" gen_revoke_and_ack
+ (Right . encode_revoke_and_ack) decode_revoke_and_ack
+ , roundtrip "update_fee" gen_update_fee
+ (Right . encode_update_fee) decode_update_fee
+ , roundtrip "channel_reestablish" gen_channel_reestablish
+ encode_channel_reestablish decode_channel_reestablish
+ ]
+ , roundtrip "messages round-trip" gen_message encode_message decode_message
+ , testProperty "decoded messages re-encode exactly" $
+ withMaxSuccess 2000 $
+ forAll gen_message $ \m -> forAll gen_edit $ \e ->
+ case encode_message m of
+ Left err -> counterexample ("encode failed: " ++ show err) False
+ Right w ->
+ let w' = apply_edit e w
+ in case decode_message w' of
+ Left _ -> property True
+ Right m' -> encode_message m' === Right w'
+ ]