Skip to content
Merged
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
8 changes: 4 additions & 4 deletions triedb/pathdb/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ func New(diskdb ethdb.Database, config *Config, isVerkle bool) *Database {
}
// TODO (rjl493456442) disable the background indexing in read-only mode
if db.stateFreezer != nil && db.config.EnableStateIndexing {
db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID())
db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID(), typeStateHistory)
log.Info("Enabled state history indexing")
}
fields := config.fields()
Expand Down Expand Up @@ -245,7 +245,7 @@ func (db *Database) repairHistory() error {
}
// Truncate the extra state histories above in freezer in case it's not
// aligned with the disk layer. It might happen after a unclean shutdown.
pruned, err := truncateFromHead(db.stateFreezer, id)
pruned, err := truncateFromHead(db.stateFreezer, typeStateHistory, id)
if err != nil {
log.Crit("Failed to truncate extra state histories", "err", err)
}
Expand Down Expand Up @@ -448,7 +448,7 @@ func (db *Database) Enable(root common.Hash) error {
// 2. Re-initialize the indexer so it starts indexing from the new state root.
if db.stateIndexer != nil && db.stateFreezer != nil && db.config.EnableStateIndexing {
db.stateIndexer.close()
db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID())
db.stateIndexer = newHistoryIndexer(db.diskdb, db.stateFreezer, db.tree.bottom().stateID(), typeStateHistory)
log.Info("Re-enabled state history indexing")
}
log.Info("Rebuilt trie database", "root", root)
Expand Down Expand Up @@ -502,7 +502,7 @@ func (db *Database) Recover(root common.Hash) error {
if err := db.diskdb.SyncKeyValue(); err != nil {
return err
}
_, err := truncateFromHead(db.stateFreezer, dl.stateID())
_, err := truncateFromHead(db.stateFreezer, typeStateHistory, dl.stateID())
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion triedb/pathdb/disklayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ func (dl *diskLayer) writeStateHistory(diff *diffLayer) (bool, error) {
log.Debug("Skip tail truncation", "persistentID", persistentID, "tailID", tail+1, "headID", diff.stateID(), "limit", limit)
return true, nil
}
pruned, err := truncateFromTail(dl.db.stateFreezer, newFirst-1)
pruned, err := truncateFromTail(dl.db.stateFreezer, typeStateHistory, newFirst-1)
if err != nil {
return false, err
}
Expand Down
146 changes: 141 additions & 5 deletions triedb/pathdb/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,155 @@ package pathdb
import (
"errors"
"fmt"
"iter"

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

// historyType represents the category of historical data.
type historyType uint8

const (
// typeStateHistory indicates history data related to account or storage changes.
typeStateHistory historyType = 0
)

// String returns the string format representation.
func (h historyType) String() string {
switch h {
case typeStateHistory:
return "state"
default:
return fmt.Sprintf("unknown type: %d", h)
}
}

// elementType represents the category of state element.
type elementType uint8

const (
typeAccount elementType = 0 // represents the account data
typeStorage elementType = 1 // represents the storage slot data
)

// String returns the string format representation.
func (e elementType) String() string {
switch e {
case typeAccount:
return "account"
case typeStorage:
return "storage"
default:
return fmt.Sprintf("unknown element type: %d", e)
}
}

// toHistoryType maps an element type to its corresponding history type.
func toHistoryType(typ elementType) historyType {
if typ == typeAccount || typ == typeStorage {
return typeStateHistory
}
panic(fmt.Sprintf("unknown element type %v", typ))
}

// stateIdent represents the identifier of a state element, which can be
// an account or a storage slot.
type stateIdent struct {
typ elementType

// The hash of the account address. This is used instead of the raw account
// address is to align the traversal order with the Merkle-Patricia-Trie.
addressHash common.Hash

// The hash of the storage slot key. This is used instead of the raw slot key
// because, in legacy state histories (prior to the Cancun fork), the slot
// identifier is the hash of the key, and the original key (preimage) cannot
// be recovered. To maintain backward compatibility, the key hash is used.
//
// Meanwhile, using the storage key hash also preserve the traversal order
// with Merkle-Patricia-Trie.
//
// This field is null if the identifier refers to an account or a trie node.
storageHash common.Hash
}

// String returns the string format state identifier.
func (ident stateIdent) String() string {
if ident.typ == typeAccount {
return ident.addressHash.Hex()
}
return ident.addressHash.Hex() + ident.storageHash.Hex()
}

// newAccountIdent constructs a state identifier for an account.
func newAccountIdent(addressHash common.Hash) stateIdent {
return stateIdent{
typ: typeAccount,
addressHash: addressHash,
}
}

// newStorageIdent constructs a state identifier for a storage slot.
// The address denotes the address hash of the associated account;
// the storageHash denotes the hash of the raw storage slot key;
func newStorageIdent(addressHash common.Hash, storageHash common.Hash) stateIdent {
return stateIdent{
typ: typeStorage,
addressHash: addressHash,
storageHash: storageHash,
}
}

// stateIdentQuery is the extension of stateIdent by adding the account address
// and raw storage key.
type stateIdentQuery struct {
stateIdent

address common.Address
storageKey common.Hash
}

// newAccountIdentQuery constructs a state identifier for an account.
func newAccountIdentQuery(address common.Address, addressHash common.Hash) stateIdentQuery {
return stateIdentQuery{
stateIdent: newAccountIdent(addressHash),
address: address,
}
}

// newStorageIdentQuery constructs a state identifier for a storage slot.
// the address denotes the address of the associated account;
// the addressHash denotes the address hash of the associated account;
// the storageKey denotes the raw storage slot key;
// the storageHash denotes the hash of the raw storage slot key;
func newStorageIdentQuery(address common.Address, addressHash common.Hash, storageKey common.Hash, storageHash common.Hash) stateIdentQuery {
return stateIdentQuery{
stateIdent: newStorageIdent(addressHash, storageHash),
address: address,
storageKey: storageKey,
}
}

// history defines the interface of historical data, implemented by stateHistory
// and trienodeHistory (in the near future).
type history interface {
// typ returns the historical data type held in the history.
typ() historyType

// forEach returns an iterator to traverse the state entries in the history.
forEach() iter.Seq[stateIdent]
}

var (
errHeadTruncationOutOfRange = errors.New("history head truncation out of range")
errTailTruncationOutOfRange = errors.New("history tail truncation out of range")
)

// truncateFromHead removes excess elements from the head of the freezer based
// on the given parameters. It returns the number of items that were removed.
func truncateFromHead(store ethdb.AncientStore, nhead uint64) (int, error) {
func truncateFromHead(store ethdb.AncientStore, typ historyType, nhead uint64) (int, error) {
ohead, err := store.Ancients()
if err != nil {
return 0, err
Expand All @@ -40,11 +176,11 @@ func truncateFromHead(store ethdb.AncientStore, nhead uint64) (int, error) {
if err != nil {
return 0, err
}
log.Info("Truncating from head", "ohead", ohead, "tail", otail, "nhead", nhead)
log.Info("Truncating from head", "type", typ.String(), "ohead", ohead, "tail", otail, "nhead", nhead)

// Ensure that the truncation target falls within the valid range.
if ohead < nhead || nhead < otail {
return 0, fmt.Errorf("%w, tail: %d, head: %d, target: %d", errHeadTruncationOutOfRange, otail, ohead, nhead)
return 0, fmt.Errorf("%w, %s, tail: %d, head: %d, target: %d", errHeadTruncationOutOfRange, typ, otail, ohead, nhead)
}
// Short circuit if nothing to truncate.
if ohead == nhead {
Expand All @@ -61,7 +197,7 @@ func truncateFromHead(store ethdb.AncientStore, nhead uint64) (int, error) {

// truncateFromTail removes excess elements from the end of the freezer based
// on the given parameters. It returns the number of items that were removed.
func truncateFromTail(store ethdb.AncientStore, ntail uint64) (int, error) {
func truncateFromTail(store ethdb.AncientStore, typ historyType, ntail uint64) (int, error) {
ohead, err := store.Ancients()
if err != nil {
return 0, err
Expand All @@ -72,7 +208,7 @@ func truncateFromTail(store ethdb.AncientStore, ntail uint64) (int, error) {
}
// Ensure that the truncation target falls within the valid range.
if otail > ntail || ntail > ohead {
return 0, fmt.Errorf("%w, tail: %d, head: %d, target: %d", errTailTruncationOutOfRange, otail, ohead, ntail)
return 0, fmt.Errorf("%w, %s, tail: %d, head: %d, target: %d", errTailTruncationOutOfRange, typ, otail, ohead, ntail)
}
// Short circuit if nothing to truncate.
if otail == ntail {
Expand Down
Loading