Passphrase-based peer rendezvous for iroh-gossip swarms over the public Mainline DHT. Two nodes that share a passphrase + application salt find each other and join the same gossip topic — no bootstrap server, no prior knowledge of peer addresses. The algorithm is a clock-free variant of HyParView-style partition healing on BEP 44 mutable items, with K-sharded slots, logical aging with random-tie eviction, vouching of up to V neighbors at age 1, and a two-layer AEAD record envelope. Full spec in PROTOCOL.md. Ships with: * 119 tests (unit / property / integration / doctest) all green * 100% mutation-testing catch rate (cargo-mutants) * cargo-fuzz harnesses for the parse surface * Kani proofs of no-UB / no-panic on crypto::epoch_of and wire::decode_entries across their full symbolic input spaces * Stateright distributed-system model: safety invariants S1 + S2 proved exhaustively for N ≤ 6, K ≤ 3; liveness witness under weak fairness * Miri green on the synchronous modules * cargo-deny + cargo-audit clean * criterion benches * Full-matrix CI (Linux + macOS + Windows × stable + MSRV 1.93.0) * Dedicated nightly lane for Miri / mutation / fuzz / Kani / Stateright Pinned iroh 0.98.x. License MIT. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.3 KiB
5.3 KiB
Architecture
A map of where each piece of iroh-gossip-rendezvous lives. For the
algorithm itself see PROTOCOL.md.
Data flow
flowchart TB
Caller["Rendezvous::join(passphrase, app_salt)"]
Builder[Builder]
Keys[keys: HKDF per-slot]
GossipGlue["gossip_glue<br/>(iroh Endpoint + gossip actor +<br/>event fan-out)"]
ProtocolLoops["protocol<br/>HealLoop + WriteLoop"]
Merge[merge]
Wire[wire]
Crypto[crypto<br/>seal / open]
DHT[("DHT backend<br/>(DhtSlots trait)")]
Mainline[dht::mainline]
InMemory[dht::memory]
Rendezvous[Rendezvous handle]
Caller --> Builder --> Keys
Builder --> GossipGlue
Builder --> ProtocolLoops
Builder --> Rendezvous
ProtocolLoops --> Merge
ProtocolLoops --> Wire
ProtocolLoops --> Crypto
ProtocolLoops --> DHT
DHT --> Mainline
DHT --> InMemory
Rendezvous -.holds.-> GossipGlue
Rendezvous -.holds.-> ProtocolLoops
Module responsibilities
| Module | Pure? | Async? | What it does |
|---|---|---|---|
keys |
✓ | ✗ | HKDF-SHA256 derivation of topic id + per-slot Ed25519 keypairs + wrapper master keys |
wire |
✓ | ✗ | Entry list (id, age) encode/decode, byte-budget checks |
crypto |
✓ | ✗ | Two-layer AEAD (ChaCha20-Poly1305) with per-epoch wrapper ratchet |
merge |
✓ | ✗ | Record merge: age-up, vouch, evict by max-age with random tie-break |
dht/mod |
✓ | ✓ | DhtSlots trait + types (SlotKey, SlotRecord, DhtError) |
dht/memory |
✓ | ✓ | In-memory DhtSlots — tests + simulator + test-support |
dht/mainline |
— | ✓ | Production DhtSlots over the public Mainline DHT (BEP 44) |
protocol |
— | ✓ | initial_join, spawn_loops (HealLoop + WriteLoop), ProtoState, GossipView trait |
gossip_glue |
— | ✓ | iroh Endpoint build, gossip subscription, Event fan-out via tokio::broadcast |
rendezvous |
— | ✓ | Public Rendezvous handle; owns the JoinSet of background tasks and the CancellationToken |
builder |
— | ✓ | Public Builder with parameter validation + defaults (§2 of PROTOCOL.md) |
state |
✓ | ✗ | Observable RendezvousState + DhtStatus enum |
error |
✓ | ✗ | thiserror Error enum |
sim_impl |
— | ✓ | Discrete-event simulator (feature-gated sim); re-exported as crate::sim |
"Pure" = no I/O, no tokio, Miri-compatible.
Lifetime invariants
Rendezvousis the keep-alive anchor. It owns theCancellationTokenand theJoinSetof all background tasks. Drop it, and the DHT maintenance loops cancel.- Any
GossipSenderorbroadcast::Receiver<Event>extracted from aRendezvousmust be used only while theRendezvousis alive. After drop, the internal event fan-out task terminates and new subscribers receiveRecvError::Closed. - The iroh
Endpointis closed duringRendezvous::shutdown. Callers who need the endpoint past that point should construct it themselves and inject it viaBuilder::endpoint.
Background tasks
Started by Builder::build, stored in two JoinSet<()>s inside
Rendezvous:
- Gossip (
gossip_glue::build):spawn_accept_loop— handles incoming iroh connections for the gossip ALPN.spawn_event_task— drains the iroh-gossipGossipReceiver, updates theRendezvousState.neighbor_count, and broadcasts everyEventto caller-side subscribers.
- Protocol (
protocol::spawn_loops):- HealLoop — every
T_h · jitter, reads = max(1, K − |N|)random slots and hand discovered peer IDs toGossipView::join_peers. - WriteLoop — first iteration publishes immediately (completes §5 Join
from the spec); thereafter sleeps
T_w · jitterand writes with probability1/(|N|+1).
- HealLoop — every
Both loops cancel on Rendezvous::shutdown or on drop.
DHT abstraction
#[async_trait]
trait DhtSlots: Send + Sync + 'static {
async fn read(&self, slot: SlotKey) -> Result<Option<SlotRecord>, DhtError>;
async fn write(&self, slot: SlotKey, record: SlotRecord) -> Result<(), DhtError>;
}
Two impls:
dht::mainline::MainlineDht— BEP 44 puts/gets over the public Mainline DHT via themainlinecrate. Re-signs viaMutableItem::newso signatures match the BEP 44 canonical form remote nodes verify against.dht::memory::InMemoryDht— process-localHashMap<SlotKey, SlotRecord>, signature-verifying, LWW at equal seq. Used by tests, simulator, and thetest-supportfeature'sBuilder::dht_backendinjection.
The trait is pub(crate) by default; the sim feature exposes it publicly
for third-party DHT backends.
Feature flags
See README for the user-facing table. Implementation notes:
simexposescrate::sim::*fromsim_impl; re-exportsInMemoryDht,DhtSlots,ProtoState, and merge/wire helpers.kaniadds#[kani::proof]functions tosrc/kani_proofs.rs(invisible to regular builds).miri-compatadds#[cfg_attr(miri, ignore)]on tests that touch tokio I/O or real DHT.mainline-liveenables live-network integration tests, run only in the nightly CI lane.test-support(impliessim) addsBuilder::dht_backend(Arc<dyn DhtSlots>)so integration tests can run multi-node scenarios againstInMemoryDhtwithout a real DHT.