trie: relocate state execution logic into pathdb package (#29861)

This commit is contained in:
rjl493456442 2024-06-27 20:30:39 +08:00 committed by GitHub
parent 269e80b07e
commit 045b9718d5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 259 additions and 445 deletions

@ -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.

@ -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)
}

@ -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)
}

@ -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

@ -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)

@ -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
}

@ -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 {

@ -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
}

186
triedb/pathdb/execute.go Normal file

@ -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 <http://www.gnu.org/licenses/>
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())
}

@ -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 <http://www.gnu.org/licenses/>.
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])
}