Skip to content

cmd/capcli: dump-blobs-snapshots can silently freeze holes over correct historical segments #24037

Description

@lystopad

capcli dump-blobs-snapshots can silently replace correct historical blob segments with empty ones
and exit 0. Found while repairing a gnosis archive node; it destroyed ~1,580 chunks of previously
sound segments, including ranges that had been downloaded and hash-verified from the CDN.

Three independent defects combine.

1. Completeness checking is disabled

cmd/capcli/cli.go passes nil for blobCountFn:

return freezeblocks.DumpBlobsSidecar(ctx, blobStorage, db, from, to, salt, dirs,
    estimate.CompressSnapshot.Workers(), nil, log.LvlInfo, log.Root())

In DumpBlobSidecarsRange that sets sanityCheckBlobCount := blobCountFn != nil to false, which
disables both guards. The remaining path is:

if commitmentsCount == 0 {
    sn.AddWord(nil)
    continue
}

commitmentsCount comes from storage.KzgCommitmentsCount(ctx, blockRoot). A block with
commitments whose count row is absent from the blob store therefore reads as "no blobs" and is
written as an empty entry. The dump reports success.

cl/antiquary/antiquary.go:503 has always passed a real function, so the node path is unaffected —
only the manual command.

2. The start slot is pinned to the Deneb fork, and segments are rewritten unconditionally

from := ((beaconConfig.DenebForkEpoch * beaconConfig.SlotsPerEpoch) / snaptype.CaplinMergeLimit) * snaptype.CaplinMergeLimit

DumpBlobsSidecar iterates from → to with no existing-file check, and DumpBlobSidecarsRange
creates a fresh compressor for each range. So --to=30060000 on a node whose frontier was
28,530,000 did not dump 153 chunks; it rewrote ~1,580, back to 14,230,000.

That interacts badly with (1): blobs already frozen below the frontier are pruned from the hot
store, so every one of those ranges took the commitmentsCount == 0 branch.

3. It is version-blind

It writes current-version files regardless of what the range is pinned at. The gnosis manifest is a
mix of v1.0 and v1.1; the dump wrote v1.1 segments and indexes over v1.0 ranges. After
cleanup this left orphan v1.1-*-blocksidecars.idx files whose v1.0 counterparts had been
restored from the CDN.

Reproduction

On an archive node with a pruned blob store:

capcli dump-blobs-snapshots --chain=gnosis --datadir=<dir> --to=<frontier>
capcli check-blobs-snapshots-count --chain=gnosis --datadir=<dir> --from=<deneb-fork>
  → slot 14237725: blob count mismatch, have 0, want 1

The dump exits 0; the checker finds the damage afterwards.

Fixes

Two are implemented on lystopad/capcli-blob-fetch-36 and can be upstreamed as-is:

  • 4ba6a0bec2 — build the same blobCountFn the antiquary uses, so the dump fails on a gap
    instead of freezing one.
  • c9ff7a7ea5 — add --from, validated to be a CaplinMergeLimit multiple and at or above the
    fork boundary, so a range can be repaired without rebuilding history.

Still open:

  • Refuse to write a version the manifest pins lower. With (1) and (2) fixed the destructive path
    is much narrower, but a run that legitimately covers a v1.0 range will still emit v1.1.
  • Skip ranges whose segment already exists unless explicitly asked to overwrite. Cheap, and it
    removes the "rebuild everything by default" behaviour entirely.

Recovery, for anyone who hits this

erigon snapshots reset --datadir=<dir> --local=false drops the preverified lock so OtterSync
reconciles against the remote manifest and re-downloads damaged published ranges hash-verified.
--local defaults to true and deletes the entire chaindata directory — on a snapshotter that
forces a full resync, so pass --local=false and --dry-run first. Segments above the published
ceiling are locally produced and must be re-dumped.

Note that blob segments are blobsidecars.seg while their index is blocksidecars.idx
(db/snaptype/type.go:157), so any glob-based cleanup on one name silently misses the other.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

CaplinCaplin: Consensus Layer, Beacon API

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions