From 3eb5a260bace6f1d394b9c094fd620006bcee4b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Mon, 12 Jan 2026 18:59:16 +0100 Subject: [PATCH 1/3] test: isolate `GetTxSigOpCost` scenarios The previous monolithic test mixed several independent vectors, so the first failure would stop coverage of the remaining ones. Split the vectors into focused test cases to make failures easier to diagnose and keep follow-up edits reviewable. It's best to view this commit without whitespace changes. --- src/test/sigopcount_tests.cpp | 241 ++++++++++++++++++++-------------- 1 file changed, 141 insertions(+), 100 deletions(-) diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index d3fec0f9357d..688453888309 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -107,128 +107,169 @@ static void BuildTxs(CMutableTransaction& spendingTx, CCoinsViewCache& coins, CM AddCoins(coins, CTransaction(creationTx), 0); } -BOOST_AUTO_TEST_CASE(GetTxSigOpCost) +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_Multisig) { - // Transaction creates outputs + // Multisig script (legacy counting) CMutableTransaction creationTx; - // Transaction that spends outputs and whose - // sig op cost is going to be tested CMutableTransaction spendingTx; - // Create utxo set CCoinsView coinsDummy; CCoinsViewCache coins(&coinsDummy); - // Create key CKey key = GenerateRandomKey(); CPubKey pubkey = key.GetPubKey(); - // Default flags const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; - // Multisig script (legacy counting) - { - CScript scriptPubKey = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; - // Do not use a valid signature to avoid using wallet operations. - CScript scriptSig = CScript() << OP_0 << OP_0; - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); - // Legacy counting only includes signature operations in scriptSigs and scriptPubKeys - // of a transaction and does not take the actual executed sig operations into account. - // spendingTx in itself does not contain a signature operation. - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); - // creationTx contains two signature operations in its scriptPubKey, but legacy counting - // is not accurate. - assert(GetTransactionSigOpCost(CTransaction(creationTx), coins, flags) == MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR); - // Sanity check: script verification fails because of an invalid signature. - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); - } + CScript scriptPubKey = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + // Do not use a valid signature to avoid using wallet operations. + CScript scriptSig = CScript() << OP_0 << OP_0; + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); + // Legacy counting only includes signature operations in scriptSigs and scriptPubKeys + // of a transaction and does not take the actual executed sig operations into account. + // spendingTx in itself does not contain a signature operation. + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); + // creationTx contains two signature operations in its scriptPubKey, but legacy counting + // is not accurate. + assert(GetTransactionSigOpCost(CTransaction(creationTx), coins, flags) == MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR); + // Sanity check: script verification fails because of an invalid signature. + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); +} +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_MultisigP2SH) +{ // Multisig nested in P2SH - { - CScript redeemScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; - CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); - CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript); + CMutableTransaction creationTx; + CMutableTransaction spendingTx; - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2 * WITNESS_SCALE_FACTOR); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; - // P2SH sigops are not counted if we don't set the SCRIPT_VERIFY_P2SH flag - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, /*flags=*/0) == 0); - } + CScript redeemScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); + CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2 * WITNESS_SCALE_FACTOR); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + + // P2SH sigops are not counted if we don't set the SCRIPT_VERIFY_P2SH flag + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, /*flags=*/0) == 0); +} +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH) +{ // P2WPKH witness program - { - CScript scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkey)); - CScript scriptSig = CScript(); - CScriptWitness scriptWitness; - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(0); - - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); - // No signature operations if we don't verify the witness. - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); - - // The sig op cost for witness version != 0 is zero. - assert(scriptPubKey[0] == 0x00); - scriptPubKey[0] = 0x51; - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); - scriptPubKey[0] = 0x00; - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - - // The witness of a coinbase transaction is not taken into account. - spendingTx.vin[0].prevout.SetNull(); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); - } + CMutableTransaction creationTx; + CMutableTransaction spendingTx; + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; + + CScript scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkey)); + CScript scriptSig = CScript(); + CScriptWitness scriptWitness; + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(0); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); + // No signature operations if we don't verify the witness. + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); + + // The sig op cost for witness version != 0 is zero. + assert(scriptPubKey[0] == 0x00); + scriptPubKey[0] = 0x51; + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); + scriptPubKey[0] = 0x00; + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + + // The witness of a coinbase transaction is not taken into account. + spendingTx.vin[0].prevout.SetNull(); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); +} + +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH_P2SH) +{ // P2WPKH nested in P2SH - { - CScript scriptSig = GetScriptForDestination(WitnessV0KeyHash(pubkey)); - CScript scriptPubKey = GetScriptForDestination(ScriptHash(scriptSig)); - scriptSig = CScript() << ToByteVector(scriptSig); - CScriptWitness scriptWitness; - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(0); - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); - } + CMutableTransaction creationTx; + CMutableTransaction spendingTx; + + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; + + CScript scriptSig = GetScriptForDestination(WitnessV0KeyHash(pubkey)); + CScript scriptPubKey = GetScriptForDestination(ScriptHash(scriptSig)); + scriptSig = CScript() << ToByteVector(scriptSig); + CScriptWitness scriptWitness; + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(0); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); +} +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH) +{ // P2WSH witness program - { - CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; - CScript scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); - CScript scriptSig = CScript(); - CScriptWitness scriptWitness; - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); - } + CMutableTransaction creationTx; + CMutableTransaction spendingTx; + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; + + CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); + CScript scriptSig = CScript(); + CScriptWitness scriptWitness; + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); +} + +BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH_P2SH) +{ // P2WSH nested in P2SH - { - CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; - CScript redeemScript = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); - CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); - CScript scriptSig = CScript() << ToByteVector(redeemScript); - CScriptWitness scriptWitness; - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(0); - scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); - - BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); - } + CMutableTransaction creationTx; + CMutableTransaction spendingTx; + + CCoinsView coinsDummy; + CCoinsViewCache coins(&coinsDummy); + CKey key = GenerateRandomKey(); + CPubKey pubkey = key.GetPubKey(); + const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; + + CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; + CScript redeemScript = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); + CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); + CScript scriptSig = CScript() << ToByteVector(redeemScript); + CScriptWitness scriptWitness; + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(0); + scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); + + BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); + assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); + assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); } BOOST_AUTO_TEST_SUITE_END() From 939e086c436ecc218dcc3a627c120daaaa0f088e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Mon, 12 Jan 2026 19:01:24 +0100 Subject: [PATCH 2/3] test: improve diagnostics for sigop cost tests `assert()` aborts the entire test run on the first failure, hiding which other vectors are broken and providing little context. Switch these checks to Boost assertions and reuse a shared set of standard script verification flags. --- src/test/sigopcount_tests.cpp | 50 ++++++++++++++++------------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index 688453888309..a38375b8c355 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -18,6 +18,8 @@ #include +static constexpr script_verify_flags STANDARD_SCRIPT_VERIFY_FLAGS{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; + // Helpers: static std::vector Serialize(const CScript& s) @@ -117,7 +119,6 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_Multisig) CCoinsViewCache coins(&coinsDummy); CKey key = GenerateRandomKey(); CPubKey pubkey = key.GetPubKey(); - const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; CScript scriptPubKey = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; // Do not use a valid signature to avoid using wallet operations. @@ -127,12 +128,12 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_Multisig) // Legacy counting only includes signature operations in scriptSigs and scriptPubKeys // of a transaction and does not take the actual executed sig operations into account. // spendingTx in itself does not contain a signature operation. - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); // creationTx contains two signature operations in its scriptPubKey, but legacy counting // is not accurate. - assert(GetTransactionSigOpCost(CTransaction(creationTx), coins, flags) == MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(creationTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR); // Sanity check: script verification fails because of an invalid signature. - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_CHECKMULTISIGVERIFY); } BOOST_AUTO_TEST_CASE(GetTxSigOpCost_MultisigP2SH) @@ -145,18 +146,17 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_MultisigP2SH) CCoinsViewCache coins(&coinsDummy); CKey key = GenerateRandomKey(); CPubKey pubkey = key.GetPubKey(); - const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; CScript redeemScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; CScript scriptPubKey = GetScriptForDestination(ScriptHash(redeemScript)); CScript scriptSig = CScript() << OP_0 << OP_0 << ToByteVector(redeemScript); BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, CScriptWitness()); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2 * WITNESS_SCALE_FACTOR); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 2 * WITNESS_SCALE_FACTOR); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_CHECKMULTISIGVERIFY); // P2SH sigops are not counted if we don't set the SCRIPT_VERIFY_P2SH flag - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, /*flags=*/0) == 0); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, /*flags=*/0), 0); } BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH) @@ -169,7 +169,6 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH) CCoinsViewCache coins(&coinsDummy); CKey key = GenerateRandomKey(); CPubKey pubkey = key.GetPubKey(); - const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; CScript scriptPubKey = GetScriptForDestination(WitnessV0KeyHash(pubkey)); CScript scriptSig = CScript(); @@ -178,22 +177,22 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH) scriptWitness.stack.emplace_back(0); BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 1); // No signature operations if we don't verify the witness. - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS & ~SCRIPT_VERIFY_WITNESS), 0); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_EQUALVERIFY); // The sig op cost for witness version != 0 is zero. - assert(scriptPubKey[0] == 0x00); - scriptPubKey[0] = 0x51; + BOOST_REQUIRE_EQUAL(scriptPubKey[0], OP_0); + scriptPubKey[0] = OP_1; BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); - scriptPubKey[0] = 0x00; + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); + scriptPubKey[0] = OP_0; BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); // The witness of a coinbase transaction is not taken into account. spendingTx.vin[0].prevout.SetNull(); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 0); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); } BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH_P2SH) @@ -206,7 +205,6 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH_P2SH) CCoinsViewCache coins(&coinsDummy); CKey key = GenerateRandomKey(); CPubKey pubkey = key.GetPubKey(); - const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; CScript scriptSig = GetScriptForDestination(WitnessV0KeyHash(pubkey)); CScript scriptPubKey = GetScriptForDestination(ScriptHash(scriptSig)); @@ -216,8 +214,8 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH_P2SH) scriptWitness.stack.emplace_back(0); BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 1); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_EQUALVERIFY); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 1); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_EQUALVERIFY); } BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH) @@ -230,7 +228,6 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH) CCoinsViewCache coins(&coinsDummy); CKey key = GenerateRandomKey(); CPubKey pubkey = key.GetPubKey(); - const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; CScript scriptPubKey = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); @@ -241,9 +238,9 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH) scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags & ~SCRIPT_VERIFY_WITNESS) == 0); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 2); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS & ~SCRIPT_VERIFY_WITNESS), 0); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_CHECKMULTISIGVERIFY); } BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH_P2SH) @@ -256,7 +253,6 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH_P2SH) CCoinsViewCache coins(&coinsDummy); CKey key = GenerateRandomKey(); CPubKey pubkey = key.GetPubKey(); - const script_verify_flags flags{SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_P2SH}; CScript witnessScript = CScript() << 1 << ToByteVector(pubkey) << ToByteVector(pubkey) << 2 << OP_CHECKMULTISIGVERIFY; CScript redeemScript = GetScriptForDestination(WitnessV0ScriptHash(witnessScript)); @@ -268,8 +264,8 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WSH_P2SH) scriptWitness.stack.emplace_back(witnessScript.begin(), witnessScript.end()); BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - assert(GetTransactionSigOpCost(CTransaction(spendingTx), coins, flags) == 2); - assert(VerifyWithFlag(CTransaction(creationTx), spendingTx, flags) == SCRIPT_ERR_CHECKMULTISIGVERIFY); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 2); + BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_CHECKMULTISIGVERIFY); } BOOST_AUTO_TEST_SUITE_END() From be4e60256e5e21b97c7f62c6f5a3acd59cf1229f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C5=91rinc?= Date: Mon, 12 Jan 2026 19:07:57 +0100 Subject: [PATCH 3/3] test: cover coinbase sigop accounting rules `GetTransactionSigOpCost` treats coinbase transactions specially: they only contribute legacy sigops, skipping P2SH and witness accounting and avoiding input lookups. Add explicit coverage for coinbase-shaped inputs in both witness and P2SH contexts, and assert the witness precondition so the intent stays clear. Co-authored-by: Gary Krause --- src/test/sigopcount_tests.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/test/sigopcount_tests.cpp b/src/test/sigopcount_tests.cpp index a38375b8c355..a36f8fe07c86 100644 --- a/src/test/sigopcount_tests.cpp +++ b/src/test/sigopcount_tests.cpp @@ -28,6 +28,13 @@ Serialize(const CScript& s) return sSerialized; } +static CTransaction MakeCoinBase(const CMutableTransaction& tx) +{ + CMutableTransaction coinbase{tx}; + coinbase.vin[0].prevout.SetNull(); + return CTransaction{coinbase}; +} + BOOST_FIXTURE_TEST_SUITE(sigopcount_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(GetSigOpCount) @@ -129,8 +136,8 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_Multisig) // of a transaction and does not take the actual executed sig operations into account. // spendingTx in itself does not contain a signature operation. BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); - // creationTx contains two signature operations in its scriptPubKey, but legacy counting - // is not accurate. + // Even though creationTx is a coinbase, its legacy sigops in scriptPubKey are counted + // (and legacy counting is not accurate). BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(creationTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), MAX_PUBKEYS_PER_MULTISIG * WITNESS_SCALE_FACTOR); // Sanity check: script verification fails because of an invalid signature. BOOST_CHECK_EQUAL(VerifyWithFlag(CTransaction(creationTx), spendingTx, STANDARD_SCRIPT_VERIFY_FLAGS), SCRIPT_ERR_CHECKMULTISIGVERIFY); @@ -157,6 +164,9 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_MultisigP2SH) // P2SH sigops are not counted if we don't set the SCRIPT_VERIFY_P2SH flag BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, /*flags=*/0), 0); + + // For coinbase transactions, GetTransactionSigOpCost only includes legacy sigops (no P2SH/witness). + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(MakeCoinBase(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); } BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH) @@ -190,9 +200,9 @@ BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH) scriptPubKey[0] = OP_0; BuildTxs(spendingTx, coins, creationTx, scriptPubKey, scriptSig, scriptWitness); - // The witness of a coinbase transaction is not taken into account. - spendingTx.vin[0].prevout.SetNull(); - BOOST_CHECK_EQUAL(GetTransactionSigOpCost(CTransaction(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); + // For coinbase transactions, GetTransactionSigOpCost only includes legacy sigops (no P2SH/witness). + BOOST_REQUIRE(!spendingTx.vin[0].scriptWitness.IsNull()); + BOOST_CHECK_EQUAL(GetTransactionSigOpCost(MakeCoinBase(spendingTx), coins, STANDARD_SCRIPT_VERIFY_FLAGS), 0); } BOOST_AUTO_TEST_CASE(GetTxSigOpCost_P2WPKH_P2SH)