Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2474 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。$ G2 Q0 m# z) L, Z8 ^9 G
  1. func (s *Ethereum) StartMining(local bool) error {: A0 G* t, B6 L2 Q
  2.         eb, err := s.Etherbase()//用户地址
      G6 z; [+ ^: c2 A6 A3 K6 a1 }# Z
  3.         if err != nil {
    * C0 F, H0 C8 U# L8 z; l- W
  4.                 log.Error("Cannot start mining without etherbase", "err", err)/ K# W: m2 u: N, j. t+ [7 }% ~
  5.                 return fmt.Errorf("etherbase missing: %v", err)# `0 b# G  A  j: V; k% Q$ o* p9 `
  6.         }
    & r- B- J' o2 P3 B, f
  7.         if clique, ok := s.engine.(*clique.Clique); ok {
    ! j5 o1 g% T, M
  8.                 //如果是clique共识算法  ~" ], U  b5 f: X
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象" f1 S% n3 G4 h: [5 \8 ?
  10.                 if wallet == nil || err != nil {; E* ?  Y! r% ~; \) M
  11.                         log.Error("Etherbase account unavailable locally", "err", err)( v9 g$ v2 o# `) g8 S5 p% M
  12.                         return fmt.Errorf("signer missing: %v", err)
    ) R0 N1 \2 }4 D
  13.                 }- p: [) L0 x* ^, p: i/ \0 h
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法- ?3 ]+ x& ]7 F: ?2 |' f
  15.         }6 d2 J2 F; u& p! E' _: L. W# c
  16.         if local {
    - l0 G4 A% ?* g7 q! \4 l% q
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
    2 I7 [+ b+ G$ K0 q! `  q$ S' U& t
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
    & n) M8 n: e# K  m5 g; L, W$ f1 n) m
  19.         }
    3 {- S( e0 H8 s' U( F+ Y& i+ d
  20.         go s.miner.Start(eb)
    6 A3 c1 ^% A  K+ {( q: ^
  21.         return nil
      U" n7 c- g0 t
  22. }
复制代码

5 p  g# F! v  T9 V这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。2 Q  Y( e; B7 q1 |5 P
Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
! y! o2 j$ G- Xtype Engine interface {' @$ E5 a& j+ c. l# C
        Author(header *types.Header) (common.Address, error)
3 H0 N: P9 O' g8 ?        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
( E! }  Q* i8 y. E: x3 w" u% a1 Z        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
/ h+ b2 l7 l, o6 I# ~+ q0 tEngine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:6 H& A1 G7 P, `" Y2 t
  1. type Clique struct {; M* S! u6 T. ?1 A: s! J) E# J' K
  2.         config *params.CliqueConfig // 共识引擎配置参数4 v$ _) q& V* u' c$ D
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点
    & l, s3 P) N! z) ^" H
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组2 Y% `5 s0 D/ m  {7 Z
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿
    . b/ {0 M7 G( I6 y# U9 }
  6.         proposals map[common.Address]bool // 目前正在推送的提案
    $ d! s: ^* j- H/ x' B1 g: l
  7.         signer common.Address // 签名者的以太坊地址
    & ]- f- @) a6 o6 t0 f/ v" C; x
  8.         signFn SignerFn       // 授权哈希的签名方法1 z# X& a! {. n  `
  9.         lock   sync.RWMutex   // 用锁来保护签名字段( F# Q9 s; T3 g7 e) p8 r6 Z
  10. }
复制代码

3 \- z3 U( C3 {  p8 r! z顺便来看下CliqueConfig共识引擎的配置参数结构体:
( h% s* F4 w- L& p
  1. type CliqueConfig struct {1 c# W' ~7 c0 K/ ^
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s), P) X3 I3 `) r- m$ }& {" ?& u
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)) Z, {! {2 ~, |! r7 b% k! V( {( l
  4. }
复制代码

8 I" {2 I, `+ @  r7 F" @在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:) J2 }4 M6 K5 D) ?- W
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
    - _" Y/ J% r% B' H3 }  n0 O
  2.     c.lock.Lock()
    2 r; L1 f6 W0 P( l% `0 ~* N5 q: P
  3.     defer c.lock.Unlock()
    + V& }2 f, g$ F% F1 j9 L
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
    7 S, H" h4 s. g. H6 ]3 T5 m$ z: X
  5.     c.signer = signer' E$ D* I! [9 g) I
  6.     c.signFn = signFn
    % o0 V$ p) F5 J5 E/ v: z- h
  7. }
复制代码

6 G+ A+ r, p* h8 W再来看Clique的Seal()函数的具体实现:! E+ y0 r# i, o+ x
//通过本地签名认证创建已密封的区块
* D& V% g$ {, w6 }func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {$ J0 i7 I; I6 B$ c" B6 |6 @
                                log.Info("Signed recently, must wait for others")
% g1 K5 i! F- d# V; m  o0 H                                + S1 |/ i7 N5 }5 n, x
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名0 U! x7 s# a; w2 d. r- R2 v1 t
signer不在snapshot的signer中不允许签名7 r$ [4 f' @+ h9 @  C
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名5 t  M' W. w/ |8 ?, W. N
签名存放在Extra的extraSeal的65个字节中  @/ N) z: e+ a7 q' m$ s2 i
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。" `2 z  w' ~# V! v
  1. //snap.Signers是所有的认证节点0 g- e2 @+ {. A( Z# d# a
  2. for seen, recent := range snap.Recents {, |2 E5 S* `3 q5 E' M! L- j, ~5 ~
  3.     if recent == signer {- C) {% H5 `$ J
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {
    / A4 D# J8 o9 d" x. x
  5.             log.Info("Signed recently, must wait for others")
复制代码

5 O( q; O, _# a( e4 m  ^            
8 V3 G' A* H( ?$ q在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
4 c6 j$ E/ t, g6 f+ {: y! H) h关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
; Z# D& |7 }& @8 }" `& V% v+ tdiffInTurn = big.NewInt(2)
' x4 `& }, |! L8 n% r/ FdiffNoTurn = big.NewInt(1)
! z9 X0 C7 k% ]2 `  t% W8 F当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:' r3 w" N- K1 N' U# T; p4 O4 {
// 通过给定的区块高度和签发者返回该签发者是否在轮次内
4 _5 h  c& A" ~/ h
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    # }1 j; i( a. Z( ]9 J5 a1 G% R& d, I4 q
  2.         signers, offset := s.signers(), 0" p: D. u' ^) U3 H) ?0 R
  3.         for offset
复制代码
7 z# p/ m! F. N! K5 K, U& _
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:- k* Y- {0 @# d% G/ X/ v& Q
// Snapshot对象是在给定时间点的一个认证投票的状态
) B2 g% p' N2 A4 {+ Y
  1. type Snapshot struct {
    5 O+ {' ^7 a& b; c+ M0 a: G3 o
  2.     config   *params.CliqueConfig // 共识引擎配置参数; P& |* w# l7 @/ M
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。8 q1 Z+ I+ G) ]5 M
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号
    7 P) Q$ r! y4 S; S, o7 b
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希5 c, `  d2 d  ?+ u
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表3 u6 u; g  m) D1 N) T+ R
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址5 `4 }! n% A5 O% s" y9 d. U4 i
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。4 l. S% l0 _9 f3 |& K
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。
    $ U0 X1 [- H3 j0 `6 ^4 f
  10. }
复制代码

0 R; j9 J/ u, \/ _快照Snapshot对象中存在投票的Votes和记票的Tally对象:5 Q* u) k& p8 O# A5 ^1 [
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。( X0 L- ~: ?/ T" g2 k; q1 C) g8 W" L
  2. type Vote struct {
    ( x1 W' d2 Y" @* `7 N4 `
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)2 T0 @* \7 H( B4 Y: k$ u9 b
  4.     Block     uint64         `json:"block"`     // 投票区块号
    : w4 @0 }. T+ X- Z8 v9 ]% b  g
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权
    , _$ G" z' \- [8 X( E' l% L
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权, m7 _4 ^0 j4 {1 g
  7. }
    3 `# O; f; `: k7 `5 ]$ ~
  8. // Tally是一个简单的用来保存当前投票分数的计分器% V$ _7 s: h# a- ^
  9. type Tally struct {6 R/ f0 K; |% f" N( U& @9 s
  10.     Authorize bool `json:"authorize"` // 授权true或移除false9 Y+ w6 i0 b' C
  11.     Votes     int  `json:"votes"`     // 该提案已获票数: C6 y7 M- x$ U& n' e8 T
  12. }
复制代码
6 ^' P8 L& N8 x& w$ t- O
Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:! ~4 {1 y7 d9 X  _8 [0 @
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
    8 C; f' D/ Z" x- |
  2.         //使用Database接口的Get方法通过Key来查询缓存内容0 z3 B0 C4 ?7 P  h8 v/ R9 @) l9 J
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))' a% f" W2 j2 @8 O* r" v
  4.         if err != nil {
    7 _$ A- C0 ]0 G
  5.                 return nil, err, T' f/ J( P6 p7 \: I
  6.         }1 S& s2 r& T1 ~: G
  7.         snap := new(Snapshot)# s2 `) m" E* X4 N  E5 @, r
  8.         if err := json.Unmarshal(blob, snap); err != nil {
    8 `& c  U$ i4 x/ @
  9.                 return nil, err% o: `! \. r6 D; L
  10.         }
    , U( s( T% H& K5 f0 m5 f/ e2 a$ ]
  11.         snap.config = config. N! y1 n: ]! N: E
  12.         snap.sigcache = sigcache9 Y; C' o9 a4 \* g3 o6 F5 v- u. u+ ]
  13.         return snap, nil
    + a( h3 c9 P; d+ V& I
  14. }
复制代码

: @* |' e. ?: G& ~; _1 nnewSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
  Z* k; n; F' C4 `
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
    + N& i/ U0 a( K4 r
  2.         //组装一个Snapshot对象$ S1 e+ |4 H9 Y, e
  3.         snap := &Snapshot{& |/ }1 Y& h) c: s2 ~4 p1 N
  4.                 config:   config,% a: y. `# P, [% }4 n
  5.                 sigcache: sigcache,
    9 F, Y7 M/ q: k9 {$ [5 ]
  6.                 Number:   number,8 B- g  l) v5 X. x7 O2 e
  7.                 Hash:     hash,$ P$ L$ i! Y' ^! {  {
  8.                 Signers:  make(map[common.Address]struct{}),/ ~; |+ h5 q: W) z
  9.                 Recents:  make(map[uint64]common.Address),+ F$ ~5 k: l( ^( Q
  10.                 Tally:    make(map[common.Address]Tally),; v' c9 _2 u! I2 U4 K8 `
  11.         }
    % Z# G0 X* F0 h# z
  12.         for _, signer := range signers {
    9 P/ T9 w- [; k  W& z5 t. ]) Y
  13.                 snap.Signers[signer] = struct{}{}& r3 t2 h+ z, D% I
  14.         }
    % X/ c5 m0 h: y7 X8 F  s0 r
  15.         return snap
    9 K) P$ R6 F0 d' w1 c
  16. }
复制代码
4 r/ A* D( P, d; j. D& J
继续看下snapshot函数的具体实现:
) c& N5 O9 M& n" Q4 a  k
  1. // 快照会在给定的时间点检索授权快照
    # w; g/ _9 T6 o# d: I0 _" _, J# N
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
    ' ^/ \9 \: ]2 q
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints, _  S# {, z& q- ]
  4.         var (
    ! {& d) f: }3 w# G
  5.                 headers []*types.Header        //区块头1 _) Y# Q' G8 }( P# o
  6.                 snap    *Snapshot        //快照对象( Y% }. q% ^! V1 z0 m
  7.         )
    ! F) I# r7 Y5 u( d) X% B2 U
  8.         for snap == nil {) I; Y; i; y7 _* ]
  9.                 // 如果在内存中找到快照时,快照对象从内存中取
    ! v. o1 |, ^/ D5 ]
  10.                 if s, ok := c.recents.Get(hash); ok {- O3 e/ A5 j% ~$ @; L$ v+ A& Q
  11.                         snap = s.(*Snapshot). E( Y0 m' p1 x, U
  12.                         break2 c; s0 [! T) H, p: G5 R. m
  13.                 }
    # G1 r& n' R& r  Y: {9 k; Z# w
  14.                 // 如果在磁盘检查点找到快照时2 d) P: m" V4 h1 M# M/ D9 J
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号+ \) H9 D  @: k
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
    / M9 k) h+ y  w3 e
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
    9 o  W5 R, _* ~8 l- i
  18.                                 snap = s" b- g0 |" j2 D
  19.                                 break9 i3 h0 J  |- }( k, w
  20.                         }% t4 Z% X; r* h1 d1 M9 a
  21.                 }
    # r# H( L6 h' ~: u: N
  22.                 // 如果在创世块,则新建一个快照5 F  I; C9 {$ c, Q0 v8 F+ y; Y
  23.                 if number == 0 {; N% p, A' \8 N! r4 X2 Q5 h
  24.                         genesis := chain.GetHeaderByNumber(0)' K- X) W+ I; F! {+ U% d
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {. g. t9 O2 M5 P+ Y
  26.                                 return nil, err
    5 T9 N% T7 c) w% o
  27.                         }- h% M; O  Y. o$ v$ D9 F0 [3 N
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
    " K9 k3 v6 M( p( s# l1 O
  29.                         for i := 0; i  0 {
    " G2 N, \. H! g7 f7 I% g
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)
    / V/ h* ~% ]  P& }" j+ `" k
  31.                         header = parents[len(parents)-1]
    $ r) y$ k( x% U: {  @8 y
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {9 f# C5 \! Z/ f( S
  33.                                 return nil, consensus.ErrUnknownAncestor; T% l3 W6 E" f, h3 ]! p% h
  34.                         }
    6 t; U4 j' C1 `- F8 d& z9 m" I
  35.                         parents = parents[:len(parents)-1]$ M5 m* ~+ Q; J* e  u+ Z
  36.                 } else {% ?1 j& b4 r2 \; j/ c9 e) I
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    2 Q: T& v+ j6 w: y
  38.                         header = chain.GetHeader(hash, number)
    4 Z- c# Q1 [- q2 G, P! J! c1 A
  39.                         if header == nil {
    4 Y+ r% k  ^1 R9 O9 \
  40.                                 return nil, consensus.ErrUnknownAncestor
    ( n0 z9 T9 m( ]5 R& M9 O) ^
  41.                         }
    / K/ l8 O' O5 Z# S5 B1 F
  42.                 }
    * t9 |# [8 p- C( l7 s1 X
  43.                 headers = append(headers, header)
    2 X9 I( \$ z% A! y/ H: y% U3 A# }
  44.                 number, hash = number-1, header.ParentHash4 e9 {8 ?7 L8 s8 E3 G( k
  45.         }
    # {9 d1 z' o+ `4 O+ z- d
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面
    $ |2 l" \8 [- ~# k% a
  47.         for i := 0; i  0 {
    - t  _% E3 H7 e1 Q8 b
  48.                 if err = snap.store(c.db); err != nil {, @3 w6 q; z9 I
  49.                         return nil, err
      S9 ], ~0 n0 J
  50.                 }
    : v" C/ T. B' u. z8 ]0 J' B. l
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)" r/ V- b3 ]2 T
  52.         }7 R9 x, z9 l5 d+ ~) U
  53.         return snap, err
    ( Z! w  E6 z& w3 \' L
  54. }
复制代码
$ A+ W  ~" J, U( B; a' t% b
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?
6 B2 R3 h' E. O" V  B" g# {: X
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
      x- i/ H4 @" O
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
    " N# @( n8 f8 i
  3.           //可以传空区块头
    * ?" m2 e: p6 w5 ]- Z+ W  B
  4.     if len(headers) == 0 {
    : K' w6 x8 i  |1 q
  5.         return s, nil
    9 o8 v3 n% N" i0 F
  6.     }  v. F* h1 b" ]! @
  7.           //完整性检查区块头可用性
    , b; j9 R' ]- }
  8.     for i := 0; i = limit {
    ( G! d. E! R, u5 a# a. i
  9.             delete(snap.Recents, number-limit)
    % h, `$ c- R( m6 d
  10.         }) Z9 ?- u% u! `8 ?, J8 _! d
  11.         // 从区块头中解密出来签名者地址
    $ ]2 p1 N1 }  n& z* @7 f
  12.         signer, err := ecrecover(header, s.sigcache)
    ) j  q# k% Q' M
  13.         if err != nil {
    ; G; g$ {0 ?# o0 W( D. R- j3 Z
  14.             return nil, err
    8 ?7 h# o" q5 X8 s) y
  15.         }
    - D3 h) c* s! k
  16.         if _, ok := snap.Signers[signer]; !ok {
    - k& z2 @( T4 c0 a  }& X  d' {
  17.             return nil, errUnauthorized/ N$ }7 \  Q" b3 a+ U$ Q
  18.         }5 }3 U/ e' O4 B" u) E1 ?
  19.         for _, recent := range snap.Recents {
    9 N9 @1 U  R  l1 ?' x( V: }
  20.             if recent == signer {
    8 P" L- p9 w5 m4 `
  21.                 return nil, errUnauthorized/ R! l7 g- h+ S. e7 E
  22.             }3 L( m8 F5 [2 A3 t( P8 A
  23.         }0 T- Y7 L9 P0 w
  24.         snap.Recents[number] = signer
    ; ?+ j( s* x, g6 K! y
  25.         // 区块头认证,不管该签名者之前的任何投票
    : e7 @: g4 o$ L& ^: S6 r1 b
  26.         for i, vote := range snap.Votes {
    3 P( J* [# G! o& ~* o) B
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {& T$ e7 ?- O/ z/ j
  28.                 // 从缓存计数器中移除该投票
    ! ]) G+ ^) \$ c8 H5 e. R0 T
  29.                 snap.uncast(vote.Address, vote.Authorize)0 I$ s* v$ F0 I* U* L- L8 o: w
  30.                 // 从按时间排序的列表中移除投票: x1 [  G2 r8 i6 g/ }8 r
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)# M- [9 O4 L% p5 N" v+ l- v! k
  32.                 break // 只允许一票
    + t- \/ M3 o5 Q
  33.             }
    7 r) K! A$ @" g! T. l
  34.         }3 \/ }! V, i# E! Q
  35.         // 从签名者中计数新的投票
    . D( F( G! b  s8 ~5 c4 I
  36.         var authorize bool+ E+ R9 t* E/ K+ }6 W5 P& H
  37.         switch {7 ^* ^2 U* @0 B& v  t- O
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    ! u1 a7 F/ D# e: ~' ^8 d
  39.             authorize = true
    ' K/ u$ [, g  Z8 D
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):& ^3 r. Z/ i) a1 n
  41.             authorize = false" A* o8 n& k  Q9 J% W
  42.         default:
    9 O* i: u, n, ~( M
  43.             return nil, errInvalidVote  h& n' R3 m# V* o
  44.         }
    0 M- W) H$ x/ J- |; c% t- K: _& M9 Q$ U& p
  45.         if snap.cast(header.Coinbase, authorize) {
    " t) z8 X$ [  W9 y
  46.             snap.Votes = append(snap.Votes, &Vote{( T, e1 R- _( M, J( V" p9 x
  47.                 Signer:    signer,$ X; g3 {5 Q' Q, F, _3 E
  48.                 Block:     number,
    * p/ D$ z  l, h/ d; F
  49.                 Address:   header.Coinbase,
    , z# W  [1 u2 J" i% A6 y  c+ N
  50.                 Authorize: authorize,
    - A- O. n0 ~3 i1 L! ?$ j
  51.             })
    , h; \5 E8 `/ [/ p; w$ [& B
  52.         }
    ; v' J9 }" ~  N: d( S6 b; I
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
    ; g  F3 X( @6 L. N, O/ a% a
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {! h/ M% u5 G! C3 w* N3 ?
  55.             if tally.Authorize {5 v  z& a, ^# n2 y
  56.                 snap.Signers[header.Coinbase] = struct{}{}( s. h3 Q7 M2 t) G, u3 L- i' L
  57.             } else {
    , N, \2 s1 ]! l
  58.                 delete(snap.Signers, header.Coinbase)
    0 g5 u- N  o% {, H5 }% A
  59.                                   // 签名者列表缩减,删除最近剩余的缓存
    - R$ q: }" W! D/ |' ~8 M" T9 J
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
    2 M$ Q8 x7 ]2 x8 j
  61.                     delete(snap.Recents, number-limit)) e# l% \( e0 u- M
  62.                 }5 ]4 ~% e( ^* Q) v' I) C, z, X
  63.                 for i := 0; i
复制代码
+ V, ~3 q5 e% M8 U0 G
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。
' o$ p1 O4 K! U4 V8 k6 i2 M, @) L区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。
4 l& _: v$ O' n, a# n
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
    , Z; e; v* X  ^9 ]2 E9 Z
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
    ) Y4 b9 Y* E% N8 p
  3.         // 不支持校检创世块
    + M# m9 ^) I* K8 ~3 w8 |
  4.         number := header.Number.Uint64()4 g6 [; Q8 p# R) W3 K
  5.         if number == 0 {. K  a$ {8 [9 p( x6 n
  6.                 return errUnknownBlock
    4 Q$ k/ p: V) q+ j  t
  7.         }4 P5 X: x( o. k% Z  j
  8.         // 检索出所需的区块对象来校检去开头和将其缓存3 v3 f! H, r# G
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    ( K' m& C- w  {6 N$ f# i
  10.         if err != nil {; z+ i4 S* z1 _3 U+ `- a
  11.                 return err) z# G  k4 R' n+ m: [
  12.         }
    . m: ?: ~! e2 |) h: Z  T
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址
    4 {- Z+ a4 I; J7 U  V9 W+ y( m
  14.         signer, err := ecrecover(header, c.signatures)
    ( _9 ~% J0 q- T" e' P4 {
  15.         if err != nil {. F! |; c7 Z& ^/ }' l8 K* H
  16.                 return err! `5 [2 p! o  o8 P* h1 D
  17.         }* T& c- e' K/ R) N
  18.         if _, ok := snap.Signers[signer]; !ok {
    ( w; E' P, `4 w5 g
  19.                 return errUnauthorized8 z! H/ e" l6 y: D# n; {7 R# n
  20.         }* s0 t0 Z8 C9 y  V
  21.         for seen, recent := range snap.Recents {- w2 M7 W& e  L  L
  22.                 if recent == signer {& V  ?2 ^. }0 T9 X" C6 U% o, O4 H
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等4 \' q, d, V  |! |9 J" \8 R" z
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {, M+ d* B1 B2 ^+ ~) l- C4 V
  25.                                 return errUnauthorized
    . _- c+ n0 o- V/ A
  26.                         }% C% ^% J' M9 K
  27.                 }
    ! N& D  L# b9 w( p
  28.         }. z4 C% r, M/ v. V  p
  29.         // 设置区块难度,参见上面的区块难度部分* u% e8 w3 n3 ~1 Z; s' Y8 L2 U0 a
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)
    : b" M7 @6 a8 Y% b
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {9 E5 U! ?, g8 d% d2 m5 M9 K) w5 u
  32.                 return errInvalidDifficulty
    + }* A$ M  t0 D$ }% Y0 A9 P7 W  W, w
  33.         }
    * N$ F$ G% f( S9 @3 L# s% m5 L
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
    + `7 o$ G3 u( O" n+ d# ]
  35.                 return errInvalidDifficulty
    % I% N1 T' ^* ?' `
  36.         }4 G( ]  f* B4 b1 w) R2 \* t
  37.         return nil# X  e7 D4 y, s; d0 |4 e
  38. }
复制代码

+ s! o; C  q7 v' q* I5 y& I前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?; E3 s% v' f* A* P: o& G
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:
1 h3 E- g7 E7 e4 e, v8 y5 A委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中
4 y% L( @8 @4 t
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。* t1 D5 o- P8 e: K6 d( x4 H
  2. func (api *API) Propose(address common.Address, auth bool) {
    - E5 Y3 R; e* G" K
  3.     api.clique.lock.Lock()
    ( r' W, d9 J& T; w! S5 K
  4.     defer api.clique.lock.Unlock()* W3 W8 M9 `. a: V+ Z
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    1 t2 a7 A* ?: U' R: h' i* _
  6. }
复制代码
9 z$ \$ {" N" z1 [' n
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;" f& v) s  `% g6 T6 M
  1. //Clique.Prepare5 Y1 U4 R. T, _4 T* |
  2.                 // 抓取所有有意义投票的提案
    $ A/ g( X1 F. |& Z. o
  3.                 addresses := make([]common.Address, 0, len(c.proposals))  V, t/ @$ u; ]9 y# k) n
  4.                 for address, authorize := range c.proposals {
    1 M0 r0 F. C6 s- O( @
  5.                         if snap.validVote(address, authorize) {  H6 P; b/ X6 A! H" U& h
  6.                                 addresses = append(addresses, address)$ Z4 W" \6 N% K
  7.                         }
    8 j0 t) K9 {& _' R* W3 _: M
  8.                 }
    + U" O+ {- e' O
  9.                 // If there's pending proposals, cast a vote on them
    1 ?6 w1 Z" z, x; u/ D' Z/ b3 Y
  10.                 if len(addresses) > 0 {
    4 s( _( |" |; Z/ f1 d
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。7 t/ E6 m' P1 r6 }! b3 t
  12.                         // 通过提案内容来组装区块头的随机数字段。8 ^3 t+ C3 {8 U% m
  13.                         if c.proposals[header.Coinbase] {' o3 Q8 \6 Q1 ?# c8 b4 \  H
  14.                                 copy(header.Nonce[:], nonceAuthVote): o2 }! `+ @, X. ~7 _2 G1 r
  15.                         } else {
    ! M/ r6 k8 V8 a8 w( @) z* x. H
  16.                                 copy(header.Nonce[:], nonceDropVote)4 ?! J7 L, |, ~: s' F9 i
  17.                         }
    ! t8 C* |8 ^$ E. U
  18.                 }
复制代码
' F* i0 Y, g+ `/ s
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare
. A0 t3 \! A5 e# ~2 ^9 Y6 p0 [
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {8 z' a$ d& S1 h$ A( `( E$ w3 [
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    9 ^6 z- W( K5 _' h
  3.                 return% |" l( p7 Z: m+ b# i2 u; Z
  4.         }
复制代码

) v* `5 U0 U+ n0 ?8 ?+ w其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法( _$ s& W. B1 _; _+ D

# ]7 ?' ~4 ^/ f- N5 u; \8 O
! d6 P. Z" ]4 @1 e: H- d% w+ t
. u) y/ v9 P, y7 S" Z% n2 [以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3