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_keyis 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(notstd::sync::Mutex) is used becausehandle.abort()and the newtokio::spawnhappen inside anasynccontext. Using a blockingstd::sync::Mutexinside an async context risks deadlocking the executor.
Spawned Tasks Summary
| Task | Started In | Purpose |
|---|---|---|
network_loop.run() | main | Core libp2p event loop |
gossip::start_gossip_listener() | main | Governance gossip → disk |
proxy::handle_incoming_proxy_requests() | main | P2P → local HTTP backend |
heartbeat::start_dynamic_routing_publisher() | main | Publishes HostRoutingRecord every 30s |
heartbeat::start_drand_heartbeat() | main | Monitors 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 to0.0.0.0publicly, identical tokinetic-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.