snapshot

Produces a consistent snapshot of the node's state at the current block. Two modes selected by the request body: stream the snapshot back as a gzipped tar response (operator path), or write it to a destination directory on the node's filesystem (zero-downtime upgrade-controller path).

Method: POST /snapshot

Authentication: required. Send the operator token in either header:

Authorization: Bearer <token>
X-Admin-Auth: <token>

The token is the value of GENLAYERNODE_NODE_OPERATOR_TOKEN on the node process. When that variable is unset, the endpoint returns 401 to every caller regardless of header value (default-deny). The other ops routes (/metrics, /health, /balance) are unaffected and remain public.

Network exposure: the ops server is intended for in-host or in-cluster access (Prometheus scraping /metrics, k8s probes hitting /health). The endpoint may not be reachable from outside the host depending on the deployment's firewall. The recommended operator workflow is therefore one of:

  • SSH onto the node and call the endpoint via 127.0.0.1:9153, or
  • Open an SSH tunnel from the operator workstation to the node (ssh -L 9153:127.0.0.1:9153 <node>) and call localhost:9153 through it.

Parameters:

  • scope (string, optional): Which stores the archive carries — all (default), genvm or ops. An absent scope means all, so a caller written before the store split keeps its behaviour. An unrecognised value is rejected with 400.

    ScopeContentsPortable between nodes
    allboth stores, operation store firstNo
    genvmchain-derived history onlyYes
    opsthis node's consensus record onlyNo

    Only genvm may be given to another operator. all and ops carry the vote salts this node generated and the nonce ownership of its operator key; restoring either onto a different node would make that node act on votes it never cast and nonce slots it never signed. The node refuses to start against an operation store stamped with another operator, but that guard runs at startup, not at copy time — treat a non-portable archive accordingly.

    Every archive carries a snapshot.json manifest recording the format version, the scopes inside, the rollup block the GenVM store was captured at, and — for all and ops — the operator address that produced it.

    Manifest version remains 1. finalizedRoot contains a 0x-prefixed, 32-byte chain-trie proof. The cursor and root are read from the completed GenVM checkpoint opened read-only, so they describe the archived files even while live replay advances. An absent root is unavailable, not a zero root. For a pristine empty store, the empty root is Keccak256(0x000000), the proof of an empty root branch. Replay from genesis maintains the finalized trie, including an empty root before any finalization. Every GenVM snapshot past genesis has this field unless its datadir predates root maintenance. Such datadirs require a resync from genesis to obtain roots; continuing replay or restoring their snapshots does not reconstruct missing history.

    The trie maps each address to the storage root of its latest finalized transaction with a storage effect. Only canonical replay finalization updates it, identically on full nodes and validators. A failed execution, a decision without storage state, or a successful execution whose proof equals its predecessor leaves the entry unchanged (and does not insert an absent address). A first empty storage generation also leaves the entry absent. Storage and root writes share one reversible batch, so a canonical reorg rolls both back. Speculative execution, acceptance and appeals do not update this root.

  • destDir (string, optional): Absolute path on the node's filesystem, which must resolve to a location inside the configured snapshot directory (node.admin.snapshot_dir, default <datadir>/snapshots). When set, switches to write-to-path mode; when omitted or empty, the snapshot is streamed back as application/gzip. Paths outside the snapshot directory are rejected with 400.

Returns:

  • Stream mode (destDir omitted): gzipped tar archive in the response body, Content-Type: application/gzip.
  • Write-to-path mode (destDir set): empty body, 204 No Content.

Example Request — Stream Mode (operator):

On the node (after ssh <node>):

curl -X POST http://127.0.0.1:9153/snapshot \
  -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' \
  -o snapshot.tar.gz

Or via SSH tunnel from a workstation (forwards 9153 to the node's loopback):

ssh -L 9153:127.0.0.1:9153 -N <node> &
curl -X POST http://localhost:9153/snapshot \
  -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' \
  -o snapshot.tar.gz

The response body is a stream — curl can write it to a file (as above) or pipe it through any tool that reads stdin, without staging on local disk.

Example Response — Stream Mode:

HTTP/1.1 200 OK
Content-Type: application/gzip
Content-Disposition: attachment; filename="snapshot.tar.gz"

<gzipped tar archive of the snapshot's files>

Example Request — Write-to-Path Mode (upgrade controller):

curl -X POST http://127.0.0.1:9153/snapshot \
  -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"destDir": "/var/lib/genlayer/data/node/snapshots/bradbury-100"}'

Take only the portable half, to hand to another operator:

curl -sS -X POST http://localhost:9153/snapshot \
  -H "Authorization: Bearer $OPERATOR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"scope": "genvm"}' -o genvm-snapshot.tar.gz

Example Response — Write-to-Path Mode:

HTTP/1.1 204 No Content

HTTP Status Codes:

  • 200 OK: Stream mode succeeded; body carries the gzipped tar archive.
  • 204 No Content: Write-to-path mode succeeded; snapshot is on disk at destDir.
  • 400 Bad Request: destDir is not absolute, resolves outside the configured snapshot directory, or already exists.
  • 401 Unauthorized: Missing, invalid, or empty operator token (also returned when GENLAYERNODE_NODE_OPERATOR_TOKEN is unset on the node).
  • 500 Internal Server Error: Snapshot creation failed (e.g., disk full, DB checkpoint error).

Notes:

  • A snapshot may be several GB. In stream mode the connection stays open for the duration of the snapshot — configure reverse proxies / load balancers with adequate read timeouts and disable response buffering.
  • In stream mode, a dropped connection leaves no on-disk artifact on the node; retry the request from scratch.
  • In write-to-path mode, destDir must be an absolute path that does not yet exist and that resolves (after filepath.Clean) to a location inside the configured snapshot directory; the node creates it. Paths that escape the directory via .. or point elsewhere on the filesystem are rejected with 400. This bounds where an operator-token holder can write a checkpoint — still treat the operator token as a credential that can write under the node user's filesystem permissions within that directory.
  • Snapshot directory (node.admin.snapshot_dir): bounds write-to-path mode. A relative value resolves under datadir (so the default snapshots is <datadir>/snapshots); an absolute value is used as-is, letting a node target a dedicated disk. The node creates the directory with 0700 permissions on first use if it does not exist.
  • Docker deploys: in write-to-path mode, destDir (and the snapshot directory) is resolved inside the node container's filesystem, not on the host. The default <datadir>/snapshots falls under whatever volume the deployment mounts for datadir, so it is host-reachable wherever that volume maps. To use a different location, set snapshot_dir to a path that is itself volume-mapped identically on both sides, or stick with stream mode. Stream mode is unaffected — the response body is sent over HTTP regardless of where the node runs.
  • The snapshot includes the full chain database (SSTables, MANIFEST, WAL) at the moment of capture. Treat the artifact as sensitive — anyone holding it can reconstruct the node's state.
  • Token comparison is constant-time and length-padded so neither the value nor the length of the secret can be inferred from response timing.
  • Rotation: update GENLAYERNODE_NODE_OPERATOR_TOKEN and restart the node. Tokens are read once at startup and not hot-reloaded.

Restore A Portable Snapshot:

genlayernode snapshot restore genvm-snapshot.tar.gz --datadir /var/lib/genlayer/restored
# An extracted snapshot directory is also accepted
genlayernode run --config /path/to/network.yaml --config /path/to/restore-overlay.yaml

The overlay sets datadir: /var/lib/genlayer/restored and keeps the default node.genvmdb: genvm.db and node.opsdb: ops.db. Use the source deployment's network configuration; the manifest does not identify the network. The restore command requires an absent or empty datadir, validates version 1 and exactly scope=genvm, checks the manifest cursor and any roots against the checkpoint, and installs the files at <datadir>/genvm.db. It refuses links, unexpected archive entries, and nonportable scopes. It creates no operation store; normal startup creates a fresh ops.db and resumes replay at the durable cursor + 1. Archives are trusted local input with no extraction size cap. Written files and directories are fsynced before installation, and the datadir is fsynced before and after the rename.

Non-default node.genvmdb paths are not supported by restore. A rootless snapshot with a non-zero cursor is accepted with a CLI warning: the restored node will not maintain roots and needs a resync from genesis for root comparisons.

To capture an exact replay boundary, run in full mode with consensus.sync_halt_block set to the target block and wait for genlayer_node_sync_halted == 1 before requesting the snapshot. See the configuration guide for halt health and restart semantics. Restore verifies the checkpoint's metadata, not the correctness of its historical execution. Compare snapshots captured at the same halt block with sync-check.sh --expect-root or --reference-snapshot; restoring at N and replaying to M preserves the same finalized root as replaying from genesis to M.

sync-check.sh --network bradbury --checkpoint N --checkpoint M --halt-at H captures an ascending chain on one datadir, saving snapshots/<block>.tar.gz and .json under its work directory. Every output requires a finalized root. --reference-dir <dir> compares available <block>.tar.gz references and checkpoints.json records each halt, including mismatches before exit 4. Without --halt-at, the final segment reaches its boot head; the timeout covers all segments. --config overrides network defaults, and --genvm-root selects the GenVM directory. The Task equivalent uses NETWORK, comma-separated CHECKPOINTS, HALT_AT, REFERENCE_DIR, and GENVM_ROOT.

Forward-compatibility note: when the zero-downtime upgrade controller (Phase 3) lands, /snapshot will additionally accept a second role (upgrade) so the controller can drive the snapshot + stop + start sequence with its own credential. The operator-token path documented here remains the same — no migration is required for operator tooling.

cURL Example with Bearer header:

curl -X POST http://127.0.0.1:9153/snapshot \
  -H "Authorization: Bearer $GENLAYERNODE_NODE_OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' \
  -o snapshot.tar.gz