配合代碼食用(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,這個后面再聊~