Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2481 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。
2 P* O3 E6 F  r5 N4 Q
  1. func (s *Ethereum) StartMining(local bool) error {' F7 T  F6 S) u( u4 w) `% W8 i
  2.         eb, err := s.Etherbase()//用户地址
    % p* l' q' X0 f: q) Z
  3.         if err != nil {
    0 ~1 O2 E  [- V4 k/ _# {
  4.                 log.Error("Cannot start mining without etherbase", "err", err)2 h* B. o. s: e1 ?
  5.                 return fmt.Errorf("etherbase missing: %v", err)
    / ~5 X7 T& n* M% z
  6.         }) e9 P6 U' F$ i0 m2 X
  7.         if clique, ok := s.engine.(*clique.Clique); ok {
    & |1 s! A, l! f* f) J! U' T6 a) J
  8.                 //如果是clique共识算法& [7 v$ Q/ ^- B$ X: a( s! \4 s/ Z
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象, Q# W( w/ C; l% m! R) F
  10.                 if wallet == nil || err != nil {; E1 E! M6 c2 e# G
  11.                         log.Error("Etherbase account unavailable locally", "err", err); E, {- }0 B/ e+ ~+ j
  12.                         return fmt.Errorf("signer missing: %v", err)0 l, e* g4 p; C& e
  13.                 }
    9 B4 Q* R, X; I: L0 E4 U
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法; t  y" E+ N( T, R, }2 M
  15.         }
    7 @9 X1 v! k0 I' G0 u7 }
  16.         if local {3 `2 _# u- Y9 \& e
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
    4 H- P- }  ^: g
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)' w: y! p: I- t' F
  19.         }
    7 A/ z' t+ H. i
  20.         go s.miner.Start(eb)
    & R; D4 ~) s' `, V( l5 o/ j* y) |
  21.         return nil
    ) _. z( v/ \' o/ ?/ i' T
  22. }
复制代码
3 m) U5 D, I( ]+ G; p. X
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。/ R! {% `: T* K+ O0 M
Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
5 E2 b/ I: J+ C  T9 s% F, M6 d! Ytype Engine interface {
4 J5 ~3 O- P5 v( r        Author(header *types.Header) (common.Address, error)
9 }  v7 v0 M$ {* w  b0 R        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error/ E- R6 G5 y! g8 [5 t/ ]% L; {4 J
        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
/ Y$ Y/ ]- q2 ?/ }Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:# ^) y" T2 C6 H" a7 `
  1. type Clique struct {
    5 g/ {# ~5 z/ T8 {! u0 a
  2.         config *params.CliqueConfig // 共识引擎配置参数
    : e# L+ P5 L/ e, n7 e; e( E: ]; j0 x
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点
    - V. A* X# Q4 }& _5 l4 [. t/ n
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组
    4 h. M# B9 _( h1 W! K
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿# t+ Q! A. L+ `3 u) ^& F
  6.         proposals map[common.Address]bool // 目前正在推送的提案) P* j. r, H; I* _3 P& N1 W9 c
  7.         signer common.Address // 签名者的以太坊地址
      w2 n7 n! @6 ^9 Z$ z% f* X  U9 r
  8.         signFn SignerFn       // 授权哈希的签名方法
    # W# g. A1 z' {+ N, f
  9.         lock   sync.RWMutex   // 用锁来保护签名字段
    8 H7 m( |  r9 j
  10. }
复制代码
; Z& [0 K0 Q1 I8 x7 y
顺便来看下CliqueConfig共识引擎的配置参数结构体:" r/ U6 _4 D# b5 D! U
  1. type CliqueConfig struct {
    & E* k) e7 O& M) j  B
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)1 F4 `6 |) Z0 r. G: Y, e- T
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    ! [; q: v  j: Z3 N7 i9 M/ T
  4. }
复制代码
8 S' s2 r- e# d$ N, E, R
在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
( R0 a1 s6 s2 t) x8 c5 u1 v
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
    $ R. u$ Q0 V  Y7 V
  2.     c.lock.Lock()! J# E4 [! O" m, _4 t
  3.     defer c.lock.Unlock()
    / Z5 b2 y3 w4 m% H, R& N
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块: M0 D" `$ g2 q& V) v2 o9 B
  5.     c.signer = signer
    4 E9 K" Y! l; \* E
  6.     c.signFn = signFn
    & Q" C) U. _% E; Y' N) K" g* H
  7. }
复制代码

: R5 S) f+ x9 m$ v9 p% w再来看Clique的Seal()函数的具体实现:
0 q7 w# ~6 U; c//通过本地签名认证创建已密封的区块
/ y8 K$ q5 {3 i: C9 k) N0 Zfunc (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {
6 {" J9 `4 M- R                                log.Info("Signed recently, must wait for others")
4 c- P6 `' `5 Y  s* S- I' N. b                                - m3 S2 k' O' l' Y( A& f# c
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名
* K- R4 [1 W9 B. `3 }, Fsigner不在snapshot的signer中不允许签名
1 x( D& V; a& t: qsigner不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
7 q' z2 M( |; o签名存放在Extra的extraSeal的65个字节中. `  y7 L" i8 Y+ {
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。, _7 T3 R6 ^  H" a9 _
  1. //snap.Signers是所有的认证节点
    ' J) i, L0 h6 g( B: q+ ~
  2. for seen, recent := range snap.Recents {; f5 S& C( _* z( W7 x. w
  3.     if recent == signer {' s/ b" {+ \+ w, i8 B5 E( w" O$ x
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {
    5 P2 r. c3 ~* q( s% c% F
  5.             log.Info("Signed recently, must wait for others")
复制代码

1 a' D8 u9 P2 ?8 S+ N            , w& N  E# |$ ^6 W
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。6 A- K% N& q' D" i! e1 a: A
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。5 u& V6 V+ y% F, Q) {, |- U. n
diffInTurn = big.NewInt(2) 7 ?5 X7 t0 P  G
diffNoTurn = big.NewInt(1)
- o0 y. g' b/ ?' T0 V当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:4 ?( u5 X. l0 Z! E. [
// 通过给定的区块高度和签发者返回该签发者是否在轮次内) S; a0 F- u9 b: a
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {, y: A/ A; S7 R2 K  ^) r
  2.         signers, offset := s.signers(), 0
    9 J" {* Z; ^) [  {7 {
  3.         for offset
复制代码

1 S& [' \2 b$ ]Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:
# {$ @5 Z2 Y: n5 r7 C. [// Snapshot对象是在给定时间点的一个认证投票的状态
- x7 S! z8 \) l: z
  1. type Snapshot struct {
    2 X0 f9 N* O* w% [$ M/ v! M
  2.     config   *params.CliqueConfig // 共识引擎配置参数& A5 p0 y* h1 u4 {
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。# [6 T$ P' y' }" k2 x$ T, f9 B
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号- n# y7 |& S+ ~8 S- O
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希3 R, k8 z" F# n7 {4 ]& v
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表6 J5 g7 `+ ~& Y
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址
    + g. ^, S3 r( I+ s, Y( U5 ~8 v
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    ( D  f3 d, X  Y: G3 |2 \
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。
    3 b2 V7 v; O; k# _7 j# d
  10. }
复制代码

- W4 B/ G/ }: k1 s快照Snapshot对象中存在投票的Votes和记票的Tally对象:
$ F( i- t0 a+ I: @  ~1 N
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。
    , K: `4 z$ o/ i7 G: g7 B+ k4 s9 W3 L- V
  2. type Vote struct {
    % w4 o7 t9 a2 \" c- ?& e
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)1 w, a2 F+ v1 R% F* e* ~
  4.     Block     uint64         `json:"block"`     // 投票区块号0 X1 z6 A& e( v5 H0 e! K  N) a
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权
    8 s4 M, i. Y; U# P$ Y2 X' ^+ Y0 z2 t
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权2 x+ F& U8 q- |6 o! Q
  7. }" S# S# @" x  w+ U
  8. // Tally是一个简单的用来保存当前投票分数的计分器
    + r) l0 G- F; [* a8 H
  9. type Tally struct {
    1 I7 |# }1 Y- }# P# `
  10.     Authorize bool `json:"authorize"` // 授权true或移除false8 \9 }0 T/ N( g+ t$ |. ^
  11.     Votes     int  `json:"votes"`     // 该提案已获票数; {& E. V' M. T+ b: {
  12. }
复制代码

) z/ ^. N; Z" X0 q& i. W6 QSnapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:
2 `( `3 o0 m7 Y% W
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {% T+ Q. p" t% n7 _" \& b
  2.         //使用Database接口的Get方法通过Key来查询缓存内容0 W' j* k/ c  ?: x0 V0 y
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    1 _9 S& f2 n, _! O6 ~2 `
  4.         if err != nil {: O7 Q5 G; V+ Q1 i4 c2 [$ x+ k
  5.                 return nil, err
    # b8 H( h! a9 h. t: b8 h+ a0 S
  6.         }
    2 z$ c  N  i$ y1 g3 f/ K0 b
  7.         snap := new(Snapshot)
    - u/ M$ b5 V1 [
  8.         if err := json.Unmarshal(blob, snap); err != nil {$ c6 A% J8 ~# i& G* P% A$ Z# A' _: N
  9.                 return nil, err( a* o' X; d( p4 R7 H& i9 C5 Y
  10.         }# X/ o) ^( p, M5 _. U: y
  11.         snap.config = config
    . d9 h8 V1 e9 `: u
  12.         snap.sigcache = sigcache
    ' g9 s5 n8 ~, l( v
  13.         return snap, nil
    ( B! X$ A6 Z3 G' v: L% P; V/ D+ h6 s
  14. }
复制代码

' T6 l) {- J' MnewSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
# W& Z% n4 A+ \2 b, e
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
      L) t- M  K" M% M# u, ?
  2.         //组装一个Snapshot对象1 p( e% K) d& M& s4 w; U  f' K
  3.         snap := &Snapshot{2 g9 _" L  E/ d6 s7 y
  4.                 config:   config,
    , r1 z' h, U  [1 ?. G8 o
  5.                 sigcache: sigcache,  ?$ o- V+ i7 ]" T
  6.                 Number:   number,$ x8 R+ ]$ g3 A
  7.                 Hash:     hash,
    " O; h4 `; F( M' {+ i
  8.                 Signers:  make(map[common.Address]struct{}),2 d3 |3 n( F2 q6 m
  9.                 Recents:  make(map[uint64]common.Address),; J  z7 R4 O6 _) J+ q% a( x
  10.                 Tally:    make(map[common.Address]Tally),0 S, R8 T$ k9 I
  11.         }1 w( R* e3 a; z6 b% D" s2 S0 T0 \
  12.         for _, signer := range signers {
    $ o2 }- S; i: Y9 H  ?
  13.                 snap.Signers[signer] = struct{}{}
    ' w4 b2 Z$ `0 y* U- a3 H$ P
  14.         }0 N. p  d7 x6 D$ k5 J9 p; V+ p
  15.         return snap' n8 `  a6 ^+ T
  16. }
复制代码
0 Q( \2 _( R0 }# o) g" E
继续看下snapshot函数的具体实现:
3 T7 Q6 k/ U9 d6 j) x1 Z
  1. // 快照会在给定的时间点检索授权快照
    ( f" w# ~0 I4 Q% \( m4 r. B5 J
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
    0 ^) H- @( ^6 u8 b* Z2 ^" E) Q
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints
    ; q# Z* e, ~3 R) C. ?) E+ ^7 u5 O
  4.         var (0 T5 d) s. X' q. F+ V- L
  5.                 headers []*types.Header        //区块头! N$ l& n* j0 P
  6.                 snap    *Snapshot        //快照对象( y* @2 @4 `6 ~! d; z7 ]
  7.         ), d* g, [! {/ v+ g+ a# b# z
  8.         for snap == nil {
    - }7 o: K% z2 m2 Q
  9.                 // 如果在内存中找到快照时,快照对象从内存中取
    ' Q2 C7 n& W' k+ c9 w. L7 y9 Z
  10.                 if s, ok := c.recents.Get(hash); ok {% u# q( m/ f' t! O& y
  11.                         snap = s.(*Snapshot)8 _" y+ s. l* |
  12.                         break3 `6 E+ Y! _7 ~
  13.                 }2 w, H& U/ `  f" o
  14.                 // 如果在磁盘检查点找到快照时
      Q4 [5 q' L, p  E$ X9 w% }3 y
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
    : q1 x# C$ Y; f# z% F* @5 J
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
    - h/ L3 o2 j" m. Z" F
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)% ~1 _! B1 F9 _4 _
  18.                                 snap = s
    6 \) b% F2 Q( b5 {' q9 u
  19.                                 break
    ! B( e' U/ Q' l' z) w& `
  20.                         }
    + O& ^: C  D2 d; o' g- h
  21.                 }9 M; U' Q1 k9 m  q6 G2 ]
  22.                 // 如果在创世块,则新建一个快照# M" S  J. K/ O# M- D/ y+ Y3 N7 c4 w
  23.                 if number == 0 {
    ) j% _  d3 `6 L$ e4 J! t2 d
  24.                         genesis := chain.GetHeaderByNumber(0)/ ~& O1 |" _* ?  x" ?
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    6 {/ z: E0 t$ f, m! z# q
  26.                                 return nil, err
    0 }9 [* X) B# k5 U, R
  27.                         }/ S; C5 @) Z) v
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)1 ]( H$ X3 n5 F0 z. X* `- d
  29.                         for i := 0; i  0 {2 c, j: M9 M& s; ~
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)8 ]1 i3 K3 ?+ {7 `8 m, G
  31.                         header = parents[len(parents)-1]0 w; m) W% b' l0 p9 W6 R9 i
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {
    % P: O9 b7 s7 t  O6 K9 A
  33.                                 return nil, consensus.ErrUnknownAncestor. u# l5 A9 O& p- L
  34.                         }
    , j  U& u5 F. v! W
  35.                         parents = parents[:len(parents)-1]# E  j! D3 x" X7 Y, {+ F1 K# b- A$ V9 M
  36.                 } else {
    0 K" T  k+ F& E* R3 B
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    + y" [! \4 c. I: W1 ^- `
  38.                         header = chain.GetHeader(hash, number)
    # F- I1 ~( B8 ?; k  y1 f
  39.                         if header == nil {
    6 r( Q. Q& u) C% t1 i# {
  40.                                 return nil, consensus.ErrUnknownAncestor6 R+ D/ F; F2 x& J2 n
  41.                         }6 _% O) E- G* x% x5 e2 W
  42.                 }! M9 \$ ]) }' `
  43.                 headers = append(headers, header)7 |. K' V" A. X2 S+ v. g1 P
  44.                 number, hash = number-1, header.ParentHash
    ' @, U! c% p3 ^$ _0 g+ F5 P/ O
  45.         }
    ! ~; r4 B) n% I% k( H
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面
    ' L( N& c5 d- U9 c' |% D
  47.         for i := 0; i  0 {# J, R5 T/ C, Z2 l) |
  48.                 if err = snap.store(c.db); err != nil {
    ) v; |& w- n6 _$ ^( B9 d/ A. {
  49.                         return nil, err0 Z1 @' l& P4 n8 n3 q
  50.                 }4 u2 W7 d- K) y4 ~( n0 H
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
    + Y6 @' u3 ?& O. M  F- L0 H5 z$ A8 h& b
  52.         }
    # M( d& o- i4 W' C, t
  53.         return snap, err+ Q  |% ~5 p9 y( R/ J
  54. }
复制代码
! a) S- t8 h6 _5 N0 T9 d7 _
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?6 M# N1 V% I$ @4 w
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。+ f; `$ Z7 g  O  N# t
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {6 Y. p! R# J& c1 g+ H  u4 l
  3.           //可以传空区块头
    . N- I! s9 f4 Q8 w
  4.     if len(headers) == 0 {3 R& G0 ]1 w, c& T! {6 s
  5.         return s, nil
    $ S2 F, w. |! q$ `% f# Z- F
  6.     }0 g* T) r$ [6 `6 p* i5 l/ V
  7.           //完整性检查区块头可用性, ~& O& J' e* S# P; _9 f
  8.     for i := 0; i = limit {
    7 V4 n0 W& L  ~! L; r3 U
  9.             delete(snap.Recents, number-limit)7 c1 ]6 U& J: c! {# u; S; v
  10.         }
    / b& A+ T9 ^/ l8 W- M, p& A" t
  11.         // 从区块头中解密出来签名者地址
    " d9 @7 |) E+ i* P; C
  12.         signer, err := ecrecover(header, s.sigcache)
    # `0 m( Z5 ?6 W' A4 }
  13.         if err != nil {6 d+ C' Q9 d: ?0 a( ]2 R* ~2 q# g
  14.             return nil, err
    4 @/ u. n/ _1 k* M  |! u
  15.         }
    , K4 t# V" a, |  I$ v2 P
  16.         if _, ok := snap.Signers[signer]; !ok {) ~6 j+ N" _5 t* O
  17.             return nil, errUnauthorized
    8 W$ Q2 K$ J: I
  18.         }
    * d9 L3 o2 U9 ]1 x
  19.         for _, recent := range snap.Recents {  Q4 E' @! ^3 r/ c% }7 }: |
  20.             if recent == signer {3 j' h! f& S7 K/ x* `
  21.                 return nil, errUnauthorized/ F: R1 Y* z9 g
  22.             }
    % e% b0 z6 m) v
  23.         }: W. ~" t. U' M) e
  24.         snap.Recents[number] = signer8 M/ e- W. C6 Y0 R5 m5 n9 S, t$ D
  25.         // 区块头认证,不管该签名者之前的任何投票0 G! m. @6 \" e% ~0 c' }+ C! V
  26.         for i, vote := range snap.Votes {
    2 ?; K$ R" S( D* [. Q$ g
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {
    ! X; k- R0 Y  R' p  K* R/ T  X+ S
  28.                 // 从缓存计数器中移除该投票
    6 d/ R8 k! j) Y  d' W
  29.                 snap.uncast(vote.Address, vote.Authorize)
    . o% r. q9 P% |% r+ J
  30.                 // 从按时间排序的列表中移除投票
    0 [+ f3 ~$ d" g/ H/ K
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    + W5 R$ c! F7 e1 f, h3 B7 a  R7 X
  32.                 break // 只允许一票
    * V# p+ o9 v' ^
  33.             }
    8 v( `$ _3 Y% f
  34.         }+ R. ^8 [  G/ K$ ]* ^& G8 \
  35.         // 从签名者中计数新的投票; V9 n" N) ^# r
  36.         var authorize bool
    ; |' I7 S; l1 @9 b6 O0 a5 j7 v
  37.         switch {
      y, k  K5 O$ q9 g8 N- {
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):3 D5 ^! S2 H' O# m1 J0 K4 a
  39.             authorize = true, w  D) L) _+ _0 A1 Y3 K9 F
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):
    * I2 |: d' Z. L
  41.             authorize = false- X$ p+ n7 H/ P' `4 D
  42.         default:
    4 `' C3 e  n" a
  43.             return nil, errInvalidVote
    . }" I5 y9 g" d
  44.         }
    & \& Y4 e) f6 m* k" ?' _
  45.         if snap.cast(header.Coinbase, authorize) {
    4 X9 p, i. ]" n- F- C$ v
  46.             snap.Votes = append(snap.Votes, &Vote{
    ( L& S: c) \8 j
  47.                 Signer:    signer,$ [: W8 r5 t% C. I" S! g
  48.                 Block:     number,
    + l* B: m) _0 h% _
  49.                 Address:   header.Coinbase,# y9 f1 T# d) L' f- M
  50.                 Authorize: authorize,, M0 T. t3 i# o  Y
  51.             })/ k# y) \1 x; c5 o' u- m
  52.         }/ p9 t4 K* {4 T
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表: @5 f7 U+ ^3 ?% }
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {. N8 c, |  L, v0 f- ]
  55.             if tally.Authorize {- h1 m# F1 ^# I
  56.                 snap.Signers[header.Coinbase] = struct{}{}
    ! }8 L+ h$ ?( s, D# E# L
  57.             } else {9 V! |4 o( F  e; ^
  58.                 delete(snap.Signers, header.Coinbase)
    6 d# N& q# D! n  i9 `8 k  m
  59.                                   // 签名者列表缩减,删除最近剩余的缓存" b" R3 j. F/ j$ ~  @5 e7 U! }3 o
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
    9 [' W! s4 W0 K- {( i# Q. M
  61.                     delete(snap.Recents, number-limit)
    8 w; z. }  ?8 G7 z* m1 {2 y2 \
  62.                 }& R$ j7 S9 x, T& y9 m: x, [+ s0 X
  63.                 for i := 0; i
复制代码
. b! w9 S! ~! X+ X
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。. d! J$ L% V  p( x
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。
" J) a0 G1 Z. D+ S* W
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
    - N: M$ G0 c4 f# A5 Q
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
    ( A* l- T; ^2 @' B' l, C( E
  3.         // 不支持校检创世块
    $ s# }, f" k4 u4 h5 A5 ^
  4.         number := header.Number.Uint64()
    9 @7 G7 j9 g( E9 m
  5.         if number == 0 {: i9 w" n, I  Z: S' F5 j
  6.                 return errUnknownBlock3 W5 E' d1 M/ Q6 Q+ U2 t' ]; ~
  7.         }+ P! h: j8 N3 b2 K
  8.         // 检索出所需的区块对象来校检去开头和将其缓存; ]. n. \& f3 _" H8 Z% o& y$ m
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    2 r1 h/ O0 h3 |  Z
  10.         if err != nil {
    ( f' [  j8 {1 ]2 a( K3 V
  11.                 return err; S( ~6 E* [. i/ o$ i
  12.         }1 Z& L! V* S5 |1 e1 ?! H
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址  t" x& E. X" g& A5 N' Z" U% t
  14.         signer, err := ecrecover(header, c.signatures)! r- N- v2 V2 `) r
  15.         if err != nil {7 g* u) t& t- W$ o" o4 A& I& Z+ Z' k
  16.                 return err
    * y6 W% Z+ y( Q" f3 l0 |% d; j8 c
  17.         }
    3 h* S, u6 [  q
  18.         if _, ok := snap.Signers[signer]; !ok {
    9 G# U  S) u0 V/ L6 `" Q
  19.                 return errUnauthorized. u# x& D/ S9 v4 H( d5 C) P
  20.         }) @; U8 m6 f  f  p. z
  21.         for seen, recent := range snap.Recents {
    / j) h# V2 D* E
  22.                 if recent == signer {
    + V. K( b) {8 U8 G' ^1 o  O& e" J! S
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等
    4 V* z* R4 d; P1 C# `( E
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    9 q" O1 [$ A" e) u( v* J0 l
  25.                                 return errUnauthorized
      a' n7 Y6 b1 \) }' ~( C7 B
  26.                         }
    . l" C/ X! s0 t* }4 e* ]
  27.                 }
    % ^3 y' N: V6 M2 ?+ h. x/ H  D8 W
  28.         }) o; {0 m5 A& U+ f" b  O
  29.         // 设置区块难度,参见上面的区块难度部分( S  x/ M, R. ]# r7 W2 E$ g
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)1 [7 g* h& J% |2 ?, m
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {3 G/ Y* D6 C. _; O: M$ `2 d
  32.                 return errInvalidDifficulty
    2 u: d& }1 W# [# Q/ Q$ v* o' V; `
  33.         }* h7 i* F$ K9 d2 m& U3 [0 K8 q
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {, ?6 S# l2 _2 e  Q! L$ o6 u" x
  35.                 return errInvalidDifficulty0 o# m7 p2 f( W( A+ Q" h) S1 V& \
  36.         }
    - V; }2 G! h* B8 W* N5 {( B/ T, g% m! C
  37.         return nil
    % u$ `" t# X% X( @5 Z
  38. }
复制代码

: ~- }, p+ r/ P* d前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
. @! x; ^# Q1 H4 p6 oClique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:. J, S  q+ g6 P
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中
4 o2 @3 w/ d1 D
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
    % {  S2 W' ]1 r% r
  2. func (api *API) Propose(address common.Address, auth bool) {+ h& p9 q. r4 E3 D4 k% G! w
  3.     api.clique.lock.Lock()/ ?. y: Z2 S: q3 X7 r- J
  4.     defer api.clique.lock.Unlock()
    8 b; n+ l2 l) ?+ |! F1 b9 t
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    5 @( Z; q0 E$ w! y% u
  6. }
复制代码
' h* S& u' \: p( L) E
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;7 T/ Z0 p( _# O3 T: s, G# u% y& m
  1. //Clique.Prepare
    ' c7 ?$ j8 h, D2 e1 u$ M
  2.                 // 抓取所有有意义投票的提案
    : e$ @! n/ B- T, j* _: j1 i0 J8 C
  3.                 addresses := make([]common.Address, 0, len(c.proposals))# E+ ?0 ~7 V8 o* ^
  4.                 for address, authorize := range c.proposals {4 z' ?3 h" n3 h* K' X# t
  5.                         if snap.validVote(address, authorize) {+ y1 _% B5 M5 G* S' N  F2 [( P
  6.                                 addresses = append(addresses, address)
    + x; p' B2 X; u1 r7 f
  7.                         }* T' ?: X9 I* ]  T  w0 e
  8.                 }" m8 t) h( Z8 z" j5 f' Z/ ~! x
  9.                 // If there's pending proposals, cast a vote on them0 s. H' ?! I$ ?2 }0 K: K! O7 t
  10.                 if len(addresses) > 0 {* G0 r9 P, \& f# R0 E* w
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。( E* w! J. ~1 P) A. _0 H
  12.                         // 通过提案内容来组装区块头的随机数字段。" Y* }* g/ }- l
  13.                         if c.proposals[header.Coinbase] {
    + ]1 z/ T! Q8 f! X$ v# N
  14.                                 copy(header.Nonce[:], nonceAuthVote)" R3 o9 c# Q& o& k- [; m$ q
  15.                         } else {  D% [! I5 }6 m! `. r+ ]
  16.                                 copy(header.Nonce[:], nonceDropVote)
    , y! Y# Q6 G; m, s. ?
  17.                         }  T/ r  M# a/ R1 u* Y
  18.                 }
复制代码
4 l: o: Z8 u$ n# e- q
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare) \/ \! K) G4 U8 ?& r
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {
    $ U8 n4 l" N, x: h2 A! _9 L  J
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    3 ?# o; e0 Y& k" X0 A; N& `6 V# c
  3.                 return8 M1 T  R! D" }1 x  r$ j, k3 v! O7 {
  4.         }
复制代码
6 t0 i' H$ M# W( C' S/ N
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法
; C; d4 ?- t9 a; m
1 x  f+ o* {' P7 ]' t. c& L: }" B( N7 S$ u2 H  w  @/ }- r
" k$ g! r7 j( I7 }: p, z! h  ^- \. B" ?
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3