Branch data Line data Source code
1 : : // Copyright (c) 2023 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 <primitives/transaction.h>
8 : : #include <test/fuzz/fuzz.h>
9 : : #include <test/fuzz/FuzzedDataProvider.h>
10 : : #include <test/fuzz/util.h>
11 : :
12 : : #include <assert.h>
13 : : #include <optional>
14 : : #include <memory>
15 : : #include <stdint.h>
16 : : #include <vector>
17 : :
18 : : namespace {
19 : :
20 : : /** Number of distinct COutPoint values used in this test. */
21 : : constexpr uint32_t NUM_OUTPOINTS = 256;
22 : : /** Number of distinct Coin values used in this test (ignoring nHeight). */
23 : : constexpr uint32_t NUM_COINS = 256;
24 : : /** Maximum number CCoinsViewCache objects used in this test. */
25 [ + - ]: 2 : constexpr uint32_t MAX_CACHES = 4;
26 : : /** Data type large enough to hold NUM_COINS-1. */
27 : : using coinidx_type = uint8_t;
28 : :
29 : 0 : struct PrecomputedData
30 : : {
31 : : //! Randomly generated COutPoint values.
32 : : COutPoint outpoints[NUM_OUTPOINTS];
33 : :
34 : : //! Randomly generated Coin values.
35 : : Coin coins[NUM_COINS];
36 : :
37 [ # # # # ]: 0 : PrecomputedData()
38 : : {
39 : : static const uint8_t PREFIX_O[1] = {'o'}; /** Hash prefix for outpoint hashes. */
40 : : static const uint8_t PREFIX_S[1] = {'s'}; /** Hash prefix for coins scriptPubKeys. */
41 : : static const uint8_t PREFIX_M[1] = {'m'}; /** Hash prefix for coins nValue/fCoinBase. */
42 : :
43 [ # # ]: 0 : for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {
44 : 0 : uint32_t idx = (i * 1200U) >> 12; /* Map 3 or 4 entries to same txid. */
45 : 0 : const uint8_t ser[4] = {uint8_t(idx), uint8_t(idx >> 8), uint8_t(idx >> 16), uint8_t(idx >> 24)};
46 [ # # # # : 0 : CSHA256().Write(PREFIX_O, 1).Write(ser, sizeof(ser)).Finalize(outpoints[i].hash.begin());
# # # # #
# ]
47 : 0 : outpoints[i].n = i;
48 : 0 : }
49 : :
50 [ # # ]: 0 : for (uint32_t i = 0; i < NUM_COINS; ++i) {
51 : 0 : const uint8_t ser[4] = {uint8_t(i), uint8_t(i >> 8), uint8_t(i >> 16), uint8_t(i >> 24)};
52 [ # # ]: 0 : uint256 hash;
53 [ # # # # : 0 : CSHA256().Write(PREFIX_S, 1).Write(ser, sizeof(ser)).Finalize(hash.begin());
# # # # #
# ]
54 : : /* Convert hash to scriptPubkeys (of different lengths, so SanityCheck's cached memory
55 : : * usage check has a chance to detect mismatches). */
56 [ # # # # : 0 : switch (i % 5U) {
# # ]
57 : : case 0: /* P2PKH */
58 [ # # ]: 0 : coins[i].out.scriptPubKey.resize(25);
59 [ # # ]: 0 : coins[i].out.scriptPubKey[0] = OP_DUP;
60 [ # # ]: 0 : coins[i].out.scriptPubKey[1] = OP_HASH160;
61 [ # # ]: 0 : coins[i].out.scriptPubKey[2] = 20;
62 [ # # # # : 0 : std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 3);
# # # # #
# ]
63 [ # # ]: 0 : coins[i].out.scriptPubKey[23] = OP_EQUALVERIFY;
64 [ # # ]: 0 : coins[i].out.scriptPubKey[24] = OP_CHECKSIG;
65 : 0 : break;
66 : : case 1: /* P2SH */
67 [ # # ]: 0 : coins[i].out.scriptPubKey.resize(23);
68 [ # # ]: 0 : coins[i].out.scriptPubKey[0] = OP_HASH160;
69 [ # # ]: 0 : coins[i].out.scriptPubKey[1] = 20;
70 [ # # # # : 0 : std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);
# # # # #
# ]
71 [ # # ]: 0 : coins[i].out.scriptPubKey[12] = OP_EQUAL;
72 : 0 : break;
73 : : case 2: /* P2WPKH */
74 [ # # ]: 0 : coins[i].out.scriptPubKey.resize(22);
75 [ # # ]: 0 : coins[i].out.scriptPubKey[0] = OP_0;
76 [ # # ]: 0 : coins[i].out.scriptPubKey[1] = 20;
77 [ # # # # : 0 : std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);
# # # # #
# ]
78 : 0 : break;
79 : : case 3: /* P2WSH */
80 [ # # ]: 0 : coins[i].out.scriptPubKey.resize(34);
81 [ # # ]: 0 : coins[i].out.scriptPubKey[0] = OP_0;
82 [ # # ]: 0 : coins[i].out.scriptPubKey[1] = 32;
83 [ # # # # : 0 : std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2);
# # # # #
# ]
84 : 0 : break;
85 : : case 4: /* P2TR */
86 [ # # ]: 0 : coins[i].out.scriptPubKey.resize(34);
87 [ # # ]: 0 : coins[i].out.scriptPubKey[0] = OP_1;
88 [ # # ]: 0 : coins[i].out.scriptPubKey[1] = 32;
89 [ # # # # : 0 : std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2);
# # # # #
# ]
90 : 0 : break;
91 : : }
92 : : /* Hash again to construct nValue and fCoinBase. */
93 [ # # # # : 0 : CSHA256().Write(PREFIX_M, 1).Write(ser, sizeof(ser)).Finalize(hash.begin());
# # # # #
# ]
94 [ # # ]: 0 : coins[i].out.nValue = CAmount(hash.GetUint64(0) % MAX_MONEY);
95 [ # # ]: 0 : coins[i].fCoinBase = (hash.GetUint64(1) & 7) == 0;
96 : 0 : coins[i].nHeight = 0; /* Real nHeight used in simulation is set dynamically. */
97 : 0 : }
98 : 0 : }
99 : : };
100 : :
101 : : enum class EntryType : uint8_t
102 : : {
103 : : /* This entry in the cache does not exist (so we'd have to look in the parent cache). */
104 : : NONE,
105 : :
106 : : /* This entry in the cache corresponds to an unspent coin. */
107 : : UNSPENT,
108 : :
109 : : /* This entry in the cache corresponds to a spent coin. */
110 : : SPENT,
111 : : };
112 : :
113 : : struct CacheEntry
114 : : {
115 : : /* Type of entry. */
116 : : EntryType entrytype;
117 : :
118 : : /* Index in the coins array this entry corresponds to (only if entrytype == UNSPENT). */
119 : : coinidx_type coinidx;
120 : :
121 : : /* nHeight value for this entry (so the coins[coinidx].nHeight value is ignored; only if entrytype == UNSPENT). */
122 : : uint32_t height;
123 : : };
124 : :
125 : : struct CacheLevel
126 : : {
127 : : CacheEntry entry[NUM_OUTPOINTS];
128 : :
129 : 0 : void Wipe() {
130 [ # # ]: 0 : for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {
131 : 0 : entry[i].entrytype = EntryType::NONE;
132 : 0 : }
133 : 0 : }
134 : : };
135 : :
136 : : /** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB).
137 : : *
138 : : * The initial state consists of the empty UTXO set, though coins whose output index
139 : : * is 3 (mod 5) always have GetCoin() succeed (but returning an IsSpent() coin unless a UTXO
140 : : * exists). Coins whose output index is 4 (mod 5) have GetCoin() always succeed after being spent.
141 : : * This exercises code paths with spent, non-DIRTY cache entries.
142 : : */
143 : : class CoinsViewBottom final : public CCoinsView
144 : : {
145 : : std::map<COutPoint, Coin> m_data;
146 : :
147 : : public:
148 : 0 : bool GetCoin(const COutPoint& outpoint, Coin& coin) const final
149 : : {
150 : 0 : auto it = m_data.find(outpoint);
151 [ # # ]: 0 : if (it == m_data.end()) {
152 [ # # ]: 0 : if ((outpoint.n % 5) == 3) {
153 : 0 : coin.Clear();
154 : 0 : return true;
155 : : }
156 : 0 : return false;
157 : : } else {
158 : 0 : coin = it->second;
159 : 0 : return true;
160 : : }
161 : 0 : }
162 : :
163 : 0 : bool HaveCoin(const COutPoint& outpoint) const final
164 : : {
165 : 0 : return m_data.count(outpoint);
166 : : }
167 : :
168 : 0 : uint256 GetBestBlock() const final { return {}; }
169 : 0 : std::vector<uint256> GetHeadBlocks() const final { return {}; }
170 : 0 : std::unique_ptr<CCoinsViewCursor> Cursor() const final { return {}; }
171 : 0 : size_t EstimateSize() const final { return m_data.size(); }
172 : :
173 : 0 : bool BatchWrite(CCoinsMap& data, const uint256&, bool erase) final
174 : : {
175 [ # # # # ]: 0 : for (auto it = data.begin(); it != data.end(); it = erase ? data.erase(it) : std::next(it)) {
176 [ # # ]: 0 : if (it->second.flags & CCoinsCacheEntry::DIRTY) {
177 [ # # # # ]: 0 : if (it->second.coin.IsSpent() && (it->first.n % 5) != 4) {
178 : 0 : m_data.erase(it->first);
179 [ # # ]: 0 : } else if (erase) {
180 : 0 : m_data[it->first] = std::move(it->second.coin);
181 : 0 : } else {
182 : 0 : m_data[it->first] = it->second.coin;
183 : : }
184 : 0 : } else {
185 : : /* For non-dirty entries being written, compare them with what we have. */
186 : 0 : auto it2 = m_data.find(it->first);
187 [ # # ]: 0 : if (it->second.coin.IsSpent()) {
188 [ # # # # ]: 0 : assert(it2 == m_data.end() || it2->second.IsSpent());
189 : 0 : } else {
190 [ # # ]: 0 : assert(it2 != m_data.end());
191 [ # # ]: 0 : assert(it->second.coin.out == it2->second.out);
192 [ # # ]: 0 : assert(it->second.coin.fCoinBase == it2->second.fCoinBase);
193 [ # # ]: 0 : assert(it->second.coin.nHeight == it2->second.nHeight);
194 : : }
195 : : }
196 : 0 : }
197 : 0 : return true;
198 : : }
199 : : };
200 : :
201 : : } // namespace
202 : :
203 [ - + ]: 4 : FUZZ_TARGET(coinscache_sim)
204 : : {
205 : : /** Precomputed COutPoint and CCoins values. */
206 [ # # # # : 0 : static const PrecomputedData data;
# # ]
207 : :
208 : : /** Dummy coinsview instance (base of the hierarchy). */
209 : 0 : CoinsViewBottom bottom;
210 : : /** Real CCoinsViewCache objects. */
211 : 0 : std::vector<std::unique_ptr<CCoinsViewCache>> caches;
212 : : /** Simulated cache data (sim_caches[0] matches bottom, sim_caches[i+1] matches caches[i]). */
213 : : CacheLevel sim_caches[MAX_CACHES + 1];
214 : : /** Current height in the simulation. */
215 : 0 : uint32_t current_height = 1U;
216 : :
217 : : // Initialize bottom simulated cache.
218 [ # # ]: 0 : sim_caches[0].Wipe();
219 : :
220 : : /** Helper lookup function in the simulated cache stack. */
221 : 0 : auto lookup = [&](uint32_t outpointidx, int sim_idx = -1) -> std::optional<std::pair<coinidx_type, uint32_t>> {
222 [ # # ]: 0 : uint32_t cache_idx = sim_idx == -1 ? caches.size() : sim_idx;
223 : 0 : while (true) {
224 : 0 : const auto& entry = sim_caches[cache_idx].entry[outpointidx];
225 [ # # ]: 0 : if (entry.entrytype == EntryType::UNSPENT) {
226 : 0 : return {{entry.coinidx, entry.height}};
227 [ # # ]: 0 : } else if (entry.entrytype == EntryType::SPENT) {
228 : 0 : return std::nullopt;
229 : : };
230 [ # # ]: 0 : if (cache_idx == 0) break;
231 : 0 : --cache_idx;
232 : : }
233 : 0 : return std::nullopt;
234 : 0 : };
235 : :
236 : : /** Flush changes in top cache to the one below. */
237 : 0 : auto flush = [&]() {
238 [ # # ]: 0 : assert(caches.size() >= 1);
239 : 0 : auto& cache = sim_caches[caches.size()];
240 : 0 : auto& prev_cache = sim_caches[caches.size() - 1];
241 [ # # ]: 0 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
242 [ # # ]: 0 : if (cache.entry[outpointidx].entrytype != EntryType::NONE) {
243 : 0 : prev_cache.entry[outpointidx] = cache.entry[outpointidx];
244 : 0 : cache.entry[outpointidx].entrytype = EntryType::NONE;
245 : 0 : }
246 : 0 : }
247 : 0 : };
248 : :
249 : : // Main simulation loop: read commands from the fuzzer input, and apply them
250 : : // to both the real cache stack and the simulation.
251 [ # # ]: 0 : FuzzedDataProvider provider(buffer.data(), buffer.size());
252 [ # # # # : 0 : LIMITED_WHILE(provider.remaining_bytes(), 10000) {
# # ]
253 : : // Every operation (except "Change height") moves current height forward,
254 : : // so it functions as a kind of epoch, making ~all UTXOs unique.
255 : 0 : ++current_height;
256 : : // Make sure there is always at least one CCoinsViewCache.
257 [ # # ]: 0 : if (caches.empty()) {
258 [ # # # # : 0 : caches.emplace_back(new CCoinsViewCache(&bottom, /*deterministic=*/true));
# # ]
259 [ # # ]: 0 : sim_caches[caches.size()].Wipe();
260 : 0 : }
261 : :
262 : : // Execute command.
263 [ # # ]: 0 : CallOneOf(
264 : : provider,
265 : :
266 : 0 : [&]() { // GetCoin
267 : 0 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
268 : : // Look up in simulation data.
269 : 0 : auto sim = lookup(outpointidx);
270 : : // Look up in real caches.
271 : 0 : Coin realcoin;
272 [ # # ]: 0 : auto real = caches.back()->GetCoin(data.outpoints[outpointidx], realcoin);
273 : : // Compare results.
274 [ # # ]: 0 : if (!sim.has_value()) {
275 [ # # # # : 0 : assert(!real || realcoin.IsSpent());
# # ]
276 : 0 : } else {
277 [ # # # # : 0 : assert(real && !realcoin.IsSpent());
# # ]
278 : 0 : const auto& simcoin = data.coins[sim->first];
279 [ # # # # ]: 0 : assert(realcoin.out == simcoin.out);
280 [ # # ]: 0 : assert(realcoin.fCoinBase == simcoin.fCoinBase);
281 [ # # ]: 0 : assert(realcoin.nHeight == sim->second);
282 : : }
283 : 0 : },
284 : :
285 : 0 : [&]() { // HaveCoin
286 : 0 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
287 : : // Look up in simulation data.
288 : 0 : auto sim = lookup(outpointidx);
289 : : // Look up in real caches.
290 : 0 : auto real = caches.back()->HaveCoin(data.outpoints[outpointidx]);
291 : : // Compare results.
292 [ # # ]: 0 : assert(sim.has_value() == real);
293 : 0 : },
294 : :
295 : 0 : [&]() { // HaveCoinInCache
296 : 0 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
297 : : // Invoke on real cache (there is no equivalent in simulation, so nothing to compare result with).
298 : 0 : (void)caches.back()->HaveCoinInCache(data.outpoints[outpointidx]);
299 : 0 : },
300 : :
301 : 0 : [&]() { // AccessCoin
302 : 0 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
303 : : // Look up in simulation data.
304 : 0 : auto sim = lookup(outpointidx);
305 : : // Look up in real caches.
306 : 0 : const auto& realcoin = caches.back()->AccessCoin(data.outpoints[outpointidx]);
307 : : // Compare results.
308 [ # # ]: 0 : if (!sim.has_value()) {
309 [ # # ]: 0 : assert(realcoin.IsSpent());
310 : 0 : } else {
311 [ # # ]: 0 : assert(!realcoin.IsSpent());
312 : 0 : const auto& simcoin = data.coins[sim->first];
313 [ # # ]: 0 : assert(simcoin.out == realcoin.out);
314 [ # # ]: 0 : assert(simcoin.fCoinBase == realcoin.fCoinBase);
315 [ # # ]: 0 : assert(realcoin.nHeight == sim->second);
316 : : }
317 : 0 : },
318 : :
319 : 0 : [&]() { // AddCoin (only possible_overwrite if necessary)
320 : 0 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
321 : 0 : uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1);
322 : : // Look up in simulation data (to know whether we must set possible_overwrite or not).
323 : 0 : auto sim = lookup(outpointidx);
324 : : // Invoke on real caches.
325 : 0 : Coin coin = data.coins[coinidx];
326 : 0 : coin.nHeight = current_height;
327 [ # # ]: 0 : caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), sim.has_value());
328 : : // Apply to simulation data.
329 : 0 : auto& entry = sim_caches[caches.size()].entry[outpointidx];
330 : 0 : entry.entrytype = EntryType::UNSPENT;
331 : 0 : entry.coinidx = coinidx;
332 : 0 : entry.height = current_height;
333 : 0 : },
334 : :
335 : 0 : [&]() { // AddCoin (always possible_overwrite)
336 : 0 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
337 : 0 : uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1);
338 : : // Invoke on real caches.
339 : 0 : Coin coin = data.coins[coinidx];
340 : 0 : coin.nHeight = current_height;
341 [ # # ]: 0 : caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), true);
342 : : // Apply to simulation data.
343 : 0 : auto& entry = sim_caches[caches.size()].entry[outpointidx];
344 : 0 : entry.entrytype = EntryType::UNSPENT;
345 : 0 : entry.coinidx = coinidx;
346 : 0 : entry.height = current_height;
347 : 0 : },
348 : :
349 : 0 : [&]() { // SpendCoin (moveto = nullptr)
350 : 0 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
351 : : // Invoke on real caches.
352 : 0 : caches.back()->SpendCoin(data.outpoints[outpointidx], nullptr);
353 : : // Apply to simulation data.
354 : 0 : sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
355 : 0 : },
356 : :
357 : 0 : [&]() { // SpendCoin (with moveto)
358 : 0 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
359 : : // Look up in simulation data (to compare the returned *moveto with).
360 : 0 : auto sim = lookup(outpointidx);
361 : : // Invoke on real caches.
362 : 0 : Coin realcoin;
363 [ # # ]: 0 : caches.back()->SpendCoin(data.outpoints[outpointidx], &realcoin);
364 : : // Apply to simulation data.
365 : 0 : sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
366 : : // Compare *moveto with the value expected based on simulation data.
367 [ # # ]: 0 : if (!sim.has_value()) {
368 [ # # # # ]: 0 : assert(realcoin.IsSpent());
369 : 0 : } else {
370 [ # # # # ]: 0 : assert(!realcoin.IsSpent());
371 : 0 : const auto& simcoin = data.coins[sim->first];
372 [ # # # # ]: 0 : assert(simcoin.out == realcoin.out);
373 [ # # ]: 0 : assert(simcoin.fCoinBase == realcoin.fCoinBase);
374 [ # # ]: 0 : assert(realcoin.nHeight == sim->second);
375 : : }
376 : 0 : },
377 : :
378 : 0 : [&]() { // Uncache
379 : 0 : uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
380 : : // Apply to real caches (there is no equivalent in our simulation).
381 : 0 : caches.back()->Uncache(data.outpoints[outpointidx]);
382 : 0 : },
383 : :
384 : 0 : [&]() { // Add a cache level (if not already at the max).
385 [ # # ]: 0 : if (caches.size() != MAX_CACHES) {
386 : : // Apply to real caches.
387 [ # # ]: 0 : caches.emplace_back(new CCoinsViewCache(&*caches.back(), /*deterministic=*/true));
388 : : // Apply to simulation data.
389 : 0 : sim_caches[caches.size()].Wipe();
390 : 0 : }
391 : 0 : },
392 : :
393 : 0 : [&]() { // Remove a cache level.
394 : : // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data).
395 : 0 : caches.back()->SanityCheck();
396 : 0 : caches.pop_back();
397 : 0 : },
398 : :
399 : 0 : [&]() { // Flush.
400 : : // Apply to simulation data.
401 : 0 : flush();
402 : : // Apply to real caches.
403 : 0 : caches.back()->Flush();
404 : 0 : },
405 : :
406 : 0 : [&]() { // Sync.
407 : : // Apply to simulation data (note that in our simulation, syncing and flushing is the same thing).
408 : 0 : flush();
409 : : // Apply to real caches.
410 : 0 : caches.back()->Sync();
411 : 0 : },
412 : :
413 : 0 : [&]() { // Flush + ReallocateCache.
414 : : // Apply to simulation data.
415 : 0 : flush();
416 : : // Apply to real caches.
417 : 0 : caches.back()->Flush();
418 : 0 : caches.back()->ReallocateCache();
419 : 0 : },
420 : :
421 : 0 : [&]() { // GetCacheSize
422 : 0 : (void)caches.back()->GetCacheSize();
423 : 0 : },
424 : :
425 : 0 : [&]() { // DynamicMemoryUsage
426 : 0 : (void)caches.back()->DynamicMemoryUsage();
427 : 0 : },
428 : :
429 : 0 : [&]() { // Change height
430 : 0 : current_height = provider.ConsumeIntegralInRange<uint32_t>(1, current_height - 1);
431 : 0 : }
432 : : );
433 : 0 : }
434 : :
435 : : // Sanity check all the remaining caches
436 [ # # ]: 0 : for (const auto& cache : caches) {
437 [ # # ]: 0 : cache->SanityCheck();
438 : : }
439 : :
440 : : // Full comparison between caches and simulation data, from bottom to top,
441 : : // as AccessCoin on a higher cache may affect caches below it.
442 [ # # ]: 0 : for (unsigned sim_idx = 1; sim_idx <= caches.size(); ++sim_idx) {
443 [ # # ]: 0 : auto& cache = *caches[sim_idx - 1];
444 : 0 : size_t cache_size = 0;
445 : :
446 [ # # ]: 0 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
447 [ # # ]: 0 : cache_size += cache.HaveCoinInCache(data.outpoints[outpointidx]);
448 [ # # ]: 0 : const auto& real = cache.AccessCoin(data.outpoints[outpointidx]);
449 [ # # ]: 0 : auto sim = lookup(outpointidx, sim_idx);
450 [ # # ]: 0 : if (!sim.has_value()) {
451 [ # # # # ]: 0 : assert(real.IsSpent());
452 : 0 : } else {
453 [ # # # # ]: 0 : assert(!real.IsSpent());
454 [ # # # # : 0 : assert(real.out == data.coins[sim->first].out);
# # ]
455 [ # # # # ]: 0 : assert(real.fCoinBase == data.coins[sim->first].fCoinBase);
456 [ # # # # ]: 0 : assert(real.nHeight == sim->second);
457 : : }
458 : 0 : }
459 : :
460 : : // HaveCoinInCache ignores spent coins, so GetCacheSize() may exceed it. */
461 [ # # # # ]: 0 : assert(cache.GetCacheSize() >= cache_size);
462 : 0 : }
463 : :
464 : : // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0].
465 [ # # ]: 0 : for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
466 [ # # ]: 0 : Coin realcoin;
467 [ # # ]: 0 : bool real = bottom.GetCoin(data.outpoints[outpointidx], realcoin);
468 [ # # ]: 0 : auto sim = lookup(outpointidx, 0);
469 [ # # ]: 0 : if (!sim.has_value()) {
470 [ # # # # : 0 : assert(!real || realcoin.IsSpent());
# # ]
471 : 0 : } else {
472 [ # # # # : 0 : assert(real && !realcoin.IsSpent());
# # ]
473 [ # # # # : 0 : assert(realcoin.out == data.coins[sim->first].out);
# # ]
474 [ # # # # ]: 0 : assert(realcoin.fCoinBase == data.coins[sim->first].fCoinBase);
475 [ # # # # ]: 0 : assert(realcoin.nHeight == sim->second);
476 : : }
477 : 0 : }
478 : 0 : }
|