【6】以太坊Miner模組原始碼分析
Miner模組:
類結構如下:
主要結構體
Miner、Worker、Work、CpuAgent、Result、unconfiredBlocks
Miner為挖礦的核心物件(礦工),核心成員包括註冊處理事件(mux)、工人(worker)、coinbase(預設賬戶地址hash)、後臺處理物件(eth)、共識處理引擎(engine)、判定能否挖礦的物件(canstart)、判定是否同步挖礦(shouldstart)
方法:
方法名 |
引數 |
主要描述 |
update |
空 |
防止DOS攻擊 |
Start |
coinbase |
關閉同步,啟動挖礦 |
Stop |
空 |
停止挖礦 |
Register |
空 |
註冊代理 |
Unregister |
空 |
解除代理 |
Mining |
空 |
是否挖礦 |
HashRate |
空 |
返回Pow當前挖礦的雜湊率 |
SetExtra |
空 |
設定worker的extra值(byte陣列) |
Pending |
空 |
掛起返回當前掛起的塊和關聯的狀態。 |
PendingBlock |
空 |
掛起返回當前掛起的塊 |
SetEtherbase |
空 |
設定coinbase |
Worker為挖礦管理的核心物件,主要的屬性包括區塊配置項(BlockConfig)、共識引擎(consensus.Engine)、交易事件處理通道或生產訂閱處理(txsch、chainHead、chainSide Ch or Sub)、代理物件集、
屬性:
屬性名 |
型別 |
主要描述 |
config |
*params.ChainConfig |
區塊鏈config配置 |
engine |
consensus.Engine |
共識引擎 |
mu |
sync.Mutex |
互斥鎖 |
mux |
*event.TypeMux |
Type MeXUX將事件傳送到已註冊的接收方。接收機可以註冊處理某些型別的事件。任何操作MUX停止後呼叫將返回ErrMxxOutlook零值已經準備好使用了。棄用:使用飼料 |
txsCh
|
chan core.NewTxsEvent |
|
txsSub |
event.Subscription |
|
chainHeadCh |
chan core.ChainHeadEvent |
|
chainHeadSub |
event.Subscription |
|
…… |
…… |
…… |
agents |
agents map[Agent]struct{} |
挖礦代理通道 |
recv |
chan *Result |
結果通道 |
eth |
Backend |
後臺處理物件(賬號管理、鏈、交易池以及鏈儲存管理) |
chain |
*core.BlockChain |
鏈物件 |
proc |
core.Validator |
區塊核查物件,核查區塊兒內容 |
chainDb |
ethdb.Database |
資料庫封裝所有資料庫操作。所有的方法都是安全的併發使用。 |
coinbase |
common.Address |
Coinbase |
extra |
[]byte |
區塊的extraData |
currentMu |
sync.Mutex |
當前互斥鎖 |
current |
*Work |
當前執行的工作work |
snapshotMu |
sync.RWMutex |
讀寫鎖快照 |
snapshotBlock |
*types.Block |
區塊快照 |
snapshotState |
*state.StateDB |
狀態快照 |
uncleMu |
sync.Mutex |
數塊互斥鎖 |
possibleUncles |
map[common.Hash]*types.Block |
數塊雜湊對映 |
unconfirmed |
*unconfirmedBlocks |
未確認區塊 |
atWork |
int32 |
執行中的引擎數量 |
running |
int32 |
狀態判定欄位 |
方法:
方法名 |
引數 |
描述 |
newWorker |
Config,engine,eth,mux返回*worker |
初始化Worker函式 |
setEtherbase |
addr (common.Address) |
設定coinbase |
setExtra |
extra ([]byte) |
設定extra |
pending |
返回(*types.Block, *state.StateDB) |
返回快照防止房錢互斥鎖徵用 |
pendingBlock |
返回 *types.Block |
返回區塊快照 |
start |
空 |
啟動所有agent挖礦工作 |
stop |
空 |
停止所有agent挖礦工作 |
isRunning |
空 |
返回是否執行bool值 |
register |
agent |
註冊新代理,若worker執行則啟動代理挖礦 |
unregister |
Agent |
解除已存在的agent,並停止挖礦 |
update |
空 |
處理交易、區塊頭等事件,更新區塊、叔塊和交易資訊 |
wait |
空 |
更新所有日誌中的塊雜湊,從它現在可用時,而不是在建立單個事務的收據/日誌時,廣播pending的區塊等待確認 |
push |
Work |
建立一個新的work 任務給當前活動的礦工代理 |
makeCurrent |
parent *types.Block, header *types.Header |
為當前迴圈,建立一個新的環境 |
commitNewWork |
空 |
提交新的work |
commitUncle |
Work,uncle |
在新的work中新增叔塊hash |
updateSnapshot |
空 |
更新快照 |
commitTransactions |
Mux、 txs、bc、coinbase |
提交交易 |
commitTransaction |
Tx,bc、coinbase、gp |
|
Work是礦工的執行環境,掌握所有當前狀態資訊
屬性:
屬性名 |
型別 |
主要描述 |
config |
*params.ChainConfig |
區塊鏈config配置 |
signer |
types.Signer |
簽名器封裝事務簽名處理 |
state |
*state.StateDB |
網路狀態儲存 |
ancestors |
mapset.Set |
祖先 用於檢查叔塊父塊是否合法 |
family |
mapset.Set |
用於檢查叔塊合法 |
uncles |
mapset.Set |
叔塊集合 |
gasPool |
*core.GasPool |
Gas池 用於打包交易的可用gas值 |
Block |
*types.Block |
新塊兒 |
header |
*types.Header |
…… |
txs |
[]*types.Transaction |
…… |
receipts |
[]*types.Receipt |
…… |
createdAt |
time.Time |
…… |
Agent:挖礦代理
屬性:
屬性名 |
型別 |
主要描述 |
mu |
sync.Mutex |
互斥鎖 |
workCh |
chan *Work |
Work 通道 |
quitCurrentOp |
chan struct{} |
取消當前操作 |
returnCh |
chan<- *Result |
返回執行結果(work 和區塊) |
chain |
consensus.ChainReader |
Uncle核查時提供查詢區塊鏈的物件 |
Engine |
consensus.Engine |
共識處理引擎 |
Started |
Int32 |
指定agent當前是否啟動 |
方法:
方法名 |
引數 |
描述 |
Work |
無 返回chan<-*Work |
返回工作通道 |
SetReturnCh |
ch |
設定Work 通道 |
Start |
空 |
呼叫update,啟動挖礦 |
Stop |
空 |
清空work |
Update |
consensus.ChainReader |
Uncle核查時提供查詢區塊鏈的物件 |
Mine |
work. stop |
呼叫engine.Seal封裝交易,打包新塊兒 |
Miner 中Gas及gasPrice相關重點分析:( blockheader 的gaslimit來源commitNewWork)
1)從miner、worker、work、agent的相關介面可知並無直接修改或者接受gas、gasprice、gaslimit的直接設定和修改,miner對gas、gasPrice不具有大的自主性修改許可權和接收許可權
2)挖礦打包的過程由eth.backend.go實現,其中物件Ethereum處理交易池、區塊鏈儲存、共識引擎、礦工、RPC服務等關鍵操作,值得注意的是ethereum物件包含gasprice物件,但該值初始化配置源於config的預設設定為big.NewInt(18 * params.Shannon),見eth.config.go檔案中的DefaultConfig;
3)從commitTransactions函式分析,若新的work 的可用gas值gasPool為nil則預設將work正在處理的區塊兒的區塊頭中的Gaslimit作為該work的gaspool,作為當前工作的可用gas。而當前work.header.Gaslimit來源於work. commitNewWork()中core.CalcGasLimit(parent),計算機制如下:
// contrib = (parentGasUsed * 3 / 2) / 1024,當父區塊Gas使用量>2/3的時候,會較上次加大gaslimit,反之減小;
計算新區塊gaslimit的貢獻度值contrib := (parent.GasUsed() + parent.GasUsed()/2) / params.GasLimitBoundDivisor(1024)
計算衰減量:decay = parentGasLimit / 1024 -1
- 最後計算得到limit := parent.GasLimit() - decay + contrib
- gaslimit 不可小於5000 不大於創世塊兒的gaslimit限制(4712388)
此處work在打包時要進行gas判斷:env.gasPool.Gas() < params.TxGas(21000),每個work的gas值(也就是說交易傳送者提供的每筆交易gas)不能低於21000;
commitTransaction呼叫了core.ApplyTransaction函式,該函式的具體實現Gas的消耗和獎勵機制,過程如下圖:
- ApplyTransaction() 首先根據輸入引數分別封裝出一個Message物件和一個EVM物件;
- 然後加上一個傳入的GasPool型別變數,由TransitionDb()函式完成tx的執行,待TransitionDb()返回之後,建立一個收據Receipt物件,最後返回該Recetip物件,以及整個tx執行過程所消耗Gas數量。
- GasPool物件是在一個Block執行開始時建立,並在該Block內所有tx的執行過程中共享,對於一個tx的執行可視為“全域性”儲存物件; Message由此次待執行的tx物件轉化而來,並攜帶了解析出的tx的(轉帳)轉出方地址,屬於待處理的資料物件;
- EVM 作為Ethereum世界裡的虛擬機器(Virtual Machine),作為此次tx的實際執行者,完成轉帳和合約(Contract)的相關操作。
TransitioinDb()的執行過程(/core/state_transition.go)。假設有StateTransition物件st, 其成員變數initialGas表示初始可用Gas數量,gas表示即時可用Gas數量,初始值均為0,於是st.TransitionDb() 可由以下步驟展開:
- 購買Gas。首先從交易的(轉帳)轉出方賬戶扣除一筆Ether,費用等於tx.data.GasLimit * tx.data.Price;同時 st.initialGas = st.gas = tx.data.GasLimit;然後(GasPool) gp -= st.gas。
- 計算tx的固有Gas消耗 - intrinsicGas。它分為兩個部分,每一個tx預設的消耗量,這個消耗量還因tx是否含有(轉帳)轉入方地址而略有不同;以及針對tx.data.Payload的Gas消耗,Payload型別是[]byte,關於它的固有消耗依賴於[]byte中非0字節和0字節的長度。最終,st.gas -= intrinsicGas
- EVM執行。如果交易的(轉帳)轉入方地址(tx.data.Recipient)為空,呼叫EVM的Create()函式;否則,調用Call()函式。無論哪個函式返回後,更新st.gas。
- 計算本次執行交易的實際Gas消耗: requiredGas = st.initialGas - st.gas
- 償退Gas。它包括兩個部分:首先將剩餘st.gas 折算成Ether,歸還給交易的(轉帳)轉出方賬戶;然後,基於實際消耗量requiredGas,系統提供一定的補償,數量為refundGas。refundGas 所折算的Ether會被立即加在(轉帳)轉出方賬戶上,同時st.gas += refundGas,gp += st.gas,即剩餘的Gas加上系統補償的Gas,被一起歸並進GasPool,供之後的交易執行使用。
- 獎勵所屬區塊的挖掘者:系統給所屬區塊的作者,亦即挖掘者賬戶,增加一筆金額,數額等於 st.data,Price * (st.initialGas - st.gas)。注意,這裡的st.gas在步驟5中被加上了refundGas, 所以這筆獎勵金所對應的Gas,其數量小於該交易實際消耗量requiredGas。