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 : 100903 : void Wipe() {
146 [ + + + + : 25932071 : for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {
+ + + + +
+ ]
147 : 25831168 : 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 : 492 : class CoinsViewBottom final : public CoinsViewEmpty
157 : : {
158 : : std::map<COutPoint, Coin> m_data;
159 : :
160 : : public:
161 : 4500047 : std::optional<Coin> GetCoin(const COutPoint& outpoint) const final
162 : : {
163 [ + + ]: 4500047 : if (auto it{m_data.find(outpoint)}; it != m_data.end()) {
164 [ - + ]: 449878 : assert(!it->second.IsSpent());
165 : 449878 : return it->second;
166 : : }
167 : 4050169 : return std::nullopt;
168 : : }
169 : :
170 : 57917 : void BatchWrite(CoinsViewCacheCursor& cursor, const uint256&) final
171 : : {
172 [ + + ]: 145359 : for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {
173 [ + - ]: 87442 : if (it->second.IsDirty()) {
174 [ + + ]: 87442 : if (it->second.coin.IsSpent()) {
175 : 19683 : m_data.erase(it->first);
176 : : } else {
177 [ + + ]: 67759 : if (cursor.WillErase(*it)) {
178 : 24200 : m_data[it->first] = std::move(it->second.coin);
179 : : } else {
180 : 43559 : 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 : 57917 : }
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 : 22767 : struct OverlayFetchScope
202 : : {
203 : : CCoinsViewCache::ResetGuard guard;
204 : 22767 : 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 : 492 : void StartPoolIfNeeded()
212 : : {
213 [ + + ]: 492 : if (!g_thread_pool->WorkersCount()) g_thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
214 : 492 : }
215 : :
216 : : } // namespace
217 : :
218 [ + - ][ + - : 476 : FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })
+ - + - ]
219 : : {
220 : 492 : SeedRandomStateForTest(SeedRand::ZEROS);
221 : 492 : StartPoolIfNeeded();
222 : : /** Precomputed COutPoint and CCoins values. */
223 [ + + + - : 493 : static const PrecomputedData data;
+ - ]
224 : :
225 : : /** Dummy coinsview instance (base of the hierarchy). */
226 : 492 : CoinsViewBottom bottom;
227 : : /** Real CCoinsViewCache objects. */
228 : 492 : std::vector<std::unique_ptr<CCoinsViewCache>> caches;
229 : : /** Long-lived StartFetching guard (nullptr unless corresponding level is a CoinsViewOverlay). */
230 : 492 : std::unique_ptr<OverlayFetchScope> overlay_fetch_scope;
231 : : /** Simulated cache data (sim_caches[0] matches bottom, sim_caches[i+1] matches caches[i]). */
232 : 492 : CacheLevel sim_caches[MAX_CACHES + 1];
233 : : /** Current height in the simulation. */
234 : 492 : uint32_t current_height = 1U;
235 : :
236 : : // Initialize bottom simulated cache.
237 : 492 : sim_caches[0].Wipe();
238 : :
239 : : /** Helper lookup function in the simulated cache stack. */
240 : 628952 : auto lookup = [&](uint32_t outpointidx, int sim_idx = -1) -> std::optional<std::pair<coinidx_type, uint32_t>> {
241 [ + + - + ]: 628460 : uint32_t cache_idx = sim_idx == -1 ? caches.size() : sim_idx;
242 : 2338106 : while (true) {
243 : 1483283 : const auto& entry = sim_caches[cache_idx].entry[outpointidx];
244 [ + + ]: 1483283 : if (entry.entrytype == EntryType::UNSPENT) {
245 : 179753 : return {{entry.coinidx, entry.height}};
246 [ + + ]: 1303530 : } else if (entry.entrytype == EntryType::SPENT) {
247 : 95783 : return std::nullopt;
248 : 1207747 : };
249 [ + + ]: 1207747 : if (cache_idx == 0) break;
250 : 854823 : --cache_idx;
251 : 854823 : }
252 : 352924 : return std::nullopt;
253 : 492 : };
254 : :
255 : : /** Flush changes in top cache to the one below. */
256 : 108608 : auto flush = [&]() {
257 [ - + - + ]: 108116 : assert(caches.size() >= 1);
258 : 108116 : auto& cache = sim_caches[caches.size()];
259 : 108116 : auto& prev_cache = sim_caches[caches.size() - 1];
260 [ + + ]: 27785812 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
261 [ + + ]: 27677696 : if (cache.entry[outpointidx].entrytype != EntryType::NONE) {
262 : 263168 : prev_cache.entry[outpointidx] = cache.entry[outpointidx];
263 : 263168 : cache.entry[outpointidx].entrytype = EntryType::NONE;
264 : : }
265 : : }
266 : 108608 : };
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 : 492 : FuzzedDataProvider provider(buffer.data(), buffer.size());
271 [ + + + + ]: 864564 : 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 : 864072 : ++current_height;
275 : : // Make sure there is always at least one CCoinsViewCache.
276 [ + + ]: 864072 : if (caches.empty()) {
277 [ + - + - : 28181 : caches.emplace_back(new CCoinsViewCache(&bottom, /*deterministic=*/true));
+ - - - ]
278 [ - + ]: 28181 : sim_caches[caches.size()].Wipe();
279 : : }
280 : :
281 : : // Execute command.
282 [ + - ]: 864072 : CallOneOf(
283 : : provider,
284 : :
285 : 64997 : [&]() { // PeekCoin/GetCoin
286 : 64997 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
287 : : // Look up in simulation data.
288 : 64997 : auto sim = lookup(outpointidx);
289 : : // Look up in real caches.
290 [ + + ]: 64997 : auto realcoin = provider.ConsumeBool() ?
291 : 35817 : caches.back()->PeekCoin(data.outpoints[outpointidx]) :
292 : 64997 : caches.back()->GetCoin(data.outpoints[outpointidx]);
293 : : // Compare results.
294 [ + + ]: 64997 : if (!sim.has_value()) {
295 [ - + ]: 41858 : assert(!realcoin);
296 : : } else {
297 [ + - - + ]: 23139 : assert(realcoin && !realcoin->IsSpent());
298 : 23139 : const auto& simcoin = data.coins[sim->first];
299 [ - + ]: 23139 : assert(realcoin->out == simcoin.out);
300 [ - + ]: 23139 : assert(realcoin->fCoinBase == simcoin.fCoinBase);
301 [ - + ]: 23139 : assert(realcoin->nHeight == sim->second);
302 : : }
303 : 64997 : },
304 : :
305 : 24172 : [&]() { // HaveCoin
306 : 24172 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
307 : : // Look up in simulation data.
308 : 24172 : auto sim = lookup(outpointidx);
309 : : // Look up in real caches.
310 : 24172 : auto real = caches.back()->HaveCoin(data.outpoints[outpointidx]);
311 : : // Compare results.
312 [ - + ]: 24172 : assert(sim.has_value() == real);
313 : 24172 : },
314 : :
315 : 29631 : [&]() { // HaveCoinInCache
316 : 29631 : 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 : 29631 : (void)caches.back()->HaveCoinInCache(data.outpoints[outpointidx]);
319 : 29631 : },
320 : :
321 : 29255 : [&]() { // AccessCoin
322 : 29255 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
323 : : // Look up in simulation data.
324 : 29255 : auto sim = lookup(outpointidx);
325 : : // Look up in real caches.
326 : 29255 : const auto& realcoin = caches.back()->AccessCoin(data.outpoints[outpointidx]);
327 : : // Compare results.
328 [ + + ]: 29255 : if (!sim.has_value()) {
329 [ - + ]: 19620 : assert(realcoin.IsSpent());
330 : : } else {
331 [ - + ]: 9635 : assert(!realcoin.IsSpent());
332 : 9635 : const auto& simcoin = data.coins[sim->first];
333 [ - + ]: 9635 : assert(simcoin.out == realcoin.out);
334 [ - + ]: 9635 : assert(simcoin.fCoinBase == realcoin.fCoinBase);
335 [ - + ]: 9635 : assert(realcoin.nHeight == sim->second);
336 : : }
337 : 29255 : },
338 : :
339 : 97632 : [&]() { // AddCoin (only possible_overwrite if necessary)
340 : 97632 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
341 : 97632 : 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 : 97632 : auto sim = lookup(outpointidx);
344 : : // Invoke on real caches.
345 : 97632 : Coin coin = data.coins[coinidx];
346 : 97632 : coin.nHeight = current_height;
347 [ + - ]: 97632 : caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), sim.has_value());
348 : : // Apply to simulation data.
349 [ - + ]: 97632 : auto& entry = sim_caches[caches.size()].entry[outpointidx];
350 : 97632 : entry.entrytype = EntryType::UNSPENT;
351 : 97632 : entry.coinidx = coinidx;
352 : 97632 : entry.height = current_height;
353 : 97632 : },
354 : :
355 : 128643 : [&]() { // AddCoin (always possible_overwrite)
356 : 128643 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
357 : 128643 : uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1);
358 : : // Invoke on real caches.
359 : 128643 : Coin coin = data.coins[coinidx];
360 : 128643 : coin.nHeight = current_height;
361 [ + - ]: 128643 : caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), true);
362 : : // Apply to simulation data.
363 [ - + ]: 128643 : auto& entry = sim_caches[caches.size()].entry[outpointidx];
364 : 128643 : entry.entrytype = EntryType::UNSPENT;
365 : 128643 : entry.coinidx = coinidx;
366 : 128643 : entry.height = current_height;
367 : 128643 : },
368 : :
369 : 54288 : [&]() { // SpendCoin (moveto = nullptr)
370 : 54288 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
371 : : // Invoke on real caches.
372 : 54288 : caches.back()->SpendCoin(data.outpoints[outpointidx], nullptr);
373 : : // Apply to simulation data.
374 [ - + ]: 54288 : sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
375 : 54288 : },
376 : :
377 : 59380 : [&]() { // SpendCoin (with moveto)
378 : 59380 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
379 : : // Look up in simulation data (to compare the returned *moveto with).
380 : 59380 : auto sim = lookup(outpointidx);
381 : : // Invoke on real caches.
382 : 59380 : Coin realcoin;
383 [ + - ]: 59380 : caches.back()->SpendCoin(data.outpoints[outpointidx], &realcoin);
384 : : // Apply to simulation data.
385 [ - + ]: 59380 : sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
386 : : // Compare *moveto with the value expected based on simulation data.
387 [ + + ]: 59380 : if (!sim.has_value()) {
388 [ - + ]: 32087 : assert(realcoin.IsSpent());
389 : : } else {
390 [ - + ]: 27293 : assert(!realcoin.IsSpent());
391 : 27293 : const auto& simcoin = data.coins[sim->first];
392 [ - + ]: 27293 : assert(simcoin.out == realcoin.out);
393 [ - + ]: 27293 : assert(simcoin.fCoinBase == realcoin.fCoinBase);
394 [ - + ]: 27293 : assert(realcoin.nHeight == sim->second);
395 : : }
396 : 59380 : },
397 : :
398 : 24769 : [&]() { // Uncache
399 : 24769 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
400 : : // Apply to real caches (there is no equivalent in our simulation).
401 : 24769 : caches.back()->Uncache(data.outpoints[outpointidx]);
402 : 24769 : },
403 : :
404 : 41949 : [&]() { // Add a cache level (if not already at the max).
405 [ - + + + ]: 41949 : if (caches.size() != MAX_CACHES) {
406 [ + + ]: 28628 : if (overlay_fetch_scope) {
407 : 6313 : overlay_fetch_scope.reset();
408 [ - + ]: 6313 : sim_caches[caches.size()].Wipe();
409 : : }
410 : : // Apply to real caches.
411 [ + + ]: 28628 : if (provider.ConsumeBool()) {
412 [ + - ]: 8783 : caches.emplace_back(new CCoinsViewCache(&*caches.back(), /*deterministic=*/true));
413 : : } else {
414 [ + - + - ]: 39690 : caches.emplace_back(new CoinsViewOverlay(&*caches.back(), g_thread_pool, /*deterministic=*/true));
415 : 19845 : auto& overlay{static_cast<CoinsViewOverlay&>(*caches.back())};
416 : 19845 : overlay_fetch_scope = std::make_unique<OverlayFetchScope>(overlay, data.block);
417 : : }
418 : : // Apply to simulation data.
419 [ - + ]: 28628 : sim_caches[caches.size()].Wipe();
420 : : }
421 : 41949 : },
422 : :
423 : 55922 : [&]() { // Remove a cache level.
424 : : // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data).
425 : 55922 : caches.back()->SanityCheck();
426 [ + + ]: 55922 : overlay_fetch_scope.reset();
427 : 55922 : caches.pop_back();
428 : 55922 : },
429 : :
430 : 52795 : [&]() { // Flush.
431 : : // CoinsViewOverlay::Flush() must have all inputs consumed before being called
432 [ + - ]: 52795 : if (auto* overlay{dynamic_cast<CoinsViewOverlay*>(caches.back().get())};
433 [ + + + + ]: 63501 : overlay && !overlay->AllInputsConsumed()) {
434 : : return;
435 : : }
436 : : // Apply to simulation data.
437 : 47745 : flush();
438 : : // Apply to real caches.
439 : 47745 : caches.back()->Flush(/*reallocate_cache=*/provider.ConsumeBool());
440 : : },
441 : :
442 : 73391 : [&]() { // Sync.
443 [ + + ]: 73391 : 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 : 60371 : flush();
446 : : // Apply to real caches.
447 : 60371 : caches.back()->Sync();
448 : : },
449 : :
450 : 37289 : [&]() { // Reset.
451 [ - + ]: 37289 : sim_caches[caches.size()].Wipe();
452 : : // Apply to real caches. Optionally start fetching again.
453 [ + + + + ]: 37289 : if (overlay_fetch_scope && provider.ConsumeBool()) {
454 [ + - ]: 2922 : overlay_fetch_scope.reset();
455 : 2922 : auto& overlay{static_cast<CoinsViewOverlay&>(*caches.back())};
456 : 2922 : overlay_fetch_scope = std::make_unique<OverlayFetchScope>(overlay, data.block);
457 : : } else {
458 : 34367 : (void)caches.back()->CreateResetGuard();
459 : : }
460 : 37289 : },
461 : :
462 : 33294 : [&]() { // GetCacheSize
463 : 33294 : (void)caches.back()->GetCacheSize();
464 : 33294 : },
465 : :
466 : 24177 : [&]() { // DynamicMemoryUsage
467 : 24177 : (void)caches.back()->DynamicMemoryUsage();
468 : 24177 : },
469 : :
470 : 32488 : [&]() { // Change height
471 : 32488 : current_height = provider.ConsumeIntegralInRange<uint32_t>(1, current_height - 1);
472 : 32488 : }
473 : : );
474 : : }
475 : :
476 : : // Sanity check all the remaining caches
477 [ + + ]: 1379 : for (const auto& cache : caches) {
478 [ + - ]: 887 : cache->SanityCheck();
479 : : }
480 : :
481 : : // Full comparison between caches and simulation data, from bottom to top,
482 [ - + + + ]: 1379 : for (unsigned sim_idx = 1; sim_idx <= caches.size(); ++sim_idx) {
483 : 887 : auto& cache = *caches[sim_idx - 1];
484 : 887 : size_t cache_size = 0;
485 : :
486 [ + + ]: 227959 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
487 [ + - ]: 227072 : cache_size += cache.HaveCoinInCache(data.outpoints[outpointidx]);
488 [ + - ]: 227072 : const auto real{cache.PeekCoin(data.outpoints[outpointidx])};
489 : 227072 : auto sim = lookup(outpointidx, sim_idx);
490 [ + + ]: 227072 : if (!sim.has_value()) {
491 [ - + ]: 187658 : assert(!real);
492 : : } else {
493 [ - + ]: 39414 : assert(!real->IsSpent());
494 [ - + ]: 39414 : assert(real->out == data.coins[sim->first].out);
495 [ - + ]: 39414 : assert(real->fCoinBase == data.coins[sim->first].fCoinBase);
496 [ - + ]: 39414 : assert(real->nHeight == sim->second);
497 : : }
498 : 227072 : }
499 : :
500 : : // HaveCoinInCache ignores spent coins, so GetCacheSize() may exceed it.
501 [ + - - + ]: 887 : assert(cache.GetCacheSize() >= cache_size);
502 : : }
503 : :
504 : : // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0].
505 [ + + ]: 126444 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
506 : 125952 : auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]);
507 : 125952 : auto sim = lookup(outpointidx, 0);
508 [ + + ]: 125952 : if (!sim.has_value()) {
509 [ - + ]: 109700 : assert(!realcoin);
510 : : } else {
511 [ + - - + ]: 16252 : assert(realcoin && !realcoin->IsSpent());
512 [ - + ]: 16252 : assert(realcoin->out == data.coins[sim->first].out);
513 [ - + ]: 16252 : assert(realcoin->fCoinBase == data.coins[sim->first].fCoinBase);
514 [ - + ]: 16252 : assert(realcoin->nHeight == sim->second);
515 : : }
516 : 125952 : }
517 : 492 : }
|