以太坊clique(PoA)共識引擎總結(jié)

配合代碼食用(Geth v1.9.0 stable)

背景:

以太坊目前有ethash和clique兩個共識引擎,其中ethash是用于正式網(wǎng)絡(luò)的PoW(proof-of-work)共識引擎,clique是用于測試網(wǎng)絡(luò)的PoA(proof-of-authority)共識引擎

介紹:

clique是依靠授權(quán)節(jié)點(signers)順序產(chǎn)生block,可以由已授權(quán)的signer選舉(投票超過50%)加入新的signer和踢出已授權(quán)signer的聯(lián)盟鏈共識引擎

使用:

通過編譯好的puppeth程序創(chuàng)建共識為poa的genesis.json文件,geth執(zhí)行init操作讀取json文件,然后啟動私鏈節(jié)點

代碼分析:

consenesus/clique/clique.go

1.常量和變量意義:

checkpointInterval = 1024 ,每隔1024塊保存投票快照到數(shù)據(jù)庫
inmemorySnapshots  = 128 ,保存在內(nèi)存中的快照數(shù)量
inmemorySignatures = 4096 ,保存在內(nèi)存中的最近區(qū)塊的簽名者數(shù)量
wiggleTime = 500 * time.Millisecond ,用于非順序出塊人出塊延遲時間計算,在0 ~ (signerCount/2+1)*wiggleTime范圍內(nèi)隨機取一個值作為延遲時間
epochLength = uint64(30000) ,每隔30000塊清空所有投票
extraVanity = 32 ,extra-data保留32個字節(jié)的前綴
extraSeal   = 65 ,extra-data為區(qū)塊signer保留65個字節(jié)的后綴
nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") ,投票加入一個簽名者使用的nonce
nonceDropVote = hexutil.MustDecode("0x0000000000000000") ,投票踢出一個簽名者使用的nonce
diffInTurn = big.NewInt(2) ,出塊人是順序出塊人時的區(qū)塊難度值
diffNoTurn = big.NewInt(1) ,出塊人不是順序出塊人時的區(qū)塊難度值

2.重要方法:

Prepare 
    給header.Coinbase賦值(投票目標(biāo)地址)
    給header.Nonce賦值(投票類型為加入或提出簽名者)
    給header.Difficulty賦值(是順序出塊人為2,否則為1)
    給header.Extra賦值(32字節(jié)前綴+所有簽名者地址+65字節(jié)后綴用于區(qū)塊簽名)
    給header.MixDigest賦值為空,摘要,在pow中用于防篡改校驗(VerifySeal)
    給header.Time賦值(等于parent.Time+Period或等于now>parent.Time+Period)
Finalize 
    給header.Root賦值
    給header.UncleHash賦值為空(poa沒有叔區(qū)塊)
    構(gòu)建block返回
Seal 
    判斷在最近出塊記錄中,則不允許出塊(維持一個大小為signercount/2+1的signer隊列Recents,用于判斷最近是否出過塊)
    計算應(yīng)該delay的時間
    拷貝簽名到header.Extra后65字節(jié)
    delay到出塊時間后將sealed block放入worker.resultCh
VerifySeal
    驗證header.Number不為0
    驗證區(qū)塊簽名者在簽名者列表中
    驗證區(qū)塊簽名者最近沒出過塊
    驗證header.Difficulty和輪次是否匹配
snapshot
    獲取基于某個塊的Snapshot
        優(yōu)先從內(nèi)存中獲取
        如果內(nèi)存中沒有,且恰好number是checkpointInterval的整數(shù)倍,從數(shù)據(jù)庫取
        如果恰好number是epochLength的整數(shù)倍,創(chuàng)建一個Snapshot并保存
        還是沒有從上一塊(number-1)取
    取到了Snapshot,如果是從number塊前面的塊取的,要snap.apply(headers)
    Snapshot保存到緩存中
    如果恰好number是checkpointInterval的整數(shù)倍且有執(zhí)行apply,保存Snapshot到數(shù)據(jù)庫
VerifyHeader
    驗證header有效性 
APIs
    獲取共識引擎提供的RPC接口
consenesus/clique/snapshot.go

1.結(jié)構(gòu)體:

type Snapshot struct {            // Snapshot是基于某個區(qū)塊高度的投票認(rèn)證狀態(tài)
    config   *params.CliqueConfig // 配置參數(shù)
    sigcache *lru.ARCCache        // 緩存最近區(qū)塊簽名地址,用于快速得到簽名地址

    Number  uint64                      // Snapshot創(chuàng)建時的區(qū)塊高度
    Hash    common.Hash                 // Snapshot創(chuàng)建時的區(qū)塊哈希
    Signers map[common.Address]struct{} // 已認(rèn)證簽名者集合
    Recents map[uint64]common.Address   // 最近已簽名區(qū)塊的簽名者集合 //數(shù)量為 SIGNER_COUNT/2+1 ,可保證即使存在惡意signer,他最多只能攻擊連續(xù)塊 SIGNER_COUNT/2+1 中的1個
    Votes   []*Vote                     // 按時間排序的投票集合
    Tally   map[common.Address]Tally    // 當(dāng)前投票記錄避免重復(fù)計算
}

type Tally struct {                   // 投票記錄
    Authorize bool `json:"authorize"` // 投票是加入或者提出某個賬戶
    Votes     int  `json:"votes"`     // 想要通過的提議當(dāng)前的投票數(shù)
}

type Vote struct {
    Signer    common.Address        // 投這個票的已認(rèn)證簽名者
    Block     uint64                // 投這個票的區(qū)塊高度(太舊的投票是過期投票)
    Address   common.Address        // 被投的賬戶
    Authorize bool                  // 是認(rèn)證還是解除認(rèn)證這個被投票的賬戶
}

2.重要方法:

apply
    驗證headers有效性和連續(xù)性
    snap := s.copy()
    遍歷headers:
        每隔epochLength塊清空Votes和Tally
        刪除Recents中最舊的一個singer使其能夠再次簽名
        獲取header的簽名者,判斷如果不在簽名者列表中或者在Recents列表中,return,否則將簽名者放入Recents中
        遍歷Votes,丟棄掉之前這個header的簽名者投給同一個賬戶(header.Coinbase)的投票
        投票,即在Snapshot.Tally和Snapshot.Votes中添加記錄
        根據(jù)投票記錄Tally判斷如果當(dāng)前被投票地址header.Coinbase票數(shù)大于當(dāng)前簽名者列表長度/2:
            如果提議為加入新的簽名者,簽名者列表加入新簽名者h(yuǎn)eader.Coinbase;如果提議為踢出已認(rèn)證簽名者,將其從簽名者列表刪除,然后刪除Recents中最舊的一個,丟棄所有該簽名者投的票
            丟棄之前所有投給該簽名者的票和記錄
    更新snap.Number和snap.Hash到最新
consenesus/clique/api.go

1.結(jié)構(gòu)體:

type API struct {  //API是一個面向用戶的RPC接口對象,用于控制signer和poa投票機制
    chain  consensus.ChainReader
    clique *Clique
}

2.clique共識引擎提供的RPC接口有:

func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error)  ,基于區(qū)塊高度為number的塊得到狀態(tài)快照(state snapshot)
func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error)  ,基于區(qū)塊哈希為hash的塊得到狀態(tài)快照
func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error)  ,基于區(qū)塊高度為number的塊得到簽名者列表
func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error)  ,基于區(qū)塊哈希為hash的塊得到簽名者列表
func (api *API) Proposals() map[common.Address]bool  ,獲取當(dāng)前所有提議
func (api *API) Propose(address common.Address, auth bool)  ,提議加入新的認(rèn)證簽名者或踢出現(xiàn)有認(rèn)證簽名者
func (api *API) Discard(address common.Address)  ,丟棄已有的一個提議

小結(jié)

在以太坊上增加dpos共識引擎可以參考poa,這個后面再聊~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容