mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-12 01:41:36 +00:00
core/state: build hasher skeleton
This commit is contained in:
parent
9daaef1923
commit
1ae462f08d
12 changed files with 551 additions and 797 deletions
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Reference in a new issue