摘要:下面來看看具體是怎么實現接口的可以看到,啟動了多個線程調用函數,當有線程挖到時,會通過傳入的通道傳出結果。可以看到在主要循環中,不斷遞增的值,調用函數計算上面公式中的左邊,而則是公式的右邊。
前言
挖礦(mine)是指礦工節點互相競爭生成新區塊以寫入整個區塊鏈獲得獎勵的過程.
共識(consensus)是指區塊鏈各個節點對下一個區塊的內容形成一致的過程
在以太坊中, miner包向外提供挖礦功能,consensus包對外提供共識引擎接口
miner包主要由miner.go worker.go agent.go 三個文件組成
Miner 負責與外部交互和高層次的挖礦控制
worker 負責低層次的挖礦控制 管理下屬所有Agent
Agent 負責實際的挖礦計算工作
三者之間的頂層聯系如下圖所示
Miner的定義如下
type Miner struct{ mux *event.TypeMux worker *worker coinbase common.Address eth Backend engine consensus.Engine .... }
各字段作用如下, 其中標有外的字段表示與Miner包外部有聯系
mux 外 接收來自downloader模塊的_StartEvent_ DoneEvent _FailedEvent_事件通知。在網絡中,不可能只有一個礦工節點,當downloader開始從其他節點同步Block時,我們就沒有必要再繼續挖礦了.
eth 外 通過該接口可查詢后臺TxPool BlockChain ethdb的數據.舉例來說,作為礦工,我們在生成一個新的Block時需要從TxPool中取出pending Tx(待打包成塊的交易),然后將它們中的一部分作為新的Block中的Transaction
engine 外 采用的共識引擎,目前以太坊公網采用的是ethash,測試網絡采用clique.
worker 對應的worker,從這里看出Miner和worker是一一對應的
coinbase 本礦工的賬戶地址,挖礦所得的收入將計入該賬戶
mining 標識是否正在挖礦
miner.New()創建一個Miner,它主要完成Miner字段的初始化和以下功能
使用miner.newWorker()創建一個worker
使用miner.newCpuAgent()創建Agent 并用Register方法注冊給worker
啟動miner.update() 線程.該線程等待mux上的來自 downloader模塊的事件通知用來控制挖礦開始或停止
workerworker成員比較多,其中部分成員的意義如下
mux engine eth coinbase 這幾項都來自與miner, 其中mux相對于Miner里的稍微有點不同, Miner里的mux是用來接收downloader的事件,而worker里用mux來向外部發布已經挖到新Block
txCh 外 從后臺eth接收新的Tx的Channel
chainHeadCh 外 從后臺eth接收新的Block的Channel
recv 從agents接收挖礦結果的Channel,注意,每個管理的agent都可能將挖出的Block發到該Channel,也就是說,這個收方向Channel是一對多的
agents 管理的所有Agent組成的集合
miner.newWorker() 創建一個worker,它除了完成各個成員字段的初始化,還做了以下工作
向后臺eth注冊txCh chainHeadCh chainSideCh通道用來接收對應數據
啟動worker.update() 線程.該線程等待上面幾個外部Channel 并作出相應處理
啟動worker.wait()線程.該線程等待Agent挖出的新Block
調用worker.commitNewWork() 嘗試啟動新的挖掘工作
AgentAgent(定義在worker.go)是一個抽象interface ,只要實現了其以下接口就可以充當worker的下屬agent
type Agent interface { Work() chan <-*Work SetReturnCh (chan<-*Result) Stop() Start() GetHashRate() int64 }
在agent.go中定義了CpuAgent作為一種Agent的實現,其主要成員定義如下
type CpuAgent struct { workCh chan *Work stop chan struct{} returnCh chan<-*Result chain consensus.ChainReader engine consensus.Engine }
workCh 接收來自worker下發的工作任務Work
returnCh 向worker反饋工作任務的完成情況,實際上就是挖出的新Block
stop 使該CpuAgent停止工作的信號
chain 用于訪問本地節點BlockChain數據的接口
engine 計算所采用的共識引擎
CpuAgent的創建函數中并沒有啟動新的線程, Agent的工作線程是由Agent.Start()接口啟動的
在CpuAgent實現中,啟動了CpuAgent.update()線程來監聽workCh和stop信道
func (self *CpuAgent) Start(){ if !atomic.CompareAndSwapInt32(&self.isMining, 0, 1){ return } go self.update() }
而Agent真正的挖礦工作是在收到工作任務"Work"后調用CpuAgent.mine()完成的
以上就是Miner worker Agent三者之間的聯系,將它們畫成一張圖如下:
總結以下就是
Miner監聽后臺的數據
需要挖礦時,worker發送給各個Agent工作任務Work, Agent挖出后反饋給worker
讓我們順著一次實際的挖掘工作看看一個Block是如何被挖掘出來的以及挖掘出之后的過程
從worker.commitNewWork()開始
1.parent Block是權威鏈上最新的Block
2.將標識礦工賬戶的Coinbase填入Header,這里生成的Header只是個半成品
3.對于ehtash來說,這里計算Block的Difficulty
4.工作任務Work 準確地說標識一次挖掘工作的上下文Context,在創建時,它包含了當前最新的各個賬戶信息state和2中生成的Header,在這個上下中可以通過調用work.commitTransactions()執行這些交易,這就是俗稱的打包過程
5.礦工總是選擇Price高的交易優先執行,因為這能使其獲得更高的收益率,所以對于交易的發起者來說,如果期望自己的交易能盡快被所有人承認,他可以設置更高gasPrice以吸引礦工優先打包這筆交易
6.運行EVM執行這些交易
7.調用共識引擎的Finalize()接口
8.如此,一個Block的大部分原料都已經準備好了,下一步就是發送給Agent來將這個Block挖掘出來
當Cpuagent收到Work后,調用mine()方法
func (self *CpuAgent) mine(work *Work, stop<-chan struct{}) { result, _ = self.engine.Seal(self.chain, work.Block, stop) self.returnCh <- &Result{work,result} }
可以看到實際上是調用的共識接口的Engine.Seal接口,挖掘的細節在后面共識部分詳述,這里先略過這部分且不考慮挖礦被Stop的情景,Block被挖掘出來之后將通過CpuAgent.returnCh反饋給worker,worker在wait線程收到接口后將結果寫入數據庫,通過worker.mux向外發布NewMinedBlockEvent事件,這樣以太坊的其他在該mux上訂閱了該事件組件就可以收到這個事件
共識共識部分包含由consensus對外提供共識引擎的接口定義,當前以太坊有兩個實現,分別是公網使用的基于POW的ethash包和測試網絡使用的基于POA的clique
根據前文的分析,在挖礦過程中主要涉及Prepare() Finalize() Seal() 接口,三者的職責分別為
Prepare() 初始化新Block的Header
Finalize() 在執行完交易后,對Block進行修改(比如向礦工發放挖礦所得)
Seal() 實際的挖礦工作
ethash是基于POW(Proof-of-Work),即工作量證明,礦工消耗算力來求得一個nonce,使其滿足難度要求HASH(Header) <= C / Diff,注意,這里的HASH是一個很復雜的函數,而nonce是Header的一個成員字段,一旦改變nonce,左邊的結果將發生很大的變化。 C是一個非常大的常數,Diff是Block的難度,可由此可知,Diff越大,右式越小,要想找到滿足不等式的nonce就越發的困難,而礦工正是消耗自己的算力去不斷嘗試nonce,如果找到就意味著他挖出這個區塊。
本文不打算詳述具體的HASH函數,感興趣的讀者可以參考官方文檔https://github.com/ethereum/w...
ethash的Prepare()計算新Block需要達到的難度(Diffculty),這部分理論可見https://www.jianshu.com/p/9e5...
ethash的Finalize()向礦工節點發放獎勵,再Byzantium時期之前的區塊,挖出的區塊獎勵是5 ETH
,之后的獎勵3 ETH,這部分理論比較復雜,準備以后專門寫一篇文章。
下面來看看ethash具體是怎么實現Seal接口的
core/ethash/sealer.go func (ethash *Ethash) Seal(chain consensus.ChainReader, block *types.Block, stop<-chan struct{})(*types.Block, error){ ...... abort := make(chan struct{}) found:= make(chan *types.Blocks) threads:= runtime.NumCPU() for i := 0; i < threads; i++ { go func(id int, nonce uint64){ ethash.mine(block,id,nonce,abort,found) }(i, uint64(ethash.rand.Int63())) } var result *type.Block select{ case <- stop: .... case result<-found: close(abort) } return result, nil }
可以看到,ethash啟動了多個線程調用mine()函數,當有線程挖到Block時,會通過傳入的found通道傳出結果。
core/ethash/sealer.go func (ethash *Ethash) mine(block *types.Block, id int, seed uint64, abort chan struct{}, found chan *types.Block) { ..... search: for { select { case <-abort: ...... default: digest, result := hashimotoFull(dataset.dataset, hash, nonce) if new(big.Int).SetBytes(result).Cmp(target) <= 0 { // Correct nonce found, create a new header with it header = types.CopyHeader(header) header.Nonce = types.EncodeNonce(nonce) // Seal and return a block (if still needed) select { case found <- block.WithSeal(header): ...... case <-abort: } break search } nonce++ } } ......
可以看到,在主要for循環中,不斷遞增nonce的值,調用hashimotoFull()函數計算上面公式中的左邊,而target則是公式的右邊。當找到一個nonce使得左式<=右式時,挖礦結束,nonce填到header.Nonce
clique以太網社區為開發者提供了基于POA(proof on Authortiy)的clique共識算法。與基于POS的ethash不同的是,clique挖礦不消耗礦工的算力。在clique中,節點分為兩類:
經過認證(Authorized)的節點,在源碼里稱為signer,具有生成(簽發)新區塊的能力,對應網絡里的礦工
未經過認證的節點,對應網絡里的普通節點
在ethash中,礦工的賬戶地址存放在Header的Coinbase字段,但在clique中,這個字段另有他用。那么如何知道一個Block的挖掘者呢?答案是,礦工用自己的私鑰對Block進行簽名(Signature),存放在Header的Extra字段,其他節點收到后,可以從這個字段提取出數字簽名以及簽發者(signer)的公鑰,使用這個公鑰可以計算出礦工(即signer)的賬戶地址。
一個節點a的認證狀態可以互相轉換,每個signer在簽發Block時,可以附帶一個提議(purposal),提議另一個本地記錄為非認證的節點b轉變為認證節點,或者相反。網絡中的其他節點c收到這個提議后,將其轉化為一張選票(Vote),如果支持節點的選票超過了節點c本地記錄的signer數量的一半,那么節點c就承認節點b是signer
clique包由api.go clique.go snapshot.go三個文件組成
其中api.go中是一些提供給用戶的命令行操作,比如用戶可以輸入以下命令表示他支持b成為signer
clique.propose("賬戶b的地址", true)
clique.go和snapshot.go中分別定義兩個重要的數據結構Clique和Snapshot
Clique數據結構的主要成員定義如下
type Clique struct { config *params.CliqueConfig recents *lru.ARCCache signatures *lrn.ARCCache proposals map[common.Address]bool signer common.Address signFn SignerFn ...... }
config 包含兩個配置參數,其中Period設置模擬產生新Block的時間間隔,而Epoch表示每隔一定數量的Block就要把當前的投票結果清空并存入數據庫,這么做是為了防止節點積壓過多的投票信息,類似于單機游戲中的存檔
recents 緩存最近訪問過的Snapshot,查詢的key為Block的Hash值,詳見之后的Snapshot
signatures 緩存最近訪問過的Block的signer,查詢的key為Block的Hash值
proposals 本節點待附帶的提議池,用戶通過propose()命名提交的提議會存放在這里,當本節點作為礦工對一個Block進行簽名時,會隨機選擇池中的一個提議附帶出去
signer 礦工節點的賬戶地址,意義上與ethash中的Coinbase類似
signFn 數字簽名函數,它和signer都由Clique.Authorize()進行設置,后者在eth/backend.go中的StartMining()中被調用
Snapshot翻譯過來是快照,它記錄了區塊鏈在特定的時刻(即特定的區塊高度)本地記錄的認證地址列表,舉個栗子,Block#18731的Snapshot記錄了網絡中存在3個signer分別為abc,且a已經支持另一個節點d成為signer(a投了d一張支持票),當Block#18732的挖掘者b也支持d時,Block#18732記錄的signer就會增加d的地址
type Snapshot struct{ sigcache *lru.ARCCache Number uint64 Hash Common.Hash Signers map[Common.Address] struct{} Recents map[uint64]common.Address Votes []*Vote Tally map[common.Address]Tally }
sigcache 緩存最近訪問過的signer,key為Block的Hash值
Number 本Snapshot對應的Block的高度,在創建時確定
Hash 本Snapshot對應的Block的Hash,在創建時確定
Signers 本Snapshot對應時刻網絡中認證過的節點地址(礦工),在創建時確定
Recents 最近若干個Block的signer的集合,即挖出區塊的礦工
Votes 由收到的有效proposal計入的選票集合,每張選票記錄了投票人/被投票人/投票意見 這里的有效有兩層意思
投票人是有效的的,首先他是signer(在Snapshot.Signers中),并且他不能頻繁投票(不在 Snapshot.Recents中)
被投票人是有效的,被投票人的當前認證狀態與選票中攜帶的意見不同
Tally 投票結果map,key為被投票人地址,value為投票計數
Prepare()的實現分為兩部分
func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header){ header.Coinbase = common.Address{} header.Nonce = types.BlockNonce{} number := header.Number.Uint64() snap, err := c.snapshot(chain, num-1, header.ParentHash, nil) if number % c.config.Epoch { addresses := make ([]common.Address) for address, authorize := range c.proposals{ addresses = append(addresses, address) } header.Coinbase = addresses[rand.Intn(len(addresses))] if c.proposals[header.Coinbase] { copy(header.Nonce[:], nonceAuthVote) } else { copy(header.Nonce[:], nonceDropVote) } } ......
首先獲取上一個Block的Snapshot,它有以下幾個獲取途徑
Clique的緩存
如果Block的高度恰好是在checkpoint 就可從數據庫中讀取
由一個之前已有的Snapshot經過這之間的所有Header推算出來
接下來隨機地將本地proposal池中的一個目標節點地址放到Coinbase (注意在ethash中,這個字段填寫的是礦工地址) 由于Clique不需要消耗算力,也就不需要計算nonce,因此在Clique中,Header的Nonce的字段被用來表示對目標節點投票的意見
func (c *Clique) Prepare(chain consensus.ChainReader, header *types.Header){ ...... header.Difficulty = CalcDifficulty(snap, c.signer) header.Extra = append(header.Extra, make([]byte, extraSeal)) ......
接下來填充Header中的Difficulty字段,在Clique中這個字段只有 1 和 2 兩個取值,取決與本節點是否inturn,這完全是測試網絡為了減少Block區塊生成沖突的一個技巧,因為測試網絡不存在真正的計算,那么如何確定下一個Block由誰確定呢?既然都一樣,那就輪流坐莊,inturn的意思就是自己的回合,我們知道,區塊鏈在生成中很容易出現短暫的分叉(fork),其中難度最大的鏈為權威(canonocal)鏈,因此如果一個節點inturn,它就把難度設置為 2 ,否則設置為 1
前面提到過在Clique中,礦工的地址不是存放在Coinbase,而是將自己對區塊的數字簽名存放在Header的Extra字段,可以看到在Prepare()接口中為數字簽名預留了Extra的后 65 bytes
clique的Finalize()操作比較簡單,就是計算了一下Header的Root Hash值
Seal()接口相對ethash的實現來說比較簡單 (省略了一些檢查)
func (c *Clique) Seal (chain consensus.ChainReader, block *type.Block, stop <-chan struct{}) (*types.Block, error) { header := block.Header() signer, signFn := c.signer, c.signFn snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) ...... select { case <- stop: return nil, nil case <-time.After(delay): } sighash, err := signFn(accounts.Account{Address:signer}, sigHash(header).Bytes()) copy(header.Extra[len(header.Extra) - extraSeal:], sighash) return block.WithSeal(header), nil }
總的來說就是延遲了一定時間后對Block進行簽名,然后將自己的簽名存入header的Extra字段的后 65 bytes,為了減少沖突,對于不是inturn的節點還會多延時一會兒,上面的代碼我省略了這部分
總結挖礦的框架由miner包提供,期間使用了consensus包完成新的Block中一些字段的填充,總的來說挖礦分為打包交易和挖掘兩個階段
以太坊目前實現了ethash和clique兩套共識接口實現,分別用于公網環境和測試網絡環境,前者消耗算力,后者不消耗。并且,他們對于Header中的字段的一些意義也不盡相同。!
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/24383.html
摘要:在中,該隨機數稱為,它需要滿足一個公式其中,去除區塊頭中生成的哈希值,見。固定值,生成的哈希值的最大取值。哈希值滿足條件的概率是,礦工需要進行次的判斷,才有可能找到一個符合條件的,當前以太坊難度為。 前言 Ethash實現了PoW,PoW的精妙在于通過一個隨機數確定,礦工確實做了大量的工作,并且是沒有辦法作弊的。接下來將介紹: Ethash的挖礦本質。 Ethash是如何挖礦的。 如...
摘要:前言是以太坊封定義的一個接口,它的功能可以分為類驗證區塊類,主要用在將區塊加入到區塊鏈前,對區塊進行共識驗證。輔助類生成以太坊共識相關的。被使用,是以太坊狀態管理服務,當報告數據的時候,需要獲取區塊的信息。 前言 engine是以太坊封定義的一個接口,它的功能可以分為3類: 驗證區塊類,主要用在將區塊加入到區塊鏈前,對區塊進行共識驗證。 產生區塊類,主要用在挖礦時。 輔助類。 接下...
摘要:接下來我們將從以下角度介紹礦工角色。我們分別使用礦長副礦長礦工進行類比。副礦長,負責具體挖礦工作的安排,把挖礦任務安排給。礦工的主要函數介紹和的主要函數,他們是礦工的具體運作機制。負責處理外部事件。 前言 礦工在PoW中負責了產生區塊的工作,把一大堆交易交給它,它生成一個證明自己做了很多工作的區塊,然后將這個區塊加入到本地區塊鏈并且廣播給其他節點。 接下來我們將從以下角度介紹礦工: ...
摘要:以太坊中除了基于運算能力的外,還有基于權利證明的共識機制,是以太坊的共識算法的實現,這里主要對的相關源碼做一個解讀分析。檢查包頭中包含的簽名是否滿足共識協議 以太坊中除了基于運算能力的POW(Ethash)外,還有基于權利證明的POA共識機制,Clique是以太坊的POA共識算法的實現,這里主要對POA的Clique相關源碼做一個解讀分析。 Clique的初始化在 Ethereum.S...
摘要:當前和一樣,采用基于工作量證明的共識算法來產生新的區塊。源碼解析生成通過方法生成,首先是生成,再從生成挖礦在挖礦與共識中提到了,共識算法通過實現接口,來實現挖礦算法也不例外。 Ethereum當前和Bitcoin一樣,采用基于工作量證明(Proof of Work,PoW)的共識算法來產生新的區塊。與Bitcoin不同的是,Ethereum采用的共識算法可以抵御ASIC礦機對挖礦工作的...
閱讀 2014·2021-11-15 11:38
閱讀 2048·2019-08-30 15:55
閱讀 2182·2019-08-30 15:52
閱讀 3167·2019-08-30 14:01
閱讀 2684·2019-08-30 12:47
閱讀 1128·2019-08-29 13:17
閱讀 1062·2019-08-26 13:55
閱讀 2629·2019-08-26 13:46