Skip to content

Commit 4313c77

Browse files
glozowtheuni
andcommitted
make DisconnectedBlockTransactions responsible for its own memory management
Co-authored-by: Cory Fields <[email protected]>
1 parent cf5f1fa commit 4313c77

File tree

4 files changed

+33
-27
lines changed

4 files changed

+33
-27
lines changed

src/bench/disconnected_transactions.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ static ReorgTxns CreateBlocks(size_t num_not_shared)
7373

7474
static void Reorg(const ReorgTxns& reorg)
7575
{
76-
DisconnectedBlockTransactions disconnectpool;
76+
DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_SIZE * 1000};
7777
// Disconnect block
78-
disconnectpool.AddTransactionsFromBlock(reorg.disconnected_txns);
78+
const auto evicted = disconnectpool.AddTransactionsFromBlock(reorg.disconnected_txns);
79+
assert(evicted.empty());
7980

8081
// Connect first block
8182
disconnectpool.removeForBlock(reorg.connected_txns_1);

src/kernel/disconnected_transactions.h

+23-14
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,10 @@
1212

1313
#include <list>
1414
#include <unordered_map>
15+
#include <vector>
1516

17+
/** Maximum kilobytes for transactions to store for processing during reorg */
18+
static const unsigned int MAX_DISCONNECTED_TX_POOL_SIZE = 20'000;
1619
/**
1720
* DisconnectedBlockTransactions
1821
@@ -38,11 +41,28 @@ class DisconnectedBlockTransactions {
3841
/** Cached dynamic memory usage for the CTransactions (memory for the shared pointers is
3942
* included in the container calculations). */
4043
uint64_t cachedInnerUsage = 0;
44+
const size_t m_max_mem_usage;
4145
std::list<CTransactionRef> queuedTx;
4246
using TxList = decltype(queuedTx);
4347
std::unordered_map<uint256, TxList::iterator, SaltedTxidHasher> iters_by_txid;
4448

49+
/** Trim the earliest-added entries until we are within memory bounds. */
50+
std::vector<CTransactionRef> LimitMemoryUsage()
51+
{
52+
std::vector<CTransactionRef> evicted;
53+
54+
while (!queuedTx.empty() && DynamicMemoryUsage() > m_max_mem_usage) {
55+
evicted.emplace_back(queuedTx.front());
56+
cachedInnerUsage -= RecursiveDynamicUsage(*queuedTx.front());
57+
iters_by_txid.erase(queuedTx.front()->GetHash());
58+
queuedTx.pop_front();
59+
}
60+
return evicted;
61+
}
62+
4563
public:
64+
DisconnectedBlockTransactions(size_t max_mem_usage) : m_max_mem_usage{max_mem_usage} {}
65+
4666
// It's almost certainly a logic bug if we don't clear out queuedTx before
4767
// destruction, as we add to it while disconnecting blocks, and then we
4868
// need to re-process remaining transactions to ensure mempool consistency.
@@ -66,15 +86,17 @@ class DisconnectedBlockTransactions {
6686
* We assume that callers never pass multiple transactions with the same txid, otherwise things
6787
* can go very wrong in removeForBlock due to queuedTx containing an item without a
6888
* corresponding entry in iters_by_txid.
89+
* @returns vector of transactions that were evicted for size-limiting.
6990
*/
70-
void AddTransactionsFromBlock(const std::vector<CTransactionRef>& vtx)
91+
[[nodiscard]] std::vector<CTransactionRef> AddTransactionsFromBlock(const std::vector<CTransactionRef>& vtx)
7192
{
7293
iters_by_txid.reserve(iters_by_txid.size() + vtx.size());
7394
for (auto block_it = vtx.rbegin(); block_it != vtx.rend(); ++block_it) {
7495
auto it = queuedTx.insert(queuedTx.end(), *block_it);
7596
iters_by_txid.emplace((*block_it)->GetHash(), it);
7697
cachedInnerUsage += RecursiveDynamicUsage(**block_it);
7798
}
99+
return LimitMemoryUsage();
78100
}
79101

80102
/** Remove any entries that are in this block. */
@@ -95,19 +117,6 @@ class DisconnectedBlockTransactions {
95117
}
96118
}
97119

98-
/** Remove the first entry and update memory usage. */
99-
CTransactionRef take_first()
100-
{
101-
CTransactionRef first_tx;
102-
if (!queuedTx.empty()) {
103-
first_tx = queuedTx.front();
104-
cachedInnerUsage -= RecursiveDynamicUsage(*queuedTx.front());
105-
iters_by_txid.erase(queuedTx.front()->GetHash());
106-
queuedTx.pop_front();
107-
}
108-
return first_tx;
109-
}
110-
111120
size_t size() const { return queuedTx.size(); }
112121

113122
void clear()

src/test/validation_chainstatemanager_tests.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -537,7 +537,7 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_snapshot_init, SnapshotTestSetup)
537537
// it will initialize instead of attempting to complete validation.
538538
//
539539
// Note that this is not a realistic use of DisconnectTip().
540-
DisconnectedBlockTransactions unused_pool;
540+
DisconnectedBlockTransactions unused_pool{MAX_DISCONNECTED_TX_POOL_SIZE * 1000};
541541
BlockValidationState unused_state;
542542
{
543543
LOCK2(::cs_main, bg_chainstate.MempoolMutex());

src/validation.cpp

+6-10
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,6 @@ using node::CBlockIndexWorkComparator;
8080
using node::fReindex;
8181
using node::SnapshotMetadata;
8282

83-
/** Maximum kilobytes for transactions to store for processing during reorg */
84-
static const unsigned int MAX_DISCONNECTED_TX_POOL_SIZE = 20000;
8583
/** Time to wait between writing blocks/block index to disk. */
8684
static constexpr std::chrono::hours DATABASE_WRITE_INTERVAL{1};
8785
/** Time to wait between flushing chainstate to disk. */
@@ -2723,12 +2721,10 @@ bool Chainstate::DisconnectTip(BlockValidationState& state, DisconnectedBlockTra
27232721
}
27242722

27252723
if (disconnectpool && m_mempool) {
2726-
// Save transactions to re-add to mempool at end of reorg
2727-
disconnectpool->AddTransactionsFromBlock(block.vtx);
2728-
while (disconnectpool->DynamicMemoryUsage() > MAX_DISCONNECTED_TX_POOL_SIZE * 1000) {
2729-
// Drop the earliest entry, and remove its children from the mempool.
2730-
auto ptx = disconnectpool->take_first();
2731-
m_mempool->removeRecursive(*ptx, MemPoolRemovalReason::REORG);
2724+
// Save transactions to re-add to mempool at end of reorg. If any entries are evicted for
2725+
// exceeding memory limits, remove them and their descendants from the mempool.
2726+
for (auto&& evicted_tx : disconnectpool->AddTransactionsFromBlock(block.vtx)) {
2727+
m_mempool->removeRecursive(*evicted_tx, MemPoolRemovalReason::REORG);
27322728
}
27332729
}
27342730

@@ -2978,7 +2974,7 @@ bool Chainstate::ActivateBestChainStep(BlockValidationState& state, CBlockIndex*
29782974

29792975
// Disconnect active blocks which are no longer in the best chain.
29802976
bool fBlocksDisconnected = false;
2981-
DisconnectedBlockTransactions disconnectpool;
2977+
DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_SIZE * 1000};
29822978
while (m_chain.Tip() && m_chain.Tip() != pindexFork) {
29832979
if (!DisconnectTip(state, &disconnectpool)) {
29842980
// This is likely a fatal error, but keep the mempool consistent,
@@ -3312,7 +3308,7 @@ bool Chainstate::InvalidateBlock(BlockValidationState& state, CBlockIndex* pinde
33123308

33133309
// ActivateBestChain considers blocks already in m_chain
33143310
// unconditionally valid already, so force disconnect away from it.
3315-
DisconnectedBlockTransactions disconnectpool;
3311+
DisconnectedBlockTransactions disconnectpool{MAX_DISCONNECTED_TX_POOL_SIZE * 1000};
33163312
bool ret = DisconnectTip(state, &disconnectpool);
33173313
// DisconnectTip will add transactions to disconnectpool.
33183314
// Adjust the mempool to be consistent with the new tip, adding

0 commit comments

Comments
 (0)