refactor(net): persistent endpoint/gossip/router (W7 B1)
The friends-only presence listener (W7) must answer pings while the app is open, whether or not we're in a call — but a node id has exactly one live endpoint instance (proven by the dual-endpoint spike: two endpoints sharing a SecretKey collide, all inbound connections land on one and the other's ALPN fails the QUIC handshake). So the listener can't get its own endpoint; the whole app must share one persistent endpoint. Today the core rebuilds the endpoint+gossip+router on every Join and tears them down on leave, so there's nothing alive between calls. B1 hoists those durable pieces to the app lifetime (no new behavior): - New persistent `NetStack` (endpoint + gossip + Router) built once at startup under the RelayNoDiscovery default (relay reachability, no DNS beacon); `online()` is backgrounded so launch isn't blocked. - New persistent `AudioRouter` (src/network/iroh_impl.rs) replaces the per-session `AudioProtocol`: it's registered once on the single Router and delegates each inbound audio connection to whatever session `Shared` is bound (`bind` on join, `clear` on leave), dropping links when idle. `IrohTransport:: new` now returns just `Self`. - Join reuses `net.endpoint`/`net.gossip` and only subscribes its gossip topic + binds the audio router; Leave clears the router but keeps the endpoint up. - `SetNetworkMode`/`RegenerateIdentity` rebuild the stack immediately when idle, else defer to the next Leave/Join (preserves "applies on next join"), and the existing session is always torn down before any rebuild closes the endpoint. Tests/loopback updated for the new transport API. 256 lib + 6 reconnect + 4 loopback + 2 ignored real-endpoint tests green, clippy --all-targets clean, release builds. NOT yet 2-machine field-verified — that regression is the gate before this merges to main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+29
-8
@@ -133,14 +133,35 @@ address book), though a lightweight mutual-add is optional polish.
|
||||
stranger. **Loopback integration test PASSED over real iroh endpoints** (friend
|
||||
gets Pong+room; stranger gets unusable reply); run with `cargo test -- --ignored
|
||||
presence_net`.
|
||||
- **STILL deferred (next session, needs care + 2 machines):**
|
||||
- **The endpoint-lifecycle fork (decide first):** a second always-on endpoint
|
||||
shares our node id with the per-join room endpoint (possible relay/identity
|
||||
collision) vs. refactoring to ONE persistent endpoint that hosts friends +
|
||||
room. Spike/decide before wiring `serve` live.
|
||||
- Spawn `serve` outside the room session; the rate-limited outbound ping
|
||||
scheduler (app open + slow refresh); report our current gathering via
|
||||
`restamp`; render friend status + a **room-name tag + Join** entry (overlaps P5).
|
||||
- **Endpoint-lifecycle fork — DECIDED 2026-06-15 via a throwaway spike: option (b),
|
||||
a SINGLE persistent endpoint.** The spike bound two endpoints sharing one
|
||||
`SecretKey` (one node id), each accepting a different ALPN, and probed both from a
|
||||
third endpoint. Result: **every inbound connection landed on ONE endpoint** (the
|
||||
first-bound), and connections for the other ALPN failed at the QUIC handshake with
|
||||
*"error 120: peer doesn't support any known protocol"* (the connection physically
|
||||
reached the wrong instance, which doesn't speak that ALPN). So **one node id =
|
||||
exactly one reachable endpoint instance** — option (a) (a second always-on
|
||||
friends endpoint sharing our id) is impossible, not merely risky. This is a hard
|
||||
handshake-layer collision, confirmed on one machine (no cross-network leg needed;
|
||||
which instance "wins" is just bind-order). **⇒ build (b):** one persistent
|
||||
endpoint bound once at startup, hosting friends-control + gossip + audio via a
|
||||
single persistent `Router`; a room "join" becomes subscribe-a-gossip-topic +
|
||||
spawn-audio-tasks (not rebuild-everything); `NetworkMode` changes require a full
|
||||
endpoint rebuild (acceptable — already "applies on next join").
|
||||
- **B1 (persistent network stack) — DONE 2026-06-15, tests-green (⚠️ 2-machine
|
||||
field test pending; on branch `w7-b1-persistent-endpoint`, not merged).** A
|
||||
persistent `NetStack` (endpoint + gossip + `Router`) is built once at startup and
|
||||
reused across calls; a new persistent `AudioRouter` (`src/network/iroh_impl.rs`)
|
||||
delegates inbound audio links to the active session's `Shared` (`bind` on join,
|
||||
`clear` on leave), so the single router/endpoint outlive any room session. Join
|
||||
no longer rebuilds the endpoint; network-mode/identity changes rebuild the stack
|
||||
when idle, else defer to the next Leave/Join. This is the prerequisite for wiring
|
||||
the live listener (below).
|
||||
- **STILL deferred (B2, after B1 merges):**
|
||||
- Spawn `serve` on the persistent endpoint (via the Router's FRIENDS_ALPN
|
||||
handler), outside the room session; the rate-limited outbound ping scheduler
|
||||
(app open + slow refresh); report our current gathering via `restamp`; render
|
||||
friend status + a **room-name tag + Join** entry (overlaps P5).
|
||||
|
||||
### P5 — Recents + UI — Medium
|
||||
Local recents list (cosmetic room tags). Friends-list UI with status dots
|
||||
|
||||
Reference in New Issue
Block a user