Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2771 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。
2 L; n$ {5 n; p; P
  1. func (s *Ethereum) StartMining(local bool) error {, L" ?& ?% m3 A6 W; a5 `: W3 ]9 t
  2.         eb, err := s.Etherbase()//用户地址
    * g6 ~: {- h" ~- h" Z" e% k
  3.         if err != nil {
      w) j& ]( \; E5 k/ _; w
  4.                 log.Error("Cannot start mining without etherbase", "err", err)& G; r. ^" b* ^+ p: H; k
  5.                 return fmt.Errorf("etherbase missing: %v", err)
    2 Z/ a% a2 \% m  D5 G  a
  6.         }
    ( c) x2 Z  q  u
  7.         if clique, ok := s.engine.(*clique.Clique); ok {3 h8 E% q- A  `# M- ?
  8.                 //如果是clique共识算法" T! W# h% k& q/ \' T2 L
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象, l8 Q" Z! j9 E1 y; B" n6 H
  10.                 if wallet == nil || err != nil {/ K# i* a9 f6 }
  11.                         log.Error("Etherbase account unavailable locally", "err", err): T* e2 K! G( Y. u) t8 |3 Z
  12.                         return fmt.Errorf("signer missing: %v", err)
    * T3 s7 [7 r+ d
  13.                 }
    9 A* r$ s" E, Q0 l- \
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法8 W8 F# S9 ]+ R" ~7 T9 t
  15.         }
    + \6 V. h" l" G! R" q9 ~4 O
  16.         if local {/ u' Z/ ^& U! D6 y
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
    ' W' e$ c. _! b
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
    ) Q: F# b; s1 x! D5 C
  19.         }
    6 @4 ]: a4 [% J! ]9 f1 K$ W5 g
  20.         go s.miner.Start(eb)
    : S. L/ ~0 h: }3 K7 |  P
  21.         return nil: M* I) |! a* n' i
  22. }
复制代码
+ d1 e9 H( _/ {! m6 ^
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
- L% b5 S# d5 Z8 p+ ~Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
2 m8 I  ?, u3 b; h# Htype Engine interface {
  s; P  q6 O! e! `4 q: T        Author(header *types.Header) (common.Address, error)
! N4 D, Z9 t" r- a: }. j        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
" C  n' s) Y1 i5 |) v        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
0 z( Q1 d$ a8 ?  cEngine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:/ L, ~' S# E& N, B* B! c/ ~. ]
  1. type Clique struct {
    0 E6 c3 j$ c3 `5 ^+ H$ k
  2.         config *params.CliqueConfig // 共识引擎配置参数6 A0 b$ z5 P* Z4 I
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点
    0 e- W7 N, c' W0 l9 k3 N2 J
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组
    - ]9 ?) g* Y2 D9 f- P/ i7 ^
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿
    + u+ j4 `' w0 [. G$ M
  6.         proposals map[common.Address]bool // 目前正在推送的提案1 s+ S8 U, J6 V9 _3 U: M, ?0 ]
  7.         signer common.Address // 签名者的以太坊地址( d+ p; V, d( |) j/ u, E  l. x
  8.         signFn SignerFn       // 授权哈希的签名方法
    0 n# d+ T6 M& u5 q+ a
  9.         lock   sync.RWMutex   // 用锁来保护签名字段" h3 _7 \8 L+ ^
  10. }
复制代码

5 V* [" N, F! s顺便来看下CliqueConfig共识引擎的配置参数结构体:& S- p, `- X6 K2 ~, d
  1. type CliqueConfig struct {
    ' y$ |7 M" I7 E& L8 C
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)
    6 f& k/ d& z0 c
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)2 D/ ~1 l; w4 D9 t
  4. }
复制代码
6 |# }. J) R8 x: w9 Q
在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:: w0 P/ z0 w* h5 R( ^+ R7 p
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {+ Y5 |4 X! d- l* Q/ `) x
  2.     c.lock.Lock()
    / Q7 j; }- d" s& F4 ~
  3.     defer c.lock.Unlock()! X$ L& ^, g: u, }7 f
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块7 D6 _3 O% p, ?8 \- o. H2 w7 n/ {& A
  5.     c.signer = signer/ o  P, ]- [) l! L" j) h/ b- T
  6.     c.signFn = signFn
    ) s8 E% \( t: C/ m: \- c! w# u
  7. }
复制代码
9 x/ H- p  T+ k' u
再来看Clique的Seal()函数的具体实现:
& X+ p, ]# s: L6 [//通过本地签名认证创建已密封的区块1 _) h* A8 r, z0 H, m/ K8 u! a1 \
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {4 c5 V3 v! Z5 L- W; @0 \4 q0 |$ A
                                log.Info("Signed recently, must wait for others")1 `; }2 \6 I# a, W) |( W# `
                                7 X$ H9 w; h% x$ h* F
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名
5 W$ ^* y/ }/ p' ^signer不在snapshot的signer中不允许签名
. B2 @/ F* H7 Lsigner不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
5 Z4 S- f# ^; V5 N0 T: z1 P& j签名存放在Extra的extraSeal的65个字节中
& J. J2 v0 [/ `, X+ v2 p9 p+ n关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。
3 j! J2 |% b" }0 B% {$ F
  1. //snap.Signers是所有的认证节点
    & \8 \4 g- b5 g9 c1 c
  2. for seen, recent := range snap.Recents {0 u- |; L) m/ {3 u/ ^$ P
  3.     if recent == signer {
    , ?! p2 }& C: L; P
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {$ n. W2 ^$ @: i: i! R: X& d
  5.             log.Info("Signed recently, must wait for others")
复制代码
" |  n; ^9 H# p9 \- O+ }# E3 q% y' G6 g
            $ G* q3 ]9 P1 b8 S0 x
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
& p. u7 F+ K: Z* @关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
5 N' p, r% q; J. zdiffInTurn = big.NewInt(2) ' P; B& ^7 m+ I1 u$ y) q
diffNoTurn = big.NewInt(1)
8 P2 N7 k0 ^/ _" k( [/ ~" s% S& Q当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
, U2 S+ }" K. i: o" M( @// 通过给定的区块高度和签发者返回该签发者是否在轮次内
6 {! d5 N2 e, b$ t2 s% A) L
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {% l: f0 ]; U& @7 O0 ]& P
  2.         signers, offset := s.signers(), 0
    0 T8 O$ Y7 M2 I1 P7 a: ~
  3.         for offset
复制代码
6 v) B7 s* I2 \* i
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:; G7 f( B$ l) Q& ~
// Snapshot对象是在给定时间点的一个认证投票的状态
( ]0 c0 k& ]* {' W: @( q. \) F
  1. type Snapshot struct {: `3 _  l3 z5 J' Q: c
  2.     config   *params.CliqueConfig // 共识引擎配置参数
    - \. w' Z0 c" [- l2 {* @
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。; X# i* [8 o# ^' E
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号
    8 {1 k# u9 f* Z, c( B
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    + z4 t: S8 ~3 H9 P$ `% F! A. ~
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表5 `/ k; ]) F$ s2 E# H
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址; {' u: \5 y) A* E* b
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    7 R& f! }* s  s1 O2 c: N
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。2 e7 f  e. K! o( H( M0 f
  10. }
复制代码

5 w" |) f. w, y2 J' [9 W快照Snapshot对象中存在投票的Votes和记票的Tally对象:
' @; a# x. ?" ?) C7 y2 E: Y
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。1 s& j$ I5 I3 k1 ^0 q8 }2 a
  2. type Vote struct {) T6 U, r: q# m) x3 E7 V% j7 l3 X: M
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
    - r7 \) `2 x# j' I+ Z" ]* A$ h
  4.     Block     uint64         `json:"block"`     // 投票区块号
    * H8 j( V% W& R: U
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权' p$ L! h2 K  _$ W8 ~7 a" B* A! m
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权
    3 F: J6 I+ p4 V" Q$ y8 N0 _3 r
  7. }
    / Z1 a$ M" V! L& m- D
  8. // Tally是一个简单的用来保存当前投票分数的计分器( e. g- a. F5 N" K  D
  9. type Tally struct {
    + [& F" F$ Z/ y
  10.     Authorize bool `json:"authorize"` // 授权true或移除false# r* V/ K- f+ K0 E. b" S  ^, M
  11.     Votes     int  `json:"votes"`     // 该提案已获票数
    1 X, Q, H" H' {9 b% k* E" \
  12. }
复制代码

" ^; d' D- R/ eSnapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:
6 B2 h0 K8 P7 @6 {0 ^0 I' M; s
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {, m9 r1 x- z/ |% R# A8 i$ L1 C
  2.         //使用Database接口的Get方法通过Key来查询缓存内容8 H! n& \5 g9 I: }7 D4 I/ H2 \
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))# q! |& N* F. G2 H- Q
  4.         if err != nil {
    " M5 g7 ?1 m! W3 [3 T4 F
  5.                 return nil, err, W5 A7 F- Q3 o; z% h! Q: O6 J
  6.         }
    6 Z* [7 B7 t) h6 F
  7.         snap := new(Snapshot)8 E" _; x* V/ P. T# Y+ a
  8.         if err := json.Unmarshal(blob, snap); err != nil {
    ; c" m& r) R% u# r: p
  9.                 return nil, err
    . Y+ k: p9 o# C& `/ A: I: t- o
  10.         }/ G4 ~/ p2 H7 {% F+ V0 k' n8 Q
  11.         snap.config = config% a+ J! k; m  v1 v% o( z
  12.         snap.sigcache = sigcache
    * ]& a/ T; k: i- {% W, T7 r
  13.         return snap, nil
    $ }% {. Z/ w! Y+ n; `, s% n
  14. }
复制代码
" ~+ |0 c3 ?! I, A1 m
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:3 \' \/ z9 I  P0 O, G7 c5 T' w
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
    9 y2 X8 V4 i+ S
  2.         //组装一个Snapshot对象( M$ I9 C* U/ P0 d1 J! t  }
  3.         snap := &Snapshot{- O) p4 x& |  j+ W2 i) m
  4.                 config:   config,
    " z& S5 A6 a) k% g
  5.                 sigcache: sigcache,: q( U- Q( l7 h- x. O$ K- d
  6.                 Number:   number,! J. p1 n; e6 m* c1 m) P
  7.                 Hash:     hash,4 L" O! x$ ]0 q% a( _1 t
  8.                 Signers:  make(map[common.Address]struct{}),5 F8 ?9 |7 g3 r& M3 c9 c5 e/ K7 T
  9.                 Recents:  make(map[uint64]common.Address),  f) p  U- ?9 M- _
  10.                 Tally:    make(map[common.Address]Tally),
    + w) X% K. O7 M' ]5 V' s
  11.         }
    & s1 {4 b7 G5 {
  12.         for _, signer := range signers {1 Y6 r/ N& x. J# I! Y% R5 S
  13.                 snap.Signers[signer] = struct{}{}6 w: @0 Q! y$ h
  14.         }
    1 N3 ~# I8 R) C4 [2 |
  15.         return snap
    3 o& @: O* l6 ^' g% M7 J' a
  16. }
复制代码
- d5 Q4 w  }! r9 [" E9 h
继续看下snapshot函数的具体实现:
9 `0 K% g# a! J6 X1 v9 x
  1. // 快照会在给定的时间点检索授权快照4 R# T3 f  a  J
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {5 X4 X9 Q* F" i$ @6 q0 A
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints6 \- h/ A  P4 n9 ]3 H4 ^
  4.         var (% E2 X6 g2 S/ J4 E9 r# c  m
  5.                 headers []*types.Header        //区块头
    ( \# \& c& _" S6 V1 Q, \
  6.                 snap    *Snapshot        //快照对象- g, ?6 K" M8 a7 ^8 F4 H! X6 _/ b
  7.         )
    8 O4 ^" t0 \4 U' ^
  8.         for snap == nil {
    / a; l0 m' M* \6 K0 c
  9.                 // 如果在内存中找到快照时,快照对象从内存中取
    3 }/ ~; a/ {6 f( w
  10.                 if s, ok := c.recents.Get(hash); ok {/ O3 }3 Q- i: `; P
  11.                         snap = s.(*Snapshot)
    # Z. ?5 n* H8 ~! E- q/ O
  12.                         break5 ?5 K3 l3 `! ]9 n% D/ F! P+ _
  13.                 }, }& H- h. o! p1 ~" z6 i/ D5 L
  14.                 // 如果在磁盘检查点找到快照时+ D9 f, y) r( j$ @
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号( E0 l3 z3 N( q  q0 Q+ N0 r
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {  @! Y. a3 ]- v
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)% L2 U0 p9 n! O! b' R- M
  18.                                 snap = s
    ! b8 O$ c# a5 _
  19.                                 break/ [- w8 d, u6 j& Q. [
  20.                         }
    / G: k+ ^3 C! r6 [3 M$ X
  21.                 }2 D& d/ v, z+ R6 ^
  22.                 // 如果在创世块,则新建一个快照
    ) _  v' D9 C* w- p* C
  23.                 if number == 0 {& I& G* _  U& \7 }1 ?2 Y0 j
  24.                         genesis := chain.GetHeaderByNumber(0)
    7 o9 }3 A2 l& E7 ~) S0 Z. L- _
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {! x8 [! P# g& y1 K
  26.                                 return nil, err" n* F  P; J+ t, Y# |0 ]# |0 ~
  27.                         }
    : O7 H5 E  M3 `2 t8 a  c
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)% k0 g- _  Y1 P( o% ]# O
  29.                         for i := 0; i  0 {* l0 e+ }4 K- n  f  \* e0 B7 Z
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)& @6 d9 y& f/ J- `. q# `
  31.                         header = parents[len(parents)-1]
    9 A2 k5 t: O: F! f! x
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {  `6 ?' p+ ^9 v- p- J2 R/ V) K
  33.                                 return nil, consensus.ErrUnknownAncestor
    6 l  f2 U# n1 Y/ z' J' X
  34.                         }
    * N; m5 U+ Z6 N, F* e; x
  35.                         parents = parents[:len(parents)-1]) O  D: s/ m( @" [+ ^. I0 n
  36.                 } else {
    ; l' G, C- \) d: V" E
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    ( w# I( p- E6 L
  38.                         header = chain.GetHeader(hash, number)2 z3 s- o- H/ a' i- n) E" X
  39.                         if header == nil {
    # l* J: v( k! N2 B
  40.                                 return nil, consensus.ErrUnknownAncestor
    : ^8 c" J) m  Q( d" O+ g" _
  41.                         }
    ; y7 u8 y  O( H& K5 B* p
  42.                 }* {2 ^* m0 U, g8 t6 ?5 Q
  43.                 headers = append(headers, header)
    6 ~/ t/ m8 ]$ n' p- f- e! R! N/ ~
  44.                 number, hash = number-1, header.ParentHash
    / @  p0 x; o; |0 R9 X* u9 d, w
  45.         }
    . ]* f2 C/ l" c: G) N" P. Q2 f
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面9 O- I* R2 b5 `/ `9 r& m
  47.         for i := 0; i  0 {( v- B$ m4 [! t2 u: q  n
  48.                 if err = snap.store(c.db); err != nil {0 p6 _( h6 r6 p' ?2 y: ^. a$ g
  49.                         return nil, err
    8 p% x) h3 R  I4 m
  50.                 }
    9 m8 ~; K$ G. `; P! k8 Q+ y
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash). e7 O5 A( E- B3 g- i2 V
  52.         }  v9 [: z* q7 W! B" ?& h
  53.         return snap, err
    # s/ y: W9 @$ g3 d# J
  54. }
复制代码
7 ^) o8 Q/ D. B& d
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?0 \/ B2 A! q: h. z8 r
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
    $ G5 o, F2 _" `# d5 r
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {) Z3 P* B- f3 u3 W: r3 W
  3.           //可以传空区块头
    5 G# j. u4 M, T& v9 [  h: o2 P
  4.     if len(headers) == 0 {& P$ e9 A" S# d0 f  u
  5.         return s, nil' W" Z$ e7 o  I; i. T
  6.     }9 g  ]! e; U; m6 e# X3 _
  7.           //完整性检查区块头可用性
    8 ~$ Q) M3 J$ \8 ~+ t* \
  8.     for i := 0; i = limit {
    2 e4 ~0 m% C5 V9 ?) w
  9.             delete(snap.Recents, number-limit)1 x7 s1 E* t, f+ z2 b, C
  10.         }
    # a/ G. J7 ]9 G% @
  11.         // 从区块头中解密出来签名者地址
    " p& K8 o5 C( p
  12.         signer, err := ecrecover(header, s.sigcache)
    / n# _2 Z' }8 B) p
  13.         if err != nil {
    & B- f! b# n/ T7 E
  14.             return nil, err
    1 Y! \- ~' d7 _4 s  m& e
  15.         }% F' d; }" q  y5 j' I% u" W  @
  16.         if _, ok := snap.Signers[signer]; !ok {
    ' j2 h8 ]7 m5 V9 U
  17.             return nil, errUnauthorized8 p6 _# W1 }8 d. U% x1 W$ ?
  18.         }1 v) `/ Z5 L( @5 ^/ B5 [
  19.         for _, recent := range snap.Recents {
    ' x( Z2 u1 j- y7 Y- o- z
  20.             if recent == signer {' s) J( w% g$ o% ?' K3 T' }
  21.                 return nil, errUnauthorized
    ) q* L) R* P+ \6 h& |6 o- w
  22.             }: D# A, B: M$ F/ k! F/ m
  23.         }" X$ O& ]4 _6 ?
  24.         snap.Recents[number] = signer
    / L, k) p  ~" O- ^. D
  25.         // 区块头认证,不管该签名者之前的任何投票9 `/ s" v: p" J. z9 r
  26.         for i, vote := range snap.Votes {( ^8 ^# v, Z, a5 ?* p
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {
    + [6 k) g: \& i" V! B* A8 s3 W+ E. D
  28.                 // 从缓存计数器中移除该投票
    % ^+ M- J( L, m8 j8 f& m# I0 w
  29.                 snap.uncast(vote.Address, vote.Authorize)
    9 j7 I4 ~8 F! b# h& x4 _0 ^9 @
  30.                 // 从按时间排序的列表中移除投票
    & l) j; }- v/ ^9 ]# s2 ~0 A
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    2 I2 ]. _! g( D' [- Q
  32.                 break // 只允许一票+ p% p) f; ]6 e
  33.             }
    0 G% {5 B; Q8 d3 v+ G
  34.         }
    / @$ M! y% T, o! ?  A" n! R
  35.         // 从签名者中计数新的投票
    8 o: v) U) J# g, J
  36.         var authorize bool
    5 Q& N8 W" J" S. H9 P, K( w  }! p
  37.         switch {
    / x5 t2 u$ q3 ~* K
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    , m8 x' f. Q  u" z5 F" _
  39.             authorize = true- `1 s/ X! B% s; B) d
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):
    5 o/ K) j$ ~  j# O* g5 U
  41.             authorize = false+ P8 c& c' m1 Z9 L' J1 P
  42.         default:
    : K' I/ X  `( x
  43.             return nil, errInvalidVote+ d1 s' U% ]  b+ T* h8 C
  44.         }
    3 |1 A% z, i+ p
  45.         if snap.cast(header.Coinbase, authorize) {
    * ]% H0 Q& p* B; A; a
  46.             snap.Votes = append(snap.Votes, &Vote{# A6 k0 N+ X, Q
  47.                 Signer:    signer,$ Y# d3 P3 S+ c3 Y: T. Z! y/ t
  48.                 Block:     number,' T) c& r* X7 u/ m0 K
  49.                 Address:   header.Coinbase,
    % ~4 N+ K! W) u& b# k5 {
  50.                 Authorize: authorize,, j3 l6 l; f1 r$ V/ D
  51.             })
      b8 ]. \' ]3 _4 M, n( r) Z
  52.         }8 y; f& F+ k/ P7 S/ L
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
    : K8 Q# d3 D, c0 r! ?6 V
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {% a, h1 u% ]" T$ R) f  R, |2 Q" a
  55.             if tally.Authorize {( V1 x6 ~) j0 s0 [
  56.                 snap.Signers[header.Coinbase] = struct{}{}0 X! J9 S& @2 J' k; P
  57.             } else {
    - }! A7 P  L+ L; ]6 W* |
  58.                 delete(snap.Signers, header.Coinbase)& o. |, Z( a0 L+ D9 |- v
  59.                                   // 签名者列表缩减,删除最近剩余的缓存
    5 b8 R7 C- F/ n
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {9 V" z6 W+ J% y: v  F' y) H
  61.                     delete(snap.Recents, number-limit); ]% |0 i  t6 d) V/ Q+ c4 Z0 Q
  62.                 }
    ' I6 j# F2 ]6 S" D, |8 S% M1 L
  63.                 for i := 0; i
复制代码
- l1 C. l+ b! T' K! N
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。
% R  O/ j4 q, F6 Z( ]) G区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。
$ O1 E& r0 r0 ~
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照7 ]  \3 B! F) u! v, B
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {& J9 r7 J; X( G- d% G! _
  3.         // 不支持校检创世块4 r6 M5 N4 d" O: e0 Q( j2 V7 c
  4.         number := header.Number.Uint64()
    - z, f2 g0 d" @. D8 ?
  5.         if number == 0 {2 B# z3 K* S+ b2 L, L3 G
  6.                 return errUnknownBlock
      V- \; A- f. u0 k* e$ v. ?, }
  7.         }
    + ]) }% S$ \- s9 C; h0 N
  8.         // 检索出所需的区块对象来校检去开头和将其缓存0 @' @" d5 `" ?6 F5 k& L6 P6 B' W
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)2 [: g3 @" X/ e$ T7 O1 l
  10.         if err != nil {
    & X6 l+ B; }1 s3 `1 J( [/ X$ a
  11.                 return err
    1 Z$ w- q# m% L3 m" J. i# T! b
  12.         }
    8 F8 c, G) X" Q* q3 |! N
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址) K& f0 M# r! K$ D3 K6 J, A
  14.         signer, err := ecrecover(header, c.signatures)
    ) k# h4 x2 u. W. I( H3 {  c2 A6 p
  15.         if err != nil {) H& Q) ]$ Q& C' D
  16.                 return err
      D' }- x: \: h: d, T( y% M
  17.         }2 ^0 w! a* x% o4 G' y
  18.         if _, ok := snap.Signers[signer]; !ok {) c  Q7 l: {3 G/ B" J9 `
  19.                 return errUnauthorized7 _0 r) d2 w5 m, w8 m4 f" @! E
  20.         }
    + @& C7 N: j" P: y* ]4 _8 C" X
  21.         for seen, recent := range snap.Recents {
    ! o" h. y2 `  w- |
  22.                 if recent == signer {7 }9 ^; F  A6 F% @' L. I6 i/ I
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等$ w) O% n1 D: @, L
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    ( X" |$ [  |5 `
  25.                                 return errUnauthorized7 |3 C" r/ F/ O( g  w
  26.                         }: w. `1 y' a+ D6 u
  27.                 }
    7 Y! k- L! X: W" F2 {8 i1 Q
  28.         }8 K0 |$ b0 Z+ Q. u, d
  29.         // 设置区块难度,参见上面的区块难度部分
    6 Z" g& r; s9 A4 J& x
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)6 }5 @% {  R8 J: L+ D% r; _
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
    8 U! b; ]! E4 X& ^8 z. d: X" W0 m
  32.                 return errInvalidDifficulty0 |2 l2 |; A: z9 V3 ^1 D! u
  33.         }$ c# F& ?$ Z% C
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
    1 J5 C7 H) [0 l
  35.                 return errInvalidDifficulty
    , k4 Q( f9 Z( Q1 c6 I# t
  36.         }
    & U% c% D! J7 u( U3 c+ _$ D
  37.         return nil3 ^  u% X8 t& e! h. X
  38. }
复制代码
, @. h6 R: L4 `+ C" l; [3 a
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
: c# q( C5 `. g" V: V7 dClique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:1 x: d0 p. T* B+ ]/ C2 U
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中, s" G6 D/ M5 w. c
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
    : X0 N% U& b0 D
  2. func (api *API) Propose(address common.Address, auth bool) {
      B. G, H0 q3 V6 i
  3.     api.clique.lock.Lock()' q2 ?) c& p( S" T  I
  4.     defer api.clique.lock.Unlock()
    $ o' s, u, k; b% Y+ F+ N
  5.     api.clique.proposals[address] = auth// true:授权,false:移除3 O! z2 a% g* f% ]3 E
  6. }
复制代码

) H6 U. K* P) ?5 @本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;
* d, j: B" o9 _
  1. //Clique.Prepare
    ; w4 ~3 e: U+ N6 v
  2.                 // 抓取所有有意义投票的提案
    , x5 `" E/ Z; _8 y
  3.                 addresses := make([]common.Address, 0, len(c.proposals))& r7 v* C. F$ z, A' q: S, M
  4.                 for address, authorize := range c.proposals {% c) U& w6 s/ p; B6 C8 W7 f8 Y, h! _
  5.                         if snap.validVote(address, authorize) {
    7 O6 i+ x7 o2 d! |( F- P
  6.                                 addresses = append(addresses, address)7 D5 P5 h+ [  O- E
  7.                         }
    1 f: v: L! Q- Q9 ~$ ?% k
  8.                 }( B, W' `, D$ `
  9.                 // If there's pending proposals, cast a vote on them
    % R. |& }* Z& w% {0 S, Z
  10.                 if len(addresses) > 0 {
      E* m, j4 J% g! N: F! x; W
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。
    8 @3 q' ~; V7 }/ C  K
  12.                         // 通过提案内容来组装区块头的随机数字段。
    ; p1 o; A3 V  {& B" N! q
  13.                         if c.proposals[header.Coinbase] {
    ' K, N6 L# @2 V8 z7 \5 T" {' f
  14.                                 copy(header.Nonce[:], nonceAuthVote)3 i* D6 v: ~5 C  ^3 @$ }) K
  15.                         } else {
    1 X2 m* D% {6 h5 n! ]8 Y
  16.                                 copy(header.Nonce[:], nonceDropVote)% t+ h' P) P0 `2 C. i
  17.                         }
    ' M1 H1 q: f! _7 _8 ^/ K* V+ X
  18.                 }
复制代码

6 c9 {9 @# N. x. Y* b" S在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare" g  D8 }- u, ]+ o, i) S
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {0 q8 E6 Q1 q% ?  N5 ~3 [) V; I
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    ( R! h& F" ~: ?; u8 L
  3.                 return
      t4 a; R& k! c) o
  4.         }
复制代码

! G) W  ~: F- q8 h, u5 t* Y其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法7 O3 R4 u5 _& Y. i/ y
' U* r7 V+ n2 s2 b5 r; F! {

/ e8 p; d+ Z1 h( g1 X! w1 T# @2 B0 _& n$ Z  k7 b( c2 ]4 ~" }
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3