@@ -417,6 +417,83 @@ async def test_swarm_listen_multiple_addresses_connectivity(security_protocol):
417417 )
418418
419419
420+ @pytest .mark .trio
421+ async def test_swarm_listener_resilience_on_upgrade_failure (security_protocol ):
422+ """Test that the swarm listener continues to accept connections even when individual peer negotiations fail.""" # noqa: E501
423+ from unittest .mock import patch
424+
425+ from libp2p .transport .exceptions import SecurityUpgradeFailure
426+
427+ async with SwarmFactory .create_batch_and_listen (
428+ 2 , security_protocol = security_protocol
429+ ) as swarms :
430+ listener_swarm = swarms [0 ]
431+ client_swarm = swarms [1 ]
432+
433+ # Get listener address
434+ listener_addr = tuple (
435+ addr
436+ for transport in listener_swarm .listeners .values ()
437+ for addr in transport .get_addrs ()
438+ )[0 ]
439+
440+ # Add the listener's address to client's peerstore
441+ client_swarm .peerstore .add_addrs (
442+ listener_swarm .get_peer_id (), [listener_addr ], 10000
443+ )
444+
445+ # First, establish a successful connection to verify setup
446+ await client_swarm .dial_peer (listener_swarm .get_peer_id ())
447+ assert listener_swarm .get_peer_id () in client_swarm .connections
448+
449+ # Close the first connection
450+ await client_swarm .close_peer (listener_swarm .get_peer_id ())
451+ await trio .sleep (0.1 )
452+
453+ # Now simulate a failure during upgrade by patching upgrade_security
454+ # We'll make one call fail, then let subsequent calls succeed
455+ original_upgrade_security = listener_swarm .upgrader .upgrade_security
456+ call_count = [0 ]
457+
458+ async def failing_upgrade_security (* args , ** kwargs ):
459+ call_count [0 ] += 1
460+ if call_count [0 ] == 1 :
461+ # First call after patch fails
462+ raise SecurityUpgradeFailure ("Simulated security upgrade failure" )
463+ # Subsequent calls succeed
464+ return await original_upgrade_security (* args , ** kwargs )
465+
466+ with patch .object (
467+ listener_swarm .upgrader ,
468+ "upgrade_security" ,
469+ side_effect = failing_upgrade_security ,
470+ ):
471+ # This connection attempt should fail on the listener side
472+ # The client will see a connection error, but the listener should NOT crash
473+ try :
474+ await client_swarm .dial_peer (listener_swarm .get_peer_id ())
475+ except Exception :
476+ # Connection failure expected - client can't complete the dial
477+ pass
478+
479+ # Give time for any potential listener crash to occur
480+ await trio .sleep (0.2 )
481+
482+ # Verify listener is still active by checking it has listeners
483+ assert len (listener_swarm .listeners ) > 0 , (
484+ "Listener crashed after upgrade failure!"
485+ )
486+
487+ # Now verify the listener can still accept new connections
488+ # (upgrade_security is no longer patched, so this should succeed)
489+ await trio .sleep (0.1 )
490+ await client_swarm .dial_peer (listener_swarm .get_peer_id ())
491+
492+ # Verify connection was established successfully
493+ assert listener_swarm .get_peer_id () in client_swarm .connections
494+ assert client_swarm .get_peer_id () in listener_swarm .connections
495+
496+
420497@pytest .mark .trio
421498async def test_swarm_peer_id_validation (security_protocol ):
422499 """Test that the swarm correctly validates peer IDs during connection."""
0 commit comments