/root/bitcoin/src/test/fuzz/coinscache_sim.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | 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 | | PrecomputedData() |
38 | 0 | { |
39 | 0 | static const uint8_t PREFIX_O[1] = {'o'}; /** Hash prefix for outpoint hashes. */ |
40 | 0 | static const uint8_t PREFIX_S[1] = {'s'}; /** Hash prefix for coins scriptPubKeys. */ |
41 | 0 | 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) { Branch (43:30): [True: 0, False: 0]
|
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 | uint256 txid; |
47 | 0 | CSHA256().Write(PREFIX_O, 1).Write(ser, sizeof(ser)).Finalize(txid.begin()); |
48 | 0 | outpoints[i].hash = Txid::FromUint256(txid); |
49 | 0 | outpoints[i].n = i; |
50 | 0 | } |
51 | |
|
52 | 0 | for (uint32_t i = 0; i < NUM_COINS; ++i) { Branch (52:30): [True: 0, False: 0]
|
53 | 0 | const uint8_t ser[4] = {uint8_t(i), uint8_t(i >> 8), uint8_t(i >> 16), uint8_t(i >> 24)}; |
54 | 0 | uint256 hash; |
55 | 0 | 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 | 0 | switch (i % 5U) { Branch (58:21): [True: 0, False: 0]
|
59 | 0 | case 0: /* P2PKH */ Branch (59:13): [True: 0, False: 0]
|
60 | 0 | coins[i].out.scriptPubKey.resize(25); |
61 | 0 | coins[i].out.scriptPubKey[0] = OP_DUP; |
62 | 0 | coins[i].out.scriptPubKey[1] = OP_HASH160; |
63 | 0 | coins[i].out.scriptPubKey[2] = 20; |
64 | 0 | std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 3); |
65 | 0 | coins[i].out.scriptPubKey[23] = OP_EQUALVERIFY; |
66 | 0 | coins[i].out.scriptPubKey[24] = OP_CHECKSIG; |
67 | 0 | break; |
68 | 0 | case 1: /* P2SH */ Branch (68:13): [True: 0, False: 0]
|
69 | 0 | coins[i].out.scriptPubKey.resize(23); |
70 | 0 | coins[i].out.scriptPubKey[0] = OP_HASH160; |
71 | 0 | coins[i].out.scriptPubKey[1] = 20; |
72 | 0 | std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2); |
73 | 0 | coins[i].out.scriptPubKey[12] = OP_EQUAL; |
74 | 0 | break; |
75 | 0 | case 2: /* P2WPKH */ Branch (75:13): [True: 0, False: 0]
|
76 | 0 | coins[i].out.scriptPubKey.resize(22); |
77 | 0 | coins[i].out.scriptPubKey[0] = OP_0; |
78 | 0 | coins[i].out.scriptPubKey[1] = 20; |
79 | 0 | std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2); |
80 | 0 | break; |
81 | 0 | case 3: /* P2WSH */ Branch (81:13): [True: 0, False: 0]
|
82 | 0 | coins[i].out.scriptPubKey.resize(34); |
83 | 0 | coins[i].out.scriptPubKey[0] = OP_0; |
84 | 0 | coins[i].out.scriptPubKey[1] = 32; |
85 | 0 | std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2); |
86 | 0 | break; |
87 | 0 | case 4: /* P2TR */ Branch (87:13): [True: 0, False: 0]
|
88 | 0 | coins[i].out.scriptPubKey.resize(34); |
89 | 0 | coins[i].out.scriptPubKey[0] = OP_1; |
90 | 0 | coins[i].out.scriptPubKey[1] = 32; |
91 | 0 | std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2); |
92 | 0 | break; |
93 | 0 | } |
94 | | /* Hash again to construct nValue and fCoinBase. */ |
95 | 0 | CSHA256().Write(PREFIX_M, 1).Write(ser, sizeof(ser)).Finalize(hash.begin()); |
96 | 0 | coins[i].out.nValue = CAmount(hash.GetUint64(0) % MAX_MONEY); |
97 | 0 | coins[i].fCoinBase = (hash.GetUint64(1) & 7) == 0; |
98 | 0 | coins[i].nHeight = 0; /* Real nHeight used in simulation is set dynamically. */ |
99 | 0 | } |
100 | 0 | } |
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 | 0 | void Wipe() { |
132 | 0 | for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) { Branch (132:30): [True: 0, False: 0]
|
133 | 0 | entry[i].entrytype = EntryType::NONE; |
134 | 0 | } |
135 | 0 | } |
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 | | class CoinsViewBottom final : public CCoinsView |
145 | | { |
146 | | std::map<COutPoint, Coin> m_data; |
147 | | |
148 | | public: |
149 | | std::optional<Coin> GetCoin(const COutPoint& outpoint) const final |
150 | 0 | { |
151 | | // TODO GetCoin shouldn't return spent coins |
152 | 0 | if (auto it = m_data.find(outpoint); it != m_data.end()) return it->second; Branch (152:46): [True: 0, False: 0]
|
153 | 0 | return std::nullopt; |
154 | 0 | } |
155 | | |
156 | | bool HaveCoin(const COutPoint& outpoint) const final |
157 | 0 | { |
158 | 0 | return m_data.count(outpoint); |
159 | 0 | } |
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 | | bool BatchWrite(CoinsViewCacheCursor& cursor, const uint256&) final |
167 | 0 | { |
168 | 0 | for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) { Branch (168:39): [True: 0, False: 0]
|
169 | 0 | if (it->second.IsDirty()) { Branch (169:17): [True: 0, False: 0]
|
170 | 0 | if (it->second.coin.IsSpent() && (it->first.n % 5) != 4) { Branch (170:21): [True: 0, False: 0]
Branch (170:50): [True: 0, False: 0]
|
171 | 0 | m_data.erase(it->first); |
172 | 0 | } else if (cursor.WillErase(*it)) { Branch (172:28): [True: 0, False: 0]
|
173 | 0 | m_data[it->first] = std::move(it->second.coin); |
174 | 0 | } else { |
175 | 0 | m_data[it->first] = it->second.coin; |
176 | 0 | } |
177 | 0 | } else { |
178 | | /* For non-dirty entries being written, compare them with what we have. */ |
179 | 0 | auto it2 = m_data.find(it->first); |
180 | 0 | if (it->second.coin.IsSpent()) { Branch (180:21): [True: 0, False: 0]
|
181 | 0 | assert(it2 == m_data.end() || it2->second.IsSpent()); |
182 | 0 | } 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 | 0 | } |
188 | 0 | } |
189 | 0 | } |
190 | 0 | return true; |
191 | 0 | } |
192 | | }; |
193 | | |
194 | | } // namespace |
195 | | |
196 | | FUZZ_TARGET(coinscache_sim) |
197 | 0 | { |
198 | | /** Precomputed COutPoint and CCoins values. */ |
199 | 0 | static const PrecomputedData data; |
200 | | |
201 | | /** Dummy coinsview instance (base of the hierarchy). */ |
202 | 0 | CoinsViewBottom bottom; |
203 | | /** Real CCoinsViewCache objects. */ |
204 | 0 | std::vector<std::unique_ptr<CCoinsViewCache>> caches; |
205 | | /** Simulated cache data (sim_caches[0] matches bottom, sim_caches[i+1] matches caches[i]). */ |
206 | 0 | CacheLevel sim_caches[MAX_CACHES + 1]; |
207 | | /** Current height in the simulation. */ |
208 | 0 | uint32_t current_height = 1U; |
209 | | |
210 | | // Initialize bottom simulated cache. |
211 | 0 | sim_caches[0].Wipe(); |
212 | | |
213 | | /** Helper lookup function in the simulated cache stack. */ |
214 | 0 | auto lookup = [&](uint32_t outpointidx, int sim_idx = -1) -> std::optional<std::pair<coinidx_type, uint32_t>> { |
215 | 0 | uint32_t cache_idx = sim_idx == -1 ? caches.size() : sim_idx; Branch (215:30): [True: 0, False: 0]
|
216 | 0 | while (true) { Branch (216:16): [Folded - Ignored]
|
217 | 0 | const auto& entry = sim_caches[cache_idx].entry[outpointidx]; |
218 | 0 | if (entry.entrytype == EntryType::UNSPENT) { Branch (218:17): [True: 0, False: 0]
|
219 | 0 | return {{entry.coinidx, entry.height}}; |
220 | 0 | } else if (entry.entrytype == EntryType::SPENT) { Branch (220:24): [True: 0, False: 0]
|
221 | 0 | return std::nullopt; |
222 | 0 | }; |
223 | 0 | if (cache_idx == 0) break; Branch (223:17): [True: 0, False: 0]
|
224 | 0 | --cache_idx; |
225 | 0 | } |
226 | 0 | return std::nullopt; |
227 | 0 | }; |
228 | | |
229 | | /** Flush changes in top cache to the one below. */ |
230 | 0 | auto flush = [&]() { |
231 | 0 | assert(caches.size() >= 1); |
232 | 0 | auto& cache = sim_caches[caches.size()]; |
233 | 0 | auto& prev_cache = sim_caches[caches.size() - 1]; |
234 | 0 | for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) { Branch (234:40): [True: 0, False: 0]
|
235 | 0 | if (cache.entry[outpointidx].entrytype != EntryType::NONE) { Branch (235:17): [True: 0, False: 0]
|
236 | 0 | prev_cache.entry[outpointidx] = cache.entry[outpointidx]; |
237 | 0 | cache.entry[outpointidx].entrytype = EntryType::NONE; |
238 | 0 | } |
239 | 0 | } |
240 | 0 | }; |
241 | | |
242 | | // Main simulation loop: read commands from the fuzzer input, and apply them |
243 | | // to both the real cache stack and the simulation. |
244 | 0 | FuzzedDataProvider provider(buffer.data(), buffer.size()); |
245 | 0 | LIMITED_WHILE(provider.remaining_bytes(), 10000) { |
246 | | // Every operation (except "Change height") moves current height forward, |
247 | | // so it functions as a kind of epoch, making ~all UTXOs unique. |
248 | 0 | ++current_height; |
249 | | // Make sure there is always at least one CCoinsViewCache. |
250 | 0 | if (caches.empty()) { Branch (250:13): [True: 0, False: 0]
|
251 | 0 | caches.emplace_back(new CCoinsViewCache(&bottom, /*deterministic=*/true)); |
252 | 0 | sim_caches[caches.size()].Wipe(); |
253 | 0 | } |
254 | | |
255 | | // Execute command. |
256 | 0 | CallOneOf( |
257 | 0 | provider, |
258 | |
|
259 | 0 | [&]() { // GetCoin |
260 | 0 | uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1); |
261 | | // Look up in simulation data. |
262 | 0 | auto sim = lookup(outpointidx); |
263 | | // Look up in real caches. |
264 | 0 | auto realcoin = caches.back()->GetCoin(data.outpoints[outpointidx]); |
265 | | // Compare results. |
266 | 0 | if (!sim.has_value()) { Branch (266:21): [True: 0, False: 0]
|
267 | 0 | assert(!realcoin || realcoin->IsSpent()); |
268 | 0 | } else { |
269 | 0 | assert(realcoin && !realcoin->IsSpent()); |
270 | 0 | const auto& simcoin = data.coins[sim->first]; |
271 | 0 | assert(realcoin->out == simcoin.out); |
272 | 0 | assert(realcoin->fCoinBase == simcoin.fCoinBase); |
273 | 0 | assert(realcoin->nHeight == sim->second); |
274 | 0 | } |
275 | 0 | }, |
276 | |
|
277 | 0 | [&]() { // HaveCoin |
278 | 0 | uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1); |
279 | | // Look up in simulation data. |
280 | 0 | auto sim = lookup(outpointidx); |
281 | | // Look up in real caches. |
282 | 0 | auto real = caches.back()->HaveCoin(data.outpoints[outpointidx]); |
283 | | // Compare results. |
284 | 0 | assert(sim.has_value() == real); |
285 | 0 | }, |
286 | |
|
287 | 0 | [&]() { // HaveCoinInCache |
288 | 0 | uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1); |
289 | | // Invoke on real cache (there is no equivalent in simulation, so nothing to compare result with). |
290 | 0 | (void)caches.back()->HaveCoinInCache(data.outpoints[outpointidx]); |
291 | 0 | }, |
292 | |
|
293 | 0 | [&]() { // AccessCoin |
294 | 0 | uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1); |
295 | | // Look up in simulation data. |
296 | 0 | auto sim = lookup(outpointidx); |
297 | | // Look up in real caches. |
298 | 0 | const auto& realcoin = caches.back()->AccessCoin(data.outpoints[outpointidx]); |
299 | | // Compare results. |
300 | 0 | if (!sim.has_value()) { Branch (300:21): [True: 0, False: 0]
|
301 | 0 | assert(realcoin.IsSpent()); |
302 | 0 | } else { |
303 | 0 | assert(!realcoin.IsSpent()); |
304 | 0 | const auto& simcoin = data.coins[sim->first]; |
305 | 0 | assert(simcoin.out == realcoin.out); |
306 | 0 | assert(simcoin.fCoinBase == realcoin.fCoinBase); |
307 | 0 | assert(realcoin.nHeight == sim->second); |
308 | 0 | } |
309 | 0 | }, |
310 | |
|
311 | 0 | [&]() { // AddCoin (only possible_overwrite if necessary) |
312 | 0 | uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1); |
313 | 0 | uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1); |
314 | | // Look up in simulation data (to know whether we must set possible_overwrite or not). |
315 | 0 | auto sim = lookup(outpointidx); |
316 | | // Invoke on real caches. |
317 | 0 | Coin coin = data.coins[coinidx]; |
318 | 0 | coin.nHeight = current_height; |
319 | 0 | caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), sim.has_value()); |
320 | | // Apply to simulation data. |
321 | 0 | auto& entry = sim_caches[caches.size()].entry[outpointidx]; |
322 | 0 | entry.entrytype = EntryType::UNSPENT; |
323 | 0 | entry.coinidx = coinidx; |
324 | 0 | entry.height = current_height; |
325 | 0 | }, |
326 | |
|
327 | 0 | [&]() { // AddCoin (always possible_overwrite) |
328 | 0 | uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1); |
329 | 0 | uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1); |
330 | | // Invoke on real caches. |
331 | 0 | Coin coin = data.coins[coinidx]; |
332 | 0 | coin.nHeight = current_height; |
333 | 0 | caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), true); |
334 | | // Apply to simulation data. |
335 | 0 | auto& entry = sim_caches[caches.size()].entry[outpointidx]; |
336 | 0 | entry.entrytype = EntryType::UNSPENT; |
337 | 0 | entry.coinidx = coinidx; |
338 | 0 | entry.height = current_height; |
339 | 0 | }, |
340 | |
|
341 | 0 | [&]() { // SpendCoin (moveto = nullptr) |
342 | 0 | uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1); |
343 | | // Invoke on real caches. |
344 | 0 | caches.back()->SpendCoin(data.outpoints[outpointidx], nullptr); |
345 | | // Apply to simulation data. |
346 | 0 | sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT; |
347 | 0 | }, |
348 | |
|
349 | 0 | [&]() { // SpendCoin (with moveto) |
350 | 0 | uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1); |
351 | | // Look up in simulation data (to compare the returned *moveto with). |
352 | 0 | auto sim = lookup(outpointidx); |
353 | | // Invoke on real caches. |
354 | 0 | Coin realcoin; |
355 | 0 | caches.back()->SpendCoin(data.outpoints[outpointidx], &realcoin); |
356 | | // Apply to simulation data. |
357 | 0 | sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT; |
358 | | // Compare *moveto with the value expected based on simulation data. |
359 | 0 | if (!sim.has_value()) { Branch (359:21): [True: 0, False: 0]
|
360 | 0 | assert(realcoin.IsSpent()); |
361 | 0 | } else { |
362 | 0 | assert(!realcoin.IsSpent()); |
363 | 0 | const auto& simcoin = data.coins[sim->first]; |
364 | 0 | assert(simcoin.out == realcoin.out); |
365 | 0 | assert(simcoin.fCoinBase == realcoin.fCoinBase); |
366 | 0 | assert(realcoin.nHeight == sim->second); |
367 | 0 | } |
368 | 0 | }, |
369 | |
|
370 | 0 | [&]() { // Uncache |
371 | 0 | uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1); |
372 | | // Apply to real caches (there is no equivalent in our simulation). |
373 | 0 | caches.back()->Uncache(data.outpoints[outpointidx]); |
374 | 0 | }, |
375 | |
|
376 | 0 | [&]() { // Add a cache level (if not already at the max). |
377 | 0 | if (caches.size() != MAX_CACHES) { Branch (377:21): [True: 0, False: 0]
|
378 | | // Apply to real caches. |
379 | 0 | caches.emplace_back(new CCoinsViewCache(&*caches.back(), /*deterministic=*/true)); |
380 | | // Apply to simulation data. |
381 | 0 | sim_caches[caches.size()].Wipe(); |
382 | 0 | } |
383 | 0 | }, |
384 | |
|
385 | 0 | [&]() { // Remove a cache level. |
386 | | // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data). |
387 | 0 | caches.back()->SanityCheck(); |
388 | 0 | caches.pop_back(); |
389 | 0 | }, |
390 | |
|
391 | 0 | [&]() { // Flush. |
392 | | // Apply to simulation data. |
393 | 0 | flush(); |
394 | | // Apply to real caches. |
395 | 0 | caches.back()->Flush(); |
396 | 0 | }, |
397 | |
|
398 | 0 | [&]() { // Sync. |
399 | | // Apply to simulation data (note that in our simulation, syncing and flushing is the same thing). |
400 | 0 | flush(); |
401 | | // Apply to real caches. |
402 | 0 | caches.back()->Sync(); |
403 | 0 | }, |
404 | |
|
405 | 0 | [&]() { // Flush + ReallocateCache. |
406 | | // Apply to simulation data. |
407 | 0 | flush(); |
408 | | // Apply to real caches. |
409 | 0 | caches.back()->Flush(); |
410 | 0 | caches.back()->ReallocateCache(); |
411 | 0 | }, |
412 | |
|
413 | 0 | [&]() { // GetCacheSize |
414 | 0 | (void)caches.back()->GetCacheSize(); |
415 | 0 | }, |
416 | |
|
417 | 0 | [&]() { // DynamicMemoryUsage |
418 | 0 | (void)caches.back()->DynamicMemoryUsage(); |
419 | 0 | }, |
420 | |
|
421 | 0 | [&]() { // Change height |
422 | 0 | current_height = provider.ConsumeIntegralInRange<uint32_t>(1, current_height - 1); |
423 | 0 | } |
424 | 0 | ); |
425 | 0 | } |
426 | | |
427 | | // Sanity check all the remaining caches |
428 | 0 | for (const auto& cache : caches) { Branch (428:28): [True: 0, False: 0]
|
429 | 0 | cache->SanityCheck(); |
430 | 0 | } |
431 | | |
432 | | // Full comparison between caches and simulation data, from bottom to top, |
433 | | // as AccessCoin on a higher cache may affect caches below it. |
434 | 0 | for (unsigned sim_idx = 1; sim_idx <= caches.size(); ++sim_idx) { Branch (434:32): [True: 0, False: 0]
|
435 | 0 | auto& cache = *caches[sim_idx - 1]; |
436 | 0 | size_t cache_size = 0; |
437 | |
|
438 | 0 | for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) { Branch (438:40): [True: 0, False: 0]
|
439 | 0 | cache_size += cache.HaveCoinInCache(data.outpoints[outpointidx]); |
440 | 0 | const auto& real = cache.AccessCoin(data.outpoints[outpointidx]); |
441 | 0 | auto sim = lookup(outpointidx, sim_idx); |
442 | 0 | if (!sim.has_value()) { Branch (442:17): [True: 0, False: 0]
|
443 | 0 | assert(real.IsSpent()); |
444 | 0 | } else { |
445 | 0 | assert(!real.IsSpent()); |
446 | 0 | assert(real.out == data.coins[sim->first].out); |
447 | 0 | assert(real.fCoinBase == data.coins[sim->first].fCoinBase); |
448 | 0 | assert(real.nHeight == sim->second); |
449 | 0 | } |
450 | 0 | } |
451 | | |
452 | | // HaveCoinInCache ignores spent coins, so GetCacheSize() may exceed it. */ |
453 | 0 | assert(cache.GetCacheSize() >= cache_size); |
454 | 0 | } |
455 | | |
456 | | // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0]. |
457 | 0 | for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) { Branch (457:36): [True: 0, False: 0]
|
458 | 0 | auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]); |
459 | 0 | auto sim = lookup(outpointidx, 0); |
460 | 0 | if (!sim.has_value()) { Branch (460:13): [True: 0, False: 0]
|
461 | 0 | assert(!realcoin || realcoin->IsSpent()); |
462 | 0 | } else { |
463 | 0 | assert(realcoin && !realcoin->IsSpent()); |
464 | 0 | assert(realcoin->out == data.coins[sim->first].out); |
465 | 0 | assert(realcoin->fCoinBase == data.coins[sim->first].fCoinBase); |
466 | 0 | assert(realcoin->nHeight == sim->second); |
467 | 0 | } |
468 | 0 | } |
469 | 0 | } |