Skip to content

Commit 0147278

Browse files
committed
Merge bitcoin/bitcoin#21464: Mempool Update Cut-Through Optimization
c5b36b1 Mempool Update Cut-Through Optimization (Jeremy Rubin) c49daf9 [TESTS] Increase limitancestorcount in tournament RPC test to showcase improved algorithm (Jeremy Rubin) Pull request description: Often when we're updating mempool entries we update entries that we ultimately end up removing the updated entries shortly thereafter. This patch makes it so that we filter for such entries a bit earlier in processing, which yields a mild improvement for these cases, and is negligible overhead otherwise. There's potential for a better -- but more sophisticated -- algorithm that can be used taking advantage of epochs, but I figured it is better to do something that is simple and works first and upgrade it later as the other epoch mempool work proceeds as it makes the patches for the epoch algorithm simpler to understand, so you can consider this as preparatory work. It could either go in now if it is not controversial, or we could wait until the other patch is ready to go. ACKs for top commit: instagibbs: reACK c5b36b1 sipa: utACK c5b36b1 mzumsande: Code Review ACK c5b36b1 Tree-SHA512: 78b16864f77a637d8a68a65e23c019a9757d8b2243486728ef601d212ae482f6084cf8e69d810958c356f1803178046e4697207ba40d6d10529ca57de647fae6
2 parents 417e750 + c5b36b1 commit 0147278

File tree

4 files changed

+73
-33
lines changed

4 files changed

+73
-33
lines changed

src/txmempool.cpp

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,9 @@ size_t CTxMemPoolEntry::GetTxSize() const
116116
return GetVirtualTransactionSize(nTxWeight, sigOpCost);
117117
}
118118

119-
// Update the given tx for any in-mempool descendants.
120-
// Assumes that CTxMemPool::m_children is correct for the given tx and all
121-
// descendants.
122-
void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendants, const std::set<uint256> &setExclude)
119+
void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants,
120+
const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove,
121+
uint64_t ancestor_size_limit, uint64_t ancestor_count_limit)
123122
{
124123
CTxMemPoolEntry::Children stageEntries, descendants;
125124
stageEntries = updateIt->GetMemPoolChildrenConst();
@@ -156,17 +155,18 @@ void CTxMemPool::UpdateForDescendants(txiter updateIt, cacheMap &cachedDescendan
156155
cachedDescendants[updateIt].insert(mapTx.iterator_to(descendant));
157156
// Update ancestor state for each descendant
158157
mapTx.modify(mapTx.iterator_to(descendant), update_ancestor_state(updateIt->GetTxSize(), updateIt->GetModifiedFee(), 1, updateIt->GetSigOpCost()));
158+
// Don't directly remove the transaction here -- doing so would
159+
// invalidate iterators in cachedDescendants. Mark it for removal
160+
// by inserting into descendants_to_remove.
161+
if (descendant.GetCountWithAncestors() > ancestor_count_limit || descendant.GetSizeWithAncestors() > ancestor_size_limit) {
162+
descendants_to_remove.insert(descendant.GetTx().GetHash());
163+
}
159164
}
160165
}
161166
mapTx.modify(updateIt, update_descendant_state(modifySize, modifyFee, modifyCount));
162167
}
163168

164-
// vHashesToUpdate is the set of transaction hashes from a disconnected block
165-
// which has been re-added to the mempool.
166-
// for each entry, look for descendants that are outside vHashesToUpdate, and
167-
// add fee/size information for such descendants to the parent.
168-
// for each such descendant, also update the ancestor state to include the parent.
169-
void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate)
169+
void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashesToUpdate, uint64_t ancestor_size_limit, uint64_t ancestor_count_limit)
170170
{
171171
AssertLockHeld(cs);
172172
// For each entry in vHashesToUpdate, store the set of in-mempool, but not
@@ -178,6 +178,8 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
178178
// accounted for in the state of their ancestors)
179179
std::set<uint256> setAlreadyIncluded(vHashesToUpdate.begin(), vHashesToUpdate.end());
180180

181+
std::set<uint256> descendants_to_remove;
182+
181183
// Iterate in reverse, so that whenever we are looking at a transaction
182184
// we are sure that all in-mempool descendants have already been processed.
183185
// This maximizes the benefit of the descendant cache and guarantees that
@@ -207,7 +209,15 @@ void CTxMemPool::UpdateTransactionsFromBlock(const std::vector<uint256> &vHashes
207209
}
208210
}
209211
} // release epoch guard for UpdateForDescendants
210-
UpdateForDescendants(it, mapMemPoolDescendantsToUpdate, setAlreadyIncluded);
212+
UpdateForDescendants(it, mapMemPoolDescendantsToUpdate, setAlreadyIncluded, descendants_to_remove, ancestor_size_limit, ancestor_count_limit);
213+
}
214+
215+
for (const auto& txid : descendants_to_remove) {
216+
// This txid may have been removed already in a prior call to removeRecursive.
217+
// Therefore we ensure it is not yet removed already.
218+
if (const std::optional<txiter> txiter = GetIter(txid)) {
219+
removeRecursive((*txiter)->GetTx(), MemPoolRemovalReason::SIZELIMIT);
220+
}
211221
}
212222
}
213223

src/txmempool.h

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -636,16 +636,25 @@ class CTxMemPool
636636
*/
637637
void RemoveStaged(setEntries& stage, bool updateDescendants, MemPoolRemovalReason reason) EXCLUSIVE_LOCKS_REQUIRED(cs);
638638

639-
/** When adding transactions from a disconnected block back to the mempool,
640-
* new mempool entries may have children in the mempool (which is generally
641-
* not the case when otherwise adding transactions).
642-
* UpdateTransactionsFromBlock() will find child transactions and update the
643-
* descendant state for each transaction in vHashesToUpdate (excluding any
644-
* child transactions present in vHashesToUpdate, which are already accounted
645-
* for). Note: vHashesToUpdate should be the set of transactions from the
646-
* disconnected block that have been accepted back into the mempool.
639+
/** UpdateTransactionsFromBlock is called when adding transactions from a
640+
* disconnected block back to the mempool, new mempool entries may have
641+
* children in the mempool (which is generally not the case when otherwise
642+
* adding transactions).
643+
* @post updated descendant state for descendants of each transaction in
644+
* vHashesToUpdate (excluding any child transactions present in
645+
* vHashesToUpdate, which are already accounted for). Updated state
646+
* includes add fee/size information for such descendants to the
647+
* parent and updated ancestor state to include the parent.
648+
*
649+
* @param[in] vHashesToUpdate The set of txids from the
650+
* disconnected block that have been accepted back into the mempool.
651+
* @param[in] ancestor_size_limit The maximum allowed size in virtual
652+
* bytes of an entry and its ancestors
653+
* @param[in] ancestor_count_limit The maximum allowed number of
654+
* transactions including the entry and its ancestors.
647655
*/
648-
void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch);
656+
void UpdateTransactionsFromBlock(const std::vector<uint256>& vHashesToUpdate,
657+
uint64_t ancestor_size_limit, uint64_t ancestor_count_limit) EXCLUSIVE_LOCKS_REQUIRED(cs, cs_main) LOCKS_EXCLUDED(m_epoch);
649658

650659
/** Try to calculate all in-mempool ancestors of entry.
651660
* (these are all calculated including the tx itself)
@@ -794,19 +803,38 @@ class CTxMemPool
794803
/** UpdateForDescendants is used by UpdateTransactionsFromBlock to update
795804
* the descendants for a single transaction that has been added to the
796805
* mempool but may have child transactions in the mempool, eg during a
797-
* chain reorg. setExclude is the set of descendant transactions in the
798-
* mempool that must not be accounted for (because any descendants in
799-
* setExclude were added to the mempool after the transaction being
800-
* updated and hence their state is already reflected in the parent
801-
* state).
806+
* chain reorg.
807+
*
808+
* @pre CTxMemPool::m_children is correct for the given tx and all
809+
* descendants.
810+
* @pre cachedDescendants is an accurate cache where each entry has all
811+
* descendants of the corresponding key, including those that should
812+
* be removed for violation of ancestor limits.
813+
* @post if updateIt has any non-excluded descendants, cachedDescendants has
814+
* a new cache line for updateIt.
815+
* @post descendants_to_remove has a new entry for any descendant which exceeded
816+
* ancestor limits relative to updateIt.
802817
*
803-
* cachedDescendants will be updated with the descendants of the transaction
804-
* being updated, so that future invocations don't need to walk the
805-
* same transaction again, if encountered in another transaction chain.
818+
* @param[in] updateIt the entry to update for its descendants
819+
* @param[in,out] cachedDescendants a cache where each line corresponds to all
820+
* descendants. It will be updated with the descendants of the transaction
821+
* being updated, so that future invocations don't need to walk the same
822+
* transaction again, if encountered in another transaction chain.
823+
* @param[in] setExclude the set of descendant transactions in the mempool
824+
* that must not be accounted for (because any descendants in setExclude
825+
* were added to the mempool after the transaction being updated and hence
826+
* their state is already reflected in the parent state).
827+
* @param[out] descendants_to_remove Populated with the txids of entries that
828+
* exceed ancestor limits. It's the responsibility of the caller to
829+
* removeRecursive them.
830+
* @param[in] ancestor_size_limit the max number of ancestral bytes allowed
831+
* for any descendant
832+
* @param[in] ancestor_count_limit the max number of ancestor transactions
833+
* allowed for any descendant
806834
*/
807-
void UpdateForDescendants(txiter updateIt,
808-
cacheMap &cachedDescendants,
809-
const std::set<uint256> &setExclude) EXCLUSIVE_LOCKS_REQUIRED(cs);
835+
void UpdateForDescendants(txiter updateIt, cacheMap& cachedDescendants,
836+
const std::set<uint256>& setExclude, std::set<uint256>& descendants_to_remove,
837+
uint64_t ancestor_size_limit, uint64_t ancestor_count_limit) EXCLUSIVE_LOCKS_REQUIRED(cs);
810838
/** Update ancestors of hash to add/remove it as a descendant transaction. */
811839
void UpdateAncestorsOf(bool add, txiter hash, setEntries &setAncestors) EXCLUSIVE_LOCKS_REQUIRED(cs);
812840
/** Set ancestor state for an entry */

src/validation.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,9 @@ void CChainState::MaybeUpdateMempoolForReorg(
347347
// previously-confirmed transactions back to the mempool.
348348
// UpdateTransactionsFromBlock finds descendants of any transactions in
349349
// the disconnectpool that were added back and cleans up the mempool state.
350-
m_mempool->UpdateTransactionsFromBlock(vHashUpdate);
350+
const uint64_t ancestor_count_limit = gArgs.GetIntArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
351+
const uint64_t ancestor_size_limit = gArgs.GetIntArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000;
352+
m_mempool->UpdateTransactionsFromBlock(vHashUpdate, ancestor_size_limit, ancestor_count_limit);
351353

352354
// Predicate to use for filtering transactions in removeForReorg.
353355
// Checks whether the transaction is still final and, if it spends a coinbase output, mature.

test/functional/mempool_updatefromblock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
class MempoolUpdateFromBlockTest(BitcoinTestFramework):
1818
def set_test_params(self):
1919
self.num_nodes = 1
20-
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000']]
20+
self.extra_args = [['-limitdescendantsize=1000', '-limitancestorsize=1000', '-limitancestorcount=100']]
2121

2222
def skip_test_if_missing_module(self):
2323
self.skip_if_no_wallet()

0 commit comments

Comments
 (0)