Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2475 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。
5 ^) F1 a5 u2 ~! C, z+ M
  1. func (s *Ethereum) StartMining(local bool) error {/ O( u2 c5 U2 ~% e! o' I- w
  2.         eb, err := s.Etherbase()//用户地址
    ( R$ `/ d6 v5 Y1 |& _9 M9 a* I
  3.         if err != nil {" _. M( v; r8 D; S+ R
  4.                 log.Error("Cannot start mining without etherbase", "err", err)" y) f' J8 n3 J- P
  5.                 return fmt.Errorf("etherbase missing: %v", err)( B$ |% e! f: w) W7 A
  6.         }4 J: R# x! `- F% n. F: y: f0 N
  7.         if clique, ok := s.engine.(*clique.Clique); ok {6 n/ L' y& `3 b. y6 {
  8.                 //如果是clique共识算法
    3 d0 S9 I2 i/ m  Y& s9 y
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象0 n: G/ \% |& G3 ^; \  H
  10.                 if wallet == nil || err != nil {( g& V- F+ n/ M7 t
  11.                         log.Error("Etherbase account unavailable locally", "err", err)
    - S3 Q% v* m* V+ S+ a
  12.                         return fmt.Errorf("signer missing: %v", err)1 [9 e1 j4 }( {$ M5 i: x! r0 n* P
  13.                 }
    , f1 o" n% [3 W6 q
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法
    , x% T$ @2 _8 p9 ~) V& d& u: z" m
  15.         }6 a9 ~% B5 f) j' s1 Q( f
  16.         if local {+ g/ D. r9 N- c: U
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
      f. P' E; \" ]; Q/ q7 c
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
    . [( D2 n; D( G$ P% I9 z% L% R  {: o
  19.         }9 G, Y" f* z4 d8 g8 W1 ]% G2 p
  20.         go s.miner.Start(eb)" J( B; ~: Z4 w' u( c6 v; C0 o
  21.         return nil0 }6 I$ `# m* q' C/ V1 X; h) m
  22. }
复制代码

- o1 k: [: l, z! @8 |这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
  [! X9 ^1 G/ ]4 @& G* f* q$ K- ~3 cClique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
0 K" R6 m% m6 _' B0 Itype Engine interface {
+ K: q( ]3 a) R% ^* {        Author(header *types.Header) (common.Address, error)
" W1 s" Q8 i* u6 G        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
/ \8 l( Q3 a, P) k3 c        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan3 e- L! U4 T: d  {9 H
Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:! u; Y- y+ X. h. j  j& z2 B
  1. type Clique struct {' K( e( v2 z: k. b. h0 I( r; O
  2.         config *params.CliqueConfig // 共识引擎配置参数
    % m' ~  E8 b- a+ s9 b( b
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点
    ! @& Q8 z- L/ ~5 ~1 J8 D: L4 n6 M
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组' g( P$ P+ M- x0 l3 ]( J" W# h* F
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿
    ) D- X6 A: L, e! z5 {, F, m
  6.         proposals map[common.Address]bool // 目前正在推送的提案
    $ p- ]: T; K2 {5 D2 K6 [9 o
  7.         signer common.Address // 签名者的以太坊地址
    & V7 a# @/ _% M% j  m
  8.         signFn SignerFn       // 授权哈希的签名方法6 D" ^$ @! c1 ~7 v0 M
  9.         lock   sync.RWMutex   // 用锁来保护签名字段8 P) P7 B& c$ G) y: @. X
  10. }
复制代码
- S- z2 I  W+ Y- `. y) O1 Q
顺便来看下CliqueConfig共识引擎的配置参数结构体:
. i3 [: V, s3 [$ g- z. L8 b3 u
  1. type CliqueConfig struct {
      v1 [% |9 S* [; j  ]; [
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)
    6 ~. u9 @( h  c4 |- r8 k1 D
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    6 o# r; S# S% h3 }' ~* ^0 b7 k( u3 u
  4. }
复制代码

  C6 S  O& i$ p$ l3 f1 m在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
) U+ n( X- T' S) o, N5 I1 H
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {6 @# l3 H# C/ j. L8 a/ c0 m5 @
  2.     c.lock.Lock()8 U4 ~' H9 J* v7 e) o
  3.     defer c.lock.Unlock()) [6 h7 [3 W4 Q. s
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
      X( }" H& F7 Q8 Z( O. l0 L7 c
  5.     c.signer = signer
    " d2 j3 \( l3 x- `& D6 O
  6.     c.signFn = signFn
    : `& D( }+ O* G# Q$ Z' V3 P
  7. }
复制代码
8 k* _" D1 F3 y9 J
再来看Clique的Seal()函数的具体实现:
& q+ g; u9 E& Z//通过本地签名认证创建已密封的区块
: [9 X  q3 X  ]  ufunc (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {" H4 q' \, U/ z, k5 N- D
                                log.Info("Signed recently, must wait for others")
7 ?' G5 l( i% `2 \6 s, H% g6 Q; @1 ^) U$ i                                
0 o' V$ y: }1 m1 K8 O+ @4 U3 }- \Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名
) e8 ^, t! f/ V* `signer不在snapshot的signer中不允许签名
% R2 C: \9 k0 o5 r) J# Y" [" Jsigner不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
# ]: ^' a. x6 f4 J2 q) O签名存放在Extra的extraSeal的65个字节中
$ c( c& ]! i0 Q& x关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。
- O7 M8 p2 q: V) a: l
  1. //snap.Signers是所有的认证节点
    8 a; Q4 P7 M2 x
  2. for seen, recent := range snap.Recents {
    8 _+ ?% ]$ V' S3 B9 ]% _) r
  3.     if recent == signer {$ z* K- J9 Q+ S! {" }: A$ Q2 I( F
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {: `' a+ ~/ l4 ^% A# w) Y, M
  5.             log.Info("Signed recently, must wait for others")
复制代码

* C% f1 j. g; {- k* w: M# ]            % L; m; o( V" [# B+ G/ C' H
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。8 ?; v/ U* E0 e6 I2 `
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
' W' ^7 N. r* |, ~. p5 }0 O+ QdiffInTurn = big.NewInt(2)
% r' d. T2 u3 d- v8 Q6 }  x& b3 \diffNoTurn = big.NewInt(1)
: j. }5 f, G) d当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
9 E5 Z+ v0 t3 ?) @# @// 通过给定的区块高度和签发者返回该签发者是否在轮次内! c% E& z$ G' D! e
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    2 D& k: I7 F1 _1 h
  2.         signers, offset := s.signers(), 0" V) ~: y# m8 \) i; Z
  3.         for offset
复制代码
0 S+ G* A' D7 O6 b
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:% h8 c' v: |% p1 w% M8 s
// Snapshot对象是在给定时间点的一个认证投票的状态
: q! V; ^& q. [& u" U% c
  1. type Snapshot struct {% J+ _3 H6 t% c. e% {, ]
  2.     config   *params.CliqueConfig // 共识引擎配置参数
    $ J7 t3 y9 Y: e' T
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。8 U. [6 {! _6 \
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号; Z% _1 ?7 b% Z" R" l% E# q" }
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    3 P; x4 \8 u, N, d- U
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表
      G; u% a- j! }0 E- Q# G8 m+ E
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址
    : A5 O6 K9 r3 U8 _0 p: z" [
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    / Z6 O/ E0 A9 L6 ~7 }% C) S3 v
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。! I# O- s2 S$ M3 G
  10. }
复制代码

/ J- Y. o, u/ D2 \7 n快照Snapshot对象中存在投票的Votes和记票的Tally对象:+ _9 {& F% ^! i( J3 h+ m
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。
    7 Y2 n3 \/ [) i9 d6 X9 y3 t
  2. type Vote struct {
    / S+ M$ r; N% U8 |0 Y
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
    ) j; L: g2 G) t' X+ L. Q
  4.     Block     uint64         `json:"block"`     // 投票区块号0 Q  V7 ?' G( T) K# U; E' A! i
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权2 H; n/ `5 I( G; j- k! B; b
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权
    % j' h' y: f( {) X# J
  7. }6 _/ C2 G0 m) ~4 W- e0 p1 k
  8. // Tally是一个简单的用来保存当前投票分数的计分器2 W1 S4 L- c6 M6 W5 t; a
  9. type Tally struct {' x! D* S% p& e4 F5 I5 p2 ]
  10.     Authorize bool `json:"authorize"` // 授权true或移除false
    : b+ M  l$ I) e: j/ Y) |$ P
  11.     Votes     int  `json:"votes"`     // 该提案已获票数
    6 R7 G, q* s1 a/ h/ G
  12. }
复制代码
) X  g9 K* B1 o; \4 Z) I7 U
Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:$ _9 V5 v2 Q. G; [
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
    9 p! Z* A% u% j8 u- n, [
  2.         //使用Database接口的Get方法通过Key来查询缓存内容
    * A) P9 U) v; f% s; a
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))" t/ m& g" x5 E
  4.         if err != nil {, f2 j" S7 [6 U. Y# a5 y5 ]
  5.                 return nil, err
    6 d5 E) \. ?3 D% C0 @- @
  6.         }
    ; P+ m, V/ {; W% b
  7.         snap := new(Snapshot)8 X- ]. m. V8 q+ s3 F6 ~& t1 s$ _
  8.         if err := json.Unmarshal(blob, snap); err != nil {8 H) _! o, d: s' o: F3 k1 N/ v9 r
  9.                 return nil, err
    1 e' V$ S* {. k- n8 `8 o
  10.         }
    * c( }' F, q! R
  11.         snap.config = config1 n9 {# o4 T* C# u3 R
  12.         snap.sigcache = sigcache! m+ j  }( `8 y7 ~1 H8 i
  13.         return snap, nil
    * N0 J) P7 q" O3 o) Q3 [( k
  14. }
复制代码

# |) G' _% Q  R. C; q" @, vnewSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
0 I7 @. c3 Q. ~1 B( P4 N
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
    " z; T, M8 @, {! f
  2.         //组装一个Snapshot对象2 A6 O; r2 R% Z3 F# O
  3.         snap := &Snapshot{# ?: S- v; c6 ^5 m2 z% e6 x
  4.                 config:   config,8 a. K: t  p, U
  5.                 sigcache: sigcache,
    2 y9 ^$ d, w& Q9 g5 u
  6.                 Number:   number,
    9 a; D5 b& S2 j# Z9 C
  7.                 Hash:     hash,
    + E# o/ H% y2 |: A! C: W8 f
  8.                 Signers:  make(map[common.Address]struct{}),
    ' D. g2 F+ u) |& b, r  h
  9.                 Recents:  make(map[uint64]common.Address),
    ) z/ E0 h! y- o9 u( j( T+ G
  10.                 Tally:    make(map[common.Address]Tally),
    7 ?# @0 B  O/ y) ^1 z% k1 x
  11.         }% W! A5 a6 V" ]+ b* M
  12.         for _, signer := range signers {
    9 X/ b8 H$ {/ J3 Y: d
  13.                 snap.Signers[signer] = struct{}{}
      v  b! `3 l, P& Q
  14.         }
      I6 `- k8 c( i! l
  15.         return snap
    ( ]! m& S1 L% Z6 T7 H
  16. }
复制代码

) j5 E* {. B- T5 }& h继续看下snapshot函数的具体实现:
% r3 [, T$ H, w, h* {
  1. // 快照会在给定的时间点检索授权快照/ G8 {* Y- {: L  x. q$ t+ y
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
    5 @3 a, s6 @8 `' {
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints/ y5 Z9 }& G# W0 W1 h8 d2 H
  4.         var (
    ! W: m* H0 y2 Y! N9 k5 ]  }
  5.                 headers []*types.Header        //区块头
    ) k6 }4 w/ B4 U( s) d( j- T, N( @
  6.                 snap    *Snapshot        //快照对象, G+ @" M* e0 I
  7.         )
    % h( p2 D6 ~1 r/ s1 G: H
  8.         for snap == nil {0 R  N* w# V/ }
  9.                 // 如果在内存中找到快照时,快照对象从内存中取' c" b+ L& a8 F* w+ A
  10.                 if s, ok := c.recents.Get(hash); ok {7 y6 q! B8 M4 N/ U  Z
  11.                         snap = s.(*Snapshot)5 t7 y: e5 ]# T7 ^2 e% G
  12.                         break
    ; ^7 K) Q( L5 R! d7 I, ~) c$ X: u7 [
  13.                 }
    $ k0 f  g/ A" P+ g' K4 }5 b1 I
  14.                 // 如果在磁盘检查点找到快照时
    # D" j/ m6 ~. _  R: L1 `7 M# ~
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
    + V% m& k* s. N  Q# }* R  `
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {6 [  c' J% Y/ e8 X
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
    & k3 Z6 u& M; C9 {2 v
  18.                                 snap = s: K! w* o$ i$ Z5 E2 M+ y/ c/ g' k0 W
  19.                                 break
    1 t1 [. i* l1 h4 A) y2 b
  20.                         }7 {" b3 S# p, D' N
  21.                 }; z3 v, J; q3 V% A: w
  22.                 // 如果在创世块,则新建一个快照8 f& y/ _- K* {
  23.                 if number == 0 {
    9 d& b' D9 L, d
  24.                         genesis := chain.GetHeaderByNumber(0)% I3 X8 d% A8 T/ j7 E
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    1 ]9 }% `# N0 y$ Z
  26.                                 return nil, err
    $ A* |$ q2 F0 U2 B: ^
  27.                         }
    + C9 i  N" w; n5 E
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
      B7 Z4 y7 ?5 m4 g
  29.                         for i := 0; i  0 {
    ( S0 h2 T, e) |  X! g+ P8 c& |
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)& h# z5 p: s5 V" ]
  31.                         header = parents[len(parents)-1]
    % V9 w' G2 N3 D- A( @5 @
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {+ O* _" B, s* H. X
  33.                                 return nil, consensus.ErrUnknownAncestor
    3 z) K6 j5 Q( p6 Q8 Z# Q
  34.                         }% e) P* Q( b! n! }# f, H; I! I
  35.                         parents = parents[:len(parents)-1]. b/ E! C5 K4 z9 h
  36.                 } else {/ u6 G/ A/ f, @  P0 W& X
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取. h  R9 R9 J5 C" V9 a( b  v
  38.                         header = chain.GetHeader(hash, number)/ Z. @7 E6 i6 Z6 m; @
  39.                         if header == nil {0 {- x9 N# w3 n9 G. K" t! n
  40.                                 return nil, consensus.ErrUnknownAncestor8 M" D+ D) a' k: R
  41.                         }
    ( D. H+ z1 h# E" M4 G
  42.                 }
      j, \5 a/ v  }( Y8 I# Y
  43.                 headers = append(headers, header)
    0 e6 o& p" h  Y: D2 J9 f" R0 z
  44.                 number, hash = number-1, header.ParentHash- @9 R+ Y9 k# v( n, q4 Y/ I  N
  45.         }
    + _" `+ i1 y. C
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面
    1 A* L0 V- N% _5 `: m; x
  47.         for i := 0; i  0 {
    ' d9 G& O! S% d9 y
  48.                 if err = snap.store(c.db); err != nil {/ k4 }- Y( A* z7 S; V
  49.                         return nil, err
    . s" G7 v) [, D
  50.                 }
    - R. q: l- b- r) w9 \  r0 h: g& D7 L8 i
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)3 D3 M8 ^6 h" x0 J$ E; Y. o# K8 r
  52.         }) j4 m9 d* T# D" t( O
  53.         return snap, err( G: {5 P5 v0 C& K# X, F' s' J
  54. }
复制代码
; L! o4 S. \+ d5 ^8 d3 x
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?
6 o1 R/ l& I0 b+ I! ~8 U- [- ^
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
      N% O) v8 z8 O! {
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
    3 n. ?' L7 L0 U( c1 h4 u
  3.           //可以传空区块头! X. X# g+ B: [* p/ q& ?
  4.     if len(headers) == 0 {
    . H- ?2 O8 T$ Q/ z
  5.         return s, nil
    ( L% j! b% G5 Y8 O; n5 t0 }
  6.     }9 N' a. S* g' R9 O3 y" O6 D
  7.           //完整性检查区块头可用性. C1 M4 u7 G5 _+ `* ~6 j% L
  8.     for i := 0; i = limit {
    + L7 U# ^' X) d
  9.             delete(snap.Recents, number-limit)) Z' S! Q2 r: W- ]1 ]$ E9 K8 x' F
  10.         }5 Q5 x9 O# A4 v1 b( d; {. I
  11.         // 从区块头中解密出来签名者地址) L6 A6 g0 j- Y& U
  12.         signer, err := ecrecover(header, s.sigcache)
    - X, |+ N; }* l" m; B8 _. l2 X
  13.         if err != nil {
    $ h" y9 L% Y" d$ Y9 q
  14.             return nil, err7 J( V+ K  E, Q8 _0 z
  15.         }1 i+ K! j& p: @" I1 M* o' ]8 `: k* E
  16.         if _, ok := snap.Signers[signer]; !ok {9 g- @- \- ^+ D& N8 B/ t  `& d
  17.             return nil, errUnauthorized  U3 ?5 `7 ]0 m# O' B2 T( j& x
  18.         }
    ' k- S( j3 k, `4 |
  19.         for _, recent := range snap.Recents {
      T/ A3 }0 g! J* m- n5 @
  20.             if recent == signer {
    0 j& W0 l. h4 {5 n
  21.                 return nil, errUnauthorized3 \' V: s$ v- ?. l" V
  22.             }
    & k, T1 Q6 m* O" D2 j
  23.         }9 K4 Q- V% O1 f  R
  24.         snap.Recents[number] = signer
    7 w: _3 _: q7 [) F+ |5 z0 d
  25.         // 区块头认证,不管该签名者之前的任何投票, {" M' u0 O! C) f" t% w. a) s
  26.         for i, vote := range snap.Votes {* A( M3 `% P8 k6 A9 m) J
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {9 E# g- m" J0 K& B. e5 R
  28.                 // 从缓存计数器中移除该投票) }! h: N1 `. k# W1 C
  29.                 snap.uncast(vote.Address, vote.Authorize)4 r1 {3 ^- p" f: H  j9 f. y7 b
  30.                 // 从按时间排序的列表中移除投票4 `; a* l4 [4 z* E8 Q& i4 w) c: c# K
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    & v) U6 C2 q* E. m& z& H! r! W
  32.                 break // 只允许一票
    ! p* @  Z! Z0 w, U' V% B
  33.             }
    $ y1 K4 b/ C. b( v; V9 b
  34.         }: o) I7 T. K0 S. U' m
  35.         // 从签名者中计数新的投票  Z% u7 k0 Z$ J+ u7 \) c3 n- I3 L8 `
  36.         var authorize bool" q5 k+ p8 k" z
  37.         switch {
    1 C& C8 {$ d/ b# A( _) }7 M
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    - U, w0 c2 L4 j0 ~% f6 i
  39.             authorize = true
    ) ^+ M7 a& H$ X. y; C0 N  N2 x/ }; D
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):
    & M; B# A6 ~( O; |% X' u
  41.             authorize = false
    5 {8 B" X# r6 i) x, E  P3 m6 K
  42.         default:; G1 n$ b1 e$ ?6 k
  43.             return nil, errInvalidVote
    0 C+ t, q6 H6 a
  44.         }
    ) ^: }. P  Z: A5 {$ \
  45.         if snap.cast(header.Coinbase, authorize) {
    0 g' p( W9 d$ |8 A0 T* f
  46.             snap.Votes = append(snap.Votes, &Vote{1 E: w: M! O8 z) @
  47.                 Signer:    signer,) }2 {5 x; c! [
  48.                 Block:     number,+ a, v( i5 q9 P
  49.                 Address:   header.Coinbase,
    , w1 w  [+ I, v. ^7 _$ v5 O
  50.                 Authorize: authorize,# G" ]4 j# R7 b% P9 N
  51.             })& Y' n5 q6 z8 C( C5 B
  52.         }
    ' q4 V+ j* R; H* s
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表& Z: B7 o1 e: k+ Q
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
    & T. w! J, m/ z9 L
  55.             if tally.Authorize {, @4 u$ O* R- i6 Z' k* p* f/ V
  56.                 snap.Signers[header.Coinbase] = struct{}{}0 W1 c' v6 r4 `: D% R
  57.             } else {9 b& L# B0 y4 p+ x: Q8 L
  58.                 delete(snap.Signers, header.Coinbase)
    9 ^- M2 ~2 c7 D
  59.                                   // 签名者列表缩减,删除最近剩余的缓存% a& t- g8 X0 ^7 o7 I4 k
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {7 ^& ?& K* v* T
  61.                     delete(snap.Recents, number-limit)  d( n' D0 ?5 T5 m. u2 c6 N0 y2 _1 I
  62.                 }
    4 {, C" ]% P9 K
  63.                 for i := 0; i
复制代码

" Q. F8 _7 a2 V' k; sSnapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。$ ]5 d# [1 c( j, _/ }4 U
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。- g9 t. c% E8 |, H5 i8 s; d+ E
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
    " p  `: @4 P+ w; D
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {( u0 ^/ v" a- A& D. H
  3.         // 不支持校检创世块
    5 {" O+ |- A8 Z" P2 o" E) U5 P% d
  4.         number := header.Number.Uint64()0 x2 H% c5 g+ ?# a
  5.         if number == 0 {0 e4 D/ i" y( b0 R7 d1 P
  6.                 return errUnknownBlock
    " E2 g9 ~2 z; I& w! M2 ?
  7.         }- ^7 n  v# X9 a  @7 ~: `
  8.         // 检索出所需的区块对象来校检去开头和将其缓存' C, c4 \/ P% F: ], c
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    $ e3 Q3 Q4 x0 R* k3 J6 z2 `
  10.         if err != nil {# {) ?0 e1 `0 C3 a; P, L. F
  11.                 return err
    # O, `4 I3 E( a- w- R) F$ ]1 ~( [
  12.         }
    2 ], m$ O& l: n* K( ?" x$ p; I& d
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址4 `; J0 i6 b* b
  14.         signer, err := ecrecover(header, c.signatures)
    $ X2 ^; s; |) ]  ?: O% _9 v
  15.         if err != nil {
    ( o! I( \1 I6 k: ^9 Y6 L
  16.                 return err. O$ s" |- K. S0 g( M& i* e9 o* ^
  17.         }9 @% p2 M0 ~" ^8 ~/ {/ [  W
  18.         if _, ok := snap.Signers[signer]; !ok {1 ?( ^4 m+ x: B8 `
  19.                 return errUnauthorized" Q" W! X2 h1 A! ^8 H
  20.         }
    9 ?* r1 ?5 T+ y& {! \
  21.         for seen, recent := range snap.Recents {
    ; x+ e! Q& L6 E& O* ]
  22.                 if recent == signer {
    " U5 v0 }1 _+ B% l2 ^
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等7 k8 ?; m# ?+ x
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {2 Y' g6 N9 ^: G5 j6 @
  25.                                 return errUnauthorized. |! \5 M6 R. v) c/ V4 e! t* F' R
  26.                         }4 h1 ^8 N, {' Y% Q. E4 g$ @
  27.                 }
    - k: n+ F( T) N/ T1 E
  28.         }( F6 {+ q& f0 V" t: E$ N: M
  29.         // 设置区块难度,参见上面的区块难度部分4 a( d% E# W: P
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)* i+ A+ Y# T" Y  q1 w
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {3 S# g+ L" c2 q5 k7 K- Z) [
  32.                 return errInvalidDifficulty
    5 {  a8 U& T4 t6 h! L9 h5 D
  33.         }: B: F% Z$ U* h
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {5 {# p- _- Z( }) B: e" A
  35.                 return errInvalidDifficulty1 r* f5 R! X/ v7 z
  36.         }3 |! ~1 q* k% G: R% A* K' N
  37.         return nil% b5 J  K+ L2 u) l( z
  38. }
复制代码
: x( A, _4 q" T6 ?; A: H
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
$ D2 O0 j0 ~/ B8 Y# ^! ^Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:  b. ?3 e+ X7 J9 B: Y) r
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中. F, W/ J* D' `9 [1 V' b3 O4 W0 N- Q
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
    ; b; n  C9 t3 @( _+ ]5 p+ b1 C
  2. func (api *API) Propose(address common.Address, auth bool) {4 `& c/ [; V$ z3 |1 [7 n
  3.     api.clique.lock.Lock()1 Q  ?# S7 U) Y6 s2 ]
  4.     defer api.clique.lock.Unlock()
      Z) g4 r, e/ a" }7 a/ Y5 m! |
  5.     api.clique.proposals[address] = auth// true:授权,false:移除. T( e- n) X* Q9 e
  6. }
复制代码
9 @9 N/ e) |1 L: U! C; c
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;; e2 J; V8 E8 ]9 J6 f% W% u2 O
  1. //Clique.Prepare# I2 J' }9 N0 ~) f+ H8 _
  2.                 // 抓取所有有意义投票的提案
    ( _! j; e4 H, }6 p1 Z, @
  3.                 addresses := make([]common.Address, 0, len(c.proposals))$ Y: k0 v/ @7 J# H# U
  4.                 for address, authorize := range c.proposals {; F! R" i; J; T3 \
  5.                         if snap.validVote(address, authorize) {
    . G, n1 t3 H# K# b, F' M: ]
  6.                                 addresses = append(addresses, address)
    ( h! j) Y6 m) C% l2 [
  7.                         }
    " ?" s0 ^, m% \
  8.                 }
    ' R" _& j; b6 F8 g- H
  9.                 // If there's pending proposals, cast a vote on them; @* O1 U; ~( U" K" c
  10.                 if len(addresses) > 0 {' U% J8 [& J, ~! D/ Y2 B
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。
    8 x  M* B% ~; O; {( f2 M# o' v/ F
  12.                         // 通过提案内容来组装区块头的随机数字段。* a. s- W/ H9 T" R
  13.                         if c.proposals[header.Coinbase] {
    , i0 V: k; ~7 L) S7 a
  14.                                 copy(header.Nonce[:], nonceAuthVote)0 N* ]! ?0 b: x
  15.                         } else {) M4 I4 o- ?$ o+ V/ Y2 s- K
  16.                                 copy(header.Nonce[:], nonceDropVote), _2 k7 K0 Z* R4 W+ x$ J
  17.                         }2 b! _# X* \6 _8 Y8 t8 |
  18.                 }
复制代码

6 z8 @$ K& `9 Y6 b6 ~$ T在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare0 u; K7 T+ a3 ~& X) m" `( ?( x: h
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {
    ' L/ t, g- X4 k) J- @* h
  2.                 log.Error("Failed to prepare header for mining", "err", err)( l) c& W/ R6 O2 [
  3.                 return3 n" A5 m- Z4 f: L
  4.         }
复制代码
+ h, N* G8 P& s, k7 S- D6 `
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法
5 H; _9 o( M. w8 m. x/ L4 m- L: x. }) j  N* f

4 T/ |6 V- v; w9 b' ^! Z' Q0 L& j" b, d
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3