mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-05-12 19:20:42 +02:00
Make bad-witness-nonstandard rejection more specific, and support overriding some
This commit is contained in:
parent
d926fd0325
commit
9a7339f672
@ -244,7 +244,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs,
|
||||
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())
|
||||
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.
|
||||
// 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))
|
||||
{
|
||||
out_reason = reason_prefix + "scriptsig-failure";
|
||||
return false;
|
||||
}
|
||||
if (stack.empty())
|
||||
{
|
||||
out_reason = reason_prefix + "scriptcheck-missing";
|
||||
return false;
|
||||
}
|
||||
prevScript = CScript(stack.back().begin(), stack.back().end());
|
||||
p2sh = true;
|
||||
}
|
||||
@ -285,18 +291,21 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
|
||||
|
||||
// Non-witness program must not be associated with any witness
|
||||
if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram))
|
||||
{
|
||||
out_reason = reason_prefix + "nonwitness-input";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check P2WSH standard limits
|
||||
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_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;
|
||||
if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS)
|
||||
return false;
|
||||
MaybeReject("stackitem-count");
|
||||
for (unsigned int j = 0; j < sizeWitnessStack; j++) {
|
||||
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};
|
||||
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
|
||||
// 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) {
|
||||
// Script path spend (2 or more stack elements after removing optional annex)
|
||||
const auto& control_block = SpanPopBack(stack);
|
||||
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) {
|
||||
// Leaf version 0xc0 (aka Tapscript, see BIP 342)
|
||||
if (!ignore_rejects.count(reason_prefix + "taproot-stackitem-size")) {
|
||||
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) {
|
||||
@ -326,6 +346,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
|
||||
// (no policy rules apply)
|
||||
} else {
|
||||
// 0 stack elements; this is already invalid by consensus rules
|
||||
out_reason = reason_prefix + "taproot-witness-missing";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
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). */
|
||||
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop);
|
||||
|
@ -284,7 +284,8 @@ FUZZ_TARGET(coins_view, .init = initialize_coins_view)
|
||||
(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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,8 @@ FUZZ_TARGET(transaction, .init = initialize_transaction)
|
||||
CCoinsView coins_view;
|
||||
const CCoinsViewCache coins_view_cache(&coins_view);
|
||||
(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
|
||||
{
|
||||
|
@ -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_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.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.GetRejectReason(), "bad-txns-inputs-missingorspent");
|
||||
}
|
||||
|
@ -924,8 +924,8 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
}
|
||||
|
||||
// Check for non-standard witnesses.
|
||||
if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view)) {
|
||||
MaybeReject(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard");
|
||||
if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view, "bad-witness-", reason, ignore_rejects)) {
|
||||
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, reason);
|
||||
}
|
||||
|
||||
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
|
||||
|
@ -1750,7 +1750,7 @@ class SegWitTest(BitcoinTestFramework):
|
||||
tx2.rehash()
|
||||
# This will be rejected due to a policy check:
|
||||
# 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.
|
||||
test_transaction_acceptance(self.nodes[1], self.std_node, tx2, False, True)
|
||||
@ -1819,13 +1819,13 @@ class SegWitTest(BitcoinTestFramework):
|
||||
# Testing native P2WSH
|
||||
# Witness stack size, excluding witnessScript, over 100 is non-standard
|
||||
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
|
||||
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[0], True, True)
|
||||
|
||||
# Stack element size over 80 bytes is non-standard
|
||||
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
|
||||
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
|
||||
@ -1839,16 +1839,16 @@ class SegWitTest(BitcoinTestFramework):
|
||||
|
||||
# witnessScript size at 3601 bytes is non-standard
|
||||
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
|
||||
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[3], True, True)
|
||||
|
||||
# Repeating the same tests with P2SH-P2WSH
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
@ -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[1], self.std_node, p2sh_txs[2], True, True)
|
||||
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)
|
||||
|
||||
self.generate(self.nodes[0], 1) # Mine and clean up the mempool of non-standard node
|
||||
|
Loading…
Reference in New Issue
Block a user