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

@@ -275,7 +275,7 @@ BOOST_AUTO_TEST_CASE(switchover)
BOOST_CHECK_MESSAGE(err == SCRIPT_ERR_EQUALVERIFY, ScriptErrorString(err));
}
BOOST_AUTO_TEST_CASE(AreInputsStandard)
BOOST_AUTO_TEST_CASE(ValidateInputsStandardness)
{
CCoinsView coinsDummy;
CCoinsViewCache coins(&coinsDummy);
@@ -292,7 +292,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
keys.push_back(key[i].GetPubKey());
CMutableTransaction txFrom;
txFrom.vout.resize(7);
txFrom.vout.resize(10);
// First three are standard:
CScript pay1 = GetScriptForDestination(PKHash(key[0].GetPubKey()));
@@ -334,7 +334,24 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
CScript twentySigops; twentySigops << OP_CHECKMULTISIG;
BOOST_CHECK(keystore.AddCScript(twentySigops));
txFrom.vout[6].scriptPubKey = GetScriptForDestination(ScriptHash(twentySigops));
txFrom.vout[6].nValue = 6000;
txFrom.vout[6].nValue = 3000;
// vout[7] is non-standard because it lacks sigops
CScript no_sigops;
txFrom.vout[7].scriptPubKey = no_sigops;
txFrom.vout[7].nValue = 1000;
// vout [8] is non-standard because it contains OP_RETURN in its redeemScript.
static const unsigned char op_return[] = {OP_RETURN};
const auto op_return_script = CScript(op_return, op_return + sizeof(op_return));
txFrom.vout[8].scriptPubKey = GetScriptForDestination(ScriptHash(op_return_script));
txFrom.vout[8].nValue = 1000;
// vout[9] is non-standard because its witness is unknown
CScript witnessUnknown;
witnessUnknown << OP_16 << ToByteVector(uint256::ONE);
txFrom.vout[9].scriptPubKey = witnessUnknown;
txFrom.vout[9].nValue = 1000;
AddCoins(coins, CTransaction(txFrom), 0);
@@ -360,7 +377,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
txTo.vin[3].scriptSig << OP_11 << OP_11 << std::vector<unsigned char>(oneAndTwo.begin(), oneAndTwo.end());
txTo.vin[4].scriptSig << std::vector<unsigned char>(fifteenSigops.begin(), fifteenSigops.end());
BOOST_CHECK(::AreInputsStandard(CTransaction(txTo), coins));
BOOST_CHECK(::ValidateInputsStandardness(CTransaction(txTo), coins).IsValid());
// 22 P2SH sigops for all inputs (1 for vin[0], 6 for vin[3], 15 for vin[4]
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txTo), coins), 22U);
@@ -370,6 +387,7 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
BOOST_CHECK(coinbase_tx.IsCoinBase());
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(coinbase_tx, coins), 0U);
// TxoutType::SCRIPTHASH
CMutableTransaction txToNonStd1;
txToNonStd1.vout.resize(1);
txToNonStd1.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
@@ -379,7 +397,11 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
txToNonStd1.vin[0].prevout.hash = txFrom.GetHash();
txToNonStd1.vin[0].scriptSig << std::vector<unsigned char>(sixteenSigops.begin(), sixteenSigops.end());
BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd1), coins));
const auto txToNonStd1_res = ::ValidateInputsStandardness(CTransaction(txToNonStd1), coins);
BOOST_CHECK(txToNonStd1_res.IsInvalid());
BOOST_CHECK_EQUAL(txToNonStd1_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
BOOST_CHECK_EQUAL(txToNonStd1_res.GetDebugMessage(), "p2sh redeemscript sigops exceed limit (input 0: 16 > 15)");
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd1), coins), 16U);
CMutableTransaction txToNonStd2;
@@ -391,8 +413,67 @@ BOOST_AUTO_TEST_CASE(AreInputsStandard)
txToNonStd2.vin[0].prevout.hash = txFrom.GetHash();
txToNonStd2.vin[0].scriptSig << std::vector<unsigned char>(twentySigops.begin(), twentySigops.end());
BOOST_CHECK(!::AreInputsStandard(CTransaction(txToNonStd2), coins));
const auto txToNonStd2_res = ::ValidateInputsStandardness(CTransaction(txToNonStd2), coins);
BOOST_CHECK(txToNonStd2_res.IsInvalid());
BOOST_CHECK_EQUAL(txToNonStd2_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
BOOST_CHECK_EQUAL(txToNonStd2_res.GetDebugMessage(), "p2sh redeemscript sigops exceed limit (input 0: 20 > 15)");
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd2), coins), 20U);
CMutableTransaction txToNonStd2_no_scriptSig;
txToNonStd2_no_scriptSig.vout.resize(1);
txToNonStd2_no_scriptSig.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
txToNonStd2_no_scriptSig.vout[0].nValue = 1000;
txToNonStd2_no_scriptSig.vin.resize(1);
txToNonStd2_no_scriptSig.vin[0].prevout.n = 6;
txToNonStd2_no_scriptSig.vin[0].prevout.hash = txFrom.GetHash();
const auto txToNonStd2_no_scriptSig_res = ::ValidateInputsStandardness(CTransaction(txToNonStd2_no_scriptSig), coins);
BOOST_CHECK(txToNonStd2_no_scriptSig_res.IsInvalid());
BOOST_CHECK_EQUAL(txToNonStd2_no_scriptSig_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
BOOST_CHECK_EQUAL(txToNonStd2_no_scriptSig_res.GetDebugMessage(), "input 0 P2SH redeemscript missing");
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd2), coins), 20U);
// TxoutType::NONSTANDARD
CMutableTransaction txToNonStd3;
txToNonStd3.vout.resize(1);
txToNonStd3.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
txToNonStd3.vout[0].nValue = 1000;
txToNonStd3.vin.resize(1);
txToNonStd3.vin[0].prevout.n = 7;
txToNonStd3.vin[0].prevout.hash = txFrom.GetHash();
const auto txToNonStd3_res = ::ValidateInputsStandardness(CTransaction(txToNonStd3), coins);
BOOST_CHECK(txToNonStd3_res.IsInvalid());
BOOST_CHECK_EQUAL(txToNonStd3_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
BOOST_CHECK_EQUAL(txToNonStd3_res.GetDebugMessage(), "input 0 script unknown");
// TxoutType::INCORRECT_SCRIPTSIG
CMutableTransaction txToNonStd4;
txToNonStd4.vout.resize(1);
txToNonStd4.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
txToNonStd4.vout[0].nValue = 1000;
txToNonStd4.vin.resize(1);
txToNonStd4.vin[0].prevout.n = 8;
txToNonStd4.vin[0].prevout.hash = txFrom.GetHash();
txToNonStd4.vin[0].scriptSig = op_return_script;
const auto txToNonStd4_res = ::ValidateInputsStandardness(CTransaction(txToNonStd4), coins);
BOOST_CHECK(txToNonStd4_res.IsInvalid());
BOOST_CHECK_EQUAL(txToNonStd4_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
BOOST_CHECK_EQUAL(txToNonStd4_res.GetDebugMessage(), "p2sh scriptsig malformed (input 0: OP_RETURN was encountered)");
// TxoutType::WITNESS_UNKNOWN
CMutableTransaction txWitnessUnknown;
txWitnessUnknown.vout.resize(1);
txWitnessUnknown.vout[0].scriptPubKey = GetScriptForDestination(PKHash(key[1].GetPubKey()));
txWitnessUnknown.vout[0].nValue = 1000;
txWitnessUnknown.vin.resize(1);
txWitnessUnknown.vin[0].prevout.n = 9;
txWitnessUnknown.vin[0].prevout.hash = txFrom.GetHash();
const auto txWitnessUnknown_res = ::ValidateInputsStandardness(CTransaction(txWitnessUnknown), coins);
BOOST_CHECK(txWitnessUnknown_res.IsInvalid());
BOOST_CHECK_EQUAL(txWitnessUnknown_res.GetRejectReason(), "bad-txns-nonstandard-inputs");
BOOST_CHECK_EQUAL(txWitnessUnknown_res.GetDebugMessage(), "input 0 witness program is undefined");
}
BOOST_AUTO_TEST_SUITE_END()