core/state: build hasher skeleton

This commit is contained in:
Gary Rong 2026-03-20 15:01:44 +08:00 committed by CPerezz
parent 9daaef1923
commit 1ae462f08d
No known key found for this signature in database
GPG key ID: 62045F34B97177DD
12 changed files with 551 additions and 797 deletions

View file

@ -17,8 +17,6 @@
package state package state
import ( import (
"fmt"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/overlay" "github.com/ethereum/go-ethereum/core/overlay"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -26,10 +24,8 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie" "github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/transitiontrie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
) )
@ -43,6 +39,9 @@ type Database interface {
// through which the account iterator and storage iterator can be created. // through which the account iterator and storage iterator can be created.
Iteratee(root common.Hash) (Iteratee, error) Iteratee(root common.Hash) (Iteratee, error)
// Hasher returns a state hasher associated with the specified state root.
Hasher(root common.Hash) (Hasher, error)
// OpenTrie opens the main account trie. // OpenTrie opens the main account trie.
OpenTrie(root common.Hash) (Trie, error) OpenTrie(root common.Hash) (Trie, error)
@ -221,6 +220,10 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
return newReader(db.codedb.Reader(), sr), nil return newReader(db.codedb.Reader(), sr), nil
} }
func (db *CachingDB) Hasher(stateRoot common.Hash) (Hasher, error) {
return &noopHasher{}, nil
}
// ReadersWithCacheStats creates a pair of state readers that share the same // ReadersWithCacheStats creates a pair of state readers that share the same
// underlying state reader and internal state cache, while maintaining separate // underlying state reader and internal state cache, while maintaining separate
// statistics respectively. // statistics respectively.
@ -297,16 +300,16 @@ func (db *CachingDB) Commit(update *stateUpdate) error {
} }
// If snapshotting is enabled, update the snapshot tree with this new version // If snapshotting is enabled, update the snapshot tree with this new version
if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil { if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil {
if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil { //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) // 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. //// Keep 128 diff layers in the memory, persistent layer is 129th.
// - head layer is paired with HEAD state //// - head layer is paired with HEAD state
// - head-1 layer is paired with HEAD-1 state //// - head-1 layer is paired with HEAD-1 state
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state //// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
if err := db.snap.Cap(update.root, TriesInMemory); err != nil { //if err := db.snap.Cap(update.root, TriesInMemory); err != nil {
log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err) // 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()) return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
} }
@ -316,15 +319,3 @@ func (db *CachingDB) Commit(update *stateUpdate) error {
func (db *CachingDB) Iteratee(root common.Hash) (Iteratee, error) { func (db *CachingDB) Iteratee(root common.Hash) (Iteratee, error) {
return newStateIteratee(!db.triedb.IsVerkle(), root, db.triedb, db.snap) return newStateIteratee(!db.triedb.IsVerkle(), root, db.triedb, db.snap)
} }
// mustCopyTrie returns a deep-copied trie.
func mustCopyTrie(t Trie) Trie {
switch t := t.(type) {
case *trie.StateTrie:
return t.Copy()
case *transitiontrie.TransitionTrie:
return t.Copy()
default:
panic(fmt.Errorf("unknown trie type %T", t))
}
}

View file

@ -25,10 +25,17 @@ import (
// AccountMutation describes a state transition for a single account. // AccountMutation describes a state transition for a single account.
type AccountMutation struct { type AccountMutation struct {
Account *Account // Null for deletion Account *Account // Null for deletion
DirtyCode bool // Flag whether the code is changed
Code []byte // Null for deletion
}
CodeDirty bool // Flag whether the code is changed // SecondaryHash encapsulates the secondary hash of storage tries.
Code []byte // Null for deletion // 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
} }
// Hasher defines the minimal interface for computing state root hashes. // Hasher defines the minimal interface for computing state root hashes.
@ -63,7 +70,7 @@ type Hasher interface {
// Additionally, if the hasher uses a two-layer structure, the roots of the // Additionally, if the hasher uses a two-layer structure, the roots of the
// secondary tries together with their original hashes will also be returned // secondary tries together with their original hashes will also be returned
// for all mutated accounts, regardless of whether their storage was modified. // for all mutated accounts, regardless of whether their storage was modified.
Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]common.Hash, map[common.Address]common.Hash, error) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]SecondaryHash, error)
// Copy returns a deep-copied hasher instance. // Copy returns a deep-copied hasher instance.
Copy() Hasher Copy() Hasher
@ -112,3 +119,30 @@ type Prover interface {
// the nodes required to prove its absence. // the nodes required to prove its absence.
ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error
} }
type noopHasher struct{}
func (n noopHasher) UpdateAccount(addresses []common.Address, accounts []AccountMutation) error {
//TODO implement me
panic("implement me")
}
func (n noopHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error {
//TODO implement me
panic("implement me")
}
func (n noopHasher) Hash() common.Hash {
//TODO implement me
panic("implement me")
}
func (n noopHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]SecondaryHash, error) {
//TODO implement me
panic("implement me")
}
func (n noopHasher) Copy() Hasher {
//TODO implement me
panic("implement me")
}

View file

@ -44,7 +44,7 @@ func newHistoricStateReader(r *pathdb.HistoricalStateReader) *historicStateReade
} }
// Account implements StateReader, retrieving the account specified by the address. // Account implements StateReader, retrieving the account specified by the address.
func (r *historicStateReader) Account(addr common.Address) (*types.StateAccount, error) { func (r *historicStateReader) Account(addr common.Address) (*Account, error) {
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
@ -55,18 +55,14 @@ func (r *historicStateReader) Account(addr common.Address) (*types.StateAccount,
if account == nil { if account == nil {
return nil, nil return nil, nil
} }
acct := &types.StateAccount{ acct := &Account{
Nonce: account.Nonce, Nonce: account.Nonce,
Balance: account.Balance, Balance: account.Balance,
CodeHash: account.CodeHash, CodeHash: account.CodeHash,
Root: common.BytesToHash(account.Root),
} }
if len(acct.CodeHash) == 0 { if len(acct.CodeHash) == 0 {
acct.CodeHash = types.EmptyCodeHash.Bytes() acct.CodeHash = types.EmptyCodeHash.Bytes()
} }
if acct.Root == (common.Hash{}) {
acct.Root = types.EmptyRootHash
}
return acct, nil return acct, nil
} }
@ -150,17 +146,25 @@ func newHistoricalTrieReader(root common.Hash, r *pathdb.HistoricalNodeReader) (
} }
// account is the inner version of Account and assumes the r.lock is already held. // account is the inner version of Account and assumes the r.lock is already held.
func (r *historicalTrieReader) account(addr common.Address) (*types.StateAccount, error) { func (r *historicalTrieReader) account(addr common.Address) (*Account, error) {
account, err := r.tr.GetAccount(addr) account, err := r.tr.GetAccount(addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if account == nil { if account == nil {
r.subRoots[addr] = types.EmptyRootHash r.subRoots[addr] = types.EmptyRootHash
return nil, nil
} else { } else {
r.subRoots[addr] = account.Root r.subRoots[addr] = account.Root
// Account objects resolved from the trie always include
// the full code hash.
return &Account{
Nonce: account.Nonce,
Balance: account.Balance,
CodeHash: account.CodeHash,
}, nil
} }
return account, nil
} }
// Account implements StateReader, retrieving the account specified by the address. // Account implements StateReader, retrieving the account specified by the address.
@ -169,7 +173,7 @@ func (r *historicalTrieReader) account(addr common.Address) (*types.StateAccount
// the requested account is not yet covered by the snapshot. // the requested account is not yet covered by the snapshot.
// //
// The returned account might be nil if it's not existent. // The returned account might be nil if it's not existent.
func (r *historicalTrieReader) Account(addr common.Address) (*types.StateAccount, error) { func (r *historicalTrieReader) Account(addr common.Address) (*Account, error) {
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
@ -255,6 +259,10 @@ func (db *HistoricDB) Reader(stateRoot common.Hash) (Reader, error) {
return newReader(db.codedb.Reader(), combined), nil return newReader(db.codedb.Reader(), combined), nil
} }
func (db *HistoricDB) Hasher(stateRoot common.Hash) (Hasher, error) {
return &noopHasher{}, nil
}
// OpenTrie opens the main account trie. It's not supported by historic database. // OpenTrie opens the main account trie. It's not supported by historic database.
func (db *HistoricDB) OpenTrie(root common.Hash) (Trie, error) { func (db *HistoricDB) OpenTrie(root common.Hash) (Trie, error) {
nr, err := db.triedb.HistoricNodeReader(root) nr, err := db.triedb.HistoricNodeReader(root)

View file

@ -168,7 +168,11 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
address = &addrBytes address = &addrBytes
account.Address = address account.Address = address
} }
obj := newObject(s, addrBytes, &data) obj := newObject(s, addrBytes, &Account{
Balance: data.Balance,
Nonce: data.Nonce,
CodeHash: data.CodeHash,
})
if !conf.SkipCode { if !conf.SkipCode {
account.Code = obj.Code() account.Code = obj.Code()
} }

View file

@ -71,6 +71,19 @@ func newEmptyAccount() *Account {
} }
} }
// copy returns a deep-copied account object.
func (acct *Account) copy() *Account {
var balance *uint256.Int
if acct.Balance != nil {
balance = new(uint256.Int).Set(acct.Balance)
}
return &Account{
Nonce: acct.Nonce,
Balance: balance,
CodeHash: common.CopyBytes(acct.CodeHash),
}
}
// StateReader defines the interface for accessing accounts and storage slots // StateReader defines the interface for accessing accounts and storage slots
// associated with a specific state. // associated with a specific state.
// //
@ -81,7 +94,7 @@ type StateReader interface {
// - Returns a nil account if it does not exist // - Returns a nil account if it does not exist
// - Returns an error only if an unexpected issue occurs // - Returns an error only if an unexpected issue occurs
// - The returned account is safe to modify after the call // - The returned account is safe to modify after the call
Account(addr common.Address) (*types.StateAccount, error) Account(addr common.Address) (*Account, error)
// Storage retrieves the storage slot associated with a particular account // Storage retrieves the storage slot associated with a particular account
// address and slot key. // address and slot key.
@ -118,7 +131,7 @@ func newFlatReader(reader database.StateReader) *flatReader {
// the requested account is not yet covered by the snapshot. // the requested account is not yet covered by the snapshot.
// //
// The returned account might be nil if it's not existent. // The returned account might be nil if it's not existent.
func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) { func (r *flatReader) Account(addr common.Address) (*Account, error) {
account, err := r.reader.Account(crypto.Keccak256Hash(addr[:])) account, err := r.reader.Account(crypto.Keccak256Hash(addr[:]))
if err != nil { if err != nil {
return nil, err return nil, err
@ -126,18 +139,16 @@ func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
if account == nil { if account == nil {
return nil, nil return nil, nil
} }
acct := &types.StateAccount{ acct := &Account{
Nonce: account.Nonce, Nonce: account.Nonce,
Balance: account.Balance, Balance: account.Balance,
CodeHash: account.CodeHash, CodeHash: account.CodeHash,
Root: common.BytesToHash(account.Root),
} }
// Account objects resolved from the flat state always omit the
// empty code hash.
if len(acct.CodeHash) == 0 { if len(acct.CodeHash) == 0 {
acct.CodeHash = types.EmptyCodeHash.Bytes() acct.CodeHash = types.EmptyCodeHash.Bytes()
} }
if acct.Root == (common.Hash{}) {
acct.Root = types.EmptyRootHash
}
return acct, nil return acct, nil
} }
@ -242,24 +253,32 @@ func newTrieReader(root common.Hash, db *triedb.Database) (*trieReader, error) {
} }
// account is the inner version of Account and assumes the r.lock is already held. // account is the inner version of Account and assumes the r.lock is already held.
func (r *trieReader) account(addr common.Address) (*types.StateAccount, error) { func (r *trieReader) account(addr common.Address) (*Account, error) {
account, err := r.mainTrie.GetAccount(addr) account, err := r.mainTrie.GetAccount(addr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if account == nil { if account == nil {
r.subRoots[addr] = types.EmptyRootHash r.subRoots[addr] = types.EmptyRootHash
return nil, nil
} else { } else {
r.subRoots[addr] = account.Root r.subRoots[addr] = account.Root
// Account objects resolved from the trie always include
// the full code hash.
return &Account{
Nonce: account.Nonce,
Balance: account.Balance,
CodeHash: account.CodeHash,
}, nil
} }
return account, nil
} }
// Account implements StateReader, retrieving the account specified by the address. // Account implements StateReader, retrieving the account specified by the address.
// //
// An error will be returned if the trie state is corrupted. An nil account // An error will be returned if the trie state is corrupted. An nil account
// will be returned if it's not existent in the trie. // will be returned if it's not existent in the trie.
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) { func (r *trieReader) Account(addr common.Address) (*Account, error) {
r.lock.Lock() r.lock.Lock()
defer r.lock.Unlock() defer r.lock.Unlock()
@ -340,7 +359,7 @@ func newMultiStateReader(readers ...StateReader) (*multiStateReader, error) {
// - Returns a nil account if it does not exist // - Returns a nil account if it does not exist
// - Returns an error only if an unexpected issue occurs // - Returns an error only if an unexpected issue occurs
// - The returned account is safe to modify after the call // - The returned account is safe to modify after the call
func (r *multiStateReader) Account(addr common.Address) (*types.StateAccount, error) { func (r *multiStateReader) Account(addr common.Address) (*Account, error) {
var errs []error var errs []error
for _, reader := range r.readers { for _, reader := range r.readers {
acct, err := reader.Account(addr) acct, err := reader.Account(addr)
@ -376,7 +395,7 @@ type stateReaderWithCache struct {
StateReader StateReader
// Previously resolved state entries. // Previously resolved state entries.
accounts map[common.Address]*types.StateAccount accounts map[common.Address]*Account
accountLock sync.RWMutex accountLock sync.RWMutex
// List of storage buckets, each of which is thread-safe. // List of storage buckets, each of which is thread-safe.
@ -393,7 +412,7 @@ type stateReaderWithCache struct {
func newStateReaderWithCache(sr StateReader) *stateReaderWithCache { func newStateReaderWithCache(sr StateReader) *stateReaderWithCache {
r := &stateReaderWithCache{ r := &stateReaderWithCache{
StateReader: sr, StateReader: sr,
accounts: make(map[common.Address]*types.StateAccount), accounts: make(map[common.Address]*Account),
} }
for i := range r.storageBuckets { for i := range r.storageBuckets {
r.storageBuckets[i].storages = make(map[common.Address]map[common.Hash]common.Hash) r.storageBuckets[i].storages = make(map[common.Address]map[common.Hash]common.Hash)
@ -406,7 +425,7 @@ func newStateReaderWithCache(sr StateReader) *stateReaderWithCache {
// might be nil if it's not existent. // might be nil if it's not existent.
// //
// An error will be returned if the state is corrupted in the underlying reader. // An error will be returned if the state is corrupted in the underlying reader.
func (r *stateReaderWithCache) account(addr common.Address) (*types.StateAccount, bool, error) { func (r *stateReaderWithCache) account(addr common.Address) (*Account, bool, error) {
// Try to resolve the requested account in the local cache // Try to resolve the requested account in the local cache
r.accountLock.RLock() r.accountLock.RLock()
acct, ok := r.accounts[addr] acct, ok := r.accounts[addr]
@ -429,7 +448,7 @@ func (r *stateReaderWithCache) account(addr common.Address) (*types.StateAccount
// The returned account might be nil if it's not existent. // The returned account might be nil if it's not existent.
// //
// An error will be returned if the state is corrupted in the underlying reader. // An error will be returned if the state is corrupted in the underlying reader.
func (r *stateReaderWithCache) Account(addr common.Address) (*types.StateAccount, error) { func (r *stateReaderWithCache) Account(addr common.Address) (*Account, error) {
account, _, err := r.account(addr) account, _, err := r.account(addr)
return account, err return account, err
} }
@ -502,7 +521,7 @@ func newStateReaderWithStats(sr *stateReaderWithCache) *stateReaderWithStats {
// The returned account might be nil if it's not existent. // The returned account might be nil if it's not existent.
// //
// An error will be returned if the state is corrupted in the underlying reader. // An error will be returned if the state is corrupted in the underlying reader.
func (r *stateReaderWithStats) Account(addr common.Address) (*types.StateAccount, error) { func (r *stateReaderWithStats) Account(addr common.Address) (*Account, error) {
account, incache, err := r.stateReaderWithCache.account(addr) account, incache, err := r.stateReaderWithCache.account(addr)
if err != nil { if err != nil {
return nil, err return nil, err

View file

@ -27,11 +27,6 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/bintrie"
"github.com/ethereum/go-ethereum/trie/transitiontrie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -49,13 +44,12 @@ func (s Storage) Copy() Storage {
// - Finally, call commit to return the changes of storage trie and update account data. // - Finally, call commit to return the changes of storage trie and update account data.
type stateObject struct { type stateObject struct {
db *StateDB db *StateDB
address common.Address // address of ethereum account address common.Address // address of ethereum account
addressHash *common.Hash // hash of ethereum address of the account addressHash *common.Hash // hash of ethereum address of the account
origin *types.StateAccount // Account original data without any change applied, nil means it was not existent origin *Account // Account original data without any change applied, nil means it was not existent
data types.StateAccount // Account data with all mutations applied in the scope of block data Account // Account data with all mutations applied in the scope of block
// Write caches. // Write caches.
trie Trie // storage trie, which becomes non-nil on first access
code []byte // contract bytecode, which gets set when code is loaded code []byte // contract bytecode, which gets set when code is loaded
originStorage Storage // Storage entries that have been accessed within the current block originStorage Storage // Storage entries that have been accessed within the current block
@ -94,10 +88,10 @@ func (s *stateObject) empty() bool {
} }
// newObject creates a state object. // newObject creates a state object.
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject { func newObject(db *StateDB, address common.Address, acct *Account) *stateObject {
origin := acct origin := acct
if acct == nil { if acct == nil {
acct = types.NewEmptyStateAccount() acct = newEmptyAccount()
} }
return &stateObject{ return &stateObject{
db: db, db: db,
@ -133,15 +127,7 @@ func (s *stateObject) touch() {
// If a new trie is opened, it will be cached within the state object to allow // 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. // subsequent reads to expand the same trie instead of reloading from disk.
func (s *stateObject) getTrie() (Trie, error) { func (s *stateObject) getTrie() (Trie, error) {
if s.trie == nil { return nil, nil
// Assumes the primary account trie is already loaded
tr, err := s.db.db.OpenStorageTrie(s.db.originalRoot, s.address, s.data.Root, s.db.trie)
if err != nil {
return nil, err
}
s.trie = tr
}
return s.trie, nil
} }
// GetState retrieves a value associated with the given storage key. // GetState retrieves a value associated with the given storage key.
@ -268,36 +254,16 @@ func (s *stateObject) finalise() {
} }
// updateTrie is responsible for persisting cached storage changes into the // updateTrie is responsible for persisting cached storage changes into the
// object's storage trie. In case the storage trie is not yet loaded, this // state hasher. It assumes all the dirty storage slots have been finalized
// function will load the trie automatically. If any issues arise during the // before.
// loading or updating of the trie, an error will be returned. Furthermore, func (s *stateObject) updateTrie() error {
// this function will return the mutated storage trie, or nil if there is no
// storage change at all.
//
// It assumes all the dirty storage slots have been finalized before.
func (s *stateObject) updateTrie() (Trie, error) {
// Short circuit if nothing was accessed // Short circuit if nothing was accessed
if len(s.uncommittedStorage) == 0 { if len(s.uncommittedStorage) == 0 {
return s.trie, nil return nil
} }
// Fetcher not running or empty trie, fallback to the database trie
tr, err := s.getTrie()
if err != nil {
s.db.setError(err)
return nil, err
}
// Perform trie updates before deletions. This prevents resolution of unnecessary trie nodes
// in circumstances similar to the following:
//
// Consider nodes `A` and `B` who share the same full node parent `P` and have no other siblings.
// During the execution of a block:
// - `A` is deleted,
// - `C` is created, and also shares the parent `P`.
// If the deletion is handled first, then `P` would be left with only one child, thus collapsed
// into a shortnode. This requires `B` to be resolved from disk.
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
var ( var (
deletions []common.Hash keys = make([]common.Hash, 0, len(s.uncommittedStorage))
vals = make([]common.Hash, 0, len(s.uncommittedStorage))
) )
for key, origin := range s.uncommittedStorage { for key, origin := range s.uncommittedStorage {
// Skip noop changes, persist actual changes // Skip noop changes, persist actual changes
@ -310,51 +276,16 @@ func (s *stateObject) updateTrie() (Trie, error) {
log.Error("Storage slot is not found in pending area", "address", s.address, "slot", key) log.Error("Storage slot is not found in pending area", "address", s.address, "slot", key)
continue continue
} }
if (value != common.Hash{}) { keys = append(keys, key)
if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil { vals = append(vals, value)
s.db.setError(err)
return nil, err
}
s.db.StorageUpdated.Add(1)
} else {
deletions = append(deletions, key)
}
}
for _, key := range deletions {
if err := tr.DeleteStorage(s.address, key[:]); err != nil {
s.db.setError(err)
return nil, err
}
s.db.StorageDeleted.Add(1)
} }
s.uncommittedStorage = make(Storage) // empties the commit markers s.uncommittedStorage = make(Storage) // empties the commit markers
return tr, nil return s.db.hasher.UpdateStorage(s.address, keys, vals)
}
// updateRoot flushes all cached storage mutations to trie, recalculating the
// new storage trie root.
func (s *stateObject) updateRoot() {
// Flush cached storage mutations into trie, short circuit if any error
// is occurred or there is no change in the trie.
tr, err := s.updateTrie()
if err != nil || tr == nil {
return
}
s.data.Root = tr.Hash()
} }
// commitStorage overwrites the clean storage with the storage changes and // commitStorage overwrites the clean storage with the storage changes and
// fulfills the storage diffs into the given accountUpdate struct. // fulfills the storage diffs into the given accountUpdate struct.
func (s *stateObject) commitStorage(op *accountUpdate) { func (s *stateObject) commitStorage(op *accountUpdate) {
var (
encode = func(val common.Hash) []byte {
if val == (common.Hash{}) {
return nil
}
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
return blob
}
)
for key, val := range s.pendingStorage { for key, val := range s.pendingStorage {
// Skip the noop storage changes, it might be possible the value // Skip the noop storage changes, it might be possible the value
// of tracked slot is same in originStorage and pendingStorage // of tracked slot is same in originStorage and pendingStorage
@ -365,17 +296,17 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
} }
hash := crypto.Keccak256Hash(key[:]) hash := crypto.Keccak256Hash(key[:])
if op.storages == nil { if op.storages == nil {
op.storages = make(map[common.Hash][]byte) op.storages = make(map[common.Hash]common.Hash)
} }
op.storages[hash] = encode(val) op.storages[hash] = val
if op.storagesOriginByKey == nil { if op.storagesOriginByKey == nil {
op.storagesOriginByKey = make(map[common.Hash][]byte) op.storagesOriginByKey = make(map[common.Hash]common.Hash)
} }
if op.storagesOriginByHash == nil { if op.storagesOriginByHash == nil {
op.storagesOriginByHash = make(map[common.Hash][]byte) op.storagesOriginByHash = make(map[common.Hash]common.Hash)
} }
origin := encode(s.originStorage[key]) origin := s.originStorage[key]
op.storagesOriginByKey[key] = origin op.storagesOriginByKey[key] = origin
op.storagesOriginByHash[hash] = origin op.storagesOriginByHash[hash] = origin
@ -390,14 +321,12 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
// //
// Note, commit may run concurrently across all the state objects. Do not assume // Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb. // thread-safe access to the statedb.
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) { func (s *stateObject) commit() (*accountUpdate, error) {
// commit the account metadata changes // commit the account metadata changes
op := &accountUpdate{ op := &accountUpdate{
address: s.address, address: s.address,
data: types.SlimAccountRLP(s.data), data: &s.data,
} origin: s.origin,
if s.origin != nil {
op.origin = types.SlimAccountRLP(*s.origin)
} }
// commit the contract code if it's modified // commit the contract code if it's modified
if s.dirtyCode { if s.dirtyCode {
@ -415,24 +344,8 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
} }
// Commit storage changes and the associated storage trie // Commit storage changes and the associated storage trie
s.commitStorage(op) s.commitStorage(op)
if len(op.storages) == 0 { s.origin = s.data.copy()
// nothing changed, don't bother to commit the trie return op, nil
s.origin = s.data.Copy()
return op, nil, nil
}
// In Verkle/binary trie mode, all state objects share one unified trie.
// The main account trie commit in stateDB.commit() already calls
// CollectNodes on this trie, so calling Commit here again would
// redundantly traverse and serialize the entire tree per dirty account.
if s.db.GetTrie().IsVerkle() {
s.origin = s.data.Copy()
return op, nil, nil
}
// The storage trie root is omitted, as it has already been updated in the
// previous updateRoot step.
_, nodes := s.trie.Commit(false)
s.origin = s.data.Copy()
return op, nodes, nil
} }
// AddBalance adds amount to s's balance. // AddBalance adds amount to s's balance.
@ -478,21 +391,6 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
selfDestructed: s.selfDestructed, selfDestructed: s.selfDestructed,
newContract: s.newContract, newContract: s.newContract,
} }
switch s.trie.(type) {
case *bintrie.BinaryTrie:
// UBT uses only one tree, and the copy has already been
// made in mustCopyTrie.
obj.trie = db.trie
case *transitiontrie.TransitionTrie:
// Same thing for the transition tree, since the MPT is
// read-only.
obj.trie = db.trie
case *trie.StateTrie:
obj.trie = mustCopyTrie(s.trie)
case nil:
// do nothing
}
return obj return obj
} }
@ -584,5 +482,5 @@ func (s *stateObject) Nonce() uint64 {
} }
func (s *stateObject) Root() common.Hash { func (s *stateObject) Root() common.Hash {
return s.data.Root return common.Hash{}
} }

View file

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

View file

@ -23,7 +23,6 @@ import (
"maps" "maps"
"slices" "slices"
"sort" "sort"
"sync"
"sync/atomic" "sync/atomic"
"time" "time"
@ -33,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -76,7 +76,7 @@ func (m *mutation) isDelete() bool {
type StateDB struct { type StateDB struct {
db Database db Database
reader Reader reader Reader
trie Trie // it's resolved on first access hasher Hasher
// originalRoot is the pre-state root, before any changes were made. // originalRoot is the pre-state root, before any changes were made.
// It will be updated when the Commit is called. // It will be updated when the Commit is called.
@ -172,10 +172,15 @@ func New(root common.Hash, db Database) (*StateDB, error) {
// NewWithReader creates a new state for the specified state root. Unlike New, // NewWithReader creates a new state for the specified state root. Unlike New,
// this function accepts an additional Reader which is bound to the given root. // this function accepts an additional Reader which is bound to the given root.
func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, error) { func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, error) {
hasher, err := db.Hasher(root)
if err != nil {
return nil, err
}
sdb := &StateDB{ sdb := &StateDB{
db: db, db: db,
originalRoot: root, originalRoot: root,
reader: reader, reader: reader,
hasher: hasher,
stateObjects: make(map[common.Address]*stateObject), stateObjects: make(map[common.Address]*stateObject),
stateObjectsDestruct: make(map[common.Address]*stateObject), stateObjectsDestruct: make(map[common.Address]*stateObject),
mutations: make(map[common.Address]*mutation), mutations: make(map[common.Address]*mutation),
@ -522,24 +527,6 @@ func (s *StateDB) GetTransientState(addr common.Address, key common.Hash) common
// Setting, updating & deleting state object methods. // Setting, updating & deleting state object methods.
// //
// updateStateObject writes the given object to the trie.
func (s *StateDB) updateStateObject(obj *stateObject) {
// Encode the account and update the account trie
if err := s.trie.UpdateAccount(obj.Address(), &obj.data, len(obj.code)); err != nil {
s.setError(fmt.Errorf("updateStateObject (%x) error: %v", obj.Address(), err))
}
if obj.dirtyCode {
s.trie.UpdateContractCode(obj.Address(), common.BytesToHash(obj.CodeHash()), obj.code)
}
}
// deleteStateObject removes the given object from the state trie.
func (s *StateDB) deleteStateObject(addr common.Address) {
if err := s.trie.DeleteAccount(addr); err != nil {
s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
}
}
// getStateObject retrieves a state object given by the address, returning nil if // getStateObject retrieves a state object given by the address, returning nil if
// the object is not found or was deleted in this execution context. // the object is not found or was deleted in this execution context.
func (s *StateDB) getStateObject(addr common.Address) *stateObject { func (s *StateDB) getStateObject(addr common.Address) *stateObject {
@ -631,6 +618,7 @@ func (s *StateDB) Copy() *StateDB {
state := &StateDB{ state := &StateDB{
db: s.db, db: s.db,
reader: s.reader, reader: s.reader,
hasher: s.hasher.Copy(),
originalRoot: s.originalRoot, originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)), stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)), stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)),
@ -653,9 +641,6 @@ func (s *StateDB) Copy() *StateDB {
transientStorage: s.transientStorage.Copy(), transientStorage: s.transientStorage.Copy(),
journal: s.journal.copy(), journal: s.journal.copy(),
} }
if s.trie != nil {
state.trie = mustCopyTrie(s.trie)
}
if s.accessEvents != nil { if s.accessEvents != nil {
state.accessEvents = s.accessEvents.Copy() state.accessEvents = s.accessEvents.Copy()
} }
@ -776,20 +761,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// Finalise all the dirty storage states and write them into the tries // Finalise all the dirty storage states and write them into the tries
s.Finalise(deleteEmptyObjects) s.Finalise(deleteEmptyObjects)
// Initialize the trie if it's not constructed yet. If the prefetch
// is enabled, the trie constructed below will be replaced by the
// prefetched one.
//
// This operation must be done before state object storage hashing,
// as it assumes the main trie is already loaded.
if s.trie == nil {
tr, err := s.db.OpenTrie(s.originalRoot)
if err != nil {
s.setError(err)
return common.Hash{}
}
s.trie = tr
}
// Process all storage updates concurrently. The state object update root // Process all storage updates concurrently. The state object update root
// method will internally call a blocking trie fetch from the prefetcher, // method will internally call a blocking trie fetch from the prefetcher,
// so there's no need to explicitly wait for the prefetchers to finish. // so there's no need to explicitly wait for the prefetchers to finish.
@ -797,62 +768,12 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
start = time.Now() start = time.Now()
workers errgroup.Group workers errgroup.Group
) )
if s.db.TrieDB().IsVerkle() { for addr, op := range s.mutations {
// Bypass per-account updateTrie() for binary trie. In binary trie mode if op.applied || op.isDelete() {
// there is only one unified trie (OpenStorageTrie returns self), so the continue
// per-account trie setup in updateTrie() (getPrefetchedTrie, getTrie,
// prefetcher.used) is redundant overhead. Apply all storage updates
// directly in a single pass.
for addr, op := range s.mutations {
if op.applied || op.isDelete() {
continue
}
obj := s.stateObjects[addr]
if len(obj.uncommittedStorage) == 0 {
continue
}
for key, origin := range obj.uncommittedStorage {
value, exist := obj.pendingStorage[key]
if value == origin || !exist {
continue
}
if (value != common.Hash{}) {
if err := s.trie.UpdateStorage(addr, key[:], common.TrimLeftZeroes(value[:])); err != nil {
s.setError(err)
}
s.StorageUpdated.Add(1)
} else {
if err := s.trie.DeleteStorage(addr, key[:]); err != nil {
s.setError(err)
}
s.StorageDeleted.Add(1)
}
}
}
// Clear uncommittedStorage and assign trie on each touched object.
// obj.trie must be set because this path bypasses updateTrie(), which
// is where obj.trie normally gets lazily loaded via getTrie().
for addr, op := range s.mutations {
if op.applied || op.isDelete() {
continue
}
obj := s.stateObjects[addr]
if len(obj.uncommittedStorage) > 0 {
obj.uncommittedStorage = make(Storage)
}
obj.trie = s.trie
}
} else {
for addr, op := range s.mutations {
if op.applied || op.isDelete() {
continue
}
obj := s.stateObjects[addr] // closure for the task runner below
workers.Go(func() error {
obj.updateRoot()
return nil
})
} }
obj := s.stateObjects[addr] // closure for the task runner below
workers.Go(obj.updateTrie)
} }
workers.Wait() workers.Wait()
s.StorageUpdates += time.Since(start) s.StorageUpdates += time.Since(start)
@ -866,18 +787,9 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// here could result in losing uncommitted changes from storage. // here could result in losing uncommitted changes from storage.
start = time.Now() start = time.Now()
// Perform updates before deletions. This prevents resolution of unnecessary trie nodes
// in circumstances similar to the following:
//
// Consider nodes `A` and `B` who share the same full node parent `P` and have no other siblings.
// During the execution of a block:
// - `A` self-destructs,
// - `C` is created, and also shares the parent `P`.
// If the self-destruct is handled first, then `P` would be left with only one child, thus collapsed
// into a shortnode. This requires `B` to be resolved from disk.
// Whereas if the created node is handled first, then the collapse is avoided, and `B` is not resolved.
var ( var (
deletedAddrs []common.Address addresses []common.Address
accounts []AccountMutation
) )
for addr, op := range s.mutations { for addr, op := range s.mutations {
if op.applied { if op.applied {
@ -885,11 +797,18 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
} }
op.applied = true op.applied = true
addresses = append(addresses, addr)
if op.isDelete() { if op.isDelete() {
deletedAddrs = append(deletedAddrs, addr) accounts = append(accounts, AccountMutation{Account: nil})
} else { } else {
obj := s.stateObjects[addr] obj := s.stateObjects[addr]
s.updateStateObject(obj) mut := AccountMutation{
Account: &obj.data,
DirtyCode: obj.dirtyCode,
Code: obj.code,
}
accounts = append(accounts, mut)
s.AccountUpdated += 1 s.AccountUpdated += 1
// Count code writes post-Finalise so reverted CREATEs are excluded. // Count code writes post-Finalise so reverted CREATEs are excluded.
@ -899,16 +818,15 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
} }
} }
} }
for _, deletedAddr := range deletedAddrs { if err := s.hasher.UpdateAccount(addresses, accounts); err != nil {
s.deleteStateObject(deletedAddr) return common.Hash{}
s.AccountDeleted += 1
} }
s.AccountUpdates += time.Since(start) s.AccountUpdates += time.Since(start)
// Track the amount of time wasted on hashing the account trie // Track the amount of time wasted on hashing the account trie
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
return s.trie.Hash() return s.hasher.Hash()
} }
// SetTxContext sets the current transaction hash and index which are // SetTxContext sets the current transaction hash and index which are
@ -925,11 +843,11 @@ func (s *StateDB) clearJournalAndRefund() {
} }
// deleteStorage is designed to delete the storage trie of a designated account. // deleteStorage is designed to delete the storage trie of a designated account.
func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[common.Hash][]byte, map[common.Hash][]byte, *trienode.NodeSet, error) { func (s *StateDB) deleteStorage(addrHash common.Hash) (map[common.Hash]common.Hash, map[common.Hash]common.Hash, *trienode.NodeSet, error) {
var ( var (
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil) nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
storages = make(map[common.Hash][]byte) // the set for storage mutations (value is nil) storages = make(map[common.Hash]common.Hash) // the set for storage mutations (value is nil)
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot storageOrigins = make(map[common.Hash]common.Hash) // the set for tracking the original value of slot
) )
iteratee, err := s.db.Iteratee(s.originalRoot) iteratee, err := s.db.Iteratee(s.originalRoot)
if err != nil { if err != nil {
@ -950,8 +868,15 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
return nil, nil, nil, err return nil, nil, nil, err
} }
key := it.Hash() key := it.Hash()
storages[key] = nil storages[key] = common.Hash{}
storageOrigins[key] = slot
_, content, _, err := rlp.Split(it.Slot())
if err != nil {
return nil, nil, nil, err
}
var value common.Hash
value.SetBytes(content)
storageOrigins[key] = value
if err := stack.Update(key.Bytes(), slot); err != nil { if err := stack.Update(key.Bytes(), slot); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
@ -960,9 +885,7 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
if err := it.Error(); err != nil { // error might occur during iteration if err := it.Error(); err != nil { // error might occur during iteration
return nil, nil, nil, err return nil, nil, nil, err
} }
if stack.Hash() != root { stack.Hash() // Commit the right boundary
return nil, nil, nil, fmt.Errorf("snapshot is not matched, exp %x, got %x", root, stack.Hash())
}
return storages, storageOrigins, nodes, nil return storages, storageOrigins, nodes, nil
} }
@ -984,9 +907,9 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
// with their values be tracked as original value. // with their values be tracked as original value.
// In case (d), **original** account along with its storages should be deleted, // In case (d), **original** account along with its storages should be deleted,
// with their values be tracked as original value. // with their values be tracked as original value.
func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*accountDelete, []*trienode.NodeSet, error) { func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*accountDelete, *trienode.MergedNodeSet, error) {
var ( var (
nodes []*trienode.NodeSet nodes = trienode.NewMergedNodeSet()
deletes = make(map[common.Hash]*accountDelete) deletes = make(map[common.Hash]*accountDelete)
) )
for addr, prevObj := range s.stateObjectsDestruct { for addr, prevObj := range s.stateObjectsDestruct {
@ -1004,19 +927,19 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
addrHash := crypto.Keccak256Hash(addr.Bytes()) addrHash := crypto.Keccak256Hash(addr.Bytes())
op := &accountDelete{ op := &accountDelete{
address: addr, address: addr,
origin: types.SlimAccountRLP(*prev), origin: *prev,
} }
deletes[addrHash] = op deletes[addrHash] = op
// Short circuit if the origin storage was empty. // Short circuit if the origin storage was empty.
if prev.Root == types.EmptyRootHash || s.db.TrieDB().IsVerkle() { if s.db.TrieDB().IsVerkle() {
continue continue
} }
if noStorageWiping { if noStorageWiping {
return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr) return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr)
} }
// Remove storage slots belonging to the account. // Remove storage slots belonging to the account.
storages, storagesOrigin, set, err := s.deleteStorage(addrHash, prev.Root) storages, storagesOrigin, set, err := s.deleteStorage(addrHash)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err) return nil, nil, fmt.Errorf("failed to delete storage, err: %w", err)
} }
@ -1024,16 +947,13 @@ func (s *StateDB) handleDestruction(noStorageWiping bool) (map[common.Hash]*acco
op.storagesOrigin = storagesOrigin op.storagesOrigin = storagesOrigin
// Aggregate the associated trie node changes. // Aggregate the associated trie node changes.
nodes = append(nodes, set) if err := nodes.Merge(set); err != nil {
return nil, nil, err
}
} }
return deletes, nodes, nil return deletes, nodes, nil
} }
// GetTrie returns the account trie.
func (s *StateDB) GetTrie() Trie {
return s.trie
}
// commit gathers the state mutations accumulated along with the associated // commit gathers the state mutations accumulated along with the associated
// trie changes, resetting all internal flags with the new state as the base. // trie changes, resetting all internal flags with the new state as the base.
func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*stateUpdate, error) { func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNumber uint64) (*stateUpdate, error) {
@ -1055,82 +975,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
storageTrieNodesUpdated int storageTrieNodesUpdated int
storageTrieNodesDeleted int storageTrieNodesDeleted int
lock sync.Mutex // protect two maps below
nodes = trienode.NewMergedNodeSet() // aggregated trie nodes
updates = make(map[common.Hash]*accountUpdate, len(s.mutations)) // aggregated account updates updates = make(map[common.Hash]*accountUpdate, len(s.mutations)) // aggregated account updates
// merge aggregates the dirty trie nodes into the global set.
//
// Given that some accounts may be destroyed and then recreated within
// the same block, it's possible that a node set with the same owner
// may already exist. In such cases, these two sets are combined, with
// the later one overwriting the previous one if any nodes are modified
// or deleted in both sets.
//
// merge run concurrently across all the state objects and account trie.
merge = func(set *trienode.NodeSet) error {
if set == nil {
return nil
}
lock.Lock()
defer lock.Unlock()
updates, deletes := set.Size()
if set.Owner == (common.Hash{}) {
accountTrieNodesUpdated += updates
accountTrieNodesDeleted += deletes
} else {
storageTrieNodesUpdated += updates
storageTrieNodesDeleted += deletes
}
return nodes.Merge(set)
}
) )
// Given that some accounts could be destroyed and then recreated within // Given that some accounts could be destroyed and then recreated within
// the same block, account deletions must be processed first. This ensures // the same block, account deletions must be processed first. This ensures
// that the storage trie nodes deleted during destruction and recreated // that the storage trie nodes deleted during destruction and recreated
// during subsequent resurrection can be combined correctly. // during subsequent resurrection can be combined correctly.
deletes, delNodes, err := s.handleDestruction(noStorageWiping) deletes, nodes, err := s.handleDestruction(noStorageWiping)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, set := range delNodes {
if err := merge(set); err != nil {
return nil, err
}
}
// Handle all state updates afterwards, concurrently to one another to shave
// off some milliseconds from the commit operation. Also accumulate the code
// writes to run in parallel with the computations.
var (
start = time.Now()
workers errgroup.Group
)
// Schedule the account trie first since that will be the biggest, so give
// it the most time to crunch.
//
// TODO(karalabe): This account trie commit is *very* heavy. 5-6ms at chain
// heads, which seems excessive given that it doesn't do hashing, it just
// shuffles some data. For comparison, the *hashing* at chain head is 2-3ms.
// We need to investigate what's happening as it seems something's wonky.
// Obviously it's not an end of the world issue, just something the original
// code didn't anticipate for.
workers.Go(func() error {
// Write the account trie changes, measuring the amount of wasted time
_, set := s.trie.Commit(true)
if err := merge(set); err != nil {
return err
}
s.AccountCommits = time.Since(start)
return nil
})
// Schedule each of the storage tries that need to be updated, so they can
// run concurrently to one another.
//
// TODO(karalabe): Experimentally, the account commit takes approximately the
// same time as all the storage commits combined, so we could maybe only have
// 2 threads in total. But that kind of depends on the account commit being
// more expensive than it should be, so let's fix that and revisit this todo.
for addr, op := range s.mutations { for addr, op := range s.mutations {
if op.isDelete() { if op.isDelete() {
continue continue
@ -1140,25 +994,20 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
if obj == nil { if obj == nil {
return nil, errors.New("missing state object") return nil, errors.New("missing state object")
} }
// Run the storage updates concurrently to one another update, err := obj.commit()
workers.Go(func() error { if err != nil {
// Write any storage changes in the state object to its storage trie return nil, err
update, set, err := obj.commit() }
if err != nil { updates[obj.addrHash()] = update
return err
}
if err := merge(set); err != nil {
return err
}
lock.Lock()
updates[obj.addrHash()] = update
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
lock.Unlock()
return nil
})
} }
// Wait for everything to finish and update the metrics // Handle all state updates afterwards, concurrently to one another to shave
if err := workers.Wait(); err != nil { // off some milliseconds from the commit operation. Also accumulate the code
// writes to run in parallel with the computations.
root, set, secondaryHashes, err := s.hasher.Commit()
if err != nil {
return nil, err
}
if err := nodes.MergeSet(set); err != nil {
return nil, err return nil, err
} }
accountReadMeters.Mark(int64(s.AccountLoaded)) accountReadMeters.Mark(int64(s.AccountLoaded))
@ -1185,7 +1034,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
origin := s.originalRoot origin := s.originalRoot
s.originalRoot = root s.originalRoot = root
return newStateUpdate(noStorageWiping, origin, root, blockNumber, deletes, updates, nodes), nil return newStateUpdate(noStorageWiping, origin, root, blockNumber, deletes, updates, nodes, secondaryHashes), nil
} }
// commitAndFlush is a wrapper of commit which also commits the state mutations // commitAndFlush is a wrapper of commit which also commits the state mutations
@ -1209,6 +1058,7 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool, noStorag
// The reader update must be performed as the final step, otherwise, // The reader update must be performed as the final step, otherwise,
// the new state would not be visible before db.commit. // the new state would not be visible before db.commit.
s.reader, _ = s.db.Reader(s.originalRoot) s.reader, _ = s.db.Reader(s.originalRoot)
s.hasher, _ = s.db.Hasher(s.originalRoot)
return ret, err return ret, err
} }

View file

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

View file

@ -32,13 +32,10 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/hashdb" "github.com/ethereum/go-ethereum/triedb/hashdb"
"github.com/ethereum/go-ethereum/triedb/pathdb" "github.com/ethereum/go-ethereum/triedb/pathdb"
@ -232,7 +229,7 @@ func TestCopyWithDirtyJournal(t *testing.T) {
for i := byte(0); i < 255; i++ { for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i))) obj.AddBalance(uint256.NewInt(uint64(i)))
obj.data.Root = common.HexToHash("0xdeadbeef") //obj.data.Root = common.HexToHash("0xdeadbeef")
} }
root, _ := orig.Commit(0, true, false) root, _ := orig.Commit(0, true, false)
orig, _ = New(root, db) orig, _ = New(root, db)
@ -275,7 +272,7 @@ func TestCopyObjectState(t *testing.T) {
for i := byte(0); i < 5; i++ { for i := byte(0); i < 5; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i})) obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i))) obj.AddBalance(uint256.NewInt(uint64(i)))
obj.data.Root = common.HexToHash("0xdeadbeef") //obj.data.Root = common.HexToHash("0xdeadbeef")
} }
orig.Finalise(true) orig.Finalise(true)
cpy := orig.Copy() cpy := orig.Copy()
@ -1269,60 +1266,6 @@ func TestStateDBTransientStorage(t *testing.T) {
} }
} }
func TestDeleteStorage(t *testing.T) {
var (
disk = rawdb.NewMemoryDatabase()
tdb = triedb.NewDatabase(disk, nil)
snaps, _ = snapshot.New(snapshot.Config{CacheSize: 10}, disk, tdb, types.EmptyRootHash)
db = NewDatabase(tdb, nil).WithSnapshot(snaps)
state, _ = New(types.EmptyRootHash, db)
addr = common.HexToAddress("0x1")
)
// Initialize account and populate storage
state.SetBalance(addr, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
state.CreateAccount(addr)
for i := 0; i < 1000; i++ {
slot := common.Hash(uint256.NewInt(uint64(i)).Bytes32())
value := common.Hash(uint256.NewInt(uint64(10 * i)).Bytes32())
state.SetState(addr, slot, value)
}
root, _ := state.Commit(0, true, false)
// Init phase done, create two states, one with snap and one without
fastState, _ := New(root, NewDatabase(tdb, nil).WithSnapshot(snaps))
slowState, _ := New(root, NewDatabase(tdb, nil))
obj := fastState.getOrNewStateObject(addr)
storageRoot := obj.data.Root
_, _, fastNodes, err := fastState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
if err != nil {
t.Fatal(err)
}
_, _, slowNodes, err := slowState.deleteStorage(crypto.Keccak256Hash(addr[:]), storageRoot)
if err != nil {
t.Fatal(err)
}
check := func(set *trienode.NodeSet) string {
var a []string
set.ForEachWithOrder(func(path string, n *trienode.Node) {
if n.Hash != (common.Hash{}) {
t.Fatal("delete should have empty hashes")
}
if len(n.Blob) != 0 {
t.Fatal("delete should have empty blobs")
}
a = append(a, fmt.Sprintf("%x", path))
})
return strings.Join(a, ",")
}
slowRes := check(slowNodes)
fastRes := check(fastNodes)
if slowRes != fastRes {
t.Fatalf("difference found:\nfast: %v\nslow: %v\n", fastRes, slowRes)
}
}
func TestStorageDirtiness(t *testing.T) { func TestStorageDirtiness(t *testing.T) {
var ( var (
disk = rawdb.NewMemoryDatabase() disk = rawdb.NewMemoryDatabase()

View file

@ -23,77 +23,71 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/trienode" "github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb" "github.com/ethereum/go-ethereum/triedb"
) )
// contractCode represents contract bytecode along with its associated metadata. // contractCode encapsulates contract bytecode and its associated metadata.
type contractCode struct { type contractCode struct {
hash common.Hash // hash is the cryptographic hash of the current contract code. hash common.Hash // hash is the cryptographic hash of the current contract code.
blob []byte // blob is the binary representation 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 before mutation. originHash common.Hash // originHash is the cryptographic hash of the code prior to mutation.
// Derived fields, populated only when state tracking is enabled. // Derived fields, populated only when state tracking is enabled.
duplicate bool // duplicate indicates whether the updated code already exists. duplicate bool // duplicate indicates whether the updated code already exists.
originBlob []byte // originBlob is the original binary representation of the contract code. originBlob []byte // originBlob is the original byte representation of the contract code.
} }
// accountDelete represents an operation for deleting an Ethereum account. // accountDelete represents a deletion operation for an Ethereum account.
type accountDelete struct { type accountDelete struct {
address common.Address // address is the unique account identifier address common.Address // address uniquely identifies the account.
origin []byte // origin is the original value of account data in slim-RLP encoding. origin Account // origin is the account state prior to deletion.
// storages stores mutated slots, the value should be nil. storages map[common.Hash]common.Hash // storages contains mutated storage slots.
storages map[common.Hash][]byte storagesOrigin map[common.Hash]common.Hash // storagesOrigin holds original values of mutated slots; keys are hashes of raw storage slot keys.
// storagesOrigin stores the original values of mutated slots in
// prefix-zero-trimmed RLP format. The map key refers to the **HASH**
// of the raw storage slot key.
storagesOrigin map[common.Hash][]byte
} }
// accountUpdate represents an operation for updating an Ethereum account. // accountUpdate represents an update operation for an Ethereum account.
type accountUpdate struct { type accountUpdate struct {
address common.Address // address is the unique account identifier address common.Address // address uniquely identifies the account.
data []byte // data is the slim-RLP encoded account data. data *Account // data is the updated account state; nil indicates deletion.
origin []byte // origin is the original value of account data in slim-RLP encoding. origin *Account // origin is the previous account state; nil indicates non-existence.
code *contractCode // code represents mutated contract code; nil means it's not modified. code *contractCode // code contains updated contract code; nil if unchanged.
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format. storages map[common.Hash]common.Hash // storages contains updated storage slots.
// storagesOriginByKey and storagesOriginByHash both store the original values // storagesOriginByKey and storagesOriginByHash both record original values
// of mutated slots in prefix-zero-trimmed RLP format. The difference is that // of mutated storage slots:
// storagesOriginByKey uses the **raw** storage slot key as the map ID, while // - storagesOriginByKey uses raw storage slot keys.
// storagesOriginByHash uses the **hash** of the storage slot key instead. // - storagesOriginByHash uses hashed storage slot keys.
storagesOriginByKey map[common.Hash][]byte storagesOriginByKey map[common.Hash]common.Hash
storagesOriginByHash map[common.Hash][]byte storagesOriginByHash map[common.Hash]common.Hash
} }
// stateUpdate represents the difference between two states resulting from state // stateUpdate captures the difference between two states resulting from
// execution. It contains information about mutated contract codes, accounts, // execution. It records all mutated accounts, contract codes, and storage
// and storage slots, along with their original values. // slots, along with their original values.
type stateUpdate struct { type stateUpdate struct {
originRoot common.Hash // hash of the state before applying mutation originRoot common.Hash // originRoot is the state root before applying changes.
root common.Hash // hash of the state after applying mutation root common.Hash // root is the state root after applying changes.
blockNumber uint64 // Associated block number blockNumber uint64 // blockNumber is the associated block height.
accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding accounts map[common.Hash]*Account // accounts contains mutated accounts, keyed by account hash.
accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding accountsOrigin map[common.Address]*Account // accountsOrigin holds original values of mutated accounts, keyed by address.
// storages stores mutated slots in 'prefix-zero-trimmed' RLP format. // storages contains mutated storage slots, keyed by account hash and
// The value is keyed by account hash and **storage slot key hash**. // storage slot key hash.
storages map[common.Hash]map[common.Hash][]byte storages map[common.Hash]map[common.Hash]common.Hash
// storagesOrigin stores the original values of mutated slots in // storagesOrigin holds original values of mutated storage slots.
// 'prefix-zero-trimmed' RLP format. // The key format depends on rawStorageKey:
// (a) the value is keyed by account hash and **storage slot key** if rawStorageKey is true; // - if true: keyed by account address and raw storage slot key.
// (b) the value is keyed by account hash and **storage slot key hash** if rawStorageKey is false; // - if false: keyed by account address and storage slot key hash.
storagesOrigin map[common.Address]map[common.Hash][]byte storagesOrigin map[common.Address]map[common.Hash]common.Hash
rawStorageKey bool rawStorageKey bool
codes map[common.Address]*contractCode // codes contains the set of dirty codes codes map[common.Address]*contractCode // codes contains mutated contract codes, keyed by address.
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes nodes *trienode.MergedNodeSet // nodes aggregates all dirty trie nodes produced by the update.
secondaryHashes map[common.Address]SecondaryHash // hashes of secondary tries
} }
// empty returns a flag indicating the state transition is empty or not. // empty returns a flag indicating the state transition is empty or not.
@ -107,12 +101,12 @@ func (sc *stateUpdate) empty() bool {
// //
// rawStorageKey is a flag indicating whether to use the raw storage slot key or // rawStorageKey is a flag indicating whether to use the raw storage slot key or
// the hash of the slot key for constructing state update object. // the hash of the slot key for constructing state update object.
func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet) *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]SecondaryHash) *stateUpdate {
var ( var (
accounts = make(map[common.Hash][]byte) accounts = make(map[common.Hash]*Account)
accountsOrigin = make(map[common.Address][]byte) accountsOrigin = make(map[common.Address]*Account)
storages = make(map[common.Hash]map[common.Hash][]byte) storages = make(map[common.Hash]map[common.Hash]common.Hash)
storagesOrigin = make(map[common.Address]map[common.Hash][]byte) storagesOrigin = make(map[common.Address]map[common.Hash]common.Hash)
codes = make(map[common.Address]*contractCode) codes = make(map[common.Address]*contractCode)
) )
// Since some accounts might be destroyed and recreated within the same // Since some accounts might be destroyed and recreated within the same
@ -120,7 +114,7 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
for addrHash, op := range deletes { for addrHash, op := range deletes {
addr := op.address addr := op.address
accounts[addrHash] = nil accounts[addrHash] = nil
accountsOrigin[addr] = op.origin accountsOrigin[addr] = &op.origin
// If storage wiping exists, the hash of the storage slot key must be used // If storage wiping exists, the hash of the storage slot key must be used
if len(op.storages) > 0 { if len(op.storages) > 0 {
@ -174,16 +168,17 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
} }
} }
return &stateUpdate{ return &stateUpdate{
originRoot: originRoot, originRoot: originRoot,
root: root, root: root,
blockNumber: blockNumber, blockNumber: blockNumber,
accounts: accounts, accounts: accounts,
accountsOrigin: accountsOrigin, accountsOrigin: accountsOrigin,
storages: storages, storages: storages,
storagesOrigin: storagesOrigin, storagesOrigin: storagesOrigin,
rawStorageKey: rawStorageKey, rawStorageKey: rawStorageKey,
codes: codes, codes: codes,
nodes: nodes, nodes: nodes,
secondaryHashes: secondaryHashes,
} }
} }
@ -192,13 +187,14 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
// struct and formats it into the StateSet structure consumed by the triedb // struct and formats it into the StateSet structure consumed by the triedb
// package. // package.
func (sc *stateUpdate) stateSet() *triedb.StateSet { func (sc *stateUpdate) stateSet() *triedb.StateSet {
return &triedb.StateSet{ return nil
Accounts: sc.accounts, //return &triedb.StateSet{
AccountsOrigin: sc.accountsOrigin, // Accounts: sc.accounts,
Storages: sc.storages, // AccountsOrigin: sc.accountsOrigin,
StoragesOrigin: sc.storagesOrigin, // Storages: sc.storages,
RawStorageKey: sc.rawStorageKey, // StoragesOrigin: sc.storagesOrigin,
} // RawStorageKey: sc.rawStorageKey,
//}
} }
// deriveCodeFields derives the missing fields of contract code changes // deriveCodeFields derives the missing fields of contract code changes
@ -230,140 +226,141 @@ func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
// ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate. // ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate.
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) { func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
update := &tracing.StateUpdate{ return nil, nil
OriginRoot: sc.originRoot, //update := &tracing.StateUpdate{
Root: sc.root, // OriginRoot: sc.originRoot,
BlockNumber: sc.blockNumber, // Root: sc.root,
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)), // BlockNumber: sc.blockNumber,
StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange), // AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)), // StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange), // 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 { //// Gather all account changes
addrHash := crypto.Keccak256Hash(addr.Bytes()) //for addr, oldData := range sc.accountsOrigin {
newData, exists := sc.accounts[addrHash] // addrHash := crypto.Keccak256Hash(addr.Bytes())
if !exists { // newData, exists := sc.accounts[addrHash]
return nil, fmt.Errorf("account %x not found", addr) // if !exists {
} // return nil, fmt.Errorf("account %x not found", addr)
change := &tracing.AccountChange{} // }
// change := &tracing.AccountChange{}
if len(oldData) > 0 { //
acct, err := types.FullAccount(oldData) // if len(oldData) > 0 {
if err != nil { // acct, err := types.FullAccount(oldData)
return nil, err // if err != nil {
} // return nil, err
change.Prev = &types.StateAccount{ // }
Nonce: acct.Nonce, // change.Prev = &types.StateAccount{
Balance: acct.Balance, // Nonce: acct.Nonce,
Root: acct.Root, // Balance: acct.Balance,
CodeHash: acct.CodeHash, // Root: acct.Root,
} // CodeHash: acct.CodeHash,
} // }
if len(newData) > 0 { // }
acct, err := types.FullAccount(newData) // if len(newData) > 0 {
if err != nil { // acct, err := types.FullAccount(newData)
return nil, err // if err != nil {
} // return nil, err
change.New = &types.StateAccount{ // }
Nonce: acct.Nonce, // change.New = &types.StateAccount{
Balance: acct.Balance, // Nonce: acct.Nonce,
Root: acct.Root, // Balance: acct.Balance,
CodeHash: acct.CodeHash, // Root: acct.Root,
} // CodeHash: acct.CodeHash,
} // }
update.AccountChanges[addr] = change // }
} // update.AccountChanges[addr] = change
//}
// Gather all storage slot changes //
for addr, slots := range sc.storagesOrigin { //// Gather all storage slot changes
addrHash := crypto.Keccak256Hash(addr.Bytes()) //for addr, slots := range sc.storagesOrigin {
subset, exists := sc.storages[addrHash] // addrHash := crypto.Keccak256Hash(addr.Bytes())
if !exists { // subset, exists := sc.storages[addrHash]
return nil, fmt.Errorf("storage %x not found", addr) // if !exists {
} // return nil, fmt.Errorf("storage %x not found", addr)
storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots)) // }
// storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
for key, encPrev := range slots { //
// Get new value - handle both raw and hashed key formats // for key, encPrev := range slots {
var ( // // Get new value - handle both raw and hashed key formats
exists bool // var (
encNew []byte // exists bool
decPrev []byte // encNew []byte
decNew []byte // decPrev []byte
err error // decNew []byte
) // err error
if sc.rawStorageKey { // )
encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())] // if sc.rawStorageKey {
} else { // encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())]
encNew, exists = subset[key] // } else {
} // encNew, exists = subset[key]
if !exists { // }
return nil, fmt.Errorf("storage slot %x-%x not found", addr, 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 { // // Decode the prev and new values
_, decPrev, _, err = rlp.Split(encPrev) // if len(encPrev) > 0 {
if err != nil { // _, decPrev, _, err = rlp.Split(encPrev)
return nil, fmt.Errorf("failed to decode prevValue: %v", err) // if err != nil {
} // return nil, fmt.Errorf("failed to decode prevValue: %v", err)
} // }
if len(encNew) > 0 { // }
_, decNew, _, err = rlp.Split(encNew) // if len(encNew) > 0 {
if err != nil { // _, decNew, _, err = rlp.Split(encNew)
return nil, fmt.Errorf("failed to decode newValue: %v", err) // if err != nil {
} // return nil, fmt.Errorf("failed to decode newValue: %v", err)
} // }
storageChanges[key] = &tracing.StorageChange{ // }
Prev: common.BytesToHash(decPrev), // storageChanges[key] = &tracing.StorageChange{
New: common.BytesToHash(decNew), // Prev: common.BytesToHash(decPrev),
} // New: common.BytesToHash(decNew),
} // }
update.StorageChanges[addr] = storageChanges // }
} // update.StorageChanges[addr] = storageChanges
//}
// Gather all contract code changes //
for addr, code := range sc.codes { //// Gather all contract code changes
change := &tracing.CodeChange{ //for addr, code := range sc.codes {
New: &tracing.ContractCode{ // change := &tracing.CodeChange{
Hash: code.hash, // New: &tracing.ContractCode{
Code: code.blob, // Hash: code.hash,
Exists: code.duplicate, // Code: code.blob,
}, // Exists: code.duplicate,
} // },
if code.originHash != types.EmptyCodeHash { // }
change.Prev = &tracing.ContractCode{ // if code.originHash != types.EmptyCodeHash {
Hash: code.originHash, // change.Prev = &tracing.ContractCode{
Code: code.originBlob, // Hash: code.originHash,
Exists: true, // Code: code.originBlob,
} // Exists: true,
} // }
update.CodeChanges[addr] = change // }
} // update.CodeChanges[addr] = change
//}
// Gather all trie node changes //
if sc.nodes != nil { //// Gather all trie node changes
for owner, subset := range sc.nodes.Sets { //if sc.nodes != nil {
nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins)) // for owner, subset := range sc.nodes.Sets {
for path, oldNode := range subset.Origins { // nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
newNode, exists := subset.Nodes[path] // for path, oldNode := range subset.Origins {
if !exists { // newNode, exists := subset.Nodes[path]
return nil, fmt.Errorf("node %x-%v not found", owner, path) // if !exists {
} // return nil, fmt.Errorf("node %x-%v not found", owner, path)
nodeChanges[path] = &tracing.TrieNodeChange{ // }
Prev: &trienode.Node{ // nodeChanges[path] = &tracing.TrieNodeChange{
Hash: crypto.Keccak256Hash(oldNode), // Prev: &trienode.Node{
Blob: oldNode, // Hash: crypto.Keccak256Hash(oldNode),
}, // Blob: oldNode,
New: &trienode.Node{ // },
Hash: newNode.Hash, // New: &trienode.Node{
Blob: newNode.Blob, // Hash: newNode.Hash,
}, // Blob: newNode.Blob,
} // },
} // }
update.TrieChanges[owner] = nodeChanges // }
} // update.TrieChanges[owner] = nodeChanges
} // }
return update, nil //}
//return update, nil
} }

View file

@ -259,6 +259,16 @@ func (set *MergedNodeSet) Merge(other *NodeSet) error {
return nil return nil
} }
// MergeSet merges the provided set into local one.
func (set *MergedNodeSet) MergeSet(other *MergedNodeSet) error {
for _, subset := range other.Sets {
if err := set.Merge(subset); err != nil {
return err
}
}
return nil
}
// Nodes returns a two-dimensional map for internal nodes. // Nodes returns a two-dimensional map for internal nodes.
func (set *MergedNodeSet) Nodes() map[common.Hash]map[string]*Node { func (set *MergedNodeSet) Nodes() map[common.Hash]map[string]*Node {
nodes := make(map[common.Hash]map[string]*Node, len(set.Sets)) nodes := make(map[common.Hash]map[string]*Node, len(set.Sets))