@@ -159,7 +159,7 @@ async def run(self) -> None:
159159 self .transport .set_swarm (self )
160160
161161 # Start health monitoring service if enabled
162- if self ._is_health_monitoring_enabled () :
162+ if self ._is_health_monitoring_enabled :
163163 from libp2p .network .health .monitor import ConnectionHealthMonitor
164164
165165 self ._health_monitor = ConnectionHealthMonitor (self )
@@ -414,6 +414,51 @@ async def dial_addr(self, addr: Multiaddr, peer_id: ID) -> INetConn:
414414 """
415415 return await self ._dial_with_retry (addr , peer_id )
416416
417+ async def dial_peer_replacement (self , peer_id : ID ) -> INetConn | None :
418+ """
419+ Create a new connection to peer_id for replacement purposes.
420+ This bypasses the existing connection check and always creates a new connection.
421+
422+ :param peer_id: peer ID to dial
423+ :raises SwarmException: raised when an error occurs
424+ :return: new network connection or None if no addresses available
425+ """
426+ logger .debug ("attempting to dial replacement connection to peer %s" , peer_id )
427+
428+ try :
429+ # Get peer info from peer store
430+ addrs = self .peerstore .addrs (peer_id )
431+ except PeerStoreError :
432+ logger .warning (f"No known addresses to peer { peer_id } for replacement" )
433+ return None
434+
435+ if not addrs :
436+ logger .warning (f"No addresses available for { peer_id } for replacement" )
437+ return None
438+
439+ exceptions : list [SwarmException ] = []
440+
441+ # Try all known addresses with retry logic
442+ for multiaddr in addrs :
443+ try :
444+ connection = await self ._dial_with_retry (multiaddr , peer_id )
445+ logger .info (
446+ f"Successfully established replacement connection to { peer_id } "
447+ )
448+ return connection
449+ except SwarmException as e :
450+ exceptions .append (e )
451+ logger .debug (
452+ "encountered swarm exception when trying to connect to %s, "
453+ "trying next address..." ,
454+ multiaddr ,
455+ exc_info = e ,
456+ )
457+
458+ # All addresses failed
459+ logger .warning (f"Failed to establish replacement connection to { peer_id } " )
460+ return None
461+
417462 async def new_stream (self , peer_id : ID ) -> INetStream :
418463 """
419464 Enhanced: Create a new stream with load balancing across multiple connections.
@@ -839,6 +884,7 @@ async def notify_all(self, notifier: Callable[[INotifee], Awaitable[None]]) -> N
839884
840885 # Health monitoring methods (conditional on health monitoring being enabled)
841886
887+ @property
842888 def _is_health_monitoring_enabled (self ) -> bool :
843889 """Check if health monitoring is enabled."""
844890 return (
@@ -849,7 +895,7 @@ def _is_health_monitoring_enabled(self) -> bool:
849895
850896 def initialize_connection_health (self , peer_id : ID , connection : INetConn ) -> None :
851897 """Initialize health tracking for a new connection."""
852- if not self ._is_health_monitoring_enabled () :
898+ if not self ._is_health_monitoring_enabled :
853899 return
854900
855901 from libp2p .network .health .data_structures import (
@@ -871,7 +917,7 @@ def initialize_connection_health(self, peer_id: ID, connection: INetConn) -> Non
871917
872918 def cleanup_connection_health (self , peer_id : ID , connection : INetConn ) -> None :
873919 """Clean up health tracking for a closed connection."""
874- if not self ._is_health_monitoring_enabled () :
920+ if not self ._is_health_monitoring_enabled :
875921 return
876922
877923 if peer_id in self .health_data and connection in self .health_data [peer_id ]:
@@ -885,7 +931,7 @@ def record_connection_event(
885931 ) -> None :
886932 """Record a connection lifecycle event."""
887933 if (
888- self ._is_health_monitoring_enabled ()
934+ self ._is_health_monitoring_enabled
889935 and peer_id in self .health_data
890936 and connection in self .health_data [peer_id ]
891937 ):
@@ -896,15 +942,15 @@ def record_connection_error(
896942 ) -> None :
897943 """Record a connection error."""
898944 if (
899- self ._is_health_monitoring_enabled ()
945+ self ._is_health_monitoring_enabled
900946 and peer_id in self .health_data
901947 and connection in self .health_data [peer_id ]
902948 ):
903949 self .health_data [peer_id ][connection ].add_error (error )
904950
905951 def get_peer_health_summary (self , peer_id : ID ) -> dict [str , Any ]:
906952 """Get health summary for a specific peer."""
907- if not self ._is_health_monitoring_enabled () :
953+ if not self ._is_health_monitoring_enabled :
908954 return {}
909955
910956 if peer_id not in self .health_data :
@@ -942,7 +988,7 @@ def get_peer_health_summary(self, peer_id: ID) -> dict[str, Any]:
942988
943989 def get_global_health_summary (self ) -> dict [str , Any ]:
944990 """Get global health summary across all peers."""
945- if not self ._is_health_monitoring_enabled () :
991+ if not self ._is_health_monitoring_enabled :
946992 return {}
947993
948994 all_peers = list (self .health_data .keys ())
@@ -975,7 +1021,7 @@ def get_global_health_summary(self) -> dict[str, Any]:
9751021
9761022 def export_health_metrics (self , format : str = "json" ) -> str :
9771023 """Export health metrics in various formats."""
978- if not self ._is_health_monitoring_enabled () :
1024+ if not self ._is_health_monitoring_enabled :
9791025 return "{}" if format == "json" else ""
9801026
9811027 summary = self .get_global_health_summary ()
@@ -1018,7 +1064,7 @@ def _format_prometheus_metrics(self, summary: dict[str, Any]) -> str:
10181064
10191065 async def get_health_monitor_status (self ) -> dict [str , Any ]:
10201066 """Get status information about the health monitoring service."""
1021- if not self ._is_health_monitoring_enabled () or self ._health_monitor is None :
1067+ if not self ._is_health_monitoring_enabled or self ._health_monitor is None :
10221068 return {"enabled" : False }
10231069
10241070 status = await self ._health_monitor .get_monitoring_status ()
0 commit comments