mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-19 10:22:23 +00:00
core/rawdb, triedb/pathdb: implement history indexer
This commit is contained in:
parent
8c16ef03fd
commit
4262501d8c
9 changed files with 1060 additions and 0 deletions
98
core/rawdb/accessors_history.go
Normal file
98
core/rawdb/accessors_history.go
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
// Copyright 2024 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 (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadStateHistoryIndexHead retrieves the number of latest indexed state history.
|
||||||
|
func ReadStateHistoryIndexHead(db ethdb.KeyValueReader) *uint64 {
|
||||||
|
data, _ := db.Get(stateHistoryIndexHeadKey)
|
||||||
|
if len(data) != 8 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
number := binary.BigEndian.Uint64(data)
|
||||||
|
return &number
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStateHistoryIndexHead stores the number of latest indexed state history
|
||||||
|
// into database.
|
||||||
|
func WriteStateHistoryIndexHead(db ethdb.KeyValueWriter, number uint64) {
|
||||||
|
if err := db.Put(stateHistoryIndexHeadKey, encodeBlockNumber(number)); err != nil {
|
||||||
|
log.Crit("Failed to store the state index tail", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteStateHistoryIndexHead removes the number of latest indexed state history.
|
||||||
|
func DeleteStateHistoryIndexHead(db ethdb.KeyValueWriter) {
|
||||||
|
if err := db.Delete(stateHistoryIndexHeadKey); err != nil {
|
||||||
|
log.Crit("Failed to delete the state index tail", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadStateIndex retrieves the state index with the provided account address
|
||||||
|
// and state hash.
|
||||||
|
func ReadStateIndex(db ethdb.KeyValueReader, address common.Address, state common.Hash) []byte {
|
||||||
|
data, err := db.Get(stateIndexKey(address, state))
|
||||||
|
if err != nil || len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStateIndex writes the provided state index into database.
|
||||||
|
func WriteStateIndex(db ethdb.KeyValueWriter, address common.Address, state common.Hash, data []byte) {
|
||||||
|
if err := db.Put(stateIndexKey(address, state), data); err != nil {
|
||||||
|
log.Crit("Failed to store state index", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteStateIndex deletes the specified state index from the database.
|
||||||
|
func DeleteStateIndex(db ethdb.KeyValueWriter, address common.Address, state common.Hash) {
|
||||||
|
if err := db.Delete(stateIndexKey(address, state)); err != nil {
|
||||||
|
log.Crit("Failed to delete state index", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadStateIndexBlock retrieves the state index block with the provided state
|
||||||
|
// identifier along with the block id.
|
||||||
|
func ReadStateIndexBlock(db ethdb.KeyValueReader, address common.Address, state common.Hash, id uint32) []byte {
|
||||||
|
data, err := db.Get(stateIndexBlockKey(address, state, id))
|
||||||
|
if err != nil || len(data) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStateIndexBlock writes the provided state index block into database.
|
||||||
|
func WriteStateIndexBlock(db ethdb.KeyValueWriter, address common.Address, state common.Hash, id uint32, data []byte) {
|
||||||
|
if err := db.Put(stateIndexBlockKey(address, state, id), data); err != nil {
|
||||||
|
log.Crit("Failed to store state index", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteStateIndexBlock deletes the specified state index block from the database.
|
||||||
|
func DeleteStateIndexBlock(db ethdb.KeyValueWriter, address common.Address, state common.Hash, id uint32) {
|
||||||
|
if err := db.Delete(stateIndexBlockKey(address, state, id)); err != nil {
|
||||||
|
log.Crit("Failed to delete state index", "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -251,6 +251,33 @@ func ReadStateHistory(db ethdb.AncientReaderOp, id uint64) ([]byte, []byte, []by
|
||||||
return meta, accountIndex, storageIndex, accountData, storageData, nil
|
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
|
||||||
|
}
|
||||||
|
return metaList, aIndexList, sIndexList, aDataList, sDataList, nil
|
||||||
|
}
|
||||||
|
|
||||||
// WriteStateHistory writes the provided state history to database. Compute the
|
// 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
|
// position of state history in freezer by minus one since the id of first state
|
||||||
// history starts from one(zero for initial state).
|
// history starts from one(zero for initial state).
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,9 @@ var (
|
||||||
// trieJournalKey tracks the in-memory trie node layers across restarts.
|
// trieJournalKey tracks the in-memory trie node layers across restarts.
|
||||||
trieJournalKey = []byte("TrieJournal")
|
trieJournalKey = []byte("TrieJournal")
|
||||||
|
|
||||||
|
// stateHistoryIndexHeadKey tracks the ID of the latest state that has been indexed.
|
||||||
|
stateHistoryIndexHeadKey = []byte("StateHistoryIndexHead")
|
||||||
|
|
||||||
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
|
// txIndexTailKey tracks the oldest block whose transactions have been indexed.
|
||||||
txIndexTailKey = []byte("TransactionIndexTail")
|
txIndexTailKey = []byte("TransactionIndexTail")
|
||||||
|
|
||||||
|
|
@ -117,6 +120,9 @@ var (
|
||||||
TrieNodeStoragePrefix = []byte("O") // TrieNodeStoragePrefix + accountHash + hexPath -> trie node
|
TrieNodeStoragePrefix = []byte("O") // TrieNodeStoragePrefix + accountHash + hexPath -> trie node
|
||||||
stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id
|
stateIDPrefix = []byte("L") // stateIDPrefix + state root -> state id
|
||||||
|
|
||||||
|
// state history indexing within path-based storage scheme
|
||||||
|
stateIndexPrefix = []byte("M") // stateIndexPrefix + account address or (account address + slotHash) -> index
|
||||||
|
|
||||||
// VerklePrefix is the database prefix for Verkle trie data, which includes:
|
// VerklePrefix is the database prefix for Verkle trie data, which includes:
|
||||||
// (a) Trie nodes
|
// (a) Trie nodes
|
||||||
// (b) In-memory trie node journal
|
// (b) In-memory trie node journal
|
||||||
|
|
@ -346,3 +352,21 @@ func IsStorageTrieNode(key []byte) bool {
|
||||||
ok, _, _ := ResolveStorageTrieNode(key)
|
ok, _, _ := ResolveStorageTrieNode(key)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stateIndexKey = stateIndexPrefix + address + state
|
||||||
|
func stateIndexKey(address common.Address, state common.Hash) []byte {
|
||||||
|
if state == (common.Hash{}) {
|
||||||
|
return append(stateIndexPrefix, address.Bytes()...)
|
||||||
|
}
|
||||||
|
return append(append(stateIndexPrefix, address.Bytes()...), state.Bytes()...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateIndexBlockKey = stateIndexPrefix + address + state + id
|
||||||
|
func stateIndexBlockKey(address common.Address, state common.Hash, id uint32) []byte {
|
||||||
|
var buf [4]byte
|
||||||
|
binary.BigEndian.PutUint32(buf[:], id)
|
||||||
|
if state == (common.Hash{}) {
|
||||||
|
return append(append(stateIndexPrefix, address.Bytes()...), buf[:]...)
|
||||||
|
}
|
||||||
|
return append(append(append(stateIndexPrefix, address.Bytes()...), state.Bytes()...), buf[:]...)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -161,6 +161,7 @@ type Database struct {
|
||||||
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
diskdb ethdb.Database // Persistent storage for matured trie nodes
|
||||||
tree *layerTree // The group for all known layers
|
tree *layerTree // The group for all known layers
|
||||||
freezer ethdb.ResettableAncientStore // Freezer for storing trie histories, nil possible in tests
|
freezer ethdb.ResettableAncientStore // Freezer for storing trie histories, nil possible in tests
|
||||||
|
indexer *historyIndexer // History indexer
|
||||||
lock sync.RWMutex // Lock to prevent mutations from happening at the same time
|
lock sync.RWMutex // Lock to prevent mutations from happening at the same time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -257,6 +258,8 @@ func (db *Database) repairHistory() error {
|
||||||
if pruned != 0 {
|
if pruned != 0 {
|
||||||
log.Warn("Truncated extra state histories", "number", pruned)
|
log.Warn("Truncated extra state histories", "number", pruned)
|
||||||
}
|
}
|
||||||
|
// TODO read-only mode?
|
||||||
|
db.indexer = newHistoryIndexer(db.diskdb, db.freezer, db.tree.bottom().stateID())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,9 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
|
||||||
overflow = true
|
overflow = true
|
||||||
oldest = bottom.stateID() - limit + 1 // track the id of history **after truncation**
|
oldest = bottom.stateID() - limit + 1 // track the id of history **after truncation**
|
||||||
}
|
}
|
||||||
|
if dl.db.indexer != nil {
|
||||||
|
dl.db.indexer.notify(bottom.stateID())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Mark the diskLayer as stale before applying any mutations on top.
|
// Mark the diskLayer as stale before applying any mutations on top.
|
||||||
dl.markStale()
|
dl.markStale()
|
||||||
|
|
|
||||||
|
|
@ -490,6 +490,32 @@ func readHistory(reader ethdb.AncientReader, id uint64) (*history, error) {
|
||||||
return &dec, nil
|
return &dec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readHistories reads and decodes a list of state histories with specific
|
||||||
|
// history range.
|
||||||
|
func readHistories(freezer ethdb.AncientReader, start uint64, count uint64) ([]*history, error) {
|
||||||
|
metaList, aIndexList, sIndexList, aDataList, sDataList, err := rawdb.ReadStateHistoryList(freezer, start, count)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
number := len(metaList)
|
||||||
|
if number != len(aIndexList) || number != len(sIndexList) || number != len(aDataList) || number != len(sDataList) {
|
||||||
|
return nil, errors.New("corrupted state history")
|
||||||
|
}
|
||||||
|
var result []*history
|
||||||
|
for i := 0; i < number; i++ {
|
||||||
|
var m meta
|
||||||
|
if err := m.decode(metaList[i]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dec := history{meta: &m}
|
||||||
|
if err := dec.decode(aDataList[i], sDataList[i], aIndexList[i], sIndexList[i]); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result = append(result, &dec)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
// writeHistory persists the state history with the provided state set.
|
// writeHistory persists the state history with the provided state set.
|
||||||
func writeHistory(writer ethdb.AncientWriter, dl *diffLayer) error {
|
func writeHistory(writer ethdb.AncientWriter, dl *diffLayer) error {
|
||||||
// Short circuit if state set is not available.
|
// Short circuit if state set is not available.
|
||||||
|
|
|
||||||
339
triedb/pathdb/history_index_reader.go
Normal file
339
triedb/pathdb/history_index_reader.go
Normal file
|
|
@ -0,0 +1,339 @@
|
||||||
|
// Copyright 2023 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 pathdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
type blockReader struct {
|
||||||
|
restarts []uint32
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIndexBlock(blob []byte) ([]uint32, []byte, error) {
|
||||||
|
if len(blob) < 4 {
|
||||||
|
return nil, nil, fmt.Errorf("corrupted index block, len: %d", len(blob))
|
||||||
|
}
|
||||||
|
restartLen := binary.BigEndian.Uint32(blob[len(blob)-4:])
|
||||||
|
if restartLen == 0 {
|
||||||
|
return nil, nil, errors.New("corrupted index block, no restart")
|
||||||
|
}
|
||||||
|
tailLen := int(restartLen+1) * 4
|
||||||
|
if len(blob) < tailLen {
|
||||||
|
return nil, nil, fmt.Errorf("truncated restarts, size: %d, restarts: %d", len(blob), restartLen)
|
||||||
|
}
|
||||||
|
restarts := make([]uint32, 0, restartLen)
|
||||||
|
for i := restartLen; i > 0; i-- {
|
||||||
|
restart := binary.BigEndian.Uint32(blob[len(blob)-int(i+1)*4:])
|
||||||
|
restarts = append(restarts, restart)
|
||||||
|
}
|
||||||
|
prev := restarts[0]
|
||||||
|
for i := 1; i < len(restarts); i++ {
|
||||||
|
if restarts[i] <= prev {
|
||||||
|
return nil, nil, fmt.Errorf("restart out of order, prev: %d, next: %d", prev, restarts[i])
|
||||||
|
}
|
||||||
|
if int(restarts[i]) >= len(blob)-tailLen {
|
||||||
|
return nil, nil, fmt.Errorf("invalid restart position, restart: %d, size: %d", restarts[i], len(blob)-tailLen)
|
||||||
|
}
|
||||||
|
prev = restarts[i]
|
||||||
|
}
|
||||||
|
return restarts, blob[:len(blob)-tailLen], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBlockReader(disk ethdb.KeyValueReader, addr common.Address, state common.Hash, id uint32) (*blockReader, error) {
|
||||||
|
blob := rawdb.ReadStateIndexBlock(disk, addr, state, id)
|
||||||
|
if len(blob) == 0 {
|
||||||
|
return nil, errors.New("index block is not present")
|
||||||
|
}
|
||||||
|
restarts, data, err := parseIndexBlock(blob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &blockReader{
|
||||||
|
restarts: restarts,
|
||||||
|
buf: data, // safe to own the slice
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *blockReader) readGreaterThan(id uint64) (uint64, error) {
|
||||||
|
var err error
|
||||||
|
index := sort.Search(len(br.restarts), func(i int) bool {
|
||||||
|
item, n := binary.Uvarint(br.buf[br.restarts[i]:])
|
||||||
|
if n <= 0 {
|
||||||
|
err = errors.New("failed to decode item at restart point")
|
||||||
|
}
|
||||||
|
return item > id
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if index == 0 {
|
||||||
|
item, _ := binary.Uvarint(br.buf[br.restarts[0]:])
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
start int
|
||||||
|
limit int
|
||||||
|
result uint64
|
||||||
|
)
|
||||||
|
if index == len(br.restarts) {
|
||||||
|
start = int(br.restarts[len(br.restarts)-1])
|
||||||
|
limit = len(br.buf)
|
||||||
|
} else {
|
||||||
|
start = int(br.restarts[index-1])
|
||||||
|
limit = int(br.restarts[index])
|
||||||
|
}
|
||||||
|
pos := start
|
||||||
|
for pos < limit {
|
||||||
|
x, n := binary.Uvarint(br.buf[pos:])
|
||||||
|
if pos == start {
|
||||||
|
result = x
|
||||||
|
} else {
|
||||||
|
result += x
|
||||||
|
}
|
||||||
|
if result > id {
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
pos += n
|
||||||
|
}
|
||||||
|
return 0, errors.New("not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexReader struct {
|
||||||
|
disk ethdb.KeyValueReader
|
||||||
|
descList []*indexBlockDesc
|
||||||
|
readers map[uint32]*blockReader
|
||||||
|
owner common.Address
|
||||||
|
state common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseIndex(blob []byte) ([]*indexBlockDesc, error) {
|
||||||
|
if len(blob) == 0 {
|
||||||
|
return nil, errors.New("state index not found")
|
||||||
|
}
|
||||||
|
if len(blob)%indexBlockDescSize != 0 {
|
||||||
|
return nil, fmt.Errorf("corrupted state index, len: %d", len(blob))
|
||||||
|
}
|
||||||
|
var descList []*indexBlockDesc
|
||||||
|
for i := 0; i < len(blob)/indexBlockDescSize; i++ {
|
||||||
|
var desc indexBlockDesc
|
||||||
|
desc.decode(blob[i*indexBlockDescSize : (i+1)*indexBlockDescSize])
|
||||||
|
if desc.empty() {
|
||||||
|
return nil, errors.New("empty state index block")
|
||||||
|
}
|
||||||
|
descList = append(descList, &desc)
|
||||||
|
}
|
||||||
|
return descList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIndexReader(disk ethdb.KeyValueReader, owner common.Address, state common.Hash) (*indexReader, error) {
|
||||||
|
descList, err := parseIndex(rawdb.ReadStateIndex(disk, owner, state))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &indexReader{
|
||||||
|
descList: descList,
|
||||||
|
readers: make(map[uint32]*blockReader),
|
||||||
|
disk: disk,
|
||||||
|
owner: owner,
|
||||||
|
state: state,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *indexReader) readGreaterThan(id uint64) (uint64, error) {
|
||||||
|
index := sort.Search(len(r.descList), func(i int) bool {
|
||||||
|
return id < r.descList[i].max
|
||||||
|
})
|
||||||
|
if index == len(r.descList) {
|
||||||
|
return math.MaxUint64, nil
|
||||||
|
}
|
||||||
|
desc := r.descList[index]
|
||||||
|
|
||||||
|
br, ok := r.readers[desc.id]
|
||||||
|
if !ok {
|
||||||
|
var err error
|
||||||
|
br, err = newBlockReader(r.disk, r.owner, r.state, desc.id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
r.readers[desc.id] = br
|
||||||
|
}
|
||||||
|
return br.readGreaterThan(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
type historyReader struct {
|
||||||
|
disk ethdb.KeyValueReader
|
||||||
|
freezer ethdb.AncientReader
|
||||||
|
readers map[string]*indexReader
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHistoryReader(disk ethdb.KeyValueReader, freezer ethdb.AncientReader) *historyReader {
|
||||||
|
return &historyReader{
|
||||||
|
disk: disk,
|
||||||
|
freezer: freezer,
|
||||||
|
readers: make(map[string]*indexReader),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *historyReader) findAccount(account common.Address, id uint64, resolve func([]byte)) error {
|
||||||
|
blob := rawdb.ReadStateAccountIndex(r.freezer, id)
|
||||||
|
if len(blob)%accountIndexSize != 0 {
|
||||||
|
return errors.New("corrupted account index")
|
||||||
|
}
|
||||||
|
n := len(blob) / accountIndexSize
|
||||||
|
|
||||||
|
index := sort.Search(n, func(i int) bool {
|
||||||
|
h := blob[accountIndexSize*i : accountIndexSize*i+common.HashLength]
|
||||||
|
return bytes.Compare(h, account.Bytes()) >= 0
|
||||||
|
})
|
||||||
|
if index == n {
|
||||||
|
return errors.New("account is not found")
|
||||||
|
}
|
||||||
|
if account != common.BytesToAddress(blob[accountIndexSize*index:accountIndexSize*index+common.AddressLength]) {
|
||||||
|
return errors.New("account is not found")
|
||||||
|
}
|
||||||
|
resolve(blob[accountIndexSize*index : accountIndexSize*(index+1)])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *historyReader) findStorage(storageHash common.Hash, id uint64, slotOffset, slotLength int, resolve func([]byte)) error {
|
||||||
|
blob := rawdb.ReadStateStorageIndex(r.freezer, id)
|
||||||
|
if len(blob)%slotIndexSize != 0 {
|
||||||
|
return errors.New("storage indices are not corrupted")
|
||||||
|
}
|
||||||
|
if slotIndexSize*(slotOffset+slotLength) > len(blob) {
|
||||||
|
return errors.New("out of slice")
|
||||||
|
}
|
||||||
|
subSlice := blob[slotIndexSize*slotOffset : slotIndexSize*(slotOffset+slotLength)]
|
||||||
|
|
||||||
|
index := sort.Search(slotLength, func(i int) bool {
|
||||||
|
slotHash := subSlice[slotIndexSize*i : slotIndexSize*i+common.HashLength]
|
||||||
|
return bytes.Compare(slotHash, storageHash.Bytes()) >= 0
|
||||||
|
})
|
||||||
|
if index == slotLength {
|
||||||
|
return errors.New("storage is not found")
|
||||||
|
}
|
||||||
|
if storageHash != common.BytesToHash(subSlice[slotIndexSize*index:slotIndexSize*index+common.HashLength]) {
|
||||||
|
return errors.New("storage is not found")
|
||||||
|
}
|
||||||
|
resolve(subSlice[slotIndexSize*index : slotIndexSize*(index+1)])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *historyReader) resolveAccount(accountHash common.Address, id uint64) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
offset int
|
||||||
|
length int
|
||||||
|
)
|
||||||
|
err := r.findAccount(accountHash, id, func(blob []byte) {
|
||||||
|
length = int(blob[common.AddressLength])
|
||||||
|
offset = int(binary.BigEndian.Uint32(blob[common.AddressLength+1 : common.AddressLength+5]))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO(rj493456442) optimize it with partial read
|
||||||
|
data := rawdb.ReadStateAccountHistory(r.freezer, id)
|
||||||
|
if len(data) < offset+length {
|
||||||
|
return nil, errors.New("corrupted account data")
|
||||||
|
}
|
||||||
|
return data[offset : offset+length], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *historyReader) resolveStorage(account common.Address, storageHash common.Hash, id uint64) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
slotOffset int
|
||||||
|
slotLength int
|
||||||
|
offset int
|
||||||
|
length int
|
||||||
|
)
|
||||||
|
err := r.findAccount(account, id, func(blob []byte) {
|
||||||
|
slotOffset = int(binary.BigEndian.Uint32(blob[common.AddressLength+5 : common.AddressLength+9]))
|
||||||
|
slotLength = int(binary.BigEndian.Uint32(blob[common.AddressLength+9 : common.AddressLength+13]))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = r.findStorage(storageHash, id, slotOffset, slotLength, func(blob []byte) {
|
||||||
|
length = int(blob[common.HashLength])
|
||||||
|
offset = int(binary.BigEndian.Uint32(blob[common.HashLength+1 : common.HashLength+5]))
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// TODO(rj493456442) optimize it with partial read
|
||||||
|
data := rawdb.ReadStateStorageHistory(r.freezer, id)
|
||||||
|
if len(data) < offset+length {
|
||||||
|
return nil, errors.New("corrupted storage data")
|
||||||
|
}
|
||||||
|
return data[offset : offset+length], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *historyReader) resolve(owner common.Address, state common.Hash, id uint64) ([]byte, error) {
|
||||||
|
if state == (common.Hash{}) {
|
||||||
|
return r.resolveAccount(owner, id)
|
||||||
|
}
|
||||||
|
return r.resolveStorage(owner, state, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *historyReader) read(owner common.Address, state common.Hash, id uint64, latest uint64) ([]byte, error) {
|
||||||
|
tail, err := r.freezer.Tail()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// id == tail is allowed, as the first history object preserved is tail+1
|
||||||
|
if id < tail {
|
||||||
|
return nil, errors.New("historic state is pruned")
|
||||||
|
}
|
||||||
|
head := rawdb.ReadStateHistoryIndexHead(r.disk)
|
||||||
|
|
||||||
|
/*
|
||||||
|
the available range of histories is [tail+1, head]
|
||||||
|
*/
|
||||||
|
if head == nil || *head <= id {
|
||||||
|
return nil, errors.New("state history is not fully indexed")
|
||||||
|
}
|
||||||
|
ir, ok := r.readers[owner.Hex()+state.Hex()]
|
||||||
|
if !ok {
|
||||||
|
ir, err = newIndexReader(r.disk, owner, state)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.readers[owner.Hex()+state.Hex()] = ir
|
||||||
|
}
|
||||||
|
id, err = ir.readGreaterThan(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if id == math.MaxUint64 {
|
||||||
|
if *head < latest {
|
||||||
|
return nil, errors.New("state history is not fully indexed")
|
||||||
|
}
|
||||||
|
return nil, errors.New("not found")
|
||||||
|
}
|
||||||
|
return r.resolve(owner, state, id)
|
||||||
|
}
|
||||||
330
triedb/pathdb/history_index_writer.go
Normal file
330
triedb/pathdb/history_index_writer.go
Normal file
|
|
@ -0,0 +1,330 @@
|
||||||
|
// Copyright 2024 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 pathdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
indexBlockDescSize = 24 // The size of index block descriptor
|
||||||
|
indexBlockEntriesCap = 4096 // The maximum number of entries can be grouped in a block
|
||||||
|
indexBlockRestartLen = 256 // The restart interval length of index block
|
||||||
|
|
||||||
|
// stateWriteBatch is the number of states for constructing indexes together.
|
||||||
|
// In the worst case, the database write caused by each state is roughly 4KB,
|
||||||
|
// 256MB in total is still acceptable.
|
||||||
|
stateWriteBatch = 65536
|
||||||
|
)
|
||||||
|
|
||||||
|
// indexBlockDesc is the descriptor of an index block that contains a list of
|
||||||
|
// state mutation records belonging to a specific state (account or storage slot).
|
||||||
|
type indexBlockDesc struct {
|
||||||
|
min uint64
|
||||||
|
max uint64
|
||||||
|
entries uint32
|
||||||
|
id uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIndexBlockDesc(id uint32) *indexBlockDesc {
|
||||||
|
return &indexBlockDesc{id: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *indexBlockDesc) empty() bool {
|
||||||
|
return d.entries == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *indexBlockDesc) full() bool {
|
||||||
|
return d.entries >= indexBlockEntriesCap
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode packs index block descriptor into byte stream.
|
||||||
|
func (d *indexBlockDesc) encode() []byte {
|
||||||
|
var buf [indexBlockDescSize]byte
|
||||||
|
binary.BigEndian.PutUint64(buf[:8], d.min)
|
||||||
|
binary.BigEndian.PutUint64(buf[8:16], d.max)
|
||||||
|
binary.BigEndian.PutUint32(buf[16:20], d.entries)
|
||||||
|
binary.BigEndian.PutUint32(buf[20:24], d.id)
|
||||||
|
return buf[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode unpacks index block descriptor from byte stream.
|
||||||
|
func (d *indexBlockDesc) decode(blob []byte) {
|
||||||
|
d.min = binary.BigEndian.Uint64(blob[:8])
|
||||||
|
d.max = binary.BigEndian.Uint64(blob[8:16])
|
||||||
|
d.entries = binary.BigEndian.Uint32(blob[16:20])
|
||||||
|
d.id = binary.BigEndian.Uint32(blob[20:24])
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockWriter struct {
|
||||||
|
desc *indexBlockDesc
|
||||||
|
restarts []uint32
|
||||||
|
scratch []byte
|
||||||
|
buf []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBlockWriter(blob []byte, desc *indexBlockDesc) (*blockWriter, error) {
|
||||||
|
scratch := make([]byte, binary.MaxVarintLen64)
|
||||||
|
if len(blob) == 0 {
|
||||||
|
return &blockWriter{
|
||||||
|
desc: desc,
|
||||||
|
scratch: scratch,
|
||||||
|
buf: make([]byte, 0, 1024),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
restarts, data, err := parseIndexBlock(blob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &blockWriter{
|
||||||
|
desc: desc,
|
||||||
|
restarts: restarts,
|
||||||
|
scratch: scratch,
|
||||||
|
buf: data, // safe to own the slice
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blockWriter) append(id uint64) error {
|
||||||
|
if id <= b.desc.max {
|
||||||
|
return fmt.Errorf("element out of order, last: %d, this: %d", b.desc.max, id)
|
||||||
|
}
|
||||||
|
if b.desc.entries%indexBlockRestartLen == 0 {
|
||||||
|
b.restarts = append(b.restarts, uint32(len(b.buf)))
|
||||||
|
|
||||||
|
// The restart point item can be either encoded in variable
|
||||||
|
// size or fixed size. Although variable-size encoding is
|
||||||
|
// slightly slower (2ns per operation), it is still relatively
|
||||||
|
// fast, therefore, it's picked for better space efficiency.
|
||||||
|
n := binary.PutUvarint(b.scratch[0:], id)
|
||||||
|
b.buf = append(b.buf, b.scratch[:n]...)
|
||||||
|
} else {
|
||||||
|
n := binary.PutUvarint(b.scratch[0:], id-b.desc.max)
|
||||||
|
b.buf = append(b.buf, b.scratch[:n]...)
|
||||||
|
}
|
||||||
|
b.desc.entries++
|
||||||
|
if b.desc.min == 0 {
|
||||||
|
b.desc.min = id
|
||||||
|
}
|
||||||
|
b.desc.max = id
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blockWriter) empty() bool {
|
||||||
|
return b.desc.empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blockWriter) full() bool {
|
||||||
|
return b.desc.full()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blockWriter) finish() error {
|
||||||
|
b.restarts = append(b.restarts, uint32(len(b.restarts)))
|
||||||
|
for _, number := range b.restarts {
|
||||||
|
binary.BigEndian.PutUint32(b.scratch[:4], number)
|
||||||
|
b.buf = append(b.buf, b.scratch[:4]...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type indexWriter struct {
|
||||||
|
descList []*indexBlockDesc
|
||||||
|
last uint64
|
||||||
|
bw *blockWriter
|
||||||
|
frozen []*blockWriter
|
||||||
|
|
||||||
|
db ethdb.KeyValueStore
|
||||||
|
addr common.Address
|
||||||
|
state common.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
func newIndexWriter(db ethdb.KeyValueStore, addr common.Address, state common.Hash) (*indexWriter, error) {
|
||||||
|
blob := rawdb.ReadStateIndex(db, addr, state)
|
||||||
|
if len(blob) == 0 {
|
||||||
|
desc := &indexBlockDesc{}
|
||||||
|
bw, _ := newBlockWriter(nil, desc)
|
||||||
|
return &indexWriter{
|
||||||
|
descList: []*indexBlockDesc{desc},
|
||||||
|
bw: bw,
|
||||||
|
db: db,
|
||||||
|
addr: addr,
|
||||||
|
state: state,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
descList, err := parseIndex(blob)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Open the last block writer, or create a new one in case
|
||||||
|
// it's already full.
|
||||||
|
var (
|
||||||
|
lastDesc = descList[len(descList)-1]
|
||||||
|
lastElem = lastDesc.max
|
||||||
|
)
|
||||||
|
if lastDesc.full() {
|
||||||
|
descList = append(descList, &indexBlockDesc{id: lastDesc.id + 1})
|
||||||
|
lastDesc = descList[len(descList)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
indexBlock := rawdb.ReadStateIndexBlock(db, addr, state, lastDesc.id)
|
||||||
|
bw, err := newBlockWriter(indexBlock, lastDesc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &indexWriter{
|
||||||
|
descList: descList,
|
||||||
|
last: lastElem,
|
||||||
|
bw: bw,
|
||||||
|
db: db,
|
||||||
|
addr: addr,
|
||||||
|
state: state,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *indexWriter) append(id uint64) error {
|
||||||
|
if id <= w.last {
|
||||||
|
return fmt.Errorf("element out of order, last: %d, this: %d", w.last, id)
|
||||||
|
}
|
||||||
|
if err := w.bw.append(id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.last = id
|
||||||
|
|
||||||
|
if w.bw.full() {
|
||||||
|
w.rotate()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *indexWriter) rotate() {
|
||||||
|
w.frozen = append(w.frozen, w.bw)
|
||||||
|
desc := newIndexBlockDesc(w.bw.desc.id + 1)
|
||||||
|
w.bw, _ = newBlockWriter(nil, desc)
|
||||||
|
w.descList = append(w.descList, desc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *indexWriter) finish(batch ethdb.Batch) error {
|
||||||
|
var (
|
||||||
|
writers = append(w.frozen, w.bw)
|
||||||
|
descList = w.descList
|
||||||
|
)
|
||||||
|
// Chop the last block if it's empty
|
||||||
|
if w.bw.empty() {
|
||||||
|
writers = writers[:len(writers)-1]
|
||||||
|
descList = descList[:len(descList)-1]
|
||||||
|
}
|
||||||
|
if len(writers) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, bw := range writers {
|
||||||
|
if err := bw.finish(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rawdb.WriteStateIndexBlock(batch, w.addr, w.state, bw.desc.id, bw.buf)
|
||||||
|
}
|
||||||
|
buf := make([]byte, 0, indexBlockDescSize*len(descList))
|
||||||
|
for _, desc := range descList {
|
||||||
|
buf = append(buf, desc.encode()...)
|
||||||
|
}
|
||||||
|
rawdb.WriteStateIndex(batch, w.addr, w.state, buf)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type historyWriter struct {
|
||||||
|
accounts map[common.Address][]uint64
|
||||||
|
storages map[common.Address]map[common.Hash][]uint64
|
||||||
|
total int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHistoryWriter() *historyWriter {
|
||||||
|
return &historyWriter{
|
||||||
|
accounts: make(map[common.Address][]uint64),
|
||||||
|
storages: make(map[common.Address]map[common.Hash][]uint64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *historyWriter) reset() {
|
||||||
|
w.total = 0
|
||||||
|
w.accounts = make(map[common.Address][]uint64)
|
||||||
|
w.storages = make(map[common.Address]map[common.Hash][]uint64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *historyWriter) addAccount(addr common.Address, number uint64) {
|
||||||
|
w.total += 1
|
||||||
|
w.accounts[addr] = append(w.accounts[addr], number)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *historyWriter) addSlot(addr common.Address, hash common.Hash, number uint64) {
|
||||||
|
w.total += 1
|
||||||
|
if _, ok := w.storages[addr]; !ok {
|
||||||
|
w.storages[addr] = make(map[common.Hash][]uint64)
|
||||||
|
}
|
||||||
|
w.storages[addr][hash] = append(w.storages[addr][hash], number)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *historyWriter) finish(db ethdb.KeyValueStore, force bool, head uint64) error {
|
||||||
|
if !force && w.total < stateWriteBatch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
s = time.Now()
|
||||||
|
batch = db.NewBatch()
|
||||||
|
)
|
||||||
|
for account, idList := range w.accounts {
|
||||||
|
iw, err := newIndexWriter(db, account, common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, id := range idList {
|
||||||
|
if err := iw.append(id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := iw.finish(batch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for account, slots := range w.storages {
|
||||||
|
for slot, idList := range slots {
|
||||||
|
iw, err := newIndexWriter(db, account, slot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, id := range idList {
|
||||||
|
if err := iw.append(id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := iw.finish(batch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rawdb.WriteStateHistoryIndexHead(batch, head)
|
||||||
|
if err := batch.Write(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.reset()
|
||||||
|
log.Info("Written state history indexes", "head", head, "size", common.StorageSize(batch.ValueSize()), "elapsed", common.PrettyDuration(time.Since(s)))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
210
triedb/pathdb/history_indexer.go
Normal file
210
triedb/pathdb/history_indexer.go
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
// Copyright 2024 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 pathdb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const historyReadBatch = 1000 // The batch size for reading state history
|
||||||
|
|
||||||
|
type historyIndexer struct {
|
||||||
|
disk ethdb.KeyValueStore
|
||||||
|
freezer ethdb.AncientStore
|
||||||
|
headCh chan uint64
|
||||||
|
closeCh chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func newHistoryIndexer(disk ethdb.KeyValueStore, freezer ethdb.AncientStore, head uint64) *historyIndexer {
|
||||||
|
indexer := &historyIndexer{
|
||||||
|
disk: disk,
|
||||||
|
freezer: freezer,
|
||||||
|
headCh: make(chan uint64),
|
||||||
|
closeCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
indexer.wg.Add(1)
|
||||||
|
go indexer.loop(head)
|
||||||
|
return indexer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *historyIndexer) close() {
|
||||||
|
select {
|
||||||
|
case <-i.closeCh:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
close(i.closeCh)
|
||||||
|
i.wg.Wait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *historyIndexer) notify(head uint64) error {
|
||||||
|
select {
|
||||||
|
case <-i.closeCh:
|
||||||
|
return errors.New("closed")
|
||||||
|
case i.headCh <- head:
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *historyIndexer) process(w *historyWriter, h *history, id uint64) error {
|
||||||
|
for _, account := range h.accountList {
|
||||||
|
w.addAccount(account, id)
|
||||||
|
for _, slot := range h.storageList[account] {
|
||||||
|
w.addSlot(account, slot, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w.finish(i.disk, false, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *historyIndexer) next() (uint64, error) {
|
||||||
|
tail, err := i.freezer.Tail()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
tailID := tail + 1 // compute the real history id
|
||||||
|
|
||||||
|
// Start indexing from scratch if nothing has been indexed
|
||||||
|
head := rawdb.ReadStateHistoryIndexHead(i.disk)
|
||||||
|
if head == nil {
|
||||||
|
return tailID, nil
|
||||||
|
}
|
||||||
|
// Resume indexing from the last interrupted position
|
||||||
|
if *head+1 >= tailID {
|
||||||
|
return *head + 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", *head, "newHead", tailID)
|
||||||
|
return tailID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *historyIndexer) run(done chan struct{}, head uint64, interrupt *atomic.Int32) {
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
|
begin, err := i.next()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to find next state history for indexing", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO what if head is lower than the index head. It can
|
||||||
|
// happen if the entire state history freezer is reset.
|
||||||
|
//if begin > head {
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
log.Info("Start history indexing", "begin", begin, "head", head)
|
||||||
|
|
||||||
|
var (
|
||||||
|
current = begin
|
||||||
|
writer = newHistoryWriter()
|
||||||
|
start = time.Now()
|
||||||
|
logged = time.Now()
|
||||||
|
)
|
||||||
|
for current <= head {
|
||||||
|
count := head - current + 1
|
||||||
|
if count > historyReadBatch {
|
||||||
|
count = historyReadBatch
|
||||||
|
}
|
||||||
|
s := time.Now()
|
||||||
|
result, err := readHistories(i.freezer, current, count)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to read history", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debug("Loaded histories", "number", len(result), "elapsed", common.PrettyDuration(time.Since(s)))
|
||||||
|
|
||||||
|
for _, h := range result {
|
||||||
|
if err := i.process(writer, h, current); err != nil {
|
||||||
|
log.Error("Failed to index history", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
current += 1
|
||||||
|
|
||||||
|
if time.Since(logged) > time.Second*8 {
|
||||||
|
logged = time.Now()
|
||||||
|
|
||||||
|
var (
|
||||||
|
left = head - current
|
||||||
|
done = current - begin
|
||||||
|
speed = done/uint64(time.Since(start)/time.Millisecond+1) + 1 // +1s to avoid division by zero
|
||||||
|
)
|
||||||
|
// Override the ETA if larger than the largest until now
|
||||||
|
eta := time.Duration(left/speed) * time.Millisecond
|
||||||
|
log.Info("Indexing state history", "processed", current-begin+1, "remain", head-current, "eta", common.PrettyDuration(eta))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check interruption signal and abort process if it's fired
|
||||||
|
if interrupt != nil {
|
||||||
|
if signal := interrupt.Load(); signal != 0 {
|
||||||
|
if err := writer.finish(i.disk, true, current-1); err != nil {
|
||||||
|
log.Error("Failed to flush index", "err", err)
|
||||||
|
}
|
||||||
|
log.Info("State indexing interrupted")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := writer.finish(i.disk, true, head); err != nil {
|
||||||
|
log.Error("Failed to flush index", "err", err)
|
||||||
|
}
|
||||||
|
log.Info("Indexed state history", "from", begin, "to", head, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *historyIndexer) loop(head uint64) {
|
||||||
|
defer i.wg.Done()
|
||||||
|
|
||||||
|
// Launch background indexing thread
|
||||||
|
done, interrupt := make(chan struct{}), new(atomic.Int32)
|
||||||
|
go i.run(done, head, interrupt)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case newHead := <-i.headCh:
|
||||||
|
if newHead <= head {
|
||||||
|
// TODO, reorg??
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
head = newHead
|
||||||
|
|
||||||
|
if done == nil {
|
||||||
|
done, interrupt = make(chan struct{}), new(atomic.Int32)
|
||||||
|
go i.run(done, head, interrupt)
|
||||||
|
}
|
||||||
|
case <-done:
|
||||||
|
done, interrupt = nil, nil
|
||||||
|
|
||||||
|
case <-i.closeCh:
|
||||||
|
if done != nil {
|
||||||
|
interrupt.Store(1)
|
||||||
|
log.Info("Waiting background history indexer to exit")
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue