|
| 1 | +// Copyright (c) 2023 The Bitcoin Core developers |
| 2 | +// Distributed under the MIT software license, see the accompanying |
| 3 | +// file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 4 | +// |
| 5 | +#include <boost/test/unit_test.hpp> |
| 6 | +#include <core_memusage.h> |
| 7 | +#include <kernel/disconnected_transactions.h> |
| 8 | +#include <test/util/setup_common.h> |
| 9 | + |
| 10 | +BOOST_FIXTURE_TEST_SUITE(disconnected_transactions, TestChain100Setup) |
| 11 | + |
| 12 | +//! Tests that DisconnectedBlockTransactions limits its own memory properly |
| 13 | +BOOST_AUTO_TEST_CASE(disconnectpool_memory_limits) |
| 14 | +{ |
| 15 | + // Use the coinbase transactions from TestChain100Setup. It doesn't matter whether these |
| 16 | + // transactions would realistically be in a block together, they just need distinct txids and |
| 17 | + // uniform size for this test to work. |
| 18 | + std::vector<CTransactionRef> block_vtx(m_coinbase_txns); |
| 19 | + BOOST_CHECK_EQUAL(block_vtx.size(), 100); |
| 20 | + |
| 21 | + // Roughly estimate sizes to sanity check that DisconnectedBlockTransactions::DynamicMemoryUsage |
| 22 | + // is within an expected range. |
| 23 | + |
| 24 | + // Overhead for the hashmap depends on number of buckets |
| 25 | + std::unordered_map<uint256, CTransaction*, SaltedTxidHasher> temp_map; |
| 26 | + temp_map.reserve(1); |
| 27 | + const size_t MAP_1{memusage::DynamicUsage(temp_map)}; |
| 28 | + temp_map.reserve(100); |
| 29 | + const size_t MAP_100{memusage::DynamicUsage(temp_map)}; |
| 30 | + |
| 31 | + const size_t TX_USAGE{RecursiveDynamicUsage(block_vtx.front())}; |
| 32 | + for (const auto& tx : block_vtx) |
| 33 | + BOOST_CHECK_EQUAL(RecursiveDynamicUsage(tx), TX_USAGE); |
| 34 | + |
| 35 | + // Our overall formula is unordered map overhead + usage per entry. |
| 36 | + // Implementations may vary, but we're trying to guess the usage of data structures. |
| 37 | + const size_t ENTRY_USAGE_ESTIMATE{ |
| 38 | + TX_USAGE |
| 39 | + // list entry: 2 pointers (next pointer and prev pointer) + element itself |
| 40 | + + memusage::MallocUsage((2 * sizeof(void*)) + sizeof(decltype(block_vtx)::value_type)) |
| 41 | + // unordered map: 1 pointer for the hashtable + key and value |
| 42 | + + memusage::MallocUsage(sizeof(void*) + sizeof(decltype(temp_map)::key_type) |
| 43 | + + sizeof(decltype(temp_map)::value_type))}; |
| 44 | + |
| 45 | + // DisconnectedBlockTransactions that's just big enough for 1 transaction. |
| 46 | + { |
| 47 | + DisconnectedBlockTransactions disconnectpool{MAP_1 + ENTRY_USAGE_ESTIMATE}; |
| 48 | + // Add just 2 (and not all 100) transactions to keep the unordered map's hashtable overhead |
| 49 | + // to a minimum and avoid all (instead of all but 1) transactions getting evicted. |
| 50 | + std::vector<CTransactionRef> two_txns({block_vtx.at(0), block_vtx.at(1)}); |
| 51 | + auto evicted_txns{disconnectpool.AddTransactionsFromBlock(two_txns)}; |
| 52 | + BOOST_CHECK(disconnectpool.DynamicMemoryUsage() <= MAP_1 + ENTRY_USAGE_ESTIMATE); |
| 53 | + |
| 54 | + // Only 1 transaction can be kept |
| 55 | + BOOST_CHECK_EQUAL(1, evicted_txns.size()); |
| 56 | + // Transactions are added from back to front and eviction is FIFO. |
| 57 | + BOOST_CHECK_EQUAL(block_vtx.at(1), evicted_txns.front()); |
| 58 | + |
| 59 | + disconnectpool.clear(); |
| 60 | + } |
| 61 | + |
| 62 | + // DisconnectedBlockTransactions with a comfortable maximum memory usage so that nothing is evicted. |
| 63 | + // Record usage so we can check size limiting in the next test. |
| 64 | + size_t usage_full{0}; |
| 65 | + { |
| 66 | + const size_t USAGE_100_OVERESTIMATE{MAP_100 + ENTRY_USAGE_ESTIMATE * 100}; |
| 67 | + DisconnectedBlockTransactions disconnectpool{USAGE_100_OVERESTIMATE}; |
| 68 | + auto evicted_txns{disconnectpool.AddTransactionsFromBlock(block_vtx)}; |
| 69 | + BOOST_CHECK_EQUAL(evicted_txns.size(), 0); |
| 70 | + BOOST_CHECK(disconnectpool.DynamicMemoryUsage() <= USAGE_100_OVERESTIMATE); |
| 71 | + |
| 72 | + usage_full = disconnectpool.DynamicMemoryUsage(); |
| 73 | + |
| 74 | + disconnectpool.clear(); |
| 75 | + } |
| 76 | + |
| 77 | + // DisconnectedBlockTransactions that's just a little too small for all of the transactions. |
| 78 | + { |
| 79 | + const size_t MAX_MEMUSAGE_99{usage_full - sizeof(void*)}; |
| 80 | + DisconnectedBlockTransactions disconnectpool{MAX_MEMUSAGE_99}; |
| 81 | + auto evicted_txns{disconnectpool.AddTransactionsFromBlock(block_vtx)}; |
| 82 | + BOOST_CHECK(disconnectpool.DynamicMemoryUsage() <= MAX_MEMUSAGE_99); |
| 83 | + |
| 84 | + // Only 1 transaction needed to be evicted |
| 85 | + BOOST_CHECK_EQUAL(1, evicted_txns.size()); |
| 86 | + |
| 87 | + // Transactions are added from back to front and eviction is FIFO. |
| 88 | + // The last transaction of block_vtx should be the first to be evicted. |
| 89 | + BOOST_CHECK_EQUAL(block_vtx.back(), evicted_txns.front()); |
| 90 | + |
| 91 | + disconnectpool.clear(); |
| 92 | + } |
| 93 | +} |
| 94 | + |
| 95 | +BOOST_AUTO_TEST_SUITE_END() |
0 commit comments