/ src / test / fuzz / coins_view.cpp
coins_view.cpp
  1  // Copyright (c) 2020-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 <consensus/amount.h>
  7  #include <consensus/tx_check.h>
  8  #include <consensus/tx_verify.h>
  9  #include <consensus/validation.h>
 10  #include <policy/policy.h>
 11  #include <primitives/transaction.h>
 12  #include <script/interpreter.h>
 13  #include <test/fuzz/FuzzedDataProvider.h>
 14  #include <test/fuzz/fuzz.h>
 15  #include <test/fuzz/util.h>
 16  #include <test/util/setup_common.h>
 17  #include <txdb.h>
 18  #include <util/hasher.h>
 19  
 20  #include <cassert>
 21  #include <algorithm>
 22  #include <cstdint>
 23  #include <functional>
 24  #include <limits>
 25  #include <memory>
 26  #include <optional>
 27  #include <ranges>
 28  #include <stdexcept>
 29  #include <string>
 30  #include <utility>
 31  #include <vector>
 32  
 33  namespace {
 34  const Coin EMPTY_COIN{};
 35  
 36  bool operator==(const Coin& a, const Coin& b)
 37  {
 38      if (a.IsSpent() && b.IsSpent()) return true;
 39      return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && a.out == b.out;
 40  }
 41  
 42  /**
 43   * MutationGuardCoinsViewCache asserts that nothing mutates cacheCoins until
 44   * BatchWrite is called. It keeps a snapshot of the cacheCoins state, which it
 45   * uses for the assertion in BatchWrite. After the call to the superclass
 46   * CCoinsViewCache::BatchWrite returns, it recomputes the snapshot at that
 47   * moment.
 48   */
 49  class MutationGuardCoinsViewCache final : public CCoinsViewCache
 50  {
 51  private:
 52      struct CacheCoinSnapshot {
 53          COutPoint outpoint;
 54          bool dirty{false};
 55          bool fresh{false};
 56          Coin coin;
 57          bool operator==(const CacheCoinSnapshot&) const = default;
 58      };
 59  
 60      std::vector<CacheCoinSnapshot> ComputeCacheCoinsSnapshot() const
 61      {
 62          std::vector<CacheCoinSnapshot> snapshot;
 63          snapshot.reserve(cacheCoins.size());
 64  
 65          for (const auto& [outpoint, entry] : cacheCoins) {
 66              snapshot.emplace_back(outpoint, entry.IsDirty(), entry.IsFresh(), entry.coin);
 67          }
 68  
 69          std::ranges::sort(snapshot, std::less<>{}, &CacheCoinSnapshot::outpoint);
 70          return snapshot;
 71      }
 72  
 73      mutable std::vector<CacheCoinSnapshot> m_expected_snapshot{ComputeCacheCoinsSnapshot()};
 74  
 75  public:
 76      void BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash) override
 77      {
 78          // Nothing must modify cacheCoins other than BatchWrite.
 79          assert(ComputeCacheCoinsSnapshot() == m_expected_snapshot);
 80          CCoinsViewCache::BatchWrite(cursor, block_hash);
 81          m_expected_snapshot = ComputeCacheCoinsSnapshot();
 82      }
 83  
 84      using CCoinsViewCache::CCoinsViewCache;
 85  };
 86  } // namespace
 87  
 88  void initialize_coins_view()
 89  {
 90      static const auto testing_setup = MakeNoLogFileContext<>();
 91  }
 92  
 93  void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsViewCache& coins_view_cache, CCoinsView* backend_coins_view)
 94  {
 95      const bool is_db{dynamic_cast<CCoinsViewDB*>(backend_coins_view) != nullptr};
 96      bool good_data{true};
 97      auto* original_backend{backend_coins_view};
 98  
 99      if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
100      COutPoint random_out_point;
101      Coin random_coin;
102      CMutableTransaction random_mutable_transaction;
103      LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
104      {
105          CallOneOf(
106              fuzzed_data_provider,
107              [&] {
108                  if (random_coin.IsSpent()) {
109                      return;
110                  }
111                  COutPoint outpoint{random_out_point};
112                  Coin coin{random_coin};
113                  if (fuzzed_data_provider.ConsumeBool()) {
114                      // We can only skip the check if no unspent coin exists for this outpoint.
115                      const bool possible_overwrite{coins_view_cache.PeekCoin(outpoint) || fuzzed_data_provider.ConsumeBool()};
116                      coins_view_cache.AddCoin(outpoint, std::move(coin), possible_overwrite);
117                  } else {
118                      coins_view_cache.EmplaceCoinInternalDANGER(std::move(outpoint), std::move(coin));
119                  }
120              },
121              [&] {
122                  coins_view_cache.Flush(/*reallocate_cache=*/fuzzed_data_provider.ConsumeBool());
123              },
124              [&] {
125                  coins_view_cache.Sync();
126              },
127              [&] {
128                  uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
129                  // `CCoinsViewDB::BatchWrite()` requires a non-null best block.
130                  if (is_db && best_block.IsNull()) best_block = uint256::ONE;
131                  coins_view_cache.SetBestBlock(best_block);
132              },
133              [&] {
134                  (void)coins_view_cache.CreateResetGuard();
135                  // Reset() clears the best block, so reseed db-backed caches.
136                  if (is_db) {
137                      const uint256 best_block{ConsumeUInt256(fuzzed_data_provider)};
138                      if (best_block.IsNull()) {
139                          good_data = false;
140                          return;
141                      }
142                      coins_view_cache.SetBestBlock(best_block);
143                  }
144              },
145              [&] {
146                  Coin move_to;
147                  (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr);
148              },
149              [&] {
150                  coins_view_cache.Uncache(random_out_point);
151              },
152              [&] {
153                  const bool use_original_backend{fuzzed_data_provider.ConsumeBool()};
154                  if (use_original_backend && backend_coins_view != original_backend) {
155                      // FRESH flags valid against the empty backend may be invalid
156                      // against the original backend, so reset before restoring it.
157                      (void)coins_view_cache.CreateResetGuard();
158                      // Reset() clears the best block; db backends require a non-null hash.
159                      if (is_db) coins_view_cache.SetBestBlock(uint256::ONE);
160                  }
161                  backend_coins_view = use_original_backend ? original_backend : &CoinsViewEmpty::Get();
162                  coins_view_cache.SetBackend(*backend_coins_view);
163              },
164              [&] {
165                  const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
166                  if (!opt_out_point) {
167                      good_data = false;
168                      return;
169                  }
170                  random_out_point = *opt_out_point;
171              },
172              [&] {
173                  const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
174                  if (!opt_coin) {
175                      good_data = false;
176                      return;
177                  }
178                  random_coin = *opt_coin;
179              },
180              [&] {
181                  const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
182                  if (!opt_mutable_transaction) {
183                      good_data = false;
184                      return;
185                  }
186                  random_mutable_transaction = *opt_mutable_transaction;
187              },
188              [&] {
189                  CoinsCachePair sentinel{};
190                  sentinel.second.SelfRef(sentinel);
191                  size_t dirty_count{0};
192                  CCoinsMapMemoryResource resource;
193                  CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource};
194                  LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
195                  {
196                      CCoinsCacheEntry coins_cache_entry;
197                      if (fuzzed_data_provider.ConsumeBool()) {
198                          coins_cache_entry.coin = random_coin;
199                      } else {
200                          const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
201                          if (!opt_coin) {
202                              good_data = false;
203                              return;
204                          }
205                          coins_cache_entry.coin = *opt_coin;
206                      }
207                      // Avoid setting FRESH for an outpoint that already exists unspent in the parent view.
208                      bool fresh{!coins_view_cache.PeekCoin(random_out_point) && fuzzed_data_provider.ConsumeBool()};
209                      bool dirty{fresh || fuzzed_data_provider.ConsumeBool()};
210                      auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
211                      if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel);
212                      if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel);
213                      dirty_count += dirty;
214                  }
215                  auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, coins_map, /*will_erase=*/true)};
216                  uint256 best_block{coins_view_cache.GetBestBlock()};
217                  if (fuzzed_data_provider.ConsumeBool()) best_block = ConsumeUInt256(fuzzed_data_provider);
218                  // Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite().
219                  if (is_db && best_block.IsNull()) best_block = uint256::ONE;
220                  coins_view_cache.BatchWrite(cursor, best_block);
221              });
222      }
223  
224      {
225          bool expected_code_path = false;
226          try {
227              (void)coins_view_cache.Cursor();
228          } catch (const std::logic_error&) {
229              expected_code_path = true;
230          }
231          assert(expected_code_path);
232          (void)coins_view_cache.DynamicMemoryUsage();
233          (void)coins_view_cache.EstimateSize();
234          (void)coins_view_cache.GetBestBlock();
235          (void)coins_view_cache.GetCacheSize();
236          (void)coins_view_cache.GetHeadBlocks();
237          (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction});
238      }
239  
240      {
241          if (is_db && backend_coins_view == original_backend) {
242              assert(backend_coins_view->Cursor());
243          }
244          (void)backend_coins_view->EstimateSize();
245          (void)backend_coins_view->GetBestBlock();
246          (void)backend_coins_view->GetHeadBlocks();
247      }
248  
249      if (fuzzed_data_provider.ConsumeBool()) {
250          CallOneOf(
251              fuzzed_data_provider,
252              [&] {
253                  const CTransaction transaction{random_mutable_transaction};
254                  bool is_spent = false;
255                  for (const CTxOut& tx_out : transaction.vout) {
256                      if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) {
257                          is_spent = true;
258                      }
259                  }
260                  if (is_spent) {
261                      // Avoid:
262                      // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed.
263                      return;
264                  }
265                  const int height{int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)};
266                  const bool check_for_overwrite{transaction.IsCoinBase() || [&] {
267                      for (uint32_t i{0}; i < transaction.vout.size(); ++i) {
268                          if (coins_view_cache.PeekCoin(COutPoint{transaction.GetHash(), i})) return true;
269                      }
270                      return fuzzed_data_provider.ConsumeBool();
271                  }()}; // We can only skip the check if the current txid has no unspent outputs
272                  AddCoins(coins_view_cache, transaction, height, check_for_overwrite);
273              },
274              [&] {
275                  (void)ValidateInputsStandardness(CTransaction{random_mutable_transaction}, coins_view_cache);
276              },
277              [&] {
278                  TxValidationState state;
279                  CAmount tx_fee_out;
280                  const CTransaction transaction{random_mutable_transaction};
281                  if (ContainsSpentInput(transaction, coins_view_cache)) {
282                      // Avoid:
283                      // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed.
284                      return;
285                  }
286                  TxValidationState dummy;
287                  if (!CheckTransaction(transaction, dummy)) {
288                      // It is not allowed to call CheckTxInputs if CheckTransaction failed
289                      return;
290                  }
291                  if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out)) {
292                      assert(MoneyRange(tx_fee_out));
293                  }
294              },
295              [&] {
296                  const CTransaction transaction{random_mutable_transaction};
297                  if (ContainsSpentInput(transaction, coins_view_cache)) {
298                      // Avoid:
299                      // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
300                      return;
301                  }
302                  (void)GetP2SHSigOpCount(transaction, coins_view_cache);
303              },
304              [&] {
305                  const CTransaction transaction{random_mutable_transaction};
306                  if (ContainsSpentInput(transaction, coins_view_cache)) {
307                      // Avoid:
308                      // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
309                      return;
310                  }
311                  const auto flags = script_verify_flags::from_int(fuzzed_data_provider.ConsumeIntegral<script_verify_flags::value_type>());
312                  if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
313                      // Avoid:
314                      // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness &, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
315                      return;
316                  }
317                  (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
318              },
319              [&] {
320                  (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
321              });
322      }
323  
324      {
325          const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point);
326          const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
327          const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
328          const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
329          if (auto coin{coins_view_cache.GetCoin(random_out_point)}) {
330              assert(*coin == coin_using_access_coin);
331              assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin);
332          } else {
333              assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin);
334          }
335          // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent.
336          const bool exists_using_have_coin_in_backend = backend_coins_view->HaveCoin(random_out_point);
337          if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) {
338              assert(exists_using_have_coin);
339          }
340          if (auto coin{backend_coins_view->GetCoin(random_out_point)}) {
341              assert(exists_using_have_coin_in_backend);
342              // Note we can't assert that `coin_using_get_coin == *coin` because the coin in
343              // the cache may have been modified but not yet flushed.
344          } else {
345              assert(!exists_using_have_coin_in_backend);
346          }
347      }
348  }
349  
350  FUZZ_TARGET(coins_view, .init = initialize_coins_view)
351  {
352      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
353      CCoinsViewCache coins_view_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
354      TestCoinsView(fuzzed_data_provider, coins_view_cache, &CoinsViewEmpty::Get());
355  }
356  
357  FUZZ_TARGET(coins_view_db, .init = initialize_coins_view)
358  {
359      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
360      auto db_params = DBParams{
361          .path = "",
362          .cache_bytes = 1_MiB,
363          .memory_only = true,
364      };
365      CCoinsViewDB backend_coins_view{std::move(db_params), CoinsViewOptions{}};
366      CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
367      TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_coins_view);
368  }
369  
370  // Creates a CoinsViewOverlay and a MutationGuardCoinsViewCache as the base.
371  // This allows us to exercise all methods on a CoinsViewOverlay, while also
372  // ensuring that nothing can mutate the underlying cache until Flush or Sync is
373  // called.
374  FUZZ_TARGET(coins_view_overlay, .init = initialize_coins_view)
375  {
376      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
377      MutationGuardCoinsViewCache backend_cache{&CoinsViewEmpty::Get(), /*deterministic=*/true};
378      CoinsViewOverlay coins_view_cache{&backend_cache, /*deterministic=*/true};
379      TestCoinsView(fuzzed_data_provider, coins_view_cache, &backend_cache);
380  }