Snapshot Plugin
The snapshot plugin enables near-instant node startup by serializing and restoring the full blockchain state as a JSON file. Instead of replaying millions of blocks from the block log, a node loads a pre-built snapshot and begins syncing from the snapshot's block height via P2P.
Source: plugins/snapshot/snapshot.cpp
Dependencies
chain::pluginConfiguration
CLI-only options
| Option | Default | Description |
|---|---|---|
--snapshot <path> | — | Load state from a snapshot file (DLT mode). Skips import if shared_memory.bin already exists; renames the file to .used after successful import. |
--snapshot-auto-latest | false | Auto-discover the latest snapshot in snapshot-dir by block number from filename. Ignored if --snapshot is also specified. |
--replay-from-snapshot | false | Crash recovery: import a snapshot then replay blocks from the DLT rolling block log. Always wipes shared memory; does NOT rename the snapshot file to .used. Requires --snapshot or --snapshot-auto-latest. |
--auto-recover-from-snapshot | true | Automatic runtime recovery from shared memory corruption — no restart required. Requires plugin = snapshot and snapshots in snapshot-dir. Disable with --no-auto-recover-from-snapshot. |
--create-snapshot <path> | — | Create a snapshot at the given path using the current database state, then exit. Runs before P2P or validator activate. |
--sync-snapshot-from-trusted-peer | false | Download and load a snapshot from trusted peers when state is empty. Requires trusted-snapshot-peer. Opt-in to prevent accidental state wipe. |
Config file options
| Option | Default | Description |
|---|---|---|
snapshot-at-block | 0 | Create a snapshot when this block number is reached (0 = disabled). |
snapshot-every-n-blocks | 0 | Create a snapshot every N blocks (0 = disabled). Only fires on live blocks — skipped during initial P2P sync. |
snapshot-dir | — | Directory for auto-generated snapshot files. Auto-created if absent. |
snapshot-max-age-days | 90 | Delete snapshots older than N days after creating a new one (0 = disabled). |
allow-snapshot-serving | false | Enable serving snapshots over TCP to other nodes. |
allow-snapshot-serving-only-trusted | false | Restrict snapshot serving to configured trusted peers only. |
snapshot-serve-endpoint | 0.0.0.0:8092 | TCP endpoint for the snapshot serving listener. |
trusted-snapshot-peer | — | Trusted peer endpoint for P2P snapshot sync (IP:port); may be repeated. |
The dlt-block-log-max-blocks option (in the chain plugin config section) controls the rolling DLT block log size and is closely related to snapshot operation — see DLT Rolling Block Log below.
Creating Snapshots
Method 1: One-shot (node stopped)
Stop the node, then restart it with --create-snapshot. The node opens the existing database (replaying from the block log if needed), writes the snapshot, and exits before P2P or validator activate:
vizd --create-snapshot /data/snapshots/viz-snapshot.json --plugin snapshotMethod 2: At a specific block (no downtime)
plugin = snapshot
snapshot-at-block = 5000000
snapshot-dir = /data/snapshotsWhen block 5,000,000 is applied, the node writes /data/snapshots/snapshot-block-5000000.json without interrupting operation.
Method 3: Periodic (recommended)
plugin = snapshot
snapshot-every-n-blocks = 86400
snapshot-dir = /data/snapshots
snapshot-max-age-days = 90Files are named snapshot-block-<N>.json. Recommended intervals:
| Interval | Blocks | Time at 1 s/block (from HF16) | Time at 3 s/block (before HF16) |
|---|---|---|---|
| Frequent | 10,000 | ~2.8 h | ~8 h |
| Daily | 86,400 | ~24 h | ~3 days |
| Weekly | 300,000 | ~3.5 days | ~10.4 days |
The block counts target the 1-second slot in force since HF16 (2026-10-31 00:00 UTC). Before HF16 the slot was 3 seconds, so the same block count covered one third of the time — a node still configured with snapshot-every-n-blocks = 28800 takes a snapshot every ~8 h, not every day, and a "daily" job silently triples its file count. snapshot-max-age-days prunes by age only, so disk use follows cadence, not retention. The option is node config, not a chain property — nothing rescales it for you at the fork, so re-check the value after a slot change. The default is 0 (periodic snapshots off).
Method 4: Combining at-block and periodic
Both options can be set together; snapshot-at-block fires once and snapshot-every-n-blocks fires repeatedly.
How snapshot creation works
Snapshot creation is asynchronous and split into two phases to minimize impact:
- Phase 1 (read lock, ~1 s): All 32 tracked object types are serialized into memory. Block processing waits during this phase; API and P2P reads proceed concurrently.
- Phase 2 (no lock, ~2 s): Compression, SHA-256 checksum computation, and file I/O. Normal node operation resumes.
If creation fails (e.g., disk full), the error is logged and the node continues running.
Loading from a Snapshot (DLT Mode)
First-time startup
vizd --snapshot /path/to/snapshot.json --plugin snapshotOn first load:
- The snapshot plugin validates the file header (format version, chain ID, SHA-256 checksum).
- Shared memory is wiped and re-initialized from snapshot state.
- All 32 object types are imported.
- LIB is promoted to
head_block_numso P2P sync starts from the snapshot point. - The snapshot file is renamed to
.usedto prevent re-import on restart. - P2P plugin starts and syncs forward from the snapshot head.
Restart safety
The node is safe to restart with --snapshot still on the command line (e.g., via VIZD_EXTRA_OPTS):
| Scenario | Behavior |
|---|---|
| First start (no shared memory, file exists) | Imports snapshot; renames to .used |
| Restart (shared memory exists) | Skips import — uses existing state |
Restart (shared memory wiped, file already .used) | Skips import — "snapshot file not found" |
| Force re-import | Use --resync-blockchain + provide a fresh snapshot file |
DLT mode
After snapshot import the node runs in DLT mode: the main block_log is empty. A separate DLT rolling block log stores recent blocks for P2P serving and local block queries. The mode is detected automatically on subsequent restarts (empty block_log + existing chainbase state).
Snapshot File Format
Each snapshot is a single JSON file:
{
"header": {
"version": 1,
"chain_id": "...",
"snapshot_block_num": 12345678,
"snapshot_block_id": "...",
"snapshot_block_time": "2025-01-01T00:00:00",
"last_irreversible_block_num": 12345660,
"payload_checksum": "sha256...",
"object_counts": { "account": 50000, ... }
},
"state": {
"dynamic_global_property": [...],
"account": [...],
...
}
}Included object types (32 total)
Critical (11) — consensus-essential: dynamic_global_property, witness_schedule, hardfork_property, account, account_authority, validator, witness_vote, block_summary, content, content_vote, block_post_validation
Important (15) — required for full operation: transaction, vesting_delegation, vesting_delegation_expiration, fix_vesting_delegation, withdraw_vesting_route, escrow, proposal, required_approval, committee_request, committee_vote, invite, award_shares_expire, paid_subscription, paid_subscribe, witness_penalty_expire
Optional (5) — metadata and recovery: content_type, account_metadata, master_authority_history, account_recovery_request, change_recovery_account_request
DLT Rolling Block Log
In DLT mode the main block_log is empty. The DLT rolling block log (dlt_block_log.log + dlt_block_log.index) stores the most recent irreversible blocks.
This enables:
- P2P block serving — peers requesting recent blocks for fork resolution and sync catch-up.
- Local block queries — API calls like
get_blockwork for the stored range.
Configuration
# Keep the last 100,000 blocks (default)
dlt-block-log-max-blocks = 100000
# Disable the DLT block log entirely
# dlt-block-log-max-blocks = 0The log uses a rolling window: when it exceeds dlt-block-log-max-blocks, old blocks are pruned from the front. On restart, the log is preserved and new blocks are appended from where they left off.
Stale snapshot detection
If the latest snapshot's block number is less than the DLT log's start block, downloading nodes cannot bridge the gap (snapshot at block N, DLT log starts at M > N, blocks N+1..M-1 missing). The plugin detects this at startup and creates an urgent fresh snapshot on the first live block after sync completes.
Crash Recovery: --replay-from-snapshot
When shared_memory.bin becomes corrupted and the node cannot start normally, --replay-from-snapshot combines snapshot import with DLT block log replay:
# Specify the snapshot explicitly
vizd --replay-from-snapshot --snapshot /data/snapshots/snapshot-block-79273800.vizjson --plugin snapshot
# Or auto-discover the latest snapshot
vizd --replay-from-snapshot --snapshot-auto-latest --plugin snapshotRecovery steps:
- Wipes shared memory (always — assumes corruption).
- Imports the snapshot.
- Replays blocks from
dlt_block_logbeyond the snapshot head, restoring up to the latest available state. - Resumes P2P sync from the replayed head block.
Comparison: --snapshot vs --replay-from-snapshot
| Aspect | --snapshot | --replay-from-snapshot |
|---|---|---|
| Purpose | Bootstrap a new node | Recover from corruption |
| Shared memory check | Skips if already exists | Always wipes and re-imports |
| Snapshot file rename | Renames to .used | Does NOT rename |
| DLT block log replay | No | Yes |
| Typical use | First-time setup | Crash recovery |
Example recovery
A DLT node crashes at block 79,274,318. State:
snapshots/snapshot-block-79273800.vizjson ← 518 blocks behind crash point
blockchain/dlt_block_log.log ← contains blocks 79174319..79274318
blockchain/shared_memory.bin ← CORRUPTEDRecovery command: vizd --replay-from-snapshot --snapshot-auto-latest --plugin snapshot
Loading state from snapshot: snapshot-block-79273800.vizjson
Snapshot loaded at block 79273800, elapsed time 12.3 sec
Replaying dlt_block_log from block 79273801 to 79274318 (518 blocks)...
Done replaying, head_block=79274318, elapsed time: 7.3 secP2P sync fills the gap to the live chain head.
Admin-Requested In-Process Restore
admin.snapshot.restore schedules a restore on the snapshot worker thread. Its status: started response means the job was accepted, not that import or resume succeeded. admin.snapshot.status exposes whether the job is still running; completion and failure details are logged by the worker.
The chain restore method applies these failure boundaries:
- With a started P2P plugin, a synchronous pause exception aborts before database close/import and leaves the pre-restore syncing flag unchanged. Missing or unstarted P2P is skipped, as before.
- After pause, block production is gated through
is_syncing(). If database close throws, the method does not import or resume: close may have partially changed state. It keeps the gate set and requests application shutdown. - An import exception, zero imported head, or unreadable head returns failure without resuming P2P or clearing the production gate. Import handling may already have requested shutdown; the restore method does not assume that it has, and does not automatically recover from this state.
- An immediate peer-reset/resume exception returns failure and retains the production gate. P2P flags may already have changed before the exception; this is not a guarantee that P2P remains paused after a failed resume.
- Only after a nonzero head and no immediate resume error does the method clear the syncing gate and report success. The existing post-pause P2P catchup gate still applies.
These are local error-handling boundaries, not whole-node quiescence. The recovery mutex only excludes cooperating users; pausing P2P and setting the production gate do not establish that all API calls, transactions or already in-flight validator/database work have drained. P2P reset and the resume queue are asynchronous, so later errors in those posted tasks are not observed by this method. Plugin lifecycle changes during restore are not synchronized here. After a post-close failure, inspect the logs and use the normal stopped-node recovery procedure rather than assuming the old database is intact.
Offline regression
Run from the repository root with a C++17 compiler available:
python3 tests/chain/test_snapshot_restore.pyThe test extracts the production restore_from_snapshot() method and compiles it against controlled storage, P2P, filesystem and application dependencies. It covers preflight, absent/unstarted P2P, pause exceptions with both initial syncing values, partially mutating close failures, failed/empty import, unreadable head, peer-reset/resume exceptions (including partially changed resume flags), and successful restore ordering. It starts no node and performs no real DB restore; it does not prove thread quiescence or test the queued P2P task bodies. CXX selects the compiler; RESTORE_SOURCE can select an alternate plugin.cpp for baseline regression checks.
Automatic Runtime Recovery: --auto-recover-from-snapshot
Enabled by default. When shared memory corruption is detected during block processing or block generation, the node:
- Closes the database.
- Finds the latest snapshot in
snapshot-dir. - Wipes shared memory, imports the snapshot, replays
dlt_block_log. - Resumes P2P sync — no restart required.
Prerequisites:
plugin = snapshotmust be enabled.- Snapshots must exist in
snapshot-dir. Usesnapshot-every-n-blocksto create them automatically. dlt_block_logshould cover blocks beyond the snapshot for minimal data loss.
If recovery fails (no snapshot found, import error), the node logs an error and shuts down cleanly.
To disable: --no-auto-recover-from-snapshot
P2P Snapshot Sync
Nodes can serve and download snapshots over TCP, enabling fully automated bootstrap without manual file transfers.
Server configuration
plugin = snapshot
allow-snapshot-serving = true
snapshot-serve-endpoint = 0.0.0.0:8092
snapshot-every-n-blocks = 28800
snapshot-dir = /data/snapshots
# Optional: restrict to trusted peers only
# allow-snapshot-serving-only-trusted = true
# trusted-snapshot-peer = 1.2.3.4:8092Client configuration
plugin = snapshot
trusted-snapshot-peer = seed3.viz.world:8092
trusted-snapshot-peer = seed1.viz.world:8092
trusted-snapshot-peer = seed2.viz.world:8092
trusted-snapshot-peer = rpc.viz.cx:8092
sync-snapshot-from-trusted-peer = truesync-snapshot-from-trusted-peer defaults to false — must be explicitly enabled to prevent accidental state wipe.
How it works
- Query: Connects to each trusted peer, sends a
snapshot_info_request, collects metadata (block number, checksum, size). - Select: Picks the peer with the highest block number.
- Download: Downloads in 1 MB chunks; progress is logged to console every 5%.
- Verify: SHA-256 checksum verified via streaming (no full-file load into memory).
- Import: Wipes state, loads the verified snapshot, initializes hardforks.
All operations happen inside chain::plugin_startup(), before P2P and validator activate. The node is fully blocked until import completes.
Security
- Maximum download size: 2 GB.
- Max 5 concurrent connections per server, each with a 60-second connection deadline.
- Rate limiting per IP.
- Control messages limited to 64 KB; only data replies allow up to 64 MB.
- The TCP server runs on a dedicated thread, independent of the main I/O loop.
Recommended Production Config
plugin = snapshot
# Daily snapshots pre-HF16 (~24 h at 3 s/block); 28800 blocks = ~8 h after HF16, use 86400 for a day
snapshot-every-n-blocks = 28800
snapshot-dir = /data/viz-snapshots
# Keep 90 days of snapshots (default)
snapshot-max-age-days = 90
# DLT rolling block log: last 100k blocks (default)
dlt-block-log-max-blocks = 100000Troubleshooting
| Symptom | Check |
|---|---|
| Node re-imports snapshot on every restart | shared_memory.bin is being deleted between restarts; or snapshot file was never renamed to .used |
Snapshot file not found on start | File was already renamed to .used from a previous successful import; or wrong path |
Chain ID mismatch on snapshot load | Snapshot was created from a different chain; cannot import |
Checksum mismatch | Snapshot file is corrupt or partially transferred |
| Snapshot creation never fires | snapshot-dir not set, or node is still in P2P sync (periodic snapshots skip sync mode) |
| Stale snapshot warning at startup | Latest snapshot is older than dlt_block_log start; node will create a fresh snapshot after the next live block |
| Auto-recovery fires but fails | No snapshots in snapshot-dir; check that snapshot-every-n-blocks is configured |
| P2P snapshot download fails | Check trusted-snapshot-peer is reachable on port 8092; check allow-snapshot-serving = true on server |
See also: Snapshot, Validator Plugin, Chain Plugin, P2P Overview.