diff --git a/core/state/journal.go b/core/state/journal.go index 1b7c224dc0..c612aa9853 100644 --- a/core/state/journal.go +++ b/core/state/journal.go @@ -18,6 +18,7 @@ package state import ( "math/big" + "sync" "github.com/ethereum/go-ethereum/common" ) @@ -38,6 +39,7 @@ type journalEntry interface { type journal struct { entries []journalEntry // Current changes tracked by the journal dirties map[common.Address]int // Dirty accounts and the number of changes + mutex sync.Mutex } // newJournal create a new initialized journal. @@ -49,6 +51,8 @@ func newJournal() *journal { // append inserts a new modification entry to the end of the change journal. func (j *journal) append(entry journalEntry) { + defer j.mutex.Unlock() + j.mutex.Lock() j.entries = append(j.entries, entry) if addr := entry.dirtied(); addr != nil { j.dirties[*addr]++ @@ -58,6 +62,8 @@ 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) { + defer j.mutex.Unlock() + j.mutex.Lock() for i := len(j.entries) - 1; i >= snapshot; i-- { // Undo the changes made by the operation j.entries[i].revert(statedb) @@ -76,6 +82,8 @@ func (j *journal) revert(statedb *StateDB, snapshot int) { // otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD // precompile consensus exception. func (j *journal) dirty(addr common.Address) { + defer j.mutex.Unlock() + j.mutex.Lock() j.dirties[addr]++ } @@ -145,6 +153,8 @@ type ( ) func (ch createObjectChange) revert(s *StateDB) { + defer s.mutex.Unlock() + s.mutex.Lock() delete(s.stateObjects, *ch.account) delete(s.stateObjectsDirty, *ch.account) } diff --git a/core/state/statedb.go b/core/state/statedb.go index e015002725..27b6b08a39 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -22,6 +22,7 @@ import ( "fmt" "math/big" "sort" + "sync" "time" "github.com/ethereum/go-ethereum/common" @@ -74,6 +75,9 @@ type StateDB struct { snapAccounts map[common.Hash][]byte snapStorage map[common.Hash]map[common.Hash][]byte + mutex sync.Mutex + journalMutex sync.Mutex + // Quorum - a trie to hold extra account information that cannot be stored in the accounts trie accountExtraDataTrie Trie @@ -332,9 +336,11 @@ func (s *StateDB) Reset(root common.Hash) error { return err } s.trie = tr + s.mutex.Lock() s.stateObjects = make(map[common.Address]*stateObject) s.stateObjectsPending = make(map[common.Address]struct{}) s.stateObjectsDirty = make(map[common.Address]struct{}) + s.mutex.Unlock() s.thash = common.Hash{} s.bhash = common.Hash{} s.txIndex = 0 @@ -710,6 +716,8 @@ func (s *StateDB) getDeletedStateObject(addr common.Address) *stateObject { } func (s *StateDB) setStateObject(object *stateObject) { + defer s.mutex.Unlock() + s.mutex.Lock() s.stateObjects[object.Address()] = object } @@ -797,15 +805,27 @@ func (db *StateDB) ForEachStorage(addr common.Address, cb func(key, value common // Copy creates a deep, independent copy of the state. // Snapshots of the copied state cannot be applied to the copy. func (s *StateDB) Copy() *StateDB { + s.journalMutex.Lock() + journal := s.journal + s.journalMutex.Unlock() + + journal.mutex.Lock() + size := len(journal.dirties) + dirties := make([]common.Address, 0, size) + for addr := range journal.dirties { + dirties = append(dirties, addr) + } + journal.mutex.Unlock() + // Copy all the basic fields, initialize the memory ones state := &StateDB{ db: s.db, trie: s.db.CopyTrie(s.trie), // Quorum - Privacy Enhancements accountExtraDataTrie: s.db.CopyTrie(s.accountExtraDataTrie), - stateObjects: make(map[common.Address]*stateObject, len(s.journal.dirties)), + stateObjects: make(map[common.Address]*stateObject, size), stateObjectsPending: make(map[common.Address]struct{}, len(s.stateObjectsPending)), - stateObjectsDirty: make(map[common.Address]struct{}, len(s.journal.dirties)), + stateObjectsDirty: make(map[common.Address]struct{}, size), refund: s.refund, logs: make(map[common.Hash][]*types.Log, len(s.logs)), logSize: s.logSize, @@ -813,8 +833,10 @@ func (s *StateDB) Copy() *StateDB { journal: newJournal(), hasher: crypto.NewKeccakState(), } + + s.mutex.Lock() // Copy the dirty states, logs, and preimages - for addr := range s.journal.dirties { + for _, addr := range dirties { // As documented [here](https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527), // and in the Finalise-method, there is a case where an object is in the journal but not // in the stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we need to check for @@ -844,6 +866,7 @@ func (s *StateDB) Copy() *StateDB { } state.stateObjectsDirty[addr] = struct{}{} } + s.mutex.Unlock() for hash, logs := range s.logs { cpy := make([]*types.Log, len(logs)) for i, l := range logs { @@ -930,7 +953,13 @@ func (s *StateDB) GetRefund() uint64 { // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) { addressesToPrefetch := make([][]byte, 0, len(s.journal.dirties)) + s.journal.mutex.Lock() + dirties := make([]common.Address, 0, len(s.journal.dirties)) for addr := range s.journal.dirties { + dirties = append(dirties, addr) + } + s.journal.mutex.Unlock() + for _, addr := range dirties { obj, exist := s.stateObjects[addr] if !exist { // ripeMD is 'touched' at block 1714175, in tx 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2 @@ -956,8 +985,10 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { } else { obj.finalise(true) // Prefetch slots in the background } + s.mutex.Lock() s.stateObjectsPending[addr] = struct{}{} s.stateObjectsDirty[addr] = struct{}{} + s.mutex.Unlock() // At this point, also ship the address off to the precacher. The precacher // will start loading tries, and when the change is eventually committed, @@ -978,6 +1009,8 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Finalise all the dirty storage states and write them into the tries s.Finalise(deleteEmptyObjects) + s.mutex.Lock() + defer s.mutex.Unlock() // If there was a trie prefetcher operating, it gets aborted and irrevocably // modified after we start retrieving tries. Remove it from the statedb after // this round of use. @@ -1042,6 +1075,8 @@ func (s *StateDB) Prepare(thash, bhash common.Hash, ti int) { } func (s *StateDB) clearJournalAndRefund() { + defer s.journalMutex.Unlock() + s.journalMutex.Lock() if len(s.journal.entries) > 0 { s.journal = newJournal() s.refund = 0 @@ -1059,6 +1094,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { // Finalize any pending changes and merge everything into the tries s.IntermediateRoot(deleteEmptyObjects) + s.mutex.Lock() // Commit objects to the trie, measuring the elapsed time codeWriter := s.db.TrieDB().DiskDB().NewBatch() for addr := range s.stateObjectsDirty { @@ -1077,6 +1113,7 @@ func (s *StateDB) Commit(deleteEmptyObjects bool) (common.Hash, error) { if len(s.stateObjectsDirty) > 0 { s.stateObjectsDirty = make(map[common.Address]struct{}) } + s.mutex.Unlock() if codeWriter.ValueSize() > 0 { if err := codeWriter.Write(); err != nil { log.Crit("Failed to commit dirty codes", "error", err) diff --git a/eth/backend.go b/eth/backend.go index fa75893f0b..3bbdb57c61 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -21,6 +21,7 @@ import ( "errors" "fmt" "math/big" + "os" "runtime" "sync" "sync/atomic" @@ -238,6 +239,16 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if err != nil { return nil, err } + defer func() { + if p := recover(); p != nil { + log.Error("panic occurred", "err", p) + err := eth.Stop() + if err != nil { + log.Error("error while closing", "err", err) + } + os.Exit(1) + } + }() // Rewind the chain in case of an incompatible config upgrade. if compat, ok := genesisErr.(*params.ConfigCompatError); ok { log.Warn("Rewinding chain to upgrade configuration", "err", compat)