以太坊源码之Miner启动过程分析

之前已经撸过了以太坊的交易池启动和交易池数据维护部分的源码。交易池中的交易数据全部由矿工进行验证,打包,进行工作量证明后加入主链。接下来就撸一撸以太坊Miner模块的源码,先从Miner的启动开始解读代码。
老规矩,先来介绍一下miner模块几个关键的结构体,如下:
type Miner struct {
	mux *event.TypeMux    // 事件锁,已被feed.mu.lock替代
	worker *worker             // 矿工,干活的人
	coinbase common.Address  //矿工地址
	mining   int32            // 代表挖矿进行中的状态
	eth      Backend        // Backend对象,Backend是一个自定义接口封装了所有挖矿所需方法
	engine   consensus.Engine    // 共识引擎,以太坊具有ethash和clique两种共识算法
	canStart    int32   // 是否能够开始挖矿操作
	shouldStart int32 // 同步以后是否应该开始挖矿
}

type Work struct {
	config *params.ChainConfig //链的配置属性
	signer types.Signer     // 封装了事务签名处理,椭圆算法

	state *state.StateDB // 数据库状态

	ancestors *set.Set //  祖先集,用来验证叔父块有效性
	family    *set.Set //  家庭集,用来验证叔块无效
	uncles    *set.Set // 叔块集

	tcount int // 交易量

	Block *types.Block // 新区快
	header   *types.Header     // 区块头
	txs      []*types.Transaction // 交易
	receipts []*types.Receipt  // 收据
	createdAt time.Time  //区块创建时间
}

type worker struct {
	config *params.ChainConfig // 链的配置属性
	engine consensus.Engine    // 共识引擎

	mu sync.Mutex // 同步锁

	mux          *event.TypeMux                // 事件锁
	txCh         chan core.TxPreEvent     // 用来接受txPool里面的交易的通道
	txSub        event.Subscription          // 用来接受txPool里面的交易的订阅器
	chainHeadCh  chan core.ChainHeadEvent // 用来接受区块头的通道
	chainHeadSub event.Subscription
	chainSideCh  chan core.ChainSideEvent // 用来接受一个区块链从规范区块链移出的通
	chainSideSub event.Subscription
	
	wg           sync.WaitGroup

	agents map[Agent]struct{}  // 所有挖矿代理通道
	recv   chan *Result            // agent会把结果发送到这个通道

	eth     Backend                    // Backend的接口方法
	chain   *core.BlockChain    // 区块链
	proc    core.Validator          // 区块链验证器
	chainDb ethdb.Database   // 区块链数据库

	coinbase common.Address // 挖矿者的地址
	extra    []byte                       //区块的extraData

	currentMu sync.Mutex   //当前互斥锁
	current   *Work             //当前执行的工作work

	snapshotMu    sync.RWMutex   //读写锁快照
	snapshotBlock *types.Block      //区块快照
	snapshotState *state.StateDB  //状态快照

	uncleMu        sync.Mutex      //叔块互斥锁
	possibleUncles map[common.Hash]*types.Block //可能的叔父节点
	unconfirmed *unconfirmedBlocks 	// 本地挖出的有待确认的区块

	mining int32    //状态判定字段
	atWork int32   //运行中的引擎数量
}

type CpuAgent struct {
	mu sync.Mutex      //互斥锁

	workCh        chan *Work     //Work 通道
	stop          chan struct{}       //退出
	quitCurrentOp chan struct{}    // 取消当前操作
	returnCh      chan<- *Result    //返回执行结果(work 和区块)

	chain  consensus.ChainReader   //Uncle核查时提供查询区块链的对象
	engine consensus.Engine            //共识处理引擎

	isMining int32 //指定agent当前是否启动
}

const (
	resultQueueSize  = 10  //指用于监听验证结果的通道(worker.resultCh)的缓存大小
	miningLogAtDepth = 5 //指记录成功挖矿时需要达到的确认数

	txChanSize = 4096     
	//指用于监听事件 core.NewTxsEvent 的通道(worker.txsCh)的缓存大小
	chainHeadChanSize = 10  
	//指用于监听事件 core.ChainHeadEvent 的通道(worker.chainHeadCh)的缓存大小
	chainSideChanSize = 10
	指用于监听事件 core.ChainSideEvent 的通道(worker.chainSideCh)的缓存大小
)

以上是Miner模块中几个重要的数据结构,接下来分析Miner模块代码。
Miner在创建一个fullnode的时候启动,启动部分源码如下:

源码路径:go-ethereum\miner\miner.go
func New(eth Backend, config *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine) *Miner {
	miner := &Miner{
		eth:      eth,
		mux:      mux,
		engine:   engine,
		worker:   newWorker(config, engine, common.Address{}, eth, mux),
		canStart: 1,
	}
	miner.Register(NewCpuAgent(eth.BlockChain(), engine))
	go miner.update()

	return miner
}

上面这段代码主要完成3个任务:
1.实例化miner结构体并创建一个worker(矿工)
2.注册一个Agent
3.等待更新区块链数据完成
下面将分别对这三个功能,首先来看怎么去创建一个worker(矿工),源码如下:

func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase common.Address, eth Backend, mux *event.TypeMux) *worker {
	worker := &worker{
		config: config,
		engine: engine,
		eth:    eth,
		mux:    mux,

		// 交易时 TxPool会发出该事件,当一笔交易被放入到交易池
		// 这时候如果work空闲会把Tx放到work.txs准备下一次打包进块
		txCh: make(chan core.TxPreEvent, txChanSize),

		//ChainHeadEvent事件,表示已经有一个块作为链头 work.ipdate监听到该事件会继续挖矿
		chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),

		//ChainSideEvent事件,表示一个新块作为链的旁支可能会被放入possibleUncles中
		chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),

		//区块链数据库
		chainDb: eth.ChainDb(),

		recv:  make(chan *Result, resultQueueSize),  //用于接收从Agent那边传过来的Result
		chain: eth.BlockChain(),
		proc:  eth.BlockChain().Validator(),

		// 可能的叔块
		possibleUncles: make(map[common.Hash]*types.Block),
		coinbase:       coinbase,
		agents:         make(map[Agent]struct{}),

		// 挖出的未被确认的区块
		unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
	}
	// 订阅交易池的 TxPreEvent 事件
	worker.txSub = eth.TxPool().SubscribeTxPreEvent(worker.txCh)
	// 订阅区块的 ChainHeadEven 和 ChainSideEvent 事件
	worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
	worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
	
	//监听 TxPreEvent , ChainHeadEven 和 ChainSideEvent 事件
	go worker.update()
	
	//监听recv,等待本地产生的新区块,负责将其持久化到本地数据库中,并提交下一轮的新任务给Agent实例
	go worker.wait()
	
	//提交新任务给CpuAgent,CpuAgent接收到任务后,负责执行挖矿逻辑,再将找到的新区块发送到管道中,这个时候,go worker.wait()协程开始工作
	worker.commitNewWork()
	return worker
}

在创建矿工(worker)的时候,依次用到了 worker.update(),worker.wait()和worker.commitNewWork()这三个函数,下面分别对这三个函数进行介绍,首先介绍worker.update()函数,源码如下:

func (self *worker) update() {
	defer self.txSub.Unsubscribe()
	//程序退出取消txSub订阅事件
	defer self.chainHeadSub.Unsubscribe()
	defer self.chainSideSub.Unsubscribe()

	for {
		// 循环检测时间类型,以类型做区分分别处理
		select {
		// 处理 chainHead 并提交到区块
		case <-self.chainHeadCh:
			self.commitNewWork()

		// 处理 chainSide 并加入叔父区块
		case ev := <-self.chainSideCh:
			self.uncleMu.Lock()
			self.possibleUncles[ev.Block.Hash()] = ev.Block
			self.uncleMu.Unlock()

		// 处理 TxPreEvent 事件
		case ev := <-self.txCh:
			// Apply transaction to the pending state if we're not mining
			if atomic.LoadInt32(&self.mining) == 0 {
				self.currentMu.Lock()
				acc, _ := types.Sender(self.current.signer, ev.Tx)
				txs := map[common.Address]types.Transactions{acc: {ev.Tx}}
				txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs)

				self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)
				self.updateSnapshot()
				self.currentMu.Unlock()
			} else {
				// If we're mining, but nothing is being processed, wake on new transactions
				if self.config.Clique != nil && self.config.Clique.Period == 0 {
					self.commitNewWork()
				}
			}

		// System stopped
		case <-self.txSub.Err():
			return
		case <-self.chainHeadSub.Err():
			return
		case <-self.chainSideSub.Err():
			return
		}
	}
}

实例化一个矿工后,注册Agent的源码如下:

func (self *Miner) Register(agent Agent) {
	if self.Mining() {
		agent.Start()
	}
	self.worker.register(agent)
}

//agent.Start()
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()
	}
}

//self.worker.register(agent)
func (self *worker) register(agent Agent) {
	self.mu.Lock()
	defer self.mu.Unlock()
	self.agents[agent] = struct{}{}
	agent.SetReturnCh(self.recv)
}

对于Agent,是go语言的一个借口,其定义如下:
type Agent interface {
	Work() chan<- *Work
	SetReturnCh(chan<- *Result)
	Stop()
	Start()
	GetHashRate() int64
}

等待更新区块链数据完成,其源码如下:

func (self *Miner) update() {
    //订阅了downloader的StartEvent、DoneEvent、FailedEvent事件
	events := self.mux.Subscribe(downloader.StartEvent{}, downloader.DoneEvent{}, downloader.FailedEvent{})
out:
	for ev := range events.Chan() {
		switch ev.Data.(type) {
		case downloader.StartEvent:
		    //开始区块数据的同步
			atomic.StoreInt32(&self.canStart, 0)
			if self.Mining() {
				self.Stop()
				atomic.StoreInt32(&self.shouldStart, 1)
				log.Info("Mining aborted due to sync")
			}
		case downloader.DoneEvent, downloader.FailedEvent:
		    //同步区块数据完成 或者 同步区块数据失败
			shouldStart := atomic.LoadInt32(&self.shouldStart) == 1

			atomic.StoreInt32(&self.canStart, 1)
			atomic.StoreInt32(&self.shouldStart, 0)
			if shouldStart {
				self.Start(self.coinbase)  //开始启动miner开始挖矿
			}
			// 取消downloader的StartEvent、DoneEvent、FailedEvent事件的订阅
			events.Unsubscribe()
			break out
		}
	}
}

该程序在同步区块数据完成后启动Miner挖矿并且退出(break out)该程序,接下来重点解析一下Miner.Start()代码:

func (self *Miner) Start(coinbase common.Address) {
	atomic.StoreInt32(&self.shouldStart, 1)  //设置标志位
	self.SetEtherbase(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()          //启动worker
	self.worker.commitNewWork()  //worker打包区块链
}

接下来看看self.worker.start() 的源码:
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()
	}
}
这里会遍历所有的Agent,调用它们的Start()函数。我们可以看一下CpuAgent的Start()函数:
func (self *CpuAgent) Start() {
	if !atomic.CompareAndSwapInt32(&self.isMining, 0, 1) {
		return // agent already started
	}
	go self.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.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),
		Extra:      self.extra,
		Time:       big.NewInt(tstamp),
	}
	//创建新区块的区块头
	
	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
	}
	//基于POW算法的Ethash共识引擎,获取父块的Header,然后根据规则计算新的挖矿难度值
	
	//DAO硬分叉数据处理
	if daoBlock := self.config.DAOForkBlock; daoBlock != nil {
		//设置limit = daoBlock + 10
		limit := new(big.Int).Add(daoBlock, params.DAOForkExtraRange)
		if header.Number.Cmp(daoBlock) >= 0 && header.Number.Cmp(limit) < 0 {
			// DAO分叉的区块在距离当前区块的深度在 10 之内
			if self.config.DAOForkSupport {
				header.Extra = common.CopyBytes(params.DAOForkBlockExtra)
			} else if bytes.Equal(header.Extra, params.DAOForkBlockExtra) {
				header.Extra = []byte{}
			}
		}
	}
	
	// 创建新的work
	err := self.makeCurrent(parent, header)
	if err != nil {
		log.Error("Failed to create mining context", "err", err)
		return
	}
	
	work := self.current
	if self.config.DAOForkSupport && self.config.DAOForkBlock != nil && self.config.DAOForkBlock.Cmp(header.Number) == 0 {
		misc.ApplyDAOHardFork(work.state)
	}
    
    //从pending交易池中获取交易
	pending, err := self.eth.TxPool().Pending()
	if err != nil {
		log.Error("Failed to fetch pending transactions", "err", err)
		return
	}
    
    //按照gas 从高到低 排序
	txs := types.NewTransactionsByPriceAndNonce(self.current.signer, pending)
    //把交易提交到EVM去执行
	work.commitTransactions(self.mux, txs, self.chain, self.coinbase)

	// 依据规则加入刷选叔区块&删除不符合规则的叔区块
	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)
	}

	//将数据送入共识引擎的Finalize()函数中生成新区块
	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
	}
	
	// 将上述生成的区块放进未经确认的区块列表unconfirmed中,然后调用push()把Work推送给Agent去做POW计算
	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)
    
    //用同样的数据创建了一个新区块,但是没有传叔块列表。创建的区块赋值给snapshotBlock字段,同时把当前的state也复制了一份作为快照
	self.updateSnapshot()
}

上述仅仅对Miner的启动和挖矿过程进行了简单的分析,至于具体的EVM执行交易过程、新区块数据的生成过程、共识算法的验证过程以及新区块加入到待确认区块的过程有时间将做具体的分析。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值