bolt5

On-chain transaction handling for Lightning (docs.ppad.tech/bolt5).
git clone git://git.ppad.tech/bolt5.git
Log | Files | Refs | README | LICENSE

commit 56ce6c5246ce245dae96cd3a1d2af8fb538af01d
parent 022e022a7106edc07d390c4d1d6a1249e94f2f57
Author: Jared Tobin <jared@jtobin.io>
Date:   Sun, 19 Apr 2026 12:58:39 +0800

test: add structural property tests

Adds property tests for: output+fee=input conservation,
fee monotonicity in feerate, classify output count preservation,
spending tx version 2 invariant, and revoked nSequence invariant.

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

diff --git a/test/Main.hs b/test/Main.hs @@ -932,6 +932,88 @@ property_tests = testGroup "Properties" [ then not (B5.htlc_timed_out height1 htlc) || B5.htlc_timed_out height2 htlc else True + + , testProperty "output + fee = input (spend_to_local)" $ + \(Positive val) -> + let value = Satoshi + (fromIntegral (val :: Int) + 100000) + fee = B5.spending_fee (FeeratePerKw 253) + (B5.to_local_penalty_input_weight + + B5.penalty_tx_base_weight) + in case B5.spend_to_local + dummyOutPoint value + dummyRevocationPubkey dummyDelay + dummyLocalDelayedPubkey + dummyDestScript (FeeratePerKw 253) of + Nothing -> False + Just stx -> + let tx = B5.stx_tx stx + outVal = txout_value + (head' (tx_outputs tx)) + in outVal + unSatoshi fee == + unSatoshi value + + , testProperty "fee monotonic in feerate" $ + \(Positive r1) (Positive r2) -> + let rate1 = fromIntegral (r1 :: Int) + rate2 = fromIntegral (r2 :: Int) + w = B5.to_local_penalty_input_weight + + B5.penalty_tx_base_weight + fee1 = B5.spending_fee + (FeeratePerKw rate1) w + fee2 = B5.spending_fee + (FeeratePerKw rate2) w + in if rate1 <= rate2 + then unSatoshi fee1 <= unSatoshi fee2 + else unSatoshi fee1 >= unSatoshi fee2 + + , testProperty "classify preserves output count" $ + \(NonNegative n') -> + let n = min (n' :: Int) 10 + outputs = + [ TxOutput (Satoshi 50000) toLocalScript + OutputToLocal + | _ <- [1..n] ] + commitTx = CommitmentTx + { ctx_version = 2 + , ctx_locktime = Locktime 0 + , ctx_input_outpoint = dummyOutPoint + , ctx_input_sequence = Sequence 0 + , ctx_outputs = outputs + , ctx_funding_script = dummyDestScript + } + outs = B5.classify_local_commit_outputs + commitTx dummyKeys dummyDelay + dummyFeatures [] + in length outs == n + + , testProperty "spend_to_local always version 2" $ + \(Positive val) -> + let value = Satoshi + (fromIntegral (val :: Int) + 100000) + in case B5.spend_to_local + dummyOutPoint value + dummyRevocationPubkey dummyDelay + dummyLocalDelayedPubkey + dummyDestScript (FeeratePerKw 253) of + Nothing -> False + Just stx -> + tx_version (B5.stx_tx stx) == 2 + + , testProperty "revoked nSequence is 0xFFFFFFFF" $ + \(Positive val) -> + let value = Satoshi + (fromIntegral (val :: Int) + 100000) + in case B5.spend_revoked_to_local + dummyOutPoint value + dummyRevocationPubkey dummyDelay + dummyLocalDelayedPubkey + dummyDestScript (FeeratePerKw 253) of + Nothing -> False + Just stx -> + let inp = head' + (tx_inputs (B5.stx_tx stx)) + in txin_sequence inp == 0xFFFFFFFF ] -- helpers ------------------------------------------------------------