Skip to content

Persist an age-ranked peer cache and re-join automatically when the successor list empties #775

Description

@zkjoie

Motivation

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

  1. 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).
  2. 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.
  3. 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.
  4. 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.
  • Retry cadence is jittered and cannot hot-loop (paused-time tests as in Add run-owned bootstrap reachability and automatic redial #763).

Related

#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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions