Skip to content

Commit 02b9511

Browse files
committed
tests: add tests for GetCoinsCacheSizeState
1 parent b17e91d commit 02b9511

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

src/Makefile.test.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ BITCOIN_TESTS =\
182182
test/uint256_tests.cpp \
183183
test/util_tests.cpp \
184184
test/validation_block_tests.cpp \
185+
test/validation_flush_tests.cpp \
185186
test/versionbits_tests.cpp
186187

187188
if ENABLE_PROPERTY_TESTS

src/test/validation_flush_tests.cpp

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

0 commit comments

Comments
 (0)