Event Loop Command Handler
Crate: kinetic-network Stage: 8 Reading time: 30 minutes Depends on: 01_overview.md, 10_event_loop_core.md
What Is This?
The command handler is the critical translation layer for the Kinetic network. It sits between the rest of the application and the libp2p network event loop.
When you build a peer-to-peer application using Rust’s libp2p ecosystem:
- The network state is contained within a
Swarm. - This
Swarmmanages all TCP and QUIC connections. - It handles the Kademlia routing tables for the DHT.
- It manages the Gossipsub mesh for pub/sub messaging.
- It tracks the AutoNAT status to determine public reachability.
Because Rust enforces strict ownership rules to prevent data races:
- The
Swarmis restricted to running on a single asynchronous task. - This task is the main event loop.
- The
Swarmcannot be safely shared across multiple threads.
However, the rest of the Kinetic daemon runs across multiple concurrent Tokio threads. These components include:
- The HTTP REST API handlers.
- The background block verification workers.
- The consensus engine and mempool.
These components constantly need to interact with the network. They need to:
- Publish domains to the DHT.
- Resolve names from the network.
- Broadcast new blocks and transactions.
The command handler (command_handler.rs) solves this concurrency problem. It implements an Actor Model for network communication. It receives high-level commands from other threads. These commands are sent via an asynchronous message channel (MPSC). The handler then converts these abstract requests into low-level operations. These operations mutate the state of the Kademlia DHT, the Gossipsub mesh, or the custom Content Delivery Network (CDN) layer.
Crucially, it also manages the asynchronous return paths. Networking takes time, so requests cannot be answered instantly. The command handler ensures that requests initiated by the application are properly tracked. It routes them into the peer-to-peer mesh. Finally, it guarantees that the eventual results are routed back exactly to the caller thread that requested them.
Why Kinetic Needs This
To understand why this specific file is so necessary, imagine a scenario where it didn’t exist. Imagine if the REST API tried to share the Swarm using an Arc<Mutex<Swarm>>.
- A user hits the
/resolveendpoint on the REST API. - The REST thread locks the mutex.
- It asks the Swarm to resolve a domain.
- It waits for the resolution to complete.
But libp2p relies on continuously polling the Swarm. Polling processes background network events like:
- Incoming peer connections.
- Keep-alive pings.
- Protocol negotiations.
If the REST thread holds the lock while waiting for a DHT resolution:
- The resolution could take hundreds of milliseconds or even seconds.
- During this time, the entire network layer is frozen.
- The event loop cannot poll.
- TCP Connections would time out and drop.
- Gossipsub would miss heartbeat intervals.
- The node would rapidly fall out of the mesh network.
Kinetic avoids this by using the NetworkEventLoop as a dedicated, continuously running loop. This loop owns the Swarm completely. Other threads can never touch the Swarm directly. Instead, they communicate by sending messages through a multi-producer, single-consumer (MPSC) channel.
The command_handler.rs file is the instruction manual for the event loop. It defines exactly how the event loop should react when a message arrives on that MPSC channel.
Here is the exact flow of a command:
- A client wants to resolve a domain.
- The client constructs a
Commandmessage. - Attached to that message is a one-time-use return channel (
tokio::sync::oneshot::Sender). - The client sends the command over the MPSC channel.
- The command handler (inside the event loop) unpacks the
Commandmessage. - It triggers the actual libp2p behaviors (like
kademlia.get_record). - It registers the return channel in a state map (like
pending_gets). - The event loop continues polling.
- Later, the network replies with the requested data.
- The event loop looks up the corresponding return channel in the state map.
- It sends the answer back to the exact REST thread that asked for it.
Without this file, the application would be severed from the P2P network capabilities. The REST API would have no way to ask the network to do anything. The background workers would have no way to broadcast their verifications.
How It Works
The core logic lives inside the handle_command function. This function processes a massive match statement over every possible Command variant. Because networking is fundamentally asynchronous, most commands cannot be answered immediately. Instead, they require kicking off a network request and storing a “pending state.”
Let’s break down exactly how the most important commands are processed step by step.
1. Publishing Redundantly to the DHT (Command::PublishRedundant)
When the event loop receives a Command::PublishRedundant, it needs to ensure the data is distributed widely. It must survive node churn (nodes going offline). Kademlia stores data on the nodes whose IDs are closest to the data’s key. If we only stored the data under one key, and the nodes closest to that key went offline, the data would be lost.
Here is the step-by-step process for redundant publishing:
-
Key Derivation
- It first derives a set of storage keys.
- It uses the
derive_storage_keysfunction fromkinetic-core. - This takes the human-readable domain name (like
saif.kyn). - It generates multiple distinct cryptographic DHT keys.
- This ensures the record is replicated across different regions of the network’s logical address space.
-
Local Validation
- Before it ever touches the network, it calls
enqueue_dht_puts. - This helper function attempts to insert the record into the node’s local Kademlia store first.
- Why do this locally first? This is a massive optimization and security feature.
- If the record is invalid, the local store’s validation logic will reject it.
- Reasons for invalidity include a wrong cryptographic signature, insufficient proof-of-work, or incorrect formatting.
- If the local store rejects it, every other honest node on the network will reject it too.
- By failing fast locally, Kinetic prevents the node from wasting outbound bandwidth.
- It prevents spamming the network with garbage records.
- It immediately returns an error to the caller without generating any network traffic.
- Before it ever touches the network, it calls
-
Network Dispatch
- If local validation succeeds, it proceeds to network distribution.
- It iterates over every derived key.
- It fires off a
put_recordrequest to thekademliabehavior in theSwarm. - It requests a
Quorum::One. - This means it expects at least one successful acknowledgment per key from the network.
-
State Tracking
- Every
put_recordcall returns aQueryId. - The handler records this
QueryIdin thequery_id_to_namemap. - This allows the event loop to know what operation a future response belongs to.
- It also registers the expected number of successful responses in the
pending_putsmap. - It stores the client’s
oneshot::Senderhere as well. - As network acknowledgments trickle in over the next few seconds, they are tallied.
- Once all expected keys are published, the oneshot channel is triggered.
- The client is notified of success.
- Every
2. Resolving Data (Racing Kademlia vs. CDN) (Command::ResolveRedundant)
The Command::ResolveRedundant variant is one of the most critical paths for user experience. When a user requests a domain, the resolution needs to be fast. However, Kademlia DHT lookups are notoriously slow. They require iteratively querying multiple nodes to find the closest peers to a key.
To solve this latency problem, Kinetic implements a sophisticated “racing” mechanism. It queries two different network layers simultaneously:
-
Offline Fallback Check
- First, it checks the network info for active peers.
- If the node has zero active peers, it is offline.
- In this case, it falls back to querying its own local Kademlia store.
- It derives the keys and checks if it holds the data.
- This is crucial for local testing or offline development.
- It also saves nodes that temporarily lose internet connectivity but have recently cached the required domain.
-
CDN Blast (The Fast Path)
- If online, it immediately attempts a CDN blast.
- It selects up to 3 currently connected peers from the swarm.
- It uses the custom
cdnbehavior to fire off a direct request for the domain to these 3 peers. - This skips the iterative, multi-hop routing of Kademlia.
- If one of these direct peers happens to have the domain cached in their edge storage, they will return it instantly.
- This cuts resolution time from ~800ms down to ~50ms.
-
DHT Querying (The Reliable Path)
- At the exact same time as the CDN blast, it queries the DHT.
- It derives the storage keys for the domain.
- It dispatches formal Kademlia
get_recordqueries into the DHT viadispatch_dht_queries. - This is the slow, fallback method.
- It is guaranteed to find the data if it exists anywhere on the network, even if the 3 peers from the CDN blast didn’t have it.
-
First to Win
- The state for the CDN requests is recorded in
pending_cdn_requests. - The state for the DHT queries is recorded in
pending_gets. - The event loop will accept the first valid response it receives from either system.
- Whichever subsystem returns cryptographically valid data first will trigger the oneshot response to the user.
- This architecture guarantees the speed of a centralized CDN with the decentralized, uncensorable reliability of a DHT.
- The state for the CDN requests is recorded in
3. Verifying Quorums (Command::VerifyQuorum)
Sometimes, Kinetic needs to know not just what a record is, but whether the network agrees on it. The VerifyQuorum command is used for consensus checks.
- It derives the storage keys for a domain.
- It fires off Kademlia queries using a special
QueryType::Quorum. - It registers the expected payload in
pending_quorums. - As the network responds, the event loop counts how many distinct nodes returned the exact same payload.
- It then reports this “match count” back to the client.
- This allows the application to prove that a record is widely distributed.
- It ensures the data has not been eclipsed by a malicious actor.
4. Handling Gossipsub Messages and Validation
The libp2p::gossipsub behavior manages the unstructured mesh network. This mesh is used for broadcasting blocks and mempool transactions. The event loop interacts with it via three main commands:
-
Subscribing (
Command::SubscribeGossip)- When the application wants to listen to a new topic (e.g., a specific block height), it sends this command.
- The handler takes the string topic and converts it into a
libp2p::gossipsub::IdentTopic. - It calls
subscribeon the gossipsub behavior. - This tells the libp2p swarm to actively seek out peers who are also interested in this topic.
- It starts maintaining a local mesh network for it.
- If successful, the node will begin receiving events for this topic.
-
Broadcasting (
Command::BroadcastGossip)- When the node creates a new block or transaction, it needs to propagate it.
- This command takes the payload and the topic.
- It creates the
IdentTopicand callspublish. - The swarm then forwards the message to a subset of its connected peers in the mesh.
- Those peers will then forward it to their peers, propagating it globally.
-
Validation (
Command::ReportGossipValidation)- Kinetic uses “strict validation” for Gossipsub.
- When a message is received, it is not immediately forwarded.
- Instead, it is offloaded to a background thread to check the VDF proofs and signatures.
- Once the background thread finishes its heavy cryptographic work, it sends this command back to the event loop.
- The command contains the
MessageId, thePeerIdof the sender, and whether the message was valid. - The handler translates this into
MessageAcceptance::AcceptorReject. - It calls
report_message_validation_result. - If rejected, the peer’s score is penalized.
- If a peer sends too many invalid messages, they are banned.
- This is the fundamental mechanism protecting the network from flood attacks.
5. Proxy Requests for Light Clients
Light clients cannot participate fully in the Kademlia DHT or Gossipsub mesh. They lack the bandwidth, CPU, and storage required. Instead, they rely on full nodes to proxy their requests. The command handler facilitates this via the custom proxy behavior (part of the CDN).
-
Sending Requests (
Command::SendProxyRequest)- When the node needs to ask another specific peer for data directly (without routing through the DHT), it sends this command.
- It provides the target
libp2p::PeerIdand theProxyRequestpayload. - The handler invokes
self.swarm.behaviour_mut().proxy.send_request. - This returns an
OutboundRequestId. - The handler then maps this ID to the client’s
oneshot::Senderin thepending_proxy_requestsmap. - When the target peer eventually replies, the event loop can look up the ID and route the response.
-
Sending Responses (
Command::SendProxyResponse)- When a remote peer asks this node for data, the request is surfaced to the application.
- The application processes the request.
- Once the application has the answer, it sends this command back to the event loop.
- The command includes the
ResponseChannelprovided by libp2p during the initial request, along with the data. - The handler calls
send_responseto push the data back over the wire to the requesting peer.
6. Network Status (Command::GetNetworkStatus)
This is a diagnostic command used by the CLI tooling. When triggered, it queries the swarm for its current state and returns JSON containing:
- The number of currently connected peers.
- The node’s local
PeerId. - The multiaddresses it is currently listening on.
- Its NAT status (whether it is reachable from the public internet).
- The size of its local DHT store. This provides users with a real-time dashboard of their node’s health.
Key Pieces
Command Enum
-> See: kinetic-network/src/client/command.rs — Lines 10 to 101
This is the exhaustive list of all instructions the event loop understands. It defines the complete API surface between the network layer and the rest of the node. Every variant typically contains:
- The required parameters (like a domain name, a payload, or a topic string).
- A
responder: tokio::sync::oneshot::Sender. This sender routes the asynchronous answer back to the exact thread that issued the command. Without this enum, the node’s business logic would have no way to communicate with the P2P mesh.
handle_command Function
-> See: kinetic-network/src/event_loop/command_handler.rs — Lines 89 to 314
This is the massive match statement that consumes Command instances. It dictates exactly how the state of the NetworkEventLoop mutates in response to client requests. This is the main entry point for everything described in the “How It Works” section.
enqueue_dht_puts Method
-> See: kinetic-network/src/event_loop/command_handler.rs — Lines 10 to 70
This method is responsible for taking a payload and validating it against the local store rules to prevent spam. It then queues multiple put_record requests to the Kademlia behavior to ensure redundant network storage. It gracefully handles immediate failures and warns the node operator if all puts fail immediately.
dispatch_dht_queries Method
-> See: kinetic-network/src/event_loop/command_handler.rs — Lines 72 to 87
This method fires off Kademlia get_record queries for a list of derived keys. Crucially, it registers the resulting query_id in the query_id_to_name mapping. This ensures the event loop knows exactly what domain a future, asynchronous network response belongs to.
How This Connects to the Rest of Kinetic
- CROSS-CRATE: The
Commandvariants rely onderive_storage_keysandderive_heartbeat_keysfromkinetic_core::types. These map human-readable domain names to the cryptographic Kademlia address space. - CROSS-CRATE: The commands themselves are constructed and sent by the
NetworkClient. This client lives inkinetic-networkbut provides a clean, async API utilized extensively by thekinetic-restserver and the background workers inkinetic-daemon. - Internal Integration: This handler directly mutates the
pending_gets,pending_puts, andpending_quorumsstate maps defined inevent_loop/core.rs. It provides the setup, while the teardown (completing the request) happens when network events arrive later in the loop.
Quick Reference
-
Publishing (
PublishRedundant):- Validates locally to prevent spam.
- Puts to the DHT redundantly across multiple keys.
- Returns success when the network acknowledges.
-
Resolving (
ResolveRedundant):- Checks the offline local cache first.
- Races direct CDN requests to 3 peers against slow DHT queries.
- Returns the fastest valid response.
-
Quorum Verification (
VerifyQuorum):- Queries the DHT to ensure a critical mass of nodes agree on the exact same payload.
- Used for consensus and security checks.
-
Gossipsub Management:
BroadcastGossipandSubscribeGossipact as simple pass-throughs.- They call methods on the underlying
libp2p::gossipsubbehavior. - This allows the node to participate in block and mempool meshes.
-
Validation Loopback:
ReportGossipValidationallows offloaded CPU-heavy checks to report back.- It penalizes malicious peers sending bad VDFs or signatures.
-
Network Status:
- Generates diagnostic JSON summarizing peer connections, local DHT size, and NAT status.
- Used by the CLI.
Open Questions / Things to Revisit
-
CDN Racing Peer Count
- The
ResolveRedundantcommand blindly takes the first 3 connected peers for its CDN blast:self.swarm.connected_peers().copied().take(3). - Is 3 the optimal number?
- Should it prioritize peers based on known latency, bandwidth, or past reliability?
- Just taking the first 3 from the iterator might select slow peers, losing the CDN blast advantage.
- The
-
Offline Fallback Scope
- The offline fallback for
ResolveRedundantcurrently only checks the local Kademlia store. - If a record was previously fetched via the CDN layer but never formally inserted into the local Kademlia store, the offline node will fail to resolve it.
- Should the CDN cache be queried during offline mode as well to provide better resilience?
- The offline fallback for
-
Local Put Failure Handling
- In
enqueue_dht_puts, if the local put fails, it aborts the network publish. - It assumes this is a validation error.
- This is correct for cryptographic validation failures.
- But what if the local store fails due to a disk I/O error or full disk?
- It would silently prevent the node from publishing valid data to the network.
- This could potentially isolate the node’s outputs.
- In
-
Bootstrap Redialing
- In the
Bootstrapcommand, it redials the hardcoded bootstrap peers. - If those peers change IP addresses, this command might endlessly dial dead endpoints.
- A DNS re-resolution might be necessary here if they rotate IPs.
- In the