diff --git a/core/blockchain.go b/core/blockchain.go index 35b2d35dc7..b88b1bcb32 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -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 diff --git a/core/state/database.go b/core/state/database.go index e790bb3a63..3ea8a68745 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -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, diff --git a/core/state/database_hasher.go b/core/state/database_hasher.go index 63d7aa25c4..2b8d407139 100644 --- a/core/state/database_hasher.go +++ b/core/state/database_hasher.go @@ -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) } diff --git a/core/state/state_mut.go b/core/state/state_mut.go new file mode 100644 index 0000000000..c62b152633 --- /dev/null +++ b/core/state/state_mut.go @@ -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 . + +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 +} diff --git a/core/state/state_object.go b/core/state/state_object.go index 165c216649..491a2752ec 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -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{} -} diff --git a/core/state/state_sizer.go b/core/state/state_sizer.go index 16b8aec444..7ae22c8069 100644 --- a/core/state/state_sizer.go +++ b/core/state/state_sizer.go @@ -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 { diff --git a/core/state/statedb.go b/core/state/statedb.go index 02ac12cf7b..fdf86528fd 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -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 diff --git a/core/state/statedb_fuzz_test.go b/core/state/statedb_fuzz_test.go index 82866628f8..453fc7fe5d 100644 --- a/core/state/statedb_fuzz_test.go +++ b/core/state/statedb_fuzz_test.go @@ -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}) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 3ec715d237..8d56699919 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -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) } diff --git a/core/state/stateupdate.go b/core/state/stateupdate.go index 6402733f4c..8a2bb34065 100644 --- a/core/state/stateupdate.go +++ b/core/state/stateupdate.go @@ -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 } diff --git a/eth/api_debug.go b/eth/api_debug.go index 5dd535e672..b55c3c26c2 100644 --- a/eth/api_debug.go +++ b/eth/api_debug.go @@ -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 diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 149e12c5b8..c67dff774c 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -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() }