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