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.
This commit is contained in:
Ben Johnson 2018-05-18 11:05:12 -06:00
parent f9c456e02d
commit 0c60ba5f6c
No known key found for this signature in database
GPG key ID: 81741CD251883081

View file

@ -66,6 +66,7 @@ type Work struct {
config *params.ChainConfig config *params.ChainConfig
signer types.Signer signer types.Signer
stateMu sync.RWMutex // protects state
state *state.StateDB // apply state changes here state *state.StateDB // apply state changes here
ancestors *set.Set // ancestor set (used for checking uncle parent validity) ancestors *set.Set // ancestor set (used for checking uncle parent validity)
family *set.Set // family set (used for checking uncle invalidity) 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() self.currentMu.Lock()
defer self.currentMu.Unlock() defer self.currentMu.Unlock()
self.current.stateMu.RLock()
defer self.current.stateMu.RUnlock()
return self.current.Block, self.current.state.Copy() 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 // Apply transaction to the pending state if we're not mining
if atomic.LoadInt32(&self.mining) == 0 { if atomic.LoadInt32(&self.mining) == 0 {
self.currentMu.Lock() self.currentMu.Lock()
self.current.stateMu.Lock()
acc, _ := types.Sender(self.current.signer, ev.Tx) acc, _ := types.Sender(self.current.signer, ev.Tx)
txs := map[common.Address]types.Transactions{acc: {ev.Tx}} txs := map[common.Address]types.Transactions{acc: {ev.Tx}}
txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs) txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs)
self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase) self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)
self.updateSnapshot() self.updateSnapshot()
self.current.stateMu.Unlock()
self.currentMu.Unlock() self.currentMu.Unlock()
} else { } else {
// If we're mining, but nothing is being processed, wake on new transactions // 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() l.BlockHash = block.Hash()
} }
} }
work.stateMu.Lock()
for _, log := range work.state.Logs() { for _, log := range work.state.Logs() {
log.BlockHash = block.Hash() log.BlockHash = block.Hash()
} }
@ -315,6 +324,8 @@ func (self *worker) wait() {
log.Error("Failed writing block to chain", "err", err) log.Error("Failed writing block to chain", "err", err)
continue continue
} }
work.stateMu.Unlock()
// check if canon block and write transactions // check if canon block and write transactions
if stat == core.CanonStatTy { if stat == core.CanonStatTy {
// implicit by posting ChainHeadEvent // implicit by posting ChainHeadEvent
@ -322,10 +333,13 @@ func (self *worker) wait() {
} }
// Broadcast the block and announce chain insertion event // Broadcast the block and announce chain insertion event
self.mux.Post(core.NewMinedBlockEvent{Block: block}) self.mux.Post(core.NewMinedBlockEvent{Block: block})
var (
events []interface{} var events []interface{}
logs = work.state.Logs()
) work.stateMu.RLock()
logs := work.state.Logs()
work.stateMu.RUnlock()
events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
if stat == core.CanonStatTy { if stat == core.CanonStatTy {
events = append(events, core.ChainHeadEvent{Block: block}) events = append(events, core.ChainHeadEvent{Block: block})
@ -444,6 +458,11 @@ func (self *worker) commitNewWork() {
log.Error("Failed to create mining context", "err", err) log.Error("Failed to create mining context", "err", err)
return 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 // Create the current work task and check any fork transitions needed
work := self.current work := self.current
if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 { if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 {