validation: Don't use m_chain.Tip() in FlushStateToDisk
What changed, and why it matters
This commit fixes a minor bookkeeping bug in Bitcoin Core. When flushing state to disk during a block disconnection, the code was incorrectly reporting the current chain tip (the block being disconnected) as the flushed block, instead of the last block actually written to disk. This could mislead indexers or other listeners tracking the chain's flushed state, but it does not directly allow theft, double-spending, or consensus failure.
Treat as a routine correctness fix. Index operators and downstream services consuming ChainStateFlushed should ensure they handle reorg/disconnect edge cases gracefully. No emergency deployment is warranted solely for this change.
Security signals we found
Incorrect state reporting in a chain-state flush signal
Potential inconsistency between on-disk state and signaled locator during reorg/disconnect paths
Indexers relying on ChainStateFlushed could observe a stale or mismatched block locator
Evidence from the diff
In Chainstate::FlushStateToDisk, the ChainStateFlushed signal previously used GetLocator(m_chain.Tip()). During DisconnectBlock, FlushStateToDisk can run after coins are updated but before m_chain.Tip() is moved back from the disconnected block, so the locator pointed to the wrong block. The patch changes the locator to use m_last_flushed_block, which reflects the actual persisted state. An outdated comment about wallets using the signal is also removed.
Changed components
src/validation.cppChainstate::FlushStateToDiskChainStateFlushed signalblock indexers (e.g., coinstats, address, txindex)Inspect captured patch +1 / −2
diff --git a/src/validation.cpp b/src/validation.cpp
index 9df377fb..3f4b60e6 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2841,8 +2841,7 @@ bool Chainstate::FlushStateToDisk(
}
if (full_flush_completed) {
if (m_chainman.m_options.signals) {
- // Update best block in wallet (so we can detect restored wallets).
- m_chainman.m_options.signals->ChainStateFlushed(this->GetRole(), GetLocator(m_chain.Tip()));
+ m_chainman.m_options.signals->ChainStateFlushed(this->GetRole(), GetLocator(m_last_flushed_block));
}
if (!m_chainman.m_interrupt && ShouldCompactChainstate(m_chainman.IsInitialBlockDownload())) {
Why this scored 28/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.