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 : 62251 : void Wipe() {
146 [ + + + + : 15998507 : for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {
+ + + + +
+ ]
147 : 15936256 : 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 : 373 : class CoinsViewBottom final : public CoinsViewEmpty
157 : : {
158 : : std::map<COutPoint, Coin> m_data;
159 : :
160 : : public:
161 : 1735040 : std::optional<Coin> GetCoin(const COutPoint& outpoint) const final
162 : : {
163 [ + + ]: 1735040 : if (auto it{m_data.find(outpoint)}; it != m_data.end()) {
164 [ - + ]: 287571 : assert(!it->second.IsSpent());
165 : 287571 : return it->second;
166 : : }
167 : 1447469 : return std::nullopt;
168 : : }
169 : :
170 : 42821 : void BatchWrite(CoinsViewCacheCursor& cursor, const uint256&) final
171 : : {
172 [ + + ]: 107729 : for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {
173 [ + - ]: 64908 : if (it->second.IsDirty()) {
174 [ + + ]: 64908 : if (it->second.coin.IsSpent()) {
175 : 15078 : m_data.erase(it->first);
176 : : } else {
177 [ + + ]: 49830 : if (cursor.WillErase(*it)) {
178 : 18055 : m_data[it->first] = std::move(it->second.coin);
179 : : } else {
180 : 31775 : 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 : 42821 : }
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 : 7421 : struct OverlayFetchScope
202 : : {
203 : : CCoinsViewCache::ResetGuard guard;
204 : 7421 : 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 : : std::shared_ptr<ThreadPool> g_thread_pool{std::make_shared<ThreadPool>("cache_fuzz")};
210 : :
211 : 373 : void StartPoolIfNeeded()
212 : : {
213 [ + + ]: 373 : if (!g_thread_pool->WorkersCount()) g_thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
214 : 373 : }
215 : :
216 : : } // namespace
217 : :
218 [ + - + - : 476 : FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
+ - ]
219 : : {
220 : 373 : SeedRandomStateForTest(SeedRand::ZEROS);
221 : 373 : StartPoolIfNeeded();
222 : : /** Precomputed COutPoint and CCoins values. */
223 [ + + + - : 374 : static const PrecomputedData data;
+ - ]
224 : :
225 : : /** Dummy coinsview instance (base of the hierarchy). */
226 : 373 : CoinsViewBottom bottom;
227 : : /** Real CCoinsViewCache objects. */
228 : 373 : std::vector<std::unique_ptr<CCoinsViewCache>> caches;
229 : : /** Long-lived StartFetching guard (nullptr unless corresponding level is a CoinsViewOverlay). */
230 : 373 : std::unique_ptr<OverlayFetchScope> overlay_fetch_scope;
231 : : /** Simulated cache data (sim_caches[0] matches bottom, sim_caches[i+1] matches caches[i]). */
232 : 373 : CacheLevel sim_caches[MAX_CACHES + 1];
233 : : /** Current height in the simulation. */
234 : 373 : uint32_t current_height = 1U;
235 : :
236 : : // Initialize bottom simulated cache.
237 : 373 : sim_caches[0].Wipe();
238 : :
239 : : /** Helper lookup function in the simulated cache stack. */
240 : 466265 : auto lookup = [&](uint32_t outpointidx, int sim_idx = -1) -> std::optional<std::pair<coinidx_type, uint32_t>> {
241 [ + + - + ]: 465892 : uint32_t cache_idx = sim_idx == -1 ? caches.size() : sim_idx;
242 : 1706228 : while (true) {
243 : 1086060 : const auto& entry = sim_caches[cache_idx].entry[outpointidx];
244 [ + + ]: 1086060 : if (entry.entrytype == EntryType::UNSPENT) {
245 : 129548 : return {{entry.coinidx, entry.height}};
246 [ + + ]: 956512 : } else if (entry.entrytype == EntryType::SPENT) {
247 : 71295 : return std::nullopt;
248 : 885217 : };
249 [ + + ]: 885217 : if (cache_idx == 0) break;
250 : 620168 : --cache_idx;
251 : 620168 : }
252 : 265049 : return std::nullopt;
253 : 373 : };
254 : :
255 : : /** Flush changes in top cache to the one below. */
256 : 80157 : auto flush = [&]() {
257 [ - + - + ]: 79784 : assert(caches.size() >= 1);
258 : 79784 : auto& cache = sim_caches[caches.size()];
259 : 79784 : auto& prev_cache = sim_caches[caches.size() - 1];
260 [ + + ]: 20504488 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
261 [ + + ]: 20424704 : if (cache.entry[outpointidx].entrytype != EntryType::NONE) {
262 : 189469 : prev_cache.entry[outpointidx] = cache.entry[outpointidx];
263 : 189469 : cache.entry[outpointidx].entrytype = EntryType::NONE;
264 : : }
265 : : }
266 : 80157 : };
267 : :
268 : : // Main simulation loop: read commands from the fuzzer input, and apply them
269 : : // to both the real cache stack and the simulation.
270 : 373 : FuzzedDataProvider provider(buffer.data(), buffer.size());
271 [ + + + + ]: 611473 : LIMITED_WHILE (provider.remaining_bytes(), 10000) {
272 : : // Every operation (except "Change height") moves current height forward,
273 : : // so it functions as a kind of epoch, making ~all UTXOs unique.
274 : 611100 : ++current_height;
275 : : // Make sure there is always at least one CCoinsViewCache.
276 [ + + ]: 611100 : if (caches.empty()) {
277 [ + - + - : 21074 : caches.emplace_back(new CCoinsViewCache(&bottom, /*deterministic=*/true));
+ - - - ]
278 [ - + ]: 21074 : sim_caches[caches.size()].Wipe();
279 : : }
280 : :
281 : : // Execute command.
282 [ + - ]: 611100 : CallOneOf(
283 : : provider,
284 : :
285 : 47770 : [&]() { // PeekCoin/GetCoin
286 : 47770 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
287 : : // Look up in simulation data.
288 : 47770 : auto sim = lookup(outpointidx);
289 : : // Look up in real caches.
290 [ + + ]: 47770 : auto realcoin = provider.ConsumeBool() ?
291 : 26751 : caches.back()->PeekCoin(data.outpoints[outpointidx]) :
292 : 47770 : caches.back()->GetCoin(data.outpoints[outpointidx]);
293 : : // Compare results.
294 [ + + ]: 47770 : if (!sim.has_value()) {
295 [ - + ]: 31423 : assert(!realcoin);
296 : : } else {
297 [ + - - + ]: 16347 : assert(realcoin && !realcoin->IsSpent());
298 : 16347 : const auto& simcoin = data.coins[sim->first];
299 [ - + ]: 16347 : assert(realcoin->out == simcoin.out);
300 [ - + ]: 16347 : assert(realcoin->fCoinBase == simcoin.fCoinBase);
301 [ - + ]: 16347 : assert(realcoin->nHeight == sim->second);
302 : : }
303 : 47770 : },
304 : :
305 : 17521 : [&]() { // HaveCoin
306 : 17521 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
307 : : // Look up in simulation data.
308 : 17521 : auto sim = lookup(outpointidx);
309 : : // Look up in real caches.
310 : 17521 : auto real = caches.back()->HaveCoin(data.outpoints[outpointidx]);
311 : : // Compare results.
312 [ - + ]: 17521 : assert(sim.has_value() == real);
313 : 17521 : },
314 : :
315 : 21880 : [&]() { // HaveCoinInCache
316 : 21880 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
317 : : // Invoke on real cache (there is no equivalent in simulation, so nothing to compare result with).
318 : 21880 : (void)caches.back()->HaveCoinInCache(data.outpoints[outpointidx]);
319 : 21880 : },
320 : :
321 : 20844 : [&]() { // AccessCoin
322 : 20844 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
323 : : // Look up in simulation data.
324 : 20844 : auto sim = lookup(outpointidx);
325 : : // Look up in real caches.
326 : 20844 : const auto& realcoin = caches.back()->AccessCoin(data.outpoints[outpointidx]);
327 : : // Compare results.
328 [ + + ]: 20844 : if (!sim.has_value()) {
329 [ - + ]: 14740 : assert(realcoin.IsSpent());
330 : : } else {
331 [ - + ]: 6104 : assert(!realcoin.IsSpent());
332 : 6104 : const auto& simcoin = data.coins[sim->first];
333 [ - + ]: 6104 : assert(simcoin.out == realcoin.out);
334 [ - + ]: 6104 : assert(simcoin.fCoinBase == realcoin.fCoinBase);
335 [ - + ]: 6104 : assert(realcoin.nHeight == sim->second);
336 : : }
337 : 20844 : },
338 : :
339 : 71649 : [&]() { // AddCoin (only possible_overwrite if necessary)
340 : 71649 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
341 : 71649 : uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1);
342 : : // Look up in simulation data (to know whether we must set possible_overwrite or not).
343 : 71649 : auto sim = lookup(outpointidx);
344 : : // Invoke on real caches.
345 : 71649 : Coin coin = data.coins[coinidx];
346 : 71649 : coin.nHeight = current_height;
347 [ + - ]: 71649 : caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), sim.has_value());
348 : : // Apply to simulation data.
349 [ - + ]: 71649 : auto& entry = sim_caches[caches.size()].entry[outpointidx];
350 : 71649 : entry.entrytype = EntryType::UNSPENT;
351 : 71649 : entry.coinidx = coinidx;
352 : 71649 : entry.height = current_height;
353 : 71649 : },
354 : :
355 : 95439 : [&]() { // AddCoin (always possible_overwrite)
356 : 95439 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
357 : 95439 : uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1);
358 : : // Invoke on real caches.
359 : 95439 : Coin coin = data.coins[coinidx];
360 : 95439 : coin.nHeight = current_height;
361 [ + - ]: 95439 : caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), true);
362 : : // Apply to simulation data.
363 [ - + ]: 95439 : auto& entry = sim_caches[caches.size()].entry[outpointidx];
364 : 95439 : entry.entrytype = EntryType::UNSPENT;
365 : 95439 : entry.coinidx = coinidx;
366 : 95439 : entry.height = current_height;
367 : 95439 : },
368 : :
369 : 41377 : [&]() { // SpendCoin (moveto = nullptr)
370 : 41377 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
371 : : // Invoke on real caches.
372 : 41377 : caches.back()->SpendCoin(data.outpoints[outpointidx], nullptr);
373 : : // Apply to simulation data.
374 [ - + ]: 41377 : sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
375 : 41377 : },
376 : :
377 : 42636 : [&]() { // SpendCoin (with moveto)
378 : 42636 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
379 : : // Look up in simulation data (to compare the returned *moveto with).
380 : 42636 : auto sim = lookup(outpointidx);
381 : : // Invoke on real caches.
382 : 42636 : Coin realcoin;
383 [ + - ]: 42636 : caches.back()->SpendCoin(data.outpoints[outpointidx], &realcoin);
384 : : // Apply to simulation data.
385 [ - + ]: 42636 : sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
386 : : // Compare *moveto with the value expected based on simulation data.
387 [ + + ]: 42636 : if (!sim.has_value()) {
388 [ - + ]: 22506 : assert(realcoin.IsSpent());
389 : : } else {
390 [ - + ]: 20130 : assert(!realcoin.IsSpent());
391 : 20130 : const auto& simcoin = data.coins[sim->first];
392 [ - + ]: 20130 : assert(simcoin.out == realcoin.out);
393 [ - + ]: 20130 : assert(simcoin.fCoinBase == realcoin.fCoinBase);
394 [ - + ]: 20130 : assert(realcoin.nHeight == sim->second);
395 : : }
396 : 42636 : },
397 : :
398 : 17840 : [&]() { // Uncache
399 : 17840 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
400 : : // Apply to real caches (there is no equivalent in our simulation).
401 : 17840 : caches.back()->Uncache(data.outpoints[outpointidx]);
402 : 17840 : },
403 : :
404 : 18591 : [&]() { // Add a cache level (if not already at the max).
405 [ - + + + ]: 18591 : if (caches.size() != MAX_CACHES) {
406 [ + + ]: 11304 : if (overlay_fetch_scope) {
407 : 1955 : overlay_fetch_scope.reset();
408 [ - + ]: 1955 : sim_caches[caches.size()].Wipe();
409 : : }
410 : : // Apply to real caches.
411 [ + + ]: 11304 : if (provider.ConsumeBool()) {
412 [ + - ]: 5837 : caches.emplace_back(new CCoinsViewCache(&*caches.back(), /*deterministic=*/true));
413 : : } else {
414 [ + - + - ]: 10934 : caches.emplace_back(new CoinsViewOverlay(&*caches.back(), g_thread_pool, /*deterministic=*/true));
415 : 5467 : auto& overlay{static_cast<CoinsViewOverlay&>(*caches.back())};
416 : 5467 : overlay_fetch_scope = std::make_unique<OverlayFetchScope>(overlay, data.block);
417 : : }
418 : : // Apply to simulation data.
419 [ - + ]: 11304 : sim_caches[caches.size()].Wipe();
420 : : }
421 : 18591 : },
422 : :
423 : 31714 : [&]() { // Remove a cache level.
424 : : // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data).
425 : 31714 : caches.back()->SanityCheck();
426 [ + + ]: 31714 : overlay_fetch_scope.reset();
427 : 31714 : caches.pop_back();
428 : 31714 : },
429 : :
430 : 37082 : [&]() { // Flush.
431 : : // CoinsViewOverlay::Flush() must have all inputs consumed before being called
432 [ + - ]: 37082 : if (auto* overlay{dynamic_cast<CoinsViewOverlay*>(caches.back().get())};
433 [ + + + + ]: 44269 : overlay && !overlay->AllInputsConsumed()) {
434 : : return;
435 : : }
436 : : // Apply to simulation data.
437 : 34143 : flush();
438 : : // Apply to real caches.
439 : 34143 : caches.back()->Flush(/*reallocate_cache=*/provider.ConsumeBool());
440 : : },
441 : :
442 : 54821 : [&]() { // Sync.
443 [ + + ]: 54821 : if (overlay_fetch_scope) return; // CoinsViewOverlay::Sync() is never called in production
444 : : // Apply to simulation data (note that in our simulation, syncing and flushing is the same thing).
445 : 45641 : flush();
446 : : // Apply to real caches.
447 : 45641 : caches.back()->Sync();
448 : : },
449 : :
450 : 27545 : [&]() { // Reset.
451 [ - + ]: 27545 : sim_caches[caches.size()].Wipe();
452 : : // Apply to real caches. Optionally start fetching again.
453 [ + + + + ]: 27545 : if (overlay_fetch_scope && provider.ConsumeBool()) {
454 [ + - ]: 1954 : overlay_fetch_scope.reset();
455 : 1954 : auto& overlay{static_cast<CoinsViewOverlay&>(*caches.back())};
456 : 1954 : overlay_fetch_scope = std::make_unique<OverlayFetchScope>(overlay, data.block);
457 : : } else {
458 : 25591 : (void)caches.back()->CreateResetGuard();
459 : : }
460 : 27545 : },
461 : :
462 : 23135 : [&]() { // GetCacheSize
463 : 23135 : (void)caches.back()->GetCacheSize();
464 : 23135 : },
465 : :
466 : 16230 : [&]() { // DynamicMemoryUsage
467 : 16230 : (void)caches.back()->DynamicMemoryUsage();
468 : 16230 : },
469 : :
470 : 25026 : [&]() { // Change height
471 : 25026 : current_height = provider.ConsumeIntegralInRange<uint32_t>(1, current_height - 1);
472 : 25026 : }
473 : : );
474 : : }
475 : :
476 : : // Sanity check all the remaining caches
477 [ + + ]: 1037 : for (const auto& cache : caches) {
478 [ + - ]: 664 : cache->SanityCheck();
479 : : }
480 : :
481 : : // Full comparison between caches and simulation data, from bottom to top,
482 [ - + + + ]: 1037 : for (unsigned sim_idx = 1; sim_idx <= caches.size(); ++sim_idx) {
483 : 664 : auto& cache = *caches[sim_idx - 1];
484 : 664 : size_t cache_size = 0;
485 : :
486 [ + + ]: 170648 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
487 [ + - ]: 169984 : cache_size += cache.HaveCoinInCache(data.outpoints[outpointidx]);
488 [ + - ]: 169984 : const auto real{cache.PeekCoin(data.outpoints[outpointidx])};
489 : 169984 : auto sim = lookup(outpointidx, sim_idx);
490 [ + + ]: 169984 : if (!sim.has_value()) {
491 [ - + ]: 141446 : assert(!real);
492 : : } else {
493 [ - + ]: 28538 : assert(!real->IsSpent());
494 [ - + ]: 28538 : assert(real->out == data.coins[sim->first].out);
495 [ - + ]: 28538 : assert(real->fCoinBase == data.coins[sim->first].fCoinBase);
496 [ - + ]: 28538 : assert(real->nHeight == sim->second);
497 : : }
498 : 169984 : }
499 : :
500 : : // HaveCoinInCache ignores spent coins, so GetCacheSize() may exceed it.
501 [ + - - + ]: 664 : assert(cache.GetCacheSize() >= cache_size);
502 : : }
503 : :
504 : : // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0].
505 [ + + ]: 95861 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
506 : 95488 : auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]);
507 : 95488 : auto sim = lookup(outpointidx, 0);
508 [ + + ]: 95488 : if (!sim.has_value()) {
509 [ - + ]: 84048 : assert(!realcoin);
510 : : } else {
511 [ + - - + ]: 11440 : assert(realcoin && !realcoin->IsSpent());
512 [ - + ]: 11440 : assert(realcoin->out == data.coins[sim->first].out);
513 [ - + ]: 11440 : assert(realcoin->fCoinBase == data.coins[sim->first].fCoinBase);
514 [ - + ]: 11440 : assert(realcoin->nHeight == sim->second);
515 : : }
516 : 95488 : }
517 : 373 : }
|