PoA 共识下的ethereum如何引入出块奖励?
在之前的文章中介绍了通过修改源码的方式来实现PoA共识下的出块奖励,在只有一个节点的情况下,这种方式并不会有什么问题;一旦有新的节点加入网络,那新增的节点就会卡在数据同步的阶段。那为什么会出现这种情况呢?
问题背景
在PoA共识中,一般是没有出块奖励的,但在某些情况下,我们可以修改源代码以实现自定义的出块奖励逻辑。例如,通过修改consensus/clique/clique.go
文件来向出块者发放奖励。
// consensus/clique/clique.go
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
if len(txs) != 0 {
miner, err := ecrecover(chain.CurrentHeader(), c.signatures)
if err != nil {
log.Error("Failed to recover miner address", "err", err)
return
}
// 奖励
state.AddBalance(miner, uint256.NewInt(chain.Config().Clique.Reward*1e18))
}
}
在这段代码中,我们通过ecrecover
函数来恢复出块者地址,并给出块者奖励。然而,当网络中新节点加入时(无论是同步节点还是验证者节点),就会遇到一个问题:新节点无法完成与现有节点的同步,卡在数据同步阶段。
问题的根本原因
问题的核心在于新节点无法确定正确的出块奖励地址。在现有的逻辑中,出块奖励的地址依赖于etherbase
参数的值,这会导致新节点的出块奖励地址和现有节点不同,从而导致同步失败。
具体来说:
- 对于同步节点,可以不指定
miner
相关的参数,系统会自动从创世区块中的配置获取相关信息。 - 对于验证者节点,需要手动指定
miner
地址,但如果新节点的配置与现有节点不同,ecrecover
函数返回的矿工地址也会不同,造成同步失败。
解决方案
为了解决这一问题,我们可以确保出块奖励地址的确定性,即每个节点都能从创世区块中获取相同的出块奖励地址。由于每个区块链网络都有一个唯一的创世区块,创世区块包含了网络的初始配置,因此我们可以在创世区块中写入出块奖励的地址,并确保所有节点在同步时都能使用相同的奖励地址。
实现步骤
1. 修改创世区块配置
首先,我们需要在genesis.json
中为PoA共识添加出块奖励地址(coinbases
字段)和奖励数量(reward
字段)。这样,新加入的节点可以从创世区块中获取这些配置。
// params/config.go
// CliqueConfig is the consensus engine configs for proof-of-authority based sealing.
type CliqueConfig struct {
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
Epoch uint64 `json:"epoch"` // Epoch length to reset votes and checkpoint
// new
Coinbases []string `json:"coinbases"` // List of coinbase addresses to use for getting rewards
Reward uint64 `json:"reward"` // Reward amount in ETH per block
}
修改后的genesis.json
示例如下:
{
"config": {
"chainId": 12345,
"homesteadBlock": 0,
"eip150Block": 0,
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"clique": {
"period": 1,
"epoch": 30000,
"coinbases": [
"0x618C92D30E4a7B21A0D00DC7f5038024752ADFD5",
"0xe23C2c6e7f785e74EB7AAeF96455B78C53adb2E3"
],
"reward": 2
}
},
"difficulty": "1",
"gasLimit": "0xFFFFFFFF",
"extradata": "0x0000000000000000000000000000000000000000000000000000000000000000618C92D30E4a7B21A0D00DC7f5038024752ADFD50000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"alloc": {
"618C92D30E4a7B21A0D00DC7f5038024752ADFD5": {
"balance": "10000000000000000000000"
},
"e23C2c6e7f785e74EB7AAeF96455B78C53adb2E3": {
"balance": "10000000000000000000000"
}
}
}
在此配置中:
coinbases
指定了两个地址,作为出块奖励的接收者。reward
指定每个区块的奖励数量。extradata
字段可以包含出块者的额外数据。
2. 修改Finalize
方法获取奖励地址
接下来,我们需要修改Finalize
方法,使其从创世区块的配置中动态获取出块奖励地址,而不是依赖etherbase
或ecrecover
。
// consensus/clique/clique.go
func (c *Clique) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, withdrawals []*types.Withdrawal) {
// 如果没有交易,则直接返回
if len(txs) == 0 {
return
}
decimals := 1e18
// 从创世区块配置中获取出块奖励地址
coinbases := chain.Config().Clique.Coinbases
coinbaseLen := len(coinbases)
if coinbaseLen != 0 {
// 根据区块号计算出块奖励地址
index := header.Number.Uint64() % uint64(coinbaseLen)
state.AddBalance(common.HexToAddress(coinbases[index]), uint256.NewInt(chain.Config().Clique.Reward*decimals))
}
}
在这个修改后的Finalize
方法中:
- 我们首先从创世区块中获取
coinbases
(即出块奖励地址列表)。 - 根据当前区块的编号(
header.Number
)和奖励地址列表的长度,计算出一个确定的地址,并给该地址发放奖励。
3. 重编译并启动新的节点
完成上述代码修改后,我们需要重新编译geth
客户端,并用修改后的genesis.json
文件启动新的节点。这样,所有节点都会使用从创世区块中读取的奖励地址,而不再依赖手动配置。
声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意
腾讯云开发者社区:孟斯特