|
11 | 11 | StreamEOF, |
12 | 12 | StreamReset, |
13 | 13 | ) |
| 14 | +from libp2p.peer.peerinfo import PeerInfo |
14 | 15 | from libp2p.relay.circuit_v2.config import ( |
15 | 16 | RelayConfig, |
16 | 17 | ) |
@@ -344,3 +345,80 @@ async def test_circuit_v2_transport_relay_limits(): |
344 | 345 |
|
345 | 346 | # Test successful - transports were initialized with the correct limits |
346 | 347 | logger.info("Transport limit test successful") |
| 348 | + |
| 349 | + |
| 350 | +@pytest.mark.trio |
| 351 | +async def test_circuit_v2_transport_relay_selection(): |
| 352 | + """Test relay round robin load balancing and reservation priority""" |
| 353 | + async with HostFactory.create_batch_and_listen(5) as hosts: |
| 354 | + client1_host, relay_host1, relay_host2, relay_host3, target_host = hosts |
| 355 | + |
| 356 | + # Setup relay with strict limits |
| 357 | + limits = RelayLimits( |
| 358 | + duration=DEFAULT_RELAY_LIMITS.duration, |
| 359 | + data=DEFAULT_RELAY_LIMITS.data, |
| 360 | + max_circuit_conns=DEFAULT_RELAY_LIMITS.max_circuit_conns, |
| 361 | + max_reservations=DEFAULT_RELAY_LIMITS.max_reservations, |
| 362 | + ) |
| 363 | + |
| 364 | + # Register test handler on target |
| 365 | + test_protocol = "/test/echo/1.0.0" |
| 366 | + target_host.set_stream_handler(TProtocol(test_protocol), echo_stream_handler) |
| 367 | + target_host_info = PeerInfo(target_host.get_id(), target_host.get_addrs()) |
| 368 | + |
| 369 | + client_config = RelayConfig() |
| 370 | + |
| 371 | + # Client setup |
| 372 | + client1_protocol = CircuitV2Protocol(client1_host, limits, allow_hop=False) |
| 373 | + client1_discovery = RelayDiscovery( |
| 374 | + host=client1_host, |
| 375 | + auto_reserve=False, |
| 376 | + discovery_interval=client_config.discovery_interval, |
| 377 | + max_relays=client_config.max_relays, |
| 378 | + ) |
| 379 | + |
| 380 | + client1_transport = CircuitV2Transport( |
| 381 | + client1_host, client1_protocol, client_config |
| 382 | + ) |
| 383 | + client1_transport.discovery = client1_discovery |
| 384 | + # Add relay to discovery |
| 385 | + relay_id1 = relay_host1.get_id() |
| 386 | + relay_id2 = relay_host2.get_id() |
| 387 | + relay_id3 = relay_host3.get_id() |
| 388 | + |
| 389 | + # Connect all peers |
| 390 | + try: |
| 391 | + with trio.fail_after(CONNECT_TIMEOUT): |
| 392 | + # Connect clients to relay |
| 393 | + await connect(client1_host, relay_host1) |
| 394 | + await connect(client1_host, relay_host2) |
| 395 | + await connect(client1_host, relay_host3) |
| 396 | + |
| 397 | + logger.info("All connections established") |
| 398 | + except Exception as e: |
| 399 | + logger.error("Failed to connect peers: %s", str(e)) |
| 400 | + raise |
| 401 | + |
| 402 | + await client1_discovery._add_relay(relay_id1) |
| 403 | + await client1_discovery._add_relay(relay_id2) |
| 404 | + await client1_discovery._add_relay(relay_id3) |
| 405 | + |
| 406 | + selected_relay = await client1_transport._select_relay(target_host_info) |
| 407 | + # Without reservation preference |
| 408 | + # Round robin, so 1st time must be relay1 |
| 409 | + assert selected_relay is not None and selected_relay is relay_id1 |
| 410 | + |
| 411 | + selected_relay = await client1_transport._select_relay(target_host_info) |
| 412 | + # Round robin, so 2nd time must be relay2 |
| 413 | + assert selected_relay is not None and selected_relay is relay_id2 |
| 414 | + |
| 415 | + # Mock reservation with relay1 to prioritize over relay2 |
| 416 | + relay_info3 = client1_discovery.get_relay_info(relay_id3) |
| 417 | + if relay_info3: |
| 418 | + relay_info3.has_reservation = True |
| 419 | + |
| 420 | + selected_relay = await client1_transport._select_relay(target_host_info) |
| 421 | + # With reservation preference, relay2 must be chosen for target_peer. |
| 422 | + assert selected_relay is not None and selected_relay is relay_host3.get_id() |
| 423 | + |
| 424 | + logger.info("Relay selection successful") |
0 commit comments