Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Architecture

This chapter describes how mosaik’s subsystems fit together, how protocols are multiplexed, and how the lifecycle of a node is managed.

Subsystem Composition

A Network is the top-level entry point. When built, it creates and composes four subsystems:

Network::builder(network_id)
    │
    ├── LocalNode          (QUIC endpoint, identity, lifecycle)
    ├── Discovery          (gossip announcements, catalog sync)
    ├── Streams            (typed pub/sub channels)
    └── Groups             (Raft consensus groups)
            └── Collections  (replicated Map, Vec, Set, Register, Once, PriorityQueue)

Each subsystem is created during Network::builder().build() and installed as a protocol handler on the iroh Router. The subsystems are then accessible via accessor methods:

let network = Network::builder(network_id).build().await?;

let local     = network.local();       // LocalNode
let discovery = network.discovery();   // Discovery
let streams   = network.streams();     // Streams
let groups    = network.groups();      // Groups

All handles are cheap to clone (they wrap Arc internally).

ALPN-Based Protocol Multiplexing

Mosaik multiplexes multiple protocols over a single QUIC endpoint using ALPN (Application-Layer Protocol Negotiation). Each subsystem registers its own ALPN identifier:

SubsystemALPNPurpose
Discovery (announce)/mosaik/announceReal-time gossip broadcasts
Discovery (sync)/mosaik/catalog-syncFull catalog exchange
Streams/mosaik/streams/1.0Pub/sub data channels
Groups/mosaik/groups/1Raft consensus, bonds, state sync

When a connection arrives, the iroh router inspects the ALPN and dispatches to the correct subsystem handler. This means all subsystems share the same QUIC endpoint and port.

Subsystems implement the ProtocolProvider trait to install their handlers:

// Internal trait — subsystems implement this
trait ProtocolProvider {
    fn install(self, router: &mut Router);
}

The Protocol trait defines the ALPN for typed links:

pub trait Protocol {
    const ALPN: &'static [u8];
}

Builder Pattern

Network uses a builder pattern for configuration:

let network = Network::builder(network_id)
    .with_secret_key(secret_key)           // Stable identity
    .with_relay_mode(RelayMode::Disabled)  // No relay servers
    .with_mdns_discovery(true)             // Local network discovery
    .with_discovery(
        discovery::Config::builder()
            .with_bootstrap(bootstrap_addr)
            .with_tags("my-role")
    )
    .with_streams(
        streams::Config::builder()
            .with_backoff(ExponentialBackoff::default())
    )
    .with_groups(
        groups::Config::builder()
    )
    .build()
    .await?;

For quick prototyping, Network::new(network_id) uses all defaults.

Lifecycle & Shutdown

Mosaik uses tokio_util::sync::CancellationToken for structured lifecycle management. The token lives in LocalNode and propagates shutdown to all subsystems:

Network::drop()
    │
    ├── cancels LocalNode's CancellationToken
    │       │
    │       ├── Discovery workers shut down
    │       ├── Stream producer/consumer workers shut down
    │       ├── Group bond workers + Raft shut down
    │       └── iroh Router shuts down
    │
    └── all resources released

When a Network is dropped, the cancellation token is triggered, and all background tasks gracefully terminate. Each subsystem’s internal tasks are select-looped against the cancellation token, ensuring no orphaned tasks.

Internal Communication Patterns

Mosaik uses several recurring patterns internally:

Watch Channels

Status changes are broadcast via tokio::sync::watch channels. This enables the when() API:

// Wait for a stream producer to come online
producer.when().online().await;

// Wait for a Raft group leader to be elected
group.when().leader_elected().await;

// Wait for a collection to reach a specific version
collection.when().reaches(version).await;

Arc<Inner> Pattern

All public handles (Network, Discovery, Streams, Groups, Group, Producer, Consumer, Map, Vec, Register, Once, etc.) are cheap to clone. They wrap an Arc<Inner> containing the actual state, making them safe to share across tasks.

Task-Per-Connection

Each consumer subscription and each producer-subscriber pair runs in its own tokio task. This avoids head-of-line blocking — a slow consumer doesn’t affect other consumers, and a slow subscriber doesn’t block the producer.