Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2837 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。
1 }1 E" g9 G/ W; f( V/ S8 R
  1. func (s *Ethereum) StartMining(local bool) error {
    5 w, H5 i9 q: Y; B: s) ^2 K% F* g
  2.         eb, err := s.Etherbase()//用户地址* _4 Z/ |3 f5 q4 D) r
  3.         if err != nil {
    7 b, d2 Y: H. o% M! F
  4.                 log.Error("Cannot start mining without etherbase", "err", err)
    # I7 n/ t  f& a1 o* x0 @
  5.                 return fmt.Errorf("etherbase missing: %v", err)
    - ], z7 C% w: [- R$ x
  6.         }' x& x+ D" r4 p9 Q; |% U( e5 u
  7.         if clique, ok := s.engine.(*clique.Clique); ok {- \5 _. d- l1 k2 B8 w5 W
  8.                 //如果是clique共识算法
    6 ?5 v4 Z, M' i
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象
    ' R# [. R- q" C6 z! R) Z- o
  10.                 if wallet == nil || err != nil {9 D$ }: u, C  H, S" k
  11.                         log.Error("Etherbase account unavailable locally", "err", err)2 d1 d  h5 I, C" d; F" a3 s
  12.                         return fmt.Errorf("signer missing: %v", err)
    3 |( z8 }7 H" s" M: ?/ C
  13.                 }, [+ y" v; X! o2 Z! @, @8 ]7 I
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法
    , e7 }( W2 M1 h4 k
  15.         }% ^8 A( `8 B( q8 t' h) E) B
  16.         if local {& z- d4 f, y3 A: v& R+ A+ r
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。3 e3 g0 z, m- a. G1 y6 E/ k
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)+ V. h: k3 C+ |' @; j
  19.         }
    ! S+ e7 O1 q# C
  20.         go s.miner.Start(eb)3 Y  Q9 c) Q& }- n; T
  21.         return nil
    5 s4 ]9 ^& M% }, i0 X6 Y
  22. }
复制代码

  n0 {: ^( I9 e5 U这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
" r- B7 A: ]* \3 I6 J( C% NClique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
! c8 A$ `* X: Wtype Engine interface {
, v% ~2 V# _( b        Author(header *types.Header) (common.Address, error)
: r5 v# J: w- [9 S2 v! E- [: l        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
3 c2 V' B6 m3 q        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan/ i9 n' ]7 U$ [/ O7 {( d
Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:
( t$ j% S: l7 H; x. |1 M) u
  1. type Clique struct {
    & S7 V1 t! |6 v9 ~4 ?
  2.         config *params.CliqueConfig // 共识引擎配置参数4 R( n4 _7 \7 Q! O: {  H
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点1 x) g1 a8 R+ a2 h  R1 b
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组4 u8 v; j, R0 ^! E* J; }9 Y9 ?
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿  }: c) j4 S6 v4 \2 |
  6.         proposals map[common.Address]bool // 目前正在推送的提案
    9 m/ U$ r% B4 T2 t7 V' N$ v
  7.         signer common.Address // 签名者的以太坊地址! Q" I0 b1 c5 W. O9 P) n( M; a
  8.         signFn SignerFn       // 授权哈希的签名方法$ _, k, w! u1 s
  9.         lock   sync.RWMutex   // 用锁来保护签名字段
    & r: V: {. f, K. }' ~9 @
  10. }
复制代码
9 Q2 w9 e4 Z, H9 V
顺便来看下CliqueConfig共识引擎的配置参数结构体:
, h! I* A2 x3 c% j% S
  1. type CliqueConfig struct {
    ) b% a4 z" A3 i" e  }7 A
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)
    & L- @* H, a# p& P6 i9 n
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    1 G. H, y( w: D% a: u
  4. }
复制代码

5 ?8 Q9 Q1 p( `; c) H在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
) g! b) r) ^9 n7 g6 f
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
    ) h8 b7 c7 e) s/ l- i7 i- A4 e8 E
  2.     c.lock.Lock()/ \8 A; F+ N: |( L
  3.     defer c.lock.Unlock()
    $ g% o) n" {; T
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
    ( C9 m  h4 a1 S3 d
  5.     c.signer = signer( P5 `0 Q% I0 d5 j
  6.     c.signFn = signFn4 A7 d: a2 E0 g8 W1 M
  7. }
复制代码
, i9 L( d- E) `. s
再来看Clique的Seal()函数的具体实现:
, R, D5 m& W1 v' R4 F  t//通过本地签名认证创建已密封的区块
. O4 C3 G2 `1 c! gfunc (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {9 Q+ }0 P! v: ]1 j+ Z
                                log.Info("Signed recently, must wait for others")
9 p, O- g% i8 x- f* T  h# x                                + b' D& F# J) O
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名5 S2 W" H* J. ^6 `# |
signer不在snapshot的signer中不允许签名7 c+ }; _+ ]+ b4 [
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
$ [" D/ t$ H% j1 r) W3 b- g签名存放在Extra的extraSeal的65个字节中! k* |5 C7 V. Y
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。% a% T# x1 Y2 T# }, Q) g/ S
  1. //snap.Signers是所有的认证节点; ]/ r, O5 e" Y, ?
  2. for seen, recent := range snap.Recents {7 [* R. q, R) q. x9 q
  3.     if recent == signer {
    * ]# ?; }  A/ ~5 n
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {+ W6 a+ b: L. Z8 x7 F6 G$ ?1 K
  5.             log.Info("Signed recently, must wait for others")
复制代码

9 ~: C+ R1 |1 P            
) l4 h) W: W; U在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
# c9 u( }/ B% T关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
6 z9 x+ o0 U; x; h. ldiffInTurn = big.NewInt(2) * m% c. x7 O6 t
diffNoTurn = big.NewInt(1) " I6 ]* _0 I. C1 o; w
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:* q  `; y% @7 ~0 a. z
// 通过给定的区块高度和签发者返回该签发者是否在轮次内
5 F8 T" [" N. t5 ?
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    3 }& S6 ]( b* p+ |5 ?* z! U
  2.         signers, offset := s.signers(), 0% m( S# J( `. d9 E
  3.         for offset
复制代码

6 C# F" Y2 O" M% ~, S, \% QSeal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:
# A5 ~/ q0 C8 D// Snapshot对象是在给定时间点的一个认证投票的状态- u' B6 H6 g5 _6 Y  h0 J
  1. type Snapshot struct {/ a7 y  ?7 V' J% I
  2.     config   *params.CliqueConfig // 共识引擎配置参数
    3 G5 J; b* A5 M  u# _! x' v
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。
    4 \0 \1 _* P, G. ?
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号( A; q8 f6 _! Z. U5 f4 h8 U
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    0 T' H1 V6 u- M2 `! u, h9 s
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表; \1 J$ n) B% G
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址) X( ^4 L* W, W7 I" K
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    & b9 t. L: Y% H8 @9 j/ E% `1 b
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。$ S; x' V9 \0 R) r, m. d7 N% \
  10. }
复制代码
# ^$ ?8 v5 s8 |, O8 a6 F0 y
快照Snapshot对象中存在投票的Votes和记票的Tally对象:' s! P1 f0 E4 }- u6 {: T) \; I
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。
    7 f# [3 r' F2 C5 ^. ^0 b
  2. type Vote struct {7 r0 N1 ]( a% {/ X) W" D  k
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
    . j/ ]: q) l3 @2 n. r& E
  4.     Block     uint64         `json:"block"`     // 投票区块号
    0 c1 \7 u' m, Q4 r! T4 b& l
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权8 ^& G( J$ b, j
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权
    , m/ C9 @# m# M2 v0 w
  7. }! C+ H  b. X4 Z
  8. // Tally是一个简单的用来保存当前投票分数的计分器
    * R- g5 ~5 c. }, R2 Q
  9. type Tally struct {
      o% V! Y1 G2 N, O, s: a
  10.     Authorize bool `json:"authorize"` // 授权true或移除false
    5 C: X" E6 U; ?' T& J
  11.     Votes     int  `json:"votes"`     // 该提案已获票数1 g7 D  c9 ^. Z
  12. }
复制代码
: C2 e; `! t3 ?% j. A. h
Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:6 ], ^% P9 Q8 r8 t4 L7 k
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
    6 e3 a# [6 C$ X) S
  2.         //使用Database接口的Get方法通过Key来查询缓存内容' {6 S  v& x5 x4 u. d
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    % k- C9 J3 k! b  {  O
  4.         if err != nil {! U7 B: ^% B8 p, _; q& d; K
  5.                 return nil, err- o" M' M3 ?# c% W$ j
  6.         }/ [/ m' ]1 P, l6 o# t; k3 N
  7.         snap := new(Snapshot)& Y) l* P; R8 L  k  O
  8.         if err := json.Unmarshal(blob, snap); err != nil {  ~+ A+ n* c6 M5 i+ w1 K: o
  9.                 return nil, err6 h7 e( Q8 d7 L" _" i1 n
  10.         }6 W3 v( }% o+ q! T+ N# V
  11.         snap.config = config
    ) A3 m2 \5 C( _) E
  12.         snap.sigcache = sigcache
    / I( ^7 ?* q( H# o! w' h4 A. L1 S3 H' ]
  13.         return snap, nil
    9 O# K; i4 e/ _5 J- e; y& ?
  14. }
复制代码
: r3 e& x. Z! A$ V* V% f- m: u
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:* e5 V4 n7 w$ m5 u: S" z8 O
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {; J  I" l* y" f2 d
  2.         //组装一个Snapshot对象
    # B. {$ e2 ^6 W4 a' L; B
  3.         snap := &Snapshot{
    - Y1 B, P! R* d2 K: H; w
  4.                 config:   config,
      P% a4 j, y7 C& Y  U
  5.                 sigcache: sigcache,
    + F2 r( s/ x! y8 J# R: `- K
  6.                 Number:   number,- I3 j5 d1 s9 O8 B8 n$ Y7 x
  7.                 Hash:     hash,4 j$ ~# j9 ]# s
  8.                 Signers:  make(map[common.Address]struct{}),5 z; p: O+ Q7 H2 l  a, |) |: ?
  9.                 Recents:  make(map[uint64]common.Address)," F" k) ]6 i- I5 ~/ d' `* D$ `# V
  10.                 Tally:    make(map[common.Address]Tally),6 ~( w) P& g# _2 L3 t7 K8 z7 l
  11.         }! F$ ?' R7 Q' H
  12.         for _, signer := range signers {4 M1 p# }8 p- N4 E; M
  13.                 snap.Signers[signer] = struct{}{}
    + j4 k$ Y; V6 v  l
  14.         }
    ! o/ \( l$ y; r  i
  15.         return snap+ r; z) Y. `- o; ^' d
  16. }
复制代码
8 q$ t0 l/ u' ^- }! B7 j' m% F
继续看下snapshot函数的具体实现:0 O. F( j$ W6 `. H( M  M& i
  1. // 快照会在给定的时间点检索授权快照
    + g8 h8 v3 ~; f# w
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {! N* h* C( Q: ^# q
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints4 \0 [* x, A) F8 `) a* ?. d
  4.         var (, }6 R% L* G: o+ `4 P* g0 S
  5.                 headers []*types.Header        //区块头
    8 ]. W; L' T- G" A% H3 I  I
  6.                 snap    *Snapshot        //快照对象3 U% [* ]! m! G9 s
  7.         )9 ]8 h- E/ ]) v1 B: h% ~/ B% p2 k
  8.         for snap == nil {
    3 H. ]6 p% x  X3 ^) A5 ?
  9.                 // 如果在内存中找到快照时,快照对象从内存中取* H$ [, g& p. ]8 Z, g
  10.                 if s, ok := c.recents.Get(hash); ok {, P) X, R& i, Y' Q' A5 ~/ e
  11.                         snap = s.(*Snapshot)
    4 _7 y, }2 K- }6 C5 N
  12.                         break5 w, Z$ X( ]' j5 P7 v6 ]7 i7 j
  13.                 }9 j/ x! m  g5 n
  14.                 // 如果在磁盘检查点找到快照时
    / ]2 Z' _; c. ^7 U- ?
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
    0 {: r( s. @2 u. W6 y4 v2 x
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
    5 L, W- `: K8 M9 W! W
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
    & h& P; v7 T& H4 d
  18.                                 snap = s2 `. z$ \0 k" g7 G
  19.                                 break
    9 e# y* y3 X4 b5 r
  20.                         }  v8 y+ p2 Z) i* u
  21.                 }. m  S4 J  `4 p, V! B
  22.                 // 如果在创世块,则新建一个快照
    7 p7 L( }  i! n( x
  23.                 if number == 0 {
    & S1 y" a, l7 Y" {. _) S, D
  24.                         genesis := chain.GetHeaderByNumber(0)% @8 g: M) |% w1 G0 o. {3 }
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {; c1 }0 y$ I) [) D5 G6 n7 x
  26.                                 return nil, err9 c- M, L: M/ k8 U4 B) J
  27.                         }
    % n2 v! D: K7 M& f, s
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
    + f6 ]) g  N8 ^5 X9 f6 N1 r5 X& {( l
  29.                         for i := 0; i  0 {
    # C3 w6 R: |' B/ y
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)
    . T. o$ s& P4 h" R
  31.                         header = parents[len(parents)-1]
    . e3 t# m  \  W) e3 {
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {
    ( ~" u  a+ H& d7 w  y
  33.                                 return nil, consensus.ErrUnknownAncestor8 }4 U) J3 z) _$ p0 r3 H6 b
  34.                         }3 M; J- p: E; D; `, |
  35.                         parents = parents[:len(parents)-1]2 O7 m4 o2 x) p/ V
  36.                 } else {9 q/ \6 L5 W4 @
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    4 H6 D) H3 Z5 Z$ B. [
  38.                         header = chain.GetHeader(hash, number)
    * e6 r7 M9 e: `# m2 x
  39.                         if header == nil {$ N& B( c) g3 d$ M: \+ ]
  40.                                 return nil, consensus.ErrUnknownAncestor
    4 M" m( K3 {3 F  _* @$ f9 [( R
  41.                         }
    7 B$ c* [" r/ {
  42.                 }
    # {5 P7 _! k* H! j! ]" F- E; D9 q
  43.                 headers = append(headers, header)" D) o9 t2 U3 Q6 e2 M2 g" x, m
  44.                 number, hash = number-1, header.ParentHash) b0 d* h; {0 W+ u$ F1 n
  45.         }( B% e9 E* @9 X% |9 w2 i
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面# y( @- E- o+ U" @8 |( s: u! _" u
  47.         for i := 0; i  0 {8 O8 J8 S  r- J0 x
  48.                 if err = snap.store(c.db); err != nil {# n7 n0 h! _) E0 H$ z) n  z
  49.                         return nil, err: Q1 L0 ?. j4 |" V* i% q% @: z# v) {( [
  50.                 }$ B0 U$ K8 u7 ]2 i  `% c) C& n
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)
      k& k9 _2 J" f8 w! L4 K- N
  52.         }
    ' @1 U) g  v1 b: B
  53.         return snap, err) L5 P9 W" F* }9 r, c; ~
  54. }
复制代码
6 o) R1 m# u- w  }% H, G. H
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?, t0 v8 X) n. [. j* v6 h1 u4 l# g
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。3 e# b3 e3 T- b. l9 n/ u
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {$ h. ]; w% A1 Q9 x0 c1 Y0 O: g. ?
  3.           //可以传空区块头2 n" z4 S: h) Q. o7 Q
  4.     if len(headers) == 0 {
    * s/ w0 q2 X3 E; {
  5.         return s, nil" Q/ y5 H3 m, u& u2 Q1 D# H* S
  6.     }2 ?  Q% }1 P( y5 F4 d
  7.           //完整性检查区块头可用性
    ! a. {% E) Q" `7 }  X2 t
  8.     for i := 0; i = limit {
    + ?! ^' K; D- L, o
  9.             delete(snap.Recents, number-limit); V  `+ f- L7 H
  10.         }
    % `  O6 k: m( {9 {* G2 r
  11.         // 从区块头中解密出来签名者地址
    8 ?+ C3 [0 C" R: _. p& m; ]
  12.         signer, err := ecrecover(header, s.sigcache)+ z2 U3 g9 L5 n; }
  13.         if err != nil {# ]. E, J& B: c
  14.             return nil, err
    6 y- }6 a% z7 @: m  K8 K
  15.         }! E" h# p% c" A- C* {
  16.         if _, ok := snap.Signers[signer]; !ok {2 J" N% D# _0 }8 {- `
  17.             return nil, errUnauthorized( T; ?) Y9 f1 M- i2 E1 }; D4 r9 e3 S* L
  18.         }$ Z4 L: @* D) O4 P, X+ B' C
  19.         for _, recent := range snap.Recents {2 v/ v, M2 c. @
  20.             if recent == signer {9 w' W5 u( _" v6 d/ n
  21.                 return nil, errUnauthorized  H7 E9 G% Q: L, H) l% q% }  Q
  22.             }
    # o4 y$ U) o% @3 V1 I5 k
  23.         }
    . i) L/ ^5 l+ J  _1 b- o, v% v1 c
  24.         snap.Recents[number] = signer- [4 b( a% t5 q
  25.         // 区块头认证,不管该签名者之前的任何投票2 G- B7 R* b0 K7 p% k8 y
  26.         for i, vote := range snap.Votes {
    % P: R/ F0 Z2 g9 b0 B% }+ q$ x
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {
    ' @/ g/ U4 P3 m7 L2 E8 V" F0 a
  28.                 // 从缓存计数器中移除该投票
    # _, o% f! g) q0 b1 ~! D; k/ g
  29.                 snap.uncast(vote.Address, vote.Authorize)
    + n6 ^' Y2 g; L( d- V- U3 L; M
  30.                 // 从按时间排序的列表中移除投票
    ) f8 n. ^* M$ i8 y) B
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    - w: C0 O) D2 `5 x$ g
  32.                 break // 只允许一票
    5 r7 o: m' W0 r6 _  ~" p
  33.             }7 C. _% `% R8 a
  34.         }8 O; U% P+ r7 E' T6 u( {* z
  35.         // 从签名者中计数新的投票
    ! _+ V4 e% E, @- }0 T2 b" K2 {
  36.         var authorize bool
    $ s1 f$ W- `  c) s
  37.         switch {
    # k. |2 F! S" p* y0 h8 l
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    : Z4 B5 I* h* m" ]" d" ^
  39.             authorize = true/ m5 e7 H& p3 c/ K' G* f
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):* H/ D' L* T! K
  41.             authorize = false# E) h/ g: w, ~: r# }' [
  42.         default:
    8 i" b" s, `6 [- t' k# o5 }
  43.             return nil, errInvalidVote* H+ g! a2 ^4 Q
  44.         }( S. Q2 C. D- f) r/ @
  45.         if snap.cast(header.Coinbase, authorize) {
    8 ]$ n8 Z* A3 G7 L+ h; c
  46.             snap.Votes = append(snap.Votes, &Vote{
    ; J- m$ l& X) S- ?0 |( ^. {5 {1 P
  47.                 Signer:    signer,6 V) H: y; o* d, j8 ^- T5 \
  48.                 Block:     number,6 ^* M' J$ a9 R- r/ K+ ^/ y$ z
  49.                 Address:   header.Coinbase,
      T; }* n2 |: a. N9 y3 q7 H
  50.                 Authorize: authorize,
    : R* t; S6 N7 ?7 N/ R/ y9 Z( x1 `
  51.             })
    3 u& I! l0 L5 H' T- b
  52.         }
    9 }- Q1 r! e8 X# j2 n  ~# ]. L, K
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表, @3 C# n8 ?" l/ b8 C+ y5 o8 G: j
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
    ; {$ b2 W# H0 u9 P, v
  55.             if tally.Authorize {* |& y! g# h4 Q0 D
  56.                 snap.Signers[header.Coinbase] = struct{}{}
    ( @* J2 o  q2 t! c6 X
  57.             } else {
    6 s& x. Y* H* @( c4 E/ X3 c
  58.                 delete(snap.Signers, header.Coinbase)( V+ ?+ x0 J, P
  59.                                   // 签名者列表缩减,删除最近剩余的缓存8 l2 s& C6 J$ _4 p( x
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
    0 ]. J; f* w9 J6 {6 X
  61.                     delete(snap.Recents, number-limit)5 P* u6 U4 b5 T- e
  62.                 }
    ) d3 ^- T$ P8 i" f
  63.                 for i := 0; i
复制代码
; T1 B6 G2 a0 P' B8 b5 x; K7 j7 j
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。5 x9 R0 |" D5 G
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。
, H" q) ~; g, Q2 D- H/ i' q2 j
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照% L+ f. E/ f& F# G
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {3 V1 R1 ~& m% v. }
  3.         // 不支持校检创世块1 G" t) R) e7 q
  4.         number := header.Number.Uint64()
    & N# B: U9 C: k
  5.         if number == 0 {% i9 Y1 }, Y+ x5 q3 ?- L  Q
  6.                 return errUnknownBlock, h/ E6 G" r9 `3 x- ~) Q
  7.         }! {9 S% |4 E2 x
  8.         // 检索出所需的区块对象来校检去开头和将其缓存. q- }# `; h2 u1 P, \+ p2 i
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
    # O# W. y. r, g! r
  10.         if err != nil {
    $ R* v. O% F( U( B
  11.                 return err2 I, y0 s% V4 X% J1 H, n
  12.         }
    * ]$ o. y$ _3 _' F" X* x! J% S
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址' I5 y& P, H! j0 r0 {
  14.         signer, err := ecrecover(header, c.signatures)6 Q" f# `$ L* _8 Q* K5 t
  15.         if err != nil {
    ' _! @6 `4 x- B% J* U
  16.                 return err
    ' c8 U- }; T+ C, T8 [0 C, b
  17.         }
      D- T+ w3 D0 C
  18.         if _, ok := snap.Signers[signer]; !ok {$ @& W( i) y0 z
  19.                 return errUnauthorized' {' D: _% h6 B. M8 P$ r' V; ~0 S
  20.         }' {. @' M( d& u/ ^* j
  21.         for seen, recent := range snap.Recents {
    0 ^& d7 M" v0 d& ^2 S
  22.                 if recent == signer {
    1 B& S0 B/ z5 b
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等
    , r( j: M7 [/ n0 ]8 |# ?
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    & X' ~, B4 U- C" b: |; `
  25.                                 return errUnauthorized
    3 a1 p1 Q& w9 Y4 R* \' U% s0 }9 o( S
  26.                         }, h) I* @3 h; }$ K7 t$ ?& W
  27.                 }
    * g8 k+ j8 S, R/ D6 K/ J) D9 h: i
  28.         }; o5 W2 E) a1 a, p& v5 F$ [
  29.         // 设置区块难度,参见上面的区块难度部分8 ?1 N" x$ K  F# ~" |! L: s
  30.         inturn := snap.inturn(header.Number.Uint64(), signer); F  m4 j$ ?! G& w
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
    # C- q1 t7 S* u" v1 D# j2 b1 m
  32.                 return errInvalidDifficulty
      }9 N  ?# U* f; ?) b: U
  33.         }: s! Z3 z5 U+ [* x7 R; B2 y; P
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {/ a: R3 @0 L. a, \( e- E
  35.                 return errInvalidDifficulty
    ) r/ ~& G4 d% y; l' x# ?4 s
  36.         }
    0 I/ a7 o8 g" S4 F( K4 d! U
  37.         return nil
    7 w+ [9 `, [. [; c
  38. }
复制代码

9 y" z9 n1 a' k- u* ^- i前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?8 n# ?, Z8 H* Z
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:# j! {  \- F: z5 d; ~
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中: l+ z: q6 @0 E& C
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。. H5 j" _( |8 E; M6 \. M
  2. func (api *API) Propose(address common.Address, auth bool) {
    ) x9 s/ a0 U/ ]  @# l
  3.     api.clique.lock.Lock()4 A; L6 ~8 }1 o: d: d* b9 c9 V
  4.     defer api.clique.lock.Unlock()
    ( c% V, t8 u0 ?# K7 }
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    & R2 r2 y! Q! W  J! r. G; K
  6. }
复制代码

) o8 ]" q% T) G+ k本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;
, t2 J) g  K& A: B5 w6 u$ m
  1. //Clique.Prepare* H* C  i1 J" R7 d( W2 X* ^
  2.                 // 抓取所有有意义投票的提案
    ' j( W0 d4 L4 H* C% |: s
  3.                 addresses := make([]common.Address, 0, len(c.proposals))
    0 q& T' m. R% V4 ^6 H2 K
  4.                 for address, authorize := range c.proposals {& w6 j, S% r1 {9 N$ c
  5.                         if snap.validVote(address, authorize) {" d* L. t" T$ \$ X% l3 Z1 O% X
  6.                                 addresses = append(addresses, address)4 u) Y* ]2 |5 ?
  7.                         }# [" A' J% t7 X5 P5 M: {* Z6 \
  8.                 }
    ' ?6 h9 d1 p1 c
  9.                 // If there's pending proposals, cast a vote on them
    + H& b3 m2 S! e+ g) F9 f" U1 D" S
  10.                 if len(addresses) > 0 {
    ' j: l$ c& @5 T6 {/ c. q/ ]
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。( L7 N0 X! o" p! ]
  12.                         // 通过提案内容来组装区块头的随机数字段。
    3 k# p# K- @& u1 d& ^
  13.                         if c.proposals[header.Coinbase] {
    8 H+ o, j# E( F2 A3 d# v0 Z
  14.                                 copy(header.Nonce[:], nonceAuthVote)
    6 B6 {) L" w' U4 |
  15.                         } else {6 M+ w' E! ~/ b: r9 Q& [8 K: S, Y: F
  16.                                 copy(header.Nonce[:], nonceDropVote). ]8 F3 J9 w; n) Y
  17.                         }4 j- p& x/ s( }
  18.                 }
复制代码
3 Z7 s6 k9 \% e- r: R2 E7 g' y
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare+ q4 g* r9 E1 }8 T  U! @# o! @( U
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {
    0 M0 x) J/ A3 @- c- s9 d
  2.                 log.Error("Failed to prepare header for mining", "err", err)
    0 W4 Y+ l  [6 t  N1 O( O
  3.                 return
    6 J! \  i8 Z+ X8 `* |$ {3 @
  4.         }
复制代码

) ^% V' h( x; P$ |$ _其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法
$ m: a7 j& M# a( `# N" |& I! I! Y! N! l' L. X

% W5 J4 A. }4 ~0 g0 Y9 M! U( f1 k, s5 |" ^# X- R
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3