Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2480 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。: h2 f4 E, V& ~8 ^( o1 U- Y( `; S( r+ {
  1. func (s *Ethereum) StartMining(local bool) error {
    ( H0 z7 N% s( L) l  j$ P, u3 q0 P
  2.         eb, err := s.Etherbase()//用户地址0 E6 D( l3 |/ b  y3 w
  3.         if err != nil {
    6 p) u$ W  \8 u* h& b$ \+ x
  4.                 log.Error("Cannot start mining without etherbase", "err", err)0 m2 l) B$ P. ?- k, w! u
  5.                 return fmt.Errorf("etherbase missing: %v", err)
      l: D0 y7 f' S3 P2 k
  6.         }: \& L( Z  T2 E1 T
  7.         if clique, ok := s.engine.(*clique.Clique); ok {1 d" p5 `: m; E7 W$ ]
  8.                 //如果是clique共识算法
    , [  U- y0 F: ~( a9 H! t4 Y
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象5 S8 i+ G$ n6 U
  10.                 if wallet == nil || err != nil {: [1 s' S% P6 I0 `- b: _, @
  11.                         log.Error("Etherbase account unavailable locally", "err", err)/ M+ x; p0 H7 T+ v& X3 \
  12.                         return fmt.Errorf("signer missing: %v", err)) r7 L' q$ |. T% p" Y/ a
  13.                 }
    7 s& x; J/ |1 `4 f
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法! b$ Z' ]1 b0 Z* W$ O& S& V4 S
  15.         }2 E% \  z7 r% R5 C/ f2 F7 P/ B
  16.         if local {, q" C) y9 w* l5 p: V, K* \! L2 N9 i: v0 W
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。# ]9 Z+ j( r. D
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
    + M( k, s- `8 w
  19.         }1 S0 U' D$ b  V" ?5 i1 Q: Z/ C
  20.         go s.miner.Start(eb)
    0 n% m2 Q3 L+ f
  21.         return nil- i- w' ~. G& Z! A! j
  22. }
复制代码

0 S6 i. R4 i7 B; d6 w4 {2 n" A这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
* H) p0 f. u# n" \, Q' e# I! IClique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
! F& |! G- H# p2 c+ vtype Engine interface {
, h; Q# J1 I  T: k* ]7 @$ w" S        Author(header *types.Header) (common.Address, error)! K8 A: c5 I& @
        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
" y; b7 u# n3 j4 Z        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan. z( A$ T# @1 w( b
Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:
5 j  c' Z; r; F% V! T8 A
  1. type Clique struct {
    * a' A, V, l2 \! y. i  G) J  \
  2.         config *params.CliqueConfig // 共识引擎配置参数1 K+ R& b$ G) ~- h- q$ M# `' _+ t: c
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点
    # r# E% c1 B1 T4 f! @" D1 T# \% \
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组* ]5 R3 B) Y% }5 z
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿
    9 x2 x5 l5 E# k8 }" u
  6.         proposals map[common.Address]bool // 目前正在推送的提案
    2 K4 G- p3 Q4 l/ a
  7.         signer common.Address // 签名者的以太坊地址
    ; p) I( g; J  k
  8.         signFn SignerFn       // 授权哈希的签名方法
    5 P5 L7 ?7 g( W
  9.         lock   sync.RWMutex   // 用锁来保护签名字段: h  {( h) a& j( ~  f# l2 A
  10. }
复制代码
% r" \& T0 w8 h8 }; q7 [% Y* F  F
顺便来看下CliqueConfig共识引擎的配置参数结构体:+ t/ i& E: b, O. q4 Q! `
  1. type CliqueConfig struct {
    / ~6 H3 E5 O/ F! q) I
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)
    9 ~( B6 V2 s6 i
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)) q( S& e: B( c& T; o$ g. p
  4. }
复制代码

/ l8 P5 C1 B" y5 p) a在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:9 V1 O1 O* }6 O  f; u: a
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {0 O% j* O" h! @/ ?! p
  2.     c.lock.Lock()
    ' w& Y6 i/ t. k1 d/ e
  3.     defer c.lock.Unlock()$ y9 N2 ^" q5 E; _
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
    8 p% j/ L  \0 ]- q0 [, O. s# j  v
  5.     c.signer = signer
    ) F3 j& N$ f9 j3 _; R8 w' A! S8 H
  6.     c.signFn = signFn9 _1 B8 z3 L% Z) d# ?
  7. }
复制代码

: Z8 `/ l0 I# j& u- Q& F8 L# k; K再来看Clique的Seal()函数的具体实现:) E7 R/ {5 U& Z. x4 K
//通过本地签名认证创建已密封的区块
8 T: p5 x- H, J% j! h* S, M& Ffunc (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {3 J- w) m% B! X0 G
                                log.Info("Signed recently, must wait for others")
( p- |) U' v, J. B( _+ L9 d& C                                
* z: N. r$ X- K9 N5 ySeal是共识引擎的入口之一,该函数通过clique.signer对区块签名- w0 y- H6 Z1 v$ p: C' r
signer不在snapshot的signer中不允许签名2 ]8 G8 F  _4 ~, D3 a
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名: N* F& @1 [5 L9 s* O4 _8 S
签名存放在Extra的extraSeal的65个字节中' o2 U$ W, H1 A7 n6 O( z
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。9 G: V9 ~# N0 R4 C" L
  1. //snap.Signers是所有的认证节点- F7 h' m+ C9 [( P8 \. M
  2. for seen, recent := range snap.Recents {
    $ l7 T& T( |/ t. v4 e% ~, [
  3.     if recent == signer {
    3 Q- j6 D  n7 L
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {
    8 U- d9 O1 y* q9 `9 T. _0 t% }$ M
  5.             log.Info("Signed recently, must wait for others")
复制代码
' C7 E  d0 n  B: V1 g7 ^+ p
            
' z0 c7 P. [) e6 m在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。. g+ J5 |0 A/ S2 t3 {3 {# R0 @
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。4 S1 w. G7 M6 B: J
diffInTurn = big.NewInt(2) ) v. v4 J1 v) P
diffNoTurn = big.NewInt(1) 5 \9 W& n# G* o
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:8 x9 q" \/ h( K1 K/ T
// 通过给定的区块高度和签发者返回该签发者是否在轮次内
- C% c  D+ n4 `' c: U
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {5 }  @' R- v4 o- m0 z
  2.         signers, offset := s.signers(), 05 k: k) E& ]% G4 F7 Z0 Z. _
  3.         for offset
复制代码
8 s# Z. \2 k) A. E
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:
; z- ]* m4 g8 |9 f0 T; m// Snapshot对象是在给定时间点的一个认证投票的状态" G; c$ e) ~! W, r- x5 K* O
  1. type Snapshot struct {
    4 }$ J! F% I3 z7 Q1 Y% S+ Z
  2.     config   *params.CliqueConfig // 共识引擎配置参数; X- h# h" o: A" A" ~4 E, O
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。
    % ~: S; N# e0 i$ U- e9 o
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号+ Z0 t* ^& c4 s) _/ n
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    2 a5 q  U% b3 k  r; v1 w
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表" Y2 R( k4 r0 W" G6 h1 m
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址7 u3 G9 P$ e& Y& K1 W6 u
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。2 H& r* o+ t# y# c' m
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。- b, Z6 g4 e1 C7 {, E% j  V/ |% t- u
  10. }
复制代码
& K) b" u9 o" h5 |
快照Snapshot对象中存在投票的Votes和记票的Tally对象:
4 J: K# V7 B) z/ |& P
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。6 {+ n; W9 ~9 g3 m+ p2 m. d
  2. type Vote struct {7 U( d' \" Y+ t, }1 z& J+ g
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
    * J) A% B% D9 ?" v' s5 W
  4.     Block     uint64         `json:"block"`     // 投票区块号
    9 \1 n. V  i5 `2 W
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权
    6 c. b, ^+ L. e
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权
    ) |# d$ M, h( ?2 S
  7. }! i" K5 b; I( c8 _5 Z
  8. // Tally是一个简单的用来保存当前投票分数的计分器, ]3 S$ B. }& I- S8 ^% w* }2 h
  9. type Tally struct {2 a  ?# }6 ], w  a# t" \/ h
  10.     Authorize bool `json:"authorize"` // 授权true或移除false
    % N/ [9 F1 {- a% D7 a# |5 F2 [
  11.     Votes     int  `json:"votes"`     // 该提案已获票数5 k" l; o1 W% w5 M/ _
  12. }
复制代码

* q' e- |" C4 f* MSnapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:/ F: L& L: U1 \; `
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {$ n/ |  m4 Y: r* n# B
  2.         //使用Database接口的Get方法通过Key来查询缓存内容
    : y/ t7 c% v2 D( O( N1 h( _
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    8 x& k& J1 J2 u9 G+ p  s% C1 r
  4.         if err != nil {
      w% k5 g. h- B1 M8 V$ t  D
  5.                 return nil, err- p5 d# M* w3 {7 W
  6.         }
    # x9 N3 a% R6 T( g* u, f0 b
  7.         snap := new(Snapshot)
    , R6 f' L1 [' t7 G" {
  8.         if err := json.Unmarshal(blob, snap); err != nil {9 T7 R! O0 [, m: j! d% w; _6 B1 s
  9.                 return nil, err  d. i* D; j3 y( L% F
  10.         }2 t, |! V9 G0 T
  11.         snap.config = config$ V! Q0 C7 y5 Y) O1 K$ Q
  12.         snap.sigcache = sigcache
    0 N6 Q1 X1 ]: M% s9 l; R. q8 D% w
  13.         return snap, nil
    $ O, T' W+ V; ?* n) w1 s0 B  H
  14. }
复制代码

2 N4 c# u1 P+ p- M, G2 lnewSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
  R8 P: W9 t: R: r' F6 b
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
    6 Y+ B6 Y1 N, \
  2.         //组装一个Snapshot对象
    & E' J* z. }2 Q1 m6 [' ]
  3.         snap := &Snapshot{# Y( H/ U  }8 D7 M
  4.                 config:   config,
    * R6 k7 ~7 ?1 U# z* ]2 U
  5.                 sigcache: sigcache,
    ) Y. t" ~" v, x8 e0 ?$ J
  6.                 Number:   number,' h4 I+ M' D# z& V; t1 P
  7.                 Hash:     hash," G5 k, F7 o9 H& j$ x/ {( J
  8.                 Signers:  make(map[common.Address]struct{}),
    - d; [* V* f2 }: g4 {( G8 r
  9.                 Recents:  make(map[uint64]common.Address),- M, C! ~& @. w
  10.                 Tally:    make(map[common.Address]Tally),
    ( x$ Q7 o( i1 b5 @6 S
  11.         }
    / p5 n; k3 s/ U& s
  12.         for _, signer := range signers {( m: |0 X, k% G% F( Q9 \9 H
  13.                 snap.Signers[signer] = struct{}{}
      l. x7 w4 @( I# E, U% |$ V, R
  14.         }
    $ _9 }6 L7 K/ M7 t% {- G2 s
  15.         return snap
    , F1 \/ N/ x7 ~
  16. }
复制代码

7 w: R4 V& F9 W$ U8 p+ Z( a! i继续看下snapshot函数的具体实现:6 L; b# T/ \3 |. L
  1. // 快照会在给定的时间点检索授权快照
    . \* n( G/ k1 F  Y3 x
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {% g/ x) _* t2 ]$ I
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints* z* U+ I1 ~, ^3 t
  4.         var (
    : G  F& N) D4 w  R- B5 q( I: K
  5.                 headers []*types.Header        //区块头2 i4 E4 f8 I( B# H
  6.                 snap    *Snapshot        //快照对象1 k. j. C% z8 H- J& t
  7.         )3 b# \1 y3 A' }
  8.         for snap == nil {# S# n0 p" I2 L( k/ {5 u3 a+ R  y
  9.                 // 如果在内存中找到快照时,快照对象从内存中取2 J( l7 Q! ?7 @$ i5 K
  10.                 if s, ok := c.recents.Get(hash); ok {
    ) n2 L$ ]6 |) ^
  11.                         snap = s.(*Snapshot). D4 u) B9 ?# o8 S
  12.                         break
    8 @* J% H( e! b8 |
  13.                 }) c9 G8 m& Y% D  e. f, l6 B
  14.                 // 如果在磁盘检查点找到快照时/ ~' m% n8 Z, U# L+ Q7 b2 y1 F
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号- J4 d& f% V; d5 f  l
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {+ C2 y- u; {; [/ I
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)8 @. O% P8 g% ]4 e2 L1 ?2 w4 [
  18.                                 snap = s
    9 \' \- G/ {6 i3 h3 f
  19.                                 break/ O' ~1 O) t: b% P9 t
  20.                         }" n' g5 n& L" [8 s, M
  21.                 }
    * R  T8 y! Q7 z! {
  22.                 // 如果在创世块,则新建一个快照
    ; G! p& e' Z# J! p
  23.                 if number == 0 {8 \: x% E( M- F2 p5 h
  24.                         genesis := chain.GetHeaderByNumber(0)  t" {$ r% P; k1 o# l  ~. i7 P; y" B  o
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    6 A6 O) x- {( d) |3 q/ u
  26.                                 return nil, err
    9 i/ m5 S6 L! t# f; O4 k  y
  27.                         }
    # N* {( W0 O4 O: Z/ I- L
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)/ r( b' K4 Z. _' o$ J% B
  29.                         for i := 0; i  0 {
    3 V7 l! @: ~, ]( H# B
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)
    * o- s7 P5 c! Y/ x. ?/ g7 m( w' s
  31.                         header = parents[len(parents)-1]
    * J( p. J2 k+ I$ \: Z
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {
    9 O1 _" F. N9 N% ]3 b
  33.                                 return nil, consensus.ErrUnknownAncestor
    % L2 A8 |( j' U( f
  34.                         }- c! v1 [5 ^6 L+ Y0 b+ C+ M
  35.                         parents = parents[:len(parents)-1]
    ; @! N& d6 ]* s. E! Y
  36.                 } else {% ], J1 S( q) ~) @3 r$ R7 h
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    3 F+ Y: ]; d/ U
  38.                         header = chain.GetHeader(hash, number)
    2 A- x2 F5 R+ m: ?; z* V
  39.                         if header == nil {
    7 W& R) T) X9 D* E) U; I
  40.                                 return nil, consensus.ErrUnknownAncestor
    ' `7 P, n! p& f
  41.                         }
    5 Z/ I: ?. W: N0 ]& Z; b0 |2 w
  42.                 }- s! c0 q! j( i5 {$ o. `- ]
  43.                 headers = append(headers, header)* U) r+ @! i# D6 g2 {, M0 F
  44.                 number, hash = number-1, header.ParentHash4 Q' P- J2 w' K' c7 ?& I
  45.         }/ O0 W0 T2 g! Q" v8 L
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面* C' Z: _) o: S) L' B3 v
  47.         for i := 0; i  0 {9 }+ [$ g8 ?$ _  j3 D6 }
  48.                 if err = snap.store(c.db); err != nil {4 o* @3 i) V; g$ }+ c
  49.                         return nil, err
    ; s  q9 H9 K7 g7 n3 n( Y3 K( {
  50.                 }( s- t! q6 l9 }( x% K( m$ _8 s. u
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
    3 G& _; B7 s+ h# T+ c: e
  52.         }  ~; ^: g+ x8 l7 _8 b8 W' i3 D
  53.         return snap, err
    ' }& l% C3 O9 _* c6 q5 W6 a4 y4 L
  54. }
复制代码
, s$ j8 L! M/ ^! J- Y4 _
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?
: N% M  Y( v1 T8 |6 ]
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。+ u' P0 V3 {/ `# p6 _
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
    % ]- G$ c: v& e( z& x3 s; Z) m
  3.           //可以传空区块头* _" ]6 S! s+ P9 X( t: u8 f
  4.     if len(headers) == 0 {! W3 ]2 l1 o  G5 g# k, {
  5.         return s, nil
    2 r! b& X* N4 d/ r7 K& K4 H; j2 x
  6.     }1 ]& L, q' \$ Y# Y7 T0 Z0 q
  7.           //完整性检查区块头可用性
    : f7 \1 n0 Z) Y9 N; \- w
  8.     for i := 0; i = limit {
    . x, T, r; n7 r, r& X! i
  9.             delete(snap.Recents, number-limit)
    0 A2 `1 P& U3 Q9 O0 k
  10.         }
    7 x7 [0 y* @7 b' q9 E( i- A1 E
  11.         // 从区块头中解密出来签名者地址
    8 _6 A3 F" J; x; b6 r
  12.         signer, err := ecrecover(header, s.sigcache)
    ( U- Z# c: x7 ~
  13.         if err != nil {
    9 N0 n& I& f1 A) m4 u8 u- A
  14.             return nil, err- J/ I) j/ r5 a4 p7 ~2 d) S# W
  15.         }
      [0 {; ?/ I( u% u2 O0 o* t
  16.         if _, ok := snap.Signers[signer]; !ok {. Y8 ?% J6 b  G9 p8 j7 V
  17.             return nil, errUnauthorized
    1 I$ b+ T' `; O
  18.         }
    1 X" [' C) [2 P5 c, C
  19.         for _, recent := range snap.Recents {
    2 b! h( f2 i! ^* A' _  m2 ^% I% I
  20.             if recent == signer {, m+ ^  B) m5 T0 S; [7 E0 E: T
  21.                 return nil, errUnauthorized
    * J+ u4 d0 ]# c2 o
  22.             }
    ' \% q9 g& V% D. v$ g) }0 w
  23.         }$ n1 p1 S- V7 h3 V2 H0 A8 q- g5 I+ M+ |
  24.         snap.Recents[number] = signer  ?  B. K* `  c% G- U' u9 i
  25.         // 区块头认证,不管该签名者之前的任何投票- B3 z# ]0 i5 o
  26.         for i, vote := range snap.Votes {" f4 O6 Q8 o2 o$ W8 [$ v
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {: ^& ?, z; L9 y( x5 q% Q  |1 e
  28.                 // 从缓存计数器中移除该投票% F. H- i$ L) x; u9 r2 E* S
  29.                 snap.uncast(vote.Address, vote.Authorize)
    4 E' z9 c! X  \% x
  30.                 // 从按时间排序的列表中移除投票
    # }. T7 Z# a5 Q# {
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    7 o4 Q3 P4 z1 ~' C
  32.                 break // 只允许一票
    % l  p! n9 ^  x1 j" u# j
  33.             }
    0 I4 c# A! K5 x' M- ^. S* ?/ L. H3 u
  34.         }6 h9 F, W9 c8 B9 B3 A' f
  35.         // 从签名者中计数新的投票8 b; @: U  q2 K
  36.         var authorize bool
    5 b" G5 E) A: l/ `) [! Z: g
  37.         switch {
    * N: }3 N9 s- W) s9 z9 Y
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):: H" F/ c0 T% r/ F) J% C2 g+ _
  39.             authorize = true
    , ?+ k) c9 w& z% S
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):
    1 v4 h) I* V* S  l7 s5 ]  _7 ^  w
  41.             authorize = false
    5 u# l# {1 e/ G- x
  42.         default:
    3 Z) x( o+ b! t/ x( [2 @6 F
  43.             return nil, errInvalidVote, ?; {2 D$ v% v6 m3 K+ u% m& y
  44.         }
    2 M- ^' u+ Q" A$ X. Q2 F
  45.         if snap.cast(header.Coinbase, authorize) {
    5 l0 {4 ^: R+ E, y2 x) ?7 u
  46.             snap.Votes = append(snap.Votes, &Vote{
    ( Z- U+ ~" [( e7 D. d- S2 J% b5 ^
  47.                 Signer:    signer,# U4 s) f6 @, n/ Y2 h0 s2 m
  48.                 Block:     number,
    . M; @) k7 W8 d6 t6 {' O
  49.                 Address:   header.Coinbase,* q; s3 @4 k# M! r; U6 P- K. X
  50.                 Authorize: authorize,
    # t2 p7 ~/ p+ F6 |% U
  51.             })
    & s  i  W7 l' c! l& ~
  52.         }) V6 j* ?& Y  Y3 Q* [# C
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
    ! u; \  A$ V# S& {6 t/ ]7 B- S
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
    + c1 p5 N, k- j! R
  55.             if tally.Authorize {
    , t/ `8 N8 G9 V
  56.                 snap.Signers[header.Coinbase] = struct{}{}
    + t, A! Q7 r' P3 U$ y1 |$ A
  57.             } else {" C' m% ~; Z) S. U* y
  58.                 delete(snap.Signers, header.Coinbase), m% h3 J8 N+ S: P6 {6 k+ h
  59.                                   // 签名者列表缩减,删除最近剩余的缓存" ]7 q; i, {5 E1 Q8 R( w
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
    0 m/ H, w2 L. t  @
  61.                     delete(snap.Recents, number-limit)$ u" M$ ?1 Y) c* J
  62.                 }8 d" l8 x2 P7 ~
  63.                 for i := 0; i
复制代码
8 C2 t+ s: J: Z' _
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。+ D. \* r; k8 o- D1 h4 y1 c
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。
/ H: g' n; m2 n* J
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照; c: u5 y) y' T6 L1 E- w& w
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {* D0 J7 J% h5 G; ^
  3.         // 不支持校检创世块
    ' \* L& F2 M: F
  4.         number := header.Number.Uint64()2 ?5 t3 K  `4 b" C" C! C0 W& z) u
  5.         if number == 0 {  R- Y  s- m; N7 W+ J
  6.                 return errUnknownBlock
    - x! k5 X6 H3 m0 L: H4 d- P) O
  7.         }
    6 X; H, L! v5 J5 m0 {
  8.         // 检索出所需的区块对象来校检去开头和将其缓存
    # W9 \$ b& u4 u7 P% `  t9 @
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    " j- h. N0 a* v4 B& J" R
  10.         if err != nil {! ]5 q# v* @& Q2 G1 b
  11.                 return err8 }! J4 ~3 l1 I' j
  12.         }7 p  t& d, u8 v/ }3 Z
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址
    8 u6 k4 d, {) D! _* t: B3 ~8 z+ Y7 b
  14.         signer, err := ecrecover(header, c.signatures)  A( e3 B5 @+ a& j
  15.         if err != nil {5 \. G) ]5 G; U+ I: o$ o* Z3 P
  16.                 return err
    9 R+ s  q% a* u# E8 e5 W0 a8 M
  17.         }) u1 ^% b8 j* Y2 p3 L% ^
  18.         if _, ok := snap.Signers[signer]; !ok {$ N! A9 }; \" T
  19.                 return errUnauthorized
    6 a. \" k% E) b0 B- ~. [! ?- J8 g- H
  20.         }
    & m. q: B3 S5 B2 b6 S, }
  21.         for seen, recent := range snap.Recents {" A' x1 n7 B: C+ j, n
  22.                 if recent == signer {
    & v! W+ j0 W( ^6 y, {5 w- z: M
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等
    # h9 A& u! B* Q7 U$ l. B5 R# y0 T
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    3 _$ Z3 m9 E0 ^% G* u5 j& M
  25.                                 return errUnauthorized8 N8 ]; E0 l) p1 ~5 T0 s5 X. w8 t- A
  26.                         }+ D% O# d4 P! y+ S( R
  27.                 }5 X# s5 [& U, f0 F3 K4 H( _
  28.         }
    6 {1 n) V; V( X  t& i
  29.         // 设置区块难度,参见上面的区块难度部分# Z/ L* H1 a+ ]. G
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)% w% ]+ }. u& Z0 W
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
    + s( r- @; ?: d$ [% ~
  32.                 return errInvalidDifficulty& J$ P" b# C  Q6 V' _
  33.         }* B" t2 ?* q1 X3 D2 e! `; c
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
    / o- @6 Y( ], h, k! }2 X+ E& z# A
  35.                 return errInvalidDifficulty$ l) I3 ~+ o" D
  36.         }5 C% P6 I. s6 s7 x, j+ r0 z: h
  37.         return nil  @1 U& A; n9 {' T/ y1 ~
  38. }
复制代码
6 Z0 p& E( }2 }9 {; ~6 m5 C
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
" N5 @+ A) C7 o* X- E! PClique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:
6 {$ T4 s5 h/ ]委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中
& Q7 B; J4 c6 W. P! V2 J$ X
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。; L2 p9 {, S/ K
  2. func (api *API) Propose(address common.Address, auth bool) {/ |/ y* t! v- d% p9 V
  3.     api.clique.lock.Lock()9 t1 ^1 k1 \2 I- c9 B
  4.     defer api.clique.lock.Unlock()8 Y, ~9 M, [. f' M- ^2 K3 q
  5.     api.clique.proposals[address] = auth// true:授权,false:移除5 S9 j+ W3 r6 T, l2 x3 ~. y" T3 q! I
  6. }
复制代码

0 n( D6 u1 F' K2 m% R* n( T" L本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;9 y: R- f1 M5 M! N9 p7 q* e
  1. //Clique.Prepare$ ?' q& F/ |, l
  2.                 // 抓取所有有意义投票的提案9 b0 Z( V! l3 g. `. l) Y7 E
  3.                 addresses := make([]common.Address, 0, len(c.proposals)), ]1 i2 }+ S2 l( M/ t. B
  4.                 for address, authorize := range c.proposals {
    4 [4 i  l8 d* I
  5.                         if snap.validVote(address, authorize) {
    * t% S1 r/ g, t7 U5 p% d8 ~
  6.                                 addresses = append(addresses, address)
    ( B4 i; [+ S- b' R% `9 @. u3 A
  7.                         }! R9 Q) b7 _! G* j( \8 s- {
  8.                 }
    5 s8 A9 ~5 \+ |- R+ T" o
  9.                 // If there's pending proposals, cast a vote on them0 |- s& V8 D* P8 P
  10.                 if len(addresses) > 0 {
    1 y( Q6 ]/ ^" w. v
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。' f$ [1 }8 r; R, [6 z! t% T
  12.                         // 通过提案内容来组装区块头的随机数字段。
    0 A- ]- ^7 d* @: f- Q$ I% p
  13.                         if c.proposals[header.Coinbase] {( ?: r- J% L. x7 q
  14.                                 copy(header.Nonce[:], nonceAuthVote)
    " P  j% E* t& O; U6 o" a8 o
  15.                         } else {
    * j8 J$ L& V$ R
  16.                                 copy(header.Nonce[:], nonceDropVote)9 `7 ^/ H& T$ U
  17.                         }+ g  m- {$ a; B5 P- Z- G* H
  18.                 }
复制代码

. F( F1 W! m& I  }7 R在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare
2 _# L: \' f( C  C
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {
      U# p# h1 ?# G" r# @
  2.                 log.Error("Failed to prepare header for mining", "err", err)! i/ o' w7 N% f6 J* r
  3.                 return
    , ?: S4 ?( N  P, k: V. ~$ M# A! X
  4.         }
复制代码

7 |. W2 L# X$ o' g7 V; O其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法8 R. _2 e8 v& }4 t5 q* ^

: I) o  E( x' Q) R" s6 e" m4 c2 X& p! T
9 u/ X$ P( Y/ o
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3