What changed, and why it matters
This commit adds a new read-only helper called PeekCoin() to Bitcoin Core's coin-cache system. Unlike the existing GetCoin(), PeekCoin() looks up a coin through layers of cache without writing the result into parent caches. The stated purpose is to avoid polluting the main UTXO cache when validating blocks that might turn out to be invalid. It is a defensive, non-breaking code-quality change with no direct vulnerability fix described.
No immediate action required. Treat as a routine defensive refactor. Review future commits that actually switch ConnectBlock or other consensus paths from GetCoin() to PeekCoin() to confirm the intended cache-behavior change does not introduce logic bugs or performance regressions.
Security signals we found
New non-caching lookup path added to UTXO cache hierarchy
Commit message explicitly mentions avoiding cache pollution during validation of invalid blocks
No bug fix, bounds check, memory safety, or cryptographic change present
No caller in production consensus code is changed in this commit; only tests/fuzz use PeekCoin()
Evidence from the diff
The patch introduces a virtual PeekCoin() method on CCoinsView and overrides it in CCoinsViewBacked, CCoinsViewCache, and CCoinsViewErrorCatcher. In CCoinsViewCache, PeekCoin() checks the local cacheCoins map first and, if not found, delegates to base->PeekCoin() rather than calling GetCoin(), which would cache the fetched coin. The base CCoinsView implementation simply delegates to GetCoin(). Tests and a fuzz target are updated to exercise the new method. The commit message frames this as useful for ephemeral views during ConnectBlock to avoid polluting CoinsTip() when processing invalid blocks.
Changed components
src/coins.cppsrc/coins.hsrc/test/coins_tests.cppsrc/test/fuzz/coinscache_sim.cppInspect captured patch +48 / −1
diff --git a/src/coins.cpp b/src/coins.cpp
index a3bc369d..8d51737d 100644
--- a/src/coins.cpp
+++ b/src/coins.cpp
@@ -15,6 +15,7 @@ TRACEPOINT_SEMAPHORE(utxocache, spent);
TRACEPOINT_SEMAPHORE(utxocache, uncache);
std::optional<Coin> CCoinsView::GetCoin(const COutPoint& outpoint) const { return std::nullopt; }
+std::optional<Coin> CCoinsView::PeekCoin(const COutPoint& outpoint) const { return GetCoin(outpoint); }
uint256 CCoinsView::GetBestBlock() const { return uint256(); }
std::vector<uint256> CCoinsView::GetHeadBlocks() const { return std::vector<uint256>(); }
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<Coin> CCoinsViewBacked::GetCoin(const COutPoint& outpoint) const { return base->GetCoin(outpoint); }
+std::optional<Coin> 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<uint256> CCoinsViewBacked::GetHeadBlocks() const { return base->GetHeadBlocks(); }
@@ -39,6 +41,14 @@ void CCoinsViewBacked::BatchWrite(CoinsViewCacheCursor& cursor, const uint256& h
std::unique_ptr<CCoinsViewCursor> CCoinsViewBacked::Cursor() const { return base->Cursor(); }
size_t CCoinsViewBacked::EstimateSize() const { return base->EstimateSize(); }
+std::optional<Coin> 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<bool>([&]() { return CCoinsViewBacked::HaveCoin(outpoint); }, m_err_callbacks);
}
+
+std::optional<Coin> CCoinsViewErrorCatcher::PeekCoin(const COutPoint& outpoint) const
+{
+ return ExecuteBackedWrapper<std::optional<Coin>>([&]() { return CCoinsViewBacked::PeekCoin(outpoint); }, m_err_callbacks);
+}
diff --git a/src/coins.h b/src/coins.h
index 4b39c0ba..36516ef7 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<Coin> 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<Coin> 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<Coin> GetCoin(const COutPoint& outpoint) const override;
+ std::optional<Coin> PeekCoin(const COutPoint& outpoint) const override;
bool HaveCoin(const COutPoint &outpoint) const override;
uint256 GetBestBlock() const override;
std::vector<uint256> GetHeadBlocks() const override;
@@ -386,6 +393,7 @@ public:
// Standard CCoinsView methods
std::optional<Coin> GetCoin(const COutPoint& outpoint) const override;
+ std::optional<Coin> 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<Coin> GetCoin(const COutPoint& outpoint) const override;
bool HaveCoin(const COutPoint &outpoint) const override;
+ std::optional<Coin> 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 4d6ee661..6ac9d485 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 c46c91dc..b1b28423 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);
Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.