triedb/pathdb: introduce metadata and version tracking

This commit is contained in:
Gary Rong 2025-06-02 10:21:53 +08:00
parent 27fbfdcce6
commit 69fa0683ad
6 changed files with 96 additions and 60 deletions

View file

@ -17,35 +17,29 @@
package rawdb package rawdb
import ( import (
"encoding/binary"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
// ReadLastStateHistoryIndex retrieves the number of latest indexed state history. // ReadStateHistoryIndexMetadata retrieves the metadata of state history index.
func ReadLastStateHistoryIndex(db ethdb.KeyValueReader) *uint64 { func ReadStateHistoryIndexMetadata(db ethdb.KeyValueReader) []byte {
data, _ := db.Get(headStateHistoryIndexKey) data, _ := db.Get(headStateHistoryIndexKey)
if len(data) != 8 { return data
return nil
}
number := binary.BigEndian.Uint64(data)
return &number
} }
// WriteLastStateHistoryIndex stores the number of latest indexed state history // WriteStateHistoryIndexMetadata stores the metadata of state history index
// into database. // into database.
func WriteLastStateHistoryIndex(db ethdb.KeyValueWriter, number uint64) { func WriteStateHistoryIndexMetadata(db ethdb.KeyValueWriter, blob []byte) {
if err := db.Put(headStateHistoryIndexKey, encodeBlockNumber(number)); err != nil { if err := db.Put(headStateHistoryIndexKey, blob); err != nil {
log.Crit("Failed to store the state index tail", "err", err) log.Crit("Failed to store the metadata of state history index", "err", err)
} }
} }
// DeleteLastStateHistoryIndex removes the number of latest indexed state history. // DeleteStateHistoryIndexMetadata removes the metadata of state history index.
func DeleteLastStateHistoryIndex(db ethdb.KeyValueWriter) { func DeleteStateHistoryIndexMetadata(db ethdb.KeyValueWriter) {
if err := db.Delete(headStateHistoryIndexKey); err != nil { if err := db.Delete(headStateHistoryIndexKey); err != nil {
log.Crit("Failed to delete the state index tail", "err", err) log.Crit("Failed to delete the metadata of state history index", "err", err)
} }
} }
@ -158,12 +152,12 @@ func increaseKey(key []byte) []byte {
return nil return nil
} }
// DeleteHistoryIndex completely removes all history indexing data, including indexes // DeleteStateHistoryIndex completely removes all history indexing data, including
// for accounts and storages. // indexes for accounts and storages.
// //
// Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix` // Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix`
// is exclusively occupied by the history indexing data! // is exclusively occupied by the history indexing data!
func DeleteHistoryIndex(db ethdb.KeyValueRangeDeleter) { func DeleteStateHistoryIndex(db ethdb.KeyValueRangeDeleter) {
if err := db.DeleteRange(StateHistoryIndexPrefix, increaseKey(StateHistoryIndexPrefix)); err != nil { if err := db.DeleteRange(StateHistoryIndexPrefix, increaseKey(StateHistoryIndexPrefix)); err != nil {
log.Crit("Failed to delete history index range", "err", err) log.Crit("Failed to delete history index range", "err", err)
} }

View file

@ -314,8 +314,8 @@ func (db *Database) repairHistory() error {
// TODO(rjl493456442) would be better to group them into a batch. // TODO(rjl493456442) would be better to group them into a batch.
// //
// Purge all state history indexing data first // Purge all state history indexing data first
rawdb.DeleteLastStateHistoryIndex(db.diskdb) rawdb.DeleteStateHistoryIndexMetadata(db.diskdb)
rawdb.DeleteHistoryIndex(db.diskdb) rawdb.DeleteStateHistoryIndex(db.diskdb)
err := db.freezer.Reset() err := db.freezer.Reset()
if err != nil { if err != nil {
log.Crit("Failed to reset state histories", "err", err) log.Crit("Failed to reset state histories", "err", err)
@ -507,8 +507,8 @@ func (db *Database) Enable(root common.Hash) error {
// TODO(rjl493456442) would be better to group them into a batch. // TODO(rjl493456442) would be better to group them into a batch.
// //
// Purge all state history indexing data first // Purge all state history indexing data first
rawdb.DeleteLastStateHistoryIndex(db.diskdb) rawdb.DeleteStateHistoryIndexMetadata(db.diskdb)
rawdb.DeleteHistoryIndex(db.diskdb) rawdb.DeleteStateHistoryIndex(db.diskdb)
if err := db.freezer.Reset(); err != nil { if err := db.freezer.Reset(); err != nil {
return err return err
} }

View file

@ -189,8 +189,8 @@ func TestBatchIndexerWrite(t *testing.T) {
if err := batch.finish(true); err != nil { if err := batch.finish(true); err != nil {
t.Fatalf("Failed to finish batch indexer, %v", err) t.Fatalf("Failed to finish batch indexer, %v", err)
} }
indexed := rawdb.ReadLastStateHistoryIndex(db) metadata := loadIndexMetadata(db)
if indexed == nil || *indexed != uint64(10) { if metadata == nil || metadata.Last != uint64(10) {
t.Fatal("Unexpected index position") t.Fatal("Unexpected index position")
} }
var ( var (
@ -278,8 +278,8 @@ func TestBatchIndexerDelete(t *testing.T) {
t.Fatalf("Failed to finish batch indexer, %v", err) t.Fatalf("Failed to finish batch indexer, %v", err)
} }
indexed := rawdb.ReadLastStateHistoryIndex(db) metadata := loadIndexMetadata(db)
if indexed != nil { if metadata != nil {
t.Fatal("Unexpected index position") t.Fatal("Unexpected index position")
} }
it := db.NewIterator(rawdb.StateHistoryIndexPrefix, nil) it := db.NewIterator(rawdb.StateHistoryIndexPrefix, nil)

View file

@ -28,10 +28,44 @@ import (
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
) )
const (
// The batch size for reading state histories // The batch size for reading state histories
const historyReadBatch = 1000 historyReadBatch = 1000
stateIndexV0 = uint8(0) // initial version of state index structure
stateIndexVersion = stateIndexV0 // the current state index version
)
type indexMetadata struct {
Version uint8
Last uint64
}
func loadIndexMetadata(db ethdb.KeyValueReader) *indexMetadata {
blob := rawdb.ReadStateHistoryIndexMetadata(db)
if len(blob) == 0 {
return nil
}
var m indexMetadata
if err := rlp.DecodeBytes(blob, &m); err != nil {
return nil
}
return &m
}
func storeIndexMetadata(db ethdb.KeyValueWriter, last uint64) {
var m indexMetadata
m.Version = stateIndexVersion
m.Last = last
blob, err := rlp.EncodeToBytes(m)
if err != nil {
log.Crit("Failed to encode index metadata", "err", err)
}
rawdb.WriteStateHistoryIndexMetadata(db, blob)
}
// batchIndexer is a structure designed to perform batch indexing or unindexing // batchIndexer is a structure designed to perform batch indexing or unindexing
// of state histories atomically. // of state histories atomically.
@ -144,12 +178,12 @@ func (b *batchIndexer) finish(force bool) error {
} }
// Update the position of last indexed state history // Update the position of last indexed state history
if !b.delete { if !b.delete {
rawdb.WriteLastStateHistoryIndex(batch, b.lastID) storeIndexMetadata(batch, b.lastID)
} else { } else {
if b.lastID == 1 { if b.lastID == 1 {
rawdb.DeleteLastStateHistoryIndex(batch) rawdb.DeleteStateHistoryIndexMetadata(batch)
} else { } else {
rawdb.WriteLastStateHistoryIndex(batch, b.lastID-1) storeIndexMetadata(batch, b.lastID-1)
} }
} }
if err := batch.Write(); err != nil { if err := batch.Write(); err != nil {
@ -167,11 +201,11 @@ func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancient
indexHistoryTimer.UpdateSince(start) indexHistoryTimer.UpdateSince(start)
}(time.Now()) }(time.Now())
indexed := rawdb.ReadLastStateHistoryIndex(db) metadata := loadIndexMetadata(db)
if indexed == nil || *indexed+1 != historyID { if metadata == nil || metadata.Last+1 != historyID {
last := "null" last := "null"
if indexed != nil { if metadata != nil {
last = fmt.Sprintf("%v", *indexed) last = fmt.Sprintf("%v", metadata.Last)
} }
return fmt.Errorf("history indexing is out of order, last: %s, requested: %d", last, historyID) return fmt.Errorf("history indexing is out of order, last: %s, requested: %d", last, historyID)
} }
@ -196,11 +230,11 @@ func unindexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancie
unindexHistoryTimer.UpdateSince(start) unindexHistoryTimer.UpdateSince(start)
}(time.Now()) }(time.Now())
indexed := rawdb.ReadLastStateHistoryIndex(db) metadata := loadIndexMetadata(db)
if indexed == nil || *indexed != historyID { if metadata == nil || metadata.Last != historyID {
last := "null" last := "null"
if indexed != nil { if metadata != nil {
last = fmt.Sprintf("%v", *indexed) last = fmt.Sprintf("%v", metadata.Last)
} }
return fmt.Errorf("history unindexing is out of order, last: %s, requested: %d", last, historyID) return fmt.Errorf("history unindexing is out of order, last: %s, requested: %d", last, historyID)
} }
@ -286,8 +320,8 @@ func (i *indexIniter) run(lastID uint64) {
// checkDone indicates whether all requested state histories // checkDone indicates whether all requested state histories
// have been fully indexed. // have been fully indexed.
checkDone = func() bool { checkDone = func() bool {
indexed := rawdb.ReadLastStateHistoryIndex(i.disk) metadata := loadIndexMetadata(i.disk)
return indexed != nil && *indexed == lastID return metadata != nil && metadata.Last == lastID
} }
) )
go i.index(done, interrupt, lastID) go i.index(done, interrupt, lastID)
@ -364,22 +398,22 @@ func (i *indexIniter) next() (uint64, error) {
tailID := tail + 1 // compute the id of the oldest history tailID := tail + 1 // compute the id of the oldest history
// Start indexing from scratch if nothing has been indexed // Start indexing from scratch if nothing has been indexed
lastIndexed := rawdb.ReadLastStateHistoryIndex(i.disk) metadata := loadIndexMetadata(i.disk)
if lastIndexed == nil { if metadata == nil {
log.Debug("Initialize state history indexing from scratch", "id", tailID) log.Debug("Initialize state history indexing from scratch", "id", tailID)
return tailID, nil return tailID, nil
} }
// Resume indexing from the last interrupted position // Resume indexing from the last interrupted position
if *lastIndexed+1 >= tailID { if metadata.Last+1 >= tailID {
log.Debug("Resume state history indexing", "id", *lastIndexed+1, "tail", tailID) log.Debug("Resume state history indexing", "id", metadata.Last+1, "tail", tailID)
return *lastIndexed + 1, nil return metadata.Last + 1, nil
} }
// History has been shortened without indexing. Discard the gapped segment // History has been shortened without indexing. Discard the gapped segment
// in the history and shift to the first available element. // in the history and shift to the first available element.
// //
// The missing indexes corresponding to the gapped histories won't be visible. // The missing indexes corresponding to the gapped histories won't be visible.
// It's fine to leave them unindexed. // It's fine to leave them unindexed.
log.Info("History gap detected, discard old segment", "oldHead", *lastIndexed, "newHead", tailID) log.Info("History gap detected, discard old segment", "oldHead", metadata.Last, "newHead", tailID)
return tailID, nil return tailID, nil
} }
@ -476,6 +510,14 @@ type historyIndexer struct {
// newHistoryIndexer constructs the history indexer and launches the background // newHistoryIndexer constructs the history indexer and launches the background
// initer to complete the indexing of any remaining state histories. // initer to complete the indexing of any remaining state histories.
func newHistoryIndexer(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, lastHistoryID uint64) *historyIndexer { func newHistoryIndexer(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, lastHistoryID uint64) *historyIndexer {
// Purge the obsolete index data from the database.
metadata := loadIndexMetadata(disk)
if metadata != nil && metadata.Version != stateIndexVersion {
// TODO(rjl493456442) would be better to group them into a batch.
rawdb.DeleteStateHistoryIndexMetadata(disk)
rawdb.DeleteStateHistoryIndex(disk)
log.Info("Cleaned up obsolete state history index", "version", metadata.Version, "want", stateIndexVersion)
}
return &historyIndexer{ return &historyIndexer{
initer: newIndexIniter(disk, freezer, lastHistoryID), initer: newIndexIniter(disk, freezer, lastHistoryID),
disk: disk, disk: disk,

View file

@ -113,8 +113,8 @@ type indexReaderWithLimitTag struct {
// newIndexReaderWithLimitTag constructs a index reader with indexing position. // newIndexReaderWithLimitTag constructs a index reader with indexing position.
func newIndexReaderWithLimitTag(db ethdb.KeyValueReader, state stateIdent) (*indexReaderWithLimitTag, error) { func newIndexReaderWithLimitTag(db ethdb.KeyValueReader, state stateIdent) (*indexReaderWithLimitTag, error) {
// Read the last indexed ID before the index reader construction // Read the last indexed ID before the index reader construction
indexed := rawdb.ReadLastStateHistoryIndex(db) metadata := loadIndexMetadata(db)
if indexed == nil { if metadata == nil {
return nil, errors.New("state history hasn't been indexed yet") return nil, errors.New("state history hasn't been indexed yet")
} }
r, err := newIndexReader(db, state) r, err := newIndexReader(db, state)
@ -123,7 +123,7 @@ func newIndexReaderWithLimitTag(db ethdb.KeyValueReader, state stateIdent) (*ind
} }
return &indexReaderWithLimitTag{ return &indexReaderWithLimitTag{
reader: r, reader: r,
limit: *indexed, limit: metadata.Last,
db: db, db: db,
}, nil }, nil
} }
@ -157,14 +157,14 @@ func (r *indexReaderWithLimitTag) readGreaterThan(id uint64, lastID uint64) (uin
return res, nil return res, nil
} }
// Refresh the index reader and give another attempt // Refresh the index reader and give another attempt
indexed := rawdb.ReadLastStateHistoryIndex(r.db) metadata := loadIndexMetadata(r.db)
if indexed == nil || *indexed < lastID { if metadata == nil || metadata.Last < lastID {
return 0, errors.New("state history hasn't been indexed yet") return 0, errors.New("state history hasn't been indexed yet")
} }
if err := r.reader.refresh(); err != nil { if err := r.reader.refresh(); err != nil {
return 0, err return 0, err
} }
r.limit = *indexed r.limit = metadata.Last
return r.reader.readGreaterThan(id) return r.reader.readGreaterThan(id)
} }
@ -308,15 +308,15 @@ func (r *historyReader) read(state stateIdentQuery, stateID uint64, lastID uint6
if stateID < tail { if stateID < tail {
return nil, errors.New("historical state has been pruned") return nil, errors.New("historical state has been pruned")
} }
lastIndexedID := rawdb.ReadLastStateHistoryIndex(r.disk)
// To serve the request, all state histories from stateID+1 to lastID // To serve the request, all state histories from stateID+1 to lastID
// must be indexed. It's not supposed to happen unless system is very // must be indexed. It's not supposed to happen unless system is very
// wrong. // wrong.
if lastIndexedID == nil || *lastIndexedID < lastID { metadata := loadIndexMetadata(r.disk)
if metadata == nil || metadata.Last < lastID {
indexed := "null" indexed := "null"
if lastIndexedID != nil { if metadata != nil {
indexed = fmt.Sprintf("%d", *lastIndexedID) indexed = fmt.Sprintf("%d", metadata.Last)
} }
return nil, fmt.Errorf("state history is not fully indexed, requested: %d, indexed: %s", stateID, indexed) return nil, fmt.Errorf("state history is not fully indexed, requested: %d, indexed: %s", stateID, indexed)
} }

View file

@ -28,8 +28,8 @@ import (
func waitIndexing(db *Database) { func waitIndexing(db *Database) {
for { for {
id := rawdb.ReadLastStateHistoryIndex(db.diskdb) metadata := loadIndexMetadata(db.diskdb)
if id != nil && *id >= db.tree.bottom().stateID() { if metadata != nil && metadata.Last >= db.tree.bottom().stateID() {
return return
} }
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)