Skip to content

Commit fe5bf6c

Browse files
igor-sirotinclaude
andauthored
fix: prefer connected peers (light-client test flakiness) (#6943)
* test: discovery mix full and light clients * docs: known issue wakufleetconfig.json not found * test: enable bridge network by default to prevent discovery * fix: logs * test(waku): use nwaku PR #3939 fleet image + num-shards-in-network=65 quay.io/wakuorg/nwaku-pr:3939 is the build of logos-messaging/logos-delivery#3939, the candidate fix for the light-client PX stale-ENR flake (#7513). Also enables store peer-exchange. * test: allow finding more peers * chore(deps): bump go-waku with filter prefer-connected-peers fix Pulls waku-org/go-waku fix/filter-prefer-connected-peers, which makes peer selection prefer already-connected peers. This stops light-client filter/lightpush from churning on stale/unreachable peers learned via peer-exchange, which was causing the flaky light-client message tests (#7513). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(waku): use released nwaku v0.38.1 fleet (drop #3939) Test the go-waku prefer-connected-peers fix in isolation: the status-go image already carries it (go.mod -> go-waku fix branch), so point the fleet at the released wakuorg/nwaku:v0.38.1 instead of the #3939 build. The client-side fix should keep light clients off stale peers regardless of fleet-side stale-ENR pruning. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(deps): bump go-waku to v0.10.2 Release tag including the prefer-connected-peers selection fix (logos-messaging/logos-delivery-go#1309), which keeps light-client filter/lightpush off stale/unreachable peers and fixes the flaky light-client message tests (#7513). Supersedes the temporary branch pin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: go mod tidy --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent edabcf5 commit fe5bf6c

6 files changed

Lines changed: 108 additions & 60 deletions

File tree

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ require (
102102
github.com/schollz/peerdiscovery v1.7.0
103103
github.com/status-im/extkeys v1.4.0
104104
github.com/status-im/go-wallet-sdk v0.0.0-20260428172609-d99313975e5c
105-
github.com/waku-org/go-waku v0.10.2-0.20260518064454-d6d39395ac48
105+
github.com/waku-org/go-waku v0.10.2
106106
github.com/waku-org/sds-go-bindings v0.0.0-20251222164514-d5b47a911904
107107
github.com/wk8/go-ordered-map/v2 v2.1.7
108108
go.uber.org/atomic v1.11.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2416,6 +2416,8 @@ github.com/waku-org/go-libp2p-pubsub v0.13.1-gowaku h1:ovXh1m76i0yPWXt95Z9ZRyEk0
24162416
github.com/waku-org/go-libp2p-pubsub v0.13.1-gowaku/go.mod h1:MKPU5vMI8RRFyTP0HfdsF9cLmL1nHAeJm44AxJGJx44=
24172417
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20240110193335-a67d1cc760a0 h1:R4YYx2QamhBRl/moIxkDCNW+OP7AHbyWLBygDc/xIMo=
24182418
github.com/waku-org/go-libp2p-rendezvous v0.0.0-20240110193335-a67d1cc760a0/go.mod h1:EhZP9fee0DYjKH/IOQvoNSy1tSHp2iZadsHGphcAJgY=
2419+
github.com/waku-org/go-waku v0.10.2 h1:Rjy9UhdXaeLSwcm8unMUiFM3HdLxyJvdtleREEMeyrg=
2420+
github.com/waku-org/go-waku v0.10.2/go.mod h1:PDs4hlPbONeMQGsNyfArZV4R2Js1Zy5oqLBfw2VU278=
24192421
github.com/waku-org/go-zerokit-rln v0.1.14-0.20240102145250-fa738c0bdf59 h1:jisj+OCI6QydLtFq3Pyhu49wl9ytPN7oAHjMfepHDrA=
24202422
github.com/waku-org/go-zerokit-rln v0.1.14-0.20240102145250-fa738c0bdf59/go.mod h1:1PdBdPzyTaKt3VnpAHk3zj+r9dXPFOr3IHZP9nFle6E=
24212423
github.com/waku-org/go-zerokit-rln-apple v0.0.0-20230916172309-ee0ee61dde2b h1:KgZVhsLkxsj5gb/FfndSCQu6VYwALrCOgYI3poR95yE=
@@ -2446,8 +2448,6 @@ github.com/wk8/go-ordered-map/v2 v2.1.7/go.mod h1:9Xvgm2mV2kSq2SAm0Y608tBmu8akTz
24462448
github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
24472449
github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU=
24482450
github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA=
2449-
github.com/waku-org/go-waku v0.10.2-0.20260518064454-d6d39395ac48 h1:a00GguapTGSRIN9ocw93WsrMOTD07TJ1iqCashttNwA=
2450-
github.com/waku-org/go-waku v0.10.2-0.20260518064454-d6d39395ac48/go.mod h1:PDs4hlPbONeMQGsNyfArZV4R2Js1Zy5oqLBfw2VU278=
24512451
github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs=
24522452
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
24532453
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=

nix/pkgs/status-go/library/default.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ let
1111
in pkgs.buildGoModule {
1212
pname = "status-go";
1313
src = builtins.path { path = ./../../../..; name = "status-go-library"; };
14-
vendorHash = "sha256-cYgQOPBoCI3ljBdc1XqR4D25uOJHj/f8mYSUU7mKSNo=";
14+
vendorHash = "sha256-aThfpeBpQumP0bR+/Iy5uxvcxi1xPNTMNaiLeul1dxs=";
1515

1616
inherit meta version;
1717

tests-functional/README.MD

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,4 +343,13 @@ If that is so, force Python to use the same Docker socket (from `docker context
343343
```shell
344344
export DOCKER_HOST=unix:///var/run/docker.sock
345345
```
346-
Add this before launching your test runner
346+
Add this before launching your test runner
347+
348+
## `/usr/status-user/wakufleetconfig.json: no such file or directory"`
349+
350+
When running tests against `status-backend` Docker containers, you might see this error:
351+
```
352+
DEBUG:root:Got response: b'{"error":"failed to open fleets json file: open /usr/status-user/wakufleetconfig.json: no such file or directory"}'
353+
```
354+
355+
If running with default arguments, this most likely means that you're not running local Waku fleet as described in [Prerequisites](#prerequisites).

tests-functional/docker-compose.waku.yml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ services:
2525
"--shard=64",
2626
"--staticnode=/dns4/store/tcp/60002/p2p/16Uiu2HAmCDqxtfF1DwBqs7UJ4TgSnjoh6j1RtE1hhQxLLao84jLi",
2727
"--storenode=/dns4/store/tcp/60002/p2p/16Uiu2HAmCDqxtfF1DwBqs7UJ4TgSnjoh6j1RtE1hhQxLLao84jLi",
28-
"--tcp-port=60001"
28+
"--tcp-port=60001",
29+
"--num-shards-in-network=65"
2930
]
3031
depends_on:
3132
- store
@@ -53,7 +54,7 @@ services:
5354
"--log-level=DEBUG",
5455
"--max-connections=18000",
5556
"--nodekey=3190bc9b55b18dbc171997a7a67abcd5bbf0c81002ad9617b1cb67f2f15daa64",
56-
"--peer-exchange=false",
57+
"--peer-exchange=true",
5758
"--relay=true",
5859
"--rest=true",
5960
"--rest-address=0.0.0.0",
@@ -65,7 +66,8 @@ services:
6566
"--store=true",
6667
"--store-message-db-url=sqlite:////tmp/waku-store.sqlite3",
6768
"--store-message-retention-policy=time:86400",
68-
"--tcp-port=60002"
69+
"--tcp-port=60002",
70+
"--num-shards-in-network=65"
6971
]
7072
healthcheck:
7173
test: ["CMD-SHELL", "curl -X GET -H 'Content-Type: application/json' http://0.0.0.0:8645 || exit 0"]
@@ -90,4 +92,3 @@ services:
9092

9193
volumes:
9294
wakufleetconfig:
93-
Lines changed: 89 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,110 @@
11
import logging
2-
import time
3-
import pytest
42
import threading
53

6-
from clients.status_backend import StatusBackend
7-
8-
DISCOVERY_TIMEOUT_SEC = 30
9-
10-
11-
def _all_nodes_discovered(nodes: dict[str, StatusBackend], known_peers: dict[str, str]) -> bool:
12-
"""Check if all nodes discovered each other"""
13-
for peer_id, node in nodes.items():
14-
if node is None:
15-
return False
16-
peers = node.wakuext_service.peers()
17-
18-
# Use shorter names for logging
19-
peer_names = [known_peers.get(peer, peer[-5:]) for peer in peers]
20-
logging.info(f"Node {known_peers[peer_id]} peers: {peer_names}")
4+
import pytest
5+
from tenacity import retry, stop_after_delay, wait_fixed
216

22-
for known_node in known_peers.keys():
23-
if known_node == peer_id:
24-
continue
25-
if known_node not in peers:
26-
return False
27-
return True
7+
from clients.status_backend import StatusBackend
288

299

3010
@pytest.mark.rpc
3111
class TestDiscovery:
32-
3312
def test_discovery(self, backend_new_profile):
34-
nodes_count = 3
13+
# Desired topology: 3 full nodes, 2 light nodes
14+
full_count = 3
15+
light_count = 2
16+
total_nodes = full_count + light_count
17+
3518
nodes: dict[str, StatusBackend] = {}
19+
full_ids: set[str] = set()
3620

3721
known_nodes = {
22+
# Boot nodes available in the fleet
3823
"16Uiu2HAm3vFYHkGRURyJ6F7bwDyzMLtPEuCg4DU89T7km2u8Fjyb": "boot-1",
3924
"16Uiu2HAmCDqxtfF1DwBqs7UJ4TgSnjoh6j1RtE1hhQxLLao84jLi": "store",
4025
}
4126

42-
def create_node(node_index: int):
43-
"""Function to run in each thread - waits for wakuv2.peerstats signal"""
44-
backend = backend_new_profile(f"node_{node_index}")
27+
def create_node(node_index: int, waku_light_client: bool):
28+
backend = backend_new_profile(
29+
f"node_{node_index}",
30+
# We mix full and light clients in a single test.
31+
# Light clients (edge nodes) will not discover each,
32+
# but they must discover full clients.
33+
waku_light_client=waku_light_client,
34+
# Force the container to only have the docker compose network.
35+
# Otherwise, advertised ENRs may contain the wrong IP address.
36+
bridge_network=False,
37+
)
4538
peer_id = backend.wakuext_service.peer_id()
4639
known_nodes[peer_id] = f"backend_{node_index}"
40+
if not waku_light_client:
41+
full_ids.add(peer_id)
4742
nodes[peer_id] = backend
48-
logging.info(f"Backend {node_index} ready. Peer ID: {peer_id}")
49-
50-
# Run threads, each waiting for wakuv2.peerstats signal
51-
logging.info("Starting threads to wait for wakuv2.peerstats signals...")
43+
info = f"Peer ID: {peer_id}"
44+
info += f", URL: {backend.url}"
45+
if backend.container:
46+
info += f", Container: {backend.container.short_id()}"
47+
info += f", Type: {'full' if not waku_light_client else 'light'}"
48+
logging.info(f"Backend {node_index} ready. {info}")
49+
50+
# Start threads to create nodes
51+
logging.info("Starting threads to create full and light clients...")
5252
threads = []
5353

54-
for i in range(nodes_count):
55-
thread = threading.Thread(target=create_node, args=(i,))
56-
thread.daemon = True
57-
thread.start()
58-
threads.append(thread)
59-
60-
for thread in threads:
61-
thread.join()
62-
63-
assert len(nodes.keys()) == nodes_count, "Not all nodes created"
64-
65-
# Wait for all nodes to discover each other
66-
start_time = time.time()
67-
while time.time() - start_time < DISCOVERY_TIMEOUT_SEC:
68-
if _all_nodes_discovered(nodes, known_nodes):
69-
return
70-
time.sleep(0.5)
71-
72-
assert False, f"Nodes failed to discover each other within {DISCOVERY_TIMEOUT_SEC} seconds"
54+
# First start full nodes
55+
for i in range(full_count):
56+
t = threading.Thread(target=create_node, args=(i, False))
57+
t.daemon = True
58+
t.start()
59+
threads.append(t)
60+
61+
# Then start light nodes
62+
for i in range(full_count, total_nodes):
63+
t = threading.Thread(target=create_node, args=(i, True))
64+
t.daemon = True
65+
t.start()
66+
threads.append(t)
67+
68+
for t in threads:
69+
t.join()
70+
71+
assert len(nodes.keys()) == total_nodes, "Not all nodes created"
72+
73+
# Build sets for expectations
74+
all_node_ids = set(nodes.keys())
75+
boot_ids = {k for k, v in known_nodes.items() if v in ("boot-1", "store")}
76+
77+
# Pre-build expected peers per node (static expectations)
78+
expected_by_node: dict[str, set[str]] = {}
79+
for pid in nodes.keys():
80+
if pid in full_ids:
81+
# Full clients should discover all other clients and boot nodes
82+
expected = (all_node_ids | boot_ids) - {pid}
83+
else:
84+
# Light clients should discover only full clients and boot nodes
85+
expected = boot_ids | full_ids
86+
expected_by_node[pid] = expected
87+
88+
# Check using tenacity.retry, logs peers on each iteration
89+
@retry(stop=stop_after_delay(120), wait=wait_fixed(1), reraise=True)
90+
def assert_discovery():
91+
all_ok = True
92+
93+
for peer_id, node in nodes.items():
94+
# Fetch peers once per node per attempt
95+
peers = set(node.wakuext_service.peers())
96+
97+
# Log peer names
98+
peer_names = [known_nodes.get(peer, peer[-5:]) for peer in peers]
99+
logging.info(f"Checking node {known_nodes[peer_id]} peers: {peer_names}")
100+
101+
if peers >= expected_by_node[peer_id]:
102+
logging.info(f"Node {known_nodes[peer_id]} discovered peers as expected")
103+
nodes.pop(peer_id)
104+
continue
105+
106+
all_ok = False
107+
108+
assert all_ok, "Not all nodes discovered as expected"
109+
110+
assert_discovery()

0 commit comments

Comments
 (0)