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

Deterministic execution

Determinism is the defining property of Crucible:

same configuration
+ same initial state
+ same operation sequence
+ same seed
= same resulting state + same events + same commitments + same outcomes

Where nondeterminism would hide — and how the code prevents it

Source of nondeterminismPrevention
System entropy (randomness)All randomness flows through DeterministicRng (SplitMix64), a pure function of a u64 seed. Commitments, proofs, and identities never touch the system RNG.
Wall-clock timeTime is a synthetic LedgerContext advanced by a fixed configured step (protocol.step_seconds). Nothing reads the clock.
Iteration orderEvery state collection is a BTree* map/set, so iteration is sorted. Hash* collections are never used for state.
Hash randomizationHashing is domain-separated SHA-256 over canonical byte/JSON inputs, never DefaultHasher.
Serialization driftTransaction IDs digest canonically serialized payloads (serde_json), so they do not depend on Rust formatting details.
PRNG draw orderFlows draw nonces in fixed code order, so the same operation sequence consumes the stream identically.
Floating pointAll value math is integer (u128); wrapping overflow is explicit (wrapping_* or checked_*).

The reproducible-input contract

A scenario is fully described by:

fixture (environment + seed + ordered steps)

The fixture is versioned JSON (kind, version, environment.seed, steps); the loader version-checks it and the runner replays it against a fresh Simulator. Identical fixtures produce identical ScenarioOutcomes (asserted in the determinism suite, including the failure code of expected-failure scenarios).

What is compared

The determinism suite compares, across independent runs:

  • the state root after every operation,
  • transaction IDs,
  • event IDs,
  • output (commitment) IDs,
  • full serialized stores (private values included),
  • operation results, and
  • error codes for failed operations.

Different seeds diverge only in private state (blinding nonces change the commitments) — structural facts (counts, versions, error codes) agree.

Ledger context

LedgerContext { seq, timestamp } starts from ledger.initial_seq / ledger.start_timestamp and advances deterministically. Simulator::advance_ledger() is an explicit step for scenarios that want multi-ledger behavior; it never runs on a timer.