The race:
- (1) If a channel manager is starting for the first time, it gets its
initial block hash by calling BlockSource::get_blockchain_info() and
constructing a BestBlock out of that.
- (2) During the initial chain sync, if chain_tip.is_none() then the
best chain_tip is retrieved using
lightning_block_sync::init::validate_best_block_header() which
similarly uses BlockSource::get_best_block()
- If blocks were mined between (1) and (2), the channel manager’s block
hash will be at a lower height than that of the block hash contained
inside the chain_tip fed to the SpvClient. Suppose that this results
in the channel manager being at block 1 and the SpvClient being at
block 2. Once block 3 comes in and the SpvClient detects it via
poll_best_tip(), the SpvClient computes the ChainDifference between
its saved chain_tip value and block 3, concluding that it just needs
to connect block 3. However, when Listen::filtered_block_connected is
called using the data in block 3, the channel manager panics with
“Blocks must be connected in chain-order - the connected header must
build on the last connected header” - because block 3’s prev block
hash is block 2’s block hash, rather than block 1’s block hash which
the channel manager expects
This commit fixes it by removing the fresh channel manager's query to
the block source, instead deriving its best block from the validated
block header using a new `.to_best_block()` method.
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Recipient};
use lightning::chain::{chainmonitor, ChannelMonitorUpdateStatus};
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
use lightning::chain::keysinterface::{InMemorySigner, KeysInterface, KeysManager, Recipient};
use lightning::chain::{chainmonitor, ChannelMonitorUpdateStatus};
-use lightning::chain::{BestBlock, Filter, Watch};
+use lightning::chain::{Filter, Watch};
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
ChainParameters, ChannelManagerReadArgs, SimpleArcChannelManager,
use lightning::ln::channelmanager;
use lightning::ln::channelmanager::{
ChainParameters, ChannelManagerReadArgs, SimpleArcChannelManager,
// Step 7: Read ChannelMonitor state from disk
let mut channelmonitors = persister.read_channelmonitors(keys_manager.clone()).unwrap();
// Step 7: Read ChannelMonitor state from disk
let mut channelmonitors = persister.read_channelmonitors(keys_manager.clone()).unwrap();
- // Step 8: Initialize the ChannelManager
+ // Step 8: Poll for the best chain tip, which may be used by the channel manager & spv client
+ let polled_chain_tip = init::validate_best_block_header(bitcoind_client.as_ref())
+ .await
+ .expect("Failed to fetch best block header and best block");
+
+ // Step 9: Initialize the ChannelManager
let mut user_config = UserConfig::default();
user_config.channel_handshake_limits.force_announced_channel_preference = false;
let mut restarting_node = true;
let mut user_config = UserConfig::default();
user_config.channel_handshake_limits.force_announced_channel_preference = false;
let mut restarting_node = true;
} else {
// We're starting a fresh node.
restarting_node = false;
} else {
// We're starting a fresh node.
restarting_node = false;
- let getinfo_resp = bitcoind_client.get_blockchain_info().await;
-
- let chain_params = ChainParameters {
- network: args.network,
- best_block: BestBlock::new(
- getinfo_resp.latest_blockhash,
- getinfo_resp.latest_height as u32,
- ),
- };
+
+ let polled_best_block = polled_chain_tip.to_best_block();
+ let polled_best_block_hash = polled_best_block.block_hash();
+ let chain_params =
+ ChainParameters { network: args.network, best_block: polled_best_block };
let fresh_channel_manager = channelmanager::ChannelManager::new(
fee_estimator.clone(),
chain_monitor.clone(),
let fresh_channel_manager = channelmanager::ChannelManager::new(
fee_estimator.clone(),
chain_monitor.clone(),
user_config,
chain_params,
);
user_config,
chain_params,
);
- (getinfo_resp.latest_blockhash, fresh_channel_manager)
+ (polled_best_block_hash, fresh_channel_manager)
- // Step 9: Sync ChannelMonitors and ChannelManager to chain tip
+ // Step 10: Sync ChannelMonitors and ChannelManager to chain tip
let mut chain_listener_channel_monitors = Vec::new();
let mut cache = UnboundedCache::new();
let mut chain_tip: Option<poll::ValidatedBlockHeader> = None;
let mut chain_listener_channel_monitors = Vec::new();
let mut cache = UnboundedCache::new();
let mut chain_tip: Option<poll::ValidatedBlockHeader> = None;
- // Step 10: Give ChannelMonitors to ChainMonitor
+ // Step 11: Give ChannelMonitors to ChainMonitor
for item in chain_listener_channel_monitors.drain(..) {
let channel_monitor = item.1 .0;
let funding_outpoint = item.2;
for item in chain_listener_channel_monitors.drain(..) {
let channel_monitor = item.1 .0;
let funding_outpoint = item.2;
- // Step 11: Optional: Initialize the P2PGossipSync
+ // Step 12: Optional: Initialize the P2PGossipSync
let genesis = genesis_block(args.network).header.block_hash();
let network_graph_path = format!("{}/network_graph", ldk_data_dir.clone());
let network_graph =
let genesis = genesis_block(args.network).header.block_hash();
let network_graph_path = format!("{}/network_graph", ldk_data_dir.clone());
let network_graph =
- // Step 12: Initialize the PeerManager
+ // Step 13: Initialize the PeerManager
let channel_manager: Arc<ChannelManager> = Arc::new(channel_manager);
let onion_messenger: Arc<OnionMessenger> = Arc::new(OnionMessenger::new(
Arc::clone(&keys_manager),
let channel_manager: Arc<ChannelManager> = Arc::new(channel_manager);
let onion_messenger: Arc<OnionMessenger> = Arc::new(OnionMessenger::new(
Arc::clone(&keys_manager),
- // Step 13: Initialize networking
+ // Step 14: Initialize networking
let peer_manager_connection_handler = peer_manager.clone();
let listening_port = args.ldk_peer_listening_port;
let peer_manager_connection_handler = peer_manager.clone();
let listening_port = args.ldk_peer_listening_port;
- // Step 14: Connect and Disconnect Blocks
+ // Step 15: Connect and Disconnect Blocks
- chain_tip = Some(init::validate_best_block_header(bitcoind_client.as_ref()).await.unwrap());
+ chain_tip = Some(polled_chain_tip);
}
let channel_manager_listener = channel_manager.clone();
let chain_monitor_listener = chain_monitor.clone();
}
let channel_manager_listener = channel_manager.clone();
let chain_monitor_listener = chain_monitor.clone();
- // Step 15: Handle LDK Events
+ // Step 16: Handle LDK Events
let channel_manager_event_listener = channel_manager.clone();
let keys_manager_listener = keys_manager.clone();
// TODO: persist payment info to disk
let channel_manager_event_listener = channel_manager.clone();
let keys_manager_listener = keys_manager.clone();
// TODO: persist payment info to disk
- // Step 16: Initialize routing ProbabilisticScorer
+ // Step 17: Initialize routing ProbabilisticScorer
let scorer_path = format!("{}/scorer", ldk_data_dir.clone());
let scorer = Arc::new(Mutex::new(disk::read_scorer(
Path::new(&scorer_path),
let scorer_path = format!("{}/scorer", ldk_data_dir.clone());
let scorer = Arc::new(Mutex::new(disk::read_scorer(
Path::new(&scorer_path),
Arc::clone(&logger),
)));
Arc::clone(&logger),
)));
- // Step 17: Create InvoicePayer
+ // Step 18: Create InvoicePayer
let router = DefaultRouter::new(
network_graph.clone(),
logger.clone(),
let router = DefaultRouter::new(
network_graph.clone(),
logger.clone(),
payment::Retry::Timeout(Duration::from_secs(10)),
));
payment::Retry::Timeout(Duration::from_secs(10)),
));
- // Step 18: Persist ChannelManager and NetworkGraph
+ // Step 19: Persist ChannelManager and NetworkGraph
let persister = Arc::new(FilesystemPersister::new(ldk_data_dir.clone()));
let persister = Arc::new(FilesystemPersister::new(ldk_data_dir.clone()));
- // Step 19: Background Processing
+ // Step 20: Background Processing
let background_processor = BackgroundProcessor::start(
persister,
invoice_payer.clone(),
let background_processor = BackgroundProcessor::start(
persister,
invoice_payer.clone(),