Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2757 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。2 `6 H" I7 n3 M2 e
  1. func (s *Ethereum) StartMining(local bool) error {
    # P6 y. Z' }' S$ L5 S  n" }. P; A
  2.         eb, err := s.Etherbase()//用户地址/ n: Q+ N4 x: D3 U5 V' o
  3.         if err != nil {' w6 m6 w0 n. h/ h" q9 N- n/ x
  4.                 log.Error("Cannot start mining without etherbase", "err", err)
    1 x; C! a5 d7 ^/ e& w  l
  5.                 return fmt.Errorf("etherbase missing: %v", err)
    3 H, E) Q; a  m+ m9 q3 w
  6.         }# X: v7 g0 ~: \
  7.         if clique, ok := s.engine.(*clique.Clique); ok {
    4 ]5 x$ f& s1 T# t6 \% ^: G
  8.                 //如果是clique共识算法* Z; b$ y/ v4 v! [* L: N4 Y
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象
    * x- D; N# t4 m4 R  C% z% Q
  10.                 if wallet == nil || err != nil {: y  o5 K8 O5 _& }+ g
  11.                         log.Error("Etherbase account unavailable locally", "err", err)8 V; d, k* u0 G5 b4 F5 w
  12.                         return fmt.Errorf("signer missing: %v", err)7 ~- }7 b% ?5 v; a! N$ K8 {* @
  13.                 }
    , j  ]3 W5 X- y4 p3 e6 d
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法9 V- _5 O# o0 ]
  15.         }  ~- Y# U& K+ @7 o
  16.         if local {+ m9 `# b0 I! }' H8 w
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。* @2 m8 t  ^$ t& G5 s
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)0 r" W$ D  B4 G. F1 n5 P9 y
  19.         }
    : ]* A. ?- k& R5 e4 l  s3 q' D* }
  20.         go s.miner.Start(eb)
    / U% j2 u% W' Y: y( d# L" p9 b) u
  21.         return nil
    & }* F. y# P' J  K) ~
  22. }
复制代码
) S* j7 m& }8 E& ^2 ~$ L' C
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
& [. L) z4 S$ b/ f$ M: E  fClique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
3 A- ?7 o/ m/ M5 k( \8 d# z" ptype Engine interface {
( H8 R' j' N3 Z1 g4 h9 A        Author(header *types.Header) (common.Address, error)6 l  X" A; m. \
        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
7 ?* |: C" J* Z        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
! B$ Z8 Y- x+ F* G9 ?0 m) C( @' @Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:+ K$ T, q3 ^& |2 F% i4 {+ K
  1. type Clique struct {5 U' A" O5 x5 {3 b7 p' u2 N5 O
  2.         config *params.CliqueConfig // 共识引擎配置参数
    8 O& q, Q/ ^) r7 z; ]' P+ |8 G! X( I& s
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点) i* ~* d' M1 ]' \" S9 X7 g: X
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组
      Y8 p! g0 ]) C; a) W
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿9 x; Q( h. G: x
  6.         proposals map[common.Address]bool // 目前正在推送的提案$ |; B9 ]4 g0 g& M0 O* k; |# A
  7.         signer common.Address // 签名者的以太坊地址* q# F7 ^6 T- N0 B
  8.         signFn SignerFn       // 授权哈希的签名方法
    2 K. q0 }0 D8 J, B* Q; i
  9.         lock   sync.RWMutex   // 用锁来保护签名字段
    , g5 D2 ]; `, O/ D. q+ E
  10. }
复制代码
) O8 N  j( R8 L& r6 p" Q* @
顺便来看下CliqueConfig共识引擎的配置参数结构体:1 J/ t! F8 ~; `3 b$ ^& ~3 `
  1. type CliqueConfig struct {* W* W3 z* h* C+ F" D7 K
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)5 i  Y$ Y  g6 _" L
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    + j/ w( U. }* i3 h1 `4 k
  4. }
复制代码
3 I6 L- o8 n8 ^: R  L/ x3 U+ V, i
在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:) I3 O& X- g2 q& h  x
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {' L0 V) J- ]8 L  `
  2.     c.lock.Lock()
    + r% J) P- d7 j) D/ Y
  3.     defer c.lock.Unlock()( v. L/ N. w2 O# e( ~3 m
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
    # F% z& ^4 R# g8 X
  5.     c.signer = signer
    % |# d1 X3 j2 k" \& ~" X
  6.     c.signFn = signFn
    8 P8 F9 l# R2 U! N1 v
  7. }
复制代码
% y1 p) m  y1 H7 |* ~+ z
再来看Clique的Seal()函数的具体实现:9 u2 N, }( I2 C# Q- f8 h( x
//通过本地签名认证创建已密封的区块9 p. {' y$ i' _" _( I9 G; o5 \$ u, X
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {1 U/ A+ ?1 E1 Z+ e, x9 ]
                                log.Info("Signed recently, must wait for others")3 f. t( z, r& U& o' v
                                
/ E( P; M  c8 j7 b: l, ^. K3 DSeal是共识引擎的入口之一,该函数通过clique.signer对区块签名
6 l; y9 u. Y3 P6 S  Q# O. Y, p. csigner不在snapshot的signer中不允许签名4 ^- }  K9 ^6 q4 }
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
5 P8 |6 i( ]. t% H1 X签名存放在Extra的extraSeal的65个字节中: q4 K; R& N: q* m9 k7 g
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。
; B; h# A6 N% w# @5 w& }
  1. //snap.Signers是所有的认证节点9 _) {2 s& t) Z+ X9 l! A& L: i
  2. for seen, recent := range snap.Recents {
    1 T+ ]9 i+ I. M
  3.     if recent == signer {
    ; y! x0 N/ z, }. e& ?3 n+ x
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {
    ( |2 c- f' u( [( N. z! x
  5.             log.Info("Signed recently, must wait for others")
复制代码

' h% o: M6 y5 p2 {8 J) b' J            0 S' m# V5 V$ W+ q: V9 ]5 a
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。' s$ ^  A% v8 U1 c
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。3 H  ^$ }. `; V8 p7 K/ Y
diffInTurn = big.NewInt(2)
/ [7 d% \6 j/ D8 tdiffNoTurn = big.NewInt(1) 8 }( K1 {& z. C7 a, k
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
. n4 c* ~. D& f! f2 v7 i8 C// 通过给定的区块高度和签发者返回该签发者是否在轮次内" ~) m6 [; S$ {) d
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {6 ~( O' H9 j" F: b  Y
  2.         signers, offset := s.signers(), 0
    ! Q" l! X5 f: Y. Q& n1 K; p  K
  3.         for offset
复制代码
* e  h# j0 L5 [$ c/ z1 l
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:( G3 b0 B! n1 }9 b/ `$ ]
// Snapshot对象是在给定时间点的一个认证投票的状态3 x4 R! B* X9 Y5 C% h$ h9 k
  1. type Snapshot struct {( F7 z' y, |  D2 [, Z
  2.     config   *params.CliqueConfig // 共识引擎配置参数8 g: t. j* b2 |- l/ X1 O7 r
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。
    + _8 y- |4 ~7 `$ R
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号
    . ?& C  Z3 J, m3 j
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    , d& T: o5 R+ B$ t. r
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表( N7 _6 e0 j6 ], z
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址: }3 K4 h% N" s4 U8 ~
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    9 ?" I5 @* x* I2 x2 J' G' \, b
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。
    9 E. b- H' z% d) f& T* S/ R5 b# y
  10. }
复制代码

& r; J7 {2 i9 `4 u6 _: g+ T9 A! F5 T快照Snapshot对象中存在投票的Votes和记票的Tally对象:& T  j1 U5 a  @" F- p4 P
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。/ \; {* S, L& {# r; U! d: D" h
  2. type Vote struct {! i; Y% b8 K  R; h
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)% g6 d; R8 D# T# `4 U2 ^
  4.     Block     uint64         `json:"block"`     // 投票区块号
    % I$ \2 x6 j4 a& W* P9 S  L4 u6 p
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权
    ; k+ l5 b! ~2 R5 Y/ U6 t
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权
    $ i9 U9 O/ F  w3 \
  7. }1 {+ O. ]7 k% r2 g0 E- s* A
  8. // Tally是一个简单的用来保存当前投票分数的计分器; M) R: v( h5 W" c. L7 `# N
  9. type Tally struct {7 f3 N; Z0 W" L; s' }' e
  10.     Authorize bool `json:"authorize"` // 授权true或移除false
    ; u4 U9 r6 Z+ [, d. I) [6 F
  11.     Votes     int  `json:"votes"`     // 该提案已获票数
    / @' x5 I. o: a+ ]& p- g# y
  12. }
复制代码

* ?4 d. c2 ]7 ]0 wSnapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:$ `) \, Z% y6 @2 D1 q
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {2 K" Y* J+ N  o* d# p% ~
  2.         //使用Database接口的Get方法通过Key来查询缓存内容
    . y- q9 D7 n. c( L
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))5 O0 U& G* b) t& i/ D4 ?7 ?: J
  4.         if err != nil {' b9 [$ t9 c! ~% p
  5.                 return nil, err
    7 P% d$ V% |, U+ k$ q8 _$ X
  6.         }
    ( b; b! ^& `9 M+ R! f, x
  7.         snap := new(Snapshot)) @+ s% J& n0 S1 y( E3 v; c
  8.         if err := json.Unmarshal(blob, snap); err != nil {& |# d: r. H0 m7 f9 Z
  9.                 return nil, err
    ; k+ F8 L( \4 j- J
  10.         }3 `* N1 k4 ^7 {, [; b# F
  11.         snap.config = config1 e* k$ T7 S/ S: t  J, b
  12.         snap.sigcache = sigcache/ t9 F; }' I3 i$ h
  13.         return snap, nil3 k! Y$ E9 d. u
  14. }
复制代码

# [  r  T% `' I1 B" {newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:2 e2 p- H# |6 L4 ?
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {8 T& p; R! S& r7 U2 \# z
  2.         //组装一个Snapshot对象/ A& M; M" `9 Q. t  U
  3.         snap := &Snapshot{
    ' P$ V* I( x8 m
  4.                 config:   config,
    . y4 [  b. u0 ~9 c9 C1 r7 P1 a* P
  5.                 sigcache: sigcache," P4 U1 Z4 h. z* x& A
  6.                 Number:   number,5 I  j) y# F6 Z& r
  7.                 Hash:     hash,9 E9 ~/ j* v- Z! _; e
  8.                 Signers:  make(map[common.Address]struct{}),) w6 T& i" R5 `( ~
  9.                 Recents:  make(map[uint64]common.Address),
    : H9 K; H9 _4 o' f" [- a1 V. Z
  10.                 Tally:    make(map[common.Address]Tally),$ c# A' D' Y" Y7 @; Y4 j
  11.         }
    * y2 C8 M8 ?' q) S/ Y3 D# K
  12.         for _, signer := range signers {) }8 f7 ]6 Q) g  K) R, M
  13.                 snap.Signers[signer] = struct{}{}' A- f2 }: L+ I9 Z
  14.         }
    / j6 |8 j# U, f9 K, M/ [. y
  15.         return snap2 p& R% @' Z* K  T% u2 `0 r3 d
  16. }
复制代码
+ W, ~9 F2 W' V8 `+ {* z
继续看下snapshot函数的具体实现:
1 G/ h5 q, ^* b3 R8 W
  1. // 快照会在给定的时间点检索授权快照+ T: _0 q# g" ?1 E3 w8 L0 U/ H
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
    * L, b1 G" w8 d. c5 W& z5 @/ T% \( b
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints
    7 \: g# U' Z5 H( J5 @- ?
  4.         var (  @, e) W! q- r0 d! O: r2 P+ g3 o. f
  5.                 headers []*types.Header        //区块头
    " t5 r% }* ]- [6 n( D/ m& B
  6.                 snap    *Snapshot        //快照对象" n) ~! s3 e# d6 }+ g: ~. Y" ?
  7.         )
    ) I# a" F& J2 Q- V
  8.         for snap == nil {
    : Q5 J* P. Q; O$ t
  9.                 // 如果在内存中找到快照时,快照对象从内存中取$ p' d& q8 f, U8 J) a8 l
  10.                 if s, ok := c.recents.Get(hash); ok {; C- w7 X7 z# M) Q; d
  11.                         snap = s.(*Snapshot)* G7 {: i( R+ [. F* C+ n/ S+ L
  12.                         break
    2 g( ]6 `  M' S" C+ Z! [7 b
  13.                 }
    2 P% K3 @/ T3 |
  14.                 // 如果在磁盘检查点找到快照时) l- @: ~% ^; F. [0 C
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
    4 o  Q: X% O$ I0 T0 M6 m* i5 c& H
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {7 h3 C6 G8 P9 @6 Z! i+ R! M0 {
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)4 s$ M' Z0 ]1 W9 E
  18.                                 snap = s
    2 E3 g  b$ t" t3 ~/ e
  19.                                 break& y" t& U- @; S
  20.                         }
    2 ]+ a' o$ F2 A0 ^
  21.                 }
    6 w5 u- C4 s4 }/ m5 B3 y. X
  22.                 // 如果在创世块,则新建一个快照
    $ ?4 v) I, `, m! Q2 B6 G# o% m
  23.                 if number == 0 {/ o3 J" n8 @) H7 R7 A1 e
  24.                         genesis := chain.GetHeaderByNumber(0)
    0 n; H( F+ F# h0 W
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    2 p+ t0 }. B, f5 m0 h
  26.                                 return nil, err
    # C; J: h5 d, H4 g( ]9 g
  27.                         }
    : R0 U2 s( s4 q+ T" T
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
    " V" b$ q8 I+ H: F( N) }
  29.                         for i := 0; i  0 {5 k/ n% Q$ V/ M2 f+ t/ ?+ z9 x
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)
    ! X+ ]+ w0 u  F6 k. X( a
  31.                         header = parents[len(parents)-1]3 B0 T) x) E. k, L" \* {
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {% z4 x& N; I8 \8 G9 B1 @* p4 x" C
  33.                                 return nil, consensus.ErrUnknownAncestor
    % e% L) |1 w& t7 j/ r+ z: N3 d
  34.                         }: z+ |1 E2 A$ S# E0 X- J3 s7 C* A
  35.                         parents = parents[:len(parents)-1]
    8 C" l- W' ~5 _- P9 |
  36.                 } else {
      N( u9 q: j3 @+ Q7 U
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    ! j) I& m! }/ r3 w# ^- z, n
  38.                         header = chain.GetHeader(hash, number)
    + j* F! M+ h, ]" c" ^' P. U( }7 g
  39.                         if header == nil {- ], H. A5 s  K8 a2 V2 n
  40.                                 return nil, consensus.ErrUnknownAncestor  {4 I" d% I0 u8 t4 G
  41.                         }
    8 ?1 |& k8 z* v7 T
  42.                 }4 D7 p0 o. n; O) X
  43.                 headers = append(headers, header)
    " _7 w1 l0 {  R+ l# {( y
  44.                 number, hash = number-1, header.ParentHash
    + g0 j) O& ~. o+ r* H
  45.         }( y% y0 W! j/ ~, E& O
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面
    ) Y6 u# `7 o5 \5 S3 {
  47.         for i := 0; i  0 {/ z3 n+ m* O1 L3 X3 `& S
  48.                 if err = snap.store(c.db); err != nil {
    9 M  b0 `, n1 t- T! A2 @
  49.                         return nil, err" a5 F2 L  o  f* X. h& P# \
  50.                 }, v8 T& P( i7 v9 G' Q
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)) x6 T1 U+ U4 K! N4 H' V- b8 c7 S0 d
  52.         }
    + ?) X7 \$ w: V  s# g
  53.         return snap, err  q8 I' f+ V( t# n8 a
  54. }
复制代码
  U; X1 Y9 b$ ]0 E+ r( a
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?' w8 h6 d6 l  h- W) x" E6 H
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。: O. |3 E& \8 h# M. G
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {& v5 o7 N! v5 L8 p9 |2 ~5 k3 s6 I& w
  3.           //可以传空区块头2 {( V; ~; r/ S. l, ^/ ?
  4.     if len(headers) == 0 {
    $ o: @, A1 o3 E( P) l- Q4 R$ b
  5.         return s, nil( g7 x( T7 c% B
  6.     }
    + j) J0 [/ v0 X& A) m
  7.           //完整性检查区块头可用性7 ~" `6 s4 W8 ?& i1 n
  8.     for i := 0; i = limit {0 ^9 J* }( {0 c: Q3 c  t
  9.             delete(snap.Recents, number-limit)
    ( r7 O  O- [0 [
  10.         }9 d" ^( f( @# o+ [" ~8 r( _
  11.         // 从区块头中解密出来签名者地址: ?, ^( o' v% d2 }0 [$ L
  12.         signer, err := ecrecover(header, s.sigcache)
    * Z, i1 A  s# c+ ^
  13.         if err != nil {; u! a7 d9 c* S) H
  14.             return nil, err8 T5 B" Y2 L2 v3 Z3 {
  15.         }. f1 z$ `$ q! i( z& @6 y# r# V; ~
  16.         if _, ok := snap.Signers[signer]; !ok {
    ' F# _$ `* X* N3 C, ?+ B
  17.             return nil, errUnauthorized* j4 f. X- y* a; I# D
  18.         }
    4 ]* j# f+ _1 X% U8 L
  19.         for _, recent := range snap.Recents {( [4 y+ V; b- T8 V4 x* b
  20.             if recent == signer {- I5 Z( b9 F5 H% g6 F! Z8 C6 m* ~" _
  21.                 return nil, errUnauthorized
    " l4 X6 v" _! |% g
  22.             }5 [2 X4 n  h) W! _' n
  23.         }6 b2 e1 D7 H/ ?- h! I
  24.         snap.Recents[number] = signer
    * ^9 G2 u9 w/ J! v
  25.         // 区块头认证,不管该签名者之前的任何投票& v5 x% j5 O# j* C1 z
  26.         for i, vote := range snap.Votes {  r' |; j) r" ^3 a9 p
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {
    ) Q, b( u+ M3 w: n: _
  28.                 // 从缓存计数器中移除该投票, n9 X8 p: S' a
  29.                 snap.uncast(vote.Address, vote.Authorize)
    ! I' x, q, ^! I" U
  30.                 // 从按时间排序的列表中移除投票+ Q1 W- A) L3 N+ C! P
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    ; q1 y( s- ]7 l3 Z# x
  32.                 break // 只允许一票% Y2 t, }: Q. q+ J6 W7 A4 I# {* n+ q
  33.             }
    7 m  G* X% n& i# }  f
  34.         }
    7 F: W' v0 E- A- f# Y0 \
  35.         // 从签名者中计数新的投票8 I. B, ^/ {% U: o- ]+ d% b
  36.         var authorize bool
    ( X+ B  V* k) F7 b0 e
  37.         switch {7 j9 }( u+ a: y9 e- F, w& k/ s
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    % s" T# V0 d6 P" y% i% x8 L% b
  39.             authorize = true
    ) A7 p( x' u, P3 i" r
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):
      D! m4 r; B1 x: B$ y! q
  41.             authorize = false: E1 f8 p- s( s7 K  Z/ F) q
  42.         default:
    8 ?" V6 R3 F; {3 L) A
  43.             return nil, errInvalidVote9 g: O3 C5 U9 n
  44.         }) D- |+ @: t! M+ Q7 n9 T
  45.         if snap.cast(header.Coinbase, authorize) {5 L1 N4 E8 R' D' k
  46.             snap.Votes = append(snap.Votes, &Vote{6 ]8 k# m6 u6 \; j. r! x
  47.                 Signer:    signer,
    : T2 H2 r" w! u/ D$ q' ?
  48.                 Block:     number,
    ) K  x! D' e3 s! M$ m$ X
  49.                 Address:   header.Coinbase,
    - F, v1 k) x8 i" L3 R* V, \3 \
  50.                 Authorize: authorize,& B7 M  k1 f2 i. b* Z. R
  51.             })- r8 z+ y$ W- a4 @; [
  52.         }
    2 p( R# ^; l* M/ F: N
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表% O% y1 W5 Q. G& l/ C' U) x6 \
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
    " }9 a% }' O( C" H. r
  55.             if tally.Authorize {- A* \2 }: L" g6 @8 @
  56.                 snap.Signers[header.Coinbase] = struct{}{}
    , z# t% O. U+ l$ e
  57.             } else {
    : |3 y  q3 E( i. \$ s6 V
  58.                 delete(snap.Signers, header.Coinbase)/ ]' u4 V' j) w. m
  59.                                   // 签名者列表缩减,删除最近剩余的缓存- `/ ^% \2 t! W! _
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {) @2 _1 ?$ [4 R7 t
  61.                     delete(snap.Recents, number-limit)
    . F* m4 d2 g$ a. m
  62.                 }7 d. H/ d) W& }- }4 j: L1 U
  63.                 for i := 0; i
复制代码
' ?; T( [7 s" m2 W! ^+ X$ D/ 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快照结构已经更新到哪个区块了。4 ?8 H; H- l6 O9 I( N! z6 [' {: t+ c
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。
4 h2 y5 y2 F/ X( S, ]; Z+ S
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
    " a6 }, d% i0 e! N0 j4 ]1 O4 w
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
    $ A. L" q0 ~( L  c  Z
  3.         // 不支持校检创世块# k- [. J5 O/ m; x5 ^6 Q. ^
  4.         number := header.Number.Uint64()
    $ S/ ]5 E: R" K2 w' a
  5.         if number == 0 {
    ( N$ _! k7 A: I) j+ [" z) M
  6.                 return errUnknownBlock
    0 V/ U8 G  c# U" h* I& |* Y2 T
  7.         }; S' c' }: T# x  U
  8.         // 检索出所需的区块对象来校检去开头和将其缓存
    5 Y) m3 @/ |, d# t& h
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)4 m! c* l1 B+ u, ?" P
  10.         if err != nil {
    ' g* Q  k' q7 l. V8 u
  11.                 return err, `" y4 |5 Y, }$ `4 q: }( w! W
  12.         }
    + M; v9 |7 D6 V) L! z; D
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址
    - [- L$ L' u7 ~. l/ r9 q, {3 A# W3 k
  14.         signer, err := ecrecover(header, c.signatures)
    9 a+ L6 E) x* r6 {$ M) r' d
  15.         if err != nil {# k$ I. O) V2 `1 W6 r9 U
  16.                 return err0 f$ U% e9 Q  X/ i- E7 x9 B
  17.         }  Y' h. L8 X! s- O' Y0 O7 {2 G
  18.         if _, ok := snap.Signers[signer]; !ok {
    . \5 V# t1 c  m& g. r$ P  L
  19.                 return errUnauthorized4 L; D8 s+ F) s) B1 C8 {9 b" |
  20.         }/ P! |* N, X+ q5 o$ J0 b( o/ N
  21.         for seen, recent := range snap.Recents {9 x. k7 h; b' Z: F# y
  22.                 if recent == signer {$ {1 U. ?& \  U5 B
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等. H# ]0 ?% [$ G) q  a5 X2 ]" m7 `
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    # j; G% a( u; Y$ Q& B& |. f
  25.                                 return errUnauthorized" X8 P' p) a3 j
  26.                         }
    & T; ?. I. A, K  I" u$ C+ @' B
  27.                 }
    ; u. B) p- X; _9 l+ y% o
  28.         }
    6 f/ |3 r5 q# m% `
  29.         // 设置区块难度,参见上面的区块难度部分) U$ x' P6 @% [0 S
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)
    6 W& N% V* n: }( u, s9 a
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
    5 v+ `3 M* w. B2 s* M
  32.                 return errInvalidDifficulty
    0 W4 A* k/ R7 ^' C/ k8 e: ~
  33.         }. ~# ], t" E/ x8 r, d& ~
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {* H5 w& S' l; m) P
  35.                 return errInvalidDifficulty
    3 k  I3 R# H: E+ @
  36.         }
    9 q4 ]/ g$ n5 o2 ^# |' k6 K
  37.         return nil
    5 q; `3 t; e. p2 a; w% b
  38. }
复制代码
0 T" s: P: {( r! l. K
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
0 X6 c8 n" y$ ]7 TClique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:: R3 {  D& I, ^* T* g, A
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中
/ _( ~/ c  @9 }
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
    4 S2 j0 H6 ~% H
  2. func (api *API) Propose(address common.Address, auth bool) {) j/ r4 y* x: z
  3.     api.clique.lock.Lock()
    & f& W) j+ p$ r3 m# I& E4 g
  4.     defer api.clique.lock.Unlock()( b! e3 {9 y% l( y6 G
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    / V7 ?+ r0 G  G0 G# \; a% ~
  6. }
复制代码

* d5 b, P3 m( P" W本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;
, L0 }* G5 R* {  P) h8 U2 y) c+ \- o
  1. //Clique.Prepare4 J% R6 d: f( v- Z8 S2 V. p
  2.                 // 抓取所有有意义投票的提案# S2 s' l5 _' p- I' V4 R' v
  3.                 addresses := make([]common.Address, 0, len(c.proposals))
    & t- l2 i% A' U
  4.                 for address, authorize := range c.proposals {& \# h# s4 ^7 P- }* H
  5.                         if snap.validVote(address, authorize) {
    0 U* G  V& K) H5 V7 z* [
  6.                                 addresses = append(addresses, address). F6 q: z0 E( g- Y% _, |
  7.                         }
    / s5 O; h, ]3 V5 X) X. s8 M; Y6 [
  8.                 }$ F8 z$ ^' Z% h$ T& n# N) `+ j$ Z! D
  9.                 // If there's pending proposals, cast a vote on them
    8 g4 _+ Z) k, l! F
  10.                 if len(addresses) > 0 {! c, P( P0 F# j6 ^1 E& d4 C
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。; R6 A. j% B; G! F5 N5 ~
  12.                         // 通过提案内容来组装区块头的随机数字段。# q+ W# Z. O' w1 ?' s( j8 u. U
  13.                         if c.proposals[header.Coinbase] {# i' x' K, [, {8 G+ q1 ^; n7 m( A
  14.                                 copy(header.Nonce[:], nonceAuthVote)" q% D' [! i5 J: G( v: _* [& r* ^5 m
  15.                         } else {/ v: s3 z, W6 `. q/ A
  16.                                 copy(header.Nonce[:], nonceDropVote)
    ( {3 C  t& `: l4 c+ m
  17.                         }* q9 K& b  N( ~5 S: ^+ q
  18.                 }
复制代码
# H& R& L! z5 G
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare: s! b8 _' s; C2 A. g/ C# o; [. U
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {7 U! y/ Z+ q0 w4 A5 G! {+ `
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    ( R% i' |( {  F& z% l
  3.                 return
    " G5 i3 i- @6 n, v. g8 N# G5 v
  4.         }
复制代码
( U$ k* R5 U2 Z3 k
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法
$ _4 F- `0 M7 Q( I1 d4 t7 I; Y) S% M2 ~. v
2 h+ _1 a+ ?( p
# `; j# M- q9 ^- D& j- ^& O, T& `1 d
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3