aboutsummaryrefslogtreecommitdiff
path: root/core/state/statedb.go
diff options
context:
space:
mode:
authorTed Yin <[email protected]>2020-09-18 13:14:29 -0400
committerGitHub <[email protected]>2020-09-18 13:14:29 -0400
commitd048937c48753d9eaef771bf71820cf95d79df26 (patch)
tree1a7f65fcd72e77092525ab01625b8b9d365e3e40 /core/state/statedb.go
parent7d1388c743b4ec8f4a86bea95bfada785dee83f7 (diff)
parent7d8c85cf8895b0f998d8eafb02f99d5b689fcd59 (diff)
Merge pull request #34 from ava-labs/devv0.3.0-rc.5
Dev
Diffstat (limited to 'core/state/statedb.go')
-rw-r--r--core/state/statedb.go686
1 files changed, 415 insertions, 271 deletions
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 9c7535b..81be542 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -24,13 +24,15 @@ import (
"sort"
"time"
+ "github.com/ava-labs/coreth/core/rawdb"
+ "github.com/ava-labs/coreth/core/state/snapshot"
"github.com/ava-labs/coreth/core/types"
- "github.com/ava-labs/go-ethereum/common"
- "github.com/ava-labs/go-ethereum/crypto"
- "github.com/ava-labs/go-ethereum/log"
- "github.com/ava-labs/go-ethereum/metrics"
- "github.com/ava-labs/go-ethereum/rlp"
- "github.com/ava-labs/go-ethereum/trie"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/log"
+ "github.com/ethereum/go-ethereum/metrics"
+ "github.com/ethereum/go-ethereum/rlp"
+ "github.com/ethereum/go-ethereum/trie"
)
type revision struct {
@@ -42,9 +44,6 @@ var (
// emptyRoot is the known root hash of an empty trie.
emptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
zeroRoot = common.HexToHash("0000000000000000000000000000000000000000000000000000000000000000")
-
- // emptyCode is the known hash of the empty EVM bytecode.
- emptyCode = crypto.Keccak256Hash(nil)
)
type proofList [][]byte
@@ -58,7 +57,7 @@ func (n *proofList) Delete(key []byte) error {
panic("not supported")
}
-// StateDBs within the ethereum protocol are used to store anything
+// StateDB structs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
// * Contracts
@@ -67,9 +66,16 @@ type StateDB struct {
db Database
trie Trie
+ snaps *snapshot.Tree
+ snap snapshot.Snapshot
+ snapDestructs map[common.Hash]struct{}
+ snapAccounts map[common.Hash][]byte
+ snapStorage map[common.Hash]map[common.Hash][]byte
+
// This map holds 'live' objects, which will get modified while processing a state transition.
- stateObjects map[common.Address]*stateObject
- stateObjectsDirty map[common.Address]struct{}
+ stateObjects map[common.Address]*stateObject
+ stateObjectsPending map[common.Address]struct{} // State objects finalized but not yet written to the trie
+ stateObjectsDirty map[common.Address]struct{} // State objects modified in the current execution
// DB error.
// State objects are used by the consensus core and VM which are
@@ -95,134 +101,157 @@ type StateDB struct {
nextRevisionId int
// Measurements gathered during execution for debugging purposes
- AccountReads time.Duration
- AccountHashes time.Duration
- AccountUpdates time.Duration
- AccountCommits time.Duration
- StorageReads time.Duration
- StorageHashes time.Duration
- StorageUpdates time.Duration
- StorageCommits time.Duration
-}
-
-// Create a new state from a given trie.
-func New(root common.Hash, db Database) (*StateDB, error) {
+ AccountReads time.Duration
+ AccountHashes time.Duration
+ AccountUpdates time.Duration
+ AccountCommits time.Duration
+ StorageReads time.Duration
+ StorageHashes time.Duration
+ StorageUpdates time.Duration
+ StorageCommits time.Duration
+ SnapshotAccountReads time.Duration
+ SnapshotStorageReads time.Duration
+ SnapshotCommits time.Duration
+}
+
+// New creates a new state from a given trie.
+func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) {
tr, err := db.OpenTrie(root)
if err != nil {
return nil, err
}
- return &StateDB{
- db: db,
- trie: tr,
- stateObjects: make(map[common.Address]*stateObject),
- stateObjectsDirty: make(map[common.Address]struct{}),
- logs: make(map[common.Hash][]*types.Log),
- preimages: make(map[common.Hash][]byte),
- journal: newJournal(),
- }, nil
+ sdb := &StateDB{
+ db: db,
+ trie: tr,
+ snaps: snaps,
+ stateObjects: make(map[common.Address]*stateObject),
+ stateObjectsPending: make(map[common.Address]struct{}),
+ stateObjectsDirty: make(map[common.Address]struct{}),
+ logs: make(map[common.Hash][]*types.Log),
+ preimages: make(map[common.Hash][]byte),
+ journal: newJournal(),
+ }
+ if sdb.snaps != nil {
+ if sdb.snap = sdb.snaps.Snapshot(root); sdb.snap != nil {
+ sdb.snapDestructs = make(map[common.Hash]struct{})
+ sdb.snapAccounts = make(map[common.Hash][]byte)
+ sdb.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
+ }
+ }
+ return sdb, nil
}
// setError remembers the first non-nil error it is called with.
-func (self *StateDB) setError(err error) {
- if self.dbErr == nil {
- self.dbErr = err
+func (s *StateDB) setError(err error) {
+ if s.dbErr == nil {
+ s.dbErr = err
}
}
-func (self *StateDB) Error() error {
- return self.dbErr
+func (s *StateDB) Error() error {
+ return s.dbErr
}
// Reset clears out all ephemeral state objects from the state db, but keeps
// the underlying state trie to avoid reloading data for the next operations.
-func (self *StateDB) Reset(root common.Hash) error {
- tr, err := self.db.OpenTrie(root)
+func (s *StateDB) Reset(root common.Hash) error {
+ tr, err := s.db.OpenTrie(root)
if err != nil {
return err
}
- self.trie = tr
- self.stateObjects = make(map[common.Address]*stateObject)
- self.stateObjectsDirty = make(map[common.Address]struct{})
- self.thash = common.Hash{}
- self.bhash = common.Hash{}
- self.txIndex = 0
- self.logs = make(map[common.Hash][]*types.Log)
- self.logSize = 0
- self.preimages = make(map[common.Hash][]byte)
- self.clearJournalAndRefund()
+ s.trie = tr
+ s.stateObjects = make(map[common.Address]*stateObject)
+ s.stateObjectsPending = make(map[common.Address]struct{})
+ s.stateObjectsDirty = make(map[common.Address]struct{})
+ s.thash = common.Hash{}
+ s.bhash = common.Hash{}
+ s.txIndex = 0
+ s.logs = make(map[common.Hash][]*types.Log)
+ s.logSize = 0
+ s.preimages = make(map[common.Hash][]byte)
+ s.clearJournalAndRefund()
+
+ if s.snaps != nil {
+ s.snapAccounts, s.snapDestructs, s.snapStorage = nil, nil, nil
+ if s.snap = s.snaps.Snapshot(root); s.snap != nil {
+ s.snapDestructs = make(map[common.Hash]struct{})
+ s.snapAccounts = make(map[common.Hash][]byte)
+ s.snapStorage = make(map[common.Hash]map[common.Hash][]byte)
+ }
+ }
return nil
}
-func (self *StateDB) AddLog(log *types.Log) {
- self.journal.append(addLogChange{txhash: self.thash})
+func (s *StateDB) AddLog(log *types.Log) {
+ s.journal.append(addLogChange{txhash: s.thash})
- log.TxHash = self.thash
- log.BlockHash = self.bhash
- log.TxIndex = uint(self.txIndex)
- log.Index = self.logSize
- self.logs[self.thash] = append(self.logs[self.thash], log)
- self.logSize++
+ log.TxHash = s.thash
+ log.BlockHash = s.bhash
+ log.TxIndex = uint(s.txIndex)
+ log.Index = s.logSize
+ s.logs[s.thash] = append(s.logs[s.thash], log)
+ s.logSize++
}
-func (self *StateDB) GetLogs(hash common.Hash) []*types.Log {
- return self.logs[hash]
+func (s *StateDB) GetLogs(hash common.Hash) []*types.Log {
+ return s.logs[hash]
}
-func (self *StateDB) Logs() []*types.Log {
+func (s *StateDB) Logs() []*types.Log {
var logs []*types.Log
- for _, lgs := range self.logs {
+ for _, lgs := range s.logs {
logs = append(logs, lgs...)
}
return logs
}
// AddPreimage records a SHA3 preimage seen by the VM.
-func (self *StateDB) AddPreimage(hash common.Hash, preimage []byte) {
- if _, ok := self.preimages[hash]; !ok {
- self.journal.append(addPreimageChange{hash: hash})
+func (s *StateDB) AddPreimage(hash common.Hash, preimage []byte) {
+ if _, ok := s.preimages[hash]; !ok {
+ s.journal.append(addPreimageChange{hash: hash})
pi := make([]byte, len(preimage))
copy(pi, preimage)
- self.preimages[hash] = pi
+ s.preimages[hash] = pi
}
}
// Preimages returns a list of SHA3 preimages that have been submitted.
-func (self *StateDB) Preimages() map[common.Hash][]byte {
- return self.preimages
+func (s *StateDB) Preimages() map[common.Hash][]byte {
+ return s.preimages
}
// AddRefund adds gas to the refund counter
-func (self *StateDB) AddRefund(gas uint64) {
- self.journal.append(refundChange{prev: self.refund})
- self.refund += gas
+func (s *StateDB) AddRefund(gas uint64) {
+ s.journal.append(refundChange{prev: s.refund})
+ s.refund += gas
}
// SubRefund removes gas from the refund counter.
// This method will panic if the refund counter goes below zero
-func (self *StateDB) SubRefund(gas uint64) {
- self.journal.append(refundChange{prev: self.refund})
- if gas > self.refund {
- panic("Refund counter below zero")
+func (s *StateDB) SubRefund(gas uint64) {
+ s.journal.append(refundChange{prev: s.refund})
+ if gas > s.refund {
+ panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund))
}
- self.refund -= gas
+ s.refund -= gas
}
// Exist reports whether the given account address exists in the state.
// Notably this also returns true for suicided accounts.
-func (self *StateDB) Exist(addr common.Address) bool {
- return self.getStateObject(addr) != nil
+func (s *StateDB) Exist(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 (self *StateDB) Empty(addr common.Address) bool {
- so := self.getStateObject(addr)
+func (s *StateDB) Empty(addr common.Address) bool {
+ so := s.getStateObject(addr)
return so == nil || so.empty()
}
-// Retrieve the balance from the given address or 0 if object not found
-func (self *StateDB) GetBalance(addr common.Address) *big.Int {
- stateObject := self.getStateObject(addr)
+// GetBalance retrieves the balance from the given address or 0 if object not found
+func (s *StateDB) GetBalance(addr common.Address) *big.Int {
+ stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.Balance()
}
@@ -241,11 +270,12 @@ func (self *StateDB) GetBalanceMultiCoin(addr common.Address, coinID common.Hash
func (self *StateDB) EnableMultiCoin(addr common.Address) error {
stateObject := self.GetOrNewStateObject(addr)
if stateObject.data.Root != emptyRoot && stateObject.data.Root != zeroRoot {
- return errors.New("not a fresh account")
+ return errors.New(fmt.Sprintf("not a fresh account: %s", stateObject.data.Root.Hex()))
}
if !stateObject.EnableMultiCoin() {
return errors.New("multi-coin mode already enabled")
}
+ log.Debug(fmt.Sprintf("enabled MC for %s", addr.Hex()))
return nil
}
@@ -262,8 +292,8 @@ func (self *StateDB) IsMultiCoin(addr common.Address) bool {
return false
}
-func (self *StateDB) GetNonce(addr common.Address) uint64 {
- stateObject := self.getStateObject(addr)
+func (s *StateDB) GetNonce(addr common.Address) uint64 {
+ stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.Nonce()
}
@@ -272,40 +302,33 @@ func (self *StateDB) GetNonce(addr common.Address) uint64 {
}
// TxIndex returns the current transaction index set by Prepare.
-func (self *StateDB) TxIndex() int {
- return self.txIndex
+func (s *StateDB) TxIndex() int {
+ return s.txIndex
}
// BlockHash returns the current block hash set by Prepare.
-func (self *StateDB) BlockHash() common.Hash {
- return self.bhash
+func (s *StateDB) BlockHash() common.Hash {
+ return s.bhash
}
-func (self *StateDB) GetCode(addr common.Address) []byte {
- stateObject := self.getStateObject(addr)
+func (s *StateDB) GetCode(addr common.Address) []byte {
+ stateObject := s.getStateObject(addr)
if stateObject != nil {
- return stateObject.Code(self.db)
+ return stateObject.Code(s.db)
}
return nil
}
-func (self *StateDB) GetCodeSize(addr common.Address) int {
- stateObject := self.getStateObject(addr)
- if stateObject == nil {
- return 0
- }
- if stateObject.code != nil {
- return len(stateObject.code)
- }
- size, err := self.db.ContractCodeSize(stateObject.addrHash, common.BytesToHash(stateObject.CodeHash()))
- if err != nil {
- self.setError(err)
+func (s *StateDB) GetCodeSize(addr common.Address) int {
+ stateObject := s.getStateObject(addr)
+ if stateObject != nil {
+ return stateObject.CodeSize(s.db)
}
- return size
+ return 0
}
-func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
- stateObject := self.getStateObject(addr)
+func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
+ stateObject := s.getStateObject(addr)
if stateObject == nil {
return common.Hash{}
}
@@ -313,25 +336,25 @@ func (self *StateDB) GetCodeHash(addr common.Address) common.Hash {
}
// GetState retrieves a value from the given account's storage trie.
-func (self *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
- stateObject := self.getStateObject(addr)
+func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
+ stateObject := s.getStateObject(addr)
if stateObject != nil {
- return stateObject.GetState(self.db, hash)
+ return stateObject.GetState(s.db, hash)
}
return common.Hash{}
}
// GetProof returns the MerkleProof for a given Account
-func (self *StateDB) GetProof(a common.Address) ([][]byte, error) {
+func (s *StateDB) GetProof(a common.Address) ([][]byte, error) {
var proof proofList
- err := self.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof)
+ err := s.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof)
return [][]byte(proof), err
}
-// GetProof returns the StorageProof for given key
-func (self *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) {
+// GetStorageProof returns the StorageProof for given key
+func (s *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byte, error) {
var proof proofList
- trie := self.StorageTrie(a)
+ trie := s.StorageTrie(a)
if trie == nil {
return proof, errors.New("storage trie for requested address does not exist")
}
@@ -340,32 +363,33 @@ func (self *StateDB) GetStorageProof(a common.Address, key common.Hash) ([][]byt
}
// GetCommittedState retrieves a value from the given account's committed storage trie.
-func (self *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
- stateObject := self.getStateObject(addr)
+func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash {
+ stateObject := s.getStateObject(addr)
if stateObject != nil {
- return stateObject.GetCommittedState(self.db, hash)
+ return stateObject.GetCommittedState(s.db, hash)
}
return common.Hash{}
}
// Database retrieves the low level database supporting the lower level trie ops.
-func (self *StateDB) Database() Database {
- return self.db
+func (s *StateDB) Database() Database {
+ return s.db
}
// StorageTrie returns the storage trie of an account.
// The return value is a copy and is nil for non-existent accounts.
-func (self *StateDB) StorageTrie(addr common.Address) Trie {
- stateObject := self.getStateObject(addr)
+func (s *StateDB) StorageTrie(addr common.Address) Trie {
+ stateObject := s.getStateObject(addr)
if stateObject == nil {
return nil
}
- cpy := stateObject.deepCopy(self)
- return cpy.updateTrie(self.db)
+ cpy := stateObject.deepCopy(s)
+ cpy.updateTrie(s.db)
+ return cpy.getTrie(s.db)
}
-func (self *StateDB) HasSuicided(addr common.Address) bool {
- stateObject := self.getStateObject(addr)
+func (s *StateDB) HasSuicided(addr common.Address) bool {
+ stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.suicided
}
@@ -377,81 +401,81 @@ func (self *StateDB) HasSuicided(addr common.Address) bool {
*/
// AddBalance adds amount to the account associated with addr.
-func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) {
- stateObject := self.GetOrNewStateObject(addr)
+func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) {
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.AddBalance(amount)
}
}
// SubBalance subtracts amount from the account associated with addr.
-func (self *StateDB) SubBalance(addr common.Address, amount *big.Int) {
- stateObject := self.GetOrNewStateObject(addr)
+func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) {
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SubBalance(amount)
}
}
-func (self *StateDB) SetBalance(addr common.Address, amount *big.Int) {
- stateObject := self.GetOrNewStateObject(addr)
+func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) {
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetBalance(amount)
}
}
// AddBalance adds amount to the account associated with addr.
-func (self *StateDB) AddBalanceMultiCoin(addr common.Address, coinID common.Hash, amount *big.Int) {
- stateObject := self.GetOrNewStateObject(addr)
+func (s *StateDB) AddBalanceMultiCoin(addr common.Address, coinID common.Hash, amount *big.Int) {
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
- stateObject.AddBalanceMultiCoin(coinID, amount, self.db)
+ stateObject.AddBalanceMultiCoin(coinID, amount, s.db)
}
}
// SubBalance subtracts amount from the account associated with addr.
-func (self *StateDB) SubBalanceMultiCoin(addr common.Address, coinID common.Hash, amount *big.Int) {
- stateObject := self.GetOrNewStateObject(addr)
+func (s *StateDB) SubBalanceMultiCoin(addr common.Address, coinID common.Hash, amount *big.Int) {
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
- stateObject.SubBalanceMultiCoin(coinID, amount, self.db)
+ stateObject.SubBalanceMultiCoin(coinID, amount, s.db)
}
}
-func (self *StateDB) SetBalanceMultiCoin(addr common.Address, coinID common.Hash, amount *big.Int) {
- stateObject := self.GetOrNewStateObject(addr)
+func (s *StateDB) SetBalanceMultiCoin(addr common.Address, coinID common.Hash, amount *big.Int) {
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
- stateObject.SetBalanceMultiCoin(coinID, amount, self.db)
+ stateObject.SetBalanceMultiCoin(coinID, amount, s.db)
}
}
-func (self *StateDB) SetNonce(addr common.Address, nonce uint64) {
- stateObject := self.GetOrNewStateObject(addr)
+func (s *StateDB) SetNonce(addr common.Address, nonce uint64) {
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetNonce(nonce)
}
}
-func (self *StateDB) SetCode(addr common.Address, code []byte) {
- stateObject := self.GetOrNewStateObject(addr)
+func (s *StateDB) SetCode(addr common.Address, code []byte) {
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetCode(crypto.Keccak256Hash(code), code)
}
}
-func (self *StateDB) SetState(addr common.Address, key, value common.Hash) (res error) {
+func (s *StateDB) SetState(addr common.Address, key, value common.Hash) (res error) {
res = nil
- stateObject := self.GetOrNewStateObject(addr)
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
if stateObject.data.IsMultiCoin {
NormalizeStateKey(&key)
}
- stateObject.SetState(self.db, key, value)
+ stateObject.SetState(s.db, key, value)
}
return
}
// SetStorage replaces the entire storage for the specified account with given
// storage. This function should only be used for debugging.
-func (self *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) {
- stateObject := self.GetOrNewStateObject(addr)
+func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common.Hash) {
+ stateObject := s.GetOrNewStateObject(addr)
if stateObject != nil {
stateObject.SetStorage(storage)
}
@@ -462,12 +486,12 @@ func (self *StateDB) SetStorage(addr common.Address, storage map[common.Hash]com
//
// The account's state object is still available until the state is committed,
// getStateObject will return a non-nil account after Suicide.
-func (self *StateDB) Suicide(addr common.Address) bool {
- stateObject := self.getStateObject(addr)
+func (s *StateDB) Suicide(addr common.Address) bool {
+ stateObject := s.getStateObject(addr)
if stateObject == nil {
return false
}
- self.journal.append(suicideChange{
+ s.journal.append(suicideChange{
account: &addr,
prev: stateObject.suicided,
prevbalance: new(big.Int).Set(stateObject.Balance()),
@@ -483,90 +507,154 @@ func (self *StateDB) Suicide(addr common.Address) bool {
//
// updateStateObject writes the given object to the trie.
-func (s *StateDB) updateStateObject(stateObject *stateObject) {
+func (s *StateDB) updateStateObject(obj *stateObject) {
// Track the amount of time wasted on updating the account from the trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
}
// Encode the account and update the account trie
- addr := stateObject.Address()
+ addr := obj.Address()
- data, err := rlp.EncodeToBytes(stateObject)
+ data, err := rlp.EncodeToBytes(obj)
if err != nil {
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
}
- s.setError(s.trie.TryUpdate(addr[:], data))
+ if err = s.trie.TryUpdate(addr[:], data); err != nil {
+ s.setError(fmt.Errorf("updateStateObject (%x) error: %v", addr[:], err))
+ }
+
+ // If state snapshotting is active, cache the data til commit. Note, this
+ // update mechanism is not symmetric to the deletion, because whereas it is
+ // enough to track account updates at commit time, deletions need tracking
+ // at transaction boundary level to ensure we capture state clearing.
+ if s.snap != nil {
+ s.snapAccounts[obj.addrHash] = snapshot.SlimAccountRLP(obj.data.Nonce, obj.data.Balance, obj.data.Root, obj.data.CodeHash, obj.data.IsMultiCoin)
+ }
}
// deleteStateObject removes the given object from the state trie.
-func (s *StateDB) deleteStateObject(stateObject *stateObject) {
+func (s *StateDB) deleteStateObject(obj *stateObject) {
// Track the amount of time wasted on deleting the account from the trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountUpdates += time.Since(start) }(time.Now())
}
// Delete the account from the trie
- stateObject.deleted = true
+ addr := obj.Address()
+ if err := s.trie.TryDelete(addr[:]); err != nil {
+ s.setError(fmt.Errorf("deleteStateObject (%x) error: %v", addr[:], err))
+ }
+}
- addr := stateObject.Address()
- s.setError(s.trie.TryDelete(addr[:]))
+// getStateObject retrieves a state object given by the address, returning nil if
+// the object is not found or was deleted in this execution context. If you need
+// to differentiate between non-existent/just-deleted, use getDeletedStateObject.
+func (s *StateDB) getStateObject(addr common.Address) *stateObject {
+ if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted {
+ return obj
+ }
+ return nil
}
-// Retrieve a state object given by the address. Returns nil if not found.
-func (s *StateDB) getStateObject(addr common.Address) (stateObject *stateObject) {
- // Prefer live objects
+// getDeletedStateObject is similar to getStateObject, but instead of returning
+// nil for a deleted state object, it returns the actual object with the deleted
+// flag set. This is needed by the state journal to revert to the correct s-
+// destructed object instead of wiping all knowledge about the state object.
+func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject {
+ // Prefer live objects if any is available
if obj := s.stateObjects[addr]; obj != nil {
- if obj.deleted {
- return nil
- }
return obj
}
- // Track the amount of time wasted on loading the object from the database
- if metrics.EnabledExpensive {
- defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now())
- }
- // Load the object from the database
- enc, err := s.trie.TryGet(addr[:])
- if len(enc) == 0 {
- s.setError(err)
- return nil
+ // If no live objects are available, attempt to use snapshots
+ var (
+ data *Account
+ err error
+ )
+ if s.snap != nil {
+ if metrics.EnabledExpensive {
+ defer func(start time.Time) { s.SnapshotAccountReads += time.Since(start) }(time.Now())
+ }
+ var acc *snapshot.Account
+ if acc, err = s.snap.Account(crypto.Keccak256Hash(addr.Bytes())); err == nil {
+ if acc == nil {
+ return nil
+ }
+ data = &Account{
+ Nonce: acc.Nonce,
+ Balance: acc.Balance,
+ CodeHash: acc.CodeHash,
+ IsMultiCoin: acc.IsMultiCoin,
+ Root: common.BytesToHash(acc.Root),
+ }
+ if len(data.CodeHash) == 0 {
+ data.CodeHash = emptyCodeHash
+ }
+ if data.Root == (common.Hash{}) {
+ data.Root = emptyRoot
+ }
+ }
}
- var data Account
- if err := rlp.DecodeBytes(enc, &data); err != nil {
- log.Error("Failed to decode state object", "addr", addr, "err", err)
- return nil
+ // If snapshot unavailable or reading from it failed, load from the database
+ if s.snap == nil || err != nil {
+ if metrics.EnabledExpensive {
+ defer func(start time.Time) { s.AccountReads += time.Since(start) }(time.Now())
+ }
+ enc, err := s.trie.TryGet(addr.Bytes())
+ if err != nil {
+ s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr.Bytes(), err))
+ return nil
+ }
+ if len(enc) == 0 {
+ return nil
+ }
+ data = new(Account)
+ if err := rlp.DecodeBytes(enc, data); err != nil {
+ log.Error("Failed to decode state object", "addr", addr, "err", err)
+ return nil
+ }
}
// Insert into the live set
- obj := newObject(s, addr, data)
+ obj := newObject(s, addr, *data)
s.setStateObject(obj)
return obj
}
-func (self *StateDB) setStateObject(object *stateObject) {
- self.stateObjects[object.Address()] = object
+func (s *StateDB) setStateObject(object *stateObject) {
+ s.stateObjects[object.Address()] = object
}
-// Retrieve a state object or create a new state object if nil.
-func (self *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
- stateObject := self.getStateObject(addr)
- if stateObject == nil || stateObject.deleted {
- stateObject, _ = self.createObject(addr)
+// GetOrNewStateObject retrieves a state object or create a new state object if nil.
+func (s *StateDB) GetOrNewStateObject(addr common.Address) *stateObject {
+ stateObject := s.getStateObject(addr)
+ if stateObject == nil {
+ stateObject, _ = s.createObject(addr)
}
return stateObject
}
// createObject creates a new state object. If there is an existing account with
// the given address, it is overwritten and returned as the second return value.
-func (self *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
- prev = self.getStateObject(addr)
- newobj = newObject(self, addr, Account{})
+func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject) {
+ prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that!
+
+ var prevdestruct bool
+ if s.snap != nil && prev != nil {
+ _, prevdestruct = s.snapDestructs[prev.addrHash]
+ if !prevdestruct {
+ s.snapDestructs[prev.addrHash] = struct{}{}
+ }
+ }
+ newobj = newObject(s, addr, Account{})
newobj.setNonce(0) // sets the object to dirty
if prev == nil {
- self.journal.append(createObjectChange{account: &addr})
+ s.journal.append(createObjectChange{account: &addr})
} else {
- self.journal.append(resetObjectChange{prev: prev})
+ s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct})
+ }
+ s.setStateObject(newobj)
+ if prev != nil && !prev.deleted {
+ return newobj, prev
}
- self.setStateObject(newobj)
- return newobj, prev
+ return newobj, nil
}
// CreateAccount explicitly creates a state object. If a state object with the address
@@ -579,8 +667,8 @@ func (self *StateDB) createObject(addr common.Address) (newobj, prev *stateObjec
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
//
// Carrying over the balance ensures that Ether doesn't disappear.
-func (self *StateDB) CreateAccount(addr common.Address) {
- newObj, prev := self.createObject(addr)
+func (s *StateDB) CreateAccount(addr common.Address) {
+ newObj, prev := s.createObject(addr)
if prev != nil {
newObj.setBalance(prev.data.Balance)
}
@@ -617,40 +705,52 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common
// Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy.
-func (self *StateDB) Copy() *StateDB {
+func (s *StateDB) Copy() *StateDB {
// Copy all the basic fields, initialize the memory ones
state := &StateDB{
- db: self.db,
- trie: self.db.CopyTrie(self.trie),
- stateObjects: make(map[common.Address]*stateObject, len(self.journal.dirties)),
- stateObjectsDirty: make(map[common.Address]struct{}, len(self.journal.dirties)),
- refund: self.refund,
- logs: make(map[common.Hash][]*types.Log, len(self.logs)),
- logSize: self.logSize,
- preimages: make(map[common.Hash][]byte, len(self.preimages)),
- journal: newJournal(),
+ db: s.db,
+ trie: s.db.CopyTrie(s.trie),
+ stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)),
+ stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)),
+ stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)),
+ refund: s.refund,
+ logs: make(map[common.Hash][]*types.Log, len(s.logs)),
+ logSize: s.logSize,
+ preimages: make(map[common.Hash][]byte, len(s.preimages)),
+ journal: newJournal(),
}
// Copy the dirty states, logs, and preimages
- for addr := range self.journal.dirties {
+ for addr := range s.journal.dirties {
// As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527),
// and in the Finalise-method, there is a case where an object is in the journal but not
// in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for
// nil
- if object, exist := self.stateObjects[addr]; exist {
+ if object, exist := s.stateObjects[addr]; exist {
+ // Even though the original object is dirty, we are not copying the journal,
+ // so we need to make sure that anyside effect the journal would have caused
+ // during a commit (or similar op) is already applied to the copy.
state.stateObjects[addr] = object.deepCopy(state)
- state.stateObjectsDirty[addr] = struct{}{}
+
+ state.stateObjectsDirty[addr] = struct{}{} // Mark the copy dirty to force internal (code/state) commits
+ state.stateObjectsPending[addr] = struct{}{} // Mark the copy pending to force external (account) commits
}
}
// Above, we don't copy the actual journal. This means that if the copy is copied, the
// loop above will be a no-op, since the copy's journal is empty.
// Thus, here we iterate over stateObjects, to enable copies of copies
- for addr := range self.stateObjectsDirty {
+ for addr := range s.stateObjectsPending {
+ if _, exist := state.stateObjects[addr]; !exist {
+ state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
+ }
+ state.stateObjectsPending[addr] = struct{}{}
+ }
+ for addr := range s.stateObjectsDirty {
if _, exist := state.stateObjects[addr]; !exist {
- state.stateObjects[addr] = self.stateObjects[addr].deepCopy(state)
- state.stateObjectsDirty[addr] = struct{}{}
+ state.stateObjects[addr] = s.stateObjects[addr].deepCopy(state)
}
+ state.stateObjectsDirty[addr] = struct{}{}
}
- for hash, logs := range self.logs {
+ for hash, logs := range s.logs {
cpy := make([]*types.Log, len(logs))
for i, l := range logs {
cpy[i] = new(types.Log)
@@ -658,46 +758,47 @@ func (self *StateDB) Copy() *StateDB {
}
state.logs[hash] = cpy
}
- for hash, preimage := range self.preimages {
+ for hash, preimage := range s.preimages {
state.preimages[hash] = preimage
}
return state
}
// Snapshot returns an identifier for the current revision of the state.
-func (self *StateDB) Snapshot() int {
- id := self.nextRevisionId
- self.nextRevisionId++
- self.validRevisions = append(self.validRevisions, revision{id, self.journal.length()})
+func (s *StateDB) Snapshot() int {
+ id := s.nextRevisionId
+ s.nextRevisionId++
+ s.validRevisions = append(s.validRevisions, revision{id, s.journal.length()})
return id
}
// RevertToSnapshot reverts all state changes made since the given revision.
-func (self *StateDB) RevertToSnapshot(revid int) {
+func (s *StateDB) RevertToSnapshot(revid int) {
// Find the snapshot in the stack of valid snapshots.
- idx := sort.Search(len(self.validRevisions), func(i int) bool {
- return self.validRevisions[i].id >= revid
+ idx := sort.Search(len(s.validRevisions), func(i int) bool {
+ return s.validRevisions[i].id >= revid
})
- if idx == len(self.validRevisions) || self.validRevisions[idx].id != revid {
+ if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid {
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
}
- snapshot := self.validRevisions[idx].journalIndex
+ snapshot := s.validRevisions[idx].journalIndex
// Replay the journal to undo changes and remove invalidated snapshots
- self.journal.revert(self, snapshot)
- self.validRevisions = self.validRevisions[:idx]
+ s.journal.revert(s, snapshot)
+ s.validRevisions = s.validRevisions[:idx]
}
// GetRefund returns the current value of the refund counter.
-func (self *StateDB) GetRefund() uint64 {
- return self.refund
+func (s *StateDB) GetRefund() uint64 {
+ return s.refund
}
-// Finalise finalises the state by removing the self destructed objects
-// and clears the journal as well as the refunds.
+// Finalise finalises the state by removing the s destructed objects and clears
+// the journal as well as the refunds. Finalise, however, will not push any updates
+// into the tries just yet. Only IntermediateRoot or Commit will do that.
func (s *StateDB) Finalise(deleteEmptyObjects bool) {
for addr := range s.journal.dirties {
- stateObject, exist := s.stateObjects[addr]
+ obj, exist := s.stateObjects[addr]
if !exist {
// ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
// That tx goes out of gas, and although the notion of 'touched' does not exist there, the
@@ -707,13 +808,22 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
// Thus, we can safely ignore it here
continue
}
-
- if stateObject.suicided || (deleteEmptyObjects && stateObject.empty()) {
- s.deleteStateObject(stateObject)
+ if obj.suicided || (deleteEmptyObjects && obj.empty()) {
+ obj.deleted = true
+
+ // If state snapshotting is active, also mark the destruction there.
+ // Note, we can't do this only at the end of a block because multiple
+ // transactions within the same block might self destruct and then
+ // ressurrect an account; but the snapshotter needs both events.
+ if s.snap != nil {
+ s.snapDestructs[obj.addrHash] = struct{}{} // We need to maintain account deletions explicitly (will remain set indefinitely)
+ delete(s.snapAccounts, obj.addrHash) // Clear out any previously updated account data (may be recreated via a ressurrect)
+ delete(s.snapStorage, obj.addrHash) // Clear out any previously updated storage data (may be recreated via a ressurrect)
+ }
} else {
- stateObject.updateRoot(s.db)
- s.updateStateObject(stateObject)
+ obj.finalise()
}
+ s.stateObjectsPending[addr] = struct{}{}
s.stateObjectsDirty[addr] = struct{}{}
}
// Invalidate journal because reverting across transactions is not allowed.
@@ -724,8 +834,21 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
// It is called in between transactions to get the root hash that
// goes into transaction receipts.
func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
+ // Finalise all the dirty storage states and write them into the tries
s.Finalise(deleteEmptyObjects)
+ for addr := range s.stateObjectsPending {
+ obj := s.stateObjects[addr]
+ if obj.deleted {
+ s.deleteStateObject(obj)
+ } else {
+ obj.updateRoot(s.db)
+ s.updateStateObject(obj)
+ }
+ }
+ if len(s.stateObjectsPending) > 0 {
+ s.stateObjectsPending = make(map[common.Address]struct{})
+ }
// Track the amount of time wasted on hashing the account trie
if metrics.EnabledExpensive {
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now())
@@ -735,65 +858,86 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash {
// Prepare sets the current transaction hash and index and block hash which is
// used when the EVM emits new state logs.
-func (self *StateDB) Prepare(thash, bhash common.Hash, ti int) {
- self.thash = thash
- self.bhash = bhash
- self.txIndex = ti
+func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) {
+ s.thash = thash
+ s.bhash = bhash
+ s.txIndex = ti
}
func (s *StateDB) clearJournalAndRefund() {
- s.journal = newJournal()
- s.validRevisions = s.validRevisions[:0]
- s.refund = 0
+ if len(s.journal.entries) > 0 {
+ s.journal = newJournal()
+ s.refund = 0
+ }
+ s.validRevisions = s.validRevisions[:0] // Snapshots can be created without journal entires
}
// Commit writes the state to the underlying in-memory trie database.
-func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) {
- defer s.clearJournalAndRefund()
-
- for addr := range s.journal.dirties {
- s.stateObjectsDirty[addr] = struct{}{}
+func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) {
+ if s.dbErr != nil {
+ return common.Hash{}, fmt.Errorf("commit aborted due to earlier error: %v", s.dbErr)
}
+ // Finalize any pending changes and merge everything into the tries
+ s.IntermediateRoot(deleteEmptyObjects)
+
// Commit objects to the trie, measuring the elapsed time
- for addr, stateObject := range s.stateObjects {
- _, isDirty := s.stateObjectsDirty[addr]
- switch {
- case stateObject.suicided || (isDirty && deleteEmptyObjects && stateObject.empty()):
- // If the object has been removed, don't bother syncing it
- // and just mark it for deletion in the trie.
- s.deleteStateObject(stateObject)
- case isDirty:
+ codeWriter := s.db.TrieDB().DiskDB().NewBatch()
+ for addr := range s.stateObjectsDirty {
+ if obj := s.stateObjects[addr]; !obj.deleted {
// Write any contract code associated with the state object
- if stateObject.code != nil && stateObject.dirtyCode {
- s.db.TrieDB().InsertBlob(common.BytesToHash(stateObject.CodeHash()), stateObject.code)
- stateObject.dirtyCode = false
+ if obj.code != nil && obj.dirtyCode {
+ rawdb.WriteCode(codeWriter, common.BytesToHash(obj.CodeHash()), obj.code)
+ obj.dirtyCode = false
}
- // Write any storage changes in the state object to its storage trie.
- if err := stateObject.CommitTrie(s.db); err != nil {
+ // Write any storage changes in the state object to its storage trie
+ if err := obj.CommitTrie(s.db); err != nil {
return common.Hash{}, err
}
- // Update the object in the main account trie.
- s.updateStateObject(stateObject)
}
- delete(s.stateObjectsDirty, addr)
+ }
+ if len(s.stateObjectsDirty) > 0 {
+ s.stateObjectsDirty = make(map[common.Address]struct{})
+ }
+ if codeWriter.ValueSize() > 0 {
+ if err := codeWriter.Write(); err != nil {
+ log.Crit("Failed to commit dirty codes", "error", err)
+ }
}
// Write the account trie changes, measuing the amount of wasted time
+ var start time.Time
if metrics.EnabledExpensive {
- defer func(start time.Time) { s.AccountCommits += time.Since(start) }(time.Now())
+ start = time.Now()
}
- root, err = s.trie.Commit(func(leaf []byte, parent common.Hash) error {
- var account Account
+ // The onleaf func is called _serially_, so we can reuse the same account
+ // for unmarshalling every time.
+ var account Account
+ root, err := s.trie.Commit(func(path []byte, leaf []byte, parent common.Hash) error {
if err := rlp.DecodeBytes(leaf, &account); err != nil {
return nil
}
if account.Root != emptyRoot {
s.db.TrieDB().Reference(account.Root, parent)
}
- code := common.BytesToHash(account.CodeHash)
- if code != emptyCode {
- s.db.TrieDB().Reference(code, parent)
- }
return nil
})
+ if metrics.EnabledExpensive {
+ s.AccountCommits += time.Since(start)
+ }
+ // If snapshotting is enabled, update the snapshot tree with this new version
+ if s.snap != nil {
+ if metrics.EnabledExpensive {
+ defer func(start time.Time) { s.SnapshotCommits += time.Since(start) }(time.Now())
+ }
+ // Only update if there's a state transition (skip empty Clique blocks)
+ if parent := s.snap.Root(); parent != root {
+ if err := s.snaps.Update(root, parent, s.snapDestructs, s.snapAccounts, s.snapStorage); err != nil {
+ log.Warn("Failed to update snapshot tree", "from", parent, "to", root, "err", err)
+ }
+ if err := s.snaps.Cap(root, 127); err != nil { // Persistent layer is 128th, the last available trie
+ log.Warn("Failed to cap snapshot tree", "root", root, "layers", 127, "err", err)
+ }
+ }
+ s.snap, s.snapDestructs, s.snapAccounts, s.snapStorage = nil, nil, nil, nil
+ }
return root, err
}