Repository navigation
feat(storage): diff beacon states against their snapshot, not their parent - #684
Conversation
🤖 Kimi Code ReviewI'll review this PR which changes beacon state storage from parent-linked diffs to snapshot-based diffs. This is a significant storage optimization where diffs are computed against the nearest snapshot rather than the immediate parent. Overall AssessmentThe PR is well-structured with good documentation and comprehensive tests. The core idea—making beacon state reads O(1) in snapshot+delta rather than O(n) in delta chain length—is sound. However, I found several issues ranging from correctness concerns to performance pitfalls. Critical Issues1.
|
| Priority | Item | Location |
|---|---|---|
| Critical | Add cycle detection to nearest_snapshot_root |
state_writer.rs:341-358 |
| Critical | beacon_state_delta::unframe should validate input |
state_writer.rs:347-349 |
| High | Distinguish "no block" from "no diff" errors | state_writer.rs:343-344 |
| Medium | Consider snapshot cache for reorg patterns | state_writer.rs:695-713 |
| Medium | Add reorg-across-snapshot-boundary test | store.rs tests |
| Low | Fix docstring typo | state_writer.rs:261 |
| Low | Clarify last_base contains block roots, not state roots |
state_writer.rs:579-580 |
The PR is fundamentally correct and the optimization is valuable. The critical fix needed is cycle detection in nearest_snapshot_root before this is safe to merge.
Automated review by Kimi (Moonshot AI) · kimi-k2.6 · custom prompt
🤖 Codex Code ReviewNo correctness or consensus-layer bug jumps out in this PR. The change is storage-local, and the new beacon snapshot-base scheme looks internally consistent with the reader, pruning model, and backward-compat path. What Looks Good
Review Notes
Minor Concern
Overall, this looks good to me. Automated review by OpenAI Codex · gpt-5.4 · custom prompt |
🤖 Claude Code ReviewReview of PR 684: diff beacon states against their snapshotI read the diff only and did not build or run the tests. The design is sound and I found no blocking bugs. What I checked and found correct
Minor points
Overall this looks good: the tests cover the common, restart, fork and legacy-row paths, and the docs are consistent with the code. Automated review by Claude (Anthropic) · sonnet · custom prompt |
cdf898e to
4d4ca38
Compare
4d4ca38 to
69db250
Compare
…arent A beacon non-anchor state was an xdelta3 delta against its parent, so a cold read folded up to an epoch of deltas in sequence, each over a full encoded state. Diffing every state against the nearest snapshot on its branch makes a cold read one snapshot plus one delta, whatever the interval. The writer keeps the current snapshot's bytes in memory instead of the parent's, and remembers which snapshot the last written state belongs to, so the common case reads nothing extra. On a miss (a restart, or a fork off an older block) it follows StateDiffs frames to the first root with no diff row, which is the snapshot on a beacon directory. The reader is unchanged: every frame names its own base, so chains written parent-linked by an earlier build still fold, and no DB_VERSION bump is needed. Diffs grow through the epoch, since each carries every change since its snapshot. Pruning finalized states keeps only a few epochs of them on disk. Lean keeps parent-linked diffs: its StateDiff regenerates historical_block_hashes from the parent link. (cherry picked from commit a5da3a2)
69db250 to
4db207b
Compare
🗒️ Description / Motivation
Stacked on #683.
Today a beacon non-anchor state is stored as an xdelta3 delta against its parent. A cold read of a state late in an epoch therefore folds up to 31 deltas in sequence, each over a full encoded state. This PR diffs every non-anchor beacon state against the nearest snapshot on its branch instead, so a cold read is one snapshot plus one delta.
What Changed
state_writer.rs): the writer's memo changes from the parent's encoded bytes (encoded_memo,encoded_parent_bytes) to the current snapshot's bytes (snapshot_memo). A newlast_baserecords which snapshot the last written state belongs to.nearest_snapshot_rootfollowsStateDiffsframes to the first root with no diff row. On a beacon directory a state is a snapshot or a diff, never both. It never probesStateswithcontains, since on RocksDB that reads the whole blob value.plan_beacon_state_write: the frame now names the snapshot as itsbase_root.StateWrite.encodedisSomeonly for snapshots.insert_state,beacon_state_delta.rsand theStateDiffstable doc;BEACON_SNAPSHOT_INTERVAL/snapshot_interval, which now explain that the interval bounds delta size and snapshot frequency rather than the length of the fold (the value is unchanged);docs/data_storage.md.Correctness / Behavior Guarantees
DB_VERSIONbump: every frame names its own base, so a directory holding parent-linked diffs from an earlier build still reads. The writer's walk also handles such rows when picking the snapshot for a new diff.StateDiffrebuildshistorical_block_hashesfrom the parent link, which only holds when the base is the parent.Tests Added / Run
store.rs:every_beacon_diff_in_an_interval_names_the_intervals_snapshottwo_children_of_a_mid_interval_block_both_diff_against_the_snapshota_restart_mid_interval_keeps_diffing_against_the_snapshot: exercises the memo miss.the_snapshot_walk_follows_old_parent_linked_rows_to_the_snapshot: uses hand-written parent-linked rows.a_cold_beacon_state_reads_back_as_its_snapshot_plus_one_delta, renamed from the old fold test;plan_beacon_state_writeunit tests instate_writer.rs.--profile release-fast --lib):ethlambda-storage: 162ethlambda-state-transition: 325make lintis clean.Related Issues / PRs
✅ Verification Checklist
make fmt: cleanmake lint(clippy with-D warnings): cleanmake test: lib tests of the affected crates only; spec suites not run locally