fix(storage): make pruning crash-consistent - #3904
Conversation
PR SummaryHigh Risk Overview BlockDB and StateWAL now track a durable tip advanced on ReceiptDB persists a recovered Contracts and tests are updated for pre-flush heads, durable prune caps, restart recovery, and receipt-floor commit ordering. Reviewed by Cursor Bugbot for commit b81206d. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit b81206d. Configure here.
| if err := batch.Set(receiptEarliestVersionKey, encodeBlockNumber(cutoff)); err != nil { | ||
| return err | ||
| } | ||
| if err := s.index.Set(receiptEarliestVersionKey, encodeBlockNumber(cutoff), dbtypes.WriteOptions{}); err != nil { |
There was a problem hiding this comment.
Local pruner uses volatile head
Medium Severity
startPruning still computes its keepRecent cutoff from latestVersion, while pruneBlocksBelow now caps at latestDurableVersion. When the write tip leads the durable tip, that cutoff can land at or above the durable head, so the floor advances too far and drops crash-recoverable receipts that still fall inside the configured retention window. Flush lag makes this an off-by-lag undercount; a stuck flusher can collapse retention to a single durable block.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit b81206d. Configure here.


Summary
Make storage pruning decisions crash-consistent across BlockDB, ReceiptDB, and StateWAL.
This change:
m:earliestfloor atomically withSync: truebefore publishing the in-memory floor;Why
BlockDB writes, StateWAL appends, and Litt receipt bodies are not crash-durable until their respective flush completes. The collector-facing implementations previously calculated retention from newer volatile cursors. A prune followed by a process crash could therefore lose the suffix used to justify the prune and leave less recoverable history than intended.
Receipt pruning also issued the Pebble range tombstone and retention-floor metadata as separate non-sync commits. Those form one logical visibility transition: a crash could retain only one side, and publishing a non-durable floor could release Litt bodies before the floor itself survived restart.
The fixes are conservative: failures, marker loss, or overlapping writes leave the reported head/floor behind and retain extra data rather than over-pruning.
Impact
This is storage crash-consistency hardening. It is not presented as a remotely exploitable security vulnerability or bounty submission. The shared storage garbage collector is not yet wired into production at this revision; the ReceiptDB local-pruning path benefits immediately when the Litt index backend and retention are enabled.
Validation
go test ./sei-db/db_engine/pebbledb ./sei-db/ledger_db/receipt ./sei-db/management/gc ./sei-db/state_db/statewal ./sei-db/ledger_db/block/littblock -count=1-racego test ./x/evm/keeper ./sei-tendermint/internal/autobahn/data -count=1go test ./sei-cosmos/storev2/rootmulti -count=1go test ./sei-db/state_db/sc/flatkv -run 'TestGC|TestGCLatestBlock|TestGCConcurrentWithCommitter' -count=1 -timeout=15mgo veton the changed storage packages andsei-tendermint/autobahn/typesgofmtandgit diff --checkA full FlatKV package run was attempted but exceeded its 10-minute package timeout in pre-existing snapshot/concurrency tests; the focused FlatKV GC suite passed.