以太坊clique(PoA)共识引擎总结

配合代码食用(Geth v1.9.0 stable)

背景:

以太坊目前有ethash和clique两个共识引擎,其中ethash是用于正式网络的PoW(proof-of-work)共识引擎,clique是用于测试网络的PoA(proof-of-authority)共识引擎

介绍:

clique是依靠授权节点(signers)顺序产生block,可以由已授权的signer选举(投票超过50%)加入新的signer和踢出已授权signer的联盟链共识引擎

使用:

通过编译好的puppeth程序创建共识为poa的genesis.json文件,geth执行init操作读取json文件,然后启动私链节点

代码分析:

consenesus/clique/clique.go

1.常量和变量意义:

checkpointInterval = 1024 ,每隔1024块保存投票快照到数据库
inmemorySnapshots  = 128 ,保存在内存中的快照数量
inmemorySignatures = 4096 ,保存在内存中的最近区块的签名者数量
wiggleTime = 500 * time.Millisecond ,用于非顺序出块人出块延迟时间计算,在0 ~ (signerCount/2+1)*wiggleTime范围内随机取一个值作为延迟时间
epochLength = uint64(30000) ,每隔30000块清空所有投票
extraVanity = 32 ,extra-data保留32个字节的前缀
extraSeal   = 65 ,extra-data为区块signer保留65个字节的后缀
nonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") ,投票加入一个签名者使用的nonce
nonceDropVote = hexutil.MustDecode("0x0000000000000000") ,投票踢出一个签名者使用的nonce
diffInTurn = big.NewInt(2) ,出块人是顺序出块人时的区块难度值
diffNoTurn = big.NewInt(1) ,出块人不是顺序出块人时的区块难度值

2.重要方法:

Prepare 
    给header.Coinbase赋值(投票目标地址)
    给header.Nonce赋值(投票类型为加入或提出签名者)
    给header.Difficulty赋值(是顺序出块人为2,否则为1)
    给header.Extra赋值(32字节前缀+所有签名者地址+65字节后缀用于区块签名)
    给header.MixDigest赋值为空,摘要,在pow中用于防篡改校验(VerifySeal)
    给header.Time赋值(等于parent.Time+Period或等于now>parent.Time+Period)
Finalize 
    给header.Root赋值
    给header.UncleHash赋值为空(poa没有叔区块)
    构建block返回
Seal 
    判断在最近出块记录中,则不允许出块(维持一个大小为signercount/2+1的signer队列Recents,用于判断最近是否出过块)
    计算应该delay的时间
    拷贝签名到header.Extra后65字节
    delay到出块时间后将sealed block放入worker.resultCh
VerifySeal
    验证header.Number不为0
    验证区块签名者在签名者列表中
    验证区块签名者最近没出过块
    验证header.Difficulty和轮次是否匹配
snapshot
    获取基于某个块的Snapshot
        优先从内存中获取
        如果内存中没有,且恰好number是checkpointInterval的整数倍,从数据库取
        如果恰好number是epochLength的整数倍,创建一个Snapshot并保存
        还是没有从上一块(number-1)取
    取到了Snapshot,如果是从number块前面的块取的,要snap.apply(headers)
    Snapshot保存到缓存中
    如果恰好number是checkpointInterval的整数倍且有执行apply,保存Snapshot到数据库
VerifyHeader
    验证header有效性 
APIs
    获取共识引擎提供的RPC接口
consenesus/clique/snapshot.go

1.结构体:

type Snapshot struct {            // Snapshot是基于某个区块高度的投票认证状态
    config   *params.CliqueConfig // 配置参数
    sigcache *lru.ARCCache        // 缓存最近区块签名地址,用于快速得到签名地址

    Number  uint64                      // Snapshot创建时的区块高度
    Hash    common.Hash                 // Snapshot创建时的区块哈希
    Signers map[common.Address]struct{} // 已认证签名者集合
    Recents map[uint64]common.Address   // 最近已签名区块的签名者集合 //数量为 SIGNER_COUNT/2+1 ,可保证即使存在恶意signer,他最多只能攻击连续块 SIGNER_COUNT/2+1 中的1个
    Votes   []*Vote                     // 按时间排序的投票集合
    Tally   map[common.Address]Tally    // 当前投票记录避免重复计算
}

type Tally struct {                   // 投票记录
    Authorize bool `json:"authorize"` // 投票是加入或者提出某个账户
    Votes     int  `json:"votes"`     // 想要通过的提议当前的投票数
}

type Vote struct {
    Signer    common.Address        // 投这个票的已认证签名者
    Block     uint64                // 投这个票的区块高度(太旧的投票是过期投票)
    Address   common.Address        // 被投的账户
    Authorize bool                  // 是认证还是解除认证这个被投票的账户
}

2.重要方法:

apply
    验证headers有效性和连续性
    snap := s.copy()
    遍历headers:
        每隔epochLength块清空Votes和Tally
        删除Recents中最旧的一个singer使其能够再次签名
        获取header的签名者,判断如果不在签名者列表中或者在Recents列表中,return,否则将签名者放入Recents中
        遍历Votes,丢弃掉之前这个header的签名者投给同一个账户(header.Coinbase)的投票
        投票,即在Snapshot.Tally和Snapshot.Votes中添加记录
        根据投票记录Tally判断如果当前被投票地址header.Coinbase票数大于当前签名者列表长度/2:
            如果提议为加入新的签名者,签名者列表加入新签名者header.Coinbase;如果提议为踢出已认证签名者,将其从签名者列表删除,然后删除Recents中最旧的一个,丢弃所有该签名者投的票
            丢弃之前所有投给该签名者的票和记录
    更新snap.Number和snap.Hash到最新
consenesus/clique/api.go

1.结构体:

type API struct {  //API是一个面向用户的RPC接口对象,用于控制signer和poa投票机制
    chain  consensus.ChainReader
    clique *Clique
}

2.clique共识引擎提供的RPC接口有:

func (api *API) GetSnapshot(number *rpc.BlockNumber) (*Snapshot, error)  ,基于区块高度为number的块得到状态快照(state snapshot)
func (api *API) GetSnapshotAtHash(hash common.Hash) (*Snapshot, error)  ,基于区块哈希为hash的块得到状态快照
func (api *API) GetSigners(number *rpc.BlockNumber) ([]common.Address, error)  ,基于区块高度为number的块得到签名者列表
func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error)  ,基于区块哈希为hash的块得到签名者列表
func (api *API) Proposals() map[common.Address]bool  ,获取当前所有提议
func (api *API) Propose(address common.Address, auth bool)  ,提议加入新的认证签名者或踢出现有认证签名者
func (api *API) Discard(address common.Address)  ,丢弃已有的一个提议

小结

在以太坊上增加dpos共识引擎可以参考poa,这个后面再聊~

你可能感兴趣的:(以太坊clique(PoA)共识引擎总结)