- func (s *Ethereum) StartMining(local bool) error {
- eb, err := s.Etherbase()//用户地址
- if err != nil {* d2 `6 X, f/ E1 |7 m9 @
- log.Error("Cannot start mining without etherbase", "err", err), c' e! H- U4 r: u0 l1 n
- return fmt.Errorf("etherbase missing: %v", err)
- }/ B3 m4 E z' I6 K- w
- if clique, ok := s.engine.(*clique.Clique); ok {' s/ d- R2 w5 Z3 A. z: w
- //如果是clique共识算法/ F% V; p& z' V4 G$ ^! R# o- C5 w# c; x
- wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) // 根据用它胡地址获取wallet对象8 U" j, e l: U. z @5 O9 \0 s
- if wallet == nil || err != nil {
- log.Error("Etherbase account unavailable locally", "err", err)
- return fmt.Errorf("signer missing: %v", err). L1 W% Q2 t* d4 q& b2 N5 X
- }
- clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法' o% U p0 K" F) Q H: g
- }
- if local {3 m q9 n; f) w7 m5 S2 e% L
- // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。6 u. q& w6 ]7 ]( {; F; R
- atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)' q+ H9 G5 F/ l7 ], J
- }
- go s.miner.Start(eb)
- return nil
- }
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:1 k; r9 e2 v8 n& i2 [0 I! |. ]0 }3 x
type Engine interface {2 v4 V! Q$ v, V& L4 v
Author(header *types.Header) (common.Address, error)
VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan
Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:
- type Clique struct {% j: p. h$ N6 u: h6 L b) g4 @
- config *params.CliqueConfig // 共识引擎配置参数
- db ethdb.Database // 数据库,用来存储和获取快照检查点/ g2 R1 k1 |" L# L" }7 l
- recents *lru.ARCCache // 最近区块快照,加速快照重组
- signatures *lru.ARCCache // 最近区块签名,加速挖矿1 k' b1 u! A0 d
- proposals map[common.Address]bool // 目前正在推送的提案
- signer common.Address // 签名者的以太坊地址8 }4 k. ^$ S% @6 m' Q; s
- signFn SignerFn // 授权哈希的签名方法
- lock sync.RWMutex // 用锁来保护签名字段, L/ c. f$ [# t$ Z
- }
顺便来看下CliqueConfig共识引擎的配置参数结构体:) v, _6 D! C8 S5 x! N C/ I' c# [
- type CliqueConfig struct {
- Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)' a+ L- U* `) w9 P
- Epoch uint64 `json:"epoch"` // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
- }
在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
- func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {- r- H% B+ e. Y5 a8 I/ r. @
- c.lock.Lock()
- defer c.lock.Unlock()' R0 l" O/ c O4 R. H& n
- // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
- c.signer = signer
- c.signFn = signFn! y7 {& r2 z7 e( W4 }
- }
再来看Clique的Seal()函数的具体实现:8 n4 @' q% S1 O; @$ `
//通过本地签名认证创建已密封的区块8 U! Y" l. T1 X# E2 v! o5 p3 T1 N' D D
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop number-limit {
log.Info("Signed recently, must wait for others")
0 z9 ]6 d% R# Z
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名3 _- M$ c! c# B2 w% Z3 V
signer不在snapshot的signer中不允许签名& g8 b/ G$ U" o
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名" x0 K( Z% t$ v9 c
签名存放在Extra的extraSeal的65个字节中
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。
- //snap.Signers是所有的认证节点
- for seen, recent := range snap.Recents {
- if recent == signer {
- if limit := uint64(len(snap.Signers)/2 + 1); number number-limit {1 i3 K0 x! X' N* B0 U- Q
- 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) , Q3 X9 Y) s9 P# H4 D
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
// 通过给定的区块高度和签发者返回该签发者是否在轮次内) w3 B; }+ n+ b" u; D* E
- func (s *Snapshot) inturn(number uint64, signer common.Address) bool {& [; T2 \( x9 U7 R
- signers, offset := s.signers(), 03 M# Y6 P1 z& N
- for offset
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:
// Snapshot对象是在给定时间点的一个认证投票的状态- U1 p; Q4 `1 E6 X! r, q9 Y
- type Snapshot struct {. w& B4 c8 |: n2 c% C! D! I/ a. Y
- config *params.CliqueConfig // 共识引擎配置参数1 r5 e3 P7 p- O" L. a
- sigcache *lru.ARCCache // 签名缓存,最近的区块签名加速恢复。( |# C9 [6 E3 ^: o! Y1 `
- Number uint64 `json:"number"` // 快照建立的区块号
- Hash common.Hash `json:"hash"` // 快照建立的区块哈希
- Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表4 V) Y! e) Y p; |
- Recents map[uint64]common.Address `json:"recents"` // 最近担当过数字签名算法的signer 的地址% T' V5 Y' P9 n0 j) T3 A
- Votes []*Vote `json:"votes"` // 按时间顺序排列的投票名单。
- Tally map[common.Address]Tally `json:"tally"` // 当前的投票结果,避免重新计算。( w( N! X: i; Y ?+ R; Z4 c6 o/ _
- }
快照Snapshot对象中存在投票的Votes和记票的Tally对象:* S+ K5 b. y \9 I' H
- // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。
- type Vote struct {
- Signer common.Address `json:"signer"` // 已授权的签名者(通过投票)
- Block uint64 `json:"block"` // 投票区块号0 L @4 ^, j- E9 b1 A
- Address common.Address `json:"address"` // 被投票的账户,修改它的授权* Q0 y+ t ] z6 v6 A
- Authorize bool `json:"authorize"` // 对一个被投票账户是否授权或解授权
- }
- // Tally是一个简单的用来保存当前投票分数的计分器1 x' n/ O" P+ f5 f. |
- type Tally struct {
- Authorize bool `json:"authorize"` // 授权true或移除false
- Votes int `json:"votes"` // 该提案已获票数( p4 o5 _/ _$ W, d+ ]) _2 G; l8 R/ L
- }
Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:$ R- d2 {0 m% {9 ^, A9 }
- func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {5 ?4 N& h9 }, n; Y
- //使用Database接口的Get方法通过Key来查询缓存内容$ X6 ?1 ?/ i0 _8 d5 H
- blob, err := db.Get(append([]byte("clique-"), hash[:]...))
- if err != nil {6 m8 i5 ^/ x: a7 u! Q
- return nil, err5 M3 g9 |, b8 F" ~( G
- }
- snap := new(Snapshot)
- if err := json.Unmarshal(blob, snap); err != nil {9 C. m! I6 B1 M! L9 J* l) T
- return nil, err) a0 t9 X! u2 K, v7 h
- }
- snap.config = config- M; |% d) L) @0 U- P9 [$ x
- snap.sigcache = sigcache( m- S. W- O4 z+ `" h
- return snap, nil" N X4 s, m/ ] x5 u! p7 J
- }
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块: ]3 g7 H0 c f6 [ l& {7 E
- func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {2 t5 [9 A) w: v& m+ d+ O
- //组装一个Snapshot对象
- snap := &Snapshot{
- config: config,
- sigcache: sigcache,$ n A( n- S. H3 e! t1 ]! \
- Number: number,; ]4 @, v: N3 ~$ O' R# }
- Hash: hash,- Y6 V9 F( Y& Y/ F% |
- Signers: make(map[common.Address]struct{}),
- Recents: make(map[uint64]common.Address),
- Tally: make(map[common.Address]Tally),3 \. s! g9 w' l8 ^7 ]- g
- }
- for _, signer := range signers {
- snap.Signers[signer] = struct{}{}9 t* y8 U& k/ s5 s
- }
- return snap
- }
继续看下snapshot函数的具体实现: C9 z* @ I1 B9 l2 G6 X
- // 快照会在给定的时间点检索授权快照
- func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
- // 在内存或者磁盘上查找一个快照来检查检查点checkpoints
- var (. n! F" m: X* P4 U% J
- headers []*types.Header //区块头
- snap *Snapshot //快照对象
- )1 P6 E+ v0 O. w: R s% k. w3 B/ S
- for snap == nil {; D" e( t, E% Y H
- // 如果在内存中找到快照时,快照对象从内存中取
- if s, ok := c.recents.Get(hash); ok {
- snap = s.(*Snapshot)& L$ x* Z9 E" S# ^) ~
- break7 I% j- Q" _1 w; ^" n
- }
- // 如果在磁盘检查点找到快照时
- if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号( S' y0 X: X9 N
- if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {* G" @# Y: p) T! @
- log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
- snap = s
- break2 t8 k7 C! N7 j- F+ y
- }
- }9 n, _& L9 r0 i; Y
- // 如果在创世块,则新建一个快照0 u1 I+ |/ s# p9 w* M& z% T
- if number == 0 {/ K) k& K4 }2 ~4 @# H/ r+ M1 {
- genesis := chain.GetHeaderByNumber(0)
- if err := c.VerifyHeader(chain, genesis, false); err != nil { g0 H$ ~7 y- K
- return nil, err
- }
- signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength); S5 K/ Q; P% Y4 d) z
- for i := 0; i 0 {
- // 如果我们有明确的父,从那里挑选(强制执行)( [; }- Z$ f( M) {! J1 S
- header = parents[len(parents)-1]" x" j- l Z2 h$ ~& n
- if header.Hash() != hash || header.Number.Uint64() != number {
- return nil, consensus.ErrUnknownAncestor# H. ?% V( Q0 g7 G" _1 {% Z
- }) J! m3 \5 D' E0 d+ m! S8 C
- parents = parents[:len(parents)-1]
- } else {
- // 没有明确的父(或者没有更多的父)转到数据库获取
- header = chain.GetHeader(hash, number). e8 T1 w0 D& U# G/ d3 X2 s
- if header == nil {: C4 \# _6 L% }
- return nil, consensus.ErrUnknownAncestor/ c2 h; K' Z; i+ M4 o- ^; J
- }
- }
- headers = append(headers, header)5 ?' d0 e1 m0 ] [0 v
- number, hash = number-1, header.ParentHash
- }
- // 找到了之前的快照,将所有的pedding块头放在它上面
- for i := 0; i 0 {# h) j5 {) C$ c: n) \: B; }! g& Q
- if err = snap.store(c.db); err != nil {0 J" ~- d: |& g% [! B3 S- F2 e7 P
- return nil, err6 x+ M3 Y. _' d: j1 Z- G
- }; b2 P* L! H) G0 y& f0 n7 ~
- log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)3 {' O: D- L0 y
- }
- return snap, err
- }
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?& v2 Y$ n0 w/ u* t# W: M8 e
- //apply将给定的区块头应用于原始头来创建新的授权快照。
- func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {3 V: B5 M% ^6 y+ [+ w# c+ e
- //可以传空区块头( ]7 E: |) G' t8 o( s K! d
- if len(headers) == 0 {
- return s, nil
- }9 e. B% C( b' B4 i6 C( c: ?& s* \
- //完整性检查区块头可用性
- for i := 0; i = limit {% z1 u# R. b+ n5 {% P
- delete(snap.Recents, number-limit)
- }
- // 从区块头中解密出来签名者地址
- signer, err := ecrecover(header, s.sigcache)+ {; n' q3 G$ c8 o& v5 p( p0 \
- if err != nil {
- return nil, err" K2 J! p# D3 j v7 z
- }0 m, p: v' L$ E# U
- if _, ok := snap.Signers[signer]; !ok {! p* N) N' Z+ H' N
- return nil, errUnauthorized% S7 _! H8 g5 a% s8 C
- }8 A8 J" p s, [# e, O
- for _, recent := range snap.Recents {
- if recent == signer {+ q$ A# [- t. Y5 [8 m0 |! d2 \3 K
- return nil, errUnauthorized% q7 \; g2 Q' s6 P/ i
- }
- }
- snap.Recents[number] = signer6 K1 U k' r# G' g4 T7 ]
- // 区块头认证,不管该签名者之前的任何投票
- for i, vote := range snap.Votes {7 ~" b: A2 Q3 s' O$ \8 l( y2 t6 N
- if vote.Signer == signer && vote.Address == header.Coinbase {
- // 从缓存计数器中移除该投票. X* {2 B9 B9 r$ u8 _
- snap.uncast(vote.Address, vote.Authorize)- N, l" L# O* M* ]" y
- // 从按时间排序的列表中移除投票
- snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
- break // 只允许一票
- }
- }4 l! |' V: Z6 }( A0 N# B9 d5 \
- // 从签名者中计数新的投票
- var authorize bool+ W/ j% B& ^; D X& {& l* G0 P
- switch {
- case bytes.Equal(header.Nonce[:], nonceAuthVote):- E2 y3 s) w& j0 T) y
- authorize = true3 E& g+ `+ q& u! g
- case bytes.Equal(header.Nonce[:], nonceDropVote):* k% x' z- a% o/ k
- authorize = false
- default:
- return nil, errInvalidVote; _: N% K- ^# u: i
- }
- if snap.cast(header.Coinbase, authorize) {( F* t- Y+ N j4 ~
- snap.Votes = append(snap.Votes, &Vote{
- Signer: signer,
- Block: number,/ M" d' \/ \' ~, k7 w6 S: n' w4 ?
- Address: header.Coinbase,6 u+ j n: n L" Q8 t# J# r0 k% V$ a
- Authorize: authorize,. U! R2 R `, P+ k1 } B3 w
- })3 ~' A3 \* V* _3 u/ M
- }- `! y) J& G- n% ]4 [2 h1 a1 j1 u q
- // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
- if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
- if tally.Authorize {, U4 j A8 M' w3 M
- snap.Signers[header.Coinbase] = struct{}{}
- } else {
- delete(snap.Signers, header.Coinbase)" D2 _0 ~% o9 R2 {
- // 签名者列表缩减,删除最近剩余的缓存2 @3 t: F2 G0 q x: v: l0 t
- if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { R1 t) u( {/ V; |4 K$ t
- delete(snap.Recents, number-limit)
- }
- 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快照结构已经更新到哪个区块了。* n% O# _) r- E. R8 Z' k
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。! T4 \* m4 K% M f: x P
- // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照/ _+ v, w& o1 y) E0 T# f
- func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {+ h! g8 D' y. |2 k" v p
- // 不支持校检创世块7 p& V$ w' s' ~% V# ?' Y$ A
- number := header.Number.Uint64()" Z& r/ m4 |5 O' f6 v
- if number == 0 {) ^( F O; e$ z) ^. x
- return errUnknownBlock; K: {' S, Z' Z
- }& Z- l& _ N+ Q* i, a: W+ i
- // 检索出所需的区块对象来校检去开头和将其缓存
- snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
- if err != nil {
- return err6 j+ @, ?9 Z- Z3 r3 G! w
- }
- //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址4 N" E5 F0 o2 D
- signer, err := ecrecover(header, c.signatures)
- if err != nil {' ^3 {) D/ E/ }4 j# ^' M6 T4 W( _
- return err$ i4 z: Y4 j' [/ Q
- }4 U9 S. j* Z/ N" m* Q1 H6 V8 _
- if _, ok := snap.Signers[signer]; !ok {: d) k1 [8 ^' A2 d7 P# D
- return errUnauthorized
- }
- for seen, recent := range snap.Recents {# N, Q! ] a. O7 w: l
- if recent == signer {: g1 ~, g6 M. p
- // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等
- if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {
- return errUnauthorized
- }9 g5 U G1 P9 Q
- }
- }
- // 设置区块难度,参见上面的区块难度部分
- inturn := snap.inturn(header.Number.Uint64(), signer)
- if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {
- return errInvalidDifficulty
- }1 K) ^4 j. I( v2 }$ ?0 w Z8 ^
- if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {
- return errInvalidDifficulty! ~8 K) X6 U' W$ f3 m: f
- }
- return nil
- }
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中 W s: O* ^- A |9 B2 m
- // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
- func (api *API) Propose(address common.Address, auth bool) {
- api.clique.lock.Lock()
- defer api.clique.lock.Unlock()
- api.clique.proposals[address] = auth// true:授权,false:移除
- }
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;5 o; I$ o) U: C
- //Clique.Prepare" F/ t/ n# S m/ v. R8 e# v1 I( ?, Y/ O
- // 抓取所有有意义投票的提案
- addresses := make([]common.Address, 0, len(c.proposals))
- for address, authorize := range c.proposals {
- if snap.validVote(address, authorize) {
- addresses = append(addresses, address)
- }7 C& u1 N0 \( `& ~& @
- }8 S% j9 ~1 q, j0 q5 `1 {
- // If there's pending proposals, cast a vote on them
- if len(addresses) > 0 {0 [/ [6 z4 _! @! s! o& O+ b
- header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。! t, ^0 O. Y' f% X
- // 通过提案内容来组装区块头的随机数字段。( [, \- o. J Z7 z- F4 L
- if c.proposals[header.Coinbase] {$ n) m! B( U) o! P7 H' B/ l
- copy(header.Nonce[:], nonceAuthVote)' X# L' T4 S9 ~ U( y
- } else {
- copy(header.Nonce[:], nonceDropVote)- U% c3 T1 f0 r( H
- }1 z: M" c k4 E8 Q: s, i
- }
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare
- if err := self.engine.Prepare(self.chain, header); err != nil {
- log.Error("Failed to prepare header for mining", "err", err)5 @2 j }/ U3 d6 S
- return
- }
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法9 l, {1 _8 E, Y
$ `0 w" ~4 Q w7 i' x; i
# {4 ]1 N9 n8 Y. s+ i
, `2 G! H# W7 z. r6 d& V/ n+ V0 t$ ~
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。



