docs(research): add iroh suite deep-dive references for iroh, irpc, iroh-blobs, iroh-gossip, iroh-live, and iroh-docs

This commit is contained in:
2026-06-10 12:34:30 +00:00
parent 6e71d1f306
commit 5bb5e1064c
49 changed files with 9923 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
# iroh-gossip: Utility Data Structures & Wire Format
## IndexSet (`proto::util::IndexSet`)
A wrapper around `indexmap::IndexSet` that provides random selection capabilities needed by HyParView:
```rust
pub(crate) struct IndexSet<T> {
inner: indexmap::IndexSet<T>,
}
```
### Key Operations
| Method | Purpose |
|--------|---------|
| `insert(value)` | Add element (returns false if already present) |
| `remove(value)` | Remove by value (swap-remove, O(1)) |
| `remove_index(index)` | Remove by index (swap-remove) |
| `remove_random(rng)` | Remove a random element |
| `pick_random(rng)` | Get reference to random element |
| `pick_random_without(exclude, rng)` | Random element excluding certain elements |
| `pick_random_index(rng)` | Random index |
| `shuffled(rng)` | All elements in random order |
| `shuffled_and_capped(len, rng)` | First `len` elements after shuffle |
| `shuffled_without(exclude, rng)` | Random order excluding certain elements |
| `shuffled_without_and_capped(exclude, len, rng)` | Capped shuffle excluding elements |
| `iter_without(value)` | Iterator skipping a specific element |
These operations are critical for HyParView's random walks, shuffle exchanges, and passive view management.
## TimerMap (`proto::util::TimerMap`)
A priority queue of timer entries sorted by `Instant`, with stable ordering via a sequence counter:
```rust
pub struct TimerMap<T> {
heap: BinaryHeap<TimerMapEntry<T>>,
seq: u64,
}
```
Used by the protocol state machine for scheduling future events (shuffles, graft timeouts, cache eviction). The networking layer wraps this in an async-friendly `Timers` type that can `wait_next()`.
### Key Operations
| Method | Purpose |
|--------|---------|
| `insert(instant, item)` | Schedule a timer |
| `pop_before(limit)` | Pop the earliest entry if it's before `limit` |
| `drain_until(from)` | Drain all entries up to a time |
| `first()` | Get reference to earliest entry |
## TimeBoundCache (`proto::util::TimeBoundCache`)
A `HashMap` where entries expire after a specified `Instant`:
```rust
pub struct TimeBoundCache<K, V> {
map: HashMap<K, (Instant, V)>,
expiry: TimerMap<K>,
}
```
Used by PlumTree for:
- `received_messages: TimeBoundCache<MessageId, ()>` — deduplication
- `cache: TimeBoundCache<MessageId, Gossip>` — message payload storage for Graft replies
### Key Operations
| Method | Purpose |
|--------|---------|
| `insert(key, value, expires)` | Insert with expiration |
| `contains_key(key)` | Check existence |
| `get(key)` | Get value |
| `expires(key)` | Get expiration time |
| `expire_until(instant)` | Remove all expired entries, returns count |
| `len()` / `is_empty()` | Size queries |
The `expire_until` method correctly handles re-insertions: if a key is re-inserted with a later expiration time after being added to the expiry queue, the old expiry entry is ignored (not removed from the map).
## Wire Format
### Frame Encoding
Messages are encoded using `postcard` (a `no_std`-friendly, `serde`-compatible format) and sent as length-prefixed frames:
```
┌──────────────┬──────────────┬─────────────────┐
│ Length (u32) │ TopicHeader │ Message Payload │
│ big-endian │ postcard │ postcard │
└──────────────┴──────────────┴─────────────────┘
```
### Stream Protocol
Each QUIC unidirectional stream is dedicated to a single topic. The stream begins with a `StreamHeader`:
```rust
pub(crate) struct StreamHeader {
pub(crate) topic_id: TopicId,
}
```
All subsequent frames on that stream carry messages for that topic. When a `Disconnect` message is sent, the stream is closed (via `finish()`).
### Message Types on Wire
```rust
pub enum Message<PI> {
Swarm(hyparview::Message<PI>), // Membership messages
Gossip(plumtree::Message), // Broadcast messages
}
```
Where `PI` is `PublicKey` (32-byte ed25519 public key) in the networking layer.
The `MessageKind` classification is used for metrics:
| Kind | Message Types |
|------|--------------|
| `Data` | `Gossip` messages (actual content) |
| `Control` | All Swarm messages, plus `Prune`, `Graft`, `IHave` |
### Message Size Limits
- Default max message size: 4096 bytes (minimum: 512)
- The header size is computed at compile time via `postcard::experimental::serialized_size`
- Actual payload capacity = `max_message_size - header_size`
The `SendLoop` checks message size before writing and returns `WriteError::TooLarge` if exceeded.
## PeerData & Address Propagation
The `PeerData` type is an opaque `Bytes` wrapper used in HyParView messages. In the `net` layer, it carries addressing information:
```rust
struct AddrInfo {
relay_url: Option<RelayUrl>,
direct_addresses: BTreeSet<SocketAddr>,
}
```
This is serialized with `postcard` and passed as `PeerData` in `Join`, `ForwardJoin`, and `Neighbor` messages. When received, the `AddrInfo` is decoded and fed into `GossipAddressLookup`, which implements iroh's `AddressLookup` trait, allowing gossip-discovered addresses to be used for future connections.
## GossipAddressLookup
```rust
pub(crate) struct GossipAddressLookup {
endpoints: NodeMap, // Arc<RwLock<BTreeMap<EndpointId, StoredEndpointInfo>>>
_task_handle: Arc<AbortOnDropHandle<()>>, // Background eviction task
}
```
Key behaviors:
- **Merging**: When adding addresses for an already-known endpoint, new addresses are merged (union of direct addresses, relay URL is overwritten)
- **Expiration**: Entries expire after 5 minutes, with eviction checks every 30 seconds
- **Integration**: Implements `iroh::address_lookup::AddressLookup`, returning data with provenance "gossip"
## Dialer
```rust
struct Dialer {
endpoint: Endpoint,
pending: JoinSet<(EndpointId, Option<Result<Connection, ConnectError>>)>,
pending_dials: HashMap<EndpointId, CancellationToken>,
}
```
The `Dialer` manages outgoing connection attempts:
- Queues a dial via `queue_dial(endpoint_id, alpn)`
- Checks for pending dials to avoid duplicate connections
- Supports cancellation of in-progress dials
- Returns completed connections via `next_conn()`
When a dial succeeds, the connection is passed to `handle_connection()`. When a dial fails and the peer is not already active, a `PeerDisconnected` event is injected into the protocol state.