Make bad-witness-nonstandard rejection more specific, and support overriding some

This commit is contained in:
Luke Dashjr 2023-08-01 18:12:56 +00:00
parent d926fd0325
commit 9a7339f672
7 changed files with 43 additions and 20 deletions

View File

@ -244,7 +244,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs,
return true; return true;
} }
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects)
{ {
if (tx.IsCoinBase()) if (tx.IsCoinBase())
return true; // Coinbases are skipped return true; // Coinbases are skipped
@ -273,9 +273,15 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// into a stack. We do not check IsPushOnly nor compare the hash as these will be done later anyway. // into a stack. We do not check IsPushOnly nor compare the hash as these will be done later anyway.
// If the check fails at this stage, we know that this txid must be a bad one. // If the check fails at this stage, we know that this txid must be a bad one.
if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE)) if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE))
{
out_reason = reason_prefix + "scriptsig-failure";
return false; return false;
}
if (stack.empty()) if (stack.empty())
{
out_reason = reason_prefix + "scriptcheck-missing";
return false; return false;
}
prevScript = CScript(stack.back().begin(), stack.back().end()); prevScript = CScript(stack.back().begin(), stack.back().end());
p2sh = true; p2sh = true;
} }
@ -285,18 +291,21 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// Non-witness program must not be associated with any witness // Non-witness program must not be associated with any witness
if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram)) if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram))
{
out_reason = reason_prefix + "nonwitness-input";
return false; return false;
}
// Check P2WSH standard limits // Check P2WSH standard limits
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) { if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
if (tx.vin[i].scriptWitness.stack.back().size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) if (tx.vin[i].scriptWitness.stack.back().size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE)
return false; MaybeReject("script-size");
size_t sizeWitnessStack = tx.vin[i].scriptWitness.stack.size() - 1; size_t sizeWitnessStack = tx.vin[i].scriptWitness.stack.size() - 1;
if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS) if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS)
return false; MaybeReject("stackitem-count");
for (unsigned int j = 0; j < sizeWitnessStack; j++) { for (unsigned int j = 0; j < sizeWitnessStack; j++) {
if (tx.vin[i].scriptWitness.stack[j].size() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE) if (tx.vin[i].scriptWitness.stack[j].size() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE)
return false; MaybeReject("stackitem-size");
} }
} }
@ -308,17 +317,28 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
Span stack{tx.vin[i].scriptWitness.stack}; Span stack{tx.vin[i].scriptWitness.stack};
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
// Annexes are nonstandard as long as no semantics are defined for them. // Annexes are nonstandard as long as no semantics are defined for them.
return false; MaybeReject("taproot-annex");
// If reject reason is ignored, continue as if the annex wasn't there.
SpanPopBack(stack);
} }
if (stack.size() >= 2) { if (stack.size() >= 2) {
// Script path spend (2 or more stack elements after removing optional annex) // Script path spend (2 or more stack elements after removing optional annex)
const auto& control_block = SpanPopBack(stack); const auto& control_block = SpanPopBack(stack);
SpanPopBack(stack); // Ignore script SpanPopBack(stack); // Ignore script
if (control_block.empty()) return false; // Empty control block is invalid if (control_block.empty()) {
// Empty control block is invalid
out_reason = reason_prefix + "taproot-control-missing";
return false;
}
if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) { if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
// Leaf version 0xc0 (aka Tapscript, see BIP 342) // Leaf version 0xc0 (aka Tapscript, see BIP 342)
if (!ignore_rejects.count(reason_prefix + "taproot-stackitem-size")) {
for (const auto& item : stack) { for (const auto& item : stack) {
if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) return false; if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) {
out_reason = reason_prefix + "taproot-stackitem-size";
return false;
}
}
} }
} }
} else if (stack.size() == 1) { } else if (stack.size() == 1) {
@ -326,6 +346,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// (no policy rules apply) // (no policy rules apply)
} else { } else {
// 0 stack elements; this is already invalid by consensus rules // 0 stack elements; this is already invalid by consensus rules
out_reason = reason_prefix + "taproot-witness-missing";
return false; return false;
} }
} }

View File

@ -161,7 +161,7 @@ inline bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& map
* *
* Also enforce a maximum stack item size limit and no annexes for tapscript spends. * Also enforce a maximum stack item size limit and no annexes for tapscript spends.
*/ */
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs); bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects);
/** Compute the virtual transaction size (weight reinterpreted as bytes). */ /** Compute the virtual transaction size (weight reinterpreted as bytes). */
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop); int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop);

View File

@ -284,7 +284,8 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
(void)GetTransactionSigOpCost(transaction, coins_view_cache, flags); (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
}, },
[&] { [&] {
(void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache); std::string reason;
(void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache, "bad-witness-", reason);
}); });
} }
} }

View File

@ -88,7 +88,8 @@ FUZZ_TARGET(transaction, .init = initialize_transaction)
CCoinsView coins_view; CCoinsView coins_view;
const CCoinsViewCache coins_view_cache(&coins_view); const CCoinsViewCache coins_view_cache(&coins_view);
(void)AreInputsStandard(tx, coins_view_cache); (void)AreInputsStandard(tx, coins_view_cache);
(void)IsWitnessStandard(tx, coins_view_cache); std::string reject_reason;
(void)IsWitnessStandard(tx, coins_view_cache, "fuzz", reject_reason);
if (tx.GetTotalSize() < 250'000) { // Avoid high memory usage (with msan) due to json encoding if (tx.GetTotalSize() < 250'000) { // Avoid high memory usage (with msan) due to json encoding
{ {

View File

@ -426,7 +426,7 @@ BOOST_FIXTURE_TEST_CASE(package_submission_tests, TestChain100Setup)
auto it_parent = result_quit_early.m_tx_results.find(tx_parent_invalid->GetWitnessHash()); auto it_parent = result_quit_early.m_tx_results.find(tx_parent_invalid->GetWitnessHash());
auto it_child = result_quit_early.m_tx_results.find(tx_child->GetWitnessHash()); auto it_child = result_quit_early.m_tx_results.find(tx_child->GetWitnessHash());
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetResult(), TxValidationResult::TX_WITNESS_MUTATED); BOOST_CHECK_EQUAL(it_parent->second.m_state.GetResult(), TxValidationResult::TX_WITNESS_MUTATED);
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetRejectReason(), "bad-witness-nonstandard"); BOOST_CHECK_EQUAL(it_parent->second.m_state.GetRejectReason(), "bad-witness-nonwitness-input");
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MISSING_INPUTS); BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MISSING_INPUTS);
BOOST_CHECK_EQUAL(it_child->second.m_state.GetRejectReason(), "bad-txns-inputs-missingorspent"); BOOST_CHECK_EQUAL(it_child->second.m_state.GetRejectReason(), "bad-txns-inputs-missingorspent");
} }

View File

@ -924,8 +924,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
} }
// Check for non-standard witnesses. // Check for non-standard witnesses.
if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view)) { if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view, "bad-witness-", reason, ignore_rejects)) {
MaybeReject(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard"); return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, reason);
} }
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS); int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);

View File

@ -1750,7 +1750,7 @@ class SegWitTest(BitcoinTestFramework):
tx2.rehash() tx2.rehash()
# This will be rejected due to a policy check: # This will be rejected due to a policy check:
# No witness is allowed, since it is not a witness program but a p2sh program # No witness is allowed, since it is not a witness program but a p2sh program
test_transaction_acceptance(self.nodes[1], self.std_node, tx2, True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, tx2, True, False, 'bad-witness-nonwitness-input')
# If we send without witness, it should be accepted. # If we send without witness, it should be accepted.
test_transaction_acceptance(self.nodes[1], self.std_node, tx2, False, True) test_transaction_acceptance(self.nodes[1], self.std_node, tx2, False, True)
@ -1819,13 +1819,13 @@ class SegWitTest(BitcoinTestFramework):
# Testing native P2WSH # Testing native P2WSH
# Witness stack size, excluding witnessScript, over 100 is non-standard # Witness stack size, excluding witnessScript, over 100 is non-standard
p2wsh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]] p2wsh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[0], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[0], True, False, 'bad-witness-stackitem-count')
# Non-standard nodes should accept # Non-standard nodes should accept
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[0], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[0], True, True)
# Stack element size over 80 bytes is non-standard # Stack element size over 80 bytes is non-standard
p2wsh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]] p2wsh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[1], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[1], True, False, 'bad-witness-stackitem-size')
# Non-standard nodes should accept # Non-standard nodes should accept
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[1], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[1], True, True)
# Standard nodes should accept if element size is not over 80 bytes # Standard nodes should accept if element size is not over 80 bytes
@ -1839,16 +1839,16 @@ class SegWitTest(BitcoinTestFramework):
# witnessScript size at 3601 bytes is non-standard # witnessScript size at 3601 bytes is non-standard
p2wsh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]] p2wsh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[3], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[3], True, False, 'bad-witness-script-size')
# Non-standard nodes should accept # Non-standard nodes should accept
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[3], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[3], True, True)
# Repeating the same tests with P2SH-P2WSH # Repeating the same tests with P2SH-P2WSH
p2sh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]] p2sh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[0], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[0], True, False, 'bad-witness-stackitem-count')
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[0], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[0], True, True)
p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]] p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, False, 'bad-witness-stackitem-size')
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[1], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[1], True, True)
p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 80] * 100 + [scripts[1]] p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 80] * 100 + [scripts[1]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, True) test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, True)
@ -1856,7 +1856,7 @@ class SegWitTest(BitcoinTestFramework):
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[2], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[2], True, True)
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[2], True, True) test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[2], True, True)
p2sh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]] p2sh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[3], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[3], True, False, 'bad-witness-script-size')
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[3], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[3], True, True)
self.generate(self.nodes[0], 1) # Mine and clean up the mempool of non-standard node self.generate(self.nodes[0], 1) # Mine and clean up the mempool of non-standard node