Skip to content

Commit 2ccb7cc

Browse files
committed
Add Epoch Guards to CTXMemPoolEntry and CTxMemPool
1 parent ceb789c commit 2ccb7cc

File tree

2 files changed

+73
-2
lines changed

2 files changed

+73
-2
lines changed

src/txmempool.cpp

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& _tx, const CAmount& _nFe
2323
int64_t _nTime, unsigned int _entryHeight,
2424
bool _spendsCoinbase, int64_t _sigOpsCost, LockPoints lp)
2525
: tx(_tx), nFee(_nFee), nTxWeight(GetTransactionWeight(*tx)), nUsageSize(RecursiveDynamicUsage(tx)), nTime(_nTime), entryHeight(_entryHeight),
26-
spendsCoinbase(_spendsCoinbase), sigOpCost(_sigOpsCost), lockPoints(lp)
26+
spendsCoinbase(_spendsCoinbase), sigOpCost(_sigOpsCost), lockPoints(lp), m_epoch(0)
2727
{
2828
nCountWithDescendants = 1;
2929
nSizeWithDescendants = GetTxSize();
@@ -325,7 +325,7 @@ void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee,
325325
}
326326

327327
CTxMemPool::CTxMemPool(CBlockPolicyEstimator* estimator)
328-
: nTransactionsUpdated(0), minerPolicyEstimator(estimator)
328+
: nTransactionsUpdated(0), minerPolicyEstimator(estimator), m_epoch(0), m_has_epoch_guard(false)
329329
{
330330
_clear(); //lock free clear
331331

@@ -1105,4 +1105,23 @@ void CTxMemPool::SetIsLoaded(bool loaded)
11051105
m_is_loaded = loaded;
11061106
}
11071107

1108+
1109+
CTxMemPool::EpochGuard CTxMemPool::GetFreshEpoch() const
1110+
{
1111+
return EpochGuard(*this);
1112+
}
1113+
CTxMemPool::EpochGuard::EpochGuard(const CTxMemPool& in) : pool(in)
1114+
{
1115+
assert(!pool.m_has_epoch_guard);
1116+
++pool.m_epoch;
1117+
pool.m_has_epoch_guard = true;
1118+
}
1119+
1120+
CTxMemPool::EpochGuard::~EpochGuard()
1121+
{
1122+
// prevents stale results being used
1123+
++pool.m_epoch;
1124+
pool.m_has_epoch_guard = false;
1125+
}
1126+
11081127
SaltedTxidHasher::SaltedTxidHasher() : k0(GetRand(std::numeric_limits<uint64_t>::max())), k1(GetRand(std::numeric_limits<uint64_t>::max())) {}

src/txmempool.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ class CTxMemPoolEntry
129129
int64_t GetSigOpCostWithAncestors() const { return nSigOpCostWithAncestors; }
130130

131131
mutable size_t vTxHashesIdx; //!< Index in mempool's vTxHashes
132+
mutable uint64_t m_epoch; //!< epoch when last touched, useful for graph algorithms
132133
};
133134

134135
// Helpers for modifying CTxMemPool::mapTx, which is a boost multi_index.
@@ -453,6 +454,8 @@ class CTxMemPool
453454
mutable int64_t lastRollingFeeUpdate;
454455
mutable bool blockSinceLastRollingFeeBump;
455456
mutable double rollingMinimumFeeRate; //!< minimum fee to get into the pool, decreases exponentially
457+
mutable uint64_t m_epoch;
458+
mutable bool m_has_epoch_guard;
456459

457460
void trackPackageRemoved(const CFeeRate& rate) EXCLUSIVE_LOCKS_REQUIRED(cs);
458461

@@ -736,6 +739,55 @@ class CTxMemPool
736739
* removal.
737740
*/
738741
void removeUnchecked(txiter entry, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
742+
public:
743+
/** EpochGuard: RAII-style guard for using epoch-based graph traversal algorithms.
744+
* When walking ancestors or descendants, we generally want to avoid
745+
* visiting the same transactions twice. Some traversal algorithms use
746+
* std::set (or setEntries) to deduplicate the transaction we visit.
747+
* However, use of std::set is algorithmically undesirable because it both
748+
* adds an asymptotic factor of O(log n) to traverals cost and triggers O(n)
749+
* more dynamic memory allocations.
750+
* In many algorithms we can replace std::set with an internal mempool
751+
* counter to track the time (or, "epoch") that we began a traversal, and
752+
* check + update a per-transaction epoch for each transaction we look at to
753+
* determine if that transaction has not yet been visited during the current
754+
* traversal's epoch.
755+
* Algorithms using std::set can be replaced on a one by one basis.
756+
* Both techniques are not fundamentally incomaptible across the codebase.
757+
* Generally speaking, however, the remaining use of std::set for mempool
758+
* traversal should be viewed as a TODO for replacement with an epoch based
759+
* traversal, rather than a preference for std::set over epochs in that
760+
* algorithm.
761+
*/
762+
class EpochGuard {
763+
const CTxMemPool& pool;
764+
public:
765+
EpochGuard(const CTxMemPool& in);
766+
~EpochGuard();
767+
};
768+
// N.B. GetFreshEpoch modifies mutable state via the EpochGuard construction
769+
// (and later destruction)
770+
EpochGuard GetFreshEpoch() const EXCLUSIVE_LOCKS_REQUIRED(cs);
771+
772+
/** visited marks a CTxMemPoolEntry as having been traversed
773+
* during the lifetime of the most recently created EpochGuard
774+
* and returns false if we are the first visitor, true otherwise.
775+
*
776+
* An EpochGuard must be held when visited is called or an assert will be
777+
* triggered.
778+
*
779+
*/
780+
bool visited(txiter it) const EXCLUSIVE_LOCKS_REQUIRED(cs) {
781+
assert(m_has_epoch_guard);
782+
bool ret = it->m_epoch >= m_epoch;
783+
it->m_epoch = std::max(it->m_epoch, m_epoch);
784+
return ret;
785+
}
786+
787+
bool visited(Optional<txiter> it) const EXCLUSIVE_LOCKS_REQUIRED(cs) {
788+
assert(m_has_epoch_guard);
789+
return !it || visited(*it);
790+
}
739791
};
740792

741793
/**

0 commit comments

Comments
 (0)