- func (s *Ethereum) StartMining(local bool) error {: A0 G* t, B6 L2 Q
- eb, err := s.Etherbase()//用户地址
- if err != nil {
- log.Error("Cannot start mining without etherbase", "err", err)/ K# W: m2 u: N, j. t+ [7 }% ~
- return fmt.Errorf("etherbase missing: %v", err)# `0 b# G A j: V; k% Q$ o* p9 `
- }
- if clique, ok := s.engine.(*clique.Clique); ok {
- //如果是clique共识算法 ~" ], U b5 f: X
- wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) // 根据用它胡地址获取wallet对象" f1 S% n3 G4 h: [5 \8 ?
- if wallet == nil || err != nil {; E* ? Y! r% ~; \) M
- log.Error("Etherbase account unavailable locally", "err", err)( v9 g$ v2 o# `) g8 S5 p% M
- return fmt.Errorf("signer missing: %v", err)
- }- p: [) L0 x* ^, p: i/ \0 h
- clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法- ?3 ]+ x& ]7 F: ?2 |' f
- }6 d2 J2 F; u& p! E' _: L. W# c
- if local {
- // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
- atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
- }
- go s.miner.Start(eb)
- return nil
- }
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。2 Q Y( e; B7 q1 |5 P
Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
type Engine interface {' @$ E5 a& j+ c. l# C
Author(header *types.Header) (common.Address, error)
VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:6 H& A1 G7 P, `" Y2 t
- type Clique struct {; M* S! u6 T. ?1 A: s! J) E# J' K
- config *params.CliqueConfig // 共识引擎配置参数4 v$ _) q& V* u' c$ D
- db ethdb.Database // 数据库,用来存储和获取快照检查点
- recents *lru.ARCCache // 最近区块快照,加速快照重组2 Y% `5 s0 D/ m {7 Z
- signatures *lru.ARCCache // 最近区块签名,加速挖矿
- proposals map[common.Address]bool // 目前正在推送的提案
- signer common.Address // 签名者的以太坊地址
- signFn SignerFn // 授权哈希的签名方法1 z# X& a! {. n `
- lock sync.RWMutex // 用锁来保护签名字段( F# Q9 s; T3 g7 e) p8 r6 Z
- }
顺便来看下CliqueConfig共识引擎的配置参数结构体:
- type CliqueConfig struct {1 c# W' ~7 c0 K/ ^
- Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s), P) X3 I3 `) r- m$ }& {" ?& u
- Epoch uint64 `json:"epoch"` // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)) Z, {! {2 ~, |! r7 b% k! V( {( l
- }
在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:) J2 }4 M6 K5 D) ?- W
- func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {
- c.lock.Lock()
- defer c.lock.Unlock()
- // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
- c.signer = signer' E$ D* I! [9 g) I
- c.signFn = signFn
- }
再来看Clique的Seal()函数的具体实现:! E+ y0 r# i, o+ x
//通过本地签名认证创建已密封的区块
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop number-limit {$ J0 i7 I; I6 B$ c" B6 |6 @
log.Info("Signed recently, must wait for others")
+ S1 |/ i7 N5 }5 n, x
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名0 U! x7 s# a; w2 d. r- R2 v1 t
signer不在snapshot的signer中不允许签名7 r$ [4 f' @+ h9 @ C
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名5 t M' W. w/ |8 ?, W. N
签名存放在Extra的extraSeal的65个字节中 @/ N) z: e+ a7 q' m$ s2 i
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。" `2 z w' ~# V! v
- //snap.Signers是所有的认证节点0 g- e2 @+ {. A( Z# d# a
- for seen, recent := range snap.Recents {, |2 E5 S* `3 q5 E' M! L- j, ~5 ~
- if recent == signer {- C) {% H5 `$ J
- if limit := uint64(len(snap.Signers)/2 + 1); number number-limit {
- log.Info("Signed recently, must wait for others")
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
diffInTurn = big.NewInt(2)
diffNoTurn = big.NewInt(1)
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:' r3 w" N- K1 N' U# T; p4 O4 {
// 通过给定的区块高度和签发者返回该签发者是否在轮次内
- func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
- signers, offset := s.signers(), 0" p: D. u' ^) U3 H) ?0 R
- for offset
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:- k* Y- {0 @# d% G/ X/ v& Q
// Snapshot对象是在给定时间点的一个认证投票的状态
- type Snapshot struct {
- config *params.CliqueConfig // 共识引擎配置参数; P& |* w# l7 @/ M
- sigcache *lru.ARCCache // 签名缓存,最近的区块签名加速恢复。8 q1 Z+ I+ G) ]5 M
- Number uint64 `json:"number"` // 快照建立的区块号
- Hash common.Hash `json:"hash"` // 快照建立的区块哈希5 c, ` d2 d ?+ u
- Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表3 u6 u; g m) D1 N) T+ R
- Recents map[uint64]common.Address `json:"recents"` // 最近担当过数字签名算法的signer 的地址5 `4 }! n% A5 O% s" y9 d. U4 i
- Votes []*Vote `json:"votes"` // 按时间顺序排列的投票名单。4 l. S% l0 _9 f3 |& K
- Tally map[common.Address]Tally `json:"tally"` // 当前的投票结果,避免重新计算。
- }
快照Snapshot对象中存在投票的Votes和记票的Tally对象:5 Q* u) k& p8 O# A5 ^1 [
- // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。( X0 L- ~: ?/ T" g2 k; q1 C) g8 W" L
- type Vote struct {
- Signer common.Address `json:"signer"` // 已授权的签名者(通过投票)2 T0 @* \7 H( B4 Y: k$ u9 b
- Block uint64 `json:"block"` // 投票区块号
- Address common.Address `json:"address"` // 被投票的账户,修改它的授权
- Authorize bool `json:"authorize"` // 对一个被投票账户是否授权或解授权, m7 _4 ^0 j4 {1 g
- }
- // Tally是一个简单的用来保存当前投票分数的计分器% V$ _7 s: h# a- ^
- type Tally struct {6 R/ f0 K; |% f" N( U& @9 s
- Authorize bool `json:"authorize"` // 授权true或移除false9 Y+ w6 i0 b' C
- Votes int `json:"votes"` // 该提案已获票数: C6 y7 M- x$ U& n' e8 T
- }
Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:! ~4 {1 y7 d9 X _8 [0 @
- func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
- //使用Database接口的Get方法通过Key来查询缓存内容0 z3 B0 C4 ?7 P h8 v/ R9 @) l9 J
- blob, err := db.Get(append([]byte("clique-"), hash[:]...))' a% f" W2 j2 @8 O* r" v
- if err != nil {
- return nil, err, T' f/ J( P6 p7 \: I
- }1 S& s2 r& T1 ~: G
- snap := new(Snapshot)# s2 `) m" E* X4 N E5 @, r
- if err := json.Unmarshal(blob, snap); err != nil {
- return nil, err% o: `! \. r6 D; L
- }
- snap.config = config. N! y1 n: ]! N: E
- snap.sigcache = sigcache9 Y; C' o9 a4 \* g3 o6 F5 v- u. u+ ]
- return snap, nil
- }
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
- func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
- //组装一个Snapshot对象$ S1 e+ |4 H9 Y, e
- snap := &Snapshot{& |/ }1 Y& h) c: s2 ~4 p1 N
- config: config,% a: y. `# P, [% }4 n
- sigcache: sigcache,
- Number: number,8 B- g l) v5 X. x7 O2 e
- Hash: hash,$ P$ L$ i! Y' ^! { {
- Signers: make(map[common.Address]struct{}),/ ~; |+ h5 q: W) z
- Recents: make(map[uint64]common.Address),+ F$ ~5 k: l( ^( Q
- Tally: make(map[common.Address]Tally),; v' c9 _2 u! I2 U4 K8 `
- }
- for _, signer := range signers {
- snap.Signers[signer] = struct{}{}& r3 t2 h+ z, D% I
- }
- return snap
- }
继续看下snapshot函数的具体实现:
- // 快照会在给定的时间点检索授权快照
- func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
- // 在内存或者磁盘上查找一个快照来检查检查点checkpoints, _ S# {, z& q- ]
- var (
- headers []*types.Header //区块头1 _) Y# Q' G8 }( P# o
- snap *Snapshot //快照对象( Y% }. q% ^! V1 z0 m
- )
- for snap == nil {) I; Y; i; y7 _* ]
- // 如果在内存中找到快照时,快照对象从内存中取
- if s, ok := c.recents.Get(hash); ok {- O3 e/ A5 j% ~$ @; L$ v+ A& Q
- snap = s.(*Snapshot). E( Y0 m' p1 x, U
- break2 c; s0 [! T) H, p: G5 R. m
- }
- // 如果在磁盘检查点找到快照时2 d) P: m" V4 h1 M# M/ D9 J
- if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号+ \) H9 D @: k
- if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {
- log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
- snap = s" b- g0 |" j2 D
- break9 i3 h0 J |- }( k, w
- }% t4 Z% X; r* h1 d1 M9 a
- }
- // 如果在创世块,则新建一个快照5 F I; C9 {$ c, Q0 v8 F+ y; Y
- if number == 0 {; N% p, A' \8 N! r4 X2 Q5 h
- genesis := chain.GetHeaderByNumber(0)' K- X) W+ I; F! {+ U% d
- if err := c.VerifyHeader(chain, genesis, false); err != nil {. g. t9 O2 M5 P+ Y
- return nil, err
- }- h% M; O Y. o$ v$ D9 F0 [3 N
- signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
- for i := 0; i 0 {
- // 如果我们有明确的父,从那里挑选(强制执行)
- header = parents[len(parents)-1]
- if header.Hash() != hash || header.Number.Uint64() != number {9 f# C5 \! Z/ f( S
- return nil, consensus.ErrUnknownAncestor; T% l3 W6 E" f, h3 ]! p% h
- }
- parents = parents[:len(parents)-1]$ M5 m* ~+ Q; J* e u+ Z
- } else {% ?1 j& b4 r2 \; j/ c9 e) I
- // 没有明确的父(或者没有更多的父)转到数据库获取
- header = chain.GetHeader(hash, number)
- if header == nil {
- return nil, consensus.ErrUnknownAncestor
- }
- }
- headers = append(headers, header)
- number, hash = number-1, header.ParentHash4 e9 {8 ?7 L8 s8 E3 G( k
- }
- // 找到了之前的快照,将所有的pedding块头放在它上面
- for i := 0; i 0 {
- if err = snap.store(c.db); err != nil {, @3 w6 q; z9 I
- return nil, err
- }
- log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)" r/ V- b3 ]2 T
- }7 R9 x, z9 l5 d+ ~) U
- return snap, err
- }
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?
- //apply将给定的区块头应用于原始头来创建新的授权快照。
- func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
- //可以传空区块头
- if len(headers) == 0 {
- return s, nil
- } v. F* h1 b" ]! @
- //完整性检查区块头可用性
- for i := 0; i = limit {
- delete(snap.Recents, number-limit)
- }) Z9 ?- u% u! `8 ?, J8 _! d
- // 从区块头中解密出来签名者地址
- signer, err := ecrecover(header, s.sigcache)
- if err != nil {
- return nil, err
- }
- if _, ok := snap.Signers[signer]; !ok {
- return nil, errUnauthorized/ N$ }7 \ Q" b3 a+ U$ Q
- }5 }3 U/ e' O4 B" u) E1 ?
- for _, recent := range snap.Recents {
- if recent == signer {
- return nil, errUnauthorized/ R! l7 g- h+ S. e7 E
- }3 L( m8 F5 [2 A3 t( P8 A
- }0 T- Y7 L9 P0 w
- snap.Recents[number] = signer
- // 区块头认证,不管该签名者之前的任何投票
- for i, vote := range snap.Votes {
- if vote.Signer == signer && vote.Address == header.Coinbase {& T$ e7 ?- O/ z/ j
- // 从缓存计数器中移除该投票
- snap.uncast(vote.Address, vote.Authorize)0 I$ s* v$ F0 I* U* L- L8 o: w
- // 从按时间排序的列表中移除投票: x1 [ G2 r8 i6 g/ }8 r
- snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)# M- [9 O4 L% p5 N" v+ l- v! k
- break // 只允许一票
- }
- }3 \/ }! V, i# E! Q
- // 从签名者中计数新的投票
- var authorize bool+ E+ R9 t* E/ K+ }6 W5 P& H
- switch {7 ^* ^2 U* @0 B& v t- O
- case bytes.Equal(header.Nonce[:], nonceAuthVote):
- authorize = true
- case bytes.Equal(header.Nonce[:], nonceDropVote):& ^3 r. Z/ i) a1 n
- authorize = false" A* o8 n& k Q9 J% W
- default:
- return nil, errInvalidVote h& n' R3 m# V* o
- }
- if snap.cast(header.Coinbase, authorize) {
- snap.Votes = append(snap.Votes, &Vote{( T, e1 R- _( M, J( V" p9 x
- Signer: signer,$ X; g3 {5 Q' Q, F, _3 E
- Block: number,
- Address: header.Coinbase,
- Authorize: authorize,
- })
- }
- // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
- if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {! h/ M% u5 G! C3 w* N3 ?
- if tally.Authorize {5 v z& a, ^# n2 y
- snap.Signers[header.Coinbase] = struct{}{}( s. h3 Q7 M2 t) G, u3 L- i' L
- } else {
- delete(snap.Signers, header.Coinbase)
- // 签名者列表缩减,删除最近剩余的缓存
- if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {
- delete(snap.Recents, number-limit)) e# l% \( e0 u- M
- }5 ]4 ~% e( ^* Q) v' I) C, z, X
- for i := 0; i
Snapshot.apply()方法的主要部分是迭代处理每个header对象,首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定。更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之。该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息。在所有Header对象都被处理完后,Snapshot内部的Number,Hash值会被更新,表明当前Snapshot快照结构已经更新到哪个区块了。
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。
- // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
- func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {
- // 不支持校检创世块
- number := header.Number.Uint64()4 g6 [; Q8 p# R) W3 K
- if number == 0 {. K a$ {8 [9 p( x6 n
- return errUnknownBlock
- }4 P5 X: x( o. k% Z j
- // 检索出所需的区块对象来校检去开头和将其缓存3 v3 f! H, r# G
- snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
- if err != nil {; z+ i4 S* z1 _3 U+ `- a
- return err) z# G k4 R' n+ m: [
- }
- //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址
- signer, err := ecrecover(header, c.signatures)
- if err != nil {. F! |; c7 Z& ^/ }' l8 K* H
- return err! `5 [2 p! o o8 P* h1 D
- }* T& c- e' K/ R) N
- if _, ok := snap.Signers[signer]; !ok {
- return errUnauthorized8 z! H/ e" l6 y: D# n; {7 R# n
- }* s0 t0 Z8 C9 y V
- for seen, recent := range snap.Recents {- w2 M7 W& e L L
- if recent == signer {& V ?2 ^. }0 T9 X" C6 U% o, O4 H
- // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等4 \' q, d, V |! |9 J" \8 R" z
- if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {, M+ d* B1 B2 ^+ ~) l- C4 V
- return errUnauthorized
- }% C% ^% J' M9 K
- }
- }. z4 C% r, M/ v. V p
- // 设置区块难度,参见上面的区块难度部分* u% e8 w3 n3 ~1 Z; s' Y8 L2 U0 a
- inturn := snap.inturn(header.Number.Uint64(), signer)
- if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {9 E5 U! ?, g8 d% d2 m5 M9 K) w5 u
- return errInvalidDifficulty
- }
- if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
- return errInvalidDifficulty
- }4 G( ] f* B4 b1 w) R2 \* t
- return nil# X e7 D4 y, s; d0 |4 e
- }
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?; E3 s% v' f* A* P: o& G
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中
- // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。* t1 D5 o- P8 e: K6 d( x4 H
- func (api *API) Propose(address common.Address, auth bool) {
- api.clique.lock.Lock()
- defer api.clique.lock.Unlock()* W3 W8 M9 `. a: V+ Z
- api.clique.proposals[address] = auth// true:授权,false:移除
- }
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;" f& v) s `% g6 T6 M
- //Clique.Prepare5 Y1 U4 R. T, _4 T* |
- // 抓取所有有意义投票的提案
- addresses := make([]common.Address, 0, len(c.proposals)) V, t/ @$ u; ]9 y# k) n
- for address, authorize := range c.proposals {
- if snap.validVote(address, authorize) { H6 P; b/ X6 A! H" U& h
- addresses = append(addresses, address)$ Z4 W" \6 N% K
- }
- }
- // If there's pending proposals, cast a vote on them
- if len(addresses) > 0 {
- header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。7 t/ E6 m' P1 r6 }! b3 t
- // 通过提案内容来组装区块头的随机数字段。8 ^3 t+ C3 {8 U% m
- if c.proposals[header.Coinbase] {' o3 Q8 \6 Q1 ?# c8 b4 \ H
- copy(header.Nonce[:], nonceAuthVote): o2 }! `+ @, X. ~7 _2 G1 r
- } else {
- copy(header.Nonce[:], nonceDropVote)4 ?! J7 L, |, ~: s' F9 i
- }
- }
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare
- if err := self.engine.Prepare(self.chain, header); err != nil {8 z' a$ d& S1 h$ A( `( E$ w3 [
- log.Error("Failed to prepare header for mining", "err", err)
- return% |" l( p7 Z: m+ b# i2 u; Z
- }
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法( _$ s& W. B1 _; _+ D
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。