Merge bitcoin/bitcoin#29060: Policy: Report debug message why inputs are non standard

d8f4e7caf0 doc: add release notes (ismaelsadeeq)
248c175e3d test: ensure `ValidateInputsStandardness` optionally returns debug string (ismaelsadeeq)
d2716e9e5b policy: update `AreInputsStandard` to return error string (ismaelsadeeq)

Pull request description:

  This PR is another attempt at  #13525.

  Transactions that fail `PreChecks` Validation due to non-standard inputs now  returns invalid validation state`TxValidationResult::TX_INPUTS_NOT_STANDARD` along with a debug error message.

  Previously, the debug error message for non-standard inputs do not specify why the inputs were considered non-standard.
  Instead, the same error string, `bad-txns-nonstandard-inputs`, used for all types of non-standard input scriptSigs.

  This PR updates the `AreInputsStandard`  to include the reason why inputs are non-standard in the debug message.
  This improves the `Precheck` debug message to be more descriptive.

  Furthermore, I have addressed all remaining comments from #13525 in this PR.

ACKs for top commit:
  instagibbs:
    ACK d8f4e7caf0
  achow101:
    ACK d8f4e7caf0
  sedited:
    Re-ACK d8f4e7caf0

Tree-SHA512: 19b1a73c68584522f863b9ee2c8d3a735348667f3628dc51e36be3ba59158509509fcc1ffc5683555112c09c8b14da3ad140bb879eac629b6f60b8313cfd8b91
This commit is contained in:
Ava Chow
2026-03-19 15:43:18 -07:00
10 changed files with 164 additions and 40 deletions

View File

@@ -412,7 +412,7 @@ BOOST_AUTO_TEST_CASE(test_Get)
t1.vout[0].nValue = 90*CENT;
t1.vout[0].scriptPubKey << OP_1;
BOOST_CHECK(AreInputsStandard(CTransaction(t1), coins));
BOOST_CHECK(ValidateInputsStandardness(CTransaction(t1), coins).IsValid());
}
static void CreateCreditAndSpend(const FillableSigningProvider& keystore, const CScript& outscript, CTransactionRef& output, CMutableTransaction& input, bool success = true)
@@ -1053,7 +1053,7 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
// 2490 sigops is below the limit.
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(tx_max_sigops), coins), 2490);
BOOST_CHECK(::AreInputsStandard(CTransaction(tx_max_sigops), coins));
BOOST_CHECK(::ValidateInputsStandardness(CTransaction(tx_max_sigops), coins).IsValid());
// Adding one more input will bump this to 2505, hitting the limit.
tx_create.vout.emplace_back(424242, max_sigops_p2sh);
@@ -1064,8 +1064,17 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
tx_max_sigops.vin.emplace_back(prev_txid, p2sh_inputs_count, CScript() << ToByteVector(max_sigops_redeem_script));
AddCoins(coins, CTransaction(tx_create), 0, false);
BOOST_CHECK_GT((p2sh_inputs_count + 1) * MAX_P2SH_SIGOPS, MAX_TX_LEGACY_SIGOPS);
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(tx_max_sigops), coins), 2505);
BOOST_CHECK(!::AreInputsStandard(CTransaction(tx_max_sigops), coins));
auto legacy_sigops_count = GetP2SHSigOpCount(CTransaction(tx_max_sigops), coins);
BOOST_CHECK_EQUAL(legacy_sigops_count, 2505);
std::string reject_reason("bad-txns-nonstandard-inputs");
std::string sigop_limit_reject_debug_message("non-witness sigops exceed bip54 limit");
{
auto validation_state = ValidateInputsStandardness(CTransaction(tx_max_sigops), coins);
BOOST_CHECK(validation_state.IsInvalid());
BOOST_CHECK_EQUAL(validation_state.GetRejectReason(), reject_reason);
BOOST_CHECK_EQUAL(validation_state.GetDebugMessage(), sigop_limit_reject_debug_message);
}
// Now, check the limit can be reached with regular P2PK outputs too. Use a separate
// preparation transaction, to demonstrate spending coins from a single tx is irrelevant.
@@ -1083,8 +1092,7 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
AddCoins(coins, CTransaction(tx_create_p2pk), 0, false);
// The transaction now contains exactly 2500 sigops, the check should pass.
BOOST_CHECK_EQUAL(p2sh_inputs_count * MAX_P2SH_SIGOPS + p2pk_inputs_count * 1, MAX_TX_LEGACY_SIGOPS);
BOOST_CHECK(::AreInputsStandard(CTransaction(tx_max_sigops), coins));
BOOST_CHECK(::ValidateInputsStandardness(CTransaction(tx_max_sigops), coins).IsValid());
// Now, add some Segwit inputs. We add one for each defined Segwit output type. The limit
// is exclusively on non-witness sigops and therefore those should not be counted.
@@ -1100,7 +1108,7 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
// The transaction now still contains exactly 2500 sigops, the check should pass.
AddCoins(coins, CTransaction(tx_create_segwit), 0, false);
BOOST_REQUIRE(::AreInputsStandard(CTransaction(tx_max_sigops), coins));
BOOST_REQUIRE(::ValidateInputsStandardness(CTransaction(tx_max_sigops), coins).IsValid());
// Add one more P2PK input. We'll reach the limit.
tx_create_p2pk.vout.emplace_back(212121, p2pk_script);
@@ -1111,8 +1119,14 @@ BOOST_AUTO_TEST_CASE(max_standard_legacy_sigops)
tx_max_sigops.vin.emplace_back(prev_txid, i);
}
AddCoins(coins, CTransaction(tx_create_p2pk), 0, false);
BOOST_CHECK_GT(p2sh_inputs_count * MAX_P2SH_SIGOPS + p2pk_inputs_count * 1, MAX_TX_LEGACY_SIGOPS);
BOOST_CHECK(!::AreInputsStandard(CTransaction(tx_max_sigops), coins));
auto legacy_sigop_count_p2pk = p2sh_inputs_count * MAX_P2SH_SIGOPS + p2pk_inputs_count * 1;
BOOST_CHECK_GT(legacy_sigop_count_p2pk, MAX_TX_LEGACY_SIGOPS);
{
auto validation_state = ValidateInputsStandardness(CTransaction(tx_max_sigops), coins);
BOOST_CHECK(validation_state.IsInvalid());
BOOST_CHECK_EQUAL(validation_state.GetRejectReason(), reject_reason);
BOOST_CHECK_EQUAL(validation_state.GetDebugMessage(), sigop_limit_reject_debug_message);
}
}
BOOST_AUTO_TEST_CASE(checktxinputs_invalid_transactions_test)