以太坊版本:release/1.4
在以太坊1.4版本及之前以太坊共识算法还是使用的PoW工作量证明,因为本团队开发使用的还是1.4版本,本篇文章是对1.4版本的挖矿源代码进行分析讲解,欲看更高版本的请自行绕过或等待博主更新,另外关于PoW共识可以阅读下篇文章:
点击打开链接 https://mp.csdn.net/postedit/80535270
挖矿的生命周期:
挖矿模块包括四个源码文件:miner.go(模块核心入口文件)、worker.go(模块核心执行文件)、agent.go(基于CPU挖矿代理实现)、remote_agent.go(远程挖矿代理实现);所谓代理可以理解为真正的挖矿工人,而worker可以理解为是包工头,后两者分别是对代理的两种实现,在这里主要使用的和要讲解的是两者中的前者;
读完整个挖矿模块的源码后,我按照自己的理解把以太坊挖矿大致分成了六个过程即生命周期:创建(New)、注册代理(register)、启动(Start)、发布任务(push)、共识挖矿(mine)、停止(stop),流程图如下:
创建(New):
该阶段初始化miner对象和worker对象。worker对象是挖矿模块的核心对象,包含链的基本配置信息、代理对象的切片、挖矿结果、pow、链对象、账户和交易队列等信息。
注册代理(register):
程序状态触发启动时会向worker对象中注册代理对象,可以理解为向包工头分配工人。如果包工头手下没有工人可能工作就没法进行,所以程序成功执行的条件要保证worker对象至少包含一个agent对象。
启动(Start):
在代理注册之后miner对象会调用worker对象的start方法,实际上是遍历启动worker中的所有agent对象。agent对象的start方法逻辑上是启动一个for循环监听work挖矿任务,如果有挖矿任务就进行挖矿,如果有退出类型消息就退出循环体
发布任务(push):
在启动之后worker对象向其中每一个agent对象的work管道中推送work挖矿任务,agent监听到管道中的任务后调用执行该任务挖矿
共识挖矿(mine):
agent对象挖矿的过程其实就是调用PoW模块获取符合规则的随机数的过程,如果agent对象中的quitCurrentOp消息管道不为空,那么获取的随机数为0即该work任务挖矿失败,如果quitCurrentOp消息管道为空,那么会一直尝试寻找符合规则的随机数直到找到为止
停止(stop):
触发停止动作后会关闭worker对象中的所有agent并移除,同时关闭相关的事件,挖矿进程结束
核心源码分析如下(参考注释部分):
miner.go :
/**
*启动挖矿进程
*/
func (self *Miner) Start(coinbase common.Address, threads int) {
atomic.StoreInt32(&self.shouldStart, 1)
self.threads = threads
self.worker.coinbase = coinbase
self.coinbase = coinbase
if atomic.LoadInt32(&self.canStart) == 0 {
glog.V(logger.Info).Infoln("Can not start mining operation due to network sync (starts when finished)")
return
}
atomic.StoreInt32(&self.mining, 1)
//注册代理对象
self.worker.register(NewCpuAgent(0, self.pow))
glog.V(logger.Info).Infof("Starting Accounting operation as a Bookkeeper\n")
//启动代理对象
self.worker.start()
//发布work任务,将worker对象中的当前的挖矿任务发布到管道队列中
self.worker.commitNewWork()
//发送post请求
self.mux.Post(core.StartBookingEvent{Address: &coinbase})
}
worker.go :
// Agent can register themself with the worker
//声明了代理的接口,agent.go是对Agent的CPU挖矿类型的声明
type Agent interface {
Work() chan<- *Work //获取work管道只写无缓冲队列
SetReturnCh(chan<- *Result) //挖矿结果管道队列 类型:只写无缓冲队列
Stop() //停止挖矿
Start() //启动挖矿
GetHashRate() int64 //获取算力
}
type uint64RingBuffer struct {
ints []uint64 //array of all integers in buffer
next int //where is the next insertion? assert 0 <= next < len(ints)
}
// environment is the workers current environment and holds
// all of the current state information
type Work struct {
config *core.ChainConfig //链的基本配置
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)
uncles *set.Set // uncle set
remove *set.Set // tx which will be removed
tcount int // tx count in cycle
ignoredTransactors *set.Set
lowGasTransactors *set.Set
ownedAccounts *set.Set
lowGasTxs types.Transactions
localMinedBlocks *uint64RingBuffer // the most recent block numbers that were mined locally
Block *types.Block // the new block
header *types.Header //区块头
txs []*types.Transaction //交易信息
receipts []*types.Receipt
createdAt time.Time //创建时间
}
/**
*区块的挖矿结果
*/
type Result struct {
Work *Work
Block *types.Block
}
// worker is the main object which takes care of applying messages to the new state
type worker struct {
config *core.ChainConfig //链的基本配置
mu sync.Mutex
// update loop
mux *event.TypeMux
events event.Subscription
wg sync.WaitGroup
agents map[Agent]struct{} //代理对象切片
recv chan *Result //挖矿结果
pow pow.PoW //共识
eth core.Backend
chain *core.BlockChain
proc core.Validator
chainDb ethdb.Database //区块链数据库
coinbase common.Address //账户
gasPrice *big.Int
extra []byte
currentMu sync.Mutex
current *Work
uncleMu sync.Mutex
possibleUncles map[common.Hash]*types.Block
txQueueMu sync.Mutex
txQueue map[common.Hash]*types.Transaction //交易队列
// atomic status counters
mining int32
atWork int32
fullValidation bool
}
func newWorker(config *core.ChainConfig, coinbase common.Address, eth core.Backend) *worker {
worker := &worker{
config: config,
eth: eth,
mux: eth.EventMux(),
chainDb: eth.ChainDb(),
recv: make(chan *Result, resultQueueSize),
gasPrice: new(big.Int),
chain: eth.BlockChain(),
proc: eth.BlockChain().Validator(),
possibleUncles: make(map[common.Hash]*types.Block),
coinbase: coinbase,
txQueue: make(map[common.Hash]*types.Transaction),
agents: make(map[Agent]struct{}),
fullValidation: false,
}
worker.events = worker.mux.Subscribe(core.ChainHeadEvent{}, core.ChainSideEvent{}, core.TxPreEvent{})
//启动状态事件监听
go worker.update()
go worker.wait()
//验证区块信息发布区块挖矿任务
worker.commitNewWork()
return worker
}
//启动所有代理对象,监听work任务
func (self *worker) start() {
self.mu.Lock()
defer self.mu.Unlock()
atomic.StoreInt32(&self.mining, 1)
// spin up agents
for agent := range self.agents {
agent.Start()
}
}
//停止所有代理挖矿
func (self *worker) stop() {
self.wg.Wait()
self.mu.Lock()
defer self.mu.Unlock()
if atomic.LoadInt32(&self.mining) == 1 {
// Stop all agents.
for agent := range self.agents {
agent.Stop()
// Remove CPU agents.
if _, ok := agent.(*CpuAgent); ok {
delete(self.agents, agent)
}
}
}
atomic.StoreInt32(&self.mining, 0)
atomic.StoreInt32(&self.atWork, 0)
}
//注册代理
func (self *worker) register(agent Agent) {
self.mu.Lock()
defer self.mu.Unlock()
self.agents[agent] = struct{}{}
agent.SetReturnCh(self.recv)
}
func (self *worker) unregister(agent Agent) {
self.mu.Lock()
defer self.mu.Unlock()
delete(self.agents, agent)
agent.Stop()
}
func (self *worker) update() {
for event := range self.events.Chan() {
// A real event arrived, process interesting content
switch ev := event.Data.(type) {
case core.ChainHeadEvent:
self.commitNewWork()
case core.ChainSideEvent:
self.uncleMu.Lock()
self.possibleUncles[ev.Block.Hash()] = ev.Block
self.uncleMu.Unlock()
case core.TxPreEvent:
// Apply transaction to the pending state if we're not mining
if atomic.LoadInt32(&self.mining) == 0 {
self.currentMu.Lock()
self.current.commitTransactions(self.mux, types.Transactions{ev.Tx}, self.gasPrice, self.chain)
self.currentMu.Unlock()
}
}
}
}
f
// 发布work任务
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
}
}
}
agent.go :
//CPU挖矿
type CpuAgent struct {
mu sync.Mutex
workCh chan *Work //任务无缓冲队列
//???channel
quit chan struct{} //退出消息队列
quitCurrentOp chan struct{} //停止当前挖矿动作消息队列
//??channel
returnCh chan<- *Result //挖矿结果消息队列
index int
pow pow.PoW //工作量证明共识模块
isMining int32 // 是否在挖矿状态
}
func NewCpuAgent(index int, pow pow.PoW) *CpuAgent {
miner := &CpuAgent{
pow: pow,
index: index,
}
return miner
}
/**
*以下方法是对Agent接口的声明
*/
func (self *CpuAgent) Work() chan<- *Work { return self.workCh }
func (self *CpuAgent) Pow() pow.PoW { return self.pow }
func (self *CpuAgent) SetReturnCh(ch chan<- *Result) { self.returnCh = ch }
func (self *CpuAgent) Stop() {
self.mu.Lock()
defer self.mu.Unlock()
close(self.quit)
}
func (self *CpuAgent) Start() {
self.mu.Lock()
defer self.mu.Unlock()
if !atomic.CompareAndSwapInt32(&self.isMining, 0, 1) {
return // agent already started
}
self.quit = make(chan struct{})
// creating current op ch makes sure we're not closing a nil ch
// later on
self.workCh = make(chan *Work, 1)
go self.update()
}
/**
*状态更新,for循环随机从两个管道中获取消息,如果有work消息,调用mine方法挖矿,如果有退出消息跳出for循环
*/
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.quit:
self.mu.Lock()
if self.quitCurrentOp != nil {
close(self.quitCurrentOp)
self.quitCurrentOp = nil
}
self.mu.Unlock()
break out
}
}
done:
// Empty work channel
//确保work管道为空
for {
select {
case <-self.workCh:
default:
close(self.workCh)
break done
}
}
atomic.StoreInt32(&self.isMining, 0)
}
/**
*挖矿
*/
func (self *CpuAgent) mine(work *Work, stop <-chan struct{}) {
glog.V(logger.Debug).Infof("(re)started agent[%d]. mining...\n", self.index)
// 获取随机数,如果stop中含有退出当前操作的消息,结束当前挖矿,挖矿失败
nonce, mixDigest := self.pow.Search(work.Block, stop, self.index)
if nonce != 0 {
//如果随机数!=0即挖矿成功修改区块头的随机数和摘要信息
block := work.Block.WithMiningResult(nonce, common.BytesToHash(mixDigest))
//记录挖矿成功结果
self.returnCh <- &Result{work, block}
} else {
self.returnCh <- nil
}
}
func (self *CpuAgent) GetHashRate() int64 {
return self.pow.GetHashrate()
}