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 * This boolean indicates whether the transaction was added
* without enforcing mempool fee limits. * 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. */ /* This boolean indicates whether the transaction is part of a package. */
const bool m_submitted_in_package; const bool m_submitted_in_package;
/* /*
@ -239,11 +239,11 @@ struct NewMempoolTransactionInfo {
explicit NewMempoolTransactionInfo(const CTransactionRef& tx, const CAmount& fee, explicit NewMempoolTransactionInfo(const CTransactionRef& tx, const CAmount& fee,
const int64_t vsize, const unsigned int height, 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 chainstate_is_current,
const bool has_no_mempool_parents) const bool has_no_mempool_parents)
: info{tx, fee, vsize, height}, : 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_submitted_in_package{submitted_in_package},
m_chainstate_is_current{chainstate_is_current}, m_chainstate_is_current{chainstate_is_current},
m_has_no_mempool_parents{has_no_mempool_parents} {} 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)) { if (nTime > TicksSinceEpoch<std::chrono::seconds>(now - pool.m_opts.expiry)) {
LOCK(cs_main); 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) { if (accepted.m_result_type == MempoolAcceptResult::ResultType::VALID) {
++count; ++count;
} else { } 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. // BroadcastTransaction can be called by RPC or by the wallet.
// chainman, mempool and peerman are initialized before the RPC server and wallet are started // 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(); wtxid = mempool_tx->GetWitnessHash();
} else { } else {
// Transaction is not already in the mempool. // 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) { if (max_tx_fee_set) {
// First, call ATMP with test_accept and check the fee. If ATMP // First, call ATMP with test_accept and check the fee. If ATMP
// fails here, return error immediately. // 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) { if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string); return HandleATMPError(result.m_state, err_string);
} else { } else {
@ -89,7 +92,7 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t
} }
} }
// Try to submit the transaction to the mempool. // 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) { if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
return HandleATMPError(result.m_state, err_string); return HandleATMPError(result.m_state, err_string);
} }

View File

@ -8,6 +8,7 @@
#include <common/messages.h> #include <common/messages.h>
#include <policy/feerate.h> #include <policy/feerate.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <policy/policy.h>
#include <variant> #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. * @param[in] wait_callback wait until callbacks have been processed to avoid stale result due to a sequentially RPC.
* return error * 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. * Return transaction with a given hash.

View File

@ -612,7 +612,7 @@ void CBlockPolicyEstimator::processTransaction(const NewMempoolTransactionInfo&
// - the node is not behind // - the node is not behind
// - the transaction is not dependent on any other transactions in the mempool // - the transaction is not dependent on any other transactions in the mempool
// - it's not part of a package. // - 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, // Only want to be updating estimates when our blockchain is synced,
// otherwise we'll miscalculate how many blocks its taking to get included. // 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)); 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) bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_datacarrier_bytes, TxoutType& whichType)
{ {
std::vector<std::vector<unsigned char> > vSolutions; std::vector<std::vector<unsigned char> > vSolutions;
@ -91,21 +95,36 @@ bool IsStandard(const CScript& scriptPubKey, const std::optional<unsigned>& max_
return true; 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) 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)) {
if (tx.version > TX_MAX_STANDARD_VERSION || tx.version < 1) {
reason = "version";
return false; 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 // Extremely large transactions with lots of inputs can cost the network
// almost as much to process as they cost the sender in fees, because // almost as much to process as they cost the sender in fees, because
// computing signature hashes is O(ninputs*txsize). Limiting transactions // computing signature hashes is O(ninputs*txsize). Limiting transactions
// to MAX_STANDARD_TX_WEIGHT mitigates CPU exhaustion attacks. // to MAX_STANDARD_TX_WEIGHT mitigates CPU exhaustion attacks.
unsigned int sz = GetTransactionWeight(tx); unsigned int sz = GetTransactionWeight(tx);
if (sz > MAX_STANDARD_TX_WEIGHT) { if (sz > MAX_STANDARD_TX_WEIGHT) {
reason = "tx-size"; MaybeReject("tx-size");
return false;
} }
for (const CTxIn& txin : tx.vin) 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 // 20-of-20 CHECKMULTISIG scriptPubKey, though such a scriptPubKey
// is not considered standard. // is not considered standard.
if (txin.scriptSig.size() > MAX_STANDARD_SCRIPTSIG_SIZE) { if (txin.scriptSig.size() > MAX_STANDARD_SCRIPTSIG_SIZE) {
reason = "scriptsig-size"; MaybeReject("scriptsig-size");
return false;
} }
if (!txin.scriptSig.IsPushOnly()) { if (!txin.scriptSig.IsPushOnly()) {
reason = "scriptsig-not-pushonly"; MaybeReject("scriptsig-not-pushonly");
return false;
} }
} }
@ -132,25 +149,28 @@ bool IsStandardTx(const CTransaction& tx, const std::optional<unsigned>& max_dat
TxoutType whichType; TxoutType whichType;
for (const CTxOut& txout : tx.vout) { for (const CTxOut& txout : tx.vout) {
if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) { if (!::IsStandard(txout.scriptPubKey, max_datacarrier_bytes, whichType)) {
reason = "scriptpubkey"; if (whichType == TxoutType::WITNESS_UNKNOWN) {
return false; MaybeReject("scriptpubkey-unknown-witnessversion");
} else {
MaybeReject("scriptpubkey");
}
} }
if (whichType == TxoutType::NULL_DATA) if (whichType == TxoutType::NULL_DATA) {
nDataOut++; nDataOut++;
continue;
}
else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) { else if ((whichType == TxoutType::MULTISIG) && (!permit_bare_multisig)) {
reason = "bare-multisig"; MaybeReject("bare-multisig");
return false; }
} else if (IsDust(txout, dust_relay_fee)) { if (IsDust(txout, dust_relay_fee)) {
reason = "dust"; MaybeReject("dust");
return false;
} }
} }
// only one OP_RETURN txout is permitted // only one OP_RETURN txout is permitted
if (nDataOut > 1) { if (nDataOut > 1) {
reason = "multi-op-return"; MaybeReject("multi-op-return");
return false;
} }
return true; 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. * 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()) { if (tx.IsCoinBase()) {
return true; // Coinbases don't use vin normally 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; std::vector<std::vector<unsigned char> > vSolutions;
TxoutType whichType = Solver(prev.scriptPubKey, 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 // WITNESS_UNKNOWN failures are typically also caught with a policy
// flag in the script interpreter, but it can be helpful to catch // flag in the script interpreter, but it can be helpful to catch
// this type of NONSTANDARD transaction earlier in transaction // this type of NONSTANDARD transaction earlier in transaction
// validation. // validation.
return false; MaybeReject("witness-unknown");
} else if (whichType == TxoutType::SCRIPTHASH) { } 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; std::vector<std::vector<unsigned char> > stack;
// convert the scriptSig into a stack, so we can inspect the redeemScript // 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)) 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; return false;
}
if (stack.empty()) if (stack.empty())
{
// Also invalid
out_reason = reason_prefix + "scriptcheck-missing";
return false; return false;
}
CScript subscript(stack.back().begin(), stack.back().end()); CScript subscript(stack.back().begin(), stack.back().end());
if (subscript.GetSigOpCount(true) > MAX_P2SH_SIGOPS) { 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; return true;
} }
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs) bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects)
{ {
if (tx.IsCoinBase()) if (tx.IsCoinBase())
return true; // Coinbases are skipped return true; // Coinbases are skipped
@ -227,7 +263,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// witness stuffing detected // witness stuffing detected
if (prevScript.IsPayToAnchor()) { if (prevScript.IsPayToAnchor()) {
return false; MaybeReject("anchor-not-empty");
} }
bool p2sh = false; 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. // into a stack. We do not check IsPushOnly nor compare the hash as these will be done later anyway.
// If the check fails at this stage, we know that this txid must be a bad one. // If the check fails at this stage, we know that this txid must be a bad one.
if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE)) if (!EvalScript(stack, tx.vin[i].scriptSig, SCRIPT_VERIFY_NONE, BaseSignatureChecker(), SigVersion::BASE))
{
out_reason = reason_prefix + "scriptsig-failure";
return false; return false;
}
if (stack.empty()) if (stack.empty())
{
out_reason = reason_prefix + "scriptcheck-missing";
return false; return false;
}
prevScript = CScript(stack.back().begin(), stack.back().end()); prevScript = CScript(stack.back().begin(), stack.back().end());
p2sh = true; p2sh = true;
} }
@ -249,18 +291,21 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// Non-witness program must not be associated with any witness // Non-witness program must not be associated with any witness
if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram)) if (!prevScript.IsWitnessProgram(witnessversion, witnessprogram))
{
out_reason = reason_prefix + "nonwitness-input";
return false; return false;
}
// Check P2WSH standard limits // Check P2WSH standard limits
if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) { if (witnessversion == 0 && witnessprogram.size() == WITNESS_V0_SCRIPTHASH_SIZE) {
if (tx.vin[i].scriptWitness.stack.back().size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE) if (tx.vin[i].scriptWitness.stack.back().size() > MAX_STANDARD_P2WSH_SCRIPT_SIZE)
return false; MaybeReject("script-size");
size_t sizeWitnessStack = tx.vin[i].scriptWitness.stack.size() - 1; size_t sizeWitnessStack = tx.vin[i].scriptWitness.stack.size() - 1;
if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS) if (sizeWitnessStack > MAX_STANDARD_P2WSH_STACK_ITEMS)
return false; MaybeReject("stackitem-count");
for (unsigned int j = 0; j < sizeWitnessStack; j++) { for (unsigned int j = 0; j < sizeWitnessStack; j++) {
if (tx.vin[i].scriptWitness.stack[j].size() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE) if (tx.vin[i].scriptWitness.stack[j].size() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE)
return false; MaybeReject("stackitem-size");
} }
} }
@ -272,17 +317,28 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
Span stack{tx.vin[i].scriptWitness.stack}; Span stack{tx.vin[i].scriptWitness.stack};
if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) { if (stack.size() >= 2 && !stack.back().empty() && stack.back()[0] == ANNEX_TAG) {
// Annexes are nonstandard as long as no semantics are defined for them. // Annexes are nonstandard as long as no semantics are defined for them.
return false; MaybeReject("taproot-annex");
// If reject reason is ignored, continue as if the annex wasn't there.
SpanPopBack(stack);
} }
if (stack.size() >= 2) { if (stack.size() >= 2) {
// Script path spend (2 or more stack elements after removing optional annex) // Script path spend (2 or more stack elements after removing optional annex)
const auto& control_block = SpanPopBack(stack); const auto& control_block = SpanPopBack(stack);
SpanPopBack(stack); // Ignore script SpanPopBack(stack); // Ignore script
if (control_block.empty()) return false; // Empty control block is invalid if (control_block.empty()) {
// Empty control block is invalid
out_reason = reason_prefix + "taproot-control-missing";
return false;
}
if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) { if ((control_block[0] & TAPROOT_LEAF_MASK) == TAPROOT_LEAF_TAPSCRIPT) {
// Leaf version 0xc0 (aka Tapscript, see BIP 342) // Leaf version 0xc0 (aka Tapscript, see BIP 342)
if (!ignore_rejects.count(reason_prefix + "taproot-stackitem-size")) {
for (const auto& item : stack) { for (const auto& item : stack) {
if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) return false; if (item.size() > MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE) {
out_reason = reason_prefix + "taproot-stackitem-size";
return false;
}
}
} }
} }
} else if (stack.size() == 1) { } else if (stack.size() == 1) {
@ -290,6 +346,7 @@ bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs)
// (no policy rules apply) // (no policy rules apply)
} else { } else {
// 0 stack elements; this is already invalid by consensus rules // 0 stack elements; this is already invalid by consensus rules
out_reason = reason_prefix + "taproot-witness-missing";
return false; return false;
} }
} }

View File

@ -14,6 +14,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <unordered_set>
class CCoinsViewCache; class CCoinsViewCache;
class CFeeRate; 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. */ /** 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}; 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); CAmount GetDustThreshold(const CTxOut& txout, const CFeeRate& dustRelayFee);
bool IsDust(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 * Check for standard transaction types
* @return True if all outputs (scriptPubKeys) use only standard transaction forms * @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 * Check for standard transaction types
* @param[in] mapInputs Map of previous transactions that have outputs we're spending * @param[in] mapInputs Map of previous transactions that have outputs we're spending
* @return True if all inputs (scriptSigs) use only standard transaction forms * @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: * Check if the transaction is over standard P2WSH resources limit:
* 3600bytes witnessScript size, 80bytes per witness stack element, 100 witness stack elements * 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. * Also enforce a maximum stack item size limit and no annexes for tapscript spends.
*/ */
bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs); bool IsWitnessStandard(const CTransaction& tx, const CCoinsViewCache& mapInputs, const std::string& reason_prefix, std::string& out_reason, const ignore_rejects_type& ignore_rejects=empty_ignore_rejects);
/** Compute the virtual transaction size (weight reinterpreted as bytes). */ /** Compute the virtual transaction size (weight reinterpreted as bytes). */
int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop); int64_t GetVirtualTransactionSize(int64_t nWeight, int64_t nSigOpCost, unsigned int bytes_per_sigop);

View File

@ -7,6 +7,7 @@
#include <consensus/amount.h> #include <consensus/amount.h>
#include <kernel/mempool_entry.h> #include <kernel/mempool_entry.h>
#include <policy/feerate.h> #include <policy/feerate.h>
#include <policy/policy.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <sync.h> #include <sync.h>
#include <tinyformat.h> #include <tinyformat.h>
@ -59,7 +60,8 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx)
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx, std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx,
CTxMemPool& pool, CTxMemPool& pool,
const CTxMemPool::setEntries& iters_conflicting, const CTxMemPool::setEntries& iters_conflicting,
CTxMemPool::setEntries& all_conflicts) CTxMemPool::setEntries& all_conflicts,
const ignore_rejects_type& ignore_rejects)
{ {
AssertLockHeld(pool.cs); AssertLockHeld(pool.cs);
const uint256 txid = tx.GetHash(); 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 // 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 // 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. // 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", return strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
txid.ToString(), txid.ToString(),
nConflictingCount, nConflictingCount,

View File

@ -6,6 +6,7 @@
#define BITCOIN_POLICY_RBF_H #define BITCOIN_POLICY_RBF_H
#include <consensus/amount.h> #include <consensus/amount.h>
#include <policy/policy.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <threadsafety.h> #include <threadsafety.h>
#include <txmempool.h> #include <txmempool.h>
@ -68,7 +69,8 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx);
*/ */
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx, CTxMemPool& pool, std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx, CTxMemPool& pool,
const CTxMemPool::setEntries& iters_conflicting, 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); EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
/** The replacement transaction may only include an unconfirmed input if that input was included in /** 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, 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 Package& package,
const CTxMemPool::setEntries& mempool_ancestors) 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. // Now we have all ancestors, so we can start checking TRUC rules.
if (ptx->version == TRUC_VERSION) { if (ptx->version == TRUC_VERSION) {
// SingleTRUCChecks should have checked this already. // 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", 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); 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", return strprintf("tx %s (wtxid=%s) would have too many ancestors",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString()); 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}; const bool has_parent{mempool_ancestors.size() + in_package_parents.size() > 0};
if (has_parent) { if (has_parent) {
// A TRUC child cannot be too large. // 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", return strprintf("version=3 child tx %s (wtxid=%s) is too big: %u > %u virtual bytes",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
vsize, TRUC_CHILD_MAX_VSIZE); 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 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)", return strprintf("version=3 tx %s (wtxid=%s) cannot spend from non-version=3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
parent_info.m_txid.ToString(), parent_info.m_wtxid.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 // 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 // sibling is to-be-replaced (done in SingleTRUCChecks) because these transactions
// are within the same package. // 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", return strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
parent_info.m_txid.ToString(), parent_info.m_txid.ToString(),
parent_info.m_wtxid.ToString()); parent_info.m_wtxid.ToString());
} }
// This tx can't have both a parent and an in-package child. // 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", return strprintf("tx %s (wtxid=%s) would have too many ancestors",
package_tx->GetHash().ToString(), package_tx->GetWitnessHash().ToString()); 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", return strprintf("tx %s (wtxid=%s) would exceed descendant count limit",
parent_info.m_txid.ToString(), parent_info.m_wtxid.ToString()); 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 { } else {
// Non-TRUC transactions cannot have TRUC parents. // Non-TRUC transactions cannot have TRUC parents.
for (auto it : mempool_ancestors) { 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)", return strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
it->GetSharedTx()->GetHash().ToString(), it->GetSharedTx()->GetWitnessHash().ToString()); it->GetSharedTx()->GetHash().ToString(), it->GetSharedTx()->GetWitnessHash().ToString());
} }
} }
for (const auto& index: in_package_parents) { 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)", return strprintf("non-version=3 tx %s (wtxid=%s) cannot spend from version=3 tx %s (wtxid=%s)",
ptx->GetHash().ToString(), ptx->GetHash().ToString(),
ptx->GetWitnessHash().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, 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 CTxMemPool::setEntries& mempool_ancestors,
const std::set<Txid>& direct_conflicts, const std::set<Txid>& direct_conflicts,
int64_t vsize) int64_t vsize)
{ {
// Check TRUC and non-TRUC inheritance. // Check TRUC and non-TRUC inheritance.
for (const auto& entry : mempool_ancestors) { 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)", 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(), ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()), entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->GetWitnessHash().ToString()),
nullptr); 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)", 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(), ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(),
entry->GetSharedTx()->GetHash().ToString(), entry->GetSharedTx()->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. // The rest of the rules only apply to transactions with version=3.
if (ptx->version != TRUC_VERSION) return std::nullopt; 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", 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), ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_MAX_VSIZE),
nullptr); nullptr);
} }
// Check that TRUC_ANCESTOR_LIMIT would not be violated. // 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", return std::make_pair(strprintf("tx %s (wtxid=%s) would have too many ancestors",
ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString()), ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString()),
nullptr); nullptr);
@ -204,7 +221,8 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CT
// Remaining checks only pertain to transactions with unconfirmed ancestors. // Remaining checks only pertain to transactions with unconfirmed ancestors.
if (mempool_ancestors.size() > 0) { if (mempool_ancestors.size() > 0) {
// If this transaction spends TRUC parents, it cannot be too large. // 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", 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), ptx->GetHash().ToString(), ptx->GetWitnessHash().ToString(), vsize, TRUC_CHILD_MAX_VSIZE),
nullptr); nullptr);
@ -222,7 +240,7 @@ std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CT
const bool child_will_be_replaced = !children.empty() && const bool child_will_be_replaced = !children.empty() &&
std::any_of(children.cbegin(), children.cend(), std::any_of(children.cbegin(), children.cend(),
[&direct_conflicts](const CTxMemPoolEntry& child){return direct_conflicts.count(child.GetTx().GetHash()) > 0;}); [&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 // 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 // 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 // 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 // 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. // 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", return std::make_pair(strprintf("tx %u (wtxid=%s) would exceed descendant count limit",
parent_entry->GetSharedTx()->GetHash().ToString(), parent_entry->GetSharedTx()->GetHash().ToString(),
parent_entry->GetSharedTx()->GetWitnessHash().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. * applicable.
*/ */
std::optional<std::pair<std::string, CTransactionRef>> SingleTRUCChecks(const CTransactionRef& ptx, 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 CTxMemPool::setEntries& mempool_ancestors,
const std::set<Txid>& direct_conflicts, const std::set<Txid>& direct_conflicts,
int64_t vsize); 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. * @returns debug string if an error occurs, std::nullopt otherwise.
* */ * */
std::optional<std::string> PackageTRUCChecks(const CTransactionRef& ptx, int64_t vsize, 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 Package& package,
const CTxMemPool::setEntries& mempool_ancestors); const CTxMemPool::setEntries& mempool_ancestors);

View File

@ -142,8 +142,10 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "signrawtransactionwithwallet", 1, "prevtxs" }, { "signrawtransactionwithwallet", 1, "prevtxs" },
{ "sendrawtransaction", 1, "maxfeerate" }, { "sendrawtransaction", 1, "maxfeerate" },
{ "sendrawtransaction", 2, "maxburnamount" }, { "sendrawtransaction", 2, "maxburnamount" },
{ "sendrawtransaction", 3, "ignore_rejects" },
{ "testmempoolaccept", 0, "rawtxs" }, { "testmempoolaccept", 0, "rawtxs" },
{ "testmempoolaccept", 1, "maxfeerate" }, { "testmempoolaccept", 1, "maxfeerate" },
{ "testmempoolaccept", 2, "ignore_rejects" },
{ "submitpackage", 0, "package" }, { "submitpackage", 0, "package" },
{ "submitpackage", 1, "maxfeerate" }, { "submitpackage", 1, "maxfeerate" },
{ "submitpackage", 2, "maxburnamount" }, { "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"}, {"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())}, {"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 + "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)}, {"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" "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" "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{
RPCResult::Type::STR_HEX, "", "The transaction hash in hex" RPCResult::Type::STR_HEX, "", "The transaction hash in hex"
@ -80,7 +89,24 @@ static RPCHelpMan sendrawtransaction()
}, },
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue [&](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; CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) { if (!DecodeHexTx(mtx, request.params[0].get_str())) {
@ -95,12 +121,18 @@ static RPCHelpMan sendrawtransaction()
CTransactionRef tx(MakeTransactionRef(std::move(mtx))); 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; std::string err_string;
AssertLockNotHeld(cs_main); AssertLockNotHeld(cs_main);
NodeContext& node = EnsureAnyNodeContext(request.context); 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) { if (TransactionError::OK != err) {
throw JSONRPCTransactionError(err, err_string); 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())}, {"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 + "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."},
{"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{
RPCResult::Type::ARR, "", "The result of the mempool acceptance test for each raw transaction in the input array.\n" 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 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; std::vector<CTransactionRef> txns;
txns.reserve(raw_transactions.size()); txns.reserve(raw_transactions.size());
for (const auto& rawtx : raw_transactions.getValues()) { for (const auto& rawtx : raw_transactions.getValues()) {
@ -191,9 +238,9 @@ static RPCHelpMan testmempoolaccept()
Chainstate& chainstate = chainman.ActiveChainstate(); Chainstate& chainstate = chainman.ActiveChainstate();
const PackageMempoolAcceptResult package_result = [&] { const PackageMempoolAcceptResult package_result = [&] {
LOCK(::cs_main); 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(), 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); UniValue rpc_result(UniValue::VARR);
@ -223,7 +270,8 @@ static RPCHelpMan testmempoolaccept()
// Check that fee does not exceed maximum fee // Check that fee does not exceed maximum fee
const int64_t virtual_size = tx_result.m_vsize.value(); const int64_t virtual_size = tx_result.m_vsize.value();
const CAmount max_raw_tx_fee = max_raw_tx_fee_rate.GetFee(virtual_size); 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("allowed", false);
result_inner.pushKV("reject-reason", "max-fee-exceeded"); result_inner.pushKV("reject-reason", "max-fee-exceeded");
exit_early = true; 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)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_has_mempool_parents = fuzzed_data_provider.ConsumeBool();
const auto tx_info = NewMempoolTransactionInfo(entry.GetSharedTx(), entry.GetFee(), const auto tx_info = NewMempoolTransactionInfo(entry.GetSharedTx(), entry.GetFee(),
entry.GetTxSize(), entry.GetHeight(), entry.GetTxSize(), entry.GetHeight(),
/*mempool_limit_bypassed=*/false, empty_ignore_rejects,
tx_submitted_in_package, tx_submitted_in_package,
/*chainstate_is_current=*/true, /*chainstate_is_current=*/true,
tx_has_mempool_parents); tx_has_mempool_parents);

View File

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

View File

@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
feeV[j], feeV[j],
virtual_size, virtual_size,
entry.nHeight, entry.nHeight,
/*mempool_limit_bypassed=*/false, empty_ignore_rejects,
/*submitted_in_package=*/false, /*submitted_in_package=*/false,
/*chainstate_is_current=*/true, /*chainstate_is_current=*/true,
/*has_no_mempool_parents=*/true)}; /*has_no_mempool_parents=*/true)};
@ -172,7 +172,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
feeV[j], feeV[j],
virtual_size, virtual_size,
entry.nHeight, entry.nHeight,
/*mempool_limit_bypassed=*/false, empty_ignore_rejects,
/*submitted_in_package=*/false, /*submitted_in_package=*/false,
/*chainstate_is_current=*/true, /*chainstate_is_current=*/true,
/*has_no_mempool_parents=*/true)}; /*has_no_mempool_parents=*/true)};
@ -236,7 +236,7 @@ BOOST_AUTO_TEST_CASE(BlockPolicyEstimates)
feeV[j], feeV[j],
virtual_size, virtual_size,
entry.nHeight, entry.nHeight,
/*mempool_limit_bypassed=*/false, empty_ignore_rejects,
/*submitted_in_package=*/false, /*submitted_in_package=*/false,
/*chainstate_is_current=*/true, /*chainstate_is_current=*/true,
/*has_no_mempool_parents=*/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_parent = result_quit_early.m_tx_results.find(tx_parent_invalid->GetWitnessHash());
auto it_child = result_quit_early.m_tx_results.find(tx_child->GetWitnessHash()); auto it_child = result_quit_early.m_tx_results.find(tx_child->GetWitnessHash());
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetResult(), TxValidationResult::TX_WITNESS_MUTATED); BOOST_CHECK_EQUAL(it_parent->second.m_state.GetResult(), TxValidationResult::TX_WITNESS_MUTATED);
BOOST_CHECK_EQUAL(it_parent->second.m_state.GetRejectReason(), "bad-witness-nonstandard"); BOOST_CHECK_EQUAL(it_parent->second.m_state.GetRejectReason(), "bad-witness-nonwitness-input");
BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MISSING_INPUTS); BOOST_CHECK_EQUAL(it_child->second.m_state.GetResult(), TxValidationResult::TX_MISSING_INPUTS);
BOOST_CHECK_EQUAL(it_child->second.m_state.GetRejectReason(), "bad-txns-inputs-missingorspent"); BOOST_CHECK_EQUAL(it_child->second.m_state.GetRejectReason(), "bad-txns-inputs-missingorspent");
} }

View File

@ -20,6 +20,18 @@
BOOST_AUTO_TEST_SUITE(txvalidation_tests) 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. * Ensure that the mempool won't accept coinbase transactions.
*/ */

View File

@ -448,7 +448,7 @@ public:
struct ATMPArgs { struct ATMPArgs {
const CChainParams& m_chainparams; const CChainParams& m_chainparams;
const int64_t m_accept_time; 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 * Return any outpoints which were not previously present in the coins
* cache, but were added as a result of validating the tx for mempool * cache, but were added as a result of validating the tx for mempool
@ -485,11 +485,11 @@ public:
/** Parameters for single transaction mempool validation. */ /** Parameters for single transaction mempool validation. */
static ATMPArgs SingleAccept(const CChainParams& chainparams, int64_t accept_time, 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) { bool test_accept) {
return ATMPArgs{/* m_chainparams */ chainparams, return ATMPArgs{/* m_chainparams */ chainparams,
/* m_accept_time */ accept_time, /* m_accept_time */ accept_time,
/* m_bypass_limits */ bypass_limits, /* m_ignore_rejects */ ignore_rejects,
/* m_coins_to_uncache */ coins_to_uncache, /* m_coins_to_uncache */ coins_to_uncache,
/* m_test_accept */ test_accept, /* m_test_accept */ test_accept,
/* m_allow_replacement */ true, /* m_allow_replacement */ true,
@ -503,10 +503,10 @@ public:
/** Parameters for test package mempool validation through testmempoolaccept. */ /** Parameters for test package mempool validation through testmempoolaccept. */
static ATMPArgs PackageTestAccept(const CChainParams& chainparams, int64_t accept_time, 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, return ATMPArgs{/* m_chainparams */ chainparams,
/* m_accept_time */ accept_time, /* m_accept_time */ accept_time,
/* m_bypass_limits */ false, /* m_ignore_rejects */ ignore_rejects,
/* m_coins_to_uncache */ coins_to_uncache, /* m_coins_to_uncache */ coins_to_uncache,
/* m_test_accept */ true, /* m_test_accept */ true,
/* m_allow_replacement */ false, /* m_allow_replacement */ false,
@ -520,10 +520,10 @@ public:
/** Parameters for child-with-unconfirmed-parents package validation. */ /** Parameters for child-with-unconfirmed-parents package validation. */
static ATMPArgs PackageChildWithParents(const CChainParams& chainparams, int64_t accept_time, 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, return ATMPArgs{/* m_chainparams */ chainparams,
/* m_accept_time */ accept_time, /* m_accept_time */ accept_time,
/* m_bypass_limits */ false, /* m_ignore_rejects */ ignore_rejects,
/* m_coins_to_uncache */ coins_to_uncache, /* m_coins_to_uncache */ coins_to_uncache,
/* m_test_accept */ false, /* m_test_accept */ false,
/* m_allow_replacement */ true, /* m_allow_replacement */ true,
@ -539,7 +539,7 @@ public:
static ATMPArgs SingleInPackageAccept(const ATMPArgs& package_args) { static ATMPArgs SingleInPackageAccept(const ATMPArgs& package_args) {
return ATMPArgs{/* m_chainparams */ package_args.m_chainparams, return ATMPArgs{/* m_chainparams */ package_args.m_chainparams,
/* m_accept_time */ package_args.m_accept_time, /* 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_coins_to_uncache */ package_args.m_coins_to_uncache,
/* m_test_accept */ package_args.m_test_accept, /* m_test_accept */ package_args.m_test_accept,
/* m_allow_replacement */ true, /* m_allow_replacement */ true,
@ -556,7 +556,7 @@ public:
// mixing up the order of the arguments. Use static functions above instead. // mixing up the order of the arguments. Use static functions above instead.
ATMPArgs(const CChainParams& chainparams, ATMPArgs(const CChainParams& chainparams,
int64_t accept_time, int64_t accept_time,
bool bypass_limits, const ignore_rejects_type& ignore_rejects,
std::vector<COutPoint>& coins_to_uncache, std::vector<COutPoint>& coins_to_uncache,
bool test_accept, bool test_accept,
bool allow_replacement, bool allow_replacement,
@ -567,7 +567,7 @@ public:
bool allow_carveouts) bool allow_carveouts)
: m_chainparams{chainparams}, : m_chainparams{chainparams},
m_accept_time{accept_time}, m_accept_time{accept_time},
m_bypass_limits{bypass_limits}, m_ignore_rejects{ignore_rejects},
m_coins_to_uncache{coins_to_uncache}, m_coins_to_uncache{coins_to_uncache},
m_test_accept{test_accept}, m_test_accept{test_accept},
m_allow_replacement{allow_replacement}, m_allow_replacement{allow_replacement},
@ -662,6 +662,23 @@ private:
PrecomputedTransactionData m_precomputed_txdata; 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. // Run the policy checks on a given transaction, excluding any script checks.
// Looks up inputs, calculates feerate, considers replacement, evaluates // Looks up inputs, calculates feerate, considers replacement, evaluates
// package limits, etc. As this function can be invoked for "free" by a peer, // 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); 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. // 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 // Enforce package mempool ancestor/descendant limits (distinct from individual
// ancestor/descendant limits done in PreChecks) and run Package RBF checks. // 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, std::vector<Workspace>& workspaces,
int64_t total_vsize, int64_t total_vsize,
PackageValidationState& package_state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); PackageValidationState& package_state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
@ -701,16 +718,16 @@ private:
EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs); EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Compare a package's feerate against minimum allowed. // 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(::cs_main);
AssertLockHeld(m_pool.cs); AssertLockHeld(m_pool.cs);
CAmount mempoolRejectFee = m_pool.GetMinFee().GetFee(package_size); 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)); 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", 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))); 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 // Copy/alias what we need out of args
const int64_t nAcceptTime = args.m_accept_time; 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; std::vector<COutPoint>& coins_to_uncache = args.m_coins_to_uncache;
// Alias what we need out of ws // 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) // Rather not work on nonstandard transactions (unless -testnet/-regtest)
std::string reason; 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); return state.Invalid(TxValidationResult::TX_NOT_STANDARD, reason);
} }
// Transactions smaller than 65 non-witness bytes are not relayed to mitigate CVE-2017-12842. // 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) 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 // 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 // block; we don't want our mempool filled up with transactions that can't
// be mined yet. // be mined yet.
if (!CheckFinalTxAtTip(*Assert(m_active_chainstate.m_chain.Tip()), tx)) { 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()))) { 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 // Replaceability signaling of the original transactions may be
// ignored due to node setting. // 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) { if (!allow_rbf) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict"); 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 // 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. // 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)}; 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)) { if (!lock_points.has_value() || !CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), *lock_points)) {
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final"); 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 return false; // state filled in by CheckTxInputs
} }
if (m_pool.m_opts.require_standard && !AreInputsStandard(tx, m_view)) { 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, "bad-txns-nonstandard-inputs"); return state.Invalid(TxValidationResult::TX_INPUTS_NOT_STANDARD, reason);
} }
// Check for non-standard witnesses. // Check for non-standard witnesses.
if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view)) { if (tx.HasWitness() && m_pool.m_opts.require_standard && !IsWitnessStandard(tx, m_view, "bad-witness-", reason, ignore_rejects)) {
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard"); return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, reason);
} }
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS); int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
@ -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. // 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, entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence,
fSpendsCoinbase, nSigOpsCost, lock_points.value())); fSpendsCoinbase, nSigOpsCost, lock_points.value()));
ws.m_vsize = entry->GetTxSize(); ws.m_vsize = entry->GetTxSize();
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST) 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)); strprintf("%d", nSigOpsCost));
// No individual transactions are allowed below the min relay feerate except from disconnected blocks. // 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 // method of ensuring the tx remains bumped. For example, the fee-bumping child could disappear
// due to a replacement. // due to a replacement.
// The only exception is TRUC transactions. // 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 // 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. // TX_RECONSIDERABLE, because it cannot be bypassed using package validation.
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "min relay fee not met", 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 // 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 // blocks and transactions in a package. Package transactions will be checked using package
// feerate later. // 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); 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(); 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); ws.m_ancestors = std::move(*ancestors);
} else { } else {
// If CalculateMemPoolAncestors fails second time, we want the original error string. // 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 // 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 // check using the full ancestor set here because it's more convenient to use what we have
// already calculated. // 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. // Single transaction contexts only.
if (args.m_allow_sibling_eviction && err->second != nullptr) { if (args.m_allow_sibling_eviction && err->second != nullptr) {
// We should only be considering where replacement is considered valid as well. // 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 // (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. // have a non-TRUC and non-BIP125 descendant is due to a reorg.
} else { } 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; return true;
} }
bool MemPoolAccept::ReplacementChecks(Workspace& ws) bool MemPoolAccept::ReplacementChecks(ATMPArgs& args, Workspace& ws)
{ {
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs); 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 // 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 // descendant transaction of a direct conflict to pay a higher feerate than the transaction that
// might replace them, under these rules. // 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)}) { 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 // This fee-related failure is TX_RECONSIDERABLE because validating in a package may change
// the result. // the result.
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, return state.Invalid(TxValidationResult::TX_RECONSIDERABLE,
strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string); strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
} }
} // ignore_rejects
// Calculate all conflicting entries and enforce Rule #5. // 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, return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
strprintf("too many potential replacements%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string); strprintf("too many potential replacements%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
} }
// Enforce Rule #2. // 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)}) { 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. // Sibling eviction is only done for TRUC transactions, which cannot have multiple ancestors.
Assume(!ws.m_sibling_eviction); Assume(!ws.m_sibling_eviction);
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
strprintf("replacement-adds-unconfirmed%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string); 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 // 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. // 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_fees += it->GetModifiedFee();
m_subpackage.m_conflicting_size += it->GetTxSize(); 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, 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)}) { m_pool.m_opts.incremental_relay_feerate, hash)}) {
// Result may change in a package context // Result may change in a package context
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, return state.Invalid(TxValidationResult::TX_RECONSIDERABLE,
strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string); strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
} }
} // ignore_rejects
return true; 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, std::vector<Workspace>& workspaces,
const int64_t total_vsize, const int64_t total_vsize,
PackageValidationState& package_state) PackageValidationState& package_state)
@ -1144,7 +1174,13 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
assert(txns.size() == workspaces.size()); 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) { if (!result) {
// This is a package-wide error, separate from an individual transaction error. // 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); 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 CTransaction& tx = *ws.m_ptx;
const uint256& hash = ws.m_hash; const uint256& hash = ws.m_hash;
TxValidationState& state = ws.m_state; TxValidationState& state = ws.m_state;
const bool bypass_limits = args.m_bypass_limits;
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry; std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
if (!m_subpackage.m_all_conflicts.empty()) Assume(args.m_allow_replacement); 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 // 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 // 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. // 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()); LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip());
if (!m_pool.exists(GenTxid::Txid(hash))) if (!m_pool.exists(GenTxid::Txid(hash)))
// The tx no longer meets our (new) mempool minimum feerate but could be reconsidered in a package. // 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 // 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. // 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) { if(!ancestors) {
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state)); results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
// Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail. // 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 CTransaction& tx = *ws.m_ptx;
const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees, const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees,
ws.m_vsize, ws.m_entry->GetHeight(), 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), IsCurrentForFeeEstimation(m_active_chainstate),
m_pool.HasNoInputsOf(tx)); m_pool.HasNoInputsOf(tx));
m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); 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); 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) { if (ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE) {
// Failed for incentives-based fee reasons. Provide the effective feerate and which tx was included. // 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); 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 CTransaction& tx = *ws.m_ptx;
const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees, const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees,
ws.m_vsize, ws.m_entry->GetHeight(), 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), IsCurrentForFeeEstimation(m_active_chainstate),
m_pool.HasNoInputsOf(tx)); m_pool.HasNoInputsOf(tx));
m_pool.m_opts.signals->TransactionAddedToMempool(tx_info, m_pool.GetAndIncrementSequence()); 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. // At this point we have all in-mempool ancestors, and we know every transaction's vsize.
// Run the TRUC checks on the package. // Run the TRUC checks on the package.
std::string reason;
for (Workspace& ws : workspaces) { for (Workspace& ws : workspaces) {
if (auto err{PackageTRUCChecks(ws.m_ptx, ws.m_vsize, txns, ws.m_ancestors)}) { 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, "TRUC-violation", err.value()); package_state.Invalid(PackageValidationResult::PCKG_POLICY, reason, err.value());
return PackageMempoolAcceptResult(package_state, {}); return PackageMempoolAcceptResult(package_state, {});
} }
} }
@ -1584,7 +1620,8 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
[](const auto& ws) { return ws.m_ptx->GetWitnessHash(); }); [](const auto& ws) { return ws.m_ptx->GetWitnessHash(); });
TxValidationState placeholder_state; TxValidationState placeholder_state;
if (args.m_package_feerates && 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"); package_state.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
return PackageMempoolAcceptResult(package_state, {{workspaces.back().m_ptx->GetWitnessHash(), 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)}}); 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, // Apply package mempool ancestor/descendant limits. Skip if there is only one transaction,
// because it's unnecessary. // 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)); return PackageMempoolAcceptResult(package_state, std::move(results));
} }
@ -1859,7 +1896,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
} // anon namespace } // anon namespace
MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, 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); AssertLockHeld(::cs_main);
const CChainParams& chainparams{active_chainstate.m_chainman.GetParams()}; const CChainParams& chainparams{active_chainstate.m_chainman.GetParams()};
@ -1867,7 +1904,7 @@ MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTra
CTxMemPool& pool{*active_chainstate.GetMempool()}; CTxMemPool& pool{*active_chainstate.GetMempool()};
std::vector<COutPoint> coins_to_uncache; 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); MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransaction(tx, args);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
// Remove coins that were not present in the coins cache before calling // 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, 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); AssertLockHeld(cs_main);
assert(!package.empty()); assert(!package.empty());
@ -1900,10 +1937,10 @@ PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxM
auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) { auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
AssertLockHeld(cs_main); AssertLockHeld(cs_main);
if (test_accept) { 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); return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args);
} else { } 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); return MemPoolAccept(pool, active_chainstate).AcceptPackage(package, args);
} }
}(); }();
@ -4667,7 +4704,7 @@ bool ChainstateManager::ProcessNewBlock(const std::shared_ptr<const CBlock>& blo
return true; 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); AssertLockHeld(cs_main);
Chainstate& active_chainstate = ActiveChainstate(); Chainstate& active_chainstate = ActiveChainstate();
@ -4676,7 +4713,7 @@ MempoolAcceptResult ChainstateManager::ProcessTransaction(const CTransactionRef&
state.Invalid(TxValidationResult::TX_NO_MEMPOOL, "no-mempool"); state.Invalid(TxValidationResult::TX_NO_MEMPOOL, "no-mempool");
return MempoolAcceptResult::Failure(state); 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); active_chainstate.GetMempool()->check(active_chainstate.CoinsTip(), active_chainstate.m_chain.Height() + 1);
return result; return result;
} }

View File

@ -256,6 +256,11 @@ struct PackageMempoolAcceptResult
: m_tx_results{ {wtxid, result} } {} : 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. * 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() * Client code should use ChainstateManager::ProcessTransaction()
@ -264,15 +269,18 @@ struct PackageMempoolAcceptResult
* @param[in] tx The transaction to submit for mempool acceptance. * @param[in] tx The transaction to submit for mempool acceptance.
* @param[in] accept_time The timestamp for adding the transaction to the mempool. * @param[in] accept_time The timestamp for adding the transaction to the mempool.
* It is also used to determine when the entry expires. * It is also used to determine when the entry expires.
* @param[in] bypass_limits When true, don't enforce mempool fee and capacity limits, * @param[in] ignore_rejects Set of reject reasons to ignore and bypass, if possible.
* and set entry_sequence to zero.
* @param[in] test_accept When true, run validation checks but don't submit to mempool. * @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. * @returns a MempoolAcceptResult indicating whether the transaction was accepted/rejected with reason.
*/ */
MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTransactionRef& tx, 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) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
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 * 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. * possible for the package to be partially submitted.
*/ */
PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxMemPool& pool, 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); EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/* Mempool validation helper functions */ /* Mempool validation helper functions */
@ -1257,7 +1265,7 @@ public:
* @param[in] tx The transaction to submit for mempool acceptance. * @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. * @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); EXCLUSIVE_LOCKS_REQUIRED(cs_main);
//! Load the block tree and coins database from disk, initializing state if we're running with -reindex //! 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() anchor_nonempty_wit_spend.rehash()
self.check_mempool_result( 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()], rawtxs=[anchor_nonempty_wit_spend.serialize().hex()],
maxfeerate=0, maxfeerate=0,
) )

View File

@ -57,7 +57,7 @@ class MempoolTRUC(BitcoinTestFramework):
self.log.info("Test TRUC-specific maximum transaction vsize") 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) 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) 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"]) assert_raises_rpc_error(-26, expected_error_heavy, node.sendrawtransaction, tx_v3_heavy["hex"])
self.check_mempool([]) self.check_mempool([])
@ -77,7 +77,7 @@ class MempoolTRUC(BitcoinTestFramework):
version=3 version=3
) )
assert_greater_than_or_equal(tx_v3_child_heavy["tx"].get_vsize(), 1000) 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"]) assert_raises_rpc_error(-26, expected_error_child_heavy, node.sendrawtransaction, tx_v3_child_heavy["hex"])
self.check_mempool([tx_v3_parent_normal["txid"]]) self.check_mempool([tx_v3_parent_normal["txid"]])
# tx has no descendants # tx has no descendants
@ -157,7 +157,7 @@ class MempoolTRUC(BitcoinTestFramework):
utxo_to_spend=tx_v3_parent["new_utxo"], utxo_to_spend=tx_v3_parent["new_utxo"],
version=2 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"]) 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"]]) 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([]) self.check_mempool([])
result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_parent_2_normal["hex"], tx_v3_child_multiparent["hex"]]) 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([])
self.check_mempool([]) self.check_mempool([])
result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_child_heavy["hex"]]) result = node.submitpackage([tx_v3_parent_normal["hex"], tx_v3_child_heavy["hex"]])
# tx_v3_child_heavy is heavy based on weight, not sigops. # 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([]) self.check_mempool([])
tx_v3_parent = self.wallet.create_self_transfer(version=3) 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_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) 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"]]) 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) @cleanup(extra_args=None)
def test_truc_ancestors_package_and_mempool(self): def test_truc_ancestors_package_and_mempool(self):
@ -331,7 +331,7 @@ class MempoolTRUC(BitcoinTestFramework):
# submitpackage(B, C) should fail # submitpackage(B, C) should fail
result = node.submitpackage([tx_0fee_parent["hex"], tx_child_violator["hex"]]) 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"]]) self.check_mempool([tx_in_mempool["txid"]])
@cleanup(extra_args=None) @cleanup(extra_args=None)
@ -384,17 +384,17 @@ class MempoolTRUC(BitcoinTestFramework):
# Fails with another non-related transaction via testmempoolaccept # Fails with another non-related transaction via testmempoolaccept
tx_unrelated = self.wallet.create_self_transfer(version=3) tx_unrelated = self.wallet.create_self_transfer(version=3)
result_test_unrelated = node.testmempoolaccept([tx_sibling_1["hex"], tx_unrelated["hex"]]) 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 # Fails in a package via testmempoolaccept
result_test_1p1c = node.testmempoolaccept([tx_sibling_1["hex"], tx_has_mempool_uncle["hex"]]) 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. # Allowed when tx is submitted in a package and evaluated individually.
# Note that the child failed since it would be the 3rd generation. # 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"]]) 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"]]) 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) 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 # 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"]]) 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"]]) 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) 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([]) self.check_mempool([])
result = node.submitpackage([tx_v3_parent["hex"], tx_v2_child["hex"]]) 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([]) self.check_mempool([])
@cleanup(extra_args=None) @cleanup(extra_args=None)
@ -447,11 +447,11 @@ class MempoolTRUC(BitcoinTestFramework):
assert all([result["allowed"] for result in test_accept_v2_and_v3]) 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"]]) 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]) 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"]]) 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]) 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"]]) 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_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) 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"]]) 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]) 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 # 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 # 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"]]) 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]) 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 # Same result if the parent is already in mempool
node.sendrawtransaction(tx_v3_parent["hex"]) node.sendrawtransaction(tx_v3_parent["hex"])
@ -609,7 +609,7 @@ class MempoolTRUC(BitcoinTestFramework):
utxo_to_spend=tx_with_multi_children["new_utxos"][2], utxo_to_spend=tx_with_multi_children["new_utxos"][2],
fee_rate=DEFAULT_FEE*50 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"]) 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 # 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 # First we test this transaction against std_node
# making sure the txid is added to the reject filter # making sure the txid is added to the reject filter
self.std_node.announce_tx_and_wait_for_getdata(tx3) 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 # 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) self.std_node.announce_tx_and_wait_for_getdata(tx3, success=False)
@ -1750,7 +1750,7 @@ class SegWitTest(BitcoinTestFramework):
tx2.rehash() tx2.rehash()
# This will be rejected due to a policy check: # This will be rejected due to a policy check:
# No witness is allowed, since it is not a witness program but a p2sh program # No witness is allowed, since it is not a witness program but a p2sh program
test_transaction_acceptance(self.nodes[1], self.std_node, tx2, True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, tx2, True, False, 'bad-witness-nonwitness-input')
# If we send without witness, it should be accepted. # If we send without witness, it should be accepted.
test_transaction_acceptance(self.nodes[1], self.std_node, tx2, False, True) test_transaction_acceptance(self.nodes[1], self.std_node, tx2, False, True)
@ -1819,13 +1819,13 @@ class SegWitTest(BitcoinTestFramework):
# Testing native P2WSH # Testing native P2WSH
# Witness stack size, excluding witnessScript, over 100 is non-standard # Witness stack size, excluding witnessScript, over 100 is non-standard
p2wsh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]] p2wsh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[0], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[0], True, False, 'bad-witness-stackitem-count')
# Non-standard nodes should accept # Non-standard nodes should accept
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[0], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[0], True, True)
# Stack element size over 80 bytes is non-standard # Stack element size over 80 bytes is non-standard
p2wsh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]] p2wsh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[1], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[1], True, False, 'bad-witness-stackitem-size')
# Non-standard nodes should accept # Non-standard nodes should accept
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[1], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[1], True, True)
# Standard nodes should accept if element size is not over 80 bytes # Standard nodes should accept if element size is not over 80 bytes
@ -1839,16 +1839,16 @@ class SegWitTest(BitcoinTestFramework):
# witnessScript size at 3601 bytes is non-standard # witnessScript size at 3601 bytes is non-standard
p2wsh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]] p2wsh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[3], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2wsh_txs[3], True, False, 'bad-witness-script-size')
# Non-standard nodes should accept # Non-standard nodes should accept
test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[3], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2wsh_txs[3], True, True)
# Repeating the same tests with P2SH-P2WSH # Repeating the same tests with P2SH-P2WSH
p2sh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]] p2sh_txs[0].wit.vtxinwit[0].scriptWitness.stack = [pad] * 101 + [scripts[0]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[0], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[0], True, False, 'bad-witness-stackitem-count')
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[0], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[0], True, True)
p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]] p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 81] * 100 + [scripts[1]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, False, 'bad-witness-stackitem-size')
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[1], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[1], True, True)
p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 80] * 100 + [scripts[1]] p2sh_txs[1].wit.vtxinwit[0].scriptWitness.stack = [pad * 80] * 100 + [scripts[1]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, True) test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[1], True, True)
@ -1856,7 +1856,7 @@ class SegWitTest(BitcoinTestFramework):
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[2], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[2], True, True)
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[2], True, True) test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[2], True, True)
p2sh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]] p2sh_txs[3].wit.vtxinwit[0].scriptWitness.stack = [pad, pad, pad, scripts[3]]
test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[3], True, False, 'bad-witness-nonstandard') test_transaction_acceptance(self.nodes[1], self.std_node, p2sh_txs[3], True, False, 'bad-witness-script-size')
test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[3], True, True) test_transaction_acceptance(self.nodes[0], self.test_node, p2sh_txs[3], True, True)
self.generate(self.nodes[0], 1) # Mine and clean up the mempool of non-standard node self.generate(self.nodes[0], 1) # Mine and clean up the mempool of non-standard node

View File

@ -409,12 +409,22 @@ class RawTransactionsTest(BitcoinTestFramework):
testres = self.nodes[2].testmempoolaccept([tx['hex']], 0.00001000)[0] testres = self.nodes[2].testmempoolaccept([tx['hex']], 0.00001000)[0]
assert_equal(testres['allowed'], False) assert_equal(testres['allowed'], False)
assert_equal(testres['reject-reason'], 'max-fee-exceeded') 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 # and sendrawtransaction should throw
assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, tx['hex'], 0.00001000) assert_raises_rpc_error(-25, fee_exceeds_max, self.nodes[2].sendrawtransaction, tx['hex'], 0.00001000)
# and the following calls should both succeed # and the following calls should both succeed
testres = self.nodes[2].testmempoolaccept(rawtxs=[tx['hex']])[0] testres = self.nodes[2].testmempoolaccept(rawtxs=[tx['hex']])[0]
assert_equal(testres['allowed'], True) 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. # Test a transaction with a large fee.
# Fee rate is 0.20000000 BTC/kvB # Fee rate is 0.20000000 BTC/kvB