validation_flush_tests.cpp raw

   1  // Copyright (c) 2019-2022 The Limenka 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 <sync.h>
   6  #include <test/util/coins.h>
   7  #include <test/util/random.h>
   8  #include <test/util/setup_common.h>
   9  #include <validation.h>
  10  
  11  #include <boost/test/unit_test.hpp>
  12  
  13  BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, TestingSetup)
  14  
  15  //! Test utilities for detecting when we need to flush the coins cache based
  16  //! on estimated memory usage.
  17  //!
  18  //! @sa Chainstate::GetCoinsCacheSizeState()
  19  //!
  20  BOOST_AUTO_TEST_CASE(getcoinscachesizestate)
  21  {
  22      Chainstate& chainstate{m_node.chainman->ActiveChainstate()};
  23  
  24      constexpr bool is_64_bit = sizeof(void*) == 8;
  25  
  26      LOCK(::cs_main);
  27      auto& view = chainstate.CoinsTip();
  28  
  29      // The number of bytes consumed by coin's heap data, i.e. CScript
  30      // (prevector<28, unsigned char>) when assigned 56 bytes of data per above.
  31      //
  32      // See also: Coin::DynamicMemoryUsage().
  33      constexpr unsigned int COIN_SIZE = is_64_bit ? 80 : 64;
  34  
  35      auto print_view_mem_usage = [](CCoinsViewCache& view) {
  36          BOOST_TEST_MESSAGE("CCoinsViewCache memory usage: " << view.DynamicMemoryUsage());
  37      };
  38  
  39      // PoolResource defaults to 256 KiB that will be allocated, so we'll take that and make it a bit larger.
  40      constexpr size_t MAX_COINS_CACHE_BYTES = 262144 + 512;
  41  
  42      // Without any coins in the cache, we shouldn't need to flush.
  43      BOOST_TEST(
  44          chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes=*/ 0) != CoinsCacheSizeState::CRITICAL);
  45  
  46      // If the initial memory allocations of cacheCoins don't match these common
  47      // cases, we can't really continue to make assertions about memory usage.
  48      // End the test early.
  49      if (view.DynamicMemoryUsage() != 32 && view.DynamicMemoryUsage() != 16) {
  50          // Add a bunch of coins to see that we at least flip over to CRITICAL.
  51  
  52          for (int i{0}; i < 1000; ++i) {
  53              const COutPoint res = AddTestCoin(m_rng, view);
  54              BOOST_CHECK_EQUAL(view.AccessCoin(res).DynamicMemoryUsage(), COIN_SIZE);
  55          }
  56  
  57          BOOST_CHECK_EQUAL(
  58              chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes=*/0),
  59              CoinsCacheSizeState::CRITICAL);
  60  
  61          BOOST_TEST_MESSAGE("Exiting cache flush tests early due to unsupported arch");
  62          return;
  63      }
  64  
  65      print_view_mem_usage(view);
  66      BOOST_CHECK_EQUAL(view.DynamicMemoryUsage(), is_64_bit ? 32U : 16U);
  67  
  68      // We should be able to add COINS_UNTIL_CRITICAL coins to the cache before going CRITICAL.
  69      // This is contingent not only on the dynamic memory usage of the Coins
  70      // that we're adding (COIN_SIZE bytes per), but also on how much memory the
  71      // cacheCoins (unordered_map) preallocates.
  72      constexpr int COINS_UNTIL_CRITICAL{3};
  73  
  74      // no coin added, so we have plenty of space left.
  75      BOOST_CHECK_EQUAL(
  76          chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0),
  77          CoinsCacheSizeState::OK);
  78  
  79      for (int i{0}; i < COINS_UNTIL_CRITICAL; ++i) {
  80          const COutPoint res = AddTestCoin(m_rng, view);
  81          print_view_mem_usage(view);
  82          BOOST_CHECK_EQUAL(view.AccessCoin(res).DynamicMemoryUsage(), COIN_SIZE);
  83  
  84          // adding first coin causes the MemoryResource to allocate one 256 KiB chunk of memory,
  85          // pushing us immediately over to LARGE
  86          BOOST_CHECK_EQUAL(
  87              chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes=*/ 0),
  88              CoinsCacheSizeState::LARGE);
  89      }
  90  
  91      // Adding some additional coins will push us over the edge to CRITICAL.
  92      for (int i{0}; i < 4; ++i) {
  93          AddTestCoin(m_rng, view);
  94          print_view_mem_usage(view);
  95          if (chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes=*/0) ==
  96              CoinsCacheSizeState::CRITICAL) {
  97              break;
  98          }
  99      }
 100  
 101      BOOST_CHECK_EQUAL(
 102          chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes=*/0),
 103          CoinsCacheSizeState::CRITICAL);
 104  
 105      // Passing non-zero max mempool usage (512 KiB) should allow us more headroom.
 106      BOOST_CHECK_EQUAL(
 107          chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes=*/ 1 << 19),
 108          CoinsCacheSizeState::OK);
 109  
 110      for (int i{0}; i < 3; ++i) {
 111          AddTestCoin(m_rng, view);
 112          print_view_mem_usage(view);
 113          BOOST_CHECK_EQUAL(
 114              chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes=*/ 1 << 19),
 115              CoinsCacheSizeState::OK);
 116      }
 117  
 118      // Adding another coin with the additional mempool room will put us >90%
 119      // but not yet critical.
 120      AddTestCoin(m_rng, view);
 121      print_view_mem_usage(view);
 122  
 123      // Only perform these checks on 64 bit hosts; I haven't done the math for 32.
 124      if (is_64_bit) {
 125          float usage_percentage = (float)view.DynamicMemoryUsage() / (MAX_COINS_CACHE_BYTES + (1 << 10));
 126          BOOST_TEST_MESSAGE("CoinsTip usage percentage: " << usage_percentage);
 127          BOOST_CHECK(usage_percentage >= 0.9);
 128          BOOST_CHECK(usage_percentage < 1);
 129          BOOST_CHECK_EQUAL(
 130              chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 1 << 10), // 1024
 131              CoinsCacheSizeState::LARGE);
 132      }
 133  
 134      // Using the default max_* values permits way more coins to be added.
 135      for (int i{0}; i < 1000; ++i) {
 136          AddTestCoin(m_rng, view);
 137          BOOST_CHECK_EQUAL(
 138              chainstate.GetCoinsCacheSizeState(),
 139              CoinsCacheSizeState::OK);
 140      }
 141  
 142      // Flushing the view does take us back to OK because ReallocateCache() is called
 143  
 144      BOOST_CHECK_EQUAL(
 145          chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, 0),
 146          CoinsCacheSizeState::CRITICAL);
 147  
 148      view.SetBestBlock(m_rng.rand256());
 149      BOOST_CHECK(view.Flush());
 150      print_view_mem_usage(view);
 151  
 152      BOOST_CHECK_EQUAL(
 153          chainstate.GetCoinsCacheSizeState(MAX_COINS_CACHE_BYTES, 0),
 154          CoinsCacheSizeState::OK);
 155  }
 156  
 157  BOOST_AUTO_TEST_SUITE_END()
 158