[db] Give every snapshot stream a versioned header - #2463
Open
maxkozlovsky wants to merge 2 commits into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a minimal, versioned per-stream header to the on-disk DB snapshot format so loaders can reliably detect format revisions and mismatched shard files, while preserving backward compatibility with headerless snapshots already in the wild.
Changes:
- Introduces
monad_snapshot_stream_header(magic/version/kind/guard) and related constants indb_snapshot.h. - Updates snapshot dumping to write the header once per non-empty stream, and updates loading to optionally consume/validate the header before parsing records.
- Extends snapshot tests to (a) assert headers are present/well-formed and (b) exercise the compatibility path by stripping headers and restoring successfully.
Verdict: CORRECT
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| category/execution/ethereum/test/test_db_snapshot.cpp | Adds tests for stream headers and for restoring from headerless (legacy) snapshots; adds helpers to rewrite streams. |
| category/execution/ethereum/db/db_snapshot.h | Defines the stream header layout and constants (magic/version/guard) and documents stream framing. |
| category/execution/ethereum/db/db_snapshot.cpp | Writes headers on first write per stream and strips/validates headers on load while keeping offsets relative to the first account record. |
🤖 Generated with Claude Code
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
maxkozlovsky
marked this pull request as draft
July 28, 2026 19:36
maxkozlovsky
force-pushed
the
max/snapshot-stream-versioning
branch
2 times, most recently
from
July 28, 2026 22:15
d21a73f to
7da7e92
Compare
maxkozlovsky
marked this pull request as ready for review
July 29, 2026 22:20
maxkozlovsky
force-pushed
the
max/snapshot-stream-versioning
branch
from
July 31, 2026 18:49
7da7e92 to
d2867f4
Compare
kkuehlz
previously approved these changes
Jul 31, 2026
maxkozlovsky
force-pushed
the
max/snapshot-stream-versioning
branch
from
July 31, 2026 21:06
d2867f4 to
2b56a9c
Compare
The snapshot format carried no version marker of any kind: no manifest, no magic, no version byte. Changing any stream's layout would therefore have an older loader read the new bytes as whatever they happened to look like, and the per-file checksums do not help — a well-formed file of an unexpected layout passes them and then misparses. That the format has so far never changed is the only reason this has not bitten. Open each of the four streams with an eight-byte header holding a magic, a format version, and the monad_snapshot_type the stream contains. An unrecognised version now aborts with a clear message, and the kind field means a shard assembled from mismatched files is rejected rather than misparsed. Every record goes through one writer that opens a stream with its header on first use, so a write site cannot leave a stream unframed. Existing snapshots keep loading, because the header's absence is what identifies them: the magic's low byte is below 0xc0, which the RLP list opening an account or eth_header stream cannot be, and the guard byte puts the eight bytes above 2^56, far beyond the account offset or code length that opens a storage or code stream. Account offsets keep counting from the first account record rather than the stream start, so they mean the same thing whether or not a header is present. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
maxkozlovsky
force-pushed
the
max/snapshot-stream-versioning
branch
from
July 31, 2026 22:17
2b56a9c to
c9b11fa
Compare
Chen-Yifan
approved these changes
Aug 12, 2026
kkuehlz
approved these changes
Aug 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The snapshot format carries no version marker of any kind — no manifest, no magic, no version byte. A future change to any stream's layout would be read by an older loader as whatever the new bytes happened to look like. The per-file
.blake3checksums do not help: a well-formed file in an unexpected layout passes them and then misparses.What this does
Opens each of the four per-shard streams (
eth_header,account,storage,code) with an eight-byte header:versionaborts with a clear message instead of misparsing, so a later revision of any stream can be introduced safely.kindrejects a shard assembled from mismatched files rather than silently misreading it.reservedis written as 0 and readers must accept any value, so a later revision can give it a per-kind meaning without a version bump.Compatibility
The header is optional, and its absence is what identifies a snapshot predating it. Detection is structural and works for all four streams because the magic satisfies both constraints at once: its low byte
0x4dis below0xc0, which the RLP list opening anaccountoreth_headerstream cannot be; and the guard puts the eight bytes above 2^56 when read as the leadinguint64of astorageorcodestream, where an account offset or a code length never reaches. That same property makes an older binary abort on a newer snapshot rather than misread it.Account offsets still count from the first account record rather than the stream start, so they mean the same thing whether or not a header is present.
Testing
Both directions were checked end to end against an official mainnet snapshot (block 90045827, 11 GB, 256 shards, written before this header existed), alongside the new
SnapshotStreamHeadersandHeaderlessSnapshotRestoresunit tests:state_rootcarried in the snapshot's own block header,0x4aa9630d…f6d3fe11— canonical mainnet state, not merely a self-consistent result. Restored into a page-encoded target and reproduced the rootmainproduces from the same input.mainbinary aborts in about a second, atdb_snapshot.cpp:227on the first shard'saccountstream, and leaves the target database untouched — the intended loud rejection instead of a misparse.🤖 Generated with Claude Code