go-ethereum/nomt/bitbox/recover.go
weiihann 859312d1f5 nomt/bitbox: add Phase 4 WAL, sync controller, and crash recovery
Implement crash-safe persistence for the Bitbox hash table:
- wal.go: WAL format with START/CLEAR/UPDATE/END entries, builder/reader
- sync.go: 3-phase sync protocol (BeginSync → WriteWAL → CommitSync)
- recover.go: WAL replay for crash recovery

The WAL records page diffs (not full pages) for compact logging. The
3-phase protocol ensures: WAL fsynced before HT modification, HT fsynced
before WAL truncation, providing at-least-once delivery of page updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 17:16:29 +08:00

73 lines
1.8 KiB
Go

package bitbox
import (
"fmt"
"github.com/ethereum/go-ethereum/nomt/core"
)
// Recover replays the WAL file to restore the database to a consistent state.
// Returns the sync sequence number from the WAL, or 0 if no recovery was
// needed.
func (db *DB) Recover(walPath string) (uint32, error) {
data, err := ReadWALFile(walPath)
if err != nil {
return 0, fmt.Errorf("bitbox/recover: %w", err)
}
if len(data) == 0 {
return 0, nil // No recovery needed.
}
syncSeqn, entries, err := ReadWAL(data)
if err != nil {
return 0, fmt.Errorf("bitbox/recover: parse: %w", err)
}
for _, entry := range entries {
switch entry.Kind {
case WALEntryClear:
db.metaMap.Set(entry.ClearBucket, MetaTombstone)
case WALEntryUpdate:
// Read the existing data page at this bucket (or use a fresh one).
page, readErr := db.readDataPage(entry.UpdateBucket)
if readErr != nil {
page = new(core.RawPage)
}
// Apply the diff: unpack changed nodes into the page.
entry.Diff.UnpackChangedNodes(entry.ChangedNodes, page)
// Set elided children and page ID.
page.SetElidedChildren(entry.ElidedChildren)
page.SetPageIDBytes(entry.PageID)
// Write data page.
if err := db.writeDataPage(entry.UpdateBucket, page); err != nil {
return 0, fmt.Errorf("bitbox/recover: write page: %w", err)
}
// Update meta byte.
hash := HashPageIDBytes(db.seed, entry.PageID)
db.metaMap.Set(entry.UpdateBucket, MakeOccupied(hash))
}
}
// Write dirty meta pages.
if err := db.FlushMeta(); err != nil {
return 0, fmt.Errorf("bitbox/recover: flush meta: %w", err)
}
// fsync HT file.
if err := db.file.Sync(); err != nil {
return 0, fmt.Errorf("bitbox/recover: fsync: %w", err)
}
// Truncate WAL.
if err := TruncateWALFile(walPath); err != nil {
return 0, fmt.Errorf("bitbox/recover: truncate WAL: %w", err)
}
return syncSeqn, nil
}