mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-18 09:53:48 +00:00
feat: add state trie p1
This commit is contained in:
parent
7c0ff05685
commit
f1dabaac3a
2 changed files with 681 additions and 20 deletions
447
core/state/proof_state_object.go
Normal file
447
core/state/proof_state_object.go
Normal 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
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Reference in a new issue