Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

以太坊POA共识机制Clique源码分析

卫蒙更夜沙
2836 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。
; m' [8 n) @6 c( `1 V
  1. func (s *Ethereum) StartMining(local bool) error {
    7 G$ ]" X4 f  s4 R/ a$ N
  2.         eb, err := s.Etherbase()//用户地址7 V6 H1 v: w" O# k6 X6 d
  3.         if err != nil {
    ! D, u, N# V) r0 p8 X2 X% ]3 `
  4.                 log.Error("Cannot start mining without etherbase", "err", err)$ z" n  I2 F8 e) E6 b3 i& Y
  5.                 return fmt.Errorf("etherbase missing: %v", err)" I$ e* T, u1 V% b1 G! H
  6.         }5 z, n/ g% I8 X  L' Y
  7.         if clique, ok := s.engine.(*clique.Clique); ok {+ l% \  c7 v1 a. ^
  8.                 //如果是clique共识算法
      e8 @- Q0 V" J5 ?+ ^+ e
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象
    ! \8 i- V0 E# C% L
  10.                 if wallet == nil || err != nil {) U1 A; E- m* c* {/ c* \9 Z
  11.                         log.Error("Etherbase account unavailable locally", "err", err)1 F9 @! ~* i* x: Z
  12.                         return fmt.Errorf("signer missing: %v", err)% @1 p0 r7 A9 ]
  13.                 }
    ) i' c; h$ }* q
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法0 M0 h8 Y3 Q$ F$ Z: S8 T2 u
  15.         }. P0 I" _% j, `) k% W3 }
  16.         if local {
    % g* S% n9 o9 a' V* L* V* _
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。( n. ~0 V6 m; |, N% m7 Q( b
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
    + m8 j  M0 p/ f
  19.         }
    3 [. |: M0 N2 T; w
  20.         go s.miner.Start(eb)
    0 d4 f$ N! ^$ h& _
  21.         return nil. E% q- f1 O; n9 ^  ~( v! [
  22. }
复制代码
& U6 n4 @2 a9 g8 q
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
" I0 k$ ^+ v  u2 |Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:1 c/ |4 m! w7 R6 O% H
type Engine interface {
0 R# f. k9 c" ?' i        Author(header *types.Header) (common.Address, error)
; [+ J! ?" w( V# Y' F        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error/ `4 L% W7 j. M; R( ~
        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
/ G+ K, J- A, l9 s6 o. G9 rEngine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:; o# v3 ~* b! H" b  z
  1. type Clique struct {
    ) h- r8 F  P3 ]' V5 t9 b, C) l
  2.         config *params.CliqueConfig // 共识引擎配置参数
    2 O1 P7 V0 Q% Q0 A7 T2 _9 z: w
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点
      W+ S$ a1 C6 f7 {( a
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组
    4 P) T0 G) a/ |: O
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿
    2 f$ {: ^: K& y" m, I0 p! T8 D/ B  t! x
  6.         proposals map[common.Address]bool // 目前正在推送的提案$ Y" w8 z- K3 F, D
  7.         signer common.Address // 签名者的以太坊地址; a  N9 ?1 h8 Q, s5 A" O: N, j
  8.         signFn SignerFn       // 授权哈希的签名方法
    ( |, Z% z6 ~; t: J
  9.         lock   sync.RWMutex   // 用锁来保护签名字段
    3 J; C, C2 D: P7 Z7 b7 b/ b# \
  10. }
复制代码
( h' g4 g% E3 Z% l8 Q: |
顺便来看下CliqueConfig共识引擎的配置参数结构体:
5 f: ^# {, [0 `9 ^+ D
  1. type CliqueConfig struct {
    1 i/ g' k9 \, J# e% [
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)
    3 X  x: P+ [: B: b
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)* k9 p( q/ _- ^9 p' u! A
  4. }
复制代码

# f2 M0 Y' ]' m在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
7 _' c: v7 D. t
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
    2 |( p. H2 H' n' d% \" Z' G
  2.     c.lock.Lock()2 A% Y* o4 J9 P# s9 l& [; i, z+ t
  3.     defer c.lock.Unlock()5 \6 m% V; d) \* Z- t- Z
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
    $ _4 j5 P8 r7 G( M
  5.     c.signer = signer! I+ {- ]+ E8 @6 _9 j6 P/ L
  6.     c.signFn = signFn9 A% ]: h: Z; _% q$ O' w7 I$ l9 G
  7. }
复制代码
( b( s% B+ N/ P4 {" E1 `7 L
再来看Clique的Seal()函数的具体实现:7 a2 k4 k; F) e' ~8 p
//通过本地签名认证创建已密封的区块/ X% K) q+ g) g& }
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {& t9 P' x2 y  g) o) a* V
                                log.Info("Signed recently, must wait for others"); T) e' _" R: O9 u
                                
: T) K+ c( P+ e' G+ USeal是共识引擎的入口之一,该函数通过clique.signer对区块签名
* X/ ^7 y0 {: q* h5 R- M2 Wsigner不在snapshot的signer中不允许签名0 ]0 ?; m- R! D7 L3 h. K" @! n
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
+ t# \1 {3 |+ I- z签名存放在Extra的extraSeal的65个字节中) g- C' T. \, ~
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。
; P& S3 h1 O8 m# C& W! p
  1. //snap.Signers是所有的认证节点
    # _, P  M3 M9 t& g
  2. for seen, recent := range snap.Recents {
    2 p, [1 f" Y& H; U
  3.     if recent == signer {
    . E( c; J1 a2 t9 Y% E
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {
    * ^9 K3 J7 P7 V8 `$ P6 G  g& v' }
  5.             log.Info("Signed recently, must wait for others")
复制代码

2 g0 A2 P2 e- Q# O% z$ R! n9 B            2 e' _/ L; z: V) X
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
8 w1 Z, @8 j3 Z1 s关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
$ [7 c% w* p" }, u+ d1 E# @diffInTurn = big.NewInt(2)
& x! D+ [0 Y/ NdiffNoTurn = big.NewInt(1) 0 o0 L% F7 M' j
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
& N# B0 ^3 u; @/ f( F// 通过给定的区块高度和签发者返回该签发者是否在轮次内8 w1 l& |* j+ R  B  r
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    * c* B, W7 J" q; c* A: h
  2.         signers, offset := s.signers(), 0$ E7 ^' H9 F. f
  3.         for offset
复制代码

7 h4 u+ J! ?5 q! k% xSeal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:: Y4 o+ W% l% g( j) X# a0 H
// Snapshot对象是在给定时间点的一个认证投票的状态6 e. o2 `; N! n6 T
  1. type Snapshot struct {
      j' I/ `8 O% e8 n) \
  2.     config   *params.CliqueConfig // 共识引擎配置参数8 l! o) _  r! Y% ?
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。
    / q* ?& G2 T0 A, ~/ @7 ]
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号
    5 D5 I. u2 c) i1 o9 b
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希* N# [& H" x6 f5 J
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表* T3 y( _* }3 f( H
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址1 W9 `- l0 r9 S. d9 }; p' I
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    % q/ `; j* x* o. b5 E5 x" l
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。
    1 d& Y# [$ d2 j1 r9 ]$ x/ Y& e
  10. }
复制代码
" M$ E; U0 F& D) C6 O# |
快照Snapshot对象中存在投票的Votes和记票的Tally对象:
2 I* Y4 m; h6 N# T
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。' U2 D9 K- ^0 D* z# W; K+ T
  2. type Vote struct {' y  O1 a# j* ^, I
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
    1 s, q2 e$ A6 M% K* y& y
  4.     Block     uint64         `json:"block"`     // 投票区块号
    3 I# l& \9 |7 |: M$ j# h2 t
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权& |8 c8 m7 F( a9 P
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权
    * c" u$ D! {" q5 m3 o# D
  7. }+ e# J: ]) n! X5 u( ?4 {" s
  8. // Tally是一个简单的用来保存当前投票分数的计分器: ^8 W2 `/ J) ]7 [
  9. type Tally struct {  J( W8 T7 b" j' |& u9 k  }8 f' U
  10.     Authorize bool `json:"authorize"` // 授权true或移除false, b7 w/ y8 E& a! o
  11.     Votes     int  `json:"votes"`     // 该提案已获票数
    . r4 T1 ~) S4 e: n
  12. }
复制代码
5 M" N8 X. X) S9 N% ~; N; F
Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:
: C0 z, ?* ]$ c: m0 i7 e# N8 ]
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
    & }. Y- m/ @+ v: S' T9 S+ N; `
  2.         //使用Database接口的Get方法通过Key来查询缓存内容
    ' k3 `  h, s, @$ G2 l4 j
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    4 H: z+ L+ }' z9 `6 b8 w
  4.         if err != nil {
    - a, {0 ^$ d2 L. l) Z6 c6 n
  5.                 return nil, err+ v) ?! k) z% ]( V3 _; f" Z  [5 Y
  6.         }
    ! ^& C+ q' o/ F8 ~  h
  7.         snap := new(Snapshot)0 N5 ?* }' G& Y1 s
  8.         if err := json.Unmarshal(blob, snap); err != nil {
    , F" F3 b! I' U$ a) l5 ~  D3 g- W
  9.                 return nil, err1 e- R  s& ]  d8 s
  10.         }
    . a+ e2 n0 A: s" V; c* P, A
  11.         snap.config = config
    ) P2 k$ L$ c/ f, P0 k
  12.         snap.sigcache = sigcache8 L3 ?7 i& t5 N4 U1 L0 q
  13.         return snap, nil
      F2 P( @/ x- G6 `0 q8 T6 ^
  14. }
复制代码
; K# P) w+ H' y. v5 Q. U1 W$ S0 \5 }
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:% J4 u2 g2 z  m; \. N# A: F
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {( c! f! H% v3 Z& E8 t$ P! N7 x0 y
  2.         //组装一个Snapshot对象
    & W# X+ w( V2 |
  3.         snap := &Snapshot{4 P. h  f: c9 Q1 p1 ~3 n6 {
  4.                 config:   config," }' h. {6 R4 z/ W4 h
  5.                 sigcache: sigcache,
    / _2 f- t* j: D. w3 g& b' v
  6.                 Number:   number,1 h% \5 w, f- V6 m  m
  7.                 Hash:     hash,
    8 b. e& E7 [4 R; u, t
  8.                 Signers:  make(map[common.Address]struct{}),
    - C5 p( ^4 g8 }1 U
  9.                 Recents:  make(map[uint64]common.Address),. Q# I, L: Q, J
  10.                 Tally:    make(map[common.Address]Tally),
    6 e# {8 @( g$ O7 B
  11.         }0 Z9 Z, D4 X6 G
  12.         for _, signer := range signers {
    3 q8 j6 N8 Q$ P; a8 |+ S: ?
  13.                 snap.Signers[signer] = struct{}{}0 i* p0 P4 u) L6 _
  14.         }
    " }# j1 j( R3 _7 [0 Q: [
  15.         return snap
    9 {+ g& G4 G' K( O
  16. }
复制代码

1 y( k% |+ @) p6 L+ ?继续看下snapshot函数的具体实现:
9 N! I# ]8 U: U$ _% F+ G. ~
  1. // 快照会在给定的时间点检索授权快照6 ^! S& z+ k/ N  x1 s4 ]! ^; L2 r
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {3 C: M, E% Q6 l7 w, r1 U
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints5 y1 S' c* V9 c# ^* ^- n, V  B4 u
  4.         var (8 e( J$ Q# V" T
  5.                 headers []*types.Header        //区块头& l" k* c7 t. |9 W% m7 H, @
  6.                 snap    *Snapshot        //快照对象
    ( X4 e" r  v8 Z2 e! }  ]: G
  7.         )
    7 ]6 [9 R3 B: k6 p9 N
  8.         for snap == nil {; J2 a: ^- G) k& D
  9.                 // 如果在内存中找到快照时,快照对象从内存中取
      f) }4 o( W" Q" c$ d
  10.                 if s, ok := c.recents.Get(hash); ok {: N# r$ R) t1 ^& ~4 H( V" W5 a- A
  11.                         snap = s.(*Snapshot)* u1 l  R$ k! H% V* L
  12.                         break5 a) d! n+ r# n
  13.                 }+ I, P$ z  U5 A- u9 U& o
  14.                 // 如果在磁盘检查点找到快照时$ b' |: V1 V5 k' J, b* h6 Z7 C
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
    3 m' r  j7 B* M
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
    / o) m" G5 {6 I8 ]6 p2 h0 I
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
    * F' e/ R* b6 R1 P* b
  18.                                 snap = s+ K5 D) [& I& O" V% r
  19.                                 break
    # @# B- E$ ~( Q( `2 C
  20.                         }
    - l& b: Q! ~6 E
  21.                 }
    0 f- C$ y/ M# X- J* W2 H, a9 @
  22.                 // 如果在创世块,则新建一个快照
    * A/ M% `) P2 U! m/ w9 v
  23.                 if number == 0 {- J2 d6 C" K' C
  24.                         genesis := chain.GetHeaderByNumber(0)
    ) o6 I/ q  K# T9 k) n5 {$ l! q
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    0 x6 ?2 j; `* S; a( E( t
  26.                                 return nil, err
    " [0 W  L! _4 F4 K- h& J( N
  27.                         }1 s+ B) A- m9 X' m8 d* ]/ F
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
    ; K! |) r/ y# O+ H3 i
  29.                         for i := 0; i  0 {
    ; Y, l. J8 v# O/ ^' K% J  |7 Q
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)
    6 j9 C6 z1 |% A5 H3 {: u9 `% ]2 ~* U& j
  31.                         header = parents[len(parents)-1]3 u* d' \5 w/ Z% Q
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {
    2 Y2 L( [0 c0 G2 w
  33.                                 return nil, consensus.ErrUnknownAncestor" \5 u, n& O+ V5 ]/ U, f/ F" f. R
  34.                         }  b- L) l2 J, E! d
  35.                         parents = parents[:len(parents)-1]- W7 }3 `4 w9 J2 S9 o, c
  36.                 } else {- Q9 h4 J7 |5 }6 l, l4 O8 Q9 v: `) I4 |: C
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    3 d4 p; p) K' V5 e% l& Y/ G1 o
  38.                         header = chain.GetHeader(hash, number)
    ! N0 a5 j$ b' P( M% ]) f
  39.                         if header == nil {% v  T, X7 F& @! ~
  40.                                 return nil, consensus.ErrUnknownAncestor
    5 Z/ ?/ I# I/ U0 Z2 B4 F8 |* j
  41.                         }' Z8 s; F% P1 N3 ~# H
  42.                 }
    . ^# Q" }; t3 }* E4 m4 G' e$ b
  43.                 headers = append(headers, header)  x- L2 c, n" A- G
  44.                 number, hash = number-1, header.ParentHash! v  |3 ~; }% t8 R6 U
  45.         }
    ; w& [, ?! U) o$ [' F' S
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面
    9 T( S. v, q# K
  47.         for i := 0; i  0 {
    * M. N6 t+ ~; m
  48.                 if err = snap.store(c.db); err != nil {+ U( z7 G: J% U- ^: {$ b
  49.                         return nil, err
    ) K( ^% b4 I/ w0 H$ J
  50.                 }
    ; n5 \. Z7 U( R5 L: `' {5 I( X
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)" i' f7 }* _: l: b: D
  52.         }8 Y1 h( ?0 M" R. k! P9 S; B
  53.         return snap, err  P7 f9 y1 M$ D
  54. }
复制代码

. Y4 V- P& L, m0 w: o* ?# _4 e) U; l在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?0 K4 {$ {# [4 C
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
    3 D3 N& c' i6 I; S, q: N; n9 d3 Z
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {9 [! {+ S# E, u+ ^7 W
  3.           //可以传空区块头: g% R8 E+ @$ Y  ^# k; r9 k. m
  4.     if len(headers) == 0 {
    & |; k( ?4 a$ I( T7 Q" w- ]  L9 M
  5.         return s, nil
    2 H( ]4 [/ W: a; Z% g. p
  6.     }% d/ g+ n+ r! u+ b' |. p
  7.           //完整性检查区块头可用性! ]3 T: u9 P/ @3 e
  8.     for i := 0; i = limit {0 n& z" ~6 \8 G, t( o8 @
  9.             delete(snap.Recents, number-limit)
    6 H/ K+ E+ D! Z
  10.         }
    $ a) c( K2 @. L- n5 C
  11.         // 从区块头中解密出来签名者地址
    . h9 J# Q* b8 \; G: C7 ?5 b) w
  12.         signer, err := ecrecover(header, s.sigcache)
    ! @2 W* z$ K' l) f
  13.         if err != nil {
      N: B4 M6 r* |9 s/ W
  14.             return nil, err- N  p3 ^5 Y& b( {& [5 x
  15.         }
    8 A+ X" R4 J9 ?# ^2 S  V
  16.         if _, ok := snap.Signers[signer]; !ok {
    + r9 g- R9 D/ b% U" t' n& m
  17.             return nil, errUnauthorized
    & e9 s3 L( B& N: b) w2 y
  18.         }# D( c# X$ K$ g; `- {
  19.         for _, recent := range snap.Recents {) d- f" i" w( V7 A  J% i
  20.             if recent == signer {
    4 U/ P; ?  X, g. c* m5 M4 m) y" Q
  21.                 return nil, errUnauthorized
    2 }/ G2 }+ h/ l7 g" E; _
  22.             }
      q9 t: ]3 a& \$ ~4 b% {4 l) R0 J
  23.         }
      q1 b1 }7 C$ E# I
  24.         snap.Recents[number] = signer" m& t/ d" M! E7 Y8 P! ^2 B
  25.         // 区块头认证,不管该签名者之前的任何投票) o# e8 r) O/ ~7 Y8 q' k4 i
  26.         for i, vote := range snap.Votes {3 m6 g: D; e1 z& `' P
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {
    / q/ J; r. p* z, i
  28.                 // 从缓存计数器中移除该投票7 {% ^9 N9 I, o( K; D
  29.                 snap.uncast(vote.Address, vote.Authorize)
    2 a, ~! u0 ~2 o' X( M5 y
  30.                 // 从按时间排序的列表中移除投票
    $ k; O5 i( k4 u2 A
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    ! [: c' _, u% g& w
  32.                 break // 只允许一票
    6 G8 o% o* U* @" M5 m
  33.             }
    " A& Z3 r2 E- r" L8 _% c
  34.         }6 \, z! \$ g" i6 b( a% \
  35.         // 从签名者中计数新的投票! D8 F. C5 J  l" ]5 f& n
  36.         var authorize bool: D' ]. y3 k' I3 _0 f/ q
  37.         switch {
    6 g' Z# D; m1 J0 e5 i
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    . H1 }3 y& W9 y. _* N$ ?3 ?3 w
  39.             authorize = true
    ( x9 o- }- u% p$ F6 ?
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):, m+ h" j% C( Y% N% T& L/ d
  41.             authorize = false
    ! z% w" T, s# }) h+ U8 g! m' s
  42.         default:
    7 E+ b4 A. l$ o: W
  43.             return nil, errInvalidVote
    8 }2 Q( ?/ O- X
  44.         }- w% x5 X# ?, G5 R  N: u; ~* D
  45.         if snap.cast(header.Coinbase, authorize) {
    ( X1 Z# F' d+ G: z8 a
  46.             snap.Votes = append(snap.Votes, &Vote{
    & h/ d* l6 V; |+ m' ]& B+ ?& x
  47.                 Signer:    signer,
    ' w* y1 {8 i' R; l3 r- o
  48.                 Block:     number,
    5 ^9 {0 h3 [5 J
  49.                 Address:   header.Coinbase,+ B  j& j* P) L" V
  50.                 Authorize: authorize,
    # P. m' b6 f6 Q
  51.             })3 D6 X. j" [: r# Q4 s
  52.         }
    1 u( U9 g( V6 D$ x+ r+ F3 I7 i+ D
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
    6 ~5 v2 Y; a$ V* ]" ]* O1 ~
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {# M- @# R" k' z; x' |
  55.             if tally.Authorize {
    . e% K; |: U( p  y- G) B$ n. T
  56.                 snap.Signers[header.Coinbase] = struct{}{}. K* e: s6 s1 O- t5 X* i+ B% o
  57.             } else {
    . C/ I$ N6 i. ~" c7 `* R
  58.                 delete(snap.Signers, header.Coinbase)
    * @. |  j+ X$ a; D
  59.                                   // 签名者列表缩减,删除最近剩余的缓存
    - W! Y+ Y" _4 ~* z  L2 u
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {: ^5 y# {9 f8 X6 |
  61.                     delete(snap.Recents, number-limit)
    ; f/ ^8 R( G0 \; D
  62.                 }
    ' e  c" _& M5 ]  o2 Q9 a' A+ F5 ^" q' ~6 F5 z
  63.                 for i := 0; i
复制代码

7 p1 f1 Y7 b2 a& k& q2 W/ W0 WSnapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。
% d6 P1 [& I- U3 P' z; {区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。
! k7 m; Q! b/ e2 v  t/ r
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照" d% j0 u+ K( R6 n+ @. C7 ?, A  ]
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {+ m$ |+ n6 h/ W& D- j
  3.         // 不支持校检创世块& x; ?4 M4 U  V& o. e/ V9 x
  4.         number := header.Number.Uint64()
    # \. e2 I6 x' T- y( v
  5.         if number == 0 {" ]7 S# ^1 d, f3 c5 l3 K
  6.                 return errUnknownBlock' N' @& v0 o5 A) M
  7.         }
    3 M7 s) S: E/ i3 ]. G# q0 J! L  m
  8.         // 检索出所需的区块对象来校检去开头和将其缓存5 E: o3 u& s" S" K' B
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)# _3 e5 o+ s4 D7 Z
  10.         if err != nil {
    6 w! \4 N( w% d' q
  11.                 return err
    ! @, z9 s  a- F& e' n
  12.         }
    3 i! @; ?# V' j2 b8 N' q
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址5 ~7 H7 w& k* [* V5 T
  14.         signer, err := ecrecover(header, c.signatures)
    , B% h" U$ W8 v9 ]' B& L' j& K8 {
  15.         if err != nil {2 W. @: G9 u' `' i0 O+ z
  16.                 return err
    + o3 A% C' W8 p: K# @
  17.         }' b) q( x8 p) X
  18.         if _, ok := snap.Signers[signer]; !ok {
    6 b" i2 D: P- L- p0 S+ s
  19.                 return errUnauthorized7 u" @( A$ _3 F5 E9 F$ D
  20.         }
    * T' r, [  b5 J$ F& B
  21.         for seen, recent := range snap.Recents {
      E+ _; x; _+ w4 B, T  ~$ L; y/ ?  _
  22.                 if recent == signer {: E3 z8 W9 _+ ~8 @, R1 Y
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等, @$ `. Y" b( _; O8 D
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    0 o/ I: Z6 X& V$ l  z
  25.                                 return errUnauthorized; |$ X9 a6 R+ x% L. Z
  26.                         }
    8 u+ {: f- d1 C4 y
  27.                 }
    ; R+ ]7 W7 d8 |0 ]* O
  28.         }3 }6 F) j- [$ N' {0 n) N
  29.         // 设置区块难度,参见上面的区块难度部分
    1 m' x7 U. o# v! ]0 K8 [+ w: T
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)
    ! j/ \: C4 c% O. v3 F
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
    6 l+ T, E; l2 Y$ t# w: j% E
  32.                 return errInvalidDifficulty
    % a# A: l% H& k6 I8 t
  33.         }  j' E" E4 X2 h/ U* F6 |  R
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {0 ^* C9 N  _  @' X
  35.                 return errInvalidDifficulty
    3 S+ x2 u; i) ^# G4 M2 x
  36.         }8 d4 Z/ p; s  x6 d  R& _
  37.         return nil5 k! g. @' f0 p4 A& B* j9 z% `, O
  38. }
复制代码
. M. i2 w3 o; |2 g/ r
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?( E3 U- {- Y- Z+ d
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:. n6 |' R7 |5 h5 w
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中3 q  V& a2 e' S7 C$ X  n
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。4 s+ ]% S$ p" j1 n( l7 F+ P, k* \
  2. func (api *API) Propose(address common.Address, auth bool) {1 {3 r9 t; U- L8 W% ^) x
  3.     api.clique.lock.Lock()% L% J. K: _/ x! h# r$ M7 `
  4.     defer api.clique.lock.Unlock()
    0 U- P, l; A3 j- y# e1 t. j' [5 }
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    " Q9 r4 R; u. H: u* b' w% ?
  6. }
复制代码
+ H" _9 G- d# k# ^% f
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;
4 R. t1 R6 B: }' ~$ N
  1. //Clique.Prepare
    + w$ i' X* l: @; G8 G* M0 H
  2.                 // 抓取所有有意义投票的提案
    - K3 c& Z2 [. y
  3.                 addresses := make([]common.Address, 0, len(c.proposals)): f* r- H% m+ D
  4.                 for address, authorize := range c.proposals {
    ; y2 u" x; e, u! l: U
  5.                         if snap.validVote(address, authorize) {
    0 E( U3 r0 k6 Q* J' l9 A
  6.                                 addresses = append(addresses, address)
    8 }7 I/ V1 y4 @% w' i3 j) g0 X! u
  7.                         }
    5 I' a- }8 H; f2 G/ t% L4 K  g
  8.                 }5 k; b- s$ I6 J! w! L
  9.                 // If there's pending proposals, cast a vote on them
    , F& o7 a( H4 K& H
  10.                 if len(addresses) > 0 {
    # D/ e! n8 {  W8 ~/ s* O
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。$ i/ _0 Q% o7 I0 P% d# {
  12.                         // 通过提案内容来组装区块头的随机数字段。4 {% f, e9 m& k/ ~$ s% B; K/ Y8 Q
  13.                         if c.proposals[header.Coinbase] {: G* g: P0 Z0 x% f! a+ c
  14.                                 copy(header.Nonce[:], nonceAuthVote)- O- J& t( t: a1 a' K6 c. ?- f
  15.                         } else {
    8 w3 u5 y( ^; ~6 o5 ^
  16.                                 copy(header.Nonce[:], nonceDropVote)
    / U* t* f: F& X4 N8 f2 n! K& M
  17.                         }
    & O- V# j- k9 U+ [
  18.                 }
复制代码

) F$ _5 D8 Y7 D; V/ z, H在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare) s. F% a) \9 t: }, T. m
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {) M4 J! h5 s  j2 C
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    0 O$ ~+ I, v) C9 a0 d4 c
  3.                 return
    : Z$ W( V( z( z5 ^2 N2 K
  4.         }
复制代码

  z* Z6 E: ]; Z( n6 b- E" a其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法
* f0 m* Q$ F7 o4 u, ?/ d$ b' w( R8 r* N& e7 M: {

" u7 W7 G; U- Z$ Y3 |' j8 t8 I+ A5 y) E
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3