Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2449 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。# g2 E( ?( s; k, E- Z
  1. func (s *Ethereum) StartMining(local bool) error {1 f4 M% `6 e4 \7 x
  2.         eb, err := s.Etherbase()//用户地址+ p1 y- U# u6 A; y) f6 t
  3.         if err != nil {! t; }0 y$ y: o/ U* Y
  4.                 log.Error("Cannot start mining without etherbase", "err", err)' w) j% f( K* a4 \4 i9 [" K/ r
  5.                 return fmt.Errorf("etherbase missing: %v", err)+ L+ {0 q! {$ ^. B  v9 I3 s
  6.         }5 C+ h& q: T' I& q! n. u6 K( J
  7.         if clique, ok := s.engine.(*clique.Clique); ok {
    ) R0 E9 W! ~- J1 B
  8.                 //如果是clique共识算法
    - O% }- Z" b, l& D3 _4 }; M
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象* K* ]3 ]5 o/ o$ n3 x
  10.                 if wallet == nil || err != nil {
      h6 o; [8 b: I3 A, r& k8 P
  11.                         log.Error("Etherbase account unavailable locally", "err", err)
    8 S* T5 R$ f5 Y" V* Q9 t/ e
  12.                         return fmt.Errorf("signer missing: %v", err)" w4 |# o! \( r4 b0 t) W
  13.                 }( i8 [5 F3 \9 P6 Z1 j) b: s
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法
    5 t9 ^9 Y$ h6 m% q, d
  15.         }
    : Z: T  R3 C: s7 u+ R% F+ _
  16.         if local {
    9 ~' K, K3 R! x! p8 F3 i7 J
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
    * A) [9 f6 Q  U: A/ l+ g! S* M
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)% [  [- V% Q* i8 J
  19.         }+ O. i- `5 O: L/ h+ e8 m" S* A
  20.         go s.miner.Start(eb)$ H) h: s- R) v) V6 I% {
  21.         return nil' L9 y% a( C$ q7 h; u/ y6 i8 `" L) T
  22. }
复制代码
4 b* b* r8 F. A2 X  Z
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。; y/ {- e& ]. ]/ S; P
Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:! E) {" M( W3 B+ F& i( y
type Engine interface {# |9 q5 \" k: F5 d& R  J
        Author(header *types.Header) (common.Address, error)' l5 g1 z1 X* a% C, w
        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error/ u2 x; l2 p1 K1 s& s' |% F
        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
8 k6 ~/ w4 C3 {9 L3 b4 p  D% GEngine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:
( p, m' y* W* a8 e
  1. type Clique struct {
    % q) E4 D# j0 B; F7 c; d
  2.         config *params.CliqueConfig // 共识引擎配置参数
    - |# y4 _; F+ v9 c
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点- F) m- M2 j0 N  w
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组
    * j9 `* R$ l0 G" ^0 Z$ r, s5 E9 c
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿& G8 W2 o6 \( ^2 @% D3 i
  6.         proposals map[common.Address]bool // 目前正在推送的提案4 Z$ \+ m' a+ o
  7.         signer common.Address // 签名者的以太坊地址! W' ]5 x) ~- l
  8.         signFn SignerFn       // 授权哈希的签名方法* V0 w6 R. Z4 i- e" f8 O% @2 \" G
  9.         lock   sync.RWMutex   // 用锁来保护签名字段1 N6 |3 K) \. w
  10. }
复制代码

2 ~4 c& v" h/ f+ Z2 I3 D% ?顺便来看下CliqueConfig共识引擎的配置参数结构体:. y8 M* k5 z: w  Z3 b
  1. type CliqueConfig struct {' E. }* B5 @2 I/ k' E
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)( c3 B+ l% K& C/ h0 u$ C
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)0 Y% t- o: z9 s( m
  4. }
复制代码

8 a( M5 f4 v2 e3 c在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
7 ~; W& M; m, r/ u) d$ B
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {  b" q( \9 T# l- Q# [: x6 ~( ?
  2.     c.lock.Lock()0 h8 v% ?$ y. `$ j+ }: M' X
  3.     defer c.lock.Unlock()
    9 g/ w& }4 o4 B& g& Z: ]. d3 L
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块" I9 S% L* v, K
  5.     c.signer = signer0 R, n; R: y& a4 F- Y1 ]+ Q8 k' {! w
  6.     c.signFn = signFn
    : s1 W3 q- @1 b8 W: K5 \8 w5 b
  7. }
复制代码

- d5 H; [$ u. e& J; m) m) y再来看Clique的Seal()函数的具体实现:: Z% p$ J; T0 e3 X
//通过本地签名认证创建已密封的区块
5 j  U( c% Q4 C) sfunc (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {
9 ^) _' T" B( f! Y8 |! ^1 x                                log.Info("Signed recently, must wait for others"), S4 Q. f2 s" M4 s* c& H( D
                                
; |8 Z' j, d* T: G7 P) zSeal是共识引擎的入口之一,该函数通过clique.signer对区块签名' X( I2 D; ^4 S+ ~9 o. ?
signer不在snapshot的signer中不允许签名2 F$ q5 ?% _% i& r; Y
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
+ s+ A# z" H: l+ K& H签名存放在Extra的extraSeal的65个字节中! T( A6 ?8 V$ F9 o; k" Q
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。
3 k; A8 q6 b) P
  1. //snap.Signers是所有的认证节点( L& I5 N  h+ c+ O) t
  2. for seen, recent := range snap.Recents {$ G/ _( n; i9 C3 F. e* c  ?* X+ l
  3.     if recent == signer {3 ~7 V5 @6 l9 O& M
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {
    2 }" B; v% `9 j; G9 e+ l
  5.             log.Info("Signed recently, must wait for others")
复制代码

0 x3 @" C* ?5 K- O# A7 k' C            / e1 x! b" w1 V  G& l" L
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
1 E* l4 Z% l( k7 g: H  l$ n- w关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
0 l3 ?  {: n5 t8 K$ XdiffInTurn = big.NewInt(2) $ \* h1 b" o/ P' _# i+ Y5 R+ U
diffNoTurn = big.NewInt(1) ; n: @- F( U: s3 M) ^
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
) {; u& d0 m4 i// 通过给定的区块高度和签发者返回该签发者是否在轮次内
5 x7 W7 \! l( s# V: w$ U
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    4 S" B8 L/ e2 G) Z! c. c
  2.         signers, offset := s.signers(), 04 h' k! a$ n/ N" i' X" A; {: _
  3.         for offset
复制代码
9 }8 U& T  Q: g$ W6 W
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:% k' ?  {$ k9 p" p- `, B* E+ B
// Snapshot对象是在给定时间点的一个认证投票的状态3 f7 E! R. q" |: z
  1. type Snapshot struct {* ~% D, q1 X7 u' R3 ~% H
  2.     config   *params.CliqueConfig // 共识引擎配置参数7 I; U: b9 q* m0 \& r; S. N
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。6 q# u& J6 K1 d2 s0 }7 _
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号
    $ Q, u4 o3 [/ E0 @9 y1 k
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希4 T/ Z9 Y% L1 F. d" a! c$ q; {9 s# F
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表6 ?- N6 J9 ^1 `! u% {9 D7 m
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址0 ]* I5 }0 d# w$ M
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    ( z, e# Z2 M% u+ F8 P
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。
    2 J: [9 v: E  W2 q* L5 n8 Z
  10. }
复制代码
# P" S) ]& X( U
快照Snapshot对象中存在投票的Votes和记票的Tally对象:
6 o5 G6 O  Q( \( C0 Z. H8 j
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。  N: c) l5 M' O. \7 a& q
  2. type Vote struct {
    + ]5 [0 K& t! _- R5 D; c2 L- f; w9 z
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
    ! q* Q' S1 t! P" \4 B, ?6 z: k' V
  4.     Block     uint64         `json:"block"`     // 投票区块号3 w3 ?, K! A  `6 `8 m
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权* M+ C- m+ S8 G' h0 J* i( \
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权. C7 X4 i* ^8 y, `
  7. }/ S# a+ c4 k9 a/ M. Q, v, F
  8. // Tally是一个简单的用来保存当前投票分数的计分器% n! q: u5 T3 ]! Y1 o* h' O2 G
  9. type Tally struct {
    ; `; h. _& ?  J0 u; S
  10.     Authorize bool `json:"authorize"` // 授权true或移除false
    : x1 b/ I5 |% d# f8 m
  11.     Votes     int  `json:"votes"`     // 该提案已获票数" ~% K6 R% `. \
  12. }
复制代码

2 }8 l' U! \; y* m6 i% \Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:+ ^  K9 G& g9 ^0 l) P& B$ B
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
    * {. _5 J9 t+ g% r
  2.         //使用Database接口的Get方法通过Key来查询缓存内容
    1 n. a; H; H3 e" f6 L( D, Q
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    / p. G3 \' [* O4 s9 F6 l
  4.         if err != nil {
    0 a% @" D  O) E, ]0 K6 Z) r
  5.                 return nil, err$ M( O, `5 }  T  `5 E! `
  6.         }! V' q- q' |9 M! v# U
  7.         snap := new(Snapshot)
    " _  e6 x6 x; j) D$ E0 i7 c6 I* \
  8.         if err := json.Unmarshal(blob, snap); err != nil {/ y7 P& I" @5 n; \2 ]/ ^7 v* J; B
  9.                 return nil, err
    8 V6 j% ~0 r9 R- I3 D* d+ i
  10.         }/ x! T! m  J3 j. ]8 v
  11.         snap.config = config
    ' |# c8 u9 y% t' M5 e
  12.         snap.sigcache = sigcache' u# @8 r* I* a
  13.         return snap, nil6 V, D; j' y  ?% I
  14. }
复制代码

& c8 Z: X! q, Q# j+ F5 j/ K8 H6 J# lnewSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
; i0 W# B: m$ E9 \" j
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {) g( H$ Q; {( K5 t$ {
  2.         //组装一个Snapshot对象
      g$ f6 e8 F% e4 r- H+ o
  3.         snap := &Snapshot{5 r. s/ V% n+ y6 _$ ]: t
  4.                 config:   config,: s+ M8 G! P7 I
  5.                 sigcache: sigcache,& I9 l0 v* e, s# E& H& l+ v
  6.                 Number:   number,( P/ _7 v) q1 O# ?" ^6 G$ N8 m$ F
  7.                 Hash:     hash,
    5 V, v* I# l  F- ^. Z4 X# ?- e
  8.                 Signers:  make(map[common.Address]struct{}),
    5 |6 U6 R+ x" ?2 o# h# I0 m
  9.                 Recents:  make(map[uint64]common.Address),
    + @9 H. e3 U$ G1 a0 i8 u5 I$ ~
  10.                 Tally:    make(map[common.Address]Tally),/ ]! N8 R( c% @( _& r( b
  11.         }
    8 e* H- p9 H% }) K& T7 E# x) {- w
  12.         for _, signer := range signers {
    - t; k( \) D5 T8 s! ]; V0 w
  13.                 snap.Signers[signer] = struct{}{}
    ) Y/ H: z" U. q$ j. @
  14.         }) a8 }& v; J8 r! e
  15.         return snap+ R- d% @! m8 `. O
  16. }
复制代码
+ z6 c4 Z, A/ J2 j. w
继续看下snapshot函数的具体实现:" W/ U5 A7 y5 c# ?; \
  1. // 快照会在给定的时间点检索授权快照  s, x  z6 _( }2 b& h; B
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {& [: N  ~* a) q
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints7 A8 P3 G$ y+ ~- Y
  4.         var ($ ?# @  d; I0 d# z7 Y7 i' \( o
  5.                 headers []*types.Header        //区块头2 p1 J" y7 W  b2 M  {9 x4 O0 f: ~
  6.                 snap    *Snapshot        //快照对象
    9 b, V  g. l+ q, s: z; `
  7.         )% V5 [  a! [' O7 q5 N  s' J
  8.         for snap == nil {
    % ~, K8 i( y- g1 }
  9.                 // 如果在内存中找到快照时,快照对象从内存中取
    2 E6 ^/ M$ d8 E* Q
  10.                 if s, ok := c.recents.Get(hash); ok {2 ?5 g" r( j: X( z5 o, C  [0 V
  11.                         snap = s.(*Snapshot)/ j  _0 s% \' |. P6 H
  12.                         break" f4 y2 Q3 P, \* h, r& C
  13.                 }  M5 l) h2 F4 }$ v1 C* Z
  14.                 // 如果在磁盘检查点找到快照时
    : V; Z" |* P% o) Z% T
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
    6 g' B$ ~( Q+ g& i( X: N
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
    * U6 Q0 W7 j0 g' E- L6 }& }1 j
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash): |* l# v4 e' W) s
  18.                                 snap = s9 z1 j2 l2 r! F
  19.                                 break1 c* n5 n$ T/ R3 n% W; d( f
  20.                         }
    . O2 P6 n) y# T! L- @
  21.                 }
    4 @2 c( f" W7 [. p) t# L1 K# [, Z
  22.                 // 如果在创世块,则新建一个快照
    8 O$ j* }' I5 ]6 U- S
  23.                 if number == 0 {
    4 L1 T# v, \* C8 S
  24.                         genesis := chain.GetHeaderByNumber(0)& Q: n: A& b! J0 |
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    ) |2 \5 O: O! y! n% G9 z" s
  26.                                 return nil, err. w' q$ b% W$ g/ Z& w+ a6 |
  27.                         }$ W8 f; A- ?! ~
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
    + x4 N$ {% [+ ?/ F3 }
  29.                         for i := 0; i  0 {
    6 Z/ g5 S) N, H: O3 G+ j, m
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)& q3 L' i+ n+ J
  31.                         header = parents[len(parents)-1]1 s% _8 O4 @) e' {8 C9 ^" M/ S
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {; T9 o* r/ W! Q0 `
  33.                                 return nil, consensus.ErrUnknownAncestor" [# R% B/ q% X& _3 S
  34.                         }8 _: Q  n  z; N9 ~
  35.                         parents = parents[:len(parents)-1]
    3 W9 T% E: R9 \- `3 }: m9 d% M
  36.                 } else {& p# [( X  d& F' J0 @0 Q" u
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    ; v- n9 K" V. Q( G# p
  38.                         header = chain.GetHeader(hash, number)
    - @4 C* H5 D: c
  39.                         if header == nil {
    - G2 u, g9 r; h, U: p4 |
  40.                                 return nil, consensus.ErrUnknownAncestor
    # m; d/ y7 c  a4 i/ X
  41.                         }
    5 B) C4 O' s/ F# C+ C% G# c* E
  42.                 }9 Y; @9 @& v5 S% t' b
  43.                 headers = append(headers, header)6 m8 q; E. d6 D0 @% r4 Z
  44.                 number, hash = number-1, header.ParentHash$ d9 u% i% x5 a/ Z% T# B1 p9 c
  45.         }$ e5 g1 p; X' e
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面
    + c& b8 e" t. K  y0 r
  47.         for i := 0; i  0 {/ d! v) @; `+ I, y; N. }7 O
  48.                 if err = snap.store(c.db); err != nil {0 N; z" Y( Q7 D2 H: N
  49.                         return nil, err0 A( w+ q2 H1 R1 u5 o
  50.                 }/ h7 Q+ J' U+ h
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash): N  l' f; l6 L2 W
  52.         }0 A% m1 [6 R# ?$ y" t, e  [4 y
  53.         return snap, err0 G4 n) a+ D: h8 s' D
  54. }
复制代码
4 ]! w, ]8 n: K# F: x3 f
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?
2 ]6 X8 X3 c5 ?- @, s4 y8 X- ]+ J" Z
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
    + @  h1 Y& `: `* G7 C
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
    ) H) N8 W% o* t  K
  3.           //可以传空区块头* x" r% F7 c8 I
  4.     if len(headers) == 0 {! c  r' T+ h* v
  5.         return s, nil
    4 ^8 T. U* K6 S) e/ @- q
  6.     }3 i7 p6 O4 S2 W
  7.           //完整性检查区块头可用性
    , ~2 |1 P! }! g7 W* w
  8.     for i := 0; i = limit {
      d' `  s4 w, o2 t3 b
  9.             delete(snap.Recents, number-limit)
    & Y& K) L" j. l) F
  10.         }
    # z+ \7 u$ P7 @, \1 v* J
  11.         // 从区块头中解密出来签名者地址
    1 }3 `8 Y, M. o# E  l4 R
  12.         signer, err := ecrecover(header, s.sigcache)
    ! S/ J3 g6 e' X6 C0 E# p2 W1 _
  13.         if err != nil {; D* {# q+ t2 @
  14.             return nil, err
    ; |4 \3 k; [! a: e. A# G
  15.         }
    . Y0 |; t4 g7 F% H, O1 F4 M
  16.         if _, ok := snap.Signers[signer]; !ok {
      y; N+ v+ P% }6 V
  17.             return nil, errUnauthorized
    / F9 a- f8 {8 |) N4 w
  18.         }
    : B7 x. Y  ?% R7 q! D' |5 q
  19.         for _, recent := range snap.Recents {5 c0 U1 m" |9 c- f7 A3 q8 }6 P
  20.             if recent == signer {
    2 B; \3 h  A( x) Q
  21.                 return nil, errUnauthorized
    ) f7 F" D* e5 e: R( n
  22.             }/ p- \; l( g9 k& I
  23.         }) n" X2 W3 t8 O# F, k) H
  24.         snap.Recents[number] = signer
    & y% ?0 D0 _+ O3 l: A6 S9 ]
  25.         // 区块头认证,不管该签名者之前的任何投票
    * h' b! l1 O2 X! |
  26.         for i, vote := range snap.Votes {
    6 u5 S: m2 }* M2 O% Z" m
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {0 b, }* r4 }/ K4 j3 q5 _
  28.                 // 从缓存计数器中移除该投票
    " A* {- m% Y- {5 J* w* E- q
  29.                 snap.uncast(vote.Address, vote.Authorize)
    8 @9 u5 M! D; @5 \9 {
  30.                 // 从按时间排序的列表中移除投票5 w. c% |" |$ m& |) |
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    2 L+ _$ R2 C, A$ l
  32.                 break // 只允许一票
    1 l0 S9 W& @3 E1 |
  33.             }
    & J) E; j6 d2 a2 O: {% t9 i0 {
  34.         }
    ( P& t5 U9 X" L5 r' K3 E
  35.         // 从签名者中计数新的投票1 c% k# v0 B/ F9 w6 l5 Z, M+ [
  36.         var authorize bool" q/ N- Y1 ^  [' k+ Y
  37.         switch {) z; J0 Z& u: [2 n/ u$ A
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    ( H" s' V7 J4 E' Z
  39.             authorize = true  U6 z+ B9 _9 m7 M
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):
    ) f" B2 m  m% h" i7 z4 q
  41.             authorize = false
    . r7 V5 c: e5 X$ b9 V) m# J: s
  42.         default:( A3 x5 S1 M) R' a7 o2 p3 K
  43.             return nil, errInvalidVote/ Q0 Q3 H7 @1 [5 u: a
  44.         }
    ! t9 U% E$ H& R* s. W" w0 k5 C+ ?
  45.         if snap.cast(header.Coinbase, authorize) {
    " r8 b  Y2 G; `) T- a# {- X
  46.             snap.Votes = append(snap.Votes, &Vote{1 H; H. g: x- K! b3 p7 O
  47.                 Signer:    signer,% a: N& V( ~$ h8 Z
  48.                 Block:     number,8 }4 f! p& T( a4 O
  49.                 Address:   header.Coinbase,. R% I' f# R6 _( o# B
  50.                 Authorize: authorize,
    # W* M3 p* ?' g; W* m( B" q" Y2 a
  51.             })
    ' M$ j/ P, R, \- c7 q2 v& I
  52.         }
    - W/ K2 n/ {& e0 u  n. D' @
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表. o6 U5 y" g* S  P3 ?1 @1 H% q8 Z
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {) U7 k# T8 X# E7 O" j+ h
  55.             if tally.Authorize {
    . f# `1 h% u; l/ e9 o& m6 R
  56.                 snap.Signers[header.Coinbase] = struct{}{}
    $ g/ K5 C/ ]" K9 ?' q( L
  57.             } else {! ~" D0 X* D' k9 p2 ]% j
  58.                 delete(snap.Signers, header.Coinbase)$ P8 S" F, h5 n% u9 a) Y* i
  59.                                   // 签名者列表缩减,删除最近剩余的缓存
    ( o9 X! V- h2 X; X7 k
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
    & `7 _$ {7 {! v8 _) R
  61.                     delete(snap.Recents, number-limit)# h, `5 E. a# P. e! S5 \8 K
  62.                 }+ o( i0 f5 {4 O) }4 G& T. Z
  63.                 for i := 0; i
复制代码

5 F! u# U5 E0 j1 a9 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快照结构已经更新到哪个区块了。7 U4 l0 o+ g5 S. c3 y3 m' Z. B8 J
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。- k! C/ @- E* q  k7 Q: X
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
    ; g1 @, ^" `& x" s% v5 Y6 Y
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {, B4 |: h# C  J; _
  3.         // 不支持校检创世块4 n1 Z3 F! K. t+ D/ H$ C
  4.         number := header.Number.Uint64()
    ! l' m6 M6 E# y# D" V
  5.         if number == 0 {
    : P+ y7 j! b. C7 `- e- ]
  6.                 return errUnknownBlock8 a2 G8 y7 n1 k, ?) R2 D& K
  7.         }
    + _9 s5 E# H' `% J: C
  8.         // 检索出所需的区块对象来校检去开头和将其缓存9 C. b/ y: |" L4 s2 z, B: L: h6 o
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)" n( ?1 u+ g8 n* m
  10.         if err != nil {3 T+ K3 Y0 c  H' a
  11.                 return err  ~7 r' V' P  u- w' _/ N- W
  12.         }
    2 ?4 ?) i! D5 u/ O1 `
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址
      z& _) U: W8 w; y8 r6 c, a
  14.         signer, err := ecrecover(header, c.signatures)7 T9 e8 Z" J7 Y1 X7 O( ~3 B9 N
  15.         if err != nil {
    1 J- e; M! G) b7 }3 X: F) [! Y
  16.                 return err
    . c0 H. S2 b$ n4 W
  17.         }
    4 S$ _' j% A$ E% e0 T
  18.         if _, ok := snap.Signers[signer]; !ok {
    , J& ~8 @) M/ e+ d/ w* c# o
  19.                 return errUnauthorized
    & o( H, q) H% }8 Y
  20.         }
    9 \1 d5 j( @) L) T% N- r) T: ?
  21.         for seen, recent := range snap.Recents {
    1 Y" x/ e, r3 D# `
  22.                 if recent == signer {
    7 E( \/ Q7 C2 m( {; |
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等5 |2 {) P# ]6 b# U
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {4 J2 P% d; k- s( z- B
  25.                                 return errUnauthorized/ S8 F5 k, `  }
  26.                         }
    5 E0 @: d8 k9 q+ s
  27.                 }
    5 I* p% O( _% {6 c/ K6 v
  28.         }2 s+ E( u  O7 i6 a- v
  29.         // 设置区块难度,参见上面的区块难度部分
    ; i5 D( C6 E! \. o1 M* |
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)
    2 a5 i# t/ `  s- C; |" X0 }  n0 v9 M7 O
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
    # I* s& K9 F6 U- T6 _1 z
  32.                 return errInvalidDifficulty9 b' H! R- e9 Y) s" F8 @$ x
  33.         }  c5 D! q8 {: y' z7 I* v5 v
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {/ N) c' u6 K" e+ I
  35.                 return errInvalidDifficulty* f3 h: O; J9 {0 s
  36.         }
    + F/ R# }" @1 j5 S
  37.         return nil+ r# p+ v( p7 g
  38. }
复制代码
, V3 \. g3 [  F; Y7 ^
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
! {4 u) T9 E6 H8 B5 ]  X1 H; IClique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:
6 t6 m; K. \0 e, R% c1 r委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中4 d, R/ }5 e+ r+ Z
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
    , J" Y/ W4 M2 F7 t! N. B
  2. func (api *API) Propose(address common.Address, auth bool) {& s) |& M5 l$ B. ~
  3.     api.clique.lock.Lock()
    " m5 Q! _6 ~2 n0 n, D6 a  U
  4.     defer api.clique.lock.Unlock()
    $ Z& r1 o' k" |5 W+ h
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    1 ]9 j" i$ P0 `+ `6 I& M
  6. }
复制代码
' j3 w3 z$ i) r- n
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;
4 h3 [( N- P4 e9 J' s! f7 d
  1. //Clique.Prepare' r- |* y6 N+ [3 o1 l
  2.                 // 抓取所有有意义投票的提案4 W3 F& X% p$ v6 t/ K# r4 o
  3.                 addresses := make([]common.Address, 0, len(c.proposals))8 d- Z) t, V! a1 L
  4.                 for address, authorize := range c.proposals {$ R7 R3 ]" Z. }1 ?* D7 q
  5.                         if snap.validVote(address, authorize) {3 a4 ]( A) U- ^
  6.                                 addresses = append(addresses, address)7 I+ E# i( o/ C2 B
  7.                         }
    ' D9 l- \! z) l- q; y1 g) r8 Z; H, \
  8.                 }+ k2 _8 K0 p( y# @; a7 s1 _
  9.                 // If there's pending proposals, cast a vote on them
    3 e# r/ z- j( q8 Q, e4 f5 J
  10.                 if len(addresses) > 0 {# }& {' Y& M- e" b- C$ f, K% I
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。
    3 T. m7 ~7 i5 ?% K; |4 B
  12.                         // 通过提案内容来组装区块头的随机数字段。
      r" z! `9 m2 w5 G( I, k/ M1 W
  13.                         if c.proposals[header.Coinbase] {9 B0 w" V3 o9 g6 h$ X* F- c
  14.                                 copy(header.Nonce[:], nonceAuthVote)& u. @# }+ e* \, i
  15.                         } else {
    ; n$ ]4 `  H" p0 `/ j  E0 ~
  16.                                 copy(header.Nonce[:], nonceDropVote)
    7 P5 t5 F1 G4 O/ J' s: [
  17.                         }; C2 Q+ l: f1 D
  18.                 }
复制代码

, l, o* r" m; ^, B9 l在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare7 G1 O  T6 g" |2 A  Y- W3 n
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {8 Z5 I* j: w- Z6 b- N" ?! u
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    . k3 v- t3 U5 B( d, V$ a
  3.                 return
    5 r+ V/ k+ J  n5 v1 t
  4.         }
复制代码
! ]$ A+ p' x1 b' i0 g5 j
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法0 b" h. z/ q/ t, C, i* b* Y
9 O2 @9 O$ b) J9 N
5 U  O  z4 u* n7 H9 o/ @& C$ y7 Q

+ _- g0 A9 @- c2 j2 R; X以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3