coinsviewoverlay_tests.cpp raw

   1  // Copyright (c) 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 <kernel/chainstatemanager_opts.h>
   7  #include <primitives/block.h>
   8  #include <primitives/transaction.h>
   9  #include <primitives/transaction_identifier.h>
  10  #include <txdb.h>
  11  #include <uint256.h>
  12  #include <util/byte_units.h>
  13  #include <util/hasher.h>
  14  #include <util/threadpool.h>
  15  
  16  #include <boost/test/unit_test.hpp>
  17  
  18  #include <cstdint>
  19  #include <cstring>
  20  #include <memory>
  21  #include <ranges>
  22  #include <unordered_set>
  23  #include <vector>
  24  
  25  namespace {
  26  
  27  std::shared_ptr<ThreadPool> MakeStartedThreadPool()
  28  {
  29      auto pool{std::make_shared<ThreadPool>("fetch_test")};
  30      pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
  31      return pool;
  32  }
  33  
  34  CBlock CreateBlock() noexcept
  35  {
  36      static constexpr auto NUM_TXS{100};
  37      CBlock block;
  38      CMutableTransaction coinbase;
  39      coinbase.vin.emplace_back();
  40      block.vtx.push_back(MakeTransactionRef(coinbase));
  41  
  42      Txid prevhash{Txid::FromUint256(uint256{1})};
  43  
  44      for (const auto i : std::views::iota(1, NUM_TXS)) {
  45          CMutableTransaction tx;
  46          const Txid txid{i % 20 == 0 ? prevhash : Txid::FromUint256(uint256(i))};
  47          tx.vin.emplace_back(txid, 0);
  48          prevhash = tx.GetHash();
  49          block.vtx.push_back(MakeTransactionRef(tx));
  50      }
  51  
  52      return block;
  53  }
  54  
  55  void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)
  56  {
  57      CCoinsViewCache cache{&view};
  58      cache.SetBestBlock(uint256::ONE);
  59  
  60      std::unordered_set<Txid, SaltedTxidHasher> txids{};
  61      txids.reserve(block.vtx.size() - 1);
  62      for (const auto& tx : block.vtx | std::views::drop(1)) {
  63          for (const auto& in : tx->vin) {
  64              if (txids.contains(in.prevout.hash)) continue;
  65              Coin coin{};
  66              if (!spent) coin.out.nValue = 1;
  67              cache.EmplaceCoinInternalDANGER(COutPoint{in.prevout}, std::move(coin));
  68          }
  69          txids.emplace(tx->GetHash());
  70      }
  71  
  72      cache.Flush();
  73  }
  74  
  75  void CheckCache(const CBlock& block, const CCoinsViewCache& cache)
  76  {
  77      uint32_t counter{0};
  78      std::unordered_set<Txid, SaltedTxidHasher> txids{};
  79      txids.reserve(block.vtx.size() - 1);
  80  
  81      for (const auto& tx : block.vtx) {
  82          if (tx->IsCoinBase()) {
  83              BOOST_CHECK(!cache.HaveCoinInCache(tx->vin[0].prevout));
  84          } else {
  85              for (const auto& in : tx->vin) {
  86                  const auto& outpoint{in.prevout};
  87                  const auto& first{cache.AccessCoin(outpoint)};
  88                  const auto& second{cache.AccessCoin(outpoint)};
  89                  BOOST_CHECK_EQUAL(&first, &second);
  90                  const auto have{cache.HaveCoinInCache(outpoint)};
  91                  BOOST_CHECK_NE(txids.contains(outpoint.hash), have);
  92                  counter += have;
  93              }
  94              txids.emplace(tx->GetHash());
  95          }
  96      }
  97      BOOST_CHECK_EQUAL(cache.GetCacheSize(), counter);
  98  }
  99  
 100  } // namespace
 101  
 102  BOOST_AUTO_TEST_SUITE(coinsviewoverlay_tests)
 103  
 104  BOOST_AUTO_TEST_CASE(fetch_inputs_from_db)
 105  {
 106      const auto block{CreateBlock()};
 107      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 108      PopulateView(block, db);
 109      CCoinsViewCache main_cache{&db};
 110      CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
 111      const auto reset_guard{view.StartFetching(block)};
 112      const auto& outpoint{block.vtx[1]->vin[0].prevout};
 113  
 114      BOOST_CHECK(view.HaveCoin(outpoint));
 115      BOOST_CHECK(view.GetCoin(outpoint).has_value());
 116      BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));
 117  
 118      CheckCache(block, view);
 119      // Check that no coins have been moved up to main cache from db
 120      for (const auto& tx : block.vtx) {
 121          for (const auto& in : tx->vin) {
 122              BOOST_CHECK(!main_cache.HaveCoinInCache(in.prevout));
 123          }
 124      }
 125  
 126      view.SetBestBlock(uint256::ONE);
 127      BOOST_CHECK(view.SpendCoin(outpoint));
 128      view.Flush();
 129      BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
 130  }
 131  
 132  BOOST_AUTO_TEST_CASE(fetch_inputs_from_cache)
 133  {
 134      const auto block{CreateBlock()};
 135      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 136      CCoinsViewCache main_cache{&db};
 137      PopulateView(block, main_cache);
 138      CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
 139      const auto reset_guard{view.StartFetching(block)};
 140      CheckCache(block, view);
 141  
 142      const auto& outpoint{block.vtx[1]->vin[0].prevout};
 143      view.SetBestBlock(uint256::ONE);
 144      BOOST_CHECK(view.SpendCoin(outpoint));
 145      view.Flush();
 146      BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
 147  }
 148  
 149  // Test for the case where a block spends coins that are spent in the cache, but
 150  // the spentness has not been flushed to the db.
 151  BOOST_AUTO_TEST_CASE(fetch_no_double_spend)
 152  {
 153      const auto block{CreateBlock()};
 154      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 155      PopulateView(block, db);
 156      CCoinsViewCache main_cache{&db};
 157      // Add all inputs as spent already in cache
 158      PopulateView(block, main_cache, /*spent=*/true);
 159      CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
 160      const auto reset_guard{view.StartFetching(block)};
 161      for (const auto& tx : block.vtx) {
 162          for (const auto& in : tx->vin) {
 163              const auto& c{view.AccessCoin(in.prevout)};
 164              BOOST_CHECK(c.IsSpent());
 165              BOOST_CHECK(!view.HaveCoin(in.prevout));
 166              BOOST_CHECK(!view.GetCoin(in.prevout));
 167          }
 168      }
 169      // Coins are not added to the view, even though they exist unspent in the parent db
 170      BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
 171  }
 172  
 173  BOOST_AUTO_TEST_CASE(fetch_no_inputs)
 174  {
 175      const auto block{CreateBlock()};
 176      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 177      CCoinsViewCache main_cache{&db};
 178      CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
 179      const auto reset_guard{view.StartFetching(block)};
 180      for (const auto& tx : block.vtx) {
 181          for (const auto& in : tx->vin) {
 182              const auto& c{view.AccessCoin(in.prevout)};
 183              BOOST_CHECK(c.IsSpent());
 184              BOOST_CHECK(!view.HaveCoin(in.prevout));
 185              BOOST_CHECK(!view.GetCoin(in.prevout));
 186          }
 187      }
 188      BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
 189  }
 190  
 191  // Access coins that are not block inputs
 192  BOOST_AUTO_TEST_CASE(access_non_input_coins)
 193  {
 194      CBlock block;
 195      CMutableTransaction coinbase;
 196      coinbase.vin.emplace_back();
 197      block.vtx.push_back(MakeTransactionRef(coinbase));
 198      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 199      CCoinsViewCache main_cache{&db};
 200      Coin coin{};
 201      coin.out.nValue = 1;
 202      const COutPoint outpoint{Txid::FromUint256(uint256::ZERO), 0};
 203      main_cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, std::move(coin));
 204  
 205      CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
 206      const auto reset_guard{view.StartFetching(block)};
 207  
 208      // Non-input fallback hit.
 209      BOOST_CHECK(!view.AccessCoin(outpoint).IsSpent());
 210  
 211      // Non-input fallback miss.
 212      const COutPoint missing_outpoint{Txid::FromUint256(uint256::ONE), 0};
 213      BOOST_CHECK(view.AccessCoin(missing_outpoint).IsSpent());
 214      BOOST_CHECK(!view.HaveCoinInCache(missing_outpoint));
 215  }
 216  
 217  // Access a fetched input out of order (i.e. not the next one in m_inputs).
 218  // FetchCoinFromBase must fall back to base->PeekCoin, and the coin must still
 219  // be inserted into the cache.
 220  BOOST_AUTO_TEST_CASE(fetch_out_of_order_input_uses_normal_lookup)
 221  {
 222      const auto block{CreateBlock()};
 223      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 224      CCoinsViewCache main_cache{&db};
 225      PopulateView(block, main_cache);
 226  
 227      std::vector<COutPoint> fetched_inputs;
 228      std::unordered_set<Txid, SaltedTxidHasher> txids;
 229      txids.reserve(block.vtx.size() - 1);
 230      for (const auto& tx : block.vtx | std::views::drop(1)) {
 231          for (const auto& input : tx->vin) {
 232              if (!txids.contains(input.prevout.hash)) fetched_inputs.push_back(input.prevout);
 233          }
 234          txids.emplace(tx->GetHash());
 235      }
 236      BOOST_REQUIRE_GE(fetched_inputs.size(), 2U);
 237  
 238      CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
 239      const auto reset_guard{view.StartFetching(block)};
 240  
 241      const auto& out_of_order_input{fetched_inputs[1]};
 242      BOOST_CHECK(!view.HaveCoinInCache(out_of_order_input));
 243      BOOST_CHECK(!view.AccessCoin(out_of_order_input).IsSpent());
 244      BOOST_CHECK(view.HaveCoinInCache(out_of_order_input));
 245  
 246      CheckCache(block, view);
 247  }
 248  
 249  // The ResetGuard returned by StartFetching must clear all per-block state when
 250  // it goes out of scope, so the overlay can be reused for a subsequent block.
 251  // Flush must also clear all per-block state to be reused.
 252  BOOST_AUTO_TEST_CASE(fetch_state_is_reusable_after_teardown)
 253  {
 254      const auto block{CreateBlock()};
 255      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 256      CCoinsViewCache main_cache{&db};
 257      PopulateView(block, main_cache);
 258      CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};
 259  
 260      for (const bool use_flush : {false, true, false}) {
 261          {
 262              const auto reset_guard{view.StartFetching(block)};
 263              CheckCache(block, view);
 264              BOOST_CHECK_GT(view.GetCacheSize(), 0U);
 265              if (use_flush) {
 266                  view.SetBestBlock(uint256::ONE);
 267                  view.Flush();
 268              }
 269          }
 270          BOOST_CHECK_EQUAL(view.GetCacheSize(), 0U);
 271      }
 272  }
 273  
 274  BOOST_AUTO_TEST_SUITE_END()
 275  
 276  BOOST_AUTO_TEST_SUITE(coinsviewoverlay_tests_noworkers)
 277  
 278  // Test that disabled input fetching falls back to normal cache lookups via base->PeekCoin.
 279  BOOST_AUTO_TEST_CASE(fetch_unstarted_thread_pool)
 280  {
 281      const auto block{CreateBlock()};
 282      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 283      CCoinsViewCache main_cache{&db};
 284      PopulateView(block, main_cache);
 285      auto thread_pool{std::make_shared<ThreadPool>("fetch_none")};
 286      CoinsViewOverlay view{&main_cache, thread_pool};
 287      const auto reset_guard{view.StartFetching(block)};
 288      CheckCache(block, view);
 289  }
 290  
 291  // Test that an interrupted thread pool falls back to normal cache lookups via base->PeekCoin.
 292  BOOST_AUTO_TEST_CASE(fetch_interrupted_thread_pool_uses_normal_lookup)
 293  {
 294      const auto block{CreateBlock()};
 295      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 296      CCoinsViewCache main_cache{&db};
 297      PopulateView(block, main_cache);
 298  
 299      auto thread_pool{std::make_shared<ThreadPool>("fetch_intr")};
 300      thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);
 301      thread_pool->Interrupt();
 302      CoinsViewOverlay view{&main_cache, thread_pool};
 303      const auto reset_guard{view.StartFetching(block)};
 304      CheckCache(block, view);
 305  }
 306  
 307  BOOST_AUTO_TEST_SUITE_END()
 308