PoA共識引擎演算法實現分析(2)
阿新 • • 發佈:2019-01-23
PoA共識引擎演算法實現分析
clique中一些概念和定義
- EPOCH_LENGTH : epoch長度是30000個block, 每次進入新的epoch,前面的投票都被清空,重新開始記錄,這裡的投票是指加入或移除signer
- BLOCK_PERIOD : 出塊時間, 預設是15s
- UNCLE_HASH : 總是
Keccak256(RLP([]))
,因為沒有uncle - SIGNER_COUNT : 每個block都有一個signers的數量
- SIGNER_LIMIT : 等於
(SIGNER_COUNT / 2) + 1
. 每個singer只能簽名連續SIGNER_LIMIT個block中的1個- 比如有5個signer:ABCDE, 對4個block進行簽名, 不允許簽名者為ABAC, 因為A在連續3個block中籤名了2次
- NONCE_AUTH : 表示投票型別是加入新的signer; 值=
0xffffffffffffffff
- NONCE_DROP : 表示投票型別是踢除舊的的signer; 值=
0x0000000000000000
- EXTRA_VANITY : 代表block頭中Extra欄位中的保留欄位長度: 32位元組
- EXTRA_SEAL : 代表block頭中Extra欄位中的儲存簽名資料的長度: 65位元組
- IN-TURN/OUT-OF-TURN : 每個block都有一個in-turn的signer, 其他signers是out-of-turn, in-turn的signer的權重大一些, 出塊的時間會快一點, 這樣可以保證該高度的block被in-turn的signer挖到的概率很大.
clique中最重要的兩個資料結構:
- 共識引擎的結構:
type Clique struct { config *params.CliqueConfig // 系統配置引數 db ethdb.Database // 資料庫: 用於存取檢查點快照 recents *lru.ARCCache //儲存最近block的快照, 加速reorgs signatures *lru.ARCCache //儲存最近block的簽名, 加速挖礦 proposals map[common.Address]bool //當前signer提出的proposals列表 signer common.Address // signer地址 signFn SignerFn // 簽名函式 lock sync.RWMutex // 讀寫鎖 }
- snapshot的結構:
type Snapshot struct {
config *params.CliqueConfig // 系統配置引數
sigcache *lru.ARCCache // 儲存最近block的簽名快取,加速ecrecover
Number uint64 // 建立快照時的block號
Hash common.Hash // 建立快照時的block hash
Signers map[common.Address]struct{} // 此刻的授權的signers
Recents map[uint64]common.Address // 最近的一組signers, key=blockNumber
Votes []*Vote // 按時間順序排列的投票列表
Tally map[common.Address]Tally // 當前的投票計數,以避免重新計算
}
除了這兩個結構, 對block頭的部分欄位進行了複用定義, ethereum的block頭定義:
type Header struct {
ParentHash common.Hash
UncleHash common.Hash
Coinbase common.Address
Root common.Hash
TxHash common.Hash
ReceiptHash common.Hash
Bloom Bloom
Difficulty *big.Int
Number *big.Int
GasLimit *big.Int
GasUsed *big.Int
Time *big.Int
Extra []byte
MixDigest common.Hash
Nonce BlockNonce
}
- 創世塊中的Extra欄位包括:
- 32位元組的字首(extraVanity)
- 所有signer的地址
- 65位元組的字尾(extraSeal): 儲存signer的簽名
- 其他block的Extra欄位只包括extraVanity和extraSeal
- Time欄位表示產生block的時間間隔是:blockPeriod(15s)
- Nonce欄位表示進行一個投票: 新增( nonceAuthVote:
0xffffffffffffffff
)或者移除( nonceDropVote:0x0000000000000000
)一個signer - Coinbase欄位存放 被投票 的地址
- 舉個栗子: signerA的一個投票:加入signerB, 那麼Coinbase存放B的地址
- Difficulty欄位的值: 1-是 本block的簽名者 (in turn), 2- 非本block的簽名者 (out of turn)
下面對比較重要的函式詳細分析實現流程
Snapshot.apply(headers)
建立一個新的授權signers的快照, 將從上一個snapshot開始的區塊頭中的proposals更新到最新的snapshot上
- 對入參headers進行完整性檢查: 因為可能傳入多個區塊頭, block號必須連續
- 遍歷所有的header, 如果block號剛好處於epoch的起始(number%Epoch == 0),將snapshot中的Votes和Tally復位( 丟棄歷史全部資料 )
- 對於每一個header,從簽名中恢復得到 signer
- 如果該signer在snap.Recents中, 說明 最近已經有過簽名 , 不允許再次簽名, 直接 返回 結束
- 記錄 該signer是該block的簽名者:
snap.Recents[number] = signer
- 統計header.Coinbase的投票數,如果 超過signers總數的50%
- 執行加入或移除操作
- 刪除snap.Recents中的一個signer記錄: key=number- (uint64(len(snap.Signers)/2 + 1)), 表示釋放該signer,下次可以對block進行簽名了
- 清空被移除的Coinbase的投票
- 移除snap.Votes中該Conibase的所有投票記錄
- 移除snap.Tally中該Conibase的所有投票數記錄
共識引擎clique的初始化
在 Ethereum.StartMining
中,如果Ethereum.engine配置為clique.Clique, 根據當前節點的礦工地址(預設是acounts[0]), 配置clique的 簽名者 : clique.Authorize(eb, wallet.SignHash)
,其中 簽名函式 是SignHash,對給定的hash進行簽名.
獲取給定時間點的一個快照 Clique.snapshot
- 先查詢Clique.recents中是否有快取, 有的話就返回該snapshot
- 在查詢持久化儲存中是否有快取, 有的話就返回該snapshot
- 如果是創世塊
- 從Extra中取出所有的signers
newSnapshot(Clique.config, Clique.signatures, 0, genesis.Hash(), signers)
- signatures是最近的簽名快照
- signers是所有的初始signers
- 把snapshot加入到Clique.recents中, 並持久化到db中
- 其他普通塊
- 沿著父塊hash一直往回找是否有snapshot, 如果沒找到就記錄該區塊頭
- 如果找到最近的snapshot, 將前面記錄的headers 都
applay
到該snapshot上 - 儲存該最新的snapshot到快取Clique.recents中, 並持久化到db中
Clique.Prepare(chain , header)
Prepare是共識引擎介面之一. 該函式配置header中共識相關的引數(Cionbase, Difficulty, Extra, MixDigest, Time)
- 對於非epoch的block(
number % Epoch != 0
):
- 得到Clique.proposals中的投票資料(例:A加入C, B踢除D)
- 根據snapshot的signers分析投票數否有效(例: C原先沒有在signers中, 加入投票有效, D原先在signers中,踢除投票有效)
- 從被投票的地址列表(C,D)中, 隨機選擇一個地址 ,作為該header的Coinbase,設定Nonce為加入(
0xffffffffffffffff
)或者踢除(0x0000000000000000
) Clique.signer
如果是本輪的簽名者(in-turn), 設定header.Difficulty = diffInTurn(1), 否則就是diffNoTurn(2)- 配置header.Extra的資料為[
extraVanity
+snap中的全部signers
+extraSeal
] - MixDigest需要配置為nil
- 配置時間戳:Time為父塊的時間+15s
重點: Clique.Seal(chain, block , stop)
Seal也是共識引擎介面之一. 該函式用clique.signer對block的進行簽名. 在pow]演算法中, 該函式進行hash運算來解"難題".
- 如果signer沒有在snapshot的signers中,不允許對block進行簽名
- 如果不是本block的簽名者,延時一定的時間(隨機)後再簽名, 如果是本block的簽名者, 立即簽名.
- 簽名結果放在Extra的extraSeal的65位元組中
Clique.VerifySeal(chain, header)
VerifySeal也是共識引擎介面之一.
- 從header的簽名中恢復賬戶地址,改地址要求在snapshot的signers中
- 檢查header中的Difficulty是否匹配(in turn或out of turn)
Clique.Finalize
Finalize也是共識引擎介面之一. 該函式生成一個block, 沒有叔塊處理,也沒有獎勵機制
header.Root
: 狀態根保持原狀header.UncleHash
: 為niltypes.NewBlock(header, txs, nil, receipts)
: 封裝並返回最終的block
API.Propose(addr, auth)
新增一個proposal: 呼叫者對addr的投票, auth表示加入還是踢出
API.Discard(addr)
刪除一個proposal
作者:shi_qinfeng
連結:https://www.jianshu.com/p/7a979813d368
來源:簡書