log: update progress calculations for background validation
What changed, and why it matters
This commit only changes how Bitcoin Core calculates and prints a progress percentage in log messages during a special background validation mode (used with the assume-utxo feature). It does not change consensus rules, network behavior, wallet handling, or any security-critical logic. The old code could show a misleading progress number; the new code makes the log estimate more accurate. There is no security vulnerability here.
No security action required. Treat as a normal logging/UX improvement. Reviewers may optionally verify that the new progress fraction is bounded (it is, by construction, since pindex is at or before target_block).
Security signals we found
No changes to consensus, cryptography, networking, or transaction validation rules
Change is confined to logging/progress estimation code paths
No input parsing, memory allocation, or privilege boundary changes
No bug fixes, bounds checks, or hardening patterns present
Evidence from the diff
The patch adds a new ChainstateManager::GetBackgroundVerificationProgress() helper and a background_validation flag to UpdateTipLog(). When logging progress for the historical/background chainstate in an assume-utxo setup, it now divides the current block’s m_chain_tx_count by the snapshot target block’s m_chain_tx_count instead of by the live chain tip’s transaction count. This fixes a cosmetic/log-only miscalculation where background validation progress could appear to jump near 100% before historical validation was actually complete. GuessVerificationProgress() remains unchanged for normal use.
Changed components
src/validation.cpp logging helper UpdateTipLog()src/validation.cpp Chainstate::UpdateTip()src/validation.cpp ChainstateManager::GetBackgroundVerificationProgress()src/validation.h ChainstateManager method declarationsInspect captured patch +25 / −5
diff --git a/src/validation.cpp b/src/validation.cpp
index 1efe109b..59654c9d 100644
--- a/src/validation.cpp
+++ b/src/validation.cpp
@@ -2858,7 +2858,8 @@ static void UpdateTipLog(
const CBlockIndex* tip,
const std::string& func_name,
const std::string& prefix,
- const std::string& warning_messages) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
+ const std::string& warning_messages,
+ const bool background_validation) EXCLUSIVE_LOCKS_REQUIRED(::cs_main)
{
AssertLockHeld(::cs_main);
@@ -2869,7 +2870,7 @@ static void UpdateTipLog(
tip->GetBlockHash().ToString(), tip->nHeight, tip->nVersion,
log(tip->nChainWork.getdouble()) / log(2.0), tip->m_chain_tx_count,
FormatISO8601DateTime(tip->GetBlockTime()),
- chainman.GuessVerificationProgress(tip),
+ background_validation ? chainman.GetBackgroundVerificationProgress(*tip) : chainman.GuessVerificationProgress(tip),
coins_tip.DynamicMemoryUsage() * (1.0 / (1 << 20)),
coins_tip.GetCacheSize(),
!warning_messages.empty() ? strprintf(" warning='%s'", warning_messages) : "");
@@ -2886,7 +2887,7 @@ void Chainstate::UpdateTip(const CBlockIndex* pindexNew)
// Only log every so often so that we don't bury log messages at the tip.
constexpr int BACKGROUND_LOG_INTERVAL = 2000;
if (pindexNew->nHeight % BACKGROUND_LOG_INTERVAL == 0) {
- UpdateTipLog(m_chainman, coins_tip, pindexNew, __func__, "[background validation] ", "");
+ UpdateTipLog(m_chainman, coins_tip, pindexNew, __func__, "[background validation] ", "", /*background_validation=*/true);
}
return;
}
@@ -2909,7 +2910,7 @@ void Chainstate::UpdateTip(const CBlockIndex* pindexNew)
}
}
UpdateTipLog(m_chainman, coins_tip, pindexNew, __func__, "",
- util::Join(warning_messages, Untranslated(", ")).original);
+ util::Join(warning_messages, Untranslated(", ")).original, /*background_validation=*/false);
}
/** Disconnect m_chain's tip.
@@ -5520,6 +5521,19 @@ double ChainstateManager::GuessVerificationProgress(const CBlockIndex* pindex) c
return std::min<double>(pindex->m_chain_tx_count / fTxTotal, 1.0);
}
+double ChainstateManager::GetBackgroundVerificationProgress(const CBlockIndex& pindex) const
+{
+ AssertLockHeld(GetMutex());
+ Assert(HistoricalChainstate());
+ auto target_block = HistoricalChainstate()->TargetBlock();
+
+ if (pindex.m_chain_tx_count == 0 || target_block->m_chain_tx_count == 0) {
+ LogDebug(BCLog::VALIDATION, "[background validation] Block %d has unset m_chain_tx_count. Unable to estimate verification progress.", pindex.nHeight);
+ return 0.0;
+ }
+ return static_cast<double>(pindex.m_chain_tx_count) / static_cast<double>(target_block->m_chain_tx_count);
+}
+
Chainstate& ChainstateManager::InitializeChainstate(CTxMemPool* mempool)
{
AssertLockHeld(::cs_main);
diff --git a/src/validation.h b/src/validation.h
index 729cdbaa..cee0dd72 100644
--- a/src/validation.h
+++ b/src/validation.h
@@ -1194,9 +1194,15 @@ public:
/** Check whether we are doing an initial block download (synchronizing from disk or network) */
bool IsInitialBlockDownload() const noexcept;
- /** Guess verification progress (as a fraction between 0.0=genesis and 1.0=current tip). */
+ /** Guess verification progress (as a fraction between 0.0=genesis and 1.0=current tip).
+ * This is also the case in the assumeutxo context, meaning that the progress reported for
+ * the snapshot chainstate may suggest that all historical blocks have already been verified
+ * even though that may not actually be the case. */
double GuessVerificationProgress(const CBlockIndex* pindex) const EXCLUSIVE_LOCKS_REQUIRED(GetMutex());
+ /** Guess background verification progress in case assume-utxo was used (as a fraction between 0.0=genesis and 1.0=snapshot blocks). */
+ double GetBackgroundVerificationProgress(const CBlockIndex& pindex) const EXCLUSIVE_LOCKS_REQUIRED(GetMutex());
+
/**
* Import blocks from an external file
*
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.