mirror of
https://github.com/logos-messaging/logos-delivery.git
synced 2026-07-20 11:40:02 +00:00
* reconnect gate was "any relay peer in the peer store", not "loaded from storage" * fired even with peer persistence off (the default; FFI can't even enable it) * switch.start() boots discovery protocols before the dispatch, so bootstrap peers got swept in * backoff slept inside the per-peer loop: 62s serially per peer, connected peers included * mountRelay on a started node awaited the same sweep inline, blocking late relay mounts * PeerOrigin has no Storage value, so a "from storage only" filter isn't expressible today * the 30s relayConnectivityLoop already re-dials disconnected peers, making this path redundant * delete reconnectRelayPeers, relayReconnectFut and peerManager.reconnectPeers (no callers left) * add tests/node/test_wakunode_startup_reconnect.nim: 9 tests, 4 fail without this fix * drop the "Automatic Reconnection" sub-suite; it tested the removed proc * repair the sharded restart test: it passed via the removed sweep, not manageRelayPeers (node2 ENR now advertises Relay, node3 subscribes to its shard) * peer persistence itself is untouched; its removal is proposed as a follow-up PR Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
301 lines
12 KiB
Nim
301 lines
12 KiB
Nim
{.used.}
|
|
|
|
## Regression suite: relay peers present in the peer store at startup must not
|
|
## trigger reconnect-with-backoff behavior.
|
|
##
|
|
## Fast peer discovery (e.g. Kademlia service discovery bootstrapping) can
|
|
## populate the peer store before or during node startup. The peer store alone
|
|
## cannot tell such freshly discovered peers apart from peers restored from
|
|
## persistent storage, so any startup logic keyed on "the store has relay
|
|
## peers" misfires: discovered peers were being swept into a
|
|
## reconnect-after-prune-backoff wait (~62s), applied serially per peer, which
|
|
## stalled connectivity for minutes and blocked late relay mounts.
|
|
##
|
|
## The contract these tests pin down:
|
|
## - Adding a peer to the peer store is bookkeeping, not a dial instruction;
|
|
## neither node.start() nor a late mountRelay() may dial or sleep on store
|
|
## contents.
|
|
## - Establishing and re-establishing relay connectivity is the connectivity
|
|
## maintenance path's job (connectToRelayPeers / relayConnectivityLoop),
|
|
## which is prompt and respects per-peer failure backoff.
|
|
|
|
import testutils/unittests, chronos, libp2p/[peerid, multiaddress]
|
|
|
|
import
|
|
logos_delivery/waku/[waku_core, waku_node, node/peer_manager, waku_relay/protocol],
|
|
../testlib/[wakucore, wakunode, testasync, testutils]
|
|
|
|
# Valid peerId missing the last digit; append a digit to mint distinct peers.
|
|
const BasePeerId = "QmeuZJbXrszW2jdT7GdduSjQskPU3S7vvGWKtKgDfkDvW"
|
|
|
|
proc unreachableRelayPeer(n: int, origin = PeerOrigin.Kademlia): RemotePeerInfo =
|
|
## A relay-capable peer record the way discovery would hand it over:
|
|
## peer id, dialable-looking address, advertised relay protocol. The port
|
|
## has no listener, so any dial attempt fails fast and leaves a trace in
|
|
## numberFailedConn.
|
|
var peerId: PeerId
|
|
doAssert peerId.init(BasePeerId & $n)
|
|
RemotePeerInfo.init(
|
|
peerId = peerId,
|
|
addrs = @[MultiAddress.init("/ip4/127.0.0.1/tcp/" & $n).tryGet()],
|
|
protocols = @[WakuRelayCodec],
|
|
origin = origin,
|
|
)
|
|
|
|
suite "Startup relay reconnect regression":
|
|
asyncTest "mounting relay on a running node is not stalled by known relay peers":
|
|
## Given a started node without relay, whose peer store discovery has
|
|
## already populated with relay-capable peers
|
|
let node = newTestWakuNode(generateSecp256k1Key())
|
|
await node.start()
|
|
defer:
|
|
await node.stop()
|
|
|
|
for n in 1 .. 3:
|
|
node.peerManager.addPeer(unreachableRelayPeer(n), PeerOrigin.Kademlia)
|
|
|
|
## When relay is mounted late (e.g. edge node promoted to relay)
|
|
let mountFut = node.mountRelay()
|
|
let mountedInTime = await mountFut.withTimeout(10.seconds)
|
|
if not mountedInTime:
|
|
await mountFut.cancelAndWait()
|
|
|
|
## Then the mount completes promptly instead of sleeping a prune backoff
|
|
## per known peer
|
|
check mountedInTime
|
|
if mountedInTime:
|
|
check mountFut.read().isOk()
|
|
|
|
asyncTest "already-connected relay peers do not stall a late relay mount":
|
|
## Given a relay server and a started relay-less client connected to it
|
|
let
|
|
server = newTestWakuNode(generateSecp256k1Key())
|
|
client = newTestWakuNode(generateSecp256k1Key())
|
|
(await server.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay server: " & error
|
|
await allFutures(server.start(), client.start())
|
|
defer:
|
|
await allFutures(client.stop(), server.stop())
|
|
|
|
let
|
|
serverPeerInfo = server.switch.peerInfo.toRemotePeerInfo()
|
|
clientPeerStore = client.peerManager.switch.peerStore
|
|
await client.connectToNodes(@[serverPeerInfo])
|
|
|
|
# identify has advertised the server's relay support to the client
|
|
waitActive:
|
|
clientPeerStore.getPeer(serverPeerInfo.peerId).protocols.contains(WakuRelayCodec)
|
|
|
|
## When the client mounts relay while that relay-capable peer is connected
|
|
let mountFut = client.mountRelay()
|
|
let mountedInTime = await mountFut.withTimeout(10.seconds)
|
|
if not mountedInTime:
|
|
await mountFut.cancelAndWait()
|
|
|
|
## Then the mount is not delayed on account of the connected peer
|
|
check mountedInTime
|
|
if mountedInTime:
|
|
check mountFut.read().isOk()
|
|
check clientPeerStore.getPeer(serverPeerInfo.peerId).connectedness == Connected
|
|
|
|
asyncTest "startup does not auto-dial peers that discovery added to the peer store":
|
|
## Given a relay server and a relay client that learned about the server
|
|
## (plus some unreachable peers) via discovery before starting
|
|
let
|
|
server = newTestWakuNode(generateSecp256k1Key())
|
|
client = newTestWakuNode(generateSecp256k1Key())
|
|
(await server.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay server: " & error
|
|
(await client.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay client: " & error
|
|
await server.start()
|
|
|
|
# keep any legacy reconnect wait short enough to observe inside the window
|
|
client.wakuRelay.parameters.pruneBackoff = chronos.seconds(1)
|
|
|
|
let
|
|
serverPeerInfo = server.switch.peerInfo.toRemotePeerInfo()
|
|
clientPeerStore = client.peerManager.switch.peerStore
|
|
client.peerManager.addPeer(serverPeerInfo, PeerOrigin.Kademlia)
|
|
for n in 1 .. 2:
|
|
client.peerManager.addPeer(unreachableRelayPeer(n), PeerOrigin.Kademlia)
|
|
|
|
## When the client starts
|
|
await client.start()
|
|
defer:
|
|
await allFutures(client.stop(), server.stop())
|
|
|
|
## Then no store peer is dialed behind the connectivity policy's back
|
|
var autoDialed = false
|
|
for _ in 0 ..< 16:
|
|
if clientPeerStore.getPeer(serverPeerInfo.peerId).connectedness == Connected:
|
|
autoDialed = true
|
|
break
|
|
await sleepAsync(500.milliseconds)
|
|
|
|
check not autoDialed
|
|
for n in 1 .. 2:
|
|
check clientPeerStore.getPeer(unreachableRelayPeer(n).peerId).numberFailedConn == 0
|
|
|
|
asyncTest "startup ignores peer-store relay peers regardless of peer origin":
|
|
## Given a relay node whose peer store holds relay-capable peers of
|
|
## assorted origins (none of them from persistent storage — persistence
|
|
## is not even enabled)
|
|
let node = newTestWakuNode(generateSecp256k1Key())
|
|
(await node.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay: " & error
|
|
node.wakuRelay.parameters.pruneBackoff = chronos.seconds(1)
|
|
|
|
let peerStore = node.peerManager.switch.peerStore
|
|
let origins =
|
|
[PeerOrigin.Kademlia, PeerOrigin.Discv5, PeerOrigin.Static, PeerOrigin.Dns]
|
|
for n, origin in origins:
|
|
node.peerManager.addPeer(unreachableRelayPeer(n + 1, origin), origin)
|
|
|
|
## When the node starts and runs for a while
|
|
await node.start()
|
|
defer:
|
|
await node.stop()
|
|
await sleepAsync(6.seconds)
|
|
|
|
## Then none of them was treated as a reconnect candidate
|
|
for n, origin in origins:
|
|
let peer = peerStore.getPeer(unreachableRelayPeer(n + 1, origin).peerId)
|
|
check:
|
|
peer.numberFailedConn == 0
|
|
peer.connectedness == NotConnected
|
|
|
|
asyncTest "the connectivity loop connects discovered relay peers promptly":
|
|
## Given a relay server known to the client only through its peer store
|
|
## (as after discovery), with the connectivity maintenance loop running
|
|
## as node_factory.startNode wires it in production
|
|
let
|
|
server = newTestWakuNode(generateSecp256k1Key())
|
|
client = newTestWakuNode(generateSecp256k1Key())
|
|
(await server.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay server: " & error
|
|
(await client.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay client: " & error
|
|
await allFutures(server.start(), client.start())
|
|
defer:
|
|
await allFutures(client.stop(), server.stop())
|
|
|
|
let
|
|
serverPeerInfo = server.switch.peerInfo.toRemotePeerInfo()
|
|
clientPeerStore = client.peerManager.switch.peerStore
|
|
client.peerManager.addPeer(serverPeerInfo, PeerOrigin.Kademlia)
|
|
|
|
## When the connectivity loop starts
|
|
client.peerManager.start()
|
|
defer:
|
|
client.peerManager.stop()
|
|
|
|
## Then the discovered peer is connected promptly, no backoff involved
|
|
waitActive:
|
|
clientPeerStore.getPeer(serverPeerInfo.peerId).connectedness == Connected
|
|
|
|
asyncTest "relay connectivity is re-established on demand via the maintenance path":
|
|
## Given two connected relay nodes that then disconnect
|
|
let
|
|
server = newTestWakuNode(generateSecp256k1Key())
|
|
client = newTestWakuNode(generateSecp256k1Key())
|
|
(await server.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay server: " & error
|
|
(await client.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay client: " & error
|
|
await allFutures(server.start(), client.start())
|
|
defer:
|
|
await allFutures(client.stop(), server.stop())
|
|
|
|
let
|
|
serverPeerInfo = server.switch.peerInfo.toRemotePeerInfo()
|
|
clientPeerStore = client.peerManager.switch.peerStore
|
|
await client.connectToNodes(@[serverPeerInfo])
|
|
waitActive:
|
|
clientPeerStore.getPeer(serverPeerInfo.peerId).connectedness == Connected
|
|
|
|
await client.disconnectNode(serverPeerInfo)
|
|
waitActive:
|
|
clientPeerStore.getPeer(serverPeerInfo.peerId).connectedness == CanConnect
|
|
|
|
## When the connectivity maintenance pass runs
|
|
await client.peerManager.connectToRelayPeers()
|
|
|
|
## Then the relay peer is connected again, with no built-in delay
|
|
waitActive:
|
|
clientPeerStore.getPeer(serverPeerInfo.peerId).connectedness == Connected
|
|
|
|
asyncTest "restart re-establishes relay connectivity via the maintenance path":
|
|
## Given a relay node that was connected to a relay peer and restarted
|
|
let
|
|
node1 = newTestWakuNode(generateSecp256k1Key())
|
|
node2 = newTestWakuNode(generateSecp256k1Key())
|
|
(await node1.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay node1: " & error
|
|
(await node2.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay node2: " & error
|
|
await allFutures(node1.start(), node2.start())
|
|
|
|
let
|
|
node2PeerInfo = node2.switch.peerInfo.toRemotePeerInfo()
|
|
node1PeerStore = node1.peerManager.switch.peerStore
|
|
await node1.connectToNodes(@[node2PeerInfo])
|
|
waitActive:
|
|
node1PeerStore.getPeer(node2PeerInfo.peerId).connectedness == Connected
|
|
|
|
await node1.stop()
|
|
|
|
## When it starts again and the connectivity maintenance pass runs
|
|
let restarted = await node1.start().withTimeout(10.seconds)
|
|
check restarted
|
|
defer:
|
|
await allFutures(node1.stop(), node2.stop())
|
|
|
|
await node1.peerManager.connectToRelayPeers()
|
|
|
|
## Then the previously-known relay peer is connected again promptly
|
|
waitActive:
|
|
node1PeerStore.getPeer(node2PeerInfo.peerId).connectedness == Connected
|
|
|
|
asyncTest "start and stop with a populated peer store are prompt and clean":
|
|
## Given a relay node whose peer store holds unreachable relay peers
|
|
let node = newTestWakuNode(generateSecp256k1Key())
|
|
(await node.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay: " & error
|
|
for n in 1 .. 3:
|
|
node.peerManager.addPeer(unreachableRelayPeer(n), PeerOrigin.Kademlia)
|
|
|
|
## When it starts and immediately stops
|
|
let startedInTime = await node.start().withTimeout(10.seconds)
|
|
check startedInTime
|
|
|
|
let stoppedInTime = await node.stop().withTimeout(10.seconds)
|
|
|
|
## Then neither direction blocks on peer-store contents
|
|
check stoppedInTime
|
|
|
|
asyncTest "relay streams form normally after startup with a pre-populated peer store":
|
|
## Given a relay client that starts with discovered peers (reachable and
|
|
## not) already in its peer store
|
|
let
|
|
server = newTestWakuNode(generateSecp256k1Key())
|
|
client = newTestWakuNode(generateSecp256k1Key())
|
|
(await server.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay server: " & error
|
|
(await client.mountRelay()).isOkOr:
|
|
raiseAssert "mountRelay client: " & error
|
|
await server.start()
|
|
|
|
for n in 1 .. 2:
|
|
client.peerManager.addPeer(unreachableRelayPeer(n), PeerOrigin.Kademlia)
|
|
await client.start()
|
|
defer:
|
|
await allFutures(client.stop(), server.stop())
|
|
|
|
## When it connects to a reachable relay peer
|
|
await client.connectToNodes(@[server.switch.peerInfo.toRemotePeerInfo()])
|
|
|
|
## Then relay protocol streams are established as usual
|
|
waitActive:
|
|
client.peerManager.connectedPeers(WakuRelayCodec)[1].len == 1
|