Merge bitcoin/bitcoin#33259: rpc, logging: add backgroundvalidation to getblockchaininfo

25f69d970a release note (Pol Espinasa)
af629821cf test: add background validation test for getblockchaininfo (Pol Espinasa)
a3d6f32a39 rpc, log: add backgroundvalidation to getblockchaininfo (Pol Espinasa)
5b2e4c4a88 log: update progress calculations for background validation (Pol Espinasa)

Pull request description:

  `getblockchaininfo` returns `verificationprogress=1` and `initialblockdownload=false` even if there's background validation.
  This PR adds information about background validation to rpc `getblockchaininfo` in a similar way to `validationprogress` does.

  If assume utxo was used the output of a "sync" node performing background validation:
  ```
  $ ./build/bin/bitcoin-cli getblockchaininfo
  ...
    "mediantime": 1756933740,
    "verificationprogress": 1,
    "initialblockdownload": false,
    "backgroundvalidation": {
      "snapshotheight": 880000,
      "blocks": 527589,
      "bestblockhash": "0000000000000000002326308420fa5ccd28a9155217f4d1896ab443d84148fa",
      "mediantime": 1529076654,
      "chainwork": "0000000000000000000000000000000000000000020c92fab9e5e1d8ed2d8dbc",
      "verificationprogress": 0.2815790617966284
    },
    "chainwork": "0000000000000000000000000000000000000000df97866c410b0302954919d2",
    "size_on_disk": 61198817285,

  ...
  ```

  If assume utxo was not used the progress is hidden:
  ```
  $ ./build/bin/bitcoin-cli getblockchaininfo
  ...
    "mediantime": 1756245700,
    "verificationprogress": 1,
    "initialblockdownload": false,
    "chainwork": "00000000000000000000000000000000000000000000000000000656d6bb052b",
    "size_on_disk": 3964972194,
  ...
  ```

  The PR also updates the way we estimate the verification progress returning a 100% on the snapshot block and not on the tip as we will stop doing background validation when reaching it.

ACKs for top commit:
  fjahr:
    ACK 25f69d970a
  danielabrozzoni:
    ACK 25f69d970a
  achow101:
    ACK 25f69d970a
  sedited:
    ACK 25f69d970a

Tree-SHA512: 5e5e08fd39af5f764962b862bc6d8257b0d2175fe920d4b79dc5105578fd4ebe08aee2fe9bfa5c9cad5d7610197a435ebaac0de23e7a5efa740dfea031a8a9d4
This commit is contained in:
Ava Chow
2026-03-24 14:36:09 -07:00
6 changed files with 81 additions and 8 deletions

View File

@@ -0,0 +1,4 @@
RPC
---
The `getblockchaininfo` RPC now exposes progress for background validation if the `assumeutxo` feature is used. Once a node has synced from snapshot to tip, `verificationprogress` returns 1.0 and `initialblockdownload` false even though the node may still be validating blocks in the background. A new object, `backgroundvalidation`, provides details about the snapshot being validated, including snapshot height, number of blocks processed, best block hash, chainwork, median time, and verification progress.

View File

@@ -1374,12 +1374,21 @@ RPCHelpMan getblockchaininfo()
{RPCResult::Type::NUM, "headers", "the current number of headers we have validated"},
{RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block"},
{RPCResult::Type::STR_HEX, "bits", "nBits: compact representation of the block difficulty target"},
{RPCResult::Type::STR_HEX, "target", "The difficulty target"},
{RPCResult::Type::STR_HEX, "target", "the difficulty target"},
{RPCResult::Type::NUM, "difficulty", "the current difficulty"},
{RPCResult::Type::NUM_TIME, "time", "The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime", "The median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "time", "the block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM_TIME, "mediantime", "the median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "verificationprogress", "estimate of verification progress [0..1]"},
{RPCResult::Type::BOOL, "initialblockdownload", "(debug information) estimate of whether this node is in Initial Block Download mode"},
{RPCResult::Type::OBJ, "backgroundvalidation", /*optional=*/true, "state info regarding background validation process",
{
{RPCResult::Type::NUM, "snapshotheight", "the height of the snapshot block. Background validation verifies the chain from genesis up to this height"},
{RPCResult::Type::NUM, "blocks", "the height of the most-work background fully-validated chain. The genesis block has height 0"},
{RPCResult::Type::STR, "bestblockhash", "the hash of the currently best block validated in the background"},
{RPCResult::Type::NUM_TIME, "mediantime", "the median block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "verificationprogress", "estimate of background verification progress [0..1]"},
{RPCResult::Type::STR_HEX, "chainwork", "total amount of work in background validated chain, in hexadecimal"},
}},
{RPCResult::Type::STR_HEX, "chainwork", "total amount of work in active chain, in hexadecimal"},
{RPCResult::Type::NUM, "size_on_disk", "the estimated size of the block and undo files on disk"},
{RPCResult::Type::BOOL, "pruned", "if the blocks are subject to pruning"},
@@ -1420,6 +1429,19 @@ RPCHelpMan getblockchaininfo()
obj.pushKV("mediantime", tip.GetMedianTimePast());
obj.pushKV("verificationprogress", chainman.GuessVerificationProgress(&tip));
obj.pushKV("initialblockdownload", chainman.IsInitialBlockDownload());
auto historical_blocks{chainman.GetHistoricalBlockRange()};
if (historical_blocks) {
UniValue background_validation(UniValue::VOBJ);
const CBlockIndex& btip{*CHECK_NONFATAL(historical_blocks->first)};
const CBlockIndex& btarget{*CHECK_NONFATAL(historical_blocks->second)};
background_validation.pushKV("snapshotheight", btarget.nHeight);
background_validation.pushKV("blocks", btip.nHeight);
background_validation.pushKV("bestblockhash", btip.GetBlockHash().GetHex());
background_validation.pushKV("mediantime", btip.GetMedianTimePast());
background_validation.pushKV("chainwork", btip.nChainWork.GetHex());
background_validation.pushKV("verificationprogress", chainman.GetBackgroundVerificationProgress(btip));
obj.pushKV("backgroundvalidation", std::move(background_validation));
}
obj.pushKV("chainwork", tip.nChainWork.GetHex());
obj.pushKV("size_on_disk", chainman.m_blockman.CalculateCurrentUsage());
obj.pushKV("pruned", chainman.m_blockman.IsPruneMode());

View File

@@ -2861,7 +2861,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);
@@ -2872,7 +2873,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) : "");
@@ -2889,7 +2890,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;
}
@@ -2912,7 +2913,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.
@@ -5523,6 +5524,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);

View File

@@ -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
*

View File

@@ -542,6 +542,30 @@ class AssumeutxoTest(BitcoinTestFramework):
assert_equal(utxo_info['height'], SNAPSHOT_BASE_HEIGHT)
assert_equal(utxo_info['bestblock'], snapshot_hash)
self.log.info("Check that getblockchaininfo returns information about the background validation process")
expected_keys = [
"snapshotheight",
"blocks",
"bestblockhash",
"mediantime",
"chainwork",
"verificationprogress"
]
res = n1.getblockchaininfo()
assert "backgroundvalidation" in res.keys()
bv_res = res["backgroundvalidation"]
assert_equal(sorted(expected_keys), sorted(bv_res.keys()))
assert_equal(bv_res["snapshotheight"], SNAPSHOT_BASE_HEIGHT)
assert_equal(bv_res["blocks"], START_HEIGHT)
assert_equal(bv_res["bestblockhash"], n1.getblockhash(START_HEIGHT))
block = n1.getblockheader(bv_res["bestblockhash"])
assert_equal(bv_res["mediantime"], block["mediantime"])
assert_equal(bv_res["chainwork"], block["chainwork"])
background_tx_count = n1.getchaintxstats(blockhash=bv_res["bestblockhash"])["txcount"]
snapshot_tx_count = n1.getchaintxstats(blockhash=snapshot_hash)["txcount"]
expected_verification_progress = background_tx_count / snapshot_tx_count
assert_approx(bv_res["verificationprogress"], expected_verification_progress, vspan=0.01)
# find coinbase output at snapshot height on node0 and scan for it on node1,
# where the block is not available, but the snapshot was loaded successfully
coinbase_tx = n0.getblock(snapshot_hash, verbosity=2)['tx'][0]

View File

@@ -170,6 +170,9 @@ class BlockchainTest(BitcoinTestFramework):
assert res['pruned']
assert not res['automatic_pruning']
# check background validation is not present when we are not using assumeutxo
assert "backgroundvalidation" not in res.keys()
self.restart_node(0, ['-stopatheight=207'])
res = self.nodes[0].getblockchaininfo()
# should have exact keys