Aller au contenu

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.de by 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 for s3:// (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-db and executes on from its head. The node then downloads only the blocks it keeps: with --block-history N the backfill starts at head − N rather 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 2000000 thus 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.
  • full builds 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_status shows download.converting: true and the archive's layout.

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-history raised, or all; --backfill-from lowered). 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-history raised, or all). 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.