mirror of https://github.com/ethereum/go-ethereum
trie: relocate state execution logic into pathdb package (#29861)
parent
269e80b07e
commit
045b9718d5
@ -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]) |
|
||||||
} |
|
Loading…
Reference in new issue