From f5b682fe9007ec523401fc69fa9a57e27402a5d3 Mon Sep 17 00:00:00 2001 From: setunapo Date: Thu, 21 Apr 2022 11:54:34 +0800 Subject: [PATCH 01/12] implement SlotStateDB --- core/evm.go | 4 +- core/state/dump.go | 2 +- core/state/state_object.go | 23 +- core/state/statedb.go | 971 +++++++++++++++++++++++++++++-------- core/state_processor.go | 18 +- core/state_transition.go | 2 +- core/vm/evm.go | 10 +- core/vm/interface.go | 5 +- eth/tracers/js/tracer.go | 2 +- 9 files changed, 806 insertions(+), 231 deletions(-) diff --git a/core/evm.go b/core/evm.go index 8f69d51499..05c2301f1f 100644 --- a/core/evm.go +++ b/core/evm.go @@ -100,12 +100,12 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash // CanTransfer checks whether there are enough funds in the address' account to make a transfer. // This does not take the necessary gas in to account to make the transfer valid. -func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { +func CanTransfer(db vm.StateDBer, addr common.Address, amount *big.Int) bool { return db.GetBalance(addr).Cmp(amount) >= 0 } // Transfer subtracts amount from sender and adds amount to recipient using the given Db -func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { +func Transfer(db vm.StateDBer, sender, recipient common.Address, amount *big.Int) { db.SubBalance(sender, amount) db.AddBalance(recipient, amount) } diff --git a/core/state/dump.go b/core/state/dump.go index 55f4c7754d..c705e7a311 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -138,7 +138,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, account.SecureKey = it.Key } addr := common.BytesToAddress(addrBytes) - obj := newObject(s, s.isParallel, addr, data) + obj := newObject(s, s, s.isParallel, addr, data) if !excludeCode { account.Code = common.Bytes2Hex(obj.Code(s.db)) } diff --git a/core/state/state_object.go b/core/state/state_object.go index b442954efe..a4a743920b 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -25,6 +25,7 @@ import ( "time" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" @@ -150,6 +151,7 @@ type StateObject struct { addrHash common.Hash // hash of ethereum address of the account data Account db *StateDB + dbItf vm.StateDBer // DB error. // State objects are used by the consensus core and VM which are @@ -218,13 +220,13 @@ func (s *StateObject) empty() bool { // Slot 0 tx 2: GetNonce, lightCopy based on main DB(balance = 100) , not empty // return s.db.GetNonce(s.address) == 0 && s.db.GetBalance(s.address).Sign() == 0 && bytes.Equal(s.db.GetCodeHash(s.address).Bytes(), emptyCodeHash) - if s.db.GetBalance(s.address).Sign() != 0 { // check balance first, since it is most likely not zero + if s.dbItf.GetBalance(s.address).Sign() != 0 { // check balance first, since it is most likely not zero return false } - if s.db.GetNonce(s.address) != 0 { + if s.dbItf.GetNonce(s.address) != 0 { return false } - codeHash := s.db.GetCodeHash(s.address) + codeHash := s.dbItf.GetCodeHash(s.address) return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty } @@ -239,7 +241,7 @@ type Account struct { } // newObject creates a state object. -func newObject(db *StateDB, isParallel bool, address common.Address, data Account) *StateObject { +func newObject(db *StateDB, dbItf vm.StateDBer, isParallel bool, address common.Address, data Account) *StateObject { if data.Balance == nil { data.Balance = new(big.Int) // todo: why not common.Big0? } @@ -257,6 +259,7 @@ func newObject(db *StateDB, isParallel bool, address common.Address, data Accoun return &StateObject{ db: db, + dbItf: dbItf, address: address, addrHash: crypto.Keccak256Hash(address[:]), data: data, @@ -448,7 +451,7 @@ func (s *StateObject) SetState(db Database, key, value common.Hash) { // this `SetState could be skipped` // d.Finally, the key's value will be `val_2`, while it should be `val_1` // such as: https://bscscan.com/txs?block=2491181 - prev := s.db.GetState(s.address, key) + prev := s.db.GetState(s.address, key) // fixme: if it is for journal, may not necessary, we can remove this change record if prev == value { return } @@ -664,8 +667,8 @@ func (s *StateObject) setBalance(amount *big.Int) { // Return the gas back to the origin. Used by the Virtual machine or Closures func (s *StateObject) ReturnGas(gas *big.Int) {} -func (s *StateObject) lightCopy(db *StateDB) *StateObject { - stateObject := newObject(db, s.isParallel, s.address, s.data) +func (s *StateObject) lightCopy(db *ParallelStateDB) *StateObject { + stateObject := newObject(&db.StateDB, db, s.isParallel, s.address, s.data) if s.trie != nil { // fixme: no need to copy trie for light copy, since light copied object won't access trie DB stateObject.trie = db.db.CopyTrie(s.trie) @@ -678,7 +681,7 @@ func (s *StateObject) lightCopy(db *StateDB) *StateObject { } func (s *StateObject) deepCopy(db *StateDB) *StateObject { - stateObject := newObject(db, s.isParallel, s.address, s.data) + stateObject := newObject(db, db, s.isParallel, s.address, s.data) if s.trie != nil { stateObject.trie = db.db.CopyTrie(s.trie) } @@ -744,7 +747,7 @@ func (s *StateObject) CodeSize(db Database) int { } func (s *StateObject) SetCode(codeHash common.Hash, code []byte) { - prevcode := s.db.GetCode(s.address) + prevcode := s.dbItf.GetCode(s.address) s.db.journal.append(codeChange{ account: &s.address, prevhash: s.CodeHash(), @@ -760,7 +763,7 @@ func (s *StateObject) setCode(codeHash common.Hash, code []byte) { } func (s *StateObject) SetNonce(nonce uint64) { - prevNonce := s.db.GetNonce(s.address) + prevNonce := s.dbItf.GetNonce(s.address) s.db.journal.append(nonceChange{ account: &s.address, prev: prevNonce, diff --git a/core/state/statedb.go b/core/state/statedb.go index 4e852cdbd8..2bd0c44884 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -139,7 +139,7 @@ type ParallelState struct { baseStateDB *StateDB // for parallel mode, there will be a base StateDB in dispatcher routine. baseTxIndex int // slotDB is created base on this tx index. dirtiedStateObjectsInSlot map[common.Address]*StateObject - unconfirmedDBInShot map[int]*StateDB // do unconfirmed reference in same slot. + unconfirmedDBInShot map[int]*ParallelStateDB // do unconfirmed reference in same slot. // we will record the read detail for conflict check and // the changed addr or key for object merge, the changed detail can be acheived from the dirty object @@ -265,7 +265,7 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) // NewSlotDB creates a new State DB based on the provided StateDB. // With parallel, each execution slot would have its own StateDB. func NewSlotDB(db *StateDB, systemAddr common.Address, txIndex int, baseTxIndex int, keepSystem bool, - unconfirmedDBs *sync.Map /*map[int]*StateDB*/) *StateDB { + unconfirmedDBs *sync.Map /*map[int]*StateDB*/) *ParallelStateDB { slotDB := db.CopyForSlot() slotDB.txIndex = txIndex slotDB.originalRoot = db.originalRoot @@ -279,7 +279,7 @@ func NewSlotDB(db *StateDB, systemAddr common.Address, txIndex int, baseTxIndex for index := baseTxIndex + 1; index < slotDB.txIndex; index++ { // txIndex unconfirmedDB, ok := unconfirmedDBs.Load(index) if ok { - slotDB.parallel.unconfirmedDBInShot[index] = unconfirmedDB.(*StateDB) + slotDB.parallel.unconfirmedDBInShot[index] = unconfirmedDB.(*ParallelStateDB) } } @@ -380,7 +380,7 @@ func (s *StateDB) PrepareForParallel() { // finalized(dirty -> pending) on execution slot, the execution results should be // merged back to the main StateDB. // And it will return and keep the slot's change list for later conflict detect. -func (s *StateDB) MergeSlotDB(slotDb *StateDB, slotReceipt *types.Receipt, txIndex int) { +func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receipt, txIndex int) { // receipt.Logs use unified log index within a block // align slotDB's log index to the block stateDB's logSize for _, l := range slotReceipt.Logs { @@ -1516,36 +1516,7 @@ func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - if s.parallel.isSlotDB { - if addr == s.parallel.systemAddress { - s.parallel.systemAddressOpsCount++ - } - // if amount.Sign() != 0 { // todo: to reenable it - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) // light copy from main DB - // do balance fixup from the confirmed DB, it could be more reliable than main DB - balance := s.GetBalance(addr) - newStateObject.setBalance(balance) - // s.parallel.balanceReadsInSlot[addr] = newStateObject.Balance() // could read from main DB or unconfirmed DB - newStateObject.AddBalance(amount) - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - s.parallel.balanceChangesInSlot[addr] = struct{}{} - return - } - // already dirty, make sure the balance if fixed up - // if stateObject.Balance() - if addr != s.parallel.systemAddress { - if stateObject.Balance().Cmp(s.GetBalance(addr)) != 0 { - log.Warn("AddBalance in dirty, but balance is incorrect", "txIndex", s.txIndex, "addr", addr, - "stateObject.Balance()", stateObject.Balance(), "s.GetBalance(addr)", s.GetBalance(addr)) - stateObject.setBalance(s.GetBalance(addr)) - } - } - } stateObject.AddBalance(amount) - if s.parallel.isSlotDB { - s.parallel.balanceChangesInSlot[addr] = struct{}{} - } } } @@ -1559,88 +1530,21 @@ func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { // } stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - if s.parallel.isSlotDB { - if addr == s.parallel.systemAddress { - s.parallel.systemAddressOpsCount++ - } - - // if amount.Sign() != 0 { // todo: to reenable it - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) // light copy from main DB - // do balance fixup from the confirmed DB, it could be more reliable than main DB - balance := s.GetBalance(addr) - newStateObject.setBalance(balance) - // s.parallel.balanceReadsInSlot[addr] = newStateObject.Balance() - newStateObject.SubBalance(amount) - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - return - } - // already dirty, make sure the balance if fixed - // if stateObject.Balance() - if addr != s.parallel.systemAddress { - if stateObject.Balance().Cmp(s.GetBalance(addr)) != 0 { - log.Warn("SubBalance in dirty, but balance is incorrect", "txIndex", s.txIndex, "addr", addr, - "stateObject.Balance()", stateObject.Balance(), "s.GetBalance(addr)", s.GetBalance(addr)) - stateObject.setBalance(s.GetBalance(addr)) - } - } - } stateObject.SubBalance(amount) - if s.parallel.isSlotDB { - s.parallel.balanceChangesInSlot[addr] = struct{}{} - } - } } func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - if s.parallel.isSlotDB { - if addr == s.parallel.systemAddress { - s.parallel.systemAddressOpsCount++ - } - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) - // update balance for revert, in case child contract is revertted, - // it should revert to the previous balance - balance := s.GetBalance(addr) - newStateObject.setBalance(balance) - newStateObject.SetBalance(amount) - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - return - } - - } stateObject.SetBalance(amount) - if s.parallel.isSlotDB { - s.parallel.balanceChangesInSlot[addr] = struct{}{} - } } } func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - if s.parallel.isSlotDB { - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) - noncePre := s.GetNonce(addr) - newStateObject.setNonce(noncePre) // nonce fixup - newStateObject.SetNonce(nonce) - s.parallel.nonceChangesInSlot[addr] = struct{}{} - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - return - } - noncePre := s.GetNonce(addr) - stateObject.setNonce(noncePre) // nonce fixup - } stateObject.SetNonce(nonce) - if s.parallel.isSlotDB { - s.parallel.nonceChangesInSlot[addr] = struct{}{} - } } } @@ -1648,59 +1552,13 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { codeHash := crypto.Keccak256Hash(code) - if s.parallel.isSlotDB { - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) - codePre := s.GetCode(addr) // code fixup - codeHashPre := crypto.Keccak256Hash(codePre) - newStateObject.setCode(codeHashPre, codePre) - - newStateObject.SetCode(codeHash, code) - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - s.parallel.codeChangesInSlot[addr] = struct{}{} - return - } - codePre := s.GetCode(addr) // code fixup - codeHashPre := crypto.Keccak256Hash(codePre) - stateObject.setCode(codeHashPre, codePre) - } stateObject.SetCode(codeHash, code) - if s.parallel.isSlotDB { - s.parallel.codeChangesInSlot[addr] = struct{}{} - } } } func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { - stateObject := s.GetOrNewStateObject(addr) // attention: if StateObject's lightCopy, its storage is only a part of the full storage, + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - if s.parallel.isSlotDB { - if s.parallel.baseTxIndex+1 == s.txIndex { - // we check if state is unchanged - // only when current transaction is the next transaction to be committed - // fixme: there is a bug, block: 14,962,284, - // stateObject is in dirty (light copy), but the key is in mainStateDB - // stateObject dirty -> committed, will skip mainStateDB dirty - if s.GetState(addr, key) == value { - log.Debug("Skip set same state", "baseTxIndex", s.parallel.baseTxIndex, - "txIndex", s.txIndex, "addr", addr, - "key", key, "value", value) - return - } - } - - if s.parallel.kvChangesInSlot[addr] == nil { - s.parallel.kvChangesInSlot[addr] = make(StateKeys) // make(Storage, defaultNumOfSlots) - } - - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) - newStateObject.SetState(s.db, key, value) - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - return - } - // do State Update - } stateObject.SetState(s.db, key, value) } } @@ -1721,21 +1579,6 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // getStateObject will return a non-nil account after Suicide. func (s *StateDB) Suicide(addr common.Address) bool { var stateObject *StateObject - if s.parallel.isSlotDB { - // 1.Try to get from dirty, it could be suicided inside of contract call - stateObject = s.parallel.dirtiedStateObjectsInSlot[addr] - if stateObject == nil { - // 2.Try to get from uncomfirmed, if deleted return false, since the address does not exist - if obj, ok := s.getStateObjectFromUnconfirmedDB(addr); ok { - stateObject = obj - s.parallel.addrStateReadsInSlot[addr] = !stateObject.deleted // true: exist, false: deleted - if stateObject.deleted { - log.Error("Suicide addr alreay deleted in confirmed DB", "txIndex", s.txIndex, "addr", addr) - return false - } - } - } - } if stateObject == nil { // 3.Try to get from main StateDB stateObject = s.getStateObjectNoSlot(addr) @@ -1753,25 +1596,6 @@ func (s *StateDB) Suicide(addr common.Address) bool { prevbalance: new(big.Int).Set(s.GetBalance(addr)), }) - if s.parallel.isSlotDB { - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - // do copy-on-write for suicide "write" - newStateObject := stateObject.lightCopy(s) - newStateObject.markSuicided() - newStateObject.data.Balance = new(big.Int) - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, - // s.parallel.nonceChangesInSlot[addr] = struct{}{} - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.codeChangesInSlot[addr] = struct{}{} - // s.parallel.kvChangesInSlot[addr] = make(StateKeys) // all key changes are discarded - return true - } - s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.codeChangesInSlot[addr] = struct{}{} - } - stateObject.markSuicided() stateObject.data.Balance = new(big.Int) return true @@ -1909,7 +1733,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *StateObject { // fixme: concurrent not safe, merge could update it... // return obj //} - obj := newObject(s, s.isParallel, addr, *data) + obj := newObject(s, s, s.isParallel, addr, *data) s.SetStateObject(obj) return obj } @@ -1982,7 +1806,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj *StateObject) { } } - newobj = newObject(s, s.isParallel, addr, Account{}) + newobj = newObject(s, s, s.isParallel, addr, Account{}) newobj.setNonce(0) // sets the object to dirty if prev == nil { s.journal.append(createObjectChange{account: &addr}) @@ -2288,12 +2112,12 @@ func (s *StateDB) SlotDBPutSyncPool() { } */ // CopyForSlot copy all the basic fields, initialize the memory ones -func (s *StateDB) CopyForSlot() *StateDB { +func (s *StateDB) CopyForSlot() *ParallelStateDB { parallel := ParallelState{ // use base(dispatcher) slot db's stateObjects. // It is a SyncMap, only readable to slot, not writable stateObjects: s.parallel.stateObjects, - unconfirmedDBInShot: make(map[int]*StateDB, 100), + unconfirmedDBInShot: make(map[int]*ParallelStateDB, 100), codeReadsInSlot: make(map[common.Address][]byte, 10), // addressStructPool.Get().(map[common.Address]struct{}), codeHashReadsInSlot: make(map[common.Address]common.Hash), @@ -2311,22 +2135,23 @@ func (s *StateDB) CopyForSlot() *StateDB { isSlotDB: true, dirtiedStateObjectsInSlot: make(map[common.Address]*StateObject), // stateObjectsPool.Get().(map[common.Address]*StateObject), } - state := &StateDB{ - db: s.db, - trie: s.db.CopyTrie(s.trie), - stateObjects: make(map[common.Address]*StateObject), // replaced by parallel.stateObjects in parallel mode - stateObjectsPending: make(map[common.Address]struct{}), // addressStructPool.Get().(map[common.Address]struct{}), - stateObjectsDirty: make(map[common.Address]struct{}), //addressStructPool.Get().(map[common.Address]struct{}), - refund: s.refund, // should be 0 - logs: make(map[common.Hash][]*types.Log, defaultNumOfSlots), // logsPool.Get().(map[common.Hash][]*types.Log), - logSize: 0, - preimages: make(map[common.Hash][]byte, len(s.preimages)), - journal: newJournal(), // journalPool.Get().(*journal), - hasher: crypto.NewKeccakState(), - isParallel: true, - parallel: parallel, + state := &ParallelStateDB{ + StateDB{ + db: s.db, + trie: s.db.CopyTrie(s.trie), + stateObjects: make(map[common.Address]*StateObject), // replaced by parallel.stateObjects in parallel mode + stateObjectsPending: make(map[common.Address]struct{}), // addressStructPool.Get().(map[common.Address]struct{}), + stateObjectsDirty: make(map[common.Address]struct{}), //addressStructPool.Get().(map[common.Address]struct{}), + refund: s.refund, // should be 0 + logs: make(map[common.Hash][]*types.Log, defaultNumOfSlots), // logsPool.Get().(map[common.Hash][]*types.Log), + logSize: 0, + preimages: make(map[common.Hash][]byte, len(s.preimages)), + journal: newJournal(), // journalPool.Get().(*journal), + hasher: crypto.NewKeccakState(), + isParallel: true, + parallel: parallel, + }, } - for hash, preimage := range s.preimages { state.preimages[hash] = preimage } @@ -2362,6 +2187,7 @@ func (s *StateDB) CopyForSlot() *StateDB { // disable it in parallel slot // state.prefetcher = s.prefetcher } + return state } @@ -3101,3 +2927,748 @@ func (s *StateDB) GetDirtyAccounts() []common.Address { func (s *StateDB) GetStorage(address common.Address) *sync.Map { return s.storagePool.getStorage(address) } + +type ParallelStateDB struct { + StateDB +} + +// Exist reports whether the given account address exists in the state. +// Notably this also returns true for suicided accounts. +func (s *ParallelStateDB) Exist(addr common.Address) bool { + if s.parallel.isSlotDB { + // 1.Try to get from dirty + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // dirty object should not be deleted, since deleted is only flagged on finalise + // and if it is suicided in contract call, suicide is taken as exist until it is finalised + // todo: add a check here, to be removed later + if obj.deleted || obj.suicided { + log.Error("Exist in dirty, but marked as deleted or suicided", + "txIndex", s.txIndex, "baseTxIndex:", s.parallel.baseTxIndex) + } + return true + } + // 2.Try to get from uncomfirmed & main DB + // 2.1 Already read before + if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { + return exist + } + // 2.2 Try to get from unconfirmed DB if exist + if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { + s.parallel.addrStateReadsInSlot[addr] = exist // update and cache + return exist + } + } + // 3.Try to get from main StateDB + exist := s.getStateObjectNoSlot(addr) != nil + if s.parallel.isSlotDB { + s.parallel.addrStateReadsInSlot[addr] = exist // update and cache + } + return exist +} + +// Empty returns whether the state object is either non-existent +// or empty according to the EIP161 specification (balance = nonce = code = 0) +func (s *ParallelStateDB) Empty(addr common.Address) bool { + if s.parallel.isSlotDB { + // 1.Try to get from dirty + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // dirty object is light copied and fixup on need, + // empty could be wrong, except it is created with this TX + if _, ok := s.parallel.addrStateChangesInSlot[addr]; ok { + return obj.empty() + } + // so we have to check it manually + // empty means: Nonce == 0 && Balance == 0 && CodeHash == emptyCodeHash + if s.GetBalance(addr).Sign() != 0 { // check balance first, since it is most likely not zero + return false + } + if s.GetNonce(addr) != 0 { + return false + } + codeHash := s.GetCodeHash(addr) + return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty + } + // 2.Try to get from uncomfirmed & main DB + // 2.1 Already read before + if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { + // exist means not empty + return !exist + } + // 2.2 Try to get from unconfirmed DB if exist + if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { + s.parallel.addrStateReadsInSlot[addr] = exist // update and cache + return !exist + } + } + + so := s.getStateObjectNoSlot(addr) + empty := (so == nil || so.empty()) + if s.parallel.isSlotDB { + s.parallel.addrStateReadsInSlot[addr] = !empty // update and cache + } + return empty +} + +// GetBalance retrieves the balance from the given address or 0 if object not found +// GetFrom the dirty list => from unconfirmed DB => get from main stateDB +func (s *ParallelStateDB) GetBalance(addr common.Address) *big.Int { + if s.parallel.isSlotDB { + if addr == s.parallel.systemAddress { + s.parallel.systemAddressOpsCount++ + } + // 1.Try to get from dirty + if _, ok := s.parallel.balanceChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on balance fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup balance based on unconfirmed DB or main DB + return obj.Balance() + } + } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if balance, ok := s.parallel.balanceReadsInSlot[addr]; ok { + return balance + } + // 2.2 Try to get from unconfirmed DB if exist + if balance := s.getBalanceFromUnconfirmedDB(addr); balance != nil { + s.parallel.balanceReadsInSlot[addr] = balance + return balance + } + } + // 3. Try to get from main StateObejct + balance := common.Big0 + stateObject := s.getStateObjectNoSlot(addr) + if stateObject != nil { + balance = stateObject.Balance() + } + if s.parallel.isSlotDB { + s.parallel.balanceReadsInSlot[addr] = balance + } + return balance +} + +func (s *ParallelStateDB) GetNonce(addr common.Address) uint64 { + if s.parallel.isSlotDB { + // 1.Try to get from dirty + if _, ok := s.parallel.nonceChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on nonce fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup nonce based on unconfirmed DB or main DB + return obj.Nonce() + } + } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if nonce, ok := s.parallel.nonceReadsInSlot[addr]; ok { + return nonce + } + // 2.2 Try to get from unconfirmed DB if exist + if nonce, ok := s.getNonceFromUnconfirmedDB(addr); ok { + s.parallel.nonceReadsInSlot[addr] = nonce + return nonce + } + } + // 3.Try to get from main StateDB + var nonce uint64 = 0 + stateObject := s.getStateObjectNoSlot(addr) + if stateObject != nil { + nonce = stateObject.Nonce() + } + if s.parallel.isSlotDB { + s.parallel.nonceReadsInSlot[addr] = nonce + } + + return nonce +} + +func (s *ParallelStateDB) GetCode(addr common.Address) []byte { + if s.parallel.isSlotDB { + // 1.Try to get from dirty + if _, ok := s.parallel.codeChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on code fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup code based on unconfirmed DB or main DB + code := obj.Code(s.db) + return code + } + } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if code, ok := s.parallel.codeReadsInSlot[addr]; ok { + return code + } + // 2.2 Try to get from unconfirmed DB if exist + if code, ok := s.getCodeFromUnconfirmedDB(addr); ok { + s.parallel.codeReadsInSlot[addr] = code + return code + } + } + + // 3. Try to get from main StateObejct + stateObject := s.getStateObjectNoSlot(addr) + var code []byte + if stateObject != nil { + code = stateObject.Code(s.db) + } + if s.parallel.isSlotDB { + s.parallel.codeReadsInSlot[addr] = code + } + return code +} + +func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { + if s.parallel.isSlotDB { + // 1.Try to get from dirty + if _, ok := s.parallel.codeChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on code fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup code based on unconfirmed DB or main DB + return obj.CodeSize(s.db) + } + } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if code, ok := s.parallel.codeReadsInSlot[addr]; ok { + return len(code) // len(nil) is 0 too + } + // 2.2 Try to get from unconfirmed DB if exist + if code, ok := s.getCodeFromUnconfirmedDB(addr); ok { + s.parallel.codeReadsInSlot[addr] = code + return len(code) // len(nil) is 0 too + } + } + + // 3. Try to get from main StateObejct + var codeSize int = 0 + var code []byte + stateObject := s.getStateObjectNoSlot(addr) + + if stateObject != nil { + code = stateObject.Code(s.db) + codeSize = stateObject.CodeSize(s.db) + } + if s.parallel.isSlotDB { + s.parallel.codeReadsInSlot[addr] = code + } + return codeSize +} + +// return value of GetCodeHash: +// - common.Hash{}: the address does not exist +// - emptyCodeHash: the address exist, but code is empty +// - others: the address exist, and code is not empty +func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { + if s.parallel.isSlotDB { + // 1.Try to get from dirty + if _, ok := s.parallel.codeChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on code fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup balance based on unconfirmed DB or main DB + return common.BytesToHash(obj.CodeHash()) + } + } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if codeHash, ok := s.parallel.codeHashReadsInSlot[addr]; ok { + return codeHash + } + // 2.2 Try to get from unconfirmed DB if exist + if codeHash, ok := s.getCodeHashFromUnconfirmedDB(addr); ok { + s.parallel.codeHashReadsInSlot[addr] = codeHash + return codeHash + } + } + + // 3. Try to get from main StateObejct + stateObject := s.getStateObjectNoSlot(addr) + codeHash := common.Hash{} + if stateObject != nil { + codeHash = common.BytesToHash(stateObject.CodeHash()) + } + if s.parallel.isSlotDB { + s.parallel.codeHashReadsInSlot[addr] = codeHash + } + return codeHash +} + +// GetState retrieves a value from the given account's storage trie. +// For parallel mode wih, get from the state in order: +// -> self dirty, both Slot & MainProcessor +// -> pending of self: Slot on merge +// -> pending of unconfirmed DB +// -> pending of main StateDB +// -> origin +func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + if s.parallel.isSlotDB { + + // 1.Try to get from dirty + if exist, ok := s.parallel.addrStateChangesInSlot[addr]; ok { + if !exist { + return common.Hash{} + } + obj := s.parallel.dirtiedStateObjectsInSlot[addr] // addr must exist in dirtiedStateObjectsInSlot + return obj.GetState(s.db, hash) + } + if keys, ok := s.parallel.kvChangesInSlot[addr]; ok { + if _, ok := keys[hash]; ok { + obj := s.parallel.dirtiedStateObjectsInSlot[addr] // addr must exist in dirtiedStateObjectsInSlot + return obj.GetState(s.db, hash) + } + } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { + if val, ok := storage.GetValue(hash); ok { + return val + } + } + // 2.2 Try to get from unconfirmed DB if exist + if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) + } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + return val + } + } + + // 3.Get from main StateDB + stateObject := s.getStateObjectNoSlot(addr) + val := common.Hash{} + if stateObject != nil { + val = stateObject.GetState(s.db, hash) + } + if s.parallel.isSlotDB { + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) + } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + } + return val +} + +// GetCommittedState retrieves a value from the given account's committed storage trie. +func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + if s.parallel.isSlotDB { + // 1.No need to get from pending of itself even on merge, since stateobject in SlotDB won't do finalise + // 2.Try to get from uncomfirmed DB or main DB + // KVs in unconfirmed DB can be seen as pending storage + // KVs in main DB are merged from SlotDB and has done finalise() on merge, can be seen as pending storage too. + // 2.1 Already read before + if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { + if val, ok := storage.GetValue(hash); ok { + return val + } + } + // 2.2 Try to get from unconfirmed DB if exist + if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) + } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + return val + } + } + // 3. Try to get from main DB + stateObject := s.getStateObjectNoSlot(addr) + val := common.Hash{} + if stateObject != nil { + val = stateObject.GetCommittedState(s.db, hash) + } + if s.parallel.isSlotDB { + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) + } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + } + return val +} + +func (s *ParallelStateDB) HasSuicided(addr common.Address) bool { + if s.parallel.isSlotDB { + // 1.Try to get from dirty + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return obj.suicided + } + // 2.Try to get from uncomfirmed + if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { + return !exist + } + } + stateObject := s.getStateObjectNoSlot(addr) + if stateObject != nil { + return stateObject.suicided + } + return false +} + +// AddBalance adds amount to the account associated with addr. +func (s *ParallelStateDB) AddBalance(addr common.Address, amount *big.Int) { + // if s.parallel.isSlotDB { + // add balance will perform a read operation first + // s.parallel.balanceReadsInSlot[addr] = struct{}{} // fixme: to make the the balance valid, since unconfirmed would refer it. + // if amount.Sign() == 0 { + // if amount == 0, no balance change, but there is still an empty check. + // take this empty check as addr state read(create, suicide, empty delete) + // s.parallel.addrStateReadsInSlot[addr] = struct{}{} + // } + // } + + stateObject := s.GetOrNewStateObject(addr) + if stateObject != nil { + if s.parallel.isSlotDB { + if addr == s.parallel.systemAddress { + s.parallel.systemAddressOpsCount++ + } + // if amount.Sign() != 0 { // todo: to reenable it + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) // light copy from main DB + // do balance fixup from the confirmed DB, it could be more reliable than main DB + balance := s.GetBalance(addr) + newStateObject.setBalance(balance) + // s.parallel.balanceReadsInSlot[addr] = newStateObject.Balance() // could read from main DB or unconfirmed DB + newStateObject.AddBalance(amount) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + s.parallel.balanceChangesInSlot[addr] = struct{}{} + return + } + // already dirty, make sure the balance if fixed up + // if stateObject.Balance() + if addr != s.parallel.systemAddress { + if stateObject.Balance().Cmp(s.GetBalance(addr)) != 0 { + log.Warn("AddBalance in dirty, but balance is incorrect", "txIndex", s.txIndex, "addr", addr, + "stateObject.Balance()", stateObject.Balance(), "s.GetBalance(addr)", s.GetBalance(addr)) + stateObject.setBalance(s.GetBalance(addr)) + } + } + } + stateObject.AddBalance(amount) + if s.parallel.isSlotDB { + s.parallel.balanceChangesInSlot[addr] = struct{}{} + } + } +} + +// SubBalance subtracts amount from the account associated with addr. +func (s *ParallelStateDB) SubBalance(addr common.Address, amount *big.Int) { + // if s.parallel.isSlotDB { + // if amount.Sign() != 0 { + // unlike add, sub 0 balance will not touch empty object + // s.parallel.balanceReadsInSlot[addr] = struct{}{} + // } + // } + stateObject := s.GetOrNewStateObject(addr) + if stateObject != nil { + if s.parallel.isSlotDB { + if addr == s.parallel.systemAddress { + s.parallel.systemAddressOpsCount++ + } + + // if amount.Sign() != 0 { // todo: to reenable it + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) // light copy from main DB + // do balance fixup from the confirmed DB, it could be more reliable than main DB + balance := s.GetBalance(addr) + newStateObject.setBalance(balance) + // s.parallel.balanceReadsInSlot[addr] = newStateObject.Balance() + newStateObject.SubBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return + } + // already dirty, make sure the balance if fixed + // if stateObject.Balance() + if addr != s.parallel.systemAddress { + if stateObject.Balance().Cmp(s.GetBalance(addr)) != 0 { + log.Warn("SubBalance in dirty, but balance is incorrect", "txIndex", s.txIndex, "addr", addr, + "stateObject.Balance()", stateObject.Balance(), "s.GetBalance(addr)", s.GetBalance(addr)) + stateObject.setBalance(s.GetBalance(addr)) + } + } + } + stateObject.SubBalance(amount) + if s.parallel.isSlotDB { + s.parallel.balanceChangesInSlot[addr] = struct{}{} + } + + } +} + +func (s *ParallelStateDB) SetBalance(addr common.Address, amount *big.Int) { + stateObject := s.GetOrNewStateObject(addr) + if stateObject != nil { + if s.parallel.isSlotDB { + if addr == s.parallel.systemAddress { + s.parallel.systemAddressOpsCount++ + } + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) + // update balance for revert, in case child contract is revertted, + // it should revert to the previous balance + balance := s.GetBalance(addr) + newStateObject.setBalance(balance) + newStateObject.SetBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return + } + + } + stateObject.SetBalance(amount) + if s.parallel.isSlotDB { + s.parallel.balanceChangesInSlot[addr] = struct{}{} + } + } +} + +func (s *ParallelStateDB) SetNonce(addr common.Address, nonce uint64) { + stateObject := s.GetOrNewStateObject(addr) + if stateObject != nil { + if s.parallel.isSlotDB { + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) + noncePre := s.GetNonce(addr) + newStateObject.setNonce(noncePre) // nonce fixup + newStateObject.SetNonce(nonce) + s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return + } + noncePre := s.GetNonce(addr) + stateObject.setNonce(noncePre) // nonce fixup + } + stateObject.SetNonce(nonce) + if s.parallel.isSlotDB { + s.parallel.nonceChangesInSlot[addr] = struct{}{} + } + } +} + +func (s *ParallelStateDB) SetCode(addr common.Address, code []byte) { + stateObject := s.GetOrNewStateObject(addr) + if stateObject != nil { + codeHash := crypto.Keccak256Hash(code) + if s.parallel.isSlotDB { + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) + codePre := s.GetCode(addr) // code fixup + codeHashPre := crypto.Keccak256Hash(codePre) + newStateObject.setCode(codeHashPre, codePre) + + newStateObject.SetCode(codeHash, code) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + s.parallel.codeChangesInSlot[addr] = struct{}{} + return + } + codePre := s.GetCode(addr) // code fixup + codeHashPre := crypto.Keccak256Hash(codePre) + stateObject.setCode(codeHashPre, codePre) + } + stateObject.SetCode(codeHash, code) + if s.parallel.isSlotDB { + s.parallel.codeChangesInSlot[addr] = struct{}{} + } + } +} + +func (s *ParallelStateDB) SetState(addr common.Address, key, value common.Hash) { + stateObject := s.GetOrNewStateObject(addr) // attention: if StateObject's lightCopy, its storage is only a part of the full storage, + if stateObject != nil { + if s.parallel.isSlotDB { + if s.parallel.baseTxIndex+1 == s.txIndex { + // we check if state is unchanged + // only when current transaction is the next transaction to be committed + // fixme: there is a bug, block: 14,962,284, + // stateObject is in dirty (light copy), but the key is in mainStateDB + // stateObject dirty -> committed, will skip mainStateDB dirty + if s.GetState(addr, key) == value { + log.Debug("Skip set same state", "baseTxIndex", s.parallel.baseTxIndex, + "txIndex", s.txIndex, "addr", addr, + "key", key, "value", value) + return + } + } + + if s.parallel.kvChangesInSlot[addr] == nil { + s.parallel.kvChangesInSlot[addr] = make(StateKeys) // make(Storage, defaultNumOfSlots) + } + + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) + newStateObject.SetState(s.db, key, value) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return + } + // do State Update + } + stateObject.SetState(s.db, key, value) + } +} + +// Suicide marks the given account as suicided. +// This clears the account balance. +// +// The account's state object is still available until the state is committed, +// getStateObject will return a non-nil account after Suicide. +func (s *ParallelStateDB) Suicide(addr common.Address) bool { + var stateObject *StateObject + if s.parallel.isSlotDB { + // 1.Try to get from dirty, it could be suicided inside of contract call + stateObject = s.parallel.dirtiedStateObjectsInSlot[addr] + if stateObject == nil { + // 2.Try to get from uncomfirmed, if deleted return false, since the address does not exist + if obj, ok := s.getStateObjectFromUnconfirmedDB(addr); ok { + stateObject = obj + s.parallel.addrStateReadsInSlot[addr] = !stateObject.deleted // true: exist, false: deleted + if stateObject.deleted { + log.Error("Suicide addr alreay deleted in confirmed DB", "txIndex", s.txIndex, "addr", addr) + return false + } + } + } + } + if stateObject == nil { + // 3.Try to get from main StateDB + stateObject = s.getStateObjectNoSlot(addr) + if stateObject == nil { + s.parallel.addrStateReadsInSlot[addr] = false // true: exist, false: deleted + log.Error("Suicide addr not exist", "txIndex", s.txIndex, "addr", addr) + return false + } + s.parallel.addrStateReadsInSlot[addr] = true // true: exist, false: deleted + } + + s.journal.append(suicideChange{ + account: &addr, + prev: stateObject.suicided, // todo: must be false? + prevbalance: new(big.Int).Set(s.GetBalance(addr)), + }) + + if s.parallel.isSlotDB { + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + // do copy-on-write for suicide "write" + newStateObject := stateObject.lightCopy(s) + newStateObject.markSuicided() + newStateObject.data.Balance = new(big.Int) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, + // s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} + // s.parallel.kvChangesInSlot[addr] = make(StateKeys) // all key changes are discarded + return true + } + s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} + } + + stateObject.markSuicided() + stateObject.data.Balance = new(big.Int) + return true +} + +// GetOrNewStateObject retrieves a state object or create a new state object if nil. +// dirtyInSlot -> Unconfirmed DB -> main DB -> snapshot, no? create one +func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *StateObject { + var stateObject *StateObject = nil + exist := true + if s.parallel.isSlotDB { + if stateObject, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return stateObject + } + stateObject, _ = s.getStateObjectFromUnconfirmedDB(addr) + } + + if stateObject == nil { + stateObject = s.getStateObjectNoSlot(addr) + } + if stateObject == nil || stateObject.deleted || stateObject.suicided { + stateObject = s.createObject(addr) + exist = false + } + + if s.parallel.isSlotDB { + s.parallel.addrStateReadsInSlot[addr] = exist // true: exist, false: not exist + } + 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. + +// prev is used for CreateAccount to get its balance +// Parallel mode: +// if prev in dirty: revert is ok +// if prev in unconfirmed DB: addr state read record, revert should not put it back +// if prev in main DB: addr state read record, revert should not put it back +// if pre no exist: addr state read record, + +// `prev` is used to handle revert, to recover with the `prev` object +// In Parallel mode, we only need to recover to `prev` in SlotDB, +// a.if it is not in SlotDB, `revert` will remove it from the SlotDB +// b.if it is exist in SlotDB, `revert` will recover to the `prev` in SlotDB +// c.as `snapDestructs` it is the same +func (s *ParallelStateDB) createObject(addr common.Address) (newobj *StateObject) { + var prev *StateObject = nil + if s.parallel.isSlotDB { + // do not get from unconfirmed DB, since it will has problem on revert + prev = s.parallel.dirtiedStateObjectsInSlot[addr] + } else { + 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.address] // fixme, record the snapshot read for create Account + if s.parallel.isSlotDB { + s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct + } + if !prevdestruct { + // To destroy the previous trie node first and update the trie tree + // with the new object on block commit. + s.snapDestructs[prev.address] = struct{}{} + + } + } + newobj = newObject(&s.StateDB, s, s.isParallel, addr, Account{}) + newobj.setNonce(0) // sets the object to dirty + if prev == nil { + s.journal.append(createObjectChange{account: &addr}) + } else { + s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) + } + + if s.parallel.isSlotDB { + // s.parallel.dirtiedStateObjectsInSlot[addr] = newobj // would change the bahavior of AddBalance... + s.parallel.addrStateChangesInSlot[addr] = true // the object sis created + s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} + // notice: all the KVs are cleared if any + s.parallel.kvChangesInSlot[addr] = make(StateKeys) + } else { + s.SetStateObject(newobj) + } + return newobj +} + +// CreateAccount explicitly creates a state object. If a state object with the address +// already exists the balance is carried over to the new account. +// +// CreateAccount is called during the EVM CREATE operation. The situation might arise that +// a contract does the following: +// +// 1. sends funds to sha(account ++ (nonce + 1)) +// 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 (s *ParallelStateDB) CreateAccount(addr common.Address) { + // no matter it is got from dirty, unconfirmed or main DB + // if addr not exist, preBalance will be common.Big0, it is same as new(big.Int) which + // is the value newObject(), + preBalance := s.GetBalance(addr) // parallel balance read will be recorded inside of GetBalance + newObj := s.createObject(addr) + newObj.setBalance(new(big.Int).Set(preBalance)) // new big.Int for newObj +} diff --git a/core/state_processor.go b/core/state_processor.go index 9e6da7fee1..ae2f1000ee 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -77,7 +77,7 @@ type ParallelStateProcessor struct { // txReqAccountSorted map[common.Address][]*ParallelTxRequest // fixme: *ParallelTxRequest => ParallelTxRequest? slotState []*SlotState // idle, or pending messages mergedTxIndex int // the latest finalized tx index - slotDBsToRelease []*state.StateDB + slotDBsToRelease []*state.ParallelStateDB debugErrorRedoNum int debugConflictRedoNum int } @@ -398,8 +398,8 @@ func (p *LightStateProcessor) LightProcess(diffLayer *types.DiffLayer, block *ty type SlotState struct { pendingTxReqChan chan struct{} pendingConfirmChan chan *ParallelTxResult - pendingTxReqList []*ParallelTxRequest // maintained by dispatcher for dispatch policy - slotdbChan chan *state.StateDB // dispatch will create and send this slotDB to slot + pendingTxReqList []*ParallelTxRequest // maintained by dispatcher for dispatch policy + slotdbChan chan *state.ParallelStateDB // dispatch will create and send this slotDB to slot // txReqUnits []*ParallelDispatchUnit // only dispatch can accesssd unconfirmedStateDBs *sync.Map // [int]*state.StateDB // fixme: concurrent safe, not use sync.Map? } @@ -411,7 +411,7 @@ type ParallelTxResult struct { err error // to describe error message? txReq *ParallelTxRequest receipt *types.Receipt - slotDB *state.StateDB // if updated, it is not equal to txReq.slotDB + slotDB *state.ParallelStateDB // if updated, it is not equal to txReq.slotDB gpSlot *GasPool evm *vm.EVM result *ExecutionResult @@ -420,7 +420,7 @@ type ParallelTxResult struct { type ParallelTxRequest struct { txIndex int tx *types.Transaction - slotDB *state.StateDB + slotDB *state.ParallelStateDB gasLimit uint64 msg types.Message block *types.Block @@ -441,7 +441,7 @@ func (p *ParallelStateProcessor) init() { for i := 0; i < p.parallelNum; i++ { p.slotState[i] = &SlotState{ - slotdbChan: make(chan *state.StateDB, 1), + slotdbChan: make(chan *state.ParallelStateDB, 1), pendingTxReqChan: make(chan struct{}, 1), pendingConfirmChan: make(chan *ParallelTxResult, p.queueSize), } @@ -894,7 +894,7 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { statedb.PrepareForParallel() - p.slotDBsToRelease = make([]*state.StateDB, 0, txNum) + p.slotDBsToRelease = make([]*state.ParallelStateDB, 0, txNum) /* stateDBsToRelease := p.slotDBsToRelease go func() { @@ -1150,7 +1150,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon return receipt, err } -func applyTransactionStageExecution(msg types.Message, gp *GasPool, statedb *state.StateDB, evm *vm.EVM) (*vm.EVM, *ExecutionResult, error) { +func applyTransactionStageExecution(msg types.Message, gp *GasPool, statedb *state.ParallelStateDB, evm *vm.EVM) (*vm.EVM, *ExecutionResult, error) { // Create a new context to be used in the EVM environment. txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) @@ -1164,7 +1164,7 @@ func applyTransactionStageExecution(msg types.Message, gp *GasPool, statedb *sta return evm, result, err } -func applyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg types.Message, config *params.ChainConfig, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, receiptProcessors ...ReceiptProcessor) (*types.Receipt, error) { +func applyTransactionStageFinalization(evm *vm.EVM, result *ExecutionResult, msg types.Message, config *params.ChainConfig, statedb *state.ParallelStateDB, header *types.Header, tx *types.Transaction, usedGas *uint64, receiptProcessors ...ReceiptProcessor) (*types.Receipt, error) { // Update the state with pending changes. var root []byte if config.IsByzantium(header.Number) { diff --git a/core/state_transition.go b/core/state_transition.go index e71b980752..3be5b59e81 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -53,7 +53,7 @@ type StateTransition struct { initialGas uint64 value *big.Int data []byte - state vm.StateDB + state vm.StateDBer evm *vm.EVM } diff --git a/core/vm/evm.go b/core/vm/evm.go index 53e2e8797b..e76e51463d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -41,9 +41,9 @@ var EvmPool = sync.Pool{ type ( // CanTransferFunc is the signature of a transfer guard function - CanTransferFunc func(StateDB, common.Address, *big.Int) bool + CanTransferFunc func(StateDBer, common.Address, *big.Int) bool // TransferFunc is the signature of a transfer function - TransferFunc func(StateDB, common.Address, common.Address, *big.Int) + TransferFunc func(StateDBer, common.Address, common.Address, *big.Int) // GetHashFunc returns the n'th block hash in the blockchain // and is used by the BLOCKHASH EVM op code. GetHashFunc func(uint64) common.Hash @@ -124,7 +124,7 @@ type EVM struct { Context BlockContext TxContext // StateDB gives access to the underlying state - StateDB StateDB + StateDB StateDBer // Depth is the current call stack depth int @@ -150,7 +150,7 @@ type EVM struct { // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. -func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { +func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDBer, chainConfig *params.ChainConfig, vmConfig Config) *EVM { evm := EvmPool.Get().(*EVM) evm.Context = blockCtx evm.TxContext = txCtx @@ -189,7 +189,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig // Reset resets the EVM with a new transaction context.Reset // This is not threadsafe and should only be done very cautiously. -func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { +func (evm *EVM) Reset(txCtx TxContext, statedb StateDBer) { evm.TxContext = txCtx evm.StateDB = statedb } diff --git a/core/vm/interface.go b/core/vm/interface.go index ad9b05d666..c9f693ef8a 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -23,8 +23,9 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// StateDB is an EVM database for full state querying. -type StateDB interface { +// StateDBer is an EVM database for full state querying. +// It is used as the interface to StateDB right now. +type StateDBer interface { CreateAccount(common.Address) SubBalance(common.Address, *big.Int) diff --git a/eth/tracers/js/tracer.go b/eth/tracers/js/tracer.go index b8e035e6f3..b7ba01df71 100644 --- a/eth/tracers/js/tracer.go +++ b/eth/tracers/js/tracer.go @@ -209,7 +209,7 @@ func (sw *stackWrapper) pushObject(vm *duktape.Context) { // dbWrapper provides a JavaScript wrapper around vm.Database. type dbWrapper struct { - db vm.StateDB + db vm.StateDBer } // pushObject assembles a JSVM object wrapping a swappable database and pushes it From c40b07b405405d70c926c9ca384fb8fc6e5298b3 Mon Sep 17 00:00:00 2001 From: setunapo Date: Thu, 28 Apr 2022 16:29:43 +0800 Subject: [PATCH 02/12] remove isSlotDB in StateDB --- core/state/statedb.go | 195 ------------------------------------------ 1 file changed, 195 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 2bd0c44884..bdfee784ba 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -985,71 +985,20 @@ func (s *StateDB) Empty(addr common.Address) bool { // GetBalance retrieves the balance from the given address or 0 if object not found // GetFrom the dirty list => from unconfirmed DB => get from main stateDB func (s *StateDB) GetBalance(addr common.Address) *big.Int { - if s.parallel.isSlotDB { - if addr == s.parallel.systemAddress { - s.parallel.systemAddressOpsCount++ - } - // 1.Try to get from dirty - if _, ok := s.parallel.balanceChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on balance fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup balance based on unconfirmed DB or main DB - return obj.Balance() - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if balance, ok := s.parallel.balanceReadsInSlot[addr]; ok { - return balance - } - // 2.2 Try to get from unconfirmed DB if exist - if balance := s.getBalanceFromUnconfirmedDB(addr); balance != nil { - s.parallel.balanceReadsInSlot[addr] = balance - return balance - } - } - // 3. Try to get from main StateObejct balance := common.Big0 stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { balance = stateObject.Balance() } - if s.parallel.isSlotDB { - s.parallel.balanceReadsInSlot[addr] = balance - } return balance } func (s *StateDB) GetNonce(addr common.Address) uint64 { - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if _, ok := s.parallel.nonceChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on nonce fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup nonce based on unconfirmed DB or main DB - return obj.Nonce() - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if nonce, ok := s.parallel.nonceReadsInSlot[addr]; ok { - return nonce - } - // 2.2 Try to get from unconfirmed DB if exist - if nonce, ok := s.getNonceFromUnconfirmedDB(addr); ok { - s.parallel.nonceReadsInSlot[addr] = nonce - return nonce - } - } - // 3.Try to get from main StateDB var nonce uint64 = 0 stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { nonce = stateObject.Nonce() } - if s.parallel.isSlotDB { - s.parallel.nonceReadsInSlot[addr] = nonce - } return nonce } @@ -1198,74 +1147,21 @@ func (s *StateDB) NeedsRedo() bool { } func (s *StateDB) GetCode(addr common.Address) []byte { - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if _, ok := s.parallel.codeChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on code fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup code based on unconfirmed DB or main DB - code := obj.Code(s.db) - return code - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if code, ok := s.parallel.codeReadsInSlot[addr]; ok { - return code - } - // 2.2 Try to get from unconfirmed DB if exist - if code, ok := s.getCodeFromUnconfirmedDB(addr); ok { - s.parallel.codeReadsInSlot[addr] = code - return code - } - } - - // 3. Try to get from main StateObejct stateObject := s.getStateObjectNoSlot(addr) var code []byte if stateObject != nil { code = stateObject.Code(s.db) } - if s.parallel.isSlotDB { - s.parallel.codeReadsInSlot[addr] = code - } return code } func (s *StateDB) GetCodeSize(addr common.Address) int { - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if _, ok := s.parallel.codeChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on code fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup code based on unconfirmed DB or main DB - return obj.CodeSize(s.db) - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if code, ok := s.parallel.codeReadsInSlot[addr]; ok { - return len(code) // len(nil) is 0 too - } - // 2.2 Try to get from unconfirmed DB if exist - if code, ok := s.getCodeFromUnconfirmedDB(addr); ok { - s.parallel.codeReadsInSlot[addr] = code - return len(code) // len(nil) is 0 too - } - } - // 3. Try to get from main StateObejct var codeSize int = 0 - var code []byte stateObject := s.getStateObjectNoSlot(addr) - if stateObject != nil { - code = stateObject.Code(s.db) codeSize = stateObject.CodeSize(s.db) } - if s.parallel.isSlotDB { - s.parallel.codeReadsInSlot[addr] = code - } return codeSize } @@ -1274,36 +1170,11 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { // - emptyCodeHash: the address exist, but code is empty // - others: the address exist, and code is not empty func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if _, ok := s.parallel.codeChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on code fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup balance based on unconfirmed DB or main DB - return common.BytesToHash(obj.CodeHash()) - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if codeHash, ok := s.parallel.codeHashReadsInSlot[addr]; ok { - return codeHash - } - // 2.2 Try to get from unconfirmed DB if exist - if codeHash, ok := s.getCodeHashFromUnconfirmedDB(addr); ok { - s.parallel.codeHashReadsInSlot[addr] = codeHash - return codeHash - } - } - - // 3. Try to get from main StateObejct stateObject := s.getStateObjectNoSlot(addr) codeHash := common.Hash{} if stateObject != nil { codeHash = common.BytesToHash(stateObject.CodeHash()) } - if s.parallel.isSlotDB { - s.parallel.codeHashReadsInSlot[addr] = codeHash - } return codeHash } @@ -1315,51 +1186,11 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { // -> pending of main StateDB // -> origin func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { - if s.parallel.isSlotDB { - - // 1.Try to get from dirty - if exist, ok := s.parallel.addrStateChangesInSlot[addr]; ok { - if !exist { - return common.Hash{} - } - obj := s.parallel.dirtiedStateObjectsInSlot[addr] // addr must exist in dirtiedStateObjectsInSlot - return obj.GetState(s.db, hash) - } - if keys, ok := s.parallel.kvChangesInSlot[addr]; ok { - if _, ok := keys[hash]; ok { - obj := s.parallel.dirtiedStateObjectsInSlot[addr] // addr must exist in dirtiedStateObjectsInSlot - return obj.GetState(s.db, hash) - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { - if val, ok := storage.GetValue(hash); ok { - return val - } - } - // 2.2 Try to get from unconfirmed DB if exist - if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { - if s.parallel.kvReadsInSlot[addr] == nil { - s.parallel.kvReadsInSlot[addr] = newStorage(false) - } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache - return val - } - } - - // 3.Get from main StateDB stateObject := s.getStateObjectNoSlot(addr) val := common.Hash{} if stateObject != nil { val = stateObject.GetState(s.db, hash) } - if s.parallel.isSlotDB { - if s.parallel.kvReadsInSlot[addr] == nil { - s.parallel.kvReadsInSlot[addr] = newStorage(false) - } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache - } return val } @@ -1455,16 +1286,6 @@ func (s *StateDB) StorageTrie(addr common.Address) Trie { } func (s *StateDB) HasSuicided(addr common.Address) bool { - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - return obj.suicided - } - // 2.Try to get from uncomfirmed - if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { - return !exist - } - } stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { return stateObject.suicided @@ -1504,16 +1325,6 @@ func (s *StateDB) unconfirmedLightCopy(mainObj *StateObject) *StateObject { // AddBalance adds amount to the account associated with addr. func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { - // if s.parallel.isSlotDB { - // add balance will perform a read operation first - // s.parallel.balanceReadsInSlot[addr] = struct{}{} // fixme: to make the the balance valid, since unconfirmed would refer it. - // if amount.Sign() == 0 { - // if amount == 0, no balance change, but there is still an empty check. - // take this empty check as addr state read(create, suicide, empty delete) - // s.parallel.addrStateReadsInSlot[addr] = struct{}{} - // } - // } - stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { stateObject.AddBalance(amount) @@ -1522,12 +1333,6 @@ func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { // SubBalance subtracts amount from the account associated with addr. func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { - // if s.parallel.isSlotDB { - // if amount.Sign() != 0 { - // unlike add, sub 0 balance will not touch empty object - // s.parallel.balanceReadsInSlot[addr] = struct{}{} - // } - // } stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SubBalance(amount) From b9d76113d3d21b6304e5f4741d3e4fb46a7f5d59 Mon Sep 17 00:00:00 2001 From: setunapo Date: Fri, 29 Apr 2022 08:46:43 +0800 Subject: [PATCH 03/12] Temp: Good, logs --- core/state/state_object.go | 2 +- core/state/statedb.go | 139 +++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/core/state/state_object.go b/core/state/state_object.go index a4a743920b..f7e38ce1f5 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -451,7 +451,7 @@ func (s *StateObject) SetState(db Database, key, value common.Hash) { // this `SetState could be skipped` // d.Finally, the key's value will be `val_2`, while it should be `val_1` // such as: https://bscscan.com/txs?block=2491181 - prev := s.db.GetState(s.address, key) // fixme: if it is for journal, may not necessary, we can remove this change record + prev := s.dbItf.GetState(s.address, key) // fixme: if it is for journal, may not necessary, we can remove this change record if prev == value { return } diff --git a/core/state/statedb.go b/core/state/statedb.go index bdfee784ba..6b23117274 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -908,6 +908,7 @@ func (s *StateDB) getStateObjectFromUnconfirmedDB(addr common.Address) (*StateOb // Exist reports whether the given account address exists in the state. // Notably this also returns true for suicided accounts. func (s *StateDB) Exist(addr common.Address) bool { + log.Debug("StateDB Exist", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) if s.parallel.isSlotDB { // 1.Try to get from dirty if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { @@ -942,6 +943,8 @@ func (s *StateDB) Exist(addr common.Address) bool { // 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 { + log.Debug("StateDB Empty", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + if s.parallel.isSlotDB { // 1.Try to get from dirty if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { @@ -985,6 +988,8 @@ func (s *StateDB) Empty(addr common.Address) bool { // GetBalance retrieves the balance from the given address or 0 if object not found // GetFrom the dirty list => from unconfirmed DB => get from main stateDB func (s *StateDB) GetBalance(addr common.Address) *big.Int { + log.Debug("StateDB GetBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + balance := common.Big0 stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { @@ -994,6 +999,8 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int { } func (s *StateDB) GetNonce(addr common.Address) uint64 { + log.Debug("StateDB GetNonce", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + var nonce uint64 = 0 stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { @@ -1147,6 +1154,8 @@ func (s *StateDB) NeedsRedo() bool { } func (s *StateDB) GetCode(addr common.Address) []byte { + log.Debug("StateDB GetCode", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.getStateObjectNoSlot(addr) var code []byte if stateObject != nil { @@ -1156,6 +1165,8 @@ func (s *StateDB) GetCode(addr common.Address) []byte { } func (s *StateDB) GetCodeSize(addr common.Address) int { + log.Debug("StateDB GetCodeSize", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + // 3. Try to get from main StateObejct var codeSize int = 0 stateObject := s.getStateObjectNoSlot(addr) @@ -1170,6 +1181,8 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { // - emptyCodeHash: the address exist, but code is empty // - others: the address exist, and code is not empty func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { + log.Debug("StateDB GetCodeHash", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.getStateObjectNoSlot(addr) codeHash := common.Hash{} if stateObject != nil { @@ -1186,6 +1199,7 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { // -> pending of main StateDB // -> origin func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + log.Debug("StateDB GetState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) stateObject := s.getStateObjectNoSlot(addr) val := common.Hash{} if stateObject != nil { @@ -1233,6 +1247,8 @@ func (s *StateDB) GetStorageProofByHash(a common.Address, key common.Hash) ([][] // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + log.Debug("StateDB GetCommittedState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + if s.parallel.isSlotDB { // 1.No need to get from pending of itself even on merge, since stateobject in SlotDB won't do finalise // 2.Try to get from uncomfirmed DB or main DB @@ -1325,6 +1341,8 @@ func (s *StateDB) unconfirmedLightCopy(mainObj *StateObject) *StateObject { // AddBalance adds amount to the account associated with addr. func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { + log.Debug("StateDB AddBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { stateObject.AddBalance(amount) @@ -1333,6 +1351,8 @@ func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { // SubBalance subtracts amount from the account associated with addr. func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { + log.Debug("StateDB SubBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SubBalance(amount) @@ -1340,6 +1360,8 @@ func (s *StateDB) SubBalance(addr common.Address, amount *big.Int) { } func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) { + log.Debug("StateDB SetBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SetBalance(amount) @@ -1347,6 +1369,8 @@ func (s *StateDB) SetBalance(addr common.Address, amount *big.Int) { } func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { + log.Debug("StateDB SetNonce", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SetNonce(nonce) @@ -1354,6 +1378,8 @@ func (s *StateDB) SetNonce(addr common.Address, nonce uint64) { } func (s *StateDB) SetCode(addr common.Address, code []byte) { + log.Debug("StateDB SetCode", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { codeHash := crypto.Keccak256Hash(code) @@ -1362,6 +1388,8 @@ func (s *StateDB) SetCode(addr common.Address, code []byte) { } func (s *StateDB) SetState(addr common.Address, key, value common.Hash) { + log.Debug("StateDB SetState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { stateObject.SetState(s.db, key, value) @@ -1383,6 +1411,8 @@ func (s *StateDB) SetStorage(addr common.Address, storage map[common.Hash]common // The account's state object is still available until the state is committed, // getStateObject will return a non-nil account after Suicide. func (s *StateDB) Suicide(addr common.Address) bool { + log.Debug("StateDB Suicide", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + var stateObject *StateObject if stateObject == nil { // 3.Try to get from main StateDB @@ -1412,6 +1442,8 @@ func (s *StateDB) Suicide(addr common.Address) bool { // updateStateObject writes the given object to the trie. func (s *StateDB) updateStateObject(obj *StateObject) { + log.Debug("StateDB updateStateObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + // 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()) @@ -2740,6 +2772,7 @@ type ParallelStateDB struct { // Exist reports whether the given account address exists in the state. // Notably this also returns true for suicided accounts. func (s *ParallelStateDB) Exist(addr common.Address) bool { + log.Debug("ParallelStateDB Exist", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) if s.parallel.isSlotDB { // 1.Try to get from dirty if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { @@ -2774,6 +2807,7 @@ func (s *ParallelStateDB) Exist(addr common.Address) bool { // Empty returns whether the state object is either non-existent // or empty according to the EIP161 specification (balance = nonce = code = 0) func (s *ParallelStateDB) Empty(addr common.Address) bool { + log.Debug("ParallelStateDB Empty", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) if s.parallel.isSlotDB { // 1.Try to get from dirty if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { @@ -2817,6 +2851,7 @@ func (s *ParallelStateDB) Empty(addr common.Address) bool { // GetBalance retrieves the balance from the given address or 0 if object not found // GetFrom the dirty list => from unconfirmed DB => get from main stateDB func (s *ParallelStateDB) GetBalance(addr common.Address) *big.Int { + log.Debug("ParallelStateDB GetBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) if s.parallel.isSlotDB { if addr == s.parallel.systemAddress { s.parallel.systemAddressOpsCount++ @@ -2853,6 +2888,7 @@ func (s *ParallelStateDB) GetBalance(addr common.Address) *big.Int { } func (s *ParallelStateDB) GetNonce(addr common.Address) uint64 { + log.Debug("ParallelStateDB GetNonce", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) if s.parallel.isSlotDB { // 1.Try to get from dirty if _, ok := s.parallel.nonceChangesInSlot[addr]; ok { @@ -2887,6 +2923,7 @@ func (s *ParallelStateDB) GetNonce(addr common.Address) uint64 { } func (s *ParallelStateDB) GetCode(addr common.Address) []byte { + log.Debug("ParallelStateDB GetCode", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) if s.parallel.isSlotDB { // 1.Try to get from dirty if _, ok := s.parallel.codeChangesInSlot[addr]; ok { @@ -2922,6 +2959,8 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { } func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { + log.Debug("ParallelStateDB GetCodeSize", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + if s.parallel.isSlotDB { // 1.Try to get from dirty if _, ok := s.parallel.codeChangesInSlot[addr]; ok { @@ -2963,6 +3002,8 @@ func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { // - emptyCodeHash: the address exist, but code is empty // - others: the address exist, and code is not empty func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { + log.Debug("ParallelStateDB GetCodeHash", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + if s.parallel.isSlotDB { // 1.Try to get from dirty if _, ok := s.parallel.codeChangesInSlot[addr]; ok { @@ -3004,6 +3045,8 @@ func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { // -> pending of main StateDB // -> origin func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { + log.Debug("ParallelStateDB GetState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + if s.parallel.isSlotDB { // 1.Try to get from dirty @@ -3054,6 +3097,7 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { + log.Debug("ParallelStateDB GetCommittedState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) if s.parallel.isSlotDB { // 1.No need to get from pending of itself even on merge, since stateobject in SlotDB won't do finalise // 2.Try to get from uncomfirmed DB or main DB @@ -3090,6 +3134,8 @@ func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Has } func (s *ParallelStateDB) HasSuicided(addr common.Address) bool { + log.Debug("ParallelStateDB HasSuicided", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + if s.parallel.isSlotDB { // 1.Try to get from dirty if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { @@ -3118,6 +3164,7 @@ func (s *ParallelStateDB) AddBalance(addr common.Address, amount *big.Int) { // s.parallel.addrStateReadsInSlot[addr] = struct{}{} // } // } + log.Debug("ParallelStateDB AddBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { @@ -3162,6 +3209,8 @@ func (s *ParallelStateDB) SubBalance(addr common.Address, amount *big.Int) { // s.parallel.balanceReadsInSlot[addr] = struct{}{} // } // } + log.Debug("ParallelStateDB SubBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { if s.parallel.isSlotDB { @@ -3200,6 +3249,8 @@ func (s *ParallelStateDB) SubBalance(addr common.Address, amount *big.Int) { } func (s *ParallelStateDB) SetBalance(addr common.Address, amount *big.Int) { + log.Debug("ParallelStateDB SetBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { if s.parallel.isSlotDB { @@ -3227,6 +3278,8 @@ func (s *ParallelStateDB) SetBalance(addr common.Address, amount *big.Int) { } func (s *ParallelStateDB) SetNonce(addr common.Address, nonce uint64) { + log.Debug("ParallelStateDB SetNonce", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { if s.parallel.isSlotDB { @@ -3250,6 +3303,8 @@ func (s *ParallelStateDB) SetNonce(addr common.Address, nonce uint64) { } func (s *ParallelStateDB) SetCode(addr common.Address, code []byte) { + log.Debug("ParallelStateDB SetCode", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { codeHash := crypto.Keccak256Hash(code) @@ -3277,6 +3332,8 @@ func (s *ParallelStateDB) SetCode(addr common.Address, code []byte) { } func (s *ParallelStateDB) SetState(addr common.Address, key, value common.Hash) { + log.Debug("ParallelStateDB SetState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + stateObject := s.GetOrNewStateObject(addr) // attention: if StateObject's lightCopy, its storage is only a part of the full storage, if stateObject != nil { if s.parallel.isSlotDB { @@ -3316,6 +3373,8 @@ func (s *ParallelStateDB) SetState(addr common.Address, key, value common.Hash) // The account's state object is still available until the state is committed, // getStateObject will return a non-nil account after Suicide. func (s *ParallelStateDB) Suicide(addr common.Address) bool { + log.Debug("ParallelStateDB Suicide", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + var stateObject *StateObject if s.parallel.isSlotDB { // 1.Try to get from dirty, it could be suicided inside of contract call @@ -3376,6 +3435,8 @@ func (s *ParallelStateDB) Suicide(addr common.Address) bool { // GetOrNewStateObject retrieves a state object or create a new state object if nil. // dirtyInSlot -> Unconfirmed DB -> main DB -> snapshot, no? create one func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *StateObject { + log.Debug("ParallelStateDB GetOrNewStateObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + var stateObject *StateObject = nil exist := true if s.parallel.isSlotDB { @@ -3415,6 +3476,8 @@ func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *StateObject // b.if it is exist in SlotDB, `revert` will recover to the `prev` in SlotDB // c.as `snapDestructs` it is the same func (s *ParallelStateDB) createObject(addr common.Address) (newobj *StateObject) { + log.Debug("ParallelStateDB createObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + var prev *StateObject = nil if s.parallel.isSlotDB { // do not get from unconfirmed DB, since it will has problem on revert @@ -3477,3 +3540,79 @@ func (s *ParallelStateDB) CreateAccount(addr common.Address) { newObj := s.createObject(addr) newObj.setBalance(new(big.Int).Set(preBalance)) // new big.Int for newObj } + +// 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 *ParallelStateDB) getDeletedStateObject(addr common.Address) *StateObject { + log.Debug("ParallelStateDB getDeletedStateObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + + // Prefer live objects if any is available + if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { + return obj + } + // 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.HashData(s.hasher, addr.Bytes())); err == nil { + if acc == nil { + return nil + } + data = &Account{ + Nonce: acc.Nonce, + Balance: acc.Balance, + CodeHash: acc.CodeHash, + Root: common.BytesToHash(acc.Root), + } + if len(data.CodeHash) == 0 { + data.CodeHash = emptyCodeHash + } + if data.Root == (common.Hash{}) { + data.Root = emptyRoot + } + } + } + // If snapshot unavailable or reading from it failed, load from the database + if s.snap == nil || err != nil { + if s.trie == nil { + tr, err := s.db.OpenTrie(s.originalRoot) + if err != nil { + s.setError(fmt.Errorf("failed to open trie tree")) + return nil + } + s.trie = tr + } + 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 + // if obj, ok := s.loadStateObj(addr); ok { + // fixme: concurrent not safe, merge could update it... + // return obj + //} + obj := newObject(&s.StateDB, s, s.isParallel, addr, *data) + s.SetStateObject(obj) + return obj +} From a6b499aca45122afee6aef6f589516a4aea81003 Mon Sep 17 00:00:00 2001 From: setunapo Date: Fri, 29 Apr 2022 09:09:53 +0800 Subject: [PATCH 04/12] remove isSlotDB part 1 --- core/state/statedb.go | 165 ------------------------------------------ 1 file changed, 165 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 6b23117274..a15ae91f47 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -909,34 +909,8 @@ func (s *StateDB) getStateObjectFromUnconfirmedDB(addr common.Address) (*StateOb // Notably this also returns true for suicided accounts. func (s *StateDB) Exist(addr common.Address) bool { log.Debug("StateDB Exist", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // dirty object should not be deleted, since deleted is only flagged on finalise - // and if it is suicided in contract call, suicide is taken as exist until it is finalised - // todo: add a check here, to be removed later - if obj.deleted || obj.suicided { - log.Error("Exist in dirty, but marked as deleted or suicided", - "txIndex", s.txIndex, "baseTxIndex:", s.parallel.baseTxIndex) - } - return true - } - // 2.Try to get from uncomfirmed & main DB - // 2.1 Already read before - if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { - return exist - } - // 2.2 Try to get from unconfirmed DB if exist - if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { - s.parallel.addrStateReadsInSlot[addr] = exist // update and cache - return exist - } - } // 3.Try to get from main StateDB exist := s.getStateObjectNoSlot(addr) != nil - if s.parallel.isSlotDB { - s.parallel.addrStateReadsInSlot[addr] = exist // update and cache - } return exist } @@ -944,44 +918,8 @@ func (s *StateDB) Exist(addr common.Address) bool { // or empty according to the EIP161 specification (balance = nonce = code = 0) func (s *StateDB) Empty(addr common.Address) bool { log.Debug("StateDB Empty", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // dirty object is light copied and fixup on need, - // empty could be wrong, except it is created with this TX - if _, ok := s.parallel.addrStateChangesInSlot[addr]; ok { - return obj.empty() - } - // so we have to check it manually - // empty means: Nonce == 0 && Balance == 0 && CodeHash == emptyCodeHash - if s.GetBalance(addr).Sign() != 0 { // check balance first, since it is most likely not zero - return false - } - if s.GetNonce(addr) != 0 { - return false - } - codeHash := s.GetCodeHash(addr) - return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty - } - // 2.Try to get from uncomfirmed & main DB - // 2.1 Already read before - if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { - // exist means not empty - return !exist - } - // 2.2 Try to get from unconfirmed DB if exist - if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { - s.parallel.addrStateReadsInSlot[addr] = exist // update and cache - return !exist - } - } - so := s.getStateObjectNoSlot(addr) empty := (so == nil || so.empty()) - if s.parallel.isSlotDB { - s.parallel.addrStateReadsInSlot[addr] = !empty // update and cache - } return empty } @@ -1248,39 +1186,12 @@ func (s *StateDB) GetStorageProofByHash(a common.Address, key common.Hash) ([][] // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { log.Debug("StateDB GetCommittedState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - if s.parallel.isSlotDB { - // 1.No need to get from pending of itself even on merge, since stateobject in SlotDB won't do finalise - // 2.Try to get from uncomfirmed DB or main DB - // KVs in unconfirmed DB can be seen as pending storage - // KVs in main DB are merged from SlotDB and has done finalise() on merge, can be seen as pending storage too. - // 2.1 Already read before - if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { - if val, ok := storage.GetValue(hash); ok { - return val - } - } - // 2.2 Try to get from unconfirmed DB if exist - if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { - if s.parallel.kvReadsInSlot[addr] == nil { - s.parallel.kvReadsInSlot[addr] = newStorage(false) - } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache - return val - } - } // 3. Try to get from main DB stateObject := s.getStateObjectNoSlot(addr) val := common.Hash{} if stateObject != nil { val = stateObject.GetCommittedState(s.db, hash) } - if s.parallel.isSlotDB { - if s.parallel.kvReadsInSlot[addr] == nil { - s.parallel.kvReadsInSlot[addr] = newStorage(false) - } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache - } return val } @@ -3540,79 +3451,3 @@ func (s *ParallelStateDB) CreateAccount(addr common.Address) { newObj := s.createObject(addr) newObj.setBalance(new(big.Int).Set(preBalance)) // new big.Int for newObj } - -// 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 *ParallelStateDB) getDeletedStateObject(addr common.Address) *StateObject { - log.Debug("ParallelStateDB getDeletedStateObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - // Prefer live objects if any is available - if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { - return obj - } - // 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.HashData(s.hasher, addr.Bytes())); err == nil { - if acc == nil { - return nil - } - data = &Account{ - Nonce: acc.Nonce, - Balance: acc.Balance, - CodeHash: acc.CodeHash, - Root: common.BytesToHash(acc.Root), - } - if len(data.CodeHash) == 0 { - data.CodeHash = emptyCodeHash - } - if data.Root == (common.Hash{}) { - data.Root = emptyRoot - } - } - } - // If snapshot unavailable or reading from it failed, load from the database - if s.snap == nil || err != nil { - if s.trie == nil { - tr, err := s.db.OpenTrie(s.originalRoot) - if err != nil { - s.setError(fmt.Errorf("failed to open trie tree")) - return nil - } - s.trie = tr - } - 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 - // if obj, ok := s.loadStateObj(addr); ok { - // fixme: concurrent not safe, merge could update it... - // return obj - //} - obj := newObject(&s.StateDB, s, s.isParallel, addr, *data) - s.SetStateObject(obj) - return obj -} From d01b0166d1838c71153035ae5cd85b2a588a0bf7 Mon Sep 17 00:00:00 2001 From: setunapo Date: Fri, 29 Apr 2022 09:26:15 +0800 Subject: [PATCH 05/12] remove isSlotDB part 2 --- core/state/statedb.go | 702 ++++++++++++++++++---------------------- core/state_processor.go | 4 +- 2 files changed, 325 insertions(+), 381 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index a15ae91f47..f006ca0152 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2684,34 +2684,31 @@ type ParallelStateDB struct { // Notably this also returns true for suicided accounts. func (s *ParallelStateDB) Exist(addr common.Address) bool { log.Debug("ParallelStateDB Exist", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // dirty object should not be deleted, since deleted is only flagged on finalise - // and if it is suicided in contract call, suicide is taken as exist until it is finalised - // todo: add a check here, to be removed later - if obj.deleted || obj.suicided { - log.Error("Exist in dirty, but marked as deleted or suicided", - "txIndex", s.txIndex, "baseTxIndex:", s.parallel.baseTxIndex) - } - return true - } - // 2.Try to get from uncomfirmed & main DB - // 2.1 Already read before - if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { - return exist - } - // 2.2 Try to get from unconfirmed DB if exist - if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { - s.parallel.addrStateReadsInSlot[addr] = exist // update and cache - return exist - } + // 1.Try to get from dirty + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // dirty object should not be deleted, since deleted is only flagged on finalise + // and if it is suicided in contract call, suicide is taken as exist until it is finalised + // todo: add a check here, to be removed later + if obj.deleted || obj.suicided { + log.Error("Exist in dirty, but marked as deleted or suicided", + "txIndex", s.txIndex, "baseTxIndex:", s.parallel.baseTxIndex) + } + return true + } + // 2.Try to get from uncomfirmed & main DB + // 2.1 Already read before + if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { + return exist + } + // 2.2 Try to get from unconfirmed DB if exist + if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { + s.parallel.addrStateReadsInSlot[addr] = exist // update and cache + return exist } + // 3.Try to get from main StateDB exist := s.getStateObjectNoSlot(addr) != nil - if s.parallel.isSlotDB { - s.parallel.addrStateReadsInSlot[addr] = exist // update and cache - } + s.parallel.addrStateReadsInSlot[addr] = exist // update and cache return exist } @@ -2719,43 +2716,39 @@ func (s *ParallelStateDB) Exist(addr common.Address) bool { // or empty according to the EIP161 specification (balance = nonce = code = 0) func (s *ParallelStateDB) Empty(addr common.Address) bool { log.Debug("ParallelStateDB Empty", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // dirty object is light copied and fixup on need, - // empty could be wrong, except it is created with this TX - if _, ok := s.parallel.addrStateChangesInSlot[addr]; ok { - return obj.empty() - } - // so we have to check it manually - // empty means: Nonce == 0 && Balance == 0 && CodeHash == emptyCodeHash - if s.GetBalance(addr).Sign() != 0 { // check balance first, since it is most likely not zero - return false - } - if s.GetNonce(addr) != 0 { - return false - } - codeHash := s.GetCodeHash(addr) - return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty - } - // 2.Try to get from uncomfirmed & main DB - // 2.1 Already read before - if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { - // exist means not empty - return !exist + // 1.Try to get from dirty + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // dirty object is light copied and fixup on need, + // empty could be wrong, except it is created with this TX + if _, ok := s.parallel.addrStateChangesInSlot[addr]; ok { + return obj.empty() + } + // so we have to check it manually + // empty means: Nonce == 0 && Balance == 0 && CodeHash == emptyCodeHash + if s.GetBalance(addr).Sign() != 0 { // check balance first, since it is most likely not zero + return false } - // 2.2 Try to get from unconfirmed DB if exist - if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { - s.parallel.addrStateReadsInSlot[addr] = exist // update and cache - return !exist + if s.GetNonce(addr) != 0 { + return false } + codeHash := s.GetCodeHash(addr) + return bytes.Equal(codeHash.Bytes(), emptyCodeHash) // code is empty, the object is empty + } + // 2.Try to get from uncomfirmed & main DB + // 2.1 Already read before + if exist, ok := s.parallel.addrStateReadsInSlot[addr]; ok { + // exist means not empty + return !exist + } + // 2.2 Try to get from unconfirmed DB if exist + if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { + s.parallel.addrStateReadsInSlot[addr] = exist // update and cache + return !exist } so := s.getStateObjectNoSlot(addr) empty := (so == nil || so.empty()) - if s.parallel.isSlotDB { - s.parallel.addrStateReadsInSlot[addr] = !empty // update and cache - } + s.parallel.addrStateReadsInSlot[addr] = !empty // update and cache return empty } @@ -2763,99 +2756,91 @@ func (s *ParallelStateDB) Empty(addr common.Address) bool { // GetFrom the dirty list => from unconfirmed DB => get from main stateDB func (s *ParallelStateDB) GetBalance(addr common.Address) *big.Int { log.Debug("ParallelStateDB GetBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - if addr == s.parallel.systemAddress { - s.parallel.systemAddressOpsCount++ - } - // 1.Try to get from dirty - if _, ok := s.parallel.balanceChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on balance fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup balance based on unconfirmed DB or main DB - return obj.Balance() - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if balance, ok := s.parallel.balanceReadsInSlot[addr]; ok { - return balance - } - // 2.2 Try to get from unconfirmed DB if exist - if balance := s.getBalanceFromUnconfirmedDB(addr); balance != nil { - s.parallel.balanceReadsInSlot[addr] = balance - return balance + if addr == s.parallel.systemAddress { + s.parallel.systemAddressOpsCount++ + } + // 1.Try to get from dirty + if _, ok := s.parallel.balanceChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on balance fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup balance based on unconfirmed DB or main DB + return obj.Balance() } } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if balance, ok := s.parallel.balanceReadsInSlot[addr]; ok { + return balance + } + // 2.2 Try to get from unconfirmed DB if exist + if balance := s.getBalanceFromUnconfirmedDB(addr); balance != nil { + s.parallel.balanceReadsInSlot[addr] = balance + return balance + } + // 3. Try to get from main StateObejct balance := common.Big0 stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { balance = stateObject.Balance() } - if s.parallel.isSlotDB { - s.parallel.balanceReadsInSlot[addr] = balance - } + s.parallel.balanceReadsInSlot[addr] = balance return balance } func (s *ParallelStateDB) GetNonce(addr common.Address) uint64 { log.Debug("ParallelStateDB GetNonce", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if _, ok := s.parallel.nonceChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on nonce fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup nonce based on unconfirmed DB or main DB - return obj.Nonce() - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if nonce, ok := s.parallel.nonceReadsInSlot[addr]; ok { - return nonce - } - // 2.2 Try to get from unconfirmed DB if exist - if nonce, ok := s.getNonceFromUnconfirmedDB(addr); ok { - s.parallel.nonceReadsInSlot[addr] = nonce - return nonce + // 1.Try to get from dirty + if _, ok := s.parallel.nonceChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on nonce fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup nonce based on unconfirmed DB or main DB + return obj.Nonce() } } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if nonce, ok := s.parallel.nonceReadsInSlot[addr]; ok { + return nonce + } + // 2.2 Try to get from unconfirmed DB if exist + if nonce, ok := s.getNonceFromUnconfirmedDB(addr); ok { + s.parallel.nonceReadsInSlot[addr] = nonce + return nonce + } + // 3.Try to get from main StateDB var nonce uint64 = 0 stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { nonce = stateObject.Nonce() } - if s.parallel.isSlotDB { - s.parallel.nonceReadsInSlot[addr] = nonce - } + s.parallel.nonceReadsInSlot[addr] = nonce return nonce } func (s *ParallelStateDB) GetCode(addr common.Address) []byte { log.Debug("ParallelStateDB GetCode", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if _, ok := s.parallel.codeChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on code fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup code based on unconfirmed DB or main DB - code := obj.Code(s.db) - return code - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if code, ok := s.parallel.codeReadsInSlot[addr]; ok { - return code - } - // 2.2 Try to get from unconfirmed DB if exist - if code, ok := s.getCodeFromUnconfirmedDB(addr); ok { - s.parallel.codeReadsInSlot[addr] = code + // 1.Try to get from dirty + if _, ok := s.parallel.codeChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on code fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup code based on unconfirmed DB or main DB + code := obj.Code(s.db) return code } } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if code, ok := s.parallel.codeReadsInSlot[addr]; ok { + return code + } + // 2.2 Try to get from unconfirmed DB if exist + if code, ok := s.getCodeFromUnconfirmedDB(addr); ok { + s.parallel.codeReadsInSlot[addr] = code + return code + } // 3. Try to get from main StateObejct stateObject := s.getStateObjectNoSlot(addr) @@ -2863,35 +2848,31 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { if stateObject != nil { code = stateObject.Code(s.db) } - if s.parallel.isSlotDB { - s.parallel.codeReadsInSlot[addr] = code - } + s.parallel.codeReadsInSlot[addr] = code return code } func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { log.Debug("ParallelStateDB GetCodeSize", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if _, ok := s.parallel.codeChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on code fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup code based on unconfirmed DB or main DB - return obj.CodeSize(s.db) - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if code, ok := s.parallel.codeReadsInSlot[addr]; ok { - return len(code) // len(nil) is 0 too - } - // 2.2 Try to get from unconfirmed DB if exist - if code, ok := s.getCodeFromUnconfirmedDB(addr); ok { - s.parallel.codeReadsInSlot[addr] = code - return len(code) // len(nil) is 0 too + // 1.Try to get from dirty + if _, ok := s.parallel.codeChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on code fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup code based on unconfirmed DB or main DB + return obj.CodeSize(s.db) } } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if code, ok := s.parallel.codeReadsInSlot[addr]; ok { + return len(code) // len(nil) is 0 too + } + // 2.2 Try to get from unconfirmed DB if exist + if code, ok := s.getCodeFromUnconfirmedDB(addr); ok { + s.parallel.codeReadsInSlot[addr] = code + return len(code) // len(nil) is 0 too + } // 3. Try to get from main StateObejct var codeSize int = 0 @@ -2902,9 +2883,7 @@ func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { code = stateObject.Code(s.db) codeSize = stateObject.CodeSize(s.db) } - if s.parallel.isSlotDB { - s.parallel.codeReadsInSlot[addr] = code - } + s.parallel.codeReadsInSlot[addr] = code return codeSize } @@ -2915,36 +2894,31 @@ func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { log.Debug("ParallelStateDB GetCodeHash", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if _, ok := s.parallel.codeChangesInSlot[addr]; ok { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - // on code fixup, addr may not exist in dirtiedStateObjectsInSlot - // we intend to fixup balance based on unconfirmed DB or main DB - return common.BytesToHash(obj.CodeHash()) - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if codeHash, ok := s.parallel.codeHashReadsInSlot[addr]; ok { - return codeHash - } - // 2.2 Try to get from unconfirmed DB if exist - if codeHash, ok := s.getCodeHashFromUnconfirmedDB(addr); ok { - s.parallel.codeHashReadsInSlot[addr] = codeHash - return codeHash + // 1.Try to get from dirty + if _, ok := s.parallel.codeChangesInSlot[addr]; ok { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + // on code fixup, addr may not exist in dirtiedStateObjectsInSlot + // we intend to fixup balance based on unconfirmed DB or main DB + return common.BytesToHash(obj.CodeHash()) } } - + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if codeHash, ok := s.parallel.codeHashReadsInSlot[addr]; ok { + return codeHash + } + // 2.2 Try to get from unconfirmed DB if exist + if codeHash, ok := s.getCodeHashFromUnconfirmedDB(addr); ok { + s.parallel.codeHashReadsInSlot[addr] = codeHash + return codeHash + } // 3. Try to get from main StateObejct stateObject := s.getStateObjectNoSlot(addr) codeHash := common.Hash{} if stateObject != nil { codeHash = common.BytesToHash(stateObject.CodeHash()) } - if s.parallel.isSlotDB { - s.parallel.codeHashReadsInSlot[addr] = codeHash - } + s.parallel.codeHashReadsInSlot[addr] = codeHash return codeHash } @@ -2958,38 +2932,35 @@ func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common.Hash { log.Debug("ParallelStateDB GetState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - - // 1.Try to get from dirty - if exist, ok := s.parallel.addrStateChangesInSlot[addr]; ok { - if !exist { - return common.Hash{} - } + // 1.Try to get from dirty + if exist, ok := s.parallel.addrStateChangesInSlot[addr]; ok { + if !exist { + return common.Hash{} + } + obj := s.parallel.dirtiedStateObjectsInSlot[addr] // addr must exist in dirtiedStateObjectsInSlot + return obj.GetState(s.db, hash) + } + if keys, ok := s.parallel.kvChangesInSlot[addr]; ok { + if _, ok := keys[hash]; ok { obj := s.parallel.dirtiedStateObjectsInSlot[addr] // addr must exist in dirtiedStateObjectsInSlot return obj.GetState(s.db, hash) } - if keys, ok := s.parallel.kvChangesInSlot[addr]; ok { - if _, ok := keys[hash]; ok { - obj := s.parallel.dirtiedStateObjectsInSlot[addr] // addr must exist in dirtiedStateObjectsInSlot - return obj.GetState(s.db, hash) - } - } - // 2.Try to get from uncomfirmed DB or main DB - // 2.1 Already read before - if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { - if val, ok := storage.GetValue(hash); ok { - return val - } - } - // 2.2 Try to get from unconfirmed DB if exist - if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { - if s.parallel.kvReadsInSlot[addr] == nil { - s.parallel.kvReadsInSlot[addr] = newStorage(false) - } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + } + // 2.Try to get from uncomfirmed DB or main DB + // 2.1 Already read before + if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { + if val, ok := storage.GetValue(hash); ok { return val } } + // 2.2 Try to get from unconfirmed DB if exist + if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) + } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + return val + } // 3.Get from main StateDB stateObject := s.getStateObjectNoSlot(addr) @@ -2997,66 +2968,59 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common if stateObject != nil { val = stateObject.GetState(s.db, hash) } - if s.parallel.isSlotDB { - if s.parallel.kvReadsInSlot[addr] == nil { - s.parallel.kvReadsInSlot[addr] = newStorage(false) - } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache return val } // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { log.Debug("ParallelStateDB GetCommittedState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - if s.parallel.isSlotDB { - // 1.No need to get from pending of itself even on merge, since stateobject in SlotDB won't do finalise - // 2.Try to get from uncomfirmed DB or main DB - // KVs in unconfirmed DB can be seen as pending storage - // KVs in main DB are merged from SlotDB and has done finalise() on merge, can be seen as pending storage too. - // 2.1 Already read before - if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { - if val, ok := storage.GetValue(hash); ok { - return val - } - } - // 2.2 Try to get from unconfirmed DB if exist - if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { - if s.parallel.kvReadsInSlot[addr] == nil { - s.parallel.kvReadsInSlot[addr] = newStorage(false) - } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + // 1.No need to get from pending of itself even on merge, since stateobject in SlotDB won't do finalise + // 2.Try to get from uncomfirmed DB or main DB + // KVs in unconfirmed DB can be seen as pending storage + // KVs in main DB are merged from SlotDB and has done finalise() on merge, can be seen as pending storage too. + // 2.1 Already read before + if storage, ok := s.parallel.kvReadsInSlot[addr]; ok { + if val, ok := storage.GetValue(hash); ok { return val } } + // 2.2 Try to get from unconfirmed DB if exist + if val, ok := s.getKVFromUnconfirmedDB(addr, hash); ok { + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) + } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + return val + } + // 3. Try to get from main DB stateObject := s.getStateObjectNoSlot(addr) val := common.Hash{} if stateObject != nil { val = stateObject.GetCommittedState(s.db, hash) } - if s.parallel.isSlotDB { - if s.parallel.kvReadsInSlot[addr] == nil { - s.parallel.kvReadsInSlot[addr] = newStorage(false) - } - s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache + if s.parallel.kvReadsInSlot[addr] == nil { + s.parallel.kvReadsInSlot[addr] = newStorage(false) } + s.parallel.kvReadsInSlot[addr].StoreValue(hash, val) // update cache return val } func (s *ParallelStateDB) HasSuicided(addr common.Address) bool { log.Debug("ParallelStateDB HasSuicided", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - if s.parallel.isSlotDB { - // 1.Try to get from dirty - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - return obj.suicided - } - // 2.Try to get from uncomfirmed - if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { - return !exist - } + // 1.Try to get from dirty + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return obj.suicided } + // 2.Try to get from uncomfirmed + if exist, ok := s.getAddrStateFromUnconfirmedDB(addr); ok { + return !exist + } + stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { return stateObject.suicided @@ -3079,36 +3043,33 @@ func (s *ParallelStateDB) AddBalance(addr common.Address, amount *big.Int) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - if s.parallel.isSlotDB { - if addr == s.parallel.systemAddress { - s.parallel.systemAddressOpsCount++ - } - // if amount.Sign() != 0 { // todo: to reenable it - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) // light copy from main DB - // do balance fixup from the confirmed DB, it could be more reliable than main DB - balance := s.GetBalance(addr) - newStateObject.setBalance(balance) - // s.parallel.balanceReadsInSlot[addr] = newStateObject.Balance() // could read from main DB or unconfirmed DB - newStateObject.AddBalance(amount) - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - s.parallel.balanceChangesInSlot[addr] = struct{}{} - return - } - // already dirty, make sure the balance if fixed up - // if stateObject.Balance() - if addr != s.parallel.systemAddress { - if stateObject.Balance().Cmp(s.GetBalance(addr)) != 0 { - log.Warn("AddBalance in dirty, but balance is incorrect", "txIndex", s.txIndex, "addr", addr, - "stateObject.Balance()", stateObject.Balance(), "s.GetBalance(addr)", s.GetBalance(addr)) - stateObject.setBalance(s.GetBalance(addr)) - } - } + if addr == s.parallel.systemAddress { + s.parallel.systemAddressOpsCount++ } - stateObject.AddBalance(amount) - if s.parallel.isSlotDB { + // if amount.Sign() != 0 { // todo: to reenable it + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) // light copy from main DB + // do balance fixup from the confirmed DB, it could be more reliable than main DB + balance := s.GetBalance(addr) + newStateObject.setBalance(balance) + // s.parallel.balanceReadsInSlot[addr] = newStateObject.Balance() // could read from main DB or unconfirmed DB + newStateObject.AddBalance(amount) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject s.parallel.balanceChangesInSlot[addr] = struct{}{} + return } + // already dirty, make sure the balance if fixed up + // if stateObject.Balance() + if addr != s.parallel.systemAddress { + if stateObject.Balance().Cmp(s.GetBalance(addr)) != 0 { + log.Warn("AddBalance in dirty, but balance is incorrect", "txIndex", s.txIndex, "addr", addr, + "stateObject.Balance()", stateObject.Balance(), "s.GetBalance(addr)", s.GetBalance(addr)) + stateObject.setBalance(s.GetBalance(addr)) + } + } + + stateObject.AddBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} } } @@ -3124,38 +3085,34 @@ func (s *ParallelStateDB) SubBalance(addr common.Address, amount *big.Int) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - if s.parallel.isSlotDB { - if addr == s.parallel.systemAddress { - s.parallel.systemAddressOpsCount++ - } - - // if amount.Sign() != 0 { // todo: to reenable it - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) // light copy from main DB - // do balance fixup from the confirmed DB, it could be more reliable than main DB - balance := s.GetBalance(addr) - newStateObject.setBalance(balance) - // s.parallel.balanceReadsInSlot[addr] = newStateObject.Balance() - newStateObject.SubBalance(amount) - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - return - } - // already dirty, make sure the balance if fixed - // if stateObject.Balance() - if addr != s.parallel.systemAddress { - if stateObject.Balance().Cmp(s.GetBalance(addr)) != 0 { - log.Warn("SubBalance in dirty, but balance is incorrect", "txIndex", s.txIndex, "addr", addr, - "stateObject.Balance()", stateObject.Balance(), "s.GetBalance(addr)", s.GetBalance(addr)) - stateObject.setBalance(s.GetBalance(addr)) - } - } + if addr == s.parallel.systemAddress { + s.parallel.systemAddressOpsCount++ } - stateObject.SubBalance(amount) - if s.parallel.isSlotDB { + + // if amount.Sign() != 0 { // todo: to reenable it + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) // light copy from main DB + // do balance fixup from the confirmed DB, it could be more reliable than main DB + balance := s.GetBalance(addr) + newStateObject.setBalance(balance) + // s.parallel.balanceReadsInSlot[addr] = newStateObject.Balance() + newStateObject.SubBalance(amount) s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return + } + // already dirty, make sure the balance if fixed + // if stateObject.Balance() + if addr != s.parallel.systemAddress { + if stateObject.Balance().Cmp(s.GetBalance(addr)) != 0 { + log.Warn("SubBalance in dirty, but balance is incorrect", "txIndex", s.txIndex, "addr", addr, + "stateObject.Balance()", stateObject.Balance(), "s.GetBalance(addr)", s.GetBalance(addr)) + stateObject.setBalance(s.GetBalance(addr)) + } } + stateObject.SubBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} } } @@ -3164,27 +3121,23 @@ func (s *ParallelStateDB) SetBalance(addr common.Address, amount *big.Int) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - if s.parallel.isSlotDB { - if addr == s.parallel.systemAddress { - s.parallel.systemAddressOpsCount++ - } - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) - // update balance for revert, in case child contract is revertted, - // it should revert to the previous balance - balance := s.GetBalance(addr) - newStateObject.setBalance(balance) - newStateObject.SetBalance(amount) - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - return - } - + if addr == s.parallel.systemAddress { + s.parallel.systemAddressOpsCount++ } - stateObject.SetBalance(amount) - if s.parallel.isSlotDB { + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) + // update balance for revert, in case child contract is revertted, + // it should revert to the previous balance + balance := s.GetBalance(addr) + newStateObject.setBalance(balance) + newStateObject.SetBalance(amount) s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return } + + stateObject.SetBalance(amount) + s.parallel.balanceChangesInSlot[addr] = struct{}{} } } @@ -3193,23 +3146,20 @@ func (s *ParallelStateDB) SetNonce(addr common.Address, nonce uint64) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { - if s.parallel.isSlotDB { - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) - noncePre := s.GetNonce(addr) - newStateObject.setNonce(noncePre) // nonce fixup - newStateObject.SetNonce(nonce) - s.parallel.nonceChangesInSlot[addr] = struct{}{} - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - return - } + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) noncePre := s.GetNonce(addr) - stateObject.setNonce(noncePre) // nonce fixup - } - stateObject.SetNonce(nonce) - if s.parallel.isSlotDB { + newStateObject.setNonce(noncePre) // nonce fixup + newStateObject.SetNonce(nonce) s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return } + noncePre := s.GetNonce(addr) + stateObject.setNonce(noncePre) // nonce fixup + + stateObject.SetNonce(nonce) + s.parallel.nonceChangesInSlot[addr] = struct{}{} } } @@ -3219,26 +3169,23 @@ func (s *ParallelStateDB) SetCode(addr common.Address, code []byte) { stateObject := s.GetOrNewStateObject(addr) if stateObject != nil { codeHash := crypto.Keccak256Hash(code) - if s.parallel.isSlotDB { - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) - codePre := s.GetCode(addr) // code fixup - codeHashPre := crypto.Keccak256Hash(codePre) - newStateObject.setCode(codeHashPre, codePre) - - newStateObject.SetCode(codeHash, code) - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - s.parallel.codeChangesInSlot[addr] = struct{}{} - return - } + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) codePre := s.GetCode(addr) // code fixup codeHashPre := crypto.Keccak256Hash(codePre) - stateObject.setCode(codeHashPre, codePre) - } - stateObject.SetCode(codeHash, code) - if s.parallel.isSlotDB { + newStateObject.setCode(codeHashPre, codePre) + + newStateObject.SetCode(codeHash, code) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject s.parallel.codeChangesInSlot[addr] = struct{}{} + return } + codePre := s.GetCode(addr) // code fixup + codeHashPre := crypto.Keccak256Hash(codePre) + stateObject.setCode(codeHashPre, codePre) + + stateObject.SetCode(codeHash, code) + s.parallel.codeChangesInSlot[addr] = struct{}{} } } @@ -3247,33 +3194,31 @@ func (s *ParallelStateDB) SetState(addr common.Address, key, value common.Hash) stateObject := s.GetOrNewStateObject(addr) // attention: if StateObject's lightCopy, its storage is only a part of the full storage, if stateObject != nil { - if s.parallel.isSlotDB { - if s.parallel.baseTxIndex+1 == s.txIndex { - // we check if state is unchanged - // only when current transaction is the next transaction to be committed - // fixme: there is a bug, block: 14,962,284, - // stateObject is in dirty (light copy), but the key is in mainStateDB - // stateObject dirty -> committed, will skip mainStateDB dirty - if s.GetState(addr, key) == value { - log.Debug("Skip set same state", "baseTxIndex", s.parallel.baseTxIndex, - "txIndex", s.txIndex, "addr", addr, - "key", key, "value", value) - return - } + if s.parallel.baseTxIndex+1 == s.txIndex { + // we check if state is unchanged + // only when current transaction is the next transaction to be committed + // fixme: there is a bug, block: 14,962,284, + // stateObject is in dirty (light copy), but the key is in mainStateDB + // stateObject dirty -> committed, will skip mainStateDB dirty + if s.GetState(addr, key) == value { + log.Debug("Skip set same state", "baseTxIndex", s.parallel.baseTxIndex, + "txIndex", s.txIndex, "addr", addr, + "key", key, "value", value) + return } + } - if s.parallel.kvChangesInSlot[addr] == nil { - s.parallel.kvChangesInSlot[addr] = make(StateKeys) // make(Storage, defaultNumOfSlots) - } + if s.parallel.kvChangesInSlot[addr] == nil { + s.parallel.kvChangesInSlot[addr] = make(StateKeys) // make(Storage, defaultNumOfSlots) + } - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - newStateObject := stateObject.lightCopy(s) - newStateObject.SetState(s.db, key, value) - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - return - } - // do State Update + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + newStateObject := stateObject.lightCopy(s) + newStateObject.SetState(s.db, key, value) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject + return } + // do State Update stateObject.SetState(s.db, key, value) } } @@ -3287,21 +3232,20 @@ func (s *ParallelStateDB) Suicide(addr common.Address) bool { log.Debug("ParallelStateDB Suicide", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) var stateObject *StateObject - if s.parallel.isSlotDB { - // 1.Try to get from dirty, it could be suicided inside of contract call - stateObject = s.parallel.dirtiedStateObjectsInSlot[addr] - if stateObject == nil { - // 2.Try to get from uncomfirmed, if deleted return false, since the address does not exist - if obj, ok := s.getStateObjectFromUnconfirmedDB(addr); ok { - stateObject = obj - s.parallel.addrStateReadsInSlot[addr] = !stateObject.deleted // true: exist, false: deleted - if stateObject.deleted { - log.Error("Suicide addr alreay deleted in confirmed DB", "txIndex", s.txIndex, "addr", addr) - return false - } + // 1.Try to get from dirty, it could be suicided inside of contract call + stateObject = s.parallel.dirtiedStateObjectsInSlot[addr] + if stateObject == nil { + // 2.Try to get from uncomfirmed, if deleted return false, since the address does not exist + if obj, ok := s.getStateObjectFromUnconfirmedDB(addr); ok { + stateObject = obj + s.parallel.addrStateReadsInSlot[addr] = !stateObject.deleted // true: exist, false: deleted + if stateObject.deleted { + log.Error("Suicide addr alreay deleted in confirmed DB", "txIndex", s.txIndex, "addr", addr) + return false } } } + if stateObject == nil { // 3.Try to get from main StateDB stateObject = s.getStateObjectNoSlot(addr) @@ -3319,24 +3263,22 @@ func (s *ParallelStateDB) Suicide(addr common.Address) bool { prevbalance: new(big.Int).Set(s.GetBalance(addr)), }) - if s.parallel.isSlotDB { - if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - // do copy-on-write for suicide "write" - newStateObject := stateObject.lightCopy(s) - newStateObject.markSuicided() - newStateObject.data.Balance = new(big.Int) - s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject - s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, - // s.parallel.nonceChangesInSlot[addr] = struct{}{} - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.codeChangesInSlot[addr] = struct{}{} - // s.parallel.kvChangesInSlot[addr] = make(StateKeys) // all key changes are discarded - return true - } + if _, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + // do copy-on-write for suicide "write" + newStateObject := stateObject.lightCopy(s) + newStateObject.markSuicided() + newStateObject.data.Balance = new(big.Int) + s.parallel.dirtiedStateObjectsInSlot[addr] = newStateObject s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, + // s.parallel.nonceChangesInSlot[addr] = struct{}{} s.parallel.balanceChangesInSlot[addr] = struct{}{} s.parallel.codeChangesInSlot[addr] = struct{}{} + // s.parallel.kvChangesInSlot[addr] = make(StateKeys) // all key changes are discarded + return true } + s.parallel.addrStateChangesInSlot[addr] = false // false: the address does not exist any more, + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} stateObject.markSuicided() stateObject.data.Balance = new(big.Int) diff --git a/core/state_processor.go b/core/state_processor.go index ae2f1000ee..4dde8c064a 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -916,10 +916,12 @@ func (p *ParallelStateProcessor) Process(block *types.Block, statedb *state.Stat header = block.Header() gp = new(GasPool).AddGas(block.GasLimit()) ) - log.Info("ProcessParallel", "block", header.Number) var receipts = make([]*types.Receipt, 0) txNum := len(block.Transactions()) p.resetState(txNum, statedb) + if txNum > 0 { + log.Info("ProcessParallel", "block", header.Number) + } // Iterate over and process the individual transactions posa, isPoSA := p.engine.(consensus.PoSA) From 8588b4cdb5995fc53666caf0b4cd54db5add76d8 Mon Sep 17 00:00:00 2001 From: setunapo Date: Fri, 29 Apr 2022 11:36:05 +0800 Subject: [PATCH 06/12] state.StateDBer & remove getStateObjectNoSlot --- core/state/interface.go | 81 +++++++++++++++++++++ core/state/journal.go | 38 ++++++---- core/state/state_object.go | 5 +- core/state/statedb.go | 145 +++++++++++++++---------------------- 4 files changed, 163 insertions(+), 106 deletions(-) create mode 100644 core/state/interface.go diff --git a/core/state/interface.go b/core/state/interface.go new file mode 100644 index 0000000000..68847fe59c --- /dev/null +++ b/core/state/interface.go @@ -0,0 +1,81 @@ +// Copyright 2016 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 ( + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// StateDBer is copied from vm/interface.go +// It is used by StateObject & Journal right now, to abstract StateDB & ParallelStateDB +type StateDBer interface { + getStateObject(common.Address) *StateObject // only accessible for journal + getBaseStateDB() *StateDB + + CreateAccount(common.Address) + + SubBalance(common.Address, *big.Int) + AddBalance(common.Address, *big.Int) + GetBalance(common.Address) *big.Int + + GetNonce(common.Address) uint64 + SetNonce(common.Address, uint64) + + GetCodeHash(common.Address) common.Hash + GetCode(common.Address) []byte + SetCode(common.Address, []byte) + GetCodeSize(common.Address) int + + AddRefund(uint64) + SubRefund(uint64) + GetRefund() uint64 + + GetCommittedState(common.Address, common.Hash) common.Hash + GetState(common.Address, common.Hash) common.Hash + SetState(common.Address, common.Hash, common.Hash) + + Suicide(common.Address) bool + HasSuicided(common.Address) bool + + // Exist reports whether the given account exists in state. + // Notably this should also return true for suicided accounts. + Exist(common.Address) bool + // Empty returns whether the given account is empty. Empty + // is defined according to EIP161 (balance = nonce = code = 0). + Empty(common.Address) bool + + PrepareAccessList(sender common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList) + AddressInAccessList(addr common.Address) bool + SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool) + // AddAddressToAccessList adds the given address to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddAddressToAccessList(addr common.Address) + // AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform + // even if the feature/fork is not active yet + AddSlotToAccessList(addr common.Address, slot common.Hash) + + RevertToSnapshot(int) + Snapshot() int + + AddLog(*types.Log) + AddPreimage(common.Hash, []byte) + + ForEachStorage(common.Address, func(common.Hash, common.Hash) bool) error +} diff --git a/core/state/journal.go b/core/state/journal.go index 96655d007d..d11096cd18 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -26,7 +26,7 @@ import ( // reverted on demand. type journalEntry interface { // revert undoes the changes introduced by this journal entry. - revert(*StateDB) + revert(StateDBer) // dirtied returns the Ethereum address modified by this journal entry. dirtied() *common.Address @@ -58,7 +58,7 @@ func (j *journal) append(entry journalEntry) { // revert undoes a batch of journalled modifications along with any reverted // dirty handling too. -func (j *journal) revert(statedb *StateDB, snapshot int) { +func (j *journal) revert(statedb StateDBer, snapshot int) { for i := len(j.entries) - 1; i >= snapshot; i-- { // Undo the changes made by the operation j.entries[i].revert(statedb) @@ -141,7 +141,8 @@ type ( } ) -func (ch createObjectChange) revert(s *StateDB) { +func (ch createObjectChange) revert(itf StateDBer) { + s := itf.getBaseStateDB() if s.parallel.isSlotDB { delete(s.parallel.dirtiedStateObjectsInSlot, *ch.account) delete(s.parallel.addrStateChangesInSlot, *ch.account) @@ -159,7 +160,8 @@ func (ch createObjectChange) dirtied() *common.Address { return ch.account } -func (ch resetObjectChange) revert(s *StateDB) { +func (ch resetObjectChange) revert(itf StateDBer) { + s := itf.getBaseStateDB() if s.parallel.isSlotDB { // ch.prev must be from dirtiedStateObjectsInSlot, put it back s.parallel.dirtiedStateObjectsInSlot[ch.prev.address] = ch.prev @@ -176,7 +178,7 @@ func (ch resetObjectChange) dirtied() *common.Address { return nil } -func (ch suicideChange) revert(s *StateDB) { +func (ch suicideChange) revert(s StateDBer) { obj := s.getStateObject(*ch.account) if obj != nil { obj.suicided = ch.prev @@ -190,14 +192,14 @@ func (ch suicideChange) dirtied() *common.Address { var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") -func (ch touchChange) revert(s *StateDB) { +func (ch touchChange) revert(statedb StateDBer) { } func (ch touchChange) dirtied() *common.Address { return ch.account } -func (ch balanceChange) revert(s *StateDB) { +func (ch balanceChange) revert(s StateDBer) { s.getStateObject(*ch.account).setBalance(ch.prev) } @@ -205,7 +207,7 @@ func (ch balanceChange) dirtied() *common.Address { return ch.account } -func (ch nonceChange) revert(s *StateDB) { +func (ch nonceChange) revert(s StateDBer) { s.getStateObject(*ch.account).setNonce(ch.prev) } @@ -213,7 +215,7 @@ func (ch nonceChange) dirtied() *common.Address { return ch.account } -func (ch codeChange) revert(s *StateDB) { +func (ch codeChange) revert(s StateDBer) { s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) } @@ -221,7 +223,7 @@ func (ch codeChange) dirtied() *common.Address { return ch.account } -func (ch storageChange) revert(s *StateDB) { +func (ch storageChange) revert(s StateDBer) { s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) } @@ -229,7 +231,8 @@ func (ch storageChange) dirtied() *common.Address { return ch.account } -func (ch refundChange) revert(s *StateDB) { +func (ch refundChange) revert(itf StateDBer) { + s := itf.getBaseStateDB() s.refund = ch.prev } @@ -237,7 +240,9 @@ func (ch refundChange) dirtied() *common.Address { return nil } -func (ch addLogChange) revert(s *StateDB) { +func (ch addLogChange) revert(itf StateDBer) { + s := itf.getBaseStateDB() + logs := s.logs[ch.txhash] if len(logs) == 1 { delete(s.logs, ch.txhash) @@ -251,7 +256,8 @@ func (ch addLogChange) dirtied() *common.Address { return nil } -func (ch addPreimageChange) revert(s *StateDB) { +func (ch addPreimageChange) revert(itf StateDBer) { + s := itf.getBaseStateDB() delete(s.preimages, ch.hash) } @@ -259,7 +265,8 @@ func (ch addPreimageChange) dirtied() *common.Address { return nil } -func (ch accessListAddAccountChange) revert(s *StateDB) { +func (ch accessListAddAccountChange) revert(itf StateDBer) { + s := itf.getBaseStateDB() /* One important invariant here, is that whenever a (addr, slot) is added, if the addr is not already present, the add causes two journal entries: @@ -278,7 +285,8 @@ func (ch accessListAddAccountChange) dirtied() *common.Address { return nil } -func (ch accessListAddSlotChange) revert(s *StateDB) { +func (ch accessListAddSlotChange) revert(itf StateDBer) { + s := itf.getBaseStateDB() if s.accessList != nil { s.accessList.DeleteSlot(*ch.address, *ch.slot) } diff --git a/core/state/state_object.go b/core/state/state_object.go index f7e38ce1f5..4ad5cf93cb 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -25,7 +25,6 @@ import ( "time" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" @@ -151,7 +150,7 @@ type StateObject struct { addrHash common.Hash // hash of ethereum address of the account data Account db *StateDB - dbItf vm.StateDBer + dbItf StateDBer // DB error. // State objects are used by the consensus core and VM which are @@ -241,7 +240,7 @@ type Account struct { } // newObject creates a state object. -func newObject(db *StateDB, dbItf vm.StateDBer, isParallel bool, address common.Address, data Account) *StateObject { +func newObject(db *StateDB, dbItf StateDBer, isParallel bool, address common.Address, data Account) *StateObject { if data.Balance == nil { data.Balance = new(big.Int) // todo: why not common.Big0? } diff --git a/core/state/statedb.go b/core/state/statedb.go index f006ca0152..c2776fbf8a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -910,7 +910,7 @@ func (s *StateDB) getStateObjectFromUnconfirmedDB(addr common.Address) (*StateOb func (s *StateDB) Exist(addr common.Address) bool { log.Debug("StateDB Exist", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) // 3.Try to get from main StateDB - exist := s.getStateObjectNoSlot(addr) != nil + exist := s.getStateObject(addr) != nil return exist } @@ -918,7 +918,7 @@ func (s *StateDB) Exist(addr common.Address) bool { // or empty according to the EIP161 specification (balance = nonce = code = 0) func (s *StateDB) Empty(addr common.Address) bool { log.Debug("StateDB Empty", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - so := s.getStateObjectNoSlot(addr) + so := s.getStateObject(addr) empty := (so == nil || so.empty()) return empty } @@ -929,7 +929,7 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int { log.Debug("StateDB GetBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) balance := common.Big0 - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.getStateObject(addr) if stateObject != nil { balance = stateObject.Balance() } @@ -940,7 +940,7 @@ func (s *StateDB) GetNonce(addr common.Address) uint64 { log.Debug("StateDB GetNonce", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) var nonce uint64 = 0 - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.getStateObject(addr) if stateObject != nil { nonce = stateObject.Nonce() } @@ -1039,7 +1039,7 @@ func (s *StateDB) IsParallelReadsValid() bool { // addr state check for addr, stateSlot := range slotDB.parallel.addrStateReadsInSlot { stateMain := false // addr not exist - if mainDB.getStateObjectNoSlot(addr) != nil { + if mainDB.getStateObject(addr) != nil { stateMain = true // addr exist in main DB } if stateSlot != stateMain { @@ -1056,7 +1056,7 @@ func (s *StateDB) IsParallelReadsValid() bool { // snapshot destructs check for addr, destructRead := range slotDB.parallel.addrSnapDestructsReadsInSlot { - mainObj := mainDB.getStateObjectNoSlot(addr) + mainObj := mainDB.getStateObject(addr) if mainObj == nil { log.Debug("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", "addr", addr, "destruct", destructRead, @@ -1094,7 +1094,7 @@ func (s *StateDB) NeedsRedo() bool { func (s *StateDB) GetCode(addr common.Address) []byte { log.Debug("StateDB GetCode", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.getStateObject(addr) var code []byte if stateObject != nil { code = stateObject.Code(s.db) @@ -1107,7 +1107,7 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { // 3. Try to get from main StateObejct var codeSize int = 0 - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.getStateObject(addr) if stateObject != nil { codeSize = stateObject.CodeSize(s.db) } @@ -1121,7 +1121,7 @@ func (s *StateDB) GetCodeSize(addr common.Address) int { func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { log.Debug("StateDB GetCodeHash", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.getStateObject(addr) codeHash := common.Hash{} if stateObject != nil { codeHash = common.BytesToHash(stateObject.CodeHash()) @@ -1138,7 +1138,7 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { // -> origin func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { log.Debug("StateDB GetState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.getStateObject(addr) val := common.Hash{} if stateObject != nil { val = stateObject.GetState(s.db, hash) @@ -1187,7 +1187,7 @@ func (s *StateDB) GetStorageProofByHash(a common.Address, key common.Hash) ([][] func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { log.Debug("StateDB GetCommittedState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) // 3. Try to get from main DB - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.getStateObject(addr) val := common.Hash{} if stateObject != nil { val = stateObject.GetCommittedState(s.db, hash) @@ -1213,7 +1213,7 @@ func (s *StateDB) StorageTrie(addr common.Address) Trie { } func (s *StateDB) HasSuicided(addr common.Address) bool { - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.getStateObject(addr) if stateObject != nil { return stateObject.suicided } @@ -1327,7 +1327,7 @@ func (s *StateDB) Suicide(addr common.Address) bool { var stateObject *StateObject if stateObject == nil { // 3.Try to get from main StateDB - stateObject = s.getStateObjectNoSlot(addr) + stateObject = s.getStateObject(addr) if stateObject == nil { s.parallel.addrStateReadsInSlot[addr] = false // true: exist, false: deleted log.Error("Suicide addr not exist", "txIndex", s.txIndex, "addr", addr) @@ -1390,22 +1390,7 @@ func (s *StateDB) deleteStateObject(obj *StateObject) { // 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. -// fixme: avoid getStateObjectNoSlot, may be we define a new struct SlotDB which inherit StateDB -func (s *StateDB) getStateObjectNoSlot(addr common.Address) *StateObject { - if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { - return obj - } - return nil -} - -// for parallel execution mode, try to get dirty StateObject in slot first. func (s *StateDB) getStateObject(addr common.Address) *StateObject { - if s.parallel.isSlotDB { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - return obj - } - } - if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { return obj } @@ -1494,24 +1479,11 @@ func (s *StateDB) SetStateObject(object *StateObject) { // dirtyInSlot -> Unconfirmed DB -> main DB -> snapshot, no? create one func (s *StateDB) GetOrNewStateObject(addr common.Address) *StateObject { var stateObject *StateObject = nil - exist := true - if s.parallel.isSlotDB { - if stateObject, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - return stateObject - } - stateObject, _ = s.getStateObjectFromUnconfirmedDB(addr) - } - if stateObject == nil { - stateObject = s.getStateObjectNoSlot(addr) + stateObject = s.getStateObject(addr) } if stateObject == nil || stateObject.deleted || stateObject.suicided { stateObject = s.createObject(addr) - exist = false - } - - if s.parallel.isSlotDB { - s.parallel.addrStateReadsInSlot[addr] = exist // true: exist, false: not exist } return stateObject } @@ -2707,7 +2679,7 @@ func (s *ParallelStateDB) Exist(addr common.Address) bool { } // 3.Try to get from main StateDB - exist := s.getStateObjectNoSlot(addr) != nil + exist := s.StateDB.getStateObject(addr) != nil s.parallel.addrStateReadsInSlot[addr] = exist // update and cache return exist } @@ -2746,7 +2718,7 @@ func (s *ParallelStateDB) Empty(addr common.Address) bool { return !exist } - so := s.getStateObjectNoSlot(addr) + so := s.StateDB.getStateObject(addr) empty := (so == nil || so.empty()) s.parallel.addrStateReadsInSlot[addr] = !empty // update and cache return empty @@ -2780,7 +2752,7 @@ func (s *ParallelStateDB) GetBalance(addr common.Address) *big.Int { // 3. Try to get from main StateObejct balance := common.Big0 - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.StateDB.getStateObject(addr) if stateObject != nil { balance = stateObject.Balance() } @@ -2811,7 +2783,7 @@ func (s *ParallelStateDB) GetNonce(addr common.Address) uint64 { // 3.Try to get from main StateDB var nonce uint64 = 0 - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.StateDB.getStateObject(addr) if stateObject != nil { nonce = stateObject.Nonce() } @@ -2843,7 +2815,7 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { } // 3. Try to get from main StateObejct - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.StateDB.getStateObject(addr) var code []byte if stateObject != nil { code = stateObject.Code(s.db) @@ -2877,7 +2849,7 @@ func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { // 3. Try to get from main StateObejct var codeSize int = 0 var code []byte - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.StateDB.getStateObject(addr) if stateObject != nil { code = stateObject.Code(s.db) @@ -2913,7 +2885,7 @@ func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { return codeHash } // 3. Try to get from main StateObejct - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.StateDB.getStateObject(addr) codeHash := common.Hash{} if stateObject != nil { codeHash = common.BytesToHash(stateObject.CodeHash()) @@ -2963,7 +2935,7 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common } // 3.Get from main StateDB - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.StateDB.getStateObject(addr) val := common.Hash{} if stateObject != nil { val = stateObject.GetState(s.db, hash) @@ -2998,7 +2970,7 @@ func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Has } // 3. Try to get from main DB - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.StateDB.getStateObject(addr) val := common.Hash{} if stateObject != nil { val = stateObject.GetCommittedState(s.db, hash) @@ -3021,7 +2993,7 @@ func (s *ParallelStateDB) HasSuicided(addr common.Address) bool { return !exist } - stateObject := s.getStateObjectNoSlot(addr) + stateObject := s.StateDB.getStateObject(addr) if stateObject != nil { return stateObject.suicided } @@ -3030,7 +3002,6 @@ func (s *ParallelStateDB) HasSuicided(addr common.Address) bool { // AddBalance adds amount to the account associated with addr. func (s *ParallelStateDB) AddBalance(addr common.Address, amount *big.Int) { - // if s.parallel.isSlotDB { // add balance will perform a read operation first // s.parallel.balanceReadsInSlot[addr] = struct{}{} // fixme: to make the the balance valid, since unconfirmed would refer it. // if amount.Sign() == 0 { @@ -3038,7 +3009,6 @@ func (s *ParallelStateDB) AddBalance(addr common.Address, amount *big.Int) { // take this empty check as addr state read(create, suicide, empty delete) // s.parallel.addrStateReadsInSlot[addr] = struct{}{} // } - // } log.Debug("ParallelStateDB AddBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) stateObject := s.GetOrNewStateObject(addr) @@ -3075,12 +3045,10 @@ func (s *ParallelStateDB) AddBalance(addr common.Address, amount *big.Int) { // SubBalance subtracts amount from the account associated with addr. func (s *ParallelStateDB) SubBalance(addr common.Address, amount *big.Int) { - // if s.parallel.isSlotDB { // if amount.Sign() != 0 { // unlike add, sub 0 balance will not touch empty object // s.parallel.balanceReadsInSlot[addr] = struct{}{} // } - // } log.Debug("ParallelStateDB SubBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) stateObject := s.GetOrNewStateObject(addr) @@ -3248,7 +3216,7 @@ func (s *ParallelStateDB) Suicide(addr common.Address) bool { if stateObject == nil { // 3.Try to get from main StateDB - stateObject = s.getStateObjectNoSlot(addr) + stateObject = s.StateDB.getStateObject(addr) if stateObject == nil { s.parallel.addrStateReadsInSlot[addr] = false // true: exist, false: deleted log.Error("Suicide addr not exist", "txIndex", s.txIndex, "addr", addr) @@ -3292,24 +3260,20 @@ func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *StateObject var stateObject *StateObject = nil exist := true - if s.parallel.isSlotDB { - if stateObject, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - return stateObject - } - stateObject, _ = s.getStateObjectFromUnconfirmedDB(addr) + if stateObject, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return stateObject } + stateObject, _ = s.getStateObjectFromUnconfirmedDB(addr) if stateObject == nil { - stateObject = s.getStateObjectNoSlot(addr) + stateObject = s.StateDB.getStateObject(addr) // try to get from base db } if stateObject == nil || stateObject.deleted || stateObject.suicided { stateObject = s.createObject(addr) exist = false } - if s.parallel.isSlotDB { - s.parallel.addrStateReadsInSlot[addr] = exist // true: exist, false: not exist - } + s.parallel.addrStateReadsInSlot[addr] = exist // true: exist, false: not exist return stateObject } @@ -3331,26 +3295,18 @@ func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *StateObject func (s *ParallelStateDB) createObject(addr common.Address) (newobj *StateObject) { log.Debug("ParallelStateDB createObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - var prev *StateObject = nil - if s.parallel.isSlotDB { - // do not get from unconfirmed DB, since it will has problem on revert - prev = s.parallel.dirtiedStateObjectsInSlot[addr] - } else { - prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! - } + // do not get from unconfirmed DB, since it will has problem on revert + prev := s.parallel.dirtiedStateObjectsInSlot[addr] var prevdestruct bool if s.snap != nil && prev != nil { _, prevdestruct = s.snapDestructs[prev.address] // fixme, record the snapshot read for create Account - if s.parallel.isSlotDB { - s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct - } + s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct if !prevdestruct { // To destroy the previous trie node first and update the trie tree // with the new object on block commit. s.snapDestructs[prev.address] = struct{}{} - } } newobj = newObject(&s.StateDB, s, s.isParallel, addr, Account{}) @@ -3361,17 +3317,13 @@ func (s *ParallelStateDB) createObject(addr common.Address) (newobj *StateObject s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) } - if s.parallel.isSlotDB { - // s.parallel.dirtiedStateObjectsInSlot[addr] = newobj // would change the bahavior of AddBalance... - s.parallel.addrStateChangesInSlot[addr] = true // the object sis created - s.parallel.nonceChangesInSlot[addr] = struct{}{} - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.codeChangesInSlot[addr] = struct{}{} - // notice: all the KVs are cleared if any - s.parallel.kvChangesInSlot[addr] = make(StateKeys) - } else { - s.SetStateObject(newobj) - } + // s.parallel.dirtiedStateObjectsInSlot[addr] = newobj // would change the bahavior of AddBalance... + s.parallel.addrStateChangesInSlot[addr] = true // the object sis created + s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} + // notice: all the KVs are cleared if any + s.parallel.kvChangesInSlot[addr] = make(StateKeys) return newobj } @@ -3393,3 +3345,20 @@ func (s *ParallelStateDB) CreateAccount(addr common.Address) { newObj := s.createObject(addr) newObj.setBalance(new(big.Int).Set(preBalance)) // new big.Int for newObj } + +// for parallel execution mode, try to get dirty StateObject in slot first. +// it is mainly used by journal revert right now. +func (s *ParallelStateDB) getStateObject(addr common.Address) *StateObject { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return obj + } + return s.StateDB.getStateObject(addr) +} + +func (s *StateDB) getBaseStateDB() *StateDB { + return s +} + +func (s *ParallelStateDB) getBaseStateDB() *StateDB { + return &s.StateDB +} From e21d6db992d3d6531a1a5a7dd3a4b2c80bd5d699 Mon Sep 17 00:00:00 2001 From: setunapo Date: Fri, 29 Apr 2022 18:40:41 +0800 Subject: [PATCH 07/12] 0429: temp, misc --- core/state/dump.go | 2 +- core/state/state_object.go | 10 +- core/state/statedb.go | 288 +++++++++++++++++++++---------------- 3 files changed, 170 insertions(+), 130 deletions(-) diff --git a/core/state/dump.go b/core/state/dump.go index c705e7a311..55f4c7754d 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -138,7 +138,7 @@ func (s *StateDB) DumpToCollector(c DumpCollector, excludeCode, excludeStorage, account.SecureKey = it.Key } addr := common.BytesToAddress(addrBytes) - obj := newObject(s, s, s.isParallel, addr, data) + obj := newObject(s, s.isParallel, addr, data) if !excludeCode { account.Code = common.Bytes2Hex(obj.Code(s.db)) } diff --git a/core/state/state_object.go b/core/state/state_object.go index 4ad5cf93cb..b516db042a 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -240,7 +240,8 @@ type Account struct { } // newObject creates a state object. -func newObject(db *StateDB, dbItf StateDBer, isParallel bool, address common.Address, data Account) *StateObject { +func newObject(dbItf StateDBer, isParallel bool, address common.Address, data Account) *StateObject { + db := dbItf.getBaseStateDB() if data.Balance == nil { data.Balance = new(big.Int) // todo: why not common.Big0? } @@ -636,7 +637,6 @@ func (s *StateObject) AddBalance(amount *big.Int) { return } s.SetBalance(new(big.Int).Add(s.Balance(), amount)) - // s.SetBalance(new(big.Int).Add(s.db.GetBalance(s.address), amount)) } // SubBalance removes amount from s's balance. @@ -646,11 +646,9 @@ func (s *StateObject) SubBalance(amount *big.Int) { return } s.SetBalance(new(big.Int).Sub(s.Balance(), amount)) - // s.SetBalance(new(big.Int).Sub(s.db.GetBalance(s.address), amount)) } func (s *StateObject) SetBalance(amount *big.Int) { - // prevBalance := new(big.Int).Set(s.db.GetBalance(s.address)) s.db.journal.append(balanceChange{ account: &s.address, prev: new(big.Int).Set(s.data.Balance), // prevBalance, @@ -667,7 +665,7 @@ func (s *StateObject) setBalance(amount *big.Int) { func (s *StateObject) ReturnGas(gas *big.Int) {} func (s *StateObject) lightCopy(db *ParallelStateDB) *StateObject { - stateObject := newObject(&db.StateDB, db, s.isParallel, s.address, s.data) + stateObject := newObject(db, s.isParallel, s.address, s.data) if s.trie != nil { // fixme: no need to copy trie for light copy, since light copied object won't access trie DB stateObject.trie = db.db.CopyTrie(s.trie) @@ -680,7 +678,7 @@ func (s *StateObject) lightCopy(db *ParallelStateDB) *StateObject { } func (s *StateObject) deepCopy(db *StateDB) *StateObject { - stateObject := newObject(db, db, s.isParallel, s.address, s.data) + stateObject := newObject(db, s.isParallel, s.address, s.data) if s.trie != nil { stateObject.trie = db.db.CopyTrie(s.trie) } diff --git a/core/state/statedb.go b/core/state/statedb.go index c2776fbf8a..5912ffb800 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -101,6 +101,7 @@ func (s *StateDB) storeStateObj(addr common.Address, stateObject *StateObject) { // the object could be create in SlotDB, if it got the object from DB and // update it to the shared `s.parallel.stateObjects`` stateObject.db = s.parallel.baseStateDB + stateObject.dbItf = s.parallel.baseStateDB stateObject.db.storeParallelLock.Lock() if _, ok := s.parallel.stateObjects.Load(addr); !ok { s.parallel.stateObjects.Store(addr, stateObject) @@ -342,6 +343,10 @@ func newStateDB(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, return sdb, nil } +func (s *StateDB) getBaseStateDB() *StateDB { + return s +} + func (s *StateDB) getStateObjectFromStateObjects(addr common.Address) (*StateObject, bool) { return s.loadStateObj(addr) } @@ -927,7 +932,9 @@ func (s *StateDB) Empty(addr common.Address) bool { // GetFrom the dirty list => from unconfirmed DB => get from main stateDB func (s *StateDB) GetBalance(addr common.Address) *big.Int { log.Debug("StateDB GetBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - + if s.parallel.SlotIndex != -1 { + log.Debug("StateDB GetBalance in slot", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + } balance := common.Big0 stateObject := s.getStateObject(addr) if stateObject != nil { @@ -1397,20 +1404,9 @@ func (s *StateDB) getStateObject(addr common.Address) *StateObject { return nil } -// 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.getStateObjectFromStateObjects(addr); obj != nil { - return obj - } +func (s *StateDB) getStateObjectFromSnapshotOrTrie(addr common.Address) (data *Account, ok bool) { + var err error // 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()) @@ -1418,7 +1414,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *StateObject { var acc *snapshot.Account if acc, err = s.snap.Account(crypto.HashData(s.hasher, addr.Bytes())); err == nil { if acc == nil { - return nil + return nil, false } data = &Account{ Nonce: acc.Nonce, @@ -1440,7 +1436,7 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *StateObject { tr, err := s.db.OpenTrie(s.originalRoot) if err != nil { s.setError(fmt.Errorf("failed to open trie tree")) - return nil + return nil, false } s.trie = tr } @@ -1450,23 +1446,39 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *StateObject { enc, err := s.trie.TryGet(addr.Bytes()) if err != nil { s.setError(fmt.Errorf("getDeleteStateObject (%x) error: %v", addr.Bytes(), err)) - return nil + return nil, false } if len(enc) == 0 { - return nil + return nil, false } data = new(Account) if err := rlp.DecodeBytes(enc, data); err != nil { log.Error("Failed to decode state object", "addr", addr, "err", err) - return nil + return nil, false } } + return data, true +} + +// 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.getStateObjectFromStateObjects(addr); obj != nil { + return obj + } + data, ok := s.getStateObjectFromSnapshotOrTrie(addr) + if !ok { + return nil + } // Insert into the live set // if obj, ok := s.loadStateObj(addr); ok { // fixme: concurrent not safe, merge could update it... // return obj //} - obj := newObject(s, s, s.isParallel, addr, *data) + obj := newObject(s, s.isParallel, addr, *data) s.SetStateObject(obj) return obj } @@ -1526,7 +1538,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj *StateObject) { } } - newobj = newObject(s, s, s.isParallel, addr, Account{}) + newobj = newObject(s, s.isParallel, addr, Account{}) newobj.setNonce(0) // sets the object to dirty if prev == nil { s.journal.append(createObjectChange{account: &addr}) @@ -2652,6 +2664,127 @@ type ParallelStateDB struct { StateDB } +func (s *ParallelStateDB) getBaseStateDB() *StateDB { + return &s.StateDB +} + +// for parallel execution mode, try to get dirty StateObject in slot first. +// it is mainly used by journal revert right now. +func (s *ParallelStateDB) getStateObject(addr common.Address) *StateObject { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return obj + } + // can not call s.StateDB.getStateObject(), since `newObject` need ParallelStateDB as the interface + return s.getStateObjectNoSlot(addr) +} + +func (s *ParallelStateDB) getStateObjectNoSlot(addr common.Address) *StateObject { + if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { + return obj + } + return nil +} + +// 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. + +// prev is used for CreateAccount to get its balance +// Parallel mode: +// if prev in dirty: revert is ok +// if prev in unconfirmed DB: addr state read record, revert should not put it back +// if prev in main DB: addr state read record, revert should not put it back +// if pre no exist: addr state read record, + +// `prev` is used to handle revert, to recover with the `prev` object +// In Parallel mode, we only need to recover to `prev` in SlotDB, +// a.if it is not in SlotDB, `revert` will remove it from the SlotDB +// b.if it is exist in SlotDB, `revert` will recover to the `prev` in SlotDB +// c.as `snapDestructs` it is the same +func (s *ParallelStateDB) createObject(addr common.Address) (newobj *StateObject) { + log.Debug("ParallelStateDB createObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + + // do not get from unconfirmed DB, since it will has problem on revert + prev := s.parallel.dirtiedStateObjectsInSlot[addr] + + var prevdestruct bool + + if s.snap != nil && prev != nil { + _, prevdestruct = s.snapDestructs[prev.address] // fixme, record the snapshot read for create Account + s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct + if !prevdestruct { + // To destroy the previous trie node first and update the trie tree + // with the new object on block commit. + s.snapDestructs[prev.address] = struct{}{} + } + } + newobj = newObject(s, s.isParallel, addr, Account{}) + newobj.setNonce(0) // sets the object to dirty + if prev == nil { + s.journal.append(createObjectChange{account: &addr}) + } else { + s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) + } + + // s.parallel.dirtiedStateObjectsInSlot[addr] = newobj // would change the bahavior of AddBalance... + s.parallel.addrStateChangesInSlot[addr] = true // the object sis created + s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} + // notice: all the KVs are cleared if any + s.parallel.kvChangesInSlot[addr] = make(StateKeys) + return newobj +} + +// 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 *ParallelStateDB) getDeletedStateObject(addr common.Address) *StateObject { + // Prefer live objects if any is available + if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { + return obj + } + data, ok := s.getStateObjectFromSnapshotOrTrie(addr) + if !ok { + log.Warn("object not exist", "addr", addr) + return nil + } + // Insert into the live set + // if obj, ok := s.loadStateObj(addr); ok { + // fixme: concurrent not safe, merge could update it... + // return obj + // } + // this is why we have to use a seperate getDeletedStateObject for ParallelStateDB + // `s` has to be the ParallelStateDB + obj := newObject(s, s.isParallel, addr, *data) + s.SetStateObject(obj) + return obj +} + +// GetOrNewStateObject retrieves a state object or create a new state object if nil. +// dirtyInSlot -> Unconfirmed DB -> main DB -> snapshot, no? create one +func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *StateObject { + log.Debug("ParallelStateDB GetOrNewStateObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + + var stateObject *StateObject = nil + exist := true + if stateObject, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return stateObject + } + stateObject, _ = s.getStateObjectFromUnconfirmedDB(addr) + + if stateObject == nil { + stateObject = s.getStateObjectNoSlot(addr) // try to get from base db + } + if stateObject == nil || stateObject.deleted || stateObject.suicided { + stateObject = s.createObject(addr) + exist = false + } + + s.parallel.addrStateReadsInSlot[addr] = exist // true: exist, false: not exist + return stateObject +} + // Exist reports whether the given account address exists in the state. // Notably this also returns true for suicided accounts. func (s *ParallelStateDB) Exist(addr common.Address) bool { @@ -2679,7 +2812,7 @@ func (s *ParallelStateDB) Exist(addr common.Address) bool { } // 3.Try to get from main StateDB - exist := s.StateDB.getStateObject(addr) != nil + exist := s.getStateObjectNoSlot(addr) != nil s.parallel.addrStateReadsInSlot[addr] = exist // update and cache return exist } @@ -2718,7 +2851,7 @@ func (s *ParallelStateDB) Empty(addr common.Address) bool { return !exist } - so := s.StateDB.getStateObject(addr) + so := s.getStateObjectNoSlot(addr) empty := (so == nil || so.empty()) s.parallel.addrStateReadsInSlot[addr] = !empty // update and cache return empty @@ -2752,7 +2885,7 @@ func (s *ParallelStateDB) GetBalance(addr common.Address) *big.Int { // 3. Try to get from main StateObejct balance := common.Big0 - stateObject := s.StateDB.getStateObject(addr) + stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { balance = stateObject.Balance() } @@ -2783,7 +2916,7 @@ func (s *ParallelStateDB) GetNonce(addr common.Address) uint64 { // 3.Try to get from main StateDB var nonce uint64 = 0 - stateObject := s.StateDB.getStateObject(addr) + stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { nonce = stateObject.Nonce() } @@ -2815,7 +2948,7 @@ func (s *ParallelStateDB) GetCode(addr common.Address) []byte { } // 3. Try to get from main StateObejct - stateObject := s.StateDB.getStateObject(addr) + stateObject := s.getStateObjectNoSlot(addr) var code []byte if stateObject != nil { code = stateObject.Code(s.db) @@ -2849,7 +2982,7 @@ func (s *ParallelStateDB) GetCodeSize(addr common.Address) int { // 3. Try to get from main StateObejct var codeSize int = 0 var code []byte - stateObject := s.StateDB.getStateObject(addr) + stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { code = stateObject.Code(s.db) @@ -2885,7 +3018,7 @@ func (s *ParallelStateDB) GetCodeHash(addr common.Address) common.Hash { return codeHash } // 3. Try to get from main StateObejct - stateObject := s.StateDB.getStateObject(addr) + stateObject := s.getStateObjectNoSlot(addr) codeHash := common.Hash{} if stateObject != nil { codeHash = common.BytesToHash(stateObject.CodeHash()) @@ -2935,7 +3068,7 @@ func (s *ParallelStateDB) GetState(addr common.Address, hash common.Hash) common } // 3.Get from main StateDB - stateObject := s.StateDB.getStateObject(addr) + stateObject := s.getStateObjectNoSlot(addr) val := common.Hash{} if stateObject != nil { val = stateObject.GetState(s.db, hash) @@ -2970,7 +3103,7 @@ func (s *ParallelStateDB) GetCommittedState(addr common.Address, hash common.Has } // 3. Try to get from main DB - stateObject := s.StateDB.getStateObject(addr) + stateObject := s.getStateObjectNoSlot(addr) val := common.Hash{} if stateObject != nil { val = stateObject.GetCommittedState(s.db, hash) @@ -2993,7 +3126,7 @@ func (s *ParallelStateDB) HasSuicided(addr common.Address) bool { return !exist } - stateObject := s.StateDB.getStateObject(addr) + stateObject := s.getStateObjectNoSlot(addr) if stateObject != nil { return stateObject.suicided } @@ -3216,7 +3349,7 @@ func (s *ParallelStateDB) Suicide(addr common.Address) bool { if stateObject == nil { // 3.Try to get from main StateDB - stateObject = s.StateDB.getStateObject(addr) + stateObject = s.getStateObjectNoSlot(addr) if stateObject == nil { s.parallel.addrStateReadsInSlot[addr] = false // true: exist, false: deleted log.Error("Suicide addr not exist", "txIndex", s.txIndex, "addr", addr) @@ -3253,80 +3386,6 @@ func (s *ParallelStateDB) Suicide(addr common.Address) bool { return true } -// GetOrNewStateObject retrieves a state object or create a new state object if nil. -// dirtyInSlot -> Unconfirmed DB -> main DB -> snapshot, no? create one -func (s *ParallelStateDB) GetOrNewStateObject(addr common.Address) *StateObject { - log.Debug("ParallelStateDB GetOrNewStateObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - var stateObject *StateObject = nil - exist := true - if stateObject, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - return stateObject - } - stateObject, _ = s.getStateObjectFromUnconfirmedDB(addr) - - if stateObject == nil { - stateObject = s.StateDB.getStateObject(addr) // try to get from base db - } - if stateObject == nil || stateObject.deleted || stateObject.suicided { - stateObject = s.createObject(addr) - exist = false - } - - s.parallel.addrStateReadsInSlot[addr] = exist // true: exist, false: not exist - 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. - -// prev is used for CreateAccount to get its balance -// Parallel mode: -// if prev in dirty: revert is ok -// if prev in unconfirmed DB: addr state read record, revert should not put it back -// if prev in main DB: addr state read record, revert should not put it back -// if pre no exist: addr state read record, - -// `prev` is used to handle revert, to recover with the `prev` object -// In Parallel mode, we only need to recover to `prev` in SlotDB, -// a.if it is not in SlotDB, `revert` will remove it from the SlotDB -// b.if it is exist in SlotDB, `revert` will recover to the `prev` in SlotDB -// c.as `snapDestructs` it is the same -func (s *ParallelStateDB) createObject(addr common.Address) (newobj *StateObject) { - log.Debug("ParallelStateDB createObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - // do not get from unconfirmed DB, since it will has problem on revert - prev := s.parallel.dirtiedStateObjectsInSlot[addr] - - var prevdestruct bool - - if s.snap != nil && prev != nil { - _, prevdestruct = s.snapDestructs[prev.address] // fixme, record the snapshot read for create Account - s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct - if !prevdestruct { - // To destroy the previous trie node first and update the trie tree - // with the new object on block commit. - s.snapDestructs[prev.address] = struct{}{} - } - } - newobj = newObject(&s.StateDB, s, s.isParallel, addr, Account{}) - newobj.setNonce(0) // sets the object to dirty - if prev == nil { - s.journal.append(createObjectChange{account: &addr}) - } else { - s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) - } - - // s.parallel.dirtiedStateObjectsInSlot[addr] = newobj // would change the bahavior of AddBalance... - s.parallel.addrStateChangesInSlot[addr] = true // the object sis created - s.parallel.nonceChangesInSlot[addr] = struct{}{} - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.codeChangesInSlot[addr] = struct{}{} - // notice: all the KVs are cleared if any - s.parallel.kvChangesInSlot[addr] = make(StateKeys) - return newobj -} - // CreateAccount explicitly creates a state object. If a state object with the address // already exists the balance is carried over to the new account. // @@ -3345,20 +3404,3 @@ func (s *ParallelStateDB) CreateAccount(addr common.Address) { newObj := s.createObject(addr) newObj.setBalance(new(big.Int).Set(preBalance)) // new big.Int for newObj } - -// for parallel execution mode, try to get dirty StateObject in slot first. -// it is mainly used by journal revert right now. -func (s *ParallelStateDB) getStateObject(addr common.Address) *StateObject { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - return obj - } - return s.StateDB.getStateObject(addr) -} - -func (s *StateDB) getBaseStateDB() *StateDB { - return s -} - -func (s *ParallelStateDB) getBaseStateDB() *StateDB { - return &s.StateDB -} From ca5530730cf75c220564d6a243092f4ee0ac5f9a Mon Sep 17 00:00:00 2001 From: setunapo Date: Fri, 29 Apr 2022 19:39:38 +0800 Subject: [PATCH 08/12] recover vm.StateDB --- core/evm.go | 4 ++-- core/state_transition.go | 2 +- core/vm/evm.go | 10 +++++----- core/vm/interface.go | 5 ++--- eth/tracers/js/tracer.go | 2 +- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/core/evm.go b/core/evm.go index 05c2301f1f..8f69d51499 100644 --- a/core/evm.go +++ b/core/evm.go @@ -100,12 +100,12 @@ func GetHashFn(ref *types.Header, chain ChainContext) func(n uint64) common.Hash // CanTransfer checks whether there are enough funds in the address' account to make a transfer. // This does not take the necessary gas in to account to make the transfer valid. -func CanTransfer(db vm.StateDBer, addr common.Address, amount *big.Int) bool { +func CanTransfer(db vm.StateDB, addr common.Address, amount *big.Int) bool { return db.GetBalance(addr).Cmp(amount) >= 0 } // Transfer subtracts amount from sender and adds amount to recipient using the given Db -func Transfer(db vm.StateDBer, sender, recipient common.Address, amount *big.Int) { +func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) { db.SubBalance(sender, amount) db.AddBalance(recipient, amount) } diff --git a/core/state_transition.go b/core/state_transition.go index 3be5b59e81..e71b980752 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -53,7 +53,7 @@ type StateTransition struct { initialGas uint64 value *big.Int data []byte - state vm.StateDBer + state vm.StateDB evm *vm.EVM } diff --git a/core/vm/evm.go b/core/vm/evm.go index e76e51463d..53e2e8797b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -41,9 +41,9 @@ var EvmPool = sync.Pool{ type ( // CanTransferFunc is the signature of a transfer guard function - CanTransferFunc func(StateDBer, common.Address, *big.Int) bool + CanTransferFunc func(StateDB, common.Address, *big.Int) bool // TransferFunc is the signature of a transfer function - TransferFunc func(StateDBer, common.Address, common.Address, *big.Int) + TransferFunc func(StateDB, common.Address, common.Address, *big.Int) // GetHashFunc returns the n'th block hash in the blockchain // and is used by the BLOCKHASH EVM op code. GetHashFunc func(uint64) common.Hash @@ -124,7 +124,7 @@ type EVM struct { Context BlockContext TxContext // StateDB gives access to the underlying state - StateDB StateDBer + StateDB StateDB // Depth is the current call stack depth int @@ -150,7 +150,7 @@ type EVM struct { // NewEVM returns a new EVM. The returned EVM is not thread safe and should // only ever be used *once*. -func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDBer, chainConfig *params.ChainConfig, vmConfig Config) *EVM { +func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig *params.ChainConfig, vmConfig Config) *EVM { evm := EvmPool.Get().(*EVM) evm.Context = blockCtx evm.TxContext = txCtx @@ -189,7 +189,7 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDBer, chainConf // Reset resets the EVM with a new transaction context.Reset // This is not threadsafe and should only be done very cautiously. -func (evm *EVM) Reset(txCtx TxContext, statedb StateDBer) { +func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) { evm.TxContext = txCtx evm.StateDB = statedb } diff --git a/core/vm/interface.go b/core/vm/interface.go index c9f693ef8a..ad9b05d666 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -23,9 +23,8 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// StateDBer is an EVM database for full state querying. -// It is used as the interface to StateDB right now. -type StateDBer interface { +// StateDB is an EVM database for full state querying. +type StateDB interface { CreateAccount(common.Address) SubBalance(common.Address, *big.Int) diff --git a/eth/tracers/js/tracer.go b/eth/tracers/js/tracer.go index b7ba01df71..b8e035e6f3 100644 --- a/eth/tracers/js/tracer.go +++ b/eth/tracers/js/tracer.go @@ -209,7 +209,7 @@ func (sw *stackWrapper) pushObject(vm *duktape.Context) { // dbWrapper provides a JavaScript wrapper around vm.Database. type dbWrapper struct { - db vm.StateDBer + db vm.StateDB } // pushObject assembles a JSVM object wrapping a swappable database and pushes it From 48599b6fed7274fc1134706a4f1dc038a3fda1c5 Mon Sep 17 00:00:00 2001 From: setunapo Date: Fri, 29 Apr 2022 19:55:12 +0800 Subject: [PATCH 09/12] 0429 good: journal RevertToSnapshot --- core/state/statedb.go | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 5912ffb800..40bd409aa7 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -2746,7 +2746,6 @@ func (s *ParallelStateDB) getDeletedStateObject(addr common.Address) *StateObjec } data, ok := s.getStateObjectFromSnapshotOrTrie(addr) if !ok { - log.Warn("object not exist", "addr", addr) return nil } // Insert into the live set @@ -3404,3 +3403,42 @@ func (s *ParallelStateDB) CreateAccount(addr common.Address) { newObj := s.createObject(addr) newObj.setBalance(new(big.Int).Set(preBalance)) // new big.Int for newObj } + +// RevertToSnapshot reverts all state changes made since the given revision. +func (s *ParallelStateDB) RevertToSnapshot(revid int) { + // Find the snapshot in the stack of valid snapshots. + idx := sort.Search(len(s.validRevisions), func(i int) bool { + return s.validRevisions[i].id >= revid + }) + if idx == len(s.validRevisions) || s.validRevisions[idx].id != revid { + panic(fmt.Errorf("revision id %v cannot be reverted", revid)) + } + snapshot := s.validRevisions[idx].journalIndex + + // Replay the journal to undo changes and remove invalidated snapshots + s.journal.revert(s, snapshot) + s.validRevisions = s.validRevisions[:idx] +} + +// AddRefund adds gas to the refund counter +// journal.append will use ParallelState for revert +func (s *ParallelStateDB) AddRefund(gas uint64) { // fixme: not needed + 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 (s *ParallelStateDB) SubRefund(gas uint64) { // fixme: not needed + s.journal.append(refundChange{prev: s.refund}) + if gas > s.refund { + if s.isParallel { + // we don't need to panic here if we read the wrong state, we just need to redo this transaction + log.Info(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund), "tx", s.thash.String()) + s.parallel.needsRedo = true + return + } + panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund)) + } + s.refund -= gas +} From 4d650fbe493296c13f3311c050765dac005b4f84 Mon Sep 17 00:00:00 2001 From: setunapo Date: Fri, 29 Apr 2022 21:57:44 +0800 Subject: [PATCH 10/12] 0429: universal unconfirmed DB --- core/state_processor.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/state_processor.go b/core/state_processor.go index 4dde8c064a..3398d9f32b 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -80,6 +80,7 @@ type ParallelStateProcessor struct { slotDBsToRelease []*state.ParallelStateDB debugErrorRedoNum int debugConflictRedoNum int + unconfirmedStateDBs *sync.Map // [int]*state.StateDB // fixme: concurrent safe, not use sync.Map? } func NewParallelStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine, parallelNum int, queueSize int) *ParallelStateProcessor { @@ -401,7 +402,7 @@ type SlotState struct { pendingTxReqList []*ParallelTxRequest // maintained by dispatcher for dispatch policy slotdbChan chan *state.ParallelStateDB // dispatch will create and send this slotDB to slot // txReqUnits []*ParallelDispatchUnit // only dispatch can accesssd - unconfirmedStateDBs *sync.Map // [int]*state.StateDB // fixme: concurrent safe, not use sync.Map? + // unconfirmedStateDBs *sync.Map // [int]*state.StateDB // fixme: concurrent safe, not use sync.Map? } type ParallelTxResult struct { @@ -679,7 +680,7 @@ func (p *ParallelStateProcessor) waitUntilNextTxDone(statedb *state.StateDB, gp // the target slot is waiting for new slotDB slotState := p.slotState[result.slotIndex] slotDB := state.NewSlotDB(statedb, consensus.SystemAddress, result.txReq.txIndex, - p.mergedTxIndex, result.keepSystem, slotState.unconfirmedStateDBs) + p.mergedTxIndex, result.keepSystem, p.unconfirmedStateDBs) slotDB.SetSlotIndex(result.slotIndex) p.slotDBsToRelease = append(p.slotDBsToRelease, slotDB) slotState.slotdbChan <- slotDB @@ -866,7 +867,7 @@ func (p *ParallelStateProcessor) runSlotLoop(slotIndex int) { txReq.slotDB = <-curSlot.slotdbChan } result := p.executeInSlot(slotIndex, txReq) - curSlot.unconfirmedStateDBs.Store(txReq.txIndex, txReq.slotDB) + p.unconfirmedStateDBs.Store(txReq.txIndex, txReq.slotDB) curSlot.pendingConfirmChan <- result } } @@ -905,8 +906,9 @@ func (p *ParallelStateProcessor) resetState(txNum int, statedb *state.StateDB) { */ for _, slot := range p.slotState { slot.pendingTxReqList = make([]*ParallelTxRequest, 0) - slot.unconfirmedStateDBs = new(sync.Map) // make(map[int]*state.StateDB), fixme: resue not new? + // slot.unconfirmedStateDBs = new(sync.Map) // make(map[int]*state.StateDB), fixme: resue not new? } + p.unconfirmedStateDBs = new(sync.Map) // make(map[int]*state.StateDB), fixme: resue not new? } // Implement BEP-130: Parallel Transaction Execution. From c27d3b9deb441cf625ecdbc6c5f53ab68dcd6d67 Mon Sep 17 00:00:00 2001 From: setunapo Date: Sat, 30 Apr 2022 06:44:35 +0800 Subject: [PATCH 11/12] StateDB -> ParallelStateDB --- core/state/statedb.go | 1373 +++++++++++++++++++++-------------------- 1 file changed, 687 insertions(+), 686 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 40bd409aa7..c7576a2ff9 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -263,40 +263,6 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) return newStateDB(root, db, snaps) } -// NewSlotDB creates a new State DB based on the provided StateDB. -// With parallel, each execution slot would have its own StateDB. -func NewSlotDB(db *StateDB, systemAddr common.Address, txIndex int, baseTxIndex int, keepSystem bool, - unconfirmedDBs *sync.Map /*map[int]*StateDB*/) *ParallelStateDB { - slotDB := db.CopyForSlot() - slotDB.txIndex = txIndex - slotDB.originalRoot = db.originalRoot - slotDB.parallel.baseStateDB = db - slotDB.parallel.baseTxIndex = baseTxIndex - slotDB.parallel.systemAddress = systemAddr - slotDB.parallel.systemAddressOpsCount = 0 - slotDB.parallel.keepSystemAddressBalance = keepSystem - slotDB.storagePool = NewStoragePool() - slotDB.EnableWriteOnSharedStorage() - for index := baseTxIndex + 1; index < slotDB.txIndex; index++ { // txIndex - unconfirmedDB, ok := unconfirmedDBs.Load(index) - if ok { - slotDB.parallel.unconfirmedDBInShot[index] = unconfirmedDB.(*ParallelStateDB) - } - } - - // All transactions will pay gas fee to the systemAddr at the end, this address is - // deemed to conflict, we handle it specially, clear it now and set it back to the main - // StateDB later; - // But there are transactions that will try to read systemAddr's balance, such as: - // https://bscscan.com/tx/0xcd69755be1d2f55af259441ff5ee2f312830b8539899e82488a21e85bc121a2a. - // It will trigger transaction redo and keepSystem will be marked as true. - if !keepSystem { - slotDB.SetBalance(systemAddr, big.NewInt(0)) - } - - return slotDB -} - // NewWithSharedPool creates a new state with sharedStorge on layer 1.5 func NewWithSharedPool(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error) { statedb, err := newStateDB(root, db, snaps) @@ -351,196 +317,9 @@ func (s *StateDB) getStateObjectFromStateObjects(addr common.Address) (*StateObj return s.loadStateObj(addr) } -// RevertSlotDB keep the Read list for conflict detect, -// discard all state changes except: -// - nonce and balance of from address -// - balance of system address: will be used on merge to update SystemAddress's balance -func (s *StateDB) RevertSlotDB(from common.Address) { - s.parallel.kvChangesInSlot = make(map[common.Address]StateKeys) - - // balance := s.parallel.balanceChangesInSlot[from] - s.parallel.nonceChangesInSlot = make(map[common.Address]struct{}) - s.parallel.balanceChangesInSlot = make(map[common.Address]struct{}, 1) - s.parallel.addrStateChangesInSlot = make(map[common.Address]bool) // 0: created, 1: deleted - - selfStateObject := s.parallel.dirtiedStateObjectsInSlot[from] - systemAddress := s.parallel.systemAddress - systemStateObject := s.parallel.dirtiedStateObjectsInSlot[systemAddress] - s.parallel.dirtiedStateObjectsInSlot = make(map[common.Address]*StateObject, 2) - // keep these elements - s.parallel.dirtiedStateObjectsInSlot[from] = selfStateObject - s.parallel.dirtiedStateObjectsInSlot[systemAddress] = systemStateObject - s.parallel.balanceChangesInSlot[from] = struct{}{} - s.parallel.balanceChangesInSlot[systemAddress] = struct{}{} - s.parallel.nonceChangesInSlot[from] = struct{}{} -} - -// PrepareForParallel prepares for state db to be used in parallel execution mode. -func (s *StateDB) PrepareForParallel() { - s.isParallel = true - s.parallel.stateObjects = &StateObjectSyncMap{} -} - -// MergeSlotDB is for Parallel execution mode, when the transaction has been -// finalized(dirty -> pending) on execution slot, the execution results should be -// merged back to the main StateDB. -// And it will return and keep the slot's change list for later conflict detect. -func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receipt, txIndex int) { - // receipt.Logs use unified log index within a block - // align slotDB's log index to the block stateDB's logSize - for _, l := range slotReceipt.Logs { - l.Index += s.logSize - } - s.logSize += slotDb.logSize - - // before merge, pay the gas fee first: AddBalance to consensus.SystemAddress - systemAddress := slotDb.parallel.systemAddress - if slotDb.parallel.keepSystemAddressBalance { - s.SetBalance(systemAddress, slotDb.GetBalance(systemAddress)) - } else { - s.AddBalance(systemAddress, slotDb.GetBalance(systemAddress)) - } - - // only merge dirty objects - addressesToPrefetch := make([][]byte, 0, len(slotDb.stateObjectsDirty)) - for addr := range slotDb.stateObjectsDirty { - if _, exist := s.stateObjectsDirty[addr]; !exist { - s.stateObjectsDirty[addr] = struct{}{} - } - // system address is EOA account, it should have no storage change - if addr == systemAddress { - continue - } - - // stateObjects: KV, balance, nonce... - dirtyObj, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr] - if !ok { - log.Error("parallel merge, but dirty object not exist!", "SlotIndex", slotDb.parallel.SlotIndex, "txIndex:", slotDb.txIndex, "addr", addr) - continue - } - mainObj, exist := s.loadStateObj(addr) - if !exist { // fixme: it is also state change - // addr not exist on main DB, do ownership transfer - // dirtyObj.db = s - // dirtyObj.finalise(true) // true: prefetch on dispatcher - mainObj = dirtyObj.deepCopy(s) - mainObj.finalise(true) - s.storeStateObj(addr, mainObj) - // fixme: should not delete, would cause unconfirmed DB incorrect? - // delete(slotDb.parallel.dirtiedStateObjectsInSlot, addr) // transfer ownership, fixme: shared read? - if dirtyObj.deleted { - // remove the addr from snapAccounts&snapStorage only when object is deleted. - // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for - // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts - delete(s.snapAccounts, addr) - delete(s.snapStorage, addr) - } - } else { - // addr already in main DB, do merge: balance, KV, code, State(create, suicide) - // can not do copy or ownership transfer directly, since dirtyObj could have outdated - // data(may be updated within the conflict window) - - var newMainObj = mainObj // we don't need to copy the object since the storages are thread safe - if _, ok := slotDb.parallel.addrStateChangesInSlot[addr]; ok { - // there are 3 kinds of state change: - // 1.Suicide - // 2.Empty Delete - // 3.createObject - // a.AddBalance,SetState to an unexist or deleted(suicide, empty delete) address. - // b.CreateAccount: like DAO the fork, regenerate a account carry its balance without KV - // For these state change, do ownership transafer for efficiency: - // dirtyObj.db = s - // newMainObj = dirtyObj - newMainObj = dirtyObj.deepCopy(s) - // should not delete, would cause unconfirmed DB incorrect. - // delete(slotDb.parallel.dirtiedStateObjectsInSlot, addr) // transfer ownership, fixme: shared read? - if dirtyObj.deleted { - // remove the addr from snapAccounts&snapStorage only when object is deleted. - // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for - // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts - delete(s.snapAccounts, addr) - delete(s.snapStorage, addr) - } - } else { - // deepCopy a temporary *StateObject for safety, since slot could read the address, - // dispatch should avoid overwrite the StateObject directly otherwise, it could - // crash for: concurrent map iteration and map write - - if _, balanced := slotDb.parallel.balanceChangesInSlot[addr]; balanced { - newMainObj.SetBalance(dirtyObj.Balance()) - } - if _, coded := slotDb.parallel.codeChangesInSlot[addr]; coded { - newMainObj.code = dirtyObj.code - newMainObj.data.CodeHash = dirtyObj.data.CodeHash - newMainObj.dirtyCode = true - } - if keys, stated := slotDb.parallel.kvChangesInSlot[addr]; stated { - newMainObj.MergeSlotObject(s.db, dirtyObj, keys) - } - if _, nonced := slotDb.parallel.nonceChangesInSlot[addr]; nonced { - // dirtyObj.Nonce() should not be less than newMainObj - newMainObj.setNonce(dirtyObj.Nonce()) - } - } - newMainObj.finalise(true) // true: prefetch on dispatcher - // update the object - s.storeStateObj(addr, newMainObj) - } - addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure - } - - if s.prefetcher != nil && len(addressesToPrefetch) > 0 { - s.prefetcher.prefetch(s.originalRoot, addressesToPrefetch, emptyAddr) // prefetch for trie node of account - } - - for addr := range slotDb.stateObjectsPending { - if _, exist := s.stateObjectsPending[addr]; !exist { - s.stateObjectsPending[addr] = struct{}{} - } - } - - // slotDb.logs: logs will be kept in receipts, no need to do merge - - for hash, preimage := range slotDb.preimages { - s.preimages[hash] = preimage - } - if s.accessList != nil { - // fixme: accessList is not enabled yet, but it should use merge rather than overwrite Copy - s.accessList = slotDb.accessList.Copy() - } - - if slotDb.snaps != nil { - for k := range slotDb.snapDestructs { - // There could be a race condition for parallel transaction execution - // One transaction add balance 0 to an empty address, will delete it(delete empty is enabled). - // While another concurrent transaction could add a none-zero balance to it, make it not empty - // We fixed it by add a addr state read record for add balance 0 - s.snapParallelLock.Lock() - s.snapDestructs[k] = struct{}{} - s.snapParallelLock.Unlock() - } - - // slotDb.snapAccounts should be empty, comment out and to be deleted later - // for k, v := range slotDb.snapAccounts { - // s.snapAccounts[k] = v - // } - // slotDb.snapStorage should be empty, comment out and to be deleted later - // for k, v := range slotDb.snapStorage { - // temp := make(map[string][]byte) - // for kk, vv := range v { - // temp[kk] = vv - // } - // s.snapStorage[k] = temp - // } - } -} - func (s *StateDB) EnableWriteOnSharedStorage() { s.writeOnSharedStorage = true } -func (s *StateDB) SetSlotIndex(index int) { - s.parallel.SlotIndex = index -} // 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 @@ -690,234 +469,14 @@ func (s *StateDB) SubRefund(gas uint64) { s.refund -= gas } -// For Parallel Execution Mode, it can be seen as Penetrated Access: -// ------------------------------------------------------- -// | BaseTxIndex | Unconfirmed Txs... | Current TxIndex | -// ------------------------------------------------------- -// Access from the unconfirmed DB with range&priority: txIndex -1(previous tx) -> baseTxIndex + 1 -func (s *StateDB) getBalanceFromUnconfirmedDB(addr common.Address) *big.Int { - if addr == s.parallel.systemAddress { - // never get systemaddress from unconfirmed DB - return nil - } - - for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { - if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { - // 1.Refer the state of address, exist or not in dirtiedStateObjectsInSlot - if obj, exist := db.parallel.dirtiedStateObjectsInSlot[addr]; exist { - balanceHit := false - if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { - balanceHit = true - } - if _, exist := db.parallel.balanceChangesInSlot[addr]; exist { // only changed balance is reliable - balanceHit = true - } - if !balanceHit { - continue - } - balance := obj.Balance() - if obj.deleted { - balance = common.Big0 - } - return balance - } - } - } - return nil -} - -// Similar to getBalanceFromUnconfirmedDB -func (s *StateDB) getNonceFromUnconfirmedDB(addr common.Address) (uint64, bool) { - if addr == s.parallel.systemAddress { - // never get systemaddress from unconfirmed DB - return 0, false - } - - for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { - if unconfirmedDb, ok := s.parallel.unconfirmedDBInShot[i]; ok { - nonceHit := false - if _, ok := unconfirmedDb.parallel.addrStateChangesInSlot[addr]; ok { - nonceHit = true - } else if _, ok := unconfirmedDb.parallel.nonceChangesInSlot[addr]; ok { - nonceHit = true - } - if !nonceHit { - // nonce refer not hit, try next unconfirmedDb - continue - } - // nonce hit, return the nonce - obj := unconfirmedDb.parallel.dirtiedStateObjectsInSlot[addr] - if obj == nil { - // could not exist, if it is changed but reverted - // fixme: revert should remove the change record - log.Debug("Get nonce from UnconfirmedDB, changed but object not exist, ", - "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) - continue - } - nonce := obj.Nonce() - // deleted object with nonce == 0 - if obj.deleted { - nonce = 0 - } - return nonce, true - } - } - return 0, false -} - -// Similar to getBalanceFromUnconfirmedDB -// It is not only for code, but also codeHash and codeSize, we return the *StateObject for convienence. -func (s *StateDB) getCodeFromUnconfirmedDB(addr common.Address) ([]byte, bool) { - if addr == s.parallel.systemAddress { - // never get systemaddress from unconfirmed DB - return nil, false - } - - for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { - if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { - codeHit := false - if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { - codeHit = true - } - if _, exist := db.parallel.codeChangesInSlot[addr]; exist { - codeHit = true - } - if !codeHit { - // try next unconfirmedDb - continue - } - obj := db.parallel.dirtiedStateObjectsInSlot[addr] - if obj == nil { - // could not exist, if it is changed but reverted - // fixme: revert should remove the change record - log.Debug("Get code from UnconfirmedDB, changed but object not exist, ", - "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) - continue - } - code := obj.Code(s.db) - if obj.deleted { - code = nil - } - return code, true - } - } - return nil, false -} - -// Similar to getCodeFromUnconfirmedDB -// but differ when address is deleted or not exist -func (s *StateDB) getCodeHashFromUnconfirmedDB(addr common.Address) (common.Hash, bool) { - if addr == s.parallel.systemAddress { - // never get systemaddress from unconfirmed DB - return common.Hash{}, false - } - - for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { - if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { - hashHit := false - if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { - hashHit = true - } - if _, exist := db.parallel.codeChangesInSlot[addr]; exist { - hashHit = true - } - if !hashHit { - // try next unconfirmedDb - continue - } - - obj := db.parallel.dirtiedStateObjectsInSlot[addr] - if obj == nil { - // could not exist, if it is changed but reverted - // fixme: revert should remove the change record - log.Debug("Get codeHash from UnconfirmedDB, changed but object not exist, ", - "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) - continue - } - codeHash := common.Hash{} - if !obj.deleted { - codeHash = common.BytesToHash(obj.CodeHash()) - } - return codeHash, true - } - } - return common.Hash{}, false -} - -// Similar to getCodeFromUnconfirmedDB -// It is for address state check of: Exist(), Empty() and HasSuicided() -// Since the unconfirmed DB should have done Finalise() with `deleteEmptyObjects = true` -// If the dirty address is empty or suicided, it will be marked as deleted, so we only need to return `deleted` or not. -func (s *StateDB) getAddrStateFromUnconfirmedDB(addr common.Address) (bool, bool) { - if addr == s.parallel.systemAddress { - // never get systemaddress from unconfirmed DB - return false, false - } - - // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) - for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { - if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { - if exist, ok := db.parallel.addrStateChangesInSlot[addr]; ok { - if _, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; !ok { - // could not exist, if it is changed but reverted - // fixme: revert should remove the change record - log.Debug("Get addr State from UnconfirmedDB, changed but object not exist, ", - "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) - continue - } - - return exist, true - } - } - } - return false, false -} - -func (s *StateDB) getKVFromUnconfirmedDB(addr common.Address, key common.Hash) (common.Hash, bool) { - // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) - for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { - if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { - if obj, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; ok { // if deleted on merge, can get from main StateDB, ok but fixme: concurrent safe - if obj.deleted { - return common.Hash{}, true - } - if _, ok := db.parallel.kvChangesInSlot[addr]; ok { - if val, exist := obj.dirtyStorage.GetValue(key); exist { - return val, true - } - if val, exist := obj.pendingStorage.GetValue(key); exist { // fixme: can be removed - log.Error("Get KV from Unconfirmed StateDB, in pending", - "my txIndex", s.txIndex, "DB's txIndex", i, "addr", addr, - "key", key, "val", val) - return val, true - } - } - } - } - } - return common.Hash{}, false -} - -func (s *StateDB) getStateObjectFromUnconfirmedDB(addr common.Address) (*StateObject, bool) { - // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) - for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { - if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { - if obj, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; ok { // if deleted on merge, can get from main StateDB, ok but fixme: concurrent safe - return obj, true - } - } - } - return nil, false -} - -// Exist reports whether the given account address exists in the state. -// Notably this also returns true for suicided accounts. -func (s *StateDB) Exist(addr common.Address) bool { - log.Debug("StateDB Exist", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - // 3.Try to get from main StateDB - exist := s.getStateObject(addr) != nil - return exist -} +// Exist reports whether the given account address exists in the state. +// Notably this also returns true for suicided accounts. +func (s *StateDB) Exist(addr common.Address) bool { + log.Debug("StateDB Exist", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + // 3.Try to get from main StateDB + exist := s.getStateObject(addr) != nil + return exist +} // Empty returns whether the state object is either non-existent // or empty according to the EIP161 specification (balance = nonce = code = 0) @@ -970,168 +529,40 @@ func (s *StateDB) BaseTxIndex() int { return s.parallel.baseTxIndex } -func (s *StateDB) IsParallelReadsValid() bool { - slotDB := s - if !slotDB.parallel.isSlotDB { - log.Error("IsSlotDBReadsValid slotDB should be slot DB", "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex) - return false - } +func (s *StateDB) GetCode(addr common.Address) []byte { + log.Debug("StateDB GetCode", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - mainDB := slotDB.parallel.baseStateDB - if mainDB.parallel.isSlotDB { - log.Error("IsSlotDBReadsValid s should be main DB", "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex) - return false + stateObject := s.getStateObject(addr) + var code []byte + if stateObject != nil { + code = stateObject.Code(s.db) } - // for nonce - for addr, nonceSlot := range slotDB.parallel.nonceReadsInSlot { - nonceMain := mainDB.GetNonce(addr) - if nonceSlot != nonceMain { - log.Debug("IsSlotDBReadsValid nonce read is invalid", "addr", addr, - "nonceSlot", nonceSlot, "nonceMain", nonceMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - return false - } + return code +} + +func (s *StateDB) GetCodeSize(addr common.Address) int { + log.Debug("StateDB GetCodeSize", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + + // 3. Try to get from main StateObejct + var codeSize int = 0 + stateObject := s.getStateObject(addr) + if stateObject != nil { + codeSize = stateObject.CodeSize(s.db) } - // balance - for addr, balanceSlot := range slotDB.parallel.balanceReadsInSlot { - if addr != s.parallel.systemAddress { // skip balance check for system address - balanceMain := mainDB.GetBalance(addr) - if balanceSlot.Cmp(balanceMain) != 0 { - log.Debug("IsSlotDBReadsValid balance read is invalid", "addr", addr, - "balanceSlot", balanceSlot, "balanceMain", balanceMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - return false - } - } - } - // check code - for addr, codeSlot := range slotDB.parallel.codeReadsInSlot { - codeMain := mainDB.GetCode(addr) - if !bytes.Equal(codeSlot, codeMain) { - log.Debug("IsSlotDBReadsValid code read is invalid", "addr", addr, - "len codeSlot", len(codeSlot), "len codeMain", len(codeMain), "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - return false - } - } - // check codeHash - for addr, codeHashSlot := range slotDB.parallel.codeHashReadsInSlot { - codeHashMain := mainDB.GetCodeHash(addr) - if !bytes.Equal(codeHashSlot.Bytes(), codeHashMain.Bytes()) { - log.Debug("IsSlotDBReadsValid codehash read is invalid", "addr", addr, - "codeHashSlot", codeHashSlot, "codeHashMain", codeHashMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - return false - } - } - // check KV - for addr, slotStorage := range slotDB.parallel.kvReadsInSlot { - conflict := false - slotStorage.Range(func(keySlot, valSlot interface{}) bool { - valMain := mainDB.GetState(addr, keySlot.(common.Hash)) - if !bytes.Equal(valSlot.(common.Hash).Bytes(), valMain.Bytes()) { - log.Debug("IsSlotDBReadsValid KV read is invalid", "addr", addr, - "key", keySlot.(common.Hash), "valSlot", valSlot.(common.Hash), - "valMain", valMain, "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - conflict = true - return false // return false, Range will be terminated. - } - return true // return true, Range will try next KV - }) - if conflict { - return false - } - } - // addr state check - for addr, stateSlot := range slotDB.parallel.addrStateReadsInSlot { - stateMain := false // addr not exist - if mainDB.getStateObject(addr) != nil { - stateMain = true // addr exist in main DB - } - if stateSlot != stateMain { - // skip addr state check for system address - if addr != s.parallel.systemAddress { - log.Debug("IsSlotDBReadsValid addrState read invalid(true: exist, false: not exist)", - "addr", addr, "stateSlot", stateSlot, "stateMain", stateMain, - "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - return false - } - } - } - // snapshot destructs check - - for addr, destructRead := range slotDB.parallel.addrSnapDestructsReadsInSlot { - mainObj := mainDB.getStateObject(addr) - if mainObj == nil { - log.Debug("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", - "addr", addr, "destruct", destructRead, - "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - return false - } - _, destructMain := mainDB.snapDestructs[addr] // addr not exist - if destructRead != destructMain { - log.Debug("IsSlotDBReadsValid snapshot destructs read invalid", - "addr", addr, "destructRead", destructRead, "destructMain", destructMain, - "SlotIndex", slotDB.parallel.SlotIndex, - "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) - return false - } - } - - return true -} - -// For most of the transactions, systemAddressOpsCount should be 3: -// one for SetBalance(0) on NewSlotDB() -// the other is for AddBalance(GasFee) at the end. -// (systemAddressOpsCount > 3) means the transaction tries to access systemAddress, in -// this case, we should redo and keep its balance on NewSlotDB() -func (s *StateDB) SystemAddressRedo() bool { - return s.parallel.systemAddressOpsCount > 4 -} - -// NeedsRedo returns true if there is any clear reason that we need to redo this transaction -func (s *StateDB) NeedsRedo() bool { - return s.parallel.needsRedo -} - -func (s *StateDB) GetCode(addr common.Address) []byte { - log.Debug("StateDB GetCode", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - stateObject := s.getStateObject(addr) - var code []byte - if stateObject != nil { - code = stateObject.Code(s.db) - } - return code -} - -func (s *StateDB) GetCodeSize(addr common.Address) int { - log.Debug("StateDB GetCodeSize", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - // 3. Try to get from main StateObejct - var codeSize int = 0 - stateObject := s.getStateObject(addr) - if stateObject != nil { - codeSize = stateObject.CodeSize(s.db) - } - return codeSize -} - -// return value of GetCodeHash: -// - common.Hash{}: the address does not exist -// - emptyCodeHash: the address exist, but code is empty -// - others: the address exist, and code is not empty -func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { - log.Debug("StateDB GetCodeHash", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - stateObject := s.getStateObject(addr) - codeHash := common.Hash{} - if stateObject != nil { - codeHash = common.BytesToHash(stateObject.CodeHash()) + return codeSize +} + +// return value of GetCodeHash: +// - common.Hash{}: the address does not exist +// - emptyCodeHash: the address exist, but code is empty +// - others: the address exist, and code is not empty +func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { + log.Debug("StateDB GetCodeHash", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + + stateObject := s.getStateObject(addr) + codeHash := common.Hash{} + if stateObject != nil { + codeHash = common.BytesToHash(stateObject.CodeHash()) } return codeHash } @@ -2660,93 +2091,315 @@ func (s *StateDB) GetStorage(address common.Address) *sync.Map { return s.storagePool.getStorage(address) } -type ParallelStateDB struct { - StateDB -} - -func (s *ParallelStateDB) getBaseStateDB() *StateDB { - return &s.StateDB +// PrepareForParallel prepares for state db to be used in parallel execution mode. +func (s *StateDB) PrepareForParallel() { + s.isParallel = true + s.parallel.stateObjects = &StateObjectSyncMap{} } -// for parallel execution mode, try to get dirty StateObject in slot first. -// it is mainly used by journal revert right now. -func (s *ParallelStateDB) getStateObject(addr common.Address) *StateObject { - if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { - return obj +// MergeSlotDB is for Parallel execution mode, when the transaction has been +// finalized(dirty -> pending) on execution slot, the execution results should be +// merged back to the main StateDB. +// And it will return and keep the slot's change list for later conflict detect. +func (s *StateDB) MergeSlotDB(slotDb *ParallelStateDB, slotReceipt *types.Receipt, txIndex int) { + // receipt.Logs use unified log index within a block + // align slotDB's log index to the block stateDB's logSize + for _, l := range slotReceipt.Logs { + l.Index += s.logSize } - // can not call s.StateDB.getStateObject(), since `newObject` need ParallelStateDB as the interface - return s.getStateObjectNoSlot(addr) -} + s.logSize += slotDb.logSize -func (s *ParallelStateDB) getStateObjectNoSlot(addr common.Address) *StateObject { - if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { - return obj + // before merge, pay the gas fee first: AddBalance to consensus.SystemAddress + systemAddress := slotDb.parallel.systemAddress + if slotDb.parallel.keepSystemAddressBalance { + s.SetBalance(systemAddress, slotDb.GetBalance(systemAddress)) + } else { + s.AddBalance(systemAddress, slotDb.GetBalance(systemAddress)) } - return nil -} - -// 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. - -// prev is used for CreateAccount to get its balance -// Parallel mode: -// if prev in dirty: revert is ok -// if prev in unconfirmed DB: addr state read record, revert should not put it back -// if prev in main DB: addr state read record, revert should not put it back -// if pre no exist: addr state read record, - -// `prev` is used to handle revert, to recover with the `prev` object -// In Parallel mode, we only need to recover to `prev` in SlotDB, -// a.if it is not in SlotDB, `revert` will remove it from the SlotDB -// b.if it is exist in SlotDB, `revert` will recover to the `prev` in SlotDB -// c.as `snapDestructs` it is the same -func (s *ParallelStateDB) createObject(addr common.Address) (newobj *StateObject) { - log.Debug("ParallelStateDB createObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - - // do not get from unconfirmed DB, since it will has problem on revert - prev := s.parallel.dirtiedStateObjectsInSlot[addr] - - var prevdestruct bool - if s.snap != nil && prev != nil { - _, prevdestruct = s.snapDestructs[prev.address] // fixme, record the snapshot read for create Account - s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct - if !prevdestruct { - // To destroy the previous trie node first and update the trie tree - // with the new object on block commit. - s.snapDestructs[prev.address] = struct{}{} + // only merge dirty objects + addressesToPrefetch := make([][]byte, 0, len(slotDb.stateObjectsDirty)) + for addr := range slotDb.stateObjectsDirty { + if _, exist := s.stateObjectsDirty[addr]; !exist { + s.stateObjectsDirty[addr] = struct{}{} + } + // system address is EOA account, it should have no storage change + if addr == systemAddress { + continue } - } - newobj = newObject(s, s.isParallel, addr, Account{}) - newobj.setNonce(0) // sets the object to dirty - if prev == nil { - s.journal.append(createObjectChange{account: &addr}) - } else { - s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) - } - // s.parallel.dirtiedStateObjectsInSlot[addr] = newobj // would change the bahavior of AddBalance... - s.parallel.addrStateChangesInSlot[addr] = true // the object sis created - s.parallel.nonceChangesInSlot[addr] = struct{}{} - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.codeChangesInSlot[addr] = struct{}{} - // notice: all the KVs are cleared if any - s.parallel.kvChangesInSlot[addr] = make(StateKeys) - return newobj -} + // stateObjects: KV, balance, nonce... + dirtyObj, ok := slotDb.parallel.dirtiedStateObjectsInSlot[addr] + if !ok { + log.Error("parallel merge, but dirty object not exist!", "SlotIndex", slotDb.parallel.SlotIndex, "txIndex:", slotDb.txIndex, "addr", addr) + continue + } + mainObj, exist := s.loadStateObj(addr) + if !exist { // fixme: it is also state change + // addr not exist on main DB, do ownership transfer + // dirtyObj.db = s + // dirtyObj.finalise(true) // true: prefetch on dispatcher + mainObj = dirtyObj.deepCopy(s) + mainObj.finalise(true) + s.storeStateObj(addr, mainObj) + // fixme: should not delete, would cause unconfirmed DB incorrect? + // delete(slotDb.parallel.dirtiedStateObjectsInSlot, addr) // transfer ownership, fixme: shared read? + if dirtyObj.deleted { + // remove the addr from snapAccounts&snapStorage only when object is deleted. + // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for + // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts + delete(s.snapAccounts, addr) + delete(s.snapStorage, addr) + } + } else { + // addr already in main DB, do merge: balance, KV, code, State(create, suicide) + // can not do copy or ownership transfer directly, since dirtyObj could have outdated + // data(may be updated within the conflict window) -// 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 *ParallelStateDB) getDeletedStateObject(addr common.Address) *StateObject { - // Prefer live objects if any is available - if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { - return obj - } - data, ok := s.getStateObjectFromSnapshotOrTrie(addr) - if !ok { - return nil + var newMainObj = mainObj // we don't need to copy the object since the storages are thread safe + if _, ok := slotDb.parallel.addrStateChangesInSlot[addr]; ok { + // there are 3 kinds of state change: + // 1.Suicide + // 2.Empty Delete + // 3.createObject + // a.AddBalance,SetState to an unexist or deleted(suicide, empty delete) address. + // b.CreateAccount: like DAO the fork, regenerate a account carry its balance without KV + // For these state change, do ownership transafer for efficiency: + // dirtyObj.db = s + // newMainObj = dirtyObj + newMainObj = dirtyObj.deepCopy(s) + // should not delete, would cause unconfirmed DB incorrect. + // delete(slotDb.parallel.dirtiedStateObjectsInSlot, addr) // transfer ownership, fixme: shared read? + if dirtyObj.deleted { + // remove the addr from snapAccounts&snapStorage only when object is deleted. + // "deleted" is not equal to "snapDestructs", since createObject() will add an addr for + // snapDestructs to destroy previous object, while it will keep the addr in snapAccounts & snapAccounts + delete(s.snapAccounts, addr) + delete(s.snapStorage, addr) + } + } else { + // deepCopy a temporary *StateObject for safety, since slot could read the address, + // dispatch should avoid overwrite the StateObject directly otherwise, it could + // crash for: concurrent map iteration and map write + + if _, balanced := slotDb.parallel.balanceChangesInSlot[addr]; balanced { + newMainObj.SetBalance(dirtyObj.Balance()) + } + if _, coded := slotDb.parallel.codeChangesInSlot[addr]; coded { + newMainObj.code = dirtyObj.code + newMainObj.data.CodeHash = dirtyObj.data.CodeHash + newMainObj.dirtyCode = true + } + if keys, stated := slotDb.parallel.kvChangesInSlot[addr]; stated { + newMainObj.MergeSlotObject(s.db, dirtyObj, keys) + } + if _, nonced := slotDb.parallel.nonceChangesInSlot[addr]; nonced { + // dirtyObj.Nonce() should not be less than newMainObj + newMainObj.setNonce(dirtyObj.Nonce()) + } + } + newMainObj.finalise(true) // true: prefetch on dispatcher + // update the object + s.storeStateObj(addr, newMainObj) + } + addressesToPrefetch = append(addressesToPrefetch, common.CopyBytes(addr[:])) // Copy needed for closure + } + + if s.prefetcher != nil && len(addressesToPrefetch) > 0 { + s.prefetcher.prefetch(s.originalRoot, addressesToPrefetch, emptyAddr) // prefetch for trie node of account + } + + for addr := range slotDb.stateObjectsPending { + if _, exist := s.stateObjectsPending[addr]; !exist { + s.stateObjectsPending[addr] = struct{}{} + } + } + + // slotDb.logs: logs will be kept in receipts, no need to do merge + + for hash, preimage := range slotDb.preimages { + s.preimages[hash] = preimage + } + if s.accessList != nil { + // fixme: accessList is not enabled yet, but it should use merge rather than overwrite Copy + s.accessList = slotDb.accessList.Copy() + } + + if slotDb.snaps != nil { + for k := range slotDb.snapDestructs { + // There could be a race condition for parallel transaction execution + // One transaction add balance 0 to an empty address, will delete it(delete empty is enabled). + // While another concurrent transaction could add a none-zero balance to it, make it not empty + // We fixed it by add a addr state read record for add balance 0 + s.snapParallelLock.Lock() + s.snapDestructs[k] = struct{}{} + s.snapParallelLock.Unlock() + } + + // slotDb.snapAccounts should be empty, comment out and to be deleted later + // for k, v := range slotDb.snapAccounts { + // s.snapAccounts[k] = v + // } + // slotDb.snapStorage should be empty, comment out and to be deleted later + // for k, v := range slotDb.snapStorage { + // temp := make(map[string][]byte) + // for kk, vv := range v { + // temp[kk] = vv + // } + // s.snapStorage[k] = temp + // } + } +} + +type ParallelStateDB struct { + StateDB +} + +// NewSlotDB creates a new State DB based on the provided StateDB. +// With parallel, each execution slot would have its own StateDB. +func NewSlotDB(db *StateDB, systemAddr common.Address, txIndex int, baseTxIndex int, keepSystem bool, + unconfirmedDBs *sync.Map /*map[int]*ParallelStateDB*/) *ParallelStateDB { + slotDB := db.CopyForSlot() + slotDB.txIndex = txIndex + slotDB.originalRoot = db.originalRoot + slotDB.parallel.baseStateDB = db + slotDB.parallel.baseTxIndex = baseTxIndex + slotDB.parallel.systemAddress = systemAddr + slotDB.parallel.systemAddressOpsCount = 0 + slotDB.parallel.keepSystemAddressBalance = keepSystem + slotDB.storagePool = NewStoragePool() + slotDB.EnableWriteOnSharedStorage() + for index := baseTxIndex + 1; index < slotDB.txIndex; index++ { // txIndex + unconfirmedDB, ok := unconfirmedDBs.Load(index) + if ok { + slotDB.parallel.unconfirmedDBInShot[index] = unconfirmedDB.(*ParallelStateDB) + } + } + + // All transactions will pay gas fee to the systemAddr at the end, this address is + // deemed to conflict, we handle it specially, clear it now and set it back to the main + // StateDB later; + // But there are transactions that will try to read systemAddr's balance, such as: + // https://bscscan.com/tx/0xcd69755be1d2f55af259441ff5ee2f312830b8539899e82488a21e85bc121a2a. + // It will trigger transaction redo and keepSystem will be marked as true. + if !keepSystem { + slotDB.SetBalance(systemAddr, big.NewInt(0)) + } + + return slotDB +} + +// RevertSlotDB keep the Read list for conflict detect, +// discard all state changes except: +// - nonce and balance of from address +// - balance of system address: will be used on merge to update SystemAddress's balance +func (s *ParallelStateDB) RevertSlotDB(from common.Address) { + s.parallel.kvChangesInSlot = make(map[common.Address]StateKeys) + + // balance := s.parallel.balanceChangesInSlot[from] + s.parallel.nonceChangesInSlot = make(map[common.Address]struct{}) + s.parallel.balanceChangesInSlot = make(map[common.Address]struct{}, 1) + s.parallel.addrStateChangesInSlot = make(map[common.Address]bool) // 0: created, 1: deleted + + selfStateObject := s.parallel.dirtiedStateObjectsInSlot[from] + systemAddress := s.parallel.systemAddress + systemStateObject := s.parallel.dirtiedStateObjectsInSlot[systemAddress] + s.parallel.dirtiedStateObjectsInSlot = make(map[common.Address]*StateObject, 2) + // keep these elements + s.parallel.dirtiedStateObjectsInSlot[from] = selfStateObject + s.parallel.dirtiedStateObjectsInSlot[systemAddress] = systemStateObject + s.parallel.balanceChangesInSlot[from] = struct{}{} + s.parallel.balanceChangesInSlot[systemAddress] = struct{}{} + s.parallel.nonceChangesInSlot[from] = struct{}{} +} + +func (s *ParallelStateDB) getBaseStateDB() *StateDB { + return &s.StateDB +} + +func (s *ParallelStateDB) SetSlotIndex(index int) { + s.parallel.SlotIndex = index +} + +// for parallel execution mode, try to get dirty StateObject in slot first. +// it is mainly used by journal revert right now. +func (s *ParallelStateDB) getStateObject(addr common.Address) *StateObject { + if obj, ok := s.parallel.dirtiedStateObjectsInSlot[addr]; ok { + return obj + } + // can not call s.StateDB.getStateObject(), since `newObject` need ParallelStateDB as the interface + return s.getStateObjectNoSlot(addr) +} + +func (s *ParallelStateDB) getStateObjectNoSlot(addr common.Address) *StateObject { + if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { + return obj + } + return nil +} + +// 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. + +// prev is used for CreateAccount to get its balance +// Parallel mode: +// if prev in dirty: revert is ok +// if prev in unconfirmed DB: addr state read record, revert should not put it back +// if prev in main DB: addr state read record, revert should not put it back +// if pre no exist: addr state read record, + +// `prev` is used to handle revert, to recover with the `prev` object +// In Parallel mode, we only need to recover to `prev` in SlotDB, +// a.if it is not in SlotDB, `revert` will remove it from the SlotDB +// b.if it is exist in SlotDB, `revert` will recover to the `prev` in SlotDB +// c.as `snapDestructs` it is the same +func (s *ParallelStateDB) createObject(addr common.Address) (newobj *StateObject) { + log.Debug("ParallelStateDB createObject", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) + + // do not get from unconfirmed DB, since it will has problem on revert + prev := s.parallel.dirtiedStateObjectsInSlot[addr] + + var prevdestruct bool + + if s.snap != nil && prev != nil { + _, prevdestruct = s.snapDestructs[prev.address] // fixme, record the snapshot read for create Account + s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct + if !prevdestruct { + // To destroy the previous trie node first and update the trie tree + // with the new object on block commit. + s.snapDestructs[prev.address] = struct{}{} + } + } + newobj = newObject(s, s.isParallel, addr, Account{}) + newobj.setNonce(0) // sets the object to dirty + if prev == nil { + s.journal.append(createObjectChange{account: &addr}) + } else { + s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) + } + + // s.parallel.dirtiedStateObjectsInSlot[addr] = newobj // would change the bahavior of AddBalance... + s.parallel.addrStateChangesInSlot[addr] = true // the object sis created + s.parallel.nonceChangesInSlot[addr] = struct{}{} + s.parallel.balanceChangesInSlot[addr] = struct{}{} + s.parallel.codeChangesInSlot[addr] = struct{}{} + // notice: all the KVs are cleared if any + s.parallel.kvChangesInSlot[addr] = make(StateKeys) + return newobj +} + +// 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 *ParallelStateDB) getDeletedStateObject(addr common.Address) *StateObject { + // Prefer live objects if any is available + if obj, _ := s.getStateObjectFromStateObjects(addr); obj != nil { + return obj + } + data, ok := s.getStateObjectFromSnapshotOrTrie(addr) + if !ok { + return nil } // Insert into the live set // if obj, ok := s.loadStateObj(addr); ok { @@ -3442,3 +3095,351 @@ func (s *ParallelStateDB) SubRefund(gas uint64) { // fixme: not needed } s.refund -= gas } + +// For Parallel Execution Mode, it can be seen as Penetrated Access: +// ------------------------------------------------------- +// | BaseTxIndex | Unconfirmed Txs... | Current TxIndex | +// ------------------------------------------------------- +// Access from the unconfirmed DB with range&priority: txIndex -1(previous tx) -> baseTxIndex + 1 +func (s *ParallelStateDB) getBalanceFromUnconfirmedDB(addr common.Address) *big.Int { + if addr == s.parallel.systemAddress { + // never get systemaddress from unconfirmed DB + return nil + } + + for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { + if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { + // 1.Refer the state of address, exist or not in dirtiedStateObjectsInSlot + if obj, exist := db.parallel.dirtiedStateObjectsInSlot[addr]; exist { + balanceHit := false + if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { + balanceHit = true + } + if _, exist := db.parallel.balanceChangesInSlot[addr]; exist { // only changed balance is reliable + balanceHit = true + } + if !balanceHit { + continue + } + balance := obj.Balance() + if obj.deleted { + balance = common.Big0 + } + return balance + } + } + } + return nil +} + +// Similar to getBalanceFromUnconfirmedDB +func (s *ParallelStateDB) getNonceFromUnconfirmedDB(addr common.Address) (uint64, bool) { + if addr == s.parallel.systemAddress { + // never get systemaddress from unconfirmed DB + return 0, false + } + + for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { + if unconfirmedDb, ok := s.parallel.unconfirmedDBInShot[i]; ok { + nonceHit := false + if _, ok := unconfirmedDb.parallel.addrStateChangesInSlot[addr]; ok { + nonceHit = true + } else if _, ok := unconfirmedDb.parallel.nonceChangesInSlot[addr]; ok { + nonceHit = true + } + if !nonceHit { + // nonce refer not hit, try next unconfirmedDb + continue + } + // nonce hit, return the nonce + obj := unconfirmedDb.parallel.dirtiedStateObjectsInSlot[addr] + if obj == nil { + // could not exist, if it is changed but reverted + // fixme: revert should remove the change record + log.Debug("Get nonce from UnconfirmedDB, changed but object not exist, ", + "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) + continue + } + nonce := obj.Nonce() + // deleted object with nonce == 0 + if obj.deleted { + nonce = 0 + } + return nonce, true + } + } + return 0, false +} + +// Similar to getBalanceFromUnconfirmedDB +// It is not only for code, but also codeHash and codeSize, we return the *StateObject for convienence. +func (s *ParallelStateDB) getCodeFromUnconfirmedDB(addr common.Address) ([]byte, bool) { + if addr == s.parallel.systemAddress { + // never get systemaddress from unconfirmed DB + return nil, false + } + + for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { + if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { + codeHit := false + if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { + codeHit = true + } + if _, exist := db.parallel.codeChangesInSlot[addr]; exist { + codeHit = true + } + if !codeHit { + // try next unconfirmedDb + continue + } + obj := db.parallel.dirtiedStateObjectsInSlot[addr] + if obj == nil { + // could not exist, if it is changed but reverted + // fixme: revert should remove the change record + log.Debug("Get code from UnconfirmedDB, changed but object not exist, ", + "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) + continue + } + code := obj.Code(s.db) + if obj.deleted { + code = nil + } + return code, true + } + } + return nil, false +} + +// Similar to getCodeFromUnconfirmedDB +// but differ when address is deleted or not exist +func (s *ParallelStateDB) getCodeHashFromUnconfirmedDB(addr common.Address) (common.Hash, bool) { + if addr == s.parallel.systemAddress { + // never get systemaddress from unconfirmed DB + return common.Hash{}, false + } + + for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { + if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { + hashHit := false + if _, exist := db.parallel.addrStateChangesInSlot[addr]; exist { + hashHit = true + } + if _, exist := db.parallel.codeChangesInSlot[addr]; exist { + hashHit = true + } + if !hashHit { + // try next unconfirmedDb + continue + } + + obj := db.parallel.dirtiedStateObjectsInSlot[addr] + if obj == nil { + // could not exist, if it is changed but reverted + // fixme: revert should remove the change record + log.Debug("Get codeHash from UnconfirmedDB, changed but object not exist, ", + "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) + continue + } + codeHash := common.Hash{} + if !obj.deleted { + codeHash = common.BytesToHash(obj.CodeHash()) + } + return codeHash, true + } + } + return common.Hash{}, false +} + +// Similar to getCodeFromUnconfirmedDB +// It is for address state check of: Exist(), Empty() and HasSuicided() +// Since the unconfirmed DB should have done Finalise() with `deleteEmptyObjects = true` +// If the dirty address is empty or suicided, it will be marked as deleted, so we only need to return `deleted` or not. +func (s *ParallelStateDB) getAddrStateFromUnconfirmedDB(addr common.Address) (bool, bool) { + if addr == s.parallel.systemAddress { + // never get systemaddress from unconfirmed DB + return false, false + } + + // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) + for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { + if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { + if exist, ok := db.parallel.addrStateChangesInSlot[addr]; ok { + if _, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; !ok { + // could not exist, if it is changed but reverted + // fixme: revert should remove the change record + log.Debug("Get addr State from UnconfirmedDB, changed but object not exist, ", + "txIndex", s.txIndex, "referred txIndex", i, "addr", addr) + continue + } + + return exist, true + } + } + } + return false, false +} + +func (s *ParallelStateDB) getKVFromUnconfirmedDB(addr common.Address, key common.Hash) (common.Hash, bool) { + // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) + for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { + if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { + if obj, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; ok { // if deleted on merge, can get from main StateDB, ok but fixme: concurrent safe + if obj.deleted { + return common.Hash{}, true + } + if _, ok := db.parallel.kvChangesInSlot[addr]; ok { + if val, exist := obj.dirtyStorage.GetValue(key); exist { + return val, true + } + if val, exist := obj.pendingStorage.GetValue(key); exist { // fixme: can be removed + log.Error("Get KV from Unconfirmed StateDB, in pending", + "my txIndex", s.txIndex, "DB's txIndex", i, "addr", addr, + "key", key, "val", val) + return val, true + } + } + } + } + } + return common.Hash{}, false +} + +func (s *ParallelStateDB) getStateObjectFromUnconfirmedDB(addr common.Address) (*StateObject, bool) { + // check the unconfirmed DB with range: baseTxIndex -> txIndex -1(previous tx) + for i := s.txIndex - 1; i > s.parallel.baseTxIndex; i-- { + if db, ok := s.parallel.unconfirmedDBInShot[i]; ok { + if obj, ok := db.parallel.dirtiedStateObjectsInSlot[addr]; ok { // if deleted on merge, can get from main StateDB, ok but fixme: concurrent safe + return obj, true + } + } + } + return nil, false +} + +func (s *ParallelStateDB) IsParallelReadsValid() bool { + slotDB := s + if !slotDB.parallel.isSlotDB { + log.Error("IsSlotDBReadsValid slotDB should be slot DB", "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex) + return false + } + + mainDB := slotDB.parallel.baseStateDB + if mainDB.parallel.isSlotDB { + log.Error("IsSlotDBReadsValid s should be main DB", "SlotIndex", slotDB.parallel.SlotIndex, "txIndex", slotDB.txIndex) + return false + } + // for nonce + for addr, nonceSlot := range slotDB.parallel.nonceReadsInSlot { + nonceMain := mainDB.GetNonce(addr) + if nonceSlot != nonceMain { + log.Debug("IsSlotDBReadsValid nonce read is invalid", "addr", addr, + "nonceSlot", nonceSlot, "nonceMain", nonceMain, "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + // balance + for addr, balanceSlot := range slotDB.parallel.balanceReadsInSlot { + if addr != s.parallel.systemAddress { // skip balance check for system address + balanceMain := mainDB.GetBalance(addr) + if balanceSlot.Cmp(balanceMain) != 0 { + log.Debug("IsSlotDBReadsValid balance read is invalid", "addr", addr, + "balanceSlot", balanceSlot, "balanceMain", balanceMain, "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + } + // check code + for addr, codeSlot := range slotDB.parallel.codeReadsInSlot { + codeMain := mainDB.GetCode(addr) + if !bytes.Equal(codeSlot, codeMain) { + log.Debug("IsSlotDBReadsValid code read is invalid", "addr", addr, + "len codeSlot", len(codeSlot), "len codeMain", len(codeMain), "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + // check codeHash + for addr, codeHashSlot := range slotDB.parallel.codeHashReadsInSlot { + codeHashMain := mainDB.GetCodeHash(addr) + if !bytes.Equal(codeHashSlot.Bytes(), codeHashMain.Bytes()) { + log.Debug("IsSlotDBReadsValid codehash read is invalid", "addr", addr, + "codeHashSlot", codeHashSlot, "codeHashMain", codeHashMain, "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + // check KV + for addr, slotStorage := range slotDB.parallel.kvReadsInSlot { + conflict := false + slotStorage.Range(func(keySlot, valSlot interface{}) bool { + valMain := mainDB.GetState(addr, keySlot.(common.Hash)) + if !bytes.Equal(valSlot.(common.Hash).Bytes(), valMain.Bytes()) { + log.Debug("IsSlotDBReadsValid KV read is invalid", "addr", addr, + "key", keySlot.(common.Hash), "valSlot", valSlot.(common.Hash), + "valMain", valMain, "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + conflict = true + return false // return false, Range will be terminated. + } + return true // return true, Range will try next KV + }) + if conflict { + return false + } + } + // addr state check + for addr, stateSlot := range slotDB.parallel.addrStateReadsInSlot { + stateMain := false // addr not exist + if mainDB.getStateObject(addr) != nil { + stateMain = true // addr exist in main DB + } + if stateSlot != stateMain { + // skip addr state check for system address + if addr != s.parallel.systemAddress { + log.Debug("IsSlotDBReadsValid addrState read invalid(true: exist, false: not exist)", + "addr", addr, "stateSlot", stateSlot, "stateMain", stateMain, + "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + } + // snapshot destructs check + + for addr, destructRead := range slotDB.parallel.addrSnapDestructsReadsInSlot { + mainObj := mainDB.getStateObject(addr) + if mainObj == nil { + log.Debug("IsSlotDBReadsValid snapshot destructs read invalid, address should exist", + "addr", addr, "destruct", destructRead, + "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + _, destructMain := mainDB.snapDestructs[addr] // addr not exist + if destructRead != destructMain { + log.Debug("IsSlotDBReadsValid snapshot destructs read invalid", + "addr", addr, "destructRead", destructRead, "destructMain", destructMain, + "SlotIndex", slotDB.parallel.SlotIndex, + "txIndex", slotDB.txIndex, "baseTxIndex", slotDB.parallel.baseTxIndex) + return false + } + } + + return true +} + +// For most of the transactions, systemAddressOpsCount should be 3: +// one for SetBalance(0) on NewSlotDB() +// the other is for AddBalance(GasFee) at the end. +// (systemAddressOpsCount > 3) means the transaction tries to access systemAddress, in +// this case, we should redo and keep its balance on NewSlotDB() +func (s *ParallelStateDB) SystemAddressRedo() bool { + return s.parallel.systemAddressOpsCount > 4 +} + +// NeedsRedo returns true if there is any clear reason that we need to redo this transaction +func (s *ParallelStateDB) NeedsRedo() bool { + return s.parallel.needsRedo +} From f3111af3d2ae69593e65f103077392ee601b1c1e Mon Sep 17 00:00:00 2001 From: setunapo Date: Sat, 30 Apr 2022 06:50:41 +0800 Subject: [PATCH 12/12] WIP: SetStateObject ParallelStateDB. storeStateObj --- core/state/interface.go | 3 +- core/state/journal.go | 56 +++++++++--------- core/state/statedb.go | 126 +++++++++++----------------------------- 3 files changed, 63 insertions(+), 122 deletions(-) diff --git a/core/state/interface.go b/core/state/interface.go index 68847fe59c..2362ac828b 100644 --- a/core/state/interface.go +++ b/core/state/interface.go @@ -26,8 +26,9 @@ import ( // StateDBer is copied from vm/interface.go // It is used by StateObject & Journal right now, to abstract StateDB & ParallelStateDB type StateDBer interface { - getStateObject(common.Address) *StateObject // only accessible for journal getBaseStateDB() *StateDB + getStateObject(common.Address) *StateObject // only accessible for journal + storeStateObj(common.Address, *StateObject) // only accessible for journal CreateAccount(common.Address) diff --git a/core/state/journal.go b/core/state/journal.go index d11096cd18..e267205688 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -58,10 +58,10 @@ func (j *journal) append(entry journalEntry) { // revert undoes a batch of journalled modifications along with any reverted // dirty handling too. -func (j *journal) revert(statedb StateDBer, snapshot int) { +func (j *journal) revert(dber StateDBer, snapshot int) { for i := len(j.entries) - 1; i >= snapshot; i-- { // Undo the changes made by the operation - j.entries[i].revert(statedb) + j.entries[i].revert(dber) // Drop any dirty tracking induced by the change if addr := j.entries[i].dirtied(); addr != nil { @@ -141,8 +141,8 @@ type ( } ) -func (ch createObjectChange) revert(itf StateDBer) { - s := itf.getBaseStateDB() +func (ch createObjectChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() if s.parallel.isSlotDB { delete(s.parallel.dirtiedStateObjectsInSlot, *ch.account) delete(s.parallel.addrStateChangesInSlot, *ch.account) @@ -160,14 +160,14 @@ func (ch createObjectChange) dirtied() *common.Address { return ch.account } -func (ch resetObjectChange) revert(itf StateDBer) { - s := itf.getBaseStateDB() +func (ch resetObjectChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() if s.parallel.isSlotDB { // ch.prev must be from dirtiedStateObjectsInSlot, put it back s.parallel.dirtiedStateObjectsInSlot[ch.prev.address] = ch.prev } else { // ch.prev was got from main DB, put it back to main DB. - s.SetStateObject(ch.prev) + s.storeStateObj(ch.prev.address, ch.prev) } if !ch.prevdestruct && s.snap != nil { delete(s.snapDestructs, ch.prev.address) @@ -178,8 +178,8 @@ func (ch resetObjectChange) dirtied() *common.Address { return nil } -func (ch suicideChange) revert(s StateDBer) { - obj := s.getStateObject(*ch.account) +func (ch suicideChange) revert(dber StateDBer) { + obj := dber.getStateObject(*ch.account) if obj != nil { obj.suicided = ch.prev obj.setBalance(ch.prevbalance) @@ -192,47 +192,47 @@ func (ch suicideChange) dirtied() *common.Address { var ripemd = common.HexToAddress("0000000000000000000000000000000000000003") -func (ch touchChange) revert(statedb StateDBer) { +func (ch touchChange) revert(dber StateDBer) { } func (ch touchChange) dirtied() *common.Address { return ch.account } -func (ch balanceChange) revert(s StateDBer) { - s.getStateObject(*ch.account).setBalance(ch.prev) +func (ch balanceChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setBalance(ch.prev) } func (ch balanceChange) dirtied() *common.Address { return ch.account } -func (ch nonceChange) revert(s StateDBer) { - s.getStateObject(*ch.account).setNonce(ch.prev) +func (ch nonceChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setNonce(ch.prev) } func (ch nonceChange) dirtied() *common.Address { return ch.account } -func (ch codeChange) revert(s StateDBer) { - s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) +func (ch codeChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode) } func (ch codeChange) dirtied() *common.Address { return ch.account } -func (ch storageChange) revert(s StateDBer) { - s.getStateObject(*ch.account).setState(ch.key, ch.prevalue) +func (ch storageChange) revert(dber StateDBer) { + dber.getStateObject(*ch.account).setState(ch.key, ch.prevalue) } func (ch storageChange) dirtied() *common.Address { return ch.account } -func (ch refundChange) revert(itf StateDBer) { - s := itf.getBaseStateDB() +func (ch refundChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() s.refund = ch.prev } @@ -240,8 +240,8 @@ func (ch refundChange) dirtied() *common.Address { return nil } -func (ch addLogChange) revert(itf StateDBer) { - s := itf.getBaseStateDB() +func (ch addLogChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() logs := s.logs[ch.txhash] if len(logs) == 1 { @@ -256,8 +256,8 @@ func (ch addLogChange) dirtied() *common.Address { return nil } -func (ch addPreimageChange) revert(itf StateDBer) { - s := itf.getBaseStateDB() +func (ch addPreimageChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() delete(s.preimages, ch.hash) } @@ -265,8 +265,8 @@ func (ch addPreimageChange) dirtied() *common.Address { return nil } -func (ch accessListAddAccountChange) revert(itf StateDBer) { - s := itf.getBaseStateDB() +func (ch accessListAddAccountChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() /* One important invariant here, is that whenever a (addr, slot) is added, if the addr is not already present, the add causes two journal entries: @@ -285,8 +285,8 @@ func (ch accessListAddAccountChange) dirtied() *common.Address { return nil } -func (ch accessListAddSlotChange) revert(itf StateDBer) { - s := itf.getBaseStateDB() +func (ch accessListAddSlotChange) revert(dber StateDBer) { + s := dber.getBaseStateDB() if s.accessList != nil { s.accessList.DeleteSlot(*ch.address, *ch.slot) } diff --git a/core/state/statedb.go b/core/state/statedb.go index c7576a2ff9..217bdd6a1b 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -97,21 +97,9 @@ func (s *StateDB) storeStateObj(addr common.Address, stateObject *StateObject) { if s.isParallel { // When a state object is stored into s.parallel.stateObjects, // it belongs to base StateDB, it is confirmed and valid. - if s.parallel.isSlotDB { - // the object could be create in SlotDB, if it got the object from DB and - // update it to the shared `s.parallel.stateObjects`` - stateObject.db = s.parallel.baseStateDB - stateObject.dbItf = s.parallel.baseStateDB - stateObject.db.storeParallelLock.Lock() - if _, ok := s.parallel.stateObjects.Load(addr); !ok { - s.parallel.stateObjects.Store(addr, stateObject) - } - stateObject.db.storeParallelLock.Unlock() - } else { - stateObject.db.storeParallelLock.Lock() - s.parallel.stateObjects.Store(addr, stateObject) - stateObject.db.storeParallelLock.Unlock() - } + stateObject.db.storeParallelLock.Lock() + s.parallel.stateObjects.Store(addr, stateObject) + stateObject.db.storeParallelLock.Unlock() } else { s.stateObjects[addr] = stateObject } @@ -128,7 +116,7 @@ func (s *StateDB) deleteStateObj(addr common.Address) { // For parallel mode only type ParallelState struct { - isSlotDB bool // isSlotDB denotes StateDB is used in slot + isSlotDB bool // denotes StateDB is used in slot, we will try to remove it SlotIndex int // fixme: to be removed // stateObjects holds the state objects in the base slot db // the reason for using stateObjects instead of stateObjects on the outside is @@ -458,12 +446,6 @@ func (s *StateDB) AddRefund(gas uint64) { func (s *StateDB) SubRefund(gas uint64) { s.journal.append(refundChange{prev: s.refund}) if gas > s.refund { - if s.isParallel { - // we don't need to panic here if we read the wrong state, we just need to redo this transaction - log.Info(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund), "tx", s.thash.String()) - s.parallel.needsRedo = true - return - } panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund)) } s.refund -= gas @@ -543,7 +525,6 @@ func (s *StateDB) GetCode(addr common.Address) []byte { func (s *StateDB) GetCodeSize(addr common.Address) int { log.Debug("StateDB GetCodeSize", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - // 3. Try to get from main StateObejct var codeSize int = 0 stateObject := s.getStateObject(addr) if stateObject != nil { @@ -568,12 +549,6 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash { } // GetState retrieves a value from the given account's storage trie. -// For parallel mode wih, get from the state in order: -// -> self dirty, both Slot & MainProcessor -// -> pending of self: Slot on merge -// -> pending of unconfirmed DB -// -> pending of main StateDB -// -> origin func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { log.Debug("StateDB GetState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) stateObject := s.getStateObject(addr) @@ -624,7 +599,6 @@ func (s *StateDB) GetStorageProofByHash(a common.Address, key common.Hash) ([][] // GetCommittedState retrieves a value from the given account's committed storage trie. func (s *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { log.Debug("StateDB GetCommittedState", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) - // 3. Try to get from main DB stateObject := s.getStateObject(addr) val := common.Hash{} if stateObject != nil { @@ -662,32 +636,6 @@ func (s *StateDB) HasSuicided(addr common.Address) bool { * SETTERS */ -// the source mainObj should be got from the main StateDB -// we have to update its nonce, balance, code if they have updated in the unconfirmed DBs -/* -func (s *StateDB) unconfirmedLightCopy(mainObj *StateObject) *StateObject { - newObj := mainObj.lightCopy(s) // copied nonce, balance, code from base DB - - // do balance fixup only when it exist in unconfirmed DB - if nonce, ok := s.getNonceFromUnconfirmedDB(mainObj.address); ok { - // code got from unconfirmed DB - newObj.setNonce(nonce) - } - - // do balance fixup - if balance := s.getBalanceFromUnconfirmedDB(mainObj.address); balance != nil { - // balance got from unconfirmed DB - newObj.setBalance(balance) - } - // do code fixup - if codeObj, ok := s.getCodeFromUnconfirmedDB(mainObj.address); ok { - newObj.setCode(crypto.Keccak256Hash(codeObj), codeObj) // fixme: to confirm if we should use "codeObj.Code(db)" - newObj.dirtyCode = false // copy does not make the code dirty, - } - return newObj -} -*/ - // AddBalance adds amount to the account associated with addr. func (s *StateDB) AddBalance(addr common.Address, amount *big.Int) { log.Debug("StateDB AddBalance", "SlotxIndex", s.parallel.SlotIndex, "txIndex", s.TxIndex()) @@ -910,13 +858,13 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *StateObject { // return obj //} obj := newObject(s, s.isParallel, addr, *data) - s.SetStateObject(obj) + s.storeStateObj(addr, obj) return obj } -func (s *StateDB) SetStateObject(object *StateObject) { - s.storeStateObj(object.Address(), object) -} +// func (s *StateDB) SetStateObject(object *StateObject) { +// s.storeStateObj(object.Address(), object) +// } // GetOrNewStateObject retrieves a state object or create a new state object if nil. // dirtyInSlot -> Unconfirmed DB -> main DB -> snapshot, no? create one @@ -947,26 +895,15 @@ func (s *StateDB) GetOrNewStateObject(addr common.Address) *StateObject { // b.if it is exist in SlotDB, `revert` will recover to the `prev` in SlotDB // c.as `snapDestructs` it is the same func (s *StateDB) createObject(addr common.Address) (newobj *StateObject) { - var prev *StateObject = nil - if s.parallel.isSlotDB { - // do not get from unconfirmed DB, since it will has problem on revert - prev = s.parallel.dirtiedStateObjectsInSlot[addr] - } else { - prev = s.getDeletedStateObject(addr) // Note, prev might have been deleted, we need that! - } - + 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.address] // fixme, record the snapshot read for create Account - if s.parallel.isSlotDB { - s.parallel.addrSnapDestructsReadsInSlot[addr] = prevdestruct - } + _, prevdestruct = s.snapDestructs[prev.address] if !prevdestruct { // To destroy the previous trie node first and update the trie tree // with the new object on block commit. s.snapDestructs[prev.address] = struct{}{} - } } newobj = newObject(s, s.isParallel, addr, Account{}) @@ -977,17 +914,7 @@ func (s *StateDB) createObject(addr common.Address) (newobj *StateObject) { s.journal.append(resetObjectChange{prev: prev, prevdestruct: prevdestruct}) } - if s.parallel.isSlotDB { - // s.parallel.dirtiedStateObjectsInSlot[addr] = newobj // would change the bahavior of AddBalance... - s.parallel.addrStateChangesInSlot[addr] = true // the object sis created - s.parallel.nonceChangesInSlot[addr] = struct{}{} - s.parallel.balanceChangesInSlot[addr] = struct{}{} - s.parallel.codeChangesInSlot[addr] = struct{}{} - // notice: all the KVs are cleared if any - s.parallel.kvChangesInSlot[addr] = make(StateKeys) - } else { - s.SetStateObject(newobj) - } + s.storeStateObj(addr, newobj) return newobj } @@ -1005,7 +932,7 @@ func (s *StateDB) CreateAccount(addr common.Address) { // no matter it is got from dirty, unconfirmed or main DB // if addr not exist, preBalance will be common.Big0, it is same as new(big.Int) which // is the value newObject(), - preBalance := s.GetBalance(addr) // parallel balance read will be recorded inside of GetBalance + preBalance := s.GetBalance(addr) newObj := s.createObject(addr) newObj.setBalance(new(big.Int).Set(preBalance)) // new big.Int for newObj } @@ -2331,6 +2258,20 @@ func (s *ParallelStateDB) getStateObject(addr common.Address) *StateObject { return s.getStateObjectNoSlot(addr) } +func (s *ParallelStateDB) storeStateObj(addr common.Address, stateObject *StateObject) { + // When a state object is stored into s.parallel.stateObjects, + // it belongs to base StateDB, it is confirmed and valid. + stateObject.db = s.parallel.baseStateDB + stateObject.dbItf = s.parallel.baseStateDB + // the object could be create in SlotDB, if it got the object from DB and + // update it to the shared `s.parallel.stateObjects`` + stateObject.db.storeParallelLock.Lock() + if _, ok := s.parallel.stateObjects.Load(addr); !ok { + s.parallel.stateObjects.Store(addr, stateObject) + } + stateObject.db.storeParallelLock.Unlock() +} + func (s *ParallelStateDB) getStateObjectNoSlot(addr common.Address) *StateObject { if obj := s.getDeletedStateObject(addr); obj != nil && !obj.deleted { return obj @@ -2409,7 +2350,8 @@ func (s *ParallelStateDB) getDeletedStateObject(addr common.Address) *StateObjec // this is why we have to use a seperate getDeletedStateObject for ParallelStateDB // `s` has to be the ParallelStateDB obj := newObject(s, s.isParallel, addr, *data) - s.SetStateObject(obj) + s.storeStateObj(addr, obj) + // s.SetStateObject(obj) return obj } @@ -3085,13 +3027,11 @@ func (s *ParallelStateDB) AddRefund(gas uint64) { // fixme: not needed func (s *ParallelStateDB) SubRefund(gas uint64) { // fixme: not needed s.journal.append(refundChange{prev: s.refund}) if gas > s.refund { - if s.isParallel { - // we don't need to panic here if we read the wrong state, we just need to redo this transaction - log.Info(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund), "tx", s.thash.String()) - s.parallel.needsRedo = true - return - } - panic(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund)) + // we don't need to panic here if we read the wrong state in parallelm mode + // we just need to redo this transaction + log.Info(fmt.Sprintf("Refund counter below zero (gas: %d > refund: %d)", gas, s.refund), "tx", s.thash.String()) + s.parallel.needsRedo = true + return } s.refund -= gas }