Skip to content

Commit 01e145b

Browse files
committed
Move changeset from workspace to subpackage
Removes a redundant check that mempool limits will not be violated during package acceptance.
1 parent 802214c commit 01e145b

File tree

3 files changed

+56
-51
lines changed

3 files changed

+56
-51
lines changed

src/txmempool.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1383,9 +1383,16 @@ void CTxMemPool::ChangeSet::Apply()
13831383
m_pool->RemoveStaged(m_to_remove, false, MemPoolRemovalReason::REPLACED);
13841384
for (size_t i=0; i<m_entry_vec.size(); ++i) {
13851385
auto tx_entry = m_entry_vec[i];
1386-
m_pool->addUnchecked(*tx_entry);
1386+
if (i == 0 && m_ancestors.count(tx_entry)) {
1387+
m_pool->addUnchecked(*tx_entry, m_ancestors[tx_entry]);
1388+
} else {
1389+
// We always recalculate ancestors from scratch if we're dealing
1390+
// with transactions which may have parents in the same package.
1391+
m_pool->addUnchecked(*tx_entry);
1392+
}
13871393
}
13881394
m_to_add.clear();
13891395
m_to_remove.clear();
13901396
m_entry_vec.clear();
1397+
m_ancestors.clear();
13911398
}

src/txmempool.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -832,8 +832,15 @@ class CTxMemPool
832832

833833
util::Result<CTxMemPool::setEntries> CalculateMemPoolAncestors(TxHandle tx, const Limits& limits)
834834
{
835+
// Look up transaction in our cache first
836+
auto it = m_ancestors.find(tx);
837+
if (it != m_ancestors.end()) return it->second;
838+
839+
// If not found, try to have the mempool calculate it, and cache
840+
// for later.
835841
LOCK(m_pool->cs);
836842
auto ret{m_pool->CalculateMemPoolAncestors(*tx, limits)};
843+
if (ret) m_ancestors.try_emplace(tx, *ret);
837844
return ret;
838845
}
839846

@@ -843,6 +850,8 @@ class CTxMemPool
843850
CTxMemPool* m_pool;
844851
CTxMemPool::indexed_transaction_set m_to_add;
845852
std::vector<CTxMemPool::txiter> m_entry_vec; // track the added transactions' insertion order
853+
// map from the m_to_add index to the ancestors for the transaction
854+
std::map<CTxMemPool::txiter, CTxMemPool::setEntries, CompareIteratorByHash> m_ancestors;
846855
CTxMemPool::setEntries m_to_remove;
847856
};
848857

src/validation.cpp

Lines changed: 39 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -633,8 +633,7 @@ class MemPoolAccept
633633
CTxMemPool::setEntries m_iters_conflicting;
634634
/** All mempool ancestors of this transaction. */
635635
CTxMemPool::setEntries m_ancestors;
636-
/* Changeset representing adding a transaction and removing its conflicts. */
637-
std::unique_ptr<CTxMemPool::ChangeSet> m_changeset;
636+
/* Handle to the tx in the changeset */
638637
CTxMemPool::ChangeSet::TxHandle m_tx_handle;
639638
/** Whether RBF-related data structures (m_conflicts, m_iters_conflicting, m_all_conflicting,
640639
* m_replaced_transactions) include a sibling in addition to txns with conflicting inputs. */
@@ -691,7 +690,7 @@ class MemPoolAccept
691690
// Try to add the transaction to the mempool, removing any conflicts first.
692691
// Returns true if the transaction is in the mempool after any size
693692
// limiting is performed, false otherwise.
694-
bool Finalize(const ATMPArgs& args, Workspace& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
693+
bool FinalizeSubpackage(const ATMPArgs& args, std::vector<Workspace>& ws) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
695694

696695
// Submit all transactions to the mempool and call ConsensusScriptChecks to add to the script
697696
// cache - should only be called after successful validation of all transactions in the package.
@@ -746,6 +745,8 @@ class MemPoolAccept
746745
CTxMemPool::setEntries m_all_conflicts;
747746
/** Mempool transactions that were replaced. */
748747
std::list<CTransactionRef> m_replaced_transactions;
748+
/* Changeset representing adding transactions and removing their conflicts. */
749+
std::unique_ptr<CTxMemPool::ChangeSet> m_changeset;
749750

750751
/** Total modified fees of mempool transactions being replaced. */
751752
CAmount m_conflicting_fees{0};
@@ -908,8 +909,10 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
908909
// Set entry_sequence to 0 when bypass_limits is used; this allows txs from a block
909910
// reorg to be marked earlier than any child txs that were already in the mempool.
910911
const uint64_t entry_sequence = bypass_limits ? 0 : m_pool.GetSequence();
911-
ws.m_changeset = m_pool.GetChangeSet();
912-
ws.m_tx_handle = ws.m_changeset->StageAddition(ptx, ws.m_base_fees, nAcceptTime, m_active_chainstate.m_chain.Height(), entry_sequence, fSpendsCoinbase, nSigOpsCost, lock_points.value());
912+
if (!m_subpackage.m_changeset) {
913+
m_subpackage.m_changeset = m_pool.GetChangeSet();
914+
}
915+
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());
913916

914917
ws.m_vsize = ws.m_tx_handle->GetTxSize();
915918

@@ -983,7 +986,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
983986
maybe_rbf_limits.descendant_size_vbytes += conflict->GetSizeWithDescendants();
984987
}
985988

986-
if (auto ancestors{ws.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle, maybe_rbf_limits)}) {
989+
if (auto ancestors{m_subpackage.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle, maybe_rbf_limits)}) {
987990
ws.m_ancestors = std::move(*ancestors);
988991
} else {
989992
// If CalculateMemPoolAncestors fails second time, we want the original error string.
@@ -1015,7 +1018,7 @@ bool MemPoolAccept::PreChecks(ATMPArgs& args, Workspace& ws)
10151018
if (ws.m_vsize > EXTRA_DESCENDANT_TX_SIZE_LIMIT || ws.m_ptx->version == TRUC_VERSION) {
10161019
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
10171020
}
1018-
if (auto ancestors_retry{ws.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle, cpfp_carve_out_limits)}) {
1021+
if (auto ancestors_retry{m_subpackage.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle, cpfp_carve_out_limits)}) {
10191022
ws.m_ancestors = std::move(*ancestors_retry);
10201023
} else {
10211024
return state.Invalid(TxValidationResult::TX_MEMPOOL_POLICY, "too-long-mempool-chain", error_message);
@@ -1117,7 +1120,7 @@ bool MemPoolAccept::ReplacementChecks(Workspace& ws)
11171120

11181121
// Add all the to-be-removed transactions to the changeset.
11191122
for (auto it : m_subpackage.m_all_conflicts) {
1120-
ws.m_changeset->StageRemoval(it);
1123+
m_subpackage.m_changeset->StageRemoval(it);
11211124
}
11221125
return true;
11231126
}
@@ -1180,7 +1183,7 @@ bool MemPoolAccept::PackageMempoolChecks(const std::vector<CTransactionRef>& txn
11801183

11811184

11821185
for (CTxMemPool::txiter it : m_subpackage.m_all_conflicts) {
1183-
parent_ws.m_changeset->StageRemoval(it);
1186+
m_subpackage.m_changeset->StageRemoval(it);
11841187
m_subpackage.m_conflicting_fees += it->GetModifiedFee();
11851188
m_subpackage.m_conflicting_size += it->GetTxSize();
11861189
}
@@ -1282,13 +1285,13 @@ bool MemPoolAccept::ConsensusScriptChecks(const ATMPArgs& args, Workspace& ws)
12821285
return true;
12831286
}
12841287

1285-
bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
1288+
bool MemPoolAccept::FinalizeSubpackage(const ATMPArgs& args, std::vector<Workspace>& workspaces)
12861289
{
12871290
AssertLockHeld(cs_main);
12881291
AssertLockHeld(m_pool.cs);
1289-
const CTransaction& tx = *ws.m_ptx;
1290-
const uint256& hash = ws.m_hash;
1291-
TxValidationState& state = ws.m_state;
1292+
const CTransaction& tx = *workspaces.front().m_ptx;
1293+
const uint256& hash = workspaces.front().m_hash;
1294+
TxValidationState& state = workspaces.front().m_state;
12921295
const bool bypass_limits = args.m_bypass_limits;
12931296

12941297
if (!m_subpackage.m_all_conflicts.empty()) Assume(args.m_allow_replacement);
@@ -1302,20 +1305,21 @@ bool MemPoolAccept::Finalize(const ATMPArgs& args, Workspace& ws)
13021305
it->GetTxSize(),
13031306
hash.ToString(),
13041307
tx.GetWitnessHash().ToString(),
1305-
ws.m_tx_handle->GetFee(),
1306-
ws.m_tx_handle->GetTxSize());
1308+
workspaces[0].m_tx_handle->GetFee(),
1309+
workspaces[0].m_tx_handle->GetTxSize());
13071310
TRACEPOINT(mempool, replaced,
13081311
it->GetTx().GetHash().data(),
13091312
it->GetTxSize(),
13101313
it->GetFee(),
13111314
std::chrono::duration_cast<std::chrono::duration<std::uint64_t>>(it->GetTime()).count(),
13121315
hash.data(),
1313-
ws.m_tx_handle->GetTxSize(),
1314-
ws.m_tx_handle->GetFee()
1316+
workspaces[0].m_tx_handle->GetTxSize(),
1317+
workspaces[0].m_tx_handle->GetFee()
13151318
);
13161319
m_subpackage.m_replaced_transactions.push_back(it->GetSharedTx());
13171320
}
1318-
ws.m_changeset->Apply();
1321+
m_subpackage.m_changeset->Apply();
1322+
m_subpackage.m_changeset.reset();
13191323
// Don't attempt to process the same conflicts repeatedly during subpackage evaluation:
13201324
// they no longer exist on subsequent calls to Finalize() post-Apply()
13211325
m_subpackage.m_all_conflicts.clear();
@@ -1345,6 +1349,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
13451349
return !m_pool.exists(GenTxid::Txid(ws.m_ptx->GetHash())); }));
13461350

13471351
bool all_submitted = true;
1352+
FinalizeSubpackage(args, workspaces);
13481353
// ConsensusScriptChecks adds to the script cache and is therefore consensus-critical;
13491354
// CheckInputsFromMempoolAndCache asserts that transactions only spend coins available from the
13501355
// mempool or UTXO set. Submit each transaction to the mempool immediately after calling
@@ -1358,36 +1363,19 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
13581363
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
13591364
strprintf("BUG! PolicyScriptChecks succeeded but ConsensusScriptChecks failed: %s",
13601365
ws.m_ptx->GetHash().ToString()));
1361-
}
1362-
1363-
// Re-calculate mempool ancestors to call addUnchecked(). They may have changed since the
1364-
// last calculation done in PreChecks, since package ancestors have already been submitted.
1365-
{
1366-
auto ancestors{ws.m_changeset->CalculateMemPoolAncestors(ws.m_tx_handle, m_pool.m_opts.limits)};
1367-
if(!ancestors) {
1368-
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
1369-
// Since PreChecks() and PackageMempoolChecks() both enforce limits, this should never fail.
1370-
Assume(false);
1371-
all_submitted = false;
1372-
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
1373-
strprintf("BUG! Mempool ancestors or descendants were underestimated: %s",
1374-
ws.m_ptx->GetHash().ToString()));
1375-
}
1376-
ws.m_ancestors = std::move(ancestors).value_or(ws.m_ancestors);
1377-
}
1378-
// If we call LimitMempoolSize() for each individual Finalize(), the mempool will not take
1379-
// the transaction's descendant feerate into account because it hasn't seen them yet. Also,
1380-
// we risk evicting a transaction that a subsequent package transaction depends on. Instead,
1381-
// allow the mempool to temporarily bypass limits, the maximum package size) while
1382-
// submitting transactions individually and then trim at the very end.
1383-
if (!Finalize(args, ws)) {
1384-
results.emplace(ws.m_ptx->GetWitnessHash(), MempoolAcceptResult::Failure(ws.m_state));
1385-
// Since LimitMempoolSize() won't be called, this should never fail.
1386-
Assume(false);
1387-
all_submitted = false;
1388-
package_state.Invalid(PackageValidationResult::PCKG_MEMPOOL_ERROR,
1389-
strprintf("BUG! Adding to mempool failed: %s", ws.m_ptx->GetHash().ToString()));
1390-
}
1366+
// Remove the transaction from the mempool.
1367+
if (!m_subpackage.m_changeset) m_subpackage.m_changeset = m_pool.GetChangeSet();
1368+
m_subpackage.m_changeset->StageRemoval(m_pool.GetIter(ws.m_ptx->GetHash()).value());
1369+
}
1370+
}
1371+
if (!all_submitted) {
1372+
Assume(m_subpackage.m_changeset);
1373+
// This code should be unreachable; it's here as belt-and-suspenders
1374+
// to try to ensure we have no consensus-invalid transactions in the
1375+
// mempool.
1376+
m_subpackage.m_changeset->Apply();
1377+
m_subpackage.m_changeset.reset();
1378+
return false;
13911379
}
13921380

13931381
std::vector<Wtxid> all_package_wtxids;
@@ -1430,7 +1418,8 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
14301418
AssertLockHeld(cs_main);
14311419
LOCK(m_pool.cs); // mempool "read lock" (held through m_pool.m_opts.signals->TransactionAddedToMempool())
14321420

1433-
Workspace ws(ptx);
1421+
std::vector<Workspace> workspaces{Workspace(ptx)};
1422+
Workspace &ws = workspaces.front();
14341423
const std::vector<Wtxid> single_wtxid{ws.m_ptx->GetWitnessHash()};
14351424

14361425
if (!PreChecks(args, ws)) {
@@ -1478,7 +1467,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
14781467
ws.m_base_fees, effective_feerate, single_wtxid);
14791468
}
14801469

1481-
if (!Finalize(args, ws)) {
1470+
if (!FinalizeSubpackage(args, workspaces)) {
14821471
// The only possible failure reason is fee-related (mempool full).
14831472
// Failed for fee reasons. Provide the effective feerate and which txns were included.
14841473
Assume(ws.m_state.GetResult() == TxValidationResult::TX_RECONSIDERABLE);

0 commit comments

Comments
 (0)