mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-01-19 14:53:43 +01:00
Merge bitcoin/bitcoin#31122: cluster mempool: Implement changeset interface for mempool
5736d1ddactracing: pass if replaced by tx/pkg to tracepoint (0xb10c)a4ec07f194doc: add comments for CTxMemPool::ChangeSet (Suhas Daftuar)83f814b1d1Remove m_all_conflicts from SubPackageState (Suhas Daftuar)d3c8e7dfb6Ensure that we don't add duplicate transactions in rbf fuzz tests (Suhas Daftuar)d7dc9fd2f7Move CalculateChunksForRBF() to the mempool changeset (Suhas Daftuar)284a1d33f1Move prioritisation into changeset (Suhas Daftuar)446b08b599Don't distinguish between direct conflicts and all conflicts when doing cluster-size-2-rbf checks (Suhas Daftuar)b53041021aDuplicate transactions are not permitted within a changeset (Suhas Daftuar)b447416fddPublic mempool removal methods Assume() no changeset is outstanding (Suhas Daftuar)2b30f4d36cMake RemoveStaged() private (Suhas Daftuar)18829194caEnforce that there is only one changeset at a time (Suhas Daftuar)7fb62f7db6Apply mempool changeset transactions directly into the mempool (Suhas Daftuar)34b6c5833dClean up FinalizeSubpackage to avoid workspace-specific information (Suhas Daftuar)57983b8addMove LimitMempoolSize to take place outside FinalizeSubpackage (Suhas Daftuar)01e145b975Move changeset from workspace to subpackage (Suhas Daftuar)802214c083Introduce mempool changesets (Suhas Daftuar)87d92fa340test: Add unit test coverage of package rbf + prioritisetransaction (Suhas Daftuar)15d982f91eAdd package hash to package-rbf log message (Suhas Daftuar) Pull request description: part of cluster mempool: #30289 It became clear while working on cluster mempool that it would be helpful for transaction validation if we could consider a full set of proposed changes to the mempool -- consisting of a set of transactions to add, and a set of transactions (ie conflicts) to simultaneously remove -- and perform calculations on what the mempool would look like if the proposed changes were to be applied. Two specific examples of where we'd like to do this: - Determining if ancestor/descendant/TRUC limits would be violated (in the future, cluster limits) if either a single transaction or a package of transactions were to be accepted - Determining if an RBF would make the mempool "better", however that idea is defined, both in the single transaction and package of transaction cases In preparation for cluster mempool, I have pulled this reworking of the mempool interface out of #28676 so it can be reviewed on its own. I have not re-implemented ancestor/descendant limits to be run through the changeset, since with cluster mempool those limits will be going away, so this seems like wasted effort. However, I have rebased #28676 on top of this branch so reviewers can see what the new mempool interface could look like in the cluster mempool setting. There are some minor behavior changes here, which I believe are inconsequential: - In the package validation setting, transactions would be added to the mempool before the `ConsensusScriptChecks()` are run. In theory, `ConsensusScriptChecks()` should always pass if the `PolicyScriptChecks()` have passed and it's just a belt-and-suspenders for us, but if somehow they were to diverge then there could be some small behavior change from adding transactions and then removing them, versus never adding them at all. - The error reporting on `CheckConflictTopology()` has slightly changed due to no longer distinguishing between direct conflicts and indirect conflicts. I believe this should be entirely inconsequential because there shouldn't be a logical difference between those two ideas from the perspective of this function, but I did have to update some error strings in some tests. - Because, in a package setting, RBFs now happen as part of the entire package being accepted, the logging has changed slightly because we do not know which transaction specifically evicted a given removed transaction. - Specifically, the "package hash" is now used to reference the set of transactions that are being accepted, rather than any single txid. The log message relating to package RBF that happen in the `TXPACKAGES` category has been updated as well to include the package hash, so that it's possible to see which specific set of transactions are being referenced by that package hash. - Relatedly, the tracepoint logging in the package rbf case has been updated as well to reference the package hash, rather than a transaction hash. ACKs for top commit: naumenkogs: ACK5736d1ddacinstagibbs: ACK5736d1ddacismaelsadeeq: reACK5736d1ddacglozow: ACK5736d1ddacTree-SHA512: 21810872e082920d337c89ac406085aa71c5f8e5151ab07aedf41e6601f60a909b22fbf462ef3b735d5d5881e9b76142c53957158e674dd5dfe6f6aabbdf630b
This commit is contained in:
@@ -324,7 +324,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
|
||||
@@ -633,10 +633,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};
|
||||
|
||||
@@ -689,9 +688,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.
|
||||
@@ -742,10 +739,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};
|
||||
@@ -780,7 +777,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;
|
||||
|
||||
if (!CheckTransaction(tx, state)) {
|
||||
return false; // state filled in by CheckTransaction
|
||||
@@ -891,10 +887,6 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
|
||||
|
||||
int64_t nSigOpsCost = GetTransactionSigOpCost(tx, m_view, STANDARD_SCRIPT_VERIFY_FLAGS);
|
||||
|
||||
// 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;
|
||||
@@ -909,9 +901,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()));
|
||||
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());
|
||||
|
||||
// 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) {
|
||||
@@ -983,7 +981,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.
|
||||
@@ -1015,7 +1013,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);
|
||||
@@ -1089,13 +1087,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,
|
||||
@@ -1104,7 +1104,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();
|
||||
}
|
||||
@@ -1114,6 +1114,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;
|
||||
}
|
||||
|
||||
@@ -1167,13 +1172,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();
|
||||
}
|
||||
@@ -1200,14 +1208,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;
|
||||
@@ -1274,58 +1283,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,
|
||||
@@ -1340,6 +1346,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
|
||||
@@ -1353,36 +1360,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;
|
||||
@@ -1399,6 +1389,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 :
|
||||
@@ -1409,7 +1401,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));
|
||||
@@ -1434,6 +1426,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", "");
|
||||
@@ -1471,17 +1466,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));
|
||||
@@ -1817,6 +1819,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());
|
||||
|
||||
Reference in New Issue
Block a user