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