Merge f34fe0806a into merged_master (Bitcoin PR bitcoin/bitcoin#31122)

This commit is contained in:
ivanlele 2026-04-06 08:29:35 +00:00
commit d4461d4c55
No known key found for this signature in database
27 changed files with 708 additions and 398 deletions

View file

@ -337,7 +337,7 @@ void Chainstate::MaybeUpdateMempoolForReorg(
}
}
// AcceptToMemoryPool/addUnchecked all assume that new mempool entries have
// AcceptToMemoryPool/addNewTransaction all assume that new mempool entries have
// no in-mempool children, which is generally not true when adding
// previously-confirmed transactions back to the mempool.
// UpdateTransactionsFromBlock finds descendants of any transactions in
@ -653,10 +653,9 @@ private:
CTxMemPool::setEntries m_iters_conflicting;
/** All mempool ancestors of this transaction. */
CTxMemPool::setEntries m_ancestors;
/** Mempool entry constructed for this transaction. Constructed in PreChecks() but not
* inserted into the mempool until Finalize(). */
std::unique_ptr<CTxMemPoolEntry> m_entry;
/** Whether RBF-related data structures (m_conflicts, m_iters_conflicting, m_all_conflicting,
/* Handle to the tx in the changeset */
CTxMemPool::ChangeSet::TxHandle m_tx_handle;
/** Whether RBF-related data structures (m_conflicts, m_iters_conflicting,
* m_replaced_transactions) include a sibling in addition to txns with conflicting inputs. */
bool m_sibling_eviction{false};
@ -709,9 +708,7 @@ private:
bool ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws) 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(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
void FinalizeSubpackage(const ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Submit all transactions to the mempool and call ConsensusScriptChecks to add to the script
// cache - should only be called after successful validation of all transactions in the package.
@ -891,10 +888,10 @@ private:
/** Whether the transaction(s) would replace any mempool transactions and/or evict any siblings.
* If so, RBF rules apply. */
bool m_rbf{false};
/** All directly conflicting mempool transactions and their descendants. */
CTxMemPool::setEntries m_all_conflicts;
/** Mempool transactions that were replaced. */
std::list<CTransactionRef> m_replaced_transactions;
/* Changeset representing adding transactions and removing their conflicts. */
std::unique_ptr<CTxMemPool::ChangeSet> m_changeset;
/** Total modified fees of mempool transactions being replaced. */
CAmount m_conflicting_fees{0};
@ -930,7 +927,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Alias what we need out of ws
TxValidationState& state = ws.m_state;
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
std::set<std::pair<uint256, COutPoint>>& setPeginsSpent = ws.m_set_pegins_spent;
if (!CheckTransaction(tx, state)) {
@ -1109,10 +1105,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// We only consider policyAsset
ws.m_base_fees = fee_map[policyAsset];
// ws.m_modified_fees includes any fee deltas from PrioritiseTransaction
ws.m_modified_fees = ws.m_base_fees;
m_pool.ApplyDelta(hash, ws.m_modified_fees);
// Keep track of transactions that spend a coinbase, which we re-scan
// during reorgs to ensure COINBASE_MATURITY is still met.
bool fSpendsCoinbase = false;
@ -1131,9 +1123,15 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
// Set entry_sequence to 0 when bypass_limits is used; this allows txs from a block
// reorg to be marked earlier than any child txs that were already in the mempool.
const uint64_t entry_sequence = bypass_limits ? 0 : m_pool.GetSequence();
entry.reset(new CTxMemPoolEntry(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence,
fSpendsCoinbase, nSigOpsCost, lock_points.value(), setPeginsSpent));
ws.m_vsize = entry->GetTxSize();
if (!m_subpackage.m_changeset) {
m_subpackage.m_changeset = m_pool.GetChangeSet();
}
ws.m_tx_handle = m_subpackage.m_changeset->StageAddition(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence, fSpendsCoinbase, nSigOpsCost, lock_points.value(), setPeginsSpent);
// ws.m_modified_fees includes any fee deltas from PrioritiseTransaction
ws.m_modified_fees = ws.m_tx_handle->GetModifiedFee();
ws.m_vsize = ws.m_tx_handle->GetTxSize();
// Enforces 0-fee for dust transactions, no incentive to be mined alone
if (m_pool.m_opts.require_standard) {
@ -1210,7 +1208,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
maybe_rbf_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants();
}
if (auto ancestors{m_pool.CalculateMemPoolAncestors(*entry, maybe_rbf_limits)}) {
if (auto ancestors{m_subpackage.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle, maybe_rbf_limits)}) {
ws.m_ancestors = std::move(*ancestors);
} else {
// If CalculateMemPoolAncestors fails second time, we want the original error string.
@ -1242,7 +1240,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->version == TRUC_VERSION) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
}
if (auto ancestors_retry{m_pool.CalculateMemPoolAncestors(*entry, cpfp_carve_out_limits)}) {
if (auto ancestors_retry{m_subpackage.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle, cpfp_carve_out_limits)}) {
ws.m_ancestors = std::move(*ancestors_retry);
} else {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
@ -1316,13 +1314,15 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
}
CTxMemPool::setEntries all_conflicts;
// 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, all_conflicts)}) {
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
strprintf("too many potential replacements%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
}
// Enforce Rule #2.
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, m_subpackage.m_all_conflicts)}) {
if (const auto err_string{HasNoNewUnconfirmed(tx, m_pool, all_conflicts)}) {
// Sibling eviction is only done for TRUC transactions, which cannot have multiple ancestors.
Assume(!ws.m_sibling_eviction);
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY,
@ -1331,7 +1331,7 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
// 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 : m_subpackage.m_all_conflicts) {
for (CTxMemPool::txiter it : all_conflicts) {
m_subpackage.m_conflicting_fees += it->GetModifiedFee();
m_subpackage.m_conflicting_size += it->GetTxSize();
}
@ -1341,6 +1341,11 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE,
strprintf("insufficient fee%s", ws.m_sibling_eviction ? " (including sibling eviction)" : ""), *err_string);
}
// Add all the to-be-removed transactions to the changeset.
for (auto it : all_conflicts) {
m_subpackage.m_changeset->StageRemoval(it);
}
return true;
}
@ -1394,13 +1399,16 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
// Don't consider replacements that would cause us to remove a large number of mempool entries.
// This limit is not increased in a package RBF. Use the aggregate number of transactions.
CTxMemPool::setEntries all_conflicts;
if (const auto err_string{GetEntriesForConflicts(*child_ws.m_ptx, m_pool, direct_conflict_iters,
m_subpackage.m_all_conflicts)}) {
all_conflicts)}) {
return package_state.Invalid(PackageValidationResult::PCKG_POLICY,
"package RBF failed: too many potential replacements", *err_string);
}
for (CTxMemPool::txiter it : m_subpackage.m_all_conflicts) {
for (CTxMemPool::txiter it : all_conflicts) {
m_subpackage.m_changeset->StageRemoval(it);
m_subpackage.m_conflicting_fees += it->GetModifiedFee();
m_subpackage.m_conflicting_size += it->GetTxSize();
}
@ -1427,14 +1435,15 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
// Check if it's economically rational to mine this package rather than the ones it replaces.
// This takes the place of ReplacementChecks()'s PaysMoreThanConflicts() in the package RBF setting.
if (const auto err_tup{ImprovesFeerateDiagram(m_pool, direct_conflict_iters, m_subpackage.m_all_conflicts, m_subpackage.m_total_modified_fees, m_subpackage.m_total_vsize)}) {
if (const auto err_tup{ImprovesFeerateDiagram(*m_subpackage.m_changeset)}) {
return package_state.Invalid(PackageValidationResult::PCKG_POLICY,
"package RBF failed: " + err_tup.value().second, "");
}
LogDebug(BCLog::TXPACKAGES, "package RBF checks passed: parent %s (wtxid=%s), child %s (wtxid=%s)\n",
LogDebug(BCLog::TXPACKAGES, "package RBF checks passed: parent %s (wtxid=%s), child %s (wtxid=%s), package hash (%s)\n",
txns.front()->GetHash().ToString(), txns.front()->GetWitnessHash().ToString(),
txns.back()->GetHash().ToString(), txns.back()->GetWitnessHash().ToString());
txns.back()->GetHash().ToString(), txns.back()->GetWitnessHash().ToString(),
GetPackageHash(txns).ToString());
return true;
@ -1509,58 +1518,55 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws)
return true;
}
bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
void MemPoolAccept::FinalizeSubpackage(const ATMPArgs& args)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs);
const CTransaction& tx = *ws.m_ptx;
const uint256& hash = ws.m_hash;
TxValidationState& state = ws.m_state;
const bool bypass_limits = args.m_bypass_limits;
std::unique_ptr<CTxMemPoolEntry>& entry = ws.m_entry;
if (!m_subpackage.m_all_conflicts.empty()) Assume(args.m_allow_replacement);
if (!m_subpackage.m_changeset->GetRemovals().empty()) Assume(args.m_allow_replacement);
// Remove conflicting transactions from the mempool
for (CTxMemPool::txiter it : m_subpackage.m_all_conflicts)
for (CTxMemPool::txiter it : m_subpackage.m_changeset->GetRemovals())
{
LogDebug(BCLog::MEMPOOL, "replacing mempool tx %s (wtxid=%s, fees=%s, vsize=%s). New tx %s (wtxid=%s, fees=%s, vsize=%s)\n",
it->GetTx().GetHash().ToString(),
it->GetTx().GetWitnessHash().ToString(),
it->GetFee(),
it->GetTxSize(),
hash.ToString(),
tx.GetWitnessHash().ToString(),
entry->GetFee(),
entry->GetTxSize());
std::string log_string = strprintf("replacing mempool tx %s (wtxid=%s, fees=%s, vsize=%s). ",
it->GetTx().GetHash().ToString(),
it->GetTx().GetWitnessHash().ToString(),
it->GetFee(),
it->GetTxSize());
FeeFrac feerate{m_subpackage.m_total_modified_fees, int32_t(m_subpackage.m_total_vsize)};
uint256 tx_or_package_hash{};
const bool replaced_with_tx{m_subpackage.m_changeset->GetTxCount() == 1};
if (replaced_with_tx) {
const CTransaction& tx = m_subpackage.m_changeset->GetAddedTxn(0);
tx_or_package_hash = tx.GetHash();
log_string += strprintf("New tx %s (wtxid=%s, fees=%s, vsize=%s)",
tx.GetHash().ToString(),
tx.GetWitnessHash().ToString(),
feerate.fee,
feerate.size);
} else {
tx_or_package_hash = GetPackageHash(m_subpackage.m_changeset->GetAddedTxns());
log_string += strprintf("New package %s with %lu txs, fees=%s, vsize=%s",
tx_or_package_hash.ToString(),
m_subpackage.m_changeset->GetTxCount(),
feerate.fee,
feerate.size);
}
LogDebug(BCLog::MEMPOOL, "%s\n", log_string);
TRACEPOINT(mempool, replaced,
it->GetTx().GetHash().data(),
it->GetTxSize(),
it->GetFee(),
std::chrono::duration_cast<std::chrono::duration<std::uint64_t>>(it->GetTime()).count(),
hash.data(),
entry->GetTxSize(),
entry->GetFee()
tx_or_package_hash.data(),
feerate.size,
feerate.fee,
replaced_with_tx
);
m_subpackage.m_replaced_transactions.push_back(it->GetSharedTx());
}
m_pool.RemoveStaged(m_subpackage.m_all_conflicts, false, MemPoolRemovalReason::REPLACED);
// Don't attempt to process the same conflicts repeatedly during subpackage evaluation:
// they no longer exist on subsequent calls to Finalize() post-RemoveStaged
m_subpackage.m_all_conflicts.clear();
// Store transaction in memory
m_pool.addUnchecked(*entry, ws.m_ancestors);
// trim mempool and check if tx was trimmed
// If we are validating a package, don't trim here because we could evict a previous transaction
// in the package. LimitMempoolSize() should be called at the very end to make sure the mempool
// is still within limits and package submission happens atomically.
if (!args.m_package_submission && !bypass_limits) {
LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip());
if (!m_pool.exists(GenTxid::Txid(hash)))
// The tx no longer meets our (new) mempool minimum feerate but could be reconsidered in a package.
return state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool full");
}
return true;
m_subpackage.m_changeset->Apply();
m_subpackage.m_changeset.reset();
}
bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>& workspaces,
@ -1575,6 +1581,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
return !m_pool.exists(GenTxid::Txid(ws.m_ptx->GetHash())); }));
bool all_submitted = true;
FinalizeSubpackage(args);
// ConsensusScriptChecks adds to the script cache and is therefore consensus-critical;
// CheckInputsFromMempoolAndCache asserts that transactions only spend coins available from the
// mempool or UTXO set. Submit each transaction to the mempool immediately after calling
@ -1588,36 +1595,19 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
strprintf("BUG! PolicyScriptChecks succeeded but ConsensusScriptChecks failed: %s",
ws.m_ptx->GetHash().ToString()));
// Remove the transaction from the mempool.
if (!m_subpackage.m_changeset) m_subpackage.m_changeset = m_pool.GetChangeSet();
m_subpackage.m_changeset->StageRemoval(m_pool.GetIter(ws.m_ptx->GetHash()).value());
}
// Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the
// last calculation done in PreChecks, since package ancestors have already been submitted.
{
auto ancestors{m_pool.CalculateMemPoolAncestors(*ws.m_entry, m_pool.m_opts.limits)};
if(!ancestors) {
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
// Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail.
Assume(false);
all_submitted = false;
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
strprintf("BUG! Mempool ancestors or descendants were underestimated: %s",
ws.m_ptx->GetHash().ToString()));
}
ws.m_ancestors = std::move(ancestors).value_or(ws.m_ancestors);
}
// If we call LimitMempoolSize() for each individual Finalize(), the mempool will not take
// the transaction's descendant feerate into account because it hasn't seen them yet. Also,
// we risk evicting a transaction that a subsequent package transaction depends on. Instead,
// allow the mempool to temporarily bypass limits, the maximum package size) while
// submitting transactions individually and then trim at the very end.
if (!Finalize(args, ws)) {
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
// Since LimitMempoolSize() won't be called, this should never fail.
Assume(false);
all_submitted = false;
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
strprintf("BUG! Adding to mempool failed: %s", ws.m_ptx->GetHash().ToString()));
}
}
if (!all_submitted) {
Assume(m_subpackage.m_changeset);
// This code should be unreachable; it's here as belt-and-suspenders
// to try to ensure we have no consensus-invalid transactions in the
// mempool.
m_subpackage.m_changeset->Apply();
m_subpackage.m_changeset.reset();
return false;
}
std::vector<Wtxid> all_package_wtxids;
@ -1634,6 +1624,8 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
// Add successful results. The returned results may change later if LimitMempoolSize() evicts them.
for (Workspace& ws : workspaces) {
auto iter = m_pool.GetIter(ws.m_ptx->GetHash());
Assume(iter.has_value());
const auto effective_feerate = args.m_package_feerates ? ws.m_package_feerate :
CFeeRate{ws.m_modified_fees, static_cast<uint32_t>(ws.m_vsize)};
const auto effective_feerate_wtxids = args.m_package_feerates ? all_package_wtxids :
@ -1644,7 +1636,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
if (!m_pool.m_opts.signals) continue;
const CTransaction& tx = *ws.m_ptx;
const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees,
ws.m_vsize, ws.m_entry->GetHeight(),
ws.m_vsize, (*iter)->GetHeight(),
args.m_bypass_limits, args.m_package_submission,
IsCurrentForFeeEstimation(m_active_chainstate),
m_pool.HasNoInputsOf(tx));
@ -1669,6 +1661,9 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
return MempoolAcceptResult::Failure(ws.m_state);
}
m_subpackage.m_total_vsize = ws.m_vsize;
m_subpackage.m_total_modified_fees = ws.m_modified_fees;
// Individual modified feerate exceeded caller-defined max; abort
if (args.m_client_maxfeerate && CFeeRate(ws.m_modified_fees, ws.m_vsize) > args.m_client_maxfeerate.value()) {
ws.m_state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "max feerate exceeded", "");
@ -1706,17 +1701,24 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
ws.m_base_fees, effective_feerate, single_wtxid);
}
if (!Finalize(args, ws)) {
// The only possible failure reason is fee-related (mempool full).
// Failed for fee reasons. Provide the effective feerate and which txns were included.
Assume(ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE);
return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {ws.m_ptx->GetWitnessHash()});
FinalizeSubpackage(args);
// Limit the mempool, if appropriate.
if (!args.m_package_submission && !args.m_bypass_limits) {
LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip());
if (!m_pool.exists(GenTxid::Txid(ws.m_hash))) {
// The tx no longer meets our (new) mempool minimum feerate but could be reconsidered in a package.
ws.m_state.Invalid(TxValidationResult::TX_RECONSIDERABLE, "mempool full");
return MempoolAcceptResult::FeeFailure(ws.m_state, CFeeRate(ws.m_modified_fees, ws.m_vsize), {ws.m_ptx->GetWitnessHash()});
}
}
if (m_pool.m_opts.signals) {
const CTransaction& tx = *ws.m_ptx;
auto iter = m_pool.GetIter(tx.GetHash());
Assume(iter.has_value());
const auto tx_info = NewMempoolTransactionInfo(ws.m_ptx, ws.m_base_fees,
ws.m_vsize, ws.m_entry->GetHeight(),
ws.m_vsize, (*iter)->GetHeight(),
args.m_bypass_limits, args.m_package_submission,
IsCurrentForFeeEstimation(m_active_chainstate),
m_pool.HasNoInputsOf(tx));
@ -2052,6 +2054,11 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptPackage(const Package& package,
AcceptSubPackage(txns_package_eval, args);
PackageValidationState& package_state_final = multi_submission_result.m_state;
// This is invoked by AcceptSubPackage() already, so this is just here for
// clarity (since it's not permitted to invoke LimitMempoolSize() while a
// changeset is outstanding).
ClearSubPackageState();
// Make sure we haven't exceeded max mempool size.
// Package transactions that were submitted to mempool or already in mempool may be evicted.
LimitMempoolSize(m_pool, m_active_chainstate.CoinsTip());