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.AccountLoaded = statedb.AccountLoaded
stats.AccountUpdated = statedb.AccountUpdated
//stats.AccountUpdated = statedb.AccountUpdated
stats.AccountDeleted = statedb.AccountDeleted
stats.StorageLoaded = statedb.StorageLoaded
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.CodeLoadBytes = statedb.CodeLoadBytes
stats.CodeUpdated = statedb.CodeUpdated
stats.CodeUpdateBytes = statedb.CodeUpdateBytes
//stats.CodeUpdated = statedb.CodeUpdated
//stats.CodeUpdateBytes = statedb.CodeUpdateBytes
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

View file

@ -24,6 +24,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
"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
}
// Hasher implements Database, returning a hasher associated with the specified
// state root.
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
@ -300,18 +303,26 @@ func (db *CachingDB) Commit(update *stateUpdate) error {
}
// If snapshotting is enabled, update the snapshot tree with this new version
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 {
// log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
//}
//// Keep 128 diff layers in the memory, persistent layer is 129th.
//// - 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)
//}
accounts, _, storages, _, err := update.encodeMerkle()
if err != nil {
return err
}
if err := db.snap.Update(update.root, update.originRoot, accounts, storages); err != nil {
log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
}
// Keep 128 diff layers in the memory, persistent layer is 129th.
// - 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,

View file

@ -17,25 +17,38 @@
package state
import (
"maps"
"sync"
"github.com/ethereum/go-ethereum/common"
"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/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb"
)
// AccountMutation describes a state transition for a single account.
type AccountMutation struct {
Account *Account // Null for deletion
DirtyCode bool // Flag whether the code is changed
Code []byte // Null for deletion
// CodeMut represents a mutation to contract code.
type CodeMut struct {
Code []byte // Null for deletion
}
// SecondaryHash encapsulates the secondary hash of storage tries.
// It is only relevant in the context of the Merkle Patricia Trie and
// includes both the post-transition root and the original root.
type SecondaryHash struct {
Hash common.Hash
Prev common.Hash
// AccountMut represents a mutation to an account.
// Semantics:
// - Account == nil: delete the account
// - Code == nil: leave code unchanged
// - Code != nil: apply the given code mutation
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.
@ -54,11 +67,9 @@ type SecondaryHash struct {
// compatibility with pre-Byzantium semantics.
type Hasher interface {
// 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
// encoding internally. Note, the value with empty data should be
// interpreted as deletion.
// UpdateStorage writes a list of storage slot value.
UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error
// 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
// secondary tries together with their original hashes will also be returned
// 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() Hasher
}
// 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 {
// PrefetchAccount schedules the account for prefetching.
PrefetchAccount(addresses []common.Address, read bool)
@ -87,9 +98,9 @@ type Prefetcher interface {
}
// 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 {
// Witness returns the stateless witness corresponding to the most recent
// Witness returns the state witness corresponding to the most recent
// committed state transition.
Witness() (*stateless.Witness, error)
}
@ -120,29 +131,207 @@ type Prover interface {
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{}
func (n noopHasher) UpdateAccount(addresses []common.Address, accounts []AccountMutation) error {
//TODO implement me
panic("implement me")
func (n *noopHasher) UpdateAccount([]common.Address, []AccountMut) error { return nil }
func (n *noopHasher) UpdateStorage(common.Address, []common.Hash, []common.Hash) error {
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 {
//TODO implement me
panic("implement me")
func newMerkleHasher(root common.Hash, db *triedb.Database) (*merkleHasher, error) {
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db)
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 {
//TODO implement me
panic("implement me")
// accountStorageRoot reads the storage root of account from the account trie.
func (h *merkleHasher) accountStorageRoot(addr common.Address) common.Hash {
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) {
//TODO implement me
panic("implement me")
// recordOrigin records the original (pre-mutation) storage root for addr.
// Only the first call per address has any effect.
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 {
//TODO implement me
panic("implement me")
// openStorageTrie returns the cached storage trie for the given address,
// or opens one from the database if not already cached.
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)
}
// 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.
func (s *stateObject) GetState(key common.Hash) common.Hash {
value, _ := s.getState(key)
@ -294,6 +285,7 @@ func (s *stateObject) updateTrie() error {
vals = append(vals, value)
}
s.uncommittedStorage = make(Storage) // empties the commit markers
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
if s.dirtyCode {
s.dirtyCode = false // reset the dirty flag
op.code = &contractCode{
hash: common.BytesToHash(s.CodeHash()),
blob: s.code,
}
s.dirtyCode = false // reset the dirty flag
if s.origin == nil {
op.code.originHash = types.EmptyCodeHash
} else {
@ -494,7 +486,3 @@ func (s *stateObject) Balance() *uint256.Int {
func (s *stateObject) Nonce() uint64 {
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/core/rawdb"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"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.
func calSizeStats(update *stateUpdate) (SizeStats, error) {
return SizeStats{}, nil
//stats := SizeStats{
// BlockNumber: update.blockNumber,
// StateRoot: update.root,
//}
//
//// Measure the account changes
//for addr, oldValue := range update.accountsOrigin {
// addrHash := crypto.Keccak256Hash(addr.Bytes())
// newValue, exists := update.accounts[addrHash]
// if !exists {
// return SizeStats{}, fmt.Errorf("account %x not found", addr)
// }
// oldLen, newLen := len(oldValue), len(newValue)
//
// switch {
// case oldLen > 0 && newLen == 0:
// // Account deletion
// stats.Accounts -= 1
// stats.AccountBytes -= accountKeySize + int64(oldLen)
// case oldLen == 0 && newLen > 0:
// // Account creation
// stats.Accounts += 1
// 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]
// if !exists {
// return SizeStats{}, fmt.Errorf("storage %x not found", addr)
// }
// for key, oldValue := range slots {
// var (
// exists bool
// newValue []byte
// )
// if update.rawStorageKey {
// newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
// } else {
// newValue, exists = subset[key]
// }
// if !exists {
// return SizeStats{}, fmt.Errorf("storage slot %x-%x not found", addr, key)
// }
// oldLen, newLen := len(oldValue), len(newValue)
//
// switch {
// case oldLen > 0 && newLen == 0:
// // Storage deletion
// stats.Storages -= 1
// stats.StorageBytes -= storageKeySize + int64(oldLen)
// case oldLen == 0 && newLen > 0:
// // Storage creation
// stats.Storages += 1
// 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
// isAccount = owner == (common.Hash{})
// )
// if isAccount {
// keyPrefix = accountTrienodePrefixSize
// } else {
// keyPrefix = storageTrienodePrefixSize
// }
//
// // Iterate over Origins since every modified node has an origin entry
// for path, oldNode := range subset.Origins {
// newNode, exists := subset.Nodes[path]
// if !exists {
// return SizeStats{}, fmt.Errorf("node %x-%v not found", owner, path)
// }
// keySize := keyPrefix + int64(len(path))
//
// switch {
// case len(oldNode) > 0 && len(newNode.Blob) == 0:
// // Node deletion
// if isAccount {
// stats.AccountTrienodes -= 1
// stats.AccountTrienodeBytes -= keySize + int64(len(oldNode))
// } else {
// stats.StorageTrienodes -= 1
// stats.StorageTrienodeBytes -= keySize + int64(len(oldNode))
// }
// case len(oldNode) == 0 && len(newNode.Blob) > 0:
// // Node creation
// if isAccount {
// stats.AccountTrienodes += 1
// stats.AccountTrienodeBytes += keySize + int64(len(newNode.Blob))
// } else {
// stats.StorageTrienodes += 1
// stats.StorageTrienodeBytes += keySize + int64(len(newNode.Blob))
// }
// default:
// // Node update
// 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
// }
// stats.ContractCodes += 1
// stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
// codeExists[code.hash] = struct{}{}
//}
//return stats, nil
stats := SizeStats{
BlockNumber: update.blockNumber,
StateRoot: update.root,
}
accounts, accountOrigin, storages, storageOrigin, err := update.encodeMerkle()
if err != nil {
return SizeStats{}, err
}
// Measure the account changes
for addr, oldValue := range accountOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
newValue, exists := accounts[addrHash]
if !exists {
return SizeStats{}, fmt.Errorf("account %x not found", addr)
}
oldLen, newLen := len(oldValue), len(newValue)
switch {
case oldLen > 0 && newLen == 0:
// Account deletion
stats.Accounts -= 1
stats.AccountBytes -= accountKeySize + int64(oldLen)
case oldLen == 0 && newLen > 0:
// Account creation
stats.Accounts += 1
stats.AccountBytes += accountKeySize + int64(newLen)
default:
// Account update
stats.AccountBytes += int64(newLen - oldLen)
}
}
// Measure storage changes
for addr, slots := range storageOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
subset, exists := storages[addrHash]
if !exists {
return SizeStats{}, fmt.Errorf("storage %x not found", addr)
}
for key, oldValue := range slots {
var (
exists bool
newValue []byte
)
if update.rawStorageKey {
newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
} else {
newValue, exists = subset[key]
}
if !exists {
return SizeStats{}, fmt.Errorf("storage slot %x-%x not found", addr, key)
}
oldLen, newLen := len(oldValue), len(newValue)
switch {
case oldLen > 0 && newLen == 0:
// Storage deletion
stats.Storages -= 1
stats.StorageBytes -= storageKeySize + int64(oldLen)
case oldLen == 0 && newLen > 0:
// Storage creation
stats.Storages += 1
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
isAccount = owner == (common.Hash{})
)
if isAccount {
keyPrefix = accountTrienodePrefixSize
} else {
keyPrefix = storageTrienodePrefixSize
}
// Iterate over Origins since every modified node has an origin entry
for path, oldNode := range subset.Origins {
newNode, exists := subset.Nodes[path]
if !exists {
return SizeStats{}, fmt.Errorf("node %x-%v not found", owner, path)
}
keySize := keyPrefix + int64(len(path))
switch {
case len(oldNode) > 0 && len(newNode.Blob) == 0:
// Node deletion
if isAccount {
stats.AccountTrienodes -= 1
stats.AccountTrienodeBytes -= keySize + int64(len(oldNode))
} else {
stats.StorageTrienodes -= 1
stats.StorageTrienodeBytes -= keySize + int64(len(oldNode))
}
case len(oldNode) == 0 && len(newNode.Blob) > 0:
// Node creation
if isAccount {
stats.AccountTrienodes += 1
stats.AccountTrienodeBytes += keySize + int64(len(newNode.Blob))
} else {
stats.StorageTrienodes += 1
stats.StorageTrienodeBytes += keySize + int64(len(newNode.Blob))
}
default:
// Node update
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
}
stats.ContractCodes += 1
stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
codeExists[code.hash] = struct{}{}
}
return stats, nil
}
type stateSizeQuery struct {

View file

@ -42,26 +42,6 @@ import (
// TriesInMemory represents the number of layers that are kept in RAM.
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
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
@ -144,7 +124,6 @@ type StateDB struct {
CodeReads time.Duration
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
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
@ -154,10 +133,8 @@ type StateDB struct {
// 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
// cache or the database when the size is not available in the cache.
CodeLoaded int // Number of contract code loaded during the state transition
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
CodeLoaded int // Number of contract code loaded during the state transition
CodeLoadBytes int // Total bytes of resolved code
}
// New creates a new state from a given trie.
@ -311,19 +288,6 @@ func (s *StateDB) GetNonce(addr common.Address) uint64 {
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.
func (s *StateDB) TxIndex() int {
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
s.Finalise(deleteEmptyObjects)
// Process all storage updates concurrently. The state object update root
// method will internally call a blocking trie fetch from the prefetcher,
// so there's no need to explicitly wait for the prefetchers to finish.
// Pre-process mutations whose preceding deletion has not yet been
// applied. This happens when an account is deleted and then re-created
// 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 (
start = time.Now()
workers errgroup.Group
delAddrs []common.Address
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 {
if op.applied || op.isDelete() {
continue
}
obj := s.stateObjects[addr] // closure for the task runner below
obj := s.stateObjects[addr]
workers.Go(obj.updateTrie)
}
workers.Wait()
if err := workers.Wait(); err != nil {
s.setError(err)
}
s.StorageUpdates += time.Since(start)
// Now we're about to start to write changes to the trie. The trie is so far
// _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()
// Process all account updates
var (
addresses []common.Address
accounts []AccountMutation
accounts []AccountMut
)
start = time.Now()
for addr, op := range s.mutations {
if op.applied {
continue
}
op.applied = true
addresses = append(addresses, addr)
if op.isDelete() {
accounts = append(accounts, AccountMutation{Account: nil})
} else {
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)
}
accounts = append(accounts, AccountMut{Account: nil})
continue
}
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 {
s.setError(err)
return common.Hash{}
}
s.AccountUpdates += time.Since(start)
@ -1028,7 +1001,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
}
accountReadMeters.Mark(int64(s.AccountLoaded))
storageReadMeters.Mark(int64(s.StorageLoaded))
accountUpdatedMeter.Mark(int64(s.AccountUpdated))
storageUpdatedMeter.Mark(s.StorageUpdated.Load())
accountDeletedMeter.Mark(int64(s.AccountDeleted))
storageDeletedMeter.Mark(s.StorageDeleted.Load())
@ -1038,7 +1010,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
// Clear the metric markers
s.AccountLoaded, s.AccountUpdated, s.AccountDeleted = 0, 0, 0
s.AccountLoaded, s.AccountDeleted = 0, 0
s.StorageLoaded = 0
s.StorageUpdated.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)
}
// 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.
func (s *StateDB) Witness() *stateless.Witness {
return nil

View file

@ -21,6 +21,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"maps"
"math"
"math/rand"
"reflect"
@ -182,10 +183,11 @@ func (test *stateTest) run() bool {
storages []map[common.Hash]map[common.Hash][]byte
storageOrigin []map[common.Address]map[common.Hash][]byte
copyUpdate = func(update *stateUpdate) {
//accounts = append(accounts, maps.Clone(update.accounts))
//accountOrigin = append(accountOrigin, maps.Clone(update.accountsOrigin))
//storages = append(storages, maps.Clone(update.storages))
//storageOrigin = append(storageOrigin, maps.Clone(update.storagesOrigin))
encoded, _ := update.stateSet()
accounts = append(accounts, maps.Clone(encoded.Accounts))
accountOrigin = append(accountOrigin, maps.Clone(encoded.AccountsOrigin))
storages = append(storages, maps.Clone(encoded.Storages))
storageOrigin = append(storageOrigin, maps.Clone(encoded.StoragesOrigin))
}
disk = rawdb.NewMemoryDatabase()
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/tracing"
"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/hashdb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
@ -540,47 +538,6 @@ func (test *snapshotTest) run() bool {
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.
func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
for _, addr := range test.addrs {
@ -606,12 +563,6 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
}
// Check storage.
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)
// Check dirty storage which is not in trie
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{})
}
// Commit state, ensure states can be loaded from disk
root, _ := state.Commit(0, false, false)
state, _ = New(root, tdb)
root, err := state.Commit(0, false, false)
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 {
t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42)
}

View file

@ -17,12 +17,15 @@
package state
import (
"errors"
"fmt"
"maps"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing"
"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/triedb"
)
@ -30,8 +33,8 @@ import (
// contractCode encapsulates contract bytecode and its associated metadata.
type contractCode struct {
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.
blob []byte // blob is the raw byte representation of the current contract code.
// Derived fields, populated only when state tracking is enabled.
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.
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.
@ -101,7 +104,7 @@ func (sc *stateUpdate) empty() bool {
//
// 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.
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 (
accounts = make(map[common.Hash]*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
// object. This function extracts the necessary data from the stateUpdate
// struct and formats it into the StateSet structure consumed by the triedb
// package.
func (sc *stateUpdate) stateSet() *triedb.StateSet {
return nil
//return &triedb.StateSet{
// Accounts: sc.accounts,
// AccountsOrigin: sc.accountsOrigin,
// Storages: sc.storages,
// StoragesOrigin: sc.storagesOrigin,
// RawStorageKey: sc.rawStorageKey,
//}
func (sc *stateUpdate) stateSet() (*triedb.StateSet, error) {
accounts, accountOrigin, storages, storageOrigin, err := sc.encodeMerkle()
if err != nil {
return nil, err
}
return &triedb.StateSet{
Accounts: accounts,
AccountsOrigin: accountOrigin,
Storages: storages,
StoragesOrigin: storageOrigin,
RawStorageKey: sc.rawStorageKey,
}, nil
}
// 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.
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
return nil, nil
//update := &tracing.StateUpdate{
// OriginRoot: sc.originRoot,
// Root: sc.root,
// BlockNumber: sc.blockNumber,
// AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
// StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
// CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
// TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
//}
//// Gather all account changes
//for addr, oldData := range sc.accountsOrigin {
// addrHash := crypto.Keccak256Hash(addr.Bytes())
// newData, exists := sc.accounts[addrHash]
// if !exists {
// return nil, fmt.Errorf("account %x not found", addr)
// }
// change := &tracing.AccountChange{}
//
// if len(oldData) > 0 {
// acct, err := types.FullAccount(oldData)
// if err != nil {
// return nil, err
// }
// change.Prev = &types.StateAccount{
// Nonce: acct.Nonce,
// Balance: acct.Balance,
// Root: acct.Root,
// CodeHash: acct.CodeHash,
// }
// }
// if len(newData) > 0 {
// acct, err := types.FullAccount(newData)
// if err != nil {
// return nil, err
// }
// change.New = &types.StateAccount{
// Nonce: acct.Nonce,
// Balance: acct.Balance,
// Root: acct.Root,
// CodeHash: acct.CodeHash,
// }
// }
// update.AccountChanges[addr] = change
//}
//
//// Gather all storage slot changes
//for addr, slots := range sc.storagesOrigin {
// addrHash := crypto.Keccak256Hash(addr.Bytes())
// subset, exists := sc.storages[addrHash]
// if !exists {
// return nil, fmt.Errorf("storage %x not found", addr)
// }
// storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
//
// for key, encPrev := range slots {
// // Get new value - handle both raw and hashed key formats
// var (
// exists bool
// encNew []byte
// decPrev []byte
// decNew []byte
// err error
// )
// if sc.rawStorageKey {
// encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())]
// } else {
// encNew, exists = subset[key]
// }
// if !exists {
// return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
// }
//
// // Decode the prev and new values
// if len(encPrev) > 0 {
// _, decPrev, _, err = rlp.Split(encPrev)
// if err != nil {
// return nil, fmt.Errorf("failed to decode prevValue: %v", err)
// }
// }
// if len(encNew) > 0 {
// _, decNew, _, err = rlp.Split(encNew)
// if err != nil {
// return nil, fmt.Errorf("failed to decode newValue: %v", err)
// }
// }
// storageChanges[key] = &tracing.StorageChange{
// Prev: common.BytesToHash(decPrev),
// New: common.BytesToHash(decNew),
// }
// }
// update.StorageChanges[addr] = storageChanges
//}
//
//// Gather all contract code changes
//for addr, code := range sc.codes {
// change := &tracing.CodeChange{
// New: &tracing.ContractCode{
// Hash: code.hash,
// Code: code.blob,
// Exists: code.duplicate,
// },
// }
// if code.originHash != types.EmptyCodeHash {
// change.Prev = &tracing.ContractCode{
// Hash: code.originHash,
// Code: code.originBlob,
// Exists: true,
// }
// }
// update.CodeChanges[addr] = change
//}
//
//// 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
update := &tracing.StateUpdate{
OriginRoot: sc.originRoot,
Root: sc.root,
BlockNumber: sc.blockNumber,
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
}
// Gather all account changes
for addr, oldData := range sc.accountsOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
newData, exists := sc.accounts[addrHash]
if !exists {
return nil, fmt.Errorf("account %x not found", addr)
}
hashes := sc.secondaryHashes[addr]
change := &tracing.AccountChange{}
if oldData != nil {
change.Prev = &types.StateAccount{
Nonce: oldData.Nonce,
Balance: oldData.Balance,
Root: hashes.Prev,
CodeHash: oldData.CodeHash,
}
}
if newData != nil {
change.New = &types.StateAccount{
Nonce: newData.Nonce,
Balance: newData.Balance,
Root: hashes.Hash,
CodeHash: newData.CodeHash,
}
}
update.AccountChanges[addr] = change
}
// Gather all storage slot changes
for addr, slots := range sc.storagesOrigin {
addrHash := crypto.Keccak256Hash(addr.Bytes())
subset, exists := sc.storages[addrHash]
if !exists {
return nil, fmt.Errorf("storage %x not found", addr)
}
storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
for key, prev := range slots {
// Get new value - handle both raw and hashed key formats
var (
exists bool
current common.Hash
)
if sc.rawStorageKey {
current, exists = subset[crypto.Keccak256Hash(key.Bytes())]
} else {
current, exists = subset[key]
}
if !exists {
return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
}
storageChanges[key] = &tracing.StorageChange{
Prev: prev,
New: current,
}
}
update.StorageChanges[addr] = storageChanges
}
// Gather all contract code changes
for addr, code := range sc.codes {
change := &tracing.CodeChange{
New: &tracing.ContractCode{
Hash: code.hash,
Code: code.blob,
Exists: code.duplicate,
},
}
if code.originHash != types.EmptyCodeHash {
change.Prev = &tracing.ContractCode{
Hash: code.originHash,
Code: code.originBlob,
Exists: true,
}
}
update.CodeChanges[addr] = change
}
// 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) {
storageRoot := statedb.GetStorageRoot(address)
if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) {
return StorageRangeResult{}, nil // empty storage
it, err := statedb.Database().Iteratee(root)
if err != nil {
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
// 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{}}
for i := 0; i < maxResult && it.Next(); i++ {
_, content, _, err := rlp.Split(it.Value)
for i := 0; i < maxResult && storageIt.Next(); i++ {
_, content, _, err := rlp.Split(storageIt.Slot())
if err != nil {
return StorageRangeResult{}, err
}
e := storageEntry{Value: common.BytesToHash(content)}
if preimage := tr.GetKey(it.Key); preimage != nil {
preimage := common.BytesToHash(preimage)
if preimage, err := storageIt.Key(); err == nil {
e.Key = &preimage
}
result.Storage[common.BytesToHash(it.Key)] = e
result.Storage[storageIt.Hash()] = e
}
// Add the 'next key' so clients can continue downloading.
if it.Next() {
next := common.BytesToHash(it.Key)
if storageIt.Next() {
next := storageIt.Hash()
result.NextKey = &next
}
return result, nil

View file

@ -388,17 +388,15 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
return nil, err
}
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 {
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.
for i, key := range keys {
if err := ctx.Err(); err != nil {
@ -414,12 +412,8 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
} else {
outputKey = hexutil.Encode(key[:])
}
if storageTrie == nil {
storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}}
continue
}
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
}
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.
tr, err := statedb.Database().OpenTrie(header.Root)
if err != nil {
return nil, err
}
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
}
balance := statedb.GetBalance(address).ToBig()
@ -442,7 +432,7 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
Balance: (*hexutil.Big)(balance),
CodeHash: codeHash,
Nonce: hexutil.Uint64(statedb.GetNonce(address)),
StorageHash: storageRoot,
//StorageHash: storageRoot, // TODO(rjl493456442)
StorageProof: storageProof,
}, statedb.Error()
}