From 69b01af0eb9017a6ae7ca3134c9dcf89e74dbfa8 Mon Sep 17 00:00:00 2001 From: Andrew Toth Date: Wed, 24 Dec 2025 09:00:10 +0200 Subject: [PATCH] coins: add PeekCoin() Introduce a helper to look up a Coin through a stack of CCoinsViewCache layers without populating parent caches. This is useful for ephemeral views (e.g. during ConnectBlock) that want to avoid polluting CoinsTip() when validating invalid blocks. Co-authored-by: l0rinc Co-authored-by: Pieter Wuille Co-authored-by: Ryan Ofsky --- src/coins.cpp | 15 +++++++++++++++ src/coins.h | 9 +++++++++ src/test/coins_tests.cpp | 21 +++++++++++++++++++++ src/test/fuzz/coinscache_sim.cpp | 4 +++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/coins.cpp b/src/coins.cpp index a3bc369de30..8d51737deb8 100644 --- a/src/coins.cpp +++ b/src/coins.cpp @@ -15,6 +15,7 @@ TRACEPOINT_SEMAPHORE(utxocache, spent); TRACEPOINT_SEMAPHORE(utxocache, uncache); std::optional CCoinsView::GetCoin(const COutPoint& outpoint) const { return std::nullopt; } +std::optional CCoinsView::PeekCoin(const COutPoint& outpoint) const { return GetCoin(outpoint); } uint256 CCoinsView::GetBestBlock() const { return uint256(); } std::vector CCoinsView::GetHeadBlocks() const { return std::vector(); } void CCoinsView::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& hashBlock) @@ -31,6 +32,7 @@ bool CCoinsView::HaveCoin(const COutPoint &outpoint) const CCoinsViewBacked::CCoinsViewBacked(CCoinsView *viewIn) : base(viewIn) { } std::optional CCoinsViewBacked::GetCoin(const COutPoint& outpoint) const { return base->GetCoin(outpoint); } +std::optional CCoinsViewBacked::PeekCoin(const COutPoint& outpoint) const { return base->PeekCoin(outpoint); } bool CCoinsViewBacked::HaveCoin(const COutPoint &outpoint) const { return base->HaveCoin(outpoint); } uint256 CCoinsViewBacked::GetBestBlock() const { return base->GetBestBlock(); } std::vector CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); } @@ -39,6 +41,14 @@ void CCoinsViewBacked::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& h std::unique_ptr CCoinsViewBacked::Cursor() const { return base->Cursor(); } size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); } +std::optional CCoinsViewCache::PeekCoin(const COutPoint& outpoint) const +{ + if (auto it{cacheCoins.find(outpoint)}; it != cacheCoins.end()) { + return it->second.coin.IsSpent() ? std::nullopt : std::optional{it->second.coin}; + } + return base->PeekCoin(outpoint); +} + CCoinsViewCache::CCoinsViewCache(CCoinsView* baseIn, bool deterministic) : CCoinsViewBacked(baseIn), m_deterministic(deterministic), cacheCoins(0, SaltedOutpointHasher(/*deterministic=*/deterministic), CCoinsMap::key_equal{}, &m_cache_coins_memory_resource) @@ -393,3 +403,8 @@ bool CCoinsViewErrorCatcher::HaveCoin(const COutPoint& outpoint) const { return ExecuteBackedWrapper([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks); } + +std::optional CCoinsViewErrorCatcher::PeekCoin(const COutPoint& outpoint) const +{ + return ExecuteBackedWrapper>([&]() { return CCoinsViewBacked::PeekCoin(outpoint); }, m_err_callbacks); +} diff --git a/src/coins.h b/src/coins.h index 4b39c0bacd2..36516ef7038 100644 --- a/src/coins.h +++ b/src/coins.h @@ -302,9 +302,15 @@ class CCoinsView { public: //! Retrieve the Coin (unspent transaction output) for a given outpoint. + //! May populate the cache. Use PeekCoin() to perform a non-caching lookup. virtual std::optional GetCoin(const COutPoint& outpoint) const; + //! Retrieve the Coin (unspent transaction output) for a given outpoint, without caching results. + //! Does not populate the cache. Use GetCoin() to cache the result. + virtual std::optional PeekCoin(const COutPoint& outpoint) const; + //! Just check whether a given outpoint is unspent. + //! May populate the cache. Use PeekCoin() to perform a non-caching lookup. virtual bool HaveCoin(const COutPoint &outpoint) const; //! Retrieve the block hash whose state this CCoinsView currently represents @@ -340,6 +346,7 @@ protected: public: CCoinsViewBacked(CCoinsView *viewIn); std::optional GetCoin(const COutPoint& outpoint) const override; + std::optional PeekCoin(const COutPoint& outpoint) const override; bool HaveCoin(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; std::vector GetHeadBlocks() const override; @@ -386,6 +393,7 @@ public: // Standard CCoinsView methods std::optional GetCoin(const COutPoint& outpoint) const override; + std::optional PeekCoin(const COutPoint& outpoint) const override; bool HaveCoin(const COutPoint &outpoint) const override; uint256 GetBestBlock() const override; void SetBestBlock(const uint256 &hashBlock); @@ -536,6 +544,7 @@ public: std::optional GetCoin(const COutPoint& outpoint) const override; bool HaveCoin(const COutPoint &outpoint) const override; + std::optional PeekCoin(const COutPoint& outpoint) const override; private: /** A list of callbacks to execute upon leveldb read error. */ diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp index 4d6ee6613ec..6ac9d485f3a 100644 --- a/src/test/coins_tests.cpp +++ b/src/test/coins_tests.cpp @@ -1159,4 +1159,25 @@ BOOST_AUTO_TEST_CASE(ccoins_reset_guard) BOOST_CHECK(!root_cache.HaveCoinInCache(outpoint)); } +BOOST_AUTO_TEST_CASE(ccoins_peekcoin) +{ + CCoinsViewTest base{m_rng}; + + // Populate the base view with a coin. + const COutPoint outpoint{Txid::FromUint256(m_rng.rand256()), m_rng.rand32()}; + const Coin coin{CTxOut{m_rng.randrange(10), CScript{}}, 1, false}; + { + CCoinsViewCache cache{&base}; + cache.AddCoin(outpoint, Coin{coin}, /*possible_overwrite=*/false); + cache.Flush(); + } + + // Verify PeekCoin can read through the cache stack without mutating the intermediate cache. + CCoinsViewCacheTest main_cache{&base}; + const auto fetched{main_cache.PeekCoin(outpoint)}; + BOOST_CHECK(fetched.has_value()); + BOOST_CHECK(*fetched == coin); + BOOST_CHECK(!main_cache.HaveCoinInCache(outpoint)); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/fuzz/coinscache_sim.cpp b/src/test/fuzz/coinscache_sim.cpp index c46c91dc4c1..b1b2842300d 100644 --- a/src/test/fuzz/coinscache_sim.cpp +++ b/src/test/fuzz/coinscache_sim.cpp @@ -257,7 +257,9 @@ FUZZ_TARGET(coinscache_sim) // Look up in simulation data. auto sim = lookup(outpointidx); // Look up in real caches. - auto realcoin = caches.back()->GetCoin(data.outpoints[outpointidx]); + auto realcoin = provider.ConsumeBool() ? + caches.back()->PeekCoin(data.outpoints[outpointidx]) : + caches.back()->GetCoin(data.outpoints[outpointidx]); // Compare results. if (!sim.has_value()) { assert(!realcoin);