Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2567 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。/ s& M4 J3 e+ ^7 U6 Y4 k/ t5 A$ J) f
  1. func (s *Ethereum) StartMining(local bool) error {
    9 `# j6 r- g1 A. H3 ~5 P( o
  2.         eb, err := s.Etherbase()//用户地址
    $ l/ j- V% w; \. `! o
  3.         if err != nil {
    - A5 Q; o9 c3 s( ?5 c. I- l- q
  4.                 log.Error("Cannot start mining without etherbase", "err", err)6 B' D8 u# ^' R6 H& L
  5.                 return fmt.Errorf("etherbase missing: %v", err)% R6 ]9 C7 P  c3 `8 j0 n6 z
  6.         }
      g! o, ]( ?% b3 N1 V) p& e
  7.         if clique, ok := s.engine.(*clique.Clique); ok {
    + `- D# m% J4 e. f* @6 e$ b
  8.                 //如果是clique共识算法
    9 _& x+ `1 Y2 W, X& Z, _% ~
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象& T- o( ~! j; o& A
  10.                 if wallet == nil || err != nil {
    % L9 L9 W& |2 }# r0 }( N8 `
  11.                         log.Error("Etherbase account unavailable locally", "err", err), {6 e! `7 q, ?; z
  12.                         return fmt.Errorf("signer missing: %v", err)
    ) [1 X) Y3 |& ]6 E7 w$ a: K
  13.                 }0 U& n7 ]( q, g* f: W( d- c% Y
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法
    + y2 M7 z5 ]" ]- D
  15.         }
    & Y( a* `3 l* [. F
  16.         if local {
    6 |6 d; C* e: I! [. F+ y
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
    9 C# h7 a5 F$ {+ @# T
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)1 G; l9 e2 U% e$ }% l: P
  19.         }$ K+ l( D& \; r* W- b+ [  k/ ?/ l
  20.         go s.miner.Start(eb): y2 Y4 H  \! ], L3 ]' `
  21.         return nil. _8 `  l/ s, H! S
  22. }
复制代码

% v) q- h0 X' b) k6 g( ?) G这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。$ w4 R0 t! n0 K; c, u/ _3 H* r! r
Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
# |" I- N. K( Utype Engine interface {
8 M9 r. L6 X1 Q/ H        Author(header *types.Header) (common.Address, error)
' a7 _) z  }3 ]$ n& P* F8 z, E, M+ a        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
6 K; J4 E, Z' c' J        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan; B6 C3 ?1 R9 z, a% H0 y: z
Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:
9 l6 \2 t2 K! ~' z8 [8 }0 K
  1. type Clique struct {
    ( m$ F- Q. ?  `
  2.         config *params.CliqueConfig // 共识引擎配置参数0 h3 p9 E! z: L; x" ]# O# j+ \
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点5 `* [8 t$ Y  l
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组6 f6 n6 g3 q4 e6 j
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿
    % v( _8 x' F- u
  6.         proposals map[common.Address]bool // 目前正在推送的提案' R9 g6 o4 T* k. |( s
  7.         signer common.Address // 签名者的以太坊地址. q) E; {+ n# x2 Z$ C; M
  8.         signFn SignerFn       // 授权哈希的签名方法
    . {) L# T: R2 C6 L9 v* S0 s
  9.         lock   sync.RWMutex   // 用锁来保护签名字段
    & ^/ `4 W' D) K5 r0 ^2 k3 O
  10. }
复制代码

4 z% R! z$ {: ?0 W% w1 b( k顺便来看下CliqueConfig共识引擎的配置参数结构体:
( C9 `+ B/ ]0 U# v4 S: T2 f" a
  1. type CliqueConfig struct {5 E8 s# `4 x8 R5 M+ d( O* J
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)
    4 d* W9 I5 B5 y- S5 q, \
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    , `$ y& ~' @) n
  4. }
复制代码

' O( F3 C9 O) u8 a% X在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
1 f/ b3 r8 m/ X) D9 w- _, ^+ e
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
    7 }6 V. ^5 h, [" a) K" j
  2.     c.lock.Lock()
    % F+ l) H- `& ^0 P+ V
  3.     defer c.lock.Unlock()2 F% B! ~1 x& O! Y9 o
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
    / S( [7 a( O8 V0 @1 _1 h' X6 K1 \
  5.     c.signer = signer" f0 \* S* t2 `0 m( T
  6.     c.signFn = signFn
    $ B. }! s" |$ H* \
  7. }
复制代码

" t1 t, @9 Y7 l+ c  D8 y* k/ u再来看Clique的Seal()函数的具体实现:( t& D+ T7 ~* @5 U$ P5 D! v
//通过本地签名认证创建已密封的区块& j% H, O2 G/ H  H( h
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {2 c2 s* @7 b: P
                                log.Info("Signed recently, must wait for others")
, s" ^/ O/ M. q8 f9 ~" K                                ; a: {; O/ N2 K5 k  e/ [: s
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名6 i, R8 L  u/ ~6 i% S" V
signer不在snapshot的signer中不允许签名8 W6 H7 f# J7 M8 W0 D4 I3 i! b
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名5 [; x, C4 o( u' N+ J: l
签名存放在Extra的extraSeal的65个字节中
+ u' y' Z" [9 ^" O2 X9 `# k2 m) I2 A关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。/ G$ @0 ^0 ^& H+ |) F$ b8 r
  1. //snap.Signers是所有的认证节点
    ( m7 K- c  \" F! g5 P/ b3 c6 ]
  2. for seen, recent := range snap.Recents {: M8 p# \" b" d' _( i6 H
  3.     if recent == signer {2 D$ U4 A. s, L2 G/ o. B
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {( H6 M6 m' j, x( }, e  U
  5.             log.Info("Signed recently, must wait for others")
复制代码
" Z! B9 n6 M2 g. O$ Z
            $ v" P! c. N) D5 v* }. t
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
7 p! [5 s8 I8 y0 E; F关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
7 }4 R7 J* e& V" p+ [/ y4 U6 UdiffInTurn = big.NewInt(2)
! M% B7 W( ^$ @) P' s8 M9 U$ ^diffNoTurn = big.NewInt(1) ; Y. C- ^% ?* k! k4 P
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
; i3 N" f& I3 `0 k// 通过给定的区块高度和签发者返回该签发者是否在轮次内7 }3 s6 W/ X: ^5 F
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    ( ?/ A* G0 W. Y+ |  F8 U7 T1 M" w
  2.         signers, offset := s.signers(), 09 f  G8 j8 s# u
  3.         for offset
复制代码

! q0 {5 X' T" ySeal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:
! e1 o, E* W0 r; Z4 I; L// Snapshot对象是在给定时间点的一个认证投票的状态- \: U0 i3 b+ Y' I- r
  1. type Snapshot struct {
    $ G# V% B4 b6 |2 T; V, a7 a
  2.     config   *params.CliqueConfig // 共识引擎配置参数
    $ C* d. G' [8 S9 x% w; O2 d
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。
    4 C* O4 ?, l/ Z9 }) }9 l/ n% i# P. S
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号) x  t: U$ r7 r" ]' ], ]
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    ' _6 A5 d2 y3 Z6 [: k2 S, @
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表
    , ]; [3 h  Z" R
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址
    / ^# ^7 p; u) S6 S* l3 x& G
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    6 r5 c& h+ ?. j5 Y; j/ o
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。" ?: C1 T3 F* l7 \6 G4 {: k
  10. }
复制代码
+ U7 v5 p9 z  Q* G8 s) z
快照Snapshot对象中存在投票的Votes和记票的Tally对象:
3 {  Z$ }- K( Z  U0 W: _
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。: p) O8 a6 Q2 z, q% v
  2. type Vote struct {5 N. y4 r5 h* ]* [2 R6 ?
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)9 M) a$ ^. ~8 H7 w  G: r( @) v
  4.     Block     uint64         `json:"block"`     // 投票区块号$ d7 C! d3 o, G) h4 X* J# B
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权
    . \1 o; p1 J; ?3 t5 `( t& M
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权4 B+ i% d) Y' x3 C8 j
  7. }! w( v( ^$ X4 T  `8 ?' ~
  8. // Tally是一个简单的用来保存当前投票分数的计分器
    - v& r, q: P, B9 j) W
  9. type Tally struct {. A  _, N" Y  n; Z
  10.     Authorize bool `json:"authorize"` // 授权true或移除false
    , l% g) O! S9 S
  11.     Votes     int  `json:"votes"`     // 该提案已获票数2 D  I# W. V0 E4 w' Z
  12. }
复制代码

/ q. ?) }& H0 O' X. N/ D/ [. JSnapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:+ {/ g% E  {' z; @
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
    ( X' Q  a8 {! W
  2.         //使用Database接口的Get方法通过Key来查询缓存内容; u4 u+ b7 v! ?1 E5 F6 O
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))% t' B7 F$ G: [: Q+ x! _# {
  4.         if err != nil {
    0 P3 ~8 _' d' l! g  r. I* L
  5.                 return nil, err6 I/ B: X. R7 S% U, W5 W
  6.         }
    , @' }- i/ U4 C
  7.         snap := new(Snapshot)
    8 x+ e+ K+ |3 d1 j6 m1 z
  8.         if err := json.Unmarshal(blob, snap); err != nil {1 h! A" d1 s7 Z7 k) l" k  y; |) T
  9.                 return nil, err
    # v" J+ _( ^5 W8 C
  10.         }/ `6 D0 F% D1 m5 v0 V; R2 s
  11.         snap.config = config
    - a( Q  Y, B' n, Y9 _  O4 L
  12.         snap.sigcache = sigcache; E& }5 t& c  l) P6 F
  13.         return snap, nil; r' F6 ]' J: `# ?3 m
  14. }
复制代码

6 C! Z- P8 K3 A4 h6 b7 KnewSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
& |5 J, u/ g" A) b* I8 x
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {1 P  G" \3 o6 @, z
  2.         //组装一个Snapshot对象
    8 r2 R4 O1 x* G8 }8 U" h: a
  3.         snap := &Snapshot{
    ! f! ~5 X) n* J  B1 {$ v
  4.                 config:   config,& Q- l- X9 Z2 X1 W7 ~
  5.                 sigcache: sigcache,- @, b6 Z5 Q* g( y# [
  6.                 Number:   number,, j' r% q+ d6 I6 w+ U/ B, y
  7.                 Hash:     hash,7 V" Y+ r. Y. @$ Z
  8.                 Signers:  make(map[common.Address]struct{}),) }  G/ O- D2 Y0 r  b. W, U9 E4 C
  9.                 Recents:  make(map[uint64]common.Address),# U. A: n0 @% g4 S: x' C
  10.                 Tally:    make(map[common.Address]Tally),5 u! z- e- s, |5 r
  11.         }
      E, l5 h+ N- [# `6 m
  12.         for _, signer := range signers {
    9 N4 g- Q- ?( `8 c; ~5 g
  13.                 snap.Signers[signer] = struct{}{}( l" [% L/ ]' \7 q8 U
  14.         }5 ~& a& G( t% `: n6 p5 M
  15.         return snap
    ) S) ~% R& U! W2 Z" S" Z
  16. }
复制代码

, t( x# U" M5 G4 M继续看下snapshot函数的具体实现:
6 j( ~$ n: `$ f1 Z
  1. // 快照会在给定的时间点检索授权快照! U8 B% e. s. g. |$ k" N
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
    % t' _0 v% E6 y/ w' p  @
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints/ v1 ?0 O  g. ^
  4.         var (# ]/ C( x" w& L- G) Z: x
  5.                 headers []*types.Header        //区块头' |3 i4 u! u$ o  O6 i
  6.                 snap    *Snapshot        //快照对象8 A$ ^& v- Y6 _0 O/ j
  7.         )9 t$ \: y. r" x. H: c+ \0 t; o) k
  8.         for snap == nil {5 c" m# h0 e* E' W
  9.                 // 如果在内存中找到快照时,快照对象从内存中取
    6 U4 y& u, ?5 ~, b4 h7 a
  10.                 if s, ok := c.recents.Get(hash); ok {8 [1 a, m. o" J) e
  11.                         snap = s.(*Snapshot)
    1 j& N2 R2 U2 s# A2 E/ I
  12.                         break
    ; c7 f, E; I+ ^9 Q
  13.                 }7 r1 w  X1 N- K2 U; A4 B5 J2 y2 Z
  14.                 // 如果在磁盘检查点找到快照时8 s9 _* L+ `) v- r# o
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号  D' n1 T; [0 T1 z# l& }6 F: Q
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {3 I* K- ]( M; V% N0 Q* c  A
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
    . Z: ^9 t$ U+ |; K$ ~7 Z  G
  18.                                 snap = s, @1 P% x! c3 j
  19.                                 break+ \- y% o$ E  `& X) W7 c- S
  20.                         }: e* v  U& W' e2 L8 K4 T
  21.                 }$ o% ~8 l. D7 s6 Q! n. m& ?3 `. S1 ^8 }
  22.                 // 如果在创世块,则新建一个快照" G3 p" y: T! J& c( u
  23.                 if number == 0 {
    8 A4 r' M" h8 [3 b0 S& u
  24.                         genesis := chain.GetHeaderByNumber(0). S% N! L: Y$ ]  s" z- Q, j
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    , c9 P. L( O- y! N8 q+ f
  26.                                 return nil, err) v& V0 E) }+ w3 F
  27.                         }
    0 \5 J- R7 s: n' w0 A
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
    9 ]- h; e8 V  X! o+ S8 R; O
  29.                         for i := 0; i  0 {
    3 d1 B+ v$ ?! D; a
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)
    ) y- u1 Z( J, f- N8 J
  31.                         header = parents[len(parents)-1]) l' k$ F, x  o) m8 q( U
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {" E7 p/ V. F, s% r4 D1 r& o
  33.                                 return nil, consensus.ErrUnknownAncestor
      ]' {$ ]. \/ @, }- g$ E/ k
  34.                         }6 @8 \2 G$ X0 ~# Q; _" C& l# @
  35.                         parents = parents[:len(parents)-1]
    ' M1 V  y4 N* Z$ g
  36.                 } else {
    $ C  {4 g3 x( L5 q( i. \3 E
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    1 ~& K! R! ~- u, y. E/ X& Y; o4 [
  38.                         header = chain.GetHeader(hash, number)
    3 F( {6 K6 l# t0 {0 A/ B' o9 W
  39.                         if header == nil {
    ( J9 V2 g: L; ^) c# ]
  40.                                 return nil, consensus.ErrUnknownAncestor
    % g& w5 P6 p1 W: g9 }9 T0 a
  41.                         }9 Q3 p4 a( G' D3 z) M
  42.                 }8 J% A$ o2 S. R. V  f
  43.                 headers = append(headers, header)
    % ?/ b1 q  T- |+ W- G
  44.                 number, hash = number-1, header.ParentHash
    * r1 L4 [1 e9 q5 C+ Q/ X% D2 h7 {2 V. w( V
  45.         }3 w# H5 k: I6 b6 Q; N6 G
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面8 O& Y4 ^# F, P4 T& B
  47.         for i := 0; i  0 {# e+ H! I2 H; H3 j( A+ j
  48.                 if err = snap.store(c.db); err != nil {
    9 p3 j+ j& B, Y. F9 H2 A9 p
  49.                         return nil, err
    ! q& e/ i5 Q% @
  50.                 }9 M* V/ }. g, |8 C3 W* w! m: C
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)' w+ O& W: T$ ]/ H" d0 O
  52.         }% p% S1 H7 i$ [* M% T
  53.         return snap, err
    ! d1 \% ]# U  i9 N* M! p) J
  54. }
复制代码
9 q5 G4 J% C6 X2 u
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?# Z* a( w1 l, p( w. Z4 a# |
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。( V! _: p7 e/ c5 X3 a2 B( r9 C
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
    : i' a; `+ Z* t
  3.           //可以传空区块头
    7 {$ {4 N. s$ L, B$ g' T. ]% S
  4.     if len(headers) == 0 {' G3 W# S- q; M
  5.         return s, nil; L1 g5 g1 F5 ~4 J8 i. L: Y/ ]
  6.     }
    6 l' M& a+ ~% ?
  7.           //完整性检查区块头可用性) X# g. m+ J. y, X1 t
  8.     for i := 0; i = limit {
    2 F. g6 r6 l  h! k# D& @; M, S- \
  9.             delete(snap.Recents, number-limit)
    / `- l# A% d* @" ^, m1 ^' j
  10.         }# r5 o% K* h( U
  11.         // 从区块头中解密出来签名者地址/ C2 E8 e" G7 g- P1 x$ B) f- d
  12.         signer, err := ecrecover(header, s.sigcache)9 o9 Q% _0 n9 p6 H& V% l. R& w
  13.         if err != nil {% b  P: ]) g7 r' ]: T! n& {
  14.             return nil, err
    : ]. s' e# V' l& ~
  15.         }0 t1 P) i1 J2 g6 Y
  16.         if _, ok := snap.Signers[signer]; !ok {: U& o1 I2 L4 B# y- X+ ~
  17.             return nil, errUnauthorized, Q6 g, q$ P7 Q
  18.         }
    / }$ K1 r" L# R. A% ]2 R
  19.         for _, recent := range snap.Recents {
    1 q, e4 D2 T; t
  20.             if recent == signer {
    9 F7 E6 U5 ]/ n5 h0 g# u9 o* ~5 L: Y
  21.                 return nil, errUnauthorized
    * i: I: e$ h$ i& h' O0 ?9 O
  22.             }, a! V3 C% [; ^* d
  23.         }+ e% G) E3 @9 g( E
  24.         snap.Recents[number] = signer& `: m6 t0 A5 M3 e& y8 ^
  25.         // 区块头认证,不管该签名者之前的任何投票; @  F$ d$ g4 m" l. t4 \. w
  26.         for i, vote := range snap.Votes {
    % `2 c7 z& S3 ^1 c: g7 w4 u
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {% J, `8 [  Q1 j  U* }
  28.                 // 从缓存计数器中移除该投票
    0 R- B9 r4 x2 H
  29.                 snap.uncast(vote.Address, vote.Authorize)/ Y: }; I4 Q5 Z; c, X; {' @$ P
  30.                 // 从按时间排序的列表中移除投票& x5 B& ?) P5 R; a7 C7 `- z$ S3 t
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)" y" M1 g/ }0 k! |- i' B5 R* T
  32.                 break // 只允许一票
    - O8 t8 \1 X7 _
  33.             }' l2 O1 H' Y, S  Z% T$ Q
  34.         }8 H5 T( C' x7 f( Z& D' \
  35.         // 从签名者中计数新的投票4 r3 r7 w2 k! ^3 S2 s
  36.         var authorize bool. N! w6 x' x' R
  37.         switch {+ C- D7 ?' R0 u( n* Z
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    8 P3 j& _9 b# ~
  39.             authorize = true
    - E5 k) K9 v+ x5 b; K% h! T
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):9 [+ y. P, y) j
  41.             authorize = false0 n& _( d6 `+ |7 v, Z
  42.         default:
    ' k* y9 B3 J' f( e7 Q4 D
  43.             return nil, errInvalidVote, x" o  S, V$ G( S; r
  44.         }- ]% j/ E$ n# d3 P7 p
  45.         if snap.cast(header.Coinbase, authorize) {' ?2 h& y) {9 w2 U8 N, y- F
  46.             snap.Votes = append(snap.Votes, &Vote{
    1 K9 _5 o$ d5 f! y" h' y: l
  47.                 Signer:    signer,
    % A. @% T* O6 o6 {7 p( c7 w9 O8 @
  48.                 Block:     number,9 S8 p& [/ n! K; N6 L5 L# y! c
  49.                 Address:   header.Coinbase,
    & O0 X9 a! O3 q5 z3 i" @
  50.                 Authorize: authorize,
    % L  B$ ?! h5 j
  51.             })
    9 t. F4 v8 L, C" B
  52.         }
    / @' {7 l/ X  G3 j; G4 e, u: F4 F
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
    " P5 q" }, |/ m; f; b  L
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
    9 b0 M/ C- I, ?6 G$ b+ j( y
  55.             if tally.Authorize {9 k0 \0 c' C% J: P, m
  56.                 snap.Signers[header.Coinbase] = struct{}{}  n% f+ x  {& w0 a
  57.             } else {
    % H0 n# W. q- f% x
  58.                 delete(snap.Signers, header.Coinbase)& }2 r6 I( s2 Z' ^3 a
  59.                                   // 签名者列表缩减,删除最近剩余的缓存7 Z8 K* ?+ S- C* q# ^- x) P
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {! Z2 |( [2 Z: Y  D9 E8 x& @7 H
  61.                     delete(snap.Recents, number-limit)) T! ^+ \& z0 b8 i+ Z, |5 f
  62.                 }
      X& ^  d0 V4 i; a
  63.                 for i := 0; i
复制代码
0 M5 c1 Y% j# h
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。! Z2 B. J1 o: H/ C. k
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。6 ?( v6 t4 X% {& h# ~
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
    % D9 ^3 S. X: [% v1 L) `* p
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {1 r4 E8 j8 u1 k+ F
  3.         // 不支持校检创世块% B0 l9 {" r5 p( h1 L
  4.         number := header.Number.Uint64()/ V$ A1 |, l$ N4 ~: h/ G
  5.         if number == 0 {
    9 j+ e, F& o' Y* _- I* _
  6.                 return errUnknownBlock
    : q( v3 D0 d+ O) B+ j/ `/ A0 l
  7.         }
    5 }! s8 T3 e& B- j( Q
  8.         // 检索出所需的区块对象来校检去开头和将其缓存& v% X* n  I( R# [* l" Z% U
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    : q# H; H) x( E4 ]+ D; @& O9 w' O" y
  10.         if err != nil {& x8 @) o9 S. D" t7 C
  11.                 return err
    3 v+ g+ Q0 |" X( n
  12.         }
    8 }. Y: z( X' i) }% }6 X
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址/ r. ?  i) R1 ^
  14.         signer, err := ecrecover(header, c.signatures)
    # S3 C, K/ Z+ T2 A) r. G) e0 ]
  15.         if err != nil {; ^; _9 N% e! ?2 g; z
  16.                 return err
    5 r+ N& M. ?6 \) k! V* x  l( r, a
  17.         }2 X. s0 X/ |9 B8 Z. H
  18.         if _, ok := snap.Signers[signer]; !ok {( A3 j4 \* g# @3 P. I; q
  19.                 return errUnauthorized
    1 U1 ~4 Z$ [& r. t
  20.         }9 }& K9 o: e$ A1 X. I( [
  21.         for seen, recent := range snap.Recents {! _! @* ~/ I1 G( Q* W
  22.                 if recent == signer {
    - i8 [2 y% `  A' }
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等0 T0 k( r, X  Y
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {/ H- G# q( [; e0 Q( O- ~9 T
  25.                                 return errUnauthorized
      z+ u3 k* L$ `% r/ J& h+ i; [3 x
  26.                         }
    : v  W1 u0 Y1 D% n8 ]3 _; D
  27.                 }2 }6 Y' V* J9 E5 j
  28.         }, S+ G7 t4 b2 T4 ?4 [: z3 m
  29.         // 设置区块难度,参见上面的区块难度部分/ y2 b3 K  E2 G
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)
    $ Z. @6 ~% u: D2 T9 v8 ]
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {) \8 D( E# F6 n# b
  32.                 return errInvalidDifficulty
    & S' w% f& t1 o2 T
  33.         }
    1 B) \5 G6 N  {- O
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {1 g$ S: A& B0 K( F; {. h
  35.                 return errInvalidDifficulty
    ; x, h+ B$ a7 U5 W
  36.         }
    ; [: e3 W6 L, u8 L; |
  37.         return nil4 m: K& B2 T: |8 n/ a4 @" j
  38. }
复制代码
) g+ n/ G4 {$ ^$ X+ n/ R
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
& R* I% t0 i% J  [Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:7 b0 {, g: k9 g8 M5 u5 l
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中: g1 v& `) e" D. v
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
    % j/ [! K- e7 b& T
  2. func (api *API) Propose(address common.Address, auth bool) {
    " a, {8 [  a8 F/ x
  3.     api.clique.lock.Lock()
    7 v- m! X/ Z. B0 a5 o/ U- T: e
  4.     defer api.clique.lock.Unlock()+ z5 e% w- A: O2 V$ E2 j9 n
  5.     api.clique.proposals[address] = auth// true:授权,false:移除% A2 t  H; v2 v3 R& W* z- o
  6. }
复制代码
) K3 }; R  R) O0 R, m) h7 {
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;5 f7 S) k! q3 S9 O
  1. //Clique.Prepare. Q! Q( A6 m+ N8 G
  2.                 // 抓取所有有意义投票的提案( `3 `1 m- O6 [6 S  Y9 b, J- a
  3.                 addresses := make([]common.Address, 0, len(c.proposals))+ _" I) h% T% {, h# ~+ ?. P) _
  4.                 for address, authorize := range c.proposals {) t( Q7 J; _' F3 q4 b# N0 Z5 z
  5.                         if snap.validVote(address, authorize) {
    ) j$ I+ K2 Y8 R
  6.                                 addresses = append(addresses, address)1 F% D+ r' o) y3 d- B. A0 i
  7.                         }* u& T% F2 |% O4 ?; M
  8.                 }' f; d' _3 l+ r6 o% ?6 I5 K
  9.                 // If there's pending proposals, cast a vote on them! F9 U# N1 G3 T2 \
  10.                 if len(addresses) > 0 {
    4 N3 Y4 ?% n- }' F) I4 p0 c
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。& ?3 f4 Y, o/ P9 a
  12.                         // 通过提案内容来组装区块头的随机数字段。) A: z; [5 S, o8 O
  13.                         if c.proposals[header.Coinbase] {
    & w* q: j  j" C$ z; o: ]1 q! Q' X: X
  14.                                 copy(header.Nonce[:], nonceAuthVote)9 F/ e# M- G" j
  15.                         } else {1 d' ^0 @/ u6 f+ J' T
  16.                                 copy(header.Nonce[:], nonceDropVote). }+ C- p6 d0 W! t
  17.                         }2 N, H. q: }$ _, ~
  18.                 }
复制代码
3 R8 l. V8 u1 w( I% f( D" O
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare
3 z2 Y! I) b' b# T
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {. t5 {: F# W5 f, A
  2.                 log.Error("Failed to prepare header for mining", "err", err)) K' c% T5 D: {. ~+ M+ Q2 `" X
  3.                 return
    # v* C' A' L& `# r  f. i
  4.         }
复制代码

' N7 [# u7 R7 y6 ]. u6 e+ f, F其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法3 P0 d2 G. r; U& I8 a+ H! f; w: P
4 z9 w* T2 V8 P; D& i5 u; y* `  a

0 V8 k! S# h* a  J3 z. v3 y% c  v  F* G  c/ i. x
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3