- func (s *Ethereum) StartMining(local bool) error {/ O( u2 c5 U2 ~% e! o' I- w
- eb, err := s.Etherbase()//用户地址
- if err != nil {" _. M( v; r8 D; S+ R
- log.Error("Cannot start mining without etherbase", "err", err)" y) f' J8 n3 J- P
- return fmt.Errorf("etherbase missing: %v", err)( B$ |% e! f: w) W7 A
- }4 J: R# x! `- F% n. F: y: f0 N
- if clique, ok := s.engine.(*clique.Clique); ok {6 n/ L' y& `3 b. y6 {
- //如果是clique共识算法
- wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) // 根据用它胡地址获取wallet对象0 n: G/ \% |& G3 ^; \ H
- if wallet == nil || err != nil {( g& V- F+ n/ M7 t
- log.Error("Etherbase account unavailable locally", "err", err)
- return fmt.Errorf("signer missing: %v", err)1 [9 e1 j4 }( {$ M5 i: x! r0 n* P
- }
- clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法
- }6 a9 ~% B5 f) j' s1 Q( f
- if local {+ g/ D. r9 N- c: U
- // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
- atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
- }9 G, Y" f* z4 d8 g8 W1 ]% G2 p
- go s.miner.Start(eb)" J( B; ~: Z4 w' u( c6 v; C0 o
- return nil0 }6 I$ `# m* q' C/ V1 X; h) m
- }
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。
Clique的代码块在go-ethereum/consensus/clique路径下。和ethash一样,在clique.go 中实现了consensus的接口, consensus 定义了下面这些接口:
type Engine interface {
Author(header *types.Header) (common.Address, error)
VerifyHeader(chain ChainReader, header *types.Header, seal bool) error
VerifyHeaders(chain ChainReader, headers []*types.Header, seals []bool) (chan3 e- L! U4 T: d {9 H
Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:! u; Y- y+ X. h. j j& z2 B
- type Clique struct {' K( e( v2 z: k. b. h0 I( r; O
- config *params.CliqueConfig // 共识引擎配置参数
- db ethdb.Database // 数据库,用来存储和获取快照检查点
- recents *lru.ARCCache // 最近区块快照,加速快照重组' g( P$ P+ M- x0 l3 ]( J" W# h* F
- signatures *lru.ARCCache // 最近区块签名,加速挖矿
- proposals map[common.Address]bool // 目前正在推送的提案
- signer common.Address // 签名者的以太坊地址
- signFn SignerFn // 授权哈希的签名方法6 D" ^$ @! c1 ~7 v0 M
- lock sync.RWMutex // 用锁来保护签名字段8 P) P7 B& c$ G) y: @. X
- }
顺便来看下CliqueConfig共识引擎的配置参数结构体:
- type CliqueConfig struct {
- Period uint64 `json:"period"` // 在区块之间执行的秒数(比如出块秒数15s)
- Epoch uint64 `json:"epoch"` // Epoch长度,重置投票和检查点(比如Epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空, 重新开始记录)
- }
在上面的 StartMining中,通过Clique. Authorize来注入签名者和签名方法,先来看下Authorize:
- func (c *Clique) Authorize(signer common.Address, signFn SignerFn) {6 @# l3 H# C/ j. L8 a/ c0 m5 @
- c.lock.Lock()8 U4 ~' H9 J* v7 e) o
- defer c.lock.Unlock()) [6 h7 [3 W4 Q. s
- // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
- c.signer = signer
- c.signFn = signFn
- }
再来看Clique的Seal()函数的具体实现:
//通过本地签名认证创建已密封的区块
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop number-limit {" H4 q' \, U/ z, k5 N- D
log.Info("Signed recently, must wait for others")
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名
signer不在snapshot的signer中不允许签名
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
签名存放在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 {$ z* K- J9 Q+ S! {" }: A$ Q2 I( F
- if limit := uint64(len(snap.Signers)/2 + 1); number number-limit {: `' a+ ~/ l4 ^% A# w) Y, M
- log.Info("Signed recently, must wait for others")
% L; m; o( V" [# B+ G/ C' H
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。8 ?; v/ U* E0 e6 I2 `
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的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中:
// 通过给定的区块高度和签发者返回该签发者是否在轮次内! c% E& z$ G' D! e
- func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
- signers, offset := s.signers(), 0" V) ~: y# m8 \) i; Z
- for offset
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:% h8 c' v: |% p1 w% M8 s
// Snapshot对象是在给定时间点的一个认证投票的状态
- type Snapshot struct {% J+ _3 H6 t% c. e% {, ]
- config *params.CliqueConfig // 共识引擎配置参数
- sigcache *lru.ARCCache // 签名缓存,最近的区块签名加速恢复。8 U. [6 {! _6 \
- Number uint64 `json:"number"` // 快照建立的区块号; Z% _1 ?7 b% Z" R" l% E# q" }
- Hash common.Hash `json:"hash"` // 快照建立的区块哈希
- Signers map[common.Address]struct{} `json:"signers"` // 当下认证签名者的列表
- Recents map[uint64]common.Address `json:"recents"` // 最近担当过数字签名算法的signer 的地址
- Votes []*Vote `json:"votes"` // 按时间顺序排列的投票名单。
- Tally map[common.Address]Tally `json:"tally"` // 当前的投票结果,避免重新计算。! I# O- s2 S$ M3 G
- }
快照Snapshot对象中存在投票的Votes和记票的Tally对象:+ _9 {& F% ^! i( J3 h+ m
- // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。
- type Vote struct {
- Signer common.Address `json:"signer"` // 已授权的签名者(通过投票)
- Block uint64 `json:"block"` // 投票区块号0 Q V7 ?' G( T) K# U; E' A! i
- Address common.Address `json:"address"` // 被投票的账户,修改它的授权2 H; n/ `5 I( G; j- k! B; b
- Authorize bool `json:"authorize"` // 对一个被投票账户是否授权或解授权
- }6 _/ C2 G0 m) ~4 W- e0 p1 k
- // Tally是一个简单的用来保存当前投票分数的计分器2 W1 S4 L- c6 M6 W5 t; a
- type Tally struct {' x! D* S% p& e4 F5 I5 p2 ]
- Authorize bool `json:"authorize"` // 授权true或移除false
- Votes int `json:"votes"` // 该提案已获票数
- }
Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:$ _9 V5 v2 Q. G; [
- func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
- //使用Database接口的Get方法通过Key来查询缓存内容
- blob, err := db.Get(append([]byte("clique-"), hash[:]...))" t/ m& g" x5 E
- if err != nil {, f2 j" S7 [6 U. Y# a5 y5 ]
- return nil, err
- }
- snap := new(Snapshot)8 X- ]. m. V8 q+ s3 F6 ~& t1 s$ _
- if err := json.Unmarshal(blob, snap); err != nil {8 H) _! o, d: s' o: F3 k1 N/ v9 r
- return nil, err
- }
- snap.config = config1 n9 {# o4 T* C# u3 R
- snap.sigcache = sigcache! m+ j }( `8 y7 ~1 H8 i
- return snap, nil
- }
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
- func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {
- //组装一个Snapshot对象2 A6 O; r2 R% Z3 F# O
- snap := &Snapshot{# ?: S- v; c6 ^5 m2 z% e6 x
- config: config,8 a. K: t p, U
- sigcache: sigcache,
- Number: number,
- Hash: hash,
- Signers: make(map[common.Address]struct{}),
- Recents: make(map[uint64]common.Address),
- Tally: make(map[common.Address]Tally),
- }% W! A5 a6 V" ]+ b* M
- for _, signer := range signers {
- snap.Signers[signer] = struct{}{}
- }
- return snap
- }
继续看下snapshot函数的具体实现:
- // 快照会在给定的时间点检索授权快照/ G8 {* Y- {: L x. q$ t+ y
- func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
- // 在内存或者磁盘上查找一个快照来检查检查点checkpoints/ y5 Z9 }& G# W0 W1 h8 d2 H
- var (
- headers []*types.Header //区块头
- snap *Snapshot //快照对象, G+ @" M* e0 I
- )
- for snap == nil {0 R N* w# V/ }
- // 如果在内存中找到快照时,快照对象从内存中取' c" b+ L& a8 F* w+ A
- if s, ok := c.recents.Get(hash); ok {7 y6 q! B8 M4 N/ U Z
- snap = s.(*Snapshot)5 t7 y: e5 ]# T7 ^2 e% G
- break
- }
- // 如果在磁盘检查点找到快照时
- if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号
- if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {6 [ c' J% Y/ e8 X
- log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
- snap = s: K! w* o$ i$ Z5 E2 M+ y/ c/ g' k0 W
- break
- }7 {" b3 S# p, D' N
- }; z3 v, J; q3 V% A: w
- // 如果在创世块,则新建一个快照8 f& y/ _- K* {
- if number == 0 {
- genesis := chain.GetHeaderByNumber(0)% I3 X8 d% A8 T/ j7 E
- if err := c.VerifyHeader(chain, genesis, false); err != nil {
- return nil, err
- }
- signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
- for i := 0; i 0 {
- // 如果我们有明确的父,从那里挑选(强制执行)& h# z5 p: s5 V" ]
- header = parents[len(parents)-1]
- if header.Hash() != hash || header.Number.Uint64() != number {+ O* _" B, s* H. X
- return nil, consensus.ErrUnknownAncestor
- }% e) P* Q( b! n! }# f, H; I! I
- parents = parents[:len(parents)-1]. b/ E! C5 K4 z9 h
- } else {/ u6 G/ A/ f, @ P0 W& X
- // 没有明确的父(或者没有更多的父)转到数据库获取. h R9 R9 J5 C" V9 a( b v
- header = chain.GetHeader(hash, number)/ Z. @7 E6 i6 Z6 m; @
- if header == nil {0 {- x9 N# w3 n9 G. K" t! n
- return nil, consensus.ErrUnknownAncestor8 M" D+ D) a' k: R
- }
- }
- headers = append(headers, header)
- number, hash = number-1, header.ParentHash- @9 R+ Y9 k# v( n, q4 Y/ I N
- }
- // 找到了之前的快照,将所有的pedding块头放在它上面
- for i := 0; i 0 {
- if err = snap.store(c.db); err != nil {/ k4 }- Y( A* z7 S; V
- return nil, err
- }
- log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)3 D3 M8 ^6 h" x0 J$ E; Y. o# K8 r
- }) j4 m9 d* T# D" t( O
- return snap, err( G: {5 P5 v0 C& K# X, F' s' J
- }
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?
- //apply将给定的区块头应用于原始头来创建新的授权快照。
- func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
- //可以传空区块头! X. X# g+ B: [* p/ q& ?
- if len(headers) == 0 {
- return s, nil
- }9 N' a. S* g' R9 O3 y" O6 D
- //完整性检查区块头可用性. C1 M4 u7 G5 _+ `* ~6 j% L
- for i := 0; i = limit {
- delete(snap.Recents, number-limit)) Z' S! Q2 r: W- ]1 ]$ E9 K8 x' F
- }5 Q5 x9 O# A4 v1 b( d; {. I
- // 从区块头中解密出来签名者地址) L6 A6 g0 j- Y& U
- signer, err := ecrecover(header, s.sigcache)
- if err != nil {
- return nil, err7 J( V+ K E, Q8 _0 z
- }1 i+ K! j& p: @" I1 M* o' ]8 `: k* E
- if _, ok := snap.Signers[signer]; !ok {9 g- @- \- ^+ D& N8 B/ t `& d
- return nil, errUnauthorized U3 ?5 `7 ]0 m# O' B2 T( j& x
- }
- for _, recent := range snap.Recents {
- if recent == signer {
- return nil, errUnauthorized3 \' V: s$ v- ?. l" V
- }
- }9 K4 Q- V% O1 f R
- snap.Recents[number] = signer
- // 区块头认证,不管该签名者之前的任何投票, {" M' u0 O! C) f" t% w. a) s
- for i, vote := range snap.Votes {* A( M3 `% P8 k6 A9 m) J
- if vote.Signer == signer && vote.Address == header.Coinbase {9 E# g- m" J0 K& B. e5 R
- // 从缓存计数器中移除该投票) }! h: N1 `. k# W1 C
- snap.uncast(vote.Address, vote.Authorize)4 r1 {3 ^- p" f: H j9 f. y7 b
- // 从按时间排序的列表中移除投票4 `; a* l4 [4 z* E8 Q& i4 w) c: c# K
- snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)
- break // 只允许一票
- }
- }: o) I7 T. K0 S. U' m
- // 从签名者中计数新的投票 Z% u7 k0 Z$ J+ u7 \) c3 n- I3 L8 `
- var authorize bool" q5 k+ p8 k" z
- switch {
- case bytes.Equal(header.Nonce[:], nonceAuthVote):
- authorize = true
- case bytes.Equal(header.Nonce[:], nonceDropVote):
- authorize = false
- default:; G1 n$ b1 e$ ?6 k
- return nil, errInvalidVote
- }
- if snap.cast(header.Coinbase, authorize) {
- snap.Votes = append(snap.Votes, &Vote{1 E: w: M! O8 z) @
- Signer: signer,) }2 {5 x; c! [
- Block: number,+ a, v( i5 q9 P
- Address: header.Coinbase,
- Authorize: authorize,# G" ]4 j# R7 b% P9 N
- })& Y' n5 q6 z8 C( C5 B
- }
- // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表& Z: B7 o1 e: k+ Q
- if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
- if tally.Authorize {, @4 u$ O* R- i6 Z' k* p* f/ V
- snap.Signers[header.Coinbase] = struct{}{}0 W1 c' v6 r4 `: D% R
- } else {9 b& L# B0 y4 p+ x: Q8 L
- delete(snap.Signers, header.Coinbase)
- // 签名者列表缩减,删除最近剩余的缓存% a& t- g8 X0 ^7 o7 I4 k
- if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {7 ^& ?& K* v* T
- delete(snap.Recents, number-limit) d( n' D0 ?5 T5 m. u2 c6 N0 y2 _1 I
- }
- 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快照结构已经更新到哪个区块了。$ ]5 d# [1 c( j, _/ }4 U
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。- g9 t. c% E8 |, H5 i8 s; d+ E
- // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
- func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {( u0 ^/ v" a- A& D. H
- // 不支持校检创世块
- number := header.Number.Uint64()0 x2 H% c5 g+ ?# a
- if number == 0 {0 e4 D/ i" y( b0 R7 d1 P
- return errUnknownBlock
- }- ^7 n v# X9 a @7 ~: `
- // 检索出所需的区块对象来校检去开头和将其缓存' C, c4 \/ P% F: ], c
- snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
- if err != nil {# {) ?0 e1 `0 C3 a; P, L. F
- return err
- }
- //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址4 `; J0 i6 b* b
- signer, err := ecrecover(header, c.signatures)
- if err != nil {
- return err. O$ s" |- K. S0 g( M& i* e9 o* ^
- }9 @% p2 M0 ~" ^8 ~/ {/ [ W
- if _, ok := snap.Signers[signer]; !ok {1 ?( ^4 m+ x: B8 `
- return errUnauthorized" Q" W! X2 h1 A! ^8 H
- }
- for seen, recent := range snap.Recents {
- if recent == signer {
- // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等7 k8 ?; m# ?+ x
- if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {2 Y' g6 N9 ^: G5 j6 @
- return errUnauthorized. |! \5 M6 R. v) c/ V4 e! t* F' R
- }4 h1 ^8 N, {' Y% Q. E4 g$ @
- }
- }( F6 {+ q& f0 V" t: E$ N: M
- // 设置区块难度,参见上面的区块难度部分4 a( d% E# W: P
- inturn := snap.inturn(header.Number.Uint64(), signer)* i+ A+ Y# T" Y q1 w
- if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {3 S# g+ L" c2 q5 k7 K- Z) [
- return errInvalidDifficulty
- }: B: F% Z$ U* h
- if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {5 {# p- _- Z( }) B: e" A
- return errInvalidDifficulty1 r* f5 R! X/ v7 z
- }3 |! ~1 q* k% G: R% A* K' N
- return nil% b5 J K+ L2 u) l( z
- }
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下: b. ?3 e+ X7 J9 B: Y) r
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中. F, W/ J* D' `9 [1 V' b3 O4 W0 N- Q
- // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
- func (api *API) Propose(address common.Address, auth bool) {4 `& c/ [; V$ z3 |1 [7 n
- api.clique.lock.Lock()1 Q ?# S7 U) Y6 s2 ]
- defer api.clique.lock.Unlock()
- api.clique.proposals[address] = auth// true:授权,false:移除. T( e- n) X* Q9 e
- }
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;; e2 J; V8 E8 ]9 J6 f% W% u2 O
- //Clique.Prepare# I2 J' }9 N0 ~) f+ H8 _
- // 抓取所有有意义投票的提案
- addresses := make([]common.Address, 0, len(c.proposals))$ Y: k0 v/ @7 J# H# U
- for address, authorize := range c.proposals {; F! R" i; J; T3 \
- if snap.validVote(address, authorize) {
- addresses = append(addresses, address)
- }
- }
- // If there's pending proposals, cast a vote on them; @* O1 U; ~( U" K" c
- if len(addresses) > 0 {' U% J8 [& J, ~! D/ Y2 B
- header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。
- // 通过提案内容来组装区块头的随机数字段。* a. s- W/ H9 T" R
- if c.proposals[header.Coinbase] {
- copy(header.Nonce[:], nonceAuthVote)0 N* ]! ?0 b: x
- } else {) M4 I4 o- ?$ o+ V/ Y2 s- K
- copy(header.Nonce[:], nonceDropVote), _2 k7 K0 Z* R4 W+ x$ J
- }2 b! _# X* \6 _8 Y8 t8 |
- }
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare0 u; K7 T+ a3 ~& X) m" `( ?( x: h
- if err := self.engine.Prepare(self.chain, header); err != nil {
- log.Error("Failed to prepare header for mining", "err", err)( l) c& W/ R6 O2 [
- return3 n" A5 m- Z4 f: L
- }
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法
/ L4 m- L: x. }) j N* f
' Q0 L& j" b, d
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。