Skip to content

feat(seidb): add exact-version state store snapshots - #3919

Open
blindchaser wants to merge 10 commits into
mainfrom
yiren/ss-snapshot-v2
Open

feat(seidb): add exact-version state store snapshots#3919
blindchaser wants to merge 10 commits into
mainfrom
yiren/ss-snapshot-v2

Conversation

@blindchaser

Copy link
Copy Markdown
Contributor

Summary

Add opt-in, exact-version Pebble checkpoints for State Store. This PR contains snapshot creation and retention only; SS rollback remains separate and will use these checkpoints as state-WAL replay inputs.

  • Add ordered checkpoint barriers after version H and before H+1 in each backend's single-writer FIFO.
  • Support Cosmos, unified EVM, and split EVM checkpoints; publish only after all required backends finish.
  • Publish through staging, durable rename, and an atomic current symlink, with startup recovery and retention.
  • Report the highest honest earliest-version floor and persist earliest markers before pruning history.
  • Trigger snapshots only from the block commit path for both populated and empty blocks.
  • Keep snapshots default-off and mirror SC's interval, minimum-time gate, and retention settings.
  • Export snapshot attempts, skips, outcomes, duration, in-flight state, height, retained count, and apparent bytes.

Snapshots require Pebble and hardlinks. All enabled Cosmos and EVM SS databases must share a filesystem with the snapshot root. SS snapshots are internal rollback restore points, not state-sync or archive inputs.

Test plan

  • go test -race -count=1 ./sei-db/db_engine/pebbledb/... ./sei-db/config/... ./sei-db/state_db/ss/... ./sei-cosmos/storev2/rootmulti/... ./app/... ./sei-cosmos/server/config/...
  • golangci-lint run ./sei-db/... ./app/... ./sei-cosmos/storev2/... ./sei-cosmos/server/config/...
  • Verify exact labels and exclusion of writes above the boundary.
  • Verify Cosmos, unified EVM, and split EVM snapshot layouts.
  • Verify state-sync isolation, empty blocks, restart recovery, cancellation, retention, and hardlink rejection.

Made with Cursor

Add opt-in Pebble checkpoints for State Store with ordered queue barriers, crash-safe publication, retention, metrics, and SC-aligned cadence. Preserve honest per-database version bounds across snapshot and prune races, including split EVM stores.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

The latest Buf updates on your PR. Results from workflow Buf / buf (pull_request).

BuildFormatLintBreakingUpdated (UTC)
✅ passed✅ passed✅ passed✅ passedAug 14, 2026, 7:57 PM

@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 70.81930% with 260 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.61%. Comparing base (a34ceab) to head (d04377c).

Files with missing lines Patch % Lines
sei-db/state_db/ss/snapshot/manager.go 56.62% 102 Missing and 55 partials ⚠️
sei-db/state_db/ss/composite/snapshot.go 83.43% 20 Missing and 8 partials ⚠️
sei-db/state_db/ss/evm/store.go 65.82% 23 Missing and 4 partials ⚠️
sei-db/db_engine/pebbledb/mvcc/db.go 78.57% 7 Missing and 8 partials ⚠️
sei-db/management/checkpoint_scheduler.go 75.00% 6 Missing and 4 partials ⚠️
sei-db/state_db/ss/composite/store.go 79.54% 6 Missing and 3 partials ⚠️
sei-cosmos/storev2/rootmulti/store.go 82.35% 2 Missing and 1 partial ⚠️
sei-db/db_engine/pebbledb/mvcc/db_ascending.go 40.00% 1 Missing and 2 partials ⚠️
sei-cosmos/server/config/config.go 50.00% 1 Missing and 1 partial ⚠️
sei-db/common/utils/path.go 50.00% 2 Missing ⚠️
... and 2 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #3919      +/-   ##
==========================================
- Coverage   59.48%   58.61%   -0.88%     
==========================================
  Files        2325     2242      -83     
  Lines      198660   190263    -8397     
==========================================
- Hits       118180   111519    -6661     
+ Misses      69240    68140    -1100     
+ Partials    11240    10604     -636     
Flag Coverage Δ
sei-chain-pr 59.96% <78.91%> (?)
sei-db 70.41% <ø> (-0.22%) ⬇️
sei-db-state-db ?
sei-db-state-db-pr 70.21% <68.96%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
app/seidb.go 90.90% <100.00%> (+0.21%) ⬆️
sei-db/config/sc_config.go 100.00% <100.00%> (ø)
sei-db/config/ss_config.go 100.00% <100.00%> (ø)
sei-db/db_engine/pebbledb/mvcc/metrics.go 50.00% <ø> (ø)
sei-db/db_engine/types/types.go 100.00% <ø> (ø)
sei-db/state_db/sc/composite/store.go 71.87% <100.00%> (-0.22%) ⬇️
sei-cosmos/server/config/config.go 97.69% <50.00%> (-0.75%) ⬇️
sei-db/common/utils/path.go 85.45% <50.00%> (-2.79%) ⬇️
sei-db/state_db/ss/cosmos/store.go 90.16% <93.75%> (+3.49%) ⬆️
sei-db/state_db/ss/snapshot/metrics.go 94.73% <94.73%> (ø)
... and 8 more

... and 124 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2a2ca0794e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sei-db/state_db/ss/composite/store.go

@seidroid seidroid Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A large but carefully built, default-off feature: ordered in-queue checkpoint barriers give exact snapshot labels, publication is staged/renamed/symlinked with startup recovery, and the config plumbing follows the repo's characterization conventions. No blocking correctness or security problems found; the notes below are documentation, test-quality, and edge-case robustness items.

Findings: 0 blocking | 9 non-blocking | 5 posted inline

Blockers

  • None at the file/PR level.

Non-blocking

  • CosmosStateStore.WaitForPendingWrites and EVMStateStore.WaitForPendingWrites are new exported methods with no production caller — they exist solely for snapshot_test.go's settle helper. An export_test.go shim or an unexported hook would keep the public store surface free of test-only plumbing.
  • sei-cosmos/storev2/rootmulti now imports sei-db/state_db/ss/composite only to hold var _ stateStoreSnapshotScheduler = (*sscomposite.CompositeStateStore)(nil), coupling the Cosmos store layer to a concrete SS implementation. The same build-time protection could live in package ss (or as a method on seidbtypes.StateStore), keeping the dependency one-directional.
  • The trigger sits in flush(), which is reached first from GetWorkingHash() — i.e. before Commit. A boundary snapshot can therefore be published for a height the node has not yet committed (SS is already ahead in that window, so the image is self-consistent). Worth confirming this is acceptable for the rollback restore model, since exact labelling is the point of the barrier.
  • 5 suggestion(s)/nit(s) flagged inline on specific lines.
  • 1 non-blocking pre-existing issue(s) listed below under pre-existing issues.

Pre-existing issues

  • [suggestion] sei-cosmos/storev2/rootmulti/store.go empty-block path (~line 276) calls ssStore.SetLatestVersion(currentVersion) synchronously while older changesets may still sit in the async apply queue. ApplyChangesetSync persists the latest-version marker inside its own batch (NewBatch(..., version, ...)), so a later-applied older batch can overwrite the on-disk marker and regress the SS watermark across a crash/restart.

Comment thread sei-db/db_engine/pebbledb/mvcc/db.go Outdated
Comment thread sei-db/state_db/ss/composite/snapshot.go Outdated
Comment thread sei-db/state_db/ss/composite/snapshot.go Outdated
Comment thread sei-db/state_db/ss/composite/snapshot.go Outdated
Comment thread sei-db/state_db/ss/composite/recovery_test.go
Reject historical SS queries below the highest member floor before they can return partial routed state. Keep the snapshot referenced by current during retention so a failed link update cannot leave a dangling target.

Also detach the snapshot design overview from exported const godoc and make recovery mismatch tests assert the tolerated highest-floor contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread sei-db/state_db/ss/composite/store.go Outdated
blindchaser and others added 3 commits August 14, 2026 11:09
The composite read gate turned a prune that races an in-flight historical
query into a process crash: the cosmos KVStore wrapper over a StateStore
panics on any read error, and a query store keeps reading at its fixed
version after the floor moved. Enforce the floor only where an error can be
returned to the caller (query-store construction, Query, VersionExists) and
let a pruned member report absence, which is what its engine already does.

Co-authored-by: Cursor <cursoragent@cursor.com>
Cosmos SS and EVM SS now own their snapshot roots, staging, current links, and retention, while the composite layer only coordinates cadence and best-effort joint publication. This prepares future GC wiring by adding ExternalPruning stand-down without implementing gc.PrunableStore before rollback can report an honest floor.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread sei-db/state_db/ss/snapshot/manager.go
Comment thread sei-db/state_db/ss/snapshot/manager.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 6839692. Configure here.

Comment thread sei-db/state_db/ss/composite/snapshot.go
blindchaser and others added 4 commits August 14, 2026 15:03
PruneSnapshots is the entry point an external collector prunes an SS through, and
gc.PrunableStore only calls it when ExternalPruning reports true. Standing it down in
that mode made it a silent no-op, so snapshots would grow without bound once the
collector owned retention. Only internal count-based retention stands down now, which
is what ExternalPruning is for.

Co-authored-by: Cursor <cursoragent@cursor.com>
A member that could not be staged left barriers queued for the members before it, while
the coordinator released the version for another attempt. The retry then cleared the
staging directory the queued barrier was writing into, and published whatever the
interrupted checkpoint had reached under a label claiming to hold every version up to it.

Staging is now split: every member reserves its directory before any barrier is queued,
so a request either queues all of them or none. Preparing also refuses a staging
directory that already exists rather than clearing it, since a checkpoint may still be
writing there; Open clears the ones a crash left behind. A failed checkpoint now aborts
the staging directories of its peers instead of leaving them until the next startup.

Co-authored-by: Cursor <cursoragent@cursor.com>
Each member counts only its own snapshot directories, so an unpaired newer height took the
keep slot the newest shared height occupied and retention deleted it, in Open and after
every publish. A restore reads the newest height every member holds, so that left nothing
to restore from until the next fully paired publication.

The coordinator now hands the members a shared floor — the newest height they all hold —
which retention keeps regardless of the keep window, and republishes it after every
publication. It is resolved before the members open, because opening one runs its
retention.

Also: the hardlink preflight probe takes a fixed name and is cleared on both sides before
use, so a crash mid-probe leaves one known path per directory instead of accumulating; and
pebbledb reports consecutive prune failures, since every pass raises the earliest-version
marker before it deletes and repeated failures narrow the served range while nothing
leaves disk.

Co-authored-by: Cursor <cursoragent@cursor.com>
Each of these was written twice by the snapshot work, so a later change had two
places to keep in agreement:

- the fan-in over parallel checkpoints, now management.FanIn
- the checkpoint capability test, now management.SupportsCheckpoint
- the retention delete loop, now Manager.removeSnapshots
- the prune marker prologue, now beginPrunePass/endPrunePass, which also holds
  the skip-baseline reasoning that four comments carried
- the "-snapshots" sibling root, now one path helper
- the pending-write capability, now types.PendingWriteWaiter

PruneSnapshots takes publishMu, because publication renames a directory in and
swaps current under it, so a cut line resolved a moment earlier could otherwise
delete what current now names. Retention attempts every candidate and joins the
errors, which is what the count-based path already did.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant