Skip to content

Commit c1045a6

Browse files
committed
feat: add --no-bootnode flag and improve bootnode reconnection
- Add --no-bootnode CLI flag to disable bootnode connection - Track bootnode peer IDs separately from regular peers - Retry bootnode connection every 30s if not connected - Better logging: distinguish bootnode vs regular peer connections - Show 'will retry in 30s' message on bootnode connection failure
1 parent fcc099c commit c1045a6

File tree

2 files changed

+79
-53
lines changed

2 files changed

+79
-53
lines changed

bins/validator-node/src/main.rs

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ struct Args {
8282
#[arg(short, long)]
8383
bootstrap: Option<String>,
8484

85+
/// Disable bootnode connection (for running as bootnode or isolated testing)
86+
#[arg(long)]
87+
no_bootnode: bool,
88+
8589
/// Data directory
8690
#[arg(short, long, default_value = "./data")]
8791
data_dir: PathBuf,
@@ -920,19 +924,24 @@ async fn main() -> Result<()> {
920924

921925
// Parse bootstrap peers (default to official bootnode if not specified)
922926
const DEFAULT_BOOTNODE: &str = "/dns4/bootnode.platform.network/tcp/9000/p2p/12D3KooWEpZoR9A1fpMN4QGspuRSa9UYHYvnFda2GWkXXZyYgAkN";
923-
let bootstrap_peers: Vec<_> = args
924-
.bootstrap
925-
.map(|s| {
926-
s.split(',')
927-
.filter_map(|addr| addr.trim().parse().ok())
928-
.collect()
929-
})
930-
.unwrap_or_else(|| {
931-
// Use default bootnode
932-
vec![DEFAULT_BOOTNODE
933-
.parse()
934-
.expect("Invalid default bootnode address")]
935-
});
927+
let bootstrap_peers: Vec<_> = if args.no_bootnode {
928+
info!("Bootnode connection disabled (--no-bootnode)");
929+
vec![]
930+
} else {
931+
args.bootstrap
932+
.as_ref()
933+
.map(|s| {
934+
s.split(',')
935+
.filter_map(|addr| addr.trim().parse().ok())
936+
.collect()
937+
})
938+
.unwrap_or_else(|| {
939+
// Use default bootnode
940+
vec![DEFAULT_BOOTNODE
941+
.parse()
942+
.expect("Invalid default bootnode address")]
943+
})
944+
};
936945

937946
// Create network node with deterministic peer ID derived from hotkey public key
938947
let node_config = NodeConfig {

crates/network/src/node.rs

Lines changed: 57 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,12 @@ pub struct NetworkNode {
9595
/// Bootstrap peers to connect to
9696
bootstrap_peers: Vec<Multiaddr>,
9797

98+
/// Bootstrap peer IDs (extracted from multiaddrs)
99+
bootstrap_peer_ids: HashSet<PeerId>,
100+
101+
/// Whether we've ever successfully connected to a bootstrap peer
102+
bootstrap_connected: Arc<RwLock<bool>>,
103+
98104
/// Event sender
99105
event_tx: mpsc::Sender<NetworkEvent>,
100106

@@ -138,11 +144,28 @@ impl NetworkNode {
138144

139145
let (event_tx, event_rx) = mpsc::channel(1000);
140146

147+
// Extract peer IDs from bootstrap multiaddrs
148+
let bootstrap_peer_ids: HashSet<PeerId> = config
149+
.bootstrap_peers
150+
.iter()
151+
.filter_map(|addr| {
152+
addr.iter().find_map(|p| {
153+
if let libp2p::multiaddr::Protocol::P2p(peer_id) = p {
154+
Some(peer_id)
155+
} else {
156+
None
157+
}
158+
})
159+
})
160+
.collect();
161+
141162
Ok(Self {
142163
swarm,
143164
local_peer_id,
144165
peers: Arc::new(RwLock::new(HashSet::new())),
145166
bootstrap_peers: config.bootstrap_peers.clone(),
167+
bootstrap_peer_ids,
168+
bootstrap_connected: Arc::new(RwLock::new(false)),
146169
event_tx,
147170
event_rx: Some(event_rx),
148171
})
@@ -193,14 +216,24 @@ impl NetworkNode {
193216
if self.bootstrap_peers.is_empty() {
194217
return true; // No bootstrap peers configured
195218
}
196-
// Check if we have any peers connected
197-
!self.peers.read().is_empty()
219+
// Check if we're connected to at least one bootstrap peer
220+
let peers = self.peers.read();
221+
for peer in peers.iter() {
222+
if self.bootstrap_peer_ids.contains(peer) {
223+
return true;
224+
}
225+
}
226+
false
198227
}
199228

200229
/// Retry connecting to bootstrap peers if not connected
201230
pub fn retry_bootstrap_if_needed(&mut self) {
231+
if self.bootstrap_peers.is_empty() {
232+
return; // No bootnode configured
233+
}
234+
202235
if !self.has_bootstrap_connection() {
203-
info!("No peers connected, retrying bootstrap peers...");
236+
info!("Not connected to bootnode, retrying in 30s...");
204237
self.dial_bootstrap_peers();
205238
}
206239
}
@@ -238,40 +271,8 @@ impl NetworkNode {
238271

239272
/// Process the next swarm event (single step)
240273
pub async fn process_next_event(&mut self) {
241-
match self.swarm.select_next_some().await {
242-
SwarmEvent::Behaviour(event) => {
243-
self.handle_behaviour_event(event).await;
244-
}
245-
SwarmEvent::NewListenAddr { address, .. } => {
246-
info!("Listening on {}", address);
247-
}
248-
SwarmEvent::ConnectionEstablished { peer_id, .. } => {
249-
info!("Connected to peer: {}", peer_id);
250-
self.peers.write().insert(peer_id);
251-
let _ = self
252-
.event_tx
253-
.send(NetworkEvent::PeerConnected(peer_id))
254-
.await;
255-
}
256-
SwarmEvent::ConnectionClosed { peer_id, .. } => {
257-
info!("Disconnected from peer: {}", peer_id);
258-
self.peers.write().remove(&peer_id);
259-
let _ = self
260-
.event_tx
261-
.send(NetworkEvent::PeerDisconnected(peer_id))
262-
.await;
263-
}
264-
SwarmEvent::IncomingConnection { .. } => {}
265-
SwarmEvent::OutgoingConnectionError {
266-
peer_id: Some(peer_id),
267-
error,
268-
..
269-
} => {
270-
warn!("Failed to connect to {}: {}", peer_id, error);
271-
}
272-
SwarmEvent::OutgoingConnectionError { .. } => {}
273-
_ => {}
274-
}
274+
let event = self.swarm.select_next_some().await;
275+
self.handle_swarm_event(event).await;
275276
}
276277

277278
/// Run the event loop (should be spawned as a task)
@@ -302,15 +303,26 @@ impl NetworkNode {
302303
info!("Listening on {}", address);
303304
}
304305
SwarmEvent::ConnectionEstablished { peer_id, .. } => {
305-
info!("Connected to peer: {}", peer_id);
306+
let is_bootstrap = self.bootstrap_peer_ids.contains(&peer_id);
307+
if is_bootstrap {
308+
info!("Connected to bootnode: {}", peer_id);
309+
*self.bootstrap_connected.write() = true;
310+
} else {
311+
info!("Connected to peer: {}", peer_id);
312+
}
306313
self.peers.write().insert(peer_id);
307314
let _ = self
308315
.event_tx
309316
.send(NetworkEvent::PeerConnected(peer_id))
310317
.await;
311318
}
312319
SwarmEvent::ConnectionClosed { peer_id, .. } => {
313-
info!("Disconnected from peer: {}", peer_id);
320+
let is_bootstrap = self.bootstrap_peer_ids.contains(&peer_id);
321+
if is_bootstrap {
322+
info!("Disconnected from bootnode: {}", peer_id);
323+
} else {
324+
info!("Disconnected from peer: {}", peer_id);
325+
}
314326
self.peers.write().remove(&peer_id);
315327
let _ = self
316328
.event_tx
@@ -323,7 +335,12 @@ impl NetworkNode {
323335
error,
324336
..
325337
} => {
326-
warn!("Failed to connect to {}: {}", peer_id, error);
338+
let is_bootstrap = self.bootstrap_peer_ids.contains(&peer_id);
339+
if is_bootstrap {
340+
warn!("Failed to connect to bootnode {}: {} (will retry in 30s)", peer_id, error);
341+
} else {
342+
warn!("Failed to connect to {}: {}", peer_id, error);
343+
}
327344
}
328345
SwarmEvent::OutgoingConnectionError { .. } => {}
329346
_ => {}

0 commit comments

Comments
 (0)