Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.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/threadpool.h>15#include <boost/test/unit_test.hpp>17#include <cstdint>18#include <cstring>19#include <memory>20#include <ranges>21#include <unordered_set>22#include <vector>24namespace {26std::shared_ptr<ThreadPool> MakeStartedThreadPool()27{28auto pool{std::make_shared<ThreadPool>("fetch_test")};29pool->Start(DEFAULT_PREVOUTFETCH_THREADS);30return pool;31}33CBlock CreateBlock() noexcept34{35static constexpr auto NUM_TXS{100};36CBlock block;37CMutableTransaction coinbase;38coinbase.vin.emplace_back();39block.vtx.push_back(MakeTransactionRef(coinbase));41Txid prevhash{Txid::FromUint256(uint256{1})};43for (const auto i : std::views::iota(1, NUM_TXS)) {44CMutableTransaction tx;45const Txid txid{i % 20 == 0 ? prevhash : Txid::FromUint256(uint256(i))};46tx.vin.emplace_back(txid, 0);47prevhash = tx.GetHash();48block.vtx.push_back(MakeTransactionRef(tx));49}51return block;52}54void PopulateView(const CBlock& block, CCoinsView& view, bool spent = false)55{56CCoinsViewCache cache{&view};57cache.SetBestBlock(uint256::ONE);59std::unordered_set<Txid, SaltedCoinsCacheHasher> txids{};60txids.reserve(block.vtx.size() - 1);61for (const auto& tx : block.vtx | std::views::drop(1)) {62for (const auto& in : tx->vin) {63if (txids.contains(in.prevout.hash)) continue;64Coin coin{};65if (!spent) coin.out.nValue = 1;66cache.EmplaceCoinInternalDANGER(in.prevout, std::move(coin));67}68txids.emplace(tx->GetHash());69}71cache.Flush();72}74void CheckCache(const CBlock& block, const CCoinsViewCache& cache)75{76uint32_t counter{0};77std::unordered_set<Txid, SaltedCoinsCacheHasher> txids{};78txids.reserve(block.vtx.size() - 1);80for (const auto& tx : block.vtx) {81if (tx->IsCoinBase()) {82BOOST_CHECK(!cache.HaveCoinInCache(tx->vin[0].prevout));83} else {84for (const auto& in : tx->vin) {85const auto& outpoint{in.prevout};86const auto& first{cache.AccessCoin(outpoint)};87const auto& second{cache.AccessCoin(outpoint)};88BOOST_CHECK_EQUAL(&first, &second);89const auto have{cache.HaveCoinInCache(outpoint)};90BOOST_CHECK_NE(txids.contains(outpoint.hash), have);91counter += have;92}93txids.emplace(tx->GetHash());94}95}96BOOST_CHECK_EQUAL(cache.GetCacheSize(), counter);97}99} // namespace101BOOST_AUTO_TEST_SUITE(coinsviewoverlay_tests)103BOOST_AUTO_TEST_CASE(fetch_inputs_from_db)104{105const auto block{CreateBlock()};106CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};107PopulateView(block, db);108CCoinsViewCache main_cache{&db};109CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};110const auto reset_guard{view.StartFetching(block)};111const auto& outpoint{block.vtx[1]->vin[0].prevout};113BOOST_CHECK(view.HaveCoin(outpoint));114BOOST_CHECK(view.GetCoin(outpoint).has_value());115BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint));117CheckCache(block, view);118// Check that no coins have been moved up to main cache from db119for (const auto& tx : block.vtx) {120for (const auto& in : tx->vin) {121BOOST_CHECK(!main_cache.HaveCoinInCache(in.prevout));122}123}125view.SetBestBlock(uint256::ONE);126BOOST_CHECK(view.SpendCoin(outpoint));127view.Flush();128BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());129}131BOOST_AUTO_TEST_CASE(fetch_inputs_from_cache)132{133const auto block{CreateBlock()};134CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};135CCoinsViewCache main_cache{&db};136PopulateView(block, main_cache);137CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};138const auto reset_guard{view.StartFetching(block)};139CheckCache(block, view);141const auto& outpoint{block.vtx[1]->vin[0].prevout};142view.SetBestBlock(uint256::ONE);143BOOST_CHECK(view.SpendCoin(outpoint));144view.Flush();145BOOST_CHECK(!main_cache.PeekCoin(outpoint).has_value());146}148// Test for the case where a block spends coins that are spent in the cache, but149// the spentness has not been flushed to the db.150BOOST_AUTO_TEST_CASE(fetch_no_double_spend)151{152const auto block{CreateBlock()};153CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};154PopulateView(block, db);155CCoinsViewCache main_cache{&db};156// Add all inputs as spent already in cache157PopulateView(block, main_cache, /*spent=*/true);158CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};159const auto reset_guard{view.StartFetching(block)};160for (const auto& tx : block.vtx) {161for (const auto& in : tx->vin) {162const auto& c{view.AccessCoin(in.prevout)};163BOOST_CHECK(c.IsSpent());164BOOST_CHECK(!view.HaveCoin(in.prevout));165BOOST_CHECK(!view.GetCoin(in.prevout));166}167}168// Coins are not added to the view, even though they exist unspent in the parent db169BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);170}172BOOST_AUTO_TEST_CASE(fetch_no_inputs)173{174const auto block{CreateBlock()};175CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};176CCoinsViewCache main_cache{&db};177CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};178const auto reset_guard{view.StartFetching(block)};179for (const auto& tx : block.vtx) {180for (const auto& in : tx->vin) {181const auto& c{view.AccessCoin(in.prevout)};182BOOST_CHECK(c.IsSpent());183BOOST_CHECK(!view.HaveCoin(in.prevout));184BOOST_CHECK(!view.GetCoin(in.prevout));185}186}187BOOST_CHECK_EQUAL(view.GetCacheSize(), 0);188}190// Access coins that are not block inputs191BOOST_AUTO_TEST_CASE(access_non_input_coins)192{193CBlock block;194CMutableTransaction coinbase;195coinbase.vin.emplace_back();196block.vtx.push_back(MakeTransactionRef(coinbase));197CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};198CCoinsViewCache main_cache{&db};199Coin coin{};200coin.out.nValue = 1;201const COutPoint outpoint{Txid::FromUint256(uint256::ZERO), 0};202main_cache.EmplaceCoinInternalDANGER(COutPoint{outpoint}, std::move(coin));204CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};205// The block has no non-coinbase transactions, so this fetches nothing and only creates the206// reset guard. All lookups below use the fallback path.207const auto reset_guard{view.StartFetching(block)};209// Non-input fallback hit.210BOOST_CHECK(!view.AccessCoin(outpoint).IsSpent());212// Non-input fallback miss.213const COutPoint missing_outpoint{Txid::FromUint256(uint256::ONE), 0};214BOOST_CHECK(view.AccessCoin(missing_outpoint).IsSpent());215BOOST_CHECK(!view.HaveCoinInCache(missing_outpoint));216}218// Access a fetched input out of order (i.e. not the next one in m_inputs).219// FetchCoinFromBase must fall back to base->PeekCoin, and the coin must still220// be inserted into the cache.221BOOST_AUTO_TEST_CASE(fetch_out_of_order_input_uses_normal_lookup)222{223const auto block{CreateBlock()};224CCoinsViewDB db{{.path = "", .cache_bytes = 1_MiB, .memory_only = true}, {}};225CCoinsViewCache main_cache{&db};226PopulateView(block, main_cache);228std::vector<COutPoint> fetched_inputs;229std::unordered_set<Txid, SaltedCoinsCacheHasher> txids;230txids.reserve(block.vtx.size() - 1);231for (const auto& tx : block.vtx | std::views::drop(1)) {232for (const auto& input : tx->vin) {233if (!txids.contains(input.prevout.hash)) fetched_inputs.push_back(input.prevout);234}235txids.emplace(tx->GetHash());236}237BOOST_REQUIRE_GE(fetched_inputs.size(), 2U);239CoinsViewOverlay view{&main_cache, MakeStartedThreadPool()};240const auto reset_guard{view.StartFetching(block)};242const auto& out_of_order_input{fetched_inputs[1]};243BOOST_CHECK(!view.HaveCoinInCache(out_of_order_input));244BOOST_CHECK(!view.AccessCoin(out_of_order_input).IsSpent());245BOOST_CHECK(view.HaveCoinInCache(out_of_order_input));247CheckCache(block, view);248}250// The ResetGuard returned by StartFetching must clear all per-block state when
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.