Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2784 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。
, Q( q. Y& ?) l3 B- F
  1. func (s *Ethereum) StartMining(local bool) error {" A! J; ]8 p% a: }6 L
  2.         eb, err := s.Etherbase()//用户地址
    6 V3 Z" c  F6 i7 M0 K9 C" y, V
  3.         if err != nil {
    + w1 I  z, T! Y4 E& f5 o
  4.                 log.Error("Cannot start mining without etherbase", "err", err)* }6 l& e: S* \+ f
  5.                 return fmt.Errorf("etherbase missing: %v", err)
    ) W) K; J( X: Z
  6.         }
      @- Y5 |9 Q# n! O' `
  7.         if clique, ok := s.engine.(*clique.Clique); ok {' M, ]8 d( p7 S% o
  8.                 //如果是clique共识算法
    / X- ?' }3 v- l! C1 z) P
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象
    0 K' A. \2 l) Y+ a: z- [5 @
  10.                 if wallet == nil || err != nil {
    ' P& D$ A7 E- s# s) R
  11.                         log.Error("Etherbase account unavailable locally", "err", err)& e: k: g, f2 l' k# h
  12.                         return fmt.Errorf("signer missing: %v", err). w1 S! Y% O1 p
  13.                 }
    + {: z/ A$ ]3 A  }* A; `
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法
    " |$ y0 n; U0 s- Q5 M% v, y
  15.         }, c* k2 @; f' u/ C7 f8 A
  16.         if local {
    # C! u9 @" ~2 V
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
    2 g7 W9 N( J' y7 ?
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)" U* f. n0 @/ r" L" N) R) [
  19.         }
    ; G$ ^. k* \: X1 i
  20.         go s.miner.Start(eb)
    1 n) C5 ~: H* t) e- k- x6 u
  21.         return nil% P2 c% ^& }3 q
  22. }
复制代码
8 _1 Q: ^8 K; Y) j* l8 \+ l
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
5 S$ r1 s. {) g& x# e( IClique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
0 L8 t1 _8 ~1 V% ~! ftype Engine interface {# k5 C% E6 W' k6 H" M
        Author(header *types.Header) (common.Address, error)+ z9 q  U$ b8 Q" c! j) j+ t- l
        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error. d8 r* E3 d! y' f
        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
' D8 K8 M0 U# T9 z* k% j" i$ j8 P3 C, pEngine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:; J8 w; w$ Q2 W0 \; g
  1. type Clique struct {) @9 b. a+ @& a# J
  2.         config *params.CliqueConfig // 共识引擎配置参数7 h6 W& h) _, @' o( A4 h) B
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点' P) o. z* d: N. {% {: f$ B
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组
    1 E# t, i  S; S& c, z6 }. o* z
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿
    ; {' C) b% v  U
  6.         proposals map[common.Address]bool // 目前正在推送的提案
    ; Z7 T4 x( {1 K- F
  7.         signer common.Address // 签名者的以太坊地址
    + u' z1 y) ]# W! I( a; X& z
  8.         signFn SignerFn       // 授权哈希的签名方法
    3 Y- f  B$ C) s1 `6 s; d
  9.         lock   sync.RWMutex   // 用锁来保护签名字段/ @9 d0 G) }; K5 x% M( L
  10. }
复制代码

0 p* P) D7 ^/ u6 N顺便来看下CliqueConfig共识引擎的配置参数结构体:
( ?5 M. |$ e' I2 s8 [5 ?) |
  1. type CliqueConfig struct {
    ( }3 j$ }# D( K3 N. [% ]
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s). y2 ]" F* s- z- `( y
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    / v3 U8 W+ |3 [7 [4 U
  4. }
复制代码
; O% o$ N( A, H( G; P& |8 b
在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:% l. K7 P" }0 T6 @
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
    ! Q" k3 |2 C2 h' O0 [1 C, E2 @
  2.     c.lock.Lock()
    ) ~1 y& ]3 E0 L% E( K- }& `6 W0 T
  3.     defer c.lock.Unlock()) Z. V2 b! R4 _% _& {3 r/ q  \0 X
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块/ ]" V1 D. \- w. V% Z, g0 Z" A, q% W% S
  5.     c.signer = signer
    ( F1 i- M5 ^& I) g! r) ?6 _& o# C- b
  6.     c.signFn = signFn
    ) z3 }/ \% F( M' P( U( ]
  7. }
复制代码
0 g$ }1 X2 h1 m  ]
再来看Clique的Seal()函数的具体实现:6 [! [7 A' B4 i7 E
//通过本地签名认证创建已密封的区块
. R0 H; j, v' N4 K$ x  ifunc (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {
( q+ ?, b5 d+ e8 k/ q+ d! c$ n                                log.Info("Signed recently, must wait for others")
* K5 \$ q3 z$ s! \                                
7 h/ v7 k+ J. @. MSeal是共识引擎的入口之一,该函数通过clique.signer对区块签名2 m# k# }, a, F( \7 E# m' o
signer不在snapshot的signer中不允许签名
# S9 D2 u) H, Qsigner不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
2 E# p. p2 P1 q) R- v  g/ J2 v# q签名存放在Extra的extraSeal的65个字节中) X+ e; B  Z3 n
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。
1 v2 r1 [* Z$ a1 X$ W; B7 f
  1. //snap.Signers是所有的认证节点6 x4 C5 J+ B5 T
  2. for seen, recent := range snap.Recents {: B/ N. U- O2 `* i# K" ?
  3.     if recent == signer {* x" y0 T7 K% D" x% u, ]
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {
    % A8 w1 w; m$ w" u1 D7 }; H* c: m
  5.             log.Info("Signed recently, must wait for others")
复制代码
2 w/ _/ R$ M) {
            / m6 q& l+ n$ i) q8 f/ m4 H
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。4 W. q5 l2 o& K/ ~" U" O& i# j
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
/ z5 @1 q9 {5 Y/ S; [diffInTurn = big.NewInt(2) 2 u6 H% ]- o% y' L' _$ R) v% }
diffNoTurn = big.NewInt(1)
( O0 v, Z, q% f9 k* {2 i当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
$ @4 t  f/ z! P' f6 y3 W( V// 通过给定的区块高度和签发者返回该签发者是否在轮次内+ ?1 g" t; V6 X0 F6 }
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {& T% E- g- X5 b' k. G% L
  2.         signers, offset := s.signers(), 0- b' L$ A5 j( \
  3.         for offset
复制代码
2 h) P9 L7 A* F( d4 L
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:4 O" ]8 X- S: E' \& T; X5 c
// Snapshot对象是在给定时间点的一个认证投票的状态
: T3 F7 M' K1 s2 d, z% f: n
  1. type Snapshot struct {
    " b( `4 f4 k# T
  2.     config   *params.CliqueConfig // 共识引擎配置参数
    * A+ K, z; Q5 A# D. L1 E
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。
    ' c( I# S% y/ r1 a3 b
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号
    1 a5 s. u" Z7 ^. }# z# @! ]
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    * t& r( p! G! z' p2 \
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表# O+ {- S# o  r& I, z# s. i
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址
    / z+ J: w' T, {8 Q
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。# @+ q" r: ]! c4 H3 |0 e
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。
    ; h# w- v  a) g$ |3 |" C" x  x) F7 P$ s
  10. }
复制代码
; ~* j7 z; C6 u  h0 X
快照Snapshot对象中存在投票的Votes和记票的Tally对象:
& Q& c! b+ l  c& ~3 }$ v3 x: V
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。7 n. J  F3 k$ r% o
  2. type Vote struct {
    + p8 s/ ]# ]& ~0 z  `6 R/ X
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)1 E& d6 ?$ Y* _+ i, ]8 u6 C
  4.     Block     uint64         `json:"block"`     // 投票区块号2 W$ ^- k) i% k1 h$ L
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权" t: i# L! h9 ?
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权. k* }& `9 ]% ^
  7. }/ z) L( b! i3 }+ U
  8. // Tally是一个简单的用来保存当前投票分数的计分器
    * e" ?7 o( h$ T/ G# X
  9. type Tally struct {$ Y+ j# t4 I% P2 K& [1 b  D# {
  10.     Authorize bool `json:"authorize"` // 授权true或移除false- ^6 `4 x3 n- v# y+ E
  11.     Votes     int  `json:"votes"`     // 该提案已获票数
    , g: v2 k3 J: o' l; h
  12. }
复制代码

! y  t2 o% B& E& a  {Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:
2 Q# ^+ E% l, K5 g$ [
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {0 j: _" t; P+ B
  2.         //使用Database接口的Get方法通过Key来查询缓存内容, A  Z6 o7 i3 V' _; Z$ q  {( e
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    9 I9 @# ?9 n1 T& m: r# X. e
  4.         if err != nil {
    * J0 t4 v% j3 q7 t' ]! @) I
  5.                 return nil, err
    & D0 ^$ q+ a, i& H, R( V. Y3 L, F
  6.         }' k% W; \7 ]  p. p* d. g, u& m
  7.         snap := new(Snapshot)( O4 V- X! F9 \, m5 v
  8.         if err := json.Unmarshal(blob, snap); err != nil {
    $ s8 G9 |" C+ d
  9.                 return nil, err. `2 B0 ~7 m/ n# t& c
  10.         }
    # ?' w- x3 P5 r9 y' W6 J" L0 @
  11.         snap.config = config# a4 C" D& Z6 ?; _7 F. @
  12.         snap.sigcache = sigcache7 E7 c) N, ]  }
  13.         return snap, nil' F( {+ r. B9 G2 R# M  X; d
  14. }
复制代码
1 H2 Y% ^6 z+ m7 ]9 r4 w
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:# f/ |/ ]' E+ @5 q+ Q
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
    # `8 l  L( s8 p6 c
  2.         //组装一个Snapshot对象
    9 o5 r, ~, v8 c( {5 A1 F, _9 i6 U& A
  3.         snap := &Snapshot{, _' C$ l% W5 T! L% E6 r
  4.                 config:   config," @* c7 Y; j/ [9 H# X7 I8 C- R
  5.                 sigcache: sigcache,
    ' Y' ~3 o; c  l9 m9 m+ V5 m0 H1 r# M
  6.                 Number:   number,, d0 P2 y3 Q. H& l2 O$ R0 l8 ^, j
  7.                 Hash:     hash,
    $ ]9 `: w2 }/ x
  8.                 Signers:  make(map[common.Address]struct{}),+ o* A2 x) L: m5 c# ^) r! D
  9.                 Recents:  make(map[uint64]common.Address),4 u" e* t: l1 H. \# A
  10.                 Tally:    make(map[common.Address]Tally),  A" z8 \: \1 R8 D- p( o
  11.         }
    ; N% G  y: t, Y5 d% s
  12.         for _, signer := range signers {/ G, `0 `3 G7 W" X
  13.                 snap.Signers[signer] = struct{}{}  @; i4 |8 J8 b3 v: N( }* Q
  14.         }
    / X% K9 I- h. o& c+ H2 F, q  V7 m& N
  15.         return snap
    ) m( p. Y) S( Q3 f. c: C
  16. }
复制代码

, U6 @1 ~# O5 B继续看下snapshot函数的具体实现:! `  b7 H- K3 j8 f* O( S+ f6 |  k! W% Q
  1. // 快照会在给定的时间点检索授权快照! G; V6 c1 ]6 ]9 ?- G
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
    - [: l% K, a. O, O
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints
    2 y7 Q2 F& n* {5 U
  4.         var (
    * E4 E, {% T3 h! y  i! T
  5.                 headers []*types.Header        //区块头
    % Y- G: @' Z7 q. _
  6.                 snap    *Snapshot        //快照对象
    , e' s: ~+ o* b
  7.         )' [( t, [' q' X- h0 D3 ~
  8.         for snap == nil {6 Q# F# |% R9 e7 ~/ B
  9.                 // 如果在内存中找到快照时,快照对象从内存中取- h- u% q8 c9 ?+ Z) j/ `2 [! O% s
  10.                 if s, ok := c.recents.Get(hash); ok {
    % _& @; T9 Q3 s6 S
  11.                         snap = s.(*Snapshot)
    5 R, c7 _4 @, r: _
  12.                         break0 H/ @: |1 Q" s( ]' Z
  13.                 }
    9 F" R- M6 N. q2 g5 q: N
  14.                 // 如果在磁盘检查点找到快照时
    8 E; U1 i0 w) t: B, H7 R* B6 {0 }
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号# ^- L, z8 x3 m1 O  q: n  m5 _* Q
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
    ! y/ U" m, q  E+ x2 N7 e
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)5 P5 \2 i+ g3 f: w! ~2 Z  {
  18.                                 snap = s/ {. s+ `3 {1 }% o9 A
  19.                                 break
    5 o6 q/ }+ o! u, \2 `, [) k+ K9 y6 c
  20.                         }  w4 T+ s/ I$ r0 p; Q# e: R
  21.                 }3 K6 S( \/ ~* S7 l* H4 @/ @
  22.                 // 如果在创世块,则新建一个快照3 O2 D" H5 Y+ `# X: Y9 J) ], ?
  23.                 if number == 0 {( H$ ]; q/ Y3 a2 _, q9 m3 v$ k
  24.                         genesis := chain.GetHeaderByNumber(0)
    # h& h% c, S$ S! y' ~6 r" X
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    1 y6 _  G! k4 Z% s/ U  j  S
  26.                                 return nil, err9 Z9 s8 L, a/ Z! h. f
  27.                         }
    3 e0 L; d1 D" {/ i0 G" [4 m' b
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
    + L0 Q5 u: ^+ V. e7 F; C. R! o
  29.                         for i := 0; i  0 {# {! L7 Q- I4 x2 ?8 u
  30.                         // 如果我们有明确的父,从那里挑选(强制执行), c4 ]1 G& L9 p5 |% r, {
  31.                         header = parents[len(parents)-1]
    - m0 Z6 u8 M3 e4 P& P. x
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {
    # l; d, C( W0 t2 `7 X& l5 R
  33.                                 return nil, consensus.ErrUnknownAncestor4 {* `6 N) P' x' j6 r1 q% H
  34.                         }
    8 O6 v% \, o- r' v
  35.                         parents = parents[:len(parents)-1]) l+ y8 t% a1 {7 ^% p7 O
  36.                 } else {9 E9 e- M+ `1 o( l
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    9 b- |# {0 S$ [1 m: y6 g
  38.                         header = chain.GetHeader(hash, number)
    / v7 ?$ J5 P0 ^" }$ F
  39.                         if header == nil {2 [. `' P! X: @7 _$ Z& c& v
  40.                                 return nil, consensus.ErrUnknownAncestor
    / `- x& R  [' p3 P
  41.                         }3 Z& D& a) V: E9 u* U
  42.                 }
    4 V( s5 T; g* N* o0 ~+ n% a: S
  43.                 headers = append(headers, header)! K; g, R# i- b' B0 V; z, n
  44.                 number, hash = number-1, header.ParentHash
    " s$ k. {( S# E' @
  45.         }
    : z8 }- Z) M' A& \) e0 A4 a; S
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面0 ]! C$ c: D! P* t
  47.         for i := 0; i  0 {
    9 P/ Q: k6 q. S8 R/ P& I5 L
  48.                 if err = snap.store(c.db); err != nil {
    0 H5 B$ V1 Q% j6 n: E5 M1 ]. ]; e
  49.                         return nil, err* K6 F! p7 q: _6 K% L( O- D
  50.                 }
    . ^6 Y6 U9 A; f8 G: D
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
    + D6 Q* |/ z. H4 g  q8 O% K
  52.         }( g7 K; v+ o) I. i5 L1 q
  53.         return snap, err8 y' W. x, M" ]: M* a$ ^: M& e
  54. }
复制代码
. ^1 F$ a. p! @
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?! O: M* R3 v; g6 O! D' Q: u" Q& W
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
    ! x" [  c( W7 r7 k( e3 Q
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
    : s" n  E5 T7 ]* U1 {
  3.           //可以传空区块头( ?" p4 }8 S3 w, Y+ w1 k7 H
  4.     if len(headers) == 0 {
    ; G  d; q9 t3 E
  5.         return s, nil/ h* d& `) J3 j; m- g' `
  6.     }" `% k! M& E6 v. {2 V  I
  7.           //完整性检查区块头可用性& k# J4 c; H, c
  8.     for i := 0; i = limit {
    0 v# j1 `/ q7 P' g6 H: G
  9.             delete(snap.Recents, number-limit)
    3 C0 ^- U' [. T6 x$ t' {+ V3 j% b* V
  10.         }
      d+ x+ B( B0 I" u
  11.         // 从区块头中解密出来签名者地址
    9 L4 D* {' O% c
  12.         signer, err := ecrecover(header, s.sigcache)5 E, a. y4 k5 {5 C4 a5 {
  13.         if err != nil {
    7 R5 ]# U- I# ]* w1 H
  14.             return nil, err& u5 B9 G  [0 J
  15.         }
    / d5 m' u7 \5 Y6 o. i9 l
  16.         if _, ok := snap.Signers[signer]; !ok {
    7 H$ {* L, @( L
  17.             return nil, errUnauthorized) O; Y/ E2 ]* M8 C' t5 A3 H' g
  18.         }
    ; d; q- ]( n3 I  }/ p
  19.         for _, recent := range snap.Recents {% |9 z: H3 ^8 U$ L# t
  20.             if recent == signer {
    ) Y1 ]+ y+ p$ C7 ]
  21.                 return nil, errUnauthorized4 ^, U; T, ]6 G: {& V2 ?& ^5 v
  22.             }; \+ B. F- L' X& n
  23.         }
    $ W0 K7 l- _5 O# D! Q$ T0 H) X
  24.         snap.Recents[number] = signer
    & n! b) W% }! R9 ?- x
  25.         // 区块头认证,不管该签名者之前的任何投票) f5 }3 ~. d' v9 T
  26.         for i, vote := range snap.Votes {
    ) m3 \4 B; X+ I
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {! \: v" \: r" p: M/ H) S- y6 {( U
  28.                 // 从缓存计数器中移除该投票
    . `6 j# O) m/ b0 S- `& ]
  29.                 snap.uncast(vote.Address, vote.Authorize)
    & ~, l* T. i! M. I7 b9 D
  30.                 // 从按时间排序的列表中移除投票
    5 n5 S6 `/ v4 D( j) }2 ~! }9 p$ Z7 E* L7 t
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)/ x( {3 {6 B/ U& m( m; s
  32.                 break // 只允许一票. v4 q& J, b# }+ ^" Y
  33.             }- f$ S! b( I5 Q. x# z; w8 n
  34.         }
    ' K0 s0 L2 ]# q5 b4 e* ~6 s. }5 f
  35.         // 从签名者中计数新的投票+ {  R* \  N) N/ q6 q
  36.         var authorize bool; F0 n4 m/ N( B' j4 F
  37.         switch {5 z+ s( o! ?, }+ V" x: |
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    . X/ `9 a1 W, [  x6 t
  39.             authorize = true
    * \" e; J2 S, P, Y& u) r
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):
    ( L3 j5 A' g* q: }5 w( C
  41.             authorize = false
    ! T  A/ z: z4 N0 H* ?, @
  42.         default:
    ) w! f( G$ Z2 n
  43.             return nil, errInvalidVote
    2 d5 ]7 z: K8 y8 B
  44.         }2 o4 H- ?) }% m8 p" x/ m
  45.         if snap.cast(header.Coinbase, authorize) {+ r0 q2 [+ Q& }3 i" W! E* {5 a
  46.             snap.Votes = append(snap.Votes, &Vote{
    6 i$ `9 E  N% a5 ?, v
  47.                 Signer:    signer,9 C  K+ X7 G7 v- j* d$ n
  48.                 Block:     number,# w( ?3 F" ]- O; a" y
  49.                 Address:   header.Coinbase,8 F( @# h) c% g8 ^& B) h
  50.                 Authorize: authorize,% w$ `3 N/ b% V" \, Q
  51.             })4 f0 V$ u3 w7 k  k4 a. G* i1 r
  52.         }
    1 A% O. W' [7 E% i0 P: J
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表( r6 T! `2 V0 e7 X
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
    0 k5 w$ O  F, h3 c1 s9 b7 Q
  55.             if tally.Authorize {' x/ \$ z8 l7 b8 D' U1 ]6 H
  56.                 snap.Signers[header.Coinbase] = struct{}{}
    ( u8 @( K5 \- Z, i4 X8 D# D; p, w% }
  57.             } else {5 K" |+ v) u5 ?
  58.                 delete(snap.Signers, header.Coinbase)! i* _9 w* v7 T' y) c8 G6 H8 d
  59.                                   // 签名者列表缩减,删除最近剩余的缓存4 e! g% E2 H% i) \3 |& b3 S2 C
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {) _3 g. q2 g0 O( t3 Y
  61.                     delete(snap.Recents, number-limit)
    ! J7 S6 H6 m; R/ x# ]( z8 l
  62.                 }
    ) h# o7 Y$ C* c( q" D, O' B5 }
  63.                 for i := 0; i
复制代码
- m: ]! j+ H% j0 ]2 u3 b  f
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。
' A) o1 C. Q, o0 V, T; o6 \6 u区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。# D# j0 Y! F' p9 y: i/ @
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照9 r$ b3 V- B  f
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
    4 ?# d8 ]; _9 p
  3.         // 不支持校检创世块
    ; ?9 v* d$ e1 ~  L
  4.         number := header.Number.Uint64()
    ; V8 Z6 I1 r# g" `9 E
  5.         if number == 0 {
    - d. {9 e+ g  d8 Q7 z
  6.                 return errUnknownBlock
    ) [8 o( ^* f* z, [
  7.         }
    : K# M: V- h) T* m2 R, z9 h) j' F
  8.         // 检索出所需的区块对象来校检去开头和将其缓存
    ) _1 G! {( k1 {. \5 p
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)2 [7 B2 T; o& S, P. t
  10.         if err != nil {
    " c. T0 @( K# l
  11.                 return err' k7 H# q5 ~( ^, g
  12.         }
    $ f. K) g- K2 R8 i4 I  p; H* `
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址+ P' s# k+ {" g3 i* V& H
  14.         signer, err := ecrecover(header, c.signatures)
    * @  E% {6 r5 ^5 J, L- G9 C
  15.         if err != nil {7 m( o# _* W- V) G. I
  16.                 return err0 u4 G. f- q1 C
  17.         }1 a& O  ?# ?# n8 F
  18.         if _, ok := snap.Signers[signer]; !ok {9 t; e# G8 P1 K, H3 U" j0 }1 I3 v# B
  19.                 return errUnauthorized- Y# d5 q! Y- g! h; ]8 ^
  20.         }
    # ?/ T" X3 v6 u
  21.         for seen, recent := range snap.Recents {# ]3 w3 j  H/ v5 Y
  22.                 if recent == signer {# K& u5 Z2 a7 S
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等
    ' d2 Y. {+ U7 f, |7 W+ c0 S3 {+ ~' l+ c8 M
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {% L& @1 ~: ~4 M# j! ?+ q
  25.                                 return errUnauthorized" D/ k: m# h, `3 c
  26.                         }
    # W7 w! `, [6 H6 |- g# R6 c: P# g' V
  27.                 }
    0 @0 l( ^( W7 G, Q: n" }
  28.         }
    * }; w$ H3 ]3 R# K
  29.         // 设置区块难度,参见上面的区块难度部分
    , b+ }* H  u0 l* M: X8 J
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)+ Q, B' N$ _! B: }
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
      d0 D; d1 l0 D9 d8 Y
  32.                 return errInvalidDifficulty
    . F1 S) V9 _) t
  33.         }
    1 P( n, D' L4 T1 k6 c
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
    9 n  [9 a: h4 g* G# D1 S
  35.                 return errInvalidDifficulty+ u: P/ Q, j6 [$ X! U9 a
  36.         }
    9 i% ?2 o* O6 `% L+ \# a9 C
  37.         return nil
    " C( x0 c  I; b. `6 W1 ^1 j6 y% B
  38. }
复制代码

2 x1 h" g9 q; l" t  M前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?& \& f1 D/ X! }. Q
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:- U( }/ ?% `, H, h6 j
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中
% o/ |+ z( p- ~1 n
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。0 O$ R' K& O: ^, n* a/ M
  2. func (api *API) Propose(address common.Address, auth bool) {
    5 t. M$ J8 A) Z9 p" \( r2 w
  3.     api.clique.lock.Lock()4 J6 n9 n5 c0 L$ G. y/ v! c
  4.     defer api.clique.lock.Unlock()' S1 n* \+ D8 t7 I
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    $ m, c! X4 M' E0 \4 ^
  6. }
复制代码
7 x$ @, Z9 m. O* _
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;& X% N; v8 z: [, G( D6 b1 }. {- e% b
  1. //Clique.Prepare7 K+ I% m5 |+ b, [$ n
  2.                 // 抓取所有有意义投票的提案$ E' w! w4 \4 _! q  v* W8 }  l
  3.                 addresses := make([]common.Address, 0, len(c.proposals))0 j, O+ q. r% B* D& c8 c# B
  4.                 for address, authorize := range c.proposals {: P) w$ D$ I# o8 B! Q% m* ^
  5.                         if snap.validVote(address, authorize) {' ]" _9 l( V9 m- H* A7 J
  6.                                 addresses = append(addresses, address)( @. ~7 \; P, b3 M  z
  7.                         }
    ! W0 \: f  j1 A" Z: `' s' ?) @- b
  8.                 }
    * ]! L) [; V5 E3 M0 u$ q7 c
  9.                 // If there's pending proposals, cast a vote on them
    6 {: t# f1 r8 H# ^- z! ^$ e% |
  10.                 if len(addresses) > 0 {' v  P* _, x1 A) I* F8 |' P) R, M! C
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。
    8 S$ t+ M: k, }( @" }
  12.                         // 通过提案内容来组装区块头的随机数字段。" x" A6 p! q: i+ f$ Q
  13.                         if c.proposals[header.Coinbase] {9 i6 Y0 W$ ]/ {5 R2 @: d
  14.                                 copy(header.Nonce[:], nonceAuthVote)
    $ {# v+ t( l" V& D- y! Q/ P
  15.                         } else {
    * i' [, l7 e* o0 [% I& i" v
  16.                                 copy(header.Nonce[:], nonceDropVote)$ Y( x& g: D0 L" h7 O: }. B. z" ^7 b7 |
  17.                         }
    9 j* E! t- j1 d
  18.                 }
复制代码
& G2 I" W9 b! Z2 s) R  U7 ?
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare
. i+ |& d" ^! C
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {
    3 ~7 U, d  G. E' M4 s
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    ( K( \8 ~& u# Z# M. H8 i6 N6 W
  3.                 return+ E- @* w: l+ X" a7 C* ^7 f. P; A, t
  4.         }
复制代码
! k" q' E6 G- |% E4 X4 c) g) v$ R
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法, \5 G5 ~! \% @0 r- ^# p" e

2 _$ N* \0 F& y3 J/ S3 S1 b, W/ L1 N% ~5 `9 T& I; D

1 D' `- \( V2 k. m以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3