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è)定的岸夯,可能不需要做什么操作麻献。
- 如果挖礦正在進(jìn)行中筑累,則定期重新提交新的工作周期以便更新到價(jià)格較高的交易袱蜡。對(duì)于待處理中的區(qū)塊禁用此操作開銷。
- 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é)程短纵。
- startCh:
命名協(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ū)塊頭哈希。
- 接收到新的消息 ev(事件 ChainSideEvent)
- 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é)程薯鳍。
- newWorkCh:
(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é)程。
- 當(dāng)接收到退出消息時(shí)
- taskCh:
(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é)程外恕。
- resultCh:
命名協(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()
- case core.ErrGasLimitReached
- 在以下三種情況下殴蹄,我們將中斷交易的執(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() 的工作流程戳杀。具備的可以參考代碼羽峰。
- 我們?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
Contributor
- Windstamp, https://github.com/windstamp