144 lines
6.1 KiB
Markdown
144 lines
6.1 KiB
Markdown
# ADR-0009: chronogrid_net — Transport & Discovery Layer (Task 2.3)
|
|
|
|
**Status:** Accepted
|
|
**Date:** 2026-08-22
|
|
**Task:** 2.3 — Implement chronogrid_net: Transport & Discovery Layer
|
|
**Author:** Autonomous Agent
|
|
|
|
---
|
|
|
|
## Context
|
|
|
|
Task 2.3 required implementing the libp2p transport stack and peer discovery layer
|
|
inside `crates/chronogrid_net/`, building on the `NodeIdentity` and `SwarmKey`
|
|
primitives established in Task 2.2 (ADR-0008).
|
|
|
|
The implementation must satisfy:
|
|
- QUIC over UDP as the primary transport (low-latency, multiplexed).
|
|
- TCP + Noise XK as a fallback transport for UDP-blocked environments.
|
|
- SwarmKey-based cluster authentication to prevent unauthorized nodes from joining.
|
|
- Zero-configuration LAN peer discovery via mDNS.
|
|
- WAN peer discovery via Kademlia DHT with bootstrap node support.
|
|
- A unified `PeerEvent` enum consumed by `SwarmManager` (Task 2.5).
|
|
|
|
---
|
|
|
|
## Decision
|
|
|
|
### New modules added to `crates/chronogrid_net/`
|
|
|
|
| Module | Responsibility |
|
|
|-----------------|-----------------------------------------------------------------------|
|
|
| `peer_event` | `PeerEvent` enum — unified peer lifecycle events for all consumers |
|
|
| `transport` | `TransportConfig`, `build_transport()`, `dalek_to_libp2p_keypair()` |
|
|
| `discovery` | `MdnsDiscovery` (LAN) and `KademliaDht` (WAN) discovery wrappers |
|
|
|
|
### Transport Architecture
|
|
|
|
**Primary transport (QUIC):** Configured via `SwarmBuilder::with_quic()` in
|
|
`SwarmManager` (Task 2.5). QUIC provides built-in TLS 1.3-equivalent security,
|
|
stream multiplexing, and connection migration — no separate Noise layer needed.
|
|
|
|
**Fallback transport (TCP + Noise + Yamux):** `build_transport()` constructs a
|
|
`Boxed<(PeerId, StreamMuxerBox)>` using:
|
|
```
|
|
TCP (tokio) → Noise XX (Ed25519) → Yamux multiplexer
|
|
```
|
|
The Noise XX pattern is used for the TCP fallback (not XK) because XK requires
|
|
the initiator to know the responder's static key in advance, which is not
|
|
guaranteed in a dynamic peer mesh. XX provides mutual authentication with
|
|
forward secrecy and is the libp2p standard.
|
|
|
|
**SwarmKey authentication:** The 256-bit pre-shared `SwarmKey` is enforced at
|
|
the application layer via the `identify` protocol. The key fingerprint
|
|
(`SHA-256(key)[0..8]` as 16 hex chars) is embedded in the `UserAgent` string
|
|
exchanged during identification:
|
|
```
|
|
chronogrid/<version>/swarm:<fingerprint>
|
|
```
|
|
Peers with a mismatched fingerprint are disconnected by `SwarmManager` after
|
|
the identify handshake completes. This approach is compatible with libp2p 0.54's
|
|
`SwarmBuilder` API, which does not expose a raw private-network transport layer.
|
|
|
|
**Keypair conversion:** `dalek_to_libp2p_keypair()` extracts the 32-byte seed
|
|
from the `ed25519-dalek` `SigningKey` and re-imports it into `libp2p::identity::ed25519::Keypair`.
|
|
Both libraries use the same Ed25519 curve parameters, ensuring the derived
|
|
`PeerId` is stable and consistent with the `NodeId` (SHA-256 of the public key).
|
|
|
|
### Discovery Architecture
|
|
|
|
**`MdnsDiscovery`** wraps `libp2p::mdns::tokio::Behaviour` with:
|
|
- A 5-minute TTL for peer advertisements.
|
|
- A 5-second re-query interval.
|
|
- IPv4-only (IPv6 disabled for simplicity; can be enabled in Task 2.5 config).
|
|
- Deduplication: `PeerDiscovered` is only emitted for genuinely new peers.
|
|
- An `active_peers: HashSet<PeerId>` registry tracking non-expired LAN peers.
|
|
|
|
**`KademliaDht`** wraps `libp2p::kad::Behaviour<MemoryStore>` with:
|
|
- An in-memory routing store (sufficient for small personal device clusters).
|
|
- 60-second replication interval and 5-minute publication interval.
|
|
- `add_bootstrap_peer()` / `bootstrap()` for initial DHT seeding.
|
|
- `RoutingUpdated` events (new peers) → `PeerDiscovered`.
|
|
- `UnroutablePeer` events → `PeerExpired`.
|
|
|
|
### PeerEvent enum
|
|
|
|
```rust
|
|
pub enum PeerEvent {
|
|
PeerDiscovered { peer_id: PeerId, addrs: Vec<Multiaddr> },
|
|
PeerExpired { peer_id: PeerId },
|
|
PeerConnected { peer_id: PeerId },
|
|
PeerDisconnected { peer_id: PeerId },
|
|
}
|
|
```
|
|
|
|
`PeerConnected` and `PeerDisconnected` are emitted by `SwarmManager` (Task 2.5)
|
|
from the libp2p `Swarm` connection lifecycle events. This module defines the
|
|
enum; Task 2.5 completes the emission.
|
|
|
|
### libp2p feature flags added
|
|
|
|
```toml
|
|
libp2p = { version = "0.54", features = [
|
|
"noise", "quic", "tcp", "yamux",
|
|
"mdns", "gossipsub", "kad", "identify", "relay",
|
|
"tokio", "macros",
|
|
] }
|
|
```
|
|
|
|
`tcp` and `yamux` were not included in the initial Task 2.2 dependency
|
|
declaration and were added during Task 2.3 implementation.
|
|
|
|
---
|
|
|
|
## Test Coverage
|
|
|
|
65 unit tests pass across all modules:
|
|
|
|
| Module | Tests | Coverage highlights |
|
|
|---------------|-------|------------------------------------------------------------------|
|
|
| `peer_event` | 10 | All 4 variants: accessors, display, clone, is_discovery/connected |
|
|
| `transport` | 12 | Config, user-agent, fingerprint extraction, keypair conversion, transport construction |
|
|
| `discovery` | 13 | mDNS: discovered/expired/dedup/multi-peer; Kademlia: bootstrap, routing, unroutable |
|
|
| `identity` | 9 | (from Task 2.2, unchanged) |
|
|
| `swarm_key` | 16 | (from Task 2.2, unchanged) |
|
|
|
|
**Note on Kademlia `RoutingUpdated` tests:** `KadEvent::RoutingUpdated` uses
|
|
`libp2p::kad::Addresses` and `libp2p::kad::Distance` which are not constructable
|
|
from outside the `libp2p-kad` crate (private internal types). The routing-table
|
|
tracking logic is tested via a `#[cfg(test)]` helper `insert_peer_for_test()`.
|
|
Full end-to-end `RoutingUpdated` coverage will be provided by the integration
|
|
test in Task 2.5 where a real swarm runs.
|
|
|
|
---
|
|
|
|
## Consequences
|
|
|
|
- `SwarmManager` (Task 2.5) can now import `MdnsDiscovery`, `KademliaDht`,
|
|
`TransportConfig`, `build_transport`, and `PeerEvent` directly.
|
|
- The `identify` protocol must be included in the Task 2.5 composite behaviour
|
|
to enable SwarmKey fingerprint validation.
|
|
- The `PeerConnected` / `PeerDisconnected` variants of `PeerEvent` are defined
|
|
but not yet emitted — Task 2.5 completes this.
|
|
- No breaking changes to existing `identity` or `swarm_key` modules.
|