validation_flush_tests.cpp
1 // Copyright (c) 2019-2022 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 <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(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(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(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(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(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(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(InsecureRand256()); 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()