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

State model

Explicit transitions

The state engine never silently mutates state:

State0 ──operation──▶ Transition ──apply──▶ State1

Every applied operation produces a StateTransition recording the operation, the concrete Changes, the state before, the state after, and the events emitted. Recording is audited: a transition whose resulting_state does not exactly match the store’s current fingerprint is rejected as StateCorruption, so bookkeeping mistakes fail loudly.

Transitions chain: consecutive records satisfy previous_state == preceding.resulting_state, and every transition advances the state version by exactly one.

What the store owns

CollectionType keyNotes
AccountsAccountIdRegistration + permission state, confidential heads
TokensTokenIdPolicy configuration
CommitmentsCommitmentIdValue + blinding nonce (private simulation state)
StatusesCommitmentIdActive / Consumed
NullifiersNullifierReplay protection; one per consumed commitment
TransactionsTransactionIdDeterministic records of applied operations
EventsEventIdValue-free observable output
TransitionsorderedThe audited transition log
SnapshotsnameFull-state captures

All collections are BTree* so iteration order — and therefore the state root — is deterministic.

State root

StateStore::state_ref() digests the version plus canonical JSON of every account, token, commitment (+ status), and nullifier in sorted order. The root is an internal simulation artifact (commitments carry private values into it); it is what determinism tests compare and what transactions refer to as state_before / state_after.

The commitment lifecycle

create (Active) ──consume──▶ Consumed + nullifier registered
  • create_commitment requires the owner and token to exist and rejects nonce collisions. Re-creating an already-consumed identical commitment is rejected as a double spend (ConsumedCommitment).
  • Consuming registers the commitment’s deterministic nullifier in the same step, so spending and replay protection cannot diverge.
  • Flows spend all of an account’s active commitments for a token at once; balances are the sum of active commitment values.

Snapshots and rollback

  • TransactionScope::begin captures a full-state checkpoint; dropping the scope without commit() restores it exactly — failed operations can never leave partial state, nullifier gaps, or skipped sequence numbers.
  • Named snapshots capture the whole store; restore_snapshot replaces the store with the captured state and re-registers the snapshot, so the same snapshot can be restored repeatedly. Cost is O(state) — the deliberate trade for a deterministic testing environment.

Invariants enforced (and tested)

  1. Conservation — transfers never create or destroy value.
  2. Commitment integrity — consumed commitments are never reused; each consumption registers exactly one nullifier; active + consumed == created.
  3. Registration — unregistered accounts cannot hold or move confidential value on registration-required tokens.
  4. Atomicity — failed operations leave the state fingerprint bit-identical.
  5. Determinism — same inputs produce the same state, commitments, events, and outcomes.

The invariant suite (tests/state) drives deterministic pseudo-random operation sequences to probe these properties.