摘要:以太坊中除了基于運算能力的外,還有基于權(quán)利證明的共識機制,是以太坊的共識算法的實現(xiàn),這里主要對的相關(guān)源碼做一個解讀分析。檢查包頭中包含的簽名是否滿足共識協(xié)議
以太坊中除了基于運算能力的POW(Ethash)外,還有基于權(quán)利證明的POA共識機制,Clique是以太坊的POA共識算法的實現(xiàn),這里主要對POA的Clique相關(guān)源碼做一個解讀分析。
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置為clique.Clique, 根據(jù)當(dāng)前節(jié)點的礦工地址(默認是acounts[0]), 配置clique的 簽名者 : clique.Authorize(eb, wallet.SignHash) ,其中簽名函數(shù)是SignHash,對給定的hash進行簽名。
func (s *Ethereum) StartMining(local bool) error { eb, err := s.Etherbase()//用戶地址 if err != nil { log.Error("Cannot start mining without etherbase", "err", err) return fmt.Errorf("etherbase missing: %v", err) } if clique, ok := s.engine.(*clique.Clique); ok { //如果是clique共識算法 wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) // 根據(jù)用它胡地址獲取wallet對象 if wallet == nil || err != nil { log.Error("Etherbase account unavailable locally", "err", err) return fmt.Errorf("signer missing: %v", err) } clique.Authorize(eb, wallet.SignHash) // 注入簽名者以及wallet對象獲取簽名方法 } if local { // 如果本地CPU已開始挖礦,我們可以禁用引入的交易拒絕機制來加速同步時間。CPU挖礦在主網(wǎng)是荒誕的,所以沒有人能碰到這個路徑,然而一旦CPU挖礦同步標(biāo)志完成以后,將保證私網(wǎng)工作也在一個獨立礦工結(jié)點。 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1) } go s.miner.Start(eb) return nil }
這個StartMining會在miner.start前調(diào)用,然后通過woker -> agent -> CPUAgent -> update -> seal 挖掘區(qū)塊和組裝(后面會寫多帶帶的文章來對挖礦過程做源碼分析)。
Clique的代碼塊在go-ethereum/consensus/clique路徑下。和ethash一樣,在clique.go 中實現(xiàn)了consensus的接口, consensus 定義了下面這些接口:
type Engine interface { Author(header *types.Header) (common.Address, error) VerifyHeader(chain ChainReader, header *types.Header, seal bool) error VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) VerifyUncles(chain ChainReader, block *types.Block) error VerifySeal(chain ChainReader, header *types.Header) error Prepare(chain ChainReader, header *types.Header) error Finalize(chain ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) Seal(chain ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) CalcDifficulty(chain ChainReader, time uint64, parent *types.Header) *big.Int APIs(chain ChainReader) []rpc.API }
Engine.Seal()函數(shù)可對一個調(diào)用過 Finalize()的區(qū)塊進行授權(quán)或封印,成功時返回的區(qū)塊全部成員齊整,可視為一個正常區(qū)塊,可被廣播到整個網(wǎng)絡(luò)中,也可以被插入?yún)^(qū)塊鏈等。對于挖掘一個新區(qū)塊來說,所有相關(guān)代碼里 Engine.Seal()是其中最重要最復(fù)雜的一步,所以這里我們首先來看下Clique 結(jié)構(gòu)體:
type Clique struct { config *params.CliqueConfig // 共識引擎配置參數(shù) db ethdb.Database // 數(shù)據(jù)庫,用來存儲和獲取快照檢查點 recents *lru.ARCCache // 最近區(qū)塊快照,加速快照重組 signatures *lru.ARCCache // 最近區(qū)塊簽名,加速挖礦 proposals map[common.Address]bool // 目前正在推送的提案 signer common.Address // 簽名者的以太坊地址 signFn SignerFn // 授權(quán)哈希的簽名方法 lock sync.RWMutex // 用鎖來保護簽名字段 }
順便來看下CliqueConfig共識引擎的配置參數(shù)結(jié)構(gòu)體:
type CliqueConfig struct { Period uint64 `json:"period"` // 在區(qū)塊之間執(zhí)行的秒數(shù)(比如出塊秒數(shù)15s) Epoch uint64 `json:"epoch"` // Epoch長度,重置投票和檢查點(比如Epoch長度是30000個block, 每次進入新的epoch,前面的投票都被清空, 重新開始記錄) }
在上面的 StartMining中,通過Clique. Authorize來注入簽名者和簽名方法,先來看下Authorize:
func (c *Clique) Authorize(signer common.Address, signFn SignerFn) { c.lock.Lock() defer c.lock.Unlock() // 這個方法就是為clique共識注入一個簽名者的私鑰地址已經(jīng)簽名函數(shù)用來挖出新塊 c.signer = signer c.signFn = signFn }
再來看Clique的Seal()函數(shù)的具體實現(xiàn):
//通過本地簽名認證創(chuàng)建已密封的區(qū)塊 func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { header := block.Header() // 不密封創(chuàng)世塊 number := header.Number.Uint64() if number == 0 { return nil, errUnknownBlock } // 不支持0-period的鏈,不支持空塊密封,沒有獎勵但是能夠密封 if c.config.Period == 0 && len(block.Transactions()) == 0 { return nil, errWaitTransactions } // 在整個密封區(qū)塊的過程中不要持有signer簽名者字段 c.lock.RLock() signer, signFn := c.signer, c.signFn //獲取簽名者和簽名方法 c.lock.RUnlock() snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) //調(diào)用獲取快照 if err != nil { return nil, err } //檢查我們是否被授權(quán)去簽名一個區(qū)塊 if _, authorized := snap.Signers[signer]; !authorized { return nil, errUnauthorized } // 如果我們是在‘最近簽名者’中則等待下一個區(qū)塊 for seen, recent := range snap.Recents { if recent == signer { // 當(dāng)前簽名者在‘最近簽名者’中,如果當(dāng)前區(qū)塊沒有剔除他的話只能等待(這里涉及到機會均等) if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit { log.Info("Signed recently, must wait for others") <-stop return nil, nil } } } // 好了,走到這說明協(xié)議已經(jīng)允許我們來簽名這個區(qū)塊,等待我們的時間 delay := time.Unix(header.Time.Int64(), 0).Sub(time.Now()) // nolint: gosimple if header.Difficulty.Cmp(diffNoTurn) == 0 { // 這不是我們的輪次來簽名,延遲一點,隨機延遲,這樣對于每一個簽簽名者來說來允許并發(fā)簽名 wiggle := time.Duration(len(snap.Signers)/2+1) * wiggleTime delay += time.Duration(rand.Int63n(int64(wiggle))) log.Trace("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle)) } log.Trace("Waiting for slot to sign and propagate", "delay", common.PrettyDuration(delay)) select { case <-stop: return nil, nil case <-time.After(delay): } // 通過signFn簽名函數(shù)開始簽名 sighash, err := signFn(accounts.Account{Address: signer}, sigHash(header).Bytes()) if err != nil { return nil, err } //將簽名結(jié)果替換保存在區(qū)塊頭的Extra字段中 copy(header.Extra[len(header.Extra)-extraSeal:], sighash) //通過區(qū)塊頭重新組裝生成一個區(qū)塊 return block.WithSeal(header), nil }
Seal是共識引擎的入口之一,該函數(shù)通過clique.signer對區(qū)塊簽名
signer不在snapshot的signer中不允許簽名
signer不是本區(qū)塊的簽名者需要延時隨機一段時候后再簽名,是本區(qū)塊的簽名者則直接簽名
簽名存放在Extra的extraSeal的65個字節(jié)中
關(guān)于機會均等
為了使得出塊的負載(或者說是機會)對于每個認證節(jié)點盡量均等,同時避免某些惡意節(jié)點持續(xù)出塊,clique中規(guī)定每一個認證節(jié)點在連續(xù)SIGNER_LIMIT個區(qū)塊中,最多只能簽發(fā)一個區(qū)塊,也就是說,每一輪中,最多只有SIGNER_COUNT - SIGNER_LIMIT個認證節(jié)點可以參與區(qū)塊簽發(fā)。
其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示認證節(jié)點的個數(shù)。
//snap.Signers是所有的認證節(jié)點 for seen, recent := range snap.Recents { if recent == signer { if limit := uint64(len(snap.Signers)/2 + 1); number < limit || seen > number-limit { log.Info("Signed recently, must wait for others") <-stop return nil, nil } } }
在保證好節(jié)點的個數(shù)大于壞節(jié)點的前提下,好節(jié)點最少的個數(shù)為SIGNER_LIMIT(大于50%),壞節(jié)點最多的個數(shù)為SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一個節(jié)點在SIGNER_LIMIT這個時間窗口內(nèi)最多只能簽發(fā)一個區(qū)塊,這就使得惡意節(jié)點在不超過50%的情況下,從理論上無法一直掌握區(qū)塊的簽發(fā)權(quán)。
關(guān)于難度計算
為了讓每個認證節(jié)點都有均等的機會去簽發(fā)一個區(qū)塊,每個節(jié)點在簽發(fā)時都會判斷本節(jié)點是不是本輪的inturn節(jié)點,若是inturn節(jié)點,則該節(jié)點產(chǎn)生的區(qū)塊難度為2,否則為1。每一輪僅有一個節(jié)點為inturn節(jié)點。
diffInTurn = big.NewInt(2) diffNoTurn = big.NewInt(1)
當(dāng)inturn的結(jié)點離線時,其他結(jié)點會來競爭,難度值降為1。然而正常出塊時,limit中的所有認證結(jié)點包括一個inturn和其他noturn的結(jié)點,clique是采用了給noturn加延遲時間的方式來支持inturn首先出塊,避免noturn的結(jié)點無謂生成區(qū)塊,上面的延時代碼段已經(jīng)有提現(xiàn)了。
判斷是否為inturn的節(jié)點,將本地維護的認證節(jié)點按照字典序排序,若當(dāng)前區(qū)塊號除以認證節(jié)點個數(shù)的余數(shù)等于該節(jié)點的下標(biāo),則該節(jié)點為inturn節(jié)點。代碼實現(xiàn)在 snapshot.go中:
// 通過給定的區(qū)塊高度和簽發(fā)者返回該簽發(fā)者是否在輪次內(nèi) func (s *Snapshot) inturn(number uint64, signer common.Address) bool { signers, offset := s.signers(), 0 for offset < len(signers) && signers[offset] != signer { offset++ } return (number % uint64(len(signers))) == uint64(offset) }
Seal()代碼中有獲取快照,然后從快照中來檢查授權(quán)區(qū)塊簽名者的邏輯,那么我們繼續(xù)來看下Snapshot,首先看下Snapshot的結(jié)構(gòu)體:
// Snapshot對象是在給定時間點的一個認證投票的狀態(tài) type Snapshot struct { config *params.CliqueConfig // 共識引擎配置參數(shù) sigcache *lru.ARCCache // 簽名緩存,最近的區(qū)塊簽名加速恢復(fù)。 Number uint64 `json:"number"` // 快照建立的區(qū)塊號 Hash common.Hash `json:"hash"` // 快照建立的區(qū)塊哈希 Signers map[common.Address]struct{} `json:"signers"` // 當(dāng)下認證簽名者的列表 Recents map[uint64]common.Address `json:"recents"` // 最近擔(dān)當(dāng)過數(shù)字簽名算法的signer 的地址 Votes []*Vote `json:"votes"` // 按時間順序排列的投票名單。 Tally map[common.Address]Tally `json:"tally"` // 當(dāng)前的投票結(jié)果,避免重新計算。 }
快照Snapshot對象中存在投票的Votes和記票的Tally對象:
// Vote代表了一個獨立的投票,這個投票可以授權(quán)一個簽名者,更改授權(quán)列表。 type Vote struct { Signer common.Address `json:"signer"` // 已授權(quán)的簽名者(通過投票) Block uint64 `json:"block"` // 投票區(qū)塊號 Address common.Address `json:"address"` // 被投票的賬戶,修改它的授權(quán) Authorize bool `json:"authorize"` // 對一個被投票賬戶是否授權(quán)或解授權(quán) } // Tally是一個簡單的用來保存當(dāng)前投票分?jǐn)?shù)的計分器 type Tally struct { Authorize bool `json:"authorize"` // 授權(quán)true或移除false Votes int `json:"votes"` // 該提案已獲票數(shù) }
Snapshot是一個快照,不僅是一個緩存,而且存儲了最近簽名者的map
loadSnapshot用來從數(shù)據(jù)庫中加載一個已存在的快照:
func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) { //使用Database接口的Get方法通過Key來查詢緩存內(nèi)容 blob, err := db.Get(append([]byte("clique-"), hash[:]...)) if err != nil { return nil, err } snap := new(Snapshot) if err := json.Unmarshal(blob, snap); err != nil { return nil, err } snap.config = config snap.sigcache = sigcache return snap, nil }
newSnapshot函數(shù)用于創(chuàng)建快照,這個方法沒有初始化最近的簽名者集合,所以只使用創(chuàng)世塊:
func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot { //組裝一個Snapshot對象 snap := &Snapshot{ config: config, sigcache: sigcache, Number: number, Hash: hash, Signers: make(map[common.Address]struct{}), Recents: make(map[uint64]common.Address), Tally: make(map[common.Address]Tally), } for _, signer := range signers { snap.Signers[signer] = struct{}{} } return snap }
繼續(xù)看下snapshot函數(shù)的具體實現(xiàn):
// 快照會在給定的時間點檢索授權(quán)快照 func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) { // 在內(nèi)存或者磁盤上查找一個快照來檢查檢查點checkpoints var ( headers []*types.Header //區(qū)塊頭 snap *Snapshot //快照對象 ) for snap == nil { // 如果在內(nèi)存中找到快照時,快照對象從內(nèi)存中取 if s, ok := c.recents.Get(hash); ok { snap = s.(*Snapshot) break } // 如果在磁盤檢查點找到快照時 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到數(shù)據(jù)庫的區(qū)塊的區(qū)塊號 if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil { log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash) snap = s break } } // 如果在創(chuàng)世塊,則新建一個快照 if number == 0 { genesis := chain.GetHeaderByNumber(0) if err := c.VerifyHeader(chain, genesis, false); err != nil { return nil, err } signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength) for i := 0; i < len(signers); i++ { copy(signers[i][:], genesis.Extra[extraVanity+i*common.AddressLength:]) } snap = newSnapshot(c.config, c.signatures, 0, genesis.Hash(), signers) if err := snap.store(c.db); err != nil { return nil, err } log.Trace("Stored genesis voting snapshot to disk") break } // 沒有對于這個區(qū)塊頭的快照,收集區(qū)塊頭并向后移 var header *types.Header if len(parents) > 0 { // 如果我們有明確的父,從那里挑選(強制執(zhí)行) header = parents[len(parents)-1] if header.Hash() != hash || header.Number.Uint64() != number { return nil, consensus.ErrUnknownAncestor } parents = parents[:len(parents)-1] } else { // 沒有明確的父(或者沒有更多的父)轉(zhuǎn)到數(shù)據(jù)庫獲取 header = chain.GetHeader(hash, number) if header == nil { return nil, consensus.ErrUnknownAncestor } } headers = append(headers, header) number, hash = number-1, header.ParentHash } // 找到了之前的快照,將所有的pedding塊頭放在它上面 for i := 0; i < len(headers)/2; i++ { headers[i], headers[len(headers)-1-i] = headers[len(headers)-1-i], headers[i] } snap, err := snap.apply(headers) //通過區(qū)塊頭生成一個新的快照 if err != nil { return nil, err } c.recents.Add(snap.Hash, snap) //將當(dāng)前區(qū)塊的區(qū)塊hash保存到最近區(qū)塊快照,加速快照重組 // 如果我們已經(jīng)生成一個新的檢查點快照,保存在磁盤上 if snap.Number%checkpointInterval == 0 && len(headers) > 0 { if err = snap.store(c.db); err != nil { return nil, err } log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash) } return snap, err }
在snapshot中,snap.apply通過區(qū)塊頭來創(chuàng)建一個新的快照,這個apply中主要做什么操作?
//apply將給定的區(qū)塊頭應(yīng)用于原始頭來創(chuàng)建新的授權(quán)快照。 func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { //可以傳空區(qū)塊頭 if len(headers) == 0 { return s, nil } //完整性檢查區(qū)塊頭可用性 for i := 0; i < len(headers)-1; i++ { if headers[i+1].Number.Uint64() != headers[i].Number.Uint64()+1 { return nil, errInvalidVotingChain } } if headers[0].Number.Uint64() != s.Number+1 { return nil, errInvalidVotingChain } //迭代區(qū)塊頭,創(chuàng)建一個新的快照 snap := s.copy() // 投票的處理核心代碼 for _, header := range headers { // 刪除檢查點區(qū)塊的所有投票 number := header.Number.Uint64() // 如果區(qū)塊高度正好在Epoch結(jié)束,則清空投票和計分器,避免了維護統(tǒng)計信息無限增大的內(nèi)存開銷; if number%s.config.Epoch == 0 { snap.Votes = nil snap.Tally = make(map[common.Address]Tally) } //從最近的簽名者列表中刪除最舊的簽名者以允許它再次簽名 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { delete(snap.Recents, number-limit) } // 從區(qū)塊頭中解密出來簽名者地址 signer, err := ecrecover(header, s.sigcache) if err != nil { return nil, err } if _, ok := snap.Signers[signer]; !ok { return nil, errUnauthorized } for _, recent := range snap.Recents { if recent == signer { return nil, errUnauthorized } } snap.Recents[number] = signer // 區(qū)塊頭認證,不管該簽名者之前的任何投票 for i, vote := range snap.Votes { if vote.Signer == signer && vote.Address == header.Coinbase { // 從緩存計數(shù)器中移除該投票 snap.uncast(vote.Address, vote.Authorize) // 從按時間排序的列表中移除投票 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) break // 只允許一票 } } // 從簽名者中計數(shù)新的投票 var authorize bool switch { case bytes.Equal(header.Nonce[:], nonceAuthVote): authorize = true case bytes.Equal(header.Nonce[:], nonceDropVote): authorize = false default: return nil, errInvalidVote } if snap.cast(header.Coinbase, authorize) { snap.Votes = append(snap.Votes, &Vote{ Signer: signer, Block: number, Address: header.Coinbase, Authorize: authorize, }) } // 判斷票數(shù)是否超過一半的投票者,如果投票通過,更新簽名者列表 if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 { if tally.Authorize { snap.Signers[header.Coinbase] = struct{}{} } else { delete(snap.Signers, header.Coinbase) // 簽名者列表縮減,刪除最近剩余的緩存 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { delete(snap.Recents, number-limit) } for i := 0; i < len(snap.Votes); i++ { if snap.Votes[i].Signer == header.Coinbase { // 從緩存計數(shù)器中移除該投票 snap.uncast(snap.Votes[i].Address, snap.Votes[i].Authorize) // 從按時間排序的列表中移除投票 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) i-- } } } // 不管之前的任何投票,直接改變賬戶 for i := 0; i < len(snap.Votes); i++ { if snap.Votes[i].Address == header.Coinbase { snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) i-- } } delete(snap.Tally, header.Coinbase) } } snap.Number += uint64(len(headers)) snap.Hash = headers[len(headers)-1].Hash() return snap, nil }
Snapshot.apply()方法的主要部分是迭代處理每個header對象,首先從數(shù)字簽名中恢復(fù)出簽名所用公鑰,轉(zhuǎn)化為common.Address類型,作為signer地址。數(shù)字簽名(signagure)長度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未認證的,則直接退出本次迭代;如果是已認證的,則投票+1。所以一個父區(qū)塊可添加一張記名投票,signer作為投票方地址,Header.Coinbase作為被投票地址,投票內(nèi)容authorized可由Header.Nonce取值確定。更新投票統(tǒng)計信息。如果被投票地址的總投票次數(shù)達到已認證地址個數(shù)的一半,則通過之。該被投票地址的認證狀態(tài)立即被更改,根據(jù)是何種更改,相應(yīng)的更新緩存數(shù)據(jù),并刪除過時的投票信息。在所有Header對象都被處理完后,Snapshot內(nèi)部的Number,Hash值會被更新,表明當(dāng)前Snapshot快照結(jié)構(gòu)已經(jīng)更新到哪個區(qū)塊了。
區(qū)塊驗證的過程是普通節(jié)點在收到一個新區(qū)塊時,會從區(qū)塊頭的extraData字段中取出認證節(jié)點的簽名,利用標(biāo)準(zhǔn)的spec256k1橢圓曲線進行反解公鑰信息,并且從公鑰中截取出簽發(fā)節(jié)點的地址,若該節(jié)點是認證節(jié)點,且該節(jié)點本輪擁有簽名的權(quán)限,則認為該區(qū)塊為合法區(qū)塊。verifySeal是被SubmitWork(miner/remote_agent.go) 來調(diào)用,SubmitWork函數(shù)嘗試注入一個pow解決方案(共識引擎)到遠程代理,返回這個解決方案是否被接受。(不能同時是一個壞的pow也不能有其他任何錯誤,例如沒有工作被pending)解決方案有效時,返回到礦工并且通知接受結(jié)果。
// 檢查包頭中包含的簽名是否滿足共識協(xié)議要求。該方法接受一個可選的父頭的列表,這些父頭還不是本地區(qū)塊鏈的一部分,用于生成快照 func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error { // 不支持校檢創(chuàng)世塊 number := header.Number.Uint64() if number == 0 { return errUnknownBlock } // 檢索出所需的區(qū)塊對象來校檢去開頭和將其緩存 snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) if err != nil { return err } //解析授權(quán)密鑰并檢查簽署者,ecrecover方法從區(qū)塊頭中反解出Extra字段中簽名字符串來獲取簽名者地址 signer, err := ecrecover(header, c.signatures) if err != nil { return err } if _, ok := snap.Signers[signer]; !ok { return errUnauthorized } for seen, recent := range snap.Recents { if recent == signer { // 簽署者是最近的,只有當(dāng)前塊沒有移出時才會失敗,參見seal中的機會均等 if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit { return errUnauthorized } } } // 設(shè)置區(qū)塊難度,參見上面的區(qū)塊難度部分 inturn := snap.inturn(header.Number.Uint64(), signer) if inturn && header.Difficulty.Cmp(diffInTurn) != 0 { return errInvalidDifficulty } if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 { return errInvalidDifficulty } return nil }
前面已經(jīng)分析了Clique的認證節(jié)點的出塊和校檢的過程,那么如何來區(qū)分一個節(jié)點是認證節(jié)點還是一個普通節(jié)點?以及一個授權(quán)者列表是如何產(chǎn)生并如何全網(wǎng)同步的?
Clique通過投票機制來確認一個認證節(jié)點,投票的范圍在委員會中,委員會就是所有節(jié)點礦工集合,普通節(jié)點沒有區(qū)塊生成權(quán)利。礦工的投票流程如下:
委員會節(jié)點通過RPC調(diào)用Propose,對某節(jié)點狀態(tài)變更,從普通節(jié)點變成認證階段,或者相反,寫入到Clique.purposal集合中
// Propose注入一個新的授權(quán)提案,可以授權(quán)一個簽名者或者移除一個。 func (api *API) Propose(address common.Address, auth bool) { api.clique.lock.Lock() defer api.clique.lock.Unlock() api.clique.proposals[address] = auth// true:授權(quán),false:移除 }
本地認證節(jié)點在一次區(qū)塊打包的過程中,從purposal池中隨機挑選一條還未被應(yīng)用的purposal,并將信息填入?yún)^(qū)塊頭,將區(qū)塊廣播給其他節(jié)點;
//Clique.Prepare // 抓取所有有意義投票的提案 addresses := make([]common.Address, 0, len(c.proposals)) for address, authorize := range c.proposals { if snap.validVote(address, authorize) { addresses = append(addresses, address) } } // If there"s pending proposals, cast a vote on them if len(addresses) > 0 { header.Coinbase = addresses[rand.Intn(len(addresses))] //隨機挑選一條投票節(jié)點的地址賦值給區(qū)塊頭的Coinbase字段。 // 通過提案內(nèi)容來組裝區(qū)塊頭的隨機數(shù)字段。 if c.proposals[header.Coinbase] { copy(header.Nonce[:], nonceAuthVote) } else { copy(header.Nonce[:], nonceDropVote) } }
在挖礦開始以后,會在miner.start()中提交一個commitNewWork,其中調(diào)用上面Prepare
if err := self.engine.Prepare(self.chain, header); err != nil { log.Error("Failed to prepare header for mining", "err", err) return }
其他節(jié)點在接收到區(qū)塊后,取出其中的信息,封裝成一個vote進行存儲,并將投票結(jié)果應(yīng)用到本地,若關(guān)于目標(biāo)節(jié)點的狀態(tài)更改獲得的一致投票超過1/2,則更改目標(biāo)節(jié)點的狀態(tài):若為新增認證節(jié)點,將目標(biāo)節(jié)點的地址添加到本地的認證節(jié)點的列表中;若為刪除認證節(jié)點,將目標(biāo)節(jié)點的地址從本地的認證節(jié)點列表中刪除。具體實現(xiàn)可以查看上面的Snapshot.apply()方法
轉(zhuǎn)載請注明: 轉(zhuǎn)載自Ryan是菜鳥 | LNMP技術(shù)棧筆記
如果覺得本篇文章對您十分有益,何不 打賞一下
本文鏈接地址: 以太坊POA共識機制Clique源碼分析
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/24032.html
摘要:前言是以太坊封定義的一個接口,它的功能可以分為類驗證區(qū)塊類,主要用在將區(qū)塊加入到區(qū)塊鏈前,對區(qū)塊進行共識驗證。輔助類生成以太坊共識相關(guān)的。被使用,是以太坊狀態(tài)管理服務(wù),當(dāng)報告數(shù)據(jù)的時候,需要獲取區(qū)塊的信息。 前言 engine是以太坊封定義的一個接口,它的功能可以分為3類: 驗證區(qū)塊類,主要用在將區(qū)塊加入到區(qū)塊鏈前,對區(qū)塊進行共識驗證。 產(chǎn)生區(qū)塊類,主要用在挖礦時。 輔助類。 接下...
摘要:本文首發(fā)于深入淺出區(qū)塊鏈社區(qū)原文鏈接以太坊創(chuàng)世區(qū)塊與鏈配置載入分析,原文已更新,請讀者前往原文閱讀。以太坊允許通過創(chuàng)世配置文件來初始化創(chuàng)世區(qū)塊,也可使用選擇使用內(nèi)置的多個網(wǎng)絡(luò)環(huán)境的創(chuàng)世配置。再準(zhǔn)備兩個以太坊賬戶,以便在創(chuàng)世時存入資產(chǎn)。 本文首發(fā)于深入淺出區(qū)塊鏈社區(qū)原文鏈接:以太坊創(chuàng)世區(qū)塊與鏈配置載入分析,原文已更新,請讀者前往原文閱讀。 創(chuàng)世區(qū)塊作為第零個區(qū)塊,其他區(qū)塊直接或間接引用到...
摘要:下面來看看具體是怎么實現(xiàn)接口的可以看到,啟動了多個線程調(diào)用函數(shù),當(dāng)有線程挖到時,會通過傳入的通道傳出結(jié)果。可以看到在主要循環(huán)中,不斷遞增的值,調(diào)用函數(shù)計算上面公式中的左邊,而則是公式的右邊。 前言 挖礦(mine)是指礦工節(jié)點互相競爭生成新區(qū)塊以寫入整個區(qū)塊鏈獲得獎勵的過程.共識(consensus)是指區(qū)塊鏈各個節(jié)點對下一個區(qū)塊的內(nèi)容形成一致的過程在以太坊中, miner包向外提供挖...
摘要:月日上午點,共享云路由器在香港正式開售,售價港幣,用戶可使用香港本地手機號碼在香港電視注冊后進行選購。目前云鉆已上線競拍和區(qū)塊貓等落地應(yīng)用,已有用戶使用云鉆成功競拍獲得。6月22日上午10點,360共享云路由器在香港正式開售,售價699港幣,用戶可使用香港本地手機號碼在香港電視 HKTVmall 注冊后進行選購。消息一經(jīng)公開,便吸引了眾多關(guān)注。作為360的首款區(qū)塊鏈硬件產(chǎn)品,360共享云路由...
摘要:本文首發(fā)于深入淺出區(qū)塊鏈社區(qū)原文鏈接以太坊客戶端命令用法參數(shù)詳解原文已更新,請讀者前往原文閱讀在以太坊智能合約開發(fā)中最常用的工具必備開發(fā)工具,一個多用途的命令行工具。如果你還不知道是什么,請先閱讀入門篇以太坊是什么。 本文首發(fā)于深入淺出區(qū)塊鏈社區(qū)原文鏈接:以太坊客戶端Geth命令用法-參數(shù)詳解原文已更新,請讀者前往原文閱讀 Geth在以太坊智能合約開發(fā)中最常用的工具(必備開發(fā)工具),一...
閱讀 3560·2021-11-22 11:59
閱讀 956·2021-09-27 13:36
閱讀 3617·2021-09-24 09:47
閱讀 2268·2021-09-01 11:39
閱讀 985·2021-08-31 09:37
閱讀 2318·2021-08-05 10:01
閱讀 1685·2019-08-30 15:55
閱讀 706·2019-08-30 15:54