mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-03 11:41:17 +00:00
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>
73 lines
1.8 KiB
Go
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
|
|
}
|