From 0c60ba5f6c74d348ab09c4a2d3d68e72da41e1d3 Mon Sep 17 00:00:00 2001 From: Ben Johnson Date: Fri, 18 May 2018 11:05:12 -0600 Subject: [PATCH] miner: Add Work.stateMu to protect Work.state. Previously, the `Work.state` was protected by the `Worker.currentMu`. However, when the worker sends work to the `Agent` and the `Agent` sends the work back to the worker via the `recv` channel, there is no mutex available to protect access/mutation. The worker calls `WriteBlockWithState()` which in turn calls `Commit()` which then clears `StateDB.stateObjectsDirty` without a mutex. That causes the panic when iterating over the map in `Copy()`. This commit adds a `stateMu` mutex to the `Work` type so the mutex can follow the work between the agent & worker. --- miner/worker.go | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/miner/worker.go b/miner/worker.go index 48b0b27652..9dd43b3d10 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -66,6 +66,7 @@ type Work struct { config *params.ChainConfig signer types.Signer + stateMu sync.RWMutex // protects state state *state.StateDB // apply state changes here ancestors *set.Set // ancestor set (used for checking uncle parent validity) family *set.Set // family set (used for checking uncle invalidity) @@ -184,6 +185,8 @@ func (self *worker) pending() (*types.Block, *state.StateDB) { self.currentMu.Lock() defer self.currentMu.Unlock() + self.current.stateMu.RLock() + defer self.current.stateMu.RUnlock() return self.current.Block, self.current.state.Copy() } @@ -263,12 +266,16 @@ func (self *worker) update() { // Apply transaction to the pending state if we're not mining if atomic.LoadInt32(&self.mining) == 0 { self.currentMu.Lock() + self.current.stateMu.Lock() + acc, _ := types.Sender(self.current.signer, ev.Tx) txs := map[common.Address]types.Transactions{acc: {ev.Tx}} txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs) self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase) self.updateSnapshot() + + self.current.stateMu.Unlock() self.currentMu.Unlock() } else { // If we're mining, but nothing is being processed, wake on new transactions @@ -307,6 +314,8 @@ func (self *worker) wait() { l.BlockHash = block.Hash() } } + + work.stateMu.Lock() for _, log := range work.state.Logs() { log.BlockHash = block.Hash() } @@ -315,6 +324,8 @@ func (self *worker) wait() { log.Error("Failed writing block to chain", "err", err) continue } + work.stateMu.Unlock() + // check if canon block and write transactions if stat == core.CanonStatTy { // implicit by posting ChainHeadEvent @@ -322,10 +333,13 @@ func (self *worker) wait() { } // Broadcast the block and announce chain insertion event self.mux.Post(core.NewMinedBlockEvent{Block: block}) - var ( - events []interface{} - logs = work.state.Logs() - ) + + var events []interface{} + + work.stateMu.RLock() + logs := work.state.Logs() + work.stateMu.RUnlock() + events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) if stat == core.CanonStatTy { events = append(events, core.ChainHeadEvent{Block: block}) @@ -444,6 +458,11 @@ func (self *worker) commitNewWork() { log.Error("Failed to create mining context", "err", err) return } + + // Obtain current work's state lock after we receive new work assignment. + self.current.stateMu.Lock() + defer self.current.stateMu.Unlock() + // Create the current work task and check any fork transitions needed work := self.current if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 {