Hi 游客

更多精彩,请登录!

比特池塘 区块链技术 正文

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

卫蒙更夜沙
2772 1 0
Clique的初始化在 Ethereum.StartMining中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中签名函数是SignHash,对给定的hash进行签名。) ?$ {' V! F. _5 S/ R+ @5 p
  1. func (s *Ethereum) StartMining(local bool) error {, C7 p" _# U8 y' w: q
  2.         eb, err := s.Etherbase()//用户地址
    9 x" T" a$ b0 B/ K0 b: |' s) p
  3.         if err != nil {
    ( [- y5 A7 V) X1 k: I
  4.                 log.Error("Cannot start mining without etherbase", "err", err)
    ( {2 j& I8 _# G0 E9 |
  5.                 return fmt.Errorf("etherbase missing: %v", err)& [3 k* o& `! _2 e
  6.         }
    : m5 z3 _. Q. J' D- z  n
  7.         if clique, ok := s.engine.(*clique.Clique); ok {2 i; L$ u: l" w1 \/ J
  8.                 //如果是clique共识算法
    # L& Z8 \$ U% U5 d' U1 J. G4 q
  9.                 wallet, err := s.accountManager.Find(accounts.Account{Address: eb})        // 根据用它胡地址获取wallet对象
      T! d( c" O0 _) W) ?7 {# E
  10.                 if wallet == nil || err != nil {
    " e9 {: L. [" _5 R
  11.                         log.Error("Etherbase account unavailable locally", "err", err)3 j" |) ^% k9 ~' B" [: ^
  12.                         return fmt.Errorf("signer missing: %v", err)
      \- Z* @& t0 P2 y! Y; F- f. V
  13.                 }
    8 v6 K3 b, x% L
  14.                 clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法5 v* i% P* y0 q
  15.         }' _$ w, v9 u" L7 @2 r& G
  16.         if local {
    * z9 n$ V9 k1 q7 `7 Q
  17.                 // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
      ~4 R/ U* a. ~% v9 ?% h2 P$ N  }2 U. C
  18.                 atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
    & a5 V' t4 n& F$ F  `. j/ W7 |
  19.         }
    2 g' c8 b0 z9 x: _9 ]( e$ }, S
  20.         go s.miner.Start(eb)
    . {! A: V+ Q4 F2 p6 L) S% h* |
  21.         return nil1 _! W0 n3 E6 p
  22. }
复制代码
3 x$ @/ u- j3 r* a  F* J
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
7 M# t% }1 J& {  T( U, r5 ^Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:; N& O6 w$ Z2 t
type Engine interface {; N9 i6 X3 h0 j
        Author(header *types.Header) (common.Address, error)$ ~8 z) f6 y! b# U+ Z
        VerifyHeader(chain ChainReader, header *types.Header, seal bool) error  T4 O6 `! z  M8 Y- g
        VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan# _4 p5 X9 t- q; [3 {
Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:& F+ `/ w9 P4 @) L( X
  1. type Clique struct {
    * L1 p3 q: `2 a4 Q4 _- f
  2.         config *params.CliqueConfig // 共识引擎配置参数: g% j, G) y+ R* ?, a
  3.         db     ethdb.Database       // 数据库,用来存储和获取快照检查点/ `; j, g( v! k) A4 R  f( k
  4.         recents    *lru.ARCCache // 最近区块快照,加速快照重组
    0 h+ G) w; g1 o& _
  5.         signatures *lru.ARCCache // 最近区块签名,加速挖矿# |7 t5 x* @# S1 J/ {7 M% s9 l
  6.         proposals map[common.Address]bool // 目前正在推送的提案# `" M: ^* o' D' ?  \
  7.         signer common.Address // 签名者的以太坊地址
    . f: A5 h; ~$ L+ l
  8.         signFn SignerFn       // 授权哈希的签名方法; {  C6 e& i5 D8 E# `* n- [4 C
  9.         lock   sync.RWMutex   // 用锁来保护签名字段7 T; h; m; x  Q" j9 q6 e% W4 \
  10. }
复制代码

) |) V% \- ^/ m顺便来看下CliqueConfig共识引擎的配置参数结构体:& k. `: w0 C1 Y
  1. type CliqueConfig struct {
    ' a* b1 c7 J4 K( k  A
  2.     Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)
    & V+ Z4 R+ E% L
  3.     Epoch  uint64 `json:"epoch"`  // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
    - U% M& f. J! V0 s
  4. }
复制代码

' Y: G9 M1 o3 ]% i$ P* T+ ]9 R' R在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
+ v0 A$ e! Q. M5 g
  1. func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {0 j4 i0 t: {* Z, F- D9 D
  2.     c.lock.Lock()# z; @* ^! H/ L
  3.     defer c.lock.Unlock()
    2 Z% r: [1 v6 t  h( f+ A* a
  4.     // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
    7 M. r8 X: B9 W2 U
  5.     c.signer = signer
    ( _* A4 Z: b0 o' g9 H  q
  6.     c.signFn = signFn
    9 c4 @5 d! k& }& x5 f5 `
  7. }
复制代码
* Y" O5 S  p# d: B, A3 x" _: A$ Q
再来看Clique的Seal()函数的具体实现:' K4 S, _, S" F4 j1 C, m5 p) B8 _
//通过本地签名认证创建已密封的区块6 Z( z4 p7 |/ c
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop  number-limit {/ d8 n6 O- v) @8 Q/ w, x
                                log.Info("Signed recently, must wait for others")
+ T+ `6 x' R6 t7 T                                ( R7 G; N( T6 P4 U
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名2 [6 q' A5 u  F1 }! |" k
signer不在snapshot的signer中不允许签名
5 b( S) T  {' {" A6 }  u& x' osigner不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名6 S  I4 z  t5 |3 F4 _) C' o
签名存放在Extra的extraSeal的65个字节中6 e! `; s' _3 [0 z1 Z" x, }
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。5 J5 a( G( U4 [6 `" {7 r7 P( P
  1. //snap.Signers是所有的认证节点, _. N/ l4 q. C: `& [  J4 b
  2. for seen, recent := range snap.Recents {% b% j: L5 a7 q: f* s# i% E2 }
  3.     if recent == signer {
    / k; p$ R9 X5 z2 R
  4.         if limit := uint64(len(snap.Signers)/2 + 1); number  number-limit {6 o4 Q) L. X( q
  5.             log.Info("Signed recently, must wait for others")
复制代码
, k' f7 ^7 A$ d+ Q/ a
            
* S' T2 x" `+ S8 p" x  W8 i在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
" e! q7 t* ]; F关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。8 }$ Q: `( n4 l5 c
diffInTurn = big.NewInt(2) # c' m; ?& o$ t8 T  P* `5 g% Q
diffNoTurn = big.NewInt(1)
6 G; i" w1 j3 V' s2 w当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:2 m* X# U: j* @# @, p6 B+ x; R
// 通过给定的区块高度和签发者返回该签发者是否在轮次内' y; x6 Y. E9 K4 R' |. @* @# u
  1. func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
    ) ^  C# r5 T$ C4 \  \% @
  2.         signers, offset := s.signers(), 0
    3 T5 C  @$ n- b
  3.         for offset
复制代码

& i3 P8 ]0 }& n$ ^Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:
  U+ }0 z3 i( C& F* u// Snapshot对象是在给定时间点的一个认证投票的状态
8 c3 W# F2 w: Z6 v$ S
  1. type Snapshot struct {  k  ?" l: ?* E3 a: G
  2.     config   *params.CliqueConfig // 共识引擎配置参数
    ' ?/ e2 S( y* ?  C/ {( l, d
  3.     sigcache *lru.ARCCache        // 签名缓存,最近的区块签名加速恢复。
    6 g: S2 U3 j- F
  4.     Number  uint64                      `json:"number"`  // 快照建立的区块号7 o7 a9 u7 \) }5 x& Y  K) J
  5.     Hash    common.Hash                 `json:"hash"`    // 快照建立的区块哈希
    9 j! A; j+ Z( {
  6.     Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表) ^, A! u4 c. x. \- E
  7.     Recents map[uint64]common.Address   `json:"recents"` // 最近担当过数字签名算法的signer 的地址: x& q* b- H" \  w* Y. }2 N
  8.     Votes   []*Vote                     `json:"votes"`   // 按时间顺序排列的投票名单。
    * l. L; `) w+ j0 g9 {2 k4 p, e
  9.     Tally   map[common.Address]Tally    `json:"tally"`   // 当前的投票结果,避免重新计算。
      |+ _/ e# w8 Z6 [5 t
  10. }
复制代码
& |$ i& I( K/ }& K. @. r( s7 p. T
快照Snapshot对象中存在投票的Votes和记票的Tally对象:- ]. y9 J0 g! p- S
  1. // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。! A1 l' A( U9 _' {) U3 y
  2. type Vote struct {
    0 X6 M7 I" C( z/ h- d$ ?3 ~: S
  3.     Signer    common.Address `json:"signer"`    // 已授权的签名者(通过投票)
    # E5 }2 O" b+ p
  4.     Block     uint64         `json:"block"`     // 投票区块号
    ! b+ J- R' z" j  C2 B+ v$ ~0 z& i
  5.     Address   common.Address `json:"address"`   // 被投票的账户,修改它的授权/ }) N2 ?" H5 o/ y2 V
  6.     Authorize bool           `json:"authorize"` // 对一个被投票账户是否授权或解授权9 G% a5 \0 d$ v
  7. }( R/ n/ J0 ?/ \
  8. // Tally是一个简单的用来保存当前投票分数的计分器" m$ N) l9 z: A4 ?$ J
  9. type Tally struct {
    2 K; \" m, o* j  T: z& Y! F3 O
  10.     Authorize bool `json:"authorize"` // 授权true或移除false! I* h9 g& c$ N' F! D3 R& q* U$ i
  11.     Votes     int  `json:"votes"`     // 该提案已获票数( M' ~' @* Q# F! Z% t
  12. }
复制代码

! @; B6 R" m' V5 wSnapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:
; f1 x" H2 C: ^4 w
  1. func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {& C; s! g; G/ {. m, f/ R+ X) Q
  2.         //使用Database接口的Get方法通过Key来查询缓存内容
    : v. a# o: ^. ~1 j' v
  3.         blob, err := db.Get(append([]byte("clique-"), hash[:]...))
    2 D) ^& G1 T/ s) l: H
  4.         if err != nil {
    . {; I5 r: \# E8 j/ Z* I
  5.                 return nil, err) ?6 t% M1 m, N
  6.         }- ]6 L6 A+ N5 J" h$ L, z+ o0 j
  7.         snap := new(Snapshot)( {) ?5 Z- O9 L' \* X, d
  8.         if err := json.Unmarshal(blob, snap); err != nil {
    5 M0 u" A- B- @
  9.                 return nil, err  B2 q' E  Y9 x( l1 d% H) [1 h
  10.         }
    & L) R3 u" `, V) z& \4 q
  11.         snap.config = config0 y7 g2 D* k! m' z# b
  12.         snap.sigcache = sigcache
    3 _, W" ~$ G7 F0 B( `' F
  13.         return snap, nil* W4 S$ l/ G( A& |+ O
  14. }
复制代码
. `8 G' P* Q* u1 X0 n
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
: F1 O' \2 ~5 U: R; q
  1. func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {8 R, }2 g( J2 z! e- H/ J+ O+ D4 |
  2.         //组装一个Snapshot对象5 ?; O9 y5 B! C4 r8 M7 k. L
  3.         snap := &Snapshot{
    6 D4 v2 O% [- S: L
  4.                 config:   config,# K# @+ ?9 J8 P9 ?+ Z! B
  5.                 sigcache: sigcache,
    . k' Z2 T2 v) w# D' d2 t- w
  6.                 Number:   number,
    . p; E3 v+ V; f2 p4 Y
  7.                 Hash:     hash,
      f. ]  ^: X! q+ B+ ^5 i" D
  8.                 Signers:  make(map[common.Address]struct{}),/ `. I8 i5 e# j3 g" {/ Y
  9.                 Recents:  make(map[uint64]common.Address),9 a' O$ U! G5 k  D  f1 w
  10.                 Tally:    make(map[common.Address]Tally),
    ) f" V1 e* H# E# B$ {4 ^
  11.         }
    % `: r) R+ \% w: W3 g- u( ^! D* X
  12.         for _, signer := range signers {1 @; @' u0 d' S- Q6 U+ Y. d
  13.                 snap.Signers[signer] = struct{}{}# _- n$ Z/ e3 ~; M1 `  q, ]
  14.         }7 v! u% x/ Q" ~
  15.         return snap3 Y8 F2 A8 u! v. J4 w, j! E  T
  16. }
复制代码

% ^. A) x7 s$ p+ o, Q继续看下snapshot函数的具体实现:
0 R. Y3 k- {- V
  1. // 快照会在给定的时间点检索授权快照
    * y" o+ w6 L$ H3 v
  2. func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
    ( z) m) b7 ^# z) d5 i  v
  3.         // 在内存或者磁盘上查找一个快照来检查检查点checkpoints
    : @) l7 ~) e! e
  4.         var (
    4 {& a" X+ @% M
  5.                 headers []*types.Header        //区块头. |9 {+ W1 Z4 b* |2 d, L# k
  6.                 snap    *Snapshot        //快照对象" ]( V8 f+ h; n/ Q0 t4 S
  7.         )' I) u  V- d3 }$ u
  8.         for snap == nil {
    8 ^. M6 T- V- R6 C+ |
  9.                 // 如果在内存中找到快照时,快照对象从内存中取. I! P/ C& _8 _7 `0 p( V+ {
  10.                 if s, ok := c.recents.Get(hash); ok {
    ! _% c& f9 Z; |& K
  11.                         snap = s.(*Snapshot)
    / c* q5 `/ _6 v, o& S* p2 b1 s+ N
  12.                         break: y* }0 \: N+ Q! h  @( {$ `' _' |" `
  13.                 }
    7 _) n& P# M2 Y% z' y
  14.                 // 如果在磁盘检查点找到快照时
    ; K0 k) ?. s% e5 I
  15.                 if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
    ! @! p$ q6 n6 D1 }1 \+ z
  16.                         if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
      ]% b- \6 x/ F8 i2 r9 b
  17.                                 log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
    # L+ D. s. e  j+ w
  18.                                 snap = s/ G, J( M( ~& W& ^
  19.                                 break! R0 I, g! a8 a0 ~9 v
  20.                         }
    - n" O5 I. H9 ?) n$ ?5 [
  21.                 }% n5 {# B4 u5 ?/ P8 b3 p- S* d# B
  22.                 // 如果在创世块,则新建一个快照
    3 k' M' O  f" w  o7 w, N/ Q/ w0 d/ b
  23.                 if number == 0 {- G7 J; c6 {0 r" D
  24.                         genesis := chain.GetHeaderByNumber(0)
    1 ?( C9 R7 h7 u" M# z3 h
  25.                         if err := c.VerifyHeader(chain, genesis, false); err != nil {
    % h; }6 B/ d7 p$ @7 m3 L6 {
  26.                                 return nil, err
    0 @. d7 x9 V' P! ~3 N' {9 `/ ~
  27.                         }
    2 l: L: Z9 h* h- Y1 d; M
  28.                         signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)9 E' |6 q' [5 `# L3 J
  29.                         for i := 0; i  0 {
    & c4 d+ [3 O2 z& _
  30.                         // 如果我们有明确的父,从那里挑选(强制执行)2 m) Y; ]* t# L7 b6 Y: z! \
  31.                         header = parents[len(parents)-1]1 x) {/ A' E# d, ?$ Z- _
  32.                         if header.Hash() != hash || header.Number.Uint64() != number {; \# N" L/ f& S" w5 E
  33.                                 return nil, consensus.ErrUnknownAncestor
    8 T! X4 z6 g6 L
  34.                         }4 M: H) C: J) i! v. [* ?
  35.                         parents = parents[:len(parents)-1]
    & T- K4 a8 Y$ \
  36.                 } else {2 U2 J* s5 P! F: U% q
  37.                         // 没有明确的父(或者没有更多的父)转到数据库获取
    $ |5 W# ~  j& ~. F' ?" J
  38.                         header = chain.GetHeader(hash, number)
    ! d8 K8 f  f4 c
  39.                         if header == nil {
    " e5 v8 J5 y* K+ _+ ~0 H# ^
  40.                                 return nil, consensus.ErrUnknownAncestor2 B3 i$ n% j' I0 Z1 B' R$ R5 J' V. O
  41.                         }
    " J7 w2 l  v# N4 @5 F. l- F
  42.                 }
    ( A! ?0 {9 B& J& |! m  c* _
  43.                 headers = append(headers, header)
    1 D' t  ^( O3 T
  44.                 number, hash = number-1, header.ParentHash
    " U2 B" {: v5 o
  45.         }
    ; ?7 ?. ~4 S, Z& o$ |8 {
  46.         // 找到了之前的快照,将所有的pedding块头放在它上面. [( W9 B3 }' ?. Q3 v! B. u
  47.         for i := 0; i  0 {
    7 G, i: e0 f5 z% a/ a
  48.                 if err = snap.store(c.db); err != nil {
    ! K  h8 {6 M6 [. E
  49.                         return nil, err! y, P7 J, v, H( E- D8 _# k! w6 c
  50.                 }
    4 j5 U0 D# e' B9 u4 u! B
  51.                 log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)* K1 ]6 P: |# x# A
  52.         }
    + S9 {# q' s! k
  53.         return snap, err* @/ u! i( `0 ^( L3 a; o0 _  I8 ]
  54. }
复制代码

/ k: t% b: E! h5 \/ _6 ?5 R在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?
0 z% s0 I' y0 Z! W
  1. //apply将给定的区块头应用于原始头来创建新的授权快照。
    * Q/ W/ V8 b5 E
  2. func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {6 ?; ^; G- M9 d; ?
  3.           //可以传空区块头
    % V" ~( N0 r0 O! I5 _4 \
  4.     if len(headers) == 0 {# r' x+ N% y! f. r
  5.         return s, nil
    ; M- k' K& T8 _
  6.     }
    . ?) I0 Y5 m9 D7 L3 o: [  {0 Q
  7.           //完整性检查区块头可用性
    ; c$ J) E' D* k8 s
  8.     for i := 0; i = limit {$ h- K& q9 f6 R5 `+ w, G( H; j
  9.             delete(snap.Recents, number-limit): J! n* Y( R# P+ ]" \3 C3 q
  10.         }9 T  y6 i+ q0 n$ |9 R# T
  11.         // 从区块头中解密出来签名者地址" B' y. l/ m6 t( U* C) e2 Q% Y
  12.         signer, err := ecrecover(header, s.sigcache)0 f: f  _% y  k" K
  13.         if err != nil {: \/ `; U2 l4 c
  14.             return nil, err( _, S9 [* F3 M8 f1 i4 K
  15.         }$ B8 d$ r  L2 u+ G- Q
  16.         if _, ok := snap.Signers[signer]; !ok {7 \! u0 Q4 S! k) r
  17.             return nil, errUnauthorized" K( |5 j) l5 `' R: l1 w! }7 n
  18.         }
    0 R3 o0 \3 J: B! }. [6 F: v  H
  19.         for _, recent := range snap.Recents {
    + R: i$ f3 h$ l  J' S
  20.             if recent == signer {
    . g" Q8 K4 ?5 J1 z. I2 Y# ]
  21.                 return nil, errUnauthorized6 t7 K$ K  J! d. ?& {8 \
  22.             }
    & L4 E/ `, _2 ~$ O
  23.         }/ S( J$ o& g9 L
  24.         snap.Recents[number] = signer; l! H# }; z4 M9 L4 d% z
  25.         // 区块头认证,不管该签名者之前的任何投票
    & q" ^# @( U! h0 K; R
  26.         for i, vote := range snap.Votes {; f) E) a5 v/ \1 i. ]6 R
  27.             if vote.Signer == signer && vote.Address == header.Coinbase {8 X4 M3 x+ C0 D! R
  28.                 // 从缓存计数器中移除该投票
    ! N( H8 r9 r# W, \# C
  29.                 snap.uncast(vote.Address, vote.Authorize)
    ; ]  N) |; j9 F/ {1 L1 @
  30.                 // 从按时间排序的列表中移除投票+ L7 H/ f1 R9 n- v; S) L
  31.                 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
    ' |$ k+ Q- e" B4 V
  32.                 break // 只允许一票! r1 T$ U! J# d, w& T
  33.             }9 L- K9 k' l8 n: a0 ^
  34.         }* l  |& ^! e6 Y* @- e% \/ R4 K
  35.         // 从签名者中计数新的投票' {3 c) M7 J9 n3 k9 d, z
  36.         var authorize bool; a* `5 Q7 M! V$ X8 M' c
  37.         switch {+ p' M% _4 b) `- K: l8 f
  38.         case bytes.Equal(header.Nonce[:], nonceAuthVote):
    ' g$ f- i. s. I
  39.             authorize = true
    - N! Y) t4 ]: q5 U: U8 h
  40.         case bytes.Equal(header.Nonce[:], nonceDropVote):6 N+ {( k- N- x& q
  41.             authorize = false
    0 F" k6 y# t( z/ g
  42.         default:! ^/ `0 C2 u' P6 @9 R" \( n
  43.             return nil, errInvalidVote4 V- |; {$ S' I& q+ V4 @2 }
  44.         }# I! R/ W" j- V$ I% j; r9 R. [
  45.         if snap.cast(header.Coinbase, authorize) {
    ! }, w% i$ a  I# z& M
  46.             snap.Votes = append(snap.Votes, &Vote{
    * x  V9 p" g1 N8 w1 r9 B! l
  47.                 Signer:    signer,4 g! x* K" h/ ]0 p; b! f& M; r
  48.                 Block:     number,
    * d( P+ \& V7 K5 ^" U) X+ ?
  49.                 Address:   header.Coinbase,; ~/ i$ l: j. {+ H1 [7 Z6 _* |, \. n" s, V
  50.                 Authorize: authorize,
    : F8 k- q' w$ D/ I( C' w; A. T
  51.             })
      v" v1 H1 @. R+ R% g3 f/ M' `
  52.         }( C# S) {% T8 k+ D* s0 H
  53.         // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表% _! R! s, U% M' @7 h0 y
  54.         if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
    ) p( o: Z& H3 D, p6 T
  55.             if tally.Authorize {
      \( u- y0 i" M# C4 L; m( V' [
  56.                 snap.Signers[header.Coinbase] = struct{}{}0 m; W; V* [! `- W% h2 ~# z! A
  57.             } else {
    * a( L: L; L. @4 r3 ^! ~2 F1 n
  58.                 delete(snap.Signers, header.Coinbase)
    : K* ?' U. U7 T
  59.                                   // 签名者列表缩减,删除最近剩余的缓存" q4 E, G4 Q3 I
  60.                 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {6 H  X7 x' ]  k$ U2 Y
  61.                     delete(snap.Recents, number-limit)2 N7 L. E+ q! A( Z
  62.                 }
    . p' r) R( {. V2 Z7 x
  63.                 for i := 0; i
复制代码
7 d. c/ U3 `- b7 Q+ q# e  G* N! S# A/ 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快照结构已经更新到哪个区块了。
* W' A; `: t4 Q8 C# k区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。
2 B' T2 g& h5 W& x  ~+ v1 K1 G
  1. // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照( t, f  t5 P$ a+ S- \
  2. func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {9 t5 D, [) g5 L' y( b( c* X
  3.         // 不支持校检创世块
    # v) i" y- t4 E
  4.         number := header.Number.Uint64(): S/ P& u; n$ V% @
  5.         if number == 0 {1 G% G# F6 {, j! _/ B& k
  6.                 return errUnknownBlock  o) J, j  f# L  G
  7.         }
    + ]- i! U' e) g# q
  8.         // 检索出所需的区块对象来校检去开头和将其缓存
    ! @4 l5 c8 _8 U5 j
  9.         snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)' {, a7 v2 D" T! M0 s7 z
  10.         if err != nil {- f3 ?$ ?" F) S' s
  11.                 return err
    ) J( J) f4 A1 M% b9 q
  12.         }
    ' J  K* @& w  U+ W  q
  13.         //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址; J3 k) O5 N' |" U; M4 O' z# |, n
  14.         signer, err := ecrecover(header, c.signatures)9 V4 {4 U  l1 e! t: C/ _7 q& H
  15.         if err != nil {
    . |" D% `- E! s9 m
  16.                 return err
    1 O5 }4 P' _. t# Q
  17.         }0 ]/ N6 n+ ?- E5 |
  18.         if _, ok := snap.Signers[signer]; !ok {/ q/ F8 Z  u& j% t! z" S
  19.                 return errUnauthorized
    1 l( T; F1 v# y6 J7 A% v  {
  20.         }
    4 s$ r" i. X8 e1 H, W7 d0 |- V
  21.         for seen, recent := range snap.Recents {
    . D3 D+ [. X' Z5 C* B
  22.                 if recent == signer {& ?& \! S9 F, E+ t3 Q2 K( }9 C
  23.                         // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等
    2 I* i. g: j/ F2 u
  24.                         if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
    / r* ^0 u/ R3 T' R5 U
  25.                                 return errUnauthorized
    $ _* O2 Y  L/ }
  26.                         }3 V' H; K9 ]1 _. g
  27.                 }  G% z* m% e3 O5 @2 F
  28.         }; ~  @6 u( y. x7 Y0 z: C* f
  29.         // 设置区块难度,参见上面的区块难度部分
    ) I! o7 l+ e5 e
  30.         inturn := snap.inturn(header.Number.Uint64(), signer)% R1 P- o& u" S7 V2 l' L
  31.         if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {/ k1 b4 C( c) v, F  ^
  32.                 return errInvalidDifficulty4 P: u8 Z( O& j) C+ C+ I2 H
  33.         }
    7 r4 N! ^, X( l
  34.         if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {: L" M4 m0 @% z; _' \) d! ]
  35.                 return errInvalidDifficulty( n. @. k, o7 M; I# v
  36.         }
    ; B4 c! g% L5 S0 K& H
  37.         return nil
    6 y& ~0 D# X% P8 e5 R* U7 y
  38. }
复制代码

- K/ ]3 W6 a6 M& e& w7 H前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?! d1 d; d0 p6 N% _6 |" z& i
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:5 T- O: {; p- {6 N2 A4 S
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中
3 Z3 }* j$ J* }5 Q3 o5 u
  1. // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。9 \% q+ p5 y, Z+ C, a* Q9 H
  2. func (api *API) Propose(address common.Address, auth bool) {
    & i) U1 G1 j! k7 \
  3.     api.clique.lock.Lock()3 N9 C! X. S& k1 |# r% W* T
  4.     defer api.clique.lock.Unlock()
    - g1 ~4 ]' m, Y
  5.     api.clique.proposals[address] = auth// true:授权,false:移除
    3 s0 p8 x8 @6 G" v" g8 O
  6. }
复制代码

  C7 w9 X6 I6 b  R( ?本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;& X; [- p% p/ @/ s
  1. //Clique.Prepare
    3 x5 M' B6 E1 z' F: M
  2.                 // 抓取所有有意义投票的提案9 V6 ^5 U9 ?# D5 \
  3.                 addresses := make([]common.Address, 0, len(c.proposals))
    2 x0 O7 F4 s% s
  4.                 for address, authorize := range c.proposals {& E& V( t  {* C* C- Z5 H0 n
  5.                         if snap.validVote(address, authorize) {/ I" Z; i. u0 n+ Q
  6.                                 addresses = append(addresses, address)+ p3 A+ ]# A1 r: h' X
  7.                         }2 a' x0 S/ J# H- u9 U, g
  8.                 }
    % b+ c8 X4 O, _+ d) Z7 I. C+ y
  9.                 // If there's pending proposals, cast a vote on them- k( x' B6 y1 I
  10.                 if len(addresses) > 0 {; n/ \9 M' x( _9 s
  11.                         header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。7 {& j1 z/ U$ P
  12.                         // 通过提案内容来组装区块头的随机数字段。: ~. a1 U4 M4 F: r! j3 c' e/ h- P
  13.                         if c.proposals[header.Coinbase] {
    1 H7 J0 y; o9 D! I/ ^/ c! b
  14.                                 copy(header.Nonce[:], nonceAuthVote)
    2 @1 S' B7 D$ o. e" |( n
  15.                         } else {& u2 m6 {3 _. d, ^: c; [- n
  16.                                 copy(header.Nonce[:], nonceDropVote)
      |2 y' v4 T% ]* h, q6 y1 u
  17.                         }, Z3 f6 A8 s6 l& u' @" G/ b
  18.                 }
复制代码

! E+ y  I; {; W- Y* K在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare, o2 V/ z- H) z1 z( E% U
  1.         if err := self.engine.Prepare(self.chain, header); err != nil {3 q# @" [6 |$ Z4 p  x
  2.                 log.Error("Failed to prepare header for mining", "err", err)' d# c( |7 r( \
  3.                 return1 J) `- u7 [7 y( n4 o
  4.         }
复制代码
6 v# x2 z5 j) Q* D
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法# q+ J% l7 x2 Z0 ?
6 m9 |  o! F; o7 t/ w
8 j7 N, D8 z! b) N# u' U4 H

& c8 J2 p4 N2 g8 `! J  T" r以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

成为第一个吐槽的人

卫蒙更夜沙 小学生
  • 粉丝

    0

  • 关注

    0

  • 主题

    3