Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2682 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。
! w* ]1 T2 e: B6 e# F3 R$ k
  1. func (s *Ethereum) StartMining(local bool) error {6 I! Y9 E  `' r# E. i3 |
  2.         eb, err := s.Etherbase()//用户地址
    : A8 H0 B' L: f$ G$ `1 F9 A) n3 k: T
  3.         if err != nil {
    - z" e" }! Q2 J& M9 l9 S
  4.                 log.Error("Cannot start mining without etherbase", "err", err)
    2 J- m" s( Q( ]' C8 N: ?
  5.                 return fmt.Errorf("etherbase missing: %v", err)7 A/ d1 J: }1 y* Q. F6 M/ S
  6.         }" }5 `1 H. X3 r$ o
  7.         if clique, ok := s.engine.(*clique.Clique); ok {% O4 x$ X  a4 C  N2 e
  8.                 //如果是clique共识算法! y; A/ _' A7 g0 F
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象
      r" `9 Y* v1 ?  L1 \( y( J
  10.                 if wallet == nil || err != nil {
    2 ~% ~' l$ g: `2 D& [9 @' b
  11.                         log.Error("Etherbase account unavailable locally", "err", err)
    * h) d) R2 x( h! Q- e+ p
  12.                         return fmt.Errorf("signer missing: %v", err)
    8 ~) I1 W. O, G( |* Z/ D3 q
  13.                 }
    , v  x' h6 O5 y: G3 S) V
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法
    % y' L& S( V4 x1 k* q
  15.         }
    # \/ C6 _# d3 ]* M
  16.         if local {' T6 m3 X# j  V
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
    $ l; Z2 |% O" F+ s
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)# A) J1 @0 X( L0 n- y% z; M* V
  19.         }
    8 l6 h: x5 }  V# s% R
  20.         go s.miner.Start(eb)' {! ^( l3 _* g0 G8 C1 u6 |% c& Q" _6 n
  21.         return nil/ [$ ~! U- L7 ~0 D! {
  22. }
复制代码

. L( ?6 Z2 F. Z这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
9 n8 P  I% B' c6 {Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:6 T# M* h- j4 @
type Engine interface {
' ]7 @7 a: ]6 u% F/ Q% A; L, y: U        Author(header *types.Header) (common.Address, error)) y- T- I9 _" @+ ]+ I' A
        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error/ S# _9 g9 }( y4 l  Z5 ~
        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
$ T; e6 p* n& R# e% s% [Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:# }; G9 ^' g0 c5 v; z1 V: ]
  1. type Clique struct {& R6 V" t& J6 E$ l% ]% l9 I! D1 V
  2.         config *params.CliqueConfig // 共识引擎配置参数
    . `: v1 g8 r0 Z, d
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点  P/ v! k$ @; y7 K" Z0 |9 u
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组
    6 ^6 c* b/ U" C% g; |0 n  }) Y
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿
    4 k8 g+ ^. R. I
  6.         proposals map[common.Address]bool // 目前正在推送的提案
    1 i% X* `/ ], j( [' W6 a9 _
  7.         signer common.Address // 签名者的以太坊地址+ Z; Q: \7 y* G) c6 o5 o
  8.         signFn SignerFn       // 授权哈希的签名方法
    7 T  F6 t2 j) H$ p
  9.         lock   sync.RWMutex   // 用锁来保护签名字段
    + ]# u/ l0 a' C8 q
  10. }
复制代码

3 t2 N, A* i6 x/ f  W0 l顺便来看下CliqueConfig共识引擎的配置参数结构体:
4 s( Y4 j! _- p2 w* M. d
  1. type CliqueConfig struct {
    . w4 f- v7 ?/ V) W6 z2 |  K9 ^
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s). g- @' L" E/ y* m! m
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    ' c( l+ U9 P8 V3 c. n
  4. }
复制代码

5 Y! g8 P- p# m' c1 C* M& r8 a, e- X在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:7 E; D+ B4 ^) h0 J
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
    * J6 O, j9 a; ~5 V+ U
  2.     c.lock.Lock()
    + B6 P1 f$ P1 _. j  H- k/ j
  3.     defer c.lock.Unlock()4 \2 I" n# e8 @5 W
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块5 x, W/ u& g: Y5 c; b& B5 `
  5.     c.signer = signer% y! j- v  d+ O
  6.     c.signFn = signFn
    . x  [  o$ M  ?' ~
  7. }
复制代码
8 a8 S7 a/ Q6 L# p
再来看Clique的Seal()函数的具体实现:
4 T7 G, }3 z% U* _//通过本地签名认证创建已密封的区块- Y, U% n' l  |  K
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {1 X% C& Z# T- k3 Q9 Z% j. V4 e  Z9 ?' L
                                log.Info("Signed recently, must wait for others")
1 v6 S; |" h# }2 g: I/ O6 f5 q                                # p$ e- Z' N" J  b1 a- U8 g
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名: C! _  d4 J; y4 f0 C" \8 ?% s
signer不在snapshot的signer中不允许签名
! }" W8 g  a. D* P% f# h4 isigner不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名& b$ J, A! ]- F3 t6 A1 }
签名存放在Extra的extraSeal的65个字节中! \  f" Z% P+ W2 }8 e7 t
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。
6 d  b! s( P6 w+ e
  1. //snap.Signers是所有的认证节点$ h, x2 b% B$ ]* e
  2. for seen, recent := range snap.Recents {
    7 H& ^" Z& W! T
  3.     if recent == signer {
      c4 u& w% v! n% }. W
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {0 C0 g1 \% l% _3 f  e$ z
  5.             log.Info("Signed recently, must wait for others")
复制代码
2 ^2 F% ?/ t. m  @" {9 _. d
            ) ]4 b0 O) I  W% v, e# h! R
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。  r! t% L2 @* h; B7 w: X6 y
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。$ m  s+ B2 k, n% x% g5 E  s7 k( h: G
diffInTurn = big.NewInt(2) ( ^; C- b- e4 v2 [- S
diffNoTurn = big.NewInt(1) , G' Y4 E% I3 H( N/ [4 q- @
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
* }4 V+ C1 ]+ _8 Q// 通过给定的区块高度和签发者返回该签发者是否在轮次内
1 R( P" I2 g1 C8 {5 i; p: w5 s0 K' E
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    ! S3 u& R# l/ d8 l  g
  2.         signers, offset := s.signers(), 0
    ' p! @3 V1 {' l* p
  3.         for offset
复制代码

$ c) x+ q  W8 ySeal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:) S+ p$ n3 `2 j) R) |! Z0 ^3 `% Q
// Snapshot对象是在给定时间点的一个认证投票的状态, @" j4 {& v+ n! x
  1. type Snapshot struct {  i0 ?6 L  e+ |  h4 a) V. b6 n
  2.     config   *params.CliqueConfig // 共识引擎配置参数3 K. x7 o1 p( v, p- u5 e8 J1 e
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。
    ) R% Y4 K+ C0 {: T% b3 s0 B7 }
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号
    , N3 C1 O& h/ C+ {4 G; x. l3 ]
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    4 m; l1 [$ @: s5 h: W+ \1 v
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表0 E% o2 p- B5 \" B
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址
    - {2 A+ z  H0 x! K: m8 k: t
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。& V( W& s( }: n6 \4 s5 U: R
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。
      J6 L# ?( F0 L; o  y' I
  10. }
复制代码

0 X  U% w/ [. E1 z: E快照Snapshot对象中存在投票的Votes和记票的Tally对象:
( k& H5 T  w7 F, M" a) Y
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。
    9 e. d/ {* I' i$ C% l
  2. type Vote struct {
    6 q9 B0 U9 Q' o' j+ c
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
    ( Y' X2 P1 E' m$ [' {  ~
  4.     Block     uint64         `json:"block"`     // 投票区块号$ c) I' E( z- D8 u! z, l
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权
    6 C4 H3 Q0 u' @; V, X9 E
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权# \% E6 {1 Z: w4 [1 E- b4 h
  7. }% e/ X- @% U5 b* M0 T
  8. // Tally是一个简单的用来保存当前投票分数的计分器" t' V! [9 z0 x+ X7 F2 ^
  9. type Tally struct {! @6 V, V+ v+ O5 ]* g
  10.     Authorize bool `json:"authorize"` // 授权true或移除false
    ( v# B. i; ~* s" r6 t' p+ ^
  11.     Votes     int  `json:"votes"`     // 该提案已获票数
    ! [, L2 H4 |3 W) i
  12. }
复制代码

: Q' P6 p' {* K; P. \Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:1 q# K  {0 ~- Z0 l# t( f: U; E
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {) d$ V' Y" b( m4 P  L
  2.         //使用Database接口的Get方法通过Key来查询缓存内容: O, Z, z' x& ?% c2 j
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    8 `/ k$ M6 H. d% O3 K* |& M. M
  4.         if err != nil {
    + I8 E7 I; S: u" C% |" K0 c
  5.                 return nil, err
    ; [& p5 U; C2 @. @# r6 [/ }8 G/ S2 b
  6.         }  g' s6 q; H8 S1 m2 q
  7.         snap := new(Snapshot)1 t$ B/ S  a2 B
  8.         if err := json.Unmarshal(blob, snap); err != nil {
    * R) ^  P1 N% J: M* |
  9.                 return nil, err
    5 @+ B1 W; R1 \% t
  10.         }5 s8 p6 d9 L# n) V
  11.         snap.config = config% u' V8 H! e  J& ~
  12.         snap.sigcache = sigcache) {# a8 n7 ^7 h$ ]3 u5 r
  13.         return snap, nil
    0 [. q! K9 n5 L" T& T9 M  @
  14. }
复制代码

) D7 Y& m* R( K& h4 OnewSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:. m6 o8 z3 M1 g. K# Q$ |  E9 a: x
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
    ; ~5 `3 ~3 R3 D. Q: x. j
  2.         //组装一个Snapshot对象
    * S! J8 S# @$ [6 E$ Z: _  e4 `8 r
  3.         snap := &Snapshot{- e: R  d; t: D2 K$ a
  4.                 config:   config,
    * [8 y8 Q% [0 C" H# H
  5.                 sigcache: sigcache,/ L5 \; Y' M2 H
  6.                 Number:   number,
    # L$ U. w+ P4 i
  7.                 Hash:     hash,
    / G0 Y( J, X. ^1 j
  8.                 Signers:  make(map[common.Address]struct{}),
    ) }9 Y* o) _, D/ ?( F* W" `
  9.                 Recents:  make(map[uint64]common.Address),; ?# _" [# V6 W9 a( L" `, ]
  10.                 Tally:    make(map[common.Address]Tally),  D( h8 c: C) ^3 |2 i, Z2 _- q% j
  11.         }
    8 q5 j9 D8 y( ?
  12.         for _, signer := range signers {: o2 l+ M) `7 @1 u9 c. h; l
  13.                 snap.Signers[signer] = struct{}{}
    / [/ H8 C, C/ @. A" B0 i
  14.         }2 T8 g4 O" F" T( B& U
  15.         return snap
    7 \3 p0 ?6 L; h
  16. }
复制代码

2 F  S6 }; ^9 {继续看下snapshot函数的具体实现:
, X6 n. Z8 T! k7 V# H# v
  1. // 快照会在给定的时间点检索授权快照
    4 i* k7 N) O/ F; A; n2 n1 P
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {$ D0 S" h6 T/ l1 ^2 r
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints6 f  p* s' A* D9 Y. h- o
  4.         var (- A7 x8 e' ^4 A; V; Q  k% ~) d
  5.                 headers []*types.Header        //区块头
    + o; U& ]+ B# Y! R* ]# Z
  6.                 snap    *Snapshot        //快照对象
    + q% p6 @  k* V/ q* a
  7.         )' v  w4 a# I; h0 r
  8.         for snap == nil {
    8 [& E% p9 p1 x8 a
  9.                 // 如果在内存中找到快照时,快照对象从内存中取
    ) V# R0 ?. ~0 G7 p
  10.                 if s, ok := c.recents.Get(hash); ok {
    $ Q+ u% q. \8 R6 [6 T
  11.                         snap = s.(*Snapshot)
    ) t2 I# G" z2 Z7 H6 O# q$ {  f% P/ o
  12.                         break
    5 M  [( L6 c) Z9 E+ U$ F' Q: R
  13.                 }
    ; {$ o4 p* K# M) _. E& D2 s
  14.                 // 如果在磁盘检查点找到快照时, j$ _; Z3 N1 K: a) f
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
    0 x9 y6 x+ G2 i0 N$ `) K1 a
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {2 L2 H' }# L; f1 J& L7 m
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)* b" h8 H. c1 ~- ]5 p3 h7 w
  18.                                 snap = s3 X- W& i0 O5 Z* @9 u
  19.                                 break
    & ]. V- t2 X( b# L4 P' E; F
  20.                         }
    6 _% ?4 g' ~% d3 i3 _; h
  21.                 }
    " n  g# C/ a4 a9 s4 L' ]
  22.                 // 如果在创世块,则新建一个快照
    ) A) L# b$ d! n% z! p" ~6 D4 P
  23.                 if number == 0 {
    5 @' z0 L: E5 g. W
  24.                         genesis := chain.GetHeaderByNumber(0)+ x4 Q/ O/ O& V1 J
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    $ e2 e! n3 b4 [9 `* ?5 t
  26.                                 return nil, err) z* b. M6 w7 _
  27.                         }  A; q4 C) G7 E8 |0 n
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)) A# t7 \, H$ v' i
  29.                         for i := 0; i  0 {
    0 s$ k4 [4 i- i  \9 o
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)
    4 k4 D% S% ]7 O+ L# n
  31.                         header = parents[len(parents)-1]
    ) ]0 H% B* R# t' V$ i8 d* [$ U
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {
    * ~. k- h8 C% T) D
  33.                                 return nil, consensus.ErrUnknownAncestor$ r( K! y- x1 H) t- w! A/ D
  34.                         }) C" ~% f. v8 V/ ~6 P8 O$ q5 N( w
  35.                         parents = parents[:len(parents)-1]0 a. i; }: m7 K: w" p; z
  36.                 } else {( _6 f( ^, i. _: w9 i
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取8 S, B3 U) A& [5 T1 H% a0 L
  38.                         header = chain.GetHeader(hash, number)
    ' R2 ~* w' u+ c" W- c1 {; d
  39.                         if header == nil {4 u. b; y" m+ I7 x& E4 \3 K: T9 O
  40.                                 return nil, consensus.ErrUnknownAncestor) j! l+ A5 K% s
  41.                         }
    ( C! x6 i/ n2 n' s/ U8 n) G
  42.                 }
    ! ~6 T$ M: |9 E% s
  43.                 headers = append(headers, header)6 R1 c  |6 H4 A0 Q* f- s0 X
  44.                 number, hash = number-1, header.ParentHash
    1 L  S8 T  m8 y7 M0 x2 H* C1 o- N
  45.         }
    4 \8 z3 k# ?  R. ?# z. z2 F
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面" F  |- t' U. b$ R# \
  47.         for i := 0; i  0 {
    9 A5 i7 ~2 H, B' W
  48.                 if err = snap.store(c.db); err != nil {
      [# ]# ~$ z* t: I% O3 U: O
  49.                         return nil, err
    ( |' [7 U; W1 R- ]
  50.                 }
    : F" B9 S" \$ s9 M8 ?
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
    2 v- E/ S5 c& w! k2 I
  52.         }
    ! S: c! k0 I% ?* R) A' A( Z# a
  53.         return snap, err
    9 z( d: t. m" U
  54. }
复制代码

1 s) ]2 i$ c2 _$ m6 w/ A2 Y- k在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?
1 C7 y4 x, I% R# R! U
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
    / M6 t' Q; i( j3 |# W0 G8 |
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {5 s% O1 N# n6 ]7 b  d
  3.           //可以传空区块头
    1 t& Z4 }7 A- V1 F9 t6 _
  4.     if len(headers) == 0 {3 o! y0 E! n8 \/ j- [
  5.         return s, nil
    % b; R9 Y6 y+ r
  6.     }- ]' L/ l- l$ \4 K. \( k% o
  7.           //完整性检查区块头可用性% o- Z$ ~- c- t$ @
  8.     for i := 0; i = limit {2 z  S3 {" u. F% _; ~; C  n
  9.             delete(snap.Recents, number-limit)
    + h7 C" k: E- N6 n' i
  10.         }
    * y" q% N0 _1 \$ P- F0 _" l
  11.         // 从区块头中解密出来签名者地址  L+ X1 N1 l. J
  12.         signer, err := ecrecover(header, s.sigcache)
    3 D$ S. J. H* ^% Y  p- K
  13.         if err != nil {
    4 v! Z- T. w& v
  14.             return nil, err& a. V* z: z! y( y
  15.         }5 r3 k& e7 G9 q
  16.         if _, ok := snap.Signers[signer]; !ok {
    - E1 ~% w) s8 j. s) b
  17.             return nil, errUnauthorized) f5 e; ?2 M9 t8 D2 P
  18.         }
    7 n% m) l0 ~$ e2 H. N1 E5 i+ L/ p/ @
  19.         for _, recent := range snap.Recents {$ Q. K$ C. v  k7 e( T( I
  20.             if recent == signer {! H1 l8 p  ^8 z. z9 [
  21.                 return nil, errUnauthorized
    0 B2 H' ~( i8 }
  22.             }
    4 I0 z8 `+ c! k% L! E
  23.         }& @- i, M! ^& h; i0 T3 C7 L/ k
  24.         snap.Recents[number] = signer' R9 y! S2 p" v$ K0 @
  25.         // 区块头认证,不管该签名者之前的任何投票0 L- @% t" h9 ~, u/ X* j, {
  26.         for i, vote := range snap.Votes {
    ( E2 j  ~$ P5 z9 V, S4 e+ b
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {& v( Q) i1 l3 P" N0 R
  28.                 // 从缓存计数器中移除该投票
    5 x1 E& _4 H7 [& k, m& j2 y& N
  29.                 snap.uncast(vote.Address, vote.Authorize)
    ' ?9 a% p! S# Y5 T" g
  30.                 // 从按时间排序的列表中移除投票! g' p& I% l  J
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)9 A! l+ N8 F& D* o$ K4 g# e
  32.                 break // 只允许一票/ U# `7 O# i; X3 `$ s1 a
  33.             }) ]3 ^% O+ k; ]" A8 d6 j
  34.         }
    0 M! J! Z. Y! S6 ]
  35.         // 从签名者中计数新的投票2 g4 H/ j3 O1 g5 T* o) S5 |
  36.         var authorize bool. L8 R  p0 M; v. P/ X- E
  37.         switch {
    9 M2 G2 \! k$ J1 A4 C
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):5 }9 v' R+ L: N, _
  39.             authorize = true
    ; ]- I- }& c6 z$ Z
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):
    8 P" O' w! b0 O1 S+ M9 p
  41.             authorize = false
    % p% N; ^* J: y5 E
  42.         default:, g3 U: q5 ^+ ]0 X) [$ S& f
  43.             return nil, errInvalidVote
    , b6 c3 v! a% X( S+ \  G; Z
  44.         }, n% ~) M" s  Y$ {7 i& L
  45.         if snap.cast(header.Coinbase, authorize) {
    * L4 ~5 q6 r: X' r
  46.             snap.Votes = append(snap.Votes, &Vote{3 P3 W& c( f- K" `4 b6 k/ J
  47.                 Signer:    signer,
    8 Q: D6 q. w, h; {' R. }/ K' }% j
  48.                 Block:     number,% N, K0 B/ E2 A) I$ L, r8 F; U
  49.                 Address:   header.Coinbase,) c/ f3 h& O6 v# R9 C. X7 o+ v2 A
  50.                 Authorize: authorize,
    ' _1 ^; s# d) f
  51.             })
    4 S6 p% I- k4 g" m$ T
  52.         }; _& m% Q6 H+ j) D
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
    1 W4 s+ r/ ]) T9 X: u  I
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {9 K2 C/ J2 H1 K$ j
  55.             if tally.Authorize {* y: Y$ `3 r; v8 k
  56.                 snap.Signers[header.Coinbase] = struct{}{}
    5 {( R1 j& M- P9 U2 A2 S0 D* A
  57.             } else {2 H- ~% b8 W+ r" H
  58.                 delete(snap.Signers, header.Coinbase)
    ! H# @6 P0 _1 ?7 }/ \* T
  59.                                   // 签名者列表缩减,删除最近剩余的缓存2 c7 x' b1 B  B4 j
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
    5 C7 \" ]# M: k
  61.                     delete(snap.Recents, number-limit)
    ; }! V4 a( j  u7 E% ^
  62.                 }
    & z$ ], I! ^! {3 X& c
  63.                 for i := 0; i
复制代码
- K% C: J! p; o
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。
$ L* N- r5 ?! K7 t7 ~2 }  _! M区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。$ k) C! ^+ k$ r( W- K( T
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照& n# b/ }$ ?4 O7 @; \
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {5 L; X3 o$ X9 i' ~2 ?+ i% Y- K
  3.         // 不支持校检创世块4 R9 o6 A$ \$ P9 E
  4.         number := header.Number.Uint64()& b" c% q# e+ y/ F+ o
  5.         if number == 0 {- G% y2 e! d! s, D
  6.                 return errUnknownBlock
    * B! `  {; g+ v7 n( ^0 V$ a
  7.         }
    8 n7 d# C# ~7 u6 J( {
  8.         // 检索出所需的区块对象来校检去开头和将其缓存' J" W1 K$ I) [- Q
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    6 j; x/ w- X1 [: I. ^( ^# _% r) z
  10.         if err != nil {
    ! ^8 |' S/ F% t" q
  11.                 return err
      M8 p" L; k- f) s& D
  12.         }
    , ~* ~/ t, y) p) y% @
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址2 f0 u, y) o2 m* D
  14.         signer, err := ecrecover(header, c.signatures)
    * L# g3 k, J  [8 I+ p' T
  15.         if err != nil {9 o2 y/ o' ]# e) _3 p) w  o
  16.                 return err
    5 K8 D1 t# L, p/ y3 t
  17.         }
    $ V( y$ x7 K9 T- h
  18.         if _, ok := snap.Signers[signer]; !ok {. ~! T) A, j8 g! Q8 W
  19.                 return errUnauthorized/ n' Z* Q' {7 e' A
  20.         }
    & t# `. q4 L1 d7 @) ^$ V2 B6 _
  21.         for seen, recent := range snap.Recents {
    * K- a9 }% Z/ s2 a+ S
  22.                 if recent == signer {  @6 n1 c% O, z$ C9 I* j1 H% O
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等  L7 C  e- m/ T2 B- q" B
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    6 l4 g4 A6 v4 P1 K. A- H
  25.                                 return errUnauthorized
    1 R" e1 B; {" G% N3 ]9 C; c
  26.                         }
    4 k+ f# l1 A) V" x! C
  27.                 }
    3 u. d5 d9 e; F% f
  28.         }" O4 }3 `/ J( M- [) z
  29.         // 设置区块难度,参见上面的区块难度部分
    - O( ^2 E1 E1 W3 C
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)
    ( g) |- Q; P$ K0 f& c5 F& E  s
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
    + D2 @4 {' @' @; C
  32.                 return errInvalidDifficulty
    " r9 R, j0 q  A& H& r. P
  33.         }
    4 D( O+ A3 v7 U: R% w0 G! U9 ?4 L
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
    6 p+ k  X1 x0 h7 Z6 v
  35.                 return errInvalidDifficulty
    * W  y# n. E! u! G, t( C7 Q
  36.         }* P# k% \) e# j# b
  37.         return nil2 _$ B6 Z& R, \  @/ _( U
  38. }
复制代码
2 _0 d0 H: R2 X6 ^
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?# v& \  ^( z) c
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:! U( G# L6 V) D6 r5 U
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中8 X) S: `! I" @# }- d* {) d
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
    1 o6 Q0 Y- d% W% m; i6 L4 n. n% q
  2. func (api *API) Propose(address common.Address, auth bool) {  Q0 K8 E9 G* E3 t6 Y
  3.     api.clique.lock.Lock()
    6 b* y3 @+ X3 A  c1 ~
  4.     defer api.clique.lock.Unlock()
    0 v2 E, H' o8 l3 C- q
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    8 ~0 J( V: Z! H# B8 J, S
  6. }
复制代码
1 F& G; I" Z7 U& G' `, A0 t
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;. l3 w. q- L6 ]; D
  1. //Clique.Prepare9 O, ]( {+ M5 I9 `* i
  2.                 // 抓取所有有意义投票的提案
    6 @8 u1 K7 o' G& {% T3 r+ E# i
  3.                 addresses := make([]common.Address, 0, len(c.proposals))" r$ @  @( f3 l) A2 Z* v4 b
  4.                 for address, authorize := range c.proposals {
    0 f8 D; ^( i% M& P
  5.                         if snap.validVote(address, authorize) {; z9 `: O' U# n, F" [
  6.                                 addresses = append(addresses, address)
    * @3 l- \! e% C& a; P8 M
  7.                         }  i0 t  v& G# [. A
  8.                 }8 z5 d$ s: D4 n4 v; {& i. k
  9.                 // If there's pending proposals, cast a vote on them
    8 u* \' w. D! _& m8 a( ?7 `
  10.                 if len(addresses) > 0 {% \. W0 I; [3 Y7 s& K1 H
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。
    7 o6 Y" \$ r4 j' a0 g( U
  12.                         // 通过提案内容来组装区块头的随机数字段。
    ! {; [! ?9 a- ^0 C( J
  13.                         if c.proposals[header.Coinbase] {2 o/ @* M; X. b1 h: S& l
  14.                                 copy(header.Nonce[:], nonceAuthVote)8 H8 q! `) i8 w) M9 P& q
  15.                         } else {
    # c( d4 w% H, k, X" _
  16.                                 copy(header.Nonce[:], nonceDropVote)' v1 L# [! o2 X6 p6 {
  17.                         }
    ' ^% [  F" j% c; n$ d
  18.                 }
复制代码
+ b! @3 E- H: g/ n& Q( s$ q
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare: X  ?# j8 e: B' ?% l" q3 P0 a7 K8 j
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {" o( J9 L2 F8 N3 f. m
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    3 E: z3 i( w( Y# d
  3.                 return2 G5 O  ]; `( ~
  4.         }
复制代码

6 J: c4 \& m: ~# `5 m7 b其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法
0 Q! X! }0 o( U: F& ?/ `: x; L
' D$ j% A( M  l4 U! ?9 @* o
8 L; k3 C9 _0 [6 }& v% d4 |7 n! t5 q
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3