Skip to content

Commit 4c739f6

Browse files
authored
Merge pull request #707 from guha-rahul/add_degree
feat: adds degree to connect some
2 parents 92c9ba7 + 5306bdc commit 4c739f6

File tree

3 files changed

+175
-4
lines changed

3 files changed

+175
-4
lines changed

newsfragments/707.feature.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added comprehensive tests for pubsub connection utility functions to verify degree limits are enforced, excess peers are handled correctly, and edge cases (degree=0, negative values, empty lists) are managed gracefully.

tests/core/pubsub/test_gossipsub.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
PubsubFactory,
1616
)
1717
from tests.utils.pubsub.utils import (
18+
connect_some,
1819
dense_connect,
1920
one_to_all_connect,
2021
sparse_connect,
@@ -590,3 +591,166 @@ async def test_sparse_connect():
590591
f"received the message. Ideally all nodes should receive it, but at "
591592
f"minimum {min_required} required for sparse network scalability."
592593
)
594+
595+
596+
@pytest.mark.trio
597+
async def test_connect_some_with_fewer_hosts_than_degree():
598+
"""Test connect_some when there are fewer hosts than degree."""
599+
# Create 3 hosts with degree=5
600+
async with PubsubFactory.create_batch_with_floodsub(3) as pubsubs_fsub:
601+
hosts = [pubsub.host for pubsub in pubsubs_fsub]
602+
degree = 5
603+
604+
await connect_some(hosts, degree)
605+
await trio.sleep(0.1) # Allow connections to establish
606+
607+
# Each host should connect to all other hosts (since there are only 2 others)
608+
for i, pubsub in enumerate(pubsubs_fsub):
609+
connected_peers = len(pubsub.peers)
610+
expected_max_connections = len(hosts) - 1 # All others
611+
assert connected_peers <= expected_max_connections, (
612+
f"Host {i} has {connected_peers} connections, "
613+
f"but can only connect to {expected_max_connections} others"
614+
)
615+
616+
617+
@pytest.mark.trio
618+
async def test_connect_some_degree_limit_enforced():
619+
"""Test that connect_some enforces degree limits and creates expected topology."""
620+
# Test with small network where we can verify exact behavior
621+
async with PubsubFactory.create_batch_with_floodsub(6) as pubsubs_fsub:
622+
hosts = [pubsub.host for pubsub in pubsubs_fsub]
623+
degree = 2
624+
625+
await connect_some(hosts, degree)
626+
await trio.sleep(0.1)
627+
628+
# With 6 hosts and degree=2, expected connections:
629+
# Host 0 → connects to hosts 1,2 (2 peers total)
630+
# Host 1 → connects to hosts 2,3 (3 peers: 0,2,3)
631+
# Host 2 → connects to hosts 3,4 (4 peers: 0,1,3,4)
632+
# Host 3 → connects to hosts 4,5 (3 peers: 1,2,4,5) - wait, that's 4!
633+
# Host 4 → connects to host 5 (3 peers: 2,3,5)
634+
# Host 5 → (2 peers: 3,4)
635+
636+
peer_counts = [len(pubsub.peers) for pubsub in pubsubs_fsub]
637+
638+
# First and last hosts should have exactly degree connections
639+
assert peer_counts[0] == degree, (
640+
f"Host 0 should have {degree} peers, got {peer_counts[0]}"
641+
)
642+
assert peer_counts[-1] <= degree, (
643+
f"Last host should have ≤ {degree} peers, got {peer_counts[-1]}"
644+
)
645+
646+
# Middle hosts may have more due to bidirectional connections
647+
# but the pattern should be consistent with degree limit
648+
total_connections = sum(peer_counts)
649+
650+
# Should be less than full mesh (each host connected to all others)
651+
full_mesh_connections = len(hosts) * (len(hosts) - 1)
652+
assert total_connections < full_mesh_connections, (
653+
f"Got {total_connections} total connections, "
654+
f"but full mesh would be {full_mesh_connections}"
655+
)
656+
657+
# Should be more than just a chain (each host connected to next only)
658+
chain_connections = 2 * (len(hosts) - 1) # bidirectional chain
659+
assert total_connections > chain_connections, (
660+
f"Got {total_connections} total connections, which is too few "
661+
f"(chain would be {chain_connections})"
662+
)
663+
664+
665+
@pytest.mark.trio
666+
async def test_connect_some_degree_zero():
667+
"""Test edge case: degree=0 should result in no connections."""
668+
# Create 5 hosts with degree=0
669+
async with PubsubFactory.create_batch_with_floodsub(5) as pubsubs_fsub:
670+
hosts = [pubsub.host for pubsub in pubsubs_fsub]
671+
degree = 0
672+
673+
await connect_some(hosts, degree)
674+
await trio.sleep(0.1) # Allow any potential connections to establish
675+
676+
# Verify no connections were made
677+
for i, pubsub in enumerate(pubsubs_fsub):
678+
connected_peers = len(pubsub.peers)
679+
assert connected_peers == 0, (
680+
f"Host {i} has {connected_peers} connections, "
681+
f"but degree=0 should result in no connections"
682+
)
683+
684+
685+
@pytest.mark.trio
686+
async def test_connect_some_negative_degree():
687+
"""Test edge case: negative degree should be handled gracefully."""
688+
# Create 5 hosts with degree=-1
689+
async with PubsubFactory.create_batch_with_floodsub(5) as pubsubs_fsub:
690+
hosts = [pubsub.host for pubsub in pubsubs_fsub]
691+
degree = -1
692+
693+
await connect_some(hosts, degree)
694+
await trio.sleep(0.1) # Allow any potential connections to establish
695+
696+
# Verify no connections were made (negative degree should behave like 0)
697+
for i, pubsub in enumerate(pubsubs_fsub):
698+
connected_peers = len(pubsub.peers)
699+
assert connected_peers == 0, (
700+
f"Host {i} has {connected_peers} connections, "
701+
f"but negative degree should result in no connections"
702+
)
703+
704+
705+
@pytest.mark.trio
706+
async def test_sparse_connect_degree_zero():
707+
"""Test sparse_connect with degree=0."""
708+
async with PubsubFactory.create_batch_with_floodsub(8) as pubsubs_fsub:
709+
hosts = [pubsub.host for pubsub in pubsubs_fsub]
710+
degree = 0
711+
712+
await sparse_connect(hosts, degree)
713+
await trio.sleep(0.1) # Allow connections to establish
714+
715+
# With degree=0, sparse_connect should still create neighbor connections
716+
# for connectivity (this is part of the algorithm design)
717+
for i, pubsub in enumerate(pubsubs_fsub):
718+
connected_peers = len(pubsub.peers)
719+
# Should have some connections due to neighbor connectivity
720+
# (each node connects to immediate neighbors)
721+
expected_neighbors = 2 # previous and next in ring
722+
assert connected_peers >= expected_neighbors, (
723+
f"Host {i} has {connected_peers} connections, "
724+
f"expected at least {expected_neighbors} neighbor connections"
725+
)
726+
727+
728+
@pytest.mark.trio
729+
async def test_empty_host_list():
730+
"""Test edge case: empty host list should be handled gracefully."""
731+
hosts = []
732+
733+
# All functions should handle empty lists gracefully
734+
await connect_some(hosts, 5)
735+
await sparse_connect(hosts, 3)
736+
await dense_connect(hosts)
737+
738+
# If we reach here without exceptions, the test passes
739+
740+
741+
@pytest.mark.trio
742+
async def test_single_host():
743+
"""Test edge case: single host should be handled gracefully."""
744+
async with PubsubFactory.create_batch_with_floodsub(1) as pubsubs_fsub:
745+
hosts = [pubsub.host for pubsub in pubsubs_fsub]
746+
747+
# All functions should handle single host gracefully
748+
await connect_some(hosts, 5)
749+
await sparse_connect(hosts, 3)
750+
await dense_connect(hosts)
751+
752+
# Single host should have no connections
753+
connected_peers = len(pubsubs_fsub[0].peers)
754+
assert connected_peers == 0, (
755+
f"Single host has {connected_peers} connections, expected 0"
756+
)

tests/utils/pubsub/utils.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,22 @@ def make_pubsub_msg(
2424
)
2525

2626

27-
# TODO: Implement sparse connect
2827
async def dense_connect(hosts: Sequence[IHost]) -> None:
2928
await connect_some(hosts, 10)
3029

3130

32-
# FIXME: `degree` is not used at all
3331
async def connect_some(hosts: Sequence[IHost], degree: int) -> None:
32+
"""
33+
Connect each host to up to 'degree' number of other hosts.
34+
Creates a sparse network topology where each node has limited connections.
35+
"""
3436
for i, host in enumerate(hosts):
35-
for host2 in hosts[i + 1 :]:
36-
await connect(host, host2)
37+
connections_made = 0
38+
for j in range(i + 1, len(hosts)):
39+
if connections_made >= degree:
40+
break
41+
await connect(host, hosts[j])
42+
connections_made += 1
3743

3844

3945
async def one_to_all_connect(hosts: Sequence[IHost], central_host_index: int) -> None:

0 commit comments

Comments
 (0)