diff --git a/cli/src/node/mod.rs b/cli/src/node/mod.rs index c1e4015031..6032e19d17 100644 --- a/cli/src/node/mod.rs +++ b/cli/src/node/mod.rs @@ -72,9 +72,11 @@ impl Node { is_single_node: bool, ) -> Result { let base = NodeBase::builder(&node_config.base, &global_config) - .init_network(public_addr, &keys.as_secret())? - .init_storage() - .await?; + .init_network(public_addr, &keys.as_secret())?; + + base.check_any_bootstrap_peer_is_alive().await; + + let base = base.init_storage().await?; let rpc_mempool_adapter = if is_single_node { RpcMempoolAdapter { diff --git a/core/src/node/mod.rs b/core/src/node/mod.rs index 1acfbf7d02..24aecf6c47 100644 --- a/core/src/node/mod.rs +++ b/core/src/node/mod.rs @@ -3,9 +3,11 @@ use std::path::PathBuf; use std::sync::Arc; use anyhow::{Context, Result}; +use futures_util::future::select_ok; use tycho_crypto::ed25519; use tycho_network::{ - DhtClient, DhtService, Network, OverlayService, PeerInfo, PeerResolver, PublicOverlay, Router, + DhtClient, DhtService, Network, NetworkExt, OverlayService, PeerInfo, PeerResolver, + PublicOverlay, Router, }; use tycho_storage::{StorageConfig, StorageContext}; use tycho_types::models::{BlockId, ValidatorSet}; @@ -293,6 +295,40 @@ impl<'a> NodeBaseBuilder<'a, ()> { } impl<'a> NodeBaseBuilder<'a, init::Step0> { + async fn check_bootstrap_peer(&self, peer: &PeerInfo) -> Result<()> { + let request = tycho_network::Request::from_tl(tycho_network::proto::dht::rpc::GetNodeInfo); + tokio::time::timeout( + self.step.net.network.connect_timeout(), + self.step.net.network.query(&peer.id, request), + ) + .await??; + Ok(()) + } + + pub async fn check_any_bootstrap_peer_is_alive(&self) { + let bootstrap_peers = &self.common.global_config.bootstrap_peers; + if bootstrap_peers.is_empty() { + return; + } + let local_peer_id = *self.step.net.network.peer_id(); + + let checks: Vec<_> = bootstrap_peers + .iter() + .filter(|peer| peer.id != local_peer_id) + .map(|peer| Box::pin(self.check_bootstrap_peer(peer))) + .collect(); + + let reachable_any = select_ok(checks).await.is_ok(); + + if !reachable_any { + panic!( + "all bootstrap peers from global config are unreachable; \ + your node global config is likely stale. \ + update node global config to include live bootstrap peers" + ); + } + } + // TODO: Add some options here if needed. pub async fn init_storage(self) -> Result> { let store = ConfiguredStorage::new( diff --git a/network/src/network/mod.rs b/network/src/network/mod.rs index 35e349e51a..d6103c0e53 100644 --- a/network/src/network/mod.rs +++ b/network/src/network/mod.rs @@ -1,5 +1,6 @@ use std::net::{SocketAddr, ToSocketAddrs}; use std::sync::{Arc, Weak}; +use std::time::Duration; #[cfg(target_os = "linux")] use anyhow::Context; @@ -274,6 +275,10 @@ impl Network { pub fn max_frame_size(&self) -> usize { self.0.config.max_frame_size.0 as usize } + + pub fn connect_timeout(&self) -> Duration { + self.0.config.connect_timeout + } } struct NetworkInner {