diff --git a/core/state/database.go b/core/state/database.go
index 002ce57fbc..c603e3ad7a 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -39,6 +39,10 @@ type Database interface {
// Reader returns a state reader associated with the specified state root.
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(root common.Hash) (Trie, error)
@@ -48,9 +52,6 @@ type Database interface {
// TrieDB returns the underlying trie database for managing trie nodes.
TrieDB() *triedb.Database
- // Snapshot returns the underlying state snapshot.
- Snapshot() *snapshot.Tree
-
// Commit flushes all pending writes and finalizes the state transition,
// committing the changes to the underlying storage. It returns an error
// 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())
}
+// 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.
func mustCopyTrie(t Trie) Trie {
switch t := t.(type) {
diff --git a/core/state/database_history.go b/core/state/database_history.go
index c25c4eae4b..0dbb8cc546 100644
--- a/core/state/database_history.go
+++ b/core/state/database_history.go
@@ -22,7 +22,6 @@ import (
"sync"
"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/crypto"
"github.com/ethereum/go-ethereum/rlp"
@@ -289,14 +288,15 @@ func (db *HistoricDB) TrieDB() *triedb.Database {
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,
// committing the changes to the underlying storage. It returns an error
// if the commit fails.
func (db *HistoricDB) Commit(update *stateUpdate) error {
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")
+}
diff --git a/core/state/database_iterator.go b/core/state/database_iterator.go
new file mode 100644
index 0000000000..8fad66a1e8
--- /dev/null
+++ b/core/state/database_iterator.go
@@ -0,0 +1,435 @@
+// 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 .
+
+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. It returns false if the iterator
+ // is exhausted or if an error occurs. Any error encountered is retained and
+ // can be retrieved via Error().
+ 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. It returns false if the iterator
+// is exhausted or if an error occurs. Any error encountered is retained and
+// can be retrieved via Error().
+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. It returns false if the iterator
+// is exhausted or if an error occurs. Any error encountered is retained and
+// can be retrieved via Error().
+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. It returns false if the iterator
+// is exhausted or if an error occurs. Any error encountered is retained and
+// can be retrieved via Error().
+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 storage 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
+}
diff --git a/core/state/database_iterator_test.go b/core/state/database_iterator_test.go
new file mode 100644
index 0000000000..87819e5526
--- /dev/null
+++ b/core/state/database_iterator_test.go
@@ -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 .
+
+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
+}
diff --git a/core/state/dump.go b/core/state/dump.go
index 829d106ed3..71138143d9 100644
--- a/core/state/dump.go
+++ b/core/state/dump.go
@@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
- "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
)
@@ -45,6 +44,7 @@ type DumpConfig struct {
type DumpCollector interface {
// OnRoot is called with the state root
OnRoot(common.Hash)
+
// OnAccount is called once for each account in the trie
OnAccount(*common.Address, DumpAccount)
}
@@ -65,9 +65,10 @@ type DumpAccount struct {
type Dump struct {
Root string `json:"root"`
Accounts map[string]DumpAccount `json:"accounts"`
+
// 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.
- 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
@@ -114,9 +115,6 @@ func (d iterativeDump) OnRoot(root common.Hash) {
// DumpToCollector iterates the state according to the given options and inserts
// 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) {
// Sanitize the input to allow nil configs
if conf == nil {
@@ -131,20 +129,23 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
log.Info("Trie dumping started", "root", s.originalRoot)
c.OnRoot(s.originalRoot)
- tr, err := s.db.OpenTrie(s.originalRoot)
+ iteratee, err := s.db.Iteratee(s.originalRoot)
if err != 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 {
- log.Error("Trie dumping error", "err", err)
return nil
}
- it := trie.NewIterator(trieIt)
+ defer acctIt.Release()
- for it.Next() {
+ for acctIt.Next() {
var data types.StateAccount
- if err := rlp.DecodeBytes(it.Value, &data); err != nil {
+ if err := rlp.DecodeBytes(acctIt.Account(), &data); err != nil {
panic(err)
}
var (
@@ -153,63 +154,55 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
Nonce: data.Nonce,
Root: data.Root[:],
CodeHash: data.CodeHash,
- AddressHash: it.Key,
+ AddressHash: acctIt.Hash().Bytes(),
}
- address *common.Address
- addr common.Address
- addrBytes = tr.GetKey(it.Key)
+ address *common.Address
)
- if addrBytes == nil {
+ addrBytes, err := acctIt.Address()
+ if err != nil {
missingPreimages++
if conf.OnlyWithAddresses {
continue
}
} else {
- addr = common.BytesToAddress(addrBytes)
- address = &addr
+ address = &addrBytes
account.Address = address
}
- obj := newObject(s, addr, &data)
+ obj := newObject(s, addrBytes, &data)
if !conf.SkipCode {
account.Code = obj.Code()
}
if !conf.SkipStorage {
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 {
log.Error("Failed to load storage trie", "err", err)
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() {
- _, content, _, err := rlp.Split(storageIt.Value)
+ _, content, _, err := rlp.Split(storageIt.Slot())
if err != nil {
log.Error("Failed to decode the value returned by iterator", "error", err)
continue
}
- key := storageTr.GetKey(storageIt.Key)
- if key == nil {
+ key, err := storageIt.Key()
+ if err != nil {
continue
}
- account.Storage[common.BytesToHash(key)] = common.Bytes2Hex(content)
+ account.Storage[key] = common.Bytes2Hex(content)
}
+ storageIt.Release()
}
c.OnAccount(address, account)
accounts++
if time.Since(logged) > 8*time.Second {
- log.Info("Trie dumping in progress", "at", common.Bytes2Hex(it.Key), "accounts", accounts,
- "elapsed", common.PrettyDuration(time.Since(start)))
+ log.Info("Trie dumping in progress", "at", acctIt.Hash().Hex(), "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
logged = time.Now()
}
if conf.Max > 0 && accounts >= conf.Max {
- if it.Next() {
- nextKey = it.Key
+ if acctIt.Next() {
+ nextKey = acctIt.Hash().Bytes()
}
break
}
@@ -217,9 +210,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
if missingPreimages > 0 {
log.Warn("Dump incomplete due to missing preimages", "missing", missingPreimages)
}
- log.Info("Trie dumping complete", "accounts", accounts,
- "elapsed", common.PrettyDuration(time.Since(start)))
-
+ log.Info("Trie dumping complete", "accounts", accounts, "elapsed", common.PrettyDuration(time.Since(start)))
return nextKey
}
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 769c8504c2..854aaf6109 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -28,7 +28,6 @@ import (
"time"
"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/tracing"
"github.com/ethereum/go-ethereum/core/types"
@@ -1039,31 +1038,32 @@ func (s *StateDB) clearJournalAndRefund() {
s.refund = 0
}
-// fastDeleteStorage is the function that efficiently deletes the storage trie
-// of a specific account. It leverages the associated state snapshot for fast
-// 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()
-
+// deleteStorage is designed to delete the storage trie of a designated account.
+func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) {
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
)
+ 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) {
nodes.AddNode(path, trienode.NewDeletedWithPrev(blob))
})
- for iter.Next() {
- slot := common.CopyBytes(iter.Slot())
- if err := iter.Error(); err != nil { // error might occur after Slot function
+ for it.Next() {
+ slot := common.CopyBytes(it.Slot())
+ if err := it.Error(); err != nil { // error might occur after Slot function
return nil, nil, nil, err
}
- key := iter.Hash()
+ key := it.Hash()
storages[key] = nil
storageOrigins[key] = slot
@@ -1071,7 +1071,7 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
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
}
if stack.Hash() != root {
@@ -1080,68 +1080,6 @@ func (s *StateDB) fastDeleteStorage(snaps *snapshot.Tree, addrHash common.Hash,
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
// and associated storage slots if necessary. There are four potential scenarios
// 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)
}
// 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 {
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
}
diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go
index 8d1f93ca1b..d29b262eea 100644
--- a/core/state/statedb_test.go
+++ b/core/state/statedb_test.go
@@ -1296,12 +1296,12 @@ func TestDeleteStorage(t *testing.T) {
obj := fastState.getOrNewStateObject(addr)
storageRoot := obj.data.Root
- _, _, fastNodes, err := fastState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
+ _, _, fastNodes, err := fastState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
if err != nil {
t.Fatal(err)
}
- _, _, slowNodes, err := slowState.deleteStorage(addr, crypto.Keccak256Hash(addr[:]), storageRoot)
+ _, _, slowNodes, err := slowState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
if err != nil {
t.Fatal(err)
}
diff --git a/eth/api_debug.go b/eth/api_debug.go
index 988eb44216..5dd535e672 100644
--- a/eth/api_debug.go
+++ b/eth/api_debug.go
@@ -236,6 +236,8 @@ func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Add
if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) {
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)
tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
if err != nil {