Files
Igor Sirotin b7ba8e52d6 refactor: move test suites under test/
tests-functional/    -> test/functional/
  tests-unit-network/  -> test/unit-network/

No Go code changes beyond the two package paths. Every reference follows:
Makefile targets, the pytest-lint and reliability workflows, the three
Jenkinsfiles, the root Dockerfile, pyrightconfig.json, .gitignore, the
benchmark and functional-test scripts, and the docs.

All paths inside the moved directories are self-contained (docker compose
uses `context: .`, the Python helpers resolve from __file__), so the extra
level of nesting does not reach outside.

refs #7067
2026-08-20 20:59:45 +02:00

117 lines
4.6 KiB
Python

import logging
import threading
import pytest
from tenacity import retry, stop_after_delay, wait_fixed
from clients.status_backend import StatusBackend
@pytest.mark.rpc
class TestDiscovery:
def test_discovery(self, backend_new_profile):
# Desired topology: 3 full nodes, 2 light nodes
full_count = 3
light_count = 2
total_nodes = full_count + light_count
nodes: dict[str, StatusBackend] = {}
full_ids: set[str] = set()
known_nodes = {
# Boot node in the fleet: every client is expected to discover and stay
# connected to it.
"16Uiu2HAm3vFYHkGRURyJ6F7bwDyzMLtPEuCg4DU89T7km2u8Fjyb": "boot-1",
# Store node: queried on demand by the StoreClient, so it is NOT expected
# to be a persistent peer. Kept here only to label it in the peer dumps.
"16Uiu2HAmCDqxtfF1DwBqs7UJ4TgSnjoh6j1RtE1hhQxLLao84jLi": "store",
}
def create_node(node_index: int, waku_light_client: bool):
backend = backend_new_profile(
f"node_{node_index}",
# We mix full and light clients in a single test.
# Light clients (edge nodes) will not discover each,
# but they must discover full clients.
waku_light_client=waku_light_client,
# Force the container to only have the docker compose network.
# Otherwise, advertised ENRs may contain the wrong IP address.
bridge_network=False,
)
peer_id = backend.wakuext_service.peer_id()
known_nodes[peer_id] = f"backend_{node_index}"
if not waku_light_client:
full_ids.add(peer_id)
nodes[peer_id] = backend
info = f"Peer ID: {peer_id}"
info += f", URL: {backend.url}"
if backend.container:
info += f", Container: {backend.container.short_id()}"
info += f", Type: {'full' if not waku_light_client else 'light'}"
logging.info(f"Backend {node_index} ready. {info}")
# Start threads to create nodes
logging.info("Starting threads to create full and light clients...")
threads = []
# First start full nodes
for i in range(full_count):
t = threading.Thread(target=create_node, args=(i, False))
t.daemon = True
t.start()
threads.append(t)
# Then start light nodes
for i in range(full_count, total_nodes):
t = threading.Thread(target=create_node, args=(i, True))
t.daemon = True
t.start()
threads.append(t)
for t in threads:
t.join()
assert len(nodes.keys()) == total_nodes, "Not all nodes created"
# Build sets for expectations
all_node_ids = set(nodes.keys())
# Only the boot node is expected as a persistent peer. The store node is
# contacted on demand by the StoreClient (there is no storenode cycle keeping
# it connected), so it is intentionally excluded from the expectations.
boot_ids = {k for k, v in known_nodes.items() if v == "boot-1"}
# Pre-build expected peers per node (static expectations)
expected_by_node: dict[str, set[str]] = {}
for pid in nodes.keys():
if pid in full_ids:
# Full clients should discover all other clients and boot nodes
expected = (all_node_ids | boot_ids) - {pid}
else:
# Light clients should discover only full clients and boot nodes
expected = boot_ids | full_ids
expected_by_node[pid] = expected
# Check using tenacity.retry, logs peers on each iteration
@retry(stop=stop_after_delay(120), wait=wait_fixed(1), reraise=True)
def assert_discovery():
all_ok = True
for peer_id, node in nodes.items():
# Fetch peers once per node per attempt
peers = set(node.wakuext_service.peers())
# Log peer names
peer_names = [known_nodes.get(peer, peer[-5:]) for peer in peers]
logging.info(f"Checking node {known_nodes[peer_id]} peers: {peer_names}")
if peers >= expected_by_node[peer_id]:
logging.info(f"Node {known_nodes[peer_id]} discovered peers as expected")
nodes.pop(peer_id)
continue
all_ok = False
assert all_ok, "Not all nodes discovered as expected"
assert_discovery()