@@ -275,6 +275,48 @@ impl NetworkNode {
275275 }
276276 }
277277
278+ /// Repair the gossipsub mesh if it's empty but we have connected peers
279+ /// This handles the case where SUBSCRIBE messages weren't exchanged properly
280+ pub fn repair_mesh_if_needed ( & mut self ) {
281+ let connected_peers = self . peers . read ( ) . len ( ) ;
282+ let mesh_peers = self . swarm . behaviour ( ) . mesh_peer_count ( ) ;
283+ let topic_peers = self . swarm . behaviour ( ) . topic_peer_count ( ) ;
284+
285+ // Log mesh status for debugging
286+ if connected_peers > 0 {
287+ debug ! (
288+ "Mesh status: {} connected, {} in topic, {} in mesh" ,
289+ connected_peers, topic_peers, mesh_peers
290+ ) ;
291+ }
292+
293+ // If we have connected peers but none in the topic, force re-subscribe
294+ // This sends our SUBSCRIBE to all connected gossipsub peers
295+ if connected_peers > 0 && topic_peers == 0 {
296+ info ! (
297+ "Mesh repair: {} peers connected but none subscribed to topic, refreshing subscription" ,
298+ connected_peers
299+ ) ;
300+ if let Err ( e) = self . swarm . behaviour_mut ( ) . refresh_subscription ( ) {
301+ warn ! ( "Failed to refresh subscription: {}" , e) ;
302+ }
303+ }
304+
305+ // If we have peers in topic but mesh is still empty, add them explicitly
306+ // This happens when GRAFT messages fail to form the mesh
307+ if topic_peers > 0 && mesh_peers == 0 {
308+ info ! (
309+ "Mesh repair: {} peers in topic but mesh empty, adding peers explicitly" ,
310+ topic_peers
311+ ) ;
312+ // Get list of connected peers and add them
313+ let peers_to_add: Vec < PeerId > = self . peers . read ( ) . iter ( ) . cloned ( ) . collect ( ) ;
314+ for peer_id in peers_to_add {
315+ self . swarm . behaviour_mut ( ) . add_peer_to_mesh ( & peer_id) ;
316+ }
317+ }
318+ }
319+
278320 /// Broadcast a message via gossip
279321 pub fn broadcast ( & mut self , message : & SignedNetworkMessage ) -> anyhow:: Result < ( ) > {
280322 let data = bincode:: serialize ( message) ?;
@@ -314,10 +356,14 @@ impl NetworkNode {
314356
315357 /// Run the event loop (should be spawned as a task)
316358 /// Includes automatic retry of bootstrap peers every 30 seconds if not connected
359+ /// and mesh repair every 10 seconds
317360 pub async fn run ( & mut self ) {
318361 let mut bootstrap_retry_interval = tokio:: time:: interval ( Duration :: from_secs ( 30 ) ) ;
319362 bootstrap_retry_interval. set_missed_tick_behavior ( tokio:: time:: MissedTickBehavior :: Skip ) ;
320363
364+ let mut mesh_repair_interval = tokio:: time:: interval ( Duration :: from_secs ( 10 ) ) ;
365+ mesh_repair_interval. set_missed_tick_behavior ( tokio:: time:: MissedTickBehavior :: Skip ) ;
366+
321367 loop {
322368 tokio:: select! {
323369 event = self . swarm. select_next_some( ) => {
@@ -326,6 +372,9 @@ impl NetworkNode {
326372 _ = bootstrap_retry_interval. tick( ) => {
327373 self . retry_bootstrap_if_needed( ) ;
328374 }
375+ _ = mesh_repair_interval. tick( ) => {
376+ self . repair_mesh_if_needed( ) ;
377+ }
329378 }
330379 }
331380 }
@@ -483,6 +532,7 @@ impl NetworkNode {
483532 // Extract hotkey from agent_version if present
484533 // Format: "platform-validator/1.0.0/HOTKEY_HEX"
485534 let hotkey = info. agent_version . split ( '/' ) . nth ( 2 ) . map ( String :: from) ;
535+ let is_platform_validator = info. agent_version . starts_with ( "platform-validator/" ) ;
486536
487537 info ! (
488538 "Identify received from {}: agent={}, hotkey={:?}" ,
@@ -501,12 +551,25 @@ impl NetworkNode {
501551 } )
502552 . await ;
503553
504- // NOTE: Do NOT call add_explicit_peer here!
505- // Explicit peers become "direct peers" that bypass the gossipsub mesh.
506- // The mesh should form automatically via the gossipsub protocol:
507- // 1. Connection established -> gossipsub protocol negotiated
508- // 2. SUBSCRIBE messages exchanged -> peers know each other's topics
509- // 3. GRAFT/PRUNE in heartbeats -> mesh forms naturally
554+ // If this is a platform validator, check mesh status and trigger refresh if needed
555+ // This ensures that new validators joining the network get properly added to the mesh
556+ if is_platform_validator {
557+ let mesh_peers = self . swarm . behaviour ( ) . mesh_peer_count ( ) ;
558+ let topic_peers = self . swarm . behaviour ( ) . topic_peer_count ( ) ;
559+
560+ debug ! (
561+ "New platform validator {}: mesh has {} peers, topic has {} peers" ,
562+ peer_id, mesh_peers, topic_peers
563+ ) ;
564+
565+ // If mesh is empty but we have this peer, refresh to exchange SUBSCRIBEs
566+ if mesh_peers == 0 {
567+ info ! ( "Mesh empty after new validator joined, refreshing subscription" ) ;
568+ if let Err ( e) = self . swarm . behaviour_mut ( ) . refresh_subscription ( ) {
569+ warn ! ( "Failed to refresh subscription: {}" , e) ;
570+ }
571+ }
572+ }
510573
511574 // Also connect to other peers they know about through their observed addr
512575 // This helps with peer discovery in small networks
0 commit comments