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

Error codes

Every validation and execution failure carries a stable machine-readable code in addition to a human-readable message. Downstream consumers branch on the code, not on the message text: crucible-scenarios asserts that a transfer from an unregistered account fails with not_registered, and the regression suite pins the exact code a fixed bug used to produce.

Two properties this document exists to guarantee:

  • Every code the code can return is listed here. A consumer can look up any code it observes without reading this repository’s source.
  • Every code listed here exists. A stale table is worse than no table, because consumers trust it.

Both are enforced by scripts/check-error-codes.py, which extracts the codes returned by every code() method and compares them against the tables below. It runs in CI, so documentation drift is a build failure rather than something a reader discovers.

Why strings, not numbers

Codes are short snake_case strings rather than numeric constants such as E1042. A string is self-describing in a log line, a fixture, or a JUnit report, and it survives a reordering of the enum it comes from — with numbers, adding a variant in the middle of an enum silently renumbers every code after it. The trade is that codes are not compact; that has not mattered here.

The stability contract

A code is a public interface. Treat it as one:

  • Never rename a code. Renaming breaks every consumer’s match. If a code’s meaning has to change, add a new code and keep the old one until consumers migrate.
  • Never reuse a retired code for a different condition.
  • Adding a new code is not a breaking change; consumers should treat an unrecognised code as “some failure I do not specifically handle” rather than as a panic or an unreachable state.

Codes

Core validation

Raised while checking that an operation is well-formed, before any state is touched. A failure here means the request itself is wrong.

CodeCategoryRetryableRaised when
invalid_operationvalidationnoThe operation is malformed or structurally invalid.
invalid_accountvalidationnoA referenced account does not exist.
invalid_tokenvalidationnoA referenced token does not exist.
zero_amountvalidationnoAn amount is present but not strictly positive.

Authorization

The operation is well-formed and may be cryptographically valid, yet the actor is not entitled to perform it. Kept distinct from validation on purpose: the fix is a different actor, not a different request.

CodeCategoryRetryableRaised when
permission_deniedauthorizationnoAn account transacts on a token it has no permission for.
unauthorizedauthorizationnoThe actor is not authorized for this operation.
wrong_ownerauthorizationnoA commitment belongs to a different owner than the one claiming it.

Registration and accounts

CodeCategoryRetryableRaised when
not_registeredstatenoAn account is not registered for the token it is transacting on.
duplicate_registrationconflictnoAn account registers twice for the same token.

Value and commitments

The state preconditions that make a confidential transfer sound.

CodeCategoryRetryableRaised when
insufficient_balancestatenoAvailable confidential value is less than the requested amount. The message carries both figures.
unknown_commitmentstatenoA referenced commitment does not exist.
inactive_commitmentstatenoA commitment is referenced but is not in an active state.
consumed_commitmentstatenoA commitment was already consumed — a double-spend attempt. Also the code a replayed deposit produces.
invalid_proofcryptographicnoA proof reference is missing, unverifiable, or invalid.
invalid_statestatenoThe operation would move the system into a state that violates its invariants.

Internal

CodeCategoryRetryableRaised when
state_corruptioninternalnoA recorded transition disagrees with the state it describes, or another internal invariant is violated. This should never happen; it indicates a bug in the simulator rather than a bad request.

Snapshots

CodeCategoryRetryableRaised when
unknown_snapshotsnapshotnoA named snapshot does not exist.
duplicate_snapshotsnapshotnoA named snapshot already exists.

Fixtures

CodeCategoryRetryableRaised when
invalid_fixturefixturenoA fixture failed to parse or failed its version check.

Soroban adapter

Raised at the execution boundary when an operation is translated into a contract call. The adapter also passes through every core code it receives from the simulator rather than re-coding it, so a consumer sees one code for one condition regardless of where it was raised.

CodeCategoryRetryableRaised when
missing_amountadapternoThe adapter needs an amount but none was supplied.
bad_argumentsadapternoArguments did not match the call’s expected arity or types.
unimplementedadapternoThe operation has no contract-call translation.

Command-line interface

Raised by the crucible binary. The CLI also passes through the simulator’s codes, so crucible --dir /tmp/x transfer ... on an unregistered account reports not_registered, not a CLI-specific wrapper.

CodeCategoryRetryableRaised when
ioenvironmentyesReading or writing the persisted environment or a fixture failed. The only code in this table worth retrying: the cause is the filesystem, not the request.
corrupt_environmentenvironmentnoThe persisted environment exists but is unreadable or does not parse.
usageusagenoThe command line was wrong: an unknown identity, an unknown fixture, a missing argument.

Using codes

#![allow(unused)]
fn main() {
use crucible_core::errors::Error;

match error {
    // Branch on the code when what matters is the condition, not the payload.
    e if e.code() == "consumed_commitment" => report_double_spend(),
    e if e.code() == "insufficient_balance" => report_underfunded(),
    other => return Err(other.into()),
}
}

The typed variants remain the better choice inside Rust, because a match on the enum is exhaustive and the compiler checks it. Codes exist for the places a type cannot go: a fixture file, a JSON report, a CLI exit path, or another language.