/ src / test / disconnected_transactions.cpp
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()