以太坊POA共识算法解析

1.clique中的概念和定义

  • EPOCH_LENGTH : epoch长度是30000个block, 每次进入新的epoch,前面的投票都被清空,重新开始记录,这里的投票是指加入或移除signer
  • BLOCK_PERIOD : 出块时间, 默认是15s
  • UNCLE_HASH : 总是 Keccak256(RLP([])) ,因为没有uncle
  • SIGNER_COUNT : 每个block都有一个signers的数量
  • SIGNER_LIMIT : 等于 (SIGNER_COUNT / 2) + 1 . 每个singer只能签名连续SIGNER_LIMIT个block中的1个
    • 比如有5个signer:ABCDE, 对4个block进行签名, 不允许签名者为ABAC, 因为A在连续3个block中签名了2次
  • NONCE_AUTH : 表示投票类型是加入新的signer; 值= 0xffffffffffffffff
  • NONCE_DROP : 表示投票类型是踢除旧的的signer; 值= 0x0000000000000000
  • EXTRA_VANITY : 代表block头中Extra字段中的保留字段长度: 32字节
  • EXTRA_SEAL : 代表block头中Extra字段中的存储签名数据的长度: 65字节
  • IN-TURN/OUT-OF-TURN : 每个block都有一个in-turn的signer, 其他signers是out-of-turn, in-turn的signer的权重大一些, 出块的时间会快一点, 这样可以保证该高度的block被in-turn的signer挖到的概率很大.
  • 创世块中的Extra字段包括:
    • 32字节的前缀(extraVanity)
    • 所有signer的地址
    • 65字节的后缀(extraSeal): 保存signer的签名
  • 其他block的Extra字段只包括extraVanity和extraSeal
  • Time字段表示产生block的时间间隔是:blockPeriod(15s)
  • Nonce字段表示进行一个投票: 添加( nonceAuthVote: 0xffffffffffffffff )或者移除( nonceDropVote: 0x0000000000000000 )一个signer
  • Coinbase字段存放 被投票 的地址
    • 举个栗子: signerA的一个投票:加入signerB, 那么Coinbase存放B的地址
  • Difficulty字段的值: 1-是 本block的签名者 (in turn), 2- 非本block的签名者 (out of turn)

2. PoA的特点

  • PoA是依靠预设好的授权节点(signers),负责产生block。普通节点不能挖矿无生成区块的权利
  • 可以由已授权的signer选举(投票超过50%)加入新的signer。
  • 即使存在恶意signer,他最多只能攻击连续块(数量是 (SIGNER_COUNT / 2) + 1) 中的1个,期间可以由其他signer投票踢出该恶意signer。
  • 可指定产生block的时间。
  • 无挖矿奖励

3. PoA的工作流程及接口

  1. 在创世块中指定一组初始授权的signers, 所有地址保存在创世块Extra字段中
  2. 启动挖矿后, 该组signers开始对生成的block进行 签名并广播.
  3. 签名结果 保存在区块头的Extra字段中
  4. Extra中更新当前高度已授权的 所有signers的地址 ,因为有新加入或踢出的signer
  5. 每一高度都有一个signer处于IN-TURN状态, 其他signer处于OUT-OF-TURN状态, IN-TURN的signer签名的block会 立即广播 , OUT-OF-TURN的signer签名的block会 延时 一点随机时间后再广播, 保证IN-TURN的签名block有更高的优先级上链
  6. 如果需要加入一个新的signer, signer通过API接口发起一个proposal, 该proposal通过复用区块头 Coinbase(新signer地址)和Nonce("0xffffffffffffffff") 字段广播给其他节点. 所有已授权的signers对该新的signer进行"加入"投票, 如果赞成票超过signers总数的50%, 表示同意加入
  7. 如果需要踢出一个旧的signer, 所有已授权的signers对该旧的signer进行"踢出"投票, 如果赞成票超过signers总数的50%, 表示同意踢出

signer对区块头进行签名

  1. Extra的长度至少65字节以上(签名结果是65字节,即R, S , V, V是0或1)
  2. 对blockHeader中所有字段除了Extra的 后65字节 外进行 RLP编码
  3. 对编码后的数据进行 Keccak256 hash
  4. 签名后的数据(65字节)保存到Extra的 后65字节 中

clique.go中实现了consensus中的所有接口完成POA算法的实现

Clique.Prepare(chain , header)

Prepare是共识引擎接口之一. 该函数配置header中共识相关的参数(Cionbase, Difficulty, Extra, MixDigest, Time)

  • 对于非epoch的block( number % Epoch != 0 ):
  1. 得到Clique.proposals中的投票数据(例:A加入C, B踢除D)
  2. 根据snapshot的signers分析投票数否有效(例: C原先没有在signers中, 加入投票有效, D原先在signers中,踢除投票有效)
  3. 从被投票的地址列表(C,D)中, 随机选择一个地址 ,作为该header的Coinbase,设置Nonce为加入( 0xffffffffffffffff )或者踢除( 0x0000000000000000 )
  4. Clique.signer 如果是本轮的签名者(in-turn), 设置header.Difficulty = diffInTurn(1), 否则就是diffNoTurn(2)
  5. 配置header.Extra的数据为[ extraVanity + snap中的全部signers + extraSeal ]
  6. MixDigest需要配置为nil
  7. 配置时间戳:Time为父块的时间+15s
  8. 填充header对象的Coinbase,Nonce,Difficulty,Extra(预留后缀,用于Seal()存入当前块签名者的签名)

共识引擎clique的初始化

在 Ethereum.StartMining 中,如果Ethereum.engine配置为clique.Clique, 根据当前节点的矿工地址(默认是acounts[0]), 配置clique的 签名者 : clique.Authorize(eb, wallet.SignHash) ,其中 签名函数 是SignHash,对给定的hash进行签名.

Clique.snapshot(chain,number,hash,parents)

快照在指定的时间内检索授权的快照,首先封装了在内存和磁盘中,寻找快照;

如果是在创世区块中则创建一个新的快照;

如果没有区块头的快照,则收集区块向后移,有明确的父就强制到父,没有明确的父到数据库中找

找到快照后,将所有的header的后半部分前移

再通过区块头生成一个新的快照,将当前区块的hash保存到最近的快照中,将生成的快照保存到磁盘上

其中的votes应用存储投票的数量

Snapshot.apply(headers)

创建一个新的授权signers的快照, 将从上一个snapshot开始的区块头中的proposals更新到最新的snapshot上

  1. 对入参headers进行完整性检查: 因为可能传入多个区块头, block号必须连续
  2. 遍历所有的header, 如果block号刚好处于epoch的起始(number%Epoch == 0),将snapshot中的Votes和Tally复位( 丢弃历史全部数据 )
  3. 对于每一个header,从签名中得到 signer
  4. 如果该signer在snap.Recents中, 说明 最近已经有过签名 , 不允许再次签名, 返回空
  5. 记录 该signer是该block的签名者: snap.Recents[number] = signer
  6. 统计header.Coinbase的投票数,如果 超过signers总数的50%
  7. 执行加入或移除操作
  8. 删除snap.Recents中的一个signer记录: key=number- (uint64(len(snap.Signers)/2 + 1)), 表示释放该signer,下次可以对block进行签名了
  9. 清空被移除的Coinbase的投票
  10. 移除snap.Votes中该Conibase的所有投票记录
  11. 移除snap.Tally中该Conibase的所有投票数记录

 Clique.Seal(chain, block , stop)

Seal也是共识引擎接口之一. 该函数用clique.signer对block的进行签名. 实现共识,引擎,尝试使用创建密封块,生成签名到header.Extra的后缀中

可对一个调用过 Finalize()的区块进行授权或封印,成功时返回的区块全部成员齐整,可视为一个正常区块,可被广播到整个网络中,也可以被插入区块链等

  • 不支持密封genesis区块
  • 如果signer没有在snapshot的signers中,不允许对block进行签名
  • signer不是本区块的签名者需要延时随机一段时候后再签名,是本区块的签名者则直接签名
  • 当前签名者在‘最近签名者’中,则等待下一个epoch
  • 不支持0-period的链,不支持空块密封,没有奖励但是能够密封
  • 把签名的结果用copy替换保存到区块头Extra字段的extraSeal的65字节中中

Clique.VerifySeal(chain, header)

VerifySeal也是共识引擎接口之一.

  1. 从header的签名中恢复账户地址,改地址要求在snapshot的signers中
  2. 检查区块头中的计算难度是否匹配(in turn或out of turn)

Clique.Finalize

Finalize也是共识引擎接口之一. 该函数生成一个block, 对新区块“定型”,没有叔块处理,也没有奖励机制

  1. header.Root : 状态根保持原状
  2. header.UncleHash : 为nil
  3. types.NewBlock(header, txs, nil, receipts) : 封装并返回最终的block

API.Propose(addr, auth)

添加一个proposal: 调用者对addr的投票, auth表示加入还是踢出

verifyUncles(chain,block)

判断block中的叔伯块是否大于零,由于这个共识中不允许有叔伯块

ecercover(header,sigcache)

从签名头中提取以太坊帐户地址

CalcDifficulty(chain,time,parent)

返回区块的计算难度,计算难度是难度调整算法

Difficulty字段的值: 1-是 本block的签名者 (in turn), 2- 非本block的签名者 (out of turn)

投票策略

因为blockchain可能会小范围重组(small reorgs), 常规的投票机制(cast-and-forget, 投票和忘记)可能不是最佳的,因为包含单个投票的block可能不会在最终的链上,会因为已有最新的block而被抛弃。

一个简单但有效的办法是对signers配置"提议(proposal)".例如 "add 0x...", "drop 0x...", 有多个并发的提议时, 签名代码"随机"选择一个提议注入到该签名者签名的block中,这样多个并发的提议和重组(reorgs)都可以保存在链上.

该列表可能在一定数量的block/epoch 之后过期,提案通过并不意味着它不会被重新调用,因此在提议通过时不应立即丢弃。

  • 加入和踢除新的signer的投票都是立即生效的,参与下一次投票计数
  • 加入和踢除都需要 超过当前signer总数的50% 的signer进行投票
  • 可以踢除自己(也需要超过50%投票)
  • 可以并行投票(A,B交叉对C,D进行投票), 只要最终投票数操作50%
  • 再没进入新的epoch对于以一个signer的投票未被通过时,后面有其他signer对该signer的投票也算入判断中
  • 进入一个新的epoch, 所有之前的pending投票都作废, 重新开始统计投票
  • 不允许反复连续投 (由于有最近签名者的限制,在SIGNER_COUNT / 2 + 1个块内,签名者只能签署一个块)

投票场景举例

  • ABCD, AB先分别踢除CD, C踢除D, 结果是剩下ABC
  • ABCD, AB先分别踢除CD, C踢除D, B又投给C留下的票, 结果是剩下ABC
  • ABCD, AB先分别踢除CD, C踢除D, 即使C投给自己留下的票, 结果是剩下AB
  • ABCDE, ABC先分别加入F(成功,ABCDEF), BCDE踢除F(成功,ABCDE), DE加入F(失败,ABCDE), BCD踢除A(成功, BCDE), B加入F(由于DE加入F还存在,此时BDE加入F,满足超过50%投票), 结果是剩下BCDEF

4. PoA中的攻击及防御

  • 恶意签名者(Malicious signer). 恶意用户被添加到签名者列表中,或签名者密钥/机器遭到入侵. 解决方案是,N个授权签名人的列表,任一签名者只能对每K个block签名其中的1个。这样尽量减少损害,其余的矿工可以投票踢出恶意用户。
  • 审查签名者(Censoring signer). 如果一个签名者(或一组签名者)试图检查block中其他signer的提议(特别是投票踢出他们), 为了解决这个问题,我们将签名者的允许的挖矿频率限制在1/(N/2)。如果他不想被踢出出去, 就必须控制超过50%的signers.
  • "垃圾邮件"签名者(Spamming signer). 这些signer在每个他们签名的block中都注入一个新的投票提议.由于节点需要统计所有投票以创建授权签名者列表, 久而久之之后会产生大量垃圾的无用的投票, 导致系统运行变慢.通过epoch的机制,每次进入新的epoch都会丢弃旧的投票
  • 并发块(Concurrent blocks). 如果授权签名者的数量为N,我们允许每个签名者签名是1/K,那么在任何时候,至少N-K个签名者都可以成功签名一个block。为了避免这些block竞争( 分叉 ),每个签名者生成一个新block时都会加一点随机延时。这确保了很难发生分叉。

猜你喜欢

转载自blog.csdn.net/m0_55345224/article/details/120881258
今日推荐