Skip to content

Commit a12c5a2

Browse files
committed
core/rawdb, triedb/pathdb: implement history indexer
1 parent 8b9f2d4 commit a12c5a2

17 files changed

+2795
-13
lines changed

core/rawdb/accessors_history.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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+
"encoding/binary"
21+
22+
"github.com/ethereum/go-ethereum/common"
23+
"github.com/ethereum/go-ethereum/ethdb"
24+
"github.com/ethereum/go-ethereum/log"
25+
)
26+
27+
// ReadLastStateHistoryIndex retrieves the number of latest indexed state history.
28+
func ReadLastStateHistoryIndex(db ethdb.KeyValueReader) *uint64 {
29+
data, _ := db.Get(headStateHistoryIndexKey)
30+
if len(data) != 8 {
31+
return nil
32+
}
33+
number := binary.BigEndian.Uint64(data)
34+
return &number
35+
}
36+
37+
// WriteLastStateHistoryIndex stores the number of latest indexed state history
38+
// into database.
39+
func WriteLastStateHistoryIndex(db ethdb.KeyValueWriter, number uint64) {
40+
if err := db.Put(headStateHistoryIndexKey, encodeBlockNumber(number)); err != nil {
41+
log.Crit("Failed to store the state index tail", "err", err)
42+
}
43+
}
44+
45+
// DeleteLastStateHistoryIndex removes the number of latest indexed state history.
46+
func DeleteLastStateHistoryIndex(db ethdb.KeyValueWriter) {
47+
if err := db.Delete(headStateHistoryIndexKey); err != nil {
48+
log.Crit("Failed to delete the state index tail", "err", err)
49+
}
50+
}
51+
52+
// ReadAccountHistoryIndex retrieves the account history index with the provided
53+
// account address.
54+
func ReadAccountHistoryIndex(db ethdb.KeyValueReader, address common.Address) []byte {
55+
data, err := db.Get(accountHistoryIndexKey(address))
56+
if err != nil || len(data) == 0 {
57+
return nil
58+
}
59+
return data
60+
}
61+
62+
// WriteAccountHistoryIndex writes the provided account history index into database.
63+
func WriteAccountHistoryIndex(db ethdb.KeyValueWriter, address common.Address, data []byte) {
64+
if err := db.Put(accountHistoryIndexKey(address), data); err != nil {
65+
log.Crit("Failed to store account history index", "err", err)
66+
}
67+
}
68+
69+
// DeleteAccountHistoryIndex deletes the specified account history index from
70+
// the database.
71+
func DeleteAccountHistoryIndex(db ethdb.KeyValueWriter, address common.Address) {
72+
if err := db.Delete(accountHistoryIndexKey(address)); err != nil {
73+
log.Crit("Failed to delete account history index", "err", err)
74+
}
75+
}
76+
77+
// ReadStorageHistoryIndex retrieves the storage history index with the provided
78+
// account address and storage key hash.
79+
func ReadStorageHistoryIndex(db ethdb.KeyValueReader, address common.Address, storageHash common.Hash) []byte {
80+
data, err := db.Get(storageHistoryIndexKey(address, storageHash))
81+
if err != nil || len(data) == 0 {
82+
return nil
83+
}
84+
return data
85+
}
86+
87+
// WriteStorageHistoryIndex writes the provided storage history index into database.
88+
func WriteStorageHistoryIndex(db ethdb.KeyValueWriter, address common.Address, storageHash common.Hash, data []byte) {
89+
if err := db.Put(storageHistoryIndexKey(address, storageHash), data); err != nil {
90+
log.Crit("Failed to store storage history index", "err", err)
91+
}
92+
}
93+
94+
// DeleteStorageHistoryIndex deletes the specified state index from the database.
95+
func DeleteStorageHistoryIndex(db ethdb.KeyValueWriter, address common.Address, storageHash common.Hash) {
96+
if err := db.Delete(storageHistoryIndexKey(address, storageHash)); err != nil {
97+
log.Crit("Failed to delete storage history index", "err", err)
98+
}
99+
}
100+
101+
// ReadAccountHistoryIndexBlock retrieves the index block with the provided
102+
// account address along with the block id.
103+
func ReadAccountHistoryIndexBlock(db ethdb.KeyValueReader, address common.Address, blockID uint32) []byte {
104+
data, err := db.Get(accountHistoryIndexBlockKey(address, blockID))
105+
if err != nil || len(data) == 0 {
106+
return nil
107+
}
108+
return data
109+
}
110+
111+
// WriteAccountHistoryIndexBlock writes the provided index block into database.
112+
func WriteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, address common.Address, blockID uint32, data []byte) {
113+
if err := db.Put(accountHistoryIndexBlockKey(address, blockID), data); err != nil {
114+
log.Crit("Failed to store account index block", "err", err)
115+
}
116+
}
117+
118+
// DeleteAccountHistoryIndexBlock deletes the specified index block from the database.
119+
func DeleteAccountHistoryIndexBlock(db ethdb.KeyValueWriter, address common.Address, blockID uint32) {
120+
if err := db.Delete(accountHistoryIndexBlockKey(address, blockID)); err != nil {
121+
log.Crit("Failed to delete account index block", "err", err)
122+
}
123+
}
124+
125+
// ReadStorageHistoryIndexBlock retrieves the index block with the provided state
126+
// identifier along with the block id.
127+
func ReadStorageHistoryIndexBlock(db ethdb.KeyValueReader, address common.Address, storageHash common.Hash, blockID uint32) []byte {
128+
data, err := db.Get(storageHistoryIndexBlockKey(address, storageHash, blockID))
129+
if err != nil || len(data) == 0 {
130+
return nil
131+
}
132+
return data
133+
}
134+
135+
// WriteStorageHistoryIndexBlock writes the provided index block into database.
136+
func WriteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, address common.Address, storageHash common.Hash, id uint32, data []byte) {
137+
if err := db.Put(storageHistoryIndexBlockKey(address, storageHash, id), data); err != nil {
138+
log.Crit("Failed to store storage index block", "err", err)
139+
}
140+
}
141+
142+
// DeleteStorageHistoryIndexBlock deletes the specified index block from the database.
143+
func DeleteStorageHistoryIndexBlock(db ethdb.KeyValueWriter, address common.Address, state common.Hash, id uint32) {
144+
if err := db.Delete(storageHistoryIndexBlockKey(address, state, id)); err != nil {
145+
log.Crit("Failed to delete storage index block", "err", err)
146+
}
147+
}
148+
149+
// increaseKey increase the input key by one bit. Return nil if the entire
150+
// addition operation overflows.
151+
func increaseKey(key []byte) []byte {
152+
for i := len(key) - 1; i >= 0; i-- {
153+
key[i]++
154+
if key[i] != 0x0 {
155+
return key
156+
}
157+
}
158+
return nil
159+
}
160+
161+
// DeleteHistoryIndex completely removes all history indexing data, including indexes
162+
// for accounts and storages.
163+
//
164+
// Note, this method assumes the storage space with prefix `StateHistoryIndexPrefix`
165+
// is exclusively occupied by the history indexing data!
166+
func DeleteHistoryIndex(db ethdb.KeyValueRangeDeleter) {
167+
if err := db.DeleteRange(StateHistoryIndexPrefix, increaseKey(StateHistoryIndexPrefix)); err != nil {
168+
log.Crit("Failed to delete history index range", "err", err)
169+
}
170+
}

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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,9 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
387387
filterMapLastBlock stat
388388
filterMapBlockLV stat
389389

390+
// Path-mode archive data
391+
stateIndex stat
392+
390393
// Verkle statistics
391394
verkleTries stat
392395
verkleStateLookups stat
@@ -464,6 +467,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
464467
case bytes.HasPrefix(key, bloomBitsMetaPrefix) && len(key) < len(bloomBitsMetaPrefix)+8:
465468
bloomBits.Add(size)
466469

470+
// Path-based historic state indexes
471+
case bytes.HasPrefix(key, StateHistoryIndexPrefix) && len(key) >= len(StateHistoryIndexPrefix)+common.AddressLength:
472+
stateIndex.Add(size)
473+
467474
// Verkle trie data is detected, determine the sub-category
468475
case bytes.HasPrefix(key, VerklePrefix):
469476
remain := key[len(VerklePrefix):]
@@ -519,6 +526,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error {
519526
{"Key-Value store", "Path trie state lookups", stateLookups.Size(), stateLookups.Count()},
520527
{"Key-Value store", "Path trie account nodes", accountTries.Size(), accountTries.Count()},
521528
{"Key-Value store", "Path trie storage nodes", storageTries.Size(), storageTries.Count()},
529+
{"Key-Value store", "Path state history indexes", stateIndex.Size(), stateIndex.Count()},
522530
{"Key-Value store", "Verkle trie nodes", verkleTries.Size(), verkleTries.Count()},
523531
{"Key-Value store", "Verkle trie state lookups", verkleStateLookups.Size(), verkleStateLookups.Count()},
524532
{"Key-Value store", "Trie preimages", preimages.Size(), preimages.Count()},

core/rawdb/schema.go

Lines changed: 31 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,9 @@ 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") // StateHistoryIndexPrefix + account address or (account address + slotHash) -> index
126+
120127
// VerklePrefix is the database prefix for Verkle trie data, which includes:
121128
// (a) Trie nodes
122129
// (b) In-memory trie node journal
@@ -362,3 +369,27 @@ func filterMapBlockLVKey(number uint64) []byte {
362369
binary.BigEndian.PutUint64(key[l:], number)
363370
return key
364371
}
372+
373+
// accountHistoryIndexKey = StateHistoryIndexPrefix + address
374+
func accountHistoryIndexKey(address common.Address) []byte {
375+
return append(StateHistoryIndexPrefix, address.Bytes()...)
376+
}
377+
378+
// storageHistoryIndexKey = StateHistoryIndexPrefix + address + storageHash
379+
func storageHistoryIndexKey(address common.Address, storageHash common.Hash) []byte {
380+
return append(append(StateHistoryIndexPrefix, address.Bytes()...), storageHash.Bytes()...)
381+
}
382+
383+
// accountHistoryIndexBlockKey = StateHistoryIndexPrefix + address + blockID
384+
func accountHistoryIndexBlockKey(address common.Address, blockID uint32) []byte {
385+
var buf [4]byte
386+
binary.BigEndian.PutUint32(buf[:], blockID)
387+
return append(append(StateHistoryIndexPrefix, address.Bytes()...), buf[:]...)
388+
}
389+
390+
// storageHistoryIndexBlockKey = StateHistoryIndexPrefix + address + storageHash + blockID
391+
func storageHistoryIndexBlockKey(address common.Address, storageHash common.Hash, blockID uint32) []byte {
392+
var buf [4]byte
393+
binary.BigEndian.PutUint32(buf[:], blockID)
394+
return append(append(append(StateHistoryIndexPrefix, address.Bytes()...), storageHash.Bytes()...), buf[:]...)
395+
}

triedb/pathdb/database.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ type Database struct {
209209
tree *layerTree // The group for all known layers
210210
freezer ethdb.ResettableAncientStore // Freezer for storing trie histories, nil possible in tests
211211
lock sync.RWMutex // Lock to prevent mutations from happening at the same time
212+
indexer *historyIndexer // History indexer
212213
}
213214

214215
// New attempts to load an already existing layer from a persistent key-value
@@ -258,6 +259,10 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
258259
if err := db.setStateGenerator(); err != nil {
259260
log.Crit("Failed to setup the generator", "err", err)
260261
}
262+
// TODO (rjl493456442) disable the background indexing in read-only mode
263+
if db.freezer != nil {
264+
db.indexer = newHistoryIndexer(db.diskdb, db.freezer, db.tree.bottom().stateID())
265+
}
261266
fields := config.fields()
262267
if db.isVerkle {
263268
fields = append(fields, "verkle", true)
@@ -295,6 +300,11 @@ func (db *Database) repairHistory() error {
295300
log.Crit("Failed to retrieve head of state history", "err", err)
296301
}
297302
if frozen != 0 {
303+
// TODO(rjl493456442) would be better to group them into a batch.
304+
//
305+
// Purge all state history indexing data first
306+
rawdb.DeleteLastStateHistoryIndex(db.diskdb)
307+
rawdb.DeleteHistoryIndex(db.diskdb)
298308
err := db.freezer.Reset()
299309
if err != nil {
300310
log.Crit("Failed to reset state histories", "err", err)
@@ -477,6 +487,11 @@ func (db *Database) Enable(root common.Hash) error {
477487
// mappings can be huge and might take a while to clear
478488
// them, just leave them in disk and wait for overwriting.
479489
if db.freezer != nil {
490+
// TODO(rjl493456442) would be better to group them into a batch.
491+
//
492+
// Purge all state history indexing data first
493+
rawdb.DeleteLastStateHistoryIndex(db.diskdb)
494+
rawdb.DeleteHistoryIndex(db.diskdb)
480495
if err := db.freezer.Reset(); err != nil {
481496
return err
482497
}
@@ -599,6 +614,10 @@ func (db *Database) Close() error {
599614
}
600615
disk.resetCache() // release the memory held by clean cache
601616

617+
// Terminate the background state history indexer
618+
if db.indexer != nil {
619+
db.indexer.close()
620+
}
602621
// Close the attached state history freezer.
603622
if db.freezer == nil {
604623
return nil

triedb/pathdb/database_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,20 @@ func (t *tester) hashPreimage(hash common.Hash) common.Hash {
163163
return common.BytesToHash(t.preimages[hash])
164164
}
165165

166+
func (t *tester) extend(layers int) {
167+
for i := 0; i < layers; i++ {
168+
var parent = types.EmptyRootHash
169+
if len(t.roots) != 0 {
170+
parent = t.roots[len(t.roots)-1]
171+
}
172+
root, nodes, states := t.generate(parent, true)
173+
if err := t.db.Update(root, parent, uint64(i), nodes, states); err != nil {
174+
panic(fmt.Errorf("failed to update state changes, err: %w", err))
175+
}
176+
t.roots = append(t.roots, root)
177+
}
178+
}
179+
166180
func (t *tester) release() {
167181
t.db.Close()
168182
t.db.diskdb.Close()

triedb/pathdb/disklayer.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,12 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
325325
overflow = true
326326
oldest = bottom.stateID() - limit + 1 // track the id of history **after truncation**
327327
}
328+
// Notify the state history indexer for newly created history
329+
if dl.db.indexer != nil {
330+
if err := dl.db.indexer.extend(bottom.stateID()); err != nil {
331+
return nil, err
332+
}
333+
}
328334
}
329335
// Mark the diskLayer as stale before applying any mutations on top.
330336
dl.stale = true
@@ -418,6 +424,12 @@ func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
418424

419425
dl.stale = true
420426

427+
// Unindex the corresponding state history
428+
if dl.db.indexer != nil {
429+
if err := dl.db.indexer.shorten(dl.id); err != nil {
430+
return nil, err
431+
}
432+
}
421433
// State change may be applied to node buffer, or the persistent
422434
// state, depends on if node buffer is empty or not. If the node
423435
// buffer is not empty, it means that the state transition that

0 commit comments

Comments
 (0)