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

Crate: kinetic-core

Stage: 18 (Miscellaneous Core)

Reading Time: 15 minutes

Depends On: network.json, build.rs, Axum, Tokio, Sled

What Is This?

This document covers the miscellaneous but essential foundational elements of the kinetic-core crate. We are looking at a collection of files that act as the structural glue holding the protocol together. First, we have lib.rs, which serves as the crate root. It maps the entire crate architecture and re-exports critical error types for ease of use. Next, we have constants.rs, the immutable rulebook of the Kinetic network. It hardcodes everything from cryptographic constraints to database prefixes. We also deeply cover net.rs, which contains specialized network security primitives. These primitives are specifically designed to prevent Server-Side Request Forgery (SSRF) attacks. Furthermore, this module includes request_id.rs. This is a robust mechanism for task-local correlation ID generation. It is used in asynchronous tracing to track API requests across threads. Finally, we examine shutdown.rs. This provides cross-platform handlers for graceful node termination. Together, these seemingly disparate files define the environment. They define the limits. They define the operational safety boundaries of a Kinetic node.

Why Kinetic Needs This

A decentralized naming network like Kinetic operates in an inherently hostile environment. It is asynchronous. It is heterogeneous. These files address specific operational realities of that environment.

First, consider the necessity of constants.rs. A decentralized network only achieves consensus if every single participant agrees on the exact same rules. If one node thinks a DHT record lives for 2 hours, state will diverge. If another thinks it lives for 4 hours, the network will fracture. By hardcoding timing limits, we guarantee consistency. By hardcoding Proof-of-Work difficulty bits, we lock the security parameters. By hardcoding cryptographic key iterations, we standardize computation limits. By hardcoding database namespaces into a single constants file, we avoid collisions. We ensure that the compiled binary is locked to a specific protocol version. Furthermore, dynamically namespacing these constants using NETWORK_ID prevents disastrous mistakes. For example, it stops a user from accidentally pointing a Mainnet binary at a Testnet database directory.

Second, consider the security implications addressed by net.rs. Kinetic nodes often act as proxies or HTTP gateways. They fetch resources on behalf of decentralized applications. If a user can command a Kinetic node to fetch resources, they might act maliciously. A malicious user might instruct the node to fetch 127.0.0.1:1337. Or they might target 10.0.0.1:80. This is known as Server-Side Request Forgery (SSRF). The attacker could bypass external firewalls. They could access internal administrative panels. They could probe the node’s local network for vulnerabilities. Kinetic requires a watertight, zero-tolerance IP filtering mechanism. It must ensure it only ever proxies to legitimate, external, public-facing IP addresses.

Third, asynchronous tracing in Rust requires special handling. This is exactly why request_id.rs exists. Kinetic uses the Tokio asynchronous runtime. This means a single HTTP API request might jump across multiple operating system threads. Futures yield and resume based on I/O readiness. If an error occurs deep within a storage layer, the logs will be useless. They are useless if we cannot tie them back to the specific HTTP request that initiated the action. Passing a request_id down through every single function parameter signature is terrible. It would cause massive ergonomic damage to the codebase. Kinetic needs a way to transparently attach correlation IDs to execution contexts.

Finally, graceful shutdown via shutdown.rs is mandatory. It is mandatory for data integrity. If a node operator presses Ctrl+C, instantly killing the process is dangerous. The Sled database might be in the middle of writing a page to disk. Killing it could lead to corruption. The libp2p network swarm might drop connections suddenly. This happens without sending proper termination messages to peers. This causes routing disruptions across the DHT. Kinetic must intercept these termination signals. It must block the immediate exit. It must allow the asynchronous executor to gracefully spin down its tasks. It must flush its data to disk. It must bid farewell to its peers.

How It Works

The Protocol Rulebook: constants.rs

The constants.rs file acts as the immutable anchor for the protocol. It begins by dynamically injecting configuration generated by build.rs. This configuration comes from network.json. The macro include!(concat!(env!("OUT_DIR"), "/network_constants.rs")); does the heavy lifting. It pulls in values like the Top-Level Domain (TLD) and the NETWORK_ID at compile time. This ensures that the binary is permanently branded. It belongs to a specific network.

The file defines critical timing constraints for the Kademlia DHT. The constant KADEMLIA_PROVIDER_RECORD_TTL_SECS is set to 4 hours. That is 14,400 seconds. This dictates how long the decentralized network will remember a peer. It dictates how long it remembers that a specific peer hosts a specific name record. To complement this, KADEMLIA_PUBLICATION_INTERVAL_SECS is set to 3 hours. This creates a one-hour overlap window. During this window, a node republishes its records before they expire. This ensures high availability across the DHT.

Cryptographic limits are firmly established here as well. POW_DIFFICULTY_BITS is set to 15. This defines the amount of work required for certain network operations. Key derivation constraints are also hardcoded. WALLET_PBKDF2_ITERATIONS is set to 600,000. KEYGEN_PBKDF2_ITERATIONS is set to 2,048. These are hardcoded to balance security against brute-force attacks. They must also meet the practical performance needs of the application. The ARGON2_MEMORY_COST_KB is locked at 16,384. This equals 16MB. It fixes the memory hardness for specific hashing routines.

A crucial feature of constants.rs is strict namespacing. It namespaces storage and network channels. Variables like DB_PREFIX_OWNED_NAMES are constructed dynamically. DB_PREFIX_REVEAL is also constructed dynamically. GOSSIP_TOPIC_GOVERNANCE uses macros like concat!("n:", env!("KINETIC_NETWORK_ID"), "_owned_names"). This means that if NETWORK_ID is mainnet, the database prefix is n:mainnet_owned_names. If a node operator accidentally runs a Testnet binary against a Mainnet Sled database, it is safe. The binary will look for n:testnet_owned_names. It will find nothing. It will avoid corrupting the Mainnet data. It provides a robust, silent safeguard against cross-environment contamination.

Finally, the file houses the absolute root of trust for the network. These are the post-quantum ML-DSA-65 keys. The prod_keys module contains ROOT_PUBLIC_KEY_HEX. This is an air-gapped key used for signing governance proposals. To prevent accidental modification of this critical asset, we have a test. A dedicated unit test verifies the exact SHA-256 fingerprint of this hex string. If a developer accidentally deletes a character, the test fails. The test suite will instantly fail with a critical security alert.

SSRF Prevention: net.rs

The net.rs file implements a singular, paranoid security function. The function is is_ssrf_safe(ip: IpAddr). When a Kinetic proxy needs to forward traffic, it resolves the domain to an IP. It then passes the IP through this function.

For IPv4 addresses, the function inspects the properties of the address. It uses Rust’s standard library methods. It immediately rejects anything that evaluates to true for is_loopback(). This includes 127.0.0.1. It rejects is_unspecified(), like 0.0.0.0. It rejects is_private(), which covers the RFC 1918 spaces. These are 10.x.x.x, 172.16.x.x, and 192.168.x.x. It rejects is_link_local(). It rejects is_broadcast(). It rejects is_multicast(). It rejects is_documentation().

However, standard library checks are sometimes not enough. The function manually extracts the octets to check for edge cases. It blocks 0.0.0.0/8 by checking if the first octet is 0. It also checks for Carrier-Grade NAT (CGNAT) ranges. It ensures the IP does not fall into the 100.64.0.0/10 block. While CGNAT is technically public-facing for ISPs, it is dangerous. Routing to it internally can sometimes expose router administration interfaces. This makes it a significant SSRF risk.

For IPv6 addresses, the function is even more complex. This is due to the massive variety of IPv6 addressing modes. It blocks loopback. It blocks unspecified addresses. It blocks multicast addresses. It manually checks segments to block IPv6 Link-Local. Link-Local is fe80::/10. It blocks Unique Local addresses. Unique Local is fc00::/7.

Crucially, it checks for IPv4-mapped IPv6 addresses. An attacker might try to send an address like ::ffff:127.0.0.1. They do this to bypass the IPv4 loopback filter. The function calls v6.to_ipv4_mapped(). If it succeeds, it recursively calls is_ssrf_safe on the underlying IPv4 address. It also blocks the 64:ff9b::/96 NAT64 prefix. This prefix is used to translate IPv6 packets to IPv4. It presents another avenue to smuggle internal IPs through external proxies.

Asynchronous Tracing: request_id.rs

The request_id.rs file leverages a Tokio-specific macro. The macro is tokio::task_local!. It defines CURRENT_REQUEST_ID. Unlike std::thread_local!, which ties data to an operating system thread, this is different. task_local! ties data to a Tokio asynchronous task. This means that even if a future is suspended, the data is safe. It can be subsequently resumed on an different OS thread in the executor pool. The request_id remains intact.

The system relies on an AtomicU64 counter. It is initialized to 1. When a new API request enters the system, the scope function is called. It uses COUNTER.fetch_add(1, Ordering::Relaxed). This atomically increments the counter. It generates a string like "req-42".

The core magic happens in the line CURRENT_REQUEST_ID.scope(id, f).await. This takes the generated ID and a future f. It binds the ID to the execution context of that specific future. While that future runs, any nested function can access it. No matter how deep the function is, it can call current(). The current function attempts to read the task-local variable. It uses the try_with method. If successful, it clones the string and returns it. If it fails, it means the code is running outside of a wrapped scope. It safely degrades to returning "no-request-id". It does this instead of panicking.

There is also a scope_with_id function. This is designed for environments where an external correlation ID already exists. For example, if a client sends an X-Request-ID HTTP header. The Axum middleware can parse that header. It can pass it to scope_with_id. This ensures that the client’s original tracing ID is propagated. It propagates throughout the Kinetic node’s internal logs.

Graceful Termination: shutdown.rs

The shutdown.rs file provides the async function shutdown_signal(). This function abstracts away the underlying operating system differences. It provides a unified termination future.

On non-Wasm platforms, it sets up two distinct asynchronous listeners. This includes Linux, macOS, and Windows. The first is tokio::signal::ctrl_c(). This listens for the SIGINT signal. It is sent when a user presses Ctrl+C in the terminal. The second listener is specific to Unix platforms. It uses tokio::signal::unix::signal. It listens for SIGTERM. This is the standard signal sent by process managers. Systemd, Docker, or Kubernetes use this when they ask a container to shut down.

These two futures are placed inside a tokio::select! block. The select! macro races the futures against each other. Whichever signal resolves first will execute its corresponding block. It prints an informational log message. The message is "SIGTERM received, starting graceful shutdown". Then the overall shutdown_signal future will complete.

The main daemon loop typically tokio::select!s on its primary application logic. It races it against this shutdown_signal(). When the signal resolves, the main loop breaks. The application proceeds to execute its teardown code.

On WebAssembly, the environment is different. This is when target_arch = "wasm32". A browser tab does not have standard OS signals like SIGINT or SIGTERM. To keep the codebase uniform without littering it with cfg attributes, we adapt. The Wasm implementation of shutdown_signal() simply returns std::future::pending::<()>().await. This creates a future that never resolves. It ensures that the Wasm application loop continues indefinitely. It runs until the browser environment forcibly terminates the WebAssembly instance.

The Crate Root: lib.rs

The lib.rs file orchestrates the module structure. It uses #![deny(missing_docs)]. This enforces strict documentation standards across the entire crate.

It handles conditional compilation gracefully. kinetic-core is meant to be shared across multiple binaries. This includes the daemon, the RPC proxy, and lightweight WebAssembly clients. It must compile safely everywhere. Modules like api_error and request_id are gated. They use #[cfg(not(target_arch = "wasm32"))]. This is because a Wasm client running in a browser does not need them. It does not handle Axum HTTP requests. It does not generate server-side request IDs.

The crate root also serves as a facade for error handling. It prevents forcing developers to import kinetic_core::error::KineticError manually. Instead, it re-exports the primary error taxonomy directly at the crate root. This provides a clean, ergonomic API surface. It benefits the rest of the Kinetic ecosystem.

Key Pieces

constants::POW_DIFFICULTY_BITS

-> See: kinetic-core/src/constants.rs — Line 27 This constant defines the required number of leading zero bits. It is used for Proof-of-Work operations in the Kinetic network. It ensures that operations requiring spam resistance demand a consistent amount of computational effort. It is verifiable from all participants.

constants::KADEMLIA_PROVIDER_RECORD_TTL_SECS

-> See: kinetic-core/src/constants.rs — Lines 62 to 63 Sets the Time-To-Live for DHT provider records. It is set to 4 hours. This is a critical consensus parameter. If nodes do not agree on how long a record should live, they will prematurely drop data. Or they might hold onto stale data, breaking name resolution routing.

constants::KADEMLIA_PUBLICATION_INTERVAL_SECS

-> See: kinetic-core/src/constants.rs — Lines 65 to 66 Sets the republish interval for DHT records. It is set to 3 hours. This ensures a one-hour overlap where nodes can refresh their records.

constants::DB_PREFIX_OWNED_NAMES

-> See: kinetic-core/src/constants.rs — Lines 74 to 75 A dynamic database prefix. It is constructed at compile time using the NETWORK_ID. By injecting the network identifier into the byte array, it prevents collisions. It guarantees that the Sled database engine creates separate namespaces. It prevents state corruption if a single directory is accidentally shared between networks.

constants::ENV_DATA_DIR

-> See: kinetic-core/src/constants.rs — Lines 102 to 103 Constructs the environment variable string. It is used to override the data directory. For example, it generates KINETIC_MAINNET_DATA_DIR. This dynamic naming allows users to run multiple environments on the same machine.

constants::prod_keys::ROOT_PUBLIC_KEY_HEX

-> See: kinetic-core/src/constants.rs — Lines 41 to 42 The offline, air-gapped ML-DSA-65 post-quantum root of trust key. This hex string is the ultimate authority for governance proposals. It is protected by a dedicated SHA-256 fingerprint test. The test is in the same file to prevent accidental tampering.

net::is_ssrf_safe

-> See: kinetic-core/src/net.rs — Lines 23 to 90 The impenetrable firewall function for outgoing IP connections. It comprehensively blocks loopback. It blocks private RFC 1918 addresses. It blocks CGNAT, link-local, and multicast. It handles complex IPv6 edge cases like IPv4-mapped addresses. It ensures the node only connects to legitimate public infrastructure.

request_id::scope

-> See: kinetic-core/src/request_id.rs — Lines 21 to 25 The entry point for generating task-local correlation IDs. It atomically increments a counter. It generates a string like "req-N". It uses CURRENT_REQUEST_ID.scope(id, f) to bind that string. It binds it to the asynchronous execution context of the provided future.

request_id::current

-> See: kinetic-core/src/request_id.rs — Lines 14 to 19 Retrieves the correlation ID for the current asynchronous task. If the code is running outside of a wrapped scope, it handles it safely. It safely returns "no-request-id" instead of panicking. This ensures that logging macros never crash the application.

shutdown::shutdown_signal

-> See: kinetic-core/src/shutdown.rs — Lines 8 to 35 A cross-platform future. It resolves when the operating system issues a termination request. This could be SIGINT or SIGTERM. It allows the main executor loop to unblock. It begins the graceful teardown of network sockets and database handles.

lib.rs Root Re-exports

-> See: kinetic-core/src/lib.rs — Lines 67 to 70 The crate root re-exports KineticError and PublishError. It also exports RecordRejectReason, and ResolutionError. This provides a flattened, ergonomic API surface. It allows dependent crates to simply use kinetic_core::KineticError;. They do not need to traverse deep module paths.

How This Connects to the Rest of Kinetic

  • FORWARD DEPENDENCY: The HTTP daemons in kinetic-node or kinetic-daemon will rely on request_id::scope_with_id. Their Axum middleware will parse incoming headers. They establish the scope. They pass the future to the handler, ensuring all subsequent logging is traceable.

  • CROSS-CRATE: The net::is_ssrf_safe function is a critical dependency for kinetic-rpc. It is used for any proxy subsystems. Whenever the network resolves a .kin domain to a backend IP and attempts to open a socket, it is checked. It must pass the resolved IP through this gatekeeper first. This prevents malicious routing into the node’s internal network.

  • CROSS-CRATE: The storage engines using the Sled database depend on constants.rs. They use it to structure their disk state. They use the DB_PREFIX_* constants to open specific Sled trees. The dynamic injection of NETWORK_ID into these prefixes is what prevents catastrophic database collisions. It protects Mainnet from Testnet nodes.

  • CROSS-CRATE: The governance verification logic located in kinetic-core/src/governance/logic.rs imports the keys. It imports constants::prod_keys::ROOT_PUBLIC_KEY_HEX directly. It parses this hex string into a cryptographic public key. It uses it to verify the signatures attached to incoming network parameter updates.

  • FORWARD DEPENDENCY: The main entry point (main.rs) of any Kinetic binary running on a traditional OS will utilize this. It will utilize tokio::select! to race its primary application logic against shutdown::shutdown_signal(). This is what triggers the orderly shutdown of the P2P swarm. It triggers the flushing of the Sled database.

Quick Reference

  • Need to change how long records live in the DHT? Modify KADEMLIA_PROVIDER_RECORD_TTL_SECS in constants.rs. Ensure you understand the consensus implications before doing so.

  • Writing a new feature that connects to an external IP? You must validate the target by running it through net::is_ssrf_safe(ip). Do this before establishing the TCP connection.

  • Adding deep nested logs in an async function? Ensure that the log message includes a call to request_id::current(). This ensures that the log can be correlated back to the originating HTTP request.

  • Why isn’t my Wasm client responding to Ctrl+C? WebAssembly does not have OS signals. The shutdown_signal() function in Wasm simply returns a pending future. This future never resolves. Wasm clients are terminated by the browser environment.

  • Why is the root key test failing? If the unit test in constants.rs fails, it means the ROOT_PUBLIC_KEY_HEX was altered. Its SHA-256 fingerprint no longer matches. If this was an authorized key rotation, update the fingerprint in the test. If not, revert the change immediately.

Open Questions / Things to Revisit

  • IPv6 SSRF Coverage in net.rs: While is_ssrf_safe handles IPv4-mapped addresses and NAT64, IPv6 has numerous obscure transition mechanisms. This includes tunneling mechanisms like Teredo, 6to4, or ISATAP. Could an attacker use one of these obscure formats to trick the kernel? Could they route a seemingly safe IPv6 address into a private IPv4 space? We may need to expand the blacklist to cover more legacy IPv6 tunneling prefixes.

  • Key Fingerprint Tests in constants.rs: We currently test the SHA-256 fingerprint of the production root key. This is to prevent accidental changes. We should strongly consider adding a similar fingerprint test for test_keys::ROOT_PUBLIC_KEY_HEX. While less critical, accidentally swapping the test key could cause confusing failures. It would break tests in the CI pipeline unexpectedly.

  • Correlation ID Generation in request_id.rs: The system currently uses an AtomicU64 to generate IDs like "req-42". While this is fast and will effectively never overflow, it provides limited value. It is limited in a distributed tracing scenario where a request spans multiple nodes. We should evaluate whether migrating to UUID v4 generation would be worth it. It might have a minor performance penalty to gain true distributed observability.

  • Signal Handling Robustness in shutdown.rs: Currently, shutdown_signal listens for the first SIGINT or SIGTERM. It then resolves. If the shutdown process hangs due to a deadlocked future, the node operator might press Ctrl+C a second time. They would expect it to force quit. We might need to implement a mechanism where a second signal bypasses the graceful shutdown. It should trigger an immediate std::process::exit(1).