coins_view.cpp raw

   1  // Copyright (c) 2020-2022 The Limenka 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 <chainparams.h>
  11  #include <policy/policy.h>
  12  #include <primitives/transaction.h>
  13  #include <script/interpreter.h>
  14  #include <test/fuzz/FuzzedDataProvider.h>
  15  #include <test/fuzz/fuzz.h>
  16  #include <test/fuzz/util.h>
  17  #include <test/util/setup_common.h>
  18  #include <test/util/transaction_utils.h>
  19  #include <util/hasher.h>
  20  
  21  #include <cassert>
  22  #include <cstdint>
  23  #include <limits>
  24  #include <memory>
  25  #include <optional>
  26  #include <stdexcept>
  27  #include <string>
  28  #include <utility>
  29  #include <vector>
  30  
  31  namespace {
  32  const Coin EMPTY_COIN{};
  33  
  34  bool operator==(const Coin& a, const Coin& b)
  35  {
  36      if (a.IsSpent() && b.IsSpent()) return true;
  37      return a.fCoinBase == b.fCoinBase && a.nHeight == b.nHeight && a.out == b.out;
  38  }
  39  } // namespace
  40  
  41  void initialize_coins_view()
  42  {
  43      static const auto testing_setup = MakeNoLogFileContext<>();
  44  }
  45  
  46  FUZZ_TARGET(coins_view, .init = initialize_coins_view)
  47  {
  48      FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()};
  49      bool good_data{true};
  50  
  51      CCoinsView backend_coins_view;
  52      CCoinsViewCache coins_view_cache{&backend_coins_view, /*deterministic=*/true};
  53      COutPoint random_out_point;
  54      Coin random_coin;
  55      CMutableTransaction random_mutable_transaction;
  56      LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
  57      {
  58          CallOneOf(
  59              fuzzed_data_provider,
  60              [&] {
  61                  if (random_coin.IsSpent()) {
  62                      return;
  63                  }
  64                  Coin coin = random_coin;
  65                  bool expected_code_path = false;
  66                  const bool possible_overwrite = fuzzed_data_provider.ConsumeBool();
  67                  try {
  68                      coins_view_cache.AddCoin(random_out_point, std::move(coin), possible_overwrite);
  69                      expected_code_path = true;
  70                  } catch (const std::logic_error& e) {
  71                      if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) {
  72                          assert(!possible_overwrite);
  73                          expected_code_path = true;
  74                      }
  75                  }
  76                  assert(expected_code_path);
  77              },
  78              [&] {
  79                  (void)coins_view_cache.Flush();
  80              },
  81              [&] {
  82                  (void)coins_view_cache.Sync();
  83              },
  84              [&] {
  85                  coins_view_cache.SetBestBlock(ConsumeUInt256(fuzzed_data_provider));
  86              },
  87              [&] {
  88                  Coin move_to;
  89                  (void)coins_view_cache.SpendCoin(random_out_point, fuzzed_data_provider.ConsumeBool() ? &move_to : nullptr);
  90              },
  91              [&] {
  92                  coins_view_cache.Uncache(random_out_point);
  93              },
  94              [&] {
  95                  if (fuzzed_data_provider.ConsumeBool()) {
  96                      backend_coins_view = CCoinsView{};
  97                  }
  98                  coins_view_cache.SetBackend(backend_coins_view);
  99              },
 100              [&] {
 101                  const std::optional<COutPoint> opt_out_point = ConsumeDeserializable<COutPoint>(fuzzed_data_provider);
 102                  if (!opt_out_point) {
 103                      good_data = false;
 104                      return;
 105                  }
 106                  random_out_point = *opt_out_point;
 107              },
 108              [&] {
 109                  const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
 110                  if (!opt_coin) {
 111                      good_data = false;
 112                      return;
 113                  }
 114                  random_coin = *opt_coin;
 115              },
 116              [&] {
 117                  const std::optional<CMutableTransaction> opt_mutable_transaction = ConsumeDeserializable<CMutableTransaction>(fuzzed_data_provider, TX_WITH_WITNESS);
 118                  if (!opt_mutable_transaction) {
 119                      good_data = false;
 120                      return;
 121                  }
 122                  random_mutable_transaction = *opt_mutable_transaction;
 123              },
 124              [&] {
 125                  CoinsCachePair sentinel{};
 126                  sentinel.second.SelfRef(sentinel);
 127                  size_t usage{0};
 128                  CCoinsMapMemoryResource resource;
 129                  CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource};
 130                  LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
 131                  {
 132                      CCoinsCacheEntry coins_cache_entry;
 133                      const auto dirty{fuzzed_data_provider.ConsumeBool()};
 134                      const auto fresh{fuzzed_data_provider.ConsumeBool()};
 135                      if (fuzzed_data_provider.ConsumeBool()) {
 136                          coins_cache_entry.coin = random_coin;
 137                      } else {
 138                          const std::optional<Coin> opt_coin = ConsumeDeserializable<Coin>(fuzzed_data_provider);
 139                          if (!opt_coin) {
 140                              good_data = false;
 141                              return;
 142                          }
 143                          coins_cache_entry.coin = *opt_coin;
 144                      }
 145                      auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
 146                      if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel);
 147                      if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel);
 148                      usage += it->second.coin.DynamicMemoryUsage();
 149                  }
 150                  bool expected_code_path = false;
 151                  try {
 152                      auto cursor{CoinsViewCacheCursor(usage, sentinel, coins_map, /*will_erase=*/true)};
 153                      coins_view_cache.BatchWrite(cursor, fuzzed_data_provider.ConsumeBool() ? ConsumeUInt256(fuzzed_data_provider) : coins_view_cache.GetBestBlock());
 154                      expected_code_path = true;
 155                  } catch (const std::logic_error& e) {
 156                      if (e.what() == std::string{"FRESH flag misapplied to coin that exists in parent cache"}) {
 157                          expected_code_path = true;
 158                      }
 159                  }
 160                  assert(expected_code_path);
 161              });
 162      }
 163  
 164      {
 165          const Coin& coin_using_access_coin = coins_view_cache.AccessCoin(random_out_point);
 166          const bool exists_using_access_coin = !(coin_using_access_coin == EMPTY_COIN);
 167          const bool exists_using_have_coin = coins_view_cache.HaveCoin(random_out_point);
 168          const bool exists_using_have_coin_in_cache = coins_view_cache.HaveCoinInCache(random_out_point);
 169          if (auto coin{coins_view_cache.GetCoin(random_out_point)}) {
 170              assert(*coin == coin_using_access_coin);
 171              assert(exists_using_access_coin && exists_using_have_coin_in_cache && exists_using_have_coin);
 172          } else {
 173              assert(!exists_using_access_coin && !exists_using_have_coin_in_cache && !exists_using_have_coin);
 174          }
 175          // If HaveCoin on the backend is true, it must also be on the cache if the coin wasn't spent.
 176          const bool exists_using_have_coin_in_backend = backend_coins_view.HaveCoin(random_out_point);
 177          if (!coin_using_access_coin.IsSpent() && exists_using_have_coin_in_backend) {
 178              assert(exists_using_have_coin);
 179          }
 180          if (auto coin{backend_coins_view.GetCoin(random_out_point)}) {
 181              assert(exists_using_have_coin_in_backend);
 182              // Note we can't assert that `coin_using_get_coin == *coin` because the coin in
 183              // the cache may have been modified but not yet flushed.
 184          } else {
 185              assert(!exists_using_have_coin_in_backend);
 186          }
 187      }
 188  
 189      {
 190          bool expected_code_path = false;
 191          try {
 192              (void)coins_view_cache.Cursor();
 193          } catch (const std::logic_error&) {
 194              expected_code_path = true;
 195          }
 196          assert(expected_code_path);
 197          (void)coins_view_cache.DynamicMemoryUsage();
 198          (void)coins_view_cache.EstimateSize();
 199          (void)coins_view_cache.GetBestBlock();
 200          (void)coins_view_cache.GetCacheSize();
 201          (void)coins_view_cache.GetHeadBlocks();
 202          (void)coins_view_cache.HaveInputs(CTransaction{random_mutable_transaction});
 203      }
 204  
 205      {
 206          std::unique_ptr<CCoinsViewCursor> coins_view_cursor = backend_coins_view.Cursor();
 207          assert(!coins_view_cursor);
 208          (void)backend_coins_view.EstimateSize();
 209          (void)backend_coins_view.GetBestBlock();
 210          (void)backend_coins_view.GetHeadBlocks();
 211      }
 212  
 213      if (fuzzed_data_provider.ConsumeBool()) {
 214          CallOneOf(
 215              fuzzed_data_provider,
 216              [&] {
 217                  const CTransaction transaction{random_mutable_transaction};
 218                  bool is_spent = false;
 219                  for (const CTxOut& tx_out : transaction.vout) {
 220                      if (Coin{tx_out, 0, transaction.IsCoinBase()}.IsSpent()) {
 221                          is_spent = true;
 222                      }
 223                  }
 224                  if (is_spent) {
 225                      // Avoid:
 226                      // coins.cpp:69: void CCoinsViewCache::AddCoin(const COutPoint &, Coin &&, bool): Assertion `!coin.IsSpent()' failed.
 227                      return;
 228                  }
 229                  bool expected_code_path = false;
 230                  const int height{int(fuzzed_data_provider.ConsumeIntegral<uint32_t>() >> 1)};
 231                  const bool possible_overwrite = fuzzed_data_provider.ConsumeBool();
 232                  try {
 233                      AddCoins(coins_view_cache, transaction, height, possible_overwrite);
 234                      expected_code_path = true;
 235                  } catch (const std::logic_error& e) {
 236                      if (e.what() == std::string{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"}) {
 237                          assert(!possible_overwrite);
 238                          expected_code_path = true;
 239                      }
 240                  }
 241                  assert(expected_code_path);
 242              },
 243              [&] {
 244                  (void)AreInputsStandard(CTransaction{random_mutable_transaction}, coins_view_cache);
 245              },
 246              [&] {
 247                  TxValidationState state;
 248                  CAmount tx_fee_out;
 249                  const CTransaction transaction{random_mutable_transaction};
 250                  if (ContainsSpentInput(transaction, coins_view_cache)) {
 251                      // Avoid:
 252                      // consensus/tx_verify.cpp:171: bool Consensus::CheckTxInputs(const CTransaction &, TxValidationState &, const CCoinsViewCache &, int, CAmount &): Assertion `!coin.IsSpent()' failed.
 253                      return;
 254                  }
 255                  TxValidationState dummy;
 256                  if (!CheckTransaction(transaction, dummy)) {
 257                      // It is not allowed to call CheckTxInputs if CheckTransaction failed
 258                      return;
 259                  }
 260                  if (Consensus::CheckTxInputs(transaction, state, coins_view_cache, fuzzed_data_provider.ConsumeIntegralInRange<int>(0, std::numeric_limits<int>::max()), tx_fee_out, CheckTxInputsRules::OutputSizeLimit, Params().GetConsensus(), /*fork_active=*/fuzzed_data_provider.ConsumeBool())) {
 261                      assert(MoneyRange(tx_fee_out));
 262                  }
 263              },
 264              [&] {
 265                  const CTransaction transaction{random_mutable_transaction};
 266                  if (ContainsSpentInput(transaction, coins_view_cache)) {
 267                      // Avoid:
 268                      // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
 269                      return;
 270                  }
 271                  (void)GetP2SHSigOpCount(transaction, coins_view_cache);
 272              },
 273              [&] {
 274                  const CTransaction transaction{random_mutable_transaction};
 275                  if (ContainsSpentInput(transaction, coins_view_cache)) {
 276                      // Avoid:
 277                      // consensus/tx_verify.cpp:130: unsigned int GetP2SHSigOpCount(const CTransaction &, const CCoinsViewCache &): Assertion `!coin.IsSpent()' failed.
 278                      return;
 279                  }
 280                  const auto flags{fuzzed_data_provider.ConsumeIntegral<uint32_t>()};
 281                  if (!transaction.vin.empty() && (flags & SCRIPT_VERIFY_WITNESS) != 0 && (flags & SCRIPT_VERIFY_P2SH) == 0) {
 282                      // Avoid:
 283                      // script/interpreter.cpp:1705: size_t CountWitnessSigOps(const CScript &, const CScript &, const CScriptWitness *, unsigned int): Assertion `(flags & SCRIPT_VERIFY_P2SH) != 0' failed.
 284                      return;
 285                  }
 286                  (void)GetTransactionSigOpCost(transaction, coins_view_cache, flags);
 287              },
 288              [&] {
 289                  std::string reason;
 290                  (void)IsWitnessStandard(CTransaction{random_mutable_transaction}, coins_view_cache, "bad-witness-", reason);
 291              });
 292      }
 293  }
 294