Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2838 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。8 U1 `: S, ]/ C5 _! V
  1. func (s *Ethereum) StartMining(local bool) error {
    " B3 k0 }6 k3 D/ V$ m
  2.         eb, err := s.Etherbase()//用户地址
    , t) x4 @$ U4 F
  3.         if err != nil {* d2 `6 X, f/ E1 |7 m9 @
  4.                 log.Error("Cannot start mining without etherbase", "err", err), c' e! H- U4 r: u0 l1 n
  5.                 return fmt.Errorf("etherbase missing: %v", err)
    ; R0 b1 i3 e% u! @' r
  6.         }/ B3 m4 E  z' I6 K- w
  7.         if clique, ok := s.engine.(*clique.Clique); ok {' s/ d- R2 w5 Z3 A. z: w
  8.                 //如果是clique共识算法/ F% V; p& z' V4 G$ ^! R# o- C5 w# c; x
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象8 U" j, e  l: U. z  @5 O9 \0 s
  10.                 if wallet == nil || err != nil {
    4 e! H. v2 A! w! @0 @1 f" S, G3 \4 s$ p1 B
  11.                         log.Error("Etherbase account unavailable locally", "err", err)
      a8 x" Y9 c3 X0 K$ {3 S' |
  12.                         return fmt.Errorf("signer missing: %v", err). L1 W% Q2 t* d4 q& b2 N5 X
  13.                 }
    % S- [) u1 D; [
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法' o% U  p0 K" F) Q  H: g
  15.         }
    1 h  R# `- X# j2 v2 E) R& _
  16.         if local {3 m  q9 n; f) w7 m5 S2 e% L
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。6 u. q& w6 ]7 ]( {; F; R
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)' q+ H9 G5 F/ l7 ], J
  19.         }
    7 _* e0 k/ i& v2 B
  20.         go s.miner.Start(eb)
    4 T; ?# C9 I' E) M
  21.         return nil
    1 Q2 E# Y" d2 M% g% z7 z' s5 n% H
  22. }
复制代码

  I& o/ R: b+ Y# \$ L7 ?" |这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
2 _) }( H6 K# G. r4 ^/ v* GClique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:1 k; r9 e2 v8 n& i2 [0 I! |. ]0 }3 x
type Engine interface {2 v4 V! Q$ v, V& L4 v
        Author(header *types.Header) (common.Address, error)
2 D; ~- \2 L, C  O& k. O) P        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
% G7 `0 ^* |$ r8 i        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
. n2 |7 L' w  w& F) XEngine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:
9 _! _; ]8 o$ T: F$ x. ?2 \# N
  1. type Clique struct {% j: p. h$ N6 u: h6 L  b) g4 @
  2.         config *params.CliqueConfig // 共识引擎配置参数
    9 `7 G) Y. C3 N/ N5 g6 r
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点/ g2 R1 k1 |" L# L" }7 l
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组
    : r1 |' C* h( V! |* @
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿1 k' b1 u! A0 d
  6.         proposals map[common.Address]bool // 目前正在推送的提案
    : u  H9 W, w( y3 r% A1 z' R
  7.         signer common.Address // 签名者的以太坊地址8 }4 k. ^$ S% @6 m' Q; s
  8.         signFn SignerFn       // 授权哈希的签名方法
    7 [' [$ _" Y* S2 U
  9.         lock   sync.RWMutex   // 用锁来保护签名字段, L/ c. f$ [# t$ Z
  10. }
复制代码
3 W: l% I0 V% x( ], F
顺便来看下CliqueConfig共识引擎的配置参数结构体:) v, _6 D! C8 S5 x! N  C/ I' c# [
  1. type CliqueConfig struct {
    & a, _3 `0 F* f& Y
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)' a+ L- U* `) w9 P
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    : [6 M: [$ ^. Z3 {/ P0 G- e( z/ W) k
  4. }
复制代码

, y' a4 C: [5 G在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
3 e7 o5 `& S! i& N  F
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {- r- H% B+ e. Y5 a8 I/ r. @
  2.     c.lock.Lock()
    ; D/ U& w+ T4 S0 X
  3.     defer c.lock.Unlock()' R0 l" O/ c  O4 R. H& n
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
    : y0 _/ }0 A7 m7 H* ~
  5.     c.signer = signer
    ' ]3 Y# }& r5 g: I
  6.     c.signFn = signFn! y7 {& r2 z7 e( W4 }
  7. }
复制代码
- G5 l( {  ]- d1 C: p! s" I1 d
再来看Clique的Seal()函数的具体实现:8 n4 @' q% S1 O; @$ `
//通过本地签名认证创建已密封的区块8 U! Y" l. T1 X# E2 v! o5 p3 T1 N' D  D
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {
1 [& U3 F2 w7 j% k! F                                log.Info("Signed recently, must wait for others")
( n3 I/ ]9 @/ V                                0 z9 ]6 d% R# Z
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名3 _- M$ c! c# B2 w% Z3 V
signer不在snapshot的signer中不允许签名& g8 b/ G$ U" o
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名" x0 K( Z% t$ v9 c
签名存放在Extra的extraSeal的65个字节中
/ P# l5 `: D; u# L关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。
* @0 R2 w0 h* [
  1. //snap.Signers是所有的认证节点
    7 y, F, H$ ]) p2 J4 w/ j1 g
  2. for seen, recent := range snap.Recents {
    + ]* K4 ^8 X. t2 K/ z
  3.     if recent == signer {
    , z' O6 s/ ?( w2 |
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {1 i3 K0 x! X' N* B0 U- Q
  5.             log.Info("Signed recently, must wait for others")
复制代码

; _( s% @% f& d( g            
1 S8 l: I6 V( v( Q: J在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
1 @2 o# L/ a# g7 }) O3 ^关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
5 R6 E' r8 ?( g3 _* z% JdiffInTurn = big.NewInt(2)
8 I6 Y* {0 w; Y) @# M! A# E. R8 [diffNoTurn = big.NewInt(1) , Q3 X9 Y) s9 P# H4 D
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
2 H% ]6 k/ H( [// 通过给定的区块高度和签发者返回该签发者是否在轮次内) w3 B; }+ n+ b" u; D* E
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {& [; T2 \( x9 U7 R
  2.         signers, offset := s.signers(), 03 M# Y6 P1 z& N
  3.         for offset
复制代码

. h0 b3 i" o8 J! O" T3 C5 _' hSeal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:
* i, L0 I8 q. I// Snapshot对象是在给定时间点的一个认证投票的状态- U1 p; Q4 `1 E6 X! r, q9 Y
  1. type Snapshot struct {. w& B4 c8 |: n2 c% C! D! I/ a. Y
  2.     config   *params.CliqueConfig // 共识引擎配置参数1 r5 e3 P7 p- O" L. a
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。( |# C9 [6 E3 ^: o! Y1 `
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号
    ; {* B1 Z. a. `& @) n/ N
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    0 B. Q" Q  [1 a6 m* L% v- R1 V
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表4 V) Y! e) Y  p; |
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址% T' V5 Y' P9 n0 j) T3 A
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    # g& o% m) E! T3 J+ ]
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。( w( N! X: i; Y  ?+ R; Z4 c6 o/ _
  10. }
复制代码

" O, a+ l$ |4 {$ }/ n5 {7 r/ G: D快照Snapshot对象中存在投票的Votes和记票的Tally对象:* S+ K5 b. y  \9 I' H
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。
    ( a+ F& U* |7 r0 f6 P2 ?' b& l( A& h
  2. type Vote struct {
    2 [8 K; F6 U6 V- l: d; f3 k
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
    8 m  k7 I0 o- T) E/ B/ h* s
  4.     Block     uint64         `json:"block"`     // 投票区块号0 L  @4 ^, j- E9 b1 A
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权* Q0 y+ t  ]  z6 v6 A
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权
    0 }( ?# d# B5 p6 |6 H
  7. }
    & l7 V* b. |8 r- j
  8. // Tally是一个简单的用来保存当前投票分数的计分器1 x' n/ O" P+ f5 f. |
  9. type Tally struct {
    4 o* v& H4 T' |# F; v$ v
  10.     Authorize bool `json:"authorize"` // 授权true或移除false
    8 A! y7 I5 P* S0 n4 E
  11.     Votes     int  `json:"votes"`     // 该提案已获票数( p4 o5 _/ _$ W, d+ ]) _2 G; l8 R/ L
  12. }
复制代码

! H+ e1 F+ v$ l# y$ h9 H) JSnapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:$ R- d2 {0 m% {9 ^, A9 }
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {5 ?4 N& h9 }, n; Y
  2.         //使用Database接口的Get方法通过Key来查询缓存内容$ X6 ?1 ?/ i0 _8 d5 H
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    ! ?6 b: ~1 q5 j9 k) n6 A
  4.         if err != nil {6 m8 i5 ^/ x: a7 u! Q
  5.                 return nil, err5 M3 g9 |, b8 F" ~( G
  6.         }
    / A( Q- d9 _& D& M/ c4 O
  7.         snap := new(Snapshot)
    - c! l3 `( m6 b0 V* G, h% j
  8.         if err := json.Unmarshal(blob, snap); err != nil {9 C. m! I6 B1 M! L9 J* l) T
  9.                 return nil, err) a0 t9 X! u2 K, v7 h
  10.         }
    6 V* r$ b4 P/ |1 F- n' p; W
  11.         snap.config = config- M; |% d) L) @0 U- P9 [$ x
  12.         snap.sigcache = sigcache( m- S. W- O4 z+ `" h
  13.         return snap, nil" N  X4 s, m/ ]  x5 u! p7 J
  14. }
复制代码
& w+ a9 J( o1 q3 F; e6 u
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:  ]3 g7 H0 c  f6 [  l& {7 E
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {2 t5 [9 A) w: v& m+ d+ O
  2.         //组装一个Snapshot对象
    . [- e6 \. |1 R! T
  3.         snap := &Snapshot{
    1 z2 [+ A8 l' a! E0 ~$ t
  4.                 config:   config,
    ( ?  Y$ H4 W9 a) ?5 A  m
  5.                 sigcache: sigcache,$ n  A( n- S. H3 e! t1 ]! \
  6.                 Number:   number,; ]4 @, v: N3 ~$ O' R# }
  7.                 Hash:     hash,- Y6 V9 F( Y& Y/ F% |
  8.                 Signers:  make(map[common.Address]struct{}),
    " E0 }: ]; D( g0 F! U# k1 @
  9.                 Recents:  make(map[uint64]common.Address),
    & H7 B& ^" P& E: C8 |+ A
  10.                 Tally:    make(map[common.Address]Tally),3 \. s! g9 w' l8 ^7 ]- g
  11.         }
    9 y  q) \4 M. X+ b
  12.         for _, signer := range signers {
    ) }! O; F0 J, c1 K" |6 k. ?
  13.                 snap.Signers[signer] = struct{}{}9 t* y8 U& k/ s5 s
  14.         }
    # R. S) J' ?4 o1 ]! d
  15.         return snap
    7 d- w1 d* l4 p- w0 v2 Q
  16. }
复制代码

& A" l+ Z+ `0 J4 I! K; E继续看下snapshot函数的具体实现:  C9 z* @  I1 B9 l2 G6 X
  1. // 快照会在给定的时间点检索授权快照
    0 e; j6 m4 K7 X9 C/ m
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
    8 Y4 z# m( }% N$ P# n. i
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints
      C* \' F' m+ g1 t! J/ k8 `
  4.         var (. n! F" m: X* P4 U% J
  5.                 headers []*types.Header        //区块头
    0 C/ U6 h; f3 F8 k
  6.                 snap    *Snapshot        //快照对象
    3 Q! k8 u. v3 e, m( s* K
  7.         )1 P6 E+ v0 O. w: R  s% k. w3 B/ S
  8.         for snap == nil {; D" e( t, E% Y  H
  9.                 // 如果在内存中找到快照时,快照对象从内存中取
    3 y! y) H0 |  Y  K& v
  10.                 if s, ok := c.recents.Get(hash); ok {
    0 |. g7 z6 g" s3 i
  11.                         snap = s.(*Snapshot)& L$ x* Z9 E" S# ^) ~
  12.                         break7 I% j- Q" _1 w; ^" n
  13.                 }
    ; O0 z' |! ?+ A. @2 A* C
  14.                 // 如果在磁盘检查点找到快照时
    ; w, u5 q, O1 h( T8 T' p1 Q2 X* F# `
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号( S' y0 X: X9 N
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {* G" @# Y: p) T! @
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
    : C' S% l/ V4 ~
  18.                                 snap = s
    3 G+ S6 i; Y3 g4 ^6 q8 x
  19.                                 break2 t8 k7 C! N7 j- F+ y
  20.                         }
    2 g% C) l' k4 h. {
  21.                 }9 n, _& L9 r0 i; Y
  22.                 // 如果在创世块,则新建一个快照0 u1 I+ |/ s# p9 w* M& z% T
  23.                 if number == 0 {/ K) k& K4 }2 ~4 @# H/ r+ M1 {
  24.                         genesis := chain.GetHeaderByNumber(0)
    . N1 V. o& S% f4 T! H
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {  g0 H$ ~7 y- K
  26.                                 return nil, err
    5 l. p# O8 s0 L9 g2 D
  27.                         }
    - t! y% F  s2 d$ I8 \
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength); S5 K/ Q; P% Y4 d) z
  29.                         for i := 0; i  0 {
    3 B1 U* p, O, _% O! ~7 {3 y
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)( [; }- Z$ f( M) {! J1 S
  31.                         header = parents[len(parents)-1]" x" j- l  Z2 h$ ~& n
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {
    / P) Y5 @1 `1 I( `& z1 b
  33.                                 return nil, consensus.ErrUnknownAncestor# H. ?% V( Q0 g7 G" _1 {% Z
  34.                         }) J! m3 \5 D' E0 d+ m! S8 C
  35.                         parents = parents[:len(parents)-1]
    / Y; P2 ^& j% ?; B4 z  R; D" Z
  36.                 } else {
    . B- @0 O1 ]$ {6 C/ Z' g
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    # d! Q+ H  k+ r  x. s
  38.                         header = chain.GetHeader(hash, number). e8 T1 w0 D& U# G/ d3 X2 s
  39.                         if header == nil {: C4 \# _6 L% }
  40.                                 return nil, consensus.ErrUnknownAncestor/ c2 h; K' Z; i+ M4 o- ^; J
  41.                         }
    . s( @% s: u, W7 g5 m
  42.                 }
    3 H6 D5 Z- V, E$ J; w- P  |
  43.                 headers = append(headers, header)5 ?' d0 e1 m0 ]  [0 v
  44.                 number, hash = number-1, header.ParentHash
    # P. f" U% a: ?, Q# t) _) D3 e
  45.         }
    $ [' l& }4 d) W+ U' V8 P+ M3 \
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面
    5 a; G& u4 m& b
  47.         for i := 0; i  0 {# h) j5 {) C$ c: n) \: B; }! g& Q
  48.                 if err = snap.store(c.db); err != nil {0 J" ~- d: |& g% [! B3 S- F2 e7 P
  49.                         return nil, err6 x+ M3 Y. _' d: j1 Z- G
  50.                 }; b2 P* L! H) G0 y& f0 n7 ~
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)3 {' O: D- L0 y
  52.         }
    0 _! k1 o4 N) O0 }1 ~2 L( O$ i
  53.         return snap, err
    ) S7 @6 ]% x4 P3 m3 e
  54. }
复制代码

4 U) P7 m* A2 N. b# C) w3 U" v在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?& v2 Y$ n0 w/ u* t# W: M8 e
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
    ! d: s9 o4 k' K
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {3 V: B5 M% ^6 y+ [+ w# c+ e
  3.           //可以传空区块头( ]7 E: |) G' t8 o( s  K! d
  4.     if len(headers) == 0 {
      o, J* B: f( I+ E
  5.         return s, nil
    8 W: H; C6 P: d; K' T  H
  6.     }9 e. B% C( b' B4 i6 C( c: ?& s* \
  7.           //完整性检查区块头可用性
    % _2 o! Q6 `6 X0 B! k" b  _" }
  8.     for i := 0; i = limit {% z1 u# R. b+ n5 {% P
  9.             delete(snap.Recents, number-limit)
    * }6 w' C: @; v! W
  10.         }
    " Z7 v* f! y; g2 i& k8 ]
  11.         // 从区块头中解密出来签名者地址
    ) A7 G! z" J/ t
  12.         signer, err := ecrecover(header, s.sigcache)+ {; n' q3 G$ c8 o& v5 p( p0 \
  13.         if err != nil {
    ! K" u8 Z( i( Q6 i
  14.             return nil, err" K2 J! p# D3 j  v7 z
  15.         }0 m, p: v' L$ E# U
  16.         if _, ok := snap.Signers[signer]; !ok {! p* N) N' Z+ H' N
  17.             return nil, errUnauthorized% S7 _! H8 g5 a% s8 C
  18.         }8 A8 J" p  s, [# e, O
  19.         for _, recent := range snap.Recents {
    # ^. p$ s6 H; p& q+ z
  20.             if recent == signer {+ q$ A# [- t. Y5 [8 m0 |! d2 \3 K
  21.                 return nil, errUnauthorized% q7 \; g2 Q' s6 P/ i
  22.             }
    # I/ l% Z! l2 i2 B! G; t& ^
  23.         }
    ! L! o/ b) h3 A6 u( ~
  24.         snap.Recents[number] = signer6 K1 U  k' r# G' g4 T7 ]
  25.         // 区块头认证,不管该签名者之前的任何投票
    - D; Z0 c  q+ N4 g8 @9 b
  26.         for i, vote := range snap.Votes {7 ~" b: A2 Q3 s' O$ \8 l( y2 t6 N
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {
    5 z. W. t5 _* l
  28.                 // 从缓存计数器中移除该投票. X* {2 B9 B9 r$ u8 _
  29.                 snap.uncast(vote.Address, vote.Authorize)- N, l" L# O* M* ]" y
  30.                 // 从按时间排序的列表中移除投票
    , E2 I7 h9 f! w7 R: c
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    . `) x( c. l3 P7 S
  32.                 break // 只允许一票
    ) k1 X/ e5 y# u: k- L7 a0 @- w& c. ]6 T
  33.             }
    6 ^2 c4 h4 [' b8 [1 U# I" U' i
  34.         }4 l! |' V: Z6 }( A0 N# B9 d5 \
  35.         // 从签名者中计数新的投票
    ( H8 v( h6 h: `* ]5 K! G' W
  36.         var authorize bool+ W/ j% B& ^; D  X& {& l* G0 P
  37.         switch {
    1 v% v4 e4 J: p0 k( C. W! Z
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):- E2 y3 s) w& j0 T) y
  39.             authorize = true3 E& g+ `+ q& u! g
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):* k% x' z- a% o/ k
  41.             authorize = false
      i* P9 b; u7 \5 s0 u; z, ^
  42.         default:
    ( }2 w1 v/ L' `* W
  43.             return nil, errInvalidVote; _: N% K- ^# u: i
  44.         }
    2 }) p  d. r) z4 y% G4 @" I/ Y) m+ x
  45.         if snap.cast(header.Coinbase, authorize) {( F* t- Y+ N  j4 ~
  46.             snap.Votes = append(snap.Votes, &Vote{
    ' e; `8 W* J7 S4 ], O" W
  47.                 Signer:    signer,
    ; a8 C# M- h; c; o
  48.                 Block:     number,/ M" d' \/ \' ~, k7 w6 S: n' w4 ?
  49.                 Address:   header.Coinbase,6 u+ j  n: n  L" Q8 t# J# r0 k% V$ a
  50.                 Authorize: authorize,. U! R2 R  `, P+ k1 }  B3 w
  51.             })3 ~' A3 \* V* _3 u/ M
  52.         }- `! y) J& G- n% ]4 [2 h1 a1 j1 u  q
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
    " H9 Z7 c  F7 W% ^
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
    4 F  g4 T, ^3 c* p1 [. \
  55.             if tally.Authorize {, U4 j  A8 M' w3 M
  56.                 snap.Signers[header.Coinbase] = struct{}{}
    - J  ~; u: q9 J; v: U$ P
  57.             } else {
    & S$ ]) O, v* w% S+ O8 c+ u- g
  58.                 delete(snap.Signers, header.Coinbase)" D2 _0 ~% o9 R2 {
  59.                                   // 签名者列表缩减,删除最近剩余的缓存2 @3 t: F2 G0 q  x: v: l0 t
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {  R1 t) u( {/ V; |4 K$ t
  61.                     delete(snap.Recents, number-limit)
    $ L' y, C5 h1 f  |1 c3 ?
  62.                 }
    1 }% i0 j& X; z8 g: T; {4 g
  63.                 for i := 0; i
复制代码
& `+ p0 S1 s& I. W4 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快照结构已经更新到哪个区块了。* n% O# _) r- E. R8 Z' k
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。! T4 \* m4 K% M  f: x  P
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照/ _+ v, w& o1 y) E0 T# f
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {+ h! g8 D' y. |2 k" v  p
  3.         // 不支持校检创世块7 p& V$ w' s' ~% V# ?' Y$ A
  4.         number := header.Number.Uint64()" Z& r/ m4 |5 O' f6 v
  5.         if number == 0 {) ^( F  O; e$ z) ^. x
  6.                 return errUnknownBlock; K: {' S, Z' Z
  7.         }& Z- l& _  N+ Q* i, a: W+ i
  8.         // 检索出所需的区块对象来校检去开头和将其缓存
    ; j. d1 z9 D$ W& w
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    , T5 p5 [8 @. N5 o% E7 r6 H
  10.         if err != nil {
    , \; X# b  a% D: z
  11.                 return err6 j+ @, ?9 Z- Z3 r3 G! w
  12.         }
    - T0 k! r( {; ]4 I7 X
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址4 N" E5 F0 o2 D
  14.         signer, err := ecrecover(header, c.signatures)
    " [" w( E4 r1 ]) F1 [% u
  15.         if err != nil {' ^3 {) D/ E/ }4 j# ^' M6 T4 W( _
  16.                 return err$ i4 z: Y4 j' [/ Q
  17.         }4 U9 S. j* Z/ N" m* Q1 H6 V8 _
  18.         if _, ok := snap.Signers[signer]; !ok {: d) k1 [8 ^' A2 d7 P# D
  19.                 return errUnauthorized
    . _9 R6 j8 k6 f. W2 v+ C. ]! w
  20.         }
    ' f3 s% q0 U5 e
  21.         for seen, recent := range snap.Recents {# N, Q! ]  a. O7 w: l
  22.                 if recent == signer {: g1 ~, g6 M. p
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等
    7 }% I, l0 R" Y
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    2 M- [' L9 `# M( i4 T
  25.                                 return errUnauthorized
    - Q" c0 j# m1 e2 T, b
  26.                         }9 g5 U  G1 P9 Q
  27.                 }
    ( C0 P: N. E* y. C
  28.         }
    1 p. E; G0 _  W* x* d6 L: ^
  29.         // 设置区块难度,参见上面的区块难度部分
    % H3 N5 e) D% O* Z; H
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)
    $ D2 X2 j5 {5 z- n: x4 x
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
    ( G, ]- {/ Z0 a- q3 F
  32.                 return errInvalidDifficulty
    7 d7 O( h. T0 W  O$ O
  33.         }1 K) ^4 j. I( v2 }$ ?0 w  Z8 ^
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
    0 |- ?. ^# I) }
  35.                 return errInvalidDifficulty! ~8 K) X6 U' W$ f3 m: f
  36.         }
    0 L& j: }. i9 w7 m. Z+ v) Q
  37.         return nil
    1 h/ }5 j: x! r# G1 S; w8 V: h
  38. }
复制代码
# O% d: ?6 g, J/ @! ]2 T
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
: s/ j) h* o+ C, F+ t' uClique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:
$ S6 ^" b* `) \& m委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中  W  s: O* ^- A  |9 B2 m
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
    7 e. j1 D8 W- S) b  R. x# B
  2. func (api *API) Propose(address common.Address, auth bool) {
    ! _- {2 ^: \/ l2 w4 P4 Q
  3.     api.clique.lock.Lock()
    , m+ M! j- K& `( P
  4.     defer api.clique.lock.Unlock()
    - ?% a6 U1 i/ R: ~% d" B
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    & f7 y: w8 c6 P/ t
  6. }
复制代码
# m: ~/ g" q! |: X: C7 \
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;5 o; I$ o) U: C
  1. //Clique.Prepare" F/ t/ n# S  m/ v. R8 e# v1 I( ?, Y/ O
  2.                 // 抓取所有有意义投票的提案
    2 o' I- H6 E; }: [( _; i" C2 \
  3.                 addresses := make([]common.Address, 0, len(c.proposals))
    8 w4 r% j8 Y- |/ k! f( ^
  4.                 for address, authorize := range c.proposals {
    5 \( q( z' p* @% N# e6 u/ X! A. h  s
  5.                         if snap.validVote(address, authorize) {
    & l7 F) V6 g; X5 l1 R! O' @4 G+ x
  6.                                 addresses = append(addresses, address)
    9 j# ^/ J  b4 n! E* z0 o
  7.                         }7 C& u1 N0 \( `& ~& @
  8.                 }8 S% j9 ~1 q, j0 q5 `1 {
  9.                 // If there's pending proposals, cast a vote on them
    & B! J* f2 W5 w4 b. M
  10.                 if len(addresses) > 0 {0 [/ [6 z4 _! @! s! o& O+ b
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。! t, ^0 O. Y' f% X
  12.                         // 通过提案内容来组装区块头的随机数字段。( [, \- o. J  Z7 z- F4 L
  13.                         if c.proposals[header.Coinbase] {$ n) m! B( U) o! P7 H' B/ l
  14.                                 copy(header.Nonce[:], nonceAuthVote)' X# L' T4 S9 ~  U( y
  15.                         } else {
    9 }$ w2 w+ I# F1 v* e% T2 W  a
  16.                                 copy(header.Nonce[:], nonceDropVote)- U% c3 T1 f0 r( H
  17.                         }1 z: M" c  k4 E8 Q: s, i
  18.                 }
复制代码
: I. F! g/ R+ X1 u$ T2 V2 N
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare
3 S4 ]9 K# ^) T2 v7 G
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {
    . g7 G/ G* H  H, \7 @5 p
  2.                 log.Error("Failed to prepare header for mining", "err", err)5 @2 j  }/ U3 d6 S
  3.                 return
    7 [( H* n& r9 [" p/ w; N, X
  4.         }
复制代码

# A) z: u" g+ w其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法9 l, {1 _8 E, Y
$ `0 w" ~4 Q  w7 i' x; i
# {4 ]1 N9 n8 Y. s+ i
, `2 G! H# W7 z. r6 d& V/ n+ V0 t$ ~
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3