Skip to content

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 Noosraft InstallSnapshot to 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.sql
  • src/db/migrations/022_raft_snapshots.sql
  • src/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:

FieldMeaningCanonical hash input?
formatVersionSnapshot schema version.Yes
chainIdChain identity.Yes
heightFinalized block height represented by the checkpoint.Yes
blockHashFinalized block hash at height.Yes
stateRootState root at height.Yes
stateRootEngineVersionEngine that produced stateRoot.Yes
protocolVersionProtocol version for the checkpoint block.Yes
exportedAtOperator/export timestamp.No
exportModeconsensus_state_only or include_local_ciphertext.Yes
tablesEncoded deterministic table data.Yes
smtSnapshot state-tree metadata, including root hash.Yes
metadataSnapshot metadata. Include only stable metadata here.Yes
snapshotHashCanonical 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:

json
{
  "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.

CategoryExamples
Chain identityChain id, height, block hash, state root, protocol version, state-root engine version.
Chain actorsOrganizations, users, nodes, validators, validator status, voting power, endpoints, Raft URLs.
Access and governanceChain access rules, validator governance, protocol upgrades, state-root engine activations.
Transactions and bucketsTransactions, account nonces, buckets, access rules, index schemas, bucket keys, encrypted-record metadata, public record indexes.
Smart contractsCode, manifests, registry entries, runtime activation/rollout rows, contract instances, releases, state, events, call results, migrations.
State-root dataState leaves, SMT root metadata, engine metadata.
Snapshot metadataTable 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_only mode.

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:

  1. parse the snapshot or archive shape;
  2. validate supported formatVersion;
  3. validate table declarations and required columns;
  4. decode row arrays into table rows;
  5. check primary-key uniqueness;
  6. check references between included tables;
  7. enforce export-mode ciphertext rules;
  8. verify per-table/archive hashes when present;
  9. recompute canonical snapshotHash;
  10. validate protocol and state-root engine metadata;
  11. rebuild state leaves from snapshot rows;
  12. 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:

  1. verifies the artifact;
  2. writes materialized consensus state;
  3. reconstructs state leaves and SMT storage;
  4. creates a chain_checkpoints row at the snapshot height;
  5. 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:

  1. A follower falls behind far enough to cross the snapshot threshold.
  2. The leader exports a consensus_state_only snapshot.
  3. The leader sends the snapshot in a Raft InstallSnapshot message.
  4. The follower verifies the snapshot before import.
  5. The follower imports it as a checkpoint.
  6. Raft records snapshotLastIncludedIndex and snapshotLastIncludedTerm.
  7. Raft discards local log entries at or below the installed snapshot index.
  8. Later AppendEntries continue 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

RiskTest evidence
Snapshot shape, schema, hash, references, and state rootnpm run test:snapshots
Export modes and ciphertext rulesnpm run test:snapshots
Import and snapshot-checkpoint replaynpm run test:snapshots, npm run verify:replay, npm run verify:chain
Large archive, streaming, COPY, and batching pathsnpm run test:snapshots:large
Smart-contract backup/restore coveragenpm run test:contract-backup-disaster-recovery
Capacity and performancenpm run benchmark:snapshots
Raft InstallSnapshot continuationnpm run test:raft:snapshot
State-root engine metadata and Nervos sidecar fail-closed behaviorstate-root engine and activation tests

Common commands:

powershell
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:snapshots

Use 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_DEFINITIONS when 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_only and include_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 InstallSnapshot when 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.

Audience-first NOOSChain documentation.