State Root
The state root is NOOSChain's compact commitment to deterministic chain state after a block executes. If two nodes execute the same finalized history under the same protocol rules, they should produce the same block hashes and the same state roots.
State roots let nodes verify execution without comparing every materialized database row. They are used by sync, replay verification, snapshots, checkpoints, and future state-proof workflows.
What The State Root Is
A state root is the root hash of a Sparse Merkle Tree (SMT) built from consensus-relevant state leaves. Each finalized block records:
- the block height and block hash;
- the Merkle root for ordered transactions;
- the state root after deterministic execution;
- the state-root engine version used for that block.
The state root answers one question: after applying this block, what exact consensus state did the chain reach?
Why It Matters
State roots are a guardrail around deterministic execution:
- sync rejects remote blocks whose locally recomputed state root does not match;
- replay rebuilds state from genesis or a checkpoint and compares roots;
- snapshot verification rebuilds a checkpoint root before import;
- protocol upgrades preserve historical state-root engine selection;
- future proof APIs can prove whether a state key is included in a root.
The root is only meaningful because every node follows the same transaction ordering, state-key encoding, value hashing, mutation ordering, and engine selection rules.
How It Is Calculated
State-root calculation happens during block execution:
- Consensus orders transactions into a block.
- The block executor runs deterministic domain handlers in order.
- Handlers emit state changes for consensus-relevant entities.
- Each logical entity maps to a stable state key.
- Each state value is canonicalized and hashed into a value hash.
- The selected state-root engine applies ordered
setanddeletemutations. - The resulting root is stored on the block and root metadata.
NOOSChain does not trust arbitrary peer database rows. A synced node executes the block locally, regenerates the mutation stream, computes the root, and compares it with the block commitment.
State Leaves
state_leaves is the materialized current leaf set for consensus state. It is the node's replayable view of the latest known value hash for each state key.
Each leaf records:
state_key: the stable key used by the SMT;- namespace: the kind of state, such as bucket, user, validator, or contract;
- entity id: the logical id inside that namespace;
- value hash: the canonical hash of the current state value;
- updated height: the block height that last changed the leaf.
Replay and snapshot verification do not blindly trust canonical state_leaves. They rebuild leaves from ordered transactions or from verified snapshot rows and then recompute the root.
What Goes Into The State Root
The state root includes consensus-relevant state, such as:
- organizations, users, nodes, validators, and chain access rules;
- bucket metadata, bucket policy, access rules, index schemas, and bucket keys;
- encrypted-record metadata, payload hashes, public indexes, and key references;
- account nonces and signer replay-protection state;
- protocol upgrades and state-root engine activation state;
- smart contract code, manifests, registry entries, instances, state, events, call results, releases, and migrations;
- deterministic state-root metadata needed for replay and snapshots.
The exact state value for each namespace is canonicalized before hashing, so the root depends on chain meaning rather than incidental database formatting.
What Does Not Go Into The State Root
The state root deliberately excludes local or availability-only data:
- plaintext payloads;
- ciphertext bytes;
- private keys and raw DEKs;
- local payload availability flags;
- trusted peers;
- peer scores;
- gossip propagation state;
- incidents;
- auth sessions and operator tokens;
- local contract readiness rows;
- contract metrics and other local observability rows;
- local sidecar checkpoint files.
For encrypted records, the root depends on payload hashes and metadata, not on whether this node currently stores ciphertext bytes.
Sparse Merkle Trees
A Sparse Merkle Tree is a deterministic key/value commitment structure. Given the same keys, values, empty-root semantics, mutation order, and hashing rules, it produces the same root.
In NOOSChain, the SMT is useful because:
- a small root can commit to a large state set;
setanddeletemutations change the root predictably;- replay can rebuild the tree and compare roots;
- snapshots can rebuild a checkpoint root from exported state;
- future state proofs can prove inclusion or absence for selected keys.
The empty root is engine-defined. Different engines can have different roots for the same logical state, which is why engine selection is protocol-visible.
State-Root Engines
NOOSChain treats the state-root calculator as an engine behind a narrow adapter. TypeScript owns block execution and mutation ordering. The engine owns the tree/root calculation for the selected block.
Current documented engines are:
| Engine | Status | Runtime | Root Scheme | Used For |
|---|---|---|---|---|
iden3-v1 | Legacy supported | TypeScript/Node.js with @iden3/js-merkletree | iden3 field-element SMT with NOOSChain hash-to-field adapter | Old blocks, replay, snapshots, activation-boundary tests, and benchmarks. |
nervos-smt-v2 | Default for new chains | Rust Nervos SMT sidecar | Nervos sparse-merkle-tree with Blake2b | New chains and post-activation blocks. |
The selected engine is stored with blocks, snapshots, and checkpoints. Replay uses historical block metadata, not the operator's current NOOS_STATE_ROOT_ENGINE setting. A node must not reinterpret old blocks with a new local default.
Both engine ids are known by the current binary. iden3-v1 remains available for legacy compatibility, while nervos-smt-v2 is the default used when a new chain has no historical reason to stay on iden3.
For details, see iden3 State Root, iden3 State-Root Contract, Nervos SMT Sidecar, and Nervos State-Root Contract.
Nervos SMT Sidecar
The Nervos SMT sidecar is a state-root engine. It is not consensus, not Raft, not transaction execution, and not the smart-contract runtime.
The boundary is intentionally small:
- NOOSChain TypeScript executes transactions and emits ordered state mutations.
- The Rust sidecar receives only state-root protocol messages.
- The sidecar applies mutations to the Nervos sparse-merkle-tree engine.
- The sidecar returns roots, exports/loads local checkpoints, and can generate or verify Nervos SMT proofs.
The sidecar never receives plaintext payloads, private keys, DEKs, bearer tokens, raw database mutations, or Raft log control.
For origin, rationale, build steps, and runtime configuration, see Nervos SMT Sidecar. For the frozen engine identifiers and protocol contract, see Nervos State-Root Contract.
Where State-Root Data Is Stored
State-root data appears in several places:
- blocks store the block
state_rootandstate_root_engine_version; state_leavesstores the materialized latest leaf set;- SMT backing storage or sidecar checkpoints cache tree state for execution;
- chain checkpoints preserve imported snapshot roots;
- snapshots preserve the root and the engine metadata needed to verify it;
- observability and CLI commands expose current engine/readiness status.
Local sidecar checkpoint files are operational cache. Losing them is recoverable from state_leaves or verified snapshot state, but it may be slower.
Replay, Sync, And Snapshots
State roots connect the verification workflows:
- Sync, Gossip, And Verification: synced blocks execute locally and must reproduce the fetched state root.
- Replay Verification: replay rebuilds state in isolation and compares deterministic roots.
- Snapshots: snapshot verification rebuilds the checkpoint root before import.
These flows all share the same principle: remote state claims are not trusted unless local execution or local verification can reproduce the root.
Engine Migration
Changing state-root engines changes chain semantics. nervos-smt-v2 is not byte-compatible with iden3-v1, so it is a new root scheme, not a transparent drop-in replacement.
For new chains, Nervos can be used from genesis. Existing iden3 chains must move through an explicit activation height recorded in consensus state and persisted on block/checkpoint metadata. Blocks before activation keep their old engine; activation and later blocks use the new engine.
For implementation details, see State-Root Engine Migration.