validation: reuse same CCoinsViewCache for every ConnectBlock call
What changed, and why it matters
This is a performance optimization for Bitcoin Core's block validation. It reuses a single temporary cache instead of creating and destroying a new one for every block. There is no security issue visible in the change; it is purely about reducing memory allocation overhead.
No security action required. Treat as a normal performance/refactoring change.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces a persistent CCoinsViewCache (m_connect_block_view) layered on top of the existing CoinsViews cache and reuses it across ConnectBlock calls. A CreateResetGuard is used in ConnectTip to clear the cache between blocks, and the cache is flushed only on successful connection. The change avoids repeated allocations/deallocations of a temporary cache during block validation.
Changed components
src/validation.cppsrc/validation.hChainstate::ConnectTipCoinsViews::InitCacheInspect captured patch +8 / −2
diff --git a/src/validation.cpp b/src/validation.cpp
index b6da6d2d..b505771e 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -1861,6 +1861,7 @@ void CoinsViews::InitCache()
{
AssertLockHeld(::cs_main);
m_cacheview = std::make_unique<CCoinsViewCache>(&m_catcherview);
+ m_connect_block_view = std::make_unique<CCoinsViewCache>(&*m_cacheview);
}
Chainstate::Chainstate(
@@ -3098,7 +3099,8 @@ bool Chainstate::ConnectTip(
LogDebug(BCLog::BENCH, " - Load block from disk: %.2fms\n",
Ticks<MillisecondsDouble>(time_2 - time_1));
{
- CCoinsViewCache view(&CoinsTip());
+ CCoinsViewCache& view{*m_coins_views->m_connect_block_view};
+ const auto reset_guard{view.CreateResetGuard()};
bool rv = ConnectBlock(*block_to_connect, state, pindexNew, view);
if (m_chainman.m_options.signals) {
m_chainman.m_options.signals->BlockChecked(block_to_connect, state);
@@ -3116,7 +3118,7 @@ bool Chainstate::ConnectTip(
Ticks<MillisecondsDouble>(time_3 - time_2),
Ticks<SecondsDouble>(m_chainman.time_connect_total),
Ticks<MillisecondsDouble>(m_chainman.time_connect_total) / m_chainman.num_blocks_total);
- view.Flush(/*will_reuse_cache=*/false); // local CCoinsViewCache goes out of scope
+ view.Flush(/*will_reuse_cache=*/false); // No need to reallocate since it only has capacity for 1 block
}
const auto time_4{SteadyClock::now()};
m_chainman.time_flush += time_4 - time_3;
diff --git a/src/validation.h b/src/validation.h
index e4b1e555..c5e29ab6 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -488,6 +488,10 @@ public:
//! can fit per the dbcache setting.
std::unique_ptr<CCoinsViewCache> m_cacheview GUARDED_BY(cs_main);
+ //! Temporary CCoinsViewCache layered on top of m_cacheview and passed to ConnectBlock().
+ //! Reset between calls and flushed only on success, so invalid blocks don't pollute the underlying cache.
+ std::unique_ptr<CCoinsViewCache> m_connect_block_view GUARDED_BY(cs_main);
+
//! This constructor initializes CCoinsViewDB and CCoinsViewErrorCatcher instances, but it
//! *does not* create a CCoinsViewCache instance by default. This is done separately because the
//! presence of the cache has implications on whether or not we're allowed to flush the cache's
Why this scored 15/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.