Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2769 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。: ]/ C  `& P! l
  1. func (s *Ethereum) StartMining(local bool) error {4 V& u% J( c* A6 i$ t; N
  2.         eb, err := s.Etherbase()//用户地址) T) l' I/ T' i8 P
  3.         if err != nil {: E# I; E1 z# g5 }
  4.                 log.Error("Cannot start mining without etherbase", "err", err)1 \9 S0 F+ }# J3 V5 |- t' x
  5.                 return fmt.Errorf("etherbase missing: %v", err)
    7 l! n7 x, P4 m$ E, X7 W& D6 N9 W, f
  6.         }1 ~6 G- ?6 c/ r$ I& A2 @, N
  7.         if clique, ok := s.engine.(*clique.Clique); ok {* ~) n  J3 L; P: r; _
  8.                 //如果是clique共识算法
    ) b( V) R; o" y. \4 s# s
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象$ F# S' {3 F5 R3 ?" {# Q$ x
  10.                 if wallet == nil || err != nil {
    3 ~/ ^1 o6 v+ y
  11.                         log.Error("Etherbase account unavailable locally", "err", err)2 p& A. w% l! r9 T. Q2 `( w0 ~! T2 W
  12.                         return fmt.Errorf("signer missing: %v", err)
    . i( O7 T. E7 Q; L4 R7 h
  13.                 }$ b- |0 l& f7 [; u2 n! `; K
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法! {3 B% n* R: [. y5 N1 \6 `
  15.         }' @) B* X6 l5 @" V7 }% ?
  16.         if local {
    ! L) \$ F7 \$ O
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
    ( e, b. s/ `0 i0 |/ B2 j
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1). O2 l; U6 ?. T* Z( N
  19.         }
    7 S" O. W5 ?# z+ O9 q+ G+ U- n
  20.         go s.miner.Start(eb)
    2 W& t- a: P5 J
  21.         return nil# V4 b5 x% N# q+ g* ~
  22. }
复制代码

% e+ J4 `& L/ Y! N这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
7 s1 H, i' w' J6 a1 }Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:4 e- K9 \, ~0 G( b, f
type Engine interface {5 y0 ]2 Q/ ]( [1 X3 d0 M  ^
        Author(header *types.Header) (common.Address, error), C: Y" ^: W5 g6 Z2 P
        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error" ?/ T/ o! V# s8 V# K! [8 S# V: F
        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
' h; h$ e4 @& W, i) fEngine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:/ m! H' N/ h+ a5 P& k  }
  1. type Clique struct {. [. \9 u  @4 Y. k
  2.         config *params.CliqueConfig // 共识引擎配置参数% R% ?, [  R7 w, `* f7 b: R  n
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点( `$ X4 M% ~" B! P8 d# p
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组+ n/ M& e+ t+ U
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿
    - L3 f: i9 \7 Q+ P5 d! f" w! F
  6.         proposals map[common.Address]bool // 目前正在推送的提案
    ) t4 Z( {% w, Y6 V. y0 k, h
  7.         signer common.Address // 签名者的以太坊地址/ g: O2 e! k* B% V) {  s/ I  @0 w
  8.         signFn SignerFn       // 授权哈希的签名方法$ y& B/ j& c0 b% k# x! k, j1 t
  9.         lock   sync.RWMutex   // 用锁来保护签名字段
    ) k+ X6 i& M- D5 U/ u7 c
  10. }
复制代码

. i3 \7 y' c* k  Q' t顺便来看下CliqueConfig共识引擎的配置参数结构体:
, f. V8 B1 g* Z# Z, Y- p2 O7 h( Y
  1. type CliqueConfig struct {
    ( o5 c& K4 b, }# ~  k; |% |! J
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s): j6 j# X  Z6 t
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    0 `" C6 N- X; F) j$ b: Y8 T8 R, R
  4. }
复制代码

9 m7 F7 G* @2 |  r" c9 k在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
1 B; _+ ~, c+ w2 I7 C7 s
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
    2 W9 j0 g6 {1 o! b1 C2 B- J
  2.     c.lock.Lock()% }3 L1 `2 ]/ _7 ^! C1 G. f6 e( x
  3.     defer c.lock.Unlock()
    ' q0 |1 C: F8 u- B8 s6 w
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块3 s- P) d4 W# t
  5.     c.signer = signer* ]& m3 X; _- d( p
  6.     c.signFn = signFn
    - G) _& T; P" v2 i6 Y( }
  7. }
复制代码

" C3 p! U  Q0 k/ x再来看Clique的Seal()函数的具体实现:* ^- J0 C' z3 L
//通过本地签名认证创建已密封的区块8 {2 Q. P. C% C
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {6 ]) |6 `/ \; v
                                log.Info("Signed recently, must wait for others")# o$ s9 [9 I/ B1 A
                                
3 f2 w& u- p0 O5 o0 W) Y+ N; cSeal是共识引擎的入口之一,该函数通过clique.signer对区块签名  A: k7 O" p; y6 ]+ ^0 F
signer不在snapshot的signer中不允许签名  z$ V( ], z" G# D
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名7 V& h4 G3 M5 n+ h* u# l* L
签名存放在Extra的extraSeal的65个字节中0 T8 I/ T2 u2 A3 ?
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。  N' i% s! g* g- [+ H( N* I5 N
  1. //snap.Signers是所有的认证节点% n- X0 F$ U; e' z
  2. for seen, recent := range snap.Recents {
    0 ]' d6 Y" ~% a+ j1 h9 q9 j
  3.     if recent == signer {' d$ s- U+ M' A9 J& t! P- M6 c
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {
    # o7 l% H/ ^: s, `% v) d0 ?
  5.             log.Info("Signed recently, must wait for others")
复制代码

& i+ U: w" g- M. M2 `* [            
6 W9 ]1 d2 S# U/ ]" u7 y在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。) d; {0 B. y- V0 X8 u1 S0 [
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。9 w. Z! d! W, z: R
diffInTurn = big.NewInt(2)
& k2 Z7 P$ O$ }* v; \4 \  \diffNoTurn = big.NewInt(1)
! P# m# L: S& D( [当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
7 ~; M2 a9 T9 g6 B- @% k6 A// 通过给定的区块高度和签发者返回该签发者是否在轮次内2 k1 f2 s1 {; k5 M+ p2 t
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    " @6 Q/ {0 D( `" {
  2.         signers, offset := s.signers(), 02 E# }" _  a  S% M; q" }
  3.         for offset
复制代码
9 X5 T$ F+ @% R3 ?# z. T5 L
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:
$ d% D% Q; C, P3 O4 m& @// Snapshot对象是在给定时间点的一个认证投票的状态
4 H& j% b* @9 L6 r
  1. type Snapshot struct {0 d  x' P  H/ N1 ~5 g, ~/ I# @. M
  2.     config   *params.CliqueConfig // 共识引擎配置参数
    : b, Y6 J6 ~3 C, m5 K
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。$ U  Q* p  n2 S9 _6 Y. p4 u( N4 ]
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号
    1 R, w% Y0 {9 |3 G
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希* G3 \; _5 ?! X: J3 l2 S6 o3 m
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表% ], P' {/ H3 `! k( N; C# M1 k
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址: |) f6 _7 G) m3 |
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    ( R5 I1 B8 H3 ~, Q7 B' [" n
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。( Y5 A% M, X( w; n5 I
  10. }
复制代码

- F& F: m7 u# Z8 }- W快照Snapshot对象中存在投票的Votes和记票的Tally对象:
( u0 x" V3 i: G7 |0 ]/ l
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。
    1 A. g2 \) S0 L
  2. type Vote struct {
    ) [0 U3 N9 s5 M. H3 h
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)  R. h9 t* z6 W- \& F% X- z. O" Z
  4.     Block     uint64         `json:"block"`     // 投票区块号
    * z' f2 ^" [; H) i+ D+ O
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权' Z9 D. V/ ?, u( d
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权
    ( Q- ?/ I. D: L" z
  7. }
    ) B+ T9 z9 f+ N+ b9 R$ H
  8. // Tally是一个简单的用来保存当前投票分数的计分器
    * a1 r) K$ {" m
  9. type Tally struct {
    % g9 a& K3 Z9 i8 a2 G7 _8 a- U6 ~
  10.     Authorize bool `json:"authorize"` // 授权true或移除false: O; C* U1 A/ a' C" f; v4 S
  11.     Votes     int  `json:"votes"`     // 该提案已获票数
    0 L) p7 l6 G' P* u3 m: a, D
  12. }
复制代码
5 B' `- g6 F# N. k
Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:7 Y2 I' p3 e- j* m
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
    + `6 a; {# R4 U8 d* _" f3 ^
  2.         //使用Database接口的Get方法通过Key来查询缓存内容; O. H  V) x) J' C3 m
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    4 A7 H# j# l" `1 W% e
  4.         if err != nil {! D' c  D7 x5 {% w" Y6 r
  5.                 return nil, err
    " m1 l6 k+ M% `
  6.         }8 A1 Z# q1 ^  ?2 h
  7.         snap := new(Snapshot)6 K+ v/ O. U  s' `* R5 z
  8.         if err := json.Unmarshal(blob, snap); err != nil {
    ' i4 Z+ T4 I8 H6 b8 A. t$ O6 ]
  9.                 return nil, err
    ( S+ V; t- I8 v2 Z
  10.         }* Y9 l! `0 \$ i6 A
  11.         snap.config = config
    & P) k3 I* m+ @! x/ i
  12.         snap.sigcache = sigcache$ s7 L* {7 j6 [# }
  13.         return snap, nil
    " g7 Q2 P; c; N! E. G' {4 z) H: ?; W
  14. }
复制代码
. |  j3 {0 b( e7 _
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
  K/ M5 O; K5 E* x; f
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {  H3 I. u* i5 T5 E: ?/ L
  2.         //组装一个Snapshot对象. K! V8 W: O! }
  3.         snap := &Snapshot{
    $ p2 n/ s9 M0 H$ H0 Y
  4.                 config:   config,4 r3 \) z1 G$ ]( [" h
  5.                 sigcache: sigcache,
      [7 P) j2 R* a" M  a1 B
  6.                 Number:   number,- Y8 W$ o4 K6 H1 a* T
  7.                 Hash:     hash,
    3 v( p$ n, ^- U! Y: I# m
  8.                 Signers:  make(map[common.Address]struct{}),0 U- F6 D5 Z: v1 y& W" w
  9.                 Recents:  make(map[uint64]common.Address),
    ) a6 `" ?; R) S
  10.                 Tally:    make(map[common.Address]Tally),% [* X8 J* l. N$ a7 G- z
  11.         }
    2 o$ ]1 Z# ?/ E! O( s3 a
  12.         for _, signer := range signers {
    + _3 B7 i0 Z6 |1 o- y
  13.                 snap.Signers[signer] = struct{}{}
    * o; ]7 W  s: W7 m/ \/ x6 m0 Q  b: B
  14.         }
    1 J) ?! T1 v$ [8 u
  15.         return snap: Q5 k% ]6 p% q* c
  16. }
复制代码

0 d' c+ F  M  [( R0 U/ J9 i继续看下snapshot函数的具体实现:
" v# n1 [4 A( r# w' h, W: }2 L
  1. // 快照会在给定的时间点检索授权快照; A5 g9 Z6 z" E* p4 }
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {4 g1 Y' M. m. C& S8 ?( b- l9 X2 a
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints9 U) d, U6 d' y
  4.         var (5 `# C% m1 O# `1 ]+ r
  5.                 headers []*types.Header        //区块头
    5 q$ M. A- J7 ?0 O$ ^
  6.                 snap    *Snapshot        //快照对象
    $ ~7 F$ I- w( Y0 B* {( l( L4 |
  7.         )
    1 t/ a# [, `$ s
  8.         for snap == nil {
    1 U3 K) A( \3 Y& ]. `* L. ?
  9.                 // 如果在内存中找到快照时,快照对象从内存中取  i0 p; p8 [% V0 U
  10.                 if s, ok := c.recents.Get(hash); ok {
    " J1 Y5 A6 a$ m' O6 r6 S
  11.                         snap = s.(*Snapshot)
    1 }# }+ K+ R/ X9 }' t8 T
  12.                         break, X1 A4 k0 U9 K6 ~* f& S6 e5 D
  13.                 }# |0 H2 l* K' c, t6 S6 t
  14.                 // 如果在磁盘检查点找到快照时
    ; y9 z7 y& ^3 w# n4 K  I
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
    4 l& e  e. C1 `6 ]
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
    / \% A0 u6 b; s( e$ V2 z+ w
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash), ]8 F: L& p* u+ `" p: }
  18.                                 snap = s
    4 J8 H) k& J% r* g$ u' ^
  19.                                 break4 o: W: B  q* c$ n( a% y
  20.                         }" g  B  I" N; \" Z5 [$ H: d/ j
  21.                 }
    8 S- [+ P2 R) ~: X
  22.                 // 如果在创世块,则新建一个快照- Y/ d: Q) G, V% M" w
  23.                 if number == 0 {
    ) ], o* U% @# s' y. p8 i+ |
  24.                         genesis := chain.GetHeaderByNumber(0)
    5 l/ Z3 w- n0 r8 H( ?
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {" q% i* |( |+ d: i0 D. K; K
  26.                                 return nil, err5 G: y7 F/ C6 e; ?
  27.                         }
    # a; R  B; R! k1 r+ l
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)2 J; J% Q+ t3 S- v2 V
  29.                         for i := 0; i  0 {
    7 ?9 b& Z; \: U
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)3 b0 T8 m7 w1 ?
  31.                         header = parents[len(parents)-1]
    & q( ^4 j$ r, u% W# S
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {
    8 D% c% c4 J) Y/ J) a' k4 f
  33.                                 return nil, consensus.ErrUnknownAncestor: a- g0 t' f% F. W7 V- a0 a( K) h  y
  34.                         }$ E& c/ Z) [- g) l  j, _
  35.                         parents = parents[:len(parents)-1]
    ( q  s7 y6 `6 P8 Q3 ~' I
  36.                 } else {1 x! c% ^# z: F2 ^6 s
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取( V! o) ]+ C% @
  38.                         header = chain.GetHeader(hash, number), s0 f3 S: ?- P7 M- h: R
  39.                         if header == nil {5 Y. ^9 \( j% \1 |4 v
  40.                                 return nil, consensus.ErrUnknownAncestor
    ( q5 H' b+ e7 ^
  41.                         }
    ' m2 {2 F7 r$ A, X* Z" [3 k, [
  42.                 }
    , m7 c: R. Q! C3 c. z9 S1 ^/ y6 L
  43.                 headers = append(headers, header); L( j( E2 Y, X
  44.                 number, hash = number-1, header.ParentHash8 C1 R1 ^$ |3 \3 F. m; s
  45.         }
    # g% o  S$ F" Y6 i
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面
    , A. v# t2 {# y& d( x- A
  47.         for i := 0; i  0 {2 v) q3 ]$ k' U% `% B
  48.                 if err = snap.store(c.db); err != nil {
    * A) \! A. X5 ~) ^* Z$ K- G8 ]3 O: A
  49.                         return nil, err
    4 P7 z; h% A$ d  X( A% q
  50.                 }' `3 j' e6 o8 V0 ~& S
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
    , ]) [8 k2 q# _6 d" l8 ?6 N4 }
  52.         }
    + e4 U) f; M7 g* z; W$ s
  53.         return snap, err
    ' [+ |: d# X/ F( R  x2 z
  54. }
复制代码
: d, m8 v7 M6 E) y' ]) L
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?
4 ?  a& N( i* X0 _
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
    1 @! y0 v- L- _3 y
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {5 l. ~+ g4 }4 h4 F& u
  3.           //可以传空区块头
    ' R- Q( C8 T; ^3 b, x# [$ z
  4.     if len(headers) == 0 {# s# W9 C2 ^6 M
  5.         return s, nil
    2 ~# \/ F8 E7 g+ C
  6.     }
    & C! L# `2 M/ `1 v1 S; E: z
  7.           //完整性检查区块头可用性, e. G" d) t. `1 g/ d
  8.     for i := 0; i = limit {
    ) v- j7 S! J# r( G7 x
  9.             delete(snap.Recents, number-limit)" }/ n3 y8 i6 T
  10.         }( d  i0 Y3 d$ M
  11.         // 从区块头中解密出来签名者地址5 b7 N0 q- }7 J/ y2 D: F# c8 o
  12.         signer, err := ecrecover(header, s.sigcache)
    4 r4 F. t8 O! n7 J0 a9 |7 v# B
  13.         if err != nil {2 t' ^, Y4 l) x; `
  14.             return nil, err
    8 T. W: _+ z2 V3 z
  15.         }' p! q! R6 G8 a& W4 Z( u
  16.         if _, ok := snap.Signers[signer]; !ok {
    ! f8 L" R2 Z0 y& d! z2 b
  17.             return nil, errUnauthorized
    - M: y: p; v  }% a. E0 Q
  18.         }2 L% [$ W* u2 d1 s! m
  19.         for _, recent := range snap.Recents {8 a7 F3 \2 z1 q1 q/ _8 q8 S
  20.             if recent == signer {
    . b2 @8 n: U9 j. m2 l: Y
  21.                 return nil, errUnauthorized0 G( I/ Q  t; y* w" a$ I; }" m
  22.             }
    9 ^1 y8 s' V$ F5 w4 _; X' O
  23.         }
    - i6 {/ j5 W6 y! N) X# h
  24.         snap.Recents[number] = signer9 `$ Q; Y: u2 H: {5 I/ j
  25.         // 区块头认证,不管该签名者之前的任何投票
    & I# K5 V4 J8 r/ n3 R! p
  26.         for i, vote := range snap.Votes {
    $ D) `: [; {- s" p  `: I$ X
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {
    8 M! Q& i9 B3 [9 ]* _1 B% q
  28.                 // 从缓存计数器中移除该投票
    * ~; r% W# }9 C
  29.                 snap.uncast(vote.Address, vote.Authorize)
    6 _+ ?6 d- L! Z, E: h( o9 X
  30.                 // 从按时间排序的列表中移除投票
    , l0 q( d" r; U, S
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)$ k4 g8 A5 o  c) W1 g+ o  l+ s' P
  32.                 break // 只允许一票
    7 v1 R3 V( ]" ?; c, ^
  33.             }
    0 |! E+ h8 X% `: h
  34.         }* [: E* D& R; k
  35.         // 从签名者中计数新的投票
    ) [! ]9 k2 d/ F9 Y  w0 D) m5 q
  36.         var authorize bool1 i6 f. r4 ^8 ?" l) `. ~
  37.         switch {
    . v4 k/ [( Y3 g& h% |$ f
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):* M9 U) M: w, y2 U
  39.             authorize = true
    0 ^4 T& [; f' D6 ?$ I
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):
    5 l$ _" k% T0 B. C4 V2 e7 L
  41.             authorize = false8 i3 x" o8 A, C# Z0 W
  42.         default:2 U8 Q7 M+ Q# ]4 C
  43.             return nil, errInvalidVote9 h' a2 W/ B5 M" b1 O. `9 m
  44.         }
    - ?& b/ V6 G2 d. D7 V5 {; m
  45.         if snap.cast(header.Coinbase, authorize) {8 n. ~; n  [2 m% D+ {# |( C
  46.             snap.Votes = append(snap.Votes, &Vote{+ O' l" r( N  O/ G
  47.                 Signer:    signer,+ T' D" [8 s: v
  48.                 Block:     number,
    8 S; A9 [5 ^: L% k3 u3 K
  49.                 Address:   header.Coinbase,3 e) y9 F2 g2 W0 N! Q8 \1 n! b% W
  50.                 Authorize: authorize,
    0 l  r/ z1 f" |7 ?) a% @
  51.             }); R; F/ F( Z6 d
  52.         }$ i: X: h" n3 C5 I
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
    : X* [( _  N! z/ Q/ ?9 r& n3 ]$ ~
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
    ! n. D7 I" G) S& Z
  55.             if tally.Authorize {
      i9 p9 u7 @1 f1 D+ }
  56.                 snap.Signers[header.Coinbase] = struct{}{}+ ?+ Z3 b( N1 J' m( d4 V
  57.             } else {
    9 S2 C, _2 V% g: |* X
  58.                 delete(snap.Signers, header.Coinbase)4 S8 p) ~( C; Q
  59.                                   // 签名者列表缩减,删除最近剩余的缓存5 M5 N2 b" m) R# u4 X3 N
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {0 \' u! V7 G9 r  y8 [5 a* w
  61.                     delete(snap.Recents, number-limit)/ f3 x& F; b: t# l
  62.                 }
    6 C9 W$ _- [$ d- A& P
  63.                 for i := 0; i
复制代码
8 w( D3 l$ L6 {" X" k7 b
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。
& M& ~+ E" p7 Q! _, G区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。$ C9 I1 m" ^& o' ^( m. W$ w
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
    + n7 j7 m  ~0 c$ M% s
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
    / I, k! t' n0 D1 \0 _9 j9 s0 s& o
  3.         // 不支持校检创世块
    * v  k' Q  b  h5 \
  4.         number := header.Number.Uint64()& G& c- o$ O  `4 r$ d
  5.         if number == 0 {+ o: W! X3 f( K9 I1 A
  6.                 return errUnknownBlock0 I5 k& F, L& f* C( Q
  7.         }" F% ^8 |& W. s$ q
  8.         // 检索出所需的区块对象来校检去开头和将其缓存! u2 [5 G: R2 H% H6 ^
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)# I6 V( B& G$ p% }0 `. P
  10.         if err != nil {* E  v' K1 P4 L1 g# I
  11.                 return err
    / c+ s$ R# W& z; ]" j0 ]3 b7 F
  12.         }
    % R( @; y. ~' l
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址& `# L8 i/ f9 s9 M1 h( _
  14.         signer, err := ecrecover(header, c.signatures)3 v, y5 T" q! a& N+ D6 p5 i
  15.         if err != nil {% w$ M$ r0 _2 O4 V- ^
  16.                 return err1 Z2 t( s4 `" r. e5 {, B* d" u5 A
  17.         }) `  u' H8 u! k0 {3 r. \9 H
  18.         if _, ok := snap.Signers[signer]; !ok {
    3 E& y. B% [$ |( g# x
  19.                 return errUnauthorized
    ! N1 G: w# T( b, }; X  |
  20.         }
    4 i- [' \3 q: e2 B0 W
  21.         for seen, recent := range snap.Recents {
    $ W6 X1 |2 y$ e- |* r2 r# o7 L/ f
  22.                 if recent == signer {" L- T) r* t, `" u) ?
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等! L/ {: [! `1 g
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    $ p8 p; z: V/ S2 W2 x8 P
  25.                                 return errUnauthorized+ ~0 A" E2 m; b' J* r* h2 K) P
  26.                         }
    / M$ r( a' ^# J; n7 q4 _
  27.                 }
    & K8 [7 l7 S0 x  i: F0 b+ O4 {
  28.         }8 ?: F/ K& w2 `9 `( e  q, U
  29.         // 设置区块难度,参见上面的区块难度部分
    . d, s4 `( }) N' O3 w0 E
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)6 @  h* g8 K. Q' k4 [
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {1 U+ Y* \+ T: g. w( Z/ }# O% t
  32.                 return errInvalidDifficulty& l* P1 k; i3 w5 @/ m# n" j& v
  33.         }
    % g; ~  X* T$ |$ m6 m. d
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
    ( _. o, A7 ?, r. B
  35.                 return errInvalidDifficulty+ m8 ^! l. ?) e6 E  [  Z1 X+ N
  36.         }
    8 b+ z3 e" Y; y) v- ^. V
  37.         return nil2 l2 c7 \5 [9 V$ j, x
  38. }
复制代码

) `- M7 b. g- s) t( s前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
% E/ D4 j% `" u" N8 H* y4 BClique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:
$ R, {3 |( v5 j/ w6 j. @% Q4 ?委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中8 c# K- G+ t: |: F! U' A
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。8 y( ~. t# {0 ^* G% E6 l
  2. func (api *API) Propose(address common.Address, auth bool) {  x3 M" z$ {" E4 s$ \
  3.     api.clique.lock.Lock()# I. |' z! e* h0 S( n8 ?4 Y
  4.     defer api.clique.lock.Unlock()) {- _  O* n4 ]1 ?- T6 I
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    $ I9 G: n6 i4 N4 f) ^6 }
  6. }
复制代码
5 I$ q& i- C- o- D' t. E: X5 F
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;" ?; s+ F% [. h/ @7 p
  1. //Clique.Prepare- j# l/ o' I( N6 N* B! q; H4 s/ E2 h
  2.                 // 抓取所有有意义投票的提案: r# O9 v. K, r2 ^( d
  3.                 addresses := make([]common.Address, 0, len(c.proposals))1 h- p3 y9 G: T% w  T
  4.                 for address, authorize := range c.proposals {: d  j+ s6 K" E$ U7 j) j
  5.                         if snap.validVote(address, authorize) {" D# @+ H5 ?; w, w) O4 t6 `" {
  6.                                 addresses = append(addresses, address)$ b8 J! @/ p- i+ \' j" B
  7.                         }4 q: A. R, D' d# |! z+ q
  8.                 }
    0 o( ]7 ^9 v  S- D' i
  9.                 // If there's pending proposals, cast a vote on them
    0 V$ D4 R, R. C- `$ `! r. z+ ^
  10.                 if len(addresses) > 0 {
    6 M: [+ ~9 K/ y& E) v0 z$ F% e+ H
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。. g8 _) J2 f7 r
  12.                         // 通过提案内容来组装区块头的随机数字段。
    9 _, V$ ?0 X3 p: [
  13.                         if c.proposals[header.Coinbase] {* B7 B+ W' V, i/ U/ I* E; p
  14.                                 copy(header.Nonce[:], nonceAuthVote)
    ! S$ {+ t# F2 {" M" C5 w
  15.                         } else {" |3 I) ~( L4 n! s) D
  16.                                 copy(header.Nonce[:], nonceDropVote)
    % q8 @" q7 O$ P# }7 \/ D! C, L
  17.                         }
    % i6 }3 b2 T' J: S& g/ a
  18.                 }
复制代码
! k2 y( N$ X7 y& u0 H  R
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare
  M+ y0 S# u, \
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {( z* N0 A9 }. i7 r
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    + ]/ K: @. V5 F/ r
  3.                 return8 Z2 `7 Z; m1 n% [; }: u7 P
  4.         }
复制代码
, b. {. W% m, t( t5 X
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法
% K* i' o! t! i) a+ I" D! J
+ q9 Z5 J  ]4 z  d1 v  q- o0 x* X7 e% E- E8 N
& L: J. t+ ^3 I- E
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3