mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-17 12:21:38 +00:00
core/state: implement state iterator
This commit is contained in:
parent
92b4cb2663
commit
e376aba33f
8 changed files with 759 additions and 128 deletions
|
|
@ -39,6 +39,10 @@ type Database interface {
|
||||||
// Reader returns a state reader associated with the specified state root.
|
// Reader returns a state reader associated with the specified state root.
|
||||||
Reader(root common.Hash) (Reader, error)
|
Reader(root common.Hash) (Reader, error)
|
||||||
|
|
||||||
|
// Iteratee returns a state iteratee associated with the specified state root,
|
||||||
|
// through which the account iterator and storage iterator can be created.
|
||||||
|
Iteratee(root common.Hash) (Iteratee, error)
|
||||||
|
|
||||||
// OpenTrie opens the main account trie.
|
// OpenTrie opens the main account trie.
|
||||||
OpenTrie(root common.Hash) (Trie, error)
|
OpenTrie(root common.Hash) (Trie, error)
|
||||||
|
|
||||||
|
|
@ -48,9 +52,6 @@ type Database interface {
|
||||||
// TrieDB returns the underlying trie database for managing trie nodes.
|
// TrieDB returns the underlying trie database for managing trie nodes.
|
||||||
TrieDB() *triedb.Database
|
TrieDB() *triedb.Database
|
||||||
|
|
||||||
// Snapshot returns the underlying state snapshot.
|
|
||||||
Snapshot() *snapshot.Tree
|
|
||||||
|
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
|
|
@ -310,6 +311,12 @@ func (db *CachingDB) Commit(update *stateUpdate) error {
|
||||||
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
|
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iteratee returns a state iteratee associated with the specified state root,
|
||||||
|
// through which the account iterator and storage iterator can be created.
|
||||||
|
func (db *CachingDB) Iteratee(root common.Hash) (Iteratee, error) {
|
||||||
|
return newStateIteratee(!db.triedb.IsVerkle(), root, db.triedb, db.snap)
|
||||||
|
}
|
||||||
|
|
||||||
// mustCopyTrie returns a deep-copied trie.
|
// mustCopyTrie returns a deep-copied trie.
|
||||||
func mustCopyTrie(t Trie) Trie {
|
func mustCopyTrie(t Trie) Trie {
|
||||||
switch t := t.(type) {
|
switch t := t.(type) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
|
@ -289,14 +288,15 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
|
||||||
return db.triedb
|
return db.triedb
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot returns the underlying state snapshot.
|
|
||||||
func (db *HistoricDB) Snapshot() *snapshot.Tree {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commit flushes all pending writes and finalizes the state transition,
|
// Commit flushes all pending writes and finalizes the state transition,
|
||||||
// committing the changes to the underlying storage. It returns an error
|
// committing the changes to the underlying storage. It returns an error
|
||||||
// if the commit fails.
|
// if the commit fails.
|
||||||
func (db *HistoricDB) Commit(update *stateUpdate) error {
|
func (db *HistoricDB) Commit(update *stateUpdate) error {
|
||||||
return errors.New("not implemented")
|
return errors.New("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iteratee returns a state iteratee associated with the specified state root,
|
||||||
|
// through which the account iterator and storage iterator can be created.
|
||||||
|
func (db *HistoricDB) Iteratee(root common.Hash) (Iteratee, error) {
|
||||||
|
return nil, errors.New("not implemented")
|
||||||
|
}
|
||||||
|
|
|
||||||
431
core/state/database_iterator.go
Normal file
431
core/state/database_iterator.go
Normal file
|
|
@ -0,0 +1,431 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Iterator is an iterator to step over all the accounts or the specific
|
||||||
|
// storage in the specific state.
|
||||||
|
type Iterator interface {
|
||||||
|
// Next steps the iterator forward one element, returning false if exhausted,
|
||||||
|
// or an error if iteration failed for some reason.
|
||||||
|
Next() bool
|
||||||
|
|
||||||
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
|
// caused a premature iteration exit.
|
||||||
|
Error() error
|
||||||
|
|
||||||
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
// currently at.
|
||||||
|
Hash() common.Hash
|
||||||
|
|
||||||
|
// Release releases associated resources. Release should always succeed and
|
||||||
|
// can be called multiple times without causing error.
|
||||||
|
Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountIterator is an iterator to step over all the accounts in the
|
||||||
|
// specific state.
|
||||||
|
type AccountIterator interface {
|
||||||
|
Iterator
|
||||||
|
|
||||||
|
// Address returns the raw account address the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
Address() (common.Address, error)
|
||||||
|
|
||||||
|
// Account returns the RLP encoded account the iterator is currently at.
|
||||||
|
// An error will be retained if the iterator becomes invalid.
|
||||||
|
Account() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// StorageIterator is an iterator to step over the specific storage in the
|
||||||
|
// specific state.
|
||||||
|
type StorageIterator interface {
|
||||||
|
Iterator
|
||||||
|
|
||||||
|
// Key returns the raw storage slot key the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
Key() (common.Hash, error)
|
||||||
|
|
||||||
|
// Slot returns the storage slot the iterator is currently at. An error will
|
||||||
|
// be retained if the iterator becomes invalid.
|
||||||
|
Slot() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iteratee wraps the NewIterator methods for traversing the accounts and
|
||||||
|
// storages of the specific state.
|
||||||
|
type Iteratee interface {
|
||||||
|
// NewAccountIterator creates an account iterator for the state specified by
|
||||||
|
// the given root. It begins at a specified starting position, corresponding
|
||||||
|
// to a particular initial key (or the next key if the specified one does
|
||||||
|
// not exist).
|
||||||
|
//
|
||||||
|
// The starting position here refers to the hash of the account address.
|
||||||
|
NewAccountIterator(start common.Hash) (AccountIterator, error)
|
||||||
|
|
||||||
|
// NewStorageIterator creates a storage iterator for the state specified by
|
||||||
|
// the address hash. It begins at a specified starting position, corresponding
|
||||||
|
// to a particular initial key (or the next key if the specified one does
|
||||||
|
// not exist).
|
||||||
|
//
|
||||||
|
// The starting position here refers to the hash of the slot key.
|
||||||
|
NewStorageIterator(addressHash common.Hash, start common.Hash) (StorageIterator, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreimageReader wraps the function Preimage for accessing the preimage of
|
||||||
|
// a given hash.
|
||||||
|
type PreimageReader interface {
|
||||||
|
// Preimage returns the preimage of associated hash.
|
||||||
|
Preimage(hash common.Hash) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatAccountIterator is a wrapper around the underlying flat state iterator.
|
||||||
|
// Before returning data from the iterator, it performs an additional conversion
|
||||||
|
// to bridge the slim encoding with the full encoding format.
|
||||||
|
type flatAccountIterator struct {
|
||||||
|
err error
|
||||||
|
it snapshot.AccountIterator
|
||||||
|
preimage PreimageReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFlatAccountIterator constructs the account iterator with the provided
|
||||||
|
// flat state iterator.
|
||||||
|
func newFlatAccountIterator(it snapshot.AccountIterator, preimage PreimageReader) *flatAccountIterator {
|
||||||
|
return &flatAccountIterator{it: it, preimage: preimage}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next steps the iterator forward one element, returning false if exhausted,
|
||||||
|
// or an error if iteration failed for some reason.
|
||||||
|
func (ai *flatAccountIterator) Next() bool {
|
||||||
|
if ai.err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return ai.it.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
|
// caused a premature iteration exit.
|
||||||
|
func (ai *flatAccountIterator) Error() error {
|
||||||
|
if ai.err != nil {
|
||||||
|
return ai.err
|
||||||
|
}
|
||||||
|
return ai.it.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
// currently at.
|
||||||
|
func (ai *flatAccountIterator) Hash() common.Hash {
|
||||||
|
return ai.it.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases associated resources. Release should always succeed and
|
||||||
|
// can be called multiple times without causing error.
|
||||||
|
func (ai *flatAccountIterator) Release() {
|
||||||
|
ai.it.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Address returns the raw account address the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
func (ai *flatAccountIterator) Address() (common.Address, error) {
|
||||||
|
if ai.preimage == nil {
|
||||||
|
return common.Address{}, errors.New("account address is not available")
|
||||||
|
}
|
||||||
|
preimage := ai.preimage.Preimage(ai.Hash())
|
||||||
|
if preimage == nil {
|
||||||
|
return common.Address{}, errors.New("account address is not available")
|
||||||
|
}
|
||||||
|
return common.BytesToAddress(preimage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account returns the account data the iterator is currently at. The account
|
||||||
|
// data is encoded as slim format from the underlying iterator, the conversion
|
||||||
|
// is required.
|
||||||
|
func (ai *flatAccountIterator) Account() []byte {
|
||||||
|
data, err := types.FullAccountRLP(ai.it.Account())
|
||||||
|
if err != nil {
|
||||||
|
ai.err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatStorageIterator is a wrapper around the underlying flat state iterator.
|
||||||
|
type flatStorageIterator struct {
|
||||||
|
it snapshot.StorageIterator
|
||||||
|
preimage PreimageReader
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFlatStorageIterator constructs the storage iterator with the provided
|
||||||
|
// flat state iterator.
|
||||||
|
func newFlatStorageIterator(it snapshot.StorageIterator, preimage PreimageReader) *flatStorageIterator {
|
||||||
|
return &flatStorageIterator{it: it, preimage: preimage}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next steps the iterator forward one element, returning false if exhausted,
|
||||||
|
// or an error if iteration failed for some reason.
|
||||||
|
func (si *flatStorageIterator) Next() bool {
|
||||||
|
return si.it.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
|
// caused a premature iteration exit.
|
||||||
|
func (si *flatStorageIterator) Error() error {
|
||||||
|
return si.it.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
// currently at.
|
||||||
|
func (si *flatStorageIterator) Hash() common.Hash {
|
||||||
|
return si.it.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases associated resources. Release should always succeed and
|
||||||
|
// can be called multiple times without causing error.
|
||||||
|
func (si *flatStorageIterator) Release() {
|
||||||
|
si.it.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns the raw storage slot key the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
func (si *flatStorageIterator) Key() (common.Hash, error) {
|
||||||
|
if si.preimage == nil {
|
||||||
|
return common.Hash{}, errors.New("slot key is not available")
|
||||||
|
}
|
||||||
|
preimage := si.preimage.Preimage(si.Hash())
|
||||||
|
if preimage == nil {
|
||||||
|
return common.Hash{}, errors.New("slot key is not available")
|
||||||
|
}
|
||||||
|
return common.BytesToHash(preimage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot returns the storage slot data the iterator is currently at.
|
||||||
|
func (si *flatStorageIterator) Slot() []byte {
|
||||||
|
return si.it.Slot()
|
||||||
|
}
|
||||||
|
|
||||||
|
// merkleIterator implements the Iterator interface, providing functions to traverse
|
||||||
|
// the accounts or storages with the manner of Merkle-Patricia-Trie.
|
||||||
|
type merkleIterator struct {
|
||||||
|
tr Trie
|
||||||
|
it *trie.Iterator
|
||||||
|
account bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newMerkleTrieIterator constructs the iterator with the given trie and starting position.
|
||||||
|
func newMerkleTrieIterator(tr Trie, start common.Hash, account bool) (*merkleIterator, error) {
|
||||||
|
it, err := tr.NodeIterator(start.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &merkleIterator{
|
||||||
|
tr: tr,
|
||||||
|
it: trie.NewIterator(it),
|
||||||
|
account: account,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next steps the iterator forward one element, returning false if exhausted,
|
||||||
|
// or an error if iteration failed for some reason.
|
||||||
|
func (ti *merkleIterator) Next() bool {
|
||||||
|
return ti.it.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error returns any failure that occurred during iteration, which might have
|
||||||
|
// caused a premature iteration exit.
|
||||||
|
func (ti *merkleIterator) Error() error {
|
||||||
|
return ti.it.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash returns the hash of the account or storage slot the iterator is
|
||||||
|
// currently at.
|
||||||
|
func (ti *merkleIterator) Hash() common.Hash {
|
||||||
|
return common.BytesToHash(ti.it.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release releases associated resources. Release should always succeed and
|
||||||
|
// can be called multiple times without causing error.
|
||||||
|
func (ti *merkleIterator) Release() {}
|
||||||
|
|
||||||
|
// Address returns the raw account address the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
func (ti *merkleIterator) Address() (common.Address, error) {
|
||||||
|
if !ti.account {
|
||||||
|
return common.Address{}, errors.New("account address is not available")
|
||||||
|
}
|
||||||
|
preimage := ti.tr.GetKey(ti.it.Key)
|
||||||
|
if preimage == nil {
|
||||||
|
return common.Address{}, errors.New("account address is not available")
|
||||||
|
}
|
||||||
|
return common.BytesToAddress(preimage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Account returns the account data the iterator is currently at.
|
||||||
|
func (ti *merkleIterator) Account() []byte {
|
||||||
|
if !ti.account {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ti.it.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key returns the raw storage slot key the iterator is currently at.
|
||||||
|
// An error will be returned if the preimage is not available.
|
||||||
|
func (ti *merkleIterator) Key() (common.Hash, error) {
|
||||||
|
if ti.account {
|
||||||
|
return common.Hash{}, errors.New("slot key is not available")
|
||||||
|
}
|
||||||
|
preimage := ti.tr.GetKey(ti.it.Key)
|
||||||
|
if preimage == nil {
|
||||||
|
return common.Hash{}, errors.New("slot key is not available")
|
||||||
|
}
|
||||||
|
return common.BytesToHash(preimage), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Slot returns the storage slot the iterator is currently at.
|
||||||
|
func (ti *merkleIterator) Slot() []byte {
|
||||||
|
if ti.account {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ti.it.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// stateIteratee implements Iteratee interface, providing the state traversal
|
||||||
|
// functionalities of a specific state.
|
||||||
|
type stateIteratee struct {
|
||||||
|
merkle bool
|
||||||
|
root common.Hash
|
||||||
|
triedb *triedb.Database
|
||||||
|
snap *snapshot.Tree
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStateIteratee(merkle bool, root common.Hash, triedb *triedb.Database, snap *snapshot.Tree) (*stateIteratee, error) {
|
||||||
|
return &stateIteratee{
|
||||||
|
merkle: merkle,
|
||||||
|
root: root,
|
||||||
|
triedb: triedb,
|
||||||
|
snap: snap,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAccountIterator creates an account iterator for the state specified by
|
||||||
|
// the given root. It begins at a specified starting position, corresponding
|
||||||
|
// to a particular initial key (or the next key if the specified one does
|
||||||
|
// not exist).
|
||||||
|
//
|
||||||
|
// The starting position here refers to the hash of the account address.
|
||||||
|
func (si *stateIteratee) NewAccountIterator(start common.Hash) (AccountIterator, error) {
|
||||||
|
// If the external snapshot is available (hash scheme), try to initialize
|
||||||
|
// the account iterator from there first.
|
||||||
|
if si.snap != nil {
|
||||||
|
it, err := si.snap.AccountIterator(si.root, start)
|
||||||
|
if err == nil {
|
||||||
|
return newFlatAccountIterator(it, si.triedb), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the external snapshot is not available, try to initialize the
|
||||||
|
// account iterator from the trie database (path scheme)
|
||||||
|
it, err := si.triedb.AccountIterator(si.root, start)
|
||||||
|
if err == nil {
|
||||||
|
return newFlatAccountIterator(it, si.triedb), nil
|
||||||
|
}
|
||||||
|
if !si.merkle {
|
||||||
|
return nil, fmt.Errorf("state %x is not available for account traversal", si.root)
|
||||||
|
}
|
||||||
|
// The snapshot is not usable so far, construct the account iterator from
|
||||||
|
// the trie as the fallback. It's not as efficient as the flat state iterator.
|
||||||
|
tr, err := trie.NewStateTrie(trie.StateTrieID(si.root), si.triedb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newMerkleTrieIterator(tr, start, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStorageIterator creates a storage iterator for the state specified by
|
||||||
|
// the address hash. It begins at a specified starting position, corresponding
|
||||||
|
// to a particular initial key (or the next key if the specified one does not exist).
|
||||||
|
//
|
||||||
|
// The starting position here refers to the hash of the slot key.
|
||||||
|
func (si *stateIteratee) NewStorageIterator(addressHash common.Hash, start common.Hash) (StorageIterator, error) {
|
||||||
|
// If the external snapshot is available (hash scheme), try to initialize
|
||||||
|
// the storage iterator from there first.
|
||||||
|
if si.snap != nil {
|
||||||
|
it, err := si.snap.StorageIterator(si.root, addressHash, start)
|
||||||
|
if err == nil {
|
||||||
|
return newFlatStorageIterator(it, si.triedb), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If the external snapshot is not available, try to initialize the
|
||||||
|
// storage iterator from the trie database (path scheme)
|
||||||
|
it, err := si.triedb.StorageIterator(si.root, addressHash, start)
|
||||||
|
if err == nil {
|
||||||
|
return newFlatStorageIterator(it, si.triedb), nil
|
||||||
|
}
|
||||||
|
if !si.merkle {
|
||||||
|
return nil, fmt.Errorf("state %x is not available for account traversal", si.root)
|
||||||
|
}
|
||||||
|
// The snapshot is not usable so far, construct the storage iterator from
|
||||||
|
// the trie as the fallback. It's not as efficient as the flat state iterator.
|
||||||
|
tr, err := trie.NewStateTrie(trie.StateTrieID(si.root), si.triedb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
acct, err := tr.GetAccountByHash(addressHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if acct == nil || acct.Root == types.EmptyRootHash {
|
||||||
|
return &exhaustedIterator{}, nil
|
||||||
|
}
|
||||||
|
storageTr, err := trie.NewStateTrie(trie.StorageTrieID(si.root, addressHash, acct.Root), si.triedb)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return newMerkleTrieIterator(storageTr, start, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
type exhaustedIterator struct{}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Next() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Error() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Hash() common.Hash {
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Release() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Key() (common.Hash, error) {
|
||||||
|
return common.Hash{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e exhaustedIterator) Slot() []byte {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
262
core/state/database_iterator_test.go
Normal file
262
core/state/database_iterator_test.go
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
// Copyright 2026 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 state
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestExhaustedIterator verifies the exhaustedIterator sentinel: Next is false,
|
||||||
|
// Error is nil, Hash/Key are zero, Slot is nil, and double Release is safe.
|
||||||
|
func TestExhaustedIterator(t *testing.T) {
|
||||||
|
var it exhaustedIterator
|
||||||
|
|
||||||
|
if it.Next() {
|
||||||
|
t.Fatal("Next() returned true")
|
||||||
|
}
|
||||||
|
if err := it.Error(); err != nil {
|
||||||
|
t.Fatalf("Error() = %v, want nil", err)
|
||||||
|
}
|
||||||
|
if hash := it.Hash(); hash != (common.Hash{}) {
|
||||||
|
t.Fatalf("Hash() = %x, want zero", hash)
|
||||||
|
}
|
||||||
|
if key, err := it.Key(); key != (common.Hash{}) || err != nil {
|
||||||
|
t.Fatalf("Key() = %x, %v; want zero, nil", key, err)
|
||||||
|
}
|
||||||
|
if slot := it.Slot(); slot != nil {
|
||||||
|
t.Fatalf("Slot() = %x, want nil", slot)
|
||||||
|
}
|
||||||
|
it.Release()
|
||||||
|
it.Release()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAccountIterator tests the account iterator: correct count, ascending
|
||||||
|
// hash order, valid full-format RLP, data integrity, address preimage
|
||||||
|
// resolution, and seek behavior.
|
||||||
|
func TestAccountIterator(t *testing.T) {
|
||||||
|
testAccountIterator(t, rawdb.HashScheme)
|
||||||
|
testAccountIterator(t, rawdb.PathScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccountIterator(t *testing.T, scheme string) {
|
||||||
|
_, sdb, ndb, root, accounts := makeTestState(scheme)
|
||||||
|
ndb.Commit(root, false)
|
||||||
|
|
||||||
|
iteratee, err := sdb.Iteratee(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create iteratee: %v", scheme, err)
|
||||||
|
}
|
||||||
|
// Build lookups from address hash.
|
||||||
|
addrByHash := make(map[common.Hash]*testAccount)
|
||||||
|
for _, acc := range accounts {
|
||||||
|
addrByHash[crypto.Keccak256Hash(acc.address.Bytes())] = acc
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Full iteration: count, ordering, RLP validity, data integrity, address resolution ---
|
||||||
|
acctIt, err := iteratee.NewAccountIterator(common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create account iterator: %v", scheme, err)
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
hashes []common.Hash
|
||||||
|
prevHash common.Hash
|
||||||
|
)
|
||||||
|
for acctIt.Next() {
|
||||||
|
hash := acctIt.Hash()
|
||||||
|
if hash == (common.Hash{}) {
|
||||||
|
t.Fatalf("(%s) zero hash at position %d", scheme, len(hashes))
|
||||||
|
}
|
||||||
|
if len(hashes) > 0 && bytes.Compare(prevHash.Bytes(), hash.Bytes()) >= 0 {
|
||||||
|
t.Fatalf("(%s) hashes not ascending: %x >= %x", scheme, prevHash, hash)
|
||||||
|
}
|
||||||
|
prevHash = hash
|
||||||
|
hashes = append(hashes, hash)
|
||||||
|
|
||||||
|
// Decode and verify account data.
|
||||||
|
blob := acctIt.Account()
|
||||||
|
if blob == nil {
|
||||||
|
t.Fatalf("(%s) nil account at %x", scheme, hash)
|
||||||
|
}
|
||||||
|
var decoded types.StateAccount
|
||||||
|
if err := rlp.DecodeBytes(blob, &decoded); err != nil {
|
||||||
|
t.Fatalf("(%s) bad RLP at %x: %v", scheme, hash, err)
|
||||||
|
}
|
||||||
|
acc := addrByHash[hash]
|
||||||
|
if decoded.Nonce != acc.nonce {
|
||||||
|
t.Fatalf("(%s) nonce %x: got %d, want %d", scheme, hash, decoded.Nonce, acc.nonce)
|
||||||
|
}
|
||||||
|
if decoded.Balance.Cmp(acc.balance) != 0 {
|
||||||
|
t.Fatalf("(%s) balance %x: got %v, want %v", scheme, hash, decoded.Balance, acc.balance)
|
||||||
|
}
|
||||||
|
// Verify address preimage resolution.
|
||||||
|
addr, err := acctIt.Address()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to address: %v", scheme, err)
|
||||||
|
}
|
||||||
|
if addr != acc.address {
|
||||||
|
t.Fatalf("(%s) Address() = %x, want %x", scheme, addr, acc.address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
acctIt.Release()
|
||||||
|
|
||||||
|
if err := acctIt.Error(); err != nil {
|
||||||
|
t.Fatalf("(%s) iteration error: %v", scheme, err)
|
||||||
|
}
|
||||||
|
if len(hashes) != len(accounts) {
|
||||||
|
t.Fatalf("(%s) iterated %d accounts, want %d", scheme, len(hashes), len(accounts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Seek: starting from midpoint should skip earlier entries ---
|
||||||
|
mid := hashes[len(hashes)/2]
|
||||||
|
seekIt, err := iteratee.NewAccountIterator(mid)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create seeked iterator: %v", scheme, err)
|
||||||
|
}
|
||||||
|
seekCount := 0
|
||||||
|
for seekIt.Next() {
|
||||||
|
if bytes.Compare(seekIt.Hash().Bytes(), mid.Bytes()) < 0 {
|
||||||
|
t.Fatalf("(%s) seeked iterator returned hash before start", scheme)
|
||||||
|
}
|
||||||
|
seekCount++
|
||||||
|
}
|
||||||
|
seekIt.Release()
|
||||||
|
|
||||||
|
if seekCount != len(hashes)/2 {
|
||||||
|
t.Fatalf("(%s) unexpected seeked count, %d != %d", scheme, seekCount, len(hashes)/2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStorageIterator tests the storage iterator: correct slot counts against
|
||||||
|
// the trie, ascending hash order, non-nil slot data, key preimage resolution,
|
||||||
|
// seek behavior, and empty-storage accounts.
|
||||||
|
func TestStorageIterator(t *testing.T) {
|
||||||
|
testStorageIterator(t, rawdb.HashScheme)
|
||||||
|
testStorageIterator(t, rawdb.PathScheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testStorageIterator(t *testing.T, scheme string) {
|
||||||
|
_, sdb, ndb, root, accounts := makeTestState(scheme)
|
||||||
|
ndb.Commit(root, false)
|
||||||
|
|
||||||
|
iteratee, err := sdb.Iteratee(root)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create iteratee: %v", scheme, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Slot count and ordering for every account ---
|
||||||
|
var withStorage common.Hash // remember an account that has storage for seek test
|
||||||
|
for _, acc := range accounts {
|
||||||
|
addrHash := crypto.Keccak256Hash(acc.address.Bytes())
|
||||||
|
expected := countStorageSlots(t, scheme, sdb, root, addrHash)
|
||||||
|
|
||||||
|
storageIt, err := iteratee.NewStorageIterator(addrHash, common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create storage iterator for %x: %v", scheme, acc.address, err)
|
||||||
|
}
|
||||||
|
count := 0
|
||||||
|
var prevHash common.Hash
|
||||||
|
for storageIt.Next() {
|
||||||
|
hash := storageIt.Hash()
|
||||||
|
if count > 0 && bytes.Compare(prevHash.Bytes(), hash.Bytes()) >= 0 {
|
||||||
|
t.Fatalf("(%s) storage hashes not ascending for %x", scheme, acc.address)
|
||||||
|
}
|
||||||
|
prevHash = hash
|
||||||
|
if storageIt.Slot() == nil {
|
||||||
|
t.Fatalf("(%s) nil slot at %x", scheme, hash)
|
||||||
|
}
|
||||||
|
// Check key preimage resolution on first slot.
|
||||||
|
if _, err := storageIt.Key(); err != nil {
|
||||||
|
t.Fatalf("(%s) Key() failed to resolve", scheme)
|
||||||
|
}
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if err := storageIt.Error(); err != nil {
|
||||||
|
t.Fatalf("(%s) storage iteration error for %x: %v", scheme, acc.address, err)
|
||||||
|
}
|
||||||
|
storageIt.Release()
|
||||||
|
|
||||||
|
if count != expected {
|
||||||
|
t.Fatalf("(%s) account %x: %d slots, want %d", scheme, acc.address, count, expected)
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
withStorage = addrHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Seek: starting from second slot should skip the first ---
|
||||||
|
if withStorage == (common.Hash{}) {
|
||||||
|
t.Fatalf("(%s) no account with storage found", scheme)
|
||||||
|
}
|
||||||
|
fullIt, err := iteratee.NewStorageIterator(withStorage, common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create full storage iterator: %v", scheme, err)
|
||||||
|
}
|
||||||
|
var slotHashes []common.Hash
|
||||||
|
for fullIt.Next() {
|
||||||
|
slotHashes = append(slotHashes, fullIt.Hash())
|
||||||
|
}
|
||||||
|
fullIt.Release()
|
||||||
|
|
||||||
|
seekIt, err := iteratee.NewStorageIterator(withStorage, slotHashes[1])
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to create seeked storage iterator: %v", scheme, err)
|
||||||
|
}
|
||||||
|
seekCount := 0
|
||||||
|
for seekIt.Next() {
|
||||||
|
if bytes.Compare(seekIt.Hash().Bytes(), slotHashes[1].Bytes()) < 0 {
|
||||||
|
t.Fatalf("(%s) seeked storage iterator returned hash before start", scheme)
|
||||||
|
}
|
||||||
|
seekCount++
|
||||||
|
}
|
||||||
|
seekIt.Release()
|
||||||
|
|
||||||
|
if seekCount != len(slotHashes)-1 {
|
||||||
|
t.Fatalf("(%s) unexpected seeked storage count %d != %d", scheme, seekCount, len(slotHashes)-1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// countStorageSlots counts storage slots for an account by opening the
|
||||||
|
// storage trie directly.
|
||||||
|
func countStorageSlots(t *testing.T, scheme string, sdb Database, root common.Hash, addrHash common.Hash) int {
|
||||||
|
t.Helper()
|
||||||
|
accTrie, err := trie.NewStateTrie(trie.StateTrieID(root), sdb.TrieDB())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to open account trie: %v", scheme, err)
|
||||||
|
}
|
||||||
|
acct, err := accTrie.GetAccountByHash(addrHash)
|
||||||
|
if err != nil || acct == nil || acct.Root == types.EmptyRootHash {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(root, addrHash, acct.Root), sdb.TrieDB())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("(%s) failed to open storage trie for %x: %v", scheme, addrHash, err)
|
||||||
|
}
|
||||||
|
it := trie.NewIterator(storageTrie.MustNodeIterator(nil))
|
||||||
|
count := 0
|
||||||
|
for it.Next() {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
@ -27,7 +27,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -45,6 +44,7 @@ type DumpConfig struct {
|
||||||
type DumpCollector interface {
|
type DumpCollector interface {
|
||||||
// OnRoot is called with the state root
|
// OnRoot is called with the state root
|
||||||
OnRoot(common.Hash)
|
OnRoot(common.Hash)
|
||||||
|
|
||||||
// OnAccount is called once for each account in the trie
|
// OnAccount is called once for each account in the trie
|
||||||
OnAccount(*common.Address, DumpAccount)
|
OnAccount(*common.Address, DumpAccount)
|
||||||
}
|
}
|
||||||
|
|
@ -65,9 +65,10 @@ type DumpAccount struct {
|
||||||
type Dump struct {
|
type Dump struct {
|
||||||
Root string `json:"root"`
|
Root string `json:"root"`
|
||||||
Accounts map[string]DumpAccount `json:"accounts"`
|
Accounts map[string]DumpAccount `json:"accounts"`
|
||||||
|
|
||||||
// Next can be set to represent that this dump is only partial, and Next
|
// Next can be set to represent that this dump is only partial, and Next
|
||||||
// is where an iterator should be positioned in order to continue the dump.
|
// is where an iterator should be positioned in order to continue the dump.
|
||||||
Next []byte `json:"next,omitempty"` // nil if no more accounts
|
Next hexutil.Bytes `json:"next,omitempty"` // nil if no more accounts
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnRoot implements DumpCollector interface
|
// OnRoot implements DumpCollector interface
|
||||||
|
|
@ -114,9 +115,6 @@ func (d iterativeDump) OnRoot(root common.Hash) {
|
||||||
|
|
||||||
// DumpToCollector iterates the state according to the given options and inserts
|
// DumpToCollector iterates the state according to the given options and inserts
|
||||||
// the items into a collector for aggregation or serialization.
|
// the items into a collector for aggregation or serialization.
|
||||||
//
|
|
||||||
// The state iterator is still trie-based and can be converted to snapshot-based
|
|
||||||
// once the state snapshot is fully integrated into database. TODO(rjl493456442).
|
|
||||||
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
|
func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []byte) {
|
||||||
// Sanitize the input to allow nil configs
|
// Sanitize the input to allow nil configs
|
||||||
if conf == nil {
|
if conf == nil {
|
||||||
|
|
@ -131,20 +129,23 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
log.Info("Trie dumping started", "root", s.originalRoot)
|
log.Info("Trie dumping started", "root", s.originalRoot)
|
||||||
c.OnRoot(s.originalRoot)
|
c.OnRoot(s.originalRoot)
|
||||||
|
|
||||||
tr, err := s.db.OpenTrie(s.originalRoot)
|
iteratee, err := s.db.Iteratee(s.originalRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
trieIt, err := tr.NodeIterator(conf.Start)
|
var startHash common.Hash
|
||||||
|
if conf.Start != nil {
|
||||||
|
startHash = common.BytesToHash(conf.Start)
|
||||||
|
}
|
||||||
|
acctIt, err := iteratee.NewAccountIterator(startHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Trie dumping error", "err", err)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
it := trie.NewIterator(trieIt)
|
defer acctIt.Release()
|
||||||
|
|
||||||
for it.Next() {
|
for acctIt.Next() {
|
||||||
var data types.StateAccount
|
var data types.StateAccount
|
||||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
if err := rlp.DecodeBytes(acctIt.Account(), &data); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
|
|
@ -153,63 +154,55 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
Nonce: data.Nonce,
|
Nonce: data.Nonce,
|
||||||
Root: data.Root[:],
|
Root: data.Root[:],
|
||||||
CodeHash: data.CodeHash,
|
CodeHash: data.CodeHash,
|
||||||
AddressHash: it.Key,
|
AddressHash: acctIt.Hash().Bytes(),
|
||||||
}
|
}
|
||||||
address *common.Address
|
address *common.Address
|
||||||
addr common.Address
|
|
||||||
addrBytes = tr.GetKey(it.Key)
|
|
||||||
)
|
)
|
||||||
if addrBytes == nil {
|
addrBytes, err := acctIt.Address()
|
||||||
|
if err != nil {
|
||||||
missingPreimages++
|
missingPreimages++
|
||||||
if conf.OnlyWithAddresses {
|
if conf.OnlyWithAddresses {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
addr = common.BytesToAddress(addrBytes)
|
address = &addrBytes
|
||||||
address = &addr
|
|
||||||
account.Address = address
|
account.Address = address
|
||||||
}
|
}
|
||||||
obj := newObject(s, addr, &data)
|
obj := newObject(s, addrBytes, &data)
|
||||||
if !conf.SkipCode {
|
if !conf.SkipCode {
|
||||||
account.Code = obj.Code()
|
account.Code = obj.Code()
|
||||||
}
|
}
|
||||||
if !conf.SkipStorage {
|
if !conf.SkipStorage {
|
||||||
account.Storage = make(map[common.Hash]string)
|
account.Storage = make(map[common.Hash]string)
|
||||||
|
|
||||||
storageTr, err := s.db.OpenStorageTrie(s.originalRoot, addr, obj.Root(), tr)
|
storageIt, err := iteratee.NewStorageIterator(acctIt.Hash(), common.Hash{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to load storage trie", "err", err)
|
log.Error("Failed to load storage trie", "err", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
trieIt, err := storageTr.NodeIterator(nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("Failed to create trie iterator", "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
storageIt := trie.NewIterator(trieIt)
|
|
||||||
for storageIt.Next() {
|
for storageIt.Next() {
|
||||||
_, content, _, err := rlp.Split(storageIt.Value)
|
_, content, _, err := rlp.Split(storageIt.Slot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Failed to decode the value returned by iterator", "error", err)
|
log.Error("Failed to decode the value returned by iterator", "error", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
key := storageTr.GetKey(storageIt.Key)
|
key, err := storageIt.Key()
|
||||||
if key == nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
account.Storage[common.BytesToHash(key)] = common.Bytes2Hex(content)
|
account.Storage[key] = common.Bytes2Hex(content)
|
||||||
}
|
}
|
||||||
|
storageIt.Release()
|
||||||
}
|
}
|
||||||
c.OnAccount(address, account)
|
c.OnAccount(address, account)
|
||||||
accounts++
|
accounts++
|
||||||
if time.Since(logged) > 8*time.Second {
|
if time.Since(logged) > 8*time.Second {
|
||||||
log.Info("Trie dumping in progress", "at", common.Bytes2Hex(it.Key), "accounts", accounts,
|
log.Info("Trie dumping in progress", "at", acctIt.Hash().Hex(), "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
"elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
logged = time.Now()
|
logged = time.Now()
|
||||||
}
|
}
|
||||||
if conf.Max > 0 && accounts >= conf.Max {
|
if conf.Max > 0 && accounts >= conf.Max {
|
||||||
if it.Next() {
|
if acctIt.Next() {
|
||||||
nextKey = it.Key
|
nextKey = acctIt.Hash().Bytes()
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -217,9 +210,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
|
||||||
if missingPreimages > 0 {
|
if missingPreimages > 0 {
|
||||||
log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
|
log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
|
||||||
}
|
}
|
||||||
log.Info("Trie dumping complete", "accounts", accounts,
|
log.Info("Trie dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||||
"elapsed", common.PrettyDuration(time.Since(start)))
|
|
||||||
|
|
||||||
return nextKey
|
return nextKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
@ -1039,31 +1038,32 @@ func (s *StateDB) clearJournalAndRefund() {
|
||||||
s.refund = 0
|
s.refund = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// fastDeleteStorage is the function that efficiently deletes the storage trie
|
// deleteStorage is designed to delete the storage trie of a designated account.
|
||||||
// of a specific account. It leverages the associated state snapshot for fast
|
func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
||||||
// storage iteration and constructs trie node deletion markers by creating
|
|
||||||
// stack trie with iterated slots.
|
|
||||||
func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
|
||||||
iter, err := snaps.StorageIterator(s.originalRoot, addrHash, common.Hash{})
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
defer iter.Release()
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
||||||
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
|
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
|
||||||
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
|
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
|
||||||
)
|
)
|
||||||
|
iteratee, err := s.db.Iteratee(s.originalRoot)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
it, err := iteratee.NewStorageIterator(addrHash, common.Hash{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
defer it.Release()
|
||||||
|
|
||||||
stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
stack := trie.NewStackTrie(func(path []byte, hash common.Hash, blob []byte) {
|
||||||
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
|
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
|
||||||
})
|
})
|
||||||
for iter.Next() {
|
for it.Next() {
|
||||||
slot := common.CopyBytes(iter.Slot())
|
slot := common.CopyBytes(it.Slot())
|
||||||
if err := iter.Error(); err != nil { // error might occur after Slot function
|
if err := it.Error(); err != nil { // error might occur after Slot function
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
key := iter.Hash()
|
key := it.Hash()
|
||||||
storages[key] = nil
|
storages[key] = nil
|
||||||
storageOrigins[key] = slot
|
storageOrigins[key] = slot
|
||||||
|
|
||||||
|
|
@ -1071,7 +1071,7 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := iter.Error(); err != nil { // error might occur during iteration
|
if err := it.Error(); err != nil { // error might occur during iteration
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
if stack.Hash() != root {
|
if stack.Hash() != root {
|
||||||
|
|
@ -1080,68 +1080,6 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
|
||||||
return storages, storageOrigins, nodes, nil
|
return storages, storageOrigins, nodes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// slowDeleteStorage serves as a less-efficient alternative to "fastDeleteStorage,"
|
|
||||||
// employed when the associated state snapshot is not available. It iterates the
|
|
||||||
// storage slots along with all internal trie nodes via trie directly.
|
|
||||||
func (s *StateDB) slowDeleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
|
||||||
tr, err := s.db.OpenStorageTrie(s.originalRoot, addr, root, s.trie)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("failed to open storage trie, err: %w", err)
|
|
||||||
}
|
|
||||||
it, err := tr.NodeIterator(nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, fmt.Errorf("failed to open storage iterator, err: %w", err)
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
|
|
||||||
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil)
|
|
||||||
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
|
|
||||||
)
|
|
||||||
for it.Next(true) {
|
|
||||||
if it.Leaf() {
|
|
||||||
key := common.BytesToHash(it.LeafKey())
|
|
||||||
storages[key] = nil
|
|
||||||
storageOrigins[key] = common.CopyBytes(it.LeafBlob())
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if it.Hash() == (common.Hash{}) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
nodes.AddNode(it.Path(), trienode.NewDeletedWithPrev(it.NodeBlob()))
|
|
||||||
}
|
|
||||||
if err := it.Error(); err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
return storages, storageOrigins, nodes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// deleteStorage is designed to delete the storage trie of a designated account.
|
|
||||||
// The function will make an attempt to utilize an efficient strategy if the
|
|
||||||
// associated state snapshot is reachable; otherwise, it will resort to a less
|
|
||||||
// efficient approach.
|
|
||||||
func (s *StateDB) deleteStorage(addr common.Address, addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
nodes *trienode.NodeSet // the set for trie node mutations (value is nil)
|
|
||||||
storages map[common.Hash][]byte // the set for storage mutations (value is nil)
|
|
||||||
storageOrigins map[common.Hash][]byte // the set for tracking the original value of slot
|
|
||||||
)
|
|
||||||
// The fast approach can be failed if the snapshot is not fully
|
|
||||||
// generated, or it's internally corrupted. Fallback to the slow
|
|
||||||
// one just in case.
|
|
||||||
snaps := s.db.Snapshot()
|
|
||||||
if snaps != nil {
|
|
||||||
storages, storageOrigins, nodes, err = s.fastDeleteStorage(snaps, addrHash, root)
|
|
||||||
}
|
|
||||||
if snaps == nil || err != nil {
|
|
||||||
storages, storageOrigins, nodes, err = s.slowDeleteStorage(addr, addrHash, root)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, err
|
|
||||||
}
|
|
||||||
return storages, storageOrigins, nodes, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleDestruction processes all destruction markers and deletes the account
|
// handleDestruction processes all destruction markers and deletes the account
|
||||||
// and associated storage slots if necessary. There are four potential scenarios
|
// and associated storage slots if necessary. There are four potential scenarios
|
||||||
// as following:
|
// as following:
|
||||||
|
|
@ -1192,7 +1130,7 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
|
||||||
return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr)
|
return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr)
|
||||||
}
|
}
|
||||||
// Remove storage slots belonging to the account.
|
// Remove storage slots belonging to the account.
|
||||||
storages, storagesOrigin, set, err := s.deleteStorage(addr, addrHash, prev.Root)
|
storages, storagesOrigin, set, err := s.deleteStorage(addrHash, prev.Root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1296,12 +1296,12 @@ func TestDeleteStorage(t *testing.T) {
|
||||||
obj := fastState.getOrNewStateObject(addr)
|
obj := fastState.getOrNewStateObject(addr)
|
||||||
storageRoot := obj.data.Root
|
storageRoot := obj.data.Root
|
||||||
|
|
||||||
_, _, fastNodes, err := fastState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
|
_, _, fastNodes, err := fastState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, slowNodes, err := slowState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
|
_, _, slowNodes, err := slowState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -236,6 +236,8 @@ func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Add
|
||||||
if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) {
|
if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) {
|
||||||
return StorageRangeResult{}, nil // empty storage
|
return StorageRangeResult{}, nil // empty storage
|
||||||
}
|
}
|
||||||
|
// TODO(rjl493456442) it's problematic for traversing the state with in-memory
|
||||||
|
// state mutations, specifically txIndex != 0.
|
||||||
id := trie.StorageTrieID(root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
|
id := trie.StorageTrieID(root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
|
||||||
tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
|
tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue