feat: add state trie p1

This commit is contained in:
Marcus Pang Yu Yang 2024-11-27 12:33:55 +08:00
parent 7c0ff05685
commit f1dabaac3a
No known key found for this signature in database
GPG key ID: 7DCD5A854390AAB3
2 changed files with 681 additions and 20 deletions

View file

@ -0,0 +1,447 @@
// Copyright 2014 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 (
"bytes"
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"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/trienode"
"github.com/holiman/uint256"
)
// proofStateObject represents a proof commitment which is being modified.
//
// The usage pattern is as follows:
// - First you need to obtain a state object.
// - Next, call commit to return the changes.
type proofStateObject struct {
db *StateDB
address common.Address // address of ethereum account
addrHash common.Hash // hash of ethereum address of the account
origin *common.Hash // Account original data without any change applied, nil means it was not existent
data common.Hash // Account data with all mutations applied in the scope of block
}
// empty returns whether the proof data is empty.
func (s *proofStateObject) empty() bool {
return s.data.Cmp(common.Hash{}) == 0
}
// newObject creates a state object.
func newProofStateObject(db *StateDB, address common.Address, data *common.Hash) *proofStateObject {
if data == nil {
data = &common.Hash{}
}
return &proofStateObject{
db: db,
address: address,
addrHash: crypto.Keccak256Hash(address[:]),
}
}
func (s *proofStateObject) touch() {
s.db.proofJournal.touchChange(s.address)
}
func (s *proofStateObject) GetState(key common.Hash) common.Hash {
s.db.ProofLoaded++
start := time.Now()
value, err := s.db.reader.Storage(s.address, key)
if err != nil {
s.db.setError(err)
return common.Hash{}
}
s.db.ProofReads += time.Since(start)
// Schedule the resolved storage slots for prefetching if it's enabled.
if s.db.proofPrefetcher != nil {
if err = s.db.proofPrefetcher.prefetch(s.addrHash, s.data, s.address, nil, []common.Hash{key}, true); err != nil {
log.Error("Failed to prefetch storage slot", "addr", s.address, "key", key, "err", err)
}
}
return value
}
// SetState updates a value in account storage.
// It returns the previous value
func (s *proofStateObject) SetState(key, value common.Hash) common.Hash {
// If the new value is the same as old, don't set. Otherwise, track only the
// dirty changes, supporting reverting all of it back to no change.
prev := s.GetState(key)
// New value is different, update and journal the change
s.db.proofJournal.storageChange(s.address, key, prev, common.Hash{})
s.setState(key, value)
return prev
}
// setState updates a value in account dirty storage. The dirtiness will be
// removed if the value being set equals to the original value.
func (s *stateObject) setState(key common.Hash, value common.Hash, origin common.Hash) {
// Storage slot is set back to its original value, undo the dirty marker
if value == origin {
delete(s.dirtyStorage, key)
return
}
s.dirtyStorage[key] = value
}
// finalise moves all dirty storage slots into the pending area to be hashed or
// committed later. It is invoked at the end of every transaction.
func (s *stateObject) finalise() {
slotsToPrefetch := make([]common.Hash, 0, len(s.dirtyStorage))
for key, value := range s.dirtyStorage {
if origin, exist := s.uncommittedStorage[key]; exist && origin == value {
// The slot is reverted to its original value, delete the entry
// to avoid thrashing the data structures.
delete(s.uncommittedStorage, key)
} else if exist {
// The slot is modified to another value and the slot has been
// tracked for commit, do nothing here.
} else {
// The slot is different from its original value and hasn't been
// tracked for commit yet.
s.uncommittedStorage[key] = s.GetCommittedState(key)
slotsToPrefetch = append(slotsToPrefetch, key) // Copy needed for closure
}
// Aggregate the dirty storage slots into the pending area. It might
// be possible that the value of tracked slot here is same with the
// one in originStorage (e.g. the slot was modified in tx_a and then
// modified back in tx_b). We can't blindly remove it from pending
// map as the dirty slot might have been committed already (before the
// byzantium fork) and entry is necessary to modify the value back.
s.pendingStorage[key] = value
}
if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash {
if err := s.db.prefetcher.prefetch(s.addrHash, s.data.Root, s.address, nil, slotsToPrefetch, false); err != nil {
log.Error("Failed to prefetch slots", "addr", s.address, "slots", len(slotsToPrefetch), "err", err)
}
}
if len(s.dirtyStorage) > 0 {
s.dirtyStorage = make(Storage)
}
// Revoke the flag at the end of the transaction. It finalizes the status
// of the newly-created object as it's no longer eligible for self-destruct
// by EIP-6780. For non-newly-created objects, it's a no-op.
s.newContract = false
}
// 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) {
// Short circuit if nothing was accessed, don't trigger a prefetcher warning
if len(s.uncommittedStorage) == 0 {
// Nothing was written, so we could stop early. Unless we have both reads
// and witness collection enabled, in which case we need to fetch the trie.
if s.db.witness == nil || len(s.originStorage) == 0 {
return s.trie, nil
}
}
// Retrieve a pretecher populated trie, or fall back to the database. This will
// block until all prefetch tasks are done, which are needed for witnesses even
// for unmodified state objects.
tr := s.getPrefetchedTrie()
if tr != nil {
// Prefetcher returned a live trie, swap it out for the current one
s.trie = tr
} else {
// Fetcher not running or empty trie, fallback to the database trie
var err error
tr, err = s.getTrie()
if err != nil {
s.db.setError(err)
return nil, err
}
}
// Short circuit if nothing changed, don't bother with hashing anything
if len(s.uncommittedStorage) == 0 {
return s.trie, nil
}
// 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
used = make([]common.Hash, 0, len(s.uncommittedStorage))
)
for key, origin := range s.uncommittedStorage {
// Skip noop changes, persist actual changes
value, exist := s.pendingStorage[key]
if value == origin {
log.Error("Storage update was noop", "address", s.address, "slot", key)
continue
}
if !exist {
log.Error("Storage slot is not found in pending area", 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)
}
// Cache the items for preloading
used = append(used, key) // Copy needed for closure
}
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)
}
if s.db.prefetcher != nil {
s.db.prefetcher.used(s.addrHash, s.data.Root, nil, used)
}
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()
}
// 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 (
buf = crypto.NewKeccakState()
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
// map, e.g. the storage slot is modified in tx_a and then reset
// back in tx_b.
if val == s.originStorage[key] {
continue
}
hash := crypto.HashData(buf, key[:])
if op.storages == nil {
op.storages = make(map[common.Hash][]byte)
}
op.storages[hash] = encode(val)
if op.storagesOrigin == nil {
op.storagesOrigin = make(map[common.Hash][]byte)
}
op.storagesOrigin[hash] = encode(s.originStorage[key])
// Overwrite the clean value of storage slots
s.originStorage[key] = val
}
s.pendingStorage = make(Storage)
}
// commit obtains the account changes (metadata, storage slots, code) caused by
// state execution along with the dirty storage trie nodes.
//
// 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) {
// 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)
}
// commit the contract code if it's modified
if s.dirtyCode {
op.code = &contractCode{
hash: common.BytesToHash(s.CodeHash()),
blob: s.code,
}
s.dirtyCode = false // reset the dirty flag
}
// 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
}
root, nodes := s.trie.Commit(false)
s.data.Root = root
s.origin = s.data.Copy()
return op, nodes, nil
}
// AddBalance adds amount to s's balance.
// It is used to add funds to the destination account of a transfer.
// returns the previous balance
func (s *stateObject) AddBalance(amount *uint256.Int) uint256.Int {
// EIP161: We must check emptiness for the objects such that the account
// clearing (0,0,0 objects) can take effect.
if amount.IsZero() {
if s.empty() {
s.touch()
}
return *(s.Balance())
}
return s.SetBalance(new(uint256.Int).Add(s.Balance(), amount))
}
// SetBalance sets the balance for the object, and returns the previous balance.
func (s *stateObject) SetBalance(amount *uint256.Int) uint256.Int {
prev := *s.data.Balance
s.db.journal.balanceChange(s.address, s.data.Balance)
s.setBalance(amount)
return prev
}
func (s *stateObject) setBalance(amount *uint256.Int) {
s.data.Balance = amount
}
func (s *stateObject) deepCopy(db *StateDB) *stateObject {
obj := &stateObject{
db: db,
address: s.address,
addrHash: s.addrHash,
origin: s.origin,
data: s.data,
code: s.code,
originStorage: s.originStorage.Copy(),
pendingStorage: s.pendingStorage.Copy(),
dirtyStorage: s.dirtyStorage.Copy(),
uncommittedStorage: s.uncommittedStorage.Copy(),
dirtyCode: s.dirtyCode,
selfDestructed: s.selfDestructed,
newContract: s.newContract,
}
if s.trie != nil {
obj.trie = mustCopyTrie(s.trie)
}
return obj
}
//
// Attribute accessors
//
// Address returns the address of the contract/account
func (s *stateObject) Address() common.Address {
return s.address
}
// Code returns the contract code associated with this object, if any.
func (s *stateObject) Code() []byte {
if len(s.code) != 0 {
return s.code
}
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return nil
}
code, err := s.db.db.ContractCode(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code hash %x: %v", s.CodeHash(), err))
}
s.code = code
return code
}
// CodeSize returns the size of the contract code associated with this object,
// or zero if none. This method is an almost mirror of Code, but uses a cache
// inside the database to avoid loading codes seen recently.
func (s *stateObject) CodeSize() int {
if len(s.code) != 0 {
return len(s.code)
}
if bytes.Equal(s.CodeHash(), types.EmptyCodeHash.Bytes()) {
return 0
}
size, err := s.db.db.ContractCodeSize(s.address, common.BytesToHash(s.CodeHash()))
if err != nil {
s.db.setError(fmt.Errorf("can't load code size %x: %v", s.CodeHash(), err))
}
return size
}
func (s *stateObject) SetCode(codeHash common.Hash, code []byte) {
s.db.journal.setCode(s.address)
s.setCode(codeHash, code)
}
func (s *stateObject) setCode(codeHash common.Hash, code []byte) {
s.code = code
s.data.CodeHash = codeHash[:]
s.dirtyCode = true
}
func (s *stateObject) SetNonce(nonce uint64) {
s.db.journal.nonceChange(s.address, s.data.Nonce)
s.setNonce(nonce)
}
func (s *stateObject) setNonce(nonce uint64) {
s.data.Nonce = nonce
}
func (s *stateObject) CodeHash() []byte {
return s.data.CodeHash
}
func (s *stateObject) Balance() *uint256.Int {
return s.data.Balance
}
func (s *stateObject) Nonce() uint64 {
return s.data.Nonce
}
func (s *stateObject) Root() common.Hash {
return s.data.Root
}

View file

@ -71,6 +71,7 @@ func (m *mutation) isDelete() bool {
//
// * Contracts
// * Accounts
// * Proofs
//
// Once the state is committed, tries cached in stateDB (including account
// trie, storage tries) will no longer be functional. A new state instance
@ -81,15 +82,23 @@ type StateDB struct {
prefetcher *triePrefetcher
trie Trie
reader Reader
proofPrefetcher *triePrefetcher
proofTrie Trie
proofReader Reader
// originalRoot is the pre-state root, before any changes were made.
// It will be updated when the Commit is called.
originalRoot common.Hash
// originalProofRoot is the pre-state proof root, before any changes were made.
// It will be updated when the Commit is called.
originalProofRoot common.Hash
// This map holds 'live' objects, which will get modified while
// processing a state transition.
stateObjects map[common.Address]*stateObject
proofStateObjects map[common.Address]*stateObject
// This map holds 'deleted' objects. An object with the same address
// might also occur in the 'stateObjects' map due to account
// resurrection. The account value is tracked as the original value
@ -97,12 +106,16 @@ type StateDB struct {
// boundaries.
stateObjectsDestruct map[common.Address]*stateObject
proofStateObjectsDestruct map[common.Address]*stateObject
// This map tracks the account mutations that occurred during the
// transition. Uncommitted mutations belonging to the same account
// can be merged into a single one which is equivalent from database's
// perspective. This map is populated at the transaction boundaries.
mutations map[common.Address]*mutation
proofMutations map[common.Address]*mutation
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
@ -121,6 +134,9 @@ type StateDB struct {
logs map[common.Hash][]*types.Log
logSize uint
proofLogs map[common.Hash][]*types.Log
proofLogSize uint
// Preimages occurred seen by VM in the scope of block.
preimages map[common.Hash][]byte
@ -135,9 +151,14 @@ type StateDB struct {
// Snapshot and RevertToSnapshot.
journal *journal
proofJournal *journal
// State witness if cross validation is needed
witness *stateless.Witness
// Proof witness if cross validation is needed
proofWitness *stateless.Witness
// Measurements gathered during execution for debugging purposes
AccountReads time.Duration
AccountHashes time.Duration
@ -147,6 +168,10 @@ type StateDB struct {
StorageUpdates time.Duration
StorageCommits time.Duration
SnapshotCommits time.Duration
ProofReads time.Duration
ProofHashes time.Duration
ProofUpdates time.Duration
ProofCommits time.Duration
TrieDBCommits time.Duration
AccountLoaded int // Number of accounts retrieved from the database during the state transition
@ -155,10 +180,13 @@ type StateDB struct {
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
StorageDeleted atomic.Int64 // Number of storage slots deleted during the state transition
ProofLoaded int // Number of proof nodes retrieved from the database during the state transition
ProofUpdated atomic.Int64 // Number of proof nodes updated during the state transition
ProofDeleted atomic.Int64 // Number of proof nodes deleted during the state transition
}
// New creates a new state from a given trie.
func New(root common.Hash, db Database) (*StateDB, error) {
func New(root common.Hash, proofRoot common.Hash, db Database) (*StateDB, error) {
tr, err := db.OpenTrie(root)
if err != nil {
return nil, err
@ -167,17 +195,29 @@ func New(root common.Hash, db Database) (*StateDB, error) {
if err != nil {
return nil, err
}
proofTrie, err := db.OpenTrie(proofRoot)
if err != nil {
return nil, err
}
proofReader, err := db.Reader(proofRoot)
if err != nil {
return nil, err
}
sdb := &StateDB{
db: db,
trie: tr,
originalRoot: root,
reader: reader,
proofTrie: proofTrie,
originalProofRoot: proofRoot,
proofReader: proofReader,
stateObjects: make(map[common.Address]*stateObject),
stateObjectsDestruct: make(map[common.Address]*stateObject),
mutations: make(map[common.Address]*mutation),
logs: make(map[common.Hash][]*types.Log),
preimages: make(map[common.Hash][]byte),
journal: newJournal(),
proofJournal: newJournal(),
accessList: newAccessList(),
transientStorage: newTransientStorage(),
}
@ -190,13 +230,15 @@ func New(root common.Hash, db Database) (*StateDB, error) {
// StartPrefetcher initializes a new trie prefetcher to pull in nodes from the
// state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot.
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) {
func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness, proofWitness *stateless.Witness) {
// Terminate any previously running prefetcher
s.StopPrefetcher()
// Enable witness collection if requested
s.witness = witness
s.proofWitness = proofWitness
// With the switch to the Proof-of-Stake consensus algorithm, block production
// rewards are now handled at the consensus layer. Consequently, a block may
// have no state transitions if it contains no transactions and no withdrawals.
@ -210,6 +252,11 @@ func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness)
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, nil, nil, false); err != nil {
log.Error("Failed to prefetch account trie", "root", s.originalRoot, "err", err)
}
s.proofPrefetcher = newTriePrefetcher(s.db, s.originalProofRoot, namespace, proofWitness == nil)
if err := s.proofPrefetcher.prefetch(common.Hash{}, s.originalProofRoot, common.Address{}, nil, nil, false); err != nil {
log.Error("Failed to prefetch proof trie", "root", s.originalProofRoot, "err", err)
}
}
// StopPrefetcher terminates a running prefetcher and reports any leftover stats
@ -222,6 +269,14 @@ func (s *StateDB) StopPrefetcher() {
}
}
func (s *StateDB) StopProofPrefetcher() {
if s.proofPrefetcher != nil {
s.proofPrefetcher.terminate(false)
s.proofPrefetcher.report()
s.proofPrefetcher = nil
}
}
// setError remembers the first non-nil error it is called with.
func (s *StateDB) setError(err error) {
if s.dbErr == nil {
@ -244,6 +299,16 @@ func (s *StateDB) AddLog(log *types.Log) {
s.logSize++
}
func (s *StateDB) AddProofLog(log *types.Log) {
s.proofJournal.logChange(s.thash)
log.TxHash = s.thash
log.TxIndex = uint(s.txIndex)
log.Index = s.logSize
s.proofLogs[s.thash] = append(s.proofLogs[s.thash], log)
s.proofLogSize++
}
// GetLogs returns the logs matching the specified transaction hash, and annotates
// them with the given blockNumber and blockHash.
func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log {
@ -255,6 +320,15 @@ func (s *StateDB) GetLogs(hash common.Hash, blockNumber uint64, blockHash common
return logs
}
func (s *StateDB) GetProofLogs(hash common.Hash, blockNumber uint64, blockHash common.Hash) []*types.Log {
logs := s.proofLogs[hash]
for _, l := range logs {
l.BlockNumber = blockNumber
l.BlockHash = blockHash
}
return logs
}
func (s *StateDB) Logs() []*types.Log {
var logs []*types.Log
for _, lgs := range s.logs {
@ -263,6 +337,14 @@ func (s *StateDB) Logs() []*types.Log {
return logs
}
func (s *StateDB) ProofLogs() []*types.Log {
var logs []*types.Log
for _, lgs := range s.proofLogs {
logs = append(logs, lgs...)
}
return logs
}
// AddPreimage records a SHA3 preimage seen by the VM.
func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) {
if _, ok := s.preimages[hash]; !ok {
@ -291,12 +373,24 @@ func (s *StateDB) SubRefund(gas uint64) {
s.refund -= gas
}
func (s *StateDB) SubProofRefund(gas uint64) {
s.proofJournal.refundChange(s.refund)
if gas > s.refund {
panic(fmt.Sprintf("Proof refund counter below zero (gas: %d > refund: %d)", gas, s.refund))
}
s.refund -= gas
}
// Exist reports whether the given account address exists in the state.
// Notably this also returns true for self-destructed accounts.
func (s *StateDB) Exist(addr common.Address) bool {
return s.getStateObject(addr) != nil
}
func (s *StateDB) ProofExist(addr common.Address) bool {
return s.getStateObject(addr) != nil
}
// Empty returns whether the state object is either non-existent
// or empty according to the EIP161 specification (balance = nonce = code = 0)
func (s *StateDB) Empty(addr common.Address) bool {
@ -304,6 +398,11 @@ func (s *StateDB) Empty(addr common.Address) bool {
return so == nil || so.empty()
}
func (s *StateDB) ProofEmpty(addr common.Address) bool {
so := s.getStateObject(addr)
return so == nil || so.empty()
}
// GetBalance retrieves the balance from the given address or 0 if object not found
func (s *StateDB) GetBalance(addr common.Address) *uint256.Int {
stateObject := s.getStateObject(addr)
@ -333,6 +432,24 @@ func (s *StateDB) GetStorageRoot(addr common.Address) common.Hash {
return common.Hash{}
}
func (s *StateDB) GetProofValue(addr common.Address, key common.Hash) common.Hash {
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.GetProofState(key)
}
return common.Hash{}
}
// GetProofRoot retrieves the proof root from the given address or empty
// if object not found.
func (s *StateDB) GetProofRoot(addr common.Address) common.Hash {
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.Root()
}
return common.Hash{}
}
// TxIndex returns the current transaction index set by SetTxContext.
func (s *StateDB) TxIndex() int {
return s.txIndex
@ -558,6 +675,17 @@ func (s *StateDB) updateStateObject(obj *stateObject) {
}
}
// updateProofStateObject writes the given object to the proof trie.
func (s *StateDB) updateProofStateObject(obj *stateObject) {
addr := obj.Address()
if err := s.proofTrie.UpdateAccount(addr, &obj.data, len(obj.code)); err != nil {
s.setError(fmt.Errorf("updateProofStateObject (%x) error: %v", addr[:], err))
}
if obj.dirtyCode {
s.proofTrie.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 {
@ -565,6 +693,13 @@ func (s *StateDB) deleteStateObject(addr common.Address) {
}
}
// deleteProofStateObject removes the given object from the proof trie.
func (s *StateDB) deleteProofStateObject(addr common.Address) {
if err := s.proofTrie.DeleteAccount(addr); err != nil {
s.setError(fmt.Errorf("deleteProofStateObject (%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 {
@ -603,10 +738,52 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
return obj
}
// getProofStateObject retrieves a proof state object given by the address, returning nil if
// the object is not found or was deleted in this execution context.
func (s *StateDB) getProofStateObject(addr common.Address) *stateObject {
// Prefer live objects if any is available
if obj := s.proofStateObjects[addr]; obj != nil {
return obj
}
// Short circuit if the account is already destructed in this block.
if _, ok := s.proofStateObjectsDestruct[addr]; ok {
return nil
}
s.ProofLoaded++
start := time.Now()
acct, err := s.proofReader.Account(addr)
if err != nil {
s.setError(fmt.Errorf("getProofStateObject (%x) error: %w", addr.Bytes(), err))
return nil
}
s.ProofReads += time.Since(start)
// Short circuit if the account is not found
if acct == nil {
return nil
}
// Schedule the resolved account for prefetching if it's enabled.
if s.proofPrefetcher != nil {
if err = s.proofPrefetcher.prefetch(common.Hash{}, s.originalProofRoot, common.Address{}, []common.Address{addr}, nil, true); err != nil {
log.Error("Failed to prefetch account", "addr", addr, "err", err)
}
}
// Insert into the live set
obj := newObject(s, addr, acct)
s.setProofStateObject(obj)
s.ProofLoaded++
return obj
}
func (s *StateDB) setStateObject(object *stateObject) {
s.stateObjects[object.Address()] = object
}
func (s *StateDB) setProofStateObject(object *stateObject) {
s.proofStateObjects[object.Address()] = object
}
// getOrNewStateObject retrieves a state object or create a new state object if nil.
func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
obj := s.getStateObject(addr)
@ -616,6 +793,14 @@ func (s *StateDB) getOrNewStateObject(addr common.Address) *stateObject {
return obj
}
func (s *StateDB) getOrNewProofStateObject(addr common.Address) *stateObject {
obj := s.getProofStateObject(addr)
if obj == nil {
obj = s.createProofObject(addr)
}
return obj
}
// createObject creates a new state object. The assumption is held there is no
// existing account with the given address, otherwise it will be silently overwritten.
func (s *StateDB) createObject(addr common.Address) *stateObject {
@ -625,6 +810,13 @@ func (s *StateDB) createObject(addr common.Address) *stateObject {
return obj
}
func (s *StateDB) createProofObject(addr common.Address) *stateObject {
obj := newObject(s, addr, nil)
s.journal.createObject(addr)
s.setProofStateObject(obj)
return obj
}
// CreateAccount explicitly creates a new state object, assuming that the
// account did not previously exist in the state. If the account already
// exists, this function will silently overwrite it which might lead to a
@ -633,6 +825,10 @@ func (s *StateDB) CreateAccount(addr common.Address) {
s.createObject(addr)
}
func (s *StateDB) CreateProofAccount(addr common.Address) {
s.createProofObject(addr)
}
// CreateContract is used whenever a contract is created. This may be preceded
// by CreateAccount, but that is not required if it already existed in the
// state due to funds sent beforehand.
@ -658,6 +854,12 @@ func (s *StateDB) Copy() *StateDB {
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)),
stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)),
mutations: make(map[common.Address]*mutation, len(s.mutations)),
proofTrie: mustCopyTrie(s.proofTrie),
proofReader: s.proofReader.Copy(),
originalProofRoot: s.originalProofRoot,
proofStateObjects: make(map[common.Address]*stateObject, len(s.proofStateObjects)),
proofStateObjectsDestruct: make(map[common.Address]*stateObject, len(s.proofStateObjectsDestruct)),
proofMutations: make(map[common.Address]*mutation, len(s.proofMutations)),
dbErr: s.dbErr,
refund: s.refund,
thash: s.thash,
@ -679,6 +881,9 @@ func (s *StateDB) Copy() *StateDB {
if s.witness != nil {
state.witness = s.witness.Copy()
}
if s.proofWitness != nil {
state.proofWitness = s.proofWitness.Copy()
}
if s.accessEvents != nil {
state.accessEvents = s.accessEvents.Copy()
}
@ -686,14 +891,23 @@ func (s *StateDB) Copy() *StateDB {
for addr, obj := range s.stateObjects {
state.stateObjects[addr] = obj.deepCopy(state)
}
for addr, obj := range s.proofStateObjects {
state.proofStateObjects[addr] = obj.deepCopy(state)
}
// Deep copy destructed state objects.
for addr, obj := range s.stateObjectsDestruct {
state.stateObjectsDestruct[addr] = obj.deepCopy(state)
}
for addr, obj := range s.proofStateObjectsDestruct {
state.proofStateObjectsDestruct[addr] = obj.deepCopy(state)
}
// Deep copy the object state markers.
for addr, op := range s.mutations {
state.mutations[addr] = op.copy()
}
for addr, op := range s.proofMutations {
state.proofMutations[addr] = op.copy()
}
// Deep copy the logs occurred in the scope of block
for hash, logs := range s.logs {
cpy := make([]*types.Log, len(logs))