PoA 共识下的ethereum如何引入出块奖励?

mengc -

在之前的文章中介绍了通过修改源码的方式来实现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参数的值,这会导致新节点的出块奖励地址和现有节点不同,从而导致同步失败。

具体来说:

解决方案

为了解决这一问题,我们可以确保出块奖励地址的确定性,即每个节点都能从创世区块中获取相同的出块奖励地址。由于每个区块链网络都有一个唯一的创世区块,创世区块包含了网络的初始配置,因此我们可以在创世区块中写入出块奖励的地址,并确保所有节点在同步时都能使用相同的奖励地址。

实现步骤

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"
        }
    }
}

在此配置中:

2. 修改Finalize方法获取奖励地址

接下来,我们需要修改Finalize方法,使其从创世区块的配置中动态获取出块奖励地址,而不是依赖etherbaseecrecover

// 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方法中:

3. 重编译并启动新的节点

完成上述代码修改后,我们需要重新编译geth客户端,并用修改后的genesis.json文件启动新的节点。这样,所有节点都会使用从创世区块中读取的奖励地址,而不再依赖手动配置。


声明:本作品采用署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)进行许可,使用时请注明出处。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 恋水无意
腾讯云开发者社区:孟斯特