mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 01:41:36 +00:00
core/state: implement merkle hasher
This commit is contained in:
parent
38c7021c73
commit
282cece030
12 changed files with 740 additions and 531 deletions
|
|
@ -2265,7 +2265,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
stats.CodeReads = statedb.CodeReads
|
stats.CodeReads = statedb.CodeReads
|
||||||
|
|
||||||
stats.AccountLoaded = statedb.AccountLoaded
|
stats.AccountLoaded = statedb.AccountLoaded
|
||||||
stats.AccountUpdated = statedb.AccountUpdated
|
//stats.AccountUpdated = statedb.AccountUpdated
|
||||||
stats.AccountDeleted = statedb.AccountDeleted
|
stats.AccountDeleted = statedb.AccountDeleted
|
||||||
stats.StorageLoaded = statedb.StorageLoaded
|
stats.StorageLoaded = statedb.StorageLoaded
|
||||||
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
stats.StorageUpdated = int(statedb.StorageUpdated.Load())
|
||||||
|
|
@ -2273,8 +2273,8 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
||||||
|
|
||||||
stats.CodeLoaded = statedb.CodeLoaded
|
stats.CodeLoaded = statedb.CodeLoaded
|
||||||
stats.CodeLoadBytes = statedb.CodeLoadBytes
|
stats.CodeLoadBytes = statedb.CodeLoadBytes
|
||||||
stats.CodeUpdated = statedb.CodeUpdated
|
//stats.CodeUpdated = statedb.CodeUpdated
|
||||||
stats.CodeUpdateBytes = statedb.CodeUpdateBytes
|
//stats.CodeUpdateBytes = statedb.CodeUpdateBytes
|
||||||
|
|
||||||
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
|
stats.Execution = ptime - (statedb.AccountReads + statedb.StorageReads + statedb.CodeReads) // The time spent on EVM processing
|
||||||
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/bintrie"
|
"github.com/ethereum/go-ethereum/trie/bintrie"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
|
|
@ -220,8 +221,10 @@ func (db *CachingDB) Reader(stateRoot common.Hash) (Reader, error) {
|
||||||
return newReader(db.codedb.Reader(), sr), nil
|
return newReader(db.codedb.Reader(), sr), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hasher implements Database, returning a hasher associated with the specified
|
||||||
|
// state root.
|
||||||
func (db *CachingDB) Hasher(stateRoot common.Hash) (Hasher, error) {
|
func (db *CachingDB) Hasher(stateRoot common.Hash) (Hasher, error) {
|
||||||
return &noopHasher{}, nil
|
return newMerkleHasher(stateRoot, db.triedb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadersWithCacheStats creates a pair of state readers that share the same
|
// ReadersWithCacheStats creates a pair of state readers that share the same
|
||||||
|
|
@ -300,18 +303,26 @@ func (db *CachingDB) Commit(update *stateUpdate) error {
|
||||||
}
|
}
|
||||||
// If snapshotting is enabled, update the snapshot tree with this new version
|
// If snapshotting is enabled, update the snapshot tree with this new version
|
||||||
if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil {
|
if db.snap != nil && db.snap.Snapshot(update.originRoot) != nil {
|
||||||
//if err := db.snap.Update(update.root, update.originRoot, update.accounts, update.storages); err != nil {
|
accounts, _, storages, _, err := update.encodeMerkle()
|
||||||
// log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
|
if err != nil {
|
||||||
//}
|
return err
|
||||||
//// Keep 128 diff layers in the memory, persistent layer is 129th.
|
}
|
||||||
//// - head layer is paired with HEAD state
|
if err := db.snap.Update(update.root, update.originRoot, accounts, storages); err != nil {
|
||||||
//// - head-1 layer is paired with HEAD-1 state
|
log.Warn("Failed to update snapshot tree", "from", update.originRoot, "to", update.root, "err", err)
|
||||||
//// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
}
|
||||||
//if err := db.snap.Cap(update.root, TriesInMemory); err != nil {
|
// Keep 128 diff layers in the memory, persistent layer is 129th.
|
||||||
// log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err)
|
// - head layer is paired with HEAD state
|
||||||
//}
|
// - head-1 layer is paired with HEAD-1 state
|
||||||
|
// - head-127 layer(bottom-most diff layer) is paired with HEAD-127 state
|
||||||
|
if err := db.snap.Cap(update.root, TriesInMemory); err != nil {
|
||||||
|
log.Warn("Failed to cap snapshot tree", "root", update.root, "layers", TriesInMemory, "err", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, update.stateSet())
|
stateSet, err := update.stateSet()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.triedb.Update(update.root, update.originRoot, update.blockNumber, update.nodes, stateSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iteratee returns a state iteratee associated with the specified state root,
|
// Iteratee returns a state iteratee associated with the specified state root,
|
||||||
|
|
|
||||||
|
|
@ -17,25 +17,38 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"maps"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/stateless"
|
"github.com/ethereum/go-ethereum/core/stateless"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
|
"github.com/ethereum/go-ethereum/trie"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccountMutation describes a state transition for a single account.
|
// CodeMut represents a mutation to contract code.
|
||||||
type AccountMutation struct {
|
type CodeMut struct {
|
||||||
Account *Account // Null for deletion
|
Code []byte // Null for deletion
|
||||||
DirtyCode bool // Flag whether the code is changed
|
|
||||||
Code []byte // Null for deletion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SecondaryHash encapsulates the secondary hash of storage tries.
|
// AccountMut represents a mutation to an account.
|
||||||
// It is only relevant in the context of the Merkle Patricia Trie and
|
// Semantics:
|
||||||
// includes both the post-transition root and the original root.
|
// - Account == nil: delete the account
|
||||||
type SecondaryHash struct {
|
// - Code == nil: leave code unchanged
|
||||||
Hash common.Hash
|
// - Code != nil: apply the given code mutation
|
||||||
Prev common.Hash
|
type AccountMut struct {
|
||||||
|
Account *Account // Null for deletion
|
||||||
|
Code *CodeMut // Null for unchanged
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hashes encapsulates a trie root together with its original (pre-update) root.
|
||||||
|
type Hashes struct {
|
||||||
|
Hash common.Hash // Post-mutation root
|
||||||
|
Prev common.Hash // Pre-mutation root
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hasher defines the minimal interface for computing state root hashes.
|
// Hasher defines the minimal interface for computing state root hashes.
|
||||||
|
|
@ -54,11 +67,9 @@ type SecondaryHash struct {
|
||||||
// compatibility with pre-Byzantium semantics.
|
// compatibility with pre-Byzantium semantics.
|
||||||
type Hasher interface {
|
type Hasher interface {
|
||||||
// UpdateAccount writes a list of accounts into the state.
|
// UpdateAccount writes a list of accounts into the state.
|
||||||
UpdateAccount(addresses []common.Address, accounts []AccountMutation) error
|
UpdateAccount(addresses []common.Address, accounts []AccountMut) error
|
||||||
|
|
||||||
// UpdateStorage writes a list of storage slot value. The hasher handles
|
// UpdateStorage writes a list of storage slot value.
|
||||||
// encoding internally. Note, the value with empty data should be
|
|
||||||
// interpreted as deletion.
|
|
||||||
UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error
|
UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error
|
||||||
|
|
||||||
// Hash computes and returns the state root hash without committing.
|
// Hash computes and returns the state root hash without committing.
|
||||||
|
|
@ -70,14 +81,14 @@ type Hasher interface {
|
||||||
// Additionally, if the hasher uses a two-layer structure, the roots of the
|
// Additionally, if the hasher uses a two-layer structure, the roots of the
|
||||||
// secondary tries together with their original hashes will also be returned
|
// secondary tries together with their original hashes will also be returned
|
||||||
// for all mutated accounts, regardless of whether their storage was modified.
|
// for all mutated accounts, regardless of whether their storage was modified.
|
||||||
Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]SecondaryHash, error)
|
Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error)
|
||||||
|
|
||||||
// Copy returns a deep-copied hasher instance.
|
// Copy returns a deep-copied hasher instance.
|
||||||
Copy() Hasher
|
Copy() Hasher
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefetcher is an optional extension implemented by hashers that can
|
// Prefetcher is an optional extension implemented by hashers that can
|
||||||
// asynchronously warm up trie/state data ahead of mutations or hashing.
|
// asynchronously warm up trie/state data ahead of hashing.
|
||||||
type Prefetcher interface {
|
type Prefetcher interface {
|
||||||
// PrefetchAccount schedules the account for prefetching.
|
// PrefetchAccount schedules the account for prefetching.
|
||||||
PrefetchAccount(addresses []common.Address, read bool)
|
PrefetchAccount(addresses []common.Address, read bool)
|
||||||
|
|
@ -87,9 +98,9 @@ type Prefetcher interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WitnessCollector is an optional extension implemented by hashers that can
|
// WitnessCollector is an optional extension implemented by hashers that can
|
||||||
// construct a stateless witness for the most recent committed state transition.
|
// construct a state witness for the most recent committed state transition.
|
||||||
type WitnessCollector interface {
|
type WitnessCollector interface {
|
||||||
// Witness returns the stateless witness corresponding to the most recent
|
// Witness returns the state witness corresponding to the most recent
|
||||||
// committed state transition.
|
// committed state transition.
|
||||||
Witness() (*stateless.Witness, error)
|
Witness() (*stateless.Witness, error)
|
||||||
}
|
}
|
||||||
|
|
@ -120,29 +131,207 @@ type Prover interface {
|
||||||
ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error
|
ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// noopHasher is a Hasher implementation that performs no work and always
|
||||||
|
// returns an empty state root.
|
||||||
type noopHasher struct{}
|
type noopHasher struct{}
|
||||||
|
|
||||||
func (n noopHasher) UpdateAccount(addresses []common.Address, accounts []AccountMutation) error {
|
func (n *noopHasher) UpdateAccount([]common.Address, []AccountMut) error { return nil }
|
||||||
//TODO implement me
|
func (n *noopHasher) UpdateStorage(common.Address, []common.Hash, []common.Hash) error {
|
||||||
panic("implement me")
|
return nil
|
||||||
|
}
|
||||||
|
func (n *noopHasher) Hash() common.Hash { return common.Hash{} }
|
||||||
|
func (n *noopHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) {
|
||||||
|
return common.Hash{}, trienode.NewMergedNodeSet(), make(map[common.Address]Hashes), nil
|
||||||
|
}
|
||||||
|
func (n *noopHasher) Copy() Hasher { return &noopHasher{} }
|
||||||
|
|
||||||
|
// merkleHasher is a Hasher implementation backed by the traditional two-layer
|
||||||
|
// Merkle Patricia Trie (separate account trie and per-account storage tries).
|
||||||
|
type merkleHasher struct {
|
||||||
|
db *triedb.Database
|
||||||
|
root common.Hash
|
||||||
|
|
||||||
|
accountTrie *trie.StateTrie
|
||||||
|
storageTries map[common.Address]*trie.StateTrie // lazily opened
|
||||||
|
|
||||||
|
// storageRoots tracks the storage root transition for every mutated
|
||||||
|
// account. Prev is recorded once (first touch) and Hash is updated
|
||||||
|
// on each UpdateAccount call.
|
||||||
|
storageRoots map[common.Address]Hashes
|
||||||
|
|
||||||
|
lock sync.Mutex // guards storageTries (concurrent updateTrie)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n noopHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error {
|
func newMerkleHasher(root common.Hash, db *triedb.Database) (*merkleHasher, error) {
|
||||||
//TODO implement me
|
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db)
|
||||||
panic("implement me")
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &merkleHasher{
|
||||||
|
db: db,
|
||||||
|
root: root,
|
||||||
|
accountTrie: tr,
|
||||||
|
storageTries: make(map[common.Address]*trie.StateTrie),
|
||||||
|
storageRoots: make(map[common.Address]Hashes),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n noopHasher) Hash() common.Hash {
|
// accountStorageRoot reads the storage root of account from the account trie.
|
||||||
//TODO implement me
|
func (h *merkleHasher) accountStorageRoot(addr common.Address) common.Hash {
|
||||||
panic("implement me")
|
if acc, _ := h.accountTrie.GetAccount(addr); acc != nil {
|
||||||
|
return acc.Root
|
||||||
|
}
|
||||||
|
return types.EmptyRootHash
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n noopHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]SecondaryHash, error) {
|
// recordOrigin records the original (pre-mutation) storage root for addr.
|
||||||
//TODO implement me
|
// Only the first call per address has any effect.
|
||||||
panic("implement me")
|
func (h *merkleHasher) recordOrigin(addr common.Address) {
|
||||||
|
if _, ok := h.storageRoots[addr]; !ok {
|
||||||
|
root := h.accountStorageRoot(addr)
|
||||||
|
h.storageRoots[addr] = Hashes{
|
||||||
|
Prev: root,
|
||||||
|
Hash: root,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n noopHasher) Copy() Hasher {
|
// openStorageTrie returns the cached storage trie for the given address,
|
||||||
//TODO implement me
|
// or opens one from the database if not already cached.
|
||||||
panic("implement me")
|
func (h *merkleHasher) openStorageTrie(address common.Address) (*trie.StateTrie, error) {
|
||||||
|
if st, ok := h.storageTries[address]; ok {
|
||||||
|
return st, nil
|
||||||
|
}
|
||||||
|
// Record the original storage trie root if it has not already been tracked
|
||||||
|
// when the storage trie is loaded.
|
||||||
|
h.recordOrigin(address)
|
||||||
|
|
||||||
|
id := trie.StorageTrieID(h.root, crypto.Keccak256Hash(address.Bytes()), h.accountStorageRoot(address))
|
||||||
|
st, err := trie.NewStateTrie(id, h.db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
h.storageTries[address] = st
|
||||||
|
return st, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *merkleHasher) UpdateStorage(address common.Address, keys []common.Hash, values []common.Hash) error {
|
||||||
|
h.lock.Lock()
|
||||||
|
st, err := h.openStorageTrie(address)
|
||||||
|
if err != nil {
|
||||||
|
h.lock.Unlock()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.lock.Unlock()
|
||||||
|
|
||||||
|
for i, key := range keys {
|
||||||
|
if values[i] == (common.Hash{}) {
|
||||||
|
if err := st.DeleteStorage(address, key[:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := st.UpdateStorage(address, key[:], common.TrimLeftZeroes(values[i][:])); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *merkleHasher) UpdateAccount(addresses []common.Address, accounts []AccountMut) error {
|
||||||
|
for i, addr := range addresses {
|
||||||
|
h.recordOrigin(addr)
|
||||||
|
acct := accounts[i]
|
||||||
|
|
||||||
|
// Deletion: remove from account trie and evict any cached
|
||||||
|
// storage trie so a re-created account starts fresh.
|
||||||
|
if acct.Account == nil {
|
||||||
|
if err := h.accountTrie.DeleteAccount(addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(h.storageTries, addr)
|
||||||
|
|
||||||
|
h.storageRoots[addr] = Hashes{
|
||||||
|
Prev: h.storageRoots[addr].Prev,
|
||||||
|
Hash: types.EmptyRootHash,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Determine storage root from the cached trie (if storage was
|
||||||
|
// modified) or from the account trie (unchanged storage).
|
||||||
|
storageRoot := h.accountStorageRoot(addr)
|
||||||
|
if st, ok := h.storageTries[addr]; ok {
|
||||||
|
storageRoot = st.Hash()
|
||||||
|
}
|
||||||
|
sa := &types.StateAccount{
|
||||||
|
Nonce: acct.Account.Nonce,
|
||||||
|
Balance: acct.Account.Balance,
|
||||||
|
Root: storageRoot,
|
||||||
|
CodeHash: acct.Account.CodeHash,
|
||||||
|
}
|
||||||
|
if err := h.accountTrie.UpdateAccount(addr, sa, 0); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.storageRoots[addr] = Hashes{
|
||||||
|
Prev: h.storageRoots[addr].Prev,
|
||||||
|
Hash: storageRoot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *merkleHasher) Hash() common.Hash {
|
||||||
|
return h.accountTrie.Hash()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *merkleHasher) Commit() (common.Hash, *trienode.MergedNodeSet, map[common.Address]Hashes, error) {
|
||||||
|
nodes := trienode.NewMergedNodeSet()
|
||||||
|
|
||||||
|
// Commit all dirty storage tries.
|
||||||
|
for _, st := range h.storageTries {
|
||||||
|
if _, set := st.Commit(false); set != nil {
|
||||||
|
if err := nodes.Merge(set); err != nil {
|
||||||
|
return common.Hash{}, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Commit the account trie. collectLeaf must be true so that hashdb
|
||||||
|
// can link account trie leaves to their storage trie roots.
|
||||||
|
root, set := h.accountTrie.Commit(true)
|
||||||
|
if set != nil {
|
||||||
|
if err := nodes.Merge(set); err != nil {
|
||||||
|
return common.Hash{}, nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root, nodes, h.storageRoots, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *merkleHasher) Copy() Hasher {
|
||||||
|
cpy := &merkleHasher{
|
||||||
|
db: h.db,
|
||||||
|
root: h.root,
|
||||||
|
accountTrie: h.accountTrie.Copy(),
|
||||||
|
storageTries: make(map[common.Address]*trie.StateTrie, len(h.storageTries)),
|
||||||
|
storageRoots: maps.Clone(h.storageRoots),
|
||||||
|
}
|
||||||
|
for addr, st := range h.storageTries {
|
||||||
|
cpy.storageTries[addr] = st.Copy()
|
||||||
|
}
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProveAccount implements Prover by constructing a Merkle proof for the
|
||||||
|
// given account against the current account trie.
|
||||||
|
func (h *merkleHasher) ProveAccount(addr common.Address, proofDb ethdb.KeyValueWriter) error {
|
||||||
|
return h.accountTrie.Prove(crypto.Keccak256(addr.Bytes()), proofDb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProveStorage implements Prover by constructing a Merkle proof for the given
|
||||||
|
// storage slot. The storage trie is opened lazily if not already cached.
|
||||||
|
func (h *merkleHasher) ProveStorage(addr common.Address, key common.Hash, proofDb ethdb.KeyValueWriter) error {
|
||||||
|
st, err := h.openStorageTrie(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return st.Prove(crypto.Keccak256(key.Bytes()), proofDb)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
75
core/state/state_mut.go
Normal file
75
core/state/state_mut.go
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
// Copyright 2026 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package state
|
||||||
|
|
||||||
|
import "github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
|
type mutationType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
update mutationType = iota
|
||||||
|
deletion
|
||||||
|
)
|
||||||
|
|
||||||
|
type mutation struct {
|
||||||
|
typ mutationType
|
||||||
|
applied bool
|
||||||
|
|
||||||
|
// precedingDelete indicates that a previously unapplied deletion was
|
||||||
|
// overwritten by an update (account deleted then re-created within
|
||||||
|
// the same block). IntermediateRoot uses this to notify the hasher
|
||||||
|
// of the deletion before the update so that any cached storage trie
|
||||||
|
// is evicted and the re-created account starts with a fresh trie.
|
||||||
|
precedingDelete bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mutation) copy() *mutation {
|
||||||
|
return &mutation{typ: m.typ, applied: m.applied, precedingDelete: m.precedingDelete}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mutation) isDelete() bool {
|
||||||
|
return m.typ == deletion
|
||||||
|
}
|
||||||
|
|
||||||
|
// markDelete is invoked when an account is deleted but the deletion is
|
||||||
|
// not yet committed. The pending mutation is cached and will be applied
|
||||||
|
// all together.
|
||||||
|
func (s *StateDB) markDelete(addr common.Address) {
|
||||||
|
if _, ok := s.mutations[addr]; !ok {
|
||||||
|
s.mutations[addr] = &mutation{}
|
||||||
|
}
|
||||||
|
s.mutations[addr].applied = false
|
||||||
|
s.mutations[addr].typ = deletion
|
||||||
|
s.mutations[addr].precedingDelete = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *StateDB) markUpdate(addr common.Address) {
|
||||||
|
m, ok := s.mutations[addr]
|
||||||
|
if !ok {
|
||||||
|
s.mutations[addr] = &mutation{}
|
||||||
|
m = s.mutations[addr]
|
||||||
|
}
|
||||||
|
// If this update overwrites a pending (unapplied) deletion, record it
|
||||||
|
// so that IntermediateRoot can notify the hasher of the deletion first.
|
||||||
|
// Do not reset precedingDelete otherwise: a subsequent markUpdate must
|
||||||
|
// preserve the flag set by an earlier markDelete→markUpdate sequence.
|
||||||
|
if !m.applied && m.typ == deletion {
|
||||||
|
m.precedingDelete = true
|
||||||
|
}
|
||||||
|
m.applied = false
|
||||||
|
m.typ = update
|
||||||
|
}
|
||||||
|
|
@ -121,15 +121,6 @@ func (s *stateObject) touch() {
|
||||||
s.db.journal.touchChange(s.address)
|
s.db.journal.touchChange(s.address)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTrie returns the associated storage trie. The trie will be opened if it's
|
|
||||||
// not loaded previously. An error will be returned if trie can't be loaded.
|
|
||||||
//
|
|
||||||
// If a new trie is opened, it will be cached within the state object to allow
|
|
||||||
// subsequent reads to expand the same trie instead of reloading from disk.
|
|
||||||
func (s *stateObject) getTrie() (Trie, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetState retrieves a value associated with the given storage key.
|
// GetState retrieves a value associated with the given storage key.
|
||||||
func (s *stateObject) GetState(key common.Hash) common.Hash {
|
func (s *stateObject) GetState(key common.Hash) common.Hash {
|
||||||
value, _ := s.getState(key)
|
value, _ := s.getState(key)
|
||||||
|
|
@ -294,6 +285,7 @@ func (s *stateObject) updateTrie() error {
|
||||||
vals = append(vals, value)
|
vals = append(vals, value)
|
||||||
}
|
}
|
||||||
s.uncommittedStorage = make(Storage) // empties the commit markers
|
s.uncommittedStorage = make(Storage) // empties the commit markers
|
||||||
|
|
||||||
return s.db.hasher.UpdateStorage(s.address, keys, vals)
|
return s.db.hasher.UpdateStorage(s.address, keys, vals)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -344,12 +336,12 @@ func (s *stateObject) commit() (*accountUpdate, error) {
|
||||||
}
|
}
|
||||||
// commit the contract code if it's modified
|
// commit the contract code if it's modified
|
||||||
if s.dirtyCode {
|
if s.dirtyCode {
|
||||||
|
s.dirtyCode = false // reset the dirty flag
|
||||||
|
|
||||||
op.code = &contractCode{
|
op.code = &contractCode{
|
||||||
hash: common.BytesToHash(s.CodeHash()),
|
hash: common.BytesToHash(s.CodeHash()),
|
||||||
blob: s.code,
|
blob: s.code,
|
||||||
}
|
}
|
||||||
s.dirtyCode = false // reset the dirty flag
|
|
||||||
|
|
||||||
if s.origin == nil {
|
if s.origin == nil {
|
||||||
op.code.originHash = types.EmptyCodeHash
|
op.code.originHash = types.EmptyCodeHash
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -494,7 +486,3 @@ func (s *stateObject) Balance() *uint256.Int {
|
||||||
func (s *stateObject) Nonce() uint64 {
|
func (s *stateObject) Nonce() uint64 {
|
||||||
return s.data.Nonce
|
return s.data.Nonce
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stateObject) Root() common.Hash {
|
|
||||||
return common.Hash{}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/metrics"
|
"github.com/ethereum/go-ethereum/metrics"
|
||||||
|
|
@ -126,134 +127,137 @@ func (s SizeStats) add(diff SizeStats) SizeStats {
|
||||||
|
|
||||||
// calSizeStats measures the state size changes of the provided state update.
|
// calSizeStats measures the state size changes of the provided state update.
|
||||||
func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
func calSizeStats(update *stateUpdate) (SizeStats, error) {
|
||||||
return SizeStats{}, nil
|
stats := SizeStats{
|
||||||
//stats := SizeStats{
|
BlockNumber: update.blockNumber,
|
||||||
// BlockNumber: update.blockNumber,
|
StateRoot: update.root,
|
||||||
// StateRoot: update.root,
|
}
|
||||||
//}
|
accounts, accountOrigin, storages, storageOrigin, err := update.encodeMerkle()
|
||||||
//
|
if err != nil {
|
||||||
//// Measure the account changes
|
return SizeStats{}, err
|
||||||
//for addr, oldValue := range update.accountsOrigin {
|
}
|
||||||
// addrHash := crypto.Keccak256Hash(addr.Bytes())
|
|
||||||
// newValue, exists := update.accounts[addrHash]
|
// Measure the account changes
|
||||||
// if !exists {
|
for addr, oldValue := range accountOrigin {
|
||||||
// return SizeStats{}, fmt.Errorf("account %x not found", addr)
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
// }
|
newValue, exists := accounts[addrHash]
|
||||||
// oldLen, newLen := len(oldValue), len(newValue)
|
if !exists {
|
||||||
//
|
return SizeStats{}, fmt.Errorf("account %x not found", addr)
|
||||||
// switch {
|
}
|
||||||
// case oldLen > 0 && newLen == 0:
|
oldLen, newLen := len(oldValue), len(newValue)
|
||||||
// // Account deletion
|
|
||||||
// stats.Accounts -= 1
|
switch {
|
||||||
// stats.AccountBytes -= accountKeySize + int64(oldLen)
|
case oldLen > 0 && newLen == 0:
|
||||||
// case oldLen == 0 && newLen > 0:
|
// Account deletion
|
||||||
// // Account creation
|
stats.Accounts -= 1
|
||||||
// stats.Accounts += 1
|
stats.AccountBytes -= accountKeySize + int64(oldLen)
|
||||||
// stats.AccountBytes += accountKeySize + int64(newLen)
|
case oldLen == 0 && newLen > 0:
|
||||||
// default:
|
// Account creation
|
||||||
// // Account update
|
stats.Accounts += 1
|
||||||
// stats.AccountBytes += int64(newLen - oldLen)
|
stats.AccountBytes += accountKeySize + int64(newLen)
|
||||||
// }
|
default:
|
||||||
//}
|
// Account update
|
||||||
//
|
stats.AccountBytes += int64(newLen - oldLen)
|
||||||
//// Measure storage changes
|
}
|
||||||
//for addr, slots := range update.storagesOrigin {
|
}
|
||||||
// addrHash := crypto.Keccak256Hash(addr.Bytes())
|
|
||||||
// subset, exists := update.storages[addrHash]
|
// Measure storage changes
|
||||||
// if !exists {
|
for addr, slots := range storageOrigin {
|
||||||
// return SizeStats{}, fmt.Errorf("storage %x not found", addr)
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
// }
|
subset, exists := storages[addrHash]
|
||||||
// for key, oldValue := range slots {
|
if !exists {
|
||||||
// var (
|
return SizeStats{}, fmt.Errorf("storage %x not found", addr)
|
||||||
// exists bool
|
}
|
||||||
// newValue []byte
|
for key, oldValue := range slots {
|
||||||
// )
|
var (
|
||||||
// if update.rawStorageKey {
|
exists bool
|
||||||
// newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
newValue []byte
|
||||||
// } else {
|
)
|
||||||
// newValue, exists = subset[key]
|
if update.rawStorageKey {
|
||||||
// }
|
newValue, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||||
// if !exists {
|
} else {
|
||||||
// return SizeStats{}, fmt.Errorf("storage slot %x-%x not found", addr, key)
|
newValue, exists = subset[key]
|
||||||
// }
|
}
|
||||||
// oldLen, newLen := len(oldValue), len(newValue)
|
if !exists {
|
||||||
//
|
return SizeStats{}, fmt.Errorf("storage slot %x-%x not found", addr, key)
|
||||||
// switch {
|
}
|
||||||
// case oldLen > 0 && newLen == 0:
|
oldLen, newLen := len(oldValue), len(newValue)
|
||||||
// // Storage deletion
|
|
||||||
// stats.Storages -= 1
|
switch {
|
||||||
// stats.StorageBytes -= storageKeySize + int64(oldLen)
|
case oldLen > 0 && newLen == 0:
|
||||||
// case oldLen == 0 && newLen > 0:
|
// Storage deletion
|
||||||
// // Storage creation
|
stats.Storages -= 1
|
||||||
// stats.Storages += 1
|
stats.StorageBytes -= storageKeySize + int64(oldLen)
|
||||||
// stats.StorageBytes += storageKeySize + int64(newLen)
|
case oldLen == 0 && newLen > 0:
|
||||||
// default:
|
// Storage creation
|
||||||
// // Storage update
|
stats.Storages += 1
|
||||||
// stats.StorageBytes += int64(newLen - oldLen)
|
stats.StorageBytes += storageKeySize + int64(newLen)
|
||||||
// }
|
default:
|
||||||
// }
|
// Storage update
|
||||||
//}
|
stats.StorageBytes += int64(newLen - oldLen)
|
||||||
//
|
}
|
||||||
//// Measure trienode changes
|
}
|
||||||
//for owner, subset := range update.nodes.Sets {
|
}
|
||||||
// var (
|
|
||||||
// keyPrefix int64
|
// Measure trienode changes
|
||||||
// isAccount = owner == (common.Hash{})
|
for owner, subset := range update.nodes.Sets {
|
||||||
// )
|
var (
|
||||||
// if isAccount {
|
keyPrefix int64
|
||||||
// keyPrefix = accountTrienodePrefixSize
|
isAccount = owner == (common.Hash{})
|
||||||
// } else {
|
)
|
||||||
// keyPrefix = storageTrienodePrefixSize
|
if isAccount {
|
||||||
// }
|
keyPrefix = accountTrienodePrefixSize
|
||||||
//
|
} else {
|
||||||
// // Iterate over Origins since every modified node has an origin entry
|
keyPrefix = storageTrienodePrefixSize
|
||||||
// for path, oldNode := range subset.Origins {
|
}
|
||||||
// newNode, exists := subset.Nodes[path]
|
|
||||||
// if !exists {
|
// Iterate over Origins since every modified node has an origin entry
|
||||||
// return SizeStats{}, fmt.Errorf("node %x-%v not found", owner, path)
|
for path, oldNode := range subset.Origins {
|
||||||
// }
|
newNode, exists := subset.Nodes[path]
|
||||||
// keySize := keyPrefix + int64(len(path))
|
if !exists {
|
||||||
//
|
return SizeStats{}, fmt.Errorf("node %x-%v not found", owner, path)
|
||||||
// switch {
|
}
|
||||||
// case len(oldNode) > 0 && len(newNode.Blob) == 0:
|
keySize := keyPrefix + int64(len(path))
|
||||||
// // Node deletion
|
|
||||||
// if isAccount {
|
switch {
|
||||||
// stats.AccountTrienodes -= 1
|
case len(oldNode) > 0 && len(newNode.Blob) == 0:
|
||||||
// stats.AccountTrienodeBytes -= keySize + int64(len(oldNode))
|
// Node deletion
|
||||||
// } else {
|
if isAccount {
|
||||||
// stats.StorageTrienodes -= 1
|
stats.AccountTrienodes -= 1
|
||||||
// stats.StorageTrienodeBytes -= keySize + int64(len(oldNode))
|
stats.AccountTrienodeBytes -= keySize + int64(len(oldNode))
|
||||||
// }
|
} else {
|
||||||
// case len(oldNode) == 0 && len(newNode.Blob) > 0:
|
stats.StorageTrienodes -= 1
|
||||||
// // Node creation
|
stats.StorageTrienodeBytes -= keySize + int64(len(oldNode))
|
||||||
// if isAccount {
|
}
|
||||||
// stats.AccountTrienodes += 1
|
case len(oldNode) == 0 && len(newNode.Blob) > 0:
|
||||||
// stats.AccountTrienodeBytes += keySize + int64(len(newNode.Blob))
|
// Node creation
|
||||||
// } else {
|
if isAccount {
|
||||||
// stats.StorageTrienodes += 1
|
stats.AccountTrienodes += 1
|
||||||
// stats.StorageTrienodeBytes += keySize + int64(len(newNode.Blob))
|
stats.AccountTrienodeBytes += keySize + int64(len(newNode.Blob))
|
||||||
// }
|
} else {
|
||||||
// default:
|
stats.StorageTrienodes += 1
|
||||||
// // Node update
|
stats.StorageTrienodeBytes += keySize + int64(len(newNode.Blob))
|
||||||
// if isAccount {
|
}
|
||||||
// stats.AccountTrienodeBytes += int64(len(newNode.Blob) - len(oldNode))
|
default:
|
||||||
// } else {
|
// Node update
|
||||||
// stats.StorageTrienodeBytes += int64(len(newNode.Blob) - len(oldNode))
|
if isAccount {
|
||||||
// }
|
stats.AccountTrienodeBytes += int64(len(newNode.Blob) - len(oldNode))
|
||||||
// }
|
} else {
|
||||||
// }
|
stats.StorageTrienodeBytes += int64(len(newNode.Blob) - len(oldNode))
|
||||||
//}
|
}
|
||||||
//
|
}
|
||||||
//codeExists := make(map[common.Hash]struct{})
|
}
|
||||||
//for _, code := range update.codes {
|
}
|
||||||
// if _, ok := codeExists[code.hash]; ok || code.duplicate {
|
|
||||||
// continue
|
codeExists := make(map[common.Hash]struct{})
|
||||||
// }
|
for _, code := range update.codes {
|
||||||
// stats.ContractCodes += 1
|
if _, ok := codeExists[code.hash]; ok || code.duplicate {
|
||||||
// stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
|
continue
|
||||||
// codeExists[code.hash] = struct{}{}
|
}
|
||||||
//}
|
stats.ContractCodes += 1
|
||||||
//return stats, nil
|
stats.ContractCodeBytes += codeKeySize + int64(len(code.blob))
|
||||||
|
codeExists[code.hash] = struct{}{}
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type stateSizeQuery struct {
|
type stateSizeQuery struct {
|
||||||
|
|
|
||||||
|
|
@ -42,26 +42,6 @@ import (
|
||||||
// TriesInMemory represents the number of layers that are kept in RAM.
|
// TriesInMemory represents the number of layers that are kept in RAM.
|
||||||
const TriesInMemory = 128
|
const TriesInMemory = 128
|
||||||
|
|
||||||
type mutationType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
update mutationType = iota
|
|
||||||
deletion
|
|
||||||
)
|
|
||||||
|
|
||||||
type mutation struct {
|
|
||||||
typ mutationType
|
|
||||||
applied bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mutation) copy() *mutation {
|
|
||||||
return &mutation{typ: m.typ, applied: m.applied}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mutation) isDelete() bool {
|
|
||||||
return m.typ == deletion
|
|
||||||
}
|
|
||||||
|
|
||||||
// StateDB structs within the ethereum protocol are used to store anything
|
// StateDB structs within the ethereum protocol are used to store anything
|
||||||
// within the merkle trie. StateDBs take care of caching and storing
|
// within the merkle trie. StateDBs take care of caching and storing
|
||||||
// nested states. It's the general query interface to retrieve:
|
// nested states. It's the general query interface to retrieve:
|
||||||
|
|
@ -144,7 +124,6 @@ type StateDB struct {
|
||||||
CodeReads time.Duration
|
CodeReads time.Duration
|
||||||
|
|
||||||
AccountLoaded int // Number of accounts retrieved from the database during the state transition
|
AccountLoaded int // Number of accounts retrieved from the database during the state transition
|
||||||
AccountUpdated int // Number of accounts updated during the state transition
|
|
||||||
AccountDeleted int // Number of accounts deleted during the state transition
|
AccountDeleted int // Number of accounts deleted during the state transition
|
||||||
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
|
StorageLoaded int // Number of storage slots retrieved from the database during the state transition
|
||||||
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
StorageUpdated atomic.Int64 // Number of storage slots updated during the state transition
|
||||||
|
|
@ -154,10 +133,8 @@ type StateDB struct {
|
||||||
// This value may be smaller than the actual number of bytes read, since
|
// This value may be smaller than the actual number of bytes read, since
|
||||||
// some APIs (e.g. CodeSize) may load the entire code from either the
|
// some APIs (e.g. CodeSize) may load the entire code from either the
|
||||||
// cache or the database when the size is not available in the cache.
|
// cache or the database when the size is not available in the cache.
|
||||||
CodeLoaded int // Number of contract code loaded during the state transition
|
CodeLoaded int // Number of contract code loaded during the state transition
|
||||||
CodeLoadBytes int // Total bytes of resolved code
|
CodeLoadBytes int // Total bytes of resolved code
|
||||||
CodeUpdated int // Number of contracts with code changes that persisted
|
|
||||||
CodeUpdateBytes int // Total bytes of persisted code written
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new state from a given trie.
|
// New creates a new state from a given trie.
|
||||||
|
|
@ -311,19 +288,6 @@ func (s *StateDB) GetNonce(addr common.Address) uint64 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStorageRoot retrieves the storage root from the given address or empty
|
|
||||||
// if object not found.
|
|
||||||
//
|
|
||||||
// Note: the storage root returned corresponds to the trie since last Intermediate
|
|
||||||
// operation, some recent in-memory changes are excluded.
|
|
||||||
func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash {
|
|
||||||
stateObject := s.getStateObject(addr)
|
|
||||||
if stateObject != nil {
|
|
||||||
return stateObject.Root()
|
|
||||||
}
|
|
||||||
return common.Hash{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxIndex returns the current transaction index set by SetTxContext.
|
// TxIndex returns the current transaction index set by SetTxContext.
|
||||||
func (s *StateDB) TxIndex() int {
|
func (s *StateDB) TxIndex() int {
|
||||||
return s.txIndex
|
return s.txIndex
|
||||||
|
|
@ -777,64 +741,73 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
|
||||||
// Finalise all the dirty storage states and write them into the tries
|
// Finalise all the dirty storage states and write them into the tries
|
||||||
s.Finalise(deleteEmptyObjects)
|
s.Finalise(deleteEmptyObjects)
|
||||||
|
|
||||||
// Process all storage updates concurrently. The state object update root
|
// Pre-process mutations whose preceding deletion has not yet been
|
||||||
// method will internally call a blocking trie fetch from the prefetcher,
|
// applied. This happens when an account is deleted and then re-created
|
||||||
// so there's no need to explicitly wait for the prefetchers to finish.
|
// within the same block and the deletion was overwritten by the update.
|
||||||
|
// Notify the hasher of the deletion first so that any cached storage
|
||||||
|
// trie is evicted and the re-created account starts with a fresh trie.
|
||||||
var (
|
var (
|
||||||
start = time.Now()
|
delAddrs []common.Address
|
||||||
workers errgroup.Group
|
delAccts []AccountMut
|
||||||
|
start = time.Now()
|
||||||
)
|
)
|
||||||
|
for addr, op := range s.mutations {
|
||||||
|
if !op.precedingDelete {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
op.precedingDelete = false
|
||||||
|
delAddrs = append(delAddrs, addr)
|
||||||
|
delAccts = append(delAccts, AccountMut{Account: nil})
|
||||||
|
}
|
||||||
|
if len(delAddrs) > 0 {
|
||||||
|
if err := s.hasher.UpdateAccount(delAddrs, delAccts); err != nil {
|
||||||
|
s.setError(err)
|
||||||
|
return common.Hash{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.AccountUpdates += time.Since(start)
|
||||||
|
|
||||||
|
// Process all storage updates concurrently, flushing them to hasher.
|
||||||
|
start = time.Now()
|
||||||
|
var workers errgroup.Group
|
||||||
for addr, op := range s.mutations {
|
for addr, op := range s.mutations {
|
||||||
if op.applied || op.isDelete() {
|
if op.applied || op.isDelete() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
obj := s.stateObjects[addr] // closure for the task runner below
|
obj := s.stateObjects[addr]
|
||||||
workers.Go(obj.updateTrie)
|
workers.Go(obj.updateTrie)
|
||||||
}
|
}
|
||||||
workers.Wait()
|
if err := workers.Wait(); err != nil {
|
||||||
|
s.setError(err)
|
||||||
|
}
|
||||||
s.StorageUpdates += time.Since(start)
|
s.StorageUpdates += time.Since(start)
|
||||||
|
|
||||||
// Now we're about to start to write changes to the trie. The trie is so far
|
// Process all account updates
|
||||||
// _untouched_. We can check with the prefetcher, if it can give us a trie
|
|
||||||
// which has the same root, but also has some content loaded into it.
|
|
||||||
//
|
|
||||||
// Don't check prefetcher if verkle trie has been used. In the context of verkle,
|
|
||||||
// only a single trie is used for state hashing. Replacing a non-nil verkle tree
|
|
||||||
// here could result in losing uncommitted changes from storage.
|
|
||||||
start = time.Now()
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
addresses []common.Address
|
addresses []common.Address
|
||||||
accounts []AccountMutation
|
accounts []AccountMut
|
||||||
)
|
)
|
||||||
|
start = time.Now()
|
||||||
for addr, op := range s.mutations {
|
for addr, op := range s.mutations {
|
||||||
if op.applied {
|
if op.applied {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
op.applied = true
|
op.applied = true
|
||||||
|
|
||||||
addresses = append(addresses, addr)
|
addresses = append(addresses, addr)
|
||||||
|
|
||||||
if op.isDelete() {
|
if op.isDelete() {
|
||||||
accounts = append(accounts, AccountMutation{Account: nil})
|
accounts = append(accounts, AccountMut{Account: nil})
|
||||||
} else {
|
continue
|
||||||
obj := s.stateObjects[addr]
|
|
||||||
mut := AccountMutation{
|
|
||||||
Account: &obj.data,
|
|
||||||
DirtyCode: obj.dirtyCode,
|
|
||||||
Code: obj.code,
|
|
||||||
}
|
|
||||||
accounts = append(accounts, mut)
|
|
||||||
|
|
||||||
s.AccountUpdated += 1
|
|
||||||
|
|
||||||
// Count code writes post-Finalise so reverted CREATEs are excluded.
|
|
||||||
if obj.dirtyCode {
|
|
||||||
s.CodeUpdated += 1
|
|
||||||
s.CodeUpdateBytes += len(obj.code)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
obj := s.stateObjects[addr]
|
||||||
|
mut := AccountMut{Account: &obj.data}
|
||||||
|
if obj.dirtyCode {
|
||||||
|
mut.Code = &CodeMut{Code: obj.code}
|
||||||
|
}
|
||||||
|
accounts = append(accounts, mut)
|
||||||
}
|
}
|
||||||
if err := s.hasher.UpdateAccount(addresses, accounts); err != nil {
|
if err := s.hasher.UpdateAccount(addresses, accounts); err != nil {
|
||||||
|
s.setError(err)
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
s.AccountUpdates += time.Since(start)
|
s.AccountUpdates += time.Since(start)
|
||||||
|
|
@ -1028,7 +1001,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
||||||
}
|
}
|
||||||
accountReadMeters.Mark(int64(s.AccountLoaded))
|
accountReadMeters.Mark(int64(s.AccountLoaded))
|
||||||
storageReadMeters.Mark(int64(s.StorageLoaded))
|
storageReadMeters.Mark(int64(s.StorageLoaded))
|
||||||
accountUpdatedMeter.Mark(int64(s.AccountUpdated))
|
|
||||||
storageUpdatedMeter.Mark(s.StorageUpdated.Load())
|
storageUpdatedMeter.Mark(s.StorageUpdated.Load())
|
||||||
accountDeletedMeter.Mark(int64(s.AccountDeleted))
|
accountDeletedMeter.Mark(int64(s.AccountDeleted))
|
||||||
storageDeletedMeter.Mark(s.StorageDeleted.Load())
|
storageDeletedMeter.Mark(s.StorageDeleted.Load())
|
||||||
|
|
@ -1038,7 +1010,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool, noStorageWiping bool, blockNum
|
||||||
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
|
storageTriesDeletedMeter.Mark(int64(storageTrieNodesDeleted))
|
||||||
|
|
||||||
// Clear the metric markers
|
// Clear the metric markers
|
||||||
s.AccountLoaded, s.AccountUpdated, s.AccountDeleted = 0, 0, 0
|
s.AccountLoaded, s.AccountDeleted = 0, 0
|
||||||
s.StorageLoaded = 0
|
s.StorageLoaded = 0
|
||||||
s.StorageUpdated.Store(0)
|
s.StorageUpdated.Store(0)
|
||||||
s.StorageDeleted.Store(0)
|
s.StorageDeleted.Store(0)
|
||||||
|
|
@ -1186,25 +1158,6 @@ func (s *StateDB) SlotInAccessList(addr common.Address, slot common.Hash) (addre
|
||||||
return s.accessList.Contains(addr, slot)
|
return s.accessList.Contains(addr, slot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// markDelete is invoked when an account is deleted but the deletion is
|
|
||||||
// not yet committed. The pending mutation is cached and will be applied
|
|
||||||
// all together
|
|
||||||
func (s *StateDB) markDelete(addr common.Address) {
|
|
||||||
if _, ok := s.mutations[addr]; !ok {
|
|
||||||
s.mutations[addr] = &mutation{}
|
|
||||||
}
|
|
||||||
s.mutations[addr].applied = false
|
|
||||||
s.mutations[addr].typ = deletion
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *StateDB) markUpdate(addr common.Address) {
|
|
||||||
if _, ok := s.mutations[addr]; !ok {
|
|
||||||
s.mutations[addr] = &mutation{}
|
|
||||||
}
|
|
||||||
s.mutations[addr].applied = false
|
|
||||||
s.mutations[addr].typ = update
|
|
||||||
}
|
|
||||||
|
|
||||||
// Witness retrieves the current state witness being collected.
|
// Witness retrieves the current state witness being collected.
|
||||||
func (s *StateDB) Witness() *stateless.Witness {
|
func (s *StateDB) Witness() *stateless.Witness {
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
@ -182,10 +183,11 @@ func (test *stateTest) run() bool {
|
||||||
storages []map[common.Hash]map[common.Hash][]byte
|
storages []map[common.Hash]map[common.Hash][]byte
|
||||||
storageOrigin []map[common.Address]map[common.Hash][]byte
|
storageOrigin []map[common.Address]map[common.Hash][]byte
|
||||||
copyUpdate = func(update *stateUpdate) {
|
copyUpdate = func(update *stateUpdate) {
|
||||||
//accounts = append(accounts, maps.Clone(update.accounts))
|
encoded, _ := update.stateSet()
|
||||||
//accountOrigin = append(accountOrigin, maps.Clone(update.accountsOrigin))
|
accounts = append(accounts, maps.Clone(encoded.Accounts))
|
||||||
//storages = append(storages, maps.Clone(update.storages))
|
accountOrigin = append(accountOrigin, maps.Clone(encoded.AccountsOrigin))
|
||||||
//storageOrigin = append(storageOrigin, maps.Clone(update.storagesOrigin))
|
storages = append(storages, maps.Clone(encoded.Storages))
|
||||||
|
storageOrigin = append(storageOrigin, maps.Clone(encoded.StoragesOrigin))
|
||||||
}
|
}
|
||||||
disk = rawdb.NewMemoryDatabase()
|
disk = rawdb.NewMemoryDatabase()
|
||||||
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
|
tdb = triedb.NewDatabase(disk, &triedb.Config{PathDB: pathdb.Defaults})
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,6 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/ethereum/go-ethereum/trie"
|
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
"github.com/ethereum/go-ethereum/triedb/hashdb"
|
"github.com/ethereum/go-ethereum/triedb/hashdb"
|
||||||
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
"github.com/ethereum/go-ethereum/triedb/pathdb"
|
||||||
|
|
@ -540,47 +538,6 @@ func (test *snapshotTest) run() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func forEachStorage(s *StateDB, addr common.Address, cb func(key, value common.Hash) bool) error {
|
|
||||||
so := s.getStateObject(addr)
|
|
||||||
if so == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
tr, err := so.getTrie()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
trieIt, err := tr.NodeIterator(nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
it = trie.NewIterator(trieIt)
|
|
||||||
visited = make(map[common.Hash]bool)
|
|
||||||
)
|
|
||||||
|
|
||||||
for it.Next() {
|
|
||||||
key := common.BytesToHash(tr.GetKey(it.Key))
|
|
||||||
visited[key] = true
|
|
||||||
if value, dirty := so.dirtyStorage[key]; dirty {
|
|
||||||
if !cb(key, value) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(it.Value) > 0 {
|
|
||||||
_, content, _, err := rlp.Split(it.Value)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !cb(key, common.BytesToHash(content)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkEqual checks that methods of state and checkstate return the same values.
|
// checkEqual checks that methods of state and checkstate return the same values.
|
||||||
func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||||
for _, addr := range test.addrs {
|
for _, addr := range test.addrs {
|
||||||
|
|
@ -606,12 +563,6 @@ func (test *snapshotTest) checkEqual(state, checkstate *StateDB) error {
|
||||||
}
|
}
|
||||||
// Check storage.
|
// Check storage.
|
||||||
if obj := state.getStateObject(addr); obj != nil {
|
if obj := state.getStateObject(addr); obj != nil {
|
||||||
forEachStorage(state, addr, func(key, value common.Hash) bool {
|
|
||||||
return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
|
|
||||||
})
|
|
||||||
forEachStorage(checkstate, addr, func(key, value common.Hash) bool {
|
|
||||||
return checkeq("GetState("+key.Hex()+")", checkstate.GetState(addr, key), value)
|
|
||||||
})
|
|
||||||
other := checkstate.getStateObject(addr)
|
other := checkstate.getStateObject(addr)
|
||||||
// Check dirty storage which is not in trie
|
// Check dirty storage which is not in trie
|
||||||
if !maps.Equal(obj.dirtyStorage, other.dirtyStorage) {
|
if !maps.Equal(obj.dirtyStorage, other.dirtyStorage) {
|
||||||
|
|
@ -770,8 +721,14 @@ func TestCopyCommitCopy(t *testing.T) {
|
||||||
t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
t.Fatalf("second copy committed storage slot mismatch: have %x, want %x", val, common.Hash{})
|
||||||
}
|
}
|
||||||
// Commit state, ensure states can be loaded from disk
|
// Commit state, ensure states can be loaded from disk
|
||||||
root, _ := state.Commit(0, false, false)
|
root, err := state.Commit(0, false, false)
|
||||||
state, _ = New(root, tdb)
|
if err != nil {
|
||||||
|
t.Fatalf("commit fail: %v", err)
|
||||||
|
}
|
||||||
|
state, err = New(root, tdb)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("New fail: %v", err)
|
||||||
|
}
|
||||||
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
if balance := state.GetBalance(addr); balance.Cmp(uint256.NewInt(42)) != 0 {
|
||||||
t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42)
|
t.Fatalf("state post-commit balance mismatch: have %v, want %v", balance, 42)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,15 @@
|
||||||
package state
|
package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
"maps"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||||
"github.com/ethereum/go-ethereum/triedb"
|
"github.com/ethereum/go-ethereum/triedb"
|
||||||
)
|
)
|
||||||
|
|
@ -30,8 +33,8 @@ import (
|
||||||
// contractCode encapsulates contract bytecode and its associated metadata.
|
// contractCode encapsulates contract bytecode and its associated metadata.
|
||||||
type contractCode struct {
|
type contractCode struct {
|
||||||
hash common.Hash // hash is the cryptographic hash of the current contract code.
|
hash common.Hash // hash is the cryptographic hash of the current contract code.
|
||||||
blob []byte // blob is the raw byte representation of the current contract code.
|
|
||||||
originHash common.Hash // originHash is the cryptographic hash of the code prior to mutation.
|
originHash common.Hash // originHash is the cryptographic hash of the code prior to mutation.
|
||||||
|
blob []byte // blob is the raw byte representation of the current contract code.
|
||||||
|
|
||||||
// Derived fields, populated only when state tracking is enabled.
|
// Derived fields, populated only when state tracking is enabled.
|
||||||
duplicate bool // duplicate indicates whether the updated code already exists.
|
duplicate bool // duplicate indicates whether the updated code already exists.
|
||||||
|
|
@ -87,7 +90,7 @@ type stateUpdate struct {
|
||||||
|
|
||||||
codes map[common.Address]*contractCode // codes contains mutated contract codes, keyed by address.
|
codes map[common.Address]*contractCode // codes contains mutated contract codes, keyed by address.
|
||||||
nodes *trienode.MergedNodeSet // nodes aggregates all dirty trie nodes produced by the update.
|
nodes *trienode.MergedNodeSet // nodes aggregates all dirty trie nodes produced by the update.
|
||||||
secondaryHashes map[common.Address]SecondaryHash // hashes of secondary tries
|
secondaryHashes map[common.Address]Hashes // hashes of secondary tries
|
||||||
}
|
}
|
||||||
|
|
||||||
// empty returns a flag indicating the state transition is empty or not.
|
// empty returns a flag indicating the state transition is empty or not.
|
||||||
|
|
@ -101,7 +104,7 @@ func (sc *stateUpdate) empty() bool {
|
||||||
//
|
//
|
||||||
// rawStorageKey is a flag indicating whether to use the raw storage slot key or
|
// rawStorageKey is a flag indicating whether to use the raw storage slot key or
|
||||||
// the hash of the slot key for constructing state update object.
|
// the hash of the slot key for constructing state update object.
|
||||||
func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet, secondaryHashes map[common.Address]SecondaryHash) *stateUpdate {
|
func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash, blockNumber uint64, deletes map[common.Hash]*accountDelete, updates map[common.Hash]*accountUpdate, nodes *trienode.MergedNodeSet, secondaryHashes map[common.Address]Hashes) *stateUpdate {
|
||||||
var (
|
var (
|
||||||
accounts = make(map[common.Hash]*Account)
|
accounts = make(map[common.Hash]*Account)
|
||||||
accountsOrigin = make(map[common.Address]*Account)
|
accountsOrigin = make(map[common.Address]*Account)
|
||||||
|
|
@ -182,19 +185,87 @@ func newStateUpdate(rawStorageKey bool, originRoot common.Hash, root common.Hash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func encodeSlot(val common.Hash) []byte {
|
||||||
|
if val == (common.Hash{}) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
blob, _ := rlp.EncodeToBytes(common.TrimLeftZeroes(val[:]))
|
||||||
|
return blob
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sc *stateUpdate) encodeMerkle() (map[common.Hash][]byte, map[common.Address][]byte, map[common.Hash]map[common.Hash][]byte, map[common.Address]map[common.Hash][]byte, error) {
|
||||||
|
var (
|
||||||
|
accounts = make(map[common.Hash][]byte)
|
||||||
|
storages = make(map[common.Hash]map[common.Hash][]byte)
|
||||||
|
accountOrigin = make(map[common.Address][]byte)
|
||||||
|
storageOrigin = make(map[common.Address]map[common.Hash][]byte)
|
||||||
|
)
|
||||||
|
for addr, prev := range sc.accountsOrigin {
|
||||||
|
if prev == nil {
|
||||||
|
accountOrigin[addr] = nil
|
||||||
|
} else {
|
||||||
|
pair, ok := sc.secondaryHashes[addr]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, nil, nil, errors.New("no secondary hash")
|
||||||
|
}
|
||||||
|
accountOrigin[addr] = types.SlimAccountRLP(types.StateAccount{
|
||||||
|
Balance: prev.Balance,
|
||||||
|
Nonce: prev.Nonce,
|
||||||
|
CodeHash: prev.CodeHash,
|
||||||
|
Root: pair.Prev,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
|
data := sc.accounts[addrHash]
|
||||||
|
if data == nil {
|
||||||
|
accounts[addrHash] = nil
|
||||||
|
} else {
|
||||||
|
pair, ok := sc.secondaryHashes[addr]
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, nil, nil, errors.New("no secondary hash")
|
||||||
|
}
|
||||||
|
accounts[addrHash] = types.SlimAccountRLP(types.StateAccount{
|
||||||
|
Balance: data.Balance,
|
||||||
|
Nonce: data.Nonce,
|
||||||
|
CodeHash: data.CodeHash,
|
||||||
|
Root: pair.Hash,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for addr, slots := range sc.storagesOrigin {
|
||||||
|
subset := make(map[common.Hash][]byte)
|
||||||
|
for key, val := range slots {
|
||||||
|
subset[key] = encodeSlot(val)
|
||||||
|
}
|
||||||
|
storageOrigin[addr] = subset
|
||||||
|
}
|
||||||
|
for addrHash, slots := range sc.storages {
|
||||||
|
subset := make(map[common.Hash][]byte)
|
||||||
|
for key, val := range slots {
|
||||||
|
subset[key] = encodeSlot(val)
|
||||||
|
}
|
||||||
|
storages[addrHash] = subset
|
||||||
|
}
|
||||||
|
return accounts, accountOrigin, storages, storageOrigin, nil
|
||||||
|
}
|
||||||
|
|
||||||
// stateSet converts the current stateUpdate object into a triedb.StateSet
|
// stateSet converts the current stateUpdate object into a triedb.StateSet
|
||||||
// object. This function extracts the necessary data from the stateUpdate
|
// object. This function extracts the necessary data from the stateUpdate
|
||||||
// struct and formats it into the StateSet structure consumed by the triedb
|
// struct and formats it into the StateSet structure consumed by the triedb
|
||||||
// package.
|
// package.
|
||||||
func (sc *stateUpdate) stateSet() *triedb.StateSet {
|
func (sc *stateUpdate) stateSet() (*triedb.StateSet, error) {
|
||||||
return nil
|
accounts, accountOrigin, storages, storageOrigin, err := sc.encodeMerkle()
|
||||||
//return &triedb.StateSet{
|
if err != nil {
|
||||||
// Accounts: sc.accounts,
|
return nil, err
|
||||||
// AccountsOrigin: sc.accountsOrigin,
|
}
|
||||||
// Storages: sc.storages,
|
return &triedb.StateSet{
|
||||||
// StoragesOrigin: sc.storagesOrigin,
|
Accounts: accounts,
|
||||||
// RawStorageKey: sc.rawStorageKey,
|
AccountsOrigin: accountOrigin,
|
||||||
//}
|
Storages: storages,
|
||||||
|
StoragesOrigin: storageOrigin,
|
||||||
|
RawStorageKey: sc.rawStorageKey,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deriveCodeFields derives the missing fields of contract code changes
|
// deriveCodeFields derives the missing fields of contract code changes
|
||||||
|
|
@ -226,141 +297,117 @@ func (sc *stateUpdate) deriveCodeFields(reader ContractCodeReader) error {
|
||||||
|
|
||||||
// ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate.
|
// ToTracingUpdate converts the internal stateUpdate to an exported tracing.StateUpdate.
|
||||||
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
func (sc *stateUpdate) ToTracingUpdate() (*tracing.StateUpdate, error) {
|
||||||
return nil, nil
|
update := &tracing.StateUpdate{
|
||||||
//update := &tracing.StateUpdate{
|
OriginRoot: sc.originRoot,
|
||||||
// OriginRoot: sc.originRoot,
|
Root: sc.root,
|
||||||
// Root: sc.root,
|
BlockNumber: sc.blockNumber,
|
||||||
// BlockNumber: sc.blockNumber,
|
AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
|
||||||
// AccountChanges: make(map[common.Address]*tracing.AccountChange, len(sc.accountsOrigin)),
|
StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
|
||||||
// StorageChanges: make(map[common.Address]map[common.Hash]*tracing.StorageChange),
|
CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
|
||||||
// CodeChanges: make(map[common.Address]*tracing.CodeChange, len(sc.codes)),
|
TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
|
||||||
// TrieChanges: make(map[common.Hash]map[string]*tracing.TrieNodeChange),
|
}
|
||||||
//}
|
// Gather all account changes
|
||||||
//// Gather all account changes
|
for addr, oldData := range sc.accountsOrigin {
|
||||||
//for addr, oldData := range sc.accountsOrigin {
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
// addrHash := crypto.Keccak256Hash(addr.Bytes())
|
newData, exists := sc.accounts[addrHash]
|
||||||
// newData, exists := sc.accounts[addrHash]
|
if !exists {
|
||||||
// if !exists {
|
return nil, fmt.Errorf("account %x not found", addr)
|
||||||
// return nil, fmt.Errorf("account %x not found", addr)
|
}
|
||||||
// }
|
hashes := sc.secondaryHashes[addr]
|
||||||
// change := &tracing.AccountChange{}
|
change := &tracing.AccountChange{}
|
||||||
//
|
|
||||||
// if len(oldData) > 0 {
|
if oldData != nil {
|
||||||
// acct, err := types.FullAccount(oldData)
|
change.Prev = &types.StateAccount{
|
||||||
// if err != nil {
|
Nonce: oldData.Nonce,
|
||||||
// return nil, err
|
Balance: oldData.Balance,
|
||||||
// }
|
Root: hashes.Prev,
|
||||||
// change.Prev = &types.StateAccount{
|
CodeHash: oldData.CodeHash,
|
||||||
// Nonce: acct.Nonce,
|
}
|
||||||
// Balance: acct.Balance,
|
}
|
||||||
// Root: acct.Root,
|
if newData != nil {
|
||||||
// CodeHash: acct.CodeHash,
|
change.New = &types.StateAccount{
|
||||||
// }
|
Nonce: newData.Nonce,
|
||||||
// }
|
Balance: newData.Balance,
|
||||||
// if len(newData) > 0 {
|
Root: hashes.Hash,
|
||||||
// acct, err := types.FullAccount(newData)
|
CodeHash: newData.CodeHash,
|
||||||
// if err != nil {
|
}
|
||||||
// return nil, err
|
}
|
||||||
// }
|
update.AccountChanges[addr] = change
|
||||||
// change.New = &types.StateAccount{
|
}
|
||||||
// Nonce: acct.Nonce,
|
|
||||||
// Balance: acct.Balance,
|
// Gather all storage slot changes
|
||||||
// Root: acct.Root,
|
for addr, slots := range sc.storagesOrigin {
|
||||||
// CodeHash: acct.CodeHash,
|
addrHash := crypto.Keccak256Hash(addr.Bytes())
|
||||||
// }
|
subset, exists := sc.storages[addrHash]
|
||||||
// }
|
if !exists {
|
||||||
// update.AccountChanges[addr] = change
|
return nil, fmt.Errorf("storage %x not found", addr)
|
||||||
//}
|
}
|
||||||
//
|
storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
|
||||||
//// Gather all storage slot changes
|
|
||||||
//for addr, slots := range sc.storagesOrigin {
|
for key, prev := range slots {
|
||||||
// addrHash := crypto.Keccak256Hash(addr.Bytes())
|
// Get new value - handle both raw and hashed key formats
|
||||||
// subset, exists := sc.storages[addrHash]
|
var (
|
||||||
// if !exists {
|
exists bool
|
||||||
// return nil, fmt.Errorf("storage %x not found", addr)
|
current common.Hash
|
||||||
// }
|
)
|
||||||
// storageChanges := make(map[common.Hash]*tracing.StorageChange, len(slots))
|
if sc.rawStorageKey {
|
||||||
//
|
current, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
||||||
// for key, encPrev := range slots {
|
} else {
|
||||||
// // Get new value - handle both raw and hashed key formats
|
current, exists = subset[key]
|
||||||
// var (
|
}
|
||||||
// exists bool
|
if !exists {
|
||||||
// encNew []byte
|
return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
|
||||||
// decPrev []byte
|
}
|
||||||
// decNew []byte
|
|
||||||
// err error
|
storageChanges[key] = &tracing.StorageChange{
|
||||||
// )
|
Prev: prev,
|
||||||
// if sc.rawStorageKey {
|
New: current,
|
||||||
// encNew, exists = subset[crypto.Keccak256Hash(key.Bytes())]
|
}
|
||||||
// } else {
|
}
|
||||||
// encNew, exists = subset[key]
|
update.StorageChanges[addr] = storageChanges
|
||||||
// }
|
}
|
||||||
// if !exists {
|
|
||||||
// return nil, fmt.Errorf("storage slot %x-%x not found", addr, key)
|
// Gather all contract code changes
|
||||||
// }
|
for addr, code := range sc.codes {
|
||||||
//
|
change := &tracing.CodeChange{
|
||||||
// // Decode the prev and new values
|
New: &tracing.ContractCode{
|
||||||
// if len(encPrev) > 0 {
|
Hash: code.hash,
|
||||||
// _, decPrev, _, err = rlp.Split(encPrev)
|
Code: code.blob,
|
||||||
// if err != nil {
|
Exists: code.duplicate,
|
||||||
// return nil, fmt.Errorf("failed to decode prevValue: %v", err)
|
},
|
||||||
// }
|
}
|
||||||
// }
|
if code.originHash != types.EmptyCodeHash {
|
||||||
// if len(encNew) > 0 {
|
change.Prev = &tracing.ContractCode{
|
||||||
// _, decNew, _, err = rlp.Split(encNew)
|
Hash: code.originHash,
|
||||||
// if err != nil {
|
Code: code.originBlob,
|
||||||
// return nil, fmt.Errorf("failed to decode newValue: %v", err)
|
Exists: true,
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// storageChanges[key] = &tracing.StorageChange{
|
update.CodeChanges[addr] = change
|
||||||
// Prev: common.BytesToHash(decPrev),
|
}
|
||||||
// New: common.BytesToHash(decNew),
|
|
||||||
// }
|
// Gather all trie node changes
|
||||||
// }
|
if sc.nodes != nil {
|
||||||
// update.StorageChanges[addr] = storageChanges
|
for owner, subset := range sc.nodes.Sets {
|
||||||
//}
|
nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
|
||||||
//
|
for path, oldNode := range subset.Origins {
|
||||||
//// Gather all contract code changes
|
newNode, exists := subset.Nodes[path]
|
||||||
//for addr, code := range sc.codes {
|
if !exists {
|
||||||
// change := &tracing.CodeChange{
|
return nil, fmt.Errorf("node %x-%v not found", owner, path)
|
||||||
// New: &tracing.ContractCode{
|
}
|
||||||
// Hash: code.hash,
|
nodeChanges[path] = &tracing.TrieNodeChange{
|
||||||
// Code: code.blob,
|
Prev: &trienode.Node{
|
||||||
// Exists: code.duplicate,
|
Hash: crypto.Keccak256Hash(oldNode),
|
||||||
// },
|
Blob: oldNode,
|
||||||
// }
|
},
|
||||||
// if code.originHash != types.EmptyCodeHash {
|
New: &trienode.Node{
|
||||||
// change.Prev = &tracing.ContractCode{
|
Hash: newNode.Hash,
|
||||||
// Hash: code.originHash,
|
Blob: newNode.Blob,
|
||||||
// Code: code.originBlob,
|
},
|
||||||
// Exists: true,
|
}
|
||||||
// }
|
}
|
||||||
// }
|
update.TrieChanges[owner] = nodeChanges
|
||||||
// update.CodeChanges[addr] = change
|
}
|
||||||
//}
|
}
|
||||||
//
|
return update, nil
|
||||||
//// Gather all trie node changes
|
|
||||||
//if sc.nodes != nil {
|
|
||||||
// for owner, subset := range sc.nodes.Sets {
|
|
||||||
// nodeChanges := make(map[string]*tracing.TrieNodeChange, len(subset.Origins))
|
|
||||||
// for path, oldNode := range subset.Origins {
|
|
||||||
// newNode, exists := subset.Nodes[path]
|
|
||||||
// if !exists {
|
|
||||||
// return nil, fmt.Errorf("node %x-%v not found", owner, path)
|
|
||||||
// }
|
|
||||||
// nodeChanges[path] = &tracing.TrieNodeChange{
|
|
||||||
// Prev: &trienode.Node{
|
|
||||||
// Hash: crypto.Keccak256Hash(oldNode),
|
|
||||||
// Blob: oldNode,
|
|
||||||
// },
|
|
||||||
// New: &trienode.Node{
|
|
||||||
// Hash: newNode.Hash,
|
|
||||||
// Blob: newNode.Blob,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// update.TrieChanges[owner] = nodeChanges
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//return update, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -232,38 +232,31 @@ func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockNrOrHash rpc.Block
|
||||||
}
|
}
|
||||||
|
|
||||||
func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Address, start []byte, maxResult int) (StorageRangeResult, error) {
|
func storageRangeAt(statedb *state.StateDB, root common.Hash, address common.Address, start []byte, maxResult int) (StorageRangeResult, error) {
|
||||||
storageRoot := statedb.GetStorageRoot(address)
|
it, err := statedb.Database().Iteratee(root)
|
||||||
if storageRoot == types.EmptyRootHash || storageRoot == (common.Hash{}) {
|
if err != nil {
|
||||||
return StorageRangeResult{}, nil // empty storage
|
return StorageRangeResult{}, err
|
||||||
|
}
|
||||||
|
storageIt, err := it.NewStorageIterator(crypto.Keccak256Hash(address.Bytes()), common.BytesToHash(start))
|
||||||
|
if err != nil {
|
||||||
|
return StorageRangeResult{}, err
|
||||||
}
|
}
|
||||||
// TODO(rjl493456442) it's problematic for traversing the state with in-memory
|
// TODO(rjl493456442) it's problematic for traversing the state with in-memory
|
||||||
// state mutations, specifically txIndex != 0.
|
// state mutations, specifically txIndex != 0.
|
||||||
id := trie.StorageTrieID(root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
|
|
||||||
tr, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
|
|
||||||
if err != nil {
|
|
||||||
return StorageRangeResult{}, err
|
|
||||||
}
|
|
||||||
trieIt, err := tr.NodeIterator(start)
|
|
||||||
if err != nil {
|
|
||||||
return StorageRangeResult{}, err
|
|
||||||
}
|
|
||||||
it := trie.NewIterator(trieIt)
|
|
||||||
result := StorageRangeResult{Storage: storageMap{}}
|
result := StorageRangeResult{Storage: storageMap{}}
|
||||||
for i := 0; i < maxResult && it.Next(); i++ {
|
for i := 0; i < maxResult && storageIt.Next(); i++ {
|
||||||
_, content, _, err := rlp.Split(it.Value)
|
_, content, _, err := rlp.Split(storageIt.Slot())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return StorageRangeResult{}, err
|
return StorageRangeResult{}, err
|
||||||
}
|
}
|
||||||
e := storageEntry{Value: common.BytesToHash(content)}
|
e := storageEntry{Value: common.BytesToHash(content)}
|
||||||
if preimage := tr.GetKey(it.Key); preimage != nil {
|
if preimage, err := storageIt.Key(); err == nil {
|
||||||
preimage := common.BytesToHash(preimage)
|
|
||||||
e.Key = &preimage
|
e.Key = &preimage
|
||||||
}
|
}
|
||||||
result.Storage[common.BytesToHash(it.Key)] = e
|
result.Storage[storageIt.Hash()] = e
|
||||||
}
|
}
|
||||||
// Add the 'next key' so clients can continue downloading.
|
// Add the 'next key' so clients can continue downloading.
|
||||||
if it.Next() {
|
if storageIt.Next() {
|
||||||
next := common.BytesToHash(it.Key)
|
next := storageIt.Hash()
|
||||||
result.NextKey = &next
|
result.NextKey = &next
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
|
||||||
|
|
@ -388,17 +388,15 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
codeHash := statedb.GetCodeHash(address)
|
codeHash := statedb.GetCodeHash(address)
|
||||||
storageRoot := statedb.GetStorageRoot(address)
|
hasher, err := statedb.Database().Hasher(header.Root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
prover, ok := hasher.(state.Prover)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("state proving is not supported")
|
||||||
|
}
|
||||||
if len(keys) > 0 {
|
if len(keys) > 0 {
|
||||||
var storageTrie state.Trie
|
|
||||||
if storageRoot != types.EmptyRootHash && storageRoot != (common.Hash{}) {
|
|
||||||
st, err := statedb.Database().OpenStorageTrie(header.Root, address, storageRoot, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
storageTrie = st
|
|
||||||
}
|
|
||||||
// Create the proofs for the storageKeys.
|
// Create the proofs for the storageKeys.
|
||||||
for i, key := range keys {
|
for i, key := range keys {
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
|
|
@ -414,12 +412,8 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
|
||||||
} else {
|
} else {
|
||||||
outputKey = hexutil.Encode(key[:])
|
outputKey = hexutil.Encode(key[:])
|
||||||
}
|
}
|
||||||
if storageTrie == nil {
|
|
||||||
storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var proof proofList
|
var proof proofList
|
||||||
if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil {
|
if err := prover.ProveStorage(address, crypto.Keccak256Hash(key.Bytes()), &proof); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
value := (*hexutil.Big)(statedb.GetState(address, key).Big())
|
value := (*hexutil.Big)(statedb.GetState(address, key).Big())
|
||||||
|
|
@ -427,12 +421,8 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create the accountProof.
|
// Create the accountProof.
|
||||||
tr, err := statedb.Database().OpenTrie(header.Root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var accountProof proofList
|
var accountProof proofList
|
||||||
if err := tr.Prove(crypto.Keccak256(address.Bytes()), &accountProof); err != nil {
|
if err := prover.ProveAccount(address, &accountProof); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
balance := statedb.GetBalance(address).ToBig()
|
balance := statedb.GetBalance(address).ToBig()
|
||||||
|
|
@ -442,7 +432,7 @@ func (api *BlockChainAPI) GetProof(ctx context.Context, address common.Address,
|
||||||
Balance: (*hexutil.Big)(balance),
|
Balance: (*hexutil.Big)(balance),
|
||||||
CodeHash: codeHash,
|
CodeHash: codeHash,
|
||||||
Nonce: hexutil.Uint64(statedb.GetNonce(address)),
|
Nonce: hexutil.Uint64(statedb.GetNonce(address)),
|
||||||
StorageHash: storageRoot,
|
//StorageHash: storageRoot, // TODO(rjl493456442)
|
||||||
StorageProof: storageProof,
|
StorageProof: storageProof,
|
||||||
}, statedb.Error()
|
}, statedb.Error()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue