Segment archive¶
A sealed segment is an immutable, self-contained file — which makes it a natural unit of backup and of distribution. The segment archive does both:
- Backup. With
--backup, every segment a node seals — blocks, and with execution the state history and the receipts stream — is copied to object storage (S3 or any S3-compatible service) or to a local directory as it seals, and so are its state snapshots. Off by default. - Download. An archive is also where a node starts: the
backfill worker attaches whole sealed block segments
from it — the public archive at
https://umn.zhurko.deby default, or one you name with--archive-source— instead of fetching their blocks from peers one by one, every segment verified against the chain the store already holds before it is adopted; and under--syncmode snap, the default, an empty node restores the archive's newest state snapshot and executes on from its head, downloading only the blocks it keeps. See Sync modes.
flowchart LR
subgraph node[UMNode]
ST[(Block store)]
CS[(State history)]
RC[(Receipts stream)]
UP[Uploader]
BF[Backfill worker]
DL[Downloader]
end
AR[(Archive<br/>S3 · directory · HTTP host)]
ST -->|seals| UP
CS -->|seals| UP
RC -->|seals| UP
UP -->|sealed files| AR
BF -->|next segment?| DL
AR -->|sealed block segment| DL
DL -->|verify + attach| ST
Backup: --backup¶
export AWS_ACCESS_KEY_ID=… AWS_SECRET_ACCESS_KEY=…
./bin/umnode --backup s3://my-bucket/umnode/mainnet
./bin/umnode --backup r2://my-bucket/umnode/mainnet --r2-account-id <account id>
The uploader runs as a background worker. On start it reads the archive's
manifest (manifest.json, see Layout and manifest),
creating it on a fresh archive and refusing an archive that belongs to another
chain or that publishes a kind of segment in another layout than this node
seals. Then, every time a segment seals — and once a minute as a safety net
— it scans the stores for sealed segments the manifest does not list and
uploads their data files, recording each in the manifest as it lands (one
Uploaded sealed segment line per segment). Index files are never uploaded: a
node rebuilds them from the data file in place.
What is backed up is everything the node has sealed:
| Store | File | Flag |
|---|---|---|
Block store (--block-db) |
NNNN.seg |
always |
State history (<state-db>/changesets) |
NNNN.cseg |
with --execute |
Receipts stream (<state-db>/receipts) |
NNNN.cseg |
with --execute and --receipts derive |
Only sealed segments are backed up: the open segment and the tail are still mutable. A segment the manifest lists at the local file's size is skipped, so a restart resumes where it left off with no local bookkeeping; a sealed file that changes on disk — a receipts backfill rewrites sealed block segments — is uploaded again and its manifest entry replaced. Pruned segments stay in the archive: it is a backup, not a mirror — and pruning waits for it.
Destinations:
s3://bucket[/prefix]— AWS S3, or any service speaking its API (MinIO, Backblaze B2, Wasabi, Hetzner …) via--s3-endpoint. See S3 settings. Objects above 64 MB go up as multipart uploads; every payload is hashed and signed, so a corrupted transfer is rejected by the service rather than stored.r2://bucket[/prefix]— a Cloudflare R2 bucket, addressed by bucket name: the endpoint follows from--r2-account-id, and the signing region and path-style addressing R2 expects are built in. See Cloudflare R2 settings. Transfers work exactly as fors3://(same client, multipart above 64 MB).file:///path/to/dir— a local directory, for a mounted backup disk or NAS. Files are written whole (temp file + rename), so a node downloading from the same directory never sees a partial file.
The URL is the archive root, used exactly as given, and an archive holds
one chain and one layout per kind of segment: give each network its own
root (…/mainnet, …/sepolia), and let nodes with the same locked store
settings share one. The manifest check turns a mix-up into an error log rather
than a mixed archive.
Pruning waits for the backup¶
With --backup, no retention window ever drops a sealed segment the
archive does not hold yet. The uploader installs a prune guard on every
store it backs up, and a prune — --block-history,
--state-history or
--receipts-history — stops at the first block of the lowest sealed segment
the guard has not cleared, keeping that segment and everything above it,
whatever the window says. The floor the store records is the one the files
follow, never one ahead of them: the block pruner's floor is decided under the
store's lock before it is written, so even a restart — which finishes an
interrupted prune below the recorded floor without asking — cannot drop a
segment the archive lacks.
A segment is cleared once its data file, as it is on disk, has been
uploaded and listed in the manifest: a file that changes afterwards (a
receipts rewrite) is held again until the
next scan has dealt with it, and an upload whose manifest write failed does
not count until the write is retried. Until the manifest has been read and the
first scan has run — after a restart, say — nothing is cleared, so pruning
waits for the backup to be verified; an archive that is slow, unreachable or
incompatible therefore holds pruning back rather than lose a segment, and
the floors catch up as the uploads land. The node logs
Pruning held back by the segment archive once per segment held, and
debug_status shows what is held as
upload.holdsPruning (see Status and metrics).
Download: sync modes¶
./bin/umnode # snap sync from the public archive
./bin/umnode --archive-source https://mirror.example.org/eth/mainnet
./bin/umnode --archive-source s3://my-bucket/umnode/mainnet
./bin/umnode --archive-source r2://my-bucket/umnode/mainnet --r2-account-id <account id>
./bin/umnode --archive-source file:///mnt/archive/mainnet
./bin/umnode --syncmode full --archive-source none # peers only, state from genesis
--syncmode is how an empty node gets its state:
snap(the default) restores the archive's newest state snapshot — at or below--backfill-to, when set — into the empty--state-dband executes on from its head. The node then downloads only the blocks it keeps: with--block-history Nthe backfill starts athead − Nrather than genesis — pulled down to the snapshot's head when that is lower, since execution needs every block from there on — and the restore fetches only the sealed history and receipts segments within--state-history/--receipts-history. A BNB Smart Chain node with--block-history 2000000thus starts from a few hundred gigabytes rather than the whole chain. Snap falls back to full, with a warning, when the archive is unreachable or publishes no snapshot for the network — the public archive publishes none: snapshots come from an archive you trust, see What you trust.fullbuilds the state from genesis by execution, which needs every block: the backfill fills from genesis — from the archive where it has the segments, from peers otherwise — and the executor follows.
Either way a node whose state store already exists continues as it is —
the mode describes the start, not the run — so a node synced with snap
keeps its flags, and a full sync in progress is never replaced by a snapshot.
Without execution the two modes are the same: the blocks the node keeps, from
the archive where possible.
Block segments come from the archive in both modes: whenever the fill
reaches a segment boundary — the next segment above the forward frontier,
or the one below the reverse floor — it consults the archive's manifest first.
If the archive covers the segment, the archived file(s) are downloaded into the
store's staging directory (<block-db>/segments/incoming/), checked against
the manifest's size and SHA-256, and handed to the store, which verifies and
attaches them; the fill then continues above (or below) the attached segment.
If the archive lacks it, the segment is fetched from peers as usual and the
manifest is read again ten minutes later (archives grow). Peer chunks end at
segment boundaries while an archive is configured, so the frontier always lands
on one. A segment that extends into the recent, still-mutable top of the chain
(within --seal-confirmations of the head) is never attached — it belongs in
the tail — and the segment holding the fill's lower bound on an empty store is
fetched from peers, since nothing stored can vouch for it yet.
Another layout? Converted on the fly¶
An archive is sealed in the layout of the node that published it — its segment size, block grouping and codec — which need not be this node's. The manifest records that layout, and the downloader acts on the difference:
- Same layout. The archived data file is exactly what this store would have
sealed: it is adopted as it is, with the index built locally
(
AttachSealed). - Any other layout. The archived segments covering the local segment's
block range are downloaded (one archived segment may cover several local
ones, or several archived ones make one local segment), read back block by
block, and re-packed into the local layout as they are verified
(
AttachBlocks): local segment size, local grouping, local codec, compressed at the local seal level — the same work a local seal does. Downloaded archived segments stay staged while the neighbouring local segments still need them.debug_statusshowsdownload.converting: trueand the archive'slayout.
So any node can use any archive; a node whose settings match the publisher's simply skips the re-pack.
Sources. With no --archive-source the node uses the public archive:
https://umn.zhurko.de/<network id>-<first 4 bytes of the genesis hash> —
e.g. https://umn.zhurko.de/1-d4e56740 for mainnet — derived from the
network, so nothing need be configured per chain. A user-given source is an
http(s)://, s3://, r2:// or file:// root URL used exactly as given (it
must point at the archive of this network; the manifest is checked);
--archive-source none uses no archive at all (peers only). An S3 source
needs no credentials when the bucket allows public reads; an r2:// source
always does (see below), so read a public R2 bucket through its https://
URL instead.
The public archive is being populated
The exact paths under https://umn.zhurko.de may still change. Until an
archive carries a manifest for the network, the downloader logs
Segment archive has nothing for this chain yet once and the fill
proceeds from peers; a snap sync on it says so and builds from genesis.
What is verified¶
An attached segment is as trustworthy as one fetched block by block from peers — the archive is trusted for nothing. A downloaded file must first match the manifest's size and SHA-256 (a resumed or damaged transfer is caught here) and scan as an intact sealed file (every record's CRC). Then, before a segment is adopted, the store checks that:
- a file adopted as it is carries this store's locked layout — codec, block grouping — and the right first block (an archive of another layout is converted instead, see above);
- every block decodes, carries its own number, links to the block before it (parent hash), and matches its header's roots — transactions, uncles, withdrawals and, when stored, receipts — the same checks the fetcher makes on every block a peer serves;
- the segment links to the chain the store holds: block 0 must hash to the store's genesis, and the stored block just below or just above the segment must link to it. A segment that links to nothing stored is refused.
The index is always built locally from the verified data — archives carry no index files.
Verification runs in parallel across cores (the root derivations dominate) and logs its progress every 30 s on a large segment. A segment that fails is discarded, logged as a warning, and left to the peers; the archive is asked about it again ten minutes later.
Attaching brings everything the store keeps per block up to date: the
hash index entries, the transaction total, and the
missing-receipts bitmap — blocks the archiving node stored without receipts
(--receipts derive, the default) are marked for the
receipts backfill exactly as if they had
been imported locally, so a --receipts fetch node fills them from peers.
Resuming and failures¶
A download interrupted by a restart resumes from the bytes already staged (the remotes serve byte ranges). An archive that cannot be reached is backed off (15 s doubling to 5 min) and the fill proceeds from peers meanwhile; an archive holding another chain is disabled for the run with an error log. None of this ever stalls the sync — the archive only ever saves work.
Why the backfill downloads block segments only¶
The backfill attaches blocks only: an executing node produces its own changeset and receipts segments, and the archive carries those for backup — so a node that prunes or loses its history can get it back. They come back in two places: a snap sync's restore, which fetches the sealed segments the snapshot's catalogs list within the node's retention windows, and the history extension below, when a window grows.
Pruned history, fetched back¶
Pruning under a retention window is not final: a node restarted with a larger window gets what it pruned back from the archive.
- Blocks (
--block-historyraised, orall;--backfill-fromlowered). The backfill lowers the store's prune floor to the window and, once the forward fill finds the stored chain complete, fetches the gap downward from the former floor: every chunk chains up to a stored, verified block, whole segments come from the archive — each verified against the stored block above, like any attached segment — and the rest from peers, so nothing below the stored history is taken on trust. Without an archive the blocks come from peers alone. - State history and receipts (
--state-history/--receipts-historyraised, orall). These cannot be re-executed — the state the pruned blocks started from is gone — so they come from the archive only. The history extender works each stream top down from its floor: a segment is downloaded into the stream's directory (resumable), adopted into the changeset store (catalogued, indexed), its blocks filed into the history index, and the floor lowered to it — so reads below the old floor become available segment by segment, and progress survives a restart. The extension stops at the first segment the archive lacks (a pruned publisher carries its own window only), with a warning naming where history stays pruned; an archive sealed in another layout for the stream cannot be adopted and is reported the same way. Without an archive the node says once what cannot come back and leaves the floors where they are.
Both run at start; the pruner then raises the floors again as the chain
grows, as usual. debug_status shows the state side
under execution.historyExtension (running, the floors wanted and current,
the segment being brought back and its phase — download, adopt, index —
adopted, done, lastError), the block side as the backfill's progress and
blocks.prunedBelow.
Extra block ranges¶
A pruned node can keep ranges of history outside its window:
--extra-archive-blocks A1:B1,A2:B2,… names inclusive block ranges the store
keeps whatever the window says — a BNB Smart Chain node with
--block-history 2000000 --extra-archive-blocks 1000000:1500000 holds the
recent two million blocks and the half million from block one million. Each
range is padded 256 blocks below (BLOCKHASH reaches that far back, and the
Parlia rules read recent headers) and widened to whole segments, the unit the
store prunes and fetches in. The pruner never drops a segment sharing a block
with a range, and the backfill, once
the window is filled, fills each range downward from the block above it:
that block — stored, or else a peer's taken on trust, as a reverse fill's top
is — anchors the fill, whole segments come from the archive, each verified
against the block above, the rest from peers. Ranges are filled highest first,
and a filled range is left alone.
The state follows, within what the archive has. A read at block N needs every
change from N to the head (a point-in-time read finds a key's next change
above N in the history index), so a range cannot carry state on its own: with
--execute the history prune stops just below the lowest range — its
first block executes on the state of the block before it, so the
--state-history window reaches down to that block — and the
history extender fetches the changesets from
there up where the node had pruned them (a snap sync's
restore fetches them from the start). The
receipts stream keeps the segments covering a range below its floor and
fetches the missing ones; inside a range the receipts and logs serve as usual,
outside it the floor rules. So inside the ranges eth_call, eth_getBalance,
the receipts and debug_trace* all serve — when the archive has the history.
Without an archive, or with one that lacks it, the ranges hold blocks only,
and the node says so at start.
The ranges are not persisted: they are named on each start, like the
window, and a start without the flag treats them as pruned history — the
block store drops their segments below the floor on open, the state history
and the receipts stream at their next prune.
debug_status lists them as blocks.extraRanges, each
with how far its fill has come.
State snapshots¶
By default the archive holds nothing a node could not re-derive: state is built from genesis by local execution (see EVM & state), and a fresh node re-executes the whole chain — hours on a short chain, days on mainnet. A state snapshot is this node's own executed state at one head, published to its archive next to the segments, so a fresh node can restore it and execute on from that head.
# publishing node: a snapshot once the head reaches 1,000,000 blocks, then every 1,000,000, keeping the newest two
./bin/umnode --backup s3://my-bucket/umnode/bsc --state-snapshot-every 1000000 --state-snapshot-keep 2
# any node that backs up with --execute: a snapshot now, at the current head
curl -s http://127.0.0.1:8550 -d '{"jsonrpc":"2.0","id":1,"method":"debug_snapshotState","params":[]}'
# fresh node: snap sync (the default) from that archive — the newest snapshot into the empty --state-db, then execute on, keeping 2,000,000 blocks
./bin/umnode --archive-source s3://my-bucket/umnode/bsc --block-history 2000000
When one is taken. Two ways, which combine:
- On a schedule —
--state-snapshot-every N: the first snapshot once the executed head reaches N, the next once it is N blocks past the newest published one, however that one was taken.0, the default, schedules none. - On request —
debug_snapshotState: the uploader's next pass, which the request wakes it for, takes one at whatever head the store is at then, due or not. A request is queued (a second one while it is queued, or while a snapshot is being taken, is a no-op) and stands until the snapshot lands or the request is dropped.
Either way a snapshot is taken only once every sealed history segment is in the archive — a restore must find them — so a pass whose segment uploads are still pending leaves it to the next one; and a snapshot that fails (the archive unreachable, the scratch disk full) is retried by the next pass, after the uploader's backoff, until it lands or is cancelled.
Interrupting one. debug_cancelStateSnapshot
stops the snapshot being taken: the digest, the pack or the upload ends within
a read, the scratch files are removed and the manifest is left as it was. The
checkpoint itself — seconds under the store's lock — runs to its end and is
then discarded, and a snapshot whose upload has landed is past interrupting:
its manifest entry follows. A queued request that no pass has picked up yet is
simply dropped. A scheduled snapshot that is cancelled is not retaken a
minute later: the schedule moves on by one period, as if it had been taken,
until the node restarts or a snapshot on request lands first. A cancel also
ends the retries of a snapshot that keeps failing.
Watching one. debug_status shows the snapshots
under archive.upload.snapshot (see Status and metrics):
the schedule and the request queued; the snapshot being taken — its head,
trigger (request or schedule), phase (checkpoint, digest, pack,
upload or manifest) and the bytes / total of the pack or the upload so
far — and how the last one ended (published, cancelled or failed). The
node logs Taking state snapshot, Uploaded state snapshot and State
snapshot cancelled.
What a snapshot holds. The state store minus its sealed segment files: Pebble
checkpoints of the flat/ and hist/ instances (hard-linked, taken after a
flush, so they are self-contained), the changeset and receipts streams' pending
tails and MANIFEST catalogs, the WAL-less head marker if any, and a
snapshot.json naming the chain, the head and its hash, and the state
digest. Sealed .cseg files are left out — they are immutable and archived
one by one. The whole directory travels as state/<head>.tar.zst; the
manifest lists each snapshot with its head, head hash, size, SHA-256, digest
and the client that took it. Taking one pauses commits for the flush and the
checkpoint (seconds); the digest, the pack and the upload run on the uploader,
off the executor. Older snapshots beyond --state-snapshot-keep are deleted
from the archive as new ones land.
Restore. Under --syncmode snap, the default, a
start with an empty (or missing) --state-db picks the archive's newest
published snapshot — at or below --backfill-to when that is set — downloads
it (resumable), checks its size and SHA-256, unpacks it, refuses one of
another network or genesis, fetches the sealed segments the two catalogs list
within the node's retention windows (--state-history,
--receipts-history: a segment entirely below head − window would only be
pruned, so it is left out and the store is pruned to that floor once open;
indexes are rebuilt locally; a segment the archive lacks is catalogued as
detached, so reads in its range report that rather than fail), recomputes
the state digest and refuses a mismatch, and only then opens the store. An
archive that offers no snapshot — unreachable, or publishing none for the
network — makes the node say so and build from genesis instead; a snapshot
that is found but fails to download or verify is a start-up error (a retry
resumes the download). A state directory that already holds a store is left
alone. Execution then resumes at the snapshot's head: the executor's
parent-hash check will not run block h+1 on a snapshot whose head hash it
does not link to, so a snapshot of another fork can never be built on — and
the blocks from h+1 up come from the backfill, which
starts at the snapshot's head or the retention window, whichever is lower.
What you trust. A snapshot cannot be proven against the chain the way a
block segment can — UMNode keeps no state trie, so there is no state root in
the header to check it against. Restoring one means trusting the archive's
publisher for the state itself; use it with your own archive, or one you
trust. What can be checked is the state digest: the SHA-256 of every
account, slot and code entry of the flat state in key order, a pure function
of the chain that every honest node computes identically for the same head.
A restored snapshot is checked against the digest its publisher recorded, and
digests of the same head from independent publishers can be compared with
each other. --state-snapshot-digest=false skips computing and checking it.
Layout and manifest¶
An archive root holds one chain:
manifest.json the table of contents
blocks/0000000123.seg sealed block segments
changesets/0000000123.cseg sealed state-history segments
receipts/0000000123.cseg sealed receipts-stream segments
state/0004000000.tar.zst state snapshots (--state-snapshot-every, or on request)
Only data files are published — the stores' own file names, so an archive is a plain copy of the segment directories minus the indexes. The manifest is the authority on what the archive holds:
{
"formatVersion": 1,
"networkID": 1,
"genesisHash": "0xd4e5…",
"client": "UMNode/v0.2.114",
"createdAt": "…", "updatedAt": "…",
"blocks": {
"segmentSize": 50000, "groupSize": 64, "codec": "s2", "compressionLevel": 10,
"segments": [
{ "index": 0, "first": 0, "last": 49999, "file": "blocks/0000000000.seg",
"size": 183421889, "sha256": "…" },
…
]
},
"changesets": { "segmentSize": 50000, "groupSize": 128, "codec": "s2", "segments": [ … ] },
"receipts": { … },
"state": {
"snapshots": [
{ "head": 4000000, "headHash": "0x…", "file": "state/0004000000.tar.zst",
"size": 7812345678, "sha256": "…", "digest": "…", "client": "UMNode/v0.2.119", "createdAt": "…" }
]
}
}
- The chain — network id and genesis hash — plus the creating client and times. The uploader creates the manifest on a fresh archive and refuses an archive holding another chain; the downloader refuses to use one.
- Per kind of segment, the layout it was sealed in — blocks per segment, blocks per group, codec (and the seal level, for information) — and the exact list of published files, each with its block range, size and SHA-256. A downloader never guesses a name, verifies every file it fetches, and knows whether it must convert. One archive holds one layout per kind; an uploader whose store is sealed otherwise is refused.
- The manifest is rewritten after every uploaded segment, so it is at most one segment behind the files.
A directory assembled by hand is a valid archive once it carries a manifest listing its files.
S3 settings¶
Credentials come from the standard AWS environment variables:
AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and, for temporary credentials,
AWS_SESSION_TOKEN. Without them requests are anonymous, which reads a
public bucket but cannot write (an s3:// upload destination without
credentials is a start-up error). Instance roles and SSO are not supported yet.
| Flag | Also from | Meaning |
|---|---|---|
--s3-endpoint ⚙ |
AWS_ENDPOINT_URL |
Endpoint URL of an S3-compatible service; empty selects AWS S3. |
--s3-region ⚙ |
AWS_REGION, AWS_DEFAULT_REGION |
Signing region (AWS: the bucket's region); us-east-1 when unset, which is what most S3-compatible services expect. |
--s3-path-style ⚙ |
— | Address objects as <endpoint>/<bucket>/<key> rather than <bucket>.<endpoint>/<key>. Defaults to on with a custom endpoint and off for AWS. |
The S3 client is a small built-in one (SigV4 signing; PUT, GET, HEAD and multipart uploads) rather than an AWS SDK, so the binary stays pure Go. Requests are retried on transport errors and 5xx / 429 responses.
A missing object may answer 403, not 404
S3 answers a HEAD or GET of a missing key with 403 Forbidden when the
credentials lack s3:ListBucket. Both workers treat that like "not there",
so nothing breaks — but grant s3:ListBucket (or make the bucket's
listing public) for the uploader's presence checks to be exact.
Cloudflare R2 settings¶
R2 speaks the S3 API, and r2:// is that client with R2's particulars built
in, so nothing but the account and a token need configuring:
export R2_ACCOUNT_ID=0123456789abcdef0123456789abcdef # or --r2-account-id
export R2_ACCESS_KEY_ID=… # the R2 API token's key pair
export R2_SECRET_ACCESS_KEY=…
./bin/umnode --backup r2://my-bucket/umnode/mainnet
The account id is the one shown on the dashboard's R2 overview; it selects
the endpoint https://<account id>.r2.cloudflarestorage.com. Credentials are
an R2 API token's access
key pair with Object Read & Write on the bucket (Object Read for a
download-only node). When neither R2_* key is set, the AWS_ACCESS_KEY_ID /
AWS_SECRET_ACCESS_KEY pair is used instead, which is what Cloudflare's own
examples set. A bucket created in a jurisdiction (eu, fedramp) is
reachable only through that jurisdiction's endpoint: set --r2-jurisdiction
to match.
| Flag | Also from | Meaning |
|---|---|---|
--r2-account-id ⚙ |
R2_ACCOUNT_ID, CLOUDFLARE_ACCOUNT_ID |
Cloudflare account id; selects the endpoint. Required for r2://. |
--r2-jurisdiction ⚙ |
R2_JURISDICTION |
eu or fedramp for a bucket created in that jurisdiction; empty for the default. |
The signing region is auto and objects are addressed path-style, as R2
expects; --s3-endpoint, --s3-region and --s3-path-style do not apply to
r2:// URLs, so one node can back up to R2 and read from an S3 archive (or the
other way round) without the settings interfering.
R2's S3 API has no anonymous access
Every request to the R2 endpoint must be signed, so an r2:// URL — as an
upload destination or a download source — needs credentials, and the node
refuses to start without them. To read a public R2 bucket without a
token, point --archive-source at its public URL (https://pub-….r2.dev
or the custom domain in front of it) instead: the HTTP source does the job.
r2:// is also the way to go when the same R2 bucket is already reachable via
--s3-endpoint https://<account id>.r2.cloudflarestorage.com --s3-region auto:
both work, but r2:// keeps the S3 settings free for a real S3 archive.
Status and metrics¶
debug_status gains an archive section whenever an
archive is configured:
"archive": {
"upload": { "destination": "s3://my-bucket/umnode/mainnet", "verified": true,
"segments": 880, "bytes": 1234567890, "published": 880, "pending": 0,
"holdsPruning": { "blocks": 350000 }, "lastUploadAt": "…",
"snapshot": { "every": 1000000, "uploaded": 1, "published": 2, "latest": 4000000,
"nextDue": 5000000, "requested": false,
"running": true, "head": 4321000, "trigger": "request", "phase": "upload",
"bytes": 2147483648, "total": 7812345678, "startedAt": "…",
"last": "published", "lastHead": 4000000, "lastAt": "…" } },
"download": { "source": "https://umn.zhurko.de/1-d4e56740", "state": "ready",
"segments": 12, "bytes": 987654321, "lastSegment": 11 }
}
upload.segments and upload.bytes count what this run has uploaded,
upload.published what the manifest lists in all, upload.pending how many
files still awaited upload at the last scan, and upload.holdsPruning — per
kind, present only while something is held — the first block of the lowest
sealed segment that pruning wanted to drop and the archive does not hold yet
(see Pruning waits for the backup).
upload.snapshot, present with --execute, is the state
snapshots: every (the schedule; 0 is on request only),
uploaded since start, published and the latest head the manifest lists,
nextDue (the head the schedule takes the next one at), whether one is
requested, and — while one is running — its head, trigger, phase,
the bytes / total of the pack or upload so far, startedAt and
cancelling once a cancel has interrupted it; last, lastHead and lastAt
say how the last one ended (published, cancelled or failed).
download.state is checking (identity not read yet), ready, unavailable
(nothing for this chain, or unreachable — re-checked later) or mismatch (the
archive holds another chain). Both carry a lastError after a failed step.
Prometheus (Observability):
| Metric | Meaning |
|---|---|
umnode_archive_uploaded_files_total / _uploaded_bytes_total |
Files and bytes backed up. |
umnode_archive_attached_segments_total / _downloaded_bytes_total |
Block segments attached from an archive, and the bytes downloaded for them. |
Configuration¶
The ⚙ flags can be set per-network in the config YAML (SyncMode,
ArchiveSource, Backup, S3Endpoint, S3Region, S3PathStyle,
R2AccountID, R2Jurisdiction); an explicit flag wins. See Configuration.
The former names --archive-upload / ArchiveUpload and
--archive-state-every / -keep / -digest are still accepted.
| Flag | Default | Meaning |
|---|---|---|
--syncmode ⚙ |
snap |
How an empty node gets its state: snap restores the archive's newest state snapshot and executes on from its head, downloading only the blocks the node keeps (falls back to full when the archive offers none); full builds it from genesis. See Sync modes. |
--archive-source ⚙ |
(public archive) | The archive to download from — block segments during backfill, and the snapshot under snap: an http(s)://, s3://, r2:// or file:// root, used exactly as given; empty selects the public archive's path for the network; none uses no archive. Needs --block-db. |
--backup ⚙ |
(none) | Back up every sealed segment, and the state snapshots, to this archive root (s3://bucket[/prefix], r2://bucket[/prefix] or file:///dir). Needs --block-db. |
--state-snapshot-every |
0 |
With --backup and --execute: publish a state snapshot once the executed head reaches N blocks and then every N; 0 schedules none (one is taken on request either way). |
--state-snapshot-keep |
2 |
How many state snapshots stay published. |
--state-snapshot-digest |
true |
Compute the state digest when publishing a snapshot and verify it when restoring one. |
--s3-endpoint ⚙ |
(AWS) | Endpoint URL of an S3-compatible service for s3:// archives. |
--s3-region ⚙ |
us-east-1 |
Signing region for s3:// archives. |
--s3-path-style ⚙ |
auto | Path-style addressing for s3:// archives (on with a custom endpoint). |
--r2-account-id ⚙ |
(env) | Cloudflare account id for r2:// archives; selects the endpoint. |
--r2-jurisdiction ⚙ |
(none) | eu or fedramp for r2:// buckets created in that jurisdiction. |