以太坊系列 - 挖矿与共识的源码分析(geth版)

以太坊挖矿与共识的源码分析(geth)

Clique PoA技术原理

1.共识机制

决定哪个节点能获得当前区块的记账权

  • PoW(Proof-of-Work): 工作量证明机制就是区块链网络中一堆计算机通过计算随机数的Hash值,谁先找到这个随机数谁就赢的当前区块的记账权。拼算力
  • PoA(Proof of Authority): 授权证明机制就是由一组授权节点来负责新区块的产生和区块验证,按授权节点的地址升序排序后,按顺序轮流获得记账权。 人人平等

Ethereum存在两个PoA测试网,分别是Kovan(PoA算法,Parity专用,Rust 语言)和Rinkeby(Clique PoA共识算法,Geth专用(版本号>=1.6),Go语言)
Kovan
PoA (Immune to spam attacks)
Supported by parity only
Chaindata size 13 GB - Apr 2018
Rinkeby
PoA (Immune to spam attacks)
Supported by geth only
Chaindata size 6 GB - Apr 2018

2.PoA的特点

  • PoW该有的功能PoA都有,使用方法也一样,包括交易,智能合约,发币,DAPP
  • 节点分类
    • 普通节点(node):
      • 普通的以太坊节点,没有区块生成的权利。不能挖矿,无选举权,有被选举权
    • 授权节点(signer):
      • 具有区块生成权利的以太坊节点,即矿工。能挖矿,有选举权和被选举权
  • 授权节点的职能
    • 授权节点(signers)负责产生block。可以由已授权的signer选举(投票超过50%)加入新的signer,或者投票(超过50%)踢出某个signer。
  • 创世块配置
    • 指定出块时间,
    • 预设默认的一批signers(至少一个),
    • 给指定地址设置ETH余额(得预先配置好)
  • ETH币
    • 挖矿无奖励

3.挖矿

对于一个新区块被挖掘出的过程,代码实现上基本分为两个环节:

  • 一是组装出一个新区块,这个区块的数据基本完整,包括成员Header的部分属性,和叔区块组uncles[],以及交易列表txs,并且所有交易已经执行完毕,所有收据(Receipt)也已收集完毕,这部分主要由worker完成;

  • 二是填补该区块剩余的成员属性,比如Header.Difficulty,coinbase等,并完成授权,这些工作是由Agent调用接口实现体Prepare,Seal,利用共识算法来完成的。

第一步 收集数据 worker.commitNewWork()(miner/worker.go)

  • 4种情况会调用到这个函数

  • 程序启动时,执行newWorker方法初始化worker对象时,调用commitNewWork方法,开始组装新区块。

  • 启动挖矿时,miner.start里会调用worker.start()和worker.commitNewWork()

  • 网络接收到其他矿工广播过来的新区块,该区块验证有效插入到区块链后,会产生ChainHeadEvent事件,worker对象的update协程检测到该事件后,会调用commitNewWork方法,开始生成新的区块。

  • 矿工自己生成新的区块并入链后,会向全网广播NewMinedBlockEvent,同时也会给自己发一个ChainHeadEvent,之后就跟上面的一样

以太坊系列 - 挖矿与共识的源码分析(geth版)
以太坊系列 - 挖矿与共识的源码分析(geth版)

  • commitNewWork()做了什么?
    • 准备新区块的Header属性,可确定的有Time,Number,ParentHash,其余留待之后共识算法中确定
    • 调用engine.Prepare()函数,完成部分Header对象的准备
    • 准备新区块的交易列表,并执行这些交易
    • 准备新区块的叔块uncles[]
    • 调用engine.Finalize()函数,对新区块“定型”,填充Header对象 Root(默克尔根),TxHash(默克尔根), ReceiptHash(默克尔根), UncleHash等属性。
    • 把创建的Work对象,通过channel发送给每一个登记过的Agent,进行后续的挖掘。
      worker.push() -> CpuAgent.update()

第二步 授权 CpuAgent(miner/agent.go)

  • 和共识有关的主要是这几个函数Start(),update()和mine()
    • CpuAgent.Start() 用来启动CpuAgent.update()协程
      • miner.New() -> miner.Register(NewCpuAgent) -> (if Mining) agent.Start() -> *.update()
      • miner.Start() -> worker.start() -> agent.Start() -> *.update()
    • CpuAgent.update() 用来监听worker.commitNewWork()结束前通过worker.push()发出的channel
      • worker.push() -> CpuAgent.update() -> CpuAgent.mine()
    • CpuAgent.mine() 利用Engine实现体的共识算法对传入的Block进行最终的授权, 如果成功,就将Block同Work一起通过channel发还给worker,那边worker.wait()会接收并处理
      • CpuAgent.mine() -> engine.Seal() -> worker.wait()

以太坊系列 - 挖矿与共识的源码分析(geth版)

到此,形成一个大的消息循环, 产生出了新区块,哦也!!!

4.共识

不过真正的主角还未出场,注意到这三个函数engine.Prepare() , engine.Finalize() , engine.Seal() ,它们是共识算法对外暴露的接口.该接口有两种实现体,即PoW和PoA
以太坊系列 - 挖矿与共识的源码分析(geth版)

5.Clique PoA的实现

POA 共识算法通过复用当前以太坊区块头中可用的 Extra 字段,将签名者列表(epoch块)和当前节点的签名者对该区块头的签名数据写入其中。更新一个动态的签名者列表的方法是复用区块头中的 Coinbase 和 Nonce 字段,以创建投票方案。
以太坊系列 - 挖矿与共识的源码分析(geth版)

投票规则设定

  • 投票方必须是已认证的
  • 有效投票必须满足:被投票地址的新认证状态与其现状相反。
  • 任意地值A只能给地址B投一张票
  • 只有获得记账权时,才能将票投出去
  • 不允许反复连续投 (由于有最近签名者的限制,在SIGNER_COUNT / 2 + 1个块内,签名者只能签署一个块)

共识的过程 也是 动态管理所有认证地址的过程

  • 通过API提供的方法,插入或删除投票(未上链,只缓存在本地节点内存中)
    • clique.Propose(address,bool)
    • clique.Discard(address)
  • 通过clique.Prepare() 方法,从本地众多未上链的有效投票中,随机出一张票,准备上链
    • 遍历最近一次缓存快照之后的块(最多checkpointInterval=1024个块),更新票数和授权地址列表
    • clique.Prepare() -> clique.snapshot() -> Snapshot.apply()
    • 填充header对象的Coinbase,Nonce,Difficulty,Extra(预留后缀,用于Seal()存入当前块签名者的签名)
  • 通过clique.Finalize() 方法,对新区块“定型”,计算header.Root的值(默克尔根),丢弃叔块,且不给挖矿奖励
  • 通过clique.Seal()方法,生成签名到header.Extra的后缀中
    • 更新票数和授权列表,保证即将充当公钥的地址在最新的Signers中
    • clique.Seal() -> clique.snapshot() -> Snapshot.apply()
    • 依据header.Difficulty的值,如果优先级低,则随机延迟 rand((SIGNER_COUNT / 2 + 1) * 500) ms 再签名

以太坊系列 - 挖矿与共识的源码分析(geth版)

Snapshot.apply() 做了什么

遍历传入的所有Header对象,是一个遍历历史区块头的过程

  • 首先从数字签名中恢复出签名所用公钥,转化为common.Address类型,作为signer地址。数字签名(signagure)长度65 bytes,存放在Header.Extra[]的末尾。
  • 如果signer地址是尚未认证的,则直接退出本次迭代;如果是已认证的,则记名投票+1。所以一个父区块可添加一张记名投票,signer作为投票方地址,Header.Coinbase作为被投票地址,投票内容authorized可由Header.Nonce取值确定
  • 更新投票统计信息。如果被投票地址的总投票次数达到已认证地址个数的一半,则通过之
  • 该被投票地址的认证状态立即被更改,根据是何种更改,相应的更新缓存数据,并删除过时的投票信息

以太坊系列 - 挖矿与共识的源码分析(geth版)

总结

Clique(PoA)利用数字签名算法完成Seal操作,签名所用公钥必须是已认证的。所有认证地址基于投票规则进行动态管理,记名投票由不记名投票和投票方地址随机组合而成,杜绝重复的不记名投票,严格限制外部代码恶意操纵投票数据。

参考文章

https://blog.****.net/teaspring/article/details/78050274

Clique联盟链搭建

https://blog.****.net/liuzhijun301/article/details/80784529