Add transactions to txgraph, but without cluster dependencies

Effectively this is treating all transactions in txgraph as being in a cluster
of size 1.
This commit is contained in:
Suhas Daftuar
2025-01-09 12:43:43 -05:00
parent d5ed9cb3eb
commit 838d7e3553
3 changed files with 55 additions and 17 deletions

View File

@@ -435,6 +435,8 @@ void CTxMemPool::AddTransactionsUpdated(unsigned int n)
void CTxMemPool::Apply(ChangeSet* changeset)
{
AssertLockHeld(cs);
m_txgraph->CommitStaging();
RemoveStaged(changeset->m_to_remove, false, MemPoolRemovalReason::REPLACED);
for (size_t i=0; i<changeset->m_entry_vec.size(); ++i) {
@@ -891,7 +893,10 @@ void CTxMemPool::PrioritiseTransaction(const Txid& hash, const CAmount& nFeeDelt
delta = SaturatingAdd(delta, nFeeDelta);
txiter it = mapTx.find(hash);
if (it != mapTx.end()) {
// PrioritiseTransaction calls stack on previous ones. Set the new
// transaction fee to be current modified fee + feedelta.
mapTx.modify(it, [&nFeeDelta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(nFeeDelta); });
m_txgraph->SetTransactionFee(*it, it->GetModifiedFee());
// Now update all ancestors' modified fees with descendants
auto ancestors{AssumeCalculateMemPoolAncestors(__func__, *it, Limits::NoLimits(), /*fSearchForParents=*/false)};
for (txiter ancestorIt : ancestors) {
@@ -1387,15 +1392,28 @@ CTxMemPool::ChangeSet::TxHandle CTxMemPool::ChangeSet::StageAddition(const CTran
{
LOCK(m_pool->cs);
Assume(m_to_add.find(tx->GetHash()) == m_to_add.end());
auto newit = m_to_add.emplace(TxGraph::Ref(), tx, fee, time, entry_height, entry_sequence, spends_coinbase, sigops_cost, lp).first;
CAmount delta{0};
m_pool->ApplyDelta(tx->GetHash(), delta);
if (delta) m_to_add.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(delta); });
TxGraph::Ref ref(m_pool->m_txgraph->AddTransaction(FeePerWeight(fee, GetSigOpsAdjustedWeight(GetTransactionWeight(*tx), sigops_cost, ::nBytesPerSigOp))));
auto newit = m_to_add.emplace(std::move(ref), tx, fee, time, entry_height, entry_sequence, spends_coinbase, sigops_cost, lp).first;
if (delta) {
m_to_add.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(delta); });
m_pool->m_txgraph->SetTransactionFee(*newit, newit->GetModifiedFee());
}
m_entry_vec.push_back(newit);
return newit;
}
void CTxMemPool::ChangeSet::StageRemoval(CTxMemPool::txiter it)
{
LOCK(m_pool->cs);
m_pool->m_txgraph->RemoveTransaction(*it);
m_to_remove.insert(it);
}
void CTxMemPool::ChangeSet::Apply()
{
LOCK(m_pool->cs);

View File

@@ -371,6 +371,7 @@ public:
* the mempool is consistent with the new chain tip and fully populated.
*/
mutable RecursiveMutex cs;
std::unique_ptr<TxGraph> m_txgraph GUARDED_BY(cs);
indexed_transaction_set mapTx GUARDED_BY(cs);
using txiter = indexed_transaction_set::nth_index<0>::type::const_iterator;
@@ -382,7 +383,6 @@ public:
uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs);
private:
std::unique_ptr<TxGraph> m_txgraph GUARDED_BY(cs);
typedef std::map<txiter, setEntries, CompareIteratorByHash> cacheMap;
@@ -822,8 +822,14 @@ public:
*/
class ChangeSet {
public:
explicit ChangeSet(CTxMemPool* pool) : m_pool(pool) {}
~ChangeSet() EXCLUSIVE_LOCKS_REQUIRED(m_pool->cs) { m_pool->m_have_changeset = false; }
explicit ChangeSet(CTxMemPool* pool) : m_pool(pool) { m_pool->m_txgraph->StartStaging(); }
~ChangeSet() EXCLUSIVE_LOCKS_REQUIRED(m_pool->cs) {
AssertLockHeld(m_pool->cs);
if (m_pool->m_txgraph->HaveStaging()) {
m_pool->m_txgraph->AbortStaging();
}
m_pool->m_have_changeset = false;
}
ChangeSet(const ChangeSet&) = delete;
ChangeSet& operator=(const ChangeSet&) = delete;
@@ -831,7 +837,8 @@ public:
using TxHandle = CTxMemPool::txiter;
TxHandle StageAddition(const CTransactionRef& tx, const CAmount fee, int64_t time, unsigned int entry_height, uint64_t entry_sequence, bool spends_coinbase, int64_t sigops_cost, LockPoints lp);
void StageRemoval(CTxMemPool::txiter it) { m_to_remove.insert(it); }
void StageRemoval(CTxMemPool::txiter it);
const CTxMemPool::setEntries& GetRemovals() const { return m_to_remove; }

View File

@@ -591,14 +591,26 @@ public:
void CleanupTemporaryCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
// Single transaction acceptance
MempoolAcceptResult AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
MempoolAcceptResult AcceptSingleTransactionAndCleanup(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
LOCK(m_pool.cs);
MempoolAcceptResult result = AcceptSingleTransactionInternal(ptx, args);
ClearSubPackageState();
return result;
}
MempoolAcceptResult AcceptSingleTransactionInternal(const CTransactionRef& ptx, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
/**
* Multiple transaction acceptance. Transactions may or may not be interdependent, but must not
* conflict with each other, and the transactions cannot already be in the mempool. Parents must
* come before children if any dependencies exist.
*/
PackageMempoolAcceptResult AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
PackageMempoolAcceptResult AcceptMultipleTransactionsAndCleanup(const std::vector<CTransactionRef>& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
LOCK(m_pool.cs);
PackageMempoolAcceptResult result = AcceptMultipleTransactionsInternal(txns, args);
ClearSubPackageState();
return result;
}
PackageMempoolAcceptResult AcceptMultipleTransactionsInternal(const std::vector<CTransactionRef>& txns, ATMPArgs& args) EXCLUSIVE_LOCKS_REQUIRED(cs_main, m_pool.cs);
/**
* Submission of a subpackage.
@@ -1423,10 +1435,10 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
return all_submitted;
}
MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs& args)
MempoolAcceptResult MemPoolAccept::AcceptSingleTransactionInternal(const CTransactionRef& ptx, ATMPArgs& args)
{
AssertLockHeld(cs_main);
LOCK(m_pool.cs); // mempool "read lock" (held through m_pool.m_opts.signals->TransactionAddedToMempool())
AssertLockHeld(m_pool.cs);
Workspace ws(ptx);
const std::vector<Wtxid> single_wtxid{ws.m_ptx->GetWitnessHash()};
@@ -1515,9 +1527,10 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
effective_feerate, single_wtxid);
}
PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::vector<CTransactionRef>& txns, ATMPArgs& args)
PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactionsInternal(const std::vector<CTransactionRef>& txns, ATMPArgs& args)
{
AssertLockHeld(cs_main);
AssertLockHeld(m_pool.cs);
// These context-free package limits can be done before taking the mempool lock.
PackageValidationState package_state;
@@ -1529,8 +1542,6 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
[](const auto& tx) { return Workspace(tx); });
std::map<Wtxid, MempoolAcceptResult> results;
LOCK(m_pool.cs);
// Do all PreChecks first and fail fast to avoid running expensive script checks when unnecessary.
for (Workspace& ws : workspaces) {
if (!PreChecks(args, ws)) {
@@ -1681,11 +1692,11 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptSubPackage(const std::vector<CTr
AssertLockHeld(m_pool.cs);
auto result = [&]() EXCLUSIVE_LOCKS_REQUIRED(::cs_main, m_pool.cs) {
if (subpackage.size() > 1) {
return AcceptMultipleTransactions(subpackage, args);
return AcceptMultipleTransactionsInternal(subpackage, args);
}
const auto& tx = subpackage.front();
ATMPArgs single_args = ATMPArgs::SingleInPackageAccept(args);
const auto single_res = AcceptSingleTransaction(tx, single_args);
const auto single_res = AcceptSingleTransactionInternal(tx, single_args);
PackageValidationState package_state_wrapped;
if (single_res.m_result_type != MempoolAcceptResult::ResultType::VALID) {
package_state_wrapped.Invalid(PackageValidationResult::PCKG_TX, "transaction failed");
@@ -1863,8 +1874,10 @@ MempoolAcceptResult AcceptToMemoryPool(Chainstate& active_chainstate, const CTra
CTxMemPool& pool{*active_chainstate.GetMempool()};
std::vector<COutPoint> coins_to_uncache;
auto args = MemPoolAccept::ATMPArgs::SingleAccept(chainparams, accept_time, bypass_limits, coins_to_uncache, test_accept);
MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransaction(tx, args);
MempoolAcceptResult result = MemPoolAccept(pool, active_chainstate).AcceptSingleTransactionAndCleanup(tx, args);
if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) {
// Remove coins that were not present in the coins cache before calling
// AcceptSingleTransaction(); this is to prevent memory DoS in case we receive a large
@@ -1897,7 +1910,7 @@ PackageMempoolAcceptResult ProcessNewPackage(Chainstate& active_chainstate, CTxM
AssertLockHeld(cs_main);
if (test_accept) {
auto args = MemPoolAccept::ATMPArgs::PackageTestAccept(chainparams, GetTime(), coins_to_uncache);
return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactions(package, args);
return MemPoolAccept(pool, active_chainstate).AcceptMultipleTransactionsAndCleanup(package, args);
} else {
auto args = MemPoolAccept::ATMPArgs::PackageChildWithParents(chainparams, GetTime(), coins_to_uncache, client_maxfeerate);
return MemPoolAccept(pool, active_chainstate).AcceptPackage(package, args);