1.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挖到的概率很大.
- 創世塊中的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)
2. PoA的特點
- PoA是依靠預設好的授權節點(signers),負責產生block,
- 可以由已授權的signer選舉(投票超過50%)加入新的signer,
- 即使存在惡意signer,他最多只能攻擊連續塊(數量是
(SIGNER_COUNT / 2) + 1)中的1個,期間可以由其他signer投票踢出該惡意signer, - 可指定產生block的時間,
3. PoA的作業流程及介面
- 在創世塊中指定一組初始授權的signers, 所有地址 保存在創世塊Extra欄位中
- 啟動挖礦后, 該組signers開始對生成的block進行 簽名并廣播.
- 簽名結果 保存在區塊頭的Extra欄位中
- Extra中更新當前高度已授權的 所有signers的地址 ,因為有新加入或踢出的signer
- 每一高度都有一個signer處于IN-TURN狀態, 其他signer處于OUT-OF-TURN狀態, IN-TURN的signer簽名的block會 立即廣播 , OUT-OF-TURN的signer簽名的block會 延時 一點隨機時間后再廣播, 保證IN-TURN的簽名block有更高的優先級上鏈
- 如果需要加入一個新的signer, signer通過API介面發起一個proposal, 該proposal通過復用區塊頭 Coinbase(新signer地址)和Nonce("0xffffffffffffffff") 欄位廣播給其他節點. 所有已授權的signers對該新的signer進行"加入"投票, 如果贊成票超過signers總數的50%, 表示同意加入
- 如果需要踢出一個舊的signer, 所有已授權的signers對該舊的signer進行"踢出"投票, 如果贊成票超過signers總數的50%, 表示同意踢出
signer對區塊頭進行簽名
- Extra的長度至少65位元組以上(簽名結果是65位元組,即R, S , V, V是0或1)
- 對blockHeader中所有欄位除了Extra的 后65位元組 外進行 RLP編碼
- 對編碼后的資料進行
Keccak256hash - 簽名后的資料(65位元組)保存到Extra的 后65位元組 中
clique.go中實作了consensus中的所有介面完成POA演算法的實作
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的初始化
在 Ethereum.StartMining 中,如果Ethereum.engine配置為clique.Clique, 根據當前節點的礦工地址(默認是acounts[0]), 配置clique的 簽名者 : clique.Authorize(eb, wallet.SignHash) ,其中 簽名函式 是SignHash,對給定的hash進行簽名.
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.Seal(chain, block , stop)
Seal也是共識引擎介面之一. 該函式用clique.signer對block的進行簽名. 實作共識,引擎,嘗試使用創建密封塊
- 如果signer沒有在snapshot的signers中,不允許對block進行簽名
- 如果不是本block的簽名者,延時一定的時間(隨機)后再簽名, 如果是本block的簽名者, 立即簽名.
- 簽名結果放在Extra的extraSeal的65位元組中
- 不支持密封genesis區塊
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表示加入還是踢出
verifyUncles(chain,block)
判斷block中的叔伯塊是否大于零,由于這個共識中不允許有叔伯塊
ecercover(header,sigcache)
從簽名頭中提取以太坊帳戶地址
CalcDifficulty(chain,time,parent)
回傳區塊的計算難度,計算難度是難度調整演算法
Difficulty欄位的值: 1-是 本block的簽名者 (in turn), 2- 非本block的簽名者 (out of turn)
投票策略
因為blockchain可能會小范圍重組(small reorgs), 常規的投票機制(cast-and-forget, 投票和忘記)可能不是最佳的,因為包含單個投票的block可能不會在最終的鏈上,會因為已有最新的block而被拋棄,
一個簡單但有效的辦法是對signers配置"提議(proposal)".例如 "add 0x...", "drop 0x...", 有多個并發的提議時, 簽名代碼"隨機"選擇一個提議注入到該簽名者簽名的block中,這樣多個并發的提議和重組(reorgs)都可以保存在鏈上.
該串列可能在一定數量的block/epoch 之后過期,提案通過并不意味著它不會被重新呼叫,因此在提議通過時不應立即丟棄,
- 加入和踢除新的signer的投票都是立即生效的,參與下一次投票計數
- 加入和踢除都需要 超過當前signer總數的50% 的signer進行投票
- 可以踢除自己(也需要超過50%投票)
- 可以并行投票(A,B交叉對C,D進行投票), 只要最終投票數操作50%
- 再沒進入新的epoch對于以一個signer的投票未被通過時,后面有其他signer對該signer的投票也算入判斷中
- 進入一個新的epoch, 所有之前的pending投票都作廢, 重新開始統計投票
投票場景舉例
- ABCD, AB先分別踢除CD, C踢除D, 結果是剩下ABC
- ABCD, AB先分別踢除CD, C踢除D, B又投給C留下的票, 結果是剩下ABC
- ABCD, AB先分別踢除CD, C踢除D, 即使C投給自己留下的票, 結果是剩下AB
- ABCDE, ABC先分別加入F(成功,ABCDEF), BCDE踢除F(成功,ABCDE), DE加入F(失敗,ABCDE), BCD踢除A(成功, BCDE), B加入F(由于DE加入F還存在,此時BDE加入F,滿足超過50%投票), 結果是剩下BCDEF
4. PoA中的攻擊及防御
- 惡意簽名者(Malicious signer). 惡意用戶被添加到簽名者串列中,或簽名者密鑰/機器遭到入侵. 解決方案是,N個授權簽名人的串列,任一簽名者只能對每K個block簽名其中的1個,這樣盡量減少損害,其余的礦工可以投票踢出惡意用戶,
- 審查簽名者(Censoring signer). 如果一個簽名者(或一組簽名者)試圖檢查block中其他signer的提議(特別是投票踢出他們), 為了解決這個問題,我們將簽名者的允許的挖礦頻率限制在1/(N/2),如果他不想被踢出出去, 就必須控制超過50%的signers.
- "垃圾郵件"簽名者(Spamming signer). 這些signer在每個他們簽名的block中都注入一個新的投票提議.由于節點需要統計所有投票以創建授權簽名者串列, 久而久之之后會產生大量垃圾的無用的投票, 導致系統運行變慢.通過epoch的機制,每次進入新的epoch都會丟棄舊的投票
- 并發塊(Concurrent blocks). 如果授權簽名者的數量為N,我們允許每個簽名者簽名是1/K,那么在任何時候,至少N-K個簽名者都可以成功簽名一個block,為了避免這些block競爭( 分叉 ),每個簽名者生成一個新block時都會加一點隨機延時,這確保了很難發生分叉,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/330392.html
標籤:區塊鏈
