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

Building a Bootstrap Node

This tutorial walks through the bootstrap example — a ready-to-use bootstrap node for any mosaik network.

What Is a Bootstrap Node?

A bootstrap node is the first peer that other nodes connect to when joining a network. It serves as the initial discovery point — once a new node connects to a bootstrap peer, the gossip protocol takes over and the joining node learns about all other peers.

Bootstrap nodes are typically:

  • Long-lived — they run continuously
  • Stable identity — they use a fixed secret key so their address doesn’t change across restarts
  • Well-known — their address is configured as a bootstrap peer by other nodes

A bootstrap node doesn’t need any special code — it’s just a regular mosaik node that stays online. The bootstrap example can be used in production as a universal bootstrap node for any mosaik network. The example adds CLI configuration with clap.

Project Setup

The bootstrap example is a single file at examples/bootstrap.rs. It uses these dependencies:

[dependencies]
mosaik = "0.2"
tokio = { version = "1", features = ["full"] }
clap = { version = "4", features = ["derive"] }
tracing = "0.1"
tracing-subscriber = "0.3"
anyhow = "1"

The CLI Interface

The example uses clap derive to define command-line options:

use clap::{ArgAction, Parser};
use mosaik::*;

#[derive(Debug, Parser)]
struct Opts {
    /// The secret key for stable identity across restarts
    #[clap(short, long, env = "MOSAIK_BOOTSTRAP_SECRET")]
    secret: Option<SecretKey>,

    /// The network ID (hex string or seed that gets hashed)
    #[clap(short, long, env = "MOSAIK_BOOTSTRAP_NETWORK_ID")]
    network_id: Option<NetworkId>,

    /// Other bootstrap nodes to connect to on startup
    #[clap(short, long, env = "MOSAIK_BOOTSTRAP_PEERS")]
    peers: Vec<PeerId>,

    /// Tags to advertise (default: "bootstrap")
    #[clap(short, long, default_value = "bootstrap",
           env = "MOSAIK_BOOTSTRAP_TAGS")]
    tags: Vec<Tag>,

    /// Disable relay servers (node must be directly reachable)
    #[clap(long, default_value_t = false)]
    no_relay: bool,

    /// Verbose output (-v debug, -vv trace)
    #[clap(short, action = ArgAction::Count)]
    verbose: u8,

    /// Suppress all output
    #[clap(short, long)]
    quiet: bool,
}

Every option also supports environment variables (MOSAIK_BOOTSTRAP_*), making it easy to configure in containers or systemd services.

Secret Key Handling

The secret key determines the node’s PeerId. For a bootstrap node, a stable identity is essential so that other nodes can reliably find it:

fn parse_secret_key(s: &str) -> Result<SecretKey, Infallible> {
    let bytes = Digest::from(s);
    Ok(SecretKey::from_bytes(bytes.as_bytes()))
}

This parser accepts two formats:

  • 64-character hex string — used directly as the secret key bytes
  • Any other string — treated as a seed, hashed with blake3 into a deterministic key

So --secret=my-bootstrap-1 always produces the same key, making deployment reproducible.

Building the Network

The main function assembles the Network using the builder:

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let opts = Opts::parse();

    let secret = opts.secret.unwrap_or_else(|| {
        tracing::warn!("No secret key provided, generating random key");
        SecretKey::generate(&mut rand::rng())
    });

    let network_id = opts.network_id.unwrap_or_else(|| {
        tracing::warn!("No network id provided, generating random network id");
        NetworkId::random()
    });

    let mut builder = Network::builder(network_id)
        .with_secret_key(secret)
        .with_discovery(
            discovery::Config::builder()
                .with_tags(opts.tags.clone())
                .with_bootstrap(opts.peers.clone()),
        );

    if opts.no_relay {
        builder = builder.with_relay_mode(iroh::RelayMode::Disabled);
    }

    let network = builder.build().await?;

    tracing::info!("Bootstrap node started");
    tracing::info!("Public Id: {}", network.local().id());
    tracing::info!("Network Id: {:?}", network.network_id());

    // Stay alive forever
    core::future::pending::<()>().await;
    Ok(())
}

Key points:

  1. with_secret_key() — sets the stable identity
  2. with_discovery() — configures tags and initial peers to dial
  3. with_relay_mode(Disabled) — optional, for nodes with direct connectivity
  4. core::future::pending() — keeps the process alive (the node runs in background tasks)

Running It

# Basic usage with a seed-based secret
cargo run --example bootstrap -- \
    --network-id=my-network \
    --secret=my-bootstrap-secret

# With environment variables
MOSAIK_BOOTSTRAP_SECRET=my-secret \
MOSAIK_BOOTSTRAP_NETWORK_ID=my-network \
cargo run --example bootstrap

# Multiple bootstrap nodes that know each other
cargo run --example bootstrap -- \
    --network-id=my-network \
    --secret=node-1 \
    --peers=<peer-id-of-node-2>

Using the Bootstrap Node

Other nodes reference the bootstrap node’s address when joining the network:

let network = Network::builder(network_id)
    .with_discovery(
        discovery::Config::builder()
            .with_bootstrap(bootstrap_addr)
    )
    .build()
    .await?;

The joining node connects to the bootstrap peer, performs a full catalog sync, and then discovers all other nodes through gossip. From that point on, the joining node is a full participant — it doesn’t need the bootstrap node for ongoing operation.

Key Takeaways

  1. Bootstrap nodes are just regular nodes — no special server code needed
  2. Stable identity via secret key — ensures the address doesn’t change across restarts
  3. Tags for discoverability — the "bootstrap" tag lets other nodes identify bootstrap peers
  4. Minimal configuration — a secret key and network ID are all that’s required