From 0503a559042ea788d293410c3f5de6515e260faa Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Mon, 2 Dec 2024 15:28:54 +0100 Subject: [PATCH] bitcoind RPC: Skip validating header chain for newly setup nodes When we just setup a new node, all chain listeners will still be synced up to the genesis hash. In order to allow for faster startup, we skip validating the entirety of the header chain by continuing with the current best block polled from `bitcoind`. Note that this doesn't noticeably change our security model as we're trusting the chain data source, for example to not censor us, anyways. And, we're of course still validating the PoW for the best block we're polling. --- src/chain/mod.rs | 102 +++++++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 34 deletions(-) diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 4343d8ad5..783394285 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -273,43 +273,76 @@ impl ChainSource { } } - let channel_manager_best_block_hash = + let mut channel_manager_best_block_hash = channel_manager.current_best_block().block_hash; - let sweeper_best_block_hash = output_sweeper.current_best_block().block_hash; - let onchain_wallet_best_block_hash = onchain_wallet.current_best_block().block_hash; - - let mut chain_listeners = vec![ - ( - onchain_wallet_best_block_hash, - &**onchain_wallet as &(dyn Listen + Send + Sync), - ), - ( - channel_manager_best_block_hash, - &*channel_manager as &(dyn Listen + Send + Sync), - ), - (sweeper_best_block_hash, &*output_sweeper as &(dyn Listen + Send + Sync)), - ]; + let mut sweeper_best_block_hash = output_sweeper.current_best_block().block_hash; + let mut onchain_wallet_best_block_hash = + onchain_wallet.current_best_block().block_hash; - // TODO: Eventually we might want to see if we can synchronize `ChannelMonitor`s - // before giving them to `ChainMonitor` it the first place. However, this isn't - // trivial as we load them on initialization (in the `Builder`) and only gain - // network access during `start`. For now, we just make sure we get the worst known - // block hash and sychronize them via `ChainMonitor`. - if let Some(worst_channel_monitor_block_hash) = chain_monitor - .list_monitors() - .iter() - .flat_map(|(txo, _)| chain_monitor.get_monitor(*txo)) - .map(|m| m.current_best_block()) - .min_by_key(|b| b.height) - .map(|b| b.block_hash) - { - chain_listeners.push(( - worst_channel_monitor_block_hash, - &*chain_monitor as &(dyn Listen + Send + Sync), - )); - } + let genesis_block_hash = + bitcoin::blockdata::constants::genesis_block(config.network).block_hash(); + + 'initial_listener_sync: loop { + if channel_manager_best_block_hash == genesis_block_hash + && sweeper_best_block_hash == genesis_block_hash + && onchain_wallet_best_block_hash == genesis_block_hash + { + // If all chain listeners are still at the genesis we're positive this is a + // newly setup node that was never synced to tip before. So we query the best + // tip and continue with it, avoiding to validate the entire header chain back to genesis. + let chain_tip = + match validate_best_block_header(bitcoind_rpc_client.as_ref()).await { + Ok(tip) => { + *latest_chain_tip.write().unwrap() = Some(tip); + tip + }, + Err(e) => { + log_error!(logger, "Failed to poll for chain data: {:?}", e); + tokio::time::sleep(Duration::from_secs( + CHAIN_POLLING_INTERVAL_SECS, + )) + .await; + continue 'initial_listener_sync; + }, + }; + + let tip_hash = chain_tip.to_best_block().block_hash; + channel_manager_best_block_hash = tip_hash; + sweeper_best_block_hash = tip_hash; + onchain_wallet_best_block_hash = tip_hash; + } + + let mut chain_listeners = vec![ + ( + onchain_wallet_best_block_hash, + &**onchain_wallet as &(dyn Listen + Send + Sync), + ), + ( + channel_manager_best_block_hash, + &*channel_manager as &(dyn Listen + Send + Sync), + ), + (sweeper_best_block_hash, &*output_sweeper as &(dyn Listen + Send + Sync)), + ]; + + // TODO: Eventually we might want to see if we can synchronize `ChannelMonitor`s + // before giving them to `ChainMonitor` it the first place. However, this isn't + // trivial as we load them on initialization (in the `Builder`) and only gain + // network access during `start`. For now, we just make sure we get the worst known + // block hash and sychronize them via `ChainMonitor`. + if let Some(worst_channel_monitor_block_hash) = chain_monitor + .list_monitors() + .iter() + .flat_map(|(txo, _)| chain_monitor.get_monitor(*txo)) + .map(|m| m.current_best_block()) + .min_by_key(|b| b.height) + .map(|b| b.block_hash) + { + chain_listeners.push(( + worst_channel_monitor_block_hash, + &*chain_monitor as &(dyn Listen + Send + Sync), + )); + } - loop { let mut locked_header_cache = header_cache.lock().await; match synchronize_listeners( bitcoind_rpc_client.as_ref(), @@ -347,6 +380,7 @@ impl ChainSource { log_error!(logger, "Failed to synchronize chain listeners: {:?}", e); tokio::time::sleep(Duration::from_secs(CHAIN_POLLING_INTERVAL_SECS)) .await; + continue 'initial_listener_sync; }, } }