Go-ethereum 源碼解析之 miner/worker.go (下)

Go-ethereum 源碼解析之 miner/worker.go (下)

Appendix D. 詳細(xì)批注

1. const

  • resultQueueSize: 指用于監(jiān)聽驗(yàn)證結(jié)果的通道(worker.resultCh)的緩存大小竿滨。這里的驗(yàn)證結(jié)果是已經(jīng)被簽名了的區(qū)塊轮傍。
  • txChanSize: 指用于監(jiān)聽事件 core.NewTxsEvent 的通道(worker.txsCh)的緩存大小。這里的緩存大小引用自事務(wù)池的大小风题。其中沛励,事件 core.NewTxsEvent 是事務(wù)列表( []types.Transaction)的封裝器施掏。
  • chainHeadChanSize: 指用于監(jiān)聽事件 core.ChainHeadEvent 的通道(worker.chainHeadCh)的緩存大小。事件 core.ChainHeadEvent 是區(qū)塊(types.Block)的封裝器。
  • chainSideChanSize: 指用于監(jiān)聽事件 core.ChainSideEvent 的通道(worker.chainSideCh)的緩存大小倍奢。事件 core.ChainSideEvent 是區(qū)塊(types.Block)的封裝器。
  • resubmitAdjustChanSize: 指用于重新提交間隔調(diào)整的通道(worker.resubmitAdjustCh)的緩存大小垒棋。 緩存的消息結(jié)構(gòu)為 intervalAdjust卒煞,用于描述下一次提交間隔的調(diào)整因數(shù)。
  • miningLogAtDepth: 指記錄成功挖礦時(shí)需要達(dá)到的確認(rèn)數(shù)叼架。是 miner.unconfirmedBlocks 的深度 畔裕。即本地節(jié)點(diǎn)挖出的最新區(qū)塊如果需要得到整個(gè)網(wǎng)絡(luò)的確認(rèn),需要整個(gè)網(wǎng)絡(luò)再挖出 miningLogAtDepth 個(gè)區(qū)塊乖订。舉個(gè)例子:本地節(jié)點(diǎn)挖出了編號(hào)為 1 的區(qū)塊扮饶,需要等到整個(gè)網(wǎng)絡(luò)中某個(gè)節(jié)點(diǎn)(也可以是本地節(jié)點(diǎn))挖出編號(hào)為 8 的區(qū)塊(8 = 1 + miningLogAtDepth, miningLogAtDepth = 7)之后,則編號(hào)為 1 的區(qū)塊就成為了經(jīng)典鏈的一部分乍构。
  • minRecommitInterval: 指使用任何新到達(dá)的事務(wù)重新創(chuàng)建挖礦區(qū)塊的最小時(shí)間間隔甜无。當(dāng)用戶設(shè)定的重新提交間隔太小時(shí)進(jìn)行修正。
  • maxRecommitInterval: 指使用任何新到達(dá)的事務(wù)重新創(chuàng)建挖礦區(qū)塊的最大時(shí)間間隔。當(dāng)用戶設(shè)定的重新提交間隔太大時(shí)進(jìn)行修正毫蚓。
  • intervalAdjustRatio: 指單個(gè)間隔調(diào)整對(duì)驗(yàn)證工作重新提交間隔的影響因子占键。與參數(shù) intervalAdjustBias 一起決定下一次提交間隔。
  • intervalAdjustBias: 指在新的重新提交間隔計(jì)算期間應(yīng)用intervalAdjustBias元潘,有利于增加上限或減少下限畔乙,以便可以訪問限制。與參數(shù) intervalAdjustRatio 一起決定下一次提交間隔翩概。
  • staleThreshold: 指可接受的舊區(qū)塊的最大深度牲距。注意,目前钥庇,這個(gè)值與 miningLogAtDepth 都是 7牍鞠,且表達(dá)的意思也基本差不多,是不是有一定的內(nèi)存聯(lián)系评姨。

2. type environment struct

數(shù)據(jù)結(jié)構(gòu) environment 描述了 worker 的當(dāng)前環(huán)境难述,并且包含所有的當(dāng)前狀態(tài)信息。

最主要的狀態(tài)信息有:簽名者(即本地節(jié)點(diǎn)的礦工)吐句、狀態(tài)樹(主要是記錄賬戶余額等狀態(tài)胁后?)、緩存的祖先區(qū)塊嗦枢、緩存的叔區(qū)塊攀芯、當(dāng)前周期內(nèi)的事務(wù)數(shù)量、當(dāng)前打包中區(qū)塊的區(qū)塊頭文虏、事務(wù)列表(用于構(gòu)建當(dāng)前打包中區(qū)塊)侣诺、收據(jù)列表(用于和事務(wù)列表一一對(duì)應(yīng),構(gòu)建當(dāng)前打包中區(qū)塊)氧秘。

  • signer types.Signer: 簽名者年鸳,即本地節(jié)點(diǎn)的礦工,用于對(duì)區(qū)塊進(jìn)行簽名敏储。

  • state *state.StateDB: 狀態(tài)樹阻星,用于描述賬戶相關(guān)的狀態(tài)改變,merkle trie 數(shù)據(jù)結(jié)構(gòu)已添⊥谆可以在此修改本節(jié)節(jié)點(diǎn)的狀態(tài)信息。

  • ancestors mapset.Set: ??? ancestors 區(qū)塊集合(用于檢查叔區(qū)塊的有效性)更舞。緩存畦幢。緩存數(shù)據(jù)結(jié)構(gòu)中往往存的是區(qū)塊的哈希±虏酰可以簡(jiǎn)單地認(rèn)為區(qū)塊宇葱、區(qū)塊頭瘦真、區(qū)塊哈希、區(qū)塊頭哈希能夠等價(jià)地描述區(qū)塊黍瞧,其中的任何一種方式都能惟一標(biāo)識(shí)同一個(gè)區(qū)塊诸尽。甚至可以放寬到區(qū)塊編號(hào)。

  • family mapset.Set: ??? family 區(qū)塊集合(用于驗(yàn)證無(wú)效叔區(qū)塊)印颤。family 區(qū)塊集合比 ancestors 區(qū)塊集合多了各祖先區(qū)塊的叔區(qū)塊您机。ancestors 區(qū)塊集合是區(qū)塊的直接父區(qū)塊一級(jí)一級(jí)連接起來的。

  • uncles mapset.Set: 叔區(qū)塊集合年局,即當(dāng)前區(qū)塊的叔區(qū)塊集合际看,或者說當(dāng)前正在挖的區(qū)塊的叔區(qū)塊集合。

  • tcount int: 一個(gè)周期里面的事務(wù)數(shù)量

  • gasPool *core.GasPool: 用于打包事務(wù)的可用 gas

  • header *types.Header: 區(qū)塊頭矢否。區(qū)塊頭需要滿足通用的以太坊協(xié)議共識(shí)仲闽,還需要滿足特定的 PoA 共識(shí)協(xié)議。與 PoA 共識(shí)協(xié)議相關(guān)的區(qū)塊頭 types.Header 字段用 Clique.Prepare() 方法進(jìn)行主要的設(shè)置僵朗,Clique.Finalize() 方法進(jìn)行最終的補(bǔ)充設(shè)置赖欣。那么以太坊協(xié)議共識(shí)相關(guān)的字段在哪里設(shè)置?或者說在 worker 的哪個(gè)方法中設(shè)置验庙。

  • txs []*types.Transaction: 事務(wù)(types.Transaction)列表畏鼓。當(dāng)前需要打包的事務(wù)列表(或者備選事務(wù)列表),可不可以理解為事務(wù)池壶谒。

  • receipts []*types.Receipt: 收據(jù)(types.Receipt)列表。Receipt 表示 Transaction 一一對(duì)應(yīng)的結(jié)果膳沽。

3. type task struct

數(shù)據(jù)結(jié)構(gòu) task 包含共識(shí)引擎簽名和簽名之后的結(jié)果提交的所有信息汗菜。

簽名即對(duì)已經(jīng)組裝好的區(qū)塊添加最后的簽名信息。添加了簽名的區(qū)塊即為最終的結(jié)果區(qū)塊挑社,即簽名區(qū)塊或待確認(rèn)區(qū)塊陨界。

數(shù)據(jù)結(jié)構(gòu) task 和數(shù)據(jù)結(jié)構(gòu) environment 的區(qū)別:

  • 數(shù)據(jù)結(jié)構(gòu) environment 用于 worker 的所有操作

  • 數(shù)據(jù)結(jié)構(gòu) task 僅用于 worker 的簽名相關(guān)操作

  • receipts []*types.Receipt: 收據(jù)(types.Receipt)列表

  • state *state.StateDB: 狀態(tài)樹,用于描述賬戶相關(guān)的狀態(tài)改變痛阻,merkle trie 數(shù)據(jù)結(jié)構(gòu)菌瘪。可以在此修改本節(jié)節(jié)點(diǎn)的狀態(tài)信息阱当。

  • block *types.Block: 待簽名的區(qū)塊俏扩。此時(shí),區(qū)塊已經(jīng)全部組裝好了弊添,包信了事務(wù)列表录淡、叔區(qū)塊列表。同時(shí)油坝,區(qū)塊頭中的字段已經(jīng)全部組裝好了嫉戚,就差最后的簽名刨裆。簽名后的區(qū)塊是在此原有區(qū)塊上新創(chuàng)建的區(qū)塊,并被發(fā)送到結(jié)果通道彬檀,用于驅(qū)動(dòng)本地節(jié)點(diǎn)已經(jīng)挖出新區(qū)塊之后的流程帆啃。

  • createdAt time.Time: task 的創(chuàng)建時(shí)間

數(shù)據(jù)結(jié)構(gòu) task 也是通道 worker.taskCh 發(fā)送或接收的消息。

4. const

  • commitInterruptNone 無(wú)效的中斷值
  • commitInterruptNewHead 用于描述新區(qū)塊頭到達(dá)的中斷值窍帝,當(dāng) worker 啟動(dòng)或重新啟動(dòng)時(shí)也是這個(gè)中斷值努潘。
  • commitInterruptResubmit 用于描述 worker 根據(jù)接收到的新事務(wù),中止之前挖礦盯桦,并重新開始挖礦的中斷值慈俯。

5. type newWorkReq struct

數(shù)據(jù)結(jié)構(gòu) newWorkReq 表示使用相應(yīng)的中斷值通知程序提交新簽名工作的請(qǐng)求。

數(shù)據(jù)結(jié)構(gòu) newWorkReq 也是通道 worker.newWorkCh 發(fā)送或接收的消息拥峦。

  • interrupt *int32: 具體的中斷值贴膘,為 commitInterruptNewHead 或 commitInterruptResubmit 之一。
  • noempty bool: ??? 表示創(chuàng)建的區(qū)塊是否包含事務(wù)略号?
  • timestamp int64: ??? 表示區(qū)塊開始組裝的時(shí)間刑峡?

6. type intervalAdjust struct

數(shù)據(jù)結(jié)構(gòu) intervalAdjust 表示重新提交間隔調(diào)整。

  • ratio float64: 間隔調(diào)整的比例
  • inc bool: 是上調(diào)還是下調(diào)

在當(dāng)前區(qū)塊時(shí)計(jì)算下一區(qū)塊的出塊大致時(shí)間玄柠,在基本的時(shí)間間隔之上進(jìn)行一定的微調(diào)突梦,微調(diào)的參數(shù)就是用數(shù)據(jù)結(jié)構(gòu) intervalAdjust 描述的,并發(fā)送給對(duì)應(yīng)的通道 resubmitAdjustCh羽利。下一個(gè)區(qū)塊在打包時(shí)從通道 resubmitAdjustCh 中獲取其對(duì)應(yīng)的微調(diào)參數(shù) intervalAdjust 實(shí)行微調(diào)宫患。

7. type worker struct

worker 是負(fù)責(zé)向共識(shí)引擎提交新工作并且收集簽名結(jié)果的主要對(duì)象。

共識(shí)引擎會(huì)做哪些工作呢这弧?

  • 通過方法 Clique.Prepare() 設(shè)置區(qū)塊頭中關(guān)于 PoA 共識(shí)的相關(guān)字段娃闲。
  • 通過方法 Clique.Finalize() 組裝可以被簽名的區(qū)塊。
  • 通過方法 Clieque.Seal() 對(duì)區(qū)塊進(jìn)行簽名匾浪,并發(fā)送給結(jié)果通道 worker.resultsCh皇帮。
  • 通過方法 Clique.snapshot() 處理兩種快照:檢查點(diǎn)快照和投票快照。

那么共識(shí)引擎需要哪些輸入呢蛋辈?

  • 區(qū)塊頭
  • 事務(wù)列表
  • 收據(jù)列表
  • 狀態(tài)樹
  • 叔區(qū)塊列表(PoA 共識(shí)協(xié)議中肯定為 nil)
  • 區(qū)塊属拾,是個(gè)抽象概念,主要包含:區(qū)塊頭冷溶、事務(wù)列表渐白、叔區(qū)塊列表,但是并不包含收據(jù)列表逞频。

那么共識(shí)引擎會(huì)產(chǎn)生哪些輸出呢礼预?

  • 方法 Clieque.Seal() 會(huì)將最終簽名后的區(qū)塊發(fā)送給結(jié)果通道 worker.resultsCh。

  • config *params.ChainConfig: 區(qū)塊鏈的鏈配置信息虏劲,包含鏈 ID托酸,是 ethash 還是 clique 共識(shí)協(xié)議等

  • engine consensus.Engine: 共識(shí)引擎接口

  • eth Backend: 后端褒颈,包含區(qū)塊鏈和事務(wù)池,提供挖礦所需的所有方法

  • chain *core.BlockChain: 表示整個(gè)區(qū)塊鏈励堡。這不和 eth 中的區(qū)塊鏈?zhǔn)峭粋€(gè)谷丸?

  • gasFloor uint64: 最低 gas

  • gasCeil uint64: 最高 gas

// 訂閱

  • mux *event.TypeMux: 可以簡(jiǎn)單地理解為事件的訂閱管理器,即注冊(cè)事件的響應(yīng)函數(shù)应结,和驅(qū)動(dòng)事件的響應(yīng)函數(shù)刨疼。
  • txsCh chan core.NewTxsEvent: 用于在不同協(xié)程之間交互事件 core.NewTxsEvent 的通道。事件 core.NewTxsEvent 是事務(wù)列表 []*types.Transaction 的封裝器鹅龄,即通道 txsCh 用于在不同協(xié)程之間交互事務(wù)列表揩慕。命名協(xié)程 worker.mainLoop() 從通道 txsCh 接收事件 core.NewTxsEvent,即事務(wù)列表扮休。使用通道 txsCh 作為只接收消息的通道向 core.TxPool 訂閱事件 core.NewTxsEvent迎卤,那么應(yīng)該是從 core.TxPool 發(fā)送事件 core.NewTxsEvent 到通道 txsCh。
  • txsSub event.Subscription: 向事務(wù)池(core.TxPool)訂閱事件 core.NewTxsEvent玷坠,并使用通道 txsCh 作為此次訂閱接收消息的通道蜗搔。代碼為 worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)。
  • chainHeadCh chan core.ChainHeadEvent: 用于在不同協(xié)程之間交互事件 core.ChainHeadEvent 的通道八堡。事件 core.ChainHeadEvent 是區(qū)塊 types.Block 的封裝器樟凄,即通道 chainHeadCh 用于不同協(xié)程之間交互新挖出的區(qū)塊頭。命名協(xié)程 worker.newWorkLoop() 從通道 chainHeadCh 接收事件 core.ChainHeadEvent兄渺,即新的區(qū)塊頭缝龄。使用通道 chainHeadCh 作為只接收消息的通道向 core.BlockChain 訂閱事件 core.ChainHeadEvent,那么應(yīng)該是從 core.BlockChain 發(fā)送事件 core.ChainHeadEvent 到通道 chainHeadCh挂谍。
  • chainHeadSub event.Subscription: 向區(qū)塊鏈(core.BlockChain)訂閱事件 core.ChainHeadEvent二拐,并使用通道 chainHeadCh 作為此次訂閱接收消息的通道。代碼為 worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
  • chainSideCh chan core.ChainSideEvent: 用于在不同協(xié)程之間交互事件 core.ChainSideEvent 的通道凳兵。事件 core.ChainSideEvent 是區(qū)塊 types.Block 的封裝器,即通道 chainSideCh 用于不同協(xié)程之間交互新挖出的區(qū)塊頭企软。命名協(xié)程 worker.mainLoop() 從通道 chainSideCh 接收事件 core.ChainSideEvent庐扫,即新的叔區(qū)塊頭(但 PoA 不是不存在叔區(qū)塊?)仗哨。使用通道 chainSideCh 作為只接收消息的通道向 core.BlockChain 訂閱事件 core.ChainSideEvent形庭,那么應(yīng)該是從 core.BlockChain 發(fā)送事件 core.ChainSideEvent 到通道 chainSideCh。
  • chainSideSub event.Subscription: 向區(qū)塊鏈(core.BlockChain)訂閱事件 core.ChainSideEvent厌漂,并使用通道 chainSideCh 作為此次訂閱接收消息的通道萨醒。代碼為 worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)

// 通道

  • newWorkCh chan *newWorkReq: 通道 newWorkCh 用于在不同協(xié)程之間交互消息 newWorkReq 的通道。命名協(xié)程 worker.newWorkLoop() 將消息 newWorkReq 發(fā)送給通道 newWorkCh苇倡。命名協(xié)程 worker.mainLoop() 從通道 newWorkCh 中接收消息 newWorkReq富纸。

  • taskCh chan *task: 通道 taskCh 用于在不同協(xié)程之間交互消息 task 的通道囤踩。(1)命名協(xié)程 worker.taskLoop() 從通道 taskCh 中接收消息 task。對(duì)接收到的消息 task 先存入待處理 map 中晓褪,其中 Key 為 task 中的區(qū)塊簽名哈希堵漱,Value 為 task。同時(shí)涣仿,將 task 中的區(qū)塊傳遞給共識(shí)引擎的簽名方法 w.engine.Seal() 進(jìn)行簽名勤庐,同時(shí)將結(jié)果通道 w.resultCh 和退出通道 stopCh 也傳遞給共識(shí)引擎的簽名方法,以便從中接收簽名之后的區(qū)塊或者接收中止消息好港。(2)命名協(xié)程 worker.mainLoop() 中的方法 worker.commit() 將消息 task 發(fā)送給通道 taskCh愉镰。此方法先將當(dāng)前環(huán)境中的區(qū)塊頭(w.current.header)、事務(wù)列表(w.current.txs)钧汹、收據(jù)列表(w.current.receipts)作為參數(shù)傳遞給共識(shí)引擎的方法 Finalize() 組裝出待簽名的區(qū)塊丈探,代碼為 block = w.engine.Finalize(w.chain, w.current.header, s, w.current.txs, uncles, w.current.receipts)。需要注意的是崭孤,區(qū)塊 types.Block 中只包含區(qū)塊頭 types.Header类嗤、事務(wù)列表 []types.Transaction、叔區(qū)塊列表 []types.Header辨宠,并不包含收據(jù)列表 []types.Receipt遗锣,但是區(qū)塊頭 types.Header 中的字段 ReceiptHash 是收據(jù)列表樹的根哈希,所以也需要收據(jù)列表參數(shù)嗤形。將組裝后的待簽名區(qū)塊 types.Block精偿,及前面解釋過的收據(jù)列表 []types.Receipt 等其它參數(shù)一起構(gòu)建出新的任務(wù) task 發(fā)送給通道 taskCh,同時(shí)輸出一條重要的日志信息:log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start)))赋兵。到方法 commit() 這一步笔咽,已經(jīng)組裝出了新的任務(wù) task,并將此新任務(wù) task 通過通道 taskCh 發(fā)送給命名協(xié)程 worker.taskLoop()霹期。

  • resultCh chan *types.Block: 通道 resultCh 用于在不同協(xié)程之間交互消息 types.Block叶组。(1)命名協(xié)程 worker.resultLoop() 從通道 resultCh 中接收消息 types.Block,且此區(qū)塊是被簽名過的历造。對(duì)于新接收到簽名區(qū)塊甩十,首先判斷這個(gè)簽名區(qū)塊是否為重復(fù)的;其次吭产,需要從待處理任務(wù)映射 w.pendingTasks 中獲得對(duì)應(yīng)區(qū)塊簽名哈希的任務(wù) task侣监,如果沒找到則輸出一條重要的日志信息:log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash)。并從 task 中恢復(fù) receipts 和 logs臣淤。第三橄霉,將簽名區(qū)塊及其對(duì)應(yīng)的收據(jù)列表和狀態(tài)樹等信息寫入數(shù)據(jù)庫(kù)。如果寫入失敗邑蒋,則輸出一條重要的日志信息:log.Error("Failed writing block to chain", "err", err)姓蜂,否則輸出一條重要的日志信息:log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt)))按厘。第四,通過新挖出的簽名區(qū)塊構(gòu)建事件 core.NewMinedBlockEvent覆糟,并通過事件訂閱管理器中的方法 w.mux.Post() 將本地節(jié)點(diǎn)最新簽名的區(qū)塊向網(wǎng)絡(luò)中其它節(jié)點(diǎn)進(jìn)行廣播刻剥,這是基于 p2p 模塊完成的。第五滩字,同時(shí)構(gòu)建事件 core.ChainEvent 和事件 core.ChainHeadEvent造虏,或者構(gòu)建事件 core.ChainSideEvent,并通過區(qū)塊鏈中的方法 w.chain.PostChainEvents() 進(jìn)行廣播麦箍。需要注意的時(shí)漓藕,此廣播只是針對(duì)向本地節(jié)點(diǎn)進(jìn)行了事件注冊(cè)的客戶端,且是通過 JSON-RPC 完成挟裂,和第四步中的向網(wǎng)絡(luò)中其它節(jié)點(diǎn)通過 p2p 進(jìn)行廣播是完全不同的享钞。這一部的廣播即使沒有事件接收方也沒有問題,因?yàn)檫@是業(yè)務(wù)邏輯層面的诀蓉,而第四步中的廣播則是必須有接收方的栗竖,否則就會(huì)破壞以太坊協(xié)議本身。比如:我們可以注冊(cè)一個(gè)事件渠啤,用于監(jiān)控是否有最新的區(qū)塊被挖出來狐肢,然后在此基礎(chǔ)上,查詢指定賬戶的最新余額沥曹。第六步份名,將新挖出來的簽名區(qū)塊,添加進(jìn)待確認(rèn)隊(duì)列中妓美,代碼為:w.unconfirmed.Insert(block.NumberU64(), block.Hash())僵腺。(2)共識(shí)引擎中的簽名方法 Clique.Seal() 通過匿名協(xié)程將簽名后的簽名區(qū)塊 types.Block 發(fā)送到通道 resultCh。

  • startCh chan struct{}: 通道 startCh 用于在不同協(xié)程之間交互消息 struct{}壶栋〕饺纾可以發(fā)現(xiàn),消息 struct {} 沒有包含任何有意義的信息贵试,這在 Go 中是一類特別重要的寫法琉兜,用于由某個(gè)協(xié)程向另一個(gè)協(xié)程發(fā)送開始或中止消息。(1)函數(shù) newWorker() 向通道 startCh 發(fā)送消息 struct{}锡移,其中函數(shù) newWorker() 應(yīng)該是運(yùn)行在主協(xié)程中或由其它某個(gè)包中的協(xié)程啟動(dòng)。代碼為:worker.startCh <- struct{}{}漆际。(2)方法 worker.start() 向通道 startCh 發(fā)送消息 struct{}淆珊,其它同(1)。(3)命名協(xié)程 worker.newWorkLoop() 從通道 startCh 中接收消息 struct{}奸汇。需要注意的是施符,(1)和(2)都可以向通道 startCh 發(fā)送消息 struct{} 驅(qū)動(dòng)命名協(xié)程 worker.newWorkLoop() 中邏輯往声。方法 worker.start() 表明 worker 是可以先停止的,而不關(guān)閉戳吝,之后可以重新啟動(dòng)浩销。

  • exitCh chan struct{}: 通道 exitCh 用于在不同協(xié)程之間交互消息 struct{}√蓿可以參考通道 startCh 中的注釋慢洋。(1)函數(shù) worker.close() 通過調(diào)用函數(shù) close(w.exitCh) 整個(gè)關(guān)閉通道 exitCh。(2)命名協(xié)程 worker.newWorkLoop() 從通道 exitCh 中接收消息陆盘,從而結(jié)束整個(gè)協(xié)程普筹。(3)命名協(xié)程 worker.mainLoop() 從通道 exitCh 中接收消息,從而結(jié)束整個(gè)協(xié)程隘马。(4)命名協(xié)程 worker.taskLoop() 從通道 exitCh 中接收消息太防,從而結(jié)束整個(gè)協(xié)程。(5)命名協(xié)程 worker.resultLoop() 從通道 exitCh 中接收消息酸员,從而結(jié)束整個(gè)協(xié)程蜒车。(6)命名協(xié)程 worker.mainLoop() 調(diào)用的方法 worker.commit() 從通道 exitCh 中接收消息,從而放棄后續(xù)的工作幔嗦。

  • resubmitIntervalCh chan time.Duration: 通道 resubmitIntervalCh 用于在不同的協(xié)程之間交互消息 time.Duration酿愧。time.Duration 是 Go 語(yǔ)言標(biāo)準(zhǔn)庫(kù)中的類型,在這里通道 resubmitIntervalCh 起到一個(gè)定時(shí)器的作用崭添,這也是 Go 語(yǔ)言中關(guān)于定時(shí)器的標(biāo)準(zhǔn)實(shí)現(xiàn)方式寓娩。(1)方法 worker.setRecommitInterval() 向通道 resubmitIntervalCh 發(fā)送消息 time.Duration,即設(shè)置定時(shí)器下一次觸發(fā)的時(shí)間呼渣。方法 worker.setRecommitInterval() 在方法 Miner.SetRecommitInterval() 中被調(diào)用棘伴,方法 Miner.SetRecommitInterval() 又在方法 PrivateMinerAPI.SetRecommitInterval() 中調(diào)用,這應(yīng)該是從外部通過 JSON-RPC 接口驅(qū)動(dòng)的屁置。(2)命名協(xié)程 worker.newWorkLoop() 從通道 resubmitIntervalCh 中接收消息 time.Duration焊夸,即獲得希望定時(shí)器下一次觸發(fā)的時(shí)間,并根據(jù)需要對(duì)這個(gè)時(shí)間進(jìn)行一定的修正蓝角。

  • resubmitAdjustCh chan *intervalAdjust: 通道 resubmitAdjustCh 用于在不同的協(xié)程之間交互消息 intervalAdjust阱穗。(1)命名協(xié)程 worker.newWorkLoop() 從通道 resubmitAdjustCh 中接收消息 intervalAdjust。(2)方法 worker.commitTransactions() 向通道 resubmitAdjustCh 中發(fā)送消息 intervalAdjust使鹅。通道 resubmitAdjustCh 與通道 resubmitIntervalCh 的作用類似揪阶,都是修改下一個(gè)區(qū)塊的出塊時(shí)間。只不過通道 resubmitAdjustCh 中交互的消息 time.Duration 是由外部通過 JSON-RPC 接口來設(shè)定的患朱,而通道 resubmitIntervalCh 中交互的消息 intervalAdjust 是礦工根據(jù)上一個(gè)區(qū)塊的出塊時(shí)間基于算法自定調(diào)整的鲁僚。

  • current *environment: 描述了 worker 的當(dāng)前環(huán)境和狀態(tài)信息。具體的請(qǐng)參考對(duì)數(shù)據(jù)結(jié)構(gòu) environment 的注釋默蚌。

  • possibleUncles map[common.Hash]*types.Block: 可能的叔區(qū)塊集合洪碳。Key 為區(qū)塊哈希 common.Hash,Value 為區(qū)塊 types.Block羡滑。

  • unconfirmed *unconfirmedBlocks: 本地節(jié)點(diǎn)最近新挖出的區(qū)塊集合拓挥,用于等待網(wǎng)絡(luò)中其它節(jié)點(diǎn)的確認(rèn)唠梨,從而成為經(jīng)典鏈的一部分。具體的可以參考對(duì)數(shù)據(jù)結(jié)構(gòu) unconfirmedBlocks 的注釋侥啤。

  • mu sync.RWMutex: 鎖当叭,用于保護(hù)字段 coinbase 和 extra。

  • coinbase common.Address: 礦工地址愿棋。

  • extra []byte: 分為三段:前 32 字節(jié)礦工可隨意填寫科展,最后 65 字節(jié)為對(duì)區(qū)塊頭的簽名,中間的字節(jié)為授權(quán)簽名者列表的有序列連接糠雨,且字節(jié)數(shù)為 20 的倍數(shù)才睹。

  • pendingMu sync.RWMutex: 鎖,用于保護(hù)字段 pendingTasks甘邀。

  • pendingTasks map[common.Hash]*task: 待處理的任務(wù)映射琅攘,其中:Key 為 task 中包含的區(qū)塊的哈希值,Value 為 task松邪。

  • snapshotMu sync.RWMutex: 鎖坞琴,用于保護(hù)字段 snapshotBlock 和 snapshotState。

  • snapshotBlock *types.Block: 區(qū)塊的快照逗抑。

  • snapshotState *state.StateDB: 狀態(tài)的快照剧辐。

// 原子狀態(tài)的計(jì)數(shù)器

  • running int32: 用于表示共識(shí)引擎是否正在運(yùn)行。
  • newTxs int32: 自從上次簽名工作提交之后新到達(dá)的事務(wù)數(shù)量邮府。上次簽名工作即指 worker 中已經(jīng)通過調(diào)用共識(shí)引擎的 Finalize() 方法組裝好了待簽名的區(qū)塊荧关,然后通過調(diào)用共識(shí)引擎的簽名方法 Clique.Seal() 對(duì)待簽名區(qū)塊進(jìn)行簽名。即在上一個(gè)區(qū)塊被本地節(jié)點(diǎn)挖出之后褂傀,新來的事務(wù)數(shù)量忍啤。

// Test hooks

  • newTaskHook func(*task): 接收到新簽名任務(wù)時(shí)調(diào)用此方法。
  • skipSealHook func(*task) bool: 判定是否跳過簽名時(shí)調(diào)用 此方法仙辟。
  • fullTaskHook func(): 在推送完整簽名任務(wù)之前調(diào)用此方法同波。
  • resubmitHook func(time.Duration, time.Duration): 更新重新提交間隔時(shí)調(diào)用此方法。

(1) func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, recommit time.Duration, gasFloor, gasCeil uint64) *worker

構(gòu)造函數(shù) newWorker() 用于根據(jù)給定參數(shù)構(gòu)建 worker叠国。

主要參數(shù):

  • config *params.ChainConfig: 鏈的配置信息
  • engine consensus.Engine: 共識(shí)引擎
  • eth Backend: 以太坊本地節(jié)點(diǎn)的后端
  • mux *event.TypeMux: 事件訂閱管理器
  • recommit time.Duration: 下一次任務(wù)的基礎(chǔ)時(shí)間間隔
  • gasFloor, gasCeil uint64: Gas 的下限 gasFloor 和上限 gasCeil未檩。

主要實(shí)現(xiàn):

  • 首先構(gòu)建對(duì)象 worker,并設(shè)定大部分字段的初始值粟焊。

  • 向事務(wù)池 core.TxPool 訂閱事件 core.NewTxsEvent冤狡,并通過通道 worker.txsCh 接收事件 core.NewTxsEvent校赤。

    • worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)
  • 向區(qū)塊鏈 core.BlockChain 訂閱事件 core.ChainHeadEvent,并通過通道 worker.chainHeadCh 接收事件 core.ChainHeadEvent筒溃。

    • worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
  • 向區(qū)塊鏈 core.BlockChain 訂閱事件 core.ChainSideEvent,并通過通道 worker.chainSideCh 接收事件 worker.ChainSideEvent沾乘。

    • worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
  • 如果用戶設(shè)定的重新提交間隔 recommit 太短怜奖,則重新設(shè)定 recommit = minRecommitInterval。同時(shí)翅阵,輸出日志信息:log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval)

  • 啟動(dòng)新的獨(dú)立協(xié)程運(yùn)行方法 worker.mainLoop()歪玲。

  • 啟動(dòng)新的獨(dú)立協(xié)程運(yùn)行方法 worker.newWorkLoop(recommit)。

  • 啟動(dòng)新的獨(dú)立協(xié)程運(yùn)行方法 worker.resultLoop()掷匠。

  • 啟動(dòng)新的獨(dú)立協(xié)程運(yùn)行方法 worker.taskLoop()滥崩。

  • 提交第一個(gè)工作以初始化待處理狀態(tài)。即給通道 startCh 發(fā)送消息讹语。

    • worker.startCh <- struct{}{}

(2) func (w *worker) setEtherbase(addr common.Address)

方法 setEtherbase() 設(shè)置用于初始化區(qū)塊 coinbase 字段的 etherbase钙皮。

參數(shù):

  • addr common.Address: 地址

主要實(shí)現(xiàn):

  • 加鎖和解鎖
  • w.coinbase = addr

(3) func (w *worker) setExtra(extra []byte)

方法 setExtra() 設(shè)置用于初始化區(qū)塊額外字段的內(nèi)容。

參數(shù):

  • extra []byte: 應(yīng)該是用于區(qū)塊頭 types.Header 中的字段 Extra 的前 32 字節(jié)顽决。這 32 字節(jié)是以太坊協(xié)議規(guī)定在區(qū)塊中用于存儲(chǔ)礦工相關(guān)的一些額外信息短条。上層調(diào)用方法 miner.Miner.SetExtra(),繼續(xù)上層調(diào)用方法為 eth.Ethereum 的構(gòu)造函數(shù) eth.New() 中的代碼 eth.miner.SetExtra(makeExtraData(config.MinerExtraData))才菠。這個(gè)參數(shù)最終是通過 geth 的 MINER OPTIONS 命令行參數(shù) --extradata茸时,或者 ETHEREUM OPTIONS 的命令行參數(shù) --config,這是一個(gè) TOML 配置文件赋访。

(4) func (w *worker) setRecommitInterval(interval time.Duration)

方法 setRecommitInterval() 更新礦工簽名工作重新提交的間隔可都。

參數(shù):

  • interval time.Duration: 重新提交的時(shí)間間隔。

主要實(shí)現(xiàn):

  • 將重新提交的間隔 interval 發(fā)送到通道 worker.resubmitIntervalCh蚓耽,代碼為:w.resubmitIntervalCh <- interval渠牲。命名協(xié)程 worker.newWorkLoop() 會(huì)從通道 worker.resubmitIntervalCh 中接收此消息。

(5) func (w worker) pending() (types.Block, *state.StateDB)

方法 pending() 返回待處理的狀態(tài)和相應(yīng)的區(qū)塊田晚。

主要實(shí)現(xiàn):

  • 加鎖嘱兼、解鎖 snapshotMu。
  • 返回字段 snapshotBlock 和字段 snapshotState 的副本贤徒。

(6) func (w *worker) pendingBlock() *types.Block

方法 pendingBlock() 返回待處理的區(qū)塊芹壕。

主要實(shí)現(xiàn):

  • 加鎖、解鎖 snapshotMu接奈。
  • 返回字段 snapshotBlock踢涌。

(7) func (w *worker) start()

方法 start() 采用原子操作將 running 字段置為 1,并觸發(fā)新工作的提交序宦。

主要實(shí)現(xiàn):

  • atomic.StoreInt32(&w.running, 1)
  • w.startCh <- struct{}{}

(8) func (w *worker) stop()

方法 stop() 采用原子操作將 running 字段置為 0睁壁。

主要實(shí)現(xiàn):

  • atomic.StoreInt32(&w.running, 0)

(9) func (w *worker) isRunning() bool

方法 isRunning() 返回 worker 是否正在運(yùn)行的指示符。

主要實(shí)現(xiàn):

  • return atomic.LoadInt32(&w.running) == 1

(10) func (w *worker) close()

方法 close() 終止由 worker 維護(hù)的所有后臺(tái)線程。注意 worker 不支持被關(guān)閉多次潘明,這是由 Go 語(yǔ)言不允許多次關(guān)閉同一個(gè)通道決定的行剂。

主要實(shí)現(xiàn)

  • close(w.exitCh)

(11) func (w *worker) newWorkLoop(recommit time.Duration)

方法 newWorkLoop() 是一個(gè)獨(dú)立的協(xié)程,基于接收到的事件提交新的挖礦工作钳降。不妨將此協(xié)程稱作命名協(xié)程 worker.newWorkLoop()厚宰。

參數(shù):

  • recommit time.Duration: 下一次提交間隔。

主要實(shí)現(xiàn):

  • 定義了三個(gè)變量:
    • interrupt *int32: 中斷信號(hào)
    • minRecommit = recommit: 用戶指定的最小重新提交間隔
    • timestamp int64: 每輪挖礦的時(shí)間戳
  • 定義一個(gè)定時(shí)器遂填,并丟棄初始的 tick
    • timer := time.NewTimer(0)
    • <-timer.C
  • 定義內(nèi)部提交函數(shù) commit()
    • 提交函數(shù) commit() 使用給定信號(hào)中止正在進(jìn)行的交易執(zhí)行铲觉,并重新提交新信號(hào)。
    • 構(gòu)建新工作請(qǐng)求 newWorkReq吓坚,并發(fā)送給通道 newWorkCh 來驅(qū)動(dòng)命名協(xié)程 worker.mainLoop() 來重新提交任務(wù)撵幽。
    • 設(shè)置定時(shí)器 timer 的下一次時(shí)間。代碼為:timer.Reset(recommit)
    • 重置交易計(jì)數(shù)器礁击。代碼為:atomic.StoreInt32(&w.newTxs, 0)
  • 定義內(nèi)部函數(shù) recalcRecommit()
    • 根據(jù)一套規(guī)則來計(jì)算重新提交間隔 recommit盐杂。
    • 具體規(guī)則后續(xù)補(bǔ)充注釋。
  • 定義內(nèi)部函數(shù) clearPending()
    • 此函數(shù)用于清除過期的待處理任務(wù)哆窿。
    • 參數(shù)
      • number uint64: 區(qū)塊編號(hào)
    • 加鎖 w.pendingMu.Lock()
    • 循環(huán)迭代 w.pendingTasks
      • 區(qū)塊簽名哈希 h
      • 任務(wù) t
      • 如果 t 中的區(qū)塊編號(hào)比 number 要早 staleThreshold 個(gè)區(qū)塊况褪,則將其從 w.pendingTasks 中刪除。
    • 解鎖 w.pendingMu.Unlock()
  • 在 for 循環(huán)中持續(xù)從通道 startCh更耻、timer.C测垛、resubmitIntervalCh、resubmitAdjustCh 和 exitCh 中接收消息秧均,并執(zhí)行相應(yīng)的邏輯食侮。
    • startCh:
      • 調(diào)用內(nèi)部函數(shù) clearPending() 清除鏈上當(dāng)前區(qū)塊之前的過期待處理任務(wù)。
      • 調(diào)用內(nèi)部函數(shù) commit(false, commitInterruptNewHead) 提交新的 newWorkReq目胡。
    • chainHeadCh:
      • 從通道 chainHeadCh 接收消息 head(事件 core.ChainHeadEvent)
      • 調(diào)用內(nèi)部函數(shù) clearPending() 清除 core.ChainHeadEvent 中區(qū)塊之前的過期待處理任務(wù)锯七。
      • 調(diào)用內(nèi)部函數(shù) commit(false, commitInterruptNewHead) 提交新的 newWorkReq。
    • timer.C
      • 如果挖礦正在進(jìn)行中誉己,則定期重新提交新的工作周期以提取更高價(jià)格的交易眉尸。禁用待處理區(qū)塊的此開銷。
      • 如果交易計(jì)數(shù)器 w.newTxs 為 0
        • 重置定時(shí)器巨双。代碼為:timer.Reset(recommit)
        • 退出本輪迭代噪猾。
      • 調(diào)用內(nèi)部函數(shù) commit(false, commitInterruptResubmit) 提交新的 newWorkReq。
    • timer.C:
      • 如果挖礦正在進(jìn)行中筑累,則定期重新提交新的工作周期以便更新到價(jià)格較高的交易袱蜡。對(duì)于待處理中的區(qū)塊禁用此操作開銷。
        • 對(duì)于 poa 共識(shí)引擎慢宗,需要其配置的 Clique.Period > 0坪蚁。!!!等于這里對(duì)于共識(shí)算法有個(gè)特殊處理奔穿。
      • 調(diào)用內(nèi)部函數(shù) commit(true, commitInterruptResubmit) 提交新的 newWorkReq。
      • 【批注 1】敏晤,這里用到了 time.Timer 將定時(shí)器贱田,時(shí)間間隔為 recommit。
      • 【批注 2】嘴脾,通道主要的作用是用于協(xié)程之間交互消息湘换,那么實(shí)際上影響到的就是工作流程。這個(gè)定時(shí)器應(yīng)該主要就是挖礦有周期性的概念统阿,比如 15 秒產(chǎn)生一個(gè)塊。存在兩個(gè)定時(shí)間隔筹我,一個(gè)是靜態(tài)配置的扶平,另一個(gè)是由挖礦動(dòng)態(tài)決定的。當(dāng)挖礦的實(shí)際時(shí)間長(zhǎng)于靜態(tài)設(shè)定的蔬蕊,那么可能需要做一些操作结澄,比如重新挖礦等等吧。當(dāng)挖礦的實(shí)際時(shí)間適于靜態(tài)設(shè)定的岸夯,可能不需要做什么操作麻献。
    • resubmitIntervalCh:
      • 支持由用戶來重新設(shè)定重新提交的間隔。
      • 用戶設(shè)定的值不能小于 minRecommitInterval猜扮。
      • 如果回調(diào)函數(shù) resubmitHook 不空勉吻,則調(diào)用。
    • resubmitAdjustCh:
      • 根據(jù)挖礦的反饋來動(dòng)態(tài)地調(diào)整重新提交的間隔旅赢。
      • 如果回調(diào)函數(shù) resubmitHook 不空齿桃,則調(diào)用。
    • exitCh:
      • 接收到退出消息煮盼,退出整個(gè)協(xié)程短纵。

命名協(xié)程 worker.mainLoop() 用于根據(jù)接收到的事件生成簽名任務(wù),命名協(xié)程 worker.taskLoop() 用于接收上述驗(yàn)證任務(wù)并提交給共識(shí)引擎僵控,命名協(xié)程 worker.resultLoop() 用于處理簽名結(jié)果的提交并更新相關(guān)數(shù)據(jù)到數(shù)據(jù)庫(kù)中香到。

(12) func (w *worker) mainLoop()

方法 mainLoop() 是一個(gè)獨(dú)立的協(xié)程,用于根據(jù)接收到的事件重新生成簽名任務(wù)报破。不妨將此協(xié)程稱作命名協(xié)程 worker.mainLoop()悠就。

主要實(shí)現(xiàn):

  • 在整個(gè)協(xié)程退出時(shí),取消 txsSub充易、chainHeadSub理卑、chainSideSub 這三個(gè)訂閱。
    • defer w.txsSub.Unsubscribe()
    • defer w.chainHeadSub.Unsubscribe()
    • defer w.chainSideSub.Unsubscribe()
  • 在 for 循環(huán)中持續(xù)從通道 newWorkCh蔽氨、chainSideCh藐唠、txCh 和 exitCh 中接收消息帆疟,并執(zhí)行相應(yīng)的邏輯。
    • newWorkCh:
      • 根據(jù)新接收到的消息 req(數(shù)據(jù)結(jié)構(gòu)為 newWorkReq)宇立,調(diào)用函數(shù) commitNewWork() 提交新的任務(wù)踪宠。代碼為:w.commitNewWork(req.interrupt, req.noempty, req.timestamp)。需要說明的妈嘹,雖然方法 commitNewWork() 中的參數(shù)沒有包含任何區(qū)塊柳琢、交易等信息,但這些信息都包含在當(dāng)前環(huán)境 w.current 或 w 中润脸。同時(shí)柬脸,任務(wù)最終通過通道 worker.taskCh 提交給命名協(xié)程 worker.taskLoop()。
    • chainSideCh:
      • 接收到新的消息 ev(事件 ChainSideEvent)
        • 如果 ev 中攜帶的區(qū)塊已經(jīng)在 possibleUncles 中毙驯,則退出本輪迭代倒堕。
      • 把 ev 攜帶的區(qū)塊添加到 possibleUncles中。代碼為:w.possibleUncles[ev.Block.Hash()] = ev.Block爆价。
      • 如果正在挖礦中的區(qū)塊所包含的叔區(qū)塊少于 2 個(gè)垦巴,且 ev 中攜帶的新叔區(qū)塊有效,則重新生成挖礦中的任務(wù)铭段。見代碼:if w.isRunning() && w.current != nil && w.current.uncles.Cardinality() < 2
        • 獲取任務(wù)開始時(shí)間 start骤宣。代碼為:start := time.Now()
        • 通過方法 commitUncle() 將 ev 中攜帶的區(qū)塊添加到 current.uncles 中。如果成功
          • 定義新任務(wù)中所需要的區(qū)塊頭列表 uncles序愚。代碼為:var uncles []*types.Header
          • 遍歷 w.current.uncles 中的每個(gè) uncle hash
          • 從 possibleUncles 中找到 uncle hash 對(duì)應(yīng)的區(qū)塊頭憔披,并添加到 uncles 中。代碼為:uncles = append(uncles, uncle.Header())
          • 并根據(jù)最終獲得的所有叔區(qū)塊頭列表 uncles 來調(diào)用方法 commit() 提交最終區(qū)塊爸吮。代碼為:w.commit(uncles, nil, true, start)
          • 【批注 1】:possibleUncles 用于包含可能的叔區(qū)塊活逆,起到一個(gè)緩沖的作用。 current.uncles 是當(dāng)前要打包的區(qū)塊中已經(jīng)被確認(rèn)的叔區(qū)塊拗胜。
          • 【批注 2】:possibleUncles 是<區(qū)塊頭哈希>區(qū)塊構(gòu)成的 map蔗候,current.uncles 則僅包含了區(qū)塊頭哈希。
    • txsCh:根據(jù)新接收到的消息 ev(事件 core.NewTxsEvent)
      • 如果不在挖礦狀態(tài)埂软,則將交易置于待處理狀態(tài)锈遥。
      • 注意,收到的所有交易可能與已包含在當(dāng)前挖礦區(qū)塊中的交易不連續(xù)勘畔。這些交易將自動(dòng)消除所灸。
      • if !w.isRunning() && w.current != nil
        • 加鎖、解鎖的方式獲取礦工地址 coinbase炫七。代碼為:coinbase := w.coinbase
        • 定義變量 txs爬立。代碼為:txs := make(map[common.Address]types.Transactions)
        • 遍歷消息 ev 中攜帶的交易列表,對(duì)于每個(gè)交易 tx
          • 還原出每個(gè)交易 tx 的發(fā)送者地址 acc
          • 更新映射 txs万哪。代碼為:txs[acc] = append(txs[acc], tx)
        • 將 txs 轉(zhuǎn)換為 txset(數(shù)據(jù)結(jié)構(gòu)為 types.TransactionsByPriceAndNonce)侠驯,代碼為:txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs)
        • 提交交易列表 txset抡秆。代碼為:w.commitTransactions(txset, coinbase, nil)
        • 更新快照。代碼為:w.updateSnapshot()
      • else
        • 如果我們正在挖礦中吟策,但沒有正在處理任何事情儒士,請(qǐng)?jiān)谛陆灰字行褋?/li>
        • if w.config.Clique != nil && w.config.Clique.Period == 0
          • w.commitNewWork(nil, false, time.Now().Unix())
      • 采用原子操作將 w.newTxs 的數(shù)量增加新接收到的事務(wù)數(shù)量。代碼為:atomic.AddInt32(&w.newTxs, int32(len(ev.Txs)))
    • w.exitCh
      • 當(dāng)從退出通道接收到消息時(shí)檩坚,結(jié)束整個(gè)協(xié)程着撩。
    • w.txsSub.Err()
      • 當(dāng)從交易訂閱通道接收到錯(cuò)誤消息時(shí),結(jié)束整個(gè)協(xié)程匾委。
    • w.chainHeadSub.Err()
      • 當(dāng)從區(qū)塊頭訂閱通道接收到錯(cuò)誤消息時(shí)拖叙,結(jié)束整個(gè)協(xié)程。
    • w.chainSideSub.Err()
      • 當(dāng)從側(cè)鏈區(qū)塊頭訂閱通道接收到錯(cuò)誤消息時(shí)赂乐,結(jié)束整個(gè)協(xié)程薯鳍。

(13) func (w *worker) taskLoop()

方法 taskLoop() 是一個(gè)獨(dú)立的協(xié)程,用于從生成器中獲取待簽名任務(wù)沪猴,并將它們提交給共識(shí)引擎。不妨將此協(xié)程稱作命名協(xié)程 worker.taskLoop()采章。

主要實(shí)現(xiàn):

  • 定義兩個(gè)變量:退出通道 stopCh 和上一個(gè)區(qū)塊哈希 prev
    • stopCh chan struct{}
    • prev common.Hash
  • 定義局部中斷函數(shù) interrupt()运嗜,用于關(guān)閉退出通道 stopCh,結(jié)束所有從退出通道 stopCh 接收消息的協(xié)程悯舟,這里共識(shí)引擎方法 Seal() 中用于簽名的獨(dú)立匿名協(xié)程担租,退出通道 stopCh 是作為參數(shù)傳遞過去的。
    • close(stopCh)
  • 局部通道 stopCh 和內(nèi)部函數(shù) interrupt() 用于組合終止進(jìn)行中的簽名任務(wù)(in-flight sealing task)抵怎。
  • 在 for 循環(huán)中持續(xù)從通道 taskCh 和 exitCh 中接收消息奋救,并執(zhí)行相應(yīng)的邏輯。
    • taskCh:
      • 接收新任務(wù) task
      • 如果回調(diào) w.newTaskHook != nil反惕,則調(diào)用回調(diào)函數(shù) w.newTaskHook(task)
      • 獲取任務(wù) task 中包含區(qū)塊的區(qū)塊簽名哈希 sealHash
      • 如果 sealHash == prev尝艘,則退出本輪迭代。
        • 過濾掉因重復(fù)提交產(chǎn)生的重復(fù)的簽名任務(wù)
      • 調(diào)用中斷函數(shù) interrupt() 中止共識(shí)引擎方法 Seal() 中正在簽名的獨(dú)立匿名協(xié)程姿染。這里是通過關(guān)閉退出通道 stopCh 實(shí)現(xiàn)的背亥。
      • 給退出通道 stopCh 分配空間,并設(shè)置上一個(gè)區(qū)塊哈希 prev悬赏。
        • stopCh, prev = make(chan struct{}), sealHash
      • 如果回調(diào)函數(shù) w.skipSealHook() 不為 nil 和 w.skipSealHook(task) 返回 true狡汉,則退出本輪迭代。
      • 通過對(duì)鎖 w.pendingMu 執(zhí)行加鎖闽颇、解鎖盾戴,將任務(wù) task 添加到 w.pendingTasks 中,為之后命名協(xié)程 worker.resultLoop() 中接收到已簽名區(qū)塊兵多,查找包含該區(qū)塊的任務(wù) task 而用尖啡。
      • 將任務(wù) task 中包含的區(qū)塊提交給共識(shí)引擎進(jìn)行簽名橄仆。代碼為:w.engine.Seal(w.chain, task.block, w.resultCh, stopCh)
        • 需要特別注意傳遞的兩個(gè)通道參數(shù) w.resutlCh, stopCh
        • 通道 w.resultCh 用于從共識(shí)引擎的簽名方法 Seal() 中接收已簽名區(qū)塊。
        • 通道 stopCh 用于發(fā)送中止信號(hào)給共識(shí)引擎的簽名方法 Seal()可婶,從而中止共識(shí)引擎正在進(jìn)行的簽名操作沿癞。
        • 如果簽名失敗,則輸出日志信息:log.Warn("Block sealing failed", "err", err)
    • exitCh:
      • 當(dāng)接收到退出消息時(shí)
        • 通過調(diào)用內(nèi)部中斷函數(shù) interrupt() 關(guān)閉中止通道 stopCh矛渴,從而使得共識(shí)引擎的簽名方法 Seal() 放棄本次簽名椎扬。
        • 退出整個(gè)協(xié)程。

(14) func (w *worker) resultLoop()

方法 resultLoop() 是一個(gè)獨(dú)立的協(xié)程具温,用于處理簽名區(qū)塊的提交和廣播蚕涤,以及更新相關(guān)數(shù)據(jù)到數(shù)據(jù)庫(kù)。不妨將此協(xié)程稱作命名協(xié)程 worker.resultLoop()铣猩。

主要實(shí)現(xiàn):

  • 在 for 循環(huán)中持續(xù)從通道 resultCh 和 exitCh 中接收消息揖铜,并執(zhí)行相應(yīng)的邏輯。
    • resultCh:
      • 接收已簽名區(qū)塊 block达皿。
      • 如果 block == nil天吓,則進(jìn)入下一輪迭代。
      • 如果區(qū)塊 block 已經(jīng)存在于經(jīng)典鏈中峦椰,則進(jìn)入下一輪迭代龄寞。
      • 定義兩個(gè)變量:
        • 區(qū)塊簽名哈希 sealhash,代碼為:sealhash = w.engine.SealHash(block.Header())
        • 區(qū)塊哈希 hash汤功,代碼為:hash = block.Hash()
        • 分別計(jì)算區(qū)塊頭的驗(yàn)證哈希 sealHash(不包括 extraData 中的最后 65 個(gè)字節(jié)的簽名信息)物邑,區(qū)塊的哈希 hash (即區(qū)塊頭的哈希,而且包含整個(gè) extraData)滔金。
      • 通過對(duì)鎖 w.pendingMu 進(jìn)行加鎖和解鎖的方式從 w.pendingTasks 中找到 sealHash 對(duì)應(yīng)的 task色解。這是找出已簽名區(qū)塊對(duì)應(yīng)的任務(wù) task,從中獲取需要的交易列表餐茵、交易回執(zhí)列表等相關(guān)數(shù)據(jù)科阎。
        • 如果 task 不存在,則輸出日志信息:log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash)
        • 同時(shí)忿族,退出本次迭代萧恕。
      • 定義兩個(gè)變量,交易回執(zhí)列表 receipts肠阱,交易回執(zhí)中包含的日志列表 logs票唆。
        • receipts = make([]*types.Receipt, len(task.receipts))
        • logs []*types.Log
        • 這是因?yàn)椴煌膮^(qū)塊可能會(huì)共享相同的區(qū)塊簽名哈希,建立這些副本是為了防止寫寫沖突屹徘。
        • 更新所有日志中的區(qū)塊哈希走趋。這是因?yàn)閷?duì)于這些日志來說,直到現(xiàn)在才知道對(duì)應(yīng)的區(qū)塊哈希噪伊,而在創(chuàng)建單個(gè)交易的交易回執(zhí)的接收日志時(shí)簿煌,并不知道對(duì)應(yīng)的區(qū)塊哈希氮唯。
      • 更新 task.receipts 中各 receipt.Logs 的 BlockHash 值為 hash。
      • 通過方法 w.chain.WriteBlockWithState() 將區(qū)塊 block姨伟,交易回執(zhí)列表 receipts惩琉,狀態(tài)數(shù)據(jù)庫(kù) task.state 寫入數(shù)據(jù)庫(kù),并返回寫入狀態(tài) stat夺荒。stat 的取值:NonStatTy (0)瞒渠、CanonStatTy (1)、SideStatTy(2)技扼。
        • 如果寫入失敗伍玖,則輸出日志信息:log.Error("Failed writing block to chain", "err", err)。同時(shí)剿吻,退出本輪迭代窍箍。
      • 至此,成功的驗(yàn)證了新的區(qū)塊丽旅。輸出日志信息:log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt)))
      • 將新產(chǎn)生的新區(qū)塊 block 廣播到網(wǎng)絡(luò)中的其他節(jié)點(diǎn)椰棘。這是通過構(gòu)建事件 core.NewMinedBlockEvent 進(jìn)調(diào)用 w.mux.Post() 實(shí)現(xiàn)的。代碼為:w.mux.Post(core.NewMinedBlockEvent{Block: block})
      • 定義變量事件列表 events
      • 根據(jù)寫入數(shù)據(jù)庫(kù)返回的狀態(tài) stat 的值:
        • case core.CanonStatTy:在事件列表 events 中添加新的事件 core.ChainEvent榄笙、core.ChainHeadEvent
        • case core.SideStatTy:在事件列表 events 中添加新的事件 core.ChainSideEvent邪狞。
      • 通過方法 w.chain.PostChainEvents() 廣播事件。代碼為: w.chain.PostChainEvents(events, logs)
      • 將已簽名區(qū)塊插入待確認(rèn)區(qū)塊列表中办斑。代碼為:w.unconfirmed.Insert(block.NumberU64(), block.Hash())
    • exitCh:
      • 接收到退出消息則中止整個(gè)協(xié)程外恕。

命名協(xié)程 worker.resultLoop() 從通道 resultCh 中接收消息 types.Block杆逗,且此區(qū)塊是被簽名過的乡翅。對(duì)于新接收到簽名區(qū)塊,首先判斷這個(gè)簽名區(qū)塊是否為重復(fù)的罪郊;其次蠕蚜,需要從待處理任務(wù)映射 w.pendingTasks 中獲得對(duì)應(yīng)區(qū)塊簽名哈希的任務(wù) task,如果沒找到則輸出一條重要的日志信息:log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash)悔橄。并從 task 中恢復(fù) receipts 和 logs靶累。第三,將簽名區(qū)塊及其對(duì)應(yīng)的收據(jù)列表和狀態(tài)樹等信息寫入數(shù)據(jù)庫(kù)癣疟。如果寫入失敗挣柬,則輸出一條重要的日志信息:log.Error("Failed writing block to chain", "err", err),否則輸出一條重要的日志信息:log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt)))睛挚。第四邪蛔,通過新挖出的簽名區(qū)塊構(gòu)建事件 core.NewMinedBlockEvent,并通過事件訂閱管理器中的方法 w.mux.Post() 將本地節(jié)點(diǎn)最新簽名的區(qū)塊向網(wǎng)絡(luò)中其它節(jié)點(diǎn)進(jìn)行廣播扎狱,這是基于 p2p 模塊完成的侧到。第五勃教,同時(shí)構(gòu)建事件 core.ChainEvent 和事件 core.ChainHeadEvent,或者構(gòu)建事件 core.ChainSideEvent匠抗,并通過區(qū)塊鏈中的方法 w.chain.PostChainEvents() 進(jìn)行廣播故源。需要注意的時(shí),此廣播只是針對(duì)向本地節(jié)點(diǎn)進(jìn)行了事件注冊(cè)的客戶端汞贸,且是通過 JSON-RPC 完成绳军,和第四步中的向網(wǎng)絡(luò)中其它節(jié)點(diǎn)通過 p2p 進(jìn)行廣播是完全不同的。這一部的廣播即使沒有事件接收方也沒有問題著蛙,因?yàn)檫@是業(yè)務(wù)邏輯層面的删铃,而第四步中的廣播則是必須有接收方的,否則就會(huì)破壞以太坊協(xié)議本身踏堡。比如:我們可以注冊(cè)一個(gè)事件猎唁,用于監(jiān)控是否有最新的區(qū)塊被挖出來,然后在此基礎(chǔ)上顷蟆,查詢指定賬戶的最新余額诫隅。第六步,將新挖出來的簽名區(qū)塊帐偎,添加進(jìn)待確認(rèn)隊(duì)列中逐纬,代碼為:w.unconfirmed.Insert(block.NumberU64(), block.Hash())。

(15) func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error

方法 makeCurrent() 為當(dāng)前周期創(chuàng)建新的環(huán)境 environment削樊。

參數(shù):

  • parent *types.Block: 父區(qū)塊
  • header *types.Header: 當(dāng)前區(qū)塊頭

主要實(shí)現(xiàn):

  • 先通過父區(qū)塊狀態(tài)樹的根哈希從區(qū)塊鏈中獲取狀態(tài)信息 state (state.StateDB)豁生,如果失敗,直接返回錯(cuò)誤
  • 構(gòu)建當(dāng)前環(huán)境 environment 的對(duì)象 env
    • 設(shè)定字段 signer 為 types.EIP155Signer
    • 設(shè)定字段 state 為前面獲取的 state
    • 設(shè)定字段 header 為參數(shù) header
    • 默認(rèn)初始化其它字段
  • 從區(qū)塊鏈中獲取父區(qū)塊之前的 7 個(gè)高度的所有區(qū)塊漫贞,包含叔區(qū)塊
    • 所有的直系父區(qū)塊添加到字段 ancestors
    • 所有的直系父區(qū)塊和叔區(qū)塊添加到字段 family
  • 將字段 tcount 設(shè)為 0
  • 將環(huán)境 env 賦值給字段 worker.current

(16) func (w *worker) commitUncle(env *environment, uncle *types.Header) error

方法 commitUncle() 將給定的區(qū)塊添加至叔區(qū)塊集合中甸箱,如果添加失敗則返回錯(cuò)誤。

參數(shù):

  • env *environment: 當(dāng)前環(huán)境迅脐,里面組織了本次周期里需要的所有信息
  • uncle *types.Header: 叔區(qū)塊的區(qū)塊頭

主要實(shí)現(xiàn):

  • 獲取叔區(qū)塊 hash芍殖。見代碼:hash := uncle.Hash()。
  • 判定叔區(qū)塊是否惟一谴蔑。見代碼:if env.uncles.Contains(hash) { return errors.New("uncle not unique") }
  • 判定叔區(qū)塊是否為兄弟區(qū)塊豌骏。見代碼:if env.header.ParentHash == uncle.ParentHash { return errors.New("uncleis sibling") }
  • 判定叔區(qū)塊的父區(qū)塊是否存在于鏈上。見代碼:if !env.ancestors.Contains(uncle.ParentHash) { return errors.New("uncle's parent unknown") }
  • 判定叔區(qū)塊是否已經(jīng)存在于鏈上隐锭。見代碼:if env.family.Contains(hash) { return errors.New("uncle already included") }
  • 上述四個(gè)判定都通過窃躲,則添加到當(dāng)前區(qū)塊的叔區(qū)塊列表中。見代碼:env.uncles.Add(uncle.Hash())

(17) func (w *worker) updateSnapshot()

方法 updateSnapshot() 更新待處理區(qū)塊和狀態(tài)的快照钦睡。注意蒂窒,此函數(shù)確保當(dāng)前變量是線程安全的。

主要實(shí)現(xiàn):
- 加鎖、解鎖 w.snapshotMu
- 定義叔區(qū)塊頭列表 uncles
- 對(duì)于 w.current.uncles 中的每個(gè)叔區(qū)塊頭 uncle刘绣,如果存在于
w.possibleUncles 中樱溉,則將其沒回到 uncles 中。
- 由 w.current.header, w.current.txs, uncles, w.current.receipts 構(gòu)建出快照區(qū)塊 w.snapshotBlock纬凤。
- 由 w.current.state 的副本構(gòu)建出快照狀態(tài) w.snapshotState福贞。

(18) func (w *worker) commitTransaction(tx types.Transaction, coinbase common.Address) ([]types.Log, error):

方法 commitTransaction() 提交交易 tx,并附上交易的發(fā)起者地址停士。此方法會(huì)生成交易的交易回執(zhí)挖帘。

參數(shù):

  • tx *types.Transaction: 具體的一次交易信息。
  • coinbase common.Address: 交易的發(fā)起方地址恋技,可以明確指定拇舀。如果為空,則為區(qū)塊簽名者的地址蜻底。

返回值:

  • []*types.Log: 交易回執(zhí)中的日志信息骄崩。

主要實(shí)現(xiàn):

  • 先對(duì)狀態(tài)樹進(jìn)行備份 snap,代碼為:snap := w.current.state.Snapshot()
  • 通過對(duì)交易 tx 及交易發(fā)起者 coinbase 調(diào)用方法 core.ApplyTransaction() 獲得交易回執(zhí) receipt薄辅。
    • 如果失敗要拂,則將狀態(tài)樹恢復(fù)到之前的狀態(tài) snap,并直接返回站楚。
  • 更新交易列表脱惰。代碼為 w.current.txs = append(w.current.txs, tx)
  • 更新交易回執(zhí)列表。代碼為 w.current.receipts = append(w.current.receipts, receipt)

(19) func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool:

方法 commitTransactions() 提交交易列表 txs窿春,并附上交易的發(fā)起者地址拉一。根據(jù)整個(gè)交易列表 txs 是否都被有效提交,返回 true 或 false旧乞。

參數(shù):

  • txs *types.TransactionsByPriceAndNonce: 交易列表的管理器蔚润,同時(shí)根據(jù)價(jià)格和隨機(jī)數(shù)值進(jìn)行排序,每次輸出一個(gè)排序最靠前的交易良蛮。具體的注釋抽碌,參考 types.TransactionsByPriceAndNonce悍赢。
  • coinbase common.Address: 交易的發(fā)起方地址决瞳,可以明確指定。如果為空左权,則為區(qū)塊簽名者的地址皮胡。
  • interrupt *int32: 中斷信號(hào)值。需要特別說明赏迟,這是個(gè)指針類型的值屡贺,意味著后續(xù)的每輪迭代都能讀取外部對(duì)于參數(shù) interrupt 的更新。同時(shí),此方法還能將內(nèi)部對(duì)于參數(shù) interrupt 的修改反饋給外部調(diào)用者甩栈。

返回值:

  • 整個(gè)交易列表是否都被正確處理泻仙。

主要實(shí)現(xiàn):

  • 如果 w.current 為空,直接返回量没。
  • 如果 w.current.gasPool 為空玉转,則初始化為 w.current.header.GasLimit
  • 匯總的事件日志,代碼為:var coalescedLogs []*types.Log
  • 循環(huán)處理交易列表 txs:
    • 在以下三種情況下殴蹄,我們將中斷交易的執(zhí)行究抓。對(duì)于前兩種情況,半成品將被丟棄袭灯。對(duì)于第三種情況刺下,半成品將被提交給共識(shí)引擎。需要特別說明的是稽荧,這一步會(huì)根據(jù) w.current.header.GasLimit 和 w.current.gasPool.Gas() 計(jì)算事件 intervalAdjust 的字段 ratio橘茉,并將字段 inc 設(shè)為 true,然后將事件 intervalAdjust 發(fā)送給通道 w.resubmitAdjustCh姨丈,從而驅(qū)動(dòng)命名協(xié)程 worker.newWorkLoop() 的工作流程戳杀。具備的可以參考代碼羽峰。
      • (1)新的區(qū)塊頭塊事件到達(dá),中斷信號(hào)為1。
      • (2)對(duì)象 worker 啟動(dòng)或重啟费韭,中斷信號(hào)為1。
      • (3)對(duì)象 worker 用任何新到達(dá)的交易重新創(chuàng)建挖掘區(qū)塊矾睦,中斷信號(hào)為2悟衩。
      • 直接返回,退出整個(gè)循環(huán)和此方法矾飞。見代碼:return atomic.LoadInt32(interrupt) == commitInterruptNewHead
    • 如果沒有足夠的 Gas 進(jìn)行任何進(jìn)一步的交易一膨,那么就退出循環(huán)。見代碼:if w.current.gasPool.Gas() < params.TxGas
      • 輸出一條重要的日志信息:log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas)
      • 需要說明的洒沦,已經(jīng)提交并得到正常處理的交易仍然不變豹绪。
    • 獲取下一個(gè)交易 tx,如果為空則退出整個(gè)循環(huán)申眼。
    • 獲取交易的發(fā)起者 from瞒津。見代碼:from, _ := types.Sender(w.current.signer, tx)
      • 這里可能會(huì)忽略錯(cuò)誤。交易在被加入交易池時(shí)已經(jīng)得到了檢查括尸。
      • 無(wú)論當(dāng)前的 hf 如何巷蚪,我們都使用 eip155 簽名者。
    • 檢查交易 tx 是否重播受保護(hù)濒翻。如果我們不在 EIP155 hf 階段屁柏,請(qǐng)?jiān)谖覀冮_始之前開始忽略發(fā)送方啦膜。
      • 即過濾掉此交易。當(dāng)然淌喻,仍然要從 txs 中剔除僧家。見代碼:txs.Pop(); continue
    • 開始執(zhí)行交易:
      • 更新狀態(tài)樹。需要說明的是裸删,這一步會(huì)記錄交易在區(qū)塊中的索引啸臀。見代碼:w.current.state.Prepare(tx.Hash(), common.Hash{}, w.current.tcount)
      • 通過方法 worker.commitTransaction() 提交交易。見代碼:logs, err := w.commitTransaction(tx, coinbase)烁落。根據(jù)返回值 err 決定后面的操作:
        • case core.ErrGasLimitReached
          • 彈出當(dāng)前超出 Gas 的交易乘粒,而不從賬戶中轉(zhuǎn)移下一個(gè)交易。這是因?yàn)樯怂撡~戶已經(jīng)支付不起 Gas 了灯萍,所以不需要再處理該賬戶的其它交易。這個(gè)實(shí)現(xiàn)有點(diǎn)漂亮C看稀5┟蕖!
          • 輸出重要的日志信息:log.Trace("Gas limit exceeded for current block", "sender", from)
          • txs.Pop()
        • case core.ErrNonceTooLow
          • 交易池和礦工之間的新區(qū)塊頭通知數(shù)據(jù)競(jìng)爭(zhēng)药薯,轉(zhuǎn)移該賬戶下一個(gè)交易绑洛。
          • 輸出重要的日志信息:log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
          • txs.Shift()
        • case core.ErrNonceTooHigh
          • 事務(wù)池和礦工之間的重組通知數(shù)據(jù)競(jìng)爭(zhēng),跳過 account 的所有交易
          • 輸出重要的日志信息:log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
          • txs.Pop()
        • case nil
          • 一切正常童本,收集日志并從同一帳戶轉(zhuǎn)移下一個(gè)交易
          • coalescedLogs = append(coalescedLogs, logs...)
          • w.current.tcount++真屯,需要增加當(dāng)前區(qū)塊的交易索引。
          • txs.Shift()
        • default:
          • 奇怪的錯(cuò)誤穷娱,丟棄事務(wù)并獲得下一個(gè)(注意绑蔫,nonce-too-high子句將阻止我們徒勞地執(zhí)行)。
          • 輸出重要的日志信息:log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
          • txs.Shift()
  • 我們?cè)谕诰驎r(shí)不會(huì)推送pendingLogsEvent泵额。原因是當(dāng)我們開采時(shí)配深,工人將每3秒鐘再生一次采礦區(qū)。為了避免推送重復(fù)的pendingLog嫁盲,我們禁用掛起的日志推送篓叶。
    • 構(gòu)建日志集合 coalescedLogs 的副本 cpy,避免同步問題
    • 啟動(dòng)一個(gè)獨(dú)立的匿名協(xié)程羞秤,將日志集合的副本 cpy 通過方法 TypeMux.Post() 發(fā)送出去缸托。
  • 如果當(dāng)前間隔大于用戶指定的間隔,則通知重新提交循環(huán)以減少重新提交間隔锥腻。代碼為:w.resubmitAdjustCh <- &intervalAdjust{inc: false}嗦董。即將事件 intervalAdjust 發(fā)送到通道 w.resubmitAdjustCh母谎,從而驅(qū)動(dòng)命名協(xié)和 worker.newWorkLoop() 的后續(xù)邏輯瘦黑。

(20) func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64):

方法 commitNewWork() 基于父區(qū)塊生成幾個(gè)新的簽名任務(wù)。

參數(shù):

  • interrupt *int32: 中斷信號(hào),值為:commitInterruptNone (0)幸斥、commitInterruptNewHead (1)匹摇、commitInterruptResubmit (2) 之一。
  • noempty bool: ???
  • timestamp int64: ??? 區(qū)塊時(shí)間甲葬?

主要實(shí)現(xiàn):

  • 加鎖廊勃、解鎖 w.mu。說明對(duì)整個(gè)方法進(jìn)行了加鎖處理经窖。
  • 獲取當(dāng)前時(shí)間 tstart坡垫,代碼為:tstart := time.Now()
  • 獲取父區(qū)塊 parent,即區(qū)塊鏈上的當(dāng)前區(qū)塊画侣。代碼為:parent := w.chain.CurrentBlock()
  • 根據(jù)父區(qū)塊的時(shí)間冰悠,調(diào)整下一個(gè)區(qū)塊的時(shí)間。
  • 如果挖礦太超前配乱,計(jì)算超前時(shí)間 wait溉卓,并睡眠 wait 時(shí)間。同時(shí)搬泥,輸出日志:log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait))
  • 獲取父區(qū)塊編號(hào) num桑寨,代碼為:num := parent.Number()
  • 構(gòu)建打包中的區(qū)塊頭 header,代碼為:
    header := &types.Header{
    ParentHash: parent.Hash(),
    Number: num.Add(num, common.Big1),
    GasLimit: core.CalcGasLimit(parent, w.gasFloor, w.gasCeil),
    Extra: w.extra,
    Time: big.NewInt(timestamp),
    }
  • 只有在共識(shí)引擎正在運(yùn)行中忿檩,才設(shè)置 coinbase(避免虛假區(qū)塊獎(jiǎng)勵(lì))
    • 如果 w.coinbase == (common.Address{})尉尾,則輸出日志信息:log.Error("Refusing to mine without etherbase")。同時(shí)燥透,退出整個(gè)方法代赁。
    • header.Coinbase = w.coinbase
  • 調(diào)用共識(shí)引擎的方法 Prepare() 設(shè)置區(qū)塊頭 header 中的共識(shí)字段。如果失敗兽掰,則輸出日志信息:log.Error("Failed to prepare header for mining", "err", err)芭碍。同時(shí),退出整個(gè)方法孽尽。
  • 處理 DAO 硬分叉相關(guān)內(nèi)容窖壕,暫時(shí)忽略。
  • 構(gòu)建挖礦的當(dāng)前環(huán)境杉女,代碼為:w.makeCurrent(parent, header)瞻讽。如果失敗,輸出日志:log.Error("Failed to create mining context", "err", err)熏挎。同時(shí)速勇,退出整個(gè)方法。
  • env := w.current
  • 對(duì) env 應(yīng)用 DAO 相關(guān)操作坎拐。
  • 刪除 w.possibleUncles 中相對(duì)于當(dāng)前區(qū)塊太舊的叔區(qū)塊
  • 遍歷 w.possibleUncles 累計(jì)當(dāng)前區(qū)塊的叔區(qū)塊列表 uncles烦磁,最多支持 2 個(gè)叔區(qū)塊养匈。
    • 下一個(gè)可能的叔區(qū)塊(hash 和 uncle)
    • 如果叔區(qū)塊列表 uncles 的長(zhǎng)度已經(jīng)達(dá)到 2,則退出遍歷操作都伪。
    • 通過 w.commitUncle() 提交叔區(qū)塊 uncle
      • 如果失敗呕乎,輸出日志:log.Trace("Possible uncle rejected", "hash", hash, "reason", err)
      • 如果成功,輸出日志:log.Debug("Committing new uncle to block", "hash", hash)陨晶。同時(shí)猬仁,uncles = append(uncles, uncle.Header())
  • if !noempty
    • 基于臨時(shí)復(fù)制狀態(tài)創(chuàng)建空區(qū)塊以提前進(jìn)行簽名,而無(wú)需等待區(qū)塊執(zhí)行完成先誉。
    • w.commit(uncles, nil, false, tstart)
  • 使用所有可用的待處理交易填充區(qū)塊湿刽。代碼為:pending, err := w.eth.TxPool().Pending()。如果失敗褐耳,則輸出日志:log.Error("Failed to fetch pending transactions", "err", err)叭爱。同時(shí),退出整個(gè)方法漱病。需要說明的是买雾,從交易池中獲取所有待處理的交易列表,pending 的數(shù)據(jù)結(jié)構(gòu)為:map[common.Address]types.Transactions杨帽。
  • 如果沒有待處理的交易列表
    • 更新快照漓穿。代碼為:w.updateSnapshot()
    • 退出整個(gè)方法。
  • 將交易池中的交易 pending 劃分為本地交易列表 localTxs 和遠(yuǎn)程交易列表 remoteTxs注盈。本地交易即提交者為 w.coinbase晃危。
    • 具體方法為將事務(wù)池中地址為 w.coinbase 的放入本地事務(wù)列表,否則放入遠(yuǎn)程事務(wù)列表老客。
  • 如果本地交易列表 localTxs 的長(zhǎng)度大于 0
    • 將 localTxs 封裝為數(shù)據(jù)結(jié)構(gòu) types.NewTransactionsByPriceAndNonce僚饭。代碼為:txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
    • 提交交易列表。代碼為:w.commitTransactions(txs, w.coinbase, interrupt)胧砰。如果失敗鳍鸵,退出整個(gè)方法。
  • 如果本地交易列表 remoteTxs 的長(zhǎng)度大于 0
    • 將 remoteTxs 封裝為數(shù)據(jù)結(jié)構(gòu) types.NewTransactionsByPriceAndNonce尉间。代碼為:txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
    • 提交交易列表偿乖。代碼為:w.commitTransactions(txs, w.coinbase, interrupt)。如果失敗哲嘲,退出整個(gè)方法贪薪。
  • 調(diào)用方法 w.commit() 組裝出最終的任務(wù) task。

(21) func (w worker) commit(uncles []types.Header, interval func(), update bool, start time.Time) error

方法 commit() 運(yùn)行任何交易的后續(xù)狀態(tài)修改眠副,組裝最終區(qū)塊画切,并在共識(shí)引擎運(yùn)行時(shí)提交新工作。

參數(shù):

  • uncles []*types.Header: 叔區(qū)塊列表
  • interval func(): 中斷函數(shù)
  • update bool: 是否更新快照
  • start time.Time: 方法被調(diào)用的時(shí)間

返回值:

  • 如果出錯(cuò)則返回出錯(cuò)消息囱怕,否則返回 nil霍弹。

主要實(shí)現(xiàn):

  • 為了避免在不同任務(wù)之間的交互毫别,通過深度拷貝構(gòu)建 current.receipts 的副本 receipts。
  • 構(gòu)建狀態(tài)數(shù)據(jù)庫(kù) w.current.state 的副本 s庞萍。
  • 調(diào)用共識(shí)引擎的方法 Finalize() 構(gòu)建出最終待簽名的區(qū)塊 block拧烦。需要特別說明的是:對(duì)于待組裝的區(qū)塊來說忘闻,除了叔區(qū)塊列表 uncles 是作為參數(shù)傳入之外钝计,其它的關(guān)鍵信息,如:區(qū)塊頭齐佳、交易列表私恬、交易回執(zhí)列表都是在當(dāng)前環(huán)境 w.current 中獲取的。
  • 如果對(duì)象 worker 正在運(yùn)行中:
    • 如果中斷函數(shù) interval 非空炼吴,則調(diào)用函數(shù) interval()本鸣。
    • 構(gòu)建任務(wù) task,并將其發(fā)送到通道 taskCh硅蹦,從而驅(qū)動(dòng)命名協(xié)程 worker.taskLoop() 的工作流程荣德。
      • 刪除待確認(rèn)區(qū)塊列表中的過期區(qū)塊,代碼為:w.unconfirmed.Shift(block.NumberU64() - 1)
      • 累計(jì)區(qū)塊 block 中所有交易消耗 Gas 的總和 feesWei童芹。第 i 個(gè)交易 tx 消耗的 Gas 計(jì)算方式: receipts[i].GasUsed * tx.GasPrice()
      • 將 feesWei 轉(zhuǎn)換成 feesEth涮瞻,即消耗的總以太幣。
      • 至此假褪,已經(jīng)打包好了最終的待簽名區(qū)塊署咽。輸出一條重要的日志信息:log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start)))
    • 持續(xù)監(jiān)聽通道 worker.exitCh,如果接收到中止消息則輸出日志:log.Info("Worker has exited")
  • 如果 update 為 true生音,則更新快照:
    • 調(diào)用 w.updateSnapshot() 更新待處理的快照和狀態(tài)宁否。

方法 worker.commit() (由命名協(xié)程 worker.mainLoop() 調(diào)用)將消息 task 發(fā)送給通道 taskCh。此方法先將當(dāng)前環(huán)境中的區(qū)塊頭(w.current.header)缀遍、事務(wù)列表(w.current.txs)慕匠、收據(jù)列表(w.current.receipts)作為參數(shù)傳遞給共識(shí)引擎的方法 Finalize() 組裝出待簽名的區(qū)塊,代碼為 block = w.engine.Finalize(w.chain, w.current.header, s, w.current.txs, uncles, w.current.receipts)域醇。需要注意的是絮重,區(qū)塊 types.Block 中只包含區(qū)塊頭 types.Header、事務(wù)列表 []types.Transaction歹苦、叔區(qū)塊列表 []types.Header青伤,并不包含收據(jù)列表 []types.Receipt,但是區(qū)塊頭 types.Header 中的字段 ReceiptHash 是收據(jù)列表樹的根哈希殴瘦,所以也需要收據(jù)列表參數(shù)狠角。將組裝后的待簽名區(qū)塊 types.Block,及前面解釋過的收據(jù)列表 []types.Receipt 等其它參數(shù)一起構(gòu)建出新的任務(wù) task 發(fā)送給通道 taskCh蚪腋,同時(shí)輸出一條重要的日志信息:log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start)))丰歌。到方法 commit() 這一步姨蟋,已經(jīng)組裝出了新的任務(wù) task,并將此新任務(wù) task 通過通道 taskCh 發(fā)送給命名協(xié)程 worker.taskLoop()立帖。

Reference

  1. https://github.com/ethereum/go-ethereum/blob/master/miner/worker.go

Contributor

  1. Windstamp, https://github.com/windstamp
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末眼溶,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子晓勇,更是在濱河造成了極大的恐慌堂飞,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绑咱,死亡現(xiàn)場(chǎng)離奇詭異绰筛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)描融,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門铝噩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人窿克,你說我怎么就攤上這事骏庸。” “怎么了年叮?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵具被,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我谋右,道長(zhǎng)硬猫,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任改执,我火速辦了婚禮啸蜜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辈挂。我一直安慰自己衬横,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布终蒂。 她就那樣靜靜地躺著蜂林,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拇泣。 梳的紋絲不亂的頭發(fā)上噪叙,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音霉翔,去河邊找鬼睁蕾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的子眶。 我是一名探鬼主播瀑凝,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼臭杰!你這毒婦竟也來了粤咪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤渴杆,失蹤者是張志新(化名)和其女友劉穎寥枝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體将塑,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脉顿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年蝌麸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了点寥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡来吩,死狀恐怖敢辩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情弟疆,我是刑警寧澤戚长,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站怠苔,受9級(jí)特大地震影響同廉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜柑司,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一迫肖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧攒驰,春花似錦蟆湖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至劲室,卻和暖如春伦仍,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背很洋。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工充蓝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蹲缠。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓棺克,卻偏偏與公主長(zhǎng)得像悠垛,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子娜谊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容