写作这篇文章,希望能帮你对挖矿启动工作有更清晰的了解。

信号管理基础

//miner/worker.go:409
case req := <-w.newWorkCh:
   w.commitNewWork(req.interrupt, req.noempty, req.timestamp)

worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)

挖矿启动工作由三个信号管理。这三个信号分别是新工作启动信号、新交易信号和最长链链切换信号。在上篇“挖矿工作信号监控”中已讲过这些信号的来源。新工作启动是挖矿的开端。新交易信号会引发后续一系列处理。最长链链切换信号可能影响挖矿方向和策略。

新交易信号处理

//miner/worker.go:451
case ev := <-w.txsCh: 
   if !w.isRunning() && w.current != nil {//
      w.mu.RLock()
      coinbase := w.coinbase
      w.mu.RUnlock()
      txs := make(map[common.Address]types.Transactions)
      for _, tx := range ev.Txs {//
         acc, _ := types.Sender(w.current.signer, tx)
         txs[acc] = append(txs[acc], tx)
      }
      txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs)//
      w.commitTransactions(txset, coinbase, nil)//
      w.updateSnapshot()//
   } else { 
      if w.config.Clique != nil && w.config.Clique.Period == 0 {//
         w.commitNewWork(nil, false, time.Now().Unix())
      }
   }
   atomic.AddInt32(&w.newTxs, int32(len(ev.Txs)))//

接收到新交易信号后,会依据挖矿状态做不同处理。要是还没开始挖矿,不过具备挖矿条件,交易就会被提交到待处理状态。要是正处于PoA挖矿,并且允许无间隔出块,就会放弃当前工作,重新开始。这里实际没用上数据量,它只是作为有交易进行的一个标记。

//miner/worker.go:412
case ev := <-w.chainSideCh: 
   if _, exist := w.localUncles[ev.Block.Hash()]; exist {//
      continue
   }
   if _, exist := w.remoteUncles[ev.Block.Hash()]; exist {
      continue
   } 
   if w.isLocalBlock != nil && w.isLocalBlock(ev.Block) {//
      w.localUncles[ev.Block.Hash()] = ev.Block
   } else {
      w.remoteUncles[ev.Block.Hash()] = ev.Block
   } 
   if w.isRunning() && w.current != nil && w.current.uncles.Cardinality() < 2 {//
      start := time.Now()
      if err := w.commitUncle(w.current, ev.Block.Header()); err == nil {//
         var uncles []*types.Header
         w.current.uncles.Each(func(item interface{}) bool {
            //...
         })
         w.commit(uncles, nil, true, start)//
      }
   }

挖矿时交易处理

要是还在进行挖矿,并且叔块数量少于2个,那么可以暂时不处理交易。一旦区块成功加入叔块集,就停止交易处理。接着把已处理的交易组装成区块,同时生成PoW计算信号。由于区块奖励包含叔块奖励,所以叔块挖掘对挖矿收益有影响。

//miner/worker.go:829
parent := w.chain.CurrentBlock()//

if parent.Time() >= uint64(timestamp) {//
   timestamp = int64(parent.Time() + 1)
} 
if now := time.Now().Unix(); timestamp > now+1 {
   wait := time.Duration(timestamp-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, w.gasFloor, w.gasCeil),
   Extra:      w.extra,
   Time:       uint64(timestamp),
}
if w.isRunning() {
		if w.coinbase == (common.Address{}) {
			log.Error("Refusing to mine without etherbase")
			return
		}
		header.Coinbase = w.coinbase//
}

新区块挖矿准备

新区块挖矿的第一步是构建区块。要把最新高度的区块当作父块,以此来确定新区块的基本信息。要是新区块链的时间戳比当前节点的时间快,那就需要休眠,目的是避免新出的块属于未来。另外,还得依据共识算法来设置新区块的挖矿难度,这样能保证挖矿具有有效性和公平性。

if err := w.engine.Prepare(w.chain, header); err != nil {//
   log.Error("Failed to prepare header for mining", "err", err)
   return
}

信息记录与叔块规则

专门定义了一个东西。它用于记录当前挖矿工作相关内容。这方便共享新区块信息。叔块是这样的区块。它在最近7个高度内。并且和当前新区块不在同一分支。也不重复包含在祖先块中。了解叔块规则对挖矿很重要。它影响着区块的构建。还影响着最终奖励。

	err := w.makeCurrent(parent, header)
	if err != nil {
		log.Error("Failed to create mining context", "err", err)
		return
	}

//miner/worker.go:886
uncles := make([]*types.Header, 0, 2)
commitUncles := func(blocks map[common.Hash]*types.Block) { 
   for hash, uncle := range blocks {//
      if uncle.NumberU64()+staleThreshold <= header.Number.Uint64() {
         delete(blocks, hash)
      }
   }
   for hash, uncle := range blocks {
      if len(uncles) == 2 {//
         break
      }
      if err := w.commitUncle(env, uncle.Header()); err != nil { 
      } else { 
         uncles = append(uncles, uncle.Header())
      }
   }
} 
commitUncles(w.localUncles)//
commitUncles(w.remoteUncles)

核心挖矿与任务检查

有了区块之后,核心在于执行PoW运算来寻找Nonce。登记是为了便于查找挖矿任务信息。开始新挖掘时会取消旧工作并删除标记。每次寻找Nonce之前,会关闭当前挖矿任务。正式存储新区块前,要检查其是否已经存在,还要检查挖矿任务是否已取消。同时,还需要整理交易回执、收集日志信息。大家认为在实际挖矿过程中,哪个环节最容易出现问题?快来评论区留言,记得点赞和分享本文。

//miner/worker.go:645
func (w *worker) commitUncle(env *environment, uncle *types.Header) error {
   hash := uncle.Hash() 
   //...
   if env.header.ParentHash == uncle.ParentHash {//
      return errors.New("uncle is sibling")
   }
   //...
   env.uncles.Add(uncle.Hash())
   return nil
}