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) 2023-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 <coins.h>6#include <crypto/sha256.h>7#include <kernel/chainstatemanager_opts.h>8#include <primitives/block.h>9#include <primitives/transaction.h>10#include <test/fuzz/FuzzedDataProvider.h>11#include <test/fuzz/fuzz.h>12#include <test/fuzz/util.h>13#include <test/util/setup_common.h>14#include <util/threadpool.h>16#include <cassert>17#include <cstdint>18#include <memory>19#include <optional>20#include <vector>22namespace {24/** Number of distinct COutPoint values used in this test. */25constexpr uint32_t NUM_OUTPOINTS = 256;26/** Number of distinct Coin values used in this test (ignoring nHeight). */27constexpr uint32_t NUM_COINS = 256;28/** Maximum number CCoinsViewCache objects used in this test. */29constexpr uint32_t MAX_CACHES = 4;30/** Data type large enough to hold NUM_COINS-1. */31using coinidx_type = uint8_t;33struct PrecomputedData34{35//! Randomly generated COutPoint values.36COutPoint outpoints[NUM_OUTPOINTS];38//! Randomly generated Coin values.39Coin coins[NUM_COINS];41//! Block with a tx containing as inputs the above outpoints.42CBlock block;44PrecomputedData()45{46static const uint8_t PREFIX_O[1] = {'o'}; /** Hash prefix for outpoint hashes. */47static const uint8_t PREFIX_S[1] = {'s'}; /** Hash prefix for coins scriptPubKeys. */48static const uint8_t PREFIX_M[1] = {'m'}; /** Hash prefix for coins nValue/fCoinBase. */50CMutableTransaction coinbase;51coinbase.vin.emplace_back();52block.vtx.push_back(MakeTransactionRef(coinbase));54CMutableTransaction tx;55for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {56uint32_t idx = (i * 1200U) >> 12; /* Map 3 or 4 entries to same txid. */57const uint8_t ser[4] = {uint8_t(idx), uint8_t(idx >> 8), uint8_t(idx >> 16), uint8_t(idx >> 24)};58uint256 txid;59CSHA256().Write(PREFIX_O, 1).Write(ser, sizeof(ser)).Finalize(txid.begin());60outpoints[i].hash = Txid::FromUint256(txid);61outpoints[i].n = i;62tx.vin.emplace_back(outpoints[i]);63}64block.vtx.push_back(MakeTransactionRef(tx));66for (uint32_t i = 0; i < NUM_COINS; ++i) {67const uint8_t ser[4] = {uint8_t(i), uint8_t(i >> 8), uint8_t(i >> 16), uint8_t(i >> 24)};68uint256 hash;69CSHA256().Write(PREFIX_S, 1).Write(ser, sizeof(ser)).Finalize(hash.begin());70/* Convert hash to scriptPubkeys (of different lengths, so SanityCheck's cached memory71* usage check has a chance to detect mismatches). */72switch (i % 5U) {73case 0: /* P2PKH */74coins[i].out.scriptPubKey.resize(25);75coins[i].out.scriptPubKey[0] = OP_DUP;76coins[i].out.scriptPubKey[1] = OP_HASH160;77coins[i].out.scriptPubKey[2] = 20;78std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 3);79coins[i].out.scriptPubKey[23] = OP_EQUALVERIFY;80coins[i].out.scriptPubKey[24] = OP_CHECKSIG;81break;82case 1: /* P2SH */83coins[i].out.scriptPubKey.resize(23);84coins[i].out.scriptPubKey[0] = OP_HASH160;85coins[i].out.scriptPubKey[1] = 20;86std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);87coins[i].out.scriptPubKey[22] = OP_EQUAL;88break;89case 2: /* P2WPKH */90coins[i].out.scriptPubKey.resize(22);91coins[i].out.scriptPubKey[0] = OP_0;92coins[i].out.scriptPubKey[1] = 20;93std::copy(hash.begin(), hash.begin() + 20, coins[i].out.scriptPubKey.begin() + 2);94break;95case 3: /* P2WSH */96coins[i].out.scriptPubKey.resize(34);97coins[i].out.scriptPubKey[0] = OP_0;98coins[i].out.scriptPubKey[1] = 32;99std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2);100break;101case 4: /* P2TR */102coins[i].out.scriptPubKey.resize(34);103coins[i].out.scriptPubKey[0] = OP_1;104coins[i].out.scriptPubKey[1] = 32;105std::copy(hash.begin(), hash.begin() + 32, coins[i].out.scriptPubKey.begin() + 2);106break;107}108/* Hash again to construct nValue and fCoinBase. */109CSHA256().Write(PREFIX_M, 1).Write(ser, sizeof(ser)).Finalize(hash.begin());110coins[i].out.nValue = CAmount(hash.GetUint64(0) % MAX_MONEY);111coins[i].fCoinBase = (hash.GetUint64(1) & 7) == 0;112coins[i].nHeight = 0; /* Real nHeight used in simulation is set dynamically. */113}114}115};117enum class EntryType : uint8_t118{119/* This entry in the cache does not exist (so we'd have to look in the parent cache). */120NONE,122/* This entry in the cache corresponds to an unspent coin. */123UNSPENT,125/* This entry in the cache corresponds to a spent coin. */126SPENT,127};129struct CacheEntry130{131/* Type of entry. */132EntryType entrytype;134/* Index in the coins array this entry corresponds to (only if entrytype == UNSPENT). */135coinidx_type coinidx;137/* nHeight value for this entry (so the coins[coinidx].nHeight value is ignored; only if entrytype == UNSPENT). */138uint32_t height;139};141struct CacheLevel142{143CacheEntry entry[NUM_OUTPOINTS];145void Wipe() {146for (uint32_t i = 0; i < NUM_OUTPOINTS; ++i) {147entry[i].entrytype = EntryType::NONE;148}149}150};152/** Class for the base of the hierarchy (roughly simulating a memory-backed CCoinsViewDB).153*154* The initial state consists of the empty UTXO set.155*/156class CoinsViewBottom final : public CoinsViewEmpty157{158std::map<COutPoint, Coin> m_data;160public:161std::optional<Coin> GetCoin(const COutPoint& outpoint) const final162{163if (auto it{m_data.find(outpoint)}; it != m_data.end()) {164assert(!it->second.IsSpent());165return it->second;166}167return std::nullopt;168}170void BatchWrite(CoinsViewCacheCursor& cursor, const uint256&) final171{172for (auto it{cursor.Begin()}; it != cursor.End(); it = cursor.NextAndMaybeErase(*it)) {173if (it->second.IsDirty()) {174if (it->second.coin.IsSpent()) {175m_data.erase(it->first);176} else {177if (cursor.WillErase(*it)) {178m_data[it->first] = std::move(it->second.coin);179} else {180m_data[it->first] = it->second.coin;181}182}183} else {184/* For non-dirty entries being written, compare them with what we have. */185auto it2 = m_data.find(it->first);186if (it->second.coin.IsSpent()) {187assert(it2 == m_data.end());188} else {189assert(it2 != m_data.end());190assert(it->second.coin.out == it2->second.out);191assert(it->second.coin.fCoinBase == it2->second.fCoinBase);192assert(it->second.coin.nHeight == it2->second.nHeight);193}194}195}196}197};199// Hold a non-movable ResetGuard on the heap so StartFetching can remain active200// for the lifetime of a CoinsViewOverlay cache level.201struct OverlayFetchScope202{203CCoinsViewCache::ResetGuard guard;204OverlayFetchScope(CoinsViewOverlay& view, const CBlock& block) : guard(view.StartFetching(block)) {}205};207// Reuse a single global thread pool across fuzz iterations. Creating and destroying a pool every208// iteration leaks memory, since iterations can run faster than the OS can tear down the threads.209std::shared_ptr<ThreadPool> g_thread_pool{std::make_shared<ThreadPool>("cache_fuzz")};211void StartPoolIfNeeded()212{213if (!g_thread_pool->WorkersCount()) g_thread_pool->Start(DEFAULT_PREVOUTFETCH_THREADS);214}216} // namespace218FUZZ_TARGET(coinscache_sim, .init = [] { static auto setup{MakeNoLogFileContext<>()}; })219{220SeedRandomStateForTest(SeedRand::ZEROS);221StartPoolIfNeeded();222/** Precomputed COutPoint and CCoins values. */223static const PrecomputedData data;225/** Dummy coinsview instance (base of the hierarchy). */226CoinsViewBottom bottom;227/** Real CCoinsViewCache objects. */228std::vector<std::unique_ptr<CCoinsViewCache>> caches;229/** Long-lived StartFetching guards, parallel to `caches` (entries are nullptr unless corresponding level is a CoinsViewOverlay). */230std::vector<std::unique_ptr<OverlayFetchScope>> fetch_scopes;231/** Simulated cache data (sim_caches[0] matches bottom, sim_caches[i+1] matches caches[i]). */232CacheLevel sim_caches[MAX_CACHES + 1];233/** Current height in the simulation. */234uint32_t current_height = 1U;236// Initialize bottom simulated cache.237sim_caches[0].Wipe();239/** Helper lookup function in the simulated cache stack. */240auto lookup = [&](uint32_t outpointidx, int sim_idx = -1) -> std::optional<std::pair<coinidx_type, uint32_t>> {241uint32_t cache_idx = sim_idx == -1 ? caches.size() : sim_idx;242while (true) {243const auto& entry = sim_caches[cache_idx].entry[outpointidx];244if (entry.entrytype == EntryType::UNSPENT) {245return {{entry.coinidx, entry.height}};246} else if (entry.entrytype == EntryType::SPENT) {247return std::nullopt;248};249if (cache_idx == 0) break;250--cache_idx;
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.