diff --git a/trie/secure_trie.go b/trie/secure_trie.go
index 3572117e03..6eb6defa45 100644
--- a/trie/secure_trie.go
+++ b/trie/secure_trie.go
@@ -24,6 +24,16 @@ import (
"github.com/ethereum/go-ethereum/triedb/database"
)
+// preimageStore wraps the methods of a backing store for reading and writing
+// trie node preimages.
+type preimageStore interface {
+ // Preimage retrieves the preimage of the specified hash.
+ Preimage(hash common.Hash) []byte
+
+ // InsertPreimage commits a set of preimages along with their hashes.
+ InsertPreimage(preimages map[common.Hash][]byte)
+}
+
// SecureTrie is the old name of StateTrie.
// Deprecated: use StateTrie.
type SecureTrie = StateTrie
@@ -52,6 +62,7 @@ func NewSecure(stateRoot common.Hash, owner common.Hash, root common.Hash, db da
type StateTrie struct {
trie Trie
db database.Database
+ preimages preimageStore
hashKeyBuf [common.HashLength]byte
secKeyCache map[string][]byte
secKeyCacheOwner *StateTrie // Pointer to self, replace the key cache on mismatch
@@ -70,7 +81,14 @@ func NewStateTrie(id *ID, db database.Database) (*StateTrie, error) {
if err != nil {
return nil, err
}
- return &StateTrie{trie: *trie, db: db}, nil
+ tr := &StateTrie{trie: *trie, db: db}
+
+ // link the preimage store if it's supported
+ preimages, ok := db.(preimageStore)
+ if ok {
+ tr.preimages = preimages
+ }
+ return tr, nil
}
// MustGet returns the value for key stored in the trie.
@@ -211,7 +229,10 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte {
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
return key
}
- return t.db.Preimage(common.BytesToHash(shaKey))
+ if t.preimages == nil {
+ return nil
+ }
+ return t.preimages.Preimage(common.BytesToHash(shaKey))
}
// Witness returns a set containing all trie nodes that have been accessed.
@@ -233,7 +254,9 @@ func (t *StateTrie) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
for hk, key := range t.secKeyCache {
preimages[common.BytesToHash([]byte(hk))] = key
}
- t.db.InsertPreimage(preimages)
+ if t.preimages != nil {
+ t.preimages.InsertPreimage(preimages)
+ }
t.secKeyCache = make(map[string][]byte)
}
// Commit the trie and return its modified nodeset.
diff --git a/trie/trie_reader.go b/trie/trie_reader.go
index 42bc4316fe..adbf43d287 100644
--- a/trie/trie_reader.go
+++ b/trie/trie_reader.go
@@ -20,7 +20,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/trie/triestate"
"github.com/ethereum/go-ethereum/triedb/database"
)
@@ -72,23 +71,3 @@ func (r *trieReader) node(path []byte, hash common.Hash) ([]byte, error) {
}
return blob, nil
}
-
-// MerkleLoader implements triestate.TrieLoader for constructing tries.
-type MerkleLoader struct {
- db database.Database
-}
-
-// NewMerkleLoader creates the merkle trie loader.
-func NewMerkleLoader(db database.Database) *MerkleLoader {
- return &MerkleLoader{db: db}
-}
-
-// OpenTrie opens the main account trie.
-func (l *MerkleLoader) OpenTrie(root common.Hash) (triestate.Trie, error) {
- return New(TrieID(root), l.db)
-}
-
-// OpenStorageTrie opens the storage trie of an account.
-func (l *MerkleLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) {
- return New(StorageTrieID(stateRoot, addrHash, root), l.db)
-}
diff --git a/trie/triestate/state.go b/trie/triestate/state.go
index 7508da5d60..62a9043873 100644
--- a/trie/triestate/state.go
+++ b/trie/triestate/state.go
@@ -16,43 +16,7 @@
package triestate
-import (
- "errors"
- "fmt"
- "sync"
-
- "github.com/ethereum/go-ethereum/common"
- "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/trienode"
-)
-
-// Trie is an Ethereum state trie, can be implemented by Ethereum Merkle Patricia
-// tree or Verkle tree.
-type Trie interface {
- // Get returns the value for key stored in the trie.
- Get(key []byte) ([]byte, error)
-
- // Update associates key with value in the trie.
- Update(key, value []byte) error
-
- // Delete removes any existing value for key from the trie.
- Delete(key []byte) error
-
- // Commit the trie and returns a set of dirty nodes generated along with
- // the new root hash.
- Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
-}
-
-// TrieLoader wraps functions to load tries.
-type TrieLoader interface {
- // OpenTrie opens the main account trie.
- OpenTrie(root common.Hash) (Trie, error)
-
- // OpenStorageTrie opens the storage trie of an account.
- OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (Trie, error)
-}
+import "github.com/ethereum/go-ethereum/common"
// Set represents a collection of mutated states during a state transition.
// The value refers to the original content of state before the transition
@@ -87,177 +51,3 @@ func (s *Set) Size() common.StorageSize {
}
return s.size
}
-
-// context wraps all fields for executing state diffs.
-type context struct {
- prevRoot common.Hash
- postRoot common.Hash
- accounts map[common.Address][]byte
- storages map[common.Address]map[common.Hash][]byte
- accountTrie Trie
- nodes *trienode.MergedNodeSet
-}
-
-// Apply traverses the provided state diffs, apply them in the associated
-// post-state and return the generated dirty trie nodes. The state can be
-// loaded via the provided trie loader.
-func Apply(prevRoot common.Hash, postRoot common.Hash, accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte, loader TrieLoader) (map[common.Hash]map[string]*trienode.Node, error) {
- tr, err := loader.OpenTrie(postRoot)
- if err != nil {
- return nil, err
- }
- ctx := &context{
- prevRoot: prevRoot,
- postRoot: postRoot,
- accounts: accounts,
- storages: storages,
- accountTrie: tr,
- nodes: trienode.NewMergedNodeSet(),
- }
- for addr, account := range accounts {
- var err error
- if len(account) == 0 {
- err = deleteAccount(ctx, loader, addr)
- } else {
- err = updateAccount(ctx, loader, addr)
- }
- if err != nil {
- return nil, fmt.Errorf("failed to revert state, err: %w", err)
- }
- }
- root, result := tr.Commit(false)
- if root != prevRoot {
- return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root)
- }
- if err := ctx.nodes.Merge(result); err != nil {
- return nil, err
- }
- return ctx.nodes.Flatten(), nil
-}
-
-// updateAccount the account was present in prev-state, and may or may not
-// existent in post-state. Apply the reverse diff and verify if the storage
-// root matches the one in prev-state account.
-func updateAccount(ctx *context, loader TrieLoader, addr common.Address) error {
- // The account was present in prev-state, decode it from the
- // 'slim-rlp' format bytes.
- h := newHasher()
- defer h.release()
-
- addrHash := h.hash(addr.Bytes())
- prev, err := types.FullAccount(ctx.accounts[addr])
- if err != nil {
- return err
- }
- // The account may or may not existent in post-state, try to
- // load it and decode if it's found.
- blob, err := ctx.accountTrie.Get(addrHash.Bytes())
- if err != nil {
- return err
- }
- post := types.NewEmptyStateAccount()
- if len(blob) != 0 {
- if err := rlp.DecodeBytes(blob, &post); err != nil {
- return err
- }
- }
- // Apply all storage changes into the post-state storage trie.
- st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root)
- if err != nil {
- return err
- }
- for key, val := range ctx.storages[addr] {
- var err error
- if len(val) == 0 {
- err = st.Delete(key.Bytes())
- } else {
- err = st.Update(key.Bytes(), val)
- }
- if err != nil {
- return err
- }
- }
- root, result := st.Commit(false)
- if root != prev.Root {
- return errors.New("failed to reset storage trie")
- }
- // The returned set can be nil if storage trie is not changed
- // at all.
- if result != nil {
- if err := ctx.nodes.Merge(result); err != nil {
- return err
- }
- }
- // Write the prev-state account into the main trie
- full, err := rlp.EncodeToBytes(prev)
- if err != nil {
- return err
- }
- return ctx.accountTrie.Update(addrHash.Bytes(), full)
-}
-
-// deleteAccount the account was not present in prev-state, and is expected
-// to be existent in post-state. Apply the reverse diff and verify if the
-// account and storage is wiped out correctly.
-func deleteAccount(ctx *context, loader TrieLoader, addr common.Address) error {
- // The account must be existent in post-state, load the account.
- h := newHasher()
- defer h.release()
-
- addrHash := h.hash(addr.Bytes())
- blob, err := ctx.accountTrie.Get(addrHash.Bytes())
- if err != nil {
- return err
- }
- if len(blob) == 0 {
- return fmt.Errorf("account is non-existent %#x", addrHash)
- }
- var post types.StateAccount
- if err := rlp.DecodeBytes(blob, &post); err != nil {
- return err
- }
- st, err := loader.OpenStorageTrie(ctx.postRoot, addrHash, post.Root)
- if err != nil {
- return err
- }
- for key, val := range ctx.storages[addr] {
- if len(val) != 0 {
- return errors.New("expect storage deletion")
- }
- if err := st.Delete(key.Bytes()); err != nil {
- return err
- }
- }
- root, result := st.Commit(false)
- if root != types.EmptyRootHash {
- return errors.New("failed to clear storage trie")
- }
- // The returned set can be nil if storage trie is not changed
- // at all.
- if result != nil {
- if err := ctx.nodes.Merge(result); err != nil {
- return err
- }
- }
- // Delete the post-state account from the main trie.
- return ctx.accountTrie.Delete(addrHash.Bytes())
-}
-
-// hasher is used to compute the sha256 hash of the provided data.
-type hasher struct{ sha crypto.KeccakState }
-
-var hasherPool = sync.Pool{
- New: func() interface{} { return &hasher{sha: crypto.NewKeccakState()} },
-}
-
-func newHasher() *hasher {
- return hasherPool.Get().(*hasher)
-}
-
-func (h *hasher) hash(data []byte) common.Hash {
- return crypto.HashData(h.sha, data)
-}
-
-func (h *hasher) release() {
- hasherPool.Put(h)
-}
diff --git a/triedb/database.go b/triedb/database.go
index ef757e7f5b..91386a9dbc 100644
--- a/triedb/database.go
+++ b/triedb/database.go
@@ -264,14 +264,7 @@ func (db *Database) Recover(target common.Hash) error {
if !ok {
return errors.New("not supported")
}
- var loader triestate.TrieLoader
- if db.config.IsVerkle {
- // TODO define verkle loader
- log.Crit("Verkle loader is not defined")
- } else {
- loader = trie.NewMerkleLoader(db)
- }
- return pdb.Recover(target, loader)
+ return pdb.Recover(target)
}
// Recoverable returns the indicator if the specified state is enabled to be
diff --git a/triedb/database/database.go b/triedb/database/database.go
index f11c7e9bbd..9bd5da08d1 100644
--- a/triedb/database/database.go
+++ b/triedb/database/database.go
@@ -16,9 +16,7 @@
package database
-import (
- "github.com/ethereum/go-ethereum/common"
-)
+import "github.com/ethereum/go-ethereum/common"
// Reader wraps the Node method of a backing trie reader.
type Reader interface {
@@ -31,20 +29,8 @@ type Reader interface {
Node(owner common.Hash, path []byte, hash common.Hash) ([]byte, error)
}
-// PreimageStore wraps the methods of a backing store for reading and writing
-// trie node preimages.
-type PreimageStore interface {
- // Preimage retrieves the preimage of the specified hash.
- Preimage(hash common.Hash) []byte
-
- // InsertPreimage commits a set of preimages along with their hashes.
- InsertPreimage(preimages map[common.Hash][]byte)
-}
-
// Database wraps the methods of a backing trie store.
type Database interface {
- PreimageStore
-
// Reader returns a node reader associated with the specific state.
// An error will be returned if the specified state is not available.
Reader(stateRoot common.Hash) (Reader, error)
diff --git a/triedb/pathdb/database.go b/triedb/pathdb/database.go
index bd6aeaa6ab..450c3a8f4f 100644
--- a/triedb/pathdb/database.go
+++ b/triedb/pathdb/database.go
@@ -345,7 +345,7 @@ func (db *Database) Enable(root common.Hash) error {
// Recover rollbacks the database to a specified historical point.
// The state is supported as the rollback destination only if it's
// canonical state and the corresponding trie histories are existent.
-func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error {
+func (db *Database) Recover(root common.Hash) error {
db.lock.Lock()
defer db.lock.Unlock()
@@ -371,7 +371,7 @@ func (db *Database) Recover(root common.Hash, loader triestate.TrieLoader) error
if err != nil {
return err
}
- dl, err = dl.revert(h, loader)
+ dl, err = dl.revert(h)
if err != nil {
return err
}
diff --git a/triedb/pathdb/database_test.go b/triedb/pathdb/database_test.go
index 04c8af415f..8870bfe95b 100644
--- a/triedb/pathdb/database_test.go
+++ b/triedb/pathdb/database_test.go
@@ -29,24 +29,31 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/internal/testrand"
"github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/trie/triestate"
"github.com/holiman/uint256"
)
-func updateTrie(addrHash common.Hash, root common.Hash, dirties, cleans map[common.Hash][]byte) (common.Hash, *trienode.NodeSet) {
- h, err := newTestHasher(addrHash, root, cleans)
+func updateTrie(db *Database, stateRoot common.Hash, addrHash common.Hash, root common.Hash, dirties map[common.Hash][]byte) (common.Hash, *trienode.NodeSet) {
+ var id *trie.ID
+ if addrHash == (common.Hash{}) {
+ id = trie.StateTrieID(stateRoot)
+ } else {
+ id = trie.StorageTrieID(stateRoot, addrHash, root)
+ }
+ tr, err := trie.New(id, db)
if err != nil {
- panic(fmt.Errorf("failed to create hasher, err: %w", err))
+ panic(fmt.Errorf("failed to load trie, err: %w", err))
}
for key, val := range dirties {
if len(val) == 0 {
- h.Delete(key.Bytes())
+ tr.Delete(key.Bytes())
} else {
- h.Update(key.Bytes(), val)
+ tr.Update(key.Bytes(), val)
}
}
- return h.Commit(false)
+ return tr.Commit(false)
}
func generateAccount(storageRoot common.Hash) types.StateAccount {
@@ -66,6 +73,7 @@ const (
)
type genctx struct {
+ stateRoot common.Hash
accounts map[common.Hash][]byte
storages map[common.Hash]map[common.Hash][]byte
accountOrigin map[common.Address][]byte
@@ -73,8 +81,9 @@ type genctx struct {
nodes *trienode.MergedNodeSet
}
-func newCtx() *genctx {
+func newCtx(stateRoot common.Hash) *genctx {
return &genctx{
+ stateRoot: stateRoot,
accounts: make(map[common.Hash][]byte),
storages: make(map[common.Hash]map[common.Hash][]byte),
accountOrigin: make(map[common.Address][]byte),
@@ -151,7 +160,7 @@ func (t *tester) generateStorage(ctx *genctx, addr common.Address) common.Hash {
storage[hash] = v
origin[hash] = nil
}
- root, set := updateTrie(addrHash, types.EmptyRootHash, storage, nil)
+ root, set := updateTrie(t.db, ctx.stateRoot, addrHash, types.EmptyRootHash, storage)
ctx.storages[addrHash] = storage
ctx.storageOrigin[addr] = origin
@@ -180,7 +189,7 @@ func (t *tester) mutateStorage(ctx *genctx, addr common.Address, root common.Has
storage[hash] = v
origin[hash] = nil
}
- root, set := updateTrie(crypto.Keccak256Hash(addr.Bytes()), root, storage, t.storages[addrHash])
+ root, set := updateTrie(t.db, ctx.stateRoot, crypto.Keccak256Hash(addr.Bytes()), root, storage)
ctx.storages[addrHash] = storage
ctx.storageOrigin[addr] = origin
@@ -198,7 +207,7 @@ func (t *tester) clearStorage(ctx *genctx, addr common.Address, root common.Hash
origin[hash] = val
storage[hash] = nil
}
- root, set := updateTrie(addrHash, root, storage, t.storages[addrHash])
+ root, set := updateTrie(t.db, ctx.stateRoot, addrHash, root, storage)
if root != types.EmptyRootHash {
panic("failed to clear storage trie")
}
@@ -210,7 +219,7 @@ func (t *tester) clearStorage(ctx *genctx, addr common.Address, root common.Hash
func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNodeSet, *triestate.Set) {
var (
- ctx = newCtx()
+ ctx = newCtx(parent)
dirties = make(map[common.Hash]struct{})
)
for i := 0; i < 20; i++ {
@@ -271,7 +280,7 @@ func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNode
ctx.accountOrigin[addr] = account
}
}
- root, set := updateTrie(common.Hash{}, parent, ctx.accounts, t.accounts)
+ root, set := updateTrie(t.db, parent, common.Hash{}, parent, ctx.accounts)
ctx.nodes.Merge(set)
// Save state snapshot before commit
@@ -297,6 +306,9 @@ func (t *tester) generate(parent common.Hash) (common.Hash, *trienode.MergedNode
t.storages[addrHash][sHash] = slot
}
}
+ if len(t.storages[addrHash]) == 0 {
+ delete(t.storages, addrHash)
+ }
}
return root, ctx.nodes, triestate.New(ctx.accountOrigin, ctx.storageOrigin)
}
@@ -310,25 +322,31 @@ func (t *tester) lastHash() common.Hash {
}
func (t *tester) verifyState(root common.Hash) error {
- reader, err := t.db.Reader(root)
+ tr, err := trie.New(trie.StateTrieID(root), t.db)
if err != nil {
return err
}
- _, err = reader.Node(common.Hash{}, nil, root)
- if err != nil {
- return errors.New("root node is not available")
- }
for addrHash, account := range t.snapAccounts[root] {
- path := crypto.Keccak256(addrHash.Bytes())
- blob, err := reader.Node(common.Hash{}, path, crypto.Keccak256Hash(account))
+ blob, err := tr.Get(addrHash.Bytes())
if err != nil || !bytes.Equal(blob, account) {
return fmt.Errorf("account is mismatched: %w", err)
}
}
for addrHash, slots := range t.snapStorages[root] {
+ blob := t.snapAccounts[root][addrHash]
+ if len(blob) == 0 {
+ return fmt.Errorf("account %x is missing", addrHash)
+ }
+ account := new(types.StateAccount)
+ if err := rlp.DecodeBytes(blob, account); err != nil {
+ return err
+ }
+ storageIt, err := trie.New(trie.StorageTrieID(root, addrHash, account.Root), t.db)
+ if err != nil {
+ return err
+ }
for hash, slot := range slots {
- path := crypto.Keccak256(hash.Bytes())
- blob, err := reader.Node(addrHash, path, crypto.Keccak256Hash(slot))
+ blob, err := storageIt.Get(hash.Bytes())
if err != nil || !bytes.Equal(blob, slot) {
return fmt.Errorf("slot is mismatched: %w", err)
}
@@ -395,13 +413,11 @@ func TestDatabaseRollback(t *testing.T) {
}
// Revert database from top to bottom
for i := tester.bottomIndex(); i >= 0; i-- {
- root := tester.roots[i]
parent := types.EmptyRootHash
if i > 0 {
parent = tester.roots[i-1]
}
- loader := newHashLoader(tester.snapAccounts[root], tester.snapStorages[root])
- if err := tester.db.Recover(parent, loader); err != nil {
+ if err := tester.db.Recover(parent); err != nil {
t.Fatalf("Failed to revert db, err: %v", err)
}
if i > 0 {
diff --git a/triedb/pathdb/disklayer.go b/triedb/pathdb/disklayer.go
index 964ad2ef77..e538a79280 100644
--- a/triedb/pathdb/disklayer.go
+++ b/triedb/pathdb/disklayer.go
@@ -219,7 +219,7 @@ func (dl *diskLayer) commit(bottom *diffLayer, force bool) (*diskLayer, error) {
}
// revert applies the given state history and return a reverted disk layer.
-func (dl *diskLayer) revert(h *history, loader triestate.TrieLoader) (*diskLayer, error) {
+func (dl *diskLayer) revert(h *history) (*diskLayer, error) {
if h.meta.root != dl.rootHash() {
return nil, errUnexpectedHistory
}
@@ -229,7 +229,7 @@ func (dl *diskLayer) revert(h *history, loader triestate.TrieLoader) (*diskLayer
// Apply the reverse state changes upon the current state. This must
// be done before holding the lock in order to access state in "this"
// layer.
- nodes, err := triestate.Apply(h.meta.parent, h.meta.root, h.accounts, h.storages, loader)
+ nodes, err := apply(dl.db, h.meta.parent, h.meta.root, h.accounts, h.storages)
if err != nil {
return nil, err
}
diff --git a/triedb/pathdb/execute.go b/triedb/pathdb/execute.go
new file mode 100644
index 0000000000..9074e4debf
--- /dev/null
+++ b/triedb/pathdb/execute.go
@@ -0,0 +1,186 @@
+// Copyright 2024 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see
+
+package pathdb
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/trie"
+ "github.com/ethereum/go-ethereum/trie/trienode"
+ "github.com/ethereum/go-ethereum/triedb/database"
+)
+
+// context wraps all fields for executing state diffs.
+type context struct {
+ prevRoot common.Hash
+ postRoot common.Hash
+ accounts map[common.Address][]byte
+ storages map[common.Address]map[common.Hash][]byte
+ nodes *trienode.MergedNodeSet
+
+ // TODO (rjl493456442) abstract out the state hasher
+ // for supporting verkle tree.
+ accountTrie *trie.Trie
+}
+
+// apply processes the given state diffs, updates the corresponding post-state
+// and returns the trie nodes that have been modified.
+func apply(db database.Database, prevRoot common.Hash, postRoot common.Hash, accounts map[common.Address][]byte, storages map[common.Address]map[common.Hash][]byte) (map[common.Hash]map[string]*trienode.Node, error) {
+ tr, err := trie.New(trie.TrieID(postRoot), db)
+ if err != nil {
+ return nil, err
+ }
+ ctx := &context{
+ prevRoot: prevRoot,
+ postRoot: postRoot,
+ accounts: accounts,
+ storages: storages,
+ accountTrie: tr,
+ nodes: trienode.NewMergedNodeSet(),
+ }
+ for addr, account := range accounts {
+ var err error
+ if len(account) == 0 {
+ err = deleteAccount(ctx, db, addr)
+ } else {
+ err = updateAccount(ctx, db, addr)
+ }
+ if err != nil {
+ return nil, fmt.Errorf("failed to revert state, err: %w", err)
+ }
+ }
+ root, result := tr.Commit(false)
+ if root != prevRoot {
+ return nil, fmt.Errorf("failed to revert state, want %#x, got %#x", prevRoot, root)
+ }
+ if err := ctx.nodes.Merge(result); err != nil {
+ return nil, err
+ }
+ return ctx.nodes.Flatten(), nil
+}
+
+// updateAccount the account was present in prev-state, and may or may not
+// existent in post-state. Apply the reverse diff and verify if the storage
+// root matches the one in prev-state account.
+func updateAccount(ctx *context, db database.Database, addr common.Address) error {
+ // The account was present in prev-state, decode it from the
+ // 'slim-rlp' format bytes.
+ h := newHasher()
+ defer h.release()
+
+ addrHash := h.hash(addr.Bytes())
+ prev, err := types.FullAccount(ctx.accounts[addr])
+ if err != nil {
+ return err
+ }
+ // The account may or may not existent in post-state, try to
+ // load it and decode if it's found.
+ blob, err := ctx.accountTrie.Get(addrHash.Bytes())
+ if err != nil {
+ return err
+ }
+ post := types.NewEmptyStateAccount()
+ if len(blob) != 0 {
+ if err := rlp.DecodeBytes(blob, &post); err != nil {
+ return err
+ }
+ }
+ // Apply all storage changes into the post-state storage trie.
+ st, err := trie.New(trie.StorageTrieID(ctx.postRoot, addrHash, post.Root), db)
+ if err != nil {
+ return err
+ }
+ for key, val := range ctx.storages[addr] {
+ var err error
+ if len(val) == 0 {
+ err = st.Delete(key.Bytes())
+ } else {
+ err = st.Update(key.Bytes(), val)
+ }
+ if err != nil {
+ return err
+ }
+ }
+ root, result := st.Commit(false)
+ if root != prev.Root {
+ return errors.New("failed to reset storage trie")
+ }
+ // The returned set can be nil if storage trie is not changed
+ // at all.
+ if result != nil {
+ if err := ctx.nodes.Merge(result); err != nil {
+ return err
+ }
+ }
+ // Write the prev-state account into the main trie
+ full, err := rlp.EncodeToBytes(prev)
+ if err != nil {
+ return err
+ }
+ return ctx.accountTrie.Update(addrHash.Bytes(), full)
+}
+
+// deleteAccount the account was not present in prev-state, and is expected
+// to be existent in post-state. Apply the reverse diff and verify if the
+// account and storage is wiped out correctly.
+func deleteAccount(ctx *context, db database.Database, addr common.Address) error {
+ // The account must be existent in post-state, load the account.
+ h := newHasher()
+ defer h.release()
+
+ addrHash := h.hash(addr.Bytes())
+ blob, err := ctx.accountTrie.Get(addrHash.Bytes())
+ if err != nil {
+ return err
+ }
+ if len(blob) == 0 {
+ return fmt.Errorf("account is non-existent %#x", addrHash)
+ }
+ var post types.StateAccount
+ if err := rlp.DecodeBytes(blob, &post); err != nil {
+ return err
+ }
+ st, err := trie.New(trie.StorageTrieID(ctx.postRoot, addrHash, post.Root), db)
+ if err != nil {
+ return err
+ }
+ for key, val := range ctx.storages[addr] {
+ if len(val) != 0 {
+ return errors.New("expect storage deletion")
+ }
+ if err := st.Delete(key.Bytes()); err != nil {
+ return err
+ }
+ }
+ root, result := st.Commit(false)
+ if root != types.EmptyRootHash {
+ return errors.New("failed to clear storage trie")
+ }
+ // The returned set can be nil if storage trie is not changed
+ // at all.
+ if result != nil {
+ if err := ctx.nodes.Merge(result); err != nil {
+ return err
+ }
+ }
+ // Delete the post-state account from the main trie.
+ return ctx.accountTrie.Delete(addrHash.Bytes())
+}
diff --git a/triedb/pathdb/testutils.go b/triedb/pathdb/testutils.go
deleted file mode 100644
index af832bc59c..0000000000
--- a/triedb/pathdb/testutils.go
+++ /dev/null
@@ -1,159 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package pathdb
-
-import (
- "bytes"
- "fmt"
- "slices"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/trie/trienode"
- "github.com/ethereum/go-ethereum/trie/triestate"
-)
-
-// testHasher is a test utility for computing root hash of a batch of state
-// elements. The hash algorithm is to sort all the elements in lexicographical
-// order, concat the key and value in turn, and perform hash calculation on
-// the concatenated bytes. Except the root hash, a nodeset will be returned
-// once Commit is called, which contains all the changes made to hasher.
-type testHasher struct {
- owner common.Hash // owner identifier
- root common.Hash // original root
- dirties map[common.Hash][]byte // dirty states
- cleans map[common.Hash][]byte // clean states
-}
-
-// newTestHasher constructs a hasher object with provided states.
-func newTestHasher(owner common.Hash, root common.Hash, cleans map[common.Hash][]byte) (*testHasher, error) {
- if cleans == nil {
- cleans = make(map[common.Hash][]byte)
- }
- if got, _ := hash(cleans); got != root {
- return nil, fmt.Errorf("state root mismatched, want: %x, got: %x", root, got)
- }
- return &testHasher{
- owner: owner,
- root: root,
- dirties: make(map[common.Hash][]byte),
- cleans: cleans,
- }, nil
-}
-
-// Get returns the value for key stored in the trie.
-func (h *testHasher) Get(key []byte) ([]byte, error) {
- hash := common.BytesToHash(key)
- val, ok := h.dirties[hash]
- if ok {
- return val, nil
- }
- return h.cleans[hash], nil
-}
-
-// Update associates key with value in the trie.
-func (h *testHasher) Update(key, value []byte) error {
- h.dirties[common.BytesToHash(key)] = common.CopyBytes(value)
- return nil
-}
-
-// Delete removes any existing value for key from the trie.
-func (h *testHasher) Delete(key []byte) error {
- h.dirties[common.BytesToHash(key)] = nil
- return nil
-}
-
-// Commit computes the new hash of the states and returns the set with all
-// state changes.
-func (h *testHasher) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) {
- var (
- nodes = make(map[common.Hash][]byte)
- set = trienode.NewNodeSet(h.owner)
- )
- for hash, val := range h.cleans {
- nodes[hash] = val
- }
- for hash, val := range h.dirties {
- nodes[hash] = val
- if bytes.Equal(val, h.cleans[hash]) {
- continue
- }
- // Utilize the hash of the state key as the node path to mitigate
- // potential collisions within the path.
- path := crypto.Keccak256(hash.Bytes())
- if len(val) == 0 {
- set.AddNode(path, trienode.NewDeleted())
- } else {
- set.AddNode(path, trienode.New(crypto.Keccak256Hash(val), val))
- }
- }
- root, blob := hash(nodes)
-
- // Include the dirty root node as well.
- if root != types.EmptyRootHash && root != h.root {
- set.AddNode(nil, trienode.New(root, blob))
- }
- if root == types.EmptyRootHash && h.root != types.EmptyRootHash {
- set.AddNode(nil, trienode.NewDeleted())
- }
- return root, set
-}
-
-// hash performs the hash computation upon the provided states.
-func hash(states map[common.Hash][]byte) (common.Hash, []byte) {
- var hs []common.Hash
- for hash := range states {
- hs = append(hs, hash)
- }
- slices.SortFunc(hs, common.Hash.Cmp)
-
- var input []byte
- for _, hash := range hs {
- if len(states[hash]) == 0 {
- continue
- }
- input = append(input, hash.Bytes()...)
- input = append(input, states[hash]...)
- }
- if len(input) == 0 {
- return types.EmptyRootHash, nil
- }
- return crypto.Keccak256Hash(input), input
-}
-
-type hashLoader struct {
- accounts map[common.Hash][]byte
- storages map[common.Hash]map[common.Hash][]byte
-}
-
-func newHashLoader(accounts map[common.Hash][]byte, storages map[common.Hash]map[common.Hash][]byte) *hashLoader {
- return &hashLoader{
- accounts: accounts,
- storages: storages,
- }
-}
-
-// OpenTrie opens the main account trie.
-func (l *hashLoader) OpenTrie(root common.Hash) (triestate.Trie, error) {
- return newTestHasher(common.Hash{}, root, l.accounts)
-}
-
-// OpenStorageTrie opens the storage trie of an account.
-func (l *hashLoader) OpenStorageTrie(stateRoot common.Hash, addrHash, root common.Hash) (triestate.Trie, error) {
- return newTestHasher(addrHash, root, l.storages[addrHash])
-}