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