feat: fee-ordered mempool eviction (TrimToSize/Expire) + displacement on admission
This fork never ported Bitcoin's mempool size-limiting: CTxMemPool had no TrimToSize/
Expire and LimitMempoolSize was commented out. An earlier commit added a blunt
DynamicMemoryUsage admission cap that bounded memory but bluntly REJECTED new txs when
full -- so a high-fee tx could not push out a low-fee one. This implements proper
fee-ordered eviction using the per-tx feerate index that already exists (mapTx index 1,
CompareTxMemPoolEntryByFee), with no new index and no descendant-tracking port.
- CTxMemPool::TrimToSize(sizelimit, pvNoSpendsRemaining): while DynamicMemoryUsage() is
over the limit, evict the lowest-feerate tx (the tail of the feerate index) and its
in-mempool descendants (recursive remove), re-deriving the tail each iteration.
Terminates (pool strictly shrinks) and cleans every secondary index via remove().
- CTxMemPool::Expire(time): age-based sweep (entry time older than `time`), for
LimitMempoolSize's -mempoolexpiry.
- LimitMempoolSize re-enabled (Expire + TrimToSize) and called from ConnectTip on every
block connect. (No pcoinsTip->Uncache -- CCoinsViewCache has none in this fork; it is
only a UTXO-cache perf hint.)
- AcceptToMemoryPool now ADDS the tx then TrimToSizes: a higher-fee tx displaces
lower-fee ones; if this tx was itself the lowest-feerate (evicted), it is rejected
("mempool full"). Replaces the blunt reject-when-full cap.
- DEFAULT_MEMPOOL_EXPIRY 1 -> 72 hours (age-Expire is now live; 1h was too aggressive).
Known simplification (documented in code): per-tx feerate, not descendant-aggregate
(CPFP) scoring, and no rollingMinimumFeeRate anti-thrash. Adversarially reviewed
(termination, iterator safety, recursive-lock safety, index cleanup all confirmed) and
runtime-tested on the fleet: pool stays bounded under a 1600-tx flood, verifychain ok,
no hang/crash.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -491,6 +491,45 @@ std::vector<uint256> CTxMemPool::removeExpired(unsigned int nBlockHeight)
|
||||
return ids;
|
||||
}
|
||||
|
||||
// Age-based eviction: remove txs whose entry time is older than `time`. Distinct from
|
||||
// removeExpired() (which drops txs past their consensus nExpiryHeight); this is the wall-clock
|
||||
// sweep LimitMempoolSize wants. Collect-then-remove to avoid iterating mapTx while mutating it.
|
||||
int CTxMemPool::Expire(int64_t time)
|
||||
{
|
||||
LOCK(cs);
|
||||
std::list<CTransaction> toRemove;
|
||||
for (indexed_transaction_set::const_iterator it = mapTx.begin(); it != mapTx.end(); it++) {
|
||||
if (it->GetTime() < time)
|
||||
toRemove.push_back(it->GetTx());
|
||||
}
|
||||
for (const CTransaction& tx : toRemove) {
|
||||
std::list<CTransaction> removed;
|
||||
remove(tx, removed, true);
|
||||
}
|
||||
return (int)toRemove.size();
|
||||
}
|
||||
|
||||
// Fee-ordered eviction: drop the lowest-feerate txs (and their in-mempool descendants, via the
|
||||
// recursive remove) until DynamicMemoryUsage() is at or below sizelimit. Uses the per-tx feerate
|
||||
// index (mapTx index 1, sorted feerate DESCENDING, so the worst tx is the tail). NOTE: this is a
|
||||
// per-tx feerate, not a descendant-aggregate score, so a low-fee parent funded by a high-fee child
|
||||
// (CPFP) can be evicted -- an accepted simplification (no descendant tracking in this fork). The
|
||||
// admission cap in AcceptToMemoryPool bounds growth between block connects (no rollingMinFee here).
|
||||
void CTxMemPool::TrimToSize(size_t sizelimit, std::vector<uint256>* pvNoSpendsRemaining)
|
||||
{
|
||||
LOCK(cs);
|
||||
while (DynamicMemoryUsage() > sizelimit && !mapTx.empty()) {
|
||||
// Re-derive the tail each iteration: remove() invalidates iterators.
|
||||
CTransaction tx = std::prev(mapTx.get<1>().end())->GetTx();
|
||||
std::list<CTransaction> removed;
|
||||
remove(tx, removed, true);
|
||||
if (pvNoSpendsRemaining) {
|
||||
for (const CTransaction& r : removed)
|
||||
pvNoSpendsRemaining->push_back(r.GetHash());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called when a block is connected. Removes from mempool and updates the miner fee estimator.
|
||||
void CTxMemPool::removeForBlock(const std::vector<CTransaction>& vtx, unsigned int nBlockHeight,
|
||||
std::list<CTransaction>& conflicts, bool fCurrentEstimate)
|
||||
|
||||
Reference in New Issue
Block a user