【转载请标明出处】https://blog.csdn.net/qq_25870633/article/details/82027497
今天为什么写这个文章呢,首先,前段时间有朋友问过我,说现在geth的1.8.14版本的代码和网上各路大神们的分析不一样了。我就赶紧看了下,确实,亲的geth代码中的mine部分的逻辑有所改动,想必看过源码的都知道,之前的miner真正挖矿是由worker把所需挖矿的内容全部封装成Work再交由Agent去操作的,而新版本的mining逻辑是移除了Agent全部由worker自己去操作了,worker起了四个goroutine去做这些事。那么,对于不清楚老版本mining逻辑的朋友也不要怕,我都会一一道来。首先,我们先看看老版本的mining是怎么回事:
在老代码中miner
包主要由miner.go
worker.go
agent.go
三个文件组成 【以下图是网上扣的哦】
Miner
负责与外部交互和高层次的挖矿控制worker
负责低层次的挖矿控制 管理下属所有AgentAgent
负责实际的挖矿计算工作
三者之间的顶层联系如下图所示
在老版本中我们可以知道,挖矿的入口是miner;
当实例化一个miner实例时,顺便做了几件事,一实例化了worker和注册了Agent实例CpuAgent并且启动了一个 update函数去做事件的监听【启动挖矿 或 停止挖矿】
miner的update:
这个update()会订阅(监听)几种事件,均跟Downloader相关。【1】当收到Downloader的StartEvent时,意味者此时本节点正在从其他节点下载新区块,这时miner会立即停止进行中的挖掘工作,并继续监听;【2】如果收到DoneEvent或FailEvent时,意味本节点的下载任务已结束-无论下载成功或失败-此时都可以开始挖掘新区块,并且此时会退出Downloader事件的监听。
我们再来看看在实例化一个miner的时候顺便实例化的worker是做了些什么事情【即在miner的New函数中所调用了worker的newWorker函数】注意:此时,CpuAgent还未启动。
worker的newWorker:
可以看出,在实例化了worker对象后,分别注册了3个监听调,而真正实时在监听的是在新起的协程 go worker.update() 中所做的事【待会再说】。及启动了协程 go worker.wait() 这个主要做把接收到挖好矿的Block上链的;及调用了一个 worker.commitNewWork() 把需要挖矿的信息都打包成Work 对象传递给CpuAgent。
go worker.update() :
func (self *worker) update() {
defer self.txsSub.Unsubscribe()
defer self.chainHeadSub.Unsubscribe()
defer self.chainSideSub.Unsubscribe()
for {
// A real event arrived, process interesting content
select {
// Handle ChainHeadEvent
case <-self.chainHeadCh:
self.commitNewWork()
// Handle ChainSideEvent
case ev := <-self.chainSideCh:
self.uncleMu.Lock()
self.possibleUncles[ev.Block.Hash()] = ev.Block
self.uncleMu.Unlock()
// Handle NewTxsEvent
case ev := <-self.txsCh:
// Apply transactions to the pending state if we're not mining.
//
// Note all transactions received may not be continuous with transactions
// already included in the current mining block. These transactions will
// be automatically eliminated.
if atomic.LoadInt32(&self.mining) == 0 {
self.currentMu.Lock()
txs := make(map[common.Address]types.Transactions)
for _, tx := range ev.Txs {
acc, _ := types.Sender(self.current.signer, tx)
txs[acc] = append(txs[acc], 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.txsSub.Err():
return
case <-self.chainHeadSub.Err():
return
case <-self.chainSideSub.Err():
return
}
}
}
worker.update()分别监听ChainHeadEvent,ChainSideEvent,TxPreEvent 几个事件,每个事件会触发worker不同的反应。ChainHeadEvent是指区块链中已经加入了一个新的区块作为整个链的链头,这时worker的回应是立即开始准备挖掘下一个新区块(也是够忙的);ChainSideEvent指区块链中加入了一个新区块作为当前链头的旁支,worker会把这个区块收纳进possibleUncles[]数组,作为下一个挖掘新区块可能的Uncle之一;TxPreEvent是TxPool对象发出的,指的是一个新的交易tx被加入了TxPool,这时如果worker没有处于挖掘中,那么就去执行这个tx,并把它收纳进Work.txs数组,为下次挖掘新区块备用。
【注意】:ChainHeadEvent并不一定是外部源发出。由于worker对象有个成员变量chain(eth.BlockChain),所以当worker自己完成挖掘一个新区块,并把它写入数据库,加进区块链里成为新的链头时,worker自己也可以调用chain发出一个ChainHeadEvent,从而被worker.update()函数监听到,进入下一次区块挖掘。
go worker.wait():
func (self *worker) wait() {
for {
for result := range self.recv {
atomic.AddInt32(&self.atWork, -1)
if result == nil {
continue
}
block := result.Block
work := result.Work
// Update the block hash in all logs since it is now available and not when the
// receipt/log of individual transactions were created.
for _, r := range work.receipts {
for _, l := range r.Logs {
l.BlockHash = block.Hash()
}
}
for _, log := range work.state.Logs() {
log.BlockHash = block.Hash()
}
self.currentMu.Lock()
stat, err := self.chain.WriteBlockWithState(block, work.receipts, work.state)
self.currentMu.Unlock()
if err != nil {
log.Error("Failed writing block to chain", "err", err)
continue
}
// Broadcast the block and announce chain insertion event
self.mux.Post(core.NewMinedBlockEvent{Block: block})
var (
events []interface{}
logs = work.state.Logs()
)
events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
if stat == core.CanonStatTy {
events = append(events, core.ChainHeadEvent{Block: block})
}
self.chain.PostChainEvents(events, logs)
// Insert the block into the set of pending ones to wait for confirmations
self.unconfirmed.Insert(block.NumberU64(), block.Hash())
}
}
}
worker.wait()会在一个channel处一直等待Agent完成挖掘发送回来的新Block和Work对象。这个Block会被写入数据库,加入本地的区块链试图成为最新的链头。而Wotk里头其实是各种收据信息,最终也会写入 reciept树;注意,此时区块中的所有交易,假设都已经被执行过了,所以这里的操作,不会再去执行这些交易对象。
当这一切都完成,worker就会发送一条事件(NewMinedBlockEvent{}),等于通告天下:我挖出了一个新区块!这样监听到该事件的其他节点,就会根据自身的状况,来决定是否接受这个新区块成为全网中公认的区块链新的链头。至于这个公认过程如何实现,就属于共识算法的范畴了。而发送NewMinedBlockEvent 事件的动作是在该方法最底下的 一个函数调用中实现的 self.chain.PostChainEvents(events, logs);
我们再来看看 worker.commitNewWork():
他里头所做的事无非就是把需要打包成Block的一些相关信息先封装到Work对象中,其中里头有涉及到DAO硬分叉的特殊处理和把当前状态置为最后状态封装到Work中的调用:
【注意】在挖矿过程中主要涉及Prepare() Finalize() Seal() 接口,三者的职责分别为
Prepare() 初始化新Block的Header
Finalize() 在执行完交易后,对Block进行修改(比如向矿工发放挖矿所得)
Seal() 实际的挖矿工作
最后通过push方法把Work传递给CpuAgent:
【注意】:commitNewWork()会在worker内部多处被调用,注意它每次都是被直接调用,并没有以goroutine的方式启动。commitNewWork()内部使用sync.Mutex对全部操作做了隔离。
该函数主要做了以下操作:
- 准备新区块的时间属性Header.Time,一般均等于系统当前时间,不过要确保父区块的时间(parentBlock.Time())要早于新区块的时间,父区块当然来自当前区块链的链头了。
- 创建新区块的Header对象,其各属性中:Num可确定(父区块Num +1);Time可确定;ParentHash可确定;其余诸如Difficulty,GasLimit等,均留待之后共识算法中确定。
- 调用Engine.Prepare()函数,完成Header对象的准备。
- 根据新区块的位置(Number),查看它是否处于DAO硬分叉的影响范围内,如果是,则赋值予header.Extra。
- 根据已有的Header对象,创建一个新的Work对象,并用其更新worker.current成员变量。
- 如果配置信息中支持硬分叉,在Work对象的StateDB里应用硬分叉。
- 准备新区块的交易列表,来源是TxPool中那些最近加入的tx,并执行这些交易。
- 准备新区块的叔区块uncles[],来源是worker.possibleUncles[],而possibleUncles[]中的每个区块都从事件ChainSideEvent中搜集得到。注意叔区块最多有两个。
- 调用Engine.Finalize()函数,对新区块“定型”,填充上Header.Root, TxHash, ReceiptHash, UncleHash等几个属性。
- 如果上一个区块(即旧的链头区块)处于unconfirmedBlocks中,意味着它也是由本节点挖掘出来的,尝试去验证它已经被吸纳进主干链中。
- 把创建的Work对象,通过channel发送给每一个登记过的Agent,进行后续的挖掘。
以上步骤中,4和6都是仅仅在该区块配置中支持DAO硬分叉,并且该区块的位置正好处于DAO硬分叉影响范围内时才会发生;其他步骤是普遍性的。commitNewWork()完成了待挖掘区块的组装,block.Header创建完毕,交易数组txs,叔区块Uncles[]都已取得,并且由于所有交易被执行完毕,相应的Receipt[]也已获得。万事俱备,可以交给Agent进行‘挖掘’了。
我们再看看worker.push():
// 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对象传递给了CpuAgent;
我们在往下看CpuAgent都做了什么事:
首先我们会看到外层的miner的start函数会调用worker的start函数其实是调用了CpuAgent的start函数,而在CpuAgent的start中我们看到只调用了自己的update函数而已。
func (self *CpuAgent) Start() {
if !atomic.CompareAndSwapInt32(&self.isMining, 0, 1) {
return // agent already started
}
go self.update()
}
我们再看看Agent的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
}
}
}
很显然,里头只做了一直在监听由worker发过来的Work对象和是否停止挖矿的信号;如果间听到了Work对象则启动一异步的mine函数去做挖矿动作。
我们再看Agent的mine函数:
func (self *CpuAgent) mine(work *Work, stop <-chan struct{}) {
if result, err := self.engine.Seal(self.chain, work.Block, stop); result != nil {
log.Info("Successfully sealed new block", "number", result.Number(), "hash", result.Hash())
self.returnCh <- &Result{work, result}
} else {
if err != nil {
log.Warn("Block sealing failed", "err", err)
}
self.returnCh <- nil
}
}
会看到其实真正的挖矿是交由engine的实现类去做底层共识进行挖矿的【说白了就是求目标Hash】并且把打包好的Block和Work都组装到Result中返回给worker。而上面我们说了worker会在worker.wait()中实时监听返回的Result,分别提取出Block和Work去做区块上链和时间广播等操作。OK,那么这就是老版本的挖矿逻辑;下面我们来看看新版本的挖矿逻辑是个什么鬼;
由上面我们说了新版本,如:1.8.14已经把Agent移除了,所有挖矿的逻辑都在worker中完成,下面我们从miner入口来看看本次的变动:
我们可以看到在新版的miner的New函数中已经把Agent注册的逻辑移除了
func New(eth Backend, config *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, recommit time.Duration) *Miner {
miner := &Miner{
eth: eth,
mux: mux,
engine: engine,
exitCh: make(chan struct{}),
worker: newWorker(config, engine, eth, mux, recommit),
canStart: 1,
}
go miner.update()
return miner
}
我们直接过来看newWorker函数中的实现:
func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, recommit time.Duration) *worker {
worker := &worker{
config: config,
engine: engine,
eth: eth,
mux: mux,
chain: eth.BlockChain(),
possibleUncles: make(map[common.Hash]*types.Block),
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
txsCh: make(chan core.NewTxsEvent, txChanSize),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
newWorkCh: make(chan *newWorkReq),
taskCh: make(chan *task),
resultCh: make(chan *task, resultQueueSize),
exitCh: make(chan struct{}),
startCh: make(chan struct{}, 1),
resubmitIntervalCh: make(chan time.Duration),
resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize),
}
// Subscribe NewTxsEvent for tx pool
worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
// Subscribe events for blockchain
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
// Sanitize recommit interval if the user-specified one is too short.
if recommit < minRecommitInterval {
log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval)
recommit = minRecommitInterval
}
go worker.mainLoop()
go worker.newWorkLoop(recommit)
go worker.resultLoop()
go worker.taskLoop()
// Submit first work to initialize pending state.
worker.startCh <- struct{}{}
return worker
}
从图中我们可以看到,新版本的worker中新添加或者更改了以下几个通道:
最底层有4个 异步的协程【请注意这四个协程】:
其中 newWorkLoop 是一个一直监听 startCh 中是否有挖矿信号 (startCh的信号有 start函数放置进去的):
在 go worker.mainLoop中:
func (w *worker) newWorkLoop(recommit time.Duration) {
var (
interrupt *int32
minRecommit = recommit // minimal resubmit interval specified by user.
)
timer := time.NewTimer(0)
<-timer.C // discard the initial tick
// commit aborts in-flight transaction execution with given signal and resubmits a new one.
commit := func(noempty bool, s int32) {
if interrupt != nil {
atomic.StoreInt32(interrupt, s)
}
interrupt = new(int32)
w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty}
timer.Reset(recommit)
atomic.StoreInt32(&w.newTxs, 0)
}
// recalcRecommit recalculates the resubmitting interval upon feedback.
recalcRecommit := func(target float64, inc bool) {
var (
prev = float64(recommit.Nanoseconds())
next float64
)
if inc {
next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target+intervalAdjustBias)
// Recap if interval is larger than the maximum time interval
if next > float64(maxRecommitInterval.Nanoseconds()) {
next = float64(maxRecommitInterval.Nanoseconds())
}
} else {
next = prev*(1-intervalAdjustRatio) + intervalAdjustRatio*(target-intervalAdjustBias)
// Recap if interval is less than the user specified minimum
if next < float64(minRecommit.Nanoseconds()) {
next = float64(minRecommit.Nanoseconds())
}
}
recommit = time.Duration(int64(next))
}
for {
select {
case <-w.startCh:
commit(false, commitInterruptNewHead)
case <-w.chainHeadCh:
commit(false, commitInterruptNewHead)
case <-timer.C:
// If mining is running resubmit a new work cycle periodically to pull in
// higher priced transactions. Disable this overhead for pending blocks.
if w.isRunning() && (w.config.Clique == nil || w.config.Clique.Period > 0) {
// Short circuit if no new transaction arrives.
if atomic.LoadInt32(&w.newTxs) == 0 {
timer.Reset(recommit)
continue
}
commit(true, commitInterruptResubmit)
}
case interval := <-w.resubmitIntervalCh:
// Adjust resubmit interval explicitly by user.
if interval < minRecommitInterval {
log.Warn("Sanitizing miner recommit interval", "provided", interval, "updated", minRecommitInterval)
interval = minRecommitInterval
}
log.Info("Miner recommit interval update", "from", minRecommit, "to", interval)
minRecommit, recommit = interval, interval
if w.resubmitHook != nil {
w.resubmitHook(minRecommit, recommit)
}
case adjust := <-w.resubmitAdjustCh:
// Adjust resubmit interval by feedback.
if adjust.inc {
before := recommit
recalcRecommit(float64(recommit.Nanoseconds())/adjust.ratio, true)
log.Trace("Increase miner recommit interval", "from", before, "to", recommit)
} else {
before := recommit
recalcRecommit(float64(minRecommit.Nanoseconds()), false)
log.Trace("Decrease miner recommit interval", "from", before, "to", recommit)
}
if w.resubmitHook != nil {
w.resubmitHook(minRecommit, recommit)
}
case <-w.exitCh:
return
}
}
}
我们可以看到:
如果有 就提交一个挖矿作业 方法 commit()
我们又可以看到 commit 里面其实是 构造了一个 挖矿的请求 实体而已,并且再把 请求实体交由 w.newWorkCh 通道【请记住这个通道】;
这是在 newWorkLoop中定义的内部匿名函数 commit:
// commit aborts in-flight transaction execution with given signal and resubmits a new one.
commit := func(noempty bool, s int32) {
if interrupt != nil {
atomic.StoreInt32(interrupt, s)
}
interrupt = new(int32)
w.newWorkCh <- &newWorkReq{interrupt: interrupt, noempty: noempty}
timer.Reset(recommit)
atomic.StoreInt32(&w.newTxs, 0)
}
紧接着我们再来看 newWorker 初始化函数中的四个协程之一的 worker.mainLoop():
// mainLoop is a standalone goroutine to regenerate the sealing task based on the received event.
func (w *worker) mainLoop() {
defer w.txsSub.Unsubscribe()
defer w.chainHeadSub.Unsubscribe()
defer w.chainSideSub.Unsubscribe()
for {
select {
case req := <-w.newWorkCh:
w.commitNewWork(req.interrupt, req.noempty)
case ev := <-w.chainSideCh:
if _, exist := w.possibleUncles[ev.Block.Hash()]; exist {
continue
}
// Add side block to possible uncle block set.
w.possibleUncles[ev.Block.Hash()] = ev.Block
// If our mining block contains less than 2 uncle blocks,
// add the new uncle block if valid and regenerate a mining 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 {
hash, ok := item.(common.Hash)
if !ok {
return false
}
uncle, exist := w.possibleUncles[hash]
if !exist {
return false
}
uncles = append(uncles, uncle.Header())
return false
})
w.commit(uncles, nil, true, start)
}
}
case ev := <-w.txsCh:
// Apply transactions to the pending state if we're not mining.
//
// Note all transactions received may not be continuous with transactions
// already included in the current mining block. These transactions will
// be automatically eliminated.
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 we're mining, but nothing is being processed, wake on new transactions
if w.config.Clique != nil && w.config.Clique.Period == 0 {
w.commitNewWork(nil, false)
}
}
atomic.AddInt32(&w.newTxs, int32(len(ev.Txs)))
// System stopped
case <-w.exitCh:
return
case <-w.txsSub.Err():
return
case <-w.chainHeadSub.Err():
return
case <-w.chainSideSub.Err():
return
}
}
}
我们就可以看到在里面有对 newWorkLoop 中的那个commit函数中的 w.newWorkCh 通道,且在里头做实时的监听,一发现有内容就会把它提交给一个叫做 w.commitNewWork(req.interrupt, req.noempty) 挖矿作业提交函数,我们再跟进去看,发现他无非就是做各种各样的 block 预处理工作,到方法的末尾 交付给了 w.commit(uncles, w.fullTaskHook, true, tstart)。
我们再跟进去 看看里面是做了什么
// commit runs any post-transaction state modifications, assembles the final block
// and commits new work if consensus engine is running.
func (w *worker) commit(uncles []*types.Header, interval func(), update bool, start time.Time) error {
// Deep copy receipts here to avoid interaction between different tasks.
receipts := make([]*types.Receipt, len(w.current.receipts))
for i, l := range w.current.receipts {
receipts[i] = new(types.Receipt)
*receipts[i] = *l
}
s := w.current.state.Copy()
block, err := w.engine.Finalize(w.chain, w.current.header, s, w.current.txs, uncles, w.current.receipts)
if err != nil {
return err
}
if w.isRunning() {
if interval != nil {
interval()
}
select {
case w.taskCh <- &task{receipts: receipts, state: s, block: block, createdAt: time.Now()}:
w.unconfirmed.Shift(block.NumberU64() - 1)
feesWei := new(big.Int)
for i, tx := range block.Transactions() {
feesWei.Add(feesWei, new(big.Int).Mul(new(big.Int).SetUint64(receipts[i].GasUsed), tx.GasPrice()))
}
feesEth := new(big.Float).Quo(new(big.Float).SetInt(feesWei), new(big.Float).SetInt(big.NewInt(params.Ether)))
log.Info("Commit new mining work", "number", block.Number(), "uncles", len(uncles), "txs", w.current.tcount,
"gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start)))
case <-w.exitCh:
log.Info("Worker has exited")
}
}
if update {
w.updateSnapshot()
}
return nil
}
我们发现 w.commit(uncles, w.fullTaskHook, true, tstart) 里面是先对 收据数据先做一些处理然后把 构造好的 收据 receipts 状态 state 及预先打包好的block (里头有些内容需要真正挖矿来回填的,比如 随机数,出块时间等等) 构造成一个任务实体 task 对象并提交给 w.taskCh 通道 【注意了 重头戏来了】,以及调用 engine.Finalize 函数做当前状态的封装。
我们再回去看 newWorker 函数中四个协程的 worker.taskLoop():
在里头一直监听这个 taskCh 通道过来的 task 实体,一有就启动 seal() 函数把这些信息 交由底层的共识层的实现去 做真正的挖矿
我们跟进 go w.seal(task, stopCh) 里头看看做了什么:
发现把之前预先构造好的 block 等都交由底层共识去做了,最终把挖出来的 block 通过 w.resultCh 通道返回,【这一步和以前用Agent 挖矿的逻辑的底层一致】
最后处理结果是那四个协程中的 go worker.resultLoop() 来处理的:
// resultLoop is a standalone goroutine to handle sealing result submitting
// and flush relative data to the database.
func (w *worker) resultLoop() {
for {
select {
case result := <-w.resultCh:
// Short circuit when receiving empty result.
if result == nil {
continue
}
// Short circuit when receiving duplicate result caused by resubmitting.
block := result.block
if w.chain.HasBlock(block.Hash(), block.NumberU64()) {
continue
}
// Update the block hash in all logs since it is now available and not when the
// receipt/log of individual transactions were created.
for _, r := range result.receipts {
for _, l := range r.Logs {
l.BlockHash = block.Hash()
}
}
for _, log := range result.state.Logs() {
log.BlockHash = block.Hash()
}
// Commit block and state to database.
stat, err := w.chain.WriteBlockWithState(block, result.receipts, result.state)
if err != nil {
log.Error("Failed writing block to chain", "err", err)
continue
}
// Broadcast the block and announce chain insertion event
w.mux.Post(core.NewMinedBlockEvent{Block: block})
var (
events []interface{}
logs = result.state.Logs()
)
switch stat {
case core.CanonStatTy:
events = append(events, core.ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
events = append(events, core.ChainHeadEvent{Block: block})
case core.SideStatTy:
events = append(events, core.ChainSideEvent{Block: block})
}
w.chain.PostChainEvents(events, logs)
// Insert the block into the set of pending ones to resultLoop for confirmations
w.unconfirmed.Insert(block.NumberU64(), block.Hash())
case <-w.exitCh:
return
}
}
}