以太坊PoA共识引擎算法介绍(2)

PoA共识引擎算法实现分析

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挖到的概率很大.

clique中最重要的两个数据结构:

  • 共识引擎的结构:
    type Clique struct {
        config *params.CliqueConfig // 系统配置参数
        db ethdb.Database // 数据库: 用于存取检查点快照
        recents *lru.ARCCache //保存最近block的快照, 加速reorgs
        signatures *lru.ARCCache //保存最近block的签名, 加速挖矿
        proposals map[common.Address]bool //当前signer提出的proposals列表
        signer common.Address // signer地址
        signFn SignerFn // 签名函数
        lock sync.RWMutex // 读写锁
    }
  • snapshot的结构:
    type Snapshot struct {
        config *params.CliqueConfig // 系统配置参数
        sigcache *lru.ARCCache // 保存最近block的签名缓存,加速ecrecover
        Number uint64 // 创建快照时的block号
        Hash common.Hash // 创建快照时的block hash
        Signers map[common.Address]struct{} // 此刻的授权的signers
        Recents map[uint64]common.Address // 最近的一组signers, key=blockNumber
        Votes []*Vote // 按时间顺序排列的投票列表
        Tally map[common.Address]Tally // 当前的投票计数,以避免重新计算
    }

除了这两个结构, 对block头的部分字段进行了复用定义, ethereum的block头定义:

    type Header struct {
        ParentHash common.Hash 
        UncleHash common.Hash 
        Coinbase common.Address 
        Root common.Hash 
        TxHash common.Hash 
        ReceiptHash common.Hash 
        Bloom Bloom 
        Difficulty *big.Int 
        Number *big.Int 
        GasLimit *big.Int 
        GasUsed *big.Int 
        Time *big.Int 
        Extra []byte 
        MixDigest common.Hash 
        Nonce BlockNonce 
    }
  • 创世块中的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)

下面对比较重要的函数详细分析实现流程

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的初始化

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

获取给定时间点的一个快照 Clique.snapshot

  • 先查找Clique.recents中是否有缓存, 有的话就返回该snapshot
  • 在查找持久化存储中是否有缓存, 有的话就返回该snapshot
  • 如果是创世块
    1. 从Extra中取出所有的signers
    2. newSnapshot(Clique.config, Clique.signatures, 0, genesis.Hash(), signers)
    • signatures是最近的签名快照
    • signers是所有的初始signers
    1. 把snapshot加入到Clique.recents中, 并持久化到db中
  • 其他普通块
    • 沿着父块hash一直往回找是否有snapshot, 如果没找到就记录该区块头
    • 如果找到最近的snapshot, 将前面记录的headers 都 applay 到该snapshot上
    • 保存该最新的snapshot到缓存Clique.recents中, 并持久化到db中

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

重点: Clique.Seal(chain, block , stop)

Seal也是共识引擎接口之一. 该函数用clique.signer对block的进行签名. 在pow]算法中, 该函数进行hash运算来解"难题".

  • 如果signer没有在snapshot的signers中,不允许对block进行签名
  • 如果不是本block的签名者,延时一定的时间(随机)后再签名, 如果是本block的签名者, 立即签名.
  • 签名结果放在Extra的extraSeal的65字节中

Clique.VerifySeal(chain, header)

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

  1. 从header的签名中恢复账户地址,改地址要求在snapshot的signers中
  2. 检查header中的Difficulty是否匹配(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表示加入还是踢出

API.Discard(addr)

删除一个proposal

你可能感兴趣的:(以太坊PoA共识引擎算法介绍(2))