Merge 7533 via sendraw_force-28+knots

This commit is contained in:
Luke Dashjr 2025-03-05 03:27:08 +00:00
commit 9d121259d7
25 changed files with 381 additions and 164 deletions

View File

@ -226,7 +226,7 @@ struct NewMempoolTransactionInfo {
* This boolean indicates whether the transaction was added
* without enforcing mempool fee limits.
*/
const bool m_mempool_limit_bypassed;
const ignore_rejects_type m_ignore_rejects;
/* This boolean indicates whether the transaction is part of a package. */
const bool m_submitted_in_package;
/*
@ -239,11 +239,11 @@ struct NewMempoolTransactionInfo {
explicit NewMempoolTransactionInfo(const CTransactionRef& tx, const CAmount& fee,
const int64_t vsize, const unsigned int height,
const bool mempool_limit_bypassed, const bool submitted_in_package,
const ignore_rejects_type& ignore_rejects, const bool submitted_in_package,
const bool chainstate_is_current,
const bool has_no_mempool_parents)
: info{tx, fee, vsize, height},
m_mempool_limit_bypassed{mempool_limit_bypassed},
m_ignore_rejects{ignore_rejects},
m_submitted_in_package{submitted_in_package},
m_chainstate_is_current{chainstate_is_current},
m_has_no_mempool_parents{has_no_mempool_parents} {}

View File

@ -99,7 +99,7 @@ bool LoadMempool(CTxMemPool& pool, const fs::path& load_path, Chainstate& active
}
if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) {
LOCK(cs_main);
const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, /*bypass_limits=*/false, /*test_accept=*/false);
const auto& accepted = AcceptToMemoryPool(active_chainstate, tx, nTime, empty_ignore_rejects, /*test_accept=*/false);
if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) {
++count;
} else {

View File

@ -31,7 +31,7 @@ static TransactionError HandleATMPError(const TxValidationState& state, std::str
}
}
TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const std::variant<CAmount, CFeeRate>& max_tx_fee, bool relay, bool wait_callback)
TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const std::variant<CAmount, CFeeRate>& max_tx_fee, bool relay, bool wait_callback, const ignore_rejects_type& ignore_rejects)
{
// BroadcastTransaction can be called by RPC or by the wallet.
// chainman, mempool and peerman are initialized before the RPC server and wallet are started
@ -69,11 +69,14 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
wtxid = mempool_tx->GetWitnessHash();
} else {
// Transaction is not already in the mempool.
const bool max_tx_fee_set{(std::holds_alternative<CAmount>(max_tx_fee) ? std::get<CAmount>(max_tx_fee) : std::get<CFeeRate>(max_tx_fee).GetFeePerK()) > 0};
bool max_tx_fee_set{(std::holds_alternative<CAmount>(max_tx_fee) ? std::get<CAmount>(max_tx_fee) : std::get<CFeeRate>(max_tx_fee).GetFeePerK()) > 0};
if (ignore_rejects.count("absurdly-high-fee") || ignore_rejects.count("max-fee-exceeded")) {
max_tx_fee_set = false;
}
if (max_tx_fee_set) {
// First, call ATMP with test_accept and check the fee. If ATMP
// fails here, return error immediately.
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ true);
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ true, ignore_rejects);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string);
} else {
@ -89,7 +92,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
}
}
// Try to submit the transaction to the mempool.
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ false);
const MempoolAcceptResult result = node.chainman->ProcessTransaction(tx, /*test_accept=*/ false, ignore_rejects);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string);
}

View File

@ -8,6 +8,7 @@
#include <common/messages.h>
#include <policy/feerate.h>
#include <primitives/transaction.h>
#include <policy/policy.h>
#include <variant>
@ -51,7 +52,7 @@ static const CAmount DEFAULT_MAX_BURN_AMOUNT{0};
* @param[in] wait_callback wait until callbacks have been processed to avoid stale result due to a sequentially RPC.
* return error
*/
[[nodiscard]] TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const std::variant<CAmount, CFeeRate>& max_tx_fee, bool relay, bool wait_callback);
[[nodiscard]] TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const std::variant<CAmount, CFeeRate>& max_tx_fee, bool relay, bool wait_callback, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects);
/**
* Return transaction with a given hash.

View File

@ -612,7 +612,7 @@ void CBlockPolicyEstimator::processTransaction(const NewMempoolTransactionInfo&
// - the node is not behind
// - the transaction is not dependent on any other transactions in the mempool
// - it's not part of a package.
const bool validForFeeEstimation = !tx.m_mempool_limit_bypassed && !tx.m_submitted_in_package && tx.m_chainstate_is_current && tx.m_has_no_mempool_parents;
const bool validForFeeEstimation = tx.m_ignore_rejects.empty() && !tx.m_submitted_in_package && tx.m_chainstate_is_current && tx.m_has_no_mempool_parents;
// Only want to be updating estimates when our blockchain is synced,
// otherwise we'll miscalculate how many blocks its taking to get included.

View File

@ -67,6 +67,10 @@ bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFeeIn)
return (txout.nValue < GetDustThreshold(txout, dustRelayFeeIn));
}
/**
* Note this must assign whichType even if returning false, in case
* IsStandardTx ignores the "scriptpubkey" rejection.
*/
bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType)
{
std::vector<std::vector<unsigned char> > vSolutions;
@ -91,21 +95,36 @@ bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_
return true;
}
bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason)
{
if (tx.version > TX_MAX_STANDARD_VERSION || tx.version < 1) {
reason = "version";
static inline bool MaybeReject_(std::string& out_reason, const std::string& reason, const std::string& reason_prefix, const ignore_rejects_type& ignore_rejects) {
if (ignore_rejects.count(reason_prefix + reason)) {
return false;
}
out_reason = reason_prefix + reason;
return true;
}
#define MaybeReject(reason) do { \
if (MaybeReject_(out_reason, reason, reason_prefix, ignore_rejects)) { \
return false; \
} \
} while(0)
bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& out_reason, const ignore_rejects_type& ignore_rejects)
{
const std::string reason_prefix;
if (tx.version > TX_MAX_STANDARD_VERSION || tx.version < 1) {
MaybeReject("version");
}
// Extremely large transactions with lots of inputs can cost the network
// almost as much to process as they cost the sender in fees, because
// computing signature hashes is O(ninputs*txsize). Limiting transactions
// to MAX_STANDARD_TX_WEIGHT mitigates CPU exhaustion attacks.
unsigned int sz = GetTransactionWeight(tx);
if (sz > MAX_STANDARD_TX_WEIGHT) {
reason = "tx-size";
return false;
MaybeReject("tx-size");
}
for (const CTxIn& txin : tx.vin)
@ -119,12 +138,10 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
// 20-of-20 CHECKMULTISIG scriptPubKey, though such a scriptPubKey
// is not considered standard.
if (txin.scriptSig.size() > MAX_STANDARD_SCRIPTSIG_SIZE) {
reason = "scriptsig-size";
return false;
MaybeReject("scriptsig-size");
}
if (!txin.scriptSig.IsPushOnly()) {
reason = "scriptsig-not-pushonly";
return false;
MaybeReject("scriptsig-not-pushonly");
}
}
@ -132,25 +149,28 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
TxoutType whichType;
for (const CTxOut& txout : tx.vout) {
if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) {
reason = "scriptpubkey";
return false;
if (whichType == TxoutType::WITNESS_UNKNOWN) {
MaybeReject("scriptpubkey-unknown-witnessversion");
} else {
MaybeReject("scriptpubkey");
}
}
if (whichType == TxoutType::NULL_DATA)
if (whichType == TxoutType::NULL_DATA) {
nDataOut++;
continue;
}
else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) {
reason = "bare-multisig";
return false;
} else if (IsDust(txout, dust_relay_fee)) {
reason = "dust";
return false;
MaybeReject("bare-multisig");
}
if (IsDust(txout, dust_relay_fee)) {
MaybeReject("dust");
}
}
// only one OP_RETURN txout is permitted
if (nDataOut > 1) {
reason = "multi-op-return";
return false;
MaybeReject("multi-op-return");
}
return true;
@ -174,7 +194,7 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
*
* Note that only the non-witness portion of the transaction is checked here.
*/
bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
bool AreInputsStandard(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 don't use vin normally
@ -185,22 +205,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) {
MaybeReject("script-unknown");
} 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;
MaybeReject("witness-unknown");
} else if (whichType == TxoutType::SCRIPTHASH) {
if (!tx.vin[i].scriptSig.IsPushOnly()) {
// The only way we got this far, is if the user ignored scriptsig-not-pushonly.
// However, this case is invalid, and will be caught later on.
// But for now, we don't want to run the [possibly expensive] script here.
continue;
}
std::vector<std::vector<unsigned char> > stack;
// 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))
{
// This case is also invalid or a bug
out_reason = reason_prefix + "scriptsig-failure";
return false;
}
if (stack.empty())
{
// Also invalid
out_reason = reason_prefix + "scriptcheck-missing";
return false;
}
CScript subscript(stack.back().begin(), stack.back().end());
if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) {
return false;
MaybeReject("scriptcheck-sigops");
}
}
}
@ -208,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
@ -227,7 +263,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// witness stuffing detected
if (prevScript.IsPayToAnchor()) {
return false;
MaybeReject("anchor-not-empty");
}
bool p2sh = false;
@ -237,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;
}
@ -249,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");
}
}
@ -272,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) {
@ -290,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;
}
}

View File

@ -14,6 +14,7 @@
#include <cstdint>
#include <string>
#include <unordered_set>
class CCoinsViewCache;
class CFeeRate;
@ -123,6 +124,9 @@ static constexpr unsigned int STANDARD_NOT_MANDATORY_VERIFY_FLAGS{STANDARD_SCRIP
/** Used as the flags parameter to sequence and nLocktime checks in non-consensus code. */
static constexpr unsigned int STANDARD_LOCKTIME_VERIFY_FLAGS{LOCKTIME_VERIFY_SEQUENCE};
typedef std::unordered_set<std::string> ignore_rejects_type;
static const ignore_rejects_type empty_ignore_rejects{};
CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee);
bool IsDust(const CTxOut& txout, const CFeeRate& dustRelayFee);
@ -139,13 +143,19 @@ static constexpr decltype(CTransaction::version) TX_MAX_STANDARD_VERSION{3};
* Check for standard transaction types
* @return True if all outputs (scriptPubKeys) use only standard transaction forms
*/
bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& reason);
bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_datacarrier_bytes, bool permit_bare_multisig, const CFeeRate& dust_relay_fee, std::string& out_reason, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects);
/**
* 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);
bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects);
inline bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) {
std::string reason;
return AreInputsStandard(tx, mapInputs, reason, reason);
}
/**
* Check if the transaction is over standard P2WSH resources limit:
* 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements
@ -153,7 +163,7 @@ bool AreInputsStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
*
* 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);

View File

@ -7,6 +7,7 @@
#include <consensus/amount.h>
#include <kernel/mempool_entry.h>
#include <policy/feerate.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <sync.h>
#include <tinyformat.h>
@ -59,7 +60,8 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx)
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx,
CTxMemPool& pool,
const CTxMemPool::setEntries& iters_conflicting,
CTxMemPool::setEntries& all_conflicts)
CTxMemPool::setEntries& all_conflicts,
const ignore_rejects_type& ignore_rejects)
{
AssertLockHeld(pool.cs);
const uint256 txid = tx.GetHash();
@ -70,7 +72,7 @@ std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx,
// entries from the mempool. This potentially overestimates the number of actual
// descendants (i.e. if multiple conflicts share a descendant, it will be counted multiple
// times), but we just want to be conservative to avoid doing too much work.
if (nConflictingCount > MAX_REPLACEMENT_CANDIDATES) {
if (nConflictingCount > MAX_REPLACEMENT_CANDIDATES && !ignore_rejects.count("too-many-replacements") && !ignore_rejects.count("too many potential replacements")) {
return strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
txid.ToString(),
nConflictingCount,

View File

@ -6,6 +6,7 @@
#define BITCOIN_POLICY_RBF_H
#include <consensus/amount.h>
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <threadsafety.h>
#include <txmempool.h>
@ -68,7 +69,8 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx);
*/
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx, CTxMemPool& pool,
const CTxMemPool::setEntries& iters_conflicting,
CTxMemPool::setEntries& all_conflicts)
CTxMemPool::setEntries& all_conflicts,
const ignore_rejects_type& ignore_rejects=empty_ignore_rejects)
EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
/** The replacement transaction may only include an unconfirmed input if that input was included in

View File

@ -56,6 +56,8 @@ struct ParentInfo {
};
std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t vsize,
const std::string& reason_prefix, std::string& out_reason,
const ignore_rejects_type& ignore_rejects,
const Package& package,
const CTxMemPool::setEntries& mempool_ancestors)
{
@ -68,12 +70,14 @@ std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t
// Now we have all ancestors, so we can start checking TRUC rules.
if (ptx->version == TRUC_VERSION) {
// SingleTRUCChecks should have checked this already.
if (!Assume(vsize <= TRUC_MAX_VSIZE)) {
if (!Assume(vsize <= TRUC_MAX_VSIZE || ignore_rejects.count(reason_prefix + "vsize-toobig"))) {
out_reason = reason_prefix + "vsize-toobig";
return strprintf("version=3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_MAX_VSIZE);
}
if (mempool_ancestors.size() + in_package_parents.size() + 1 > TRUC_ANCESTOR_LIMIT) {
if (mempool_ancestors.size() + in_package_parents.size() + 1 > TRUC_ANCESTOR_LIMIT && !ignore_rejects.count(reason_prefix + "ancestors-toomany")) {
out_reason = reason_prefix + "ancestors-toomany";
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString());
}
@ -81,7 +85,8 @@ std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t
const bool has_parent{mempool_ancestors.size() + in_package_parents.size() > 0};
if (has_parent) {
// A TRUC child cannot be too large.
if (vsize > TRUC_CHILD_MAX_VSIZE) {
if (vsize > TRUC_CHILD_MAX_VSIZE && !ignore_rejects.count(reason_prefix + "child-toobig")) {
out_reason = reason_prefix + "child-toobig";
return strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
vsize, TRUC_CHILD_MAX_VSIZE);
@ -106,7 +111,8 @@ std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t
}();
// If there is a parent, it must have the right version.
if (parent_info.m_version != TRUC_VERSION) {
if (parent_info.m_version != TRUC_VERSION && !ignore_rejects.count(reason_prefix + "spends-nontruc")) {
out_reason = reason_prefix + "spends-nontruc";
return strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
parent_info.m_txid.ToString(), parent_info.m_wtxid.ToString());
@ -120,21 +126,24 @@ std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t
// Fail if we find another tx with the same parent. We don't check whether the
// sibling is to-be-replaced (done in SingleTRUCChecks) because these transactions
// are within the same package.
if (input.prevout.hash == parent_info.m_txid) {
if (input.prevout.hash == parent_info.m_txid && !ignore_rejects.count(reason_prefix + "sibling-known")) {
out_reason = reason_prefix + "sibling-known";
return strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
parent_info.m_txid.ToString(),
parent_info.m_wtxid.ToString());
}
// This tx can't have both a parent and an in-package child.
if (input.prevout.hash == ptx->GetHash()) {
if (input.prevout.hash == ptx->GetHash() && !ignore_rejects.count(reason_prefix + "parent-and-child-both")) {
out_reason = reason_prefix + "parent-and-child-both";
return strprintf("tx %s (wtxid=%s) would have too many ancestors",
package_tx->GetHash().ToString(), package_tx->GetWitnessHash().ToString());
}
}
}
if (parent_info.m_has_mempool_descendant) {
if (parent_info.m_has_mempool_descendant && !ignore_rejects.count(reason_prefix + "descendant-toomany")) {
out_reason = reason_prefix + "descendant-toomany";
return strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
parent_info.m_txid.ToString(), parent_info.m_wtxid.ToString());
}
@ -142,14 +151,16 @@ std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t
} else {
// Non-TRUC transactions cannot have TRUC parents.
for (auto it : mempool_ancestors) {
if (it->GetTx().version == TRUC_VERSION) {
if (it->GetTx().version == TRUC_VERSION && !ignore_rejects.count(reason_prefix + "spent-by-nontruc")) {
out_reason = reason_prefix + "spent-by-nontruc";
return strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
it->GetSharedTx()->GetHash().ToString(), it->GetSharedTx()->GetWitnessHash().ToString());
}
}
for (const auto& index: in_package_parents) {
if (package.at(index)->version == TRUC_VERSION) {
if (package.at(index)->version == TRUC_VERSION && !ignore_rejects.count(reason_prefix + "spent-by-nontruc")) {
out_reason = reason_prefix + "spent-by-nontruc";
return strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(),
ptx->GetWitnessHash().ToString(),
@ -162,18 +173,22 @@ std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t
}
std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CTransactionRef& ptx,
const std::string& reason_prefix, std::string& out_reason,
const ignore_rejects_type& ignore_rejects,
const CTxMemPool::setEntries& mempool_ancestors,
const std::set<Txid>& direct_conflicts,
int64_t vsize)
{
// Check TRUC and non-TRUC inheritance.
for (const auto& entry : mempool_ancestors) {
if (ptx->version != TRUC_VERSION && entry->GetTx().version == TRUC_VERSION) {
if (ptx->version != TRUC_VERSION && entry->GetTx().version == TRUC_VERSION && !ignore_rejects.count(reason_prefix + "spent-by-nontruc")) {
out_reason = reason_prefix + "spent-by-nontruc";
return std::make_pair(strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()),
nullptr);
} else if (ptx->version == TRUC_VERSION && entry->GetTx().version != TRUC_VERSION) {
} else if (ptx->version == TRUC_VERSION && entry->GetTx().version != TRUC_VERSION && !ignore_rejects.count(reason_prefix + "spends-nontruc")) {
out_reason = reason_prefix + "spends-nontruc";
return std::make_pair(strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()),
@ -188,14 +203,16 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CT
// The rest of the rules only apply to transactions with version=3.
if (ptx->version != TRUC_VERSION) return std::nullopt;
if (vsize > TRUC_MAX_VSIZE) {
if (vsize > TRUC_MAX_VSIZE && !ignore_rejects.count(reason_prefix + "vsize-toobig")) {
out_reason = reason_prefix + "vsize-toobig";
return std::make_pair(strprintf("version=3 tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_MAX_VSIZE),
nullptr);
}
// Check that TRUC_ANCESTOR_LIMIT would not be violated.
if (mempool_ancestors.size() + 1 > TRUC_ANCESTOR_LIMIT) {
if (mempool_ancestors.size() + 1 > TRUC_ANCESTOR_LIMIT && !ignore_rejects.count(reason_prefix + "ancestors-toomany")) {
out_reason = reason_prefix + "ancestors-toomany";
return std::make_pair(strprintf("tx %s (wtxid=%s) would have too many ancestors",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString()),
nullptr);
@ -204,7 +221,8 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CT
// Remaining checks only pertain to transactions with unconfirmed ancestors.
if (mempool_ancestors.size() > 0) {
// If this transaction spends TRUC parents, it cannot be too large.
if (vsize > TRUC_CHILD_MAX_VSIZE) {
if (vsize > TRUC_CHILD_MAX_VSIZE && !ignore_rejects.count(reason_prefix + "child-toobig")) {
out_reason = reason_prefix + "child-toobig";
return std::make_pair(strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_CHILD_MAX_VSIZE),
nullptr);
@ -222,7 +240,7 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CT
const bool child_will_be_replaced = !children.empty() &&
std::any_of(children.cbegin(), children.cend(),
[&direct_conflicts](const CTxMemPoolEntry& child){return direct_conflicts.count(child.GetTx().GetHash()) > 0;});
if (parent_entry->GetCountWithDescendants() + 1 > TRUC_DESCENDANT_LIMIT && !child_will_be_replaced) {
if (parent_entry->GetCountWithDescendants() + 1 > TRUC_DESCENDANT_LIMIT && (!child_will_be_replaced) && !ignore_rejects.count(reason_prefix + "descendants-toomany")) {
// Allow sibling eviction for TRUC transaction: if another child already exists, even if
// we don't conflict inputs with it, consider evicting it under RBF rules. We rely on TRUC rules
// only permitting 1 descendant, as otherwise we would need to have logic for deciding
@ -233,6 +251,7 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CT
// Return the sibling if its eviction can be considered. Provide the "descendant count
// limit" string either way, as the caller may decide not to do sibling eviction.
out_reason = reason_prefix + "descendants-toomany";
return std::make_pair(strprintf("tx %u (wtxid=%s) would exceed descendant count limit",
parent_entry->GetSharedTx()->GetHash().ToString(),
parent_entry->GetSharedTx()->GetWitnessHash().ToString()),

View File

@ -62,6 +62,8 @@ static_assert(TRUC_MAX_VSIZE + TRUC_CHILD_MAX_VSIZE <= DEFAULT_DESCENDANT_SIZE_L
* applicable.
*/
std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CTransactionRef& ptx,
const std::string& reason_prefix, std::string& out_reason,
const ignore_rejects_type& ignore_rejects,
const CTxMemPool::setEntries& mempool_ancestors,
const std::set<Txid>& direct_conflicts,
int64_t vsize);
@ -88,6 +90,8 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CT
* @returns debug string if an error occurs, std::nullopt otherwise.
* */
std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t vsize,
const std::string& reason_prefix, std::string& out_reason,
const ignore_rejects_type& ignore_rejects,
const Package& package,
const CTxMemPool::setEntries& mempool_ancestors);

View File

@ -142,8 +142,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "signrawtransactionwithwallet", 1, "prevtxs" },
{ "sendrawtransaction", 1, "maxfeerate" },
{ "sendrawtransaction", 2, "maxburnamount" },
{ "sendrawtransaction", 3, "ignore_rejects" },
{ "testmempoolaccept", 0, "rawtxs" },
{ "testmempoolaccept", 1, "maxfeerate" },
{ "testmempoolaccept", 2, "ignore_rejects" },
{ "submitpackage", 0, "package" },
{ "submitpackage", 1, "maxfeerate" },
{ "submitpackage", 2, "maxburnamount" },

View File

@ -59,11 +59,20 @@ static RPCHelpMan sendrawtransaction()
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"},
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
"/kvB.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate."},
"/kvB.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate.",
RPCArgOptions{.skip_type_check = true} // for ignore_rejects compatibility
},
{"maxburnamount", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_BURN_AMOUNT)},
"Reject transactions with provably unspendable outputs (e.g. 'datacarrier' outputs that use the OP_RETURN opcode) greater than the specified value, expressed in " + CURRENCY_UNIT + ".\n"
"If burning funds through unspendable outputs is desired, increase this value.\n"
"This check is based on heuristics and does not guarantee spendability of outputs.\n"},
"This check is based on heuristics and does not guarantee spendability of outputs.\n",
RPCArgOptions{.skip_type_check = true} // for ignore_rejects compatibility
},
{"ignore_rejects", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Rejection conditions to ignore, eg 'txn-mempool-conflict'",
{
{"reject_reason", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""},
},
},
},
RPCResult{
RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
@ -80,7 +89,24 @@ static RPCHelpMan sendrawtransaction()
},
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
{
const CAmount max_burn_amount = request.params[2].isNull() ? 0 : AmountFromValue(request.params[2]);
CFeeRate max_raw_tx_fee_rate{DEFAULT_MAX_RAW_TX_FEE_RATE};
CAmount max_burn_amount{0};
const UniValue* json_ign_rejs = &request.params[3];
if (request.params[1].isArray() && request.params[2].isNull() && request.params[3].isNull()) {
// ignore_rejects used to occupy this position (v0.12.0.knots20160226.rc1-v0.17.1.knots20181229)
json_ign_rejs = &request.params[1];
} else {
if (!request.params[1].isNull()) {
max_raw_tx_fee_rate = ParseFeeRate(self.Arg<UniValue>("maxfeerate"));
}
if (request.params[2].isArray() && request.params[3].isNull()) {
// ignore_rejects used to occupy this position (v0.18.0.knots20190502-v23.0.knots20220529)
json_ign_rejs = &request.params[2];
} else if (!request.params[2].isNull()) {
max_burn_amount = AmountFromValue(request.params[2]);
}
}
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
@ -95,12 +121,18 @@ static RPCHelpMan sendrawtransaction()
CTransactionRef tx(MakeTransactionRef(std::move(mtx)));
const CFeeRate max_raw_tx_fee_rate{ParseFeeRate(self.Arg<UniValue>("maxfeerate"))};
ignore_rejects_type ignore_rejects;
if (!json_ign_rejs->isNull()) {
for (size_t i = 0; i < json_ign_rejs->size(); ++i) {
const UniValue& json_ign_rej = (*json_ign_rejs)[i];
ignore_rejects.insert(json_ign_rej.get_str());
}
}
std::string err_string;
AssertLockNotHeld(cs_main);
NodeContext& node = EnsureAnyNodeContext(request.context);
const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee_rate, /*relay=*/true, /*wait_callback=*/true);
const TransactionError err = BroadcastTransaction(node, tx, err_string, max_raw_tx_fee_rate, /*relay=*/true, /*wait_callback=*/true, ignore_rejects);
if (TransactionError::OK != err) {
throw JSONRPCTransactionError(err, err_string);
}
@ -128,6 +160,11 @@ static RPCHelpMan testmempoolaccept()
{"maxfeerate", RPCArg::Type::AMOUNT, RPCArg::Default{FormatMoney(DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK())},
"Reject transactions whose fee rate is higher than the specified value, expressed in " + CURRENCY_UNIT +
"/kvB.\nFee rates larger than 1BTC/kvB are rejected.\nSet to 0 to accept any fee rate."},
{"ignore_rejects", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Rejection conditions to ignore, eg 'txn-mempool-conflict'",
{
{"reject_reason", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""},
},
},
},
RPCResult{
RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n"
@ -174,6 +211,16 @@ static RPCHelpMan testmempoolaccept()
const CFeeRate max_raw_tx_fee_rate{ParseFeeRate(self.Arg<UniValue>("maxfeerate"))};
const UniValue* json_ign_rejs = &request.params[2];
ignore_rejects_type ignore_rejects;
if (!json_ign_rejs->isNull()) {
for (size_t i = 0; i < json_ign_rejs->size(); ++i) {
const UniValue& json_ign_rej = (*json_ign_rejs)[i];
const std::string& ign_rej = json_ign_rej.get_str();
ignore_rejects.insert(ign_rej);
}
}
std::vector<CTransactionRef> txns;
txns.reserve(raw_transactions.size());
for (const auto& rawtx : raw_transactions.getValues()) {
@ -191,9 +238,9 @@ static RPCHelpMan testmempoolaccept()
Chainstate& chainstate = chainman.ActiveChainstate();
const PackageMempoolAcceptResult package_result = [&] {
LOCK(::cs_main);
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true, /*client_maxfeerate=*/{});
if (txns.size() > 1) return ProcessNewPackage(chainstate, mempool, txns, /*test_accept=*/true, /*client_maxfeerate=*/{}, ignore_rejects);
return PackageMempoolAcceptResult(txns[0]->GetWitnessHash(),
chainman.ProcessTransaction(txns[0], /*test_accept=*/true));
chainman.ProcessTransaction(txns[0], /*test_accept=*/true, ignore_rejects));
}();
UniValue rpc_result(UniValue::VARR);
@ -223,7 +270,8 @@ static RPCHelpMan testmempoolaccept()
// Check that fee does not exceed maximum fee
const int64_t virtual_size = tx_result.m_vsize.value();
const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size);
if (max_raw_tx_fee && fee > max_raw_tx_fee) {
if (max_raw_tx_fee && fee > max_raw_tx_fee &&
0 == (ignore_rejects.count("absurdly-high-fee") + ignore_rejects.count("max-fee-exceeded"))) {
result_inner.pushKV("allowed", false);
result_inner.pushKV("reject-reason", "max-fee-exceeded");
exit_early = true;

View File

@ -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);
});
}
}

View File

@ -49,7 +49,7 @@ FUZZ_TARGET(policy_estimator, .init = initialize_policy_estimator)
const auto tx_has_mempool_parents = fuzzed_data_provider.ConsumeBool();
const auto tx_info = NewMempoolTransactionInfo(entry.GetSharedTx(), entry.GetFee(),
entry.GetTxSize(), entry.GetHeight(),
/*mempool_limit_bypassed=*/false,
empty_ignore_rejects,
tx_submitted_in_package,
/*chainstate_is_current=*/true,
tx_has_mempool_parents);

View File

@ -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
{

View File

@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
feeV[j],
virtual_size,
entry.nHeight,
/*mempool_limit_bypassed=*/false,
empty_ignore_rejects,
/*submitted_in_package=*/false,
/*chainstate_is_current=*/true,
/*has_no_mempool_parents=*/true)};
@ -172,7 +172,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
feeV[j],
virtual_size,
entry.nHeight,
/*mempool_limit_bypassed=*/false,
empty_ignore_rejects,
/*submitted_in_package=*/false,
/*chainstate_is_current=*/true,
/*has_no_mempool_parents=*/true)};
@ -236,7 +236,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
feeV[j],
virtual_size,
entry.nHeight,
/*mempool_limit_bypassed=*/false,
empty_ignore_rejects,
/*submitted_in_package=*/false,
/*chainstate_is_current=*/true,
/*has_no_mempool_parents=*/true)};

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_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");
}

View File

@ -20,6 +20,18 @@
BOOST_AUTO_TEST_SUITE(txvalidation_tests)
std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CTransactionRef& ptx, const CTxMemPool::setEntries& mempool_ancestors, const std::set<Txid>& direct_conflicts, int64_t vsize)
{
std::string dummy;
return SingleTRUCChecks(ptx, dummy, dummy, empty_ignore_rejects, mempool_ancestors, direct_conflicts, vsize);
}
std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t vsize, const Package& package, const CTxMemPool::setEntries& mempool_ancestors)
{
std::string dummy;
return PackageTRUCChecks(ptx, vsize, dummy, dummy, empty_ignore_rejects, package, mempool_ancestors);
}
/**
* Ensure that the mempool won't accept coinbase transactions.
*/

View File

@ -448,7 +448,7 @@ public:
struct ATMPArgs {
const CChainParams& m_chainparams;
const int64_t m_accept_time;
const bool m_bypass_limits;
const ignore_rejects_type& m_ignore_rejects;
/*
* Return any outpoints which were not previously present in the coins
* cache, but were added as a result of validating the tx for mempool
@ -485,11 +485,11 @@ public:
/** Parameters for single transaction mempool validation. */
static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time,
bool bypass_limits, std::vector<COutPoint>& coins_to_uncache,
const ignore_rejects_type& ignore_rejects, std::vector<COutPoint>& coins_to_uncache,
bool test_accept) {
return ATMPArgs{/* m_chainparams */ chainparams,
/* m_accept_time */ accept_time,
/* m_bypass_limits */ bypass_limits,
/* m_ignore_rejects */ ignore_rejects,
/* m_coins_to_uncache */ coins_to_uncache,
/* m_test_accept */ test_accept,
/* m_allow_replacement */ true,
@ -503,10 +503,10 @@ public:
/** Parameters for test package mempool validation through testmempoolaccept. */
static ATMPArgs PackageTestAccept(const CChainParams& chainparams, int64_t accept_time,
std::vector<COutPoint>& coins_to_uncache) {
const ignore_rejects_type& ignore_rejects, std::vector<COutPoint>& coins_to_uncache) {
return ATMPArgs{/* m_chainparams */ chainparams,
/* m_accept_time */ accept_time,
/* m_bypass_limits */ false,
/* m_ignore_rejects */ ignore_rejects,
/* m_coins_to_uncache */ coins_to_uncache,
/* m_test_accept */ true,
/* m_allow_replacement */ false,
@ -520,10 +520,10 @@ public:
/** Parameters for child-with-unconfirmed-parents package validation. */
static ATMPArgs PackageChildWithParents(const CChainParams& chainparams, int64_t accept_time,
std::vector<COutPoint>& coins_to_uncache, const std::optional<CFeeRate>& client_maxfeerate) {
std::vector<COutPoint>& coins_to_uncache, const std::optional<CFeeRate>& client_maxfeerate, const ignore_rejects_type& ignore_rejects) {
return ATMPArgs{/* m_chainparams */ chainparams,
/* m_accept_time */ accept_time,
/* m_bypass_limits */ false,
/* m_ignore_rejects */ ignore_rejects,
/* m_coins_to_uncache */ coins_to_uncache,
/* m_test_accept */ false,
/* m_allow_replacement */ true,
@ -539,7 +539,7 @@ public:
static ATMPArgs SingleInPackageAccept(const ATMPArgs& package_args) {
return ATMPArgs{/* m_chainparams */ package_args.m_chainparams,
/* m_accept_time */ package_args.m_accept_time,
/* m_bypass_limits */ false,
empty_ignore_rejects,
/* m_coins_to_uncache */ package_args.m_coins_to_uncache,
/* m_test_accept */ package_args.m_test_accept,
/* m_allow_replacement */ true,
@ -556,7 +556,7 @@ public:
// mixing up the order of the arguments. Use static functions above instead.
ATMPArgs(const CChainParams& chainparams,
int64_t accept_time,
bool bypass_limits,
const ignore_rejects_type& ignore_rejects,
std::vector<COutPoint>& coins_to_uncache,
bool test_accept,
bool allow_replacement,
@ -567,7 +567,7 @@ public:
bool allow_carveouts)
: m_chainparams{chainparams},
m_accept_time{accept_time},
m_bypass_limits{bypass_limits},
m_ignore_rejects{ignore_rejects},
m_coins_to_uncache{coins_to_uncache},
m_test_accept{test_accept},
m_allow_replacement{allow_replacement},
@ -662,6 +662,23 @@ private:
PrecomputedTransactionData m_precomputed_txdata;
};
static inline bool MaybeReject_(TxValidationResult reason, const std::string& reason_str, const std::string& debug_msg, const ignore_rejects_type& ignore_rejects, TxValidationState& state) {
if (ignore_rejects.count(reason_str)) {
return false;
}
state.Invalid(reason, reason_str, debug_msg);
return true;
}
#define MaybeRejectDbg(reason, reason_str, debug_msg) do { \
if (MaybeReject_(reason, reason_str, debug_msg, ignore_rejects, state)) { \
return false; \
} \
} while(0)
#define MaybeReject(reason, reason_str) MaybeRejectDbg(reason, reason_str, "")
// Run the policy checks on a given transaction, excluding any script checks.
// Looks up inputs, calculates feerate, considers replacement, evaluates
// package limits, etc. As this function can be invoked for "free" by a peer,
@ -669,11 +686,11 @@ private:
bool PreChecks(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Run checks for mempool replace-by-fee, only used in AcceptSingleTransaction.
bool ReplacementChecks(Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
bool ReplacementChecks(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Enforce package mempool ancestor/descendant limits (distinct from individual
// ancestor/descendant limits done in PreChecks) and run Package RBF checks.
bool PackageMempoolChecks(const std::vector<CTransactionRef>& txns,
bool PackageMempoolChecks(const ATMPArgs& args, const std::vector<CTransactionRef>& txns,
std::vector<Workspace>& workspaces,
int64_t total_vsize,
PackageValidationState& package_state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
@ -701,16 +718,16 @@ private:
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Compare a package's feerate against minimum allowed.
bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs)
bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state, const ignore_rejects_type& ignore_rejects) EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs)
{
AssertLockHeld(::cs_main);
AssertLockHeld(m_pool.cs);
CAmount mempoolRejectFee = m_pool.GetMinFee().GetFee(package_size);
if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee) {
if (mempoolRejectFee > 0 && package_fee < mempoolRejectFee && !ignore_rejects.count(rejectmsg_lowfee_mempool)) {
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool min fee not met", strprintf("%d < %d", package_fee, mempoolRejectFee));
}
if (package_fee < m_pool.m_opts.min_relay_feerate.GetFee(package_size)) {
if (package_fee < m_pool.m_opts.min_relay_feerate.GetFee(package_size) && !ignore_rejects.count(rejectmsg_lowfee_relay)) {
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "min relay fee not met",
strprintf("%d < %d", package_fee, m_pool.m_opts.min_relay_feerate.GetFee(package_size)));
}
@ -775,7 +792,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Copy/alias what we need out of args
const int64_t nAcceptTime = args.m_accept_time;
const bool bypass_limits = args.m_bypass_limits;
const ignore_rejects_type& ignore_rejects = args.m_ignore_rejects;
std::vector<COutPoint>& coins_to_uncache = args.m_coins_to_uncache;
// Alias what we need out of ws
@ -792,19 +809,19 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
std::string reason;
if (m_pool.m_opts.require_standard && !IsStandardTx(tx, m_pool.m_opts.max_datacarrier_bytes, m_pool.m_opts.permit_bare_multisig, m_pool.m_opts.dust_relay_feerate, reason)) {
if (m_pool.m_opts.require_standard && !IsStandardTx(tx, m_pool.m_opts.max_datacarrier_bytes, m_pool.m_opts.permit_bare_multisig, m_pool.m_opts.dust_relay_feerate, reason, ignore_rejects)) {
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
}
// Transactions smaller than 65 non-witness bytes are not relayed to mitigate CVE-2017-12842.
if (::GetSerializeSize(TX_NO_WITNESS(tx)) < MIN_STANDARD_TX_NONWITNESS_SIZE)
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "tx-size-small");
MaybeReject(TxValidationResult::TX_NOT_STANDARD, "tx-size-small");
// Only accept nLockTime-using transactions that can be mined in the next
// block; we don't want our mempool filled up with transactions that can't
// be mined yet.
if (!CheckFinalTxAtTip(*Assert(m_active_chainstate.m_chain.Tip()), tx)) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final");
MaybeReject(TxValidationResult::TX_PREMATURE_SPEND, "non-final");
}
if (m_pool.exists(GenTxid::Wtxid(tx.GetWitnessHash()))) {
@ -839,7 +856,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
//
// Replaceability signaling of the original transactions may be
// ignored due to node setting.
const bool allow_rbf{m_pool.m_opts.full_rbf || SignalsOptInRBF(*ptxConflicting) || ptxConflicting->version == TRUC_VERSION};
const bool allow_rbf{(m_pool.m_opts.full_rbf || ignore_rejects.count("txn-mempool-conflict")) || SignalsOptInRBF(*ptxConflicting) || ptxConflicting->version == TRUC_VERSION};
if (!allow_rbf) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
}
@ -891,6 +908,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's
// backend was removed, it no longer pulls coins from the mempool.
const std::optional<LockPoints> lock_points{CalculateLockPointsAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx)};
// NOTE: The miner doesn't check this again, so for now it may not be overridden.
if (!lock_points.has_value() || !CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), *lock_points)) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final");
}
@ -900,13 +918,13 @@ 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 && !AreInputsStandard(tx, m_view, "bad-txns-input-", reason, ignore_rejects)) {
return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, reason);
}
// Check for non-standard witnesses.
if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view)) {
return state.Invalid(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);
@ -926,15 +944,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
}
}
// Set entry_sequence to 0 when bypass_limits is used; this allows txs from a block
// Set entry_sequence to 0 when rejectmsg_zero_mempool_entry_seq is used; this allows txs from a block
// reorg to be marked earlier than any child txs that were already in the mempool.
const uint64_t entry_sequence = bypass_limits ? 0 : m_pool.GetSequence();
const uint64_t entry_sequence = args.m_ignore_rejects.count(rejectmsg_zero_mempool_entry_seq) ? 0 : m_pool.GetSequence();
entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence,
fSpendsCoinbase, nSigOpsCost, lock_points.value()));
ws.m_vsize = entry->GetTxSize();
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
MaybeRejectDbg(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
strprintf("%d", nSigOpsCost));
// No individual transactions are allowed below the min relay feerate except from disconnected blocks.
@ -943,7 +961,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// method of ensuring the tx remains bumped. For example, the fee-bumping child could disappear
// due to a replacement.
// The only exception is TRUC transactions.
if (!bypass_limits && ws.m_ptx->version != TRUC_VERSION && ws.m_modified_fees < m_pool.m_opts.min_relay_feerate.GetFee(ws.m_vsize)) {
if (ws.m_ptx->version != TRUC_VERSION && ws.m_modified_fees < m_pool.m_opts.min_relay_feerate.GetFee(ws.m_vsize) && !args.m_ignore_rejects.count(rejectmsg_lowfee_relay)) {
// Even though this is a fee-related failure, this result is TX_MEMPOOL_POLICY, not
// TX_RECONSIDERABLE, because it cannot be bypassed using package validation.
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met",
@ -952,7 +970,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// No individual transactions are allowed below the mempool min feerate except from disconnected
// blocks and transactions in a package. Package transactions will be checked using package
// feerate later.
if (!bypass_limits && !args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state)) return false;
if (!args.m_package_feerates && !CheckFeeRate(ws.m_vsize, ws.m_modified_fees, state, args.m_ignore_rejects)) return false;
ws.m_iters_conflicting = m_pool.GetIterSet(ws.m_conflicts);
@ -996,7 +1014,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
maybe_rbf_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants();
}
if (auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, maybe_rbf_limits)}) {
CTxMemPool::Limits limits;
if (ignore_rejects.count("too-long-mempool-chain")) {
limits = CTxMemPool::Limits::NoLimits();
} else {
limits = maybe_rbf_limits;
}
if (auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, limits)}) {
ws.m_ancestors = std::move(*ancestors);
} else {
// If CalculateMemPoolAncestors fails second time, we want the original error string.
@ -1038,7 +1062,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Even though just checking direct mempool parents for inheritance would be sufficient, we
// check using the full ancestor set here because it's more convenient to use what we have
// already calculated.
if (const auto err{SingleTRUCChecks(ws.m_ptx, ws.m_ancestors, ws.m_conflicts, ws.m_vsize)}) {
if (const auto err{SingleTRUCChecks(ws.m_ptx, "truc-", reason, ignore_rejects, ws.m_ancestors, ws.m_conflicts, ws.m_vsize)}) {
// Single transaction contexts only.
if (args.m_allow_sibling_eviction && err->second != nullptr) {
// We should only be considering where replacement is considered valid as well.
@ -1057,7 +1081,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// (which is normally done in PreChecks). However, the only way a TRUC transaction can
// have a non-TRUC and non-BIP125 descendant is due to a reorg.
} else {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "TRUC-violation", err->first);
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, reason, err->first);
}
}
@ -1076,7 +1100,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
return true;
}
bool MemPoolAccept::ReplacementChecks(Workspace& ws)
bool MemPoolAccept::ReplacementChecks(ATMPArgs& args, Workspace& ws)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs);
@ -1095,25 +1119,29 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
// guarantee that this is incentive-compatible for miners, because it is possible for a
// descendant transaction of a direct conflict to pay a higher feerate than the transaction that
// might replace them, under these rules.
if (!args.m_ignore_rejects.count("insufficient fee")) {
if (const auto err_string{PaysMoreThanConflicts(ws.m_iters_conflicting, newFeeRate, hash)}) {
// This fee-related failure is TX_RECONSIDERABLE because validating in a package may change
// the result.
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE,
strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
}
} // ignore_rejects
// Calculate all conflicting entries and enforce Rule #5.
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, m_subpackage.m_all_conflicts)}) {
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, m_subpackage.m_all_conflicts, args.m_ignore_rejects)}) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
strprintf("too many potential replacements%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
}
// Enforce Rule #2.
if (!args.m_ignore_rejects.count("replacement-adds-unconfirmed")) {
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, m_subpackage.m_all_conflicts)}) {
// Sibling eviction is only done for TRUC transactions, which cannot have multiple ancestors.
Assume(!ws.m_sibling_eviction);
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
strprintf("replacement-adds-unconfirmed%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
}
} // ignore_rejects
// Check if it's economically rational to mine this transaction rather than the ones it
// replaces and pays for its own relay fees. Enforce Rules #3 and #4.
@ -1121,16 +1149,18 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
m_subpackage.m_conflicting_fees += it->GetModifiedFee();
m_subpackage.m_conflicting_size += it->GetTxSize();
}
if (!args.m_ignore_rejects.count("insufficient fee")) {
if (const auto err_string{PaysForRBF(m_subpackage.m_conflicting_fees, ws.m_modified_fees, ws.m_vsize,
m_pool.m_opts.incremental_relay_feerate, hash)}) {
// Result may change in a package context
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE,
strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
}
} // ignore_rejects
return true;
}
bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txns,
bool MemPoolAccept::PackageMempoolChecks(const ATMPArgs& args, const std::vector<CTransactionRef>& txns,
std::vector<Workspace>& workspaces,
const int64_t total_vsize,
PackageValidationState& package_state)
@ -1144,7 +1174,13 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
assert(txns.size() == workspaces.size());
auto result = m_pool.CheckPackageLimits(txns, total_vsize);
util::Result<void> result = [&]() EXCLUSIVE_LOCKS_REQUIRED(m_pool.cs) {
if (args.m_ignore_rejects.count("package-mempool-limits")) {
return util::Result<void>();
} else {
return m_pool.CheckPackageLimits(txns, total_vsize);
}
}();
if (!result) {
// This is a package-wide error, separate from an individual transaction error.
return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-mempool-limits", util::ErrorString(result).original);
@ -1294,7 +1330,6 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
const CTransaction& tx = *ws.m_ptx;
const uint256& hash = ws.m_hash;
TxValidationState& state = ws.m_state;
const bool bypass_limits = args.m_bypass_limits;
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
if (!m_subpackage.m_all_conflicts.empty()) Assume(args.m_allow_replacement);
@ -1332,7 +1367,7 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
// If we are validating a package, don't trim here because we could evict a previous transaction
// in the package. LimitMempoolSize() should be called at the very end to make sure the mempool
// is still within limits and package submission happens atomically.
if (!args.m_package_submission && !bypass_limits) {
if (!args.m_package_submission && !args.m_ignore_rejects.count(rejectmsg_mempoolfull)) {
LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip());
if (!m_pool.exists(GenTxid::Txid(hash)))
// The tx no longer meets our (new) mempool minimum feerate but could be reconsidered in a package.
@ -1371,7 +1406,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
// Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the
// last calculation done in PreChecks, since package ancestors have already been submitted.
{
auto ancestors{m_pool.CalculateMemPoolAncestors(*ws.m_entry, m_pool.m_opts.limits)};
auto ancestors{m_pool.CalculateMemPoolAncestors(*ws.m_entry, CTxMemPool::Limits::NoLimits())};
if(!ancestors) {
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
// Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail.
@ -1423,7 +1458,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
const CTransaction& tx = *ws.m_ptx;
const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees,
ws.m_vsize, ws.m_entry->GetHeight(),
args.m_bypass_limits, args.m_package_submission,
args.m_ignore_rejects, args.m_package_submission,
IsCurrentForFeeEstimation(m_active_chainstate),
m_pool.HasNoInputsOf(tx));
m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence());
@ -1455,7 +1490,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
return MempoolAcceptResult::Failure(ws.m_state);
}
if (m_subpackage.m_rbf && !ReplacementChecks(ws)) {
if (m_subpackage.m_rbf && !ReplacementChecks(args, ws)) {
if (ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) {
// Failed for incentives-based fee reasons. Provide the effective feerate and which tx was included.
return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), single_wtxid);
@ -1487,7 +1522,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
const CTransaction& tx = *ws.m_ptx;
const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees,
ws.m_vsize, ws.m_entry->GetHeight(),
args.m_bypass_limits, args.m_package_submission,
args.m_ignore_rejects, args.m_package_submission,
IsCurrentForFeeEstimation(m_active_chainstate),
m_pool.HasNoInputsOf(tx));
m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence());
@ -1557,9 +1592,10 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
// At this point we have all in-mempool ancestors, and we know every transaction's vsize.
// Run the TRUC checks on the package.
std::string reason;
for (Workspace& ws : workspaces) {
if (auto err{PackageTRUCChecks(ws.m_ptx, ws.m_vsize, txns, ws.m_ancestors)}) {
package_state.Invalid(PackageValidationResult::PCKG_POLICY, "TRUC-violation", err.value());
if (auto err{PackageTRUCChecks(ws.m_ptx, ws.m_vsize, "truc-", reason, args.m_ignore_rejects, txns, ws.m_ancestors)}) {
package_state.Invalid(PackageValidationResult::PCKG_POLICY, reason, err.value());
return PackageMempoolAcceptResult(package_state, {});
}
}
@ -1584,7 +1620,8 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
[](const auto& ws) { return ws.m_ptx->GetWitnessHash(); });
TxValidationState placeholder_state;
if (args.m_package_feerates &&
!CheckFeeRate(m_subpackage.m_total_vsize, m_subpackage.m_total_modified_fees, placeholder_state)) {
(!args.m_ignore_rejects.count("package-fee-too-low")) &&
!CheckFeeRate(m_subpackage.m_total_vsize, m_subpackage.m_total_modified_fees, placeholder_state, empty_ignore_rejects)) {
package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
return PackageMempoolAcceptResult(package_state, {{workspaces.back().m_ptx->GetWitnessHash(),
MempoolAcceptResult::FeeFailure(placeholder_state, CFeeRate(m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize), all_package_wtxids)}});
@ -1592,7 +1629,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
// Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
// because it's unnecessary.
if (txns.size() > 1 && !PackageMempoolChecks(txns, workspaces, m_subpackage.m_total_vsize, package_state)) {
if (txns.size() > 1 && !PackageMempoolChecks(args, txns, workspaces, m_subpackage.m_total_vsize, package_state)) {
return PackageMempoolAcceptResult(package_state, std::move(results));
}
@ -1859,7 +1896,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
} // anon namespace
MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx,
int64_t accept_time, bool bypass_limits, bool test_accept)
int64_t accept_time, const ignore_rejects_type& ignore_rejects, bool test_accept)
{
AssertLockHeld(::cs_main);
const CChainParams& chainparams{active_chainstate.m_chainman.GetParams()};
@ -1867,7 +1904,7 @@ MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTra
CTxMemPool& pool{*active_chainstate.GetMempool()};
std::vector<COutPoint> coins_to_uncache;
auto args = MemPoolAccept::ATMPArgs::SingleAccept(chainparams, accept_time, bypass_limits, coins_to_uncache, test_accept);
auto args = MemPoolAccept::ATMPArgs::SingleAccept(chainparams, accept_time, ignore_rejects, coins_to_uncache, test_accept);
MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransaction(tx, args);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
// Remove coins that were not present in the coins cache before calling
@ -1889,7 +1926,7 @@ MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTra
}
PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool,
const Package& package, bool test_accept, const std::optional<CFeeRate>& client_maxfeerate)
const Package& package, bool test_accept, const std::optional<CFeeRate>& client_maxfeerate, const ignore_rejects_type& ignore_rejects)
{
AssertLockHeld(cs_main);
assert(!package.empty());
@ -1900,10 +1937,10 @@ PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxM
auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
AssertLockHeld(cs_main);
if (test_accept) {
auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache);
auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), ignore_rejects, coins_to_uncache);
return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args);
} else {
auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(chainparams, GetTime(), coins_to_uncache, client_maxfeerate);
auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(chainparams, GetTime(), coins_to_uncache, client_maxfeerate, ignore_rejects);
return MemPoolAccept(pool, active_chainstate).AcceptPackage(package, args);
}
}();
@ -4667,7 +4704,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo
return true;
}
MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef& tx, bool test_accept)
MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef& tx, bool test_accept, const ignore_rejects_type& ignore_rejects)
{
AssertLockHeld(cs_main);
Chainstate& active_chainstate = ActiveChainstate();
@ -4676,7 +4713,7 @@ MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef&
state.Invalid(TxValidationResult::TX_NO_MEMPOOL, "no-mempool");
return MempoolAcceptResult::Failure(state);
}
auto result = AcceptToMemoryPool(active_chainstate, tx, GetTime(), /*bypass_limits=*/ false, test_accept);
auto result = AcceptToMemoryPool(active_chainstate, tx, GetTime(), ignore_rejects, test_accept);
active_chainstate.GetMempool()->check(active_chainstate.CoinsTip(), active_chainstate.m_chain.Height() + 1);
return result;
}

View File

@ -256,6 +256,11 @@ struct PackageMempoolAcceptResult
: m_tx_results{ {wtxid, result} } {}
};
static const std::string rejectmsg_lowfee_mempool = "mempool min fee not met";
static const std::string rejectmsg_lowfee_relay = "min relay fee not met";
static const std::string rejectmsg_mempoolfull = "mempool full";
static const std::string rejectmsg_zero_mempool_entry_seq = "zero mempool entry sequence";
/**
* Try to add a transaction to the mempool. This is an internal function and is exposed only for testing.
* Client code should use ChainstateManager::ProcessTransaction()
@ -264,15 +269,18 @@ struct PackageMempoolAcceptResult
* @param[in] tx The transaction to submit for mempool acceptance.
* @param[in] accept_time The timestamp for adding the transaction to the mempool.
* It is also used to determine when the entry expires.
* @param[in] bypass_limits When true, don't enforce mempool fee and capacity limits,
* and set entry_sequence to zero.
* @param[in] ignore_rejects Set of reject reasons to ignore and bypass, if possible.
* @param[in] test_accept When true, run validation checks but don't submit to mempool.
*
* @returns a MempoolAcceptResult indicating whether the transaction was accepted/rejected with reason.
*/
MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx,
int64_t accept_time, bool bypass_limits, bool test_accept)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
int64_t accept_time, const ignore_rejects_type& ignore_rejects, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
static inline MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, int64_t accept_time, bool bypass_limits, bool test_accept) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
static const ignore_rejects_type ignore_rejects_legacy{rejectmsg_lowfee_mempool, rejectmsg_lowfee_relay, rejectmsg_mempoolfull, rejectmsg_zero_mempool_entry_seq};
return AcceptToMemoryPool(active_chainstate, tx, accept_time, (bypass_limits ? ignore_rejects_legacy : empty_ignore_rejects), test_accept);
}
/**
* Validate (and maybe submit) a package to the mempool. See doc/policy/packages.md for full details
@ -285,7 +293,7 @@ MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTra
* possible for the package to be partially submitted.
*/
PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool,
const Package& txns, bool test_accept, const std::optional<CFeeRate>& client_maxfeerate)
const Package& txns, bool test_accept, const std::optional<CFeeRate>& client_maxfeerate, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/* Mempool validation helper functions */
@ -1257,7 +1265,7 @@ public:
* @param[in] tx The transaction to submit for mempool acceptance.
* @param[in] test_accept When true, run validation checks but don't submit to mempool.
*/
[[nodiscard]] MempoolAcceptResult ProcessTransaction(const CTransactionRef& tx, bool test_accept=false)
[[nodiscard]] MempoolAcceptResult ProcessTransaction(const CTransactionRef& tx, bool test_accept=false, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! Load the block tree and coins database from disk, initializing state if we're running with -reindex

View File

@ -405,7 +405,7 @@ class MempoolAcceptanceTest(BitcoinTestFramework):
anchor_nonempty_wit_spend.rehash()
self.check_mempool_result(
result_expected=[{'txid': anchor_nonempty_wit_spend.rehash(), 'allowed': False, 'reject-reason': 'bad-witness-nonstandard'}],
result_expected=[{'txid': anchor_nonempty_wit_spend.rehash(), 'allowed': False, 'reject-reason': 'bad-witness-anchor-not-empty'}],
rawtxs=[anchor_nonempty_wit_spend.serialize().hex()],
maxfeerate=0,
)

View File

@ -57,7 +57,7 @@ class MempoolTRUC(BitcoinTestFramework):
self.log.info("Test TRUC-specific maximum transaction vsize")
tx_v3_heavy = self.wallet.create_self_transfer(target_weight=(TRUC_MAX_VSIZE + 1) * WITNESS_SCALE_FACTOR, version=3)
assert_greater_than_or_equal(tx_v3_heavy["tx"].get_vsize(), TRUC_MAX_VSIZE)
expected_error_heavy = f"TRUC-violation, version=3 tx {tx_v3_heavy['txid']} (wtxid={tx_v3_heavy['wtxid']}) is too big"
expected_error_heavy = f"truc-vsize-toobig, version=3 tx {tx_v3_heavy['txid']} (wtxid={tx_v3_heavy['wtxid']}) is too big"
assert_raises_rpc_error(-26, expected_error_heavy, node.sendrawtransaction, tx_v3_heavy["hex"])
self.check_mempool([])
@ -77,7 +77,7 @@ class MempoolTRUC(BitcoinTestFramework):
version=3
)
assert_greater_than_or_equal(tx_v3_child_heavy["tx"].get_vsize(), 1000)
expected_error_child_heavy = f"TRUC-violation, version=3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big"
expected_error_child_heavy = f"truc-child-toobig, version=3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big"
assert_raises_rpc_error(-26, expected_error_child_heavy, node.sendrawtransaction, tx_v3_child_heavy["hex"])
self.check_mempool([tx_v3_parent_normal["txid"]])
# tx has no descendants
@ -157,7 +157,7 @@ class MempoolTRUC(BitcoinTestFramework):
utxo_to_spend=tx_v3_parent["new_utxo"],
version=2
)
expected_error_v2_v3 = f"TRUC-violation, non-version=3 tx {tx_v3_child_rbf_v2['txid']} (wtxid={tx_v3_child_rbf_v2['wtxid']}) cannot spend from version=3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})"
expected_error_v2_v3 = f"truc-spent-by-nontruc, non-version=3 tx {tx_v3_child_rbf_v2['txid']} (wtxid={tx_v3_child_rbf_v2['wtxid']}) cannot spend from version=3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})"
assert_raises_rpc_error(-26, expected_error_v2_v3, node.sendrawtransaction, tx_v3_child_rbf_v2["hex"])
self.check_mempool([tx_v3_bip125_rbf_v2["txid"], tx_v3_parent["txid"], tx_v3_child["txid"]])
@ -289,20 +289,20 @@ class MempoolTRUC(BitcoinTestFramework):
self.check_mempool([])
result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_parent_2_normal["hex"], tx_v3_child_multiparent["hex"]])
assert_equal(result['package_msg'], f"TRUC-violation, tx {tx_v3_child_multiparent['txid']} (wtxid={tx_v3_child_multiparent['wtxid']}) would have too many ancestors")
assert_equal(result['package_msg'], f"truc-ancestors-toomany, tx {tx_v3_child_multiparent['txid']} (wtxid={tx_v3_child_multiparent['wtxid']}) would have too many ancestors")
self.check_mempool([])
self.check_mempool([])
result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_child_heavy["hex"]])
# tx_v3_child_heavy is heavy based on weight, not sigops.
assert_equal(result['package_msg'], f"TRUC-violation, version=3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big: {tx_v3_child_heavy['tx'].get_vsize()} > 1000 virtual bytes")
assert_equal(result['package_msg'], f"truc-child-toobig, version=3 child tx {tx_v3_child_heavy['txid']} (wtxid={tx_v3_child_heavy['wtxid']}) is too big: {tx_v3_child_heavy['tx'].get_vsize()} > 1000 virtual bytes")
self.check_mempool([])
tx_v3_parent = self.wallet.create_self_transfer(version=3)
tx_v3_child = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxo"], version=3)
tx_v3_grandchild = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_child["new_utxo"], version=3)
result = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child["hex"], tx_v3_grandchild["hex"]])
assert all([txresult["package-error"] == f"TRUC-violation, tx {tx_v3_grandchild['txid']} (wtxid={tx_v3_grandchild['wtxid']}) would have too many ancestors" for txresult in result])
assert all([txresult["package-error"] == f"truc-parent-and-child-both, tx {tx_v3_grandchild['txid']} (wtxid={tx_v3_grandchild['wtxid']}) would have too many ancestors" for txresult in result])
@cleanup(extra_args=None)
def test_truc_ancestors_package_and_mempool(self):
@ -331,7 +331,7 @@ class MempoolTRUC(BitcoinTestFramework):
# submitpackage(B, C) should fail
result = node.submitpackage([tx_0fee_parent["hex"], tx_child_violator["hex"]])
assert_equal(result['package_msg'], f"TRUC-violation, tx {tx_child_violator['txid']} (wtxid={tx_child_violator['wtxid']}) would have too many ancestors")
assert_equal(result['package_msg'], f"truc-parent-and-child-both, tx {tx_child_violator['txid']} (wtxid={tx_child_violator['wtxid']}) would have too many ancestors")
self.check_mempool([tx_in_mempool["txid"]])
@cleanup(extra_args=None)
@ -384,17 +384,17 @@ class MempoolTRUC(BitcoinTestFramework):
# Fails with another non-related transaction via testmempoolaccept
tx_unrelated = self.wallet.create_self_transfer(version=3)
result_test_unrelated = node.testmempoolaccept([tx_sibling_1["hex"], tx_unrelated["hex"]])
assert_equal(result_test_unrelated[0]["reject-reason"], "TRUC-violation")
assert_equal(result_test_unrelated[0]["reject-reason"], "truc-descendants-toomany")
# Fails in a package via testmempoolaccept
result_test_1p1c = node.testmempoolaccept([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]])
assert_equal(result_test_1p1c[0]["reject-reason"], "TRUC-violation")
assert_equal(result_test_1p1c[0]["reject-reason"], "truc-descendants-toomany")
# Allowed when tx is submitted in a package and evaluated individually.
# Note that the child failed since it would be the 3rd generation.
result_package_indiv = node.submitpackage([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]])
self.check_mempool([tx_mempool_parent["txid"], tx_sibling_1["txid"]])
expected_error_gen3 = f"TRUC-violation, tx {tx_has_mempool_uncle['txid']} (wtxid={tx_has_mempool_uncle['wtxid']}) would have too many ancestors"
expected_error_gen3 = f"truc-ancestors-toomany, tx {tx_has_mempool_uncle['txid']} (wtxid={tx_has_mempool_uncle['wtxid']}) would have too many ancestors"
assert_equal(result_package_indiv["tx-results"][tx_has_mempool_uncle['wtxid']]['error'], expected_error_gen3)
@ -405,7 +405,7 @@ class MempoolTRUC(BitcoinTestFramework):
# Child cannot pay for sibling eviction for parent, as it violates TRUC topology limits
result_package_cpfp = node.submitpackage([tx_sibling_3["hex"], tx_bumps_parent_with_sibling["hex"]])
self.check_mempool([tx_mempool_parent["txid"], tx_sibling_2["txid"]])
expected_error_cpfp = f"TRUC-violation, tx {tx_mempool_parent['txid']} (wtxid={tx_mempool_parent['wtxid']}) would exceed descendant count limit"
expected_error_cpfp = f"truc-descendants-toomany, tx {tx_mempool_parent['txid']} (wtxid={tx_mempool_parent['wtxid']}) would exceed descendant count limit"
assert_equal(result_package_cpfp["tx-results"][tx_sibling_3['wtxid']]['error'], expected_error_cpfp)
@ -426,7 +426,7 @@ class MempoolTRUC(BitcoinTestFramework):
)
self.check_mempool([])
result = node.submitpackage([tx_v3_parent["hex"], tx_v2_child["hex"]])
assert_equal(result['package_msg'], f"TRUC-violation, non-version=3 tx {tx_v2_child['txid']} (wtxid={tx_v2_child['wtxid']}) cannot spend from version=3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})")
assert_equal(result['package_msg'], f"truc-spent-by-nontruc, non-version=3 tx {tx_v2_child['txid']} (wtxid={tx_v2_child['wtxid']}) cannot spend from version=3 tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']})")
self.check_mempool([])
@cleanup(extra_args=None)
@ -447,11 +447,11 @@ class MempoolTRUC(BitcoinTestFramework):
assert all([result["allowed"] for result in test_accept_v2_and_v3])
test_accept_v3_from_v2 = node.testmempoolaccept([tx_v2["hex"], tx_v3_from_v2["hex"]])
expected_error_v3_from_v2 = f"TRUC-violation, version=3 tx {tx_v3_from_v2['txid']} (wtxid={tx_v3_from_v2['wtxid']}) cannot spend from non-version=3 tx {tx_v2['txid']} (wtxid={tx_v2['wtxid']})"
expected_error_v3_from_v2 = f"truc-spends-nontruc, version=3 tx {tx_v3_from_v2['txid']} (wtxid={tx_v3_from_v2['wtxid']}) cannot spend from non-version=3 tx {tx_v2['txid']} (wtxid={tx_v2['wtxid']})"
assert all([result["package-error"] == expected_error_v3_from_v2 for result in test_accept_v3_from_v2])
test_accept_v2_from_v3 = node.testmempoolaccept([tx_v3["hex"], tx_v2_from_v3["hex"]])
expected_error_v2_from_v3 = f"TRUC-violation, non-version=3 tx {tx_v2_from_v3['txid']} (wtxid={tx_v2_from_v3['wtxid']}) cannot spend from version=3 tx {tx_v3['txid']} (wtxid={tx_v3['wtxid']})"
expected_error_v2_from_v3 = f"truc-spent-by-nontruc, non-version=3 tx {tx_v2_from_v3['txid']} (wtxid={tx_v2_from_v3['wtxid']}) cannot spend from version=3 tx {tx_v3['txid']} (wtxid={tx_v3['wtxid']})"
assert all([result["package-error"] == expected_error_v2_from_v3 for result in test_accept_v2_from_v3])
test_accept_pairs = node.testmempoolaccept([tx_v2["hex"], tx_v3["hex"], tx_v2_from_v2["hex"], tx_v3_from_v3["hex"]])
@ -463,7 +463,7 @@ class MempoolTRUC(BitcoinTestFramework):
tx_v3_child_1 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][0], version=3)
tx_v3_child_2 = self.wallet.create_self_transfer(utxo_to_spend=tx_v3_parent["new_utxos"][1], version=3)
test_accept_2children = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"]])
expected_error_2children = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
expected_error_2children = f"truc-sibling-known, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
assert all([result["package-error"] == expected_error_2children for result in test_accept_2children])
# Extra TRUC transaction does not get incorrectly marked as extra descendant
@ -472,7 +472,7 @@ class MempoolTRUC(BitcoinTestFramework):
# Extra TRUC transaction does not make us ignore the extra descendant
test_accept_2children_with_exra = node.testmempoolaccept([tx_v3_parent["hex"], tx_v3_child_1["hex"], tx_v3_child_2["hex"], tx_v3_independent["hex"]])
expected_error_extra = f"TRUC-violation, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
expected_error_extra = f"truc-sibling-known, tx {tx_v3_parent['txid']} (wtxid={tx_v3_parent['wtxid']}) would exceed descendant count limit"
assert all([result["package-error"] == expected_error_extra for result in test_accept_2children_with_exra])
# Same result if the parent is already in mempool
node.sendrawtransaction(tx_v3_parent["hex"])
@ -609,7 +609,7 @@ class MempoolTRUC(BitcoinTestFramework):
utxo_to_spend=tx_with_multi_children["new_utxos"][2],
fee_rate=DEFAULT_FEE*50
)
expected_error_2siblings = f"TRUC-violation, tx {tx_with_multi_children['txid']} (wtxid={tx_with_multi_children['wtxid']}) would exceed descendant count limit"
expected_error_2siblings = f"truc-descendants-toomany, tx {tx_with_multi_children['txid']} (wtxid={tx_with_multi_children['wtxid']}) would exceed descendant count limit"
assert_raises_rpc_error(-26, expected_error_2siblings, node.sendrawtransaction, tx_with_sibling3["hex"])
# However, an RBF (with conflicting inputs) is possible even if the resulting cluster size exceeds 2

View File

@ -1387,7 +1387,7 @@ class SegWitTest(BitcoinTestFramework):
# First we test this transaction against std_node
# making sure the txid is added to the reject filter
self.std_node.announce_tx_and_wait_for_getdata(tx3)
test_transaction_acceptance(self.nodes[1], self.std_node, tx3, with_witness=True, accepted=False, reason="bad-txns-nonstandard-inputs")
test_transaction_acceptance(self.nodes[1], self.std_node, tx3, with_witness=True, accepted=False, reason="bad-txns-input-witness-unknown")
# Now the node will no longer ask for getdata of this transaction when advertised by same txid
self.std_node.announce_tx_and_wait_for_getdata(tx3, success=False)
@ -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

View File

@ -409,12 +409,22 @@ class RawTransactionsTest(BitcoinTestFramework):
testres = self.nodes[2].testmempoolaccept([tx['hex']], 0.00001000)[0]
assert_equal(testres['allowed'], False)
assert_equal(testres['reject-reason'], 'max-fee-exceeded')
testres = self.nodes[2].testmempoolaccept([tx['hex']], 0.00001000, ['foobar'])[0]
assert_equal(testres['allowed'], False)
assert_equal(testres['reject-reason'], 'max-fee-exceeded')
# unless ignored explicitly
testres = self.nodes[2].testmempoolaccept([tx['hex']], 0.00001000, ['max-fee-exceeded'])[0]
assert_equal(testres['allowed'], True)
assert('reject-reason' not in testres)
testres = self.nodes[2].testmempoolaccept([tx['hex']], 0.00001000, ['absurdly-high-fee'])[0]
assert_equal(testres['allowed'], True)
assert('reject-reason' not in testres)
# and sendrawtransaction should throw
assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, tx['hex'], 0.00001000)
# and the following calls should both succeed
testres = self.nodes[2].testmempoolaccept(rawtxs=[tx['hex']])[0]
assert_equal(testres['allowed'], True)
self.nodes[2].sendrawtransaction(hexstring=tx['hex'])
self.nodes[2].sendrawtransaction(hexstring=tx['hex'], maxfeerate=0.00001000, ignore_rejects=['max-fee-exceeded'])
# Test a transaction with a large fee.
# Fee rate is 0.20000000 BTC/kvB