mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-21 15:50:07 +01:00
Merge #21062: refactor: return MempoolAcceptResult from ATMP
53e716ea11[refactor] improve style for touched code (gzhao408)174cb5330a[refactor] const ATMPArgs and non-const Workspace (gzhao408)f82baf0762[refactor] return MempoolAcceptResult (gzhao408)9db10a5506[refactor] clean up logic in testmempoolaccept (gzhao408) Pull request description: This is the first 4 commits of #20833, and does refactoring only. It should be relatively simple to review, and offers a few nice things: - It makes accessing values that don't make sense (e.g. fee) when the tx is invalid an error. - Returning `MempoolAcceptResult` from ATMP makes the interface cleaner. The caller can get a const instead of passing in a mutable "out" param. - We don't have to be iterating through a bunch of lists for package validation, we can just return a `std::vector<MempoolAcceptResult>`. - We don't have to refactor all ATMP call sites again if/when we want to return more stuff from it. ACKs for top commit: MarcoFalke: ACK53e716ea11💿 jnewbery: Code review ACK53e716ea11ariard: Code Review ACK53e716e, I did tweak a bit the touched paths to see if we had good test coverage. Didn't find holes. Tree-SHA512: fa6ec324a08ad9e6e55948615cda324cba176255708bf0a0a0f37cedb7a75311aa334ac6f223be7d8df3c7379502b1081102b9589f9a9afa1713ad3d9ab3c24f
This commit is contained in:
@@ -380,10 +380,8 @@ static void UpdateMempoolForReorg(CTxMemPool& mempool, DisconnectedBlockTransact
|
||||
auto it = disconnectpool.queuedTx.get<insertion_order>().rbegin();
|
||||
while (it != disconnectpool.queuedTx.get<insertion_order>().rend()) {
|
||||
// ignore validation errors in resurrected transactions
|
||||
TxValidationState stateDummy;
|
||||
if (!fAddToMempool || (*it)->IsCoinBase() ||
|
||||
!AcceptToMemoryPool(mempool, stateDummy, *it,
|
||||
nullptr /* plTxnReplaced */, true /* bypass_limits */)) {
|
||||
AcceptToMemoryPool(mempool, *it, true /* bypass_limits */).m_result_type != MempoolAcceptResult::ResultType::VALID) {
|
||||
// If the transaction doesn't make it in to the mempool, remove any
|
||||
// transactions that depend on it (which would now be orphans).
|
||||
mempool.removeRecursive(**it, MemPoolRemovalReason::REORG);
|
||||
@@ -463,9 +461,7 @@ public:
|
||||
// around easier.
|
||||
struct ATMPArgs {
|
||||
const CChainParams& m_chainparams;
|
||||
TxValidationState &m_state;
|
||||
const int64_t m_accept_time;
|
||||
std::list<CTransactionRef>* m_replaced_transactions;
|
||||
const bool m_bypass_limits;
|
||||
/*
|
||||
* Return any outpoints which were not previously present in the coins
|
||||
@@ -476,11 +472,10 @@ public:
|
||||
*/
|
||||
std::vector<COutPoint>& m_coins_to_uncache;
|
||||
const bool m_test_accept;
|
||||
CAmount* m_fee_out;
|
||||
};
|
||||
|
||||
// Single transaction acceptance
|
||||
bool AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
MempoolAcceptResult AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
|
||||
|
||||
private:
|
||||
// All the intermediate state that gets passed between the various levels
|
||||
@@ -491,14 +486,17 @@ private:
|
||||
CTxMemPool::setEntries m_all_conflicting;
|
||||
CTxMemPool::setEntries m_ancestors;
|
||||
std::unique_ptr<CTxMemPoolEntry> m_entry;
|
||||
std::list<CTransactionRef> m_replaced_transactions;
|
||||
|
||||
bool m_replacement_transaction;
|
||||
CAmount m_base_fees;
|
||||
CAmount m_modified_fees;
|
||||
CAmount m_conflicting_fees;
|
||||
size_t m_conflicting_size;
|
||||
|
||||
const CTransactionRef& m_ptx;
|
||||
const uint256& m_hash;
|
||||
TxValidationState m_state;
|
||||
};
|
||||
|
||||
// Run the policy checks on a given transaction, excluding any script checks.
|
||||
@@ -509,18 +507,18 @@ private:
|
||||
|
||||
// Run the script checks using our policy flags. As this can be slow, we should
|
||||
// only invoke this on transactions that have otherwise passed policy checks.
|
||||
bool PolicyScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
bool PolicyScriptChecks(const ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Re-run the script checks, using consensus flags, and try to cache the
|
||||
// result in the scriptcache. This should be done after
|
||||
// PolicyScriptChecks(). This requires that all inputs either be in our
|
||||
// utxo set or in the mempool.
|
||||
bool ConsensusScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData &txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
bool ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws, PrecomputedTransactionData &txdata) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Try to add the transaction to the mempool, removing any conflicts first.
|
||||
// Returns true if the transaction is in the mempool after any size
|
||||
// limiting is performed, false otherwise.
|
||||
bool Finalize(ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
bool Finalize(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
|
||||
|
||||
// Compare a package's feerate against minimum allowed.
|
||||
bool CheckFeeRate(size_t package_size, CAmount package_fee, TxValidationState& state) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs)
|
||||
@@ -558,12 +556,12 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
const uint256& hash = ws.m_hash;
|
||||
|
||||
// Copy/alias what we need out of args
|
||||
TxValidationState &state = args.m_state;
|
||||
const int64_t nAcceptTime = args.m_accept_time;
|
||||
const bool bypass_limits = args.m_bypass_limits;
|
||||
std::vector<COutPoint>& coins_to_uncache = args.m_coins_to_uncache;
|
||||
|
||||
// Alias what we need out of ws
|
||||
TxValidationState& state = ws.m_state;
|
||||
std::set<uint256>& setConflicts = ws.m_conflicts;
|
||||
CTxMemPool::setEntries& allConflicting = ws.m_all_conflicting;
|
||||
CTxMemPool::setEntries& setAncestors = ws.m_ancestors;
|
||||
@@ -683,16 +681,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
if (!CheckSequenceLocks(m_pool, tx, STANDARD_LOCKTIME_VERIFY_FLAGS, &lp))
|
||||
return state.Invalid(TxValidationResult::TX_PREMATURE_SPEND, "non-BIP68-final");
|
||||
|
||||
CAmount nFees = 0;
|
||||
if (!Consensus::CheckTxInputs(tx, state, m_view, g_chainman.m_blockman.GetSpendHeight(m_view), nFees)) {
|
||||
if (!Consensus::CheckTxInputs(tx, state, m_view, g_chainman.m_blockman.GetSpendHeight(m_view), ws.m_base_fees)) {
|
||||
return false; // state filled in by CheckTxInputs
|
||||
}
|
||||
|
||||
// If fee_out is passed, return the fee to the caller
|
||||
if (args.m_fee_out) {
|
||||
*args.m_fee_out = nFees;
|
||||
}
|
||||
|
||||
// Check for non-standard pay-to-script-hash in inputs
|
||||
const auto& params = args.m_chainparams.GetConsensus();
|
||||
auto taproot_state = VersionBitsState(::ChainActive().Tip(), params, Consensus::DEPLOYMENT_TAPROOT, versionbitscache);
|
||||
@@ -707,7 +699,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
|
||||
|
||||
// nModifiedFees includes any fee deltas from PrioritiseTransaction
|
||||
nModifiedFees = nFees;
|
||||
nModifiedFees = ws.m_base_fees;
|
||||
m_pool.ApplyDelta(hash, nModifiedFees);
|
||||
|
||||
// Keep track of transactions that spend a coinbase, which we re-scan
|
||||
@@ -721,7 +713,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
}
|
||||
}
|
||||
|
||||
entry.reset(new CTxMemPoolEntry(ptx, nFees, nAcceptTime, ::ChainActive().Height(),
|
||||
entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, ::ChainActive().Height(),
|
||||
fSpendsCoinbase, nSigOpsCost, lp));
|
||||
unsigned int nSize = entry->GetTxSize();
|
||||
|
||||
@@ -925,11 +917,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData& txdata)
|
||||
bool MemPoolAccept::PolicyScriptChecks(const ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
|
||||
{
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
|
||||
TxValidationState &state = args.m_state;
|
||||
TxValidationState& state = ws.m_state;
|
||||
|
||||
constexpr unsigned int scriptVerifyFlags = STANDARD_SCRIPT_VERIFY_FLAGS;
|
||||
|
||||
@@ -952,12 +943,11 @@ bool MemPoolAccept::PolicyScriptChecks(ATMPArgs& args, const Workspace& ws, Prec
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::ConsensusScriptChecks(ATMPArgs& args, const Workspace& ws, PrecomputedTransactionData& txdata)
|
||||
bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws, PrecomputedTransactionData& txdata)
|
||||
{
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
const uint256& hash = ws.m_hash;
|
||||
|
||||
TxValidationState &state = args.m_state;
|
||||
TxValidationState& state = ws.m_state;
|
||||
const CChainParams& chainparams = args.m_chainparams;
|
||||
|
||||
// Check again against the current block tip's script verification
|
||||
@@ -984,11 +974,11 @@ bool MemPoolAccept::ConsensusScriptChecks(ATMPArgs& args, const Workspace& ws, P
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws)
|
||||
bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
|
||||
{
|
||||
const CTransaction& tx = *ws.m_ptx;
|
||||
const uint256& hash = ws.m_hash;
|
||||
TxValidationState &state = args.m_state;
|
||||
TxValidationState& state = ws.m_state;
|
||||
const bool bypass_limits = args.m_bypass_limits;
|
||||
|
||||
CTxMemPool::setEntries& allConflicting = ws.m_all_conflicting;
|
||||
@@ -1007,8 +997,7 @@ bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws)
|
||||
hash.ToString(),
|
||||
FormatMoney(nModifiedFees - nConflictingFees),
|
||||
(int)entry->GetTxSize() - (int)nConflictingSize);
|
||||
if (args.m_replaced_transactions)
|
||||
args.m_replaced_transactions->push_back(it->GetSharedTx());
|
||||
ws.m_replaced_transactions.push_back(it->GetSharedTx());
|
||||
}
|
||||
m_pool.RemoveStaged(allConflicting, false, MemPoolRemovalReason::REPLACED);
|
||||
|
||||
@@ -1031,14 +1020,14 @@ bool MemPoolAccept::Finalize(ATMPArgs& args, Workspace& ws)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args)
|
||||
MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args)
|
||||
{
|
||||
AssertLockHeld(cs_main);
|
||||
LOCK(m_pool.cs); // mempool "read lock" (held through GetMainSignals().TransactionAddedToMempool())
|
||||
|
||||
Workspace workspace(ptx);
|
||||
Workspace ws(ptx);
|
||||
|
||||
if (!PreChecks(args, workspace)) return false;
|
||||
if (!PreChecks(args, ws)) return MempoolAcceptResult(ws.m_state);
|
||||
|
||||
// Only compute the precomputed transaction data if we need to verify
|
||||
// scripts (ie, other policy checks pass). We perform the inexpensive
|
||||
@@ -1046,31 +1035,35 @@ bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs
|
||||
// checks pass, to mitigate CPU exhaustion denial-of-service attacks.
|
||||
PrecomputedTransactionData txdata;
|
||||
|
||||
if (!PolicyScriptChecks(args, workspace, txdata)) return false;
|
||||
if (!PolicyScriptChecks(args, ws, txdata)) return MempoolAcceptResult(ws.m_state);
|
||||
|
||||
if (!ConsensusScriptChecks(args, workspace, txdata)) return false;
|
||||
if (!ConsensusScriptChecks(args, ws, txdata)) return MempoolAcceptResult(ws.m_state);
|
||||
|
||||
// Tx was accepted, but not added
|
||||
if (args.m_test_accept) return true;
|
||||
if (args.m_test_accept) {
|
||||
return MempoolAcceptResult(std::move(ws.m_replaced_transactions), ws.m_base_fees);
|
||||
}
|
||||
|
||||
if (!Finalize(args, workspace)) return false;
|
||||
if (!Finalize(args, ws)) return MempoolAcceptResult(ws.m_state);
|
||||
|
||||
GetMainSignals().TransactionAddedToMempool(ptx, m_pool.GetAndIncrementSequence());
|
||||
|
||||
return true;
|
||||
return MempoolAcceptResult(std::move(ws.m_replaced_transactions), ws.m_base_fees);
|
||||
}
|
||||
|
||||
} // anon namespace
|
||||
|
||||
/** (try to) add transaction to memory pool with a specified acceptance time **/
|
||||
static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
|
||||
int64_t nAcceptTime, std::list<CTransactionRef>* plTxnReplaced,
|
||||
bool bypass_limits, bool test_accept, CAmount* fee_out=nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
||||
static MempoolAcceptResult AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPool& pool,
|
||||
const CTransactionRef &tx, int64_t nAcceptTime,
|
||||
bool bypass_limits, bool test_accept)
|
||||
EXCLUSIVE_LOCKS_REQUIRED(cs_main)
|
||||
{
|
||||
std::vector<COutPoint> coins_to_uncache;
|
||||
MemPoolAccept::ATMPArgs args { chainparams, state, nAcceptTime, plTxnReplaced, bypass_limits, coins_to_uncache, test_accept, fee_out };
|
||||
bool res = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
|
||||
if (!res) {
|
||||
MemPoolAccept::ATMPArgs args { chainparams, nAcceptTime, bypass_limits, coins_to_uncache, test_accept };
|
||||
|
||||
const MempoolAcceptResult result = MemPoolAccept(pool).AcceptSingleTransaction(tx, args);
|
||||
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
|
||||
// Remove coins that were not present in the coins cache before calling ATMPW;
|
||||
// this is to prevent memory DoS in case we receive a large number of
|
||||
// invalid transactions that attempt to overrun the in-memory coins cache
|
||||
@@ -1082,15 +1075,12 @@ static bool AcceptToMemoryPoolWithTime(const CChainParams& chainparams, CTxMemPo
|
||||
// After we've (potentially) uncached entries, ensure our coins cache is still within its size limits
|
||||
BlockValidationState state_dummy;
|
||||
::ChainstateActive().FlushStateToDisk(chainparams, state_dummy, FlushStateMode::PERIODIC);
|
||||
return res;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AcceptToMemoryPool(CTxMemPool& pool, TxValidationState &state, const CTransactionRef &tx,
|
||||
std::list<CTransactionRef>* plTxnReplaced,
|
||||
bool bypass_limits, bool test_accept, CAmount* fee_out)
|
||||
MempoolAcceptResult AcceptToMemoryPool(CTxMemPool& pool, const CTransactionRef &tx, bool bypass_limits, bool test_accept)
|
||||
{
|
||||
const CChainParams& chainparams = Params();
|
||||
return AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, GetTime(), plTxnReplaced, bypass_limits, test_accept, fee_out);
|
||||
return AcceptToMemoryPoolWithTime(Params(), pool, tx, GetTime(), bypass_limits, test_accept);
|
||||
}
|
||||
|
||||
CTransactionRef GetTransaction(const CBlockIndex* const block_index, const CTxMemPool* const mempool, const uint256& hash, const Consensus::Params& consensusParams, uint256& hashBlock)
|
||||
@@ -5029,13 +5019,10 @@ bool LoadMempool(CTxMemPool& pool)
|
||||
if (amountdelta) {
|
||||
pool.PrioritiseTransaction(tx->GetHash(), amountdelta);
|
||||
}
|
||||
TxValidationState state;
|
||||
if (nTime > nNow - nExpiryTimeout) {
|
||||
LOCK(cs_main);
|
||||
AcceptToMemoryPoolWithTime(chainparams, pool, state, tx, nTime,
|
||||
nullptr /* plTxnReplaced */, false /* bypass_limits */,
|
||||
false /* test_accept */);
|
||||
if (state.IsValid()) {
|
||||
if (AcceptToMemoryPoolWithTime(chainparams, pool, tx, nTime, false /* bypass_limits */,
|
||||
false /* test_accept */).m_result_type == MempoolAcceptResult::ResultType::VALID) {
|
||||
++count;
|
||||
} else {
|
||||
// mempool may contain the transaction already, e.g. from
|
||||
|
||||
Reference in New Issue
Block a user