diff --git a/core/rawdb/accessors_history.go b/core/rawdb/accessors_history.go index 8940a70013..8fbec95faa 100644 --- a/core/rawdb/accessors_history.go +++ b/core/rawdb/accessors_history.go @@ -17,35 +17,29 @@ package rawdb import ( - "encoding/binary" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" ) -// ReadLastStateHistoryIndex retrieves the number of latest indexed state history. -func ReadLastStateHistoryIndex(db ethdb.KeyValueReader) *uint64 { +// ReadStateHistoryIndexMetadata retrieves the metadata of state history index. +func ReadStateHistoryIndexMetadata(db ethdb.KeyValueReader) []byte { data, _ := db.Get(headStateHistoryIndexKey) - if len(data) != 8 { - return nil - } - number := binary.BigEndian.Uint64(data) - return &number + return data } -// WriteLastStateHistoryIndex stores the number of latest indexed state history +// WriteStateHistoryIndexMetadata stores the metadata of state history index // into database. -func WriteLastStateHistoryIndex(db ethdb.KeyValueWriter, number uint64) { - if err := db.Put(headStateHistoryIndexKey, encodeBlockNumber(number)); err != nil { - log.Crit("Failed to store the state index tail", "err", err) +func WriteStateHistoryIndexMetadata(db ethdb.KeyValueWriter, blob []byte) { + if err := db.Put(headStateHistoryIndexKey, blob); err != nil { + log.Crit("Failed to store the metadata of state history index", "err", err) } } -// DeleteLastStateHistoryIndex removes the number of latest indexed state history. -func DeleteLastStateHistoryIndex(db ethdb.KeyValueWriter) { +// DeleteStateHistoryIndexMetadata removes the metadata of state history index. +func DeleteStateHistoryIndexMetadata(db ethdb.KeyValueWriter) { 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 } -// DeleteHistoryIndex completely removes all history indexing data, including indexes -// for accounts and storages. +// DeleteStateHistoryIndex completely removes all history indexing data, including +// indexes for accounts and storages. // // Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix` // 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 { log.Crit("Failed to delete history index range", "err", err) } diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go index 676f910c10..8932f3f7f8 100644 --- a/triedb/pathdb/database.go +++ b/triedb/pathdb/database.go @@ -314,8 +314,8 @@ func (db *Database) repairHistory() error { // TODO(rjl493456442) would be better to group them into a batch. // // Purge all state history indexing data first - rawdb.DeleteLastStateHistoryIndex(db.diskdb) - rawdb.DeleteHistoryIndex(db.diskdb) + rawdb.DeleteStateHistoryIndexMetadata(db.diskdb) + rawdb.DeleteStateHistoryIndex(db.diskdb) err := db.freezer.Reset() if err != nil { 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. // // Purge all state history indexing data first - rawdb.DeleteLastStateHistoryIndex(db.diskdb) - rawdb.DeleteHistoryIndex(db.diskdb) + rawdb.DeleteStateHistoryIndexMetadata(db.diskdb) + rawdb.DeleteStateHistoryIndex(db.diskdb) if err := db.freezer.Reset(); err != nil { return err } diff --git a/triedb/pathdb/history_index_test.go b/triedb/pathdb/history_index_test.go index b54194400e..84f4a5cd41 100644 --- a/triedb/pathdb/history_index_test.go +++ b/triedb/pathdb/history_index_test.go @@ -189,8 +189,8 @@ func TestBatchIndexerWrite(t *testing.T) { if err := batch.finish(true); err != nil { t.Fatalf("Failed to finish batch indexer, %v", err) } - indexed := rawdb.ReadLastStateHistoryIndex(db) - if indexed == nil || *indexed != uint64(10) { + metadata := loadIndexMetadata(db) + if metadata == nil || metadata.Last != uint64(10) { t.Fatal("Unexpected index position") } var ( @@ -278,8 +278,8 @@ func TestBatchIndexerDelete(t *testing.T) { t.Fatalf("Failed to finish batch indexer, %v", err) } - indexed := rawdb.ReadLastStateHistoryIndex(db) - if indexed != nil { + metadata := loadIndexMetadata(db) + if metadata != nil { t.Fatal("Unexpected index position") } it := db.NewIterator(rawdb.StateHistoryIndexPrefix, nil) diff --git a/triedb/pathdb/history_indexer.go b/triedb/pathdb/history_indexer.go index 2bdbd3f1ca..8e3f187c99 100644 --- a/triedb/pathdb/history_indexer.go +++ b/triedb/pathdb/history_indexer.go @@ -28,10 +28,44 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" ) -// The batch size for reading state histories -const historyReadBatch = 1000 +const ( + // The batch size for reading state histories + 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 // of state histories atomically. @@ -144,12 +178,12 @@ func (b *batchIndexer) finish(force bool) error { } // Update the position of last indexed state history if !b.delete { - rawdb.WriteLastStateHistoryIndex(batch, b.lastID) + storeIndexMetadata(batch, b.lastID) } else { if b.lastID == 1 { - rawdb.DeleteLastStateHistoryIndex(batch) + rawdb.DeleteStateHistoryIndexMetadata(batch) } else { - rawdb.WriteLastStateHistoryIndex(batch, b.lastID-1) + storeIndexMetadata(batch, b.lastID-1) } } if err := batch.Write(); err != nil { @@ -167,11 +201,11 @@ func indexSingle(historyID uint64, db ethdb.KeyValueStore, freezer ethdb.Ancient indexHistoryTimer.UpdateSince(start) }(time.Now()) - indexed := rawdb.ReadLastStateHistoryIndex(db) - if indexed == nil || *indexed+1 != historyID { + metadata := loadIndexMetadata(db) + if metadata == nil || metadata.Last+1 != historyID { last := "null" - if indexed != nil { - last = fmt.Sprintf("%v", *indexed) + if metadata != nil { + last = fmt.Sprintf("%v", metadata.Last) } 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) }(time.Now()) - indexed := rawdb.ReadLastStateHistoryIndex(db) - if indexed == nil || *indexed != historyID { + metadata := loadIndexMetadata(db) + if metadata == nil || metadata.Last != historyID { last := "null" - if indexed != nil { - last = fmt.Sprintf("%v", *indexed) + if metadata != nil { + last = fmt.Sprintf("%v", metadata.Last) } 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 // have been fully indexed. checkDone = func() bool { - indexed := rawdb.ReadLastStateHistoryIndex(i.disk) - return indexed != nil && *indexed == lastID + metadata := loadIndexMetadata(i.disk) + return metadata != nil && metadata.Last == 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 // Start indexing from scratch if nothing has been indexed - lastIndexed := rawdb.ReadLastStateHistoryIndex(i.disk) - if lastIndexed == nil { + metadata := loadIndexMetadata(i.disk) + if metadata == nil { log.Debug("Initialize state history indexing from scratch", "id", tailID) return tailID, nil } // Resume indexing from the last interrupted position - if *lastIndexed+1 >= tailID { - log.Debug("Resume state history indexing", "id", *lastIndexed+1, "tail", tailID) - return *lastIndexed + 1, nil + if metadata.Last+1 >= tailID { + log.Debug("Resume state history indexing", "id", metadata.Last+1, "tail", tailID) + return metadata.Last + 1, nil } // History has been shortened without indexing. Discard the gapped segment // in the history and shift to the first available element. // // The missing indexes corresponding to the gapped histories won't be visible. // 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 } @@ -476,6 +510,14 @@ type historyIndexer struct { // newHistoryIndexer constructs the history indexer and launches the background // initer to complete the indexing of any remaining state histories. 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{ initer: newIndexIniter(disk, freezer, lastHistoryID), disk: disk, diff --git a/triedb/pathdb/history_reader.go b/triedb/pathdb/history_reader.go index 259540e8cf..6ec5de63fa 100644 --- a/triedb/pathdb/history_reader.go +++ b/triedb/pathdb/history_reader.go @@ -113,8 +113,8 @@ type indexReaderWithLimitTag struct { // newIndexReaderWithLimitTag constructs a index reader with indexing position. func newIndexReaderWithLimitTag(db ethdb.KeyValueReader, state stateIdent) (*indexReaderWithLimitTag, error) { // Read the last indexed ID before the index reader construction - indexed := rawdb.ReadLastStateHistoryIndex(db) - if indexed == nil { + metadata := loadIndexMetadata(db) + if metadata == nil { return nil, errors.New("state history hasn't been indexed yet") } r, err := newIndexReader(db, state) @@ -123,7 +123,7 @@ func newIndexReaderWithLimitTag(db ethdb.KeyValueReader, state stateIdent) (*ind } return &indexReaderWithLimitTag{ reader: r, - limit: *indexed, + limit: metadata.Last, db: db, }, nil } @@ -157,14 +157,14 @@ func (r *indexReaderWithLimitTag) readGreaterThan(id uint64, lastID uint64) (uin return res, nil } // Refresh the index reader and give another attempt - indexed := rawdb.ReadLastStateHistoryIndex(r.db) - if indexed == nil || *indexed < lastID { + metadata := loadIndexMetadata(r.db) + if metadata == nil || metadata.Last < lastID { return 0, errors.New("state history hasn't been indexed yet") } if err := r.reader.refresh(); err != nil { return 0, err } - r.limit = *indexed + r.limit = metadata.Last return r.reader.readGreaterThan(id) } @@ -308,15 +308,15 @@ func (r *historyReader) read(state stateIdentQuery, stateID uint64, lastID uint6 if stateID < tail { 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 // must be indexed. It's not supposed to happen unless system is very // wrong. - if lastIndexedID == nil || *lastIndexedID < lastID { + metadata := loadIndexMetadata(r.disk) + if metadata == nil || metadata.Last < lastID { indexed := "null" - if lastIndexedID != nil { - indexed = fmt.Sprintf("%d", *lastIndexedID) + if metadata != nil { + indexed = fmt.Sprintf("%d", metadata.Last) } return nil, fmt.Errorf("state history is not fully indexed, requested: %d, indexed: %s", stateID, indexed) } diff --git a/triedb/pathdb/history_reader_test.go b/triedb/pathdb/history_reader_test.go index 04bf4f643a..f58263f618 100644 --- a/triedb/pathdb/history_reader_test.go +++ b/triedb/pathdb/history_reader_test.go @@ -28,8 +28,8 @@ import ( func waitIndexing(db *Database) { for { - id := rawdb.ReadLastStateHistoryIndex(db.diskdb) - if id != nil && *id >= db.tree.bottom().stateID() { + metadata := loadIndexMetadata(db.diskdb) + if metadata != nil && metadata.Last >= db.tree.bottom().stateID() { return } time.Sleep(100 * time.Millisecond)