refactor: remove redundant usage tracking from `CoinsViewCacheCursor`
What changed, and why it matters
This is a code cleanup (refactor) in Bitcoin Core's coin cache bookkeeping. It removes a duplicate subtraction of memory usage that could never actually change the total, because spent coins already report zero memory usage. The change replaces the redundant subtraction with an assertion that documents this invariant. There is no security vulnerability here.
No security action required. This is a safe refactor. Normal code review and merge process is sufficient.
Security signals we found
No strong security signals were identified.
Evidence from the diff
CoinsViewCacheCursor::NextAndMaybeErase() previously decremented cachedCoinsUsage by current.second.coin.DynamicMemoryUsage() when erasing spent entries during Sync(). However, SpendCoin() already decrements cachedCoinsUsage and clears the coin’s scriptPubKey, making DynamicMemoryUsage() zero. Therefore the cursor’s subtraction was redundant and had no effect. The commit removes the m_usage reference from the cursor entirely and replaces the subtraction with assert(current.second.coin.DynamicMemoryUsage() == 0). Tests and fuzz targets are updated to stop tracking usage for the cursor.
Changed components
src/coins.cppsrc/coins.hsrc/test/coins_tests.cppsrc/test/fuzz/coins_view.cppInspect captured patch +10 / −14
diff --git a/src/coins.cpp b/src/coins.cpp
index 59c7d67c..28408481 100644
--- a/src/coins.cpp
+++ b/src/coins.cpp
@@ -249,7 +249,7 @@ bool CCoinsViewCache::BatchWrite(CoinsViewCacheCursor& cursor, const uint256 &ha
}
bool CCoinsViewCache::Flush() {
- auto cursor{CoinsViewCacheCursor(cachedCoinsUsage, m_sentinel, cacheCoins, /*will_erase=*/true)};
+ auto cursor{CoinsViewCacheCursor(m_sentinel, cacheCoins, /*will_erase=*/true)};
bool fOk = base->BatchWrite(cursor, hashBlock);
if (fOk) {
cacheCoins.clear();
@@ -261,7 +261,7 @@ bool CCoinsViewCache::Flush() {
bool CCoinsViewCache::Sync()
{
- auto cursor{CoinsViewCacheCursor(cachedCoinsUsage, m_sentinel, cacheCoins, /*will_erase=*/false)};
+ auto cursor{CoinsViewCacheCursor(m_sentinel, cacheCoins, /*will_erase=*/false)};
bool fOk = base->BatchWrite(cursor, hashBlock);
if (fOk) {
if (m_sentinel.second.Next() != &m_sentinel) {
diff --git a/src/coins.h b/src/coins.h
index 6725d5a5..2fcc764a 100644
--- a/src/coins.h
+++ b/src/coins.h
@@ -271,11 +271,10 @@ struct CoinsViewCacheCursor
//! This is an optimization compared to erasing all entries as the cursor iterates them when will_erase is set.
//! Calling CCoinsMap::clear() afterwards is faster because a CoinsCachePair cannot be coerced back into a
//! CCoinsMap::iterator to be erased, and must therefore be looked up again by key in the CCoinsMap before being erased.
- CoinsViewCacheCursor(size_t& usage LIFETIMEBOUND,
- CoinsCachePair& sentinel LIFETIMEBOUND,
- CCoinsMap& map LIFETIMEBOUND,
- bool will_erase) noexcept
- : m_usage(usage), m_sentinel(sentinel), m_map(map), m_will_erase(will_erase) {}
+ CoinsViewCacheCursor(CoinsCachePair& sentinel LIFETIMEBOUND,
+ CCoinsMap& map LIFETIMEBOUND,
+ bool will_erase) noexcept
+ : m_sentinel(sentinel), m_map(map), m_will_erase(will_erase) {}
inline CoinsCachePair* Begin() const noexcept { return m_sentinel.second.Next(); }
inline CoinsCachePair* End() const noexcept { return &m_sentinel; }
@@ -288,7 +287,7 @@ struct CoinsViewCacheCursor
// Otherwise, clear the state of the entry.
if (!m_will_erase) {
if (current.second.coin.IsSpent()) {
- m_usage -= current.second.coin.DynamicMemoryUsage();
+ assert(current.second.coin.DynamicMemoryUsage() == 0); // scriptPubKey was already cleared in SpendCoin
m_map.erase(current.first);
} else {
current.second.SetClean();
@@ -299,7 +298,6 @@ struct CoinsViewCacheCursor
inline bool WillErase(CoinsCachePair& current) const noexcept { return m_will_erase || current.second.coin.IsSpent(); }
private:
- size_t& m_usage;
CoinsCachePair& m_sentinel;
CCoinsMap& m_map;
bool m_will_erase;
diff --git a/src/test/coins_tests.cpp b/src/test/coins_tests.cpp
index 6ce0c799..f3926ef4 100644
--- a/src/test/coins_tests.cpp
+++ b/src/test/coins_tests.cpp
@@ -662,8 +662,8 @@ static void WriteCoinsViewEntry(CCoinsView& view, const MaybeCoin& cache_coin)
sentinel.second.SelfRef(sentinel);
CCoinsMapMemoryResource resource;
CCoinsMap map{0, CCoinsMap::hasher{}, CCoinsMap::key_equal{}, &resource};
- auto usage{cache_coin ? InsertCoinsMapEntry(map, sentinel, *cache_coin) : 0};
- auto cursor{CoinsViewCacheCursor(usage, sentinel, map, /*will_erase=*/true)};
+ if (cache_coin) InsertCoinsMapEntry(map, sentinel, *cache_coin);
+ auto cursor{CoinsViewCacheCursor(sentinel, map, /*will_erase=*/true)};
BOOST_CHECK(view.BatchWrite(cursor, {}));
}
diff --git a/src/test/fuzz/coins_view.cpp b/src/test/fuzz/coins_view.cpp
index dacef9a7..e8ba655e 100644
--- a/src/test/fuzz/coins_view.cpp
+++ b/src/test/fuzz/coins_view.cpp
@@ -131,7 +131,6 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsView& backend
[&] {
CoinsCachePair sentinel{};
sentinel.second.SelfRef(sentinel);
- size_t usage{0};
CCoinsMapMemoryResource resource;
CCoinsMap coins_map{0, SaltedOutpointHasher{/*deterministic=*/true}, CCoinsMap::key_equal{}, &resource};
LIMITED_WHILE(good_data && fuzzed_data_provider.ConsumeBool(), 10'000)
@@ -152,11 +151,10 @@ void TestCoinsView(FuzzedDataProvider& fuzzed_data_provider, CCoinsView& backend
auto it{coins_map.emplace(random_out_point, std::move(coins_cache_entry)).first};
if (dirty) CCoinsCacheEntry::SetDirty(*it, sentinel);
if (fresh) CCoinsCacheEntry::SetFresh(*it, sentinel);
- usage += it->second.coin.DynamicMemoryUsage();
}
bool expected_code_path = false;
try {
- auto cursor{CoinsViewCacheCursor(usage, sentinel, coins_map, /*will_erase=*/true)};
+ auto cursor{CoinsViewCacheCursor(sentinel, coins_map, /*will_erase=*/true)};
uint256 best_block{coins_view_cache.GetBestBlock()};
if (fuzzed_data_provider.ConsumeBool()) best_block = ConsumeUInt256(fuzzed_data_provider);
// Set best block hash to non-null to satisfy the assertion in CCoinsViewDB::BatchWrite().
Why this scored 14/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.