disconnected_transactions.cpp raw

   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()
  96