以太坊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,这个后面再聊~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349