Skip to content

Commit ad29e77

Browse files
committed
Remove CacheMap from mempool UpdateForDescendants. CacheMap was an optimization to limit re-traversal in UpdateForDescendants. But because we know that descendants limit limits the total number of descendants, this actually doesn't help that much. There are some common cases where having a CacheMap might be faster, but an adversary can trivially construct a worst-case example that causes equal behavior with or without a cachemap. For example, a transaction with N/2 children which each have N/2 outputs that get spent by N/2 grandchildren will cause the parent to iterate over N^2 entries with or without CacheMap. Post Epochs, UpdateForDescendants is sufficiently cheap such that the cachemap is just extra memory overhead.
1 parent 89a50ce commit ad29e77

File tree

2 files changed

+15
-37
lines changed

2 files changed

+15
-37
lines changed

src/txmempool.cpp

+15-35
Original file line numberDiff line numberDiff line change
@@ -57,57 +57,44 @@ size_t CTxMemPoolEntry::GetTxSize() const
5757
// Update the given tx for any in-mempool descendants.
5858
// Assumes that setMemPoolChildren is correct for the given tx and all
5959
// descendants.
60-
void CTxMemPool::UpdateForDescendants(txiter update_it, cacheMap& cache, const std::set<uint256>& exclude)
60+
void CTxMemPool::UpdateForDescendants(txiter update_it, const std::set<uint256>& exclude)
6161
{
6262
const auto epoch = GetFreshEpoch();
6363
const CTxMemPool::setEntries& direct_children = GetMemPoolChildren(update_it);
64-
// set up the update_cache to contain all of our transaction's children (note --
64+
// set up the stage to contain all of our transaction's children (note --
6565
// already de-duplicated in case multiple outputs of ours are spent in one
6666
// transaction)
67-
vecEntries update_cache;
68-
update_cache.reserve(direct_children.size());
67+
vecEntries stage;
68+
stage.reserve(direct_children.size());
6969
// mark every direct_child as visited so that we don't accidentally re-add them
7070
// to the cache in the grandchild is child case
7171
for (const txiter direct_child : direct_children) {
72-
update_cache.emplace_back(direct_child);
72+
stage.emplace_back(direct_child);
7373
visited(direct_child);
7474
}
7575
// already_traversed index keeps track of the elements that we've
7676
// already expanded. If index is < already_traversed, we've walked it.
7777
// If index is >= already_traversed, we need to walk it.
78-
// If already_traversed >= update_cache.size(), we're finished.
79-
for (size_t already_traversed = 0; already_traversed < update_cache.size(); /* modified in loop body */) {
78+
// If already_traversed >= stage.size(), we're finished.
79+
for (size_t already_traversed = 0; already_traversed < stage.size(); /* modified in loop body */) {
8080
// rotate the back() to behind already_traversed
81-
const txiter child_it = update_cache.back();
82-
std::swap(update_cache[already_traversed++], update_cache.back());
81+
const txiter child_it = stage.back();
82+
std::swap(stage[already_traversed++], stage.back());
8383

8484
// N.B. grand_children may also be children
8585
const CTxMemPool::setEntries& grand_children = GetMemPoolChildren(child_it);
8686
for (const txiter grand_child_it : grand_children) {
8787
if (visited(grand_child_it)) continue;
88-
cacheMap::iterator cached_great_grand_children = cache.find(grand_child_it);
89-
if (cached_great_grand_children != cache.end()) {
90-
for (const txiter great_grand_child : cached_great_grand_children->second) {
91-
if (visited(great_grand_child)) continue;
92-
update_cache.emplace_back(great_grand_child);
93-
// place on the back and then swap into the already_traversed index
94-
// so we don't walk it ourselves (whoever put the grand
95-
// child in the cache must have already traversed this)
96-
std::swap(update_cache[already_traversed++], update_cache.back());
97-
}
98-
} else {
99-
// Schedule for later processing
100-
update_cache.emplace_back(grand_child_it);
101-
}
88+
stage.emplace_back(grand_child_it);
10289
}
10390
}
10491

105-
// update_cache now contains all in-mempool descendants of update_it,
92+
// stage now contains all in-mempool descendants of update_it,
10693
// compute updates now.
10794
int64_t modify_size = 0;
10895
CAmount modify_fee = 0;
10996
int64_t modify_count = 0;
110-
for (txiter child_it : update_cache) {
97+
for (txiter child_it : stage) {
11198
const CTxMemPoolEntry& child = *child_it;
11299
if (!exclude.count(child.GetTx().GetHash())) {
113100
modify_size += child.GetTxSize();
@@ -117,8 +104,6 @@ void CTxMemPool::UpdateForDescendants(txiter update_it, cacheMap& cache, const s
117104
}
118105
}
119106
mapTx.modify(update_it, update_descendant_state(modify_size, modify_fee, modify_count));
120-
// share the cache (if there is one)
121-
if (!update_cache.empty()) cache.emplace(update_it, std::move(update_cache));
122107
}
123108
// vHashesToUpdate is the set of transaction hashes from a disconnected block
124109
// which has been re-added to the mempool.
@@ -128,20 +113,15 @@ void CTxMemPool::UpdateForDescendants(txiter update_it, cacheMap& cache, const s
128113
void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate)
129114
{
130115
AssertLockHeld(cs);
131-
// For each entry in vHashesToUpdate, store the set of in-mempool, but not
132-
// in-vHashesToUpdate transactions, so that we don't have to recalculate
133-
// descendants when we come across a previously seen entry.
134-
cacheMap mapMemPoolDescendantsToUpdate;
135116

136117
// Use a set for lookups into vHashesToUpdate (these entries are already
137118
// accounted for in the state of their ancestors)
138119
std::set<uint256> setAlreadyIncluded(vHashesToUpdate.begin(), vHashesToUpdate.end());
139120

140121
// Iterate in reverse, so that whenever we are looking at a transaction
141122
// we are sure that all in-mempool descendants have already been processed.
142-
// This maximizes the benefit of the descendant cache and guarantees that
143-
// setMemPoolChildren will be updated, an assumption made in
144-
// UpdateForDescendants.
123+
// This guarantees that setMemPoolChildren will be up to date, an assumption
124+
// made in UpdateForDescendants.
145125
for (const uint256 &hash : reverse_iterate(vHashesToUpdate)) {
146126
// calculate children from mapNextTx
147127
txiter it = mapTx.find(hash);
@@ -166,7 +146,7 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
166146
}
167147
}
168148
} // release epoch guard for UpdateForDescendants
169-
UpdateForDescendants(it, mapMemPoolDescendantsToUpdate, setAlreadyIncluded);
149+
UpdateForDescendants(it, setAlreadyIncluded);
170150
}
171151
}
172152

src/txmempool.h

-2
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,6 @@ class CTxMemPool
536536
const setEntries & GetMemPoolChildren(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs);
537537
uint64_t CalculateDescendantMaximum(txiter entry) const EXCLUSIVE_LOCKS_REQUIRED(cs);
538538
private:
539-
typedef std::map<txiter, vecEntries, CompareIteratorByHash> cacheMap;
540539

541540
struct TxLinks {
542541
setEntries parents;
@@ -718,7 +717,6 @@ class CTxMemPool
718717
* same transaction again, if encountered in another transaction chain.
719718
*/
720719
void UpdateForDescendants(txiter updateIt,
721-
cacheMap &cachedDescendants,
722720
const std::set<uint256> &setExclude) EXCLUSIVE_LOCKS_REQUIRED(cs);
723721
/** Update ancestors of hash to add/remove it as a descendant transaction. */
724722
void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs);

0 commit comments

Comments
 (0)