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: 指記錄成功挖礦時需要達(dá)到的確認(rèn)數(shù)。是 miner.unconfirmedBlocks 的深度 。即本地節(jié)點(diǎn)挖出的最新區(qū)塊如果需要得到整個網(wǎng)絡(luò)的確認(rèn),需要整個網(wǎng)絡(luò)再挖出 miningLogAtDepth 個區(qū)塊。舉個例子:本地節(jié)點(diǎn)挖出了編號為 1 的區(qū)塊,需要等到整個網(wǎng)絡(luò)中某個節(jié)點(diǎn)(也可以是本地節(jié)點(diǎn))挖出編號為 8 的區(qū)塊(8 = 1 + miningLogAtDepth, miningLogAtDepth = 7)之后,則編號為 1 的區(qū)塊就成為了經(jīng)典鏈的一部分。
- minRecommitInterval: 指使用任何新到達(dá)的事務(wù)重新創(chuàng)建挖礦區(qū)塊的最小時間間隔。當(dāng)用戶設(shè)定的重新提交間隔太小時進(jìn)行修正。
- maxRecommitInterval: 指使用任何新到達(dá)的事務(wù)重新創(chuàng)建挖礦區(qū)塊的最大時間間隔。當(dāng)用戶設(shè)定的重新提交間隔太大時進(jìn)行修正。
- intervalAdjustRatio: 指單個間隔調(diào)整對驗(yàn)證工作重新提交間隔的影響因子。與參數(shù) intervalAdjustBias 一起決定下一次提交間隔。
- intervalAdjustBias: 指在新的重新提交間隔計(jì)算期間應(yīng)用intervalAdjustBias,有利于增加上限或減少下限,以便可以訪問限制。與參數(shù) intervalAdjustRatio 一起決定下一次提交間隔。
- staleThreshold: 指可接受的舊區(qū)塊的最大深度。注意,目前,這個值與 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ù)列表一一對應(yīng),構(gòu)建當(dāng)前打包中區(qū)塊)。
signer types.Signer: 簽名者,即本地節(jié)點(diǎn)的礦工,用于對區(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ū)塊的哈希??梢院唵蔚卣J(rèn)為區(qū)塊、區(qū)塊頭、區(qū)塊哈希、區(qū)塊頭哈希能夠等價(jià)地描述區(qū)塊,其中的任何一種方式都能惟一標(biāo)識同一個區(qū)塊。甚至可以放寬到區(qū)塊編號。
family mapset.Set: ??? family 區(qū)塊集合(用于驗(yàn)證無效叔區(qū)塊)。family 區(qū)塊集合比 ancestors 區(qū)塊集合多了各祖先區(qū)塊的叔區(qū)塊。ancestors 區(qū)塊集合是區(qū)塊的直接父區(qū)塊一級一級連接起來的。
uncles mapset.Set: 叔區(qū)塊集合,即當(dāng)前區(qū)塊的叔區(qū)塊集合,或者說當(dāng)前正在挖的區(qū)塊的叔區(qū)塊集合。
tcount int: 一個周期里面的事務(wù)數(shù)量
gasPool *core.GasPool: 用于打包事務(wù)的可用 gas
header *types.Header: 區(qū)塊頭。區(qū)塊頭需要滿足通用的以太坊協(xié)議共識,還需要滿足特定的 PoA 共識協(xié)議。與 PoA 共識協(xié)議相關(guān)的區(qū)塊頭 types.Header 字段用 Clique.Prepare() 方法進(jìn)行主要的設(shè)置,Clique.Finalize() 方法進(jìn)行最終的補(bǔ)充設(shè)置。那么以太坊協(xié)議共識相關(guān)的字段在哪里設(shè)置?或者說在 worker 的哪個方法中設(shè)置。
txs []*types.Transaction: 事務(wù)(types.Transaction)列表。當(dāng)前需要打包的事務(wù)列表(或者備選事務(wù)列表),可不可以理解為事務(wù)池。
receipts []*types.Receipt: 收據(jù)(types.Receipt)列表。Receipt 表示 Transaction 一一對應(yīng)的結(jié)果。
3. type task struct
數(shù)據(jù)結(jié)構(gòu) task 包含共識引擎簽名和簽名之后的結(jié)果提交的所有信息。
簽名即對已經(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ū)塊。此時,區(qū)塊已經(jīng)全部組裝好了,包信了事務(wù)列表、叔區(qū)塊列表。同時,區(qū)塊頭中的字段已經(jīng)全部組裝好了,就差最后的簽名。簽名后的區(qū)塊是在此原有區(qū)塊上新創(chuàng)建的區(qū)塊,并被發(fā)送到結(jié)果通道,用于驅(qū)動本地節(jié)點(diǎn)已經(jīng)挖出新區(qū)塊之后的流程。
createdAt time.Time: task 的創(chuàng)建時間
數(shù)據(jù)結(jié)構(gòu) task 也是通道 worker.taskCh 發(fā)送或接收的消息。
4. const
- commitInterruptNone 無效的中斷值
- commitInterruptNewHead 用于描述新區(qū)塊頭到達(dá)的中斷值,當(dāng) worker 啟動或重新啟動時也是這個中斷值。
- commitInterruptResubmit 用于描述 worker 根據(jù)接收到的新事務(wù),中止之前挖礦,并重新開始挖礦的中斷值。
5. type newWorkReq struct
數(shù)據(jù)結(jié)構(gòu) newWorkReq 表示使用相應(yīng)的中斷值通知程序提交新簽名工作的請求。
數(shù)據(jù)結(jié)構(gòu) newWorkReq 也是通道 worker.newWorkCh 發(fā)送或接收的消息。
- interrupt *int32: 具體的中斷值,為 commitInterruptNewHead 或 commitInterruptResubmit 之一。
- noempty bool: ??? 表示創(chuàng)建的區(qū)塊是否包含事務(wù)?
- timestamp int64: ??? 表示區(qū)塊開始組裝的時間?
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ū)塊時計(jì)算下一區(qū)塊的出塊大致時間,在基本的時間間隔之上進(jìn)行一定的微調(diào),微調(diào)的參數(shù)就是用數(shù)據(jù)結(jié)構(gòu) intervalAdjust 描述的,并發(fā)送給對應(yīng)的通道 resubmitAdjustCh。下一個區(qū)塊在打包時從通道 resubmitAdjustCh 中獲取其對應(yīng)的微調(diào)參數(shù) intervalAdjust 實(shí)行微調(diào)。
7. type worker struct
worker 是負(fù)責(zé)向共識引擎提交新工作并且收集簽名結(jié)果的主要對象。
共識引擎會做哪些工作呢?
- 通過方法 Clique.Prepare() 設(shè)置區(qū)塊頭中關(guān)于 PoA 共識的相關(guān)字段。
- 通過方法 Clique.Finalize() 組裝可以被簽名的區(qū)塊。
- 通過方法 Clieque.Seal() 對區(qū)塊進(jìn)行簽名,并發(fā)送給結(jié)果通道 worker.resultsCh。
- 通過方法 Clique.snapshot() 處理兩種快照:檢查點(diǎn)快照和投票快照。
那么共識引擎需要哪些輸入呢?
- 區(qū)塊頭
- 事務(wù)列表
- 收據(jù)列表
- 狀態(tài)樹
- 叔區(qū)塊列表(PoA 共識協(xié)議中肯定為 nil)
- 區(qū)塊,是個抽象概念,主要包含:區(qū)塊頭、事務(wù)列表、叔區(qū)塊列表,但是并不包含收據(jù)列表。
那么共識引擎會產(chǎn)生哪些輸出呢?
方法 Clieque.Seal() 會將最終簽名后的區(qū)塊發(fā)送給結(jié)果通道 worker.resultsCh。
config *params.ChainConfig: 區(qū)塊鏈的鏈配置信息,包含鏈 ID,是 ethash 還是 clique 共識協(xié)議等
engine consensus.Engine: 共識引擎接口
eth Backend: 后端,包含區(qū)塊鏈和事務(wù)池,提供挖礦所需的所有方法
chain *core.BlockChain: 表示整個區(qū)塊鏈。這不和 eth 中的區(qū)塊鏈?zhǔn)峭粋€?
gasFloor uint64: 最低 gas
gasCeil uint64: 最高 gas
// 訂閱
- mux *event.TypeMux: 可以簡單地理解為事件的訂閱管理器,即注冊事件的響應(yīng)函數(shù),和驅(qū)動事件的響應(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。對接收到的消息 task 先存入待處理 map 中,其中 Key 為 task 中的區(qū)塊簽名哈希,Value 為 task。同時,將 task 中的區(qū)塊傳遞給共識引擎的簽名方法 w.engine.Seal() 進(jìn)行簽名,同時將結(jié)果通道 w.resultCh 和退出通道 stopCh 也傳遞給共識引擎的簽名方法,以便從中接收簽名之后的區(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ù)傳遞給共識引擎的方法 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,同時輸出一條重要的日志信息: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ū)塊是被簽名過的。對于新接收到簽名區(qū)塊,首先判斷這個簽名區(qū)塊是否為重復(fù)的;其次,需要從待處理任務(wù)映射 w.pendingTasks 中獲得對應(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ū)塊及其對應(yīng)的收據(jù)列表和狀態(tài)樹等信息寫入數(shù)據(jù)庫。如果寫入失敗,則輸出一條重要的日志信息: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 模塊完成的。第五,同時構(gòu)建事件 core.ChainEvent 和事件 core.ChainHeadEvent,或者構(gòu)建事件 core.ChainSideEvent,并通過區(qū)塊鏈中的方法 w.chain.PostChainEvents() 進(jìn)行廣播。需要注意的時,此廣播只是針對向本地節(jié)點(diǎn)進(jìn)行了事件注冊的客戶端,且是通過 JSON-RPC 完成,和第四步中的向網(wǎng)絡(luò)中其它節(jié)點(diǎn)通過 p2p 進(jìn)行廣播是完全不同的。這一部的廣播即使沒有事件接收方也沒有問題,因?yàn)檫@是業(yè)務(wù)邏輯層面的,而第四步中的廣播則是必須有接收方的,否則就會破壞以太坊協(xié)議本身。比如:我們可以注冊一個事件,用于監(jiān)控是否有最新的區(qū)塊被挖出來,然后在此基礎(chǔ)上,查詢指定賬戶的最新余額。第六步,將新挖出來的簽名區(qū)塊,添加進(jìn)待確認(rèn)隊(duì)列中,代碼為:w.unconfirmed.Insert(block.NumberU64(), block.Hash())。(2)共識引擎中的簽名方法 Clique.Seal() 通過匿名協(xié)程將簽名后的簽名區(qū)塊 types.Block 發(fā)送到通道 resultCh。
startCh chan struct{}: 通道 startCh 用于在不同協(xié)程之間交互消息 struct{}。可以發(fā)現(xiàn),消息 struct {} 沒有包含任何有意義的信息,這在 Go 中是一類特別重要的寫法,用于由某個協(xié)程向另一個協(xié)程發(fā)送開始或中止消息。(1)函數(shù) newWorker() 向通道 startCh 發(fā)送消息 struct{},其中函數(shù) newWorker() 應(yīng)該是運(yùn)行在主協(xié)程中或由其它某個包中的協(xié)程啟動。代碼為:worker.startCh <- struct{}{}。(2)方法 worker.start() 向通道 startCh 發(fā)送消息 struct{},其它同(1)。(3)命名協(xié)程 worker.newWorkLoop() 從通道 startCh 中接收消息 struct{}。需要注意的是,(1)和(2)都可以向通道 startCh 發(fā)送消息 struct{} 驅(qū)動命名協(xié)程 worker.newWorkLoop() 中邏輯。方法 worker.start() 表明 worker 是可以先停止的,而不關(guān)閉,之后可以重新啟動。
exitCh chan struct{}: 通道 exitCh 用于在不同協(xié)程之間交互消息 struct{}??梢詤⒖纪ǖ?startCh 中的注釋。(1)函數(shù) worker.close() 通過調(diào)用函數(shù) close(w.exitCh) 整個關(guān)閉通道 exitCh。(2)命名協(xié)程 worker.newWorkLoop() 從通道 exitCh 中接收消息,從而結(jié)束整個協(xié)程。(3)命名協(xié)程 worker.mainLoop() 從通道 exitCh 中接收消息,從而結(jié)束整個協(xié)程。(4)命名協(xié)程 worker.taskLoop() 從通道 exitCh 中接收消息,從而結(jié)束整個協(xié)程。(5)命名協(xié)程 worker.resultLoop() 從通道 exitCh 中接收消息,從而結(jié)束整個協(xié)程。(6)命名協(xié)程 worker.mainLoop() 調(diào)用的方法 worker.commit() 從通道 exitCh 中接收消息,從而放棄后續(xù)的工作。
resubmitIntervalCh chan time.Duration: 通道 resubmitIntervalCh 用于在不同的協(xié)程之間交互消息 time.Duration。time.Duration 是 Go 語言標(biāo)準(zhǔn)庫中的類型,在這里通道 resubmitIntervalCh 起到一個定時器的作用,這也是 Go 語言中關(guān)于定時器的標(biāo)準(zhǔn)實(shí)現(xiàn)方式。(1)方法 worker.setRecommitInterval() 向通道 resubmitIntervalCh 發(fā)送消息 time.Duration,即設(shè)置定時器下一次觸發(fā)的時間。方法 worker.setRecommitInterval() 在方法 Miner.SetRecommitInterval() 中被調(diào)用,方法 Miner.SetRecommitInterval() 又在方法 PrivateMinerAPI.SetRecommitInterval() 中調(diào)用,這應(yīng)該是從外部通過 JSON-RPC 接口驅(qū)動的。(2)命名協(xié)程 worker.newWorkLoop() 從通道 resubmitIntervalCh 中接收消息 time.Duration,即獲得希望定時器下一次觸發(fā)的時間,并根據(jù)需要對這個時間進(jìn)行一定的修正。
resubmitAdjustCh chan *intervalAdjust: 通道 resubmitAdjustCh 用于在不同的協(xié)程之間交互消息 intervalAdjust。(1)命名協(xié)程 worker.newWorkLoop() 從通道 resubmitAdjustCh 中接收消息 intervalAdjust。(2)方法 worker.commitTransactions() 向通道 resubmitAdjustCh 中發(fā)送消息 intervalAdjust。通道 resubmitAdjustCh 與通道 resubmitIntervalCh 的作用類似,都是修改下一個區(qū)塊的出塊時間。只不過通道 resubmitAdjustCh 中交互的消息 time.Duration 是由外部通過 JSON-RPC 接口來設(shè)定的,而通道 resubmitIntervalCh 中交互的消息 intervalAdjust 是礦工根據(jù)上一個區(qū)塊的出塊時間基于算法自定調(diào)整的。
current *environment: 描述了 worker 的當(dāng)前環(huán)境和狀態(tài)信息。具體的請參考對數(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)典鏈的一部分。具體的可以參考對數(shù)據(jù)結(jié)構(gòu) unconfirmedBlocks 的注釋。
mu sync.RWMutex: 鎖,用于保護(hù)字段 coinbase 和 extra。
coinbase common.Address: 礦工地址。
extra []byte: 分為三段:前 32 字節(jié)礦工可隨意填寫,最后 65 字節(jié)為對區(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: 用于表示共識引擎是否正在運(yùn)行。
- newTxs int32: 自從上次簽名工作提交之后新到達(dá)的事務(wù)數(shù)量。上次簽名工作即指 worker 中已經(jīng)通過調(diào)用共識引擎的 Finalize() 方法組裝好了待簽名的區(qū)塊,然后通過調(diào)用共識引擎的簽名方法 Clique.Seal() 對待簽名區(qū)塊進(jìn)行簽名。即在上一個區(qū)塊被本地節(jié)點(diǎn)挖出之后,新來的事務(wù)數(shù)量。
// Test hooks
- newTaskHook func(*task): 接收到新簽名任務(wù)時調(diào)用此方法。
- skipSealHook func(*task) bool: 判定是否跳過簽名時調(diào)用 此方法。
- fullTaskHook func(): 在推送完整簽名任務(wù)之前調(diào)用此方法。
- resubmitHook func(time.Duration, time.Duration): 更新重新提交間隔時調(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: 共識引擎
- eth Backend: 以太坊本地節(jié)點(diǎn)的后端
- mux *event.TypeMux: 事件訂閱管理器
- recommit time.Duration: 下一次任務(wù)的基礎(chǔ)時間間隔
- gasFloor, gasCeil uint64: Gas 的下限 gasFloor 和上限 gasCeil。
主要實(shí)現(xiàn):
首先構(gòu)建對象 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。同時,輸出日志信息:log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval)
啟動新的獨(dú)立協(xié)程運(yùn)行方法 worker.mainLoop()。
啟動新的獨(dú)立協(xié)程運(yùn)行方法 worker.newWorkLoop(recommit)。
啟動新的獨(dú)立協(xié)程運(yùn)行方法 worker.resultLoop()。
啟動新的獨(dú)立協(xié)程運(yùn)行方法 worker.taskLoop()。
-
提交第一個工作以初始化待處理狀態(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ū)塊中用于存儲礦工相關(guān)的一些額外信息。上層調(diào)用方法 miner.Miner.SetExtra(),繼續(xù)上層調(diào)用方法為 eth.Ethereum 的構(gòu)造函數(shù) eth.New() 中的代碼 eth.miner.SetExtra(makeExtraData(config.MinerExtraData))。這個參數(shù)最終是通過 geth 的 MINER OPTIONS 命令行參數(shù) --extradata,或者 ETHEREUM OPTIONS 的命令行參數(shù) --config,這是一個 TOML 配置文件。
(4) func (w *worker) setRecommitInterval(interval time.Duration)
方法 setRecommitInterval() 更新礦工簽名工作重新提交的間隔。
參數(shù):
- interval time.Duration: 重新提交的時間間隔。
主要實(shí)現(xiàn):
- 將重新提交的間隔 interval 發(fā)送到通道 worker.resubmitIntervalCh,代碼為:w.resubmitIntervalCh <- interval。命名協(xié)程 worker.newWorkLoop() 會從通道 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ù)的所有后臺線程。注意 worker 不支持被關(guān)閉多次,這是由 Go 語言不允許多次關(guān)閉同一個通道決定的。
主要實(shí)現(xiàn)
- close(w.exitCh)
(11) func (w *worker) newWorkLoop(recommit time.Duration)
方法 newWorkLoop() 是一個獨(dú)立的協(xié)程,基于接收到的事件提交新的挖礦工作。不妨將此協(xié)程稱作命名協(xié)程 worker.newWorkLoop()。
參數(shù):
- recommit time.Duration: 下一次提交間隔。
主要實(shí)現(xiàn):
- 定義了三個變量:
- interrupt *int32: 中斷信號
- minRecommit = recommit: 用戶指定的最小重新提交間隔
- timestamp int64: 每輪挖礦的時間戳
- 定義一個定時器,并丟棄初始的 tick
- timer := time.NewTimer(0)
- <-timer.C
- 定義內(nèi)部提交函數(shù) commit()
- 提交函數(shù) commit() 使用給定信號中止正在進(jìn)行的交易執(zhí)行,并重新提交新信號。
- 構(gòu)建新工作請求 newWorkReq,并發(fā)送給通道 newWorkCh 來驅(qū)動命名協(xié)程 worker.mainLoop() 來重新提交任務(wù)。
- 設(shè)置定時器 timer 的下一次時間。代碼為: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ū)塊編號
- 加鎖 w.pendingMu.Lock()
- 循環(huán)迭代 w.pendingTasks
- 區(qū)塊簽名哈希 h
- 任務(wù) t
- 如果 t 中的區(qū)塊編號比 number 要早 staleThreshold 個區(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
- 重置定時器。代碼為:timer.Reset(recommit)
- 退出本輪迭代。
- 調(diào)用內(nèi)部函數(shù) commit(false, commitInterruptResubmit) 提交新的 newWorkReq。
- timer.C:
- 如果挖礦正在進(jìn)行中,則定期重新提交新的工作周期以便更新到價(jià)格較高的交易。對于待處理中的區(qū)塊禁用此操作開銷。
- 對于 poa 共識引擎,需要其配置的 Clique.Period > 0。!!!等于這里對于共識算法有個特殊處理。
- 調(diào)用內(nèi)部函數(shù) commit(true, commitInterruptResubmit) 提交新的 newWorkReq。
- 【批注 1】,這里用到了 time.Timer 將定時器,時間間隔為 recommit。
- 【批注 2】,通道主要的作用是用于協(xié)程之間交互消息,那么實(shí)際上影響到的就是工作流程。這個定時器應(yīng)該主要就是挖礦有周期性的概念,比如 15 秒產(chǎn)生一個塊。存在兩個定時間隔,一個是靜態(tài)配置的,另一個是由挖礦動態(tài)決定的。當(dāng)挖礦的實(shí)際時間長于靜態(tài)設(shè)定的,那么可能需要做一些操作,比如重新挖礦等等吧。當(dāng)挖礦的實(shí)際時間適于靜態(tài)設(shè)定的,可能不需要做什么操作。
- 如果挖礦正在進(jìn)行中,則定期重新提交新的工作周期以便更新到價(jià)格較高的交易。對于待處理中的區(qū)塊禁用此操作開銷。
- resubmitIntervalCh:
- 支持由用戶來重新設(shè)定重新提交的間隔。
- 用戶設(shè)定的值不能小于 minRecommitInterval。
- 如果回調(diào)函數(shù) resubmitHook 不空,則調(diào)用。
- resubmitAdjustCh:
- 根據(jù)挖礦的反饋來動態(tài)地調(diào)整重新提交的間隔。
- 如果回調(diào)函數(shù) resubmitHook 不空,則調(diào)用。
- exitCh:
- 接收到退出消息,退出整個協(xié)程。
- startCh:
命名協(xié)程 worker.mainLoop() 用于根據(jù)接收到的事件生成簽名任務(wù),命名協(xié)程 worker.taskLoop() 用于接收上述驗(yàn)證任務(wù)并提交給共識引擎,命名協(xié)程 worker.resultLoop() 用于處理簽名結(jié)果的提交并更新相關(guān)數(shù)據(jù)到數(shù)據(jù)庫中。
(12) func (w *worker) mainLoop()
方法 mainLoop() 是一個獨(dú)立的協(xié)程,用于根據(jù)接收到的事件重新生成簽名任務(wù)。不妨將此協(xié)程稱作命名協(xié)程 worker.mainLoop()。
主要實(shí)現(xiàn):
- 在整個協(xié)程退出時,取消 txsSub、chainHeadSub、chainSideSub 這三個訂閱。
- 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 中。同時,任務(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 個,且 ev 中攜帶的新叔區(qū)塊有效,則重新生成挖礦中的任務(wù)。見代碼:if w.isRunning() && w.current != nil && w.current.uncles.Cardinality() < 2
- 獲取任務(wù)開始時間 start。代碼為:start := time.Now()
- 通過方法 commitUncle() 將 ev 中攜帶的區(qū)塊添加到 current.uncles 中。如果成功
- 定義新任務(wù)中所需要的區(qū)塊頭列表 uncles。代碼為:var uncles []*types.Header
- 遍歷 w.current.uncles 中的每個 uncle hash
- 從 possibleUncles 中找到 uncle hash 對應(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ū)塊,起到一個緩沖的作用。 current.uncles 是當(dāng)前要打包的區(qū)塊中已經(jīng)被確認(rèn)的叔區(qū)塊。
- 【批注 2】:possibleUncles 是<區(qū)塊頭哈希>區(qū)塊構(gòu)成的 map,current.uncles 則僅包含了區(qū)塊頭哈希。
- 接收到新的消息 ev(事件 ChainSideEvent)
- txsCh:根據(jù)新接收到的消息 ev(事件 core.NewTxsEvent)
- 如果不在挖礦狀態(tài),則將交易置于待處理狀態(tài)。
- 注意,收到的所有交易可能與已包含在當(dāng)前挖礦區(qū)塊中的交易不連續(xù)。這些交易將自動消除。
- if !w.isRunning() && w.current != nil
- 加鎖、解鎖的方式獲取礦工地址 coinbase。代碼為:coinbase := w.coinbase
- 定義變量 txs。代碼為:txs := make(map[common.Address]types.Transactions)
- 遍歷消息 ev 中攜帶的交易列表,對于每個交易 tx
- 還原出每個交易 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
- 如果我們正在挖礦中,但沒有正在處理任何事情,請?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)從退出通道接收到消息時,結(jié)束整個協(xié)程。
- w.txsSub.Err()
- 當(dāng)從交易訂閱通道接收到錯誤消息時,結(jié)束整個協(xié)程。
- w.chainHeadSub.Err()
- 當(dāng)從區(qū)塊頭訂閱通道接收到錯誤消息時,結(jié)束整個協(xié)程。
- w.chainSideSub.Err()
- 當(dāng)從側(cè)鏈區(qū)塊頭訂閱通道接收到錯誤消息時,結(jié)束整個協(xié)程。
- newWorkCh:
(13) func (w *worker) taskLoop()
方法 taskLoop() 是一個獨(dú)立的協(xié)程,用于從生成器中獲取待簽名任務(wù),并將它們提交給共識引擎。不妨將此協(xié)程稱作命名協(xié)程 worker.taskLoop()。
主要實(shí)現(xiàn):
- 定義兩個變量:退出通道 stopCh 和上一個區(qū)塊哈希 prev
- stopCh chan struct{}
- prev common.Hash
- 定義局部中斷函數(shù) interrupt(),用于關(guān)閉退出通道 stopCh,結(jié)束所有從退出通道 stopCh 接收消息的協(xié)程,這里共識引擎方法 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() 中止共識引擎方法 Seal() 中正在簽名的獨(dú)立匿名協(xié)程。這里是通過關(guān)閉退出通道 stopCh 實(shí)現(xiàn)的。
- 給退出通道 stopCh 分配空間,并設(shè)置上一個區(qū)塊哈希 prev。
- stopCh, prev = make(chan struct{}), sealHash
- 如果回調(diào)函數(shù) w.skipSealHook() 不為 nil 和 w.skipSealHook(task) 返回 true,則退出本輪迭代。
- 通過對鎖 w.pendingMu 執(zhí)行加鎖、解鎖,將任務(wù) task 添加到 w.pendingTasks 中,為之后命名協(xié)程 worker.resultLoop() 中接收到已簽名區(qū)塊,查找包含該區(qū)塊的任務(wù) task 而用。
- 將任務(wù) task 中包含的區(qū)塊提交給共識引擎進(jìn)行簽名。代碼為:w.engine.Seal(w.chain, task.block, w.resultCh, stopCh)
- 需要特別注意傳遞的兩個通道參數(shù) w.resutlCh, stopCh
- 通道 w.resultCh 用于從共識引擎的簽名方法 Seal() 中接收已簽名區(qū)塊。
- 通道 stopCh 用于發(fā)送中止信號給共識引擎的簽名方法 Seal(),從而中止共識引擎正在進(jìn)行的簽名操作。
- 如果簽名失敗,則輸出日志信息:log.Warn("Block sealing failed", "err", err)
- exitCh:
- 當(dāng)接收到退出消息時
- 通過調(diào)用內(nèi)部中斷函數(shù) interrupt() 關(guān)閉中止通道 stopCh,從而使得共識引擎的簽名方法 Seal() 放棄本次簽名。
- 退出整個協(xié)程。
- 當(dāng)接收到退出消息時
- taskCh:
(14) func (w *worker) resultLoop()
方法 resultLoop() 是一個獨(dú)立的協(xié)程,用于處理簽名區(qū)塊的提交和廣播,以及更新相關(guān)數(shù)據(jù)到數(shù)據(jù)庫。不妨將此協(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)入下一輪迭代。
- 定義兩個變量:
- 區(qū)塊簽名哈希 sealhash,代碼為:sealhash = w.engine.SealHash(block.Header())
- 區(qū)塊哈希 hash,代碼為:hash = block.Hash()
- 分別計(jì)算區(qū)塊頭的驗(yàn)證哈希 sealHash(不包括 extraData 中的最后 65 個字節(jié)的簽名信息),區(qū)塊的哈希 hash (即區(qū)塊頭的哈希,而且包含整個 extraData)。
- 通過對鎖 w.pendingMu 進(jìn)行加鎖和解鎖的方式從 w.pendingTasks 中找到 sealHash 對應(yīng)的 task。這是找出已簽名區(qū)塊對應(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)
- 同時,退出本次迭代。
- 定義兩個變量,交易回執(zhí)列表 receipts,交易回執(zhí)中包含的日志列表 logs。
- receipts = make([]*types.Receipt, len(task.receipts))
- logs []*types.Log
- 這是因?yàn)椴煌膮^(qū)塊可能會共享相同的區(qū)塊簽名哈希,建立這些副本是為了防止寫寫沖突。
- 更新所有日志中的區(qū)塊哈希。這是因?yàn)閷τ谶@些日志來說,直到現(xiàn)在才知道對應(yīng)的區(qū)塊哈希,而在創(chuàng)建單個交易的交易回執(zhí)的接收日志時,并不知道對應(yīng)的區(qū)塊哈希。
- 更新 task.receipts 中各 receipt.Logs 的 BlockHash 值為 hash。
- 通過方法 w.chain.WriteBlockWithState() 將區(qū)塊 block,交易回執(zhí)列表 receipts,狀態(tài)數(shù)據(jù)庫 task.state 寫入數(shù)據(jù)庫,并返回寫入狀態(tài) stat。stat 的取值:NonStatTy (0)、CanonStatTy (1)、SideStatTy(2)。
- 如果寫入失敗,則輸出日志信息:log.Error("Failed writing block to chain", "err", err)。同時,退出本輪迭代。
- 至此,成功的驗(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ù)庫返回的狀態(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:
- 接收到退出消息則中止整個協(xié)程。
- resultCh:
命名協(xié)程 worker.resultLoop() 從通道 resultCh 中接收消息 types.Block,且此區(qū)塊是被簽名過的。對于新接收到簽名區(qū)塊,首先判斷這個簽名區(qū)塊是否為重復(fù)的;其次,需要從待處理任務(wù)映射 w.pendingTasks 中獲得對應(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ū)塊及其對應(yīng)的收據(jù)列表和狀態(tài)樹等信息寫入數(shù)據(jù)庫。如果寫入失敗,則輸出一條重要的日志信息: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 模塊完成的。第五,同時構(gòu)建事件 core.ChainEvent 和事件 core.ChainHeadEvent,或者構(gòu)建事件 core.ChainSideEvent,并通過區(qū)塊鏈中的方法 w.chain.PostChainEvents() 進(jìn)行廣播。需要注意的時,此廣播只是針對向本地節(jié)點(diǎn)進(jìn)行了事件注冊的客戶端,且是通過 JSON-RPC 完成,和第四步中的向網(wǎng)絡(luò)中其它節(jié)點(diǎn)通過 p2p 進(jìn)行廣播是完全不同的。這一部的廣播即使沒有事件接收方也沒有問題,因?yàn)檫@是業(yè)務(wù)邏輯層面的,而第四步中的廣播則是必須有接收方的,否則就會破壞以太坊協(xié)議本身。比如:我們可以注冊一個事件,用于監(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),如果失敗,直接返回錯誤
- 構(gòu)建當(dāng)前環(huán)境 environment 的對象 env
- 設(shè)定字段 signer 為 types.EIP155Signer
- 設(shè)定字段 state 為前面獲取的 state
- 設(shè)定字段 header 為參數(shù) header
- 默認(rèn)初始化其它字段
- 從區(qū)塊鏈中獲取父區(qū)塊之前的 7 個高度的所有區(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ū)塊集合中,如果添加失敗則返回錯誤。
參數(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") }
- 上述四個判定都通過,則添加到當(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
- 對于 w.current.uncles 中的每個叔區(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ā)起者地址。此方法會生成交易的交易回執(zhí)。
參數(shù):
- tx *types.Transaction: 具體的一次交易信息。
- coinbase common.Address: 交易的發(fā)起方地址,可以明確指定。如果為空,則為區(qū)塊簽名者的地址。
返回值:
- []*types.Log: 交易回執(zhí)中的日志信息。
主要實(shí)現(xiàn):
- 先對狀態(tài)樹進(jìn)行備份 snap,代碼為:snap := w.current.state.Snapshot()
- 通過對交易 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ù)整個交易列表 txs 是否都被有效提交,返回 true 或 false。
參數(shù):
- txs *types.TransactionsByPriceAndNonce: 交易列表的管理器,同時根據(jù)價(jià)格和隨機(jī)數(shù)值進(jìn)行排序,每次輸出一個排序最靠前的交易。具體的注釋,參考 types.TransactionsByPriceAndNonce。
- coinbase common.Address: 交易的發(fā)起方地址,可以明確指定。如果為空,則為區(qū)塊簽名者的地址。
- interrupt *int32: 中斷信號值。需要特別說明,這是個指針類型的值,意味著后續(xù)的每輪迭代都能讀取外部對于參數(shù) interrupt 的更新。同時,此方法還能將內(nèi)部對于參數(shù) interrupt 的修改反饋給外部調(diào)用者。
返回值:
- 整個交易列表是否都被正確處理。
主要實(shí)現(xiàn):
- 如果 w.current 為空,直接返回。
- 如果 w.current.gasPool 為空,則初始化為 w.current.header.GasLimit
- 匯總的事件日志,代碼為:var coalescedLogs []*types.Log
- 循環(huán)處理交易列表 txs:
- 在以下三種情況下,我們將中斷交易的執(zhí)行。對于前兩種情況,半成品將被丟棄。對于第三種情況,半成品將被提交給共識引擎。需要特別說明的是,這一步會根據(jù) w.current.header.GasLimit 和 w.current.gasPool.Gas() 計(jì)算事件 intervalAdjust 的字段 ratio,并將字段 inc 設(shè)為 true,然后將事件 intervalAdjust 發(fā)送給通道 w.resubmitAdjustCh,從而驅(qū)動命名協(xié)程 worker.newWorkLoop() 的工作流程。具備的可以參考代碼。
- (1)新的區(qū)塊頭塊事件到達(dá),中斷信號為1。
- (2)對象 worker 啟動或重啟,中斷信號為1。
- (3)對象 worker 用任何新到達(dá)的交易重新創(chuàng)建挖掘區(qū)塊,中斷信號為2。
- 直接返回,退出整個循環(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)提交并得到正常處理的交易仍然不變。
- 獲取下一個交易 tx,如果為空則退出整個循環(huán)。
- 獲取交易的發(fā)起者 from。見代碼:from, _ := types.Sender(w.current.signer, tx)
- 這里可能會忽略錯誤。交易在被加入交易池時已經(jīng)得到了檢查。
- 無論當(dāng)前的 hf 如何,我們都使用 eip155 簽名者。
- 檢查交易 tx 是否重播受保護(hù)。如果我們不在 EIP155 hf 階段,請?jiān)谖覀冮_始之前開始忽略發(fā)送方。
- 即過濾掉此交易。當(dāng)然,仍然要從 txs 中剔除。見代碼:txs.Pop(); continue
- 開始執(zhí)行交易:
- 更新狀態(tài)樹。需要說明的是,這一步會記錄交易在區(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)移下一個交易。這是因?yàn)?,該賬戶已經(jīng)支付不起 Gas 了,所以不需要再處理該賬戶的其它交易。這個實(shí)現(xiàn)有點(diǎn)漂亮?。?!
- 輸出重要的日志信息:log.Trace("Gas limit exceeded for current block", "sender", from)
- txs.Pop()
- case core.ErrNonceTooLow
- 交易池和礦工之間的新區(qū)塊頭通知數(shù)據(jù)競爭,轉(zhuǎn)移該賬戶下一個交易。
- 輸出重要的日志信息:log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
- txs.Shift()
- case core.ErrNonceTooHigh
- 事務(wù)池和礦工之間的重組通知數(shù)據(jù)競爭,跳過 account 的所有交易
- 輸出重要的日志信息:log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce())
- txs.Pop()
- case nil
- 一切正常,收集日志并從同一帳戶轉(zhuǎn)移下一個交易
- coalescedLogs = append(coalescedLogs, logs...)
- w.current.tcount++,需要增加當(dāng)前區(qū)塊的交易索引。
- txs.Shift()
- default:
- 奇怪的錯誤,丟棄事務(wù)并獲得下一個(注意,nonce-too-high子句將阻止我們徒勞地執(zhí)行)。
- 輸出重要的日志信息:log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
- txs.Shift()
- case core.ErrGasLimitReached
- 在以下三種情況下,我們將中斷交易的執(zhí)行。對于前兩種情況,半成品將被丟棄。對于第三種情況,半成品將被提交給共識引擎。需要特別說明的是,這一步會根據(jù) w.current.header.GasLimit 和 w.current.gasPool.Gas() 計(jì)算事件 intervalAdjust 的字段 ratio,并將字段 inc 設(shè)為 true,然后將事件 intervalAdjust 發(fā)送給通道 w.resubmitAdjustCh,從而驅(qū)動命名協(xié)程 worker.newWorkLoop() 的工作流程。具備的可以參考代碼。
- 我們在挖掘時不會推送pendingLogsEvent。原因是當(dāng)我們開采時,工人將每3秒鐘再生一次采礦區(qū)。為了避免推送重復(fù)的pendingLog,我們禁用掛起的日志推送。
- 構(gòu)建日志集合 coalescedLogs 的副本 cpy,避免同步問題
- 啟動一個獨(dú)立的匿名協(xié)程,將日志集合的副本 cpy 通過方法 TypeMux.Post() 發(fā)送出去。
- 如果當(dāng)前間隔大于用戶指定的間隔,則通知重新提交循環(huán)以減少重新提交間隔。代碼為:w.resubmitAdjustCh <- &intervalAdjust{inc: false}。即將事件 intervalAdjust 發(fā)送到通道 w.resubmitAdjustCh,從而驅(qū)動命名協(xié)和 worker.newWorkLoop() 的后續(xù)邏輯。
(20) func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64):
方法 commitNewWork() 基于父區(qū)塊生成幾個新的簽名任務(wù)。
參數(shù):
- interrupt *int32: 中斷信號,值為:commitInterruptNone (0)、commitInterruptNewHead (1)、commitInterruptResubmit (2) 之一。
- noempty bool: ???
- timestamp int64: ??? 區(qū)塊時間?
主要實(shí)現(xiàn):
- 加鎖、解鎖 w.mu。說明對整個方法進(jìn)行了加鎖處理。
- 獲取當(dāng)前時間 tstart,代碼為:tstart := time.Now()
- 獲取父區(qū)塊 parent,即區(qū)塊鏈上的當(dāng)前區(qū)塊。代碼為:parent := w.chain.CurrentBlock()
- 根據(jù)父區(qū)塊的時間,調(diào)整下一個區(qū)塊的時間。
- 如果挖礦太超前,計(jì)算超前時間 wait,并睡眠 wait 時間。同時,輸出日志:log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait))
- 獲取父區(qū)塊編號 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),
} - 只有在共識引擎正在運(yùn)行中,才設(shè)置 coinbase(避免虛假區(qū)塊獎勵)
- 如果 w.coinbase == (common.Address{}),則輸出日志信息:log.Error("Refusing to mine without etherbase")。同時,退出整個方法。
- header.Coinbase = w.coinbase
- 調(diào)用共識引擎的方法 Prepare() 設(shè)置區(qū)塊頭 header 中的共識字段。如果失敗,則輸出日志信息:log.Error("Failed to prepare header for mining", "err", err)。同時,退出整個方法。
- 處理 DAO 硬分叉相關(guān)內(nèi)容,暫時忽略。
- 構(gòu)建挖礦的當(dāng)前環(huán)境,代碼為:w.makeCurrent(parent, header)。如果失敗,輸出日志:log.Error("Failed to create mining context", "err", err)。同時,退出整個方法。
- env := w.current
- 對 env 應(yīng)用 DAO 相關(guān)操作。
- 刪除 w.possibleUncles 中相對于當(dāng)前區(qū)塊太舊的叔區(qū)塊
- 遍歷 w.possibleUncles 累計(jì)當(dāng)前區(qū)塊的叔區(qū)塊列表 uncles,最多支持 2 個叔區(qū)塊。
- 下一個可能的叔區(qū)塊(hash 和 uncle)
- 如果叔區(qū)塊列表 uncles 的長度已經(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)。同時,uncles = append(uncles, uncle.Header())
- if !noempty
- 基于臨時復(fù)制狀態(tài)創(chuàng)建空區(qū)塊以提前進(jìn)行簽名,而無需等待區(qū)塊執(zhí)行完成。
- w.commit(uncles, nil, false, tstart)
- 使用所有可用的待處理交易填充區(qū)塊。代碼為:pending, err := w.eth.TxPool().Pending()。如果失敗,則輸出日志:log.Error("Failed to fetch pending transactions", "err", err)。同時,退出整個方法。需要說明的是,從交易池中獲取所有待處理的交易列表,pending 的數(shù)據(jù)結(jié)構(gòu)為:map[common.Address]types.Transactions。
- 如果沒有待處理的交易列表
- 更新快照。代碼為:w.updateSnapshot()
- 退出整個方法。
- 將交易池中的交易 pending 劃分為本地交易列表 localTxs 和遠(yuǎn)程交易列表 remoteTxs。本地交易即提交者為 w.coinbase。
- 具體方法為將事務(wù)池中地址為 w.coinbase 的放入本地事務(wù)列表,否則放入遠(yuǎn)程事務(wù)列表。
- 如果本地交易列表 localTxs 的長度大于 0
- 將 localTxs 封裝為數(shù)據(jù)結(jié)構(gòu) types.NewTransactionsByPriceAndNonce。代碼為:txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs)
- 提交交易列表。代碼為:w.commitTransactions(txs, w.coinbase, interrupt)。如果失敗,退出整個方法。
- 如果本地交易列表 remoteTxs 的長度大于 0
- 將 remoteTxs 封裝為數(shù)據(jù)結(jié)構(gòu) types.NewTransactionsByPriceAndNonce。代碼為:txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs)
- 提交交易列表。代碼為:w.commitTransactions(txs, w.coinbase, interrupt)。如果失敗,退出整個方法。
- 調(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ū)塊,并在共識引擎運(yùn)行時提交新工作。
參數(shù):
- uncles []*types.Header: 叔區(qū)塊列表
- interval func(): 中斷函數(shù)
- update bool: 是否更新快照
- start time.Time: 方法被調(diào)用的時間
返回值:
- 如果出錯則返回出錯消息,否則返回 nil。
主要實(shí)現(xiàn):
- 為了避免在不同任務(wù)之間的交互,通過深度拷貝構(gòu)建 current.receipts 的副本 receipts。
- 構(gòu)建狀態(tài)數(shù)據(jù)庫 w.current.state 的副本 s。
- 調(diào)用共識引擎的方法 Finalize() 構(gòu)建出最終待簽名的區(qū)塊 block。需要特別說明的是:對于待組裝的區(qū)塊來說,除了叔區(qū)塊列表 uncles 是作為參數(shù)傳入之外,其它的關(guān)鍵信息,如:區(qū)塊頭、交易列表、交易回執(zhí)列表都是在當(dāng)前環(huán)境 w.current 中獲取的。
- 如果對象 worker 正在運(yùn)行中:
- 如果中斷函數(shù) interval 非空,則調(diào)用函數(shù) interval()。
- 構(gòu)建任務(wù) task,并將其發(fā)送到通道 taskCh,從而驅(qū)動命名協(xié)程 worker.taskLoop() 的工作流程。
- 刪除待確認(rèn)區(qū)塊列表中的過期區(qū)塊,代碼為:w.unconfirmed.Shift(block.NumberU64() - 1)
- 累計(jì)區(qū)塊 block 中所有交易消耗 Gas 的總和 feesWei。第 i 個交易 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ù)傳遞給共識引擎的方法 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,同時輸出一條重要的日志信息: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
Contributor
- Windstamp, https://github.com/windstamp