/ src / test / coinsviewoverlay_tests.cpp
coinsviewoverlay_tests.cpp
  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 <primitives/block.h>
  7  #include <primitives/transaction.h>
  8  #include <primitives/transaction_identifier.h>
  9  #include <txdb.h>
 10  #include <uint256.h>
 11  #include <util/byte_units.h>
 12  #include <util/hasher.h>
 13  
 14  #include <boost/test/unit_test.hpp>
 15  
 16  #include <cstdint>
 17  #include <cstring>
 18  #include <ranges>
 19  
 20  BOOST_AUTO_TEST_SUITE(coinsviewoverlay_tests)
 21  
 22  namespace {
 23  
 24  CBlock CreateBlock() noexcept
 25  {
 26      static constexpr auto NUM_TXS{100};
 27      CBlock block;
 28      CMutableTransaction coinbase;
 29      coinbase.vin.emplace_back();
 30      block.vtx.push_back(MakeTransactionRef(coinbase));
 31  
 32      for (const auto i : std::views::iota(1, NUM_TXS)) {
 33          CMutableTransaction tx;
 34          Txid txid{Txid::FromUint256(uint256(i))};
 35          tx.vin.emplace_back(txid, 0);
 36          block.vtx.push_back(MakeTransactionRef(tx));
 37      }
 38  
 39      return block;
 40  }
 41  
 42  void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)
 43  {
 44      CCoinsViewCache cache{&view};
 45      cache.SetBestBlock(uint256::ONE);
 46  
 47      for (const auto& tx : block.vtx | std::views::drop(1)) {
 48          for (const auto& in : tx->vin) {
 49              Coin coin{};
 50              if (!spent) coin.out.nValue = 1;
 51              cache.EmplaceCoinInternalDANGER(COutPoint{in.prevout}, std::move(coin));
 52          }
 53      }
 54  
 55      cache.Flush();
 56  }
 57  
 58  void CheckCache(const CBlock& block, const CCoinsViewCache& cache)
 59  {
 60      uint32_t counter{0};
 61  
 62      for (const auto& tx : block.vtx) {
 63          if (tx->IsCoinBase()) {
 64              BOOST_CHECK(!cache.HaveCoinInCache(tx->vin[0].prevout));
 65          } else {
 66              for (const auto& in : tx->vin) {
 67                  const auto& outpoint{in.prevout};
 68                  const auto& first{cache.AccessCoin(outpoint)};
 69                  const auto& second{cache.AccessCoin(outpoint)};
 70                  BOOST_CHECK_EQUAL(&first, &second);
 71                  ++counter;
 72                  BOOST_CHECK(cache.HaveCoinInCache(outpoint));
 73              }
 74          }
 75      }
 76      BOOST_CHECK_EQUAL(cache.GetCacheSize(), counter);
 77  }
 78  
 79  } // namespace
 80  
 81  BOOST_AUTO_TEST_CASE(fetch_inputs_from_db)
 82  {
 83      const auto block{CreateBlock()};
 84      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
 85      PopulateView(block, db);
 86      CCoinsViewCache main_cache{&db};
 87      CoinsViewOverlay view{&main_cache};
 88      const auto& outpoint{block.vtx[1]->vin[0].prevout};
 89  
 90      BOOST_CHECK(view.HaveCoin(outpoint));
 91      BOOST_CHECK(view.GetCoin(outpoint).has_value());
 92      BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));
 93  
 94      CheckCache(block, view);
 95      // Check that no coins have been moved up to main cache from db
 96      for (const auto& tx : block.vtx) {
 97          for (const auto& in : tx->vin) {
 98              BOOST_CHECK(!main_cache.HaveCoinInCache(in.prevout));
 99          }
100      }
101  
102      view.SetBestBlock(uint256::ONE);
103      BOOST_CHECK(view.SpendCoin(outpoint));
104      view.Flush();
105      BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
106  }
107  
108  BOOST_AUTO_TEST_CASE(fetch_inputs_from_cache)
109  {
110      const auto block{CreateBlock()};
111      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
112      CCoinsViewCache main_cache{&db};
113      PopulateView(block, main_cache);
114      CoinsViewOverlay view{&main_cache};
115      CheckCache(block, view);
116  
117      const auto& outpoint{block.vtx[1]->vin[0].prevout};
118      view.SetBestBlock(uint256::ONE);
119      BOOST_CHECK(view.SpendCoin(outpoint));
120      view.Flush();
121      BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());
122  }
123  
124  // Test for the case where a block spends coins that are spent in the cache, but
125  // the spentness has not been flushed to the db.
126  BOOST_AUTO_TEST_CASE(fetch_no_double_spend)
127  {
128      const auto block{CreateBlock()};
129      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
130      PopulateView(block, db);
131      CCoinsViewCache main_cache{&db};
132      // Add all inputs as spent already in cache
133      PopulateView(block, main_cache, /*spent=*/true);
134      CoinsViewOverlay view{&main_cache};
135      for (const auto& tx : block.vtx) {
136          for (const auto& in : tx->vin) {
137              const auto& c{view.AccessCoin(in.prevout)};
138              BOOST_CHECK(c.IsSpent());
139              BOOST_CHECK(!view.HaveCoin(in.prevout));
140              BOOST_CHECK(!view.GetCoin(in.prevout));
141          }
142      }
143      // Coins are not added to the view, even though they exist unspent in the parent db
144      BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
145  }
146  
147  BOOST_AUTO_TEST_CASE(fetch_no_inputs)
148  {
149      const auto block{CreateBlock()};
150      CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};
151      CCoinsViewCache main_cache{&db};
152      CoinsViewOverlay view{&main_cache};
153      for (const auto& tx : block.vtx) {
154          for (const auto& in : tx->vin) {
155              const auto& c{view.AccessCoin(in.prevout)};
156              BOOST_CHECK(c.IsSpent());
157              BOOST_CHECK(!view.HaveCoin(in.prevout));
158              BOOST_CHECK(!view.GetCoin(in.prevout));
159          }
160      }
161      BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);
162  }
163  
164  BOOST_AUTO_TEST_SUITE_END()
165