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

Kinetic Error Taxonomy and HTTP Serialization

Crate: kinetic-core Stage: 9 Reading Time: 20 minutes Depends On: thiserror, serde, RFC 7807

What Is This?

In the Kinetic network, errors are not just arbitrary text strings. They are not raw operating system numbers or generic panics. Instead, they are highly structured, rigidly defined data payloads. The files kinetic-core/src/error.rs and kinetic-core/src/api_error.rs define this system. Together, they form a unified error taxonomy for the entire node architecture. They map deep internal domain failures into stable, recognizable protocol codes. For example, a mathematically invalid VDF proof is not just a “bad math” string. It becomes a structured entity with a specific HTTP status and retry logic. A rejected DHT record is translated into an RFC 7807-compliant HTTP JSON response. This system acts as a translation layer spanning the entire codebase. It guarantees that whether a failure happens in the P2P network layer, it is caught. If it happens in the Sled local storage engine, it is properly categorized. If it occurs inside the cryptographic verifier, it is safely wrapped. All errors are annotated with retry logic. They are presented to end-users over REST APIs safely. Crucially, they never leak sensitive internal execution details. They never leak raw Rust stack panics to external HTTP clients. This protects both the security and the usability of the daemon.

Why Kinetic Needs This

When building a decentralized P2P network node like Kinetic, error handling is critical. An error is rarely meant just for the local developer’s terminal console. Errors in Kinetic cross multiple complex boundaries constantly.

First, consider the User Boundary. When a user registers a name using the Kinetic command line interface (CLI). They absolutely do not want to see a Rust stack trace on their screen. They need to know immediately if they should expect a “Name Already Owned” error. Or, conversely, they need a clear “Wait 5 minutes and retry” warning. The error system must provide strings that are clean, non-technical, and actionable.

Second, consider the Network Boundary. When an HTTP RPC client queries the daemon, standard JSON payloads are mandatory. We cannot emit raw Rust panics over an HTTP socket. We cannot emit generic std::io::Error messages either. RFC 7807 standardizes exactly how problem details should look on the web. Kinetic must adhere to this standard so that web dashboards and frontend apps work.

Third, consider the Operational Boundary. The daemon constantly writes logs to disk or stdout. In a P2P network, a failure to parse a DHT record from a peer is common. It might just be an Info level event representing a noisy, misconfigured peer. However, a corrupted identity key file on disk is a vastly different scenario. That is a Critical event requiring immediate alerts and a forced node shutdown. The error system itself must dictate this operational severity. It cannot be left up to the developer to guess the log level at the call site.

Finally, consider the Support Boundary. We need stable, easily greppable error codes. Codes like KIN-RES-001 allow developers and end users to look up documentation. They identify specific failures independent of changes to the English error messages. If we change the text in a future release, the code remains the same. This prevents breaking automated tooling that regex-matches errors.

Without a strict error taxonomy, inconsistencies arise. Ad-hoc wrapping with libraries like anyhow can lead to inconsistent HTTP status codes, often defaulting to a generic HTTP 500 Internal Server Error.

How It Works

The error handling strategy in Kinetic is intentionally split into two distinct halves. The internal Rust taxonomy is defined in error.rs. The external HTTP API serialization is defined in api_error.rs.

The Domain Error Abstraction

Inside kinetic-core and its sibling crates, each operational subsystem defines errors. They use the thiserror crate to generate standard std::error::Error traits. -> See: kinetic-core/src/error.rs — Lines 37 to 55.

You will see submodules for dht, dns, drand, governance, and identity. You will also see modules for names, network, storage, and vdf. Each of these specialized domain errors is required to implement a rich metadata interface. Instead of just returning a single string message, an error type in Kinetic must provide six things:

  1. Stable Protocol Code: A static string like KIN-RES-001. This uniquely identifies the exact failure mode across all versions of the software. It is never dynamically generated.

  2. RFC 7807 Type URI: A web URI, usually pointing to kinetic.network/errors/. This points to external, hosted documentation explaining the error in deep detail.

  3. Retryability Flag: A simple boolean value. This indicates if the client can safely retry the exact same operation. A network timeout is retryable. An invalid cryptographic signature is definitely not.

  4. Severity Classifier: An enum dictating the tracing logging level. This ensures the log writer does not have to guess the severity.

  5. User Message: A clean, non-technical string intended for UI display. This is what the CLI or the web frontend will actually render to the human.

  6. Developer Details: A serde_json::Value payload. This contains exact diagnostic variables, state transitions, or invalid input dumps. It is meant for the developer reading the network payload payload.

The Unified Catch-All: KineticError

For operations that cross subsystem boundaries, we need a wrapper. For example, the core daemon kernel executing a complex flow might hit network or disk errors. Kinetic provides a unified enum called KineticError for this. -> See: kinetic-core/src/error.rs — Lines 79 to 158.

This massive enum wraps lower-level errors. It also defines generic failures like InvalidVdfProof or CommitmentMismatch. -> See RUST_CONCEPTS.md for an explanation of how thiserror’s #[from] attribute allows the Rust compiler to automatically convert inner errors, keeping business logic clean.

Severity and Operational Routing

The Severity enum is crucial for the internal logging and tracing pipeline. -> See: kinetic-core/src/error.rs — Lines 178 to 197.

It defines four distinct levels of operational impact.

  • Info: Represents normal protocol outcomes. If a DHT query finds no results, it is not a node failure. It is just reality; the name does not exist. The node functioned correctly.
  • Warning: Represents transient issues. This includes things like rate limits, temporary peer disconnects, or slow query responses.
  • Error: Represents unexpected failures requiring investigation. An example is a mathematically invalid VDF proof being sent by a peer. The peer is misbehaving, or our math is wrong.
  • Critical: Represents node-breaking states. This includes a missing identity key or local database corruption that prevents boot. The node must crash or page an operator immediately.

Crossing the HTTP Boundary: api_error.rs

When a Kinetic internal error needs to be returned to an RPC client, it must be serialized. It must cross the network boundary as JSON over HTTP. -> See: kinetic-core/src/api_error.rs — Lines 13 to 37.

The ApiError struct defines this standard payload format. It uses serde macros to serialize itself cleanly into JSON. RFC 7807 dictates that the error type field must be named exactly type. However, type is a reserved language keyword in Rust. To bypass this, the struct uses the attribute #[serde(rename = "type")] on the error_type field.

Kinetic extends the base RFC 7807 spec with custom JSON fields. These extensions are: code, retryable, details, and request_id.

To prevent massive, noisy payloads full of empty JSON objects, we use skipping. The #[serde(skip_serializing_if = ...)] macro is used heavily on fields like instance and details. If there is no specific instance URI, the key is omitted. If there are no extra developer details attached to the error, the details key is omitted entirely. This keeps the HTTP payload lightweight and readable.

The Conversion Boilerplate (From<T> for ApiError)

The bulk of api_error.rs consists of manual trait implementation blocks. These blocks convert internal domain errors into the universal ApiError struct. -> See: kinetic-core/src/api_error.rs — Lines 50 to 323.

For every domain error in the system, there is an impl From<DomainError> for ApiError. This includes ResolutionError, PublishError, DrandError, and many others. Inside these implementations, a robust match statement evaluates the specific enum variant. It then assigns a correct HTTP status code and a standard, human-readable title.

This architecture ensures that the HTTP API routing layer is ignorant. The Axum handlers never have to think about how to translate a specific system failure. The mapping logic is encapsulated centrally and safely in this exact file. If we want to change a status code, we do it here, and it applies globally.

Task-Local Request Tracing

Notice that every single ApiError conversion calls the current_request_id() function. -> See: kinetic-core/src/api_error.rs — Lines 46 to 48.

This function fetches a unique correlation ID from a task-local variable. This is likely managed by the tokio runtime and the tracing instrumentation crate. This unique ID is injected directly into the HTTP JSON response payload. When an API client receives an error with request_id: "req-12345". They can provide that exact string to the node operator. The operator can then grep their internal server logs for req-12345. They will instantly find the exact stack trace, debug variables, and timing context. This drastically reduces debugging time in a distributed system environment.

Key Pieces

KineticError

What it does: The top-level, catch-all error enum. It wraps core operations spanning multiple internal subsystems. Where it lives: kinetic-core/src/error.rs — Lines 79-158. Why it matters: It acts as the canonical return type for high-level daemon kernel functions. A core flow returns Result<(), KineticError>. Instead of forcing generic boundaries to explicitly depend on 15 different subsystem enums. They depend on this one wrapper, significantly simplifying function signatures. It drastically reduces module coupling across the crate.

Severity

What it does: Classifies the operational impact and logging necessity of an error. Where it lives: kinetic-core/src/error.rs — Lines 178-197. Why it matters: It prevents log spam in production. Decentralized, chaotic P2P networks produce a massive amount of expected failures. Peers go offline mid-stream constantly. If every offline peer logged a red Error to stdout, the logs would be useless. The node operator would quickly ignore them. Severity allows the node to downgrade structural network noise to Warning or Info. It reserves the Error and Critical levels for actual internal bugs or security issues.

ApiError

What it does: The RFC 7807 compliant struct. It is safely serialized into JSON for HTTP responses to external API clients. Where it lives: kinetic-core/src/api_error.rs — Lines 14-37. Why it matters: It ensures absolute consistency across the daemon. Every REST or JSON-RPC endpoint responds with exactly the same shape of JSON on failure. API consumers, like web dashboards or mobile apps, can write a single global parser. They can rely on it entirely without writing endpoint-specific error handling.

From<PublishError> for ApiError

What it does: Maps distributed hash table publishing failures into standard HTTP concepts. Where it lives: kinetic-core/src/api_error.rs — Lines 76-98. Why it matters: It perfectly demonstrates internal-to-external domain translation. If a user tries to publish a name that they do not cryptographically own. The subsystem raises PublishError::AlreadyOwned. This trait implementation correctly translates that variant into HTTP 409 Conflict. It communicates to the HTTP client exactly why their network request was rejected. It does this without needing to explain DHT semantics to the web client.

From<NetworkClientError> for ApiError

What it does: Translates low-level libp2p and socket networking errors into HTTP concepts. Where it lives: kinetic-core/src/api_error.rs — Lines 152-179. Why it matters: P2P networks have highly specific failure modes. These include empty routing tables (RoutingTableEmpty) or dropped multiplexed streams. This block safely maps these internal networking realities to standard HTTP codes. It uses 503 Service Unavailable and 504 Gateway Timeout responses. This allows traditional web tooling, like reverse proxies and load balancers, to understand the failure state natively.

From<RegistrationError> for ApiError

What it does: Handles the complex two-phase name registration lifecycle errors. Where it lives: kinetic-core/src/api_error.rs — Lines 100-123. Why it matters: Registration is stateful and complex. If a user tries to register a name they are already in the middle of registering, it throws AlreadyInProgress. This maps to HTTP 409 Conflict. If the VDF computation fails mid-registration, it throws VdfFailed, mapping to HTTP 500. This safely translates asynchronous state machine failures into synchronous HTTP responses.

From<GovernanceError> for ApiError

What it does: Translates council governance, voting, and timelock parameter failures. Where it lives: kinetic-core/src/api_error.rs — Lines 125-150. Why it matters: Governance rules are strict. A StaleProposal or TimelockNotExpired correctly maps to 409 Conflict. An InsufficientSignatures variant correctly maps to 401 Unauthorized. This enforces strict security semantics over standard REST APIs, making authorization boundaries clear.

From<StorageError> for ApiError

What it does: Maps sled local database corruption or lock failures. Where it lives: kinetic-core/src/api_error.rs — Lines 181-200. Why it matters: Storage errors are almost always fatal or critical. DatabaseLocked yields a 423 Locked HTTP status. Corruption yields a 500 Internal Server Error. This signals to the API consumer that the node itself is unhealthy, not just the specific request.

From<VdfError> for ApiError

What it does: Translates verifiable delay function computation and proof errors. Where it lives: kinetic-core/src/api_error.rs — Lines 202-226. Why it matters: VDFs are computationally heavy and require specific hardware. UnsupportedPlatform maps to 501 Not Implemented. InvalidProof maps to 400 Bad Request. This clearly delineates between “my node cannot run this” and “your math is wrong.”

From<DrandError> for ApiError

What it does: Translates Drand distributed randomness beacon failures. Where it lives: kinetic-core/src/api_error.rs — Lines 228-252. Why it matters: Kinetic relies on Drand for unbiased entropy. If the daemon cannot reach Drand HTTP endpoints, it throws AllEndpointsFailed. This maps cleanly to a 502 Bad Gateway, accurately representing an upstream dependency failure. NoCachedKyn maps to 404 Not Found.

From<DnsError> for ApiError

What it does: Translates traditional DNS zone file parsing and structural failures. Where it lives: kinetic-core/src/api_error.rs — Lines 254-281. Why it matters: Parsing text records is prone to user error. Every single variant in this enum, from NestedTooDeeply to TxtRecordTooLong, maps to 400 Bad Request. They represent deterministic validation failures based on bad user input.

From<IdentityError> for ApiError

What it does: Translates node cryptographic identity and seed phrase issues. Where it lives: kinetic-core/src/api_error.rs — Lines 283-305. Why it matters: If the node’s private key file is corrupted on disk, CorruptedIdentityFile maps to a 500. If the user provides an invalid BIP-39 seed phrase, it maps to a 400. This securely handles sensitive material operations without leaking the key material itself.

From<NamesError> for ApiError

What it does: Maps pure name validation logic failures. Where it lives: kinetic-core/src/api_error.rs — Lines 307-323. Why it matters: Names must follow strict regex and length limits. Any failure here is immediately mapped to a 400 Bad Request. It is entirely deterministic and requires the client to fix their spelling or formatting.

Detailed Variant Mappings

The following section explicitly defines the internal Rust enum variant to HTTP Status Code mappings logic embedded within api_error.rs. Understanding these mappings is critical for writing robust HTTP client integrations.

ResolutionError Variants

  • Offline: The local node is disconnected from the P2P swarm. Maps strictly to HTTP 503.
  • NotFound: The requested name does not exist in the DHT routing tables. Maps strictly to HTTP 404.
  • VdfVerificationFailed: The cryptography provided by a remote peer is invalid. Maps strictly to HTTP 422.
  • Expired: The name exists in the store, but its registration timelock is stale. Maps strictly to HTTP 410 (Gone).
  • Timeout: The DHT routing query took too long to complete. Maps strictly to HTTP 504.
  • Internal: An unexpected internal memory panic occurred during resolution. Maps strictly to HTTP 500.

PublishError Variants

  • Offline: The local node is disconnected and cannot broadcast. Maps strictly to HTTP 503.
  • InvalidProof: The user-provided VDF proof was explicitly malformed before sending. Maps strictly to HTTP 400.
  • AlreadyOwned: Another cryptographic key already owns this specific name. Maps strictly to HTTP 409.
  • AllFailed: The DHT propagation algorithm failed to reach any responsive peers. Maps strictly to HTTP 503.
  • Rejected: The network peers actively rejected the payload due to validation rules. Maps strictly to HTTP 422.
  • Internal: A local memory or state failure occurred before broadcast. Maps strictly to HTTP 500.

RegistrationError Variants

  • InvalidName: The requested name string failed regex or length validation. Maps strictly to HTTP 400.
  • VdfFailed: The local background VDF generation engine crashed. Maps strictly to HTTP 500.
  • CommitmentMismatch: The phase-two reveal payload does not match the phase-one commit hash. Maps strictly to HTTP 422.
  • AlreadyOwned: The user lost the registration race condition to another peer. Maps strictly to HTTP 409.
  • AlreadyInProgress: A state machine conflict occurred; the user is already registering this name. Maps strictly to HTTP 409.
  • NetworkRejected: The P2P peers refused the initial phase-one commit payload. Maps strictly to HTTP 422.
  • Internal: A local state machine memory failure occurred. Maps strictly to HTTP 500.

GovernanceError Variants

  • MissingRootKey: The node configuration is missing the mandatory governance public key. Maps strictly to HTTP 500.
  • StaleProposal: The provided governance proposal ID is too old or already executed. Maps strictly to HTTP 409.
  • TimelockNotExpired: The voting action was attempted too early in the lifecycle. Maps strictly to HTTP 409.
  • NotPendingOrVetoed: A state conflict occurred regarding the proposal status. Maps strictly to HTTP 409.
  • InsufficientSignatures: The council quorum math was not met for this execution. Maps strictly to HTTP 401.
  • GovernanceDisabled: The local node explicitly opted out of governance participation via config. Maps strictly to HTTP 403.
  • KeyLengthMismatch: The provided key schema is structurally invalid. Maps strictly to HTTP 400.
  • InvalidPremiumNameLength: The network parameter update violates core rules. Maps strictly to HTTP 400.
  • InvalidInfrastructureName: The requested parameter violates internal rules. Maps strictly to HTTP 400.

NetworkClientError Variants

  • Timeout: A remote peer did not respond within the deadline. Maps strictly to HTTP 504.
  • StreamDropped: The multiplexed libp2p stream unexpectedly disconnected mid-flight. Maps strictly to HTTP 504.
  • Offline: The local node has zero active peer connections. Maps strictly to HTTP 503.
  • RoutingTableEmpty: The Kademlia routing table is currently blank. Maps strictly to HTTP 503.
  • ChannelClosed: The internal tokio mpsc async channel was dropped. Maps strictly to HTTP 500.
  • StoreError: A wrapped local DHT store exception occurred. Maps strictly to HTTP 500.
  • UnsupportedProtocol: The remote peer does not speak the correct version of Kinetic. Maps strictly to HTTP 501.
  • GossipSubError: The mesh routing broadcast experienced a failure. Maps strictly to HTTP 502.
  • Other: A catch-all for deeply generic networking exceptions. Maps strictly to HTTP 500.

StorageError Variants

  • DatabaseLocked: The Sled lock file is currently held by another background process. Maps strictly to HTTP 423.
  • Corruption: The Sled disk checksum failed during a read operation. Maps strictly to HTTP 500.
  • OperationFailed: A generic disk IO or flushing operation failed. Maps strictly to HTTP 500.

VdfError Variants

  • LockFileError: The chiavdf classgroup engine lock file logic failed. Maps strictly to HTTP 503.
  • LockAcquireError: A local threading lock acquisition timed out. Maps strictly to HTTP 503.
  • DiscriminantError: The mathematical initialization for the VDF failed. Maps strictly to HTTP 500.
  • ProofGenerationError: The chiavdf underlying C++ engine crashed. Maps strictly to HTTP 500.
  • UnsupportedPlatform: The operating system or CPU architecture cannot run ChiaVDF. Maps strictly to HTTP 501.
  • InvalidProof: An externally provided proof string is structurally bad. Maps strictly to HTTP 400.

DrandError Variants

  • AllEndpointsFailed: The node cannot reach any League of Entropy HTTP endpoints. Maps strictly to HTTP 502.
  • Network: A low-level TCP socket failure occurred during fetch. Maps strictly to HTTP 502.
  • Reqwest: The internal HTTP client library experienced a failure. Maps strictly to HTTP 502.
  • HttpError: The upstream Drand server returned a specific error code. Maps exactly to that returned code.
  • NoCachedKyn: The background synchronizer task hasn’t fetched the first kyn yet. Maps strictly to HTTP 404.
  • Serde: The upstream Drand JSON payload schema unexpectedly changed. Maps strictly to HTTP 500.
  • Storage: Writing the downloaded kyn to the local cache failed. Maps strictly to HTTP 500.
  • InvalidSignature: The upstream BLS signature is fake or tampered with. Maps strictly to HTTP 422.
  • StaleKyn: The downloaded network kyn is older than our required target sequence. Maps strictly to HTTP 400.

DnsError Variants

  • NestedTooDeeply: The CNAME recursion depth exceeded safety limits. Maps strictly to HTTP 400.
  • ParseError: The provided text zone file syntax is badly formed. Maps strictly to HTTP 400.
  • TooManyRecords: The zone file exceeds maximum allowed resource records. Maps strictly to HTTP 400.
  • InvalidLabelLength: A specific domain label exceeds the standard 63 character limit. Maps strictly to HTTP 400.
  • InvalidLabelCharacters: A specific domain label contains non-LDH characters. Maps strictly to HTTP 400.
  • InvalidCnameConfiguration: The zone violates APEX CNAME exclusitivity rules. Maps strictly to HTTP 400.
  • TxtRecordTooLong: A specific TXT string exceeds the 255 character limit. Maps strictly to HTTP 400.
  • InvalidCnameTarget: The target of the CNAME alias is malformed. Maps strictly to HTTP 400.
  • InvalidPeerId: A _peer TXT record does not parse as a valid libp2p multiaddr. Maps strictly to HTTP 400.
  • InvalidKid: A _kid TXT record does not parse as a valid cryptographic key. Maps strictly to HTTP 400.
  • InvalidIpfsCid: An _ipfs TXT record does not parse as a valid base58 CID. Maps strictly to HTTP 400.

IdentityError Variants

  • Io: Reading the identity file from disk failed due to permissions or IO. Maps strictly to HTTP 500.
  • CorruptedIdentityFile: The file exists but the protobuf parsing failed. Maps strictly to HTTP 500.
  • IdentityNotFound: The node has not been initialized with kinetic init yet. Maps strictly to HTTP 404.
  • InvalidSeedPhrase: The user provided an invalid BIP-39 mnemonic string. Maps strictly to HTTP 400.
  • DecryptionFailed: The user provided the wrong password for their encrypted identity. Maps strictly to HTTP 401.

NamesError Variants

  • All validation failures map strictly to HTTP 400.
  • This includes regex failures, illegal unicode characters, length boundary violations, and reserved namespace conflicts.

How This Connects to the Rest of Kinetic

This error taxonomy is the central nervous system connecting disjointed parts of the daemon.

FORWARD DEPENDENCY: kinetic-rpc The HTTP server implementation, likely utilizing Axum or a similar framework, will depend heavily on ApiError. When an RPC handler executes business logic and returns an internal Result<T, DomainError>. The framework will leverage the ? operator or the IntoResponse traits. These will automatically convert the DomainError into an ApiError. It will serialize it to JSON, and correctly inject the HTTP response status code headers.

CROSS-CRATE: kinetic-network & kinetic-daemon These crates contain the actual runtime execution logic that produces the errors. For instance, the background worker module that mathematically verifies a VDF proof will return VdfError::InvalidProof. The kinetic-core crate is strictly responsible for defining the error interface and the translation mechanics. However, the actual instantiation and raising of the errors happen at the edge of the network or storage bounds in these other crates.

CROSS-CRATE: kinetic-cli The standalone command-line utility will ingest and parse the ApiError JSON returned by the daemon. Because the retryable boolean and protocol code fields are always guaranteed to be present. The CLI can automatically implement robust, silent retry loops with exponential backoff if retryable == true. This creates a seamless, resilient user experience without forcing the user to manually retype commands.

Quick Reference

Standard Error Code Prefixes

When debugging Kinetic stdout logs or analyzing HTTP API responses, use this prefix guide. It will instantly locate the subsystem that triggered the failure:

  • KIN-RES-*: Name Resolution issues. This includes DHT lookups, parsing, or encountering expired names in the store.
  • KIN-PUB-*: Name Publishing issues. This includes putting data to the DHT, or peer proof rejection.
  • KIN-REG-*: Name Registration lifecycle issues. This includes the two-phase commit and reveal process.
  • KIN-VDF-*: Verifiable Delay Function issues. This covers both proof generation engines and verifier math failures.
  • KIN-GOV-*: Council governance issues. This covers parameter updates, stale proposals, and timelocks.
  • KIN-DNS-*: Traditional DNS issues. This covers zone rendering, TXT parsing, or detecting CNAME loops.
  • KIN-DRA-*: Drand network issues. This covers HTTP acquisition of the Quicknet beacon kyns.
  • KIN-IDN-*: Cryptographic identity issues. This covers node key management or seed phrase hydration.
  • KIN-NAM-*: Name validation issues. This covers regex failures, bad characters, and length bound violations.
  • KIN-STO-*: Sled on-disk storage issues. This covers local database corruption, file locking errors, or disk I/O.
  • KIN-NET-*: P2P networking issues. This covers libp2p routing tables and gossipsub mesh broadcasting.

HTTP Status Code Mappings (Common)

The translation layer strictly adheres to the following mapping philosophy:

  • 400 Bad Request: Deterministic validation failures. Examples include NamesError, InvalidVdfProof. The client sent structurally malformed data.
  • 401 Unauthorized: Security failures. Examples include bad cryptographic signatures on governance proposals, or identity decryption failures.
  • 404 Not Found: Missing resources. Examples include a name not existing in the DHT, or a Drand kyn missing from the network cache.
  • 409 Conflict: State collisions. Examples include a name registration collision in progress, or attempting to vote on stale/expired governance proposals.
  • 422 Unprocessable Entity: Complex cryptographic checks failed. Examples include VDF math, Ed25519 signatures, or reveal commitments. The JSON structure was valid, but the cryptography was wrong.
  • 500 Internal Server Error: Fatal local failures. Examples include Sled local database corruption on disk, or internal engine panics that should never occur.
  • 503 Service Unavailable: Network isolation. Examples include the node being completely offline, or the P2P Kademlia routing table currently being empty.
  • 504 Gateway Timeout: Upstream delays. Examples include a DHT remote peer query taking too long to return verifiable data over libp2p streams.

Open Questions / Things to Revisit

  • Task-Local IDs: The current_request_id() function depends heavily on crate::request_id::current(). What exactly happens if an internal error is converted to an ApiError completely outside the context of an active async trace span? For example, what happens during the synchronous boot process before tokio starts? Does the function panic, or does it gracefully return a generic fallback string like “system-boot”? This needs to be verified to prevent startup crashes.

  • Developer Details Omission: Currently, several manual conversions hardcode details: serde_json::Value::Null. This happens in DnsError and IdentityError, for instance. This practice leaves extremely valuable, context-rich debugging data out of the payload. We should systematically revisit these specific conversions. We should serialize inner state parameters instead. For example, we should include which specific DNS CID string failed validation. We should also include the exact absolute path of the corrupted identity file on disk.

  • Sled Storage Coupling: The KineticError::StorageError variant wraps a generic String. This is done because we want to actively avoid exposing the specific sled crate error types across the API boundary. However, if we switch storage engines in the very near future, perhaps to RocksDB or SQLite. This generic string format might not provide enough deeply structured data for automated recovery tooling to fix the database automatically.

  • HTTP 422 vs HTTP 400: Cryptographic verification failures are currently mapped strictly to 422 Unprocessable Entity. While this is academically and semantically accurate according to RFCs. Many external web client frontend libraries and frameworks handle the standard 400 Bad Request much better for validation issues. We should carefully monitor how external API consumers react to these 422 responses in the wild. We may need to adjust this mapping back to 400 if it consistently breaks frontend error handling logic.

  • Tracing Overhead: The heavy use of request_id::current() on every single error conversion implies a span lookup. If a high-throughput endpoint, like a DHT gossip flood, generates thousands of Info level errors per second. Does the task-local span lookup introduce measurable CPU overhead into the serialization path? We may need to benchmark the From<T> for ApiError trait implementations under heavy load to ensure they do not become a bottleneck.