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

Network Client Commands and Types

Crate: kinetic-network Stage: 8 Reading time: 30 minutes Depends on: 01_overview.md, kinetic-types, kinetic-core


What Is This?

This documentation provides an exhaustive breakdown of the communication primitives, configuration structures, and type definitions that form the crucial bridge between Kinetic’s higher-level application logic (such as the daemon or REST API) and the low-level, background network event loop.

Specifically, we are examining three tightly coupled files within the client module:

  • command.rs — This file defines the definitive vocabulary of commands that the network event loop is capable of understanding and executing.
  • types.rs — This file holds the configuration definitions required for node instantiation, as well as the specialized error reporting types.
  • mod.rs — This is the module declaration file that ties these components together and re-exports them for clean external access.

At the absolute center of this architecture is the Command enum. In a peer-to-peer network built on libp2p, the network state is concurrent but must be mutated safely. You cannot have fifty different API endpoints, daemon threads, and background workers all attempting to lock the Distributed Hash Table (DHT) and write to it simultaneously. Doing so would either cause massive lock contention—drastically slowing down the network—or it would tear the internal network state, leading to routing failures.

To solve this, Kinetic employs an actor model approach. The network swarm runs inside a single, dedicated asynchronous event loop that constantly polls for incoming network events. When external components need the network to do something—like publishing a record or resolving a domain—they do not interact with the network directly. Instead, they construct a Command message and send it over an asynchronous channel to the event loop. The event loop processes these commands sequentially, ensuring safe state mutations without the need for complex Mutex locks spread over the entire network swarm.

The types.rs file complements this by defining the configurations used to boot the node. Booting a peer-to-peer node is drastically different from starting a standard HTTP web server; it needs to know about bootstrap nodes, transports (TCP vs QUIC), consensus parameters (like PoW and Drand), and rate limits. The NetworkConfig struct captures all of these complex requirements in one place.


Why Kinetic Needs This

Kinetic needs these specific types for three fundamental reasons: architectural thread safety, deterministic asynchronous execution, and network topology flexibility.

1. Architectural Thread Safety and Polling Uptime

Consider thread safety and the libp2p Swarm. The Swarm is an intricate state machine that drives the entire network stack. It requires a dedicated executor to constantly poll it. This polling process manages incoming connections, multiplexes streams, and keeps protocols alive through periodic pinging and routing table updates. If another thread locks the Swarm to publish something, the polling stops.

While polling is stopped, the network might drop active connections because it missed a keep-alive window, or peers might consider the local node unresponsive and drop it from their routing tables entirely. By using a Command channel, the Swarm’s primary thread is never blocked by external callers; it simply reads from the queue when it is ready, ensuring 100% uptime for critical network polling.

2. Deterministic Asynchronous Execution and RPCs

When the daemon asks the network to resolve a domain, it needs to know precisely when that resolution is complete and exactly what the result is. Because the network operates asynchronously and peer responses take unpredictable amounts of time, we need a reliable mechanism for the background thread to send data back to the calling thread.

This is achieved by embedding a tokio::sync::oneshot::Sender inside almost every Command variant. It effectively creates a dynamic RPC (Remote Procedure Call) mechanism across local threads. Without this return channel, the daemon would have to fire off a command and blindly hope it worked, which is unacceptable for consensus-critical operations where failure must be handled immediately.

3. Network Topology Flexibility

Not all Kinetic nodes are created equal, nor should they be. The NetworkConfig and NetworkMode structures allow Kinetic to adapt its behavior to its hardware environment. A validator running on a dedicated server with high bandwidth needs to be a FullNode, storing DHT records and actively routing Kademlia traffic for others. Conversely, a lightweight mobile wallet only needs to be a LightNode, querying the network and broadcasting transactions without taking on the burden of storing the entire DHT or routing external traffic. These configuration types allow the exact same codebase to run in both capacities simply by toggling a flag during instantiation.


How It Works

The architecture relies on pairing one-way message passing with dedicated return channels. This is a standard Rust async practice, but it is applied here specifically to orchestrate Kinetic’s complex peer-to-peer operations.

The Message Passing Lifecycle

When a developer calls a method on the network client to perform an action, the following rigorous sequence occurs under the hood:

  1. Channel Creation: The caller creates a one-time use channel using tokio::sync::oneshot::channel(). This yields two halves: a Sender (to transmit the result from the event loop) and a Receiver (for the caller to await the result).
  2. Command Construction: The caller builds the appropriate variant of the Command enum. It embeds the necessary request data (like a domain name or payload bytes) and attaches the Sender half of the channel inside the struct.
  3. Dispatch: The caller sends this constructed Command into the main MPSC (Multi-Producer, Single-Consumer) channel that feeds the network event loop. The multiple producers are the API endpoints and workers; the single consumer is the network event loop.
  4. Suspension: The calling async task then .awaits on the Receiver. This pauses the calling task entirely, yielding CPU time back to the tokio runtime without blocking the system thread. It will wait here until the network replies.
  5. Execution: On the other side of the boundary, the network event loop pulls the Command off the queue during its next polling cycle. It interprets the command, executes the corresponding libp2p operation (e.g., a Kademlia publish or a Proxy request), and eventually obtains an asynchronous result from the network peers.
  6. Fulfillment: The event loop transmits the result back through the Sender. If the caller has dropped the Receiver (e.g., they timed out or their task was cancelled), the oneshot::Sender detects this and safely discards the result without crashing or leaking memory.
  7. Resumption: The original calling task wakes up, receives the result from the Receiver, and continues its execution logic, confident in the network’s response.

-> See: crates/kinetic-network/src/client/command.rs — Lines 8 to 101

Memory Efficiency in Commands (Arc<str>)

You will notice that many commands use Arc<str> (an Atomically Reference Counted string slice) rather than a standard String. In a busy network environment, domain names and Gossipsub topics are cloned repeatedly. They are passed from the REST API, to the client wrapper, to the command queue, to the event loop, and finally deep into the internal libp2p protocols.

If we used a standard String, we would be constantly re-allocating memory on the heap and copying bytes every single time the domain name moved across a functional boundary. By using Arc<str>, multiple threads can cheaply share the exact same string data in memory. Cloning an Arc merely increments a counter, which is orders of magnitude faster and uses significantly less memory than copying string bytes.

Error Efficiency (Cow<'static, str>)

In types.rs, the ProxyError::Other variant uses Cow<'static, str>. Cow stands for Clone-On-Write. This is another critical memory optimization in Rust. When an error occurs, it is often a hardcoded static string (e.g., "Unexpected protocol failure"), which lives in the binary’s read-only memory space. Sometimes, however, it is a dynamically generated string (e.g., format!("Failed to parse peer {}", peer_id)). Cow allows the error to store either a zero-cost reference to the static string or an owned dynamic string, avoiding unnecessary heap allocations for the static cases while maintaining flexibility.

Re-exports in mod.rs

The mod.rs file contains statements like pub use self::command::*;. This is a Rust re-export. It takes the contents of the command.rs file and flattens them into the client namespace. This means that external crates can import Command via kinetic_network::client::Command instead of having to dig into kinetic_network::client::command::Command. This creates a much cleaner public API surface and prevents users from having to memorize deep file hierarchies.


Key Pieces

1. The Command Enum (command.rs)

This enum is the definitive list of actions the network event loop can perform on behalf of the application. Every variant represents a distinct network operation.

-> See: crates/kinetic-network/src/client/command.rs — Lines 8 to 101

DHT and Resolution Commands:

  • PublishRedundant: Instructs the network to store a record in the Kademlia DHT. Kinetic ensures this data is published redundantly to multiple peers so it survives node churn (peers going offline). It returns a Result<(), PublishError>.
  • ResolveRedundant: Queries the DHT to find a domain or record, returning the serialized payload bytes upon success.
  • VerifyQuorum: A critical security feature in Kinetic. Because the DHT is untrusted, a single malicious node might lie about a domain’s resolution to censor a user. This command queries a quorum (multiple independent nodes) to ensure they all report the exact same data before trusting it.
  • PublishHeartbeat: Specific to Kinetic’s domain system. It publishes a lightweight proof-of-ownership heartbeat to the network to ensure a registered name doesn’t expire and get purged by peers.

Gossipsub and PubSub Commands:

  • SubscribeGossip / BroadcastGossip: Used for real-time, topic-based message dissemination. When a new block is mined or a new transaction is created, it is broadcasted over Gossipsub to rapidly reach all connected peers simultaneously.
  • ReportGossipValidation: In libp2p, when a peer sends you a Gossipsub message, you have a strict, short time window to validate it. This command allows the application layer to tell the network layer whether the message was valid. If the application rejects it, the network layer will immediately penalize the peer who sent it, protecting the entire network from spam and malicious actors.

Direct P2P Proxy Commands:

  • SendProxyRequest: Sends a direct RPC-style request to a specific remote peer ID, bypassing the DHT entirely. This is used for targeted node-to-node communication, like querying a specific validator for its current state.
  • SendProxyResponse: When the local node receives a proxy request via the network, it processes it locally and uses this command to send the answer back. It uses a ResponseChannel to tie the response back to the exact incoming request stream.

Lifecycle and Diagnostics:

  • GetCurrentDrandKyn: Retrieves the latest randomness value (kyn) synchronized from the network.
  • Bootstrap: Manually forces the node to reach out to its configured bootstrap peers to integrate into the network topology. This is typically called shortly after startup to join the swarm.
  • GetNetworkStatus: Returns diagnostic JSON containing the node’s current connections, active protocols, and routing table size. This is vital for the daemon’s status API.

2. NetworkMode (types.rs)

This enum determines the node’s responsibility level within the broader network topology:

-> See: crates/kinetic-network/src/client/types.rs — Lines 31 to 37

  • FullNode: Fully participates in the network. It stores DHT records for other peers and helps route Kademlia traffic. This is the expected mode for active validators and infrastructure providers who need to actively support the network’s health.
  • LightNode: Operates in client-only mode. It will issue requests, resolve domains, and broadcast transactions, but it refuses to store DHT records on behalf of others. This is ideal for resource-constrained environments like mobile apps or background services that just need to sync state.

3. NetworkConfig (types.rs)

This struct holds all parameters required to build the network swarm.

-> See: crates/kinetic-network/src/client/types.rs — Lines 40 to 68

Notable configuration fields include:

  • listen_addrs / quic_listen_addrs: The multiaddresses where the node accepts incoming connections. Kinetic supports both TCP (reliable, traditional) and QUIC (UDP-based, faster handshakes, no head-of-line blocking). Supporting both maximizes connectivity across diverse and restrictive network conditions.
  • bootstrap_nodes: The entry points into the network. Without these, a new node cannot discover the DHT.
  • seed_domain: Pre-known domains used for DNS tree resolution during startup to locate root services.
  • enable_mdns: Enables local network peer discovery via Multicast DNS. This is useful for testing or LAN-based node clusters where internet traversal is unnecessary.
  • initial_drand_kyn: The starting randomness beacon value needed to verify Verifiable Delay Functions (VDFs) immediately upon startup before syncing with the network.
  • disable_pow: A flag used to bypass Proof of Work checks. This should only be true during integration testing to prevent automated tests from hanging while mining hashes.
  • max_reveals_per_hour: A strict rate-limiting threshold that dictates how many reveals a node will accept into its cache, protecting the node against memory exhaustion attacks from malicious peers.
  • lru_cache_size: The maximum number of items in the Least Recently Used (LRU) in-memory cache. It uses std::num::NonZeroUsize, a Rust optimization that ensures the value is never zero. Because it can never be zero, the Rust compiler can use 0 to safely represent the None variant, meaning Option<NonZeroUsize> takes up exactly the same amount of memory footprint as a regular usize.
  • test_mode: Enables testing configurations. This disables automatic UPnP (Universal Plug and Play) port forwarding and shortens timeouts so that unit tests run fast and isolated without attempting to mutate the local router’s firewall rules.

4. ProxyError (types.rs)

When executing direct SendProxyRequest commands, network reality dictates that things will occasionally fail. This enum categorizes those failures so the caller can react appropriately:

-> See: crates/kinetic-network/src/client/types.rs — Lines 6 to 26

  • Timeout: The peer did not respond within the allocated timeframe. The caller might want to retry.
  • Offline: The requested peer is not currently connected to the swarm, and Kademlia routing could not locate them on the network.
  • ConnectionClosed: The underlying TCP or QUIC stream was abruptly severed during the transfer. This often indicates poor network conditions or an intentional disconnect by the peer.
  • UnsupportedProtocols: The remote peer does not speak the requested proxy protocol, likely due to running an outdated Kinetic version.
  • ChannelClosed: The internal MPSC channel connecting the client to the event loop collapsed. This is a fatal error indicating the network event loop has crashed and cannot process further commands.
  • Other: The catch-all variant utilizing Cow<'static, str>, as explained earlier, for miscellaneous or unexpected failures that do not fit into the other categories.

How This Connects to the Rest of Kinetic

  • The Event Loop (kinetic-network): The Command enum acts as the sole input vocabulary for the network’s main event loop. The event loop is the exclusive consumer of the channel carrying these commands.
  • The Client Interface (kinetic-network): The Client struct (which we will cover in the core client documentation) provides a clean, async API for the rest of the application. Under the hood, every method on Client is simply constructing one of these Command variants and sending it. The caller never has to touch the Command enum directly.
  • Proxy Types (kinetic-types): The ProxyRequest and ProxyResponse structures embedded inside Command::SendProxyRequest are defined centrally in the kinetic-types crate, ensuring a unified RPC schema across the entire application ecosystem.
  • CROSS-CRATE: PublishError and ResolutionError — defined in kinetic-core. These specialized error types are passed back through the oneshot::Sender when DHT operations fail.
  • CROSS-CRATE: NetworkClientError — defined in kinetic-core. This is the broader error wrapper used for overarching network failures like channel collapses or unexpected libp2p behaviors.

Quick Reference

  • To mutate or query network state: You must dispatch a Command to the event loop via the MPSC channel.
  • To receive an asynchronous result: Utilize the responder: tokio::sync::oneshot::Sender<T> embedded in the command variant. The event loop will fulfill it upon completion.
  • To configure node behavior: Construct a NetworkConfig with the appropriate listen addresses, rate limits, and bootstrap peers before instantiating the Swarm.
  • To minimize resource usage: Run the node in NetworkMode::LightNode so it doesn’t store external DHT data or route external traffic.
  • String optimization: Commands utilize Arc<str> to enable cheap, zero-copy cloning of domain names and topics across threads.
  • Memory layout optimization: Caching settings utilize std::num::NonZeroUsize to optimize memory layout for optional values.
  • String Error Optimization: ProxyError uses Cow<'static, str> to allow both static string literals and dynamic strings without enforcing heap allocations for every single error creation.

Open Questions / Things to Revisit

  • Dynamic Rate Limiting: The max_reveals_per_hour field is currently a single global setting in NetworkConfig. If network spam becomes sophisticated, Kinetic may need to move toward peer-specific or IP-specific rate limits to penalize bad actors without impacting legitimate traffic.
  • Drand Kyn Overrides: GetCurrentDrandKyn allows fetching the current kyn, but there is no command to forcefully update or sync it from the client side. The event loop manages this internally, but we should investigate if manual overrides would be beneficial for localized integration testing where waiting for network sync is undesirable.
  • Gossip Validation Tracking: ReportGossipValidation requires the application layer to retain the MessageId and the propagation_source. The application must ensure it tracks these precisely when receiving gossip messages; otherwise, it will be unable to validate them, causing the local network layer to silently penalize the node for failing to respond within the narrow validation window.