-
Notifications
You must be signed in to change notification settings - Fork 38.8k
tests, bug fix: DisconnectedBlockTransactions rewrite followups #28530
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
81dfedd
29eb219
f4254e2
b2d0447
9b3da70
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
ismaelsadeeq marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| // Copyright (c) 2023 The Bitcoin Core developers | ||
| // Distributed under the MIT software license, see the accompanying | ||
| // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
|
|
||
| #include <kernel/disconnected_transactions.h> | ||
|
|
||
| #include <assert.h> | ||
| #include <core_memusage.h> | ||
| #include <memusage.h> | ||
| #include <primitives/transaction.h> | ||
| #include <util/hasher.h> | ||
|
|
||
| #include <memory> | ||
| #include <utility> | ||
|
|
||
| // It's almost certainly a logic bug if we don't clear out queuedTx before | ||
| // destruction, as we add to it while disconnecting blocks, and then we | ||
| // need to re-process remaining transactions to ensure mempool consistency. | ||
| // For now, assert() that we've emptied out this object on destruction. | ||
| // This assert() can always be removed if the reorg-processing code were | ||
| // to be refactored such that this assumption is no longer true (for | ||
| // instance if there was some other way we cleaned up the mempool after a | ||
| // reorg, besides draining this object). | ||
| DisconnectedBlockTransactions::~DisconnectedBlockTransactions() | ||
| { | ||
| assert(queuedTx.empty()); | ||
| assert(iters_by_txid.empty()); | ||
| assert(cachedInnerUsage == 0); | ||
| } | ||
|
|
||
| std::vector<CTransactionRef> DisconnectedBlockTransactions::LimitMemoryUsage() | ||
| { | ||
| std::vector<CTransactionRef> evicted; | ||
|
|
||
| while (!queuedTx.empty() && DynamicMemoryUsage() > m_max_mem_usage) { | ||
| evicted.emplace_back(queuedTx.front()); | ||
| cachedInnerUsage -= RecursiveDynamicUsage(queuedTx.front()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For future reference for b2d0447 Imagining before after So we account for
|
||
| iters_by_txid.erase(queuedTx.front()->GetHash()); | ||
| queuedTx.pop_front(); | ||
| } | ||
| return evicted; | ||
| } | ||
|
|
||
| size_t DisconnectedBlockTransactions::DynamicMemoryUsage() const | ||
| { | ||
| return cachedInnerUsage + memusage::DynamicUsage(iters_by_txid) + memusage::DynamicUsage(queuedTx); | ||
| } | ||
|
|
||
| [[nodiscard]] std::vector<CTransactionRef> DisconnectedBlockTransactions::AddTransactionsFromBlock(const std::vector<CTransactionRef>& vtx) | ||
| { | ||
| iters_by_txid.reserve(iters_by_txid.size() + vtx.size()); | ||
| for (auto block_it = vtx.rbegin(); block_it != vtx.rend(); ++block_it) { | ||
| auto it = queuedTx.insert(queuedTx.end(), *block_it); | ||
| auto [_, inserted] = iters_by_txid.emplace((*block_it)->GetHash(), it); | ||
| assert(inserted); // callers may never pass multiple transactions with the same txid | ||
| cachedInnerUsage += RecursiveDynamicUsage(*block_it); | ||
| } | ||
| return LimitMemoryUsage(); | ||
| } | ||
|
|
||
| void DisconnectedBlockTransactions::removeForBlock(const std::vector<CTransactionRef>& vtx) | ||
| { | ||
| // Short-circuit in the common case of a block being added to the tip | ||
| if (queuedTx.empty()) { | ||
| return; | ||
| } | ||
| for (const auto& tx : vtx) { | ||
| auto iter = iters_by_txid.find(tx->GetHash()); | ||
| if (iter != iters_by_txid.end()) { | ||
| auto list_iter = iter->second; | ||
| iters_by_txid.erase(iter); | ||
| cachedInnerUsage -= RecursiveDynamicUsage(*list_iter); | ||
| queuedTx.erase(list_iter); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| void DisconnectedBlockTransactions::clear() | ||
| { | ||
| cachedInnerUsage = 0; | ||
| iters_by_txid.clear(); | ||
| queuedTx.clear(); | ||
| } | ||
|
|
||
| std::list<CTransactionRef> DisconnectedBlockTransactions::take() | ||
| { | ||
| std::list<CTransactionRef> ret = std::move(queuedTx); | ||
| clear(); | ||
| return ret; | ||
| } | ||
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| // Copyright (c) 2023 The Bitcoin Core developers | ||
| // Distributed under the MIT software license, see the accompanying | ||
| // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||
| // | ||
| #include <boost/test/unit_test.hpp> | ||
| #include <core_memusage.h> | ||
| #include <kernel/disconnected_transactions.h> | ||
| #include <test/util/setup_common.h> | ||
|
|
||
| BOOST_FIXTURE_TEST_SUITE(disconnected_transactions, TestChain100Setup) | ||
|
|
||
| //! Tests that DisconnectedBlockTransactions limits its own memory properly | ||
| BOOST_AUTO_TEST_CASE(disconnectpool_memory_limits) | ||
| { | ||
| // Use the coinbase transactions from TestChain100Setup. It doesn't matter whether these | ||
| // transactions would realistically be in a block together, they just need distinct txids and | ||
| // uniform size for this test to work. | ||
| std::vector<CTransactionRef> block_vtx(m_coinbase_txns); | ||
| BOOST_CHECK_EQUAL(block_vtx.size(), 100); | ||
|
|
||
| // Roughly estimate sizes to sanity check that DisconnectedBlockTransactions::DynamicMemoryUsage | ||
| // is within an expected range. | ||
|
|
||
| // Overhead for the hashmap depends on number of buckets | ||
| std::unordered_map<uint256, CTransaction*, SaltedTxidHasher> temp_map; | ||
| temp_map.reserve(1); | ||
| const size_t MAP_1{memusage::DynamicUsage(temp_map)}; | ||
| temp_map.reserve(100); | ||
| const size_t MAP_100{memusage::DynamicUsage(temp_map)}; | ||
|
|
||
| const size_t TX_USAGE{RecursiveDynamicUsage(block_vtx.front())}; | ||
| for (const auto& tx : block_vtx) | ||
| BOOST_CHECK_EQUAL(RecursiveDynamicUsage(tx), TX_USAGE); | ||
|
|
||
| // Our overall formula is unordered map overhead + usage per entry. | ||
| // Implementations may vary, but we're trying to guess the usage of data structures. | ||
| const size_t ENTRY_USAGE_ESTIMATE{ | ||
| TX_USAGE | ||
| // list entry: 2 pointers (next pointer and prev pointer) + element itself | ||
| + memusage::MallocUsage((2 * sizeof(void*)) + sizeof(decltype(block_vtx)::value_type)) | ||
| // unordered map: 1 pointer for the hashtable + key and value | ||
| + memusage::MallocUsage(sizeof(void*) + sizeof(decltype(temp_map)::key_type) | ||
| + sizeof(decltype(temp_map)::value_type))}; | ||
|
|
||
| // DisconnectedBlockTransactions that's just big enough for 1 transaction. | ||
| { | ||
| DisconnectedBlockTransactions disconnectpool{MAP_1 + ENTRY_USAGE_ESTIMATE}; | ||
| // Add just 2 (and not all 100) transactions to keep the unordered map's hashtable overhead | ||
| // to a minimum and avoid all (instead of all but 1) transactions getting evicted. | ||
| std::vector<CTransactionRef> two_txns({block_vtx.at(0), block_vtx.at(1)}); | ||
| auto evicted_txns{disconnectpool.AddTransactionsFromBlock(two_txns)}; | ||
| BOOST_CHECK(disconnectpool.DynamicMemoryUsage() <= MAP_1 + ENTRY_USAGE_ESTIMATE); | ||
|
|
||
| // Only 1 transaction can be kept | ||
| BOOST_CHECK_EQUAL(1, evicted_txns.size()); | ||
| // Transactions are added from back to front and eviction is FIFO. | ||
| BOOST_CHECK_EQUAL(block_vtx.at(1), evicted_txns.front()); | ||
|
|
||
| disconnectpool.clear(); | ||
| } | ||
|
|
||
| // DisconnectedBlockTransactions with a comfortable maximum memory usage so that nothing is evicted. | ||
| // Record usage so we can check size limiting in the next test. | ||
| size_t usage_full{0}; | ||
| { | ||
| const size_t USAGE_100_OVERESTIMATE{MAP_100 + ENTRY_USAGE_ESTIMATE * 100}; | ||
| DisconnectedBlockTransactions disconnectpool{USAGE_100_OVERESTIMATE}; | ||
| auto evicted_txns{disconnectpool.AddTransactionsFromBlock(block_vtx)}; | ||
| BOOST_CHECK_EQUAL(evicted_txns.size(), 0); | ||
| BOOST_CHECK(disconnectpool.DynamicMemoryUsage() <= USAGE_100_OVERESTIMATE); | ||
|
|
||
| usage_full = disconnectpool.DynamicMemoryUsage(); | ||
|
|
||
| disconnectpool.clear(); | ||
| } | ||
|
|
||
| // DisconnectedBlockTransactions that's just a little too small for all of the transactions. | ||
| { | ||
| const size_t MAX_MEMUSAGE_99{usage_full - sizeof(void*)}; | ||
| DisconnectedBlockTransactions disconnectpool{MAX_MEMUSAGE_99}; | ||
| auto evicted_txns{disconnectpool.AddTransactionsFromBlock(block_vtx)}; | ||
| BOOST_CHECK(disconnectpool.DynamicMemoryUsage() <= MAX_MEMUSAGE_99); | ||
|
|
||
| // Only 1 transaction needed to be evicted | ||
| BOOST_CHECK_EQUAL(1, evicted_txns.size()); | ||
|
|
||
| // Transactions are added from back to front and eviction is FIFO. | ||
| // The last transaction of block_vtx should be the first to be evicted. | ||
| BOOST_CHECK_EQUAL(block_vtx.back(), evicted_txns.front()); | ||
ismaelsadeeq marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| disconnectpool.clear(); | ||
| } | ||
| } | ||
|
|
||
| BOOST_AUTO_TEST_SUITE_END() | ||
Uh oh!
There was an error while loading. Please reload this page.