You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
A node whose successor list empties never comes back on its own. SuccessorSeq::min() returns self (crates/core/src/dht/successor.rs), notify_predecessor and pre_stabilize skip, find_successor answers Local(self), and nothing re-dials: ConnectWithSeed is a one-shot RPC (crates/node/src/rpc_impl.rs) and there is no persisted DHT peer state at all. The only persisted peer-shaped state is the onion entry-guard set and the measurement ledger, neither of which is used to reconnect.
#763 adds a run-owned redial supervisor for the native daemon's configured seeds. That leaves two gaps:
Browser nodes: the provider has no equivalent, and a tab that loses its links has nothing but whatever seed the page last used.
Seed-only recovery: re-dialing seeds concentrates every recovering node on the same few endpoints and discards what the node already learned about who stays online.
The literature is explicit about which peers to remember. Maymounkov & Mazières, "Kademlia" (IPTPS 2002), §2.2 keep the oldest live contacts because "the longer a node has been up, the more likely it is to remain up another hour" (Fig. 1, measured on Gnutella). Stutzbach & Rejaie, "Understanding Churn in Peer-to-Peer Networks" (IMC 2006), §4.5 confirm on three systems that observed uptime predicts residual lifetime (Weibull sessions with k ≈ 0.34–0.38). Godfrey, Shenker & Stoica, "Minimizing Churn in Distributed Systems" (SIGCOMM 2006) show that selecting by longest observed uptime beats random selection, and that "preference-list" selection driven by another metric (here: ring position) systematically raises churn — which is why the cache must be age-ranked even though the ring cannot be. BitTorrent's Mainline DHT and libp2p's Kademlia both persist their routing tables across restarts for the same reason.
Proposal
Age-ranked peer cache. Maintain a bounded set (e.g. 32) of (did, first_seen, last_seen, reachability hint) records for admitted peers, ranked by observed uptime last_seen − first_seen, refreshed from the measurement ledger's Connected/Disconnected events. Persist it through KvStorageInterface next to the entry guards (crates/node/src/onion/entry_guard.rs is the pattern; browser localStorage is already used there).
Re-join supervisor in core, not node. When successor_head() is None (or admitted routable peers fall below a floor) the stabilizer enters a re-join phase: dial cached peers in age order, then seeds, with exponential backoff and full jitter (2 s … 5 min), one handshake per target, cancelled on stop. This is the browser-capable counterpart of Add run-owned bootstrap reachability and automatic redial #763 and should share its retry state machine.
Learn from routing. Kademlia's second lesson (§2.2) is that traffic keeps tables fresh for free: the mark_inbound liveness path already sees every authenticated payload, so last_seen costs nothing.
Do not pin. Cached peers are dial candidates, never DHT members; admission still goes through admit_connected, so Chord membership proofs are unchanged.
Acceptance
A browser node whose every connection is closed rejoins the overlay without a page reload, using only its cache.
Recovery order is by observed uptime; a test with three cached peers of different ages dials the oldest first.
A node with all seeds down still recovers if any cached peer is up.
The cache survives stop/listen and a provider rebuild; its size and TTL are bounded.
#763 (native seed redial), the adaptive successor-list issue (raising r shrinks how often this path is needed), and the browser lifecycle issue (a visibilitychange to visible should trigger this phase immediately).
Issue family (browser-churn stability, 2026-09-14)
#773 churn simulator · #774 adaptive successor list and stabilization period · #775 age-ranked peer cache and re-join · #776 RTT-derived timeouts, ICE restart, lookup retry · #777 inbox replication and Leave · #778 stability-weighted storage · #779 browser lifecycle events. Finger convergence is #768 / #770; native seed redial is #763.
Motivation
A node whose successor list empties never comes back on its own.
SuccessorSeq::min()returnsself(crates/core/src/dht/successor.rs),notify_predecessorandpre_stabilizeskip,find_successoranswersLocal(self), and nothing re-dials:ConnectWithSeedis a one-shot RPC (crates/node/src/rpc_impl.rs) and there is no persisted DHT peer state at all. The only persisted peer-shaped state is the onion entry-guard set and the measurement ledger, neither of which is used to reconnect.#763 adds a run-owned redial supervisor for the native daemon's configured seeds. That leaves two gaps:
The literature is explicit about which peers to remember. Maymounkov & Mazières, "Kademlia" (IPTPS 2002), §2.2 keep the oldest live contacts because "the longer a node has been up, the more likely it is to remain up another hour" (Fig. 1, measured on Gnutella). Stutzbach & Rejaie, "Understanding Churn in Peer-to-Peer Networks" (IMC 2006), §4.5 confirm on three systems that observed uptime predicts residual lifetime (Weibull sessions with k ≈ 0.34–0.38). Godfrey, Shenker & Stoica, "Minimizing Churn in Distributed Systems" (SIGCOMM 2006) show that selecting by longest observed uptime beats random selection, and that "preference-list" selection driven by another metric (here: ring position) systematically raises churn — which is why the cache must be age-ranked even though the ring cannot be. BitTorrent's Mainline DHT and libp2p's Kademlia both persist their routing tables across restarts for the same reason.
Proposal
(did, first_seen, last_seen, reachability hint)records for admitted peers, ranked by observed uptimelast_seen − first_seen, refreshed from the measurement ledger'sConnected/Disconnectedevents. Persist it throughKvStorageInterfacenext to the entry guards (crates/node/src/onion/entry_guard.rsis the pattern; browser localStorage is already used there).successor_head()isNone(or admitted routable peers fall below a floor) the stabilizer enters a re-join phase: dial cached peers in age order, then seeds, with exponential backoff and full jitter (2 s … 5 min), one handshake per target, cancelled on stop. This is the browser-capable counterpart of Add run-owned bootstrap reachability and automatic redial #763 and should share its retry state machine.mark_inboundliveness path already sees every authenticated payload, solast_seencosts nothing.admit_connected, so Chord membership proofs are unchanged.Acceptance
stop/listenand a provider rebuild; its size and TTL are bounded.Related
#763 (native seed redial), the adaptive successor-list issue (raising
rshrinks how often this path is needed), and the browser lifecycle issue (avisibilitychangeto visible should trigger this phase immediately).Issue family (browser-churn stability, 2026-09-14)
#773 churn simulator · #774 adaptive successor list and stabilization period · #775 age-ranked peer cache and re-join · #776 RTT-derived timeouts, ICE restart, lookup retry · #777 inbox replication and Leave · #778 stability-weighted storage · #779 browser lifecycle events. Finger convergence is #768 / #770; native seed redial is #763.