Branch data Line data Source code
1 : : // Copyright (c) 2023-present 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 <coins.h>
6 : : #include <crypto/sha256.h>
7 : : #include <kernel/chainstatemanager_opts.h>
8 : : #include <primitives/block.h>
9 : : #include <primitives/transaction.h>
10 : : #include <test/fuzz/FuzzedDataProvider.h>
11 : : #include <test/fuzz/fuzz.h>
12 : : #include <test/fuzz/util.h>
13 : : #include <test/util/setup_common.h>
14 : : #include <util/threadpool.h>
15 : :
16 : : #include <cassert>
17 : : #include <cstdint>
18 : : #include <memory>
19 : : #include <optional>
20 : : #include <vector>
21 : :
22 : : namespace {
23 : :
24 : : /** Number of distinct COutPoint values used in this test. */
25 : : constexpr uint32_t NUM_OUTPOINTS = 256;
26 : : /** Number of distinct Coin values used in this test (ignoring nHeight). */
27 : : constexpr uint32_t NUM_COINS = 256;
28 : : /** Maximum number CCoinsViewCache objects used in this test. */
29 : : constexpr uint32_t MAX_CACHES = 4;
30 : : /** Data type large enough to hold NUM_COINS-1. */
31 : : using coinidx_type = uint8_t;
32 : :
33 : : struct PrecomputedData
34 : : {
35 : : //! Randomly generated COutPoint values.
36 : : COutPoint outpoints[NUM_OUTPOINTS];
37 : :
38 : : //! Randomly generated Coin values.
39 : : Coin coins[NUM_COINS];
40 : :
41 : : //! Block with a tx containing as inputs the above outpoints.
42 : : CBlock block;
43 : :
44 : 1 : PrecomputedData()
45 [ + + + + ]: 513 : {
46 : 1 : static const uint8_t PREFIX_O[1] = {'o'}; /** Hash prefix for outpoint hashes. */
47 : 1 : static const uint8_t PREFIX_S[1] = {'s'}; /** Hash prefix for coins scriptPubKeys. */
48 : 1 : static const uint8_t PREFIX_M[1] = {'m'}; /** Hash prefix for coins nValue/fCoinBase. */
49 : :
50 [ + - ]: 1 : CMutableTransaction coinbase;
51 [ + - ]: 1 : coinbase.vin.emplace_back();
52 [ + - + - : 2 : block.vtx.push_back(MakeTransactionRef(coinbase));
- + ]
53 : :
54 [ + - ]: 1 : CMutableTransaction tx;
55 [ + + ]: 257 : for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {
56 : 256 : uint32_t idx = (i * 1200U) >> 12; /* Map 3 or 4 entries to same txid. */
57 : 256 : const uint8_t ser[4] = {uint8_t(idx), uint8_t(idx >> 8), uint8_t(idx >> 16), uint8_t(idx >> 24)};
58 : 256 : uint256 txid;
59 [ + - + - : 256 : CSHA256().Write(PREFIX_O, 1).Write(ser, sizeof(ser)).Finalize(txid.begin());
+ - + - ]
60 [ + - ]: 256 : outpoints[i].hash = Txid::FromUint256(txid);
61 : 256 : outpoints[i].n = i;
62 [ + - ]: 256 : tx.vin.emplace_back(outpoints[i]);
63 : : }
64 [ + - + - : 2 : block.vtx.push_back(MakeTransactionRef(tx));
- + ]
65 : :
66 [ + + ]: 257 : for (uint32_t i = 0; i < NUM_COINS; ++i) {
67 : 256 : const uint8_t ser[4] = {uint8_t(i), uint8_t(i >> 8), uint8_t(i >> 16), uint8_t(i >> 24)};
68 : 256 : uint256 hash;
69 [ + - + - : 256 : CSHA256().Write(PREFIX_S, 1).Write(ser, sizeof(ser)).Finalize(hash.begin());
+ - + - ]
70 : : /* Convert hash to scriptPubkeys (of different lengths, so SanityCheck's cached memory
71 : : * usage check has a chance to detect mismatches). */
72 [ + + + + : 256 : switch (i % 5U) {
+ - ]
73 : 52 : case 0: /* P2PKH */
74 : 52 : coins[i].out.scriptPubKey.resize(25);
75 [ + - ]: 52 : coins[i].out.scriptPubKey[0] = OP_DUP;
76 [ + - ]: 52 : coins[i].out.scriptPubKey[1] = OP_HASH160;
77 [ + - ]: 52 : coins[i].out.scriptPubKey[2] = 20;
78 [ + - ]: 104 : std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 3);
79 [ + - ]: 52 : coins[i].out.scriptPubKey[23] = OP_EQUALVERIFY;
80 [ + - ]: 52 : coins[i].out.scriptPubKey[24] = OP_CHECKSIG;
81 : 52 : break;
82 : 51 : case 1: /* P2SH */
83 : 51 : coins[i].out.scriptPubKey.resize(23);
84 [ + - ]: 51 : coins[i].out.scriptPubKey[0] = OP_HASH160;
85 [ + - ]: 51 : coins[i].out.scriptPubKey[1] = 20;
86 [ + - ]: 102 : std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);
87 [ + - ]: 51 : coins[i].out.scriptPubKey[22] = OP_EQUAL;
88 : 51 : break;
89 : 51 : case 2: /* P2WPKH */
90 : 51 : coins[i].out.scriptPubKey.resize(22);
91 [ + - ]: 51 : coins[i].out.scriptPubKey[0] = OP_0;
92 [ + - ]: 51 : coins[i].out.scriptPubKey[1] = 20;
93 [ + - ]: 102 : std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);
94 : : break;
95 : 51 : case 3: /* P2WSH */
96 : 51 : coins[i].out.scriptPubKey.resize(34);
97 [ + - ]: 51 : coins[i].out.scriptPubKey[0] = OP_0;
98 [ + - ]: 51 : coins[i].out.scriptPubKey[1] = 32;
99 [ + - ]: 102 : std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2);
100 : : break;
101 : 51 : case 4: /* P2TR */
102 : 51 : coins[i].out.scriptPubKey.resize(34);
103 [ + - ]: 51 : coins[i].out.scriptPubKey[0] = OP_1;
104 [ + - ]: 51 : coins[i].out.scriptPubKey[1] = 32;
105 [ + - ]: 102 : std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2);
106 : : break;
107 : : }
108 : : /* Hash again to construct nValue and fCoinBase. */
109 [ + - + - : 256 : CSHA256().Write(PREFIX_M, 1).Write(ser, sizeof(ser)).Finalize(hash.begin());
+ - + - ]
110 : 256 : coins[i].out.nValue = CAmount(hash.GetUint64(0) % MAX_MONEY);
111 : 256 : coins[i].fCoinBase = (hash.GetUint64(1) & 7) == 0;
112 : 256 : coins[i].nHeight = 0; /* Real nHeight used in simulation is set dynamically. */
113 : : }
114 [ - - ]: 2 : }
115 : : };
116 : :
117 : : enum class EntryType : uint8_t
118 : : {
119 : : /* This entry in the cache does not exist (so we'd have to look in the parent cache). */
120 : : NONE,
121 : :
122 : : /* This entry in the cache corresponds to an unspent coin. */
123 : : UNSPENT,
124 : :
125 : : /* This entry in the cache corresponds to a spent coin. */
126 : : SPENT,
127 : : };
128 : :
129 : : struct CacheEntry
130 : : {
131 : : /* Type of entry. */
132 : : EntryType entrytype;
133 : :
134 : : /* Index in the coins array this entry corresponds to (only if entrytype == UNSPENT). */
135 : : coinidx_type coinidx;
136 : :
137 : : /* nHeight value for this entry (so the coins[coinidx].nHeight value is ignored; only if entrytype == UNSPENT). */
138 : : uint32_t height;
139 : : };
140 : :
141 : : struct CacheLevel
142 : : {
143 : : CacheEntry entry[NUM_OUTPOINTS];
144 : :
145 : 223391 : void Wipe() {
146 [ + + + + : 57411487 : for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {
+ + + + ]
147 : 57188096 : entry[i].entrytype = EntryType::NONE;
148 : : }
149 : : }
150 : : };
151 : :
152 : : /** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB).
153 : : *
154 : : * The initial state consists of the empty UTXO set.
155 : : */
156 : 552 : class CoinsViewBottom final : public CoinsViewEmpty
157 : : {
158 : : std::map<COutPoint, Coin> m_data;
159 : :
160 : : public:
161 : 22506503 : std::optional<Coin> GetCoin(const COutPoint& outpoint) const final
162 : : {
163 [ + + ]: 22506503 : if (auto it{m_data.find(outpoint)}; it != m_data.end()) {
164 [ - + ]: 792781 : assert(!it->second.IsSpent());
165 : 792781 : return it->second;
166 : : }
167 : 21713722 : return std::nullopt;
168 : : }
169 : :
170 : 54555 : void BatchWrite(CoinsViewCacheCursor& cursor, const uint256&) final
171 : : {
172 [ + + ]: 129307 : for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {
173 [ + - ]: 74752 : if (it->second.IsDirty()) {
174 [ + + ]: 74752 : if (it->second.coin.IsSpent()) {
175 : 17718 : m_data.erase(it->first);
176 : : } else {
177 [ + + ]: 57034 : if (cursor.WillErase(*it)) {
178 : 16609 : m_data[it->first] = std::move(it->second.coin);
179 : : } else {
180 : 40425 : m_data[it->first] = it->second.coin;
181 : : }
182 : : }
183 : : } else {
184 : : /* For non-dirty entries being written, compare them with what we have. */
185 : 0 : auto it2 = m_data.find(it->first);
186 [ # # ]: 0 : if (it->second.coin.IsSpent()) {
187 [ # # ]: 0 : assert(it2 == m_data.end());
188 : : } else {
189 [ # # ]: 0 : assert(it2 != m_data.end());
190 [ # # ]: 0 : assert(it->second.coin.out == it2->second.out);
191 [ # # ]: 0 : assert(it->second.coin.fCoinBase == it2->second.fCoinBase);
192 [ # # ]: 0 : assert(it->second.coin.nHeight == it2->second.nHeight);
193 : : }
194 : : }
195 : : }
196 : 54555 : }
197 : : };
198 : :
199 : : // Hold a non-movable ResetGuard on the heap so StartFetching can remain active
200 : : // for the lifetime of a CoinsViewOverlay cache level.
201 : 132089 : struct OverlayFetchScope
202 : : {
203 : : CCoinsViewCache::ResetGuard guard;
204 : 132089 : OverlayFetchScope(CoinsViewOverlay& view, const CBlock& block) : guard(view.StartFetching(block)) {}
205 : : };
206 : :
207 : : // Reuse a single global thread pool across fuzz iterations. Creating and destroying a pool every
208 : : // iteration leaks memory, since iterations can run faster than the OS can tear down the threads.
209 : : util::NotNull g_thread_pool{std::make_shared<ThreadPool>("cache_fuzz")};
210 : :
211 : 552 : void StartPoolIfNeeded()
212 : : {
213 [ + + ]: 552 : if (!g_thread_pool->WorkersCount()) g_thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
214 : 552 : }
215 : :
216 : : } // namespace
217 : :
218 [ + - ][ + - : 478 : FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
+ - + - ]
219 : : {
220 : 552 : SeedRandomStateForTest(SeedRand::ZEROS);
221 : 552 : StartPoolIfNeeded();
222 : : /** Precomputed COutPoint and CCoins values. */
223 [ + + + - : 553 : static const PrecomputedData data;
+ - ]
224 : :
225 : : /** Dummy coinsview instance (base of the hierarchy). */
226 : 552 : CoinsViewBottom bottom;
227 : : /** Real CCoinsViewCache objects. */
228 : 552 : std::vector<std::unique_ptr<CCoinsViewCache>> caches;
229 : : /** Long-lived StartFetching guards, parallel to `caches` (entries are nullptr unless corresponding level is a CoinsViewOverlay). */
230 : 552 : std::vector<std::unique_ptr<OverlayFetchScope>> fetch_scopes;
231 : : /** Simulated cache data (sim_caches[0] matches bottom, sim_caches[i+1] matches caches[i]). */
232 : 552 : CacheLevel sim_caches[MAX_CACHES + 1];
233 : : /** Current height in the simulation. */
234 : 552 : uint32_t current_height = 1U;
235 : :
236 : : // Initialize bottom simulated cache.
237 : 552 : sim_caches[0].Wipe();
238 : :
239 : : /** Helper lookup function in the simulated cache stack. */
240 : 810206 : auto lookup = [&](uint32_t outpointidx, int sim_idx = -1) -> std::optional<std::pair<coinidx_type, uint32_t>> {
241 [ + + - + ]: 809654 : uint32_t cache_idx = sim_idx == -1 ? caches.size() : sim_idx;
242 : 3328284 : while (true) {
243 : 2068969 : const auto& entry = sim_caches[cache_idx].entry[outpointidx];
244 [ + + ]: 2068969 : if (entry.entrytype == EntryType::UNSPENT) {
245 : 214191 : return {{entry.coinidx, entry.height}};
246 [ + + ]: 1854778 : } else if (entry.entrytype == EntryType::SPENT) {
247 : 111609 : return std::nullopt;
248 : 1743169 : };
249 [ + + ]: 1743169 : if (cache_idx == 0) break;
250 : 1259315 : --cache_idx;
251 : 1259315 : }
252 : 483854 : return std::nullopt;
253 : 552 : };
254 : :
255 : : /** Flush changes in top cache to the one below. */
256 : 119129 : auto flush = [&]() {
257 [ - + - + ]: 118577 : assert(caches.size() >= 1);
258 : 118577 : auto& cache = sim_caches[caches.size()];
259 : 118577 : auto& prev_cache = sim_caches[caches.size() - 1];
260 [ + + ]: 30474289 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
261 [ + + ]: 30355712 : if (cache.entry[outpointidx].entrytype != EntryType::NONE) {
262 : 256876 : prev_cache.entry[outpointidx] = cache.entry[outpointidx];
263 : 256876 : cache.entry[outpointidx].entrytype = EntryType::NONE;
264 : : }
265 : : }
266 : 119129 : };
267 : :
268 : : /** Helper creating a fetch scope for the top cache (which must be a CoinsViewOverlay). */
269 : 132641 : const auto make_fetch_scope{[&] {
270 : 132089 : auto& overlay{static_cast<CoinsViewOverlay&>(*caches.back())};
271 : 132089 : return std::make_unique<OverlayFetchScope>(overlay, data.block);
272 : 552 : }};
273 : :
274 : : // Main simulation loop: read commands from the fuzzer input, and apply them
275 : : // to both the real cache stack and the simulation.
276 : 552 : FuzzedDataProvider provider(buffer.data(), buffer.size());
277 [ + + + + ]: 1382797 : LIMITED_WHILE (provider.remaining_bytes(), 10000) {
278 : : // Every operation (except "Change height") moves current height forward,
279 : : // so it functions as a kind of epoch, making ~all UTXOs unique.
280 : 1382245 : ++current_height;
281 : : // Make sure there is always at least one CCoinsViewCache.
282 [ + + ]: 1382245 : if (caches.empty()) {
283 [ + - + - : 24171 : caches.emplace_back(new CCoinsViewCache(&bottom, /*deterministic=*/true));
+ - - - ]
284 [ + - ]: 24171 : fetch_scopes.emplace_back();
285 [ - + ]: 24171 : sim_caches[caches.size()].Wipe();
286 : : }
287 [ - + - + : 1382245 : assert(caches.size() == fetch_scopes.size());
- + ]
288 : :
289 : : // Execute command.
290 [ + - ]: 1382245 : CallOneOf(
291 : : provider,
292 : :
293 : 97358 : [&]() { // PeekCoin/GetCoin
294 : 97358 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
295 : : // Look up in simulation data.
296 : 97358 : auto sim = lookup(outpointidx);
297 : : // Look up in real caches.
298 [ + + ]: 97358 : auto realcoin = provider.ConsumeBool() ?
299 : 46748 : caches.back()->PeekCoin(data.outpoints[outpointidx]) :
300 : 97358 : caches.back()->GetCoin(data.outpoints[outpointidx]);
301 : : // Compare results.
302 [ + + ]: 97358 : if (!sim.has_value()) {
303 [ - + ]: 66512 : assert(!realcoin);
304 : : } else {
305 [ + - - + ]: 30846 : assert(realcoin && !realcoin->IsSpent());
306 : 30846 : const auto& simcoin = data.coins[sim->first];
307 [ - + ]: 30846 : assert(realcoin->out == simcoin.out);
308 [ - + ]: 30846 : assert(realcoin->fCoinBase == simcoin.fCoinBase);
309 [ - + ]: 30846 : assert(realcoin->nHeight == sim->second);
310 : : }
311 : 97358 : },
312 : :
313 : 40927 : [&]() { // HaveCoin
314 : 40927 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
315 : : // Look up in simulation data.
316 : 40927 : auto sim = lookup(outpointidx);
317 : : // Look up in real caches.
318 : 40927 : auto real = caches.back()->HaveCoin(data.outpoints[outpointidx]);
319 : : // Compare results.
320 [ - + ]: 40927 : assert(sim.has_value() == real);
321 : 40927 : },
322 : :
323 : 38675 : [&]() { // HaveCoinInCache
324 : 38675 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
325 : : // Invoke on real cache (there is no equivalent in simulation, so nothing to compare result with).
326 : 38675 : (void)caches.back()->HaveCoinInCache(data.outpoints[outpointidx]);
327 : 38675 : },
328 : :
329 : 39283 : [&]() { // AccessCoin
330 : 39283 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
331 : : // Look up in simulation data.
332 : 39283 : auto sim = lookup(outpointidx);
333 : : // Look up in real caches.
334 : 39283 : const auto& realcoin = caches.back()->AccessCoin(data.outpoints[outpointidx]);
335 : : // Compare results.
336 [ + + ]: 39283 : if (!sim.has_value()) {
337 [ - + ]: 28046 : assert(realcoin.IsSpent());
338 : : } else {
339 [ - + ]: 11237 : assert(!realcoin.IsSpent());
340 : 11237 : const auto& simcoin = data.coins[sim->first];
341 [ - + ]: 11237 : assert(simcoin.out == realcoin.out);
342 [ - + ]: 11237 : assert(simcoin.fCoinBase == realcoin.fCoinBase);
343 [ - + ]: 11237 : assert(realcoin.nHeight == sim->second);
344 : : }
345 : 39283 : },
346 : :
347 : 131752 : [&]() { // AddCoin (only possible_overwrite if necessary)
348 : 131752 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
349 : 131752 : uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1);
350 : : // Look up in simulation data (to know whether we must set possible_overwrite or not).
351 : 131752 : auto sim = lookup(outpointidx);
352 : : // Invoke on real caches.
353 : 131752 : Coin coin = data.coins[coinidx];
354 : 131752 : coin.nHeight = current_height;
355 [ + - ]: 131752 : caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), sim.has_value());
356 : : // Apply to simulation data.
357 [ - + ]: 131752 : auto& entry = sim_caches[caches.size()].entry[outpointidx];
358 : 131752 : entry.entrytype = EntryType::UNSPENT;
359 : 131752 : entry.coinidx = coinidx;
360 : 131752 : entry.height = current_height;
361 : 131752 : },
362 : :
363 : 152983 : [&]() { // AddCoin (always possible_overwrite)
364 : 152983 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
365 : 152983 : uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1);
366 : : // Invoke on real caches.
367 : 152983 : Coin coin = data.coins[coinidx];
368 : 152983 : coin.nHeight = current_height;
369 [ + - ]: 152983 : caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), true);
370 : : // Apply to simulation data.
371 [ - + ]: 152983 : auto& entry = sim_caches[caches.size()].entry[outpointidx];
372 : 152983 : entry.entrytype = EntryType::UNSPENT;
373 : 152983 : entry.coinidx = coinidx;
374 : 152983 : entry.height = current_height;
375 : 152983 : },
376 : :
377 : 71658 : [&]() { // SpendCoin (moveto = nullptr)
378 : 71658 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
379 : : // Invoke on real caches.
380 : 71658 : caches.back()->SpendCoin(data.outpoints[outpointidx], nullptr);
381 : : // Apply to simulation data.
382 [ - + ]: 71658 : sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
383 : 71658 : },
384 : :
385 : 81774 : [&]() { // SpendCoin (with moveto)
386 : 81774 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
387 : : // Look up in simulation data (to compare the returned *moveto with).
388 : 81774 : auto sim = lookup(outpointidx);
389 : : // Invoke on real caches.
390 : 81774 : Coin realcoin;
391 [ + - ]: 81774 : caches.back()->SpendCoin(data.outpoints[outpointidx], &realcoin);
392 : : // Apply to simulation data.
393 [ - + ]: 81774 : sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
394 : : // Compare *moveto with the value expected based on simulation data.
395 [ + + ]: 81774 : if (!sim.has_value()) {
396 [ - + ]: 40864 : assert(realcoin.IsSpent());
397 : : } else {
398 [ - + ]: 40910 : assert(!realcoin.IsSpent());
399 : 40910 : const auto& simcoin = data.coins[sim->first];
400 [ - + ]: 40910 : assert(simcoin.out == realcoin.out);
401 [ - + ]: 40910 : assert(simcoin.fCoinBase == realcoin.fCoinBase);
402 [ - + ]: 40910 : assert(realcoin.nHeight == sim->second);
403 : : }
404 : 81774 : },
405 : :
406 : 38504 : [&]() { // Uncache
407 : 38504 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
408 : : // Apply to real caches (there is no equivalent in our simulation).
409 : 38504 : caches.back()->Uncache(data.outpoints[outpointidx]);
410 : 38504 : },
411 : :
412 : 174889 : [&]() { // Add a cache level (if not already at the max).
413 [ - + + + ]: 174889 : if (caches.size() != MAX_CACHES) {
414 : : // Apply to real caches.
415 [ + + ]: 126760 : if (provider.ConsumeBool()) {
416 [ + - ]: 14909 : caches.emplace_back(new CCoinsViewCache(&*caches.back(), /*deterministic=*/true));
417 : 14909 : fetch_scopes.emplace_back();
418 : : } else {
419 [ + - + - ]: 223702 : caches.emplace_back(new CoinsViewOverlay(&*caches.back(), g_thread_pool, /*deterministic=*/true));
420 [ + - ]: 111851 : fetch_scopes.emplace_back(make_fetch_scope());
421 : : }
422 : : // Apply to simulation data.
423 [ - + ]: 126760 : sim_caches[caches.size()].Wipe();
424 : : }
425 : 174889 : },
426 : :
427 : 149848 : [&]() { // Remove a cache level.
428 : : // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data).
429 : 149848 : caches.back()->SanityCheck();
430 : 149848 : fetch_scopes.pop_back();
431 : 149848 : caches.pop_back();
432 : 149848 : },
433 : :
434 : 54407 : [&]() { // Flush.
435 : : // CoinsViewOverlay::Flush() must have all inputs consumed before being called
436 [ + - ]: 54407 : if (auto* overlay{dynamic_cast<CoinsViewOverlay*>(caches.back().get())};
437 [ + + + + ]: 70454 : overlay && !overlay->AllInputsConsumed()) {
438 : : return;
439 : : }
440 : : // Apply to simulation data.
441 : 42268 : flush();
442 : : // Apply to real caches.
443 : 42268 : caches.back()->Flush(/*reallocate_cache=*/provider.ConsumeBool());
444 : : },
445 : :
446 : 110284 : [&]() { // Sync.
447 [ + + ]: 110284 : if (fetch_scopes.back()) return; // CoinsViewOverlay::Sync() is never called in production
448 : : // Apply to simulation data (note that in our simulation, syncing and flushing is the same thing).
449 : 76309 : flush();
450 : : // Apply to real caches.
451 : 76309 : caches.back()->Sync();
452 : : },
453 : :
454 : 71908 : [&]() { // Reset.
455 [ - + ]: 71908 : sim_caches[caches.size()].Wipe();
456 : : // Apply to real caches. Optionally start fetching again.
457 [ + + + + ]: 71908 : if (fetch_scopes.back() && provider.ConsumeBool()) {
458 [ + - ]: 20238 : fetch_scopes.back().reset(); // Stop fetching before starting again.
459 : 20238 : fetch_scopes.back() = make_fetch_scope();
460 : : } else {
461 : 51670 : (void)caches.back()->CreateResetGuard();
462 : : }
463 : 71908 : },
464 : :
465 : 49342 : [&]() { // GetCacheSize
466 : 49342 : (void)caches.back()->GetCacheSize();
467 : 49342 : },
468 : :
469 : 35171 : [&]() { // DynamicMemoryUsage
470 : 35171 : (void)caches.back()->DynamicMemoryUsage();
471 : 35171 : },
472 : :
473 : 43482 : [&]() { // Change height
474 : 43482 : current_height = provider.ConsumeIntegralInRange<uint32_t>(1, current_height - 1);
475 : 43482 : }
476 : : );
477 : : }
478 : :
479 : : // Sanity check all the remaining caches
480 [ + + ]: 1635 : for (const auto& cache : caches) {
481 [ + - ]: 1083 : cache->SanityCheck();
482 : : }
483 : :
484 : : // Full comparison between caches and simulation data, from bottom to top,
485 [ - + + + ]: 1635 : for (unsigned sim_idx = 1; sim_idx <= caches.size(); ++sim_idx) {
486 : 1083 : auto& cache = *caches[sim_idx - 1];
487 : 1083 : size_t cache_size = 0;
488 : :
489 [ + + ]: 278331 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
490 [ + - ]: 277248 : cache_size += cache.HaveCoinInCache(data.outpoints[outpointidx]);
491 [ + - ]: 277248 : const auto real{cache.PeekCoin(data.outpoints[outpointidx])};
492 : 277248 : auto sim = lookup(outpointidx, sim_idx);
493 [ + + ]: 277248 : if (!sim.has_value()) {
494 [ - + ]: 238773 : assert(!real);
495 : : } else {
496 [ - + ]: 38475 : assert(!real->IsSpent());
497 [ - + ]: 38475 : assert(real->out == data.coins[sim->first].out);
498 [ - + ]: 38475 : assert(real->fCoinBase == data.coins[sim->first].fCoinBase);
499 [ - + ]: 38475 : assert(real->nHeight == sim->second);
500 : : }
501 : 277248 : }
502 : :
503 : : // HaveCoinInCache ignores spent coins, so GetCacheSize() may exceed it.
504 [ + - - + ]: 1083 : assert(cache.GetCacheSize() >= cache_size);
505 : : }
506 : :
507 : : // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0].
508 [ + + ]: 141864 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
509 : 141312 : auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]);
510 : 141312 : auto sim = lookup(outpointidx, 0);
511 [ + + ]: 141312 : if (!sim.has_value()) {
512 [ - + ]: 126392 : assert(!realcoin);
513 : : } else {
514 [ + - - + ]: 14920 : assert(realcoin && !realcoin->IsSpent());
515 [ - + ]: 14920 : assert(realcoin->out == data.coins[sim->first].out);
516 [ - + ]: 14920 : assert(realcoin->fCoinBase == data.coins[sim->first].fCoinBase);
517 [ - + ]: 14920 : assert(realcoin->nHeight == sim->second);
518 : : }
519 : 141312 : }
520 : :
521 : : // Tear down the fetch scopes top down. Otherwise lower level could reset while upper level is reading from it.
522 [ + + ]: 1635 : while (!fetch_scopes.empty()) fetch_scopes.pop_back();
523 : 552 : }
|