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
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/overlay"
"github.com/ethereum/go-ethereum/core/rawdb"
@ -26,10 +24,8 @@ 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/transitiontrie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb"
)
@ -43,6 +39,9 @@ type Database interface {
// through which the account iterator and storage iterator can be created.
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(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
}
func (db *CachingDB) Hasher(stateRoot common.Hash) (Hasher, error) {
return &noopHasher{}, nil
}
// ReadersWithCacheStats creates a pair of state readers that share the same
// underlying state reader and internal state cache, while maintaining separate
// 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 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)
}
//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)
//}
}
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) {
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.
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
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
}
// 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
// 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]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() Hasher
@ -112,3 +119,30 @@ type Prover interface {
// the nodes required to prove its absence.
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.
func (r *historicStateReader) Account(addr common.Address) (*types.StateAccount, error) {
func (r *historicStateReader) Account(addr common.Address) (*Account, error) {
r.lock.Lock()
defer r.lock.Unlock()
@ -55,18 +55,14 @@ func (r *historicStateReader) Account(addr common.Address) (*types.StateAccount,
if account == nil {
return nil, nil
}
acct := &types.StateAccount{
acct := &Account{
Nonce: account.Nonce,
Balance: account.Balance,
CodeHash: account.CodeHash,
Root: common.BytesToHash(account.Root),
}
if len(acct.CodeHash) == 0 {
acct.CodeHash = types.EmptyCodeHash.Bytes()
}
if acct.Root == (common.Hash{}) {
acct.Root = types.EmptyRootHash
}
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.
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)
if err != nil {
return nil, err
}
if account == nil {
r.subRoots[addr] = types.EmptyRootHash
return nil, nil
} else {
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.
@ -169,7 +173,7 @@ func (r *historicalTrieReader) account(addr common.Address) (*types.StateAccount
// the requested account is not yet covered by the snapshot.
//
// 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()
defer r.lock.Unlock()
@ -255,6 +259,10 @@ func (db *HistoricDB) Reader(stateRoot common.Hash) (Reader, error) {
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.
func (db *HistoricDB) OpenTrie(root common.Hash) (Trie, error) {
nr, err := db.triedb.HistoricNodeReader(root)

View file

@ -168,7 +168,11 @@ func (s *StateDB) DumpToCollector(c DumpCollector, conf *DumpConfig) (nextKey []
address = &addrBytes
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 {
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
// associated with a specific state.
//
@ -81,7 +94,7 @@ type StateReader interface {
// - Returns a nil account if it does not exist
// - Returns an error only if an unexpected issue occurs
// - 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
// 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 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[:]))
if err != nil {
return nil, err
@ -126,18 +139,16 @@ func (r *flatReader) Account(addr common.Address) (*types.StateAccount, error) {
if account == nil {
return nil, nil
}
acct := &types.StateAccount{
acct := &Account{
Nonce: account.Nonce,
Balance: account.Balance,
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 {
acct.CodeHash = types.EmptyCodeHash.Bytes()
}
if acct.Root == (common.Hash{}) {
acct.Root = types.EmptyRootHash
}
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.
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)
if err != nil {
return nil, err
}
if account == nil {
r.subRoots[addr] = types.EmptyRootHash
return nil, nil
} else {
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.
//
// 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.
func (r *trieReader) Account(addr common.Address) (*types.StateAccount, error) {
func (r *trieReader) Account(addr common.Address) (*Account, error) {
r.lock.Lock()
defer r.lock.Unlock()
@ -340,7 +359,7 @@ func newMultiStateReader(readers ...StateReader) (*multiStateReader, error) {
// - Returns a nil account if it does not exist
// - Returns an error only if an unexpected issue occurs
// - 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
for _, reader := range r.readers {
acct, err := reader.Account(addr)
@ -376,7 +395,7 @@ type stateReaderWithCache struct {
StateReader
// Previously resolved state entries.
accounts map[common.Address]*types.StateAccount
accounts map[common.Address]*Account
accountLock sync.RWMutex
// List of storage buckets, each of which is thread-safe.
@ -393,7 +412,7 @@ type stateReaderWithCache struct {
func newStateReaderWithCache(sr StateReader) *stateReaderWithCache {
r := &stateReaderWithCache{
StateReader: sr,
accounts: make(map[common.Address]*types.StateAccount),
accounts: make(map[common.Address]*Account),
}
for i := range r.storageBuckets {
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.
//
// 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
r.accountLock.RLock()
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.
//
// 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)
return account, err
}
@ -502,7 +521,7 @@ func newStateReaderWithStats(sr *stateReaderWithCache) *stateReaderWithStats {
// 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.
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)
if err != nil {
return nil, err

View file

@ -27,11 +27,6 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"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"
)
@ -49,13 +44,12 @@ func (s Storage) Copy() Storage {
// - Finally, call commit to return the changes of storage trie and update account data.
type stateObject struct {
db *StateDB
address common.Address // address of ethereum 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
data types.StateAccount // Account data with all mutations applied in the scope of block
address common.Address // address of ethereum account
addressHash *common.Hash // hash of ethereum address of the account
origin *Account // Account original data without any change applied, nil means it was not existent
data Account // Account data with all mutations applied in the scope of block
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
code []byte // contract bytecode, which gets set when code is loaded
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.
func newObject(db *StateDB, address common.Address, acct *types.StateAccount) *stateObject {
func newObject(db *StateDB, address common.Address, acct *Account) *stateObject {
origin := acct
if acct == nil {
acct = types.NewEmptyStateAccount()
acct = newEmptyAccount()
}
return &stateObject{
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
// subsequent reads to expand the same trie instead of reloading from disk.
func (s *stateObject) getTrie() (Trie, error) {
if s.trie == 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
return nil, nil
}
// 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
// object's storage trie. In case the storage trie is not yet loaded, this
// function will load the trie automatically. If any issues arise during the
// loading or updating of the trie, an error will be returned. Furthermore,
// 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) {
// state hasher. It assumes all the dirty storage slots have been finalized
// before.
func (s *stateObject) updateTrie() error {
// Short circuit if nothing was accessed
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 (
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 {
// 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)
continue
}
if (value != common.Hash{}) {
if err := tr.UpdateStorage(s.address, key[:], common.TrimLeftZeroes(value[:])); err != nil {
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)
keys = append(keys, key)
vals = append(vals, value)
}
s.uncommittedStorage = make(Storage) // empties the commit markers
return tr, nil
}
// 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()
return s.db.hasher.UpdateStorage(s.address, keys, vals)
}
// commitStorage overwrites the clean storage with the storage changes and
// fulfills the storage diffs into the given accountUpdate struct.
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 {
// Skip the noop storage changes, it might be possible the value
// of tracked slot is same in originStorage and pendingStorage
@ -365,17 +296,17 @@ func (s *stateObject) commitStorage(op *accountUpdate) {
}
hash := crypto.Keccak256Hash(key[:])
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 {
op.storagesOriginByKey = make(map[common.Hash][]byte)
op.storagesOriginByKey = make(map[common.Hash]common.Hash)
}
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.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
// 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
op := &accountUpdate{
address: s.address,
data: types.SlimAccountRLP(s.data),
}
if s.origin != nil {
op.origin = types.SlimAccountRLP(*s.origin)
data: &s.data,
origin: s.origin,
}
// commit the contract code if it's modified
if s.dirtyCode {
@ -415,24 +344,8 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
}
// Commit storage changes and the associated storage trie
s.commitStorage(op)
if len(op.storages) == 0 {
// nothing changed, don't bother to commit the trie
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
s.origin = s.data.copy()
return op, nil
}
// AddBalance adds amount to s's balance.
@ -478,21 +391,6 @@ func (s *stateObject) deepCopy(db *StateDB) *stateObject {
selfDestructed: s.selfDestructed,
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
}
@ -584,5 +482,5 @@ func (s *stateObject) Nonce() uint64 {
}
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/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"
@ -41,6 +40,7 @@ const (
)
// Database key scheme for states.
// nolint:unused
var (
accountKeySize = int64(len(rawdb.SnapshotAccountPrefix) + common.HashLength)
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.
func calSizeStats(update *stateUpdate) (SizeStats, error) {
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
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
}
type stateSizeQuery struct {

View file

@ -23,7 +23,6 @@ import (
"maps"
"slices"
"sort"
"sync"
"sync/atomic"
"time"
@ -33,6 +32,7 @@ import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/holiman/uint256"
@ -76,7 +76,7 @@ func (m *mutation) isDelete() bool {
type StateDB struct {
db Database
reader Reader
trie Trie // it's resolved on first access
hasher Hasher
// originalRoot is the pre-state root, before any changes were made.
// 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,
// this function accepts an additional Reader which is bound to the given root.
func NewWithReader(root common.Hash, db Database, reader Reader) (*StateDB, error) {
hasher, err := db.Hasher(root)
if err != nil {
return nil, err
}
sdb := &StateDB{
db: db,
originalRoot: root,
reader: reader,
hasher: hasher,
stateObjects: make(map[common.Address]*stateObject),
stateObjectsDestruct: make(map[common.Address]*stateObject),
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.
//
// 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
// the object is not found or was deleted in this execution context.
func (s *StateDB) getStateObject(addr common.Address) *stateObject {
@ -631,6 +618,7 @@ func (s *StateDB) Copy() *StateDB {
state := &StateDB{
db: s.db,
reader: s.reader,
hasher: s.hasher.Copy(),
originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)),
@ -653,9 +641,6 @@ func (s *StateDB) Copy() *StateDB {
transientStorage: s.transientStorage.Copy(),
journal: s.journal.copy(),
}
if s.trie != nil {
state.trie = mustCopyTrie(s.trie)
}
if s.accessEvents != nil {
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
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
// method will internally call a blocking trie fetch from the prefetcher,
// 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()
workers errgroup.Group
)
if s.db.TrieDB().IsVerkle() {
// Bypass per-account updateTrie() for binary trie. In binary trie mode
// there is only one unified trie (OpenStorageTrie returns self), so the
// 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
})
for addr, op := range s.mutations {
if op.applied || op.isDelete() {
continue
}
obj := s.stateObjects[addr] // closure for the task runner below
workers.Go(obj.updateTrie)
}
workers.Wait()
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.
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 (
deletedAddrs []common.Address
addresses []common.Address
accounts []AccountMutation
)
for addr, op := range s.mutations {
if op.applied {
@ -885,11 +797,18 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
}
op.applied = true
addresses = append(addresses, addr)
if op.isDelete() {
deletedAddrs = append(deletedAddrs, addr)
accounts = append(accounts, AccountMutation{Account: nil})
} else {
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
// 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 {
s.deleteStateObject(deletedAddr)
s.AccountDeleted += 1
if err := s.hasher.UpdateAccount(addresses, accounts); err != nil {
return common.Hash{}
}
s.AccountUpdates += time.Since(start)
// Track the amount of time wasted on hashing the account trie
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
@ -925,11 +843,11 @@ func (s *StateDB) clearJournalAndRefund() {
}
// 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 (
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)
storageOrigins = make(map[common.Hash][]byte) // the set for tracking the original value of slot
nodes = trienode.NewNodeSet(addrHash) // the set for trie node mutations (value is nil)
storages = make(map[common.Hash]common.Hash) // the set for storage mutations (value is nil)
storageOrigins = make(map[common.Hash]common.Hash) // the set for tracking the original value of slot
)
iteratee, err := s.db.Iteratee(s.originalRoot)
if err != nil {
@ -950,8 +868,15 @@ func (s *StateDB) deleteStorage(addrHash common.Hash, root common.Hash) (map[com
return nil, nil, nil, err
}
key := it.Hash()
storages[key] = nil
storageOrigins[key] = slot
storages[key] = common.Hash{}
_, 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 {
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
return nil, nil, nil, err
}
if stack.Hash() != root {
return nil, nil, nil, fmt.Errorf("snapshot is not matched, exp %x, got %x", root, stack.Hash())
}
stack.Hash() // Commit the right boundary
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.
// In case (d), **original** account along with its storages should be deleted,
// 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 (
nodes []*trienode.NodeSet
nodes = trienode.NewMergedNodeSet()
deletes = make(map[common.Hash]*accountDelete)
)
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())
op := &accountDelete{
address: addr,
origin: types.SlimAccountRLP(*prev),
origin: *prev,
}
deletes[addrHash] = op
// Short circuit if the origin storage was empty.
if prev.Root == types.EmptyRootHash || s.db.TrieDB().IsVerkle() {
if s.db.TrieDB().IsVerkle() {
continue
}
if noStorageWiping {
return nil, nil, fmt.Errorf("unexpected storage wiping, %x", addr)
}
// 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 {
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
// 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
}
// GetTrie returns the account trie.
func (s *StateDB) GetTrie() Trie {
return s.trie
}
// commit gathers the state mutations accumulated along with the associated
// 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) {
@ -1055,82 +975,16 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
storageTrieNodesUpdated 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
// 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
// the same block, account deletions must be processed first. This ensures
// that the storage trie nodes deleted during destruction and recreated
// during subsequent resurrection can be combined correctly.
deletes, delNodes, err := s.handleDestruction(noStorageWiping)
deletes, nodes, err := s.handleDestruction(noStorageWiping)
if err != nil {
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 {
if op.isDelete() {
continue
@ -1140,25 +994,20 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
if obj == nil {
return nil, errors.New("missing state object")
}
// Run the storage updates concurrently to one another
workers.Go(func() error {
// Write any storage changes in the state object to its storage trie
update, set, err := obj.commit()
if err != nil {
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
})
update, err := obj.commit()
if err != nil {
return nil, err
}
updates[obj.addrHash()] = update
}
// Wait for everything to finish and update the metrics
if err := workers.Wait(); err != nil {
// 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.
root, set, secondaryHashes, err := s.hasher.Commit()
if err != nil {
return nil, err
}
if err := nodes.MergeSet(set); err != nil {
return nil, err
}
accountReadMeters.Mark(int64(s.AccountLoaded))
@ -1185,7 +1034,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
origin := s.originalRoot
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
@ -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 new state would not be visible before db.commit.
s.reader, _ = s.db.Reader(s.originalRoot)
s.hasher, _ = s.db.Hasher(s.originalRoot)
return ret, err
}

View file

@ -21,7 +21,6 @@ import (
"encoding/binary"
"errors"
"fmt"
"maps"
"math"
"math/rand"
"reflect"
@ -183,10 +182,10 @@ 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))
//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))
}
disk = rawdb.NewMemoryDatabase()
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/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"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"
"github.com/ethereum/go-ethereum/trie/trienode"
"github.com/ethereum/go-ethereum/triedb"
"github.com/ethereum/go-ethereum/triedb/hashdb"
"github.com/ethereum/go-ethereum/triedb/pathdb"
@ -232,7 +229,7 @@ func TestCopyWithDirtyJournal(t *testing.T) {
for i := byte(0); i < 255; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{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)
orig, _ = New(root, db)
@ -275,7 +272,7 @@ func TestCopyObjectState(t *testing.T) {
for i := byte(0); i < 5; i++ {
obj := orig.getOrNewStateObject(common.BytesToAddress([]byte{i}))
obj.AddBalance(uint256.NewInt(uint64(i)))
obj.data.Root = common.HexToHash("0xdeadbeef")
//obj.data.Root = common.HexToHash("0xdeadbeef")
}
orig.Finalise(true)
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) {
var (
disk = rawdb.NewMemoryDatabase()

View file

@ -23,77 +23,71 @@ import (
"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"
)
// contractCode represents contract bytecode along with its associated metadata.
// 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 binary representation of the current contract code.
originHash common.Hash // originHash is the cryptographic hash of the code before mutation.
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.
// Derived fields, populated only when state tracking is enabled.
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 {
address common.Address // address is the unique account identifier
origin []byte // origin is the original value of account data in slim-RLP encoding.
address common.Address // address uniquely identifies the account.
origin Account // origin is the account state prior to deletion.
// storages stores mutated slots, the value should be nil.
storages map[common.Hash][]byte
// 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
storages map[common.Hash]common.Hash // storages contains mutated storage slots.
storagesOrigin map[common.Hash]common.Hash // storagesOrigin holds original values of mutated slots; keys are hashes of raw storage slot keys.
}
// accountUpdate represents an operation for updating an Ethereum account.
// accountUpdate represents an update operation for an Ethereum account.
type accountUpdate struct {
address common.Address // address is the unique account identifier
data []byte // data is the slim-RLP encoded account data.
origin []byte // origin is the original value of account data in slim-RLP encoding.
code *contractCode // code represents mutated contract code; nil means it's not modified.
storages map[common.Hash][]byte // storages stores mutated slots in prefix-zero-trimmed RLP format.
address common.Address // address uniquely identifies the account.
data *Account // data is the updated account state; nil indicates deletion.
origin *Account // origin is the previous account state; nil indicates non-existence.
code *contractCode // code contains updated contract code; nil if unchanged.
storages map[common.Hash]common.Hash // storages contains updated storage slots.
// storagesOriginByKey and storagesOriginByHash both store the original values
// of mutated slots in prefix-zero-trimmed RLP format. The difference is that
// storagesOriginByKey uses the **raw** storage slot key as the map ID, while
// storagesOriginByHash uses the **hash** of the storage slot key instead.
storagesOriginByKey map[common.Hash][]byte
storagesOriginByHash map[common.Hash][]byte
// storagesOriginByKey and storagesOriginByHash both record original values
// of mutated storage slots:
// - storagesOriginByKey uses raw storage slot keys.
// - storagesOriginByHash uses hashed storage slot keys.
storagesOriginByKey map[common.Hash]common.Hash
storagesOriginByHash map[common.Hash]common.Hash
}
// stateUpdate represents the difference between two states resulting from state
// execution. It contains information about mutated contract codes, accounts,
// and storage slots, along with their original values.
// stateUpdate captures the difference between two states resulting from
// execution. It records all mutated accounts, contract codes, and storage
// slots, along with their original values.
type stateUpdate struct {
originRoot common.Hash // hash of the state before applying mutation
root common.Hash // hash of the state after applying mutation
blockNumber uint64 // Associated block number
originRoot common.Hash // originRoot is the state root before applying changes.
root common.Hash // root is the state root after applying changes.
blockNumber uint64 // blockNumber is the associated block height.
accounts map[common.Hash][]byte // accounts stores mutated accounts in 'slim RLP' encoding
accountsOrigin map[common.Address][]byte // accountsOrigin stores the original values of mutated accounts in 'slim RLP' encoding
accounts map[common.Hash]*Account // accounts contains mutated accounts, keyed by account hash.
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.
// The value is keyed by account hash and **storage slot key hash**.
storages map[common.Hash]map[common.Hash][]byte
// storages contains mutated storage slots, keyed by account hash and
// storage slot key hash.
storages map[common.Hash]map[common.Hash]common.Hash
// storagesOrigin stores the original values of mutated slots in
// 'prefix-zero-trimmed' RLP format.
// (a) the value is keyed by account hash and **storage slot key** if rawStorageKey is true;
// (b) the value is keyed by account hash and **storage slot key hash** if rawStorageKey is false;
storagesOrigin map[common.Address]map[common.Hash][]byte
// storagesOrigin holds original values of mutated storage slots.
// The key format depends on rawStorageKey:
// - if true: keyed by account address and raw storage slot key.
// - if false: keyed by account address and storage slot key hash.
storagesOrigin map[common.Address]map[common.Hash]common.Hash
rawStorageKey bool
codes map[common.Address]*contractCode // codes contains the set of dirty codes
nodes *trienode.MergedNodeSet // Aggregated dirty nodes caused by state changes
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
}
// 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
// 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 (
accounts = make(map[common.Hash][]byte)
accountsOrigin = make(map[common.Address][]byte)
storages = make(map[common.Hash]map[common.Hash][]byte)
storagesOrigin = make(map[common.Address]map[common.Hash][]byte)
accounts = make(map[common.Hash]*Account)
accountsOrigin = make(map[common.Address]*Account)
storages = make(map[common.Hash]map[common.Hash]common.Hash)
storagesOrigin = make(map[common.Address]map[common.Hash]common.Hash)
codes = make(map[common.Address]*contractCode)
)
// 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 {
addr := op.address
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 len(op.storages) > 0 {
@ -174,16 +168,17 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
}
}
return &stateUpdate{
originRoot: originRoot,
root: root,
blockNumber: blockNumber,
accounts: accounts,
accountsOrigin: accountsOrigin,
storages: storages,
storagesOrigin: storagesOrigin,
rawStorageKey: rawStorageKey,
codes: codes,
nodes: nodes,
originRoot: originRoot,
root: root,
blockNumber: blockNumber,
accounts: accounts,
accountsOrigin: accountsOrigin,
storages: storages,
storagesOrigin: storagesOrigin,
rawStorageKey: rawStorageKey,
codes: codes,
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
// package.
func (sc *stateUpdate) stateSet() *triedb.StateSet {
return &triedb.StateSet{
Accounts: sc.accounts,
AccountsOrigin: sc.accountsOrigin,
Storages: sc.storages,
StoragesOrigin: sc.storagesOrigin,
RawStorageKey: sc.rawStorageKey,
}
return nil
//return &triedb.StateSet{
// Accounts: sc.accounts,
// AccountsOrigin: sc.accountsOrigin,
// Storages: sc.storages,
// StoragesOrigin: sc.storagesOrigin,
// RawStorageKey: sc.rawStorageKey,
//}
}
// 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.
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
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
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
}

View file

@ -259,6 +259,16 @@ func (set *MergedNodeSet) Merge(other *NodeSet) error {
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.
func (set *MergedNodeSet) Nodes() map[common.Hash]map[string]*Node {
nodes := make(map[common.Hash]map[string]*Node, len(set.Sets))