Replay Verification
NOOSChain replay verification rebuilds chain state from genesis plus ordered blocks and ordered transactions. It is read-only against the canonical database: canonical materialized domain tables, state_leaves, SMT storage, and stored SMT roots are not copied into replay. For the general root model, see State Root.
The current replay strategy creates a temporary PostgreSQL schema named replay_*, runs the normal migrations into that schema, and creates an isolated Kysely connection whose search_path points at the replay schema. Replay then uses the same genesis initialization, transaction handlers, block execution, persistent SMT adapter, state-root persistence, block hashing, and Merkle-root logic as normal chain execution. When verification finishes, the temporary schema is dropped.
Replay validates:
- deterministic genesis initialization
- deterministic transaction execution
- deterministic SMT state-root evolution
- per-block state-root engine selection
- deterministic block hashing
- deterministic Merkle roots and transaction ordering
- deterministic permission failures and successful permission-gated writes
Replay does not trust:
organizations,users,nodes,validators,buckets, or other materialized domain tables in the canonical schema- canonical
state_leaves - canonical
state_smt_kv - canonical
state_smt_roots
Replay may trust:
- the stored canonical genesis document, or block 0 if an older database was initialized before genesis documents existed
- ordered canonical blocks
- ordered canonical transactions
Replay is driven by canonical block metadata, not the operator's current NOOS_STATE_ROOT_ENGINE setting. Each replayed block is inserted with its recorded state_root_engine_version, and the block executor receives that engine version explicitly. This matters for mixed-engine histories:
- pre-activation blocks replay with
iden3-v1; - the activation block and later Nervos blocks replay with
nervos-smt-v2; - replay fails if the replayed block engine metadata differs from canonical history.
Genesis replay follows the same rule. When a stored genesis document exists, the replay engine initializes height 0 with the canonical block-0 state_root_engine_version so environment overrides cannot rewrite historical genesis roots.
For fresh chains, each canonical transaction row stores the exact signed envelope in transactions.envelope. Replay uses that envelope rather than transactions.created_at, because DB timestamps are operational metadata and are not authoritative. If an older row has no envelope, replay falls back only for historical compatibility; chain verification emits a warning instead of silently pretending the envelope was known.
Encrypted payload bytes are not consensus state. encrypted_payload belongs to the availability layer and may be absent on future partial-replication nodes. Replay validity depends on payload_hash, metadata, key envelopes, public indexes, signer ordering, and deterministic handlers, not ciphertext bytes.
Encryption randomness does not affect replay because payload_hash is computed over plaintext bytes and state roots exclude ciphertext. Replay can therefore verify deterministic state evolution without decrypting payloads or trusting ciphertext availability.
For synced nodes, canonical transactions are replayed unchanged, but local materialized encrypted_records.encrypted_payload may be null according to bucket replication policy. This does not alter state roots because payload_available_locally and ciphertext bytes are excluded from consensus state hashing.
Bucket permission checks are part of deterministic execution. Failed transactions are canonical outcomes: if a signer lacks bucket:write or bucket:admin, replay must reproduce the same failure code and leave state roots unchanged for that transaction.
Access-rule mutation is also consensus state. UPDATE_BUCKET_ACCESS_RULE normalizes and hashes the replacement permission list; REMOVE_BUCKET_ACCESS_RULE deletes the access-rule SMT leaf. Replay must reproduce both permission grants and permission removals before applying later bucket writes, sync-relevant authorization, or failed transactions.
Bucket policy mutation is replayed through UPDATE_BUCKET_POLICY. Replay updates the bucket state leaf but never rewrites historical encrypted records. Replication-policy changes affect later sync and backfill materialization decisions, not historical state roots.
Bucket keys are replayed through CREATE_BUCKET_KEY and ROTATE_BUCKET_KEY transactions. Replay reconstructs bucket_keys from ordered transactions, verifies version sequencing, and checks encrypted-record references to the active bucket key at execution time. Plaintext DEKs are not needed for replay because state depends on encrypted key envelopes, payload hashes, key ids, and key versions.
Snapshot-bootstrapped nodes replay in snapshot_checkpoint mode. The imported snapshot is verified first by rebuilding its state root from consensus state rows with the state-root engine recorded on the snapshot/checkpoint, then replay continues from checkpoint.height + 1. Snapshot-only nodes do not fabricate historical blocks before the checkpoint, so full genesis replay still requires the genesis document and all historical blocks.
For a snapshot imported after a governed nervos-smt-v2 activation, the checkpoint stores the Nervos engine metadata. The first post-import block must extend the checkpoint block hash and execute with the engine selected by the imported checkpoint/history. The node does not choose a different engine because of its current environment defaults.
Replay does not depend on local Nervos sidecar checkpoint files being present. Those files accelerate post-activation execution, but replay can rebuild the state-root engine from ordered blocks or from a verified snapshot checkpoint. If the configured sidecar is missing, wrong-version, or unhealthy for a block whose metadata selects nervos-smt-v2, replay fails closed instead of falling back to iden3-v1. See Nervos SMT Sidecar and Nervos Sidecar Recovery.
Raft InstallSnapshot uses the same checkpoint replay path. A lagging follower that installs a Raft snapshot must report snapshot_checkpoint replay mode and then replay only blocks committed after the installed checkpoint. This keeps Raft catch-up deterministic without requiring the follower to retain or replay the entire compacted Raft log prefix.
This verifier is the foundation for future catch-up and distributed verification. A fresh node can fetch genesis, block headers, blocks, and transactions, replay them in isolation, and compare the resulting block hashes and SMT state roots before trusting the synchronized chain.