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

@@ -18,6 +18,7 @@
#include <script/solver.h>
#include <serialize.h>
#include <span.h>
#include <tinyformat.h>
#include <algorithm>
#include <cstddef>
@@ -210,14 +211,16 @@ static bool CheckSigopsBIP54(const CTransaction& tx, const CCoinsViewCache& inpu
*
* We also check the total number of non-witness sigops across the whole transaction, as per BIP54.
*/
bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
TxValidationState ValidateInputsStandardness(const CTransaction& tx, const CCoinsViewCache& mapInputs)
{
TxValidationState state;
if (tx.IsCoinBase()) {
return true; // Coinbases don't use vin normally
return state; // Coinbases don't use vin normally
}
if (!CheckSigopsBIP54(tx, mapInputs)) {
return false;
state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", "non-witness sigops exceed bip54 limit");
return state;
}
for (unsigned int i = 0; i < tx.vin.size(); i++) {
@@ -225,27 +228,38 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
std::vector<std::vector<unsigned char> > vSolutions;
TxoutType whichType = Solver(prev.scriptPubKey, vSolutions);
if (whichType == TxoutType::NONSTANDARD || whichType == TxoutType::WITNESS_UNKNOWN) {
if (whichType == TxoutType::NONSTANDARD) {
state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", strprintf("input %u script unknown", i));
return state;
} else if (whichType == TxoutType::WITNESS_UNKNOWN) {
// WITNESS_UNKNOWN failures are typically also caught with a policy
// flag in the script interpreter, but it can be helpful to catch
// this type of NONSTANDARD transaction earlier in transaction
// validation.
return false;
state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", strprintf("input %u witness program is undefined", i));
return state;
} else if (whichType == TxoutType::SCRIPTHASH) {
std::vector<std::vector<unsigned char> > stack;
ScriptError serror;
// convert the scriptSig into a stack, so we can inspect the redeemScript
if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE))
return false;
if (stack.empty())
return false;
if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE, &serror)) {
state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", strprintf("p2sh scriptsig malformed (input %u: %s)", i, ScriptErrorString(serror)));
return state;
}
if (stack.empty()) {
state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", strprintf("input %u P2SH redeemscript missing", i));
return state;
}
CScript subscript(stack.back().begin(), stack.back().end());
if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) {
return false;
unsigned int sigop_count = subscript.GetSigOpCount(true);
if (sigop_count > MAX_P2SH_SIGOPS) {
state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs", strprintf("p2sh redeemscript sigops exceed limit (input %u: %u > %u)", i, sigop_count, MAX_P2SH_SIGOPS));
return state;
}
}
}
return true;
return state;
}
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
@@ -354,7 +368,7 @@ bool SpendsNonAnchorWitnessProg(const CTransaction& tx, const CCoinsViewCache& p
}
// For P2SH extract the redeem script and check if it spends a non-Taproot witness program. Note
// this is fine to call EvalScript (as done in AreInputsStandard/IsWitnessStandard) because this
// this is fine to call EvalScript (as done in ValidateInputsStandardness/IsWitnessStandard) because this
// function is only ever called after IsStandardTx, which checks the scriptsig is pushonly.
if (prev_spk.IsPayToScriptHash()) {
// If EvalScript fails or results in an empty stack, the transaction is invalid by consensus.

View File

@@ -8,6 +8,7 @@
#include <consensus/amount.h>
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <primitives/transaction.h>
#include <script/interpreter.h>
#include <script/solver.h>
@@ -157,11 +158,12 @@ static constexpr decltype(CTransaction::version) TX_MAX_STANDARD_VERSION{3};
*/
bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason);
/**
* Check for standard transaction types
* @param[in] mapInputs Map of previous transactions that have outputs we're spending
* @return True if all inputs (scriptSigs) use only standard transaction forms
*/
bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs);
* Check for standard transaction types
* @param[in] mapInputs Map of previous transactions that have outputs we're spending
* @returns valid TxValidationState if all inputs (scriptSigs) use only standard transaction forms else returns
* invalid TxValidationState which states why the first invalid input is not standard
*/
TxValidationState ValidateInputsStandardness(const CTransaction& tx, const CCoinsViewCache& mapInputs);
/**
* Check if the transaction is over standard P2WSH resources limit:
* 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements