geth
共识替换方法
本文档基于geth v1.9.25 stable
。目前内容基于代码阅读,还没有实际应用来检验。未来可能会进行修补。
创世块
在创世块中,config
中的一个字段指示了链所用的共识算法,以及该算法所需的参数:
{
"config": {
"chainId": 114514,
"clique": {
"period": 5,
"epoch": 30000
},
...
创世块的config
字段会被解析为在params/config.go
中定义的ChainConfig
类
为了支持在创世块中配置新的共识算法,需要在该类中增加属性,并通过标签指示和JSON字段的对应关系,如:
MyBft *MyBFTConfig `json:"mybft,omitempty"`
在上面的例子中,MyBFTConfig
为一个我们自己定义的类,其中包含我们要实现的共识所需的属性
共识算法判断
在读取创世块配置后,一个指向params.ChainConfig
的指针会被传到eth/backend.go
中的CreateConsensusEngine
方法
// CreateConsensusEngine creates the required type of consensus engine instance for an Ethereum service
func CreateConsensusEngine(stack *node.Node, chainConfig *params.ChainConfig, config *ethash.Config, notify []string, noverify bool, db ethdb.Database) consensus.Engine {
// If proof-of-authority is requested, set it up
if chainConfig.Clique != nil {
return clique.New(chainConfig.Clique, db)
}
// Otherwise assume proof-of-work
...
该方法需要根据传入的链配置,返回一个实现了consensus.Engine
接口的对象。我们在其中增加一个if,构造自己实现的对象即可。关于该接口的细节详见下文
consensus.Engine
共识的核心接口,包含了有关提出区块、验证区块等的各种方法。新的共识算法需要实现下面的全部方法。
对于实现接口的包如何组织文件,并没有要求
提出本地区块
Prepare
在向新区块中添加交易前,会被调用,来向Header中填充交易无关的已知字段
FinalizeAndAssemble
区块塞满交易后会被调用。若区块中的交易除了本身的转账和合约运行外,还通过其他方式(比如blockDAG
中的区块奖励)对状态有影响,应该在这里更新状态数据库。在此之后,将不完整的区块头和区块交易列表等组装为待Seal的区块并返回
如果我们并不需要额外更新状态,用types.NewBlock(header, txs, uncles, receipts, new(trie.Trie))
就可以完成组装
Seal
组装好的区块发布前要做的工作。该方法异步返回,在完全准备好区块后才通过channel将区块交给上层
若我们要实现BFT共识中,需要进行额外的节点通信来获得多签。注意到event/event.go
中,Post
方法可以用于广播数据
验证外来区块
VerifySeal
验证一个区块头是否满足共识中的密码学要求(如PoW中哈希是否符合难度限制,以及PoA中验证签名)
之所以将密码学验证从区块中验证中提取出来,是因为geth
支持Ultra Light Client (ULC)模式,该模式需要尽可能避免复杂计算
VerifyHeader
大多数情况下,该方法检查从外界收到的区块头是否合法(考虑共识、时间戳、分叉等因素)
该方法有一个bool类型的参数seal,当seal为false时,最好避免运行VerifySeal
中的密码学检查
VerifyHeaders
一次性验证一批区块头,允许并发(当然也可以不并发)
在Quorum的IBFT
中,以及原生的Clique
中,都没有进行并发验证,原因未知。为了保险我们最好也不要并发了
verifyUncles
与ethash
中的BlockDAG
有关
我们不需要考虑,直接通过就好
Finalize
若外来区块中的交易除了本身的转账和合约运行外,还通过其他方式(比如blockDAG
中的区块奖励)对状态有影响,应该在这里更新状态数据库
我们应该不需要在这里做任何事
其他
Author
输入块头,返回一个地址:在PoW
中为块的Coinbase
受益者,在PoA
中可以自定义为其他地址,如共识中的某个签名者。
作为块的上下文,智能合约可能会使用该属性,因此需要保证一个块打包后,在不同节点上能得到相同的Author
APIs
返回共识特有的RPC-API,用于注册到RPC服务器
这里参考consensus/ethash/api.go
和consensus/ethash/ethash.go
写就行
CalcDifficulty
因为types.block.go
的Header
类中,将Difficulty
写死在了区块头数据结构里,所以我们必须给每个区块设定一个难度值
在非PoW
的共识中,可以像Quorum
的IBFT
一样直接返回0,也可以活用这一块空间放点东西,比如Clique就用它来表示是否"in turn"
SealHash
输入一个未签名区块头,返回它的哈希
注意传入的区块头可能是带签名的,此时要主动去掉签名部分