From f687145c1e5865c2e9be9aa7643c64da6f1ee0ff Mon Sep 17 00:00:00 2001 From: Antoine Poinsot Date: Wed, 18 Oct 2023 19:04:23 +0200 Subject: [PATCH] bitcoin: make sure we are *completely* synced before starting up When not in IBD but catching up to the latest tip, our rounding up of verificationprogress makes us start while bitcoind is still kind of far from being caught up. Make sure it doesn't happen by not returning until bitcoind validated all the blocks we've fetched the headers for. --- src/bitcoin/d/mod.rs | 33 +++++++++++++++++++++++++++++---- src/bitcoin/mod.rs | 10 ++++++---- src/bitcoin/poller/looper.rs | 12 ++++++++---- src/commands/mod.rs | 2 +- src/lib.rs | 2 +- src/testutils.rs | 10 +++++++--- 6 files changed, 52 insertions(+), 17 deletions(-) diff --git a/src/bitcoin/d/mod.rs b/src/bitcoin/d/mod.rs index 74f3ee56..d8a8af51 100644 --- a/src/bitcoin/d/mod.rs +++ b/src/bitcoin/d/mod.rs @@ -748,14 +748,28 @@ impl BitcoinD { self.make_node_request("getblockchaininfo", &[]) } - pub fn sync_progress(&self) -> f64 { + pub fn sync_progress(&self) -> SyncProgress { // TODO: don't harass lianad, be smarter like in revaultd. - roundup_progress( - self.block_chain_info() + let chain_info = self.block_chain_info(); + let percentage = roundup_progress( + chain_info .get("verificationprogress") .and_then(Json::as_f64) .expect("No valid 'verificationprogress' in getblockchaininfo response?"), - ) + ); + let headers = chain_info + .get("headers") + .and_then(Json::as_u64) + .expect("No valid 'verificationprogress' in getblockchaininfo response?"); + let blocks = chain_info + .get("blocks") + .and_then(Json::as_u64) + .expect("No valid 'blocks' in getblockchaininfo response?"); + SyncProgress { + percentage, + headers, + blocks, + } } pub fn chain_tip(&self) -> BlockChainTip { @@ -1122,6 +1136,17 @@ impl BitcoinD { } } +/// Information about the block chain verification progress. +#[derive(Debug, Clone, Copy)] +pub struct SyncProgress { + /// Chain verification progress as a percentage between 0 and 1. + pub percentage: f64, + /// Headers count for the best known tip. + pub headers: u64, + /// Number of blocks validated toward the best known tip. + pub blocks: u64, +} + /// An entry in the 'listdescriptors' result. #[derive(Debug, Clone)] pub struct ListDescEntry { diff --git a/src/bitcoin/mod.rs b/src/bitcoin/mod.rs index b1d32fae..86bf5ccd 100644 --- a/src/bitcoin/mod.rs +++ b/src/bitcoin/mod.rs @@ -9,6 +9,7 @@ use crate::{ bitcoin::d::{BitcoindError, CachedTxGetter, LSBlockEntry}, descriptors, }; +pub use d::SyncProgress; use std::{fmt, sync}; @@ -42,8 +43,9 @@ pub trait BitcoinInterface: Send { fn genesis_block(&self) -> BlockChainTip; /// Get the progress of the block chain synchronization. - /// Returns a percentage between 0 and 1. - fn sync_progress(&self) -> f64; + /// Returns a rounded up percentage between 0 and 1. Use the `is_synced` method to be sure the + /// backend is completely synced to the best known tip. + fn sync_progress(&self) -> SyncProgress; /// Get the best block info. fn chain_tip(&self) -> BlockChainTip; @@ -117,7 +119,7 @@ impl BitcoinInterface for d::BitcoinD { BlockChainTip { hash, height } } - fn sync_progress(&self) -> f64 { + fn sync_progress(&self) -> SyncProgress { self.sync_progress() } @@ -333,7 +335,7 @@ impl BitcoinInterface for sync::Arc> self.lock().unwrap().genesis_block() } - fn sync_progress(&self) -> f64 { + fn sync_progress(&self) -> SyncProgress { self.lock().unwrap().sync_progress() } diff --git a/src/bitcoin/poller/looper.rs b/src/bitcoin/poller/looper.rs index b15684b3..e01bffde 100644 --- a/src/bitcoin/poller/looper.rs +++ b/src/bitcoin/poller/looper.rs @@ -1,5 +1,5 @@ use crate::{ - bitcoin::{BitcoinInterface, BlockChainTip, UTxO}, + bitcoin::{BitcoinInterface, BlockChainTip, SyncProgress, UTxO}, database::{Coin, DatabaseConnection, DatabaseInterface}, descriptors, }; @@ -356,12 +356,16 @@ pub fn looper( // Don't poll until the Bitcoin backend is fully synced. if !synced { - let sync_progress = bit.sync_progress(); + let SyncProgress { + percentage, + headers, + blocks, + } = bit.sync_progress(); log::info!( "Block chain synchronization progress: {:.2}%", - sync_progress * 100.0 + percentage * 100.0 ); - synced = sync_progress == 1.0; + synced = headers == blocks; if !synced { continue; } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index fb2c2b22..6f48c350 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -263,7 +263,7 @@ impl DaemonControl { version: VERSION.to_string(), network: self.config.bitcoin_config.network, block_height, - sync: self.bitcoin.sync_progress(), + sync: self.bitcoin.sync_progress().percentage, descriptors: GetInfoDescriptors { main: self.config.main_descriptor.clone(), }, diff --git a/src/lib.rs b/src/lib.rs index 37a68284..5f58e04f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -671,7 +671,7 @@ mod tests { // Send them a response to 'getblockchaininfo' saying we are far from being synced fn complete_sync_check(server: &net::TcpListener) { let net_resp = [ - "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"verificationprogress\":0.1}}\n".as_bytes(), + "HTTP/1.1 200\n\r\n{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"verificationprogress\":0.1,\"headers\":1000,\"blocks\":100}}\n".as_bytes(), ] .concat(); let (mut stream, _) = server.accept().unwrap(); diff --git a/src/testutils.rs b/src/testutils.rs index 3c7e3ea6..0d795637 100644 --- a/src/testutils.rs +++ b/src/testutils.rs @@ -1,5 +1,5 @@ use crate::{ - bitcoin::{BitcoinInterface, Block, BlockChainTip, UTxO}, + bitcoin::{BitcoinInterface, Block, BlockChainTip, SyncProgress, UTxO}, config::{BitcoinConfig, Config}, database::{BlockInfo, Coin, CoinStatus, DatabaseConnection, DatabaseInterface, LabelItem}, descriptors, DaemonHandle, @@ -42,8 +42,12 @@ impl BitcoinInterface for DummyBitcoind { BlockChainTip { hash, height: 0 } } - fn sync_progress(&self) -> f64 { - 1.0 + fn sync_progress(&self) -> SyncProgress { + SyncProgress { + percentage: 1.0, + headers: 1_000, + blocks: 1_000, + } } fn chain_tip(&self) -> BlockChainTip {