mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-06-03 16:02:34 +02:00
AcceptToMemoryPool: Support overriding many top-level rejections
This commit is contained in:
parent
79f8c5fabb
commit
ccc3f45e09
@ -7,6 +7,7 @@
|
||||
#include <consensus/amount.h>
|
||||
#include <kernel/mempool_entry.h>
|
||||
#include <policy/feerate.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <sync.h>
|
||||
#include <tinyformat.h>
|
||||
@ -56,7 +57,8 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx)
|
||||
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx,
|
||||
CTxMemPool& pool,
|
||||
const CTxMemPool::setEntries& iters_conflicting,
|
||||
CTxMemPool::setEntries& all_conflicts)
|
||||
CTxMemPool::setEntries& all_conflicts,
|
||||
const ignore_rejects_type& ignore_rejects)
|
||||
{
|
||||
AssertLockHeld(pool.cs);
|
||||
const uint256 txid = tx.GetHash();
|
||||
@ -67,7 +69,7 @@ std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx,
|
||||
// entries from the mempool. This potentially overestimates the number of actual
|
||||
// descendants (i.e. if multiple conflicts share a descendant, it will be counted multiple
|
||||
// times), but we just want to be conservative to avoid doing too much work.
|
||||
if (nConflictingCount > MAX_REPLACEMENT_CANDIDATES) {
|
||||
if (nConflictingCount > MAX_REPLACEMENT_CANDIDATES && !ignore_rejects.count("too-many-replacements")) {
|
||||
return strprintf("rejecting replacement %s; too many potential replacements (%d > %d)\n",
|
||||
txid.ToString(),
|
||||
nConflictingCount,
|
||||
|
@ -6,6 +6,7 @@
|
||||
#define BITCOIN_POLICY_RBF_H
|
||||
|
||||
#include <consensus/amount.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <threadsafety.h>
|
||||
#include <txmempool.h>
|
||||
@ -59,7 +60,8 @@ RBFTransactionState IsRBFOptInEmptyMempool(const CTransaction& tx);
|
||||
*/
|
||||
std::optional<std::string> GetEntriesForConflicts(const CTransaction& tx, CTxMemPool& pool,
|
||||
const CTxMemPool::setEntries& iters_conflicting,
|
||||
CTxMemPool::setEntries& all_conflicts)
|
||||
CTxMemPool::setEntries& all_conflicts,
|
||||
const ignore_rejects_type& ignore_rejects=empty_ignore_rejects)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(pool.cs);
|
||||
|
||||
/** The replacement transaction may only include an unconfirmed input if that input was included in
|
||||
|
@ -625,6 +625,23 @@ private:
|
||||
PrecomputedTransactionData m_precomputed_txdata;
|
||||
};
|
||||
|
||||
static inline bool MaybeReject_(TxValidationResult reason, const std::string& reason_str, const std::string& debug_msg, const ignore_rejects_type& ignore_rejects, TxValidationState& state) {
|
||||
if (ignore_rejects.count(reason_str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.Invalid(reason, reason_str, debug_msg);
|
||||
return true;
|
||||
}
|
||||
|
||||
#define MaybeRejectDbg(reason, reason_str, debug_msg) do { \
|
||||
if (MaybeReject_(reason, reason_str, debug_msg, ignore_rejects, state)) { \
|
||||
return false; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#define MaybeReject(reason, reason_str) MaybeRejectDbg(reason, reason_str, "")
|
||||
|
||||
// Run the policy checks on a given transaction, excluding any script checks.
|
||||
// Looks up inputs, calculates feerate, considers replacement, evaluates
|
||||
// package limits, etc. As this function can be invoked for "free" by a peer,
|
||||
@ -632,11 +649,11 @@ private:
|
||||
bool PreChecks(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Run checks for mempool replace-by-fee.
|
||||
bool ReplacementChecks(Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
bool ReplacementChecks(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Enforce package mempool ancestor/descendant limits (distinct from individual
|
||||
// ancestor/descendant limits done in PreChecks).
|
||||
bool PackageMempoolChecks(const std::vector<CTransactionRef>& txns,
|
||||
bool PackageMempoolChecks(const ATMPArgs& args, const std::vector<CTransactionRef>& txns,
|
||||
int64_t total_vsize,
|
||||
PackageValidationState& package_state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
@ -701,6 +718,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
|
||||
// Copy/alias what we need out of args
|
||||
const int64_t nAcceptTime = args.m_accept_time;
|
||||
const ignore_rejects_type& ignore_rejects = args.m_ignore_rejects;
|
||||
std::vector<COutPoint>& coins_to_uncache = args.m_coins_to_uncache;
|
||||
|
||||
// Alias what we need out of ws
|
||||
@ -723,13 +741,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
|
||||
// Transactions smaller than 65 non-witness bytes are not relayed to mitigate CVE-2017-12842.
|
||||
if (::GetSerializeSize(tx, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) < MIN_STANDARD_TX_NONWITNESS_SIZE)
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "tx-size-small");
|
||||
MaybeReject(TxValidationResult::TX_NOT_STANDARD, "tx-size-small");
|
||||
|
||||
// Only accept nLockTime-using transactions that can be mined in the next
|
||||
// block; we don't want our mempool filled up with transactions that can't
|
||||
// be mined yet.
|
||||
if (!CheckFinalTxAtTip(*Assert(m_active_chainstate.m_chain.Tip()), tx)) {
|
||||
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-final");
|
||||
MaybeReject(TxValidationResult::TX_PREMATURE_SPEND, "non-final");
|
||||
}
|
||||
|
||||
if (m_pool.exists(GenTxid::Wtxid(tx.GetWitnessHash()))) {
|
||||
@ -762,7 +780,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
//
|
||||
// If replaceability signaling is ignored due to node setting,
|
||||
// replacement is always allowed.
|
||||
if (!m_pool.m_full_rbf && !SignalsOptInRBF(*ptxConflicting)) {
|
||||
if (!(m_pool.m_full_rbf || ignore_rejects.count("txn-mempool-conflict")) && !SignalsOptInRBF(*ptxConflicting)) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "txn-mempool-conflict");
|
||||
}
|
||||
|
||||
@ -813,6 +831,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
// Pass in m_view which has all of the relevant inputs cached. Note that, since m_view's
|
||||
// backend was removed, it no longer pulls coins from the mempool.
|
||||
const std::optional<LockPoints> lock_points{CalculateLockPointsAtTip(m_active_chainstate.m_chain.Tip(), m_view, tx)};
|
||||
// NOTE: The miner doesn't check this again, so for now it may not be overridden.
|
||||
if (!lock_points.has_value() || !CheckSequenceLocksAtTip(m_active_chainstate.m_chain.Tip(), *lock_points)) {
|
||||
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final");
|
||||
}
|
||||
@ -828,7 +847,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
|
||||
// Check for non-standard witnesses.
|
||||
if (tx.HasWitness() && m_pool.m_require_standard && !IsWitnessStandard(tx, m_view)) {
|
||||
return state.Invalid(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard");
|
||||
MaybeReject(TxValidationResult::TX_WITNESS_MUTATED, "bad-witness-nonstandard");
|
||||
}
|
||||
|
||||
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
|
||||
@ -856,7 +875,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
ws.m_vsize = entry->GetTxSize();
|
||||
|
||||
if (nSigOpsCost > MAX_STANDARD_TX_SIGOPS_COST)
|
||||
return state.Invalid(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
|
||||
MaybeRejectDbg(TxValidationResult::TX_NOT_STANDARD, "bad-txns-too-many-sigops",
|
||||
strprintf("%d", nSigOpsCost));
|
||||
|
||||
// No individual transactions are allowed below the min relay feerate except from disconnected blocks.
|
||||
@ -915,7 +934,13 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
maybe_rbf_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, limits)};
|
||||
if (!ancestors) {
|
||||
// If CalculateMemPoolAncestors fails second time, we want the original error string.
|
||||
// Contracting/payment channels CPFP carve-out:
|
||||
@ -959,7 +984,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::ReplacementChecks(Workspace& ws)
|
||||
bool MemPoolAccept::ReplacementChecks(ATMPArgs& args, Workspace& ws)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
AssertLockHeld(m_pool.cs);
|
||||
@ -978,34 +1003,40 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
|
||||
// guarantee that this is incentive-compatible for miners, because it is possible for a
|
||||
// descendant transaction of a direct conflict to pay a higher feerate than the transaction that
|
||||
// might replace them, under these rules.
|
||||
if (!args.m_ignore_rejects.count("insufficient fee")) {
|
||||
if (const auto err_string{PaysMoreThanConflicts(ws.m_iters_conflicting, newFeeRate, hash)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
|
||||
}
|
||||
} // ignore_rejects
|
||||
|
||||
// Calculate all conflicting entries and enforce Rule #5.
|
||||
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, ws.m_all_conflicting)}) {
|
||||
if (const auto err_string{GetEntriesForConflicts(tx, m_pool, ws.m_iters_conflicting, ws.m_all_conflicting, args.m_ignore_rejects)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
|
||||
"too many potential replacements", *err_string);
|
||||
}
|
||||
// Enforce Rule #2.
|
||||
if (!args.m_ignore_rejects.count("replacement-adds-unconfirmed")) {
|
||||
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, ws.m_iters_conflicting)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
|
||||
"replacement-adds-unconfirmed", *err_string);
|
||||
}
|
||||
} // ignore_rejects
|
||||
// Check if it's economically rational to mine this transaction rather than the ones it
|
||||
// replaces and pays for its own relay fees. Enforce Rules #3 and #4.
|
||||
for (CTxMemPool::txiter it : ws.m_all_conflicting) {
|
||||
ws.m_conflicting_fees += it->GetModifiedFee();
|
||||
ws.m_conflicting_size += it->GetTxSize();
|
||||
}
|
||||
if (!args.m_ignore_rejects.count("insufficient fee")) {
|
||||
if (const auto err_string{PaysForRBF(ws.m_conflicting_fees, ws.m_modified_fees, ws.m_vsize,
|
||||
m_pool.m_incremental_relay_feerate, hash)}) {
|
||||
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "insufficient fee", *err_string);
|
||||
}
|
||||
} // ignore_rejects
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txns,
|
||||
bool MemPoolAccept::PackageMempoolChecks(const ATMPArgs& args, const std::vector<CTransactionRef>& txns,
|
||||
const int64_t total_vsize,
|
||||
PackageValidationState& package_state)
|
||||
{
|
||||
@ -1017,7 +1048,7 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
|
||||
{ return !m_pool.exists(GenTxid::Txid(tx->GetHash()));}));
|
||||
|
||||
std::string err_string;
|
||||
if (!m_pool.CheckPackageLimits(txns, total_vsize, err_string)) {
|
||||
if ((!args.m_ignore_rejects.count("package-mempool-limits")) && !m_pool.CheckPackageLimits(txns, total_vsize, err_string)) {
|
||||
// This is a package-wide error, separate from an individual transaction error.
|
||||
return package_state.Invalid(PackageValidationResult::PCKG_POLICY, "package-mempool-limits", err_string);
|
||||
}
|
||||
@ -1171,7 +1202,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
|
||||
// Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the
|
||||
// last calculation done in PreChecks, since package ancestors have already been submitted.
|
||||
{
|
||||
auto ancestors{m_pool.CalculateMemPoolAncestors(*ws.m_entry, m_pool.m_limits)};
|
||||
auto ancestors{m_pool.CalculateMemPoolAncestors(*ws.m_entry, CTxMemPool::Limits::NoLimits())};
|
||||
if(!ancestors) {
|
||||
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
|
||||
// Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail.
|
||||
@ -1226,7 +1257,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
|
||||
|
||||
if (!PreChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
|
||||
if (m_rbf && !ReplacementChecks(ws)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
if (m_rbf && !ReplacementChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
|
||||
|
||||
// Perform the inexpensive checks first and avoid hashing and signature verification unless
|
||||
// those checks pass, to mitigate CPU exhaustion denial-of-service attacks.
|
||||
@ -1307,7 +1338,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
|
||||
// because it's unnecessary. Also, CPFP carve out can increase the limit for individual
|
||||
// transactions, but this exemption is not extended to packages in CheckPackageLimits().
|
||||
std::string err_string;
|
||||
if (txns.size() > 1 && !PackageMempoolChecks(txns, m_total_vsize, package_state)) {
|
||||
if (txns.size() > 1 && !PackageMempoolChecks(args, txns, m_total_vsize, package_state)) {
|
||||
return PackageMempoolAcceptResult(package_state, std::move(results));
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user