Skip to content

core/rawdb, triedb/pathdb: implement history indexer #31156

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -1646,11 +1646,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
cfg.TransactionHistory = 0
log.Warn("Disabled transaction unindexing for archive node")
}

if cfg.StateScheme != rawdb.HashScheme {
cfg.StateScheme = rawdb.HashScheme
log.Warn("Forcing hash state-scheme for archive mode")
}
}
if ctx.IsSet(LogHistoryFlag.Name) {
cfg.LogHistory = ctx.Uint64(LogHistoryFlag.Name)
Expand Down
7 changes: 4 additions & 3 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,10 @@ func (c *CacheConfig) triedbConfig(isVerkle bool) *triedb.Config {
}
if c.StateScheme == rawdb.PathScheme {
config.PathDB = &pathdb.Config{
StateHistory: c.StateHistory,
TrieCleanSize: c.TrieCleanLimit * 1024 * 1024,
StateCleanSize: c.SnapshotLimit * 1024 * 1024,
StateHistory: c.StateHistory,
EnableStateIndexing: c.TrieDirtyDisabled,
TrieCleanSize: c.TrieCleanLimit * 1024 * 1024,
StateCleanSize: c.SnapshotLimit * 1024 * 1024,

// TODO(rjl493456442): The write buffer represents the memory limit used
// for flushing both trie data and state data to disk. The config name
Expand Down
168 changes: 168 additions & 0 deletions core/rawdb/accessors_history.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright 2025 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package rawdb

import (
"bytes"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
)

// ReadStateHistoryIndexMetadata retrieves the metadata of state history index.
func ReadStateHistoryIndexMetadata(db ethdb.KeyValueReader) []byte {
data, _ := db.Get(headStateHistoryIndexKey)
return data
}

// WriteStateHistoryIndexMetadata stores the metadata of state history index
// into database.
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)
}
}

// 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 metadata of state history index", "err", err)
}
}

// ReadAccountHistoryIndex retrieves the account history index with the provided
// account address.
func ReadAccountHistoryIndex(db ethdb.KeyValueReader, address common.Address) []byte {
data, err := db.Get(accountHistoryIndexKey(address))
if err != nil || len(data) == 0 {
return nil
}
return data
}

// WriteAccountHistoryIndex writes the provided account history index into database.
func WriteAccountHistoryIndex(db ethdb.KeyValueWriter, address common.Address, data []byte) {
if err := db.Put(accountHistoryIndexKey(address), data); err != nil {
log.Crit("Failed to store account history index", "err", err)
}
}

// DeleteAccountHistoryIndex deletes the specified account history index from
// the database.
func DeleteAccountHistoryIndex(db ethdb.KeyValueWriter, address common.Address) {
if err := db.Delete(accountHistoryIndexKey(address)); err != nil {
log.Crit("Failed to delete account history index", "err", err)
}
}

// ReadStorageHistoryIndex retrieves the storage history index with the provided
// account address and storage key hash.
func ReadStorageHistoryIndex(db ethdb.KeyValueReader, address common.Address, storageHash common.Hash) []byte {
data, err := db.Get(storageHistoryIndexKey(address, storageHash))
if err != nil || len(data) == 0 {
return nil
}
return data
}

// WriteStorageHistoryIndex writes the provided storage history index into database.
func WriteStorageHistoryIndex(db ethdb.KeyValueWriter, address common.Address, storageHash common.Hash, data []byte) {
if err := db.Put(storageHistoryIndexKey(address, storageHash), data); err != nil {
log.Crit("Failed to store storage history index", "err", err)
}
}

// DeleteStorageHistoryIndex deletes the specified state index from the database.
func DeleteStorageHistoryIndex(db ethdb.KeyValueWriter, address common.Address, storageHash common.Hash) {
if err := db.Delete(storageHistoryIndexKey(address, storageHash)); err != nil {
log.Crit("Failed to delete storage history index", "err", err)
}
}

// ReadAccountHistoryIndexBlock retrieves the index block with the provided
// account address along with the block id.
func ReadAccountHistoryIndexBlock(db ethdb.KeyValueReader, address common.Address, blockID uint32) []byte {
data, err := db.Get(accountHistoryIndexBlockKey(address, blockID))
if err != nil || len(data) == 0 {
return nil
}
return data
}

// WriteAccountHistoryIndexBlock writes the provided index block into database.
func WriteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, address common.Address, blockID uint32, data []byte) {
if err := db.Put(accountHistoryIndexBlockKey(address, blockID), data); err != nil {
log.Crit("Failed to store account index block", "err", err)
}
}

// DeleteAccountHistoryIndexBlock deletes the specified index block from the database.
func DeleteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, address common.Address, blockID uint32) {
if err := db.Delete(accountHistoryIndexBlockKey(address, blockID)); err != nil {
log.Crit("Failed to delete account index block", "err", err)
}
}

// ReadStorageHistoryIndexBlock retrieves the index block with the provided state
// identifier along with the block id.
func ReadStorageHistoryIndexBlock(db ethdb.KeyValueReader, address common.Address, storageHash common.Hash, blockID uint32) []byte {
data, err := db.Get(storageHistoryIndexBlockKey(address, storageHash, blockID))
if err != nil || len(data) == 0 {
return nil
}
return data
}

// WriteStorageHistoryIndexBlock writes the provided index block into database.
func WriteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, address common.Address, storageHash common.Hash, id uint32, data []byte) {
if err := db.Put(storageHistoryIndexBlockKey(address, storageHash, id), data); err != nil {
log.Crit("Failed to store storage index block", "err", err)
}
}

// DeleteStorageHistoryIndexBlock deletes the specified index block from the database.
func DeleteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, address common.Address, state common.Hash, id uint32) {
if err := db.Delete(storageHistoryIndexBlockKey(address, state, id)); err != nil {
log.Crit("Failed to delete storage index block", "err", err)
}
}

// increaseKey increase the input key by one bit. Return nil if the entire
// addition operation overflows.
func increaseKey(key []byte) []byte {
for i := len(key) - 1; i >= 0; i-- {
key[i]++
if key[i] != 0x0 {
return key
}
}
return nil
}

// 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 DeleteStateHistoryIndex(db ethdb.KeyValueRangeDeleter) {
start := StateHistoryIndexPrefix
limit := increaseKey(bytes.Clone(StateHistoryIndexPrefix))
if err := db.DeleteRange(start, limit); err != nil {
log.Crit("Failed to delete history index range", "err", err)
}
}
31 changes: 31 additions & 0 deletions core/rawdb/accessors_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package rawdb

import (
"encoding/binary"
"errors"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethdb"
Expand Down Expand Up @@ -255,6 +256,36 @@ func ReadStateHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []by
return meta, accountIndex, storageIndex, accountData, storageData, nil
}

// ReadStateHistoryList retrieves a list of state histories from database with
// specific range. Compute the position of state history in freezer by minus one
// since the id of first state history starts from one(zero for initial state).
func ReadStateHistoryList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, [][]byte, [][]byte, [][]byte, [][]byte, error) {
metaList, err := db.AncientRange(stateHistoryMeta, start-1, count, 0)
if err != nil {
return nil, nil, nil, nil, nil, err
}
aIndexList, err := db.AncientRange(stateHistoryAccountIndex, start-1, count, 0)
if err != nil {
return nil, nil, nil, nil, nil, err
}
sIndexList, err := db.AncientRange(stateHistoryStorageIndex, start-1, count, 0)
if err != nil {
return nil, nil, nil, nil, nil, err
}
aDataList, err := db.AncientRange(stateHistoryAccountData, start-1, count, 0)
if err != nil {
return nil, nil, nil, nil, nil, err
}
sDataList, err := db.AncientRange(stateHistoryStorageData, start-1, count, 0)
if err != nil {
return nil, nil, nil, nil, nil, err
}
if len(metaList) != len(aIndexList) || len(metaList) != len(sIndexList) || len(metaList) != len(aDataList) || len(metaList) != len(sDataList) {
return nil, nil, nil, nil, nil, errors.New("state history is corrupted")
}
return metaList, aIndexList, sIndexList, aDataList, sDataList, nil
}

// WriteStateHistory writes the provided state history to database. Compute the
// position of state history in freezer by minus one since the id of first state
// history starts from one(zero for initial state).
Expand Down
10 changes: 9 additions & 1 deletion core/rawdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
filterMapLastBlock stat
filterMapBlockLV stat

// Path-mode archive data
stateIndex stat

// Verkle statistics
verkleTries stat
verkleStateLookups stat
Expand Down Expand Up @@ -464,6 +467,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8:
bloomBits.Add(size)

// Path-based historic state indexes
case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.AddressLength:
stateIndex.Add(size)

// Verkle trie data is detected, determine the sub-category
case bytes.HasPrefix(key, VerklePrefix):
remain := key[len(VerklePrefix):]
Expand Down Expand Up @@ -519,6 +526,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},
{"Key-Value store", "Path trie account nodes", accountTries.Size(), accountTries.Count()},
{"Key-Value store", "Path trie storage nodes", storageTries.Size(), storageTries.Count()},
{"Key-Value store", "Path state history indexes", stateIndex.Size(), stateIndex.Count()},
{"Key-Value store", "Verkle trie nodes", verkleTries.Size(), verkleTries.Count()},
{"Key-Value store", "Verkle trie state lookups", verkleStateLookups.Size(), verkleStateLookups.Count()},
{"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()},
Expand Down Expand Up @@ -566,7 +574,7 @@ var knownMetadataKeys = [][]byte{
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
filterMapsRangeKey,
filterMapsRangeKey, headStateHistoryIndexKey,
}

// printChainMetadata prints out chain metadata to stderr.
Expand Down
31 changes: 31 additions & 0 deletions core/rawdb/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ var (
// trieJournalKey tracks the in-memory trie node layers across restarts.
trieJournalKey = []byte("TrieJournal")

// headStateHistoryIndexKey tracks the ID of the latest state history that has
// been indexed.
headStateHistoryIndexKey = []byte("LastStateHistoryIndex")

// txIndexTailKey tracks the oldest block whose transactions have been indexed.
txIndexTailKey = []byte("TransactionIndexTail")

Expand Down Expand Up @@ -117,6 +121,9 @@ var (
TrieNodeStoragePrefix = []byte("O") // TrieNodeStoragePrefix + accountHash + hexPath -> trie node
stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id

// State history indexing within path-based storage scheme
StateHistoryIndexPrefix = []byte("m") // StateHistoryIndexPrefix + account address or (account address + slotHash) -> index

// VerklePrefix is the database prefix for Verkle trie data, which includes:
// (a) Trie nodes
// (b) In-memory trie node journal
Expand Down Expand Up @@ -362,3 +369,27 @@ func filterMapBlockLVKey(number uint64) []byte {
binary.BigEndian.PutUint64(key[l:], number)
return key
}

// accountHistoryIndexKey = StateHistoryIndexPrefix + address
func accountHistoryIndexKey(address common.Address) []byte {
return append(StateHistoryIndexPrefix, address.Bytes()...)
}

// storageHistoryIndexKey = StateHistoryIndexPrefix + address + storageHash
func storageHistoryIndexKey(address common.Address, storageHash common.Hash) []byte {
return append(append(StateHistoryIndexPrefix, address.Bytes()...), storageHash.Bytes()...)
}

// accountHistoryIndexBlockKey = StateHistoryIndexPrefix + address + blockID
func accountHistoryIndexBlockKey(address common.Address, blockID uint32) []byte {
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], blockID)
return append(append(StateHistoryIndexPrefix, address.Bytes()...), buf[:]...)
}

// storageHistoryIndexBlockKey = StateHistoryIndexPrefix + address + storageHash + blockID
func storageHistoryIndexBlockKey(address common.Address, storageHash common.Hash, blockID uint32) []byte {
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], blockID)
return append(append(append(StateHistoryIndexPrefix, address.Bytes()...), storageHash.Bytes()...), buf[:]...)
}
Loading