Bitcoin Core  31.0.0
P2P Digital Currency
coinscache_sim.cpp
Go to the documentation of this file.
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>
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 
21 constexpr uint32_t NUM_OUTPOINTS = 256;
23 constexpr uint32_t NUM_COINS = 256;
25 constexpr uint32_t MAX_CACHES = 4;
27 using coinidx_type = uint8_t;
28 
29 struct PrecomputedData
30 {
32  COutPoint outpoints[NUM_OUTPOINTS];
33 
35  Coin coins[NUM_COINS];
36 
37  PrecomputedData()
38  {
39  static const uint8_t PREFIX_O[1] = {'o'};
40  static const uint8_t PREFIX_S[1] = {'s'};
41  static const uint8_t PREFIX_M[1] = {'m'};
43  for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {
44  uint32_t idx = (i * 1200U) >> 12; /* Map 3 or 4 entries to same txid. */
45  const uint8_t ser[4] = {uint8_t(idx), uint8_t(idx >> 8), uint8_t(idx >> 16), uint8_t(idx >> 24)};
46  uint256 txid;
47  CSHA256().Write(PREFIX_O, 1).Write(ser, sizeof(ser)).Finalize(txid.begin());
48  outpoints[i].hash = Txid::FromUint256(txid);
49  outpoints[i].n = i;
50  }
51 
52  for (uint32_t i = 0; i < NUM_COINS; ++i) {
53  const uint8_t ser[4] = {uint8_t(i), uint8_t(i >> 8), uint8_t(i >> 16), uint8_t(i >> 24)};
54  uint256 hash;
55  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  switch (i % 5U) {
59  case 0: /* P2PKH */
60  coins[i].out.scriptPubKey.resize(25);
61  coins[i].out.scriptPubKey[0] = OP_DUP;
62  coins[i].out.scriptPubKey[1] = OP_HASH160;
63  coins[i].out.scriptPubKey[2] = 20;
64  std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 3);
65  coins[i].out.scriptPubKey[23] = OP_EQUALVERIFY;
66  coins[i].out.scriptPubKey[24] = OP_CHECKSIG;
67  break;
68  case 1: /* P2SH */
69  coins[i].out.scriptPubKey.resize(23);
70  coins[i].out.scriptPubKey[0] = OP_HASH160;
71  coins[i].out.scriptPubKey[1] = 20;
72  std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);
73  coins[i].out.scriptPubKey[12] = OP_EQUAL;
74  break;
75  case 2: /* P2WPKH */
76  coins[i].out.scriptPubKey.resize(22);
77  coins[i].out.scriptPubKey[0] = OP_0;
78  coins[i].out.scriptPubKey[1] = 20;
79  std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);
80  break;
81  case 3: /* P2WSH */
82  coins[i].out.scriptPubKey.resize(34);
83  coins[i].out.scriptPubKey[0] = OP_0;
84  coins[i].out.scriptPubKey[1] = 32;
85  std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2);
86  break;
87  case 4: /* P2TR */
88  coins[i].out.scriptPubKey.resize(34);
89  coins[i].out.scriptPubKey[0] = OP_1;
90  coins[i].out.scriptPubKey[1] = 32;
91  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  CSHA256().Write(PREFIX_M, 1).Write(ser, sizeof(ser)).Finalize(hash.begin());
96  coins[i].out.nValue = CAmount(hash.GetUint64(0) % MAX_MONEY);
97  coins[i].fCoinBase = (hash.GetUint64(1) & 7) == 0;
98  coins[i].nHeight = 0; /* Real nHeight used in simulation is set dynamically. */
99  }
100  }
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  void Wipe() {
132  for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {
133  entry[i].entrytype = EntryType::NONE;
134  }
135  }
136 };
137 
142 class CoinsViewBottom final : public CCoinsView
143 {
144  std::map<COutPoint, Coin> m_data;
145 
146 public:
147  std::optional<Coin> GetCoin(const COutPoint& outpoint) const final
148  {
149  if (auto it{m_data.find(outpoint)}; it != m_data.end()) {
150  assert(!it->second.IsSpent());
151  return it->second;
152  }
153  return std::nullopt;
154  }
155 
156  uint256 GetBestBlock() const final { return {}; }
157  std::vector<uint256> GetHeadBlocks() const final { return {}; }
158  std::unique_ptr<CCoinsViewCursor> Cursor() const final { return {}; }
159  size_t EstimateSize() const final { return m_data.size(); }
160 
161  void BatchWrite(CoinsViewCacheCursor& cursor, const uint256&) final
162  {
163  for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {
164  if (it->second.IsDirty()) {
165  if (it->second.coin.IsSpent()) {
166  m_data.erase(it->first);
167  } else {
168  if (cursor.WillErase(*it)) {
169  m_data[it->first] = std::move(it->second.coin);
170  } else {
171  m_data[it->first] = it->second.coin;
172  }
173  }
174  } else {
175  /* For non-dirty entries being written, compare them with what we have. */
176  auto it2 = m_data.find(it->first);
177  if (it->second.coin.IsSpent()) {
178  assert(it2 == m_data.end());
179  } else {
180  assert(it2 != m_data.end());
181  assert(it->second.coin.out == it2->second.out);
182  assert(it->second.coin.fCoinBase == it2->second.fCoinBase);
183  assert(it->second.coin.nHeight == it2->second.nHeight);
184  }
185  }
186  }
187  }
188 };
189 
190 } // namespace
191 
192 FUZZ_TARGET(coinscache_sim)
193 {
195  static const PrecomputedData data;
196 
198  CoinsViewBottom bottom;
200  std::vector<std::unique_ptr<CCoinsViewCache>> caches;
202  CacheLevel sim_caches[MAX_CACHES + 1];
204  uint32_t current_height = 1U;
205 
206  // Initialize bottom simulated cache.
207  sim_caches[0].Wipe();
208 
210  auto lookup = [&](uint32_t outpointidx, int sim_idx = -1) -> std::optional<std::pair<coinidx_type, uint32_t>> {
211  uint32_t cache_idx = sim_idx == -1 ? caches.size() : sim_idx;
212  while (true) {
213  const auto& entry = sim_caches[cache_idx].entry[outpointidx];
214  if (entry.entrytype == EntryType::UNSPENT) {
215  return {{entry.coinidx, entry.height}};
216  } else if (entry.entrytype == EntryType::SPENT) {
217  return std::nullopt;
218  };
219  if (cache_idx == 0) break;
220  --cache_idx;
221  }
222  return std::nullopt;
223  };
224 
226  auto flush = [&]() {
227  assert(caches.size() >= 1);
228  auto& cache = sim_caches[caches.size()];
229  auto& prev_cache = sim_caches[caches.size() - 1];
230  for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
231  if (cache.entry[outpointidx].entrytype != EntryType::NONE) {
232  prev_cache.entry[outpointidx] = cache.entry[outpointidx];
233  cache.entry[outpointidx].entrytype = EntryType::NONE;
234  }
235  }
236  };
237 
238  // Main simulation loop: read commands from the fuzzer input, and apply them
239  // to both the real cache stack and the simulation.
240  FuzzedDataProvider provider(buffer.data(), buffer.size());
241  LIMITED_WHILE(provider.remaining_bytes(), 10000) {
242  // Every operation (except "Change height") moves current height forward,
243  // so it functions as a kind of epoch, making ~all UTXOs unique.
244  ++current_height;
245  // Make sure there is always at least one CCoinsViewCache.
246  if (caches.empty()) {
247  caches.emplace_back(new CCoinsViewCache(&bottom, /*deterministic=*/true));
248  sim_caches[caches.size()].Wipe();
249  }
250 
251  // Execute command.
252  CallOneOf(
253  provider,
254 
255  [&]() { // GetCoin
256  uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
257  // Look up in simulation data.
258  auto sim = lookup(outpointidx);
259  // Look up in real caches.
260  auto realcoin = provider.ConsumeBool() ?
261  caches.back()->PeekCoin(data.outpoints[outpointidx]) :
262  caches.back()->GetCoin(data.outpoints[outpointidx]);
263  // Compare results.
264  if (!sim.has_value()) {
265  assert(!realcoin);
266  } else {
267  assert(realcoin && !realcoin->IsSpent());
268  const auto& simcoin = data.coins[sim->first];
269  assert(realcoin->out == simcoin.out);
270  assert(realcoin->fCoinBase == simcoin.fCoinBase);
271  assert(realcoin->nHeight == sim->second);
272  }
273  },
274 
275  [&]() { // HaveCoin
276  uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
277  // Look up in simulation data.
278  auto sim = lookup(outpointidx);
279  // Look up in real caches.
280  auto real = caches.back()->HaveCoin(data.outpoints[outpointidx]);
281  // Compare results.
282  assert(sim.has_value() == real);
283  },
284 
285  [&]() { // HaveCoinInCache
286  uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
287  // Invoke on real cache (there is no equivalent in simulation, so nothing to compare result with).
288  (void)caches.back()->HaveCoinInCache(data.outpoints[outpointidx]);
289  },
290 
291  [&]() { // AccessCoin
292  uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
293  // Look up in simulation data.
294  auto sim = lookup(outpointidx);
295  // Look up in real caches.
296  const auto& realcoin = caches.back()->AccessCoin(data.outpoints[outpointidx]);
297  // Compare results.
298  if (!sim.has_value()) {
299  assert(realcoin.IsSpent());
300  } else {
301  assert(!realcoin.IsSpent());
302  const auto& simcoin = data.coins[sim->first];
303  assert(simcoin.out == realcoin.out);
304  assert(simcoin.fCoinBase == realcoin.fCoinBase);
305  assert(realcoin.nHeight == sim->second);
306  }
307  },
308 
309  [&]() { // AddCoin (only possible_overwrite if necessary)
310  uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
311  uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1);
312  // Look up in simulation data (to know whether we must set possible_overwrite or not).
313  auto sim = lookup(outpointidx);
314  // Invoke on real caches.
315  Coin coin = data.coins[coinidx];
316  coin.nHeight = current_height;
317  caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), sim.has_value());
318  // Apply to simulation data.
319  auto& entry = sim_caches[caches.size()].entry[outpointidx];
320  entry.entrytype = EntryType::UNSPENT;
321  entry.coinidx = coinidx;
322  entry.height = current_height;
323  },
324 
325  [&]() { // AddCoin (always possible_overwrite)
326  uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
327  uint32_t coinidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_COINS - 1);
328  // Invoke on real caches.
329  Coin coin = data.coins[coinidx];
330  coin.nHeight = current_height;
331  caches.back()->AddCoin(data.outpoints[outpointidx], std::move(coin), true);
332  // Apply to simulation data.
333  auto& entry = sim_caches[caches.size()].entry[outpointidx];
334  entry.entrytype = EntryType::UNSPENT;
335  entry.coinidx = coinidx;
336  entry.height = current_height;
337  },
338 
339  [&]() { // SpendCoin (moveto = nullptr)
340  uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
341  // Invoke on real caches.
342  caches.back()->SpendCoin(data.outpoints[outpointidx], nullptr);
343  // Apply to simulation data.
344  sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
345  },
346 
347  [&]() { // SpendCoin (with moveto)
348  uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
349  // Look up in simulation data (to compare the returned *moveto with).
350  auto sim = lookup(outpointidx);
351  // Invoke on real caches.
352  Coin realcoin;
353  caches.back()->SpendCoin(data.outpoints[outpointidx], &realcoin);
354  // Apply to simulation data.
355  sim_caches[caches.size()].entry[outpointidx].entrytype = EntryType::SPENT;
356  // Compare *moveto with the value expected based on simulation data.
357  if (!sim.has_value()) {
358  assert(realcoin.IsSpent());
359  } else {
360  assert(!realcoin.IsSpent());
361  const auto& simcoin = data.coins[sim->first];
362  assert(simcoin.out == realcoin.out);
363  assert(simcoin.fCoinBase == realcoin.fCoinBase);
364  assert(realcoin.nHeight == sim->second);
365  }
366  },
367 
368  [&]() { // Uncache
369  uint32_t outpointidx = provider.ConsumeIntegralInRange<uint32_t>(0, NUM_OUTPOINTS - 1);
370  // Apply to real caches (there is no equivalent in our simulation).
371  caches.back()->Uncache(data.outpoints[outpointidx]);
372  },
373 
374  [&]() { // Add a cache level (if not already at the max).
375  if (caches.size() != MAX_CACHES) {
376  // Apply to real caches.
377  if (provider.ConsumeBool()) {
378  caches.emplace_back(new CCoinsViewCache(&*caches.back(), /*deterministic=*/true));
379  } else {
380  caches.emplace_back(new CoinsViewOverlay(&*caches.back(), /*deterministic=*/true));
381  }
382  // Apply to simulation data.
383  sim_caches[caches.size()].Wipe();
384  }
385  },
386 
387  [&]() { // Remove a cache level.
388  // Apply to real caches (this reduces caches.size(), implicitly doing the same on the simulation data).
389  caches.back()->SanityCheck();
390  caches.pop_back();
391  },
392 
393  [&]() { // Flush.
394  // Apply to simulation data.
395  flush();
396  // Apply to real caches.
397  caches.back()->Flush(/*reallocate_cache=*/provider.ConsumeBool());
398  },
399 
400  [&]() { // Sync.
401  // Apply to simulation data (note that in our simulation, syncing and flushing is the same thing).
402  flush();
403  // Apply to real caches.
404  caches.back()->Sync();
405  },
406 
407  [&]() { // Reset.
408  sim_caches[caches.size()].Wipe();
409  // Apply to real caches.
410  {
411  const auto reset_guard{caches.back()->CreateResetGuard()};
412  }
413  },
414 
415  [&]() { // GetCacheSize
416  (void)caches.back()->GetCacheSize();
417  },
418 
419  [&]() { // DynamicMemoryUsage
420  (void)caches.back()->DynamicMemoryUsage();
421  },
422 
423  [&]() { // Change height
424  current_height = provider.ConsumeIntegralInRange<uint32_t>(1, current_height - 1);
425  }
426  );
427  }
428 
429  // Sanity check all the remaining caches
430  for (const auto& cache : caches) {
431  cache->SanityCheck();
432  }
433 
434  // Full comparison between caches and simulation data, from bottom to top,
435  // as AccessCoin on a higher cache may affect caches below it.
436  for (unsigned sim_idx = 1; sim_idx <= caches.size(); ++sim_idx) {
437  auto& cache = *caches[sim_idx - 1];
438  size_t cache_size = 0;
439 
440  for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
441  cache_size += cache.HaveCoinInCache(data.outpoints[outpointidx]);
442  const auto& real = cache.AccessCoin(data.outpoints[outpointidx]);
443  auto sim = lookup(outpointidx, sim_idx);
444  if (!sim.has_value()) {
445  assert(real.IsSpent());
446  } else {
447  assert(!real.IsSpent());
448  assert(real.out == data.coins[sim->first].out);
449  assert(real.fCoinBase == data.coins[sim->first].fCoinBase);
450  assert(real.nHeight == sim->second);
451  }
452  }
453 
454  // HaveCoinInCache ignores spent coins, so GetCacheSize() may exceed it. */
455  assert(cache.GetCacheSize() >= cache_size);
456  }
457 
458  // Compare the bottom coinsview (not a CCoinsViewCache) with sim_cache[0].
459  for (uint32_t outpointidx = 0; outpointidx < NUM_OUTPOINTS; ++outpointidx) {
460  auto realcoin = bottom.GetCoin(data.outpoints[outpointidx]);
461  auto sim = lookup(outpointidx, 0);
462  if (!sim.has_value()) {
463  assert(!realcoin);
464  } else {
465  assert(realcoin && !realcoin->IsSpent());
466  assert(realcoin->out == data.coins[sim->first].out);
467  assert(realcoin->fCoinBase == data.coins[sim->first].fCoinBase);
468  assert(realcoin->nHeight == sim->second);
469  }
470  }
471 }
CAmount nValue
Definition: transaction.h:142
CSHA256 & Write(const unsigned char *data, size_t len)
Definition: sha256.cpp:699
bool IsSpent() const
Either this coin never existed (see e.g.
Definition: coins.h:83
void resize(size_type new_size)
Definition: prevector.h:276
assert(!tx.IsCoinBase())
CScript scriptPubKey
Definition: transaction.h:143
A UTXO entry.
Definition: coins.h:34
Definition: script.h:125
CTxOut out
unspent transaction output
Definition: coins.h:38
unsigned int fCoinBase
whether containing transaction was a coinbase
Definition: coins.h:41
#define LIMITED_WHILE(condition, limit)
Can be used to limit a theoretically unbounded loop.
Definition: fuzz.h:22
EntryType
CCoinsViewCache overlay that avoids populating/mutating parent cache layers on cache misses...
Definition: coins.h:538
Definition: script.h:76
constexpr unsigned char * begin()
Definition: uint256.h:100
Definition: script.h:83
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
uint32_t nHeight
at which height this containing transaction was included in the active block chain ...
Definition: coins.h:44
Abstract view on the open txout dataset.
Definition: coins.h:307
Cursor for iterating over the linked list of flagged entries in CCoinsViewCache.
Definition: coins.h:260
Txid hash
Definition: transaction.h:31
uint32_t n
Definition: transaction.h:32
void Finalize(unsigned char hash[OUTPUT_SIZE])
Definition: sha256.cpp:725
virtual std::vector< uint256 > GetHeadBlocks() const
Retrieve the range of blocks that may have been only partially written.
Definition: coins.cpp:20
virtual void BatchWrite(CoinsViewCacheCursor &cursor, const uint256 &hashBlock)
Do a bulk modification (multiple Coin changes + BestBlock change).
Definition: coins.cpp:21
FUZZ_TARGET(coinscache_sim)
An outpoint - a combination of a transaction hash and an index n into its vout.
Definition: transaction.h:28
256-bit opaque blob.
Definition: uint256.h:195
constexpr CAmount SPENT
virtual size_t EstimateSize() const
Estimate database size (0 if not implemented)
Definition: coins.h:342
static transaction_identifier FromUint256(const uint256 &id)
virtual uint256 GetBestBlock() const
Retrieve the block hash whose state this CCoinsView currently represents.
Definition: coins.cpp:19
virtual std::optional< Coin > GetCoin(const COutPoint &outpoint) const
Retrieve the Coin (unspent transaction output) for a given outpoint.
Definition: coins.cpp:17
constexpr uint64_t GetUint64(int pos) const
Definition: uint256.h:108
static constexpr CAmount MAX_MONEY
No amount larger than this (in satoshi) is valid.
Definition: amount.h:26
iterator begin()
Definition: prevector.h:255
size_t CallOneOf(FuzzedDataProvider &fuzzed_data_provider, Callables... callables)
Definition: util.h:35
virtual std::unique_ptr< CCoinsViewCursor > Cursor() const
Get a cursor to iterate over the whole state.
Definition: coins.cpp:26
CCoinsView that adds a memory cache for transactions to another CCoinsView.
Definition: coins.h:367
A hasher class for SHA-256.
Definition: sha256.h:13