disconnected_transactions.cpp
1 // Copyright (c) 2023-present 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_AUTO_TEST_SUITE(disconnected_transactions) 11 12 //! Tests that DisconnectedBlockTransactions limits its own memory properly 13 BOOST_FIXTURE_TEST_CASE(disconnectpool_memory_limits, TestChain100Setup) 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<Txid, 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()