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) 2014-present 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 <addresstype.h>6#include <clientversion.h>7#include <coins.h>8#include <streams.h>9#include <test/util/coins.h>10#include <test/util/common.h>11#include <test/util/poolresourcetester.h>12#include <test/util/random.h>13#include <test/util/setup_common.h>14#include <txdb.h>15#include <uint256.h>16#include <undo.h>17#include <util/byte_units.h>18#include <util/check.h>19#include <util/strencodings.h>21#include <map>22#include <string>23#include <variant>24#include <vector>26#include <boost/test/unit_test.hpp>28using namespace util::hex_literals;30int ApplyTxInUndo(Coin&& undo, CCoinsViewCache& view, const COutPoint& out);31void UpdateCoins(const CTransaction& tx, CCoinsViewCache& inputs, CTxUndo &txundo, int nHeight);33namespace34{36class CCoinsViewTest : public CoinsViewEmpty37{38FastRandomContext& m_rng;39uint256 hashBestBlock_;40std::map<COutPoint, Coin> map_;42public:43explicit CCoinsViewTest(FastRandomContext& rng) : m_rng{rng} {}45std::optional<Coin> GetCoin(const COutPoint& outpoint) const override46{47if (auto it{map_.find(outpoint)}; it != map_.end() && !it->second.IsSpent()) return it->second;48return std::nullopt;49}51uint256 GetBestBlock() const override { return hashBestBlock_; }53void BatchWrite(CoinsViewCacheCursor& cursor, const uint256& block_hash) override54{55for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)){56if (it->second.IsDirty()) {57// Same optimization used in CCoinsViewDB is to only write dirty entries.58map_[it->first] = it->second.coin;59if (it->second.coin.IsSpent() && m_rng.randrange(3) == 0) {60// Randomly delete empty entries on write.61map_.erase(it->first);62}63}64}65if (!block_hash.IsNull())66hashBestBlock_ = block_hash;67}68};70class CCoinsViewCacheTest : public CCoinsViewCache71{72public:73explicit CCoinsViewCacheTest(CCoinsView* _base) : CCoinsViewCache(_base) {}75void SelfTest(bool sanity_check = true) const76{77// Manually recompute the dynamic usage of the whole data, and compare it.78size_t ret = memusage::DynamicUsage(cacheCoins);79size_t count = 0;80for (const auto& entry : cacheCoins) {81ret += entry.second.coin.DynamicMemoryUsage();82++count;83}84BOOST_CHECK_EQUAL(GetCacheSize(), count);85BOOST_CHECK_EQUAL(DynamicMemoryUsage(), ret);86if (sanity_check) {87SanityCheck();88}89}91CCoinsMap& map() const { return cacheCoins; }92CoinsCachePair& sentinel() const { return m_sentinel; }93size_t& usage() const { return cachedCoinsUsage; }94size_t& dirty() const { return m_dirty_count; }95};97} // namespace99static const unsigned int NUM_SIMULATION_ITERATIONS = 40000;101struct CacheTest : BasicTestingSetup {102// This is a large randomized insert/remove simulation test on a variable-size103// stack of caches on top of CCoinsViewTest.104//105// It will randomly create/update/delete Coin entries to a tip of caches, with106// txids picked from a limited list of random 256-bit hashes. Occasionally, a107// new tip is added to the stack of caches, or the tip is flushed and removed.108//109// During the process, booleans are kept to make sure that the randomized110// operation hits all branches.111//112// If fake_best_block is true, assign a random uint256 to mock the recording113// of best block on flush. This is necessary when using CCoinsViewDB as the base,114// otherwise we'll hit an assertion in BatchWrite.115//116void SimulationTest(CCoinsView* base, bool fake_best_block)117{118// Various coverage trackers.119bool removed_all_caches = false;120bool reached_4_caches = false;121bool added_an_entry = false;122bool added_an_unspendable_entry = false;123bool removed_an_entry = false;124bool updated_an_entry = false;125bool found_an_entry = false;126bool missed_an_entry = false;127bool uncached_an_entry = false;128bool flushed_without_erase = false;130// A simple map to track what we expect the cache stack to represent.131std::map<COutPoint, Coin> result;133// The cache stack.134std::vector<std::unique_ptr<CCoinsViewCacheTest>> stack; // A stack of CCoinsViewCaches on top.135stack.push_back(std::make_unique<CCoinsViewCacheTest>(base)); // Start with one cache.137// Use a limited set of random transaction ids, so we do test overwriting entries.138std::vector<Txid> txids;139txids.resize(NUM_SIMULATION_ITERATIONS / 8);140for (unsigned int i = 0; i < txids.size(); i++) {141txids[i] = Txid::FromUint256(m_rng.rand256());142}144for (unsigned int i = 0; i < NUM_SIMULATION_ITERATIONS; i++) {145// Do a random modification.146{147auto txid = txids[m_rng.randrange(txids.size())]; // txid we're going to modify in this iteration.148Coin& coin = result[COutPoint(txid, 0)];150// Determine whether to test HaveCoin before or after Access* (or both). As these functions151// can influence each other's behaviour by pulling things into the cache, all combinations152// are tested.153bool test_havecoin_before = m_rng.randbits(2) == 0;154bool test_havecoin_after = m_rng.randbits(2) == 0;156bool result_havecoin = test_havecoin_before ? stack.back()->HaveCoin(COutPoint(txid, 0)) : false;158// Infrequently, test usage of AccessByTxid instead of AccessCoin - the159// former just delegates to the latter and returns the first unspent in a txn.160const Coin& entry = (m_rng.randrange(500) == 0) ?161AccessByTxid(*stack.back(), txid) : stack.back()->AccessCoin(COutPoint(txid, 0));162BOOST_CHECK_EQUAL(coin, entry);164if (test_havecoin_before) {165BOOST_CHECK(result_havecoin == !entry.IsSpent());166}168if (test_havecoin_after) {169bool ret = stack.back()->HaveCoin(COutPoint(txid, 0));170BOOST_CHECK(ret == !entry.IsSpent());171}173if (m_rng.randrange(5) == 0 || coin.IsSpent()) {174Coin newcoin;175newcoin.out.nValue = RandMoney(m_rng);176newcoin.nHeight = 1;178// Infrequently test adding unspendable coins.179if (m_rng.randrange(16) == 0 && coin.IsSpent()) {180newcoin.out.scriptPubKey.assign(1 + m_rng.randbits(6), OP_RETURN);181BOOST_CHECK(newcoin.out.scriptPubKey.IsUnspendable());182added_an_unspendable_entry = true;183} else {184// Random sizes so we can test memory usage accounting185newcoin.out.scriptPubKey.assign(m_rng.randbits(6), 0);186(coin.IsSpent() ? added_an_entry : updated_an_entry) = true;187coin = newcoin;188}189if (COutPoint op(txid, 0); !stack.back()->map().contains(op) && !newcoin.out.scriptPubKey.IsUnspendable() && m_rng.randbool()) {190stack.back()->EmplaceCoinInternalDANGER(op, std::move(newcoin));191} else {192stack.back()->AddCoin(op, std::move(newcoin), /*possible_overwrite=*/!coin.IsSpent() || m_rng.randbool());193}194} else {195// Spend the coin.196removed_an_entry = true;197coin.Clear();198BOOST_CHECK(stack.back()->SpendCoin(COutPoint(txid, 0)));199}200}202// Once every 10 iterations, remove a random entry from the cache203if (m_rng.randrange(10) == 0) {204COutPoint out(txids[m_rng.rand32() % txids.size()], 0);205int cacheid = m_rng.rand32() % stack.size();206stack[cacheid]->Uncache(out);207uncached_an_entry |= !stack[cacheid]->HaveCoinInCache(out);208}210// Once every 1000 iterations and at the end, verify the full cache.211if (m_rng.randrange(1000) == 1 || i == NUM_SIMULATION_ITERATIONS - 1) {212for (const auto& entry : result) {213bool have = stack.back()->HaveCoin(entry.first);214const Coin& coin = stack.back()->AccessCoin(entry.first);215BOOST_CHECK(have == !coin.IsSpent());216BOOST_CHECK_EQUAL(coin, entry.second);217if (coin.IsSpent()) {218missed_an_entry = true;219} else {220BOOST_CHECK(stack.back()->HaveCoinInCache(entry.first));221found_an_entry = true;222}223}224for (const auto& test : stack) {225test->SelfTest();226}227}229if (m_rng.randrange(100) == 0) {230// Every 100 iterations, flush an intermediate cache231if (stack.size() > 1 && m_rng.randbool() == 0) {232unsigned int flushIndex = m_rng.randrange(stack.size() - 1);233if (fake_best_block) stack[flushIndex]->SetBestBlock(m_rng.rand256());234bool should_erase = m_rng.randrange(4) < 3;235should_erase ? stack[flushIndex]->Flush() : stack[flushIndex]->Sync();236flushed_without_erase |= !should_erase;237}238}239if (m_rng.randrange(100) == 0) {240// Every 100 iterations, change the cache stack.241if (stack.size() > 0 && m_rng.randbool() == 0) {242//Remove the top cache243if (fake_best_block) stack.back()->SetBestBlock(m_rng.rand256());244bool should_erase = m_rng.randrange(4) < 3;245should_erase ? stack.back()->Flush() : stack.back()->Sync();246flushed_without_erase |= !should_erase;247stack.pop_back();248}249if (stack.size() == 0 || (stack.size() < 4 && m_rng.randbool())) {250//Add a new cache
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.