Skip to content

Commit 9c5c0e3

Browse files
authored
core/rawdb, triedb/pathdb: implement history indexer (#31156)
This pull request is part-1 for shipping the core part of archive node in PBSS mode.
1 parent ebff350 commit 9c5c0e3

20 files changed

+2986
-41
lines changed

cmd/utils/flags.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1661,11 +1661,6 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
16611661
cfg.TransactionHistory = 0
16621662
log.Warn("Disabled transaction unindexing for archive node")
16631663
}
1664-
1665-
if cfg.StateScheme != rawdb.HashScheme {
1666-
cfg.StateScheme = rawdb.HashScheme
1667-
log.Warn("Forcing hash state-scheme for archive mode")
1668-
}
16691664
}
16701665
if ctx.IsSet(LogHistoryFlag.Name) {
16711666
cfg.LogHistory = ctx.Uint64(LogHistoryFlag.Name)

core/blockchain.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,10 @@ func (cfg *BlockChainConfig) triedbConfig(isVerkle bool) *triedb.Config {
242242
}
243243
if cfg.StateScheme == rawdb.PathScheme {
244244
config.PathDB = &pathdb.Config{
245-
StateHistory: cfg.StateHistory,
246-
TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024,
247-
StateCleanSize: cfg.SnapshotLimit * 1024 * 1024,
245+
StateHistory: cfg.StateHistory,
246+
EnableStateIndexing: cfg.ArchiveMode,
247+
TrieCleanSize: cfg.TrieCleanLimit * 1024 * 1024,
248+
StateCleanSize: cfg.SnapshotLimit * 1024 * 1024,
248249

249250
// TODO(rjl493456442): The write buffer represents the memory limit used
250251
// for flushing both trie data and state data to disk. The config name

core/rawdb/accessors_history.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright 2025 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package rawdb
18+
19+
import (
20+
"bytes"
21+
"errors"
22+
23+
"github.com/ethereum/go-ethereum/common"
24+
"github.com/ethereum/go-ethereum/ethdb"
25+
"github.com/ethereum/go-ethereum/log"
26+
)
27+
28+
// ReadStateHistoryIndexMetadata retrieves the metadata of state history index.
29+
func ReadStateHistoryIndexMetadata(db ethdb.KeyValueReader) []byte {
30+
data, _ := db.Get(headStateHistoryIndexKey)
31+
return data
32+
}
33+
34+
// WriteStateHistoryIndexMetadata stores the metadata of state history index
35+
// into database.
36+
func WriteStateHistoryIndexMetadata(db ethdb.KeyValueWriter, blob []byte) {
37+
if err := db.Put(headStateHistoryIndexKey, blob); err != nil {
38+
log.Crit("Failed to store the metadata of state history index", "err", err)
39+
}
40+
}
41+
42+
// DeleteStateHistoryIndexMetadata removes the metadata of state history index.
43+
func DeleteStateHistoryIndexMetadata(db ethdb.KeyValueWriter) {
44+
if err := db.Delete(headStateHistoryIndexKey); err != nil {
45+
log.Crit("Failed to delete the metadata of state history index", "err", err)
46+
}
47+
}
48+
49+
// ReadAccountHistoryIndex retrieves the account history index with the provided
50+
// account address.
51+
func ReadAccountHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash) []byte {
52+
data, err := db.Get(accountHistoryIndexKey(addressHash))
53+
if err != nil || len(data) == 0 {
54+
return nil
55+
}
56+
return data
57+
}
58+
59+
// WriteAccountHistoryIndex writes the provided account history index into database.
60+
func WriteAccountHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, data []byte) {
61+
if err := db.Put(accountHistoryIndexKey(addressHash), data); err != nil {
62+
log.Crit("Failed to store account history index", "err", err)
63+
}
64+
}
65+
66+
// DeleteAccountHistoryIndex deletes the specified account history index from
67+
// the database.
68+
func DeleteAccountHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash) {
69+
if err := db.Delete(accountHistoryIndexKey(addressHash)); err != nil {
70+
log.Crit("Failed to delete account history index", "err", err)
71+
}
72+
}
73+
74+
// ReadStorageHistoryIndex retrieves the storage history index with the provided
75+
// account address and storage key hash.
76+
func ReadStorageHistoryIndex(db ethdb.KeyValueReader, addressHash common.Hash, storageHash common.Hash) []byte {
77+
data, err := db.Get(storageHistoryIndexKey(addressHash, storageHash))
78+
if err != nil || len(data) == 0 {
79+
return nil
80+
}
81+
return data
82+
}
83+
84+
// WriteStorageHistoryIndex writes the provided storage history index into database.
85+
func WriteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash, data []byte) {
86+
if err := db.Put(storageHistoryIndexKey(addressHash, storageHash), data); err != nil {
87+
log.Crit("Failed to store storage history index", "err", err)
88+
}
89+
}
90+
91+
// DeleteStorageHistoryIndex deletes the specified state index from the database.
92+
func DeleteStorageHistoryIndex(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash) {
93+
if err := db.Delete(storageHistoryIndexKey(addressHash, storageHash)); err != nil {
94+
log.Crit("Failed to delete storage history index", "err", err)
95+
}
96+
}
97+
98+
// ReadAccountHistoryIndexBlock retrieves the index block with the provided
99+
// account address along with the block id.
100+
func ReadAccountHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, blockID uint32) []byte {
101+
data, err := db.Get(accountHistoryIndexBlockKey(addressHash, blockID))
102+
if err != nil || len(data) == 0 {
103+
return nil
104+
}
105+
return data
106+
}
107+
108+
// WriteAccountHistoryIndexBlock writes the provided index block into database.
109+
func WriteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, blockID uint32, data []byte) {
110+
if err := db.Put(accountHistoryIndexBlockKey(addressHash, blockID), data); err != nil {
111+
log.Crit("Failed to store account index block", "err", err)
112+
}
113+
}
114+
115+
// DeleteAccountHistoryIndexBlock deletes the specified index block from the database.
116+
func DeleteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, blockID uint32) {
117+
if err := db.Delete(accountHistoryIndexBlockKey(addressHash, blockID)); err != nil {
118+
log.Crit("Failed to delete account index block", "err", err)
119+
}
120+
}
121+
122+
// ReadStorageHistoryIndexBlock retrieves the index block with the provided state
123+
// identifier along with the block id.
124+
func ReadStorageHistoryIndexBlock(db ethdb.KeyValueReader, addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte {
125+
data, err := db.Get(storageHistoryIndexBlockKey(addressHash, storageHash, blockID))
126+
if err != nil || len(data) == 0 {
127+
return nil
128+
}
129+
return data
130+
}
131+
132+
// WriteStorageHistoryIndexBlock writes the provided index block into database.
133+
func WriteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash, id uint32, data []byte) {
134+
if err := db.Put(storageHistoryIndexBlockKey(addressHash, storageHash, id), data); err != nil {
135+
log.Crit("Failed to store storage index block", "err", err)
136+
}
137+
}
138+
139+
// DeleteStorageHistoryIndexBlock deletes the specified index block from the database.
140+
func DeleteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, addressHash common.Hash, storageHash common.Hash, id uint32) {
141+
if err := db.Delete(storageHistoryIndexBlockKey(addressHash, storageHash, id)); err != nil {
142+
log.Crit("Failed to delete storage index block", "err", err)
143+
}
144+
}
145+
146+
// increaseKey increase the input key by one bit. Return nil if the entire
147+
// addition operation overflows.
148+
func increaseKey(key []byte) []byte {
149+
for i := len(key) - 1; i >= 0; i-- {
150+
key[i]++
151+
if key[i] != 0x0 {
152+
return key
153+
}
154+
}
155+
return nil
156+
}
157+
158+
// DeleteStateHistoryIndex completely removes all history indexing data, including
159+
// indexes for accounts and storages.
160+
//
161+
// Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix`
162+
// is exclusively occupied by the history indexing data!
163+
func DeleteStateHistoryIndex(db ethdb.KeyValueRangeDeleter) {
164+
start := StateHistoryIndexPrefix
165+
limit := increaseKey(bytes.Clone(StateHistoryIndexPrefix))
166+
167+
// Try to remove the data in the range by a loop, as the leveldb
168+
// doesn't support the native range deletion.
169+
for {
170+
err := db.DeleteRange(start, limit)
171+
if err == nil {
172+
return
173+
}
174+
if errors.Is(err, ethdb.ErrTooManyKeys) {
175+
continue
176+
}
177+
log.Crit("Failed to delete history index range", "err", err)
178+
}
179+
}

core/rawdb/accessors_state.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package rawdb
1818

1919
import (
2020
"encoding/binary"
21+
"errors"
2122

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

259+
// ReadStateHistoryList retrieves a list of state histories from database with
260+
// specific range. Compute the position of state history in freezer by minus one
261+
// since the id of first state history starts from one(zero for initial state).
262+
func ReadStateHistoryList(db ethdb.AncientReaderOp, start uint64, count uint64) ([][]byte, [][]byte, [][]byte, [][]byte, [][]byte, error) {
263+
metaList, err := db.AncientRange(stateHistoryMeta, start-1, count, 0)
264+
if err != nil {
265+
return nil, nil, nil, nil, nil, err
266+
}
267+
aIndexList, err := db.AncientRange(stateHistoryAccountIndex, start-1, count, 0)
268+
if err != nil {
269+
return nil, nil, nil, nil, nil, err
270+
}
271+
sIndexList, err := db.AncientRange(stateHistoryStorageIndex, start-1, count, 0)
272+
if err != nil {
273+
return nil, nil, nil, nil, nil, err
274+
}
275+
aDataList, err := db.AncientRange(stateHistoryAccountData, start-1, count, 0)
276+
if err != nil {
277+
return nil, nil, nil, nil, nil, err
278+
}
279+
sDataList, err := db.AncientRange(stateHistoryStorageData, start-1, count, 0)
280+
if err != nil {
281+
return nil, nil, nil, nil, nil, err
282+
}
283+
if len(metaList) != len(aIndexList) || len(metaList) != len(sIndexList) || len(metaList) != len(aDataList) || len(metaList) != len(sDataList) {
284+
return nil, nil, nil, nil, nil, errors.New("state history is corrupted")
285+
}
286+
return metaList, aIndexList, sIndexList, aDataList, sDataList, nil
287+
}
288+
258289
// WriteStateHistory writes the provided state history to database. Compute the
259290
// position of state history in freezer by minus one since the id of first state
260291
// history starts from one(zero for initial state).

core/rawdb/database.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
412412
filterMapLastBlock stat
413413
filterMapBlockLV stat
414414

415+
// Path-mode archive data
416+
stateIndex stat
417+
415418
// Verkle statistics
416419
verkleTries stat
417420
verkleStateLookups stat
@@ -489,6 +492,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
489492
case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8:
490493
bloomBits.Add(size)
491494

495+
// Path-based historic state indexes
496+
case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.HashLength:
497+
stateIndex.Add(size)
498+
492499
// Verkle trie data is detected, determine the sub-category
493500
case bytes.HasPrefix(key, VerklePrefix):
494501
remain := key[len(VerklePrefix):]
@@ -544,6 +551,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
544551
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},
545552
{"Key-Value store", "Path trie account nodes", accountTries.Size(), accountTries.Count()},
546553
{"Key-Value store", "Path trie storage nodes", storageTries.Size(), storageTries.Count()},
554+
{"Key-Value store", "Path state history indexes", stateIndex.Size(), stateIndex.Count()},
547555
{"Key-Value store", "Verkle trie nodes", verkleTries.Size(), verkleTries.Count()},
548556
{"Key-Value store", "Verkle trie state lookups", verkleStateLookups.Size(), verkleStateLookups.Count()},
549557
{"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()},
@@ -591,7 +599,7 @@ var knownMetadataKeys = [][]byte{
591599
snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey,
592600
uncleanShutdownKey, badBlockKey, transitionStatusKey, skeletonSyncStatusKey,
593601
persistentStateIDKey, trieJournalKey, snapshotSyncStatusKey, snapSyncStatusFlagKey,
594-
filterMapsRangeKey,
602+
filterMapsRangeKey, headStateHistoryIndexKey,
595603
}
596604

597605
// printChainMetadata prints out chain metadata to stderr.

core/rawdb/schema.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ var (
7676
// trieJournalKey tracks the in-memory trie node layers across restarts.
7777
trieJournalKey = []byte("TrieJournal")
7878

79+
// headStateHistoryIndexKey tracks the ID of the latest state history that has
80+
// been indexed.
81+
headStateHistoryIndexKey = []byte("LastStateHistoryIndex")
82+
7983
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
8084
txIndexTailKey = []byte("TransactionIndexTail")
8185

@@ -117,6 +121,13 @@ var (
117121
TrieNodeStoragePrefix = []byte("O") // TrieNodeStoragePrefix + accountHash + hexPath -> trie node
118122
stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id
119123

124+
// State history indexing within path-based storage scheme
125+
StateHistoryIndexPrefix = []byte("m") // The global prefix of state history index data
126+
StateHistoryAccountMetadataPrefix = []byte("ma") // StateHistoryAccountMetadataPrefix + account address hash => account metadata
127+
StateHistoryStorageMetadataPrefix = []byte("ms") // StateHistoryStorageMetadataPrefix + account address hash + storage slot hash => slot metadata
128+
StateHistoryAccountBlockPrefix = []byte("mba") // StateHistoryAccountBlockPrefix + account address hash + block_number => account block
129+
StateHistoryStorageBlockPrefix = []byte("mbs") // StateHistoryStorageBlockPrefix + account address hash + storage slot hash + block_number => slot block
130+
120131
// VerklePrefix is the database prefix for Verkle trie data, which includes:
121132
// (a) Trie nodes
122133
// (b) In-memory trie node journal
@@ -362,3 +373,27 @@ func filterMapBlockLVKey(number uint64) []byte {
362373
binary.BigEndian.PutUint64(key[l:], number)
363374
return key
364375
}
376+
377+
// accountHistoryIndexKey = StateHistoryAccountMetadataPrefix + addressHash
378+
func accountHistoryIndexKey(addressHash common.Hash) []byte {
379+
return append(StateHistoryAccountMetadataPrefix, addressHash.Bytes()...)
380+
}
381+
382+
// storageHistoryIndexKey = StateHistoryStorageMetadataPrefix + addressHash + storageHash
383+
func storageHistoryIndexKey(addressHash common.Hash, storageHash common.Hash) []byte {
384+
return append(append(StateHistoryStorageMetadataPrefix, addressHash.Bytes()...), storageHash.Bytes()...)
385+
}
386+
387+
// accountHistoryIndexBlockKey = StateHistoryAccountBlockPrefix + addressHash + blockID
388+
func accountHistoryIndexBlockKey(addressHash common.Hash, blockID uint32) []byte {
389+
var buf [4]byte
390+
binary.BigEndian.PutUint32(buf[:], blockID)
391+
return append(append(StateHistoryAccountBlockPrefix, addressHash.Bytes()...), buf[:]...)
392+
}
393+
394+
// storageHistoryIndexBlockKey = StateHistoryStorageBlockPrefix + addressHash + storageHash + blockID
395+
func storageHistoryIndexBlockKey(addressHash common.Hash, storageHash common.Hash, blockID uint32) []byte {
396+
var buf [4]byte
397+
binary.BigEndian.PutUint32(buf[:], blockID)
398+
return append(append(append(StateHistoryStorageBlockPrefix, addressHash.Bytes()...), storageHash.Bytes()...), buf[:]...)
399+
}

ethdb/pebble/pebble.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ type Database struct {
8383
estimatedCompDebtGauge *metrics.Gauge // Gauge for tracking the number of bytes that need to be compacted
8484
liveCompGauge *metrics.Gauge // Gauge for tracking the number of in-progress compactions
8585
liveCompSizeGauge *metrics.Gauge // Gauge for tracking the size of in-progress compactions
86+
liveIterGauge *metrics.Gauge // Gauge for tracking the number of live database iterators
8687
levelsGauge []*metrics.Gauge // Gauge for tracking the number of tables in levels
8788

8889
quitLock sync.RWMutex // Mutex protecting the quit channel and the closed flag
@@ -326,6 +327,7 @@ func New(file string, cache int, handles int, namespace string, readonly bool) (
326327
db.estimatedCompDebtGauge = metrics.GetOrRegisterGauge(namespace+"compact/estimateDebt", nil)
327328
db.liveCompGauge = metrics.GetOrRegisterGauge(namespace+"compact/live/count", nil)
328329
db.liveCompSizeGauge = metrics.GetOrRegisterGauge(namespace+"compact/live/size", nil)
330+
db.liveIterGauge = metrics.GetOrRegisterGauge(namespace+"iter/count", nil)
329331

330332
// Start up the metrics gathering and return
331333
go db.meter(metricsGatheringInterval, namespace)
@@ -582,6 +584,7 @@ func (d *Database) meter(refresh time.Duration, namespace string) {
582584
d.seekCompGauge.Update(stats.Compact.ReadCount)
583585
d.liveCompGauge.Update(stats.Compact.NumInProgress)
584586
d.liveCompSizeGauge.Update(stats.Compact.InProgressBytes)
587+
d.liveIterGauge.Update(stats.TableIters)
585588

586589
d.liveMemTablesGauge.Update(stats.MemTable.Count)
587590
d.zombieMemTablesGauge.Update(stats.MemTable.ZombieCount)

0 commit comments

Comments
 (0)