From bba1d4150ee8d4d4b4df2b91166dff564a756c09 Mon Sep 17 00:00:00 2001 From: ViniciusCestarii Date: Tue, 25 Aug 2026 08:55:13 -0300 Subject: [PATCH 1/3] test: cover IsFinalTx requires every input to be SEQUENCE_FINAL --- src/test/transaction_tests.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index cd81c3445db..7caacab9488 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -1167,6 +1167,29 @@ BOOST_AUTO_TEST_CASE(checktxinputs_invalid_transactions_test) TxValidationResult::TX_PREMATURE_SPEND, /*expected_reason=*/"bad-txns-premature-spend-of-coinbase"); } +BOOST_AUTO_TEST_CASE(isfinaltx_sequences_test) +{ + constexpr int height{100}; + + // Every transaction here has the same unsatisfied nLockTime, so only the + // sequences decide the outcome. + auto check_final{[](const std::vector& sequences, bool expected_final) { + CMutableTransaction mtx; + mtx.nLockTime = height; + for (const uint32_t sequence : sequences) { + mtx.vin.emplace_back(COutPoint{}, CScript{}, sequence); + } + + BOOST_CHECK_EQUAL(IsFinalTx(CTransaction{mtx}, /*nBlockHeight=*/height, /*nBlockTime=*/0), expected_final); + }}; + + check_final(/*sequences=*/{CTxIn::SEQUENCE_FINAL, CTxIn::SEQUENCE_FINAL}, /*expected_final=*/true); + + // nLockTime is only ignored when every input is SEQUENCE_FINAL + check_final(/*sequences=*/{CTxIn::SEQUENCE_FINAL, CTxIn::MAX_SEQUENCE_NONFINAL}, /*expected_final=*/false); + check_final(/*sequences=*/{CTxIn::MAX_SEQUENCE_NONFINAL, CTxIn::SEQUENCE_FINAL}, /*expected_final=*/false); +} + BOOST_AUTO_TEST_CASE(getvalueout_out_of_range_throws) { CMutableTransaction mtx; From a5fc82e2b1403b7bf0f1ad494a62ccff793f99d0 Mon Sep 17 00:00:00 2001 From: ViniciusCestarii Date: Tue, 25 Aug 2026 09:18:59 -0300 Subject: [PATCH 2/3] test: cover enforce BIP68 to tx versions higher than 2 --- src/test/transaction_tests.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 7caacab9488..beb0e740927 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -1190,6 +1191,30 @@ BOOST_AUTO_TEST_CASE(isfinaltx_sequences_test) check_final(/*sequences=*/{CTxIn::MAX_SEQUENCE_NONFINAL, CTxIn::SEQUENCE_FINAL}, /*expected_final=*/false); } +BOOST_AUTO_TEST_CASE(calculatesequencelocks_tx_version_test) +{ + constexpr int coin_height{100}; + + // A single input with a height-based relative locktime of one block. Only the + // height branch is taken, so the block index is never dereferenced. + auto check_min_height{[](uint32_t version, int expected_min_height) { + CMutableTransaction mtx; + mtx.version = version; + mtx.vin.emplace_back(COutPoint{}, CScript{}, /*nSequenceIn=*/1); + + std::vector prev_heights{coin_height}; + const CBlockIndex block{}; + const auto lock_pair{CalculateSequenceLocks(CTransaction{mtx}, LOCKTIME_VERIFY_SEQUENCE, prev_heights, block)}; + BOOST_CHECK_EQUAL(lock_pair.first, expected_min_height); + }}; + + // BIP68 only applies to versions 2 and up + check_min_height(/*version=*/0, /*expected_min_height=*/-1); + check_min_height(/*version=*/1, /*expected_min_height=*/-1); + check_min_height(/*version=*/2, /*expected_min_height=*/coin_height); + check_min_height(/*version=*/std::numeric_limits::max(), /*expected_min_height=*/coin_height); +} + BOOST_AUTO_TEST_CASE(getvalueout_out_of_range_throws) { CMutableTransaction mtx; From 5ce3a0b4aab5ad9ec710e803f88d79139b3b3c44 Mon Sep 17 00:00:00 2001 From: ViniciusCestarii Date: Tue, 25 Aug 2026 09:42:48 -0300 Subject: [PATCH 3/3] test: cover legacy sigops count CHECKMULTISIG inaccurately --- src/test/transaction_tests.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index beb0e740927..0e29024bab4 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -1129,6 +1129,22 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops) } } +BOOST_AUTO_TEST_CASE(getlegacysigopcount_inaccurate_test) +{ + // Legacy sigops are counted inaccurately in both the scriptSig and the + // scriptPubKey: a CHECKMULTISIG counts as MAX_PUBKEYS_PER_MULTISIG even when the + // preceding OP_N says it takes fewer keys. Counting it accurately would + // undercount, letting a block over the sigop limit through. + const CScript multisig{CScript() << OP_1 << OP_CHECKMULTISIG}; + + CMutableTransaction mtx; + mtx.vin.emplace_back(COutPoint{}, multisig); + BOOST_CHECK_EQUAL(GetLegacySigOpCount(CTransaction{mtx}), MAX_PUBKEYS_PER_MULTISIG); + + mtx.vout.emplace_back(0, multisig); + BOOST_CHECK_EQUAL(GetLegacySigOpCount(CTransaction{mtx}), 2 * MAX_PUBKEYS_PER_MULTISIG); +} + BOOST_AUTO_TEST_CASE(checktxinputs_invalid_transactions_test) { auto check_invalid{[](CAmount input_value, CAmount output_value, bool coinbase, int spend_height, TxValidationResult expected_result, std::string_view expected_reason) {