以太坊解析之二——POA共识过程与一些可能的修改方案

以太坊解析之二——POA共识过程与一些可能的修改方案,第1张

以太坊解析之二——POA共识过程

原始版本创建于2021-05-27


文章目录 以太坊解析之二——POA共识过程前言一、工作流程详细解析 二、详细过程1.启动2.同步3.总结4.一些其他问题 三、如果我想修改POA共识机制,把它改成质押(矿机)应该怎么做?1.如何设计惩罚机制2.如何设计激励机制3.质押机制怎么和POA绑定——块包含着质押的信息4.代码如何修改


前言

简单解释一下这个共识:
1、依靠预设好的授权节点(signers),负责产生block
2、由已授权的signer选举(投票超过50%)加入新的signer


一、工作流程

1、启动挖矿后, 该组signers开始对生成的block进行 签名并广播,签名结果 保存在区块头的Extra字段中

2、Extra中更新当前高度已授权的 所有signers的地址 ,因为有新加入或踢出的signer

3、每一高度都有一个signer处于IN-TURN状态, 其他signer处于OUT-OF-TURN状态,
——IN-TURN的signer签名的block会 立即广播 (打包节点-打包signer)、(普通signer),
——OUT-OF-TURN的signer签名的block会 延时 一点随机时间后再广播, 保证IN-TURN的签名block有更高的优先级上链

另外一个关键在于,假定打包节点(IN-TURN signer)作恶,POA限制了同一个signer只能在一定数量的连续区块中负责打包一次

也就是坏人只能在最近的x个区块中负责打包(处于IN-TURN状态)一次(x为signer的总数)

同时由于难度的设定,坏人(IN-TURN状态)发出一个坏区块(有恶意交易,完美的区块——这个坏人在IN-TURN签名过的)的速度要比其他人(OUT-OF-TURN状态)发出一个好区块(没有恶意交易,不完美——需要当前IN-TURN签名的区块)的速度要慢

详细解析

POA的共识:请看图——理想共识过程
1、每个signer都可以miner.start(),但是他们挖矿的难度有些不同(IN-TURN难度为2,其他人为1)
2、出块后会广播出去,经过N-1个参与者校验并签名后(N不确定,取决于什么时候能碰到IN-TURN的signer)
3、第N个参与者校验者(也就是IN-TURN的signer)会签名,并校验是否“满足共识”确定完成区块的生成流程后上链
4、那么“满足共识”的条件就是:当前收到的区块签名包含了第一张所述的IN-TURN的signer的签名,此时肯定满足共识。
(而这个区块也肯定是由IN-TURN的signer签名的,发出的,所以它确实是第一个打包成完美块的那个节点)

二、详细过程 1.启动

POA共识的启动过程——在backend.go:470 进行初始化:

接下来一路到worker.go:992:commit():

需要注意,其实POA共识一直到最后的广播区块之前都与POW都一样,不同的是Seal过程和收区块验证的过程
Seal现在过程如下:

请看Clique.go中的Seal函数:644行,即为签名

接下来就是广播后的同步过程
也就是将这个没有IN-TURN的signer签名的不完美区块发出去,让IN-TURN的signer签名

2.同步

接下来看一下同步过程,关键从fetcher.go:162:newLightFetcher开始(在这之前是同步节点的命令启动)
并关注166行:engine.VerifyHeader(chain, header, ulc == nil)
从这一行开始,就要使用POA共识引擎对区块进行验证了

直接进入consensus/clique/clique.go:217及246,关注VerifyHeader函数


接下来就是对区块头和Seal好的区块中的签名者进行验证,也就是307->366

来看看VerifySeal:

这就是关键的共识验证过程了,只需要知道这个函数最后会返回一个结果即可
(如果不满足共识要求就会返回error或继续广播)

3.总结

关键的共识验证过程:
说白了,如果签名中没有指定的IN-TURN signer,这个区块就是不完美的,没有满足共识要求的,否则就没问题

4.一些其他问题

1、如果我签名好的区块转了一圈一个签名者也没有碰到,又转回来了怎么办

在Seal函数中

一些参考(尤其是后三个):
http://chanye.18183.com/201807/1124156.html
https://segmentfault.com/a/1190000014544347
https://www.jianshu.com/p/071bdc6297ed
https://www.jianshu.com/p/058698a30c82
https://www.jianshu.com/p/9e3c21fb6cc2

三、如果我想修改POA共识机制,把它改成质押(矿机)应该怎么做?

要解决四个问题:

怎么设计惩罚机制(前提:已知作恶节点和coinbase)
质押机制怎么和POA绑定——块包含着质押的信息
激励机制如何设计,出块应该怎么奖励
POA代码如何修改

在四个问题解决之前:需要看一下当前POA的节点投票是如何决定添加或删除signer的?

1、节点环境:signer在不断挖矿(miner.start()),并可于任何时间调用propose进行投票,并存放在proposals[address]=bool(添加或删除)中(api.go:107)
2、在生成新区块时,先为该区块初始化一个header(clique.go:506:prepare()),并从本地保存的proposals中随机选取一个投票信息放入header的coinbase(投谁)和nonce(添加还是删除)中(clique.go:527)(!!注意这里,也就是说添加/删除一个signer就相当于只有一个提案,如果你发起了好多投票让这个人或那个人添加或删除signer的话,每次区块是从这些好多票中随机选一个上区块)
在3发生前,看一下snapshot(下图):

3、同时创建一个新的snapshot,snapshot的组成是从上一个时间点的snapshot(可能距离好几个区块)到当前这个新区块的所有header的数据组成(!!但是很明显,如果数据很多存不下怎么办?POA设置了一个Epoch,每次创建新快照时,如果之前那个snapshot的header数量达到了Epoch,那么snapshot中的Votes和Tally清零(Epoch默认值:30000)snapshot.go:185:apply())

4、将每一个header中有效的提名写入新snapshot的snap.Votes和snap.Tally集合

5、判断是否大于snap.Signers的二分之一,大于则应用这次投票结果(添加或删除)snapshot.go:apply():261

具体流程如下:

1.如何设计惩罚机制

可能的惩罚前提:
1、(由第三方或其它signer)发现某个IN-TURN-signer出块了恶意区块时,进行惩罚
2、(由第三方或其它signer)发现某个OUT-OF-TURN-signer打包了恶意区块时,进行惩罚
3、(由第三方或其它signer)发现某个signer验证通过了某个恶意区块时,进行惩罚
4、(由第三方或其它signer)发现某个非signer节点进行攻击等恶意交易时,进行惩罚
5、signers共同认为某个signer作恶时,进行惩罚
6、signers共同投票踢出某个signer时,进行惩罚

惩罚的方式:
1、signer作恶:signers共同决定踢出一个signer,并扣除质押(超过二分之一投票)
2、普通节点作恶:signer共同对惩罚进行投票,并扣除账户金额,禁止后续交易(超过二分之一投票)
惩罚的过程:
对应上面1-5的惩罚前提:即signer已经对某个作恶情况完全明确,不需投票
1、明确被惩罚者,明确惩罚方式,由一个指定signer节点通过发起特殊tx的形式来进行惩罚
2、其它signer收到tx后,识别出这是一个惩罚tx,并解析出惩罚对应信息后对本地节点数据库进行修改
对应上面6的惩罚前提:即需要投票踢出,才能进行惩罚
1、这种惩罚过程是:当原始POA共识的投票过程满足后,就对本地节点数据库进行修改

2.如何设计激励机制

每当最后的IN-TURN出块广播时,其它的signer节点收到后便会修改本地节点数据库让这个IN-TURN节点获取奖励,参考POW的奖励方法即可,具体代码后面会有解释

3.质押机制怎么和POA绑定——块包含着质押的信息

质押的信息应保存在本地节点数据库中,并保存在Account数据中,Account数据结构左图如下:
可以改为第二张图所示:


进入质押过程:
1、新的质押者使用拥有的货币,自定义一个量主动提出质押要求,以交易的方式发布到网络中

2、其它signer收到tx,如果通过了节点要求(如最低质押金额要求,地址黑白名单),这个signer会自动发起一个添加signer的proposal(包含了质押者地址,以及自己是否同意添加signer(通过节点要求就是同意,否则就是不同意)),并与投票过程一样,随自己打包的块进行传播,同时将tx转发,让其它节点继续收tx,投票。

3、其它signer收到tx后,同样的方法发出proposal,并转发tx

4、最终,当这些proposal达成了超过 二分之一(可为其它值)同意质押的信息后,与添加signer的情况相同,增加一个signer

5、而发出的tx最终会被IN-TURN signer打包上链,从而更改质押数据

其它的考虑:
在上面的2和3需要注意的一点是,由于原始的投票进块是随机的,也就是clique.go中的prepare用的是rand(int),所以投票出结果的速度和tx上链的速度不定:

情况A:质押投票可能会滞后于tx上链从而修改质押账户的速度
也就是会存在这样一种状态:“质押币账户已经加钱了,我的余额已经减钱了,但是我依然不是signer”
针对这个问题,如果存在大量的proposal等待上链,则需要再代码中优先处理质押相关的proposal

情况B:质押投票可能会快于tx上链从而修改质押账户的速度 也就是会存在这样一种状态:“我的质押币账户还没加钱,但我已经成为signer了”
针对这个问题,如果这个signer作恶其实不用担心,我们可以后面进行惩罚 如果出现恶意的质押退出,使自己没有质押的时候保持signer身份,实际上这样我们依然可以用惩罚来解决

当然,为了避免上述问题,我们尽量让tx上链和质押账户修改保持同步,也就是说,在prepare函数中,优先让质押信息进块

退出质押过程:
1、新的质押者使用拥有的质押货币,自定义一个量主动提出退出质押要求,以交易的方式发布到网络中

2、其它signer收到tx,如果通过了节点要求(如最低质押金额要求,地址黑白名单),这个signer会自动发起一个删除signer的proposal(包含了质押者地址,以及自己是否同意删除signer(通过节点要求就是同意,否则就是不同意)),并与投票过程一样,随自己打包的块进行传播,同时将tx转发,让其它节点继续收tx,投票。

3、其它signer收到tx后,同样的方法发出proposal,并转发tx

4、最终,当这些proposal达成了超过 二分之一(可为其它值)退出质押的信息后,与进入质押的情况类似,删除一个signer

5、而发出的tx最终会被IN-TURN signer打包上链,从而更改质押数据

下面讲解如何让投票速度与tx上链速度尽量同步,也就是如何让质押投票进块的速度更快:

解决方案很直接,就是在prepare过程中,优先选择那些质押的proposal,那么如何保存?
当前,用于保存proposal是一个proposals的map:proposals
map[common.Address]bool(可以查看下一页的clique的struct)

我们将这个map修改,增加一个保存proposal类型的地方,这个类型仅仅是用于本地识别proposal是单纯的投票进出signer,还是因质押的投票进入signer

这样在prepare中就从那些质押的proposal中优先选择即可

4.代码如何修改

一、数据结构
· 增加区块头结构
1、address VoteAddress(替代coinbase,标识着投关于谁的票)
2、bool VoteBool(替代nonce,标识着添加还是删除)

· 修改区块头结构(已存在,不需修改只做标记说明)
1、address Coinbase(恢复在POW当中的作用,用于发放奖励)
2、Nonce(恢复在POW当中的作用,用于标识不同的交易)

· 修改账户结构(core/state/state_object.go:101)
1、*big.Int Pledge(质押货币数量)

· 依照账户结构修改本地节点数据库,增加Pledge字段

·对应15页下半部分关于增加质押投票速度的解决方案:
1、增加clique类型,增加一个proposal类型(质押投票或是普通投票)

· 增加配置文件
1、关于质押的配置文件数据(质押的标准,质押黑名单)


· 一些前提——Account的增加、减少、设置 *** 作: 原有的POW奖励代码:consensus/ethash/consensus.go:641:accumulateRewards()-> accumulateRewards调用奖励核心代码:core/state/statedb.go:386:AddBalance()-> AddBalance关键执行代码:core/state/state_object.go:440,448:SetBalance()

二、功能修改——基本数据 *** 作修改
· 在验证区块是否为完美区块后,在clique.go:VerifyHeader给予奖励,使用上述的accumulateRewards()
· 在core/state/state_object.go中增加质押货币的增加、减少、设置 *** 作(相当于账户余额的锁仓),对应 *** 作节点数据库,具体的实现可以参考“· 一些前提”中关于余额的 *** 作

三、功能修改——投票功能修正
· 区块头在clique.go中prepare函数随机选取proposal时信息的保存位置从区块头的coinbase-nonce改为区块头的voteaddress-votebool

四、功能修改——质押tx的接收过程
· 当tx收到了一个用来修改质押账户的tx后要识别出来(可以直接从tx中识别,对应数据库修改之后,可以对tx的转账类型识别)修改位置需要参考修改质押账户的位置
· 对于质押信息生成质押结果的判别,要依赖一下配置文件,看看是否有黑名单,是否超过了质押下限,另可以开放api交给节点使用者自定义本节点能够接收的质押下线和黑名单
· 识别出来后,保存到proposal中时,除了保存地址、是否同意质押之外,增加保存proposal的类别(普通投票/质押投票)修改位置:clique/api.go内,的propose函数修改,新增类型的保存
· 在proposal进区块即clique/clique.go中的prepare函数中新增优先质押投票的识别,优先选择质押投票进块

关键的难点:对于节点数据库k-v的修改,并需要让持久化层和控制层的匹配

具体的代码参考 cmd/geth/dbcmd.go core/state/statedb.go core/state/database.go
core/state/state_object.go

另,为了减少开发难度,节点数据库中pledge质押账户的的修改可以不用走状态,仅仅让balance(上一张的setbalance函数)走状态也可

看一下Transaction从创建-广播发出-接收-再广播发出是如何进行的
先来看从创建到广播发出:

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/1498790.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-25
下一篇 2022-06-25

发表评论

登录后才能评论

评论列表(0条)

保存