以太坊原始碼分析(16)挖礦分析
阿新 • • 發佈:2019-01-03
### 從交易扔到tx的pending池開始分析,不言而喻現在該是礦工登場的時候了
/miner/miner.go/Start
```func (self *Miner) Start(coinbase common.Address) { atomic.StoreInt32(&self.shouldStart, 1) self.worker.setEtherbase(coinbase) self.coinbase = coinbase
if atomic.LoadInt32(&self.canStart) == 0 { log.Info("Network syncing, will start miner afterwards") return } atomic.StoreInt32(&self.mining, 1)
log.Info("Starting mining operation") self.worker.start() self.worker.commitNewWork()}
```
##### 該方法就是挖礦的開始處,其中 `start()`方法是呼叫所有代理cpu或者遠端代理開始挖礦工作,讓我們看核心的`commitNewWork()`程式碼
```func (self *worker) commitNewWork() { self.mu.Lock() defer self.mu.Unlock() self.uncleMu.Lock() defer self.uncleMu.Unlock() self.currentMu.Lock() defer self.currentMu.Unlock()
tstart := time.Now() parent := self.chain.CurrentBlock()
tstamp := tstart.Unix() if parent.Time().Cmp(new(big.Int).SetInt64(tstamp)) >= 0 { tstamp = parent.Time().Int64() + 1 } // this will ensure we're not going off too far in the future if now := time.Now().Unix(); tstamp > now+1 { wait := time.Duration(tstamp-now) * time.Second log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) time.Sleep(wait) }
num := parent.Number() header := &types.Header{ ParentHash: parent.Hash(), Number: num.Add(num, common.Big1), GasLimit: core.CalcGasLimit(parent), GasUsed: new(big.Int), Extra: self.extra, Time: big.NewInt(tstamp), } // Only set the coinbase if we are mining (avoid spurious block rewards) if atomic.LoadInt32(&self.mining) == 1 { header.Coinbase = self.coinbase } if err := self.engine.Prepare(self.chain, header); err != nil { log.Error("Failed to prepare header for mining", "err", err) return } // If we are care about TheDAO hard-fork check whether to override the extra-data or not if daoBlock := self.config.DAOForkBlock; daoBlock != nil { // Check whether the block is among the fork extra-override range limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 { // Depending whether we support or oppose the fork, override differently if self.config.DAOForkSupport { header.Extra = common.CopyBytes(params.DAOForkBlockExtra) } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) { header.Extra = []byte{} // If miner opposes, don't let it use the reserved extra-data } } } // Could potentially happen if starting to mine in an odd state. err := self.makeCurrent(parent, header) if err != nil { log.Error("Failed to create mining context", "err", err) return } // 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 { misc.ApplyDAOHardFork(work.state) } pending, err := self.eth.TxPool().Pending() if err != nil { log.Error("Failed to fetch pending transactions", "err", err) return } txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending) work.commitTransactions(self.mux, txs, self.chain, self.coinbase)
// compute uncles for the new block. var ( uncles []*types.Header badUncles []common.Hash ) for hash, uncle := range self.possibleUncles { if len(uncles) == 2 { break } if err := self.commitUncle(work, uncle.Header()); err != nil { log.Trace("Bad uncle found and will be removed", "hash", hash) log.Trace(fmt.Sprint(uncle))
badUncles = append(badUncles, hash) } else { log.Debug("Committing new uncle to block", "hash", hash) uncles = append(uncles, uncle.Header()) } } for _, hash := range badUncles { delete(self.possibleUncles, hash) } // Create the new block to seal with the consensus engine if work.Block, err = self.engine.Finalize(self.chain, header, work.state, work.txs, uncles, work.receipts); err != nil { log.Error("Failed to finalize block for sealing", "err", err) return } // We only care about logging if we're actually mining. if atomic.LoadInt32(&self.mining) == 1 { log.Info("Commit new mining work", "number", work.Block.Number(), "txs", work.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart))) self.unconfirmed.Shift(work.Block.NumberU64() - 1) } self.push(work)}
```
##### 該方法根據上一個塊的資訊構造出了本次出塊的header資訊,本次出塊的難度,如果上一個塊的時間是未來的某個時間,那麼就需要一直sleep直到對應的時間,然後 `work := self.current`構建了當前出塊任務的物件,然後就是從pending交易池裡面獲取交易進行建立交易,然後就是提交交易,接著看`work.commitTransactions`方法下的`env.commitTransaction`方法
```func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, coinbase common.Address, gp *core.GasPool) (error, []*types.Log) { snap := env.state.Snapshot()
receipt, _, err := core.ApplyTransaction(env.config, bc, &coinbase, gp, env.state, env.header, tx, env.header.GasUsed, vm.Config{}) if err != nil { env.state.RevertToSnapshot(snap) return err, nil } env.txs = append(env.txs, tx) env.receipts = append(env.receipts, receipt)
return nil, receipt.Logs}```
##### 該方法就是驗證當前work中的每一筆交易是不是合法的,如果合法就加入到當前work的交易列表中,接著看 `commitNewWork()`方法下的`Finalize`方法
```// Finalize implements consensus.Engine, accumulating the block and uncle rewards,// setting the final state and assembling the block.func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // Accumulate any block and uncle rewards and commit the final state root AccumulateRewards(chain.Config(), state, header, uncles) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Header seems complete, assemble into a block and return return types.NewBlock(header, txs, uncles, receipts), nil}```
##### 該方法其實就是計算好該塊的出塊獎勵,接著看`commitNewWork()`方法下的最後一個方法
```// push sends a new work task to currently live miner agents.func (self *worker) push(work *Work) { if atomic.LoadInt32(&self.mining) != 1 { return } for agent := range self.agents { atomic.AddInt32(&self.atWork, 1) if ch := agent.Work(); ch != nil { ch <- work } }}```##### 該方法就是把當前出塊的任務推送到每一個代理,通過管道的形式寫入到每個代理的work管道,到此為止,下個塊的資訊已經發送給每個代理了,那麼接著看代理如何出塊,首先看結構
```// Agent can register themself with the workertype Agent interface { Work() chan<- *Work SetReturnCh(chan<- *Result) Stop() Start() GetHashRate() int64}```
##### 上面的分析當前塊的任務以及資訊已經通過管道寫入到work中了,那麼讓我們看`work`方法,誰來接收並處理呢?讓我們將目光放到`/miner/agent.go/update`方法
```func (self *CpuAgent) update() {out: for { select { case work := <-self.workCh: self.mu.Lock() if self.quitCurrentOp != nil { close(self.quitCurrentOp) } self.quitCurrentOp = make(chan struct{}) go self.mine(work, self.quitCurrentOp) self.mu.Unlock() case <-self.stop: self.mu.Lock() if self.quitCurrentOp != nil { close(self.quitCurrentOp) self.quitCurrentOp = nil } self.mu.Unlock() break out } }}
```
/miner/miner.go/Start
```func (self *Miner) Start(coinbase common.Address) { atomic.StoreInt32(&self.shouldStart, 1) self.worker.setEtherbase(coinbase) self.coinbase = coinbase
if atomic.LoadInt32(&self.canStart) == 0 { log.Info("Network syncing, will start miner afterwards") return } atomic.StoreInt32(&self.mining, 1)
log.Info("Starting mining operation") self.worker.start() self.worker.commitNewWork()}
```
##### 該方法就是挖礦的開始處,其中
```func (self *worker) commitNewWork() { self.mu.Lock() defer self.mu.Unlock() self.uncleMu.Lock() defer self.uncleMu.Unlock() self.currentMu.Lock() defer self.currentMu.Unlock()
tstart := time.Now() parent := self.chain.CurrentBlock()
tstamp := tstart.Unix() if parent.Time().Cmp(new(big.Int).SetInt64(tstamp)) >= 0 { tstamp = parent.Time().Int64() + 1 } // this will ensure we're not going off too far in the future if now := time.Now().Unix(); tstamp > now+1 { wait := time.Duration(tstamp-now) * time.Second log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) time.Sleep(wait) }
num := parent.Number() header := &types.Header{ ParentHash: parent.Hash(), Number: num.Add(num, common.Big1), GasLimit: core.CalcGasLimit(parent), GasUsed: new(big.Int), Extra: self.extra, Time: big.NewInt(tstamp), } // Only set the coinbase if we are mining (avoid spurious block rewards) if atomic.LoadInt32(&self.mining) == 1 { header.Coinbase = self.coinbase } if err := self.engine.Prepare(self.chain, header); err != nil { log.Error("Failed to prepare header for mining", "err", err) return } // If we are care about TheDAO hard-fork check whether to override the extra-data or not if daoBlock := self.config.DAOForkBlock; daoBlock != nil { // Check whether the block is among the fork extra-override range limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange) if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 { // Depending whether we support or oppose the fork, override differently if self.config.DAOForkSupport { header.Extra = common.CopyBytes(params.DAOForkBlockExtra) } else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) { header.Extra = []byte{} // If miner opposes, don't let it use the reserved extra-data } } } // Could potentially happen if starting to mine in an odd state. err := self.makeCurrent(parent, header) if err != nil { log.Error("Failed to create mining context", "err", err) return } // 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 { misc.ApplyDAOHardFork(work.state) } pending, err := self.eth.TxPool().Pending() if err != nil { log.Error("Failed to fetch pending transactions", "err", err) return } txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending) work.commitTransactions(self.mux, txs, self.chain, self.coinbase)
// compute uncles for the new block. var ( uncles []*types.Header badUncles []common.Hash ) for hash, uncle := range self.possibleUncles { if len(uncles) == 2 { break } if err := self.commitUncle(work, uncle.Header()); err != nil { log.Trace("Bad uncle found and will be removed", "hash", hash) log.Trace(fmt.Sprint(uncle))
badUncles = append(badUncles, hash) } else { log.Debug("Committing new uncle to block", "hash", hash) uncles = append(uncles, uncle.Header()) } } for _, hash := range badUncles { delete(self.possibleUncles, hash) } // Create the new block to seal with the consensus engine if work.Block, err = self.engine.Finalize(self.chain, header, work.state, work.txs, uncles, work.receipts); err != nil { log.Error("Failed to finalize block for sealing", "err", err) return } // We only care about logging if we're actually mining. if atomic.LoadInt32(&self.mining) == 1 { log.Info("Commit new mining work", "number", work.Block.Number(), "txs", work.tcount, "uncles", len(uncles), "elapsed", common.PrettyDuration(time.Since(tstart))) self.unconfirmed.Shift(work.Block.NumberU64() - 1) } self.push(work)}
```
##### 該方法根據上一個塊的資訊構造出了本次出塊的header資訊,本次出塊的難度,如果上一個塊的時間是未來的某個時間,那麼就需要一直sleep直到對應的時間,然後
```func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, coinbase common.Address, gp *core.GasPool) (error, []*types.Log) { snap := env.state.Snapshot()
receipt, _, err := core.ApplyTransaction(env.config, bc, &coinbase, gp, env.state, env.header, tx, env.header.GasUsed, vm.Config{}) if err != nil { env.state.RevertToSnapshot(snap) return err, nil } env.txs = append(env.txs, tx) env.receipts = append(env.receipts, receipt)
return nil, receipt.Logs}```
##### 該方法就是驗證當前work中的每一筆交易是不是合法的,如果合法就加入到當前work的交易列表中,接著看
```// Finalize implements consensus.Engine, accumulating the block and uncle rewards,// setting the final state and assembling the block.func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // Accumulate any block and uncle rewards and commit the final state root AccumulateRewards(chain.Config(), state, header, uncles) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
// Header seems complete, assemble into a block and return return types.NewBlock(header, txs, uncles, receipts), nil}```
##### 該方法其實就是計算好該塊的出塊獎勵,接著看`commitNewWork()`方法下的最後一個方法
```// push sends a new work task to currently live miner agents.func (self *worker) push(work *Work) { if atomic.LoadInt32(&self.mining) != 1 { return } for agent := range self.agents { atomic.AddInt32(&self.atWork, 1) if ch := agent.Work(); ch != nil { ch <- work } }}```##### 該方法就是把當前出塊的任務推送到每一個代理,通過管道的形式寫入到每個代理的work管道,到此為止,下個塊的資訊已經發送給每個代理了,那麼接著看代理如何出塊,首先看結構
```// Agent can register themself with the workertype Agent interface { Work() chan<- *Work SetReturnCh(chan<- *Result) Stop() Start() GetHashRate() int64}```
##### 上面的分析當前塊的任務以及資訊已經通過管道寫入到work中了,那麼讓我們看`work`方法,誰來接收並處理呢?讓我們將目光放到`/miner/agent.go/update`方法
```func (self *CpuAgent) update() {out: for { select { case work := <-self.workCh: self.mu.Lock() if self.quitCurrentOp != nil { close(self.quitCurrentOp) } self.quitCurrentOp = make(chan struct{}) go self.mine(work, self.quitCurrentOp) self.mu.Unlock() case <-self.stop: self.mu.Lock() if self.quitCurrentOp != nil { close(self.quitCurrentOp) self.quitCurrentOp = nil } self.mu.Unlock() break out } }}
```
##### 可以看到剛才寫入到管道的當前塊任務在這裡並接收並且處理,呼叫`self.mine(work, self.quitCurrentOp)`進行挖礦,誰先計算出符合該塊上面的難度hash,誰就能夠產塊,至此和共識包下的pow的分析第二篇形式閉環
網址:http://www.qukuailianxueyuan.io/
欲領取造幣技術與全套虛擬機器資料
區塊鏈技術交流QQ群:756146052 備註:CSDN
尹成學院微信:備註:CSDN