1. 程式人生 > >PoA共識引擎演算法實現分析(2)

PoA共識引擎演算法實現分析(2)

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上

  1. 對入參headers進行完整性檢查: 因為可能傳入多個區塊頭, block號必須連續
  2. 遍歷所有的header, 如果block號剛好處於epoch的起始(number%Epoch == 0),將snapshot中的Votes和Tally復位( 丟棄歷史全部資料 )
  3. 對於每一個header,從簽名中恢復得到 signer
  4. 如果該signer在snap.Recents中, 說明 最近已經有過簽名 , 不允許再次簽名, 直接 返回 結束
  5. 記錄 該signer是該block的簽名者: snap.Recents[number] = signer
  6. 統計header.Coinbase的投票數,如果 超過signers總數的50%
  7. 執行加入或移除操作
  8. 刪除snap.Recents中的一個signer記錄: key=number- (uint64(len(snap.Signers)/2 + 1)), 表示釋放該signer,下次可以對block進行簽名了
  9. 清空被移除的Coinbase的投票
  10. 移除snap.Votes中該Conibase的所有投票記錄
  11. 移除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
  • 如果是創世塊
    1. 從Extra中取出所有的signers
    2. newSnapshot(Clique.config, Clique.signatures, 0, genesis.Hash(), signers)
    • signatures是最近的簽名快照
    • signers是所有的初始signers
    1. 把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 ):
  1. 得到Clique.proposals中的投票資料(例:A加入C, B踢除D)
  2. 根據snapshot的signers分析投票數否有效(例: C原先沒有在signers中, 加入投票有效, D原先在signers中,踢除投票有效)
  3. 從被投票的地址列表(C,D)中, 隨機選擇一個地址 ,作為該header的Coinbase,設定Nonce為加入( 0xffffffffffffffff )或者踢除( 0x0000000000000000 )
  4. Clique.signer 如果是本輪的簽名者(in-turn), 設定header.Difficulty = diffInTurn(1), 否則就是diffNoTurn(2)
  5. 配置header.Extra的資料為[ extraVanity + snap中的全部signers + extraSeal ]
  6. MixDigest需要配置為nil
  7. 配置時間戳: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也是共識引擎介面之一.

  1. 從header的簽名中恢復賬戶地址,改地址要求在snapshot的signers中
  2. 檢查header中的Difficulty是否匹配(in turn或out of turn)

Clique.Finalize

Finalize也是共識引擎介面之一. 該函式生成一個block, 沒有叔塊處理,也沒有獎勵機制

  1. header.Root : 狀態根保持原狀
  2. header.UncleHash : 為nil
  3. types.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
來源:簡書