- func (s *Ethereum) StartMining(local bool) error {
- eb, err := s.Etherbase()//用户地址
- if err != nil {
- log.Error("Cannot start mining without etherbase", "err", err)6 B' D8 u# ^' R6 H& L
- return fmt.Errorf("etherbase missing: %v", err)% R6 ]9 C7 P c3 `8 j0 n6 z
- }
- if clique, ok := s.engine.(*clique.Clique); ok {
- //如果是clique共识算法
- wallet, err := s.accountManager.Find(accounts.Account{Address: eb}) // 根据用它胡地址获取wallet对象& T- o( ~! j; o& A
- if wallet == nil || err != nil {
- log.Error("Etherbase account unavailable locally", "err", err), {6 e! `7 q, ?; z
- return fmt.Errorf("signer missing: %v", err)
- }0 U& n7 ]( q, g* f: W( d- c% Y
- clique.Authorize(eb, wallet.SignHash) // 注入签名者以及wallet对象获取签名方法
- }
- if local {
- // 如果本地CPU已开始挖矿,我们可以禁用引入的交易拒绝机制来加速同步时间。CPU挖矿在主网是荒诞的,所以没有人能碰到这个路径,然而一旦CPU挖矿同步标志完成以后,将保证私网工作也在一个独立矿工结点。
- atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)1 G; l9 e2 U% e$ }% l: P
- }$ K+ l( D& \; r* W- b+ [ k/ ?/ l
- go s.miner.Start(eb): y2 Y4 H \! ], L3 ]' `
- return nil. _8 ` l/ s, H! S
- }
这个StartMining会在miner.start前调用,然后通过woker -> agent -> CPUAgent -> update -> seal 挖掘区块和组装(后面会写单独的文章来对挖矿过程做源码分析)。$ w4 R0 t! n0 K; c, u/ _3 H* r! r
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) (chan; B6 C3 ?1 R9 z, a% H0 y: z
Engine.Seal()函数可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等。对于挖掘一个新区块来说,所有相关代码里 Engine.Seal()是其中最重要最复杂的一步,所以这里我们首先来看下Clique 结构体:
- type Clique struct {
- config *params.CliqueConfig // 共识引擎配置参数0 h3 p9 E! z: L; x" ]# O# j+ \
- db ethdb.Database // 数据库,用来存储和获取快照检查点5 `* [8 t$ Y l
- recents *lru.ARCCache // 最近区块快照,加速快照重组6 f6 n6 g3 q4 e6 j
- signatures *lru.ARCCache // 最近区块签名,加速挖矿
- proposals map[common.Address]bool // 目前正在推送的提案' R9 g6 o4 T* k. |( s
- signer common.Address // 签名者的以太坊地址. q) E; {+ n# x2 Z$ C; M
- signFn SignerFn // 授权哈希的签名方法
- lock sync.RWMutex // 用锁来保护签名字段
- }
顺便来看下CliqueConfig共识引擎的配置参数结构体:
- type CliqueConfig struct {5 E8 s# `4 x8 R5 M+ d( O* J
- 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) {
- c.lock.Lock()
- defer c.lock.Unlock()2 F% B! ~1 x& O! Y9 o
- // 这个方法就是为clique共识注入一个签名者的私钥地址已经签名函数用来挖出新块
- c.signer = signer" f0 \* S* t2 `0 m( T
- c.signFn = signFn
- }
再来看Clique的Seal()函数的具体实现:( t& D+ T7 ~* @5 U$ P5 D! v
//通过本地签名认证创建已密封的区块& j% H, O2 G/ H H( h
func (c *Clique) Seal(chain consensus.ChainReader, block *types.Block, stop number-limit {2 c2 s* @7 b: P
log.Info("Signed recently, must wait for others")
; a: {; O/ N2 K5 k e/ [: s
Seal是共识引擎的入口之一,该函数通过clique.signer对区块签名6 i, R8 L u/ ~6 i% S" V
signer不在snapshot的signer中不允许签名8 W6 H7 f# J7 M8 W0 D4 I3 i! b
signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名5 [; x, C4 o( u' N+ J: l
签名存放在Extra的extraSeal的65个字节中
关于机会均等 为了使得出块的负载(或者说是机会)对于每个认证节点尽量均等,同时避免某些恶意节点持续出块,clique中规定每一个认证节点在连续SIGNER_LIMIT个区块中,最多只能签发一个区块,也就是说,每一轮中,最多只有SIGNER_COUNT - SIGNER_LIMIT个认证节点可以参与区块签发。 其中SIGNER_LIMIT = floor(SIGNER_COUNT / 2) + 1,SIGNER_COUNT表示认证节点的个数。/ G$ @0 ^0 ^& H+ |) F$ b8 r
- //snap.Signers是所有的认证节点
- for seen, recent := range snap.Recents {: M8 p# \" b" d' _( i6 H
- if recent == signer {2 D$ U4 A. s, L2 G/ o. B
- if limit := uint64(len(snap.Signers)/2 + 1); number number-limit {( H6 M6 m' j, x( }, e U
- log.Info("Signed recently, must wait for others")
$ v" P! c. N) D5 v* }. t
在保证好节点的个数大于坏节点的前提下,好节点最少的个数为SIGNER_LIMIT(大于50%),坏节点最多的个数为SIGNER_COUNT - SIGNER_LIMIT(小于50%)。一个节点在SIGNER_LIMIT这个时间窗口内最多只能签发一个区块,这就使得恶意节点在不超过50%的情况下,从理论上无法一直掌握区块的签发权。
关于难度计算 为了让每个认证节点都有均等的机会去签发一个区块,每个节点在签发时都会判断本节点是不是本轮的inturn节点,若是inturn节点,则该节点产生的区块难度为2,否则为1。每一轮仅有一个节点为inturn节点。
diffInTurn = big.NewInt(2)
diffNoTurn = big.NewInt(1) ; Y. C- ^% ?* k! k4 P
当inturn的结点离线时,其他结点会来竞争,难度值降为1。然而正常出块时,limit中的所有认证结点包括一个inturn和其他noturn的结点,clique是采用了给noturn加延迟时间的方式来支持inturn首先出块,避免noturn的结点无谓生成区块,上面的延时代码段已经有提现了。 判断是否为inturn的节点,将本地维护的认证节点按照字典序排序,若当前区块号除以认证节点个数的余数等于该节点的下标,则该节点为inturn节点。代码实现在 snapshot.go中:
// 通过给定的区块高度和签发者返回该签发者是否在轮次内7 }3 s6 W/ X: ^5 F
- func (s *Snapshot) inturn(number uint64, signer common.Address) bool {
- signers, offset := s.signers(), 09 f G8 j8 s# u
- for offset
Seal()代码中有获取快照,然后从快照中来检查授权区块签名者的逻辑,那么我们继续来看下Snapshot,首先看下Snapshot的结构体:
// Snapshot对象是在给定时间点的一个认证投票的状态- \: U0 i3 b+ Y' I- r
- type Snapshot struct {
- config *params.CliqueConfig // 共识引擎配置参数
- sigcache *lru.ARCCache // 签名缓存,最近的区块签名加速恢复。
- Number uint64 `json:"number"` // 快照建立的区块号) x t: U$ r7 r" ]' ], ]
- 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"` // 当前的投票结果,避免重新计算。" ?: C1 T3 F* l7 \6 G4 {: k
- }
快照Snapshot对象中存在投票的Votes和记票的Tally对象:
- // Vote代表了一个独立的投票,这个投票可以授权一个签名者,更改授权列表。: p) O8 a6 Q2 z, q% v
- type Vote struct {5 N. y4 r5 h* ]* [2 R6 ?
- Signer common.Address `json:"signer"` // 已授权的签名者(通过投票)9 M) a$ ^. ~8 H7 w G: r( @) v
- Block uint64 `json:"block"` // 投票区块号$ d7 C! d3 o, G) h4 X* J# B
- Address common.Address `json:"address"` // 被投票的账户,修改它的授权
- Authorize bool `json:"authorize"` // 对一个被投票账户是否授权或解授权4 B+ i% d) Y' x3 C8 j
- }! w( v( ^$ X4 T `8 ?' ~
- // Tally是一个简单的用来保存当前投票分数的计分器
- type Tally struct {. A _, N" Y n; Z
- Authorize bool `json:"authorize"` // 授权true或移除false
- Votes int `json:"votes"` // 该提案已获票数2 D I# W. V0 E4 w' Z
- }
Snapshot是一个快照,不仅是一个缓存,而且存储了最近签名者的map loadSnapshot用来从数据库中加载一个已存在的快照:+ {/ g% E {' z; @
- func loadSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, db ethdb.Database, hash common.Hash) (*Snapshot, error) {
- //使用Database接口的Get方法通过Key来查询缓存内容; u4 u+ b7 v! ?1 E5 F6 O
- blob, err := db.Get(append([]byte("clique-"), hash[:]...))% t' B7 F$ G: [: Q+ x! _# {
- if err != nil {
- return nil, err6 I/ B: X. R7 S% U, W5 W
- }
- snap := new(Snapshot)
- if err := json.Unmarshal(blob, snap); err != nil {1 h! A" d1 s7 Z7 k) l" k y; |) T
- return nil, err
- }/ `6 D0 F% D1 m5 v0 V; R2 s
- snap.config = config
- snap.sigcache = sigcache; E& }5 t& c l) P6 F
- return snap, nil; r' F6 ]' J: `# ?3 m
- }
newSnapshot函数用于创建快照,这个方法没有初始化最近的签名者集合,所以只使用创世块:
- func newSnapshot(config *params.CliqueConfig, sigcache *lru.ARCCache, number uint64, hash common.Hash, signers []common.Address) *Snapshot {1 P G" \3 o6 @, z
- //组装一个Snapshot对象
- snap := &Snapshot{
- config: config,& Q- l- X9 Z2 X1 W7 ~
- sigcache: sigcache,- @, b6 Z5 Q* g( y# [
- Number: number,, j' r% q+ d6 I6 w+ U/ B, y
- Hash: hash,7 V" Y+ r. Y. @$ Z
- Signers: make(map[common.Address]struct{}),) } G/ O- D2 Y0 r b. W, U9 E4 C
- Recents: make(map[uint64]common.Address),# U. A: n0 @% g4 S: x' C
- Tally: make(map[common.Address]Tally),5 u! z- e- s, |5 r
- }
- for _, signer := range signers {
- snap.Signers[signer] = struct{}{}( l" [% L/ ]' \7 q8 U
- }5 ~& a& G( t% `: n6 p5 M
- return snap
- }
继续看下snapshot函数的具体实现:
- // 快照会在给定的时间点检索授权快照! U8 B% e. s. g. |$ k" N
- func (c *Clique) snapshot(chain consensus.ChainReader, number uint64, hash common.Hash, parents []*types.Header) (*Snapshot, error) {
- // 在内存或者磁盘上查找一个快照来检查检查点checkpoints/ v1 ?0 O g. ^
- var (# ]/ C( x" w& L- G) Z: x
- headers []*types.Header //区块头' |3 i4 u! u$ o O6 i
- snap *Snapshot //快照对象8 A$ ^& v- Y6 _0 O/ j
- )9 t$ \: y. r" x. H: c+ \0 t; o) k
- for snap == nil {5 c" m# h0 e* E' W
- // 如果在内存中找到快照时,快照对象从内存中取
- if s, ok := c.recents.Get(hash); ok {8 [1 a, m. o" J) e
- snap = s.(*Snapshot)
- break
- }7 r1 w X1 N- K2 U; A4 B5 J2 y2 Z
- // 如果在磁盘检查点找到快照时8 s9 _* L+ `) v- r# o
- if number%checkpointInterval == 0 { //checkpointInterval = 1024 表示投票快照保存到数据库的区块的区块号 D' n1 T; [0 T1 z# l& }6 F: Q
- if s, err := loadSnapshot(c.config, c.signatures, c.db, hash); err == nil {3 I* K- ]( M; V% N0 Q* c A
- log.Trace("Loaded voting snapshot form disk", "number", number, "hash", hash)
- snap = s, @1 P% x! c3 j
- break+ \- y% o$ E `& X) W7 c- S
- }: e* v U& W' e2 L8 K4 T
- }$ o% ~8 l. D7 s6 Q! n. m& ?3 `. S1 ^8 }
- // 如果在创世块,则新建一个快照" G3 p" y: T! J& c( u
- if number == 0 {
- genesis := chain.GetHeaderByNumber(0). S% N! L: Y$ ] s" z- Q, j
- if err := c.VerifyHeader(chain, genesis, false); err != nil {
- return nil, err) v& V0 E) }+ w3 F
- }
- signers := make([]common.Address, (len(genesis.Extra)-extraVanity-extraSeal)/common.AddressLength)
- for i := 0; i 0 {
- // 如果我们有明确的父,从那里挑选(强制执行)
- header = parents[len(parents)-1]) l' k$ F, x o) m8 q( U
- if header.Hash() != hash || header.Number.Uint64() != number {" E7 p/ V. F, s% r4 D1 r& o
- return nil, consensus.ErrUnknownAncestor
- }6 @8 \2 G$ X0 ~# Q; _" C& l# @
- parents = parents[:len(parents)-1]
- } else {
- // 没有明确的父(或者没有更多的父)转到数据库获取
- header = chain.GetHeader(hash, number)
- if header == nil {
- return nil, consensus.ErrUnknownAncestor
- }9 Q3 p4 a( G' D3 z) M
- }8 J% A$ o2 S. R. V f
- headers = append(headers, header)
- number, hash = number-1, header.ParentHash
- }3 w# H5 k: I6 b6 Q; N6 G
- // 找到了之前的快照,将所有的pedding块头放在它上面8 O& Y4 ^# F, P4 T& B
- for i := 0; i 0 {# e+ H! I2 H; H3 j( A+ j
- if err = snap.store(c.db); err != nil {
- return nil, err
- }9 M* V/ }. g, |8 C3 W* w! m: C
- log.Trace("Stored voting snapshot to disk", "number", snap.Number, "hash", snap.Hash)' w+ O& W: T$ ]/ H" d0 O
- }% p% S1 H7 i$ [* M% T
- return snap, err
- }
在snapshot中,snap.apply通过区块头来创建一个新的快照,这个apply中主要做什么操作?# Z* a( w1 l, p( w. Z4 a# |
- //apply将给定的区块头应用于原始头来创建新的授权快照。( V! _: p7 e/ c5 X3 a2 B( r9 C
- func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
- //可以传空区块头
- if len(headers) == 0 {' G3 W# S- q; M
- return s, nil; L1 g5 g1 F5 ~4 J8 i. L: Y/ ]
- }
- //完整性检查区块头可用性) X# g. m+ J. y, X1 t
- for i := 0; i = limit {
- delete(snap.Recents, number-limit)
- }# r5 o% K* h( U
- // 从区块头中解密出来签名者地址/ C2 E8 e" G7 g- P1 x$ B) f- d
- signer, err := ecrecover(header, s.sigcache)9 o9 Q% _0 n9 p6 H& V% l. R& w
- if err != nil {% b P: ]) g7 r' ]: T! n& {
- return nil, err
- }0 t1 P) i1 J2 g6 Y
- if _, ok := snap.Signers[signer]; !ok {: U& o1 I2 L4 B# y- X+ ~
- return nil, errUnauthorized, Q6 g, q$ P7 Q
- }
- for _, recent := range snap.Recents {
- if recent == signer {
- return nil, errUnauthorized
- }, a! V3 C% [; ^* d
- }+ e% G) E3 @9 g( E
- snap.Recents[number] = signer& `: m6 t0 A5 M3 e& y8 ^
- // 区块头认证,不管该签名者之前的任何投票; @ F$ d$ g4 m" l. t4 \. w
- for i, vote := range snap.Votes {
- if vote.Signer == signer && vote.Address == header.Coinbase {% J, `8 [ Q1 j U* }
- // 从缓存计数器中移除该投票
- snap.uncast(vote.Address, vote.Authorize)/ Y: }; I4 Q5 Z; c, X; {' @$ P
- // 从按时间排序的列表中移除投票& x5 B& ?) P5 R; a7 C7 `- z$ S3 t
- snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...)" y" M1 g/ }0 k! |- i' B5 R* T
- break // 只允许一票
- }' l2 O1 H' Y, S Z% T$ Q
- }8 H5 T( C' x7 f( Z& D' \
- // 从签名者中计数新的投票4 r3 r7 w2 k! ^3 S2 s
- var authorize bool. N! w6 x' x' R
- switch {+ C- D7 ?' R0 u( n* Z
- case bytes.Equal(header.Nonce[:], nonceAuthVote):
- authorize = true
- case bytes.Equal(header.Nonce[:], nonceDropVote):9 [+ y. P, y) j
- authorize = false0 n& _( d6 `+ |7 v, Z
- default:
- return nil, errInvalidVote, x" o S, V$ G( S; r
- }- ]% j/ E$ n# d3 P7 p
- if snap.cast(header.Coinbase, authorize) {' ?2 h& y) {9 w2 U8 N, y- F
- snap.Votes = append(snap.Votes, &Vote{
- Signer: signer,
- Block: number,9 S8 p& [/ n! K; N6 L5 L# y! c
- Address: header.Coinbase,
- Authorize: authorize,
- })
- }
- // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表
- if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 {
- if tally.Authorize {9 k0 \0 c' C% J: P, m
- snap.Signers[header.Coinbase] = struct{}{} n% f+ x {& w0 a
- } else {
- delete(snap.Signers, header.Coinbase)& }2 r6 I( s2 Z' ^3 a
- // 签名者列表缩减,删除最近剩余的缓存7 Z8 K* ?+ S- C* q# ^- x) P
- if limit := uint64(len(snap.Signers)/2 + 1); number >= limit {! Z2 |( [2 Z: Y D9 E8 x& @7 H
- delete(snap.Recents, number-limit)) T! ^+ \& z0 b8 i+ Z, |5 f
- }
- 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快照结构已经更新到哪个区块了。! Z2 B. J1 o: H/ C. k
区块验证的过程是普通节点在收到一个新区块时,会从区块头的extraData字段中取出认证节点的签名,利用标准的spec256k1椭圆曲线进行反解公钥信息,并且从公钥中截取出签发节点的地址,若该节点是认证节点,且该节点本轮拥有签名的权限,则认为该区块为合法区块。verifySeal是被SubmitWork(miner/remote_agent.go) 来调用,SubmitWork函数尝试注入一个pow解决方案(共识引擎)到远程代理,返回这个解决方案是否被接受。(不能同时是一个坏的pow也不能有其他任何错误,例如没有工作被pending)解决方案有效时,返回到矿工并且通知接受结果。6 ?( v6 t4 X% {& h# ~
- // 检查包头中包含的签名是否满足共识协议要求。该方法接受一个可选的父头的列表,这些父头还不是本地区块链的一部分,用于生成快照
- func (c *Clique) verifySeal(chain consensus.ChainReader, header *types.Header, parents []*types.Header) error {1 r4 E8 j8 u1 k+ F
- // 不支持校检创世块% B0 l9 {" r5 p( h1 L
- number := header.Number.Uint64()/ V$ A1 |, l$ N4 ~: h/ G
- if number == 0 {
- return errUnknownBlock
- }
- // 检索出所需的区块对象来校检去开头和将其缓存& v% X* n I( R# [* l" Z% U
- snap, err := c.snapshot(chain, number-1, header.ParentHash, parents)
- if err != nil {& x8 @) o9 S. D" t7 C
- return err
- }
- //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址/ r. ? i) R1 ^
- signer, err := ecrecover(header, c.signatures)
- if err != nil {; ^; _9 N% e! ?2 g; z
- return err
- }2 X. s0 X/ |9 B8 Z. H
- if _, ok := snap.Signers[signer]; !ok {( A3 j4 \* g# @3 P. I; q
- return errUnauthorized
- }9 }& K9 o: e$ A1 X. I( [
- for seen, recent := range snap.Recents {! _! @* ~/ I1 G( Q* W
- if recent == signer {
- // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等0 T0 k( r, X Y
- if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit {/ H- G# q( [; e0 Q( O- ~9 T
- return errUnauthorized
- }
- }2 }6 Y' V* J9 E5 j
- }, S+ G7 t4 b2 T4 ?4 [: z3 m
- // 设置区块难度,参见上面的区块难度部分/ y2 b3 K E2 G
- inturn := snap.inturn(header.Number.Uint64(), signer)
- if inturn && header.Difficulty.Cmp(diffInTurn) != 0 {) \8 D( E# F6 n# b
- return errInvalidDifficulty
- }
- if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 {1 g$ S: A& B0 K( F; {. h
- return errInvalidDifficulty
- }
- return nil4 m: K& B2 T: |8 n/ a4 @" j
- }
前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?
Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:7 b0 {, g: k9 g8 M5 u5 l
委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中: g1 v& `) e" D. v
- // Propose注入一个新的授权提案,可以授权一个签名者或者移除一个。
- func (api *API) Propose(address common.Address, auth bool) {
- api.clique.lock.Lock()
- defer api.clique.lock.Unlock()+ z5 e% w- A: O2 V$ E2 j9 n
- api.clique.proposals[address] = auth// true:授权,false:移除% A2 t H; v2 v3 R& W* z- o
- }
本地认证节点在一次区块打包的过程中,从purposal池中随机挑选一条还未被应用的purposal,并将信息填入区块头,将区块广播给其他节点;5 f7 S) k! q3 S9 O
- //Clique.Prepare. Q! Q( A6 m+ N8 G
- // 抓取所有有意义投票的提案( `3 `1 m- O6 [6 S Y9 b, J- a
- addresses := make([]common.Address, 0, len(c.proposals))+ _" I) h% T% {, h# ~+ ?. P) _
- for address, authorize := range c.proposals {) t( Q7 J; _' F3 q4 b# N0 Z5 z
- if snap.validVote(address, authorize) {
- addresses = append(addresses, address)1 F% D+ r' o) y3 d- B. A0 i
- }* u& T% F2 |% O4 ?; M
- }' f; d' _3 l+ r6 o% ?6 I5 K
- // If there's pending proposals, cast a vote on them! F9 U# N1 G3 T2 \
- if len(addresses) > 0 {
- header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。& ?3 f4 Y, o/ P9 a
- // 通过提案内容来组装区块头的随机数字段。) A: z; [5 S, o8 O
- if c.proposals[header.Coinbase] {
- copy(header.Nonce[:], nonceAuthVote)9 F/ e# M- G" j
- } else {1 d' ^0 @/ u6 f+ J' T
- copy(header.Nonce[:], nonceDropVote). }+ C- p6 d0 W! t
- }2 N, H. q: }$ _, ~
- }
在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare
- if err := self.engine.Prepare(self.chain, header); err != nil {. t5 {: F# W5 f, A
- log.Error("Failed to prepare header for mining", "err", err)) K' c% T5 D: {. ~+ M+ Q2 `" X
- return
- }
其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法3 P0 d2 G. r; U& I8 a+ H! f; w: P
4 z9 w* T2 V8 P; D& i5 u; y* ` a
3 y% c v F* G c/ i. x
以太坊中除了基于运算能力的POW(Ethash)外,还有基于权利证明的POA共识机制,Clique是以太坊的POA共识算法的实现,这里主要对POA的Clique相关源码做一个解读分析。