以太坊POA共识机制Clique源码分析 您所在的位置:网站首页 以太坊共识机制可随意修改 以太坊POA共识机制Clique源码分析

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

2024-07-13 10:50| 来源: 网络整理| 查看: 265

//apply将给定的区块头应用于原始头来创建新的授权快照。 func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) { //可以传空区块头 if len(headers) == 0 { return s, nil } //完整性检查区块头可用性 for i := 0; i = limit { delete(snap.Recents, number-limit) } // 从区块头中解密出来签名者地址 signer, err := ecrecover(header, s.sigcache) if err != nil { return nil, err } if _, ok := snap.Signers[signer]; !ok { return nil, errUnauthorized } for _, recent := range snap.Recents { if recent == signer { return nil, errUnauthorized } } snap.Recents[number] = signer // 区块头认证,不管该签名者之前的任何投票 for i, vote := range snap.Votes { if vote.Signer == signer && vote.Address == header.Coinbase { // 从缓存计数器中移除该投票 snap.uncast(vote.Address, vote.Authorize) // 从按时间排序的列表中移除投票 snap.Votes = append(snap.Votes[:i], snap.Votes[i+1:]...) break // 只允许一票 } } // 从签名者中计数新的投票 var authorize bool switch { case bytes.Equal(header.Nonce[:], nonceAuthVote): authorize = true case bytes.Equal(header.Nonce[:], nonceDropVote): authorize = false default: return nil, errInvalidVote } if snap.cast(header.Coinbase, authorize) { snap.Votes = append(snap.Votes, &Vote{ Signer: signer, Block: number, Address: header.Coinbase, Authorize: authorize, }) } // 判断票数是否超过一半的投票者,如果投票通过,更新签名者列表 if tally := snap.Tally[header.Coinbase]; tally.Votes > len(snap.Signers)/2 { if tally.Authorize { snap.Signers[header.Coinbase] = struct{}{} } else { delete(snap.Signers, header.Coinbase) // 签名者列表缩减,删除最近剩余的缓存 if limit := uint64(len(snap.Signers)/2 + 1); number >= limit { 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快照结构已经更新到哪个区块了。

区块验证的过程是普通节点在收到一个新区块时,会从区块头的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() if number == 0 { return errUnknownBlock } // 检索出所需的区块对象来校检去开头和将其缓存 snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) if err != nil { return err } //解析授权密钥并检查签署者,ecrecover方法从区块头中反解出Extra字段中签名字符串来获取签名者地址 signer, err := ecrecover(header, c.signatures) if err != nil { return err } if _, ok := snap.Signers[signer]; !ok { return errUnauthorized } for seen, recent := range snap.Recents { if recent == signer { // 签署者是最近的,只有当前块没有移出时才会失败,参见seal中的机会均等 if limit := uint64(len(snap.Signers)/2 + 1); seen > number-limit { return errUnauthorized } } } // 设置区块难度,参见上面的区块难度部分 inturn := snap.inturn(header.Number.Uint64(), signer) if inturn && header.Difficulty.Cmp(diffInTurn) != 0 { return errInvalidDifficulty } if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 { return errInvalidDifficulty } return nil }

前面已经分析了Clique的认证节点的出块和校检的过程,那么如何来区分一个节点是认证节点还是一个普通节点?以及一个授权者列表是如何产生并如何全网同步的?

Clique通过投票机制来确认一个认证节点,投票的范围在委员会中,委员会就是所有节点矿工集合,普通节点没有区块生成权利。矿工的投票流程如下:

委员会节点通过RPC调用Propose,对某节点状态变更,从普通节点变成认证阶段,或者相反,写入到Clique.purposal集合中

// 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,并将信息填入区块头,将区块广播给其他节点;

//Clique.Prepare // 抓取所有有意义投票的提案 addresses := make([]common.Address, 0, len(c.proposals)) for address, authorize := range c.proposals { if snap.validVote(address, authorize) { addresses = append(addresses, address) } } // If there"s pending proposals, cast a vote on them if len(addresses) > 0 { header.Coinbase = addresses[rand.Intn(len(addresses))] //随机挑选一条投票节点的地址赋值给区块头的Coinbase字段。 // 通过提案内容来组装区块头的随机数字段。 if c.proposals[header.Coinbase] { copy(header.Nonce[:], nonceAuthVote) } else { copy(header.Nonce[:], nonceDropVote) } }

在挖矿开始以后,会在miner.start()中提交一个commitNewWork,其中调用上面Prepare

if err := self.engine.Prepare(self.chain, header); err != nil { log.Error("Failed to prepare header for mining", "err", err) return }

其他节点在接收到区块后,取出其中的信息,封装成一个vote进行存储,并将投票结果应用到本地,若关于目标节点的状态更改获得的一致投票超过1/2,则更改目标节点的状态:若为新增认证节点,将目标节点的地址添加到本地的认证节点的列表中;若为删除认证节点,将目标节点的地址从本地的认证节点列表中删除。具体实现可以查看上面的Snapshot.apply()方法

转载请注明: 转载自Ryan是菜鸟 | LNMP技术栈笔记

如果觉得本篇文章对您十分有益,何不 打赏一下

本文链接地址: 以太坊POA共识机制Clique源码分析



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有