Snapshot Internals
This page is for Nooschain developers who maintain snapshot format, verification, import, checkpoint replay, streaming archives, and Raft InstallSnapshot behavior.
Operators should start with Operator Snapshots for commands and Backup, Restore, And Recovery for recovery decisions. For the general concept, see General Snapshots.
Core Invariants
A snapshot is a verified checkpoint of finalized consensus state. It is not a database dump, a fork-choice rule, or a way to bypass deterministic replay.
Snapshot code must preserve these invariants:
- verification happens before canonical chain tables are mutated;
- the artifact preserves chain id, height, block hash, state root, protocol version, and state-root engine version;
- local operational state stays outside the canonical snapshot;
- imported checkpoints are extended by later blocks, not silently forked;
- replay and sync continue from the checkpoint with recorded metadata;
- malformed, inconsistent, wrong-chain, or wrong-root artifacts fail closed.
Any format change that affects encoded fields, canonicalization, verification, import, replay, state-root handling, or compatibility needs an explicit format or compatibility decision.
Implementation Map
Format contracts and table definitions:
src/snapshots/snapshot-types.ts:SnapshotDocument, export modes, import options, and verification result contracts.src/snapshots/snapshot-table-format.ts: table list, primary keys, column order, row-array encoding, and decode/encode helpers.
Export, archive, and streaming:
src/snapshots/snapshot-exporter.ts: in-memory snapshot export.src/snapshots/snapshot-archive.ts: archive packaging and archive metadata.src/snapshots/snapshot-stream.ts: streaming export, verify, import, table files, COPY/staging paths, and large artifact handling.
Verification and import:
src/snapshots/snapshot-verifier.ts: schema validation, canonical hash, duplicate-key checks, reference checks, ciphertext rules, and state-root recomputation.src/snapshots/snapshot-importer.ts: normal import and checkpoint creation.src/snapshots/snapshot-state.ts: state-leaf reconstruction from snapshot rows.
Consensus integration:
src/consensus/adapters/raft/raft-snapshot.ts: bridges NoosraftInstallSnapshotto the NOOSChain snapshot/checkpoint subsystem.src/replay/*: snapshot-checkpoint replay behavior.src/node-sync/*: post-checkpoint block continuity and verification.
Relevant migrations:
src/db/migrations/018_snapshots.sqlsrc/db/migrations/022_raft_snapshots.sqlsrc/db/migrations/033_state_root_engine_version.sql
Snapshot Format
Snapshot format version 1 captures finalized consensus and deterministic materialized state at one height.
Important fields:
| Field | Meaning | Canonical hash input? |
|---|---|---|
formatVersion | Snapshot schema version. | Yes |
chainId | Chain identity. | Yes |
height | Finalized block height represented by the checkpoint. | Yes |
blockHash | Finalized block hash at height. | Yes |
stateRoot | State root at height. | Yes |
stateRootEngineVersion | Engine that produced stateRoot. | Yes |
protocolVersion | Protocol version for the checkpoint block. | Yes |
exportedAt | Operator/export timestamp. | No |
exportMode | consensus_state_only or include_local_ciphertext. | Yes |
tables | Encoded deterministic table data. | Yes |
smt | Snapshot state-tree metadata, including root hash. | Yes |
metadata | Snapshot metadata. Include only stable metadata here. | Yes |
snapshotHash | Canonical hash of the snapshot contents. | No, it is the result |
exportedAt is useful operator metadata but not a state transition. The hash itself is also excluded from its own input.
Table Encoding
Snapshot v1 uses compact table encoding:
{
"tables": {
"record_indexes": {
"columns": ["id", "record_id", "bucket_id", "key", "value_boolean"],
"rows": [["idx-1", "record-1", "bucket-a", "reviewed", true]]
}
}
}Each table declares columns once. Every row is an array in that exact order. snapshot-table-format.ts is the single source of truth for table names, primary keys, and column order.
NOOSChain does not support older object-per-row shapes in this format line. Unknown tables, missing columns, unexpected columns, and row arrays with the wrong length should be rejected instead of guessed.
Contents
Snapshot v1 includes consensus-relevant state and deterministic materialized tables.
| Category | Examples |
|---|---|
| Chain identity | Chain id, height, block hash, state root, protocol version, state-root engine version. |
| Chain actors | Organizations, users, nodes, validators, validator status, voting power, endpoints, Raft URLs. |
| Access and governance | Chain access rules, validator governance, protocol upgrades, state-root engine activations. |
| Transactions and buckets | Transactions, account nonces, buckets, access rules, index schemas, bucket keys, encrypted-record metadata, public record indexes. |
| Smart contracts | Code, manifests, registry entries, runtime activation/rollout rows, contract instances, releases, state, events, call results, migrations. |
| State-root data | State leaves, SMT root metadata, engine metadata. |
| Snapshot metadata | Table declarations, row counts, content hashes, deterministic snapshotHash. |
The exact table list must match exporter, verifier, importer, replay, state-root materialization, and backup/disaster-recovery expectations for the format version.
Exclusions
Snapshots intentionally exclude local operational and secret material.
Security-sensitive data:
- private keys;
- raw DEKs;
- plaintext payloads;
- auth sessions;
- operator tokens.
Local operational data:
- trusted peers;
- peer scores;
- gossip propagation state;
- active incident rows;
- local contract readiness rows;
- contract metrics.
Availability-only data:
- local payload availability flags;
- local ciphertext in
consensus_state_onlymode.
Sidecar/cache data:
- local Nervos sidecar checkpoint files;
- transient sidecar process state.
include_local_ciphertext is an explicit export mode that can include encrypted payload bytes available on the exporting node. It still must never include plaintext, private keys, raw DEKs, or operator tokens.
Canonical Hashing
snapshotHash must be stable across:
- export machines;
- local file paths;
- archive packaging method;
- JSON formatting choices;
- table streaming boundaries;
- PostgreSQL COPY versus batched import paths.
When adding a field, decide whether it affects imported consensus state or verification. If it does, include it in canonical hashing and update tests. If it is operator-only metadata, keep it outside canonical hash inputs.
Archive packaging as .tar, .tar.gz, or an unpacked .noosnap directory is a transport wrapper. Verification is over the manifest, table contents, hashes, references, and state root, not over incidental wrapper details.
Verification Pipeline
Snapshot verification must reject bad artifacts before import mutates canonical chain tables.
The verifier should:
- parse the snapshot or archive shape;
- validate supported
formatVersion; - validate table declarations and required columns;
- decode row arrays into table rows;
- check primary-key uniqueness;
- check references between included tables;
- enforce export-mode ciphertext rules;
- verify per-table/archive hashes when present;
- recompute canonical
snapshotHash; - validate protocol and state-root engine metadata;
- rebuild state leaves from snapshot rows;
- recompute the state root with the engine recorded in the snapshot.
Streaming verification must produce the same canonical result as in-memory verification. Batching, COPY staging, archive table order, and progress output are performance/transport details, not semantic differences.
Import And Checkpoint Semantics
Normal import mode is empty_database_only. It is allowed only when the target has no local blocks or checkpoints.
Normal import:
- verifies the artifact;
- writes materialized consensus state;
- reconstructs state leaves and SMT storage;
- creates a
chain_checkpointsrow at the snapshot height; - records snapshot metadata.
The checkpoint establishes a trusted bootstrap height:
- blocks before the checkpoint are not fabricated;
- future sync starts at
checkpoint.height + 1; - the first synced or produced block must extend
checkpoint.block_hash; - replay uses snapshot-checkpoint mode;
- post-checkpoint replay verifies blocks after the checkpoint;
- continuation uses the protocol and state-root engine recorded on the imported snapshot.
Failed imports must not leave partially accepted canonical rows. Staging, transaction boundaries, and cleanup paths should preserve rollback safety.
operator_restore is a guarded disaster-recovery path for intentionally replacing existing local database contents. Its operational use belongs in Backup, Restore, And Recovery.
State-Root Engine Handling
Snapshots preserve the engine that produced the checkpoint. A snapshot taken after a governed nervos-smt-v2 activation carries that engine metadata and the state_root_engine_activations rows needed to continue correctly.
This prevents local environment defaults from reinterpreting historical state. If imported history requires nervos-smt-v2, replay and execution must use that engine. If the sidecar is unavailable, reports the wrong protocol, or cannot validate checkpoints, execution should fail closed.
Normal snapshots do not include local Nervos sidecar checkpoint files. After import, the node can hydrate the sidecar from imported state_leaves and export a fresh verified local checkpoint.
For the sidecar model, see Nervos SMT Sidecar and State-Root Engine Migration.
Smart-Contract State
Snapshot coverage includes smart-contract consensus state:
- contract code bytes and code hashes;
- manifests and manifest hashes;
- package registry entries and package status;
- runtime activation and rollout rows;
- contract instances and deactivation metadata;
- contract state key/value rows;
- events and structured call results;
- release workflow rows;
- migration proposals, jobs, batches, and completion rows.
Snapshots intentionally exclude node-local readiness rows and metrics. After restore, readiness is recomputed from consensus state plus local runtime, bucket, payload, and permission availability. Metrics resume from post-restore calls.
When adding new contract tables or runtime consensus rows, update snapshot export, verification, import, replay, backup/disaster-recovery tests, and docs together.
Streaming And Archive Boundary
The streaming/archive path exists for large artifacts. It must preserve the same semantics as the in-memory document path.
Rules:
- table files and archive manifests are transport details;
- table content hashes must match the decoded table data;
- progress output must go to stderr so JSON stdout stays machine-readable;
- PostgreSQL COPY, staging tables, and batch sizes are performance choices;
- COPY and batched insert imports must produce the same canonical state;
- archive verification must reject malformed, missing, or extra table content.
Performance tuning belongs in operator docs. Developer changes should prove that tuning knobs do not change canonical snapshot meaning.
Raft InstallSnapshot
Noosraft InstallSnapshot catch-up reuses the NOOSChain snapshot/checkpoint subsystem.
The flow is:
- A follower falls behind far enough to cross the snapshot threshold.
- The leader exports a
consensus_state_onlysnapshot. - The leader sends the snapshot in a Raft
InstallSnapshotmessage. - The follower verifies the snapshot before import.
- The follower imports it as a checkpoint.
- Raft records
snapshotLastIncludedIndexandsnapshotLastIncludedTerm. - Raft discards local log entries at or below the installed snapshot index.
- Later
AppendEntriescontinue after the checkpoint.
This is not raw PostgreSQL transfer. The follower does not trust the leader's materialized tables; it verifies the artifact, rebuilt state root, checkpoint metadata, and post-checkpoint continuity.
The current MVP sends one full snapshot document. Chunking, compression, resumable transfer, and streaming transfer are future work. Those designs must preserve the same verification-before-import and checkpoint semantics.
Testing Matrix
| Risk | Test evidence |
|---|---|
| Snapshot shape, schema, hash, references, and state root | npm run test:snapshots |
| Export modes and ciphertext rules | npm run test:snapshots |
| Import and snapshot-checkpoint replay | npm run test:snapshots, npm run verify:replay, npm run verify:chain |
| Large archive, streaming, COPY, and batching paths | npm run test:snapshots:large |
| Smart-contract backup/restore coverage | npm run test:contract-backup-disaster-recovery |
| Capacity and performance | npm run benchmark:snapshots |
Raft InstallSnapshot continuation | npm run test:raft:snapshot |
| State-root engine metadata and Nervos sidecar fail-closed behavior | state-root engine and activation tests |
Common commands:
npm run test:snapshots
npm run test:snapshots:large
npm run test:contract-backup-disaster-recovery
npm run test:raft:snapshot
npm run verify:replay
npm run verify:chain
npm run benchmark:snapshotsUse benchmarks for capacity and regression visibility. Do not use benchmark success as a substitute for verification, replay, or compatibility tests.
Developer Checklist
When changing snapshot behavior:
- update
SNAPSHOT_TABLE_DEFINITIONSwhen table shape changes; - update exporter, verifier, importer, streaming, and archive paths together;
- decide whether new fields are canonical hash inputs;
- preserve compatibility or bump/branch the format version;
- reject unknown shapes rather than guessing;
- add fixtures for old and new formats when compatibility is supported;
- test
consensus_state_onlyandinclude_local_ciphertext; - test import rollback safety;
- test snapshot-checkpoint replay from
checkpoint.height + 1; - test state-root engine metadata, especially post-activation Nervos snapshots;
- test smart-contract tables when contract state shape changes;
- test Raft
InstallSnapshotwhen checkpoint semantics change; - update General, Operator, and Developer docs when behavior changes.
Anti-Patterns
Avoid these:
- treating snapshots as raw database dumps;
- importing before verification completes;
- including plaintext, raw keys, private keys, or operator tokens;
- trusting peer snapshot data without recomputing root;
- letting archive wrapper details change canonical hash;
- reinterpreting snapshot engine metadata with current env defaults;
- fabricating historical blocks before the checkpoint;
- using Raft snapshots as fork-choice or conflict resolution;
- storing local readiness, metrics, incidents, or peer scores in canonical snapshot data;
- changing table order/columns without updating verifier and compatibility tests.
Limitations And Future Work
- Historical snapshot export is not implemented yet; export targets the latest finalized height.
- Snapshot import does not perform automatic reorg, fork repair, or pruning.
- Snapshot-only nodes cannot fully replay from genesis unless historical blocks are also available.
- Persistent SMT nodes are reconstructed from verified snapshot state during import; future formats may include compact SMT node data.
- Raft snapshot transfer currently sends one full snapshot document.
- Chunking, compression, resumable transfer, and streaming transfer need format and verification design before implementation.