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

Boot Sequence: Dual Identity and Hot-Swap Wiring

File: kinetic-host/src/main.rs Crate: kinetic-host | Stage: 13


What’s Unique Here (Skip What’s Already Documented)

Service management, governance key validation, Sled storage init, Drand client init — all identical to kinetic-node (docs/learn/node/02_main.md). Skip those. What’s unique:


Dual-Key Identity — The Core Difference

Important

The infrastructure node has one identity (static). The host has two.

Step 1: Load the static host key

-> See: kinetic-host/src/main.rs — Lines 140–143

#![allow(unused)]
fn main() {
let key_path = get_base_dir().join("host.key");
let host_key = identity::load_or_generate_host_key(&key_path);
let host_peer_id = PeerId::from_public_key(&host_key.public());
}

host.key is the permanent identity. It is registered in the DNS zone record by the domain owner (host_id field in HostRoutingRecord). It never changes. Clients look up a .kin domain, find the PeerId in the zone, then use that PeerId to look up the current HostRoutingRecord to find the actual live address. This indirection is what allows the ephemeral key to rotate without breaking connectivity.


Step 2: Mine the ephemeral PoW keypair

-> See: kinetic-host/src/main.rs — Lines 145–159

#![allow(unused)]
fn main() {
let local_key = tokio::task::spawn_blocking(move || {
    kinetic_network::pow::mine_sybil_keypair(initial_drand_kyn, POW_DIFFICULTY_BITS)
}).await?;
}

mine_sybil_keypair() is CPU-intensive (mining PoW). It is run in spawn_blocking to avoid blocking the Tokio async executor. The resulting keypair is the active libp2p identity used in the NetworkEventLoop. Its Peer ID will change every Drand epoch.

Note

Unlike the node, the host’s local_key is always freshly mined at startup — never loaded from disk. This is by design: the PoW keypair is only valid for the current epoch anyway.


P2P Port from Environment Variable

-> See: kinetic-host/src/main.rs — Lines 161–164

#![allow(unused)]
fn main() {
let p2p_port = std::env::var(ENV_HOST_P2P_PORT)
    .unwrap_or_else(|_| config.network.host_port.to_string())
    .parse::<u16>()
    .unwrap_or(config.network.host_port);
}

The host uses ENV_HOST_P2P_PORT env var as a first override for its port, then falls back to config. This allows running multiple hosts on the same machine (for multi-domain hosting) by setting different port env vars without editing the config file.


Incoming Proxy Channel

-> See: kinetic-host/src/main.rs — Lines 217, 249–254

#![allow(unused)]
fn main() {
let (incoming_tx, incoming_rx) = tokio::sync::mpsc::channel(32);
}

An mpsc channel with capacity 32 sits between the network event loop and the proxy handler. When the network loop receives a ProxyRequest from a remote client (via libp2p RequestResponse), it sends the request + response channel into incoming_tx. The proxy handler receives from incoming_rx, forwards to the local backend, and sends the response back over the libp2p channel.

The capacity of 32 is intentional — it provides backpressure. If the local backend is slow and 32 requests pile up, the network loop blocks sending into the channel, applying TCP backpressure upstream to the requesting P2P peer.


Backend Configuration From Environment

-> See: kinetic-host/src/main.rs — Lines 242–248

#![allow(unused)]
fn main() {
let backend_port = std::env::var(ENV_HOST_BACKEND_PORT)
    .unwrap_or_else(|_| "80".to_string())
    .parse::<u16>()
    .unwrap_or(80);
let backend_host = std::env::var(ENV_HOST_BACKEND_HOST)
    .unwrap_or_else(|_| config.daemon.bind_ip.clone());
}

Two env vars control where the proxy forwards traffic:

  • ENV_HOST_BACKEND_PORT — the port of the local web server (default: 80).
  • ENV_HOST_BACKEND_HOST — the IP of the local web server (default: configured bind_ip).

This means the “backend” can be on a different machine in the same LAN, not just localhost.


Hot-Swap Wiring — The Arc<Mutex<JoinHandle>>

-> See: kinetic-host/src/main.rs — Lines 232–234

#![allow(unused)]
fn main() {
let network_loop_handle = Arc::new(tokio::sync::Mutex::new(tokio::spawn(async move {
    network_loop.run().await;
})));
}

The JoinHandle of the network loop is wrapped in Arc<tokio::sync::Mutex<...>> and passed to the start_drand_heartbeat task. When the epoch expires, the heartbeat acquires the mutex, calls handle.abort() to kill the old loop, mines a new keypair, and replaces the handle with a new spawned loop. The Arc allows the handle to be shared between main and the heartbeat task.

Warning

The tokio::sync::Mutex (not std::sync::Mutex) is used because handle.abort() and the new tokio::spawn happen inside an async context. Using a blocking std::sync::Mutex inside an async context risks deadlocking the executor.


Spawned Tasks Summary

TaskStarted InPurpose
network_loop.run()mainCore libp2p event loop
gossip::start_gossip_listener()mainGovernance gossip → disk
proxy::handle_incoming_proxy_requests()mainP2P → local HTTP backend
heartbeat::start_dynamic_routing_publisher()mainPublishes HostRoutingRecord every 30s
heartbeat::start_drand_heartbeat()mainMonitors epoch, hot-swaps PoW key

Cross-Crate Connections

  • kinetic_network::pow::mine_sybil_keypair(kyn, bits): CPU-intensive PoW mining that produces a keypair whose Peer ID satisfies the Sybil resistance constraint for the given Drand epoch.
  • kinetic_network::NetworkMode::FullNode: Binds to 0.0.0.0 publicly, identical to kinetic-node.
  • kinetic_core::constants::ENV_HOST_P2P_PORT: Env var key for the P2P port override.
  • kinetic_core::constants::ENV_HOST_BACKEND_PORT / ENV_HOST_BACKEND_HOST: Env vars for the reverse proxy backend.