写作这篇文章,希望能帮你对挖矿启动工作有更清晰的了解。
信号管理基础
//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
}
暂无评论
发表评论