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.
501stack.pop_back();502}503if (stack.size() == 0 || (stack.size() < 4 && m_rng.randbool())) {504CCoinsView* tip = &base;505if (stack.size() > 0) {506tip = stack.back().get();507}508stack.push_back(std::make_unique<CCoinsViewCacheTest>(tip));509}510}511}513// Verify coverage.514BOOST_CHECK(spent_a_duplicate_coinbase);515}517BOOST_AUTO_TEST_CASE(ccoins_serialization)518{519// Good example520Coin cc1;521SpanReader{"97f23c835800816115944e077fe7c803cfa57f29b36bf87c1d35"_hex} >> cc1;522BOOST_CHECK_EQUAL(cc1.IsCoinBase(), false);523BOOST_CHECK_EQUAL(cc1.nHeight, 203998U);524BOOST_CHECK_EQUAL(cc1.out.nValue, CAmount{60000000000});525BOOST_CHECK_EQUAL(HexStr(cc1.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160("816115944e077fe7c803cfa57f29b36bf87c1d35"_hex_u8)))));527// Good example528Coin cc2;529SpanReader{"8ddf77bbd123008c988f1a4a4de2161e0f50aac7f17e7f9555caa4"_hex} >> cc2;530BOOST_CHECK_EQUAL(cc2.IsCoinBase(), true);531BOOST_CHECK_EQUAL(cc2.nHeight, 120891U);532BOOST_CHECK_EQUAL(cc2.out.nValue, 110397);533BOOST_CHECK_EQUAL(HexStr(cc2.out.scriptPubKey), HexStr(GetScriptForDestination(PKHash(uint160("8c988f1a4a4de2161e0f50aac7f17e7f9555caa4"_hex_u8)))));535// Smallest possible example536Coin cc3;537SpanReader{"000006"_hex} >> cc3;538BOOST_CHECK_EQUAL(cc3.IsCoinBase(), false);539BOOST_CHECK_EQUAL(cc3.nHeight, 0U);540BOOST_CHECK_EQUAL(cc3.out.nValue, 0);541BOOST_CHECK_EQUAL(cc3.out.scriptPubKey.size(), 0U);543// scriptPubKey that ends beyond the end of the stream544BOOST_CHECK_EXCEPTION(SpanReader{"000007"_hex} >> Coin{}, std::ios_base::failure, HasReason{"end of data"});546// Very large scriptPubKey (3*10^9 bytes) past the end of the stream547DataStream tmp{};548uint64_t x = 3000000000ULL;549tmp << VARINT(x);550BOOST_CHECK_EQUAL(HexStr(tmp), "8a95c0bb00");551BOOST_CHECK_EXCEPTION(SpanReader{"00008a95c0bb00"_hex} >> Coin{}, std::ios_base::failure, HasReason{"end of data"});552}554const static COutPoint OUTPOINT;555constexpr CAmount SPENT {-1};556constexpr CAmount ABSENT{-2};557constexpr CAmount VALUE1{100};558constexpr CAmount VALUE2{200};559constexpr CAmount VALUE3{300};561struct CoinEntry {562enum class State { CLEAN, DIRTY, FRESH, DIRTY_FRESH };564const CAmount value;565const State state;567constexpr CoinEntry(const CAmount v, const State s) : value{v}, state{s} {}569bool operator==(const CoinEntry& o) const = default;570friend std::ostream& operator<<(std::ostream& os, const CoinEntry& e) { return os << e.value << ", " << e.state; }572constexpr bool IsDirtyFresh() const { return state == State::DIRTY_FRESH; }573constexpr bool IsDirty() const { return state == State::DIRTY || IsDirtyFresh(); }574constexpr bool IsFresh() const { return state == State::FRESH || IsDirtyFresh(); }576static constexpr State ToState(const bool is_dirty, const bool is_fresh) {577if (is_dirty && is_fresh) return State::DIRTY_FRESH;578if (is_dirty) return State::DIRTY;579if (is_fresh) return State::FRESH;580return State::CLEAN;581}582};584using MaybeCoin = std::optional<CoinEntry>;585using CoinOrError = std::variant<MaybeCoin, std::string>;587constexpr MaybeCoin MISSING {std::nullopt};588constexpr MaybeCoin SPENT_DIRTY {{SPENT, CoinEntry::State::DIRTY}};589constexpr MaybeCoin SPENT_DIRTY_FRESH {{SPENT, CoinEntry::State::DIRTY_FRESH}};590constexpr MaybeCoin SPENT_FRESH {{SPENT, CoinEntry::State::FRESH}};591constexpr MaybeCoin SPENT_CLEAN {{SPENT, CoinEntry::State::CLEAN}};592constexpr MaybeCoin VALUE1_DIRTY {{VALUE1, CoinEntry::State::DIRTY}};593constexpr MaybeCoin VALUE1_DIRTY_FRESH{{VALUE1, CoinEntry::State::DIRTY_FRESH}};594constexpr MaybeCoin VALUE1_FRESH {{VALUE1, CoinEntry::State::FRESH}};595constexpr MaybeCoin VALUE1_CLEAN {{VALUE1, CoinEntry::State::CLEAN}};596constexpr MaybeCoin VALUE2_DIRTY {{VALUE2, CoinEntry::State::DIRTY}};597constexpr MaybeCoin VALUE2_DIRTY_FRESH{{VALUE2, CoinEntry::State::DIRTY_FRESH}};598constexpr MaybeCoin VALUE2_FRESH {{VALUE2, CoinEntry::State::FRESH}};599constexpr MaybeCoin VALUE2_CLEAN {{VALUE2, CoinEntry::State::CLEAN}};600constexpr MaybeCoin VALUE3_DIRTY {{VALUE3, CoinEntry::State::DIRTY}};601constexpr MaybeCoin VALUE3_DIRTY_FRESH{{VALUE3, CoinEntry::State::DIRTY_FRESH}};603constexpr auto EX_OVERWRITE_UNSPENT{"Attempted to overwrite an unspent coin (when possible_overwrite is false)"};604constexpr auto EX_FRESH_MISAPPLIED {"FRESH flag misapplied to coin that exists in parent cache"};606static void SetCoinsValue(const CAmount value, Coin& coin)607{608assert(value != ABSENT);609coin.Clear();610assert(coin.IsSpent());611if (value != SPENT) {612coin.out.nValue = value;613coin.nHeight = 1;614assert(!coin.IsSpent());615}616}618static size_t InsertCoinsMapEntry(CCoinsMap& map, CoinsCachePair& sentinel, const CoinEntry& cache_coin)619{620CCoinsCacheEntry entry;621SetCoinsValue(cache_coin.value, entry.coin);622auto [iter, inserted] = map.emplace(OUTPOINT, std::move(entry));623assert(inserted);624if (cache_coin.IsDirty()) CCoinsCacheEntry::SetDirty(*iter, sentinel);625if (cache_coin.IsFresh()) CCoinsCacheEntry::SetFresh(*iter, sentinel);626return iter->second.coin.DynamicMemoryUsage();627}629static MaybeCoin GetCoinsMapEntry(const CCoinsMap& map, const COutPoint& outp = OUTPOINT)630{631if (auto it{map.find(outp)}; it != map.end()) {632return CoinEntry{633it->second.coin.IsSpent() ? SPENT : it->second.coin.out.nValue,634CoinEntry::ToState(it->second.IsDirty(), it->second.IsFresh())};635}636return MISSING;637}639static void WriteCoinsViewEntry(CCoinsView& view, const MaybeCoin& cache_coin)640{641CoinsCachePair sentinel{};642sentinel.second.SelfRef(sentinel);643CCoinsMapMemoryResource resource;644CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};645if (cache_coin) InsertCoinsMapEntry(map, sentinel, *cache_coin);646size_t dirty_count{cache_coin && cache_coin->IsDirty()};647auto cursor{CoinsViewCacheCursor(dirty_count, sentinel, map, /*will_erase=*/true)};648view.BatchWrite(cursor, {});649BOOST_CHECK_EQUAL(dirty_count, 0U);650}652class SingleEntryCacheTest653{654public:655SingleEntryCacheTest(const CAmount base_value, const MaybeCoin& cache_coin)656{657auto base_cache_coin{base_value == ABSENT ? MISSING : CoinEntry{base_value, CoinEntry::State::DIRTY}};658WriteCoinsViewEntry(base, base_cache_coin);659if (cache_coin) {660cache.usage() += InsertCoinsMapEntry(cache.map(), cache.sentinel(), *cache_coin);661cache.dirty() += cache_coin->IsDirty();662}663}665CCoinsViewCacheTest base{&CoinsViewEmpty::Get()};666CCoinsViewCacheTest cache{&base};667};669static void CheckAccessCoin(const CAmount base_value, const MaybeCoin& cache_coin, const MaybeCoin& expected)670{671SingleEntryCacheTest test{base_value, cache_coin};672auto& coin = test.cache.AccessCoin(OUTPOINT);673BOOST_CHECK_EQUAL(coin.IsSpent(), !test.cache.GetCoin(OUTPOINT));674test.cache.SelfTest(/*sanity_check=*/false);675BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), expected);676}678BOOST_AUTO_TEST_CASE(ccoins_access)679{680/* Check AccessCoin behavior, requesting a coin from a cache view layered on681* top of a base view, and checking the resulting entry in the cache after682* the access.683* Base Cache Expected684*/685for (auto base_value : {ABSENT, SPENT, VALUE1}) {686CheckAccessCoin(base_value, MISSING, base_value == VALUE1 ? VALUE1_CLEAN : MISSING);688CheckAccessCoin(base_value, SPENT_CLEAN, SPENT_CLEAN );689CheckAccessCoin(base_value, SPENT_FRESH, SPENT_FRESH );690CheckAccessCoin(base_value, SPENT_DIRTY, SPENT_DIRTY );691CheckAccessCoin(base_value, SPENT_DIRTY_FRESH, SPENT_DIRTY_FRESH );693CheckAccessCoin(base_value, VALUE2_CLEAN, VALUE2_CLEAN );694CheckAccessCoin(base_value, VALUE2_FRESH, VALUE2_FRESH );695CheckAccessCoin(base_value, VALUE2_DIRTY, VALUE2_DIRTY );696CheckAccessCoin(base_value, VALUE2_DIRTY_FRESH, VALUE2_DIRTY_FRESH);697}698}700static void CheckSpendCoins(const CAmount base_value, const MaybeCoin& cache_coin, const MaybeCoin& expected)701{702SingleEntryCacheTest test{base_value, cache_coin};703test.cache.SpendCoin(OUTPOINT);704test.cache.SelfTest();705BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), expected);706}708BOOST_AUTO_TEST_CASE(ccoins_spend)709{710/* Check SpendCoin behavior, requesting a coin from a cache view layered on711* top of a base view, spending, and then checking712* the resulting entry in the cache after the modification.713* Base Cache Expected714*/715for (auto base_value : {ABSENT, SPENT, VALUE1}) {716CheckSpendCoins(base_value, MISSING, base_value == VALUE1 ? SPENT_DIRTY : MISSING);718CheckSpendCoins(base_value, SPENT_CLEAN, SPENT_DIRTY);719CheckSpendCoins(base_value, SPENT_FRESH, MISSING );720CheckSpendCoins(base_value, SPENT_DIRTY, SPENT_DIRTY);721CheckSpendCoins(base_value, SPENT_DIRTY_FRESH, MISSING );723CheckSpendCoins(base_value, VALUE2_CLEAN, SPENT_DIRTY);724CheckSpendCoins(base_value, VALUE2_FRESH, MISSING );725CheckSpendCoins(base_value, VALUE2_DIRTY, SPENT_DIRTY);726CheckSpendCoins(base_value, VALUE2_DIRTY_FRESH, MISSING );727}728}730static void CheckAddCoin(const CAmount base_value, const MaybeCoin& cache_coin, const CAmount modify_value, const CoinOrError& expected, const bool coinbase)731{732SingleEntryCacheTest test{base_value, cache_coin};733bool possible_overwrite{coinbase};734auto add_coin{[&] { test.cache.AddCoin(OUTPOINT, Coin{CTxOut{modify_value, CScript{}}, 1, coinbase}, possible_overwrite); }};735if (auto* expected_coin{std::get_if<MaybeCoin>(&expected)}) {736add_coin();737test.cache.SelfTest();738BOOST_CHECK_EQUAL(GetCoinsMapEntry(test.cache.map()), *expected_coin);739} else {740BOOST_CHECK_EXCEPTION(add_coin(), std::logic_error, HasReason(std::get<std::string>(expected)));741}742}744BOOST_AUTO_TEST_CASE(ccoins_add)745{746/* Check AddCoin behavior, requesting a new coin from a cache view,747* writing a modification to the coin, and then checking the resulting748* entry in the cache after the modification. Verify behavior with the749* AddCoin coinbase argument set to false, and to true.750* Base Cache Write Expected Coinbase
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.