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