Boot Sequence and Runtime Loop
File: kinetic-node/src/main.rs
Crate: kinetic-node | Stage: 12
What’s Unique Here (vs kinetic-daemon)
The service management (install/uninstall/start/stop via <dyn ServiceManager>::native()) is identical to the daemon — skip that, it’s documented in docs/learn/daemon/02_main_1.md. Focus on what’s different.
1. Governance Key Validation — Hard Boot Gate
#![allow(unused)]
fn main() {
kinetic_core::governance::logic::validate_keys_initialized()
}
-> See: kinetic-node/src/main.rs — Lines 150–157
The very first thing run_node() does — before storage, before networking — is:
#![allow(unused)]
fn main() {
kinetic_core::governance::logic::validate_keys_initialized()
}
If this returns an error (meaning production governance keys are still at their placeholder values), the node prints a fatal error and calls std::process::exit(1).
Warning
The daemon does not have this check. Infrastructure nodes are held to a higher standard because they are authoritative DHT participants. Running a bootstrap node with placeholder governance keys would corrupt governance state for any peer that bootstraps through it.
2. Static Peer Identity (The Core Difference)
#![allow(unused)]
fn main() {
let key_path = kinetic_core::config::get_base_dir().join("node.key");
let local_key = identity::load_or_generate_key(&key_path);
let local_peer_id = libp2p::PeerId::from_public_key(&local_key.public());
}
-> See: kinetic-node/src/main.rs — Lines 199–201
#![allow(unused)]
fn main() {
let key_path = kinetic_core::config::get_base_dir().join("node.key");
let local_key = identity::load_or_generate_key(&key_path);
let local_peer_id = libp2p::PeerId::from_public_key(&local_key.public());
}
The daemon generates a fresh keypair on every boot. The node loads from disk. This is the defining difference: a node’s Peer ID must be stable across restarts because other peers hardcode bootstrap node addresses in their configs. If the Peer ID changes, all configured peers fail to connect.
3. NetworkMode::FullNode — Publicly Addressable
#![allow(unused)]
fn main() {
NetworkMode::FullNode
0.0.0.0
}
-> See: kinetic-node/src/main.rs — Lines 209–252
The network config binds to 0.0.0.0 (all network interfaces) on both TCP and QUIC. The daemon also binds to all interfaces (0.0.0.0) when not in LightNode mode.
Key differences from the daemon’s network config:
| Flag | Value |
|---|---|
enable_mdns: false | — Infrastructure nodes don’t use mDNS (local discovery). They communicate via explicit bootstrap addresses. |
external_address | — Can be set from config to announce the node’s public IP to the DHT, so other peers can reach it through NAT. |
max_reveals_per_hour: 100 | — Allows more DHT writes than a personal node. |
disable_pow: false | — PoW is always enforced (no test shortcuts in production infrastructure). |
4. Governance State Loading
#![allow(unused)]
fn main() {
ENV_GOVERNANCE_PATH
<base_dir>/governance.key
}
-> See: kinetic-node/src/main.rs — Lines 257–266
The node reads the governance state path from an environment variable (ENV_GOVERNANCE_PATH) or defaults to <base_dir>/governance.key. It then acquires the global governance mutex and loads the state from disk.
Note
This is done before any networking starts so the node has a correct governance baseline before it starts accepting P2P gossip that might update it.
5. Drand Heartbeat — P2P Mode vs HTTP Mode
#![allow(unused)]
fn main() {
config.drand.p2p_only = true
should_fetch_http = true
}
-> See: kinetic-node/src/main.rs — Lines 326–378
The Drand heartbeat runs every 3 seconds (Quicknet produces a beacon every 3 seconds).
The p2p_only flag is what’s unique here:
If config.drand.p2p_only = true:
- The node does NOT fetch Drand via HTTP on every tick.
- Instead, it tracks the last cached kyn and estimates the expected current kyn from the genesis timestamp.
- If the estimated current kyn is more than 5 ahead of the cached kyn, it falls back to HTTP (
should_fetch_http = true). This is the “Drand P2P fallback” — the node prefers to receive Drand updates from its peers via gossip, but won’t fall hopelessly behind if the gossip network is lagged.
If p2p_only = false (default): fetches via HTTP every 3 seconds and broadcasts the result to P2P peers so they can stay in sync without individually hammering the Drand HTTP endpoints.
The daemon does not have p2p_only mode — it always fetches from Drand HTTP.
6. Gossip Loop — Governance + Drand
#![allow(unused)]
fn main() {
GOSSIP_TOPIC_GOVERNANCE
gossip::handle_kinetic_governance_gossip()
GOSSIP_TOPIC_DRAND
RawKyn
drand_kyn_tx
}
-> See: kinetic-node/src/main.rs — Lines 292–323
The gossip receiver loop handles two topics:
GOSSIP_TOPIC_GOVERNANCE→ callsgossip::handle_kinetic_governance_gossip(), which validates the message and writes effects to Sled.GOSSIP_TOPIC_DRAND→ deserializes theRawKyn, verifies it, and if it’s fresher than the cached value, updates thedrand_kyn_txwatch channel. The daemon does this too, but here there is no UI to notify — it purely updates the internal state.
Note
RecvError::Laggedis explicitly handled withcontinue. This happens when the gossip broadcast channel fills up (more than 100 messages queued). The node explicitly drops lagged messages rather than crashing — under high network load, it is acceptable to miss some intermediate states as long as the final state converges.
7. Health-check API — Minimal
#![allow(unused)]
fn main() {
axum::serve().with_graceful_shutdown(kinetic_core::shutdown::shutdown_signal())
}
-> See: kinetic-node/src/main.rs — Lines 381–396
Axum router at 127.0.0.1:16003 (or the configured bind IP). Only two routes:
GET /health→ returns"OK"GET /peer_id→ returns the static Peer ID as a string
Uses axum::serve().with_graceful_shutdown(kinetic_core::shutdown::shutdown_signal()) — the serve loop shuts down cleanly when SIGINT/SIGTERM arrives. The daemon’s main server does the same.
Quick Reference — Boot Order
validate_keys_initialized()— exit if governance keys are placeholders.KineticConfig::load()— read config.SledStorage::new()— open embedded DB.DrandClient::fetch_latest()— get current Drand beacon (orunavailable()).identity::load_or_generate_key("node.key")— load/generate stable Peer ID.NetworkEventLoop::new(NetworkMode::FullNode, ...)— wire P2P with public interfaces.tokio::spawn(network_loop.run())— launch P2P event loop.tokio::spawn(gossip_loop)— launch governance + Drand gossip handler.tokio::spawn(drand_heartbeat)— launch 3-second Drand tick.axum::serve(16003).with_graceful_shutdown(...)— start health-check API.
Cross-Crate Connections
kinetic_core::governance::logic::validate_keys_initialized(): The production key gate. Unique to the node.kinetic_network::NetworkMode::FullNode: Tells the network layer to bind publicly, enable all DHT routing, and skip ephemeral-only mode.kinetic_storage::SledStorage: Same storage backend as the daemon.kinetic_core::drand::DrandClient: Shared Drand client. Thep2p_onlyflag behavior is unique to the node config.kinetic_core::governance::GLOBAL_GOVERNANCE_STATE: The shared globalMutex<GovernanceState>updated by incoming gossip.