fix(storage): save_block atomic — single MDBX write transaction#762
Conversation
|
Warning Review limit reached
More reviews will be available in 41 minutes and 42 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Summary
`ChainStorage::save_block` was doing three separate `mdbx.put` calls
(block JSON → hash index → height key) followed by a `sync()`. Each
put is internally atomic in MDBX, but the trio is not — a crash
between them leaves on-disk state partially consistent:
The first is recoverable: `ensure_hash_index` walks every block on next boot and rebuilds the index. The second leaves the block orphaned until the next `save_block` call advances `height` past it, and any consumer that derives "tip" from the `height` key (the live chain extent) sees the old tip on restart even though the block JSON for `tip + 1` is persisted. Patch B1 (v2.1.90) added an atomic-write contract for the bulk `save_blockchain` path for exactly this reason; `save_block` was left on the pre-Patch-B1 sequential-put path.
This PR routes the same three puts through one `begin_write` + `commit` so MDBX either writes all three or none. Same mechanism, same tables, same encoded values — only the transactional grouping changes.
What changes
`crates/sentrix-storage/src/chain.rs`:
Out of scope: the `save_height` standalone API is unchanged — other callers that need to update height without saving a block (initial chain bootstrap, test helpers) still work.
Test plan
How I found it
Audit pass against workspace crates one-by-one (operator request). `sentrix-storage` was the 9th crate reviewed. Earlier finds (faucet TOCTOU #760, wallet 0600 + zeroize + prom-exporter doc/warn #761) covered surface issues; this is the first transactional-correctness gap found in storage. Production callers verified in audit:
All three benefit identically.
Related
Operator constraint for this audit pass: "fix nya jangan buat tambah error, jangan typo, ada gap dan bug lagi". Self-review pre-push confirms: no new error types, no new gaps, clippy clean, 23/23 existing tests pass.