bolt2

Lightning peer protocol, per BOLT #2.
git clone git://git.ppad.tech/bolt2.git
Log | Files | Refs | README | LICENSE

commit 93521021143220e4829968a962b44f1f82922258
parent 3fca308b65c9f8a40c88c2bedb53eb3bcfd1e412
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 25 Jan 2026 11:15:46 +0400

Merge impl/tests: comprehensive tests for all BOLT2 codecs

Adds unit tests, error condition tests, and property tests for all 29
BOLT2 message types.

86 test cases covering:
- Roundtrip encode/decode for all messages
- Insufficient bytes errors
- EncodeLengthOverflow for Shutdown, ClosingComplete, ClosingSig
- Property-based roundtrip tests with QuickCheck

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

diff --git a/test/Main.hs b/test/Main.hs @@ -4,6 +4,9 @@ module Main where 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(..)) import Lightning.Protocol.BOLT2 import Test.Tasty import Test.Tasty.HUnit @@ -11,8 +14,1253 @@ import Test.Tasty.QuickCheck main :: IO () main = defaultMain $ testGroup "ppad-bolt2" [ + v1_establishment_tests + , v2_establishment_tests + , close_tests + , normal_operation_tests + , reestablish_tests + , error_tests + , property_tests ] +-- 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 $ txId (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) + +-- | Empty TLV stream for messages. +emptyTlvs :: TlvStream +emptyTlvs = TlvStream [] + +-- 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 + , openChannelFundingSatoshis = Satoshis 1000000 + , openChannelPushMsat = MilliSatoshis 500000 + , openChannelDustLimitSatoshis = Satoshis 546 + , openChannelMaxHtlcValueInFlight = MilliSatoshis 100000000 + , openChannelChannelReserveSat = Satoshis 10000 + , openChannelHtlcMinimumMsat = MilliSatoshis 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 + , acceptChannelDustLimitSatoshis = Satoshis 546 + , acceptChannelMaxHtlcValueInFlight = MilliSatoshis 100000000 + , acceptChannelChannelReserveSat = Satoshis 10000 + , acceptChannelHtlcMinimumMsat = MilliSatoshis 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 + ] + ] + +-- 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 + , openChannel2FundingSatoshis = Satoshis 1000000 + , openChannel2DustLimitSatoshis = Satoshis 546 + , openChannel2MaxHtlcValueInFlight = MilliSatoshis 100000000 + , openChannel2HtlcMinimumMsat = MilliSatoshis 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 + , acceptChannel2FundingSatoshis = Satoshis 500000 + , acceptChannel2DustLimitSatoshis = Satoshis 546 + , acceptChannel2MaxHtlcValueInFlight = MilliSatoshis 100000000 + , acceptChannel2HtlcMinimumMsat = MilliSatoshis 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 = 12345 + , txAddInputPrevTx = BS.pack [0x01, 0x02, 0x03, 0x04] + , txAddInputPrevVout = 0 + , txAddInputSequence = 0xfffffffe + } + encoded = encodeTxAddInput msg + 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 = 0 + , txAddInputPrevTx = BS.empty + , txAddInputPrevVout = 0 + , txAddInputSequence = 0 + } + encoded = encodeTxAddInput msg + 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 = 54321 + , txAddOutputSats = Satoshis 100000 + , txAddOutputScript = scriptPubKey (BS.pack [0x00, 0x14] <> + BS.replicate 20 0xaa) + } + encoded = encodeTxAddOutput msg + 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 = 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 = 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 = [] + } + encoded = encodeTxSignatures msg + 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] + } + encoded = encodeTxSignatures msg + 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" + } + encoded = encodeTxAbort msg + 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 + } + encoded = encodeTxAbort msg + case decodeTxAbort encoded of + Right (decoded, _) -> decoded @?= msg + Left e -> assertFailure $ "decode failed: " ++ show e + ] + ] + +-- Channel Close Tests --------------------------------------------------------- + +close_tests :: TestTree +close_tests = testGroup "Channel Close" [ + testGroup "Stfu" [ + testCase "encode/decode initiator=1" $ do + let msg = Stfu + { stfuChannelId = testChannelId + , stfuInitiator = 1 + } + encoded = encodeStfu msg + case decodeStfu encoded of + Right (decoded, _) -> decoded @?= msg + Left e -> assertFailure $ "decode failed: " ++ show e + , testCase "encode/decode initiator=0" $ do + let msg = Stfu + { stfuChannelId = testChannelId + , stfuInitiator = 0 + } + 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 + , closingSignedFeeSatoshis = Satoshis 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 + , closingCompleteFeeSatoshis = Satoshis 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 + , closingSigFeeSatoshis = Satoshis 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 + ] + ] + +-- 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 = 0 + , updateAddHtlcAmountMsat = MilliSatoshis 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 = 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 = 42 + , updateFailHtlcReason = BS.replicate 32 0xaa + , updateFailHtlcTlvs = emptyTlvs + } + encoded = encodeUpdateFailHtlc msg + 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 = 0 + , updateFailHtlcReason = BS.empty + , updateFailHtlcTlvs = emptyTlvs + } + encoded = encodeUpdateFailHtlc msg + 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 = 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 = [] + } + encoded = encodeCommitmentSigned msg + 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] + } + encoded = encodeCommitmentSigned msg + 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 = BS.replicate 32 0x11 + , 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 + ] + ] + +-- Reestablish Tests ----------------------------------------------------------- + +reestablish_tests :: TestTree +reestablish_tests = testGroup "Channel Reestablish" [ + testCase "encode/decode roundtrip" $ do + let msg = ChannelReestablish + { channelReestablishChannelId = testChannelId + , channelReestablishNextCommitNum = 5 + , channelReestablishNextRevocationNum = 4 + , channelReestablishYourLastCommitSecret = BS.replicate 32 0x22 + , 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 msg = ChannelReestablish + { channelReestablishChannelId = testChannelId + , channelReestablishNextCommitNum = 1 + , channelReestablishNextRevocationNum = 0 + , channelReestablishYourLastCommitSecret = BS.replicate 32 0x00 + , channelReestablishMyCurrentCommitPoint = testPoint + , channelReestablishTlvs = emptyTlvs + } + encoded = encodeChannelReestablish msg + case decodeChannelReestablish encoded of + Right (decoded, _) -> decoded @?= msg + Left e -> assertFailure $ "decode failed: " ++ show e + ] + +-- 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 "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 + , closingCompleteFeeSatoshis = Satoshis 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 + , closingCompleteFeeSatoshis = Satoshis 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 + , closingSigFeeSatoshis = Satoshis 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 + ] + ] + +-- 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 + ] + +-- Property: OpenChannel roundtrip +propOpenChannelRoundtrip :: Property +propOpenChannelRoundtrip = property $ do + let msg = OpenChannel + { openChannelChainHash = testChainHash + , openChannelTempChannelId = testChannelId + , openChannelFundingSatoshis = Satoshis 1000000 + , openChannelPushMsat = MilliSatoshis 500000 + , openChannelDustLimitSatoshis = Satoshis 546 + , openChannelMaxHtlcValueInFlight = MilliSatoshis 100000000 + , openChannelChannelReserveSat = Satoshis 10000 + , openChannelHtlcMinimumMsat = MilliSatoshis 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 + , acceptChannelDustLimitSatoshis = Satoshis 546 + , acceptChannelMaxHtlcValueInFlight = MilliSatoshis 100000000 + , acceptChannelChannelReserveSat = Satoshis 10000 + , acceptChannelHtlcMinimumMsat = MilliSatoshis 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 + , openChannel2FundingSatoshis = Satoshis 1000000 + , openChannel2DustLimitSatoshis = Satoshis 546 + , openChannel2MaxHtlcValueInFlight = MilliSatoshis 100000000 + , openChannel2HtlcMinimumMsat = MilliSatoshis 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 + , acceptChannel2FundingSatoshis = Satoshis 500000 + , acceptChannel2DustLimitSatoshis = Satoshis 546 + , acceptChannel2MaxHtlcValueInFlight = MilliSatoshis 100000000 + , acceptChannel2HtlcMinimumMsat = MilliSatoshis 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 = 12345 + , txAddInputPrevTx = prevTx + , txAddInputPrevVout = vout + , txAddInputSequence = seqNum + } + encoded = encodeTxAddInput msg + 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 = 54321 + , txAddOutputSats = Satoshis sats + , txAddOutputScript = script + } + encoded = encodeTxAddOutput msg + case decodeTxAddOutput encoded of + Right (decoded, _) -> decoded == msg + Left _ -> False + +-- Property: TxRemoveInput roundtrip +propTxRemoveInputRoundtrip :: Word64 -> Property +propTxRemoveInputRoundtrip serialId = property $ do + let msg = TxRemoveInput + { txRemoveInputChannelId = testChannelId + , txRemoveInputSerialId = serialId + } + encoded = encodeTxRemoveInput msg + case decodeTxRemoveInput encoded of + Right (decoded, _) -> decoded == msg + Left _ -> False + +-- Property: TxRemoveOutput roundtrip +propTxRemoveOutputRoundtrip :: Word64 -> Property +propTxRemoveOutputRoundtrip serialId = property $ do + let msg = TxRemoveOutput + { txRemoveOutputChannelId = testChannelId + , txRemoveOutputSerialId = serialId + } + 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 + } + encoded = encodeTxSignatures msg + 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 + } + encoded = encodeTxAbort msg + case decodeTxAbort encoded of + Right (decoded, _) -> decoded == msg + Left _ -> False + +-- Property: Stfu roundtrip +propStfuRoundtrip :: Word8 -> Property +propStfuRoundtrip initiator = property $ do + let msg = Stfu + { stfuChannelId = testChannelId + , stfuInitiator = initiator + } + 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 + , closingSignedFeeSatoshis = Satoshis 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 + , closingCompleteFeeSatoshis = Satoshis 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 + , closingSigFeeSatoshis = Satoshis 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 htlcId amountMsat cltvExpiry = property $ do + let msg = UpdateAddHtlc + { updateAddHtlcChannelId = testChannelId + , updateAddHtlcId = htlcId + , updateAddHtlcAmountMsat = MilliSatoshis 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 htlcId = property $ do + let msg = UpdateFulfillHtlc + { updateFulfillHtlcChannelId = testChannelId + , updateFulfillHtlcId = htlcId + , 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 htlcId reasonBytes = property $ do + let failReason = BS.pack (take 1000 reasonBytes) + msg = UpdateFailHtlc + { updateFailHtlcChannelId = testChannelId + , updateFailHtlcId = htlcId + , updateFailHtlcReason = failReason + , updateFailHtlcTlvs = emptyTlvs + } + encoded = encodeUpdateFailHtlc msg + case decodeUpdateFailHtlc encoded of + Right (decoded, _) -> decoded == msg + Left _ -> False + +-- Property: UpdateFailMalformedHtlc roundtrip +propUpdateFailMalformedHtlcRoundtrip :: Word64 -> Word16 -> Property +propUpdateFailMalformedHtlcRoundtrip htlcId failCode = property $ do + let msg = UpdateFailMalformedHtlc + { updateFailMalformedHtlcChannelId = testChannelId + , updateFailMalformedHtlcId = htlcId + , 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 + } + encoded = encodeCommitmentSigned msg + case decodeCommitmentSigned encoded of + Right (decoded, _) -> decoded == msg + Left _ -> False + +-- Property: RevokeAndAck roundtrip +propRevokeAndAckRoundtrip :: Property +propRevokeAndAckRoundtrip = property $ do + let msg = RevokeAndAck + { revokeAndAckChannelId = testChannelId + , revokeAndAckPerCommitmentSecret = BS.replicate 32 0x11 + , 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 msg = ChannelReestablish + { channelReestablishChannelId = testChannelId + , channelReestablishNextCommitNum = nextCommit + , channelReestablishNextRevocationNum = nextRevoke + , channelReestablishYourLastCommitSecret = BS.replicate 32 0x22 + , channelReestablishMyCurrentCommitPoint = testPoint + , channelReestablishTlvs = emptyTlvs + } + encoded = encodeChannelReestablish msg + case decodeChannelReestablish encoded of + Right (decoded, _) -> decoded == msg + Left _ -> False + -- Helpers --------------------------------------------------------------------- -- | Decode hex string. Fails the test on invalid hex.