core/state: implement merkle hasher

This commit is contained in:
Gary Rong 2026-03-23 10:00:58 +08:00 committed by CPerezz
parent 38c7021c73
commit 282cece030
No known key found for this signature in database
GPG key ID: 62045F34B97177DD
12 changed files with 740 additions and 531 deletions

View file

@ -2265,7 +2265,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
stats.CodeReads = statedb.CodeReads stats.CodeReads = statedb.CodeReads
stats.AccountLoaded = statedb.AccountLoaded stats.AccountLoaded = statedb.AccountLoaded
stats.AccountUpdated = statedb.AccountUpdated //stats.AccountUpdated = statedb.AccountUpdated
stats.AccountDeleted = statedb.AccountDeleted stats.AccountDeleted = statedb.AccountDeleted
stats.StorageLoaded = statedb.StorageLoaded stats.StorageLoaded = statedb.StorageLoaded
stats.StorageUpdated = int(statedb.StorageUpdated.Load()) stats.StorageUpdated = int(statedb.StorageUpdated.Load())
@ -2273,8 +2273,8 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
stats.CodeLoaded = statedb.CodeLoaded stats.CodeLoaded = statedb.CodeLoaded
stats.CodeLoadBytes = statedb.CodeLoadBytes stats.CodeLoadBytes = statedb.CodeLoadBytes
stats.CodeUpdated = statedb.CodeUpdated //stats.CodeUpdated = statedb.CodeUpdated
stats.CodeUpdateBytes = statedb.CodeUpdateBytes //stats.CodeUpdateBytes = statedb.CodeUpdateBytes
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
@ -220,8 +221,10 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
return newReader(db.codedb.Reader(), sr), nil return newReader(db.codedb.Reader(), sr), nil
} }
// Hasher implements Database, returning a hasher associated with the specified
// state root.
func (db *CachingDB) Hasher(stateRoot common.Hash) (Hasher, error) { func (db *CachingDB) Hasher(stateRoot common.Hash) (Hasher, error) {
return &noopHasher{}, nil return newMerkleHasher(stateRoot, db.triedb)
} }
// ReadersWithCacheStats creates a pair of state readers that share the same // ReadersWithCacheStats creates a pair of state readers that share the same
@ -300,18 +303,26 @@ func (db *CachingDB) Commit(update *stateUpdate) error {
} }
// If snapshotting is enabled, update the snapshot tree with this new version // If snapshotting is enabled, update the snapshot tree with this new version
if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil { if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil {
//if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil { accounts, _, storages, _, err := update.encodeMerkle()
// log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err) if err != nil {
//} return err
//// Keep 128 diff layers in the memory, persistent layer is 129th. }
//// - head layer is paired with HEAD state if err := db.snap.Update(update.root, update.originRoot, accounts, storages); err != nil {
//// - head-1 layer is paired with HEAD-1 state log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
//// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state }
//if err := db.snap.Cap(update.root, TriesInMemory); err != nil { // Keep 128 diff layers in the memory, persistent layer is 129th.
// log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err) // - head layer is paired with HEAD state
//} // - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
if err := db.snap.Cap(update.root, TriesInMemory); err != nil {
log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err)
}
} }
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet()) stateSet, err := update.stateSet()
if err != nil {
return err
}
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, stateSet)
} }
// Iteratee returns a state iteratee associated with the specified state root, // Iteratee returns a state iteratee associated with the specified state root,

View file

@ -17,25 +17,38 @@
package state package state
import ( import (
"maps"
"sync"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb"
) )
// AccountMutation describes a state transition for a single account. // CodeMut represents a mutation to contract code.
type AccountMutation struct { type CodeMut struct {
Account *Account // Null for deletion Code []byte // Null for deletion
DirtyCode bool // Flag whether the code is changed
Code []byte // Null for deletion
} }
// SecondaryHash encapsulates the secondary hash of storage tries. // AccountMut represents a mutation to an account.
// It is only relevant in the context of the Merkle Patricia Trie and // Semantics:
// includes both the post-transition root and the original root. // - Account == nil: delete the account
type SecondaryHash struct { // - Code == nil: leave code unchanged
Hash common.Hash // - Code != nil: apply the given code mutation
Prev common.Hash type AccountMut struct {
Account *Account // Null for deletion
Code *CodeMut // Null for unchanged
}
// Hashes encapsulates a trie root together with its original (pre-update) root.
type Hashes struct {
Hash common.Hash // Post-mutation root
Prev common.Hash // Pre-mutation root
} }
// Hasher defines the minimal interface for computing state root hashes. // Hasher defines the minimal interface for computing state root hashes.
@ -54,11 +67,9 @@ type SecondaryHash struct {
// compatibility with pre-Byzantium semantics. // compatibility with pre-Byzantium semantics.
type Hasher interface { type Hasher interface {
// UpdateAccount writes a list of accounts into the state. // UpdateAccount writes a list of accounts into the state.
UpdateAccount(addresses []common.Address, accounts []AccountMutation) error UpdateAccount(addresses []common.Address, accounts []AccountMut) error
// UpdateStorage writes a list of storage slot value. The hasher handles // UpdateStorage writes a list of storage slot value.
// encoding internally. Note, the value with empty data should be
// interpreted as deletion.
UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error
// Hash computes and returns the state root hash without committing. // Hash computes and returns the state root hash without committing.
@ -70,14 +81,14 @@ type Hasher interface {
// Additionally, if the hasher uses a two-layer structure, the roots of the // Additionally, if the hasher uses a two-layer structure, the roots of the
// secondary tries together with their original hashes will also be returned // secondary tries together with their original hashes will also be returned
// for all mutated accounts, regardless of whether their storage was modified. // for all mutated accounts, regardless of whether their storage was modified.
Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]SecondaryHash, error) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error)
// Copy returns a deep-copied hasher instance. // Copy returns a deep-copied hasher instance.
Copy() Hasher Copy() Hasher
} }
// Prefetcher is an optional extension implemented by hashers that can // Prefetcher is an optional extension implemented by hashers that can
// asynchronously warm up trie/state data ahead of mutations or hashing. // asynchronously warm up trie/state data ahead of hashing.
type Prefetcher interface { type Prefetcher interface {
// PrefetchAccount schedules the account for prefetching. // PrefetchAccount schedules the account for prefetching.
PrefetchAccount(addresses []common.Address, read bool) PrefetchAccount(addresses []common.Address, read bool)
@ -87,9 +98,9 @@ type Prefetcher interface {
} }
// WitnessCollector is an optional extension implemented by hashers that can // WitnessCollector is an optional extension implemented by hashers that can
// construct a stateless witness for the most recent committed state transition. // construct a state witness for the most recent committed state transition.
type WitnessCollector interface { type WitnessCollector interface {
// Witness returns the stateless witness corresponding to the most recent // Witness returns the state witness corresponding to the most recent
// committed state transition. // committed state transition.
Witness() (*stateless.Witness, error) Witness() (*stateless.Witness, error)
} }
@ -120,29 +131,207 @@ type Prover interface {
ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error
} }
// noopHasher is a Hasher implementation that performs no work and always
// returns an empty state root.
type noopHasher struct{} type noopHasher struct{}
func (n noopHasher) UpdateAccount(addresses []common.Address, accounts []AccountMutation) error { func (n *noopHasher) UpdateAccount([]common.Address, []AccountMut) error { return nil }
//TODO implement me func (n *noopHasher) UpdateStorage(common.Address, []common.Hash, []common.Hash) error {
panic("implement me") return nil
}
func (n *noopHasher) Hash() common.Hash { return common.Hash{} }
func (n *noopHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) {
return common.Hash{}, trienode.NewMergedNodeSet(), make(map[common.Address]Hashes), nil
}
func (n *noopHasher) Copy() Hasher { return &noopHasher{} }
// merkleHasher is a Hasher implementation backed by the traditional two-layer
// Merkle Patricia Trie (separate account trie and per-account storage tries).
type merkleHasher struct {
db *triedb.Database
root common.Hash
accountTrie *trie.StateTrie
storageTries map[common.Address]*trie.StateTrie // lazily opened
// storageRoots tracks the storage root transition for every mutated
// account. Prev is recorded once (first touch) and Hash is updated
// on each UpdateAccount call.
storageRoots map[common.Address]Hashes
lock sync.Mutex // guards storageTries (concurrent updateTrie)
} }
func (n noopHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error { func newMerkleHasher(root common.Hash, db *triedb.Database) (*merkleHasher, error) {
//TODO implement me tr, err := trie.NewStateTrie(trie.StateTrieID(root), db)
panic("implement me") if err != nil {
return nil, err
}
return &merkleHasher{
db: db,
root: root,
accountTrie: tr,
storageTries: make(map[common.Address]*trie.StateTrie),
storageRoots: make(map[common.Address]Hashes),
}, nil
} }
func (n noopHasher) Hash() common.Hash { // accountStorageRoot reads the storage root of account from the account trie.
//TODO implement me func (h *merkleHasher) accountStorageRoot(addr common.Address) common.Hash {
panic("implement me") if acc, _ := h.accountTrie.GetAccount(addr); acc != nil {
return acc.Root
}
return types.EmptyRootHash
} }
func (n noopHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]SecondaryHash, error) { // recordOrigin records the original (pre-mutation) storage root for addr.
//TODO implement me // Only the first call per address has any effect.
panic("implement me") func (h *merkleHasher) recordOrigin(addr common.Address) {
if _, ok := h.storageRoots[addr]; !ok {
root := h.accountStorageRoot(addr)
h.storageRoots[addr] = Hashes{
Prev: root,
Hash: root,
}
}
} }
func (n noopHasher) Copy() Hasher { // openStorageTrie returns the cached storage trie for the given address,
//TODO implement me // or opens one from the database if not already cached.
panic("implement me") func (h *merkleHasher) openStorageTrie(address common.Address) (*trie.StateTrie, error) {
if st, ok := h.storageTries[address]; ok {
return st, nil
}
// Record the original storage trie root if it has not already been tracked
// when the storage trie is loaded.
h.recordOrigin(address)
id := trie.StorageTrieID(h.root, crypto.Keccak256Hash(address.Bytes()), h.accountStorageRoot(address))
st, err := trie.NewStateTrie(id, h.db)
if err != nil {
return nil, err
}
h.storageTries[address] = st
return st, nil
}
func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error {
h.lock.Lock()
st, err := h.openStorageTrie(address)
if err != nil {
h.lock.Unlock()
return err
}
h.lock.Unlock()
for i, key := range keys {
if values[i] == (common.Hash{}) {
if err := st.DeleteStorage(address, key[:]); err != nil {
return err
}
} else {
if err := st.UpdateStorage(address, key[:], common.TrimLeftZeroes(values[i][:])); err != nil {
return err
}
}
}
return nil
}
func (h *merkleHasher) UpdateAccount(addresses []common.Address, accounts []AccountMut) error {
for i, addr := range addresses {
h.recordOrigin(addr)
acct := accounts[i]
// Deletion: remove from account trie and evict any cached
// storage trie so a re-created account starts fresh.
if acct.Account == nil {
if err := h.accountTrie.DeleteAccount(addr); err != nil {
return err
}
delete(h.storageTries, addr)
h.storageRoots[addr] = Hashes{
Prev: h.storageRoots[addr].Prev,
Hash: types.EmptyRootHash,
}
continue
}
// Determine storage root from the cached trie (if storage was
// modified) or from the account trie (unchanged storage).
storageRoot := h.accountStorageRoot(addr)
if st, ok := h.storageTries[addr]; ok {
storageRoot = st.Hash()
}
sa := &types.StateAccount{
Nonce: acct.Account.Nonce,
Balance: acct.Account.Balance,
Root: storageRoot,
CodeHash: acct.Account.CodeHash,
}
if err := h.accountTrie.UpdateAccount(addr, sa, 0); err != nil {
return err
}
h.storageRoots[addr] = Hashes{
Prev: h.storageRoots[addr].Prev,
Hash: storageRoot,
}
}
return nil
}
func (h *merkleHasher) Hash() common.Hash {
return h.accountTrie.Hash()
}
func (h *merkleHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) {
nodes := trienode.NewMergedNodeSet()
// Commit all dirty storage tries.
for _, st := range h.storageTries {
if _, set := st.Commit(false); set != nil {
if err := nodes.Merge(set); err != nil {
return common.Hash{}, nil, nil, err
}
}
}
// Commit the account trie. collectLeaf must be true so that hashdb
// can link account trie leaves to their storage trie roots.
root, set := h.accountTrie.Commit(true)
if set != nil {
if err := nodes.Merge(set); err != nil {
return common.Hash{}, nil, nil, err
}
}
return root, nodes, h.storageRoots, nil
}
func (h *merkleHasher) Copy() Hasher {
cpy := &merkleHasher{
db: h.db,
root: h.root,
accountTrie: h.accountTrie.Copy(),
storageTries: make(map[common.Address]*trie.StateTrie, len(h.storageTries)),
storageRoots: maps.Clone(h.storageRoots),
}
for addr, st := range h.storageTries {
cpy.storageTries[addr] = st.Copy()
}
return cpy
}
// ProveAccount implements Prover by constructing a Merkle proof for the
// given account against the current account trie.
func (h *merkleHasher) ProveAccount(addr common.Address, proofDb ethdb.KeyValueWriter) error {
return h.accountTrie.Prove(crypto.Keccak256(addr.Bytes()), proofDb)
}
// ProveStorage implements Prover by constructing a Merkle proof for the given
// storage slot. The storage trie is opened lazily if not already cached.
func (h *merkleHasher) ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error {
st, err := h.openStorageTrie(addr)
if err != nil {
return err
}
return st.Prove(crypto.Keccak256(key.Bytes()), proofDb)
} }

75
core/state/state_mut.go Normal file
View file

@ -0,0 +1,75 @@
// 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 <http://www.gnu.org/licenses/>.
package state
import "github.com/ethereum/go-ethereum/common"
type mutationType int
const (
update mutationType = iota
deletion
)
type mutation struct {
typ mutationType
applied bool
// precedingDelete indicates that a previously unapplied deletion was
// overwritten by an update (account deleted then re-created within
// the same block). IntermediateRoot uses this to notify the hasher
// of the deletion before the update so that any cached storage trie
// is evicted and the re-created account starts with a fresh trie.
precedingDelete bool
}
func (m *mutation) copy() *mutation {
return &mutation{typ: m.typ, applied: m.applied, precedingDelete: m.precedingDelete}
}
func (m *mutation) isDelete() bool {
return m.typ == deletion
}
// markDelete is invoked when an account is deleted but the deletion is
// not yet committed. The pending mutation is cached and will be applied
// all together.
func (s *StateDB) markDelete(addr common.Address) {
if _, ok := s.mutations[addr]; !ok {
s.mutations[addr] = &mutation{}
}
s.mutations[addr].applied = false
s.mutations[addr].typ = deletion
s.mutations[addr].precedingDelete = false
}
func (s *StateDB) markUpdate(addr common.Address) {
m, ok := s.mutations[addr]
if !ok {
s.mutations[addr] = &mutation{}
m = s.mutations[addr]
}
// If this update overwrites a pending (unapplied) deletion, record it
// so that IntermediateRoot can notify the hasher of the deletion first.
// Do not reset precedingDelete otherwise: a subsequent markUpdate must
// preserve the flag set by an earlier markDelete→markUpdate sequence.
if !m.applied && m.typ == deletion {
m.precedingDelete = true
}
m.applied = false
m.typ = update
}

View file

@ -121,15 +121,6 @@ func (s *stateObject) touch() {
s.db.journal.touchChange(s.address) s.db.journal.touchChange(s.address)
} }
// getTrie returns the associated storage trie. The trie will be opened if it's
// not loaded previously. An error will be returned if trie can't be loaded.
//
// If a new trie is opened, it will be cached within the state object to allow
// subsequent reads to expand the same trie instead of reloading from disk.
func (s *stateObject) getTrie() (Trie, error) {
return nil, nil
}
// GetState retrieves a value associated with the given storage key. // GetState retrieves a value associated with the given storage key.
func (s *stateObject) GetState(key common.Hash) common.Hash { func (s *stateObject) GetState(key common.Hash) common.Hash {
value, _ := s.getState(key) value, _ := s.getState(key)
@ -294,6 +285,7 @@ func (s *stateObject) updateTrie() error {
vals = append(vals, value) vals = append(vals, value)
} }
s.uncommittedStorage = make(Storage) // empties the commit markers s.uncommittedStorage = make(Storage) // empties the commit markers
return s.db.hasher.UpdateStorage(s.address, keys, vals) return s.db.hasher.UpdateStorage(s.address, keys, vals)
} }
@ -344,12 +336,12 @@ func (s *stateObject) commit() (*accountUpdate, error) {
} }
// commit the contract code if it's modified // commit the contract code if it's modified
if s.dirtyCode { if s.dirtyCode {
s.dirtyCode = false // reset the dirty flag
op.code = &contractCode{ op.code = &contractCode{
hash: common.BytesToHash(s.CodeHash()), hash: common.BytesToHash(s.CodeHash()),
blob: s.code, blob: s.code,
} }
s.dirtyCode = false // reset the dirty flag
if s.origin == nil { if s.origin == nil {
op.code.originHash = types.EmptyCodeHash op.code.originHash = types.EmptyCodeHash
} else { } else {
@ -494,7 +486,3 @@ func (s *stateObject) Balance() *uint256.Int {
func (s *stateObject) Nonce() uint64 { func (s *stateObject) Nonce() uint64 {
return s.data.Nonce return s.data.Nonce
} }
func (s *stateObject) Root() common.Hash {
return common.Hash{}
}

View file

@ -28,6 +28,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/metrics"
@ -126,134 +127,137 @@ func (s SizeStats) add(diff SizeStats) SizeStats {
// calSizeStats measures the state size changes of the provided state update. // calSizeStats measures the state size changes of the provided state update.
func calSizeStats(update *stateUpdate) (SizeStats, error) { func calSizeStats(update *stateUpdate) (SizeStats, error) {
return SizeStats{}, nil stats := SizeStats{
//stats := SizeStats{ BlockNumber: update.blockNumber,
// BlockNumber: update.blockNumber, StateRoot: update.root,
// StateRoot: update.root, }
//} accounts, accountOrigin, storages, storageOrigin, err := update.encodeMerkle()
// if err != nil {
//// Measure the account changes return SizeStats{}, err
//for addr, oldValue := range update.accountsOrigin { }
// addrHash := crypto.Keccak256Hash(addr.Bytes())
// newValue, exists := update.accounts[addrHash] // Measure the account changes
// if !exists { for addr, oldValue := range accountOrigin {
// return SizeStats{}, fmt.Errorf("account %x not found", addr) addrHash := crypto.Keccak256Hash(addr.Bytes())
// } newValue, exists := accounts[addrHash]
// oldLen, newLen := len(oldValue), len(newValue) if !exists {
// return SizeStats{}, fmt.Errorf("account %x not found", addr)
// switch { }
// case oldLen > 0 && newLen == 0: oldLen, newLen := len(oldValue), len(newValue)
// // Account deletion
// stats.Accounts -= 1 switch {
// stats.AccountBytes -= accountKeySize + int64(oldLen) case oldLen > 0 && newLen == 0:
// case oldLen == 0 && newLen > 0: // Account deletion
// // Account creation stats.Accounts -= 1
// stats.Accounts += 1 stats.AccountBytes -= accountKeySize + int64(oldLen)
// stats.AccountBytes += accountKeySize + int64(newLen) case oldLen == 0 && newLen > 0:
// default: // Account creation
// // Account update stats.Accounts += 1
// stats.AccountBytes += int64(newLen - oldLen) stats.AccountBytes += accountKeySize + int64(newLen)
// } default:
//} // Account update
// stats.AccountBytes += int64(newLen - oldLen)
//// Measure storage changes }
//for addr, slots := range update.storagesOrigin { }
// addrHash := crypto.Keccak256Hash(addr.Bytes())
// subset, exists := update.storages[addrHash] // Measure storage changes
// if !exists { for addr, slots := range storageOrigin {
// return SizeStats{}, fmt.Errorf("storage %x not found", addr) addrHash := crypto.Keccak256Hash(addr.Bytes())
// } subset, exists := storages[addrHash]
// for key, oldValue := range slots { if !exists {
// var ( return SizeStats{}, fmt.Errorf("storage %x not found", addr)
// exists bool }
// newValue []byte for key, oldValue := range slots {
// ) var (
// if update.rawStorageKey { exists bool
// newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())] newValue []byte
// } else { )
// newValue, exists = subset[key] if update.rawStorageKey {
// } newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
// if !exists { } else {
// return SizeStats{}, fmt.Errorf("storage slot %x-%x not found", addr, key) newValue, exists = subset[key]
// } }
// oldLen, newLen := len(oldValue), len(newValue) if !exists {
// return SizeStats{}, fmt.Errorf("storage slot %x-%x not found", addr, key)
// switch { }
// case oldLen > 0 && newLen == 0: oldLen, newLen := len(oldValue), len(newValue)
// // Storage deletion
// stats.Storages -= 1 switch {
// stats.StorageBytes -= storageKeySize + int64(oldLen) case oldLen > 0 && newLen == 0:
// case oldLen == 0 && newLen > 0: // Storage deletion
// // Storage creation stats.Storages -= 1
// stats.Storages += 1 stats.StorageBytes -= storageKeySize + int64(oldLen)
// stats.StorageBytes += storageKeySize + int64(newLen) case oldLen == 0 && newLen > 0:
// default: // Storage creation
// // Storage update stats.Storages += 1
// stats.StorageBytes += int64(newLen - oldLen) stats.StorageBytes += storageKeySize + int64(newLen)
// } default:
// } // Storage update
//} stats.StorageBytes += int64(newLen - oldLen)
// }
//// Measure trienode changes }
//for owner, subset := range update.nodes.Sets { }
// var (
// keyPrefix int64 // Measure trienode changes
// isAccount = owner == (common.Hash{}) for owner, subset := range update.nodes.Sets {
// ) var (
// if isAccount { keyPrefix int64
// keyPrefix = accountTrienodePrefixSize isAccount = owner == (common.Hash{})
// } else { )
// keyPrefix = storageTrienodePrefixSize if isAccount {
// } keyPrefix = accountTrienodePrefixSize
// } else {
// // Iterate over Origins since every modified node has an origin entry keyPrefix = storageTrienodePrefixSize
// for path, oldNode := range subset.Origins { }
// newNode, exists := subset.Nodes[path]
// if !exists { // Iterate over Origins since every modified node has an origin entry
// return SizeStats{}, fmt.Errorf("node %x-%v not found", owner, path) for path, oldNode := range subset.Origins {
// } newNode, exists := subset.Nodes[path]
// keySize := keyPrefix + int64(len(path)) if !exists {
// return SizeStats{}, fmt.Errorf("node %x-%v not found", owner, path)
// switch { }
// case len(oldNode) > 0 && len(newNode.Blob) == 0: keySize := keyPrefix + int64(len(path))
// // Node deletion
// if isAccount { switch {
// stats.AccountTrienodes -= 1 case len(oldNode) > 0 && len(newNode.Blob) == 0:
// stats.AccountTrienodeBytes -= keySize + int64(len(oldNode)) // Node deletion
// } else { if isAccount {
// stats.StorageTrienodes -= 1 stats.AccountTrienodes -= 1
// stats.StorageTrienodeBytes -= keySize + int64(len(oldNode)) stats.AccountTrienodeBytes -= keySize + int64(len(oldNode))
// } } else {
// case len(oldNode) == 0 && len(newNode.Blob) > 0: stats.StorageTrienodes -= 1
// // Node creation stats.StorageTrienodeBytes -= keySize + int64(len(oldNode))
// if isAccount { }
// stats.AccountTrienodes += 1 case len(oldNode) == 0 && len(newNode.Blob) > 0:
// stats.AccountTrienodeBytes += keySize + int64(len(newNode.Blob)) // Node creation
// } else { if isAccount {
// stats.StorageTrienodes += 1 stats.AccountTrienodes += 1
// stats.StorageTrienodeBytes += keySize + int64(len(newNode.Blob)) stats.AccountTrienodeBytes += keySize + int64(len(newNode.Blob))
// } } else {
// default: stats.StorageTrienodes += 1
// // Node update stats.StorageTrienodeBytes += keySize + int64(len(newNode.Blob))
// if isAccount { }
// stats.AccountTrienodeBytes += int64(len(newNode.Blob) - len(oldNode)) default:
// } else { // Node update
// stats.StorageTrienodeBytes += int64(len(newNode.Blob) - len(oldNode)) if isAccount {
// } stats.AccountTrienodeBytes += int64(len(newNode.Blob) - len(oldNode))
// } } else {
// } stats.StorageTrienodeBytes += int64(len(newNode.Blob) - len(oldNode))
//} }
// }
//codeExists := make(map[common.Hash]struct{}) }
//for _, code := range update.codes { }
// if _, ok := codeExists[code.hash]; ok || code.duplicate {
// continue codeExists := make(map[common.Hash]struct{})
// } for _, code := range update.codes {
// stats.ContractCodes += 1 if _, ok := codeExists[code.hash]; ok || code.duplicate {
// stats.ContractCodeBytes += codeKeySize + int64(len(code.blob)) continue
// codeExists[code.hash] = struct{}{} }
//} stats.ContractCodes += 1
//return stats, nil stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
codeExists[code.hash] = struct{}{}
}
return stats, nil
} }
type stateSizeQuery struct { type stateSizeQuery struct {

View file

@ -42,26 +42,6 @@ import (
// TriesInMemory represents the number of layers that are kept in RAM. // TriesInMemory represents the number of layers that are kept in RAM.
const TriesInMemory = 128 const TriesInMemory = 128
type mutationType int
const (
update mutationType = iota
deletion
)
type mutation struct {
typ mutationType
applied bool
}
func (m *mutation) copy() *mutation {
return &mutation{typ: m.typ, applied: m.applied}
}
func (m *mutation) isDelete() bool {
return m.typ == deletion
}
// StateDB structs within the ethereum protocol are used to store anything // StateDB structs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing // within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve: // nested states. It's the general query interface to retrieve:
@ -144,7 +124,6 @@ type StateDB struct {
CodeReads time.Duration CodeReads time.Duration
AccountLoaded int // Number of accounts retrieved from the database during the state transition AccountLoaded int // Number of accounts retrieved from the database during the state transition
AccountUpdated int // Number of accounts updated during the state transition
AccountDeleted int // Number of accounts deleted during the state transition AccountDeleted int // Number of accounts deleted during the state transition
StorageLoaded int // Number of storage slots retrieved from the database during the state transition StorageLoaded int // Number of storage slots retrieved from the database during the state transition
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
@ -154,10 +133,8 @@ type StateDB struct {
// This value may be smaller than the actual number of bytes read, since // This value may be smaller than the actual number of bytes read, since
// some APIs (e.g. CodeSize) may load the entire code from either the // some APIs (e.g. CodeSize) may load the entire code from either the
// cache or the database when the size is not available in the cache. // cache or the database when the size is not available in the cache.
CodeLoaded int // Number of contract code loaded during the state transition CodeLoaded int // Number of contract code loaded during the state transition
CodeLoadBytes int // Total bytes of resolved code CodeLoadBytes int // Total bytes of resolved code
CodeUpdated int // Number of contracts with code changes that persisted
CodeUpdateBytes int // Total bytes of persisted code written
} }
// New creates a new state from a given trie. // New creates a new state from a given trie.
@ -311,19 +288,6 @@ func (s *StateDB) GetNonce(addr common.Address) uint64 {
return 0 return 0
} }
// GetStorageRoot retrieves the storage root from the given address or empty
// if object not found.
//
// Note: the storage root returned corresponds to the trie since last Intermediate
// operation, some recent in-memory changes are excluded.
func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash {
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.Root()
}
return common.Hash{}
}
// TxIndex returns the current transaction index set by SetTxContext. // TxIndex returns the current transaction index set by SetTxContext.
func (s *StateDB) TxIndex() int { func (s *StateDB) TxIndex() int {
return s.txIndex return s.txIndex
@ -777,64 +741,73 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// Finalise all the dirty storage states and write them into the tries // Finalise all the dirty storage states and write them into the tries
s.Finalise(deleteEmptyObjects) s.Finalise(deleteEmptyObjects)
// Process all storage updates concurrently. The state object update root // Pre-process mutations whose preceding deletion has not yet been
// method will internally call a blocking trie fetch from the prefetcher, // applied. This happens when an account is deleted and then re-created
// so there's no need to explicitly wait for the prefetchers to finish. // within the same block and the deletion was overwritten by the update.
// Notify the hasher of the deletion first so that any cached storage
// trie is evicted and the re-created account starts with a fresh trie.
var ( var (
start = time.Now() delAddrs []common.Address
workers errgroup.Group delAccts []AccountMut
start = time.Now()
) )
for addr, op := range s.mutations {
if !op.precedingDelete {
continue
}
op.precedingDelete = false
delAddrs = append(delAddrs, addr)
delAccts = append(delAccts, AccountMut{Account: nil})
}
if len(delAddrs) > 0 {
if err := s.hasher.UpdateAccount(delAddrs, delAccts); err != nil {
s.setError(err)
return common.Hash{}
}
}
s.AccountUpdates += time.Since(start)
// Process all storage updates concurrently, flushing them to hasher.
start = time.Now()
var workers errgroup.Group
for addr, op := range s.mutations { for addr, op := range s.mutations {
if op.applied || op.isDelete() { if op.applied || op.isDelete() {
continue continue
} }
obj := s.stateObjects[addr] // closure for the task runner below obj := s.stateObjects[addr]
workers.Go(obj.updateTrie) workers.Go(obj.updateTrie)
} }
workers.Wait() if err := workers.Wait(); err != nil {
s.setError(err)
}
s.StorageUpdates += time.Since(start) s.StorageUpdates += time.Since(start)
// Now we're about to start to write changes to the trie. The trie is so far // Process all account updates
// _untouched_. We can check with the prefetcher, if it can give us a trie
// which has the same root, but also has some content loaded into it.
//
// Don't check prefetcher if verkle trie has been used. In the context of verkle,
// only a single trie is used for state hashing. Replacing a non-nil verkle tree
// here could result in losing uncommitted changes from storage.
start = time.Now()
var ( var (
addresses []common.Address addresses []common.Address
accounts []AccountMutation accounts []AccountMut
) )
start = time.Now()
for addr, op := range s.mutations { for addr, op := range s.mutations {
if op.applied { if op.applied {
continue continue
} }
op.applied = true op.applied = true
addresses = append(addresses, addr) addresses = append(addresses, addr)
if op.isDelete() { if op.isDelete() {
accounts = append(accounts, AccountMutation{Account: nil}) accounts = append(accounts, AccountMut{Account: nil})
} else { continue
obj := s.stateObjects[addr]
mut := AccountMutation{
Account: &obj.data,
DirtyCode: obj.dirtyCode,
Code: obj.code,
}
accounts = append(accounts, mut)
s.AccountUpdated += 1
// Count code writes post-Finalise so reverted CREATEs are excluded.
if obj.dirtyCode {
s.CodeUpdated += 1
s.CodeUpdateBytes += len(obj.code)
}
} }
obj := s.stateObjects[addr]
mut := AccountMut{Account: &obj.data}
if obj.dirtyCode {
mut.Code = &CodeMut{Code: obj.code}
}
accounts = append(accounts, mut)
} }
if err := s.hasher.UpdateAccount(addresses, accounts); err != nil { if err := s.hasher.UpdateAccount(addresses, accounts); err != nil {
s.setError(err)
return common.Hash{} return common.Hash{}
} }
s.AccountUpdates += time.Since(start) s.AccountUpdates += time.Since(start)
@ -1028,7 +1001,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
} }
accountReadMeters.Mark(int64(s.AccountLoaded)) accountReadMeters.Mark(int64(s.AccountLoaded))
storageReadMeters.Mark(int64(s.StorageLoaded)) storageReadMeters.Mark(int64(s.StorageLoaded))
accountUpdatedMeter.Mark(int64(s.AccountUpdated))
storageUpdatedMeter.Mark(s.StorageUpdated.Load()) storageUpdatedMeter.Mark(s.StorageUpdated.Load())
accountDeletedMeter.Mark(int64(s.AccountDeleted)) accountDeletedMeter.Mark(int64(s.AccountDeleted))
storageDeletedMeter.Mark(s.StorageDeleted.Load()) storageDeletedMeter.Mark(s.StorageDeleted.Load())
@ -1038,7 +1010,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted)) storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
// Clear the metric markers // Clear the metric markers
s.AccountLoaded, s.AccountUpdated, s.AccountDeleted = 0, 0, 0 s.AccountLoaded, s.AccountDeleted = 0, 0
s.StorageLoaded = 0 s.StorageLoaded = 0
s.StorageUpdated.Store(0) s.StorageUpdated.Store(0)
s.StorageDeleted.Store(0) s.StorageDeleted.Store(0)
@ -1186,25 +1158,6 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre
return s.accessList.Contains(addr, slot) return s.accessList.Contains(addr, slot)
} }
// markDelete is invoked when an account is deleted but the deletion is
// not yet committed. The pending mutation is cached and will be applied
// all together
func (s *StateDB) markDelete(addr common.Address) {
if _, ok := s.mutations[addr]; !ok {
s.mutations[addr] = &mutation{}
}
s.mutations[addr].applied = false
s.mutations[addr].typ = deletion
}
func (s *StateDB) markUpdate(addr common.Address) {
if _, ok := s.mutations[addr]; !ok {
s.mutations[addr] = &mutation{}
}
s.mutations[addr].applied = false
s.mutations[addr].typ = update
}
// Witness retrieves the current state witness being collected. // Witness retrieves the current state witness being collected.
func (s *StateDB) Witness() *stateless.Witness { func (s *StateDB) Witness() *stateless.Witness {
return nil return nil

View file

@ -21,6 +21,7 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"maps"
"math" "math"
"math/rand" "math/rand"
"reflect" "reflect"
@ -182,10 +183,11 @@ func (test *stateTest) run() bool {
storages []map[common.Hash]map[common.Hash][]byte storages []map[common.Hash]map[common.Hash][]byte
storageOrigin []map[common.Address]map[common.Hash][]byte storageOrigin []map[common.Address]map[common.Hash][]byte
copyUpdate = func(update *stateUpdate) { copyUpdate = func(update *stateUpdate) {
//accounts = append(accounts, maps.Clone(update.accounts)) encoded, _ := update.stateSet()
//accountOrigin = append(accountOrigin, maps.Clone(update.accountsOrigin)) accounts = append(accounts, maps.Clone(encoded.Accounts))
//storages = append(storages, maps.Clone(update.storages)) accountOrigin = append(accountOrigin, maps.Clone(encoded.AccountsOrigin))
//storageOrigin = append(storageOrigin, maps.Clone(update.storagesOrigin)) storages = append(storages, maps.Clone(encoded.Storages))
storageOrigin = append(storageOrigin, maps.Clone(encoded.StoragesOrigin))
} }
disk = rawdb.NewMemoryDatabase() disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults}) tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})

View file

@ -34,8 +34,6 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/hashdb" "github.com/ethereum/go-ethereum/triedb/hashdb"
"github.com/ethereum/go-ethereum/triedb/pathdb" "github.com/ethereum/go-ethereum/triedb/pathdb"
@ -540,47 +538,6 @@ func (test *snapshotTest) run() bool {
return true return true
} }
func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.Hash) bool) error {
so := s.getStateObject(addr)
if so == nil {
return nil
}
tr, err := so.getTrie()
if err != nil {
return err
}
trieIt, err := tr.NodeIterator(nil)
if err != nil {
return err
}
var (
it = trie.NewIterator(trieIt)
visited = make(map[common.Hash]bool)
)
for it.Next() {
key := common.BytesToHash(tr.GetKey(it.Key))
visited[key] = true
if value, dirty := so.dirtyStorage[key]; dirty {
if !cb(key, value) {
return nil
}
continue
}
if len(it.Value) > 0 {
_, content, _, err := rlp.Split(it.Value)
if err != nil {
return err
}
if !cb(key, common.BytesToHash(content)) {
return nil
}
}
}
return nil
}
// checkEqual checks that methods of state and checkstate return the same values. // checkEqual checks that methods of state and checkstate return the same values.
func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error { func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
for _, addr := range test.addrs { for _, addr := range test.addrs {
@ -606,12 +563,6 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
} }
// Check storage. // Check storage.
if obj := state.getStateObject(addr); obj != nil { if obj := state.getStateObject(addr); obj != nil {
forEachStorage(state, addr, func(key, value common.Hash) bool {
return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
})
forEachStorage(checkstate, addr, func(key, value common.Hash) bool {
return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
})
other := checkstate.getStateObject(addr) other := checkstate.getStateObject(addr)
// Check dirty storage which is not in trie // Check dirty storage which is not in trie
if !maps.Equal(obj.dirtyStorage, other.dirtyStorage) { if !maps.Equal(obj.dirtyStorage, other.dirtyStorage) {
@ -770,8 +721,14 @@ func TestCopyCommitCopy(t *testing.T) {
t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, common.Hash{}) t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
} }
// Commit state, ensure states can be loaded from disk // Commit state, ensure states can be loaded from disk
root, _ := state.Commit(0, false, false) root, err := state.Commit(0, false, false)
state, _ = New(root, tdb) if err != nil {
t.Fatalf("commit fail: %v", err)
}
state, err = New(root, tdb)
if err != nil {
t.Fatalf("New fail: %v", err)
}
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 { if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42) t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42)
} }

View file

@ -17,12 +17,15 @@
package state package state
import ( import (
"errors"
"fmt" "fmt"
"maps" "maps"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "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" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
) )
@ -30,8 +33,8 @@ import (
// contractCode encapsulates contract bytecode and its associated metadata. // contractCode encapsulates contract bytecode and its associated metadata.
type contractCode struct { type contractCode struct {
hash common.Hash // hash is the cryptographic hash of the current contract code. hash common.Hash // hash is the cryptographic hash of the current contract code.
blob []byte // blob is the raw byte representation of the current contract code.
originHash common.Hash // originHash is the cryptographic hash of the code prior to mutation. originHash common.Hash // originHash is the cryptographic hash of the code prior to mutation.
blob []byte // blob is the raw byte representation of the current contract code.
// Derived fields, populated only when state tracking is enabled. // Derived fields, populated only when state tracking is enabled.
duplicate bool // duplicate indicates whether the updated code already exists. duplicate bool // duplicate indicates whether the updated code already exists.
@ -87,7 +90,7 @@ type stateUpdate struct {
codes map[common.Address]*contractCode // codes contains mutated contract codes, keyed by address. codes map[common.Address]*contractCode // codes contains mutated contract codes, keyed by address.
nodes *trienode.MergedNodeSet // nodes aggregates all dirty trie nodes produced by the update. nodes *trienode.MergedNodeSet // nodes aggregates all dirty trie nodes produced by the update.
secondaryHashes map[common.Address]SecondaryHash // hashes of secondary tries secondaryHashes map[common.Address]Hashes // hashes of secondary tries
} }
// empty returns a flag indicating the state transition is empty or not. // empty returns a flag indicating the state transition is empty or not.
@ -101,7 +104,7 @@ func (sc *stateUpdate) empty() bool {
// //
// rawStorageKey is a flag indicating whether to use the raw storage slot key or // rawStorageKey is a flag indicating whether to use the raw storage slot key or
// the hash of the slot key for constructing state update object. // the hash of the slot key for constructing state update object.
func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet, secondaryHashes map[common.Address]SecondaryHash) *stateUpdate { func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet, secondaryHashes map[common.Address]Hashes) *stateUpdate {
var ( var (
accounts = make(map[common.Hash]*Account) accounts = make(map[common.Hash]*Account)
accountsOrigin = make(map[common.Address]*Account) accountsOrigin = make(map[common.Address]*Account)
@ -182,19 +185,87 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
} }
} }
func encodeSlot(val common.Hash) []byte {
if val == (common.Hash{}) {
return nil
}
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
return blob
}
func (sc *stateUpdate) encodeMerkle() (map[common.Hash][]byte, map[common.Address][]byte, map[common.Hash]map[common.Hash][]byte, map[common.Address]map[common.Hash][]byte, error) {
var (
accounts = make(map[common.Hash][]byte)
storages = make(map[common.Hash]map[common.Hash][]byte)
accountOrigin = make(map[common.Address][]byte)
storageOrigin = make(map[common.Address]map[common.Hash][]byte)
)
for addr, prev := range sc.accountsOrigin {
if prev == nil {
accountOrigin[addr] = nil
} else {
pair, ok := sc.secondaryHashes[addr]
if !ok {
return nil, nil, nil, nil, errors.New("no secondary hash")
}
accountOrigin[addr] = types.SlimAccountRLP(types.StateAccount{
Balance: prev.Balance,
Nonce: prev.Nonce,
CodeHash: prev.CodeHash,
Root: pair.Prev,
})
}
addrHash := crypto.Keccak256Hash(addr.Bytes())
data := sc.accounts[addrHash]
if data == nil {
accounts[addrHash] = nil
} else {
pair, ok := sc.secondaryHashes[addr]
if !ok {
return nil, nil, nil, nil, errors.New("no secondary hash")
}
accounts[addrHash] = types.SlimAccountRLP(types.StateAccount{
Balance: data.Balance,
Nonce: data.Nonce,
CodeHash: data.CodeHash,
Root: pair.Hash,
})
}
}
for addr, slots := range sc.storagesOrigin {
subset := make(map[common.Hash][]byte)
for key, val := range slots {
subset[key] = encodeSlot(val)
}
storageOrigin[addr] = subset
}
for addrHash, slots := range sc.storages {
subset := make(map[common.Hash][]byte)
for key, val := range slots {
subset[key] = encodeSlot(val)
}
storages[addrHash] = subset
}
return accounts, accountOrigin, storages, storageOrigin, nil
}
// stateSet converts the current stateUpdate object into a triedb.StateSet // stateSet converts the current stateUpdate object into a triedb.StateSet
// object. This function extracts the necessary data from the stateUpdate // object. This function extracts the necessary data from the stateUpdate
// struct and formats it into the StateSet structure consumed by the triedb // struct and formats it into the StateSet structure consumed by the triedb
// package. // package.
func (sc *stateUpdate) stateSet() *triedb.StateSet { func (sc *stateUpdate) stateSet() (*triedb.StateSet, error) {
return nil accounts, accountOrigin, storages, storageOrigin, err := sc.encodeMerkle()
//return &triedb.StateSet{ if err != nil {
// Accounts: sc.accounts, return nil, err
// AccountsOrigin: sc.accountsOrigin, }
// Storages: sc.storages, return &triedb.StateSet{
// StoragesOrigin: sc.storagesOrigin, Accounts: accounts,
// RawStorageKey: sc.rawStorageKey, AccountsOrigin: accountOrigin,
//} Storages: storages,
StoragesOrigin: storageOrigin,
RawStorageKey: sc.rawStorageKey,
}, nil
} }
// deriveCodeFields derives the missing fields of contract code changes // deriveCodeFields derives the missing fields of contract code changes
@ -226,141 +297,117 @@ func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
// ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate. // ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate.
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) { func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
return nil, nil update := &tracing.StateUpdate{
//update := &tracing.StateUpdate{ OriginRoot: sc.originRoot,
// OriginRoot: sc.originRoot, Root: sc.root,
// Root: sc.root, BlockNumber: sc.blockNumber,
// BlockNumber: sc.blockNumber, AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
// AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)), StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
// StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange), CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
// CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)), TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
// TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange), }
//} // Gather all account changes
//// Gather all account changes for addr, oldData := range sc.accountsOrigin {
//for addr, oldData := range sc.accountsOrigin { addrHash := crypto.Keccak256Hash(addr.Bytes())
// addrHash := crypto.Keccak256Hash(addr.Bytes()) newData, exists := sc.accounts[addrHash]
// newData, exists := sc.accounts[addrHash] if !exists {
// if !exists { return nil, fmt.Errorf("account %x not found", addr)
// return nil, fmt.Errorf("account %x not found", addr) }
// } hashes := sc.secondaryHashes[addr]
// change := &tracing.AccountChange{} change := &tracing.AccountChange{}
//
// if len(oldData) > 0 { if oldData != nil {
// acct, err := types.FullAccount(oldData) change.Prev = &types.StateAccount{
// if err != nil { Nonce: oldData.Nonce,
// return nil, err Balance: oldData.Balance,
// } Root: hashes.Prev,
// change.Prev = &types.StateAccount{ CodeHash: oldData.CodeHash,
// Nonce: acct.Nonce, }
// Balance: acct.Balance, }
// Root: acct.Root, if newData != nil {
// CodeHash: acct.CodeHash, change.New = &types.StateAccount{
// } Nonce: newData.Nonce,
// } Balance: newData.Balance,
// if len(newData) > 0 { Root: hashes.Hash,
// acct, err := types.FullAccount(newData) CodeHash: newData.CodeHash,
// if err != nil { }
// return nil, err }
// } update.AccountChanges[addr] = change
// change.New = &types.StateAccount{ }
// Nonce: acct.Nonce,
// Balance: acct.Balance, // Gather all storage slot changes
// Root: acct.Root, for addr, slots := range sc.storagesOrigin {
// CodeHash: acct.CodeHash, addrHash := crypto.Keccak256Hash(addr.Bytes())
// } subset, exists := sc.storages[addrHash]
// } if !exists {
// update.AccountChanges[addr] = change return nil, fmt.Errorf("storage %x not found", addr)
//} }
// storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
//// Gather all storage slot changes
//for addr, slots := range sc.storagesOrigin { for key, prev := range slots {
// addrHash := crypto.Keccak256Hash(addr.Bytes()) // Get new value - handle both raw and hashed key formats
// subset, exists := sc.storages[addrHash] var (
// if !exists { exists bool
// return nil, fmt.Errorf("storage %x not found", addr) current common.Hash
// } )
// storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots)) if sc.rawStorageKey {
// current, exists = subset[crypto.Keccak256Hash(key.Bytes())]
// for key, encPrev := range slots { } else {
// // Get new value - handle both raw and hashed key formats current, exists = subset[key]
// var ( }
// exists bool if !exists {
// encNew []byte return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
// decPrev []byte }
// decNew []byte
// err error storageChanges[key] = &tracing.StorageChange{
// ) Prev: prev,
// if sc.rawStorageKey { New: current,
// encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())] }
// } else { }
// encNew, exists = subset[key] update.StorageChanges[addr] = storageChanges
// } }
// if !exists {
// return nil, fmt.Errorf("storage slot %x-%x not found", addr, key) // Gather all contract code changes
// } for addr, code := range sc.codes {
// change := &tracing.CodeChange{
// // Decode the prev and new values New: &tracing.ContractCode{
// if len(encPrev) > 0 { Hash: code.hash,
// _, decPrev, _, err = rlp.Split(encPrev) Code: code.blob,
// if err != nil { Exists: code.duplicate,
// return nil, fmt.Errorf("failed to decode prevValue: %v", err) },
// } }
// } if code.originHash != types.EmptyCodeHash {
// if len(encNew) > 0 { change.Prev = &tracing.ContractCode{
// _, decNew, _, err = rlp.Split(encNew) Hash: code.originHash,
// if err != nil { Code: code.originBlob,
// return nil, fmt.Errorf("failed to decode newValue: %v", err) Exists: true,
// } }
// } }
// storageChanges[key] = &tracing.StorageChange{ update.CodeChanges[addr] = change
// Prev: common.BytesToHash(decPrev), }
// New: common.BytesToHash(decNew),
// } // Gather all trie node changes
// } if sc.nodes != nil {
// update.StorageChanges[addr] = storageChanges for owner, subset := range sc.nodes.Sets {
//} nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
// for path, oldNode := range subset.Origins {
//// Gather all contract code changes newNode, exists := subset.Nodes[path]
//for addr, code := range sc.codes { if !exists {
// change := &tracing.CodeChange{ return nil, fmt.Errorf("node %x-%v not found", owner, path)
// New: &tracing.ContractCode{ }
// Hash: code.hash, nodeChanges[path] = &tracing.TrieNodeChange{
// Code: code.blob, Prev: &trienode.Node{
// Exists: code.duplicate, Hash: crypto.Keccak256Hash(oldNode),
// }, Blob: oldNode,
// } },
// if code.originHash != types.EmptyCodeHash { New: &trienode.Node{
// change.Prev = &tracing.ContractCode{ Hash: newNode.Hash,
// Hash: code.originHash, Blob: newNode.Blob,
// Code: code.originBlob, },
// Exists: true, }
// } }
// } update.TrieChanges[owner] = nodeChanges
// update.CodeChanges[addr] = change }
//} }
// return update, nil
//// Gather all trie node changes
//if sc.nodes != nil {
// for owner, subset := range sc.nodes.Sets {
// nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
// for path, oldNode := range subset.Origins {
// newNode, exists := subset.Nodes[path]
// if !exists {
// return nil, fmt.Errorf("node %x-%v not found", owner, path)
// }
// nodeChanges[path] = &tracing.TrieNodeChange{
// Prev: &trienode.Node{
// Hash: crypto.Keccak256Hash(oldNode),
// Blob: oldNode,
// },
// New: &trienode.Node{
// Hash: newNode.Hash,
// Blob: newNode.Blob,
// },
// }
// }
// update.TrieChanges[owner] = nodeChanges
// }
//}
//return update, nil
} }

View file

@ -232,38 +232,31 @@ func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockNrOrHash rpc.Block
} }
func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Address, start []byte, maxResult int) (StorageRangeResult, error) { func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Address, start []byte, maxResult int) (StorageRangeResult, error) {
storageRoot := statedb.GetStorageRoot(address) it, err := statedb.Database().Iteratee(root)
if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) { if err != nil {
return StorageRangeResult{}, nil // empty storage return StorageRangeResult{}, err
}
storageIt, err := it.NewStorageIterator(crypto.Keccak256Hash(address.Bytes()), common.BytesToHash(start))
if err != nil {
return StorageRangeResult{}, err
} }
// TODO(rjl493456442) it's problematic for traversing the state with in-memory // TODO(rjl493456442) it's problematic for traversing the state with in-memory
// state mutations, specifically txIndex != 0. // 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 {
return StorageRangeResult{}, err
}
trieIt, err := tr.NodeIterator(start)
if err != nil {
return StorageRangeResult{}, err
}
it := trie.NewIterator(trieIt)
result := StorageRangeResult{Storage: storageMap{}} result := StorageRangeResult{Storage: storageMap{}}
for i := 0; i < maxResult && it.Next(); i++ { for i := 0; i < maxResult && storageIt.Next(); i++ {
_, content, _, err := rlp.Split(it.Value) _, content, _, err := rlp.Split(storageIt.Slot())
if err != nil { if err != nil {
return StorageRangeResult{}, err return StorageRangeResult{}, err
} }
e := storageEntry{Value: common.BytesToHash(content)} e := storageEntry{Value: common.BytesToHash(content)}
if preimage := tr.GetKey(it.Key); preimage != nil { if preimage, err := storageIt.Key(); err == nil {
preimage := common.BytesToHash(preimage)
e.Key = &preimage e.Key = &preimage
} }
result.Storage[common.BytesToHash(it.Key)] = e result.Storage[storageIt.Hash()] = e
} }
// Add the 'next key' so clients can continue downloading. // Add the 'next key' so clients can continue downloading.
if it.Next() { if storageIt.Next() {
next := common.BytesToHash(it.Key) next := storageIt.Hash()
result.NextKey = &next result.NextKey = &next
} }
return result, nil return result, nil

View file

@ -388,17 +388,15 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
return nil, err return nil, err
} }
codeHash := statedb.GetCodeHash(address) codeHash := statedb.GetCodeHash(address)
storageRoot := statedb.GetStorageRoot(address) hasher, err := statedb.Database().Hasher(header.Root)
if err != nil {
return nil, err
}
prover, ok := hasher.(state.Prover)
if !ok {
return nil, errors.New("state proving is not supported")
}
if len(keys) > 0 { if len(keys) > 0 {
var storageTrie state.Trie
if storageRoot != types.EmptyRootHash && storageRoot != (common.Hash{}) {
st, err := statedb.Database().OpenStorageTrie(header.Root, address, storageRoot, nil)
if err != nil {
return nil, err
}
storageTrie = st
}
// Create the proofs for the storageKeys. // Create the proofs for the storageKeys.
for i, key := range keys { for i, key := range keys {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
@ -414,12 +412,8 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
} else { } else {
outputKey = hexutil.Encode(key[:]) outputKey = hexutil.Encode(key[:])
} }
if storageTrie == nil {
storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}}
continue
}
var proof proofList var proof proofList
if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil { if err := prover.ProveStorage(address, crypto.Keccak256Hash(key.Bytes()), &proof); err != nil {
return nil, err return nil, err
} }
value := (*hexutil.Big)(statedb.GetState(address, key).Big()) value := (*hexutil.Big)(statedb.GetState(address, key).Big())
@ -427,12 +421,8 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
} }
} }
// Create the accountProof. // Create the accountProof.
tr, err := statedb.Database().OpenTrie(header.Root)
if err != nil {
return nil, err
}
var accountProof proofList var accountProof proofList
if err := tr.Prove(crypto.Keccak256(address.Bytes()), &accountProof); err != nil { if err := prover.ProveAccount(address, &accountProof); err != nil {
return nil, err return nil, err
} }
balance := statedb.GetBalance(address).ToBig() balance := statedb.GetBalance(address).ToBig()
@ -442,7 +432,7 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
Balance: (*hexutil.Big)(balance), Balance: (*hexutil.Big)(balance),
CodeHash: codeHash, CodeHash: codeHash,
Nonce: hexutil.Uint64(statedb.GetNonce(address)), Nonce: hexutil.Uint64(statedb.GetNonce(address)),
StorageHash: storageRoot, //StorageHash: storageRoot, // TODO(rjl493456442)
StorageProof: storageProof, StorageProof: storageProof,
}, statedb.Error() }, statedb.Error()
} }