Files
iroh-gossip-rendezvous/ARCHITECTURE.md
T
swaitsandClaude Opus 4.7 7e39781318 Initial release: v0.1.0
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>
2026-04-22 22:52:46 -06:00

131 lines
5.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture
A map of where each piece of `iroh-gossip-rendezvous` lives. For the
algorithm itself see [`PROTOCOL.md`](PROTOCOL.md).
## Data flow
```mermaid
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
- `Rendezvous` is the **keep-alive anchor**. It owns the `CancellationToken`
and the `JoinSet` of all background tasks. Drop it, and the DHT
maintenance loops cancel.
- Any `GossipSender` or `broadcast::Receiver<Event>` extracted from a
`Rendezvous` must be used only while the `Rendezvous` is alive. After
drop, the internal event fan-out task terminates and new subscribers
receive `RecvError::Closed`.
- The iroh `Endpoint` is closed during `Rendezvous::shutdown`. Callers who
need the endpoint past that point should construct it themselves and
inject it via `Builder::endpoint`.
## Background tasks
Started by `Builder::build`, stored in two `JoinSet<()>`s inside
`Rendezvous`:
1. **Gossip** (`gossip_glue::build`):
- `spawn_accept_loop` — handles incoming iroh connections for the
gossip ALPN.
- `spawn_event_task` — drains the iroh-gossip `GossipReceiver`,
updates the `RendezvousState.neighbor_count`, and broadcasts every
`Event` to caller-side subscribers.
2. **Protocol** (`protocol::spawn_loops`):
- HealLoop — every `T_h · jitter`, read `s = max(1, K − |N|)` random
slots and hand discovered peer IDs to `GossipView::join_peers`.
- WriteLoop — first iteration publishes immediately (completes §5 Join
from the spec); thereafter sleeps `T_w · jitter` and writes with
probability `1/(|N|+1)`.
Both loops cancel on `Rendezvous::shutdown` or on drop.
## DHT abstraction
```rust
#[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 the [`mainline`] crate. Re-signs via `MutableItem::new` so
signatures match the BEP 44 canonical form remote nodes verify against.
- `dht::memory::InMemoryDht` — process-local `HashMap<SlotKey, SlotRecord>`,
signature-verifying, LWW at equal seq. Used by tests, simulator, and the
`test-support` feature's `Builder::dht_backend` injection.
The trait is `pub(crate)` by default; the `sim` feature exposes it publicly
for third-party DHT backends.
[`mainline`]: https://docs.rs/mainline
## Feature flags
See [README](README.md#features) for the user-facing table. Implementation
notes:
- `sim` exposes `crate::sim::*` from `sim_impl`; re-exports `InMemoryDht`,
`DhtSlots`, `ProtoState`, and merge/wire helpers.
- `kani` adds `#[kani::proof]` functions to `src/kani_proofs.rs` (invisible
to regular builds).
- `miri-compat` adds `#[cfg_attr(miri, ignore)]` on tests that touch tokio
I/O or real DHT.
- `mainline-live` enables live-network integration tests, run only in the
nightly CI lane.
- `test-support` (implies `sim`) adds `Builder::dht_backend(Arc<dyn DhtSlots>)`
so integration tests can run multi-node scenarios against `InMemoryDht`
without a real DHT.