diff --git a/core/state/proof_state_object.go b/core/state/proof_state_object.go new file mode 100644 index 0000000000..65f4ebdded --- /dev/null +++ b/core/state/proof_state_object.go @@ -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 . + +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 +} diff --git a/core/state/statedb.go b/core/state/statedb.go index d855e5626d..5b6cf4ff60 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -71,25 +71,34 @@ 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 // must be created with new root and updated database for accessing post- // commit states. type StateDB struct { - db Database - prefetcher *triePrefetcher - trie Trie - reader Reader + db Database + 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. @@ -651,20 +847,26 @@ func (s *StateDB) CreateContract(addr common.Address) { func (s *StateDB) Copy() *StateDB { // Copy all the basic fields, initialize the memory ones state := &StateDB{ - db: s.db, - trie: mustCopyTrie(s.trie), - reader: s.reader.Copy(), - originalRoot: s.originalRoot, - 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)), - dbErr: s.dbErr, - refund: s.refund, - thash: s.thash, - txIndex: s.txIndex, - logs: make(map[common.Hash][]*types.Log, len(s.logs)), - logSize: s.logSize, - preimages: maps.Clone(s.preimages), + db: s.db, + trie: mustCopyTrie(s.trie), + reader: s.reader.Copy(), + originalRoot: s.originalRoot, + 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, + txIndex: s.txIndex, + logs: make(map[common.Hash][]*types.Log, len(s.logs)), + logSize: s.logSize, + preimages: maps.Clone(s.preimages), // Do we need to copy the access list and transient storage? // In practice: No. At the start of a transaction, these two lists are empty. @@ -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))