Line data Source code
1 : // Copyright (c) 2019-2020 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/setup_common.h> 7 : #include <txmempool.h> 8 : #include <validation.h> 9 : 10 : #include <boost/test/unit_test.hpp> 11 : 12 89 : BOOST_FIXTURE_TEST_SUITE(validation_flush_tests, BasicTestingSetup) 13 : 14 : //! Test utilities for detecting when we need to flush the coins cache based 15 : //! on estimated memory usage. 16 : //! 17 : //! @sa CChainState::GetCoinsCacheSizeState() 18 : //! 19 95 : BOOST_AUTO_TEST_CASE(getcoinscachesizestate) 20 : { 21 1 : CTxMemPool mempool; 22 1 : BlockManager blockman{}; 23 1 : CChainState chainstate{mempool, blockman}; 24 1 : chainstate.InitCoinsDB(/*cache_size_bytes*/ 1 << 10, /*in_memory*/ true, /*should_wipe*/ false); 25 2 : WITH_LOCK(::cs_main, chainstate.InitCoinsCache(1 << 10)); 26 1 : CTxMemPool tx_pool{}; 27 : 28 : constexpr bool is_64_bit = sizeof(void*) == 8; 29 : 30 1 : LOCK(::cs_main); 31 1 : auto& view = chainstate.CoinsTip(); 32 : 33 : //! Create and add a Coin with DynamicMemoryUsage of 80 bytes to the given view. 34 1001 : auto add_coin = [](CCoinsViewCache& coins_view) -> COutPoint { 35 1000 : Coin newcoin; 36 1000 : uint256 txid = InsecureRand256(); 37 1000 : COutPoint outp{txid, 0}; 38 1000 : newcoin.nHeight = 1; 39 1000 : newcoin.out.nValue = InsecureRand32(); 40 1000 : newcoin.out.scriptPubKey.assign((uint32_t)56, 1); 41 1000 : coins_view.AddCoin(outp, std::move(newcoin), false); 42 : 43 : return outp; 44 1000 : }; 45 : 46 : // The number of bytes consumed by coin's heap data, i.e. CScript 47 : // (prevector<28, unsigned char>) when assigned 56 bytes of data per above. 48 : // 49 : // See also: Coin::DynamicMemoryUsage(). 50 1 : constexpr unsigned int COIN_SIZE = is_64_bit ? 80 : 64; 51 : 52 1 : auto print_view_mem_usage = [](CCoinsViewCache& view) { 53 0 : BOOST_TEST_MESSAGE("CCoinsViewCache memory usage: " << view.DynamicMemoryUsage()); 54 0 : }; 55 : 56 : constexpr size_t MAX_COINS_CACHE_BYTES = 1024; 57 : 58 : // Without any coins in the cache, we shouldn't need to flush. 59 1 : BOOST_CHECK_EQUAL( 60 : chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), 61 : CoinsCacheSizeState::OK); 62 : 63 : // If the initial memory allocations of cacheCoins don't match these common 64 : // cases, we can't really continue to make assertions about memory usage. 65 : // End the test early. 66 1 : if (view.DynamicMemoryUsage() != 32 && view.DynamicMemoryUsage() != 16) { 67 : // Add a bunch of coins to see that we at least flip over to CRITICAL. 68 : 69 1001 : for (int i{0}; i < 1000; ++i) { 70 1000 : COutPoint res = add_coin(view); 71 1000 : BOOST_CHECK_EQUAL(view.AccessCoin(res).DynamicMemoryUsage(), COIN_SIZE); 72 1000 : } 73 : 74 1 : BOOST_CHECK_EQUAL( 75 : chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), 76 : CoinsCacheSizeState::CRITICAL); 77 : 78 1 : BOOST_TEST_MESSAGE("Exiting cache flush tests early due to unsupported arch"); 79 1 : return; 80 : } 81 : 82 0 : print_view_mem_usage(view); 83 0 : BOOST_CHECK_EQUAL(view.DynamicMemoryUsage(), is_64_bit ? 32U : 16U); 84 : 85 : // We should be able to add COINS_UNTIL_CRITICAL coins to the cache before going CRITICAL. 86 : // This is contingent not only on the dynamic memory usage of the Coins 87 : // that we're adding (COIN_SIZE bytes per), but also on how much memory the 88 : // cacheCoins (unordered_map) preallocates. 89 : constexpr int COINS_UNTIL_CRITICAL{3}; 90 : 91 0 : for (int i{0}; i < COINS_UNTIL_CRITICAL; ++i) { 92 0 : COutPoint res = add_coin(view); 93 0 : print_view_mem_usage(view); 94 0 : BOOST_CHECK_EQUAL(view.AccessCoin(res).DynamicMemoryUsage(), COIN_SIZE); 95 0 : BOOST_CHECK_EQUAL( 96 : chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), 97 : CoinsCacheSizeState::OK); 98 0 : } 99 : 100 : // Adding some additional coins will push us over the edge to CRITICAL. 101 0 : for (int i{0}; i < 4; ++i) { 102 0 : add_coin(view); 103 0 : print_view_mem_usage(view); 104 0 : if (chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0) == 105 : CoinsCacheSizeState::CRITICAL) { 106 0 : break; 107 : } 108 : } 109 : 110 0 : BOOST_CHECK_EQUAL( 111 : chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 0), 112 : CoinsCacheSizeState::CRITICAL); 113 : 114 : // Passing non-zero max mempool usage should allow us more headroom. 115 0 : BOOST_CHECK_EQUAL( 116 : chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 1 << 10), 117 : CoinsCacheSizeState::OK); 118 : 119 0 : for (int i{0}; i < 3; ++i) { 120 0 : add_coin(view); 121 0 : print_view_mem_usage(view); 122 0 : BOOST_CHECK_EQUAL( 123 : chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, /*max_mempool_size_bytes*/ 1 << 10), 124 : CoinsCacheSizeState::OK); 125 : } 126 : 127 : // Adding another coin with the additional mempool room will put us >90% 128 : // but not yet critical. 129 0 : add_coin(view); 130 0 : print_view_mem_usage(view); 131 : 132 : // Only perform these checks on 64 bit hosts; I haven't done the math for 32. 133 : if (is_64_bit) { 134 0 : float usage_percentage = (float)view.DynamicMemoryUsage() / (MAX_COINS_CACHE_BYTES + (1 << 10)); 135 0 : BOOST_TEST_MESSAGE("CoinsTip usage percentage: " << usage_percentage); 136 0 : BOOST_CHECK(usage_percentage >= 0.9); 137 0 : BOOST_CHECK(usage_percentage < 1); 138 0 : BOOST_CHECK_EQUAL( 139 : chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, 1 << 10), 140 : CoinsCacheSizeState::LARGE); 141 0 : } 142 : 143 : // Using the default max_* values permits way more coins to be added. 144 0 : for (int i{0}; i < 1000; ++i) { 145 0 : add_coin(view); 146 0 : BOOST_CHECK_EQUAL( 147 : chainstate.GetCoinsCacheSizeState(&tx_pool), 148 : CoinsCacheSizeState::OK); 149 : } 150 : 151 : // Flushing the view doesn't take us back to OK because cacheCoins has 152 : // preallocated memory that doesn't get reclaimed even after flush. 153 : 154 0 : BOOST_CHECK_EQUAL( 155 : chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, 0), 156 : CoinsCacheSizeState::CRITICAL); 157 : 158 0 : view.SetBestBlock(InsecureRand256()); 159 0 : BOOST_CHECK(view.Flush()); 160 0 : print_view_mem_usage(view); 161 : 162 0 : BOOST_CHECK_EQUAL( 163 : chainstate.GetCoinsCacheSizeState(&tx_pool, MAX_COINS_CACHE_BYTES, 0), 164 : CoinsCacheSizeState::CRITICAL); 165 1 : } 166 : 167 89 : BOOST_AUTO_TEST_SUITE_END()