API Routing, State, and Server Bootstrap
Crate: kinetic-daemon Stage: 9 Reading time: 60 minutes Depends on: Stage 7 (kinetic-core), Stage 8 (kinetic-network)
What Is This?
This file (kinetic-daemon/src/api/mod.rs) serves as the entry point, the structural core, and the ultimate security gatekeeper for the Kinetic Daemon’s HTTP API. It acts as the primary bridge between the node’s internal peer-to-peer (P2P) network capabilities and the external world. Specifically, it defines the shared state that every single API handler uses to interact with the node’s underlying services. It establishes the web server itself using the axum web framework, which is built on top of the Tokio asynchronous runtime. It configures Cross-Origin Resource Sharing (CORS) to prevent browser-based attacks from malicious websites. It enforces strict Role-Based Access Control (RBAC) via custom authentication middleware that intercepts every sensitive request. In the context of the Kinetic network, the daemon is the only practical way a user or external application can interact with a node. The P2P network speaks its own complex, binary gossip protocols (like Gossipsub) and custom RPC mechanisms over encrypted streams. These raw network protocols are not easily consumed by local bash scripts, Python utilities, or browser extensions. This HTTP API translates local, standardized web requests (like GET and POST) into complex node actions. Without this module, a Kinetic node would be an isolated, silent box sitting on a server or laptop. It would be able to synchronize with the network, validate blocks, and store records. However, it could not be commanded, queried, or monitored by the person running it. This file is what turns the node into a usable application rather than just a background service. It defines the exact pathways that data flows from the user’s CLI or UI, down into the daemon, and out to the global network. Crucially, it defines the pathways by which the daemon prevents unauthorized actors from doing the exact same thing. It is the translation layer between HTTP and the Kinetic protocol. It is the state manager that ensures the web server does not corrupt the node’s internal database. And it is the security layer that ensures your local node remains yours and yours alone. This highlights the importance of the API layer acting as a protective barrier and translation engine for the node. It cannot be overstated how critical this module is for the operational safety of a Kinetic deployment. The local environment is fundamentally dangerous because any malicious process or script running on the host machine could potentially access the API. Therefore, the daemon cannot assume that a request coming from localhost is inherently trustworthy or safe. It must rigorously authenticate and authorize every single action that modifies the state of the node. By providing a unified, secure interface, this module enables the rich ecosystem of Kinetic developer tools to exist without compromising the underlying node security. The design of this file is influenced by the principle of least privilege, ensuring that even if one component is compromised, the damage is contained. The mod.rs file effectively acts as a traffic controller, directing incoming HTTP requests to their appropriate handlers while blocking any unauthorized traffic. It is the heart of the node’s user-facing capabilities, transforming complex internal operations into simple, consumable RESTful endpoints. Understanding this file is essential for anyone looking to build applications on top of a local Kinetic node, as it defines the entire surface area available to developers. It is the definitive reference for what the node is capable of doing on behalf of the user.
Why Kinetic Needs This
The Kinetic core and network crates handle the actual heavy lifting of the protocol. They manage the gossip protocols, compute the Verifiable Delay Function (VDF) proofs, handle cryptographic name resolution, and manage distributed storage. However, those crates operate in the background. They do not have User Interfaces. They do not speak JSON. They do not understand HTTP headers. Kinetic needs a local HTTP server to expose these powerful capabilities to the user’s local environment. But a local server running on a user’s machine is a massive security risk if it is not designed with extreme paranoia. If a malicious website running in the user’s web browser can send unauthorized AJAX requests to http://localhost:<kinetic_port>, it could hijack the node. The malicious site could use the user’s funds, publish malicious records under the user’s identity, or exhaust the machine’s CPU by submitting thousands of fake VDF tasks. This file solves that exact problem by implementing an ironclad security and routing perimeter. It ensures that the convenience of a local HTTP API does not compromise the security of the node. It implements scoped authentication. Instead of a single password, it uses generated, single-purpose bearer tokens. It prevents timing attacks by using constant-time cryptographic string comparison, ensuring attackers cannot brute-force tokens by measuring response times. It restricts CORS aggressively. It blocks random web origins from accessing the API, while allowing local tools and known extension schemas. It manages state safely. It provides a thread-safe, unified view of the node’s internal state to all stateless HTTP handlers running across multiple Tokio threads. Without this constructed layer, the node would be vulnerable to a class of attacks known as Cross-Site Request Forgery (CSRF) and local privilege escalation. This module is what makes the node safe to run on a personal laptop while browsing the open web. The decisions made in this file—such as how to bind to a port, how to fail over to IPv6, and how to rotate tokens—are all driven by the need for operational resilience. A node must stay up, and it must stay secure, even when the local environment is hostile or misconfigured. This module guarantees that resilience. Every architectural choice in mod.rs was made to balance the need for developer ergonomics with the absolute necessity of node security. The local environment is often the most dangerous environment for a blockchain or P2P node. By assuming the local environment is hostile, this module protects the user from their own mistakes. The necessity of this module is driven by the reality that a node without an interface is useless, but an interface without security is dangerous. The careful balance struck here is the core value proposition of the daemon crate. If the API were exposed without these protections, any random script or browser tab could initiate a governance vote or overwrite critical zone data. By enforcing a strict boundary, Kinetic ensures that the user is always in control of their node, and that any automated interactions are authorized. This is particularly important for a decentralized system, where the user’s node is their sovereign agent on the network. A compromised node could be used to attack the rest of the network, making the security of this local API a matter of global network health. Furthermore, the clear separation of concerns provided by this module allows the core protocol engineers to focus on cryptography and networking, while the daemon engineers focus on user experience and API design. This modularity is essential for the long-term maintainability and evolution of the Kinetic codebase. Ultimately, without this security and routing layer, the system would collapse under the weight of local exploits.
How It Works
The mod.rs file orchestrates the entire API lifecycle, from the moment the daemon boots up to the processing of individual HTTP requests. The mechanisms here are complex because they bridge asynchronous Rust with web server paradigms, ensuring safety and performance.
1. Bootstrapping and Token Generation
Before the daemon ever opens a network port to listen for HTTP requests, it must secure itself. This boot sequence is handled by the ensure_api_tokens() and rotate_token_on_boot() functions.
-> See: kinetic-daemon/src/api/mod.rs — Lines 247 to 306
Unlike legacy systems that use a single static “admin password” defined in a configuration file, Kinetic uses dynamic, scoped, single-purpose tokens. During the boot phase, the system goes through a rigorous generation process:
- The system checks the local
~/.kinetic/api_tokensdirectory to ensure it exists. - It iterates through each distinct role the API supports:
admin,publish,vdf,governance, andatlas. - For each role, it generates a new 32-byte cryptographically secure random token.
- It uses the
getrandomcrate for this. This is not a pseudo-random number generator; it hooks directly into the operating system’s entropy pool (e.g.,/dev/urandomon Linux). - It encodes these raw bytes into a hex string, which is standard for HTTP Bearer tokens.
- It writes these hex strings to disk using strict Unix file permissions (
0o600). - This
0o600permission mask means that only the specific user account running the daemon can read or write the token files. Not even other users on the same physical machine can access them. - This prevents a scenario where a shared server environment compromises the node’s API tokens.
- If the token files already exist from a previous run, they are overwritten. This is the concept of ephemeral tokens.
If the getrandom call fails (which can happen on deeply embedded systems that have not gathered enough entropy yet), the daemon is programmed to crash immediately. It refuses to start with predictable or weak tokens. This avoids a vulnerability where an attacker could guess the tokens due to low system entropy. This fail-fast behavior is a cornerstone of Kinetic’s security philosophy: it is better to be unavailable than to be insecure.
2. State Construction and Concurrency
Once the security tokens are generated, the daemon bundles everything the HTTP API needs into an ApiState struct.
-> See: kinetic-daemon/src/api/mod.rs — Lines 105 to 129
Because an HTTP server is concurrent (it handles many requests at once, often across dozens of threads via the Tokio runtime), the state must be thread-safe. Rust’s compiler enforces this strictly, requiring all shared state to be Clone and Send.
- The NetworkClient: This is passed in from the network module. It is already a cheap-to-clone handle designed for cross-thread usage.
- The StorageEngine: This is wrapped in an
Arc<dyn StorageEngine>.Arc(Atomic Reference Counted) allows multiple threads to hold a pointer to the storage engine without duplicating the database connection. Thedynkeyword indicates it uses dynamic dispatch, allowing different storage backends. - VDF Tasks: Ongoing VDF tasks are stored in a
HashMap. Because multiple endpoints might want to query or update a task’s status simultaneously, it is wrapped in anArc<Mutex<HashMap<String, VdfTaskStatus>>>. TheMutex(Mutual Exclusion) ensures only one thread writes to the map at a time. - VDF Semaphore: A
tokio::sync::Semaphoreis included to throttle heavy VDF computations. If the API receives ten VDF requests at once, the semaphore limits how many can run in parallel so the daemon does not crash the host machine’s CPU. - Gossip Broadcast: A
tokio::sync::broadcast::Senderis included so that HTTP requests can inject messages directly into the P2P gossip network, and subscribe to incoming gossip streams via Server-Sent Events (SSE). - Atlas TLDs: An
Arc<RwLock<HashSet<String>>>tracks the top-level domains managed by the Atlas bridge, allowing concurrent reads but exclusive writes. - Bind IP: The IP address the daemon is bound to is stored so that handlers can reference it, particularly useful for constructing self-referential URLs or configuring CORS dynamically.
This rigorous state management ensures that no matter how many HTTP requests hit the API simultaneously, the node’s internal state remains consistent and free of race conditions. Rust’s borrow checker guarantees that the Mutex and RwLock primitives are used correctly at compile time.
3. Server Initialization and Fallback Binding
With the state fully constructed, the actual HTTP server is initialized in start_server().
-> See: kinetic-daemon/src/api/mod.rs — Lines 308 to 375
Kinetic attempts to bind to the user-specified IP and port (defaulting to 127.0.0.1 and 8080). However, local networking environments are notoriously finicky and unpredictable. If the primary bind fails, Kinetic implements a robust retry loop. It will try up to 10 times, pausing for 200 milliseconds between each attempt. This is crucial because sometimes the OS takes a few seconds to fully release a port from a previously crashed or terminated instance of the daemon (this is known as the TIME_WAIT TCP state). If all attempts to bind to the IPv4 address fail, the server initiates a fallback procedure. It attempts to bind to the IPv6 loopback address [::1]. This fallback ensures the daemon remains accessible even in strange networking environments, VPN configurations, or operating systems where IPv4 localhost networking is restricted or disabled entirely. Once successfully bound, it prints the local address to the console and hands the listener off to axum::serve(), which begins accepting incoming TCP connections and routing HTTP requests. This ensures a smooth developer experience; the daemon works hard to find a usable port configuration without requiring manual intervention from the user.
4. Routing Architecture and Submodules
The API is massive, so it is divided into a vast array of submodules. Each submodule manages a distinct part of the node’s functionality.
-> See: kinetic-daemon/src/api/mod.rs — Lines 148 to 245
The atlas module:
This module provides endpoints for the kinetic-atlas bridge. Atlas is a system for migrating traditional DNS Top-Level Domains (TLDs) into the Kinetic namespace. The endpoints here allow an external bridge service to notify the daemon of state changes, register new TLDs, or synchronize root zone data. Because modifying TLD mappings is sensitive and impacts global resolution, these routes are authenticated with the Atlas role token.
The config module:
This module allows external clients to read and mutate the daemon’s runtime configuration dynamically. For example, a user might want to adjust their default gossip verbosity or change their primary seed nodes without restarting the daemon. The GET request is authenticated to prevent local scripts from scraping node configuration, while the POST request requires the Admin token.
The gossip module:
Gossip is the heartbeat of the P2P network. This module allows local applications to interact directly with the pub/sub streams. The /gossip/publish/{topic} endpoint allows a client to inject a raw message into a specific network topic. The /gossip/subscribe/{topic} endpoint uses Server-Sent Events (SSE) to stream real-time network chatter back to the client. The subscription route is public because it only reads data, whereas publishing requires authentication to prevent local scripts from spamming the network under the node’s identity.
The kid module:
KID (Kinetic Identity Document) is the cryptographic identity system. This module provides the tooling necessary to manage the user’s local keypairs. Endpoints allow for generating a new KID, listing all currently managed KIDs, fetching the details of a specific KID, and rotating the cryptographic keys associated with a given identity. Since the KID represents the user’s sovereign identity, any modification (like generation or rotation) is locked behind the Admin role.
The publish module:
This is perhaps the most critical module. It handles the submission of data to the global network. It takes a fully formed NameRecord, signs it if necessary, commits it to local storage, and then broadcasts it to the P2P swarm via gossip. It includes specialized variants like /publish-kid for publishing identity documents, /publish-manifest for complex applications, and /publish-governance for voting. Because publishing consumes resources and commits the node cryptographically, it is protected by the Publish token.
The resolve module:
The flip side of publishing is resolution. This module allows clients to query the network for the current state of a name. When a request hits /resolve/{name}, the daemon checks its local database, and if the record is missing or stale, it issues a request to its peers to find the latest data. It resolves standard Kinetic domains as well as KIDs. Since resolution is purely read-only and harmless, these routes are public.
The time module:
The Kinetic network relies on a synchronized, decentralized clock to prevent replay attacks and ensure VDF proofs are valid in a specific window. The /time endpoint allows external local tools to ask the daemon what the current network time is, ensuring they construct records that will be accepted by the network. This is a public route.
The vdf module:
Verifiable Delay Functions (VDFs) are computationally intensive proofs required to register names in Kinetic, preventing spam and domain squatting. This module provides endpoints to submit a registration or renewal request (/vdf/register). Because computing a VDF can take minutes or hours and maxes out the CPU, the request returns a task_id immediately while moving the computation to a background thread. The client can then poll /vdf/status/{task_id} to check on progress or delete the task to abort it.
The zone module:
A zone in Kinetic is analogous to a DNS zone file. It contains the hierarchical mapping of subdomains and records. This module allows users to fetch their current zone layouts, submit modifications (like adding a new A record), and publish the updated zone to the network. Fetching a zone is public, but modifying or publishing it requires the Publish or Admin role.
The router is logically split into two distinct groupings:
- Public Routes: Endpoints exposed openly. They only query data and cannot modify the node’s state. Anyone on the local machine (or any authorized extension) can call them.
- Auth-Guarded Routes: sensitive endpoints. They are placed inside a nested router that is protected by the authentication middleware layer.
By organizing the routes in this manner, it guarantees that new sensitive endpoints cannot accidentally bypass the authentication layer, provided they are added to the correct router group.
5. CORS Enforcement
Cross-Origin Resource Sharing (CORS) is a critical defense-in-depth layer implemented at the router level. The app() function applies a restrictive CORS layer to the entire router using tower_http::cors::CorsLayer.
- It intercepts the
Originheader of every incoming HTTP request. - It evaluates a predicate function against the origin.
- It only allows requests originating from exactly matching patterns:
http://localhost,http://127.0.0.1, specific bound IPs, or browser extensions using specific protocols (chrome-extension://,moz-extension://). - If an external website (like
https://evil.com) attempts to make an AJAX request to the local daemon, the user’s browser will execute a preflightOPTIONSrequest. - The daemon will refuse to return the necessary CORS allow headers, and the browser will actively block the actual request from ever reaching the node.
- Furthermore, it allows only specific HTTP methods (
GET,POST,OPTIONS) and specific headers (CONTENT_TYPE,AUTHORIZATION). This protects the user even if they accidentally browse a malicious site while their daemon is running in the background. CORS is notoriously difficult to get right, but Kinetic takes a zero-trust approach, enumerating the very few local origins that are permitted.
6. Authentication Middleware and Timing Attacks
The core security check for guarded routes happens inside the auth_middleware() asynchronous function.
-> See: kinetic-daemon/src/api/mod.rs — Lines 377 to 435
When a request targets an authenticated route (like /publish), this middleware executes before the actual handler logic ever sees the request. First, it looks for the Authorization HTTP header. It expects the format Bearer <token>. If the header is missing, malformed, or does not start with Bearer , it rejects the request instantly with a 401 Unauthorized status code. If the token is present, it extracts the token string and prepares to compare it against the expected tokens stored in the ApiState.
The Critical Security Detail:
It does not use standard string equality (==) to compare the tokens. It uses a specialized crate: subtle::ConstantTimeEq. Why is this necessary? If it used standard ==, Rust would check the characters one by one in a loop. It would return false the exact microsecond it found a mismatch. An attacker could send thousands of guesses. By measuring exactly how many microseconds the server took to reject each guess, the attacker could figure out if they got the first character right (because it would take slightly longer to reject). They could then guess the second character, and so on. This is known as a “timing attack,” and it breaks string-based passwords over time. By using ConstantTimeEq, the daemon forces the CPU to evaluate the entire string, taking the exact same amount of time to compare the strings regardless of where the mismatch occurs. This neutralizes timing attacks.
Once the token is verified, the middleware figures out which Role it corresponds to (e.g., Admin, Publish, Vdf). It then injects that Role into the request’s internal extensions map using req.extensions_mut().insert(r). This allows the downstream handler functions to know exactly what permissions the caller has, without having to re-parse the token themselves.
7. Deep Dive: Constant Time Authentication and Timing Attacks
The auth_middleware relies on subtle::ConstantTimeEq. This is not just a theoretical concern; timing attacks are practical on local networks or loopback interfaces where network jitter is minimal. When the auth_middleware receives a token, it converts both the expected token and the provided token into byte arrays. It then uses the ct_eq method from the subtle crate.
-> See: kinetic-daemon/src/api/mod.rs — Lines 410 to 414
The ct_eq method performs a bitwise comparison of the entire byte array, accumulating any differences using bitwise OR operations. It does not use branching (if statements) to short-circuit the comparison when a mismatch is found. Because modern CPUs use branch prediction, a standard == check would cause the CPU to take a different execution path as soon as it found a wrong character. This branch misprediction is exactly what an attacker measures to perform a timing attack. By avoiding branches and always processing the full 32 bytes of the token, ConstantTimeEq ensures that an attacker cannot glean any information about which character in their guess was wrong. This is particularly important for the Admin token, as brute-forcing it would give an attacker total control over the node. The implementation in Kinetic is careful to only call ct_eq if the lengths of the tokens match. Checking lengths is generally safe from timing attacks because the length of the expected token (32 bytes hex-encoded, so 64 characters) is public knowledge by protocol design.
8. Deep Dive: The CorsLayer Configuration
The CorsLayer in Kinetic is exceptionally restrictive compared to standard web servers.
-> See: kinetic-daemon/src/api/mod.rs — Lines 152 to 179
It is configured using tower_http::cors::CorsLayer. The most critical part is the allow_origin predicate. Instead of allowing * (any origin) or a fixed list of domains, it uses a dynamic closure. This closure checks if the incoming Origin header matches http://localhost, http://127.0.0.1, or http://[::1]. It also allows chrome-extension:// and moz-extension:// schemes. This specific inclusion is what allows the Kinetic browser extension (like a wallet or identity manager) to function without needing a separate native companion app. The browser extension can inject its requests directly into the daemon. However, because standard web pages are hosted on https:// or http:// with a specific domain, they will always fail the origin check. The CORS layer also restricts the allowed HTTP methods to GET, POST, and OPTIONS. OPTIONS is required for the browser’s preflight mechanism. Finally, it restricts the allowed headers to CONTENT_TYPE and AUTHORIZATION. If an attacker tries to pass custom headers to exploit a vulnerability in axum or hyper, the CORS layer will reject the request before it even reaches the route handlers.
9. Deep Dive: The Proptests and Fuzzing Strategy
At the very bottom of mod.rs, there is a test module utilizing the proptest crate.
-> See: kinetic-daemon/src/api/mod.rs — Lines 440 to 464
proptest is a property testing framework for Rust. Unlike standard unit tests which check specific edge cases (e.g., assert_eq!(2 + 2, 4)), property tests generate random inputs to ensure the code maintains certain invariants. The test_fuzz_constant_time_eq_lengths test is a prime example of this. It generates random strings token_a and token_b with lengths anywhere from 0 to 128 characters. It then tests the ConstantTimeEq logic against these random fuzz inputs. The test asserts two properties:
- If the lengths of
token_aandtoken_bare identical, the result ofct_eqmust exactly match the result of standard string equality (==). This ensures the constant-time logic doesn’t introduce bugs where valid tokens are rejected or invalid tokens are accepted. - If the lengths are different, it ensures that the middleware logic (which avoids calling
ct_eqon different lengths) handles the discrepancy safely. By running this test with hundreds of random permutations, the daemon engineers can be confident that their critical authentication middleware is robust against unexpected token formats. Property testing is essential for security-critical pathways because human engineers often fail to imagine all the strange inputs an attacker might construct.
10. Deep Dive: axum Router Construction
The axum framework was chosen specifically because of its tight integration with the tokio asynchronous runtime and the tower middleware ecosystem. Unlike synchronous frameworks, axum handles each HTTP request in a separate lightweight async task, rather than a full OS thread. This allows the daemon to easily handle thousands of concurrent requests, such as hundreds of clients streaming gossip via SSE simultaneously. When the app() function constructs the router, it uses a pattern called “nesting” with the .nest("/api", ...) method. This takes all the previously defined routes (which were bound to bare paths like /health or /publish) and clones them so they are also available under the /api prefix. This dual-binding is a quality-of-life feature: local CLI tools often expect bare paths to minimize typing, while web UIs often proxy requests to a backend using an /api prefix to avoid routing collisions with frontend assets. By utilizing .merge() and .nest(), the router provides maximum flexibility without duplicating handler code.
11. Deep Dive: tokio Runtime Implications
Because axum runs on tokio, every single handler function must be an async fn. However, asynchronous programming introduces specific architectural constraints that mod.rs must manage. Most importantly, async tasks in Rust cannot easily borrow data with arbitrary lifetimes; they typically require 'static lifetimes or owned data. This is exactly why the ApiState relies on Arc (Atomic Reference Counted pointers). When an axum handler is invoked, axum extracts the state using the State extractor and passes it to the handler. Under the hood, axum is simply calling .clone() on the ApiState. Because all the heavy components (like the NetworkClient or the tokio::sync::Semaphore) are wrapped in Arc or are naturally cheap to clone, this .clone() operation is fast and allocates no new memory on the heap. It simply increments an atomic counter. This pattern ensures that the memory footprint of the HTTP server remains minimal, even under severe load.
12. Deep Dive: The getrandom Crate and System Entropy
The security of the entire API rests on the quality of the tokens generated during the bootstrap phase. The generate_and_write_token function uses the getrandom crate instead of a standard pseudo-random number generator (PRNG) like rand::thread_rng(). A PRNG starts with a seed and uses a mathematical formula to generate a sequence of numbers that appear random. If an attacker can guess the seed (which is often based on the system clock or PID), they can predict the entire sequence of tokens. The getrandom crate avoids this entirely. It directly asks the host operating system for cryptographic entropy. On Linux, it reads from /dev/urandom or uses the getrandom(2) syscall. On macOS, it uses getentropy. On Windows, it uses BCryptGenRandom. These OS-level systems gather entropy from unpredictable physical events: mouse movements, network packet arrival times, thermal noise on the CPU, and disk interrupt timings. By tying the token generation directly to true physical entropy, the daemon guarantees that the tokens are fundamentally unpredictable, even by a local attacker with full knowledge of the daemon’s internal state. The deliberate choice to unwrap() or return a fatal error if getrandom fails ensures the daemon “fails closed” (refuses to start) rather than “fails open” (starts with weak security).
13. Deep Dive: Handling IPv6 Fallback
The fallback mechanism in start_server() is a critical piece of operational engineering. Many modern operating systems, particularly containerized environments like Docker or specific Linux distributions, configure localhost networking differently. Sometimes 127.0.0.1 (IPv4 loopback) is disabled in favor of [::1] (IPv6 loopback). If the daemon required IPv4, it would simply crash on these systems, frustrating users. The start_server function mitigates this by looping over potential bind addresses. It first attempts the user’s explicit preference (usually IPv4). If the OS returns an EADDRINUSE (Address already in use) or a similar bind error, it waits 200ms and tries again. This handles the common scenario where a developer quickly restarts the daemon and the OS hasn’t garbage-collected the socket yet. If all 10 retries fail, it then tries to bind to the IPv6 loopback address [::1]. This dual-stack awareness is increasingly necessary as the world transitions to IPv6, ensuring the Kinetic daemon is future-proof and resilient across diverse deployments.
Key Pieces
struct ApiState
-> See: kinetic-daemon/src/api/mod.rs — Lines 105 to 129
The central hub and brain of the API layer. It holds references to everything the HTTP handlers might need. It contains the NetworkClient, the StorageEngine, the generated ApiTokens, the vdf_tasks map, the vdf_semaphore, the bind_ip, the gossip_tx channel, and the atlas_tlds set. Because the axum web framework requires state to be Clone (so it can be passed to many concurrent request handling threads), all internal components that are heavy or cannot be cheaply cloned are wrapped in thread-safe wrappers. This includes Arc (Atomic Reference Counted pointers) for shared ownership, Mutex (Mutual Exclusion) for synchronizing mutable access, and RwLock for optimized read-heavy concurrency. This is a classic, robust Rust pattern for sharing global state in an asynchronous web server. It ensures that handlers remain stateless and dependent on the injected state.
enum Role
-> See: kinetic-daemon/src/api/mod.rs — Lines 52 to 84
Defines the Role-Based Access Control (RBAC) levels for the daemon. Instead of a binary “you are admin or you are not” system, it provides fine-grained capabilities. It includes variants like Admin, Publish, Vdf, Governance, and Atlas. Crucially, it implements helper methods on the enum itself, such as can_publish(), can_vdf(), and can_govern(). This architecture allows a developer to write an automated script and grant it a token that can only submit VDF proofs. If that script is somehow compromised, the attacker cannot use its token to modify the node’s core configuration, publish unauthorized names, or vote in governance. This is the principle of least privilege in action. By separating these roles, the daemon ensures that a vulnerability in one area does not automatically grant full administrative access.
fn ensure_api_tokens()
-> See: kinetic-daemon/src/api/mod.rs — Lines 290 to 306
The function responsible for the node’s bootstrap security. It rotates all tokens every single time the daemon boots. This ensures that leaked tokens have a very short lifespan (only until the node restarts). It delegates the actual filesystem writing to the generate_and_write_token() helper function, which handles the getrandom invocation and the Unix permission setting. It is called before the HTTP server is bound to ensure that there is no race condition where the server accepts requests before the tokens are ready.
fn app(state: ApiState) -> Router
-> See: kinetic-daemon/src/api/mod.rs — Lines 148 to 245
The router builder. It maps HTTP methods (GET, POST, DELETE) and URL paths (like /zone/{name} or /health) to their corresponding asynchronous handler functions. It is responsible for applying the CORS middleware and correctly merging the public and authenticated routers into a single cohesive application tree. It also uses .nest("/api", ...) to expose all routes under an /api prefix for UI consumption, while keeping them available at bare paths for CLI convenience. This dual-routing structure provides maximum flexibility for both automated tools and browser-based frontends.
async fn auth_middleware(...)
-> See: kinetic-daemon/src/api/mod.rs — Lines 377 to 435
The ultimate gatekeeper for the daemon. It intercepts every request destined for a sensitive endpoint. It parses HTTP headers, executes the constant-time token comparison, and either aborts the request with a 401 Unauthorized HTTP status or forwards it to the intended handler. It uses axum::middleware::from_fn_with_state to gain access to the ApiState so it can check the incoming token against the freshly generated tokens. This middleware design pattern means that individual handlers never have to worry about authentication; if they are invoked, they are guaranteed to be authorized.
struct VdfTaskStatus
-> See: kinetic-daemon/src/api/mod.rs — Lines 39 to 50
A data structure representing the real-time status of an ongoing Verifiable Delay Function task. Because VDFs are complex, computationally expensive, and take a long time to run (often minutes or hours), they must be processed asynchronously in the background. This struct tracks whether the task is running, completed, or failed. It also tracks its precise progress (the number of iterations completed versus the number of iterations required). It derives Serialize and Deserialize so it can be automatically converted to JSON when requested by the /vdf/status/{task_id} HTTP endpoint. This allows the UI or CLI to display a progress bar while the daemon computes the proof.
struct PublishRequest
-> See: kinetic-daemon/src/api/mod.rs — Lines 131 to 136
The incoming JSON payload format for publishing a name record directly. It encapsulates a NameRecord (from kinetic_core) which contains the DID, the name, and the cryptographic signatures proving ownership. By accepting this directly in the API, the daemon allows external tools (like the CLI) to construct the complex cryptographic proofs offline and simply submit the finished payload for propagation to the network. This reduces the computational load on the daemon and allows for secure, offline signing of records.
struct PublishResponse
-> See: kinetic-daemon/src/api/mod.rs — Lines 138 to 145
The standard JSON response format for a publish action. It provides a high-level status string (usually ‘success’ or ‘error’) and a detailed message explaining the result of the publish attempt. This ensures that HTTP clients have a standardized way of parsing success or failure, rather than relying solely on HTTP status codes. This struct exemplifies the API’s focus on clear, structured communication with external tooling.
How This Connects to the Rest of Kinetic
This module is the grand synthesizer of the Kinetic ecosystem. It connects to almost everything below it in the architecture stack, pulling together discrete systems into a unified interface.
- CROSS-CRATE:
NetworkClient— This client handle is passed into theApiStateto allow the HTTP API to query the P2P network (e.g., resolving a name across peers) or broadcast gossip messages to connected nodes. It is defined and explained thoroughly indocs/learn/network/. - CROSS-CRATE:
StorageEngine— This trait interface is passed into theApiStateto allow the API handlers to read and write records directly to the local database on disk. It abstracts away whether the node is using Sled, RocksDB, or another backend. It is defined and explained indocs/learn/storage/. - CROSS-CRATE:
NameRecord— This struct is used inside thePublishRequestpayload to represent the data being published to the network. It is the fundamental unit of data in Kinetic, representing a registered name and its associated data. It is defined and explained indocs/learn/types/.
Ultimately, mod.rs acts as the primary consumer of almost all lower-level Kinetic systems. It exposes their internal, Rust-native functionality as consumable, standardized HTTP REST endpoints that any programming language, CLI, or UI framework can interact with easily. It acts as the translator, taking HTTP requests and turning them into P2P gossip, database writes, or VDF computations.
Quick Reference
- Token Storage Path:
~/.kinetic/api_tokens/*.token(Storesadmin.token,publish.token, etc.) - Token Rotation Policy: Tokens are rotated automatically on every single daemon boot. No static passwords are used.
- Port Fallback Logic: The server retries binding 10 times with a 200ms delay. It gracefully falls back to
[::1](IPv6 loopback) if all IPv4 attempts fail. - Constant Time Auth: It uses
subtle::ConstantTimeEqto prevent cryptographic timing attacks during Bearer token verification. - CORS Allowed Origins: The API only responds to
http://localhost,http://127.0.0.1, the specific bound IP,chrome-extension://, andmoz-extension://. - Middleware Role Injection: Authorized roles are dynamically placed into the request extensions (
req.extensions_mut()) for downstream consumption by handlers. - State Management Pattern: Uses
axum::extract::Stateto pass the concurrentApiStatestruct to all handler functions safely efficiently. - Property Testing:
proptestis used to exhaustively fuzz the constant-time equality logic. - Background Tasks: VDFs and other heavy workloads are moved to background Tokio tasks to avoid blocking the HTTP threads.
Open Questions / Things to Revisit
- Persistent Tokens vs Ephemeral Tokens: Currently, tokens rotate on every single boot. This provides excellent baseline security. However, it means that if a user sets up an external cron job or an automated bash script using a token, that script breaks the moment the daemon restarts. We may need to investigate durable tokens, or build a dedicated API endpoint that allows the user to request long-lived, named tokens for specific external integrations.
- Granular CORS for Extensions: The current CORS policy allows all
chrome-extension://andmoz-extension://origins. This means literally any extension installed in the user’s browser can query the public routes of the daemon. While they cannot access the authenticated routes without a token, we might want to tighten this up to specifically allowlist known Kinetic extension IDs in the future, preventing random extensions from scraping node data or tracking node status. - HTTP Rate Limiting: There is currently no HTTP-level rate limiting implemented in the router (other than the CPU
Semaphorespecifically for VDF computations). A malicious local script, or a compromised browser extension, could spam the public API with thousands of requests per second and consume all node resources. Adding a basic rate limiting layer using a crate liketower_governormight be necessary for long-term stability and DoS protection. - IPv6 Dual Stack Handling: The fallback logic tries IPv4 then IPv6. In a modern networking environment, it might be preferable to bind to a dual-stack socket that listens on both IPv4 and IPv6 simultaneously, rather than treating IPv6 purely as a failure fallback option. This would ensure maximum compatibility out of the box.
- Token Permissions Scoping Expansion: Right now, roles are fairly broad. As the API grows, we might need more fine-grained permissions attached to tokens, perhaps specifying exactly which zones or DIDs a specific token is permitted to modify, rather than granting blanket publish access.
- WebSockets over SSE: Currently, gossip streams use Server-Sent Events (SSE). It might be worth investigating if bidirectional WebSockets would be more efficient or flexible for future API consumers, especially for complex real-time subscriptions.
- Logging Verbosity: The current error logging during token mismatch or CORS rejection is minimal. Adding more detailed audit logs for failed API attempts could greatly assist in diagnosing misconfigured local scripts or detecting active local network attacks.
- Configuration Hot-Reloading: Currently, the API server must be fully restarted to recognize changes in the bound IP or allowed CORS origins. Implementing a hot-reload mechanism that updates the
axumrouter without dropping active connections would improve node uptime. - Test Coverage for Middleware: While the core functions are tested, ensuring the authentication middleware correctly rejects all invalid permutations (e.g., lowercase bearer, malformed tokens, expired signatures if they existed) requires expansive property-based testing.
- Token Revocation: Since tokens are rotated on boot, there is no way to revoke a compromised token without restarting the entire node. A
/token/revokeendpoint could be a critical security addition in future stages. - Performance Under Load: The current
Arc<Mutex<HashMap>>for VDF tasks might become a bottleneck under extreme load since it locks the entire map for any update. Sharding the map or using a concurrent hash map could resolve this. - Structured Error Responses: While
PublishResponseexists, many endpoints simply return a string or an HTTP status code on failure. Implementing a standardized JSON error format across all endpoints would vastly improve client developer experience.
14. Deep Dive: Tokio Semaphores for VDF Throttling
The vdf_semaphore inside the ApiState is a tokio::sync::Semaphore. A semaphore maintains a set of permits. In Kinetic, it is initialized with a specific number of permits (usually corresponding to the number of CPU cores available for VDF computations). When an HTTP request hits the /vdf/register endpoint, the handler attempts to acquire a permit from the semaphore before spawning the background task. If all permits are currently checked out (meaning the CPU is fully occupied with other VDF tasks), the semaphore will suspend the incoming asynchronous task. This is a critical form of backpressure. Without it, an attacker with a valid token could submit 10,000 VDF requests in a second. The Tokio runtime would blindly spawn 10,000 background threads, instantly exhausting the host machine’s memory and CPU, leading to an Out of Memory (OOM) kill by the operating system. The semaphore ensures that the daemon degrades gracefully under extreme load. The API request will simply wait until a permit is available. If it waits too long, the HTTP request will eventually time out, but the node itself will remain stable and responsive to other non-VDF requests.
15. Deep Dive: Mutex Contention and HashMap Scaling
The vdf_tasks field is a Arc<Mutex<HashMap<String, VdfTaskStatus>>>. While a Mutex is necessary for safely updating the status of tasks across multiple threads, it introduces a potential performance bottleneck known as lock contention. Every time a background VDF worker wants to increment its progress counter, it must acquire the Mutex lock on the entire HashMap. Similarly, every time a user polls /vdf/status/{task_id}, the HTTP handler must also acquire that exact same lock to read the status. If there are many active tasks and many clients polling them simultaneously, threads will spend most of their time waiting for the lock rather than doing useful work. In the current implementation, this is mitigated by the fact that VDF progress updates are generally batched or throttled (they don’t update on every single iteration). However, as the daemon scales, this data structure might need to be replaced with a lock-free concurrent map (like dashmap) to allow truly concurrent reads and granular writes without locking the entire task registry. This highlights the constant trade-off in Rust web services between the simplicity of standard library primitives and the extreme performance of specialized concurrent data structures.
16. Deep Dive: Broadcast Channels and SSE Subscriptions
The gossip_tx channel is a tokio::sync::broadcast::Sender. A broadcast channel is a multi-producer, multi-consumer channel where every sent value is seen by every active receiver. When a client subscribes via /gossip/subscribe/{topic}, the HTTP handler calls gossip_tx.subscribe() to obtain a new Receiver tied to that channel. The handler then converts this receiver stream into a Server-Sent Events (SSE) response using axum::response::sse. This means the HTTP connection remains held open indefinitely. As the core network stack processes incoming Gossipsub messages from the P2P swarm, it pushes them into the gossip_tx sender. The broadcast channel automatically duplicates the message to every currently connected HTTP client’s receiver. The beauty of this architecture is that it decouples the heavy network processing stack from the HTTP delivery stack. The core network doesn’t need to know how many web clients are connected, or if they are slow readers. If a web client reads too slowly, the broadcast channel will automatically drop older messages for that specific client (a feature known as lag), ensuring that a slow local client cannot back up the entire P2P node.