mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-01 00:34:01 +02:00
policy: update AreInputsStandard to return error string
This commit renames AreInputsStandard to ValidateInputsStandardness. ValidateInputsStandardness now returns valid TxValidationState if all inputs (scriptSigs) use only standard transaction forms else returns invalid TxValidationState which states why an input is not standard.
This commit is contained in:
committed by
ismaelsadeeq
parent
563747971b
commit
d2716e9e5b
@@ -49,8 +49,7 @@ static void CCoinsCaching(benchmark::Bench& bench)
|
||||
// Benchmark.
|
||||
const CTransaction tx_1(t1);
|
||||
bench.run([&] {
|
||||
bool success{AreInputsStandard(tx_1, coins)};
|
||||
assert(success);
|
||||
assert(ValidateInputsStandardness(tx_1, coins).IsValid());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
@@ -154,11 +155,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
|
||||
|
||||
@@ -252,7 +252,7 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsView& backend
|
||||
assert(expected_code_path);
|
||||
},
|
||||
[&] {
|
||||
(void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
|
||||
(void)ValidateInputsStandardness(CTransaction{random_mutable_transaction}, coins_view_cache);
|
||||
},
|
||||
[&] {
|
||||
TxValidationState state;
|
||||
|
||||
@@ -89,7 +89,7 @@ FUZZ_TARGET(transaction, .init = initialize_transaction)
|
||||
|
||||
CCoinsView coins_view;
|
||||
const CCoinsViewCache coins_view_cache(&coins_view);
|
||||
(void)AreInputsStandard(tx, coins_view_cache);
|
||||
(void)ValidateInputsStandardness(tx, coins_view_cache);
|
||||
(void)IsWitnessStandard(tx, coins_view_cache);
|
||||
|
||||
if (tx.GetTotalSize() < 250'000) { // Avoid high memory usage (with msan) due to json encoding
|
||||
|
||||
@@ -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);
|
||||
@@ -360,7 +360,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);
|
||||
|
||||
@@ -379,7 +379,7 @@ 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));
|
||||
BOOST_CHECK(::ValidateInputsStandardness(CTransaction(txToNonStd1), coins).IsInvalid());
|
||||
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd1), coins), 16U);
|
||||
|
||||
CMutableTransaction txToNonStd2;
|
||||
@@ -391,7 +391,7 @@ 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));
|
||||
BOOST_CHECK(::ValidateInputsStandardness(CTransaction(txToNonStd2), coins).IsInvalid());
|
||||
BOOST_CHECK_EQUAL(GetP2SHSigOpCount(CTransaction(txToNonStd2), coins), 20U);
|
||||
}
|
||||
|
||||
|
||||
@@ -411,7 +411,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)
|
||||
@@ -1052,7 +1052,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);
|
||||
@@ -1063,8 +1063,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.
|
||||
@@ -1082,8 +1091,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.
|
||||
@@ -1099,7 +1107,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);
|
||||
@@ -1110,8 +1118,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);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sanity check the return value of SpendsNonAnchorWitnessProg for various output types. */
|
||||
|
||||
@@ -894,8 +894,11 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
return false; // state filled in by CheckTxInputs
|
||||
}
|
||||
|
||||
if (m_pool.m_opts.require_standard && !AreInputsStandard(tx, m_view)) {
|
||||
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, "bad-txns-nonstandard-inputs");
|
||||
if (m_pool.m_opts.require_standard) {
|
||||
state = ValidateInputsStandardness(tx, m_view);
|
||||
if (state.IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for non-standard witnesses.
|
||||
|
||||
@@ -208,7 +208,8 @@ class BytesPerSigOpTest(BitcoinTestFramework):
|
||||
nonstd_tx = CTransaction()
|
||||
nonstd_tx.vin = [CTxIn(op, CScript([b"", packed_redeem_script])) for op in outpoints]
|
||||
nonstd_tx.vout = [CTxOut(0, CScript([OP_RETURN, b""]))]
|
||||
assert_raises_rpc_error(-26, "bad-txns-nonstandard-inputs", self.nodes[0].sendrawtransaction, nonstd_tx.serialize().hex())
|
||||
assert_raises_rpc_error(-26, "bad-txns-nonstandard-inputs, non-witness sigops exceed bip54 limit",
|
||||
self.nodes[0].sendrawtransaction, nonstd_tx.serialize().hex())
|
||||
|
||||
# Spending one less accounts for 2490 legacy sigops and is standard.
|
||||
std_tx = deepcopy(nonstd_tx)
|
||||
|
||||
Reference in New Issue
Block a user