Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

validation rewards #1502

Merged
merged 10 commits into from
Oct 3, 2022
4 changes: 4 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -2528,6 +2528,10 @@ func MakeChain(ctx *cli.Context, stack *node.Node, useExist bool) (chain *core.B
engine = istanbulBackend.New(ibftConfig, stack.GetNodeKey(), chainDb)
} else if config.QBFT != nil {
qbftConfig := setBFTConfig(config.QBFT.BFTConfig)
qbftConfig.BlockReward = config.QBFT.BlockReward
qbftConfig.BeneficiaryMode = config.QBFT.BeneficiaryMode // beneficiary mode: list, besu, validators
qbftConfig.BeneficiaryList = config.QBFT.BeneficiaryList // list mode
qbftConfig.MiningBeneficiary = config.QBFT.MiningBeneficiary // auto (undefined mode) and besu mode
qbftConfig.TestQBFTBlock = big.NewInt(0)
if config.Transitions != nil && len(config.Transitions) != 0 {
qbftConfig.Transitions = config.Transitions
Expand Down
33 changes: 25 additions & 8 deletions consensus/istanbul/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params"
"github.com/naoina/toml"
)
Expand Down Expand Up @@ -122,14 +123,18 @@ func (p *ProposerPolicy) ClearRegistry() {
}

type Config struct {
RequestTimeout uint64 `toml:",omitempty"` // The timeout for each Istanbul round in milliseconds.
BlockPeriod uint64 `toml:",omitempty"` // Default minimum difference between two consecutive block's timestamps in second
EmptyBlockPeriod uint64 `toml:",omitempty"` // Default minimum difference between a block and empty block's timestamps in second
ProposerPolicy *ProposerPolicy `toml:",omitempty"` // The policy for proposer selection
Epoch uint64 `toml:",omitempty"` // The number of blocks after which to checkpoint and reset the pending votes
Ceil2Nby3Block *big.Int `toml:",omitempty"` // Number of confirmations required to move from one state to next [2F + 1 to Ceil(2N/3)]
AllowedFutureBlockTime uint64 `toml:",omitempty"` // Max time (in seconds) from current time allowed for blocks, before they're considered future blocks
TestQBFTBlock *big.Int `toml:",omitempty"` // Fork block at which block confirmations are done using qbft consensus instead of ibft
RequestTimeout uint64 `toml:",omitempty"` // The timeout for each Istanbul round in milliseconds.
BlockReward *math.HexOrDecimal256 `toml:",omitempty"` // Reward
BlockPeriod uint64 `toml:",omitempty"` // Default minimum difference between two consecutive block's timestamps in second
EmptyBlockPeriod uint64 `toml:",omitempty"` // Default minimum difference between a block and empty block's timestamps in second
ProposerPolicy *ProposerPolicy `toml:",omitempty"` // The policy for proposer selection
Epoch uint64 `toml:",omitempty"` // The number of blocks after which to checkpoint and reset the pending votes
Ceil2Nby3Block *big.Int `toml:",omitempty"` // Number of confirmations required to move from one state to next [2F + 1 to Ceil(2N/3)]
AllowedFutureBlockTime uint64 `toml:",omitempty"` // Max time (in seconds) from current time allowed for blocks, before they're considered future blocks
TestQBFTBlock *big.Int `toml:",omitempty"` // Fork block at which block confirmations are done using qbft consensus instead of ibft
BeneficiaryMode *string `toml:",omitempty"` // Mode for setting the beneficiary, either: list, besu, validators (beneficiary list is the list of validators)
BeneficiaryList []common.Address `toml:",omitempty"` // List of wallet addresses that have benefit at every new block (list mode)
MiningBeneficiary *common.Address `toml:",omitempty"` // Wallet address that benefits at every new block (besu mode)
Transitions []params.Transition
ValidatorContract common.Address
Client bind.ContractCaller `toml:",omitempty"`
Expand Down Expand Up @@ -196,6 +201,18 @@ func (c Config) GetConfig(blockNumber *big.Int) Config {
if transition.EmptyBlockPeriodSeconds != nil {
newConfig.EmptyBlockPeriod = *transition.EmptyBlockPeriodSeconds
}
if transition.BeneficiaryMode != nil { // besu mode
newConfig.BeneficiaryMode = transition.BeneficiaryMode
}
if transition.BeneficiaryList != nil { // list mode
newConfig.BeneficiaryList = transition.BeneficiaryList
}
if transition.BlockReward != nil { // besu mode
newConfig.BlockReward = transition.BlockReward
}
if transition.MiningBeneficiary != nil {
newConfig.MiningBeneficiary = transition.MiningBeneficiary
}
})

return newConfig
Expand Down
84 changes: 79 additions & 5 deletions consensus/istanbul/qbft/engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,19 @@ import (
"bytes"
"fmt"
"math/big"
"strings"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/istanbul"
"github.com/ethereum/go-ethereum/consensus/istanbul/backend/contract"
istanbulcommon "github.com/ethereum/go-ethereum/consensus/istanbul/common"
"github.com/ethereum/go-ethereum/consensus/istanbul/validator"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie"
Expand Down Expand Up @@ -360,18 +364,16 @@ func WriteValidators(validators []common.Address) ApplyQBFTExtra {
// Note, the block header and state database might be updated to reflect any
// consensus rules that happen at finalization (e.g. block rewards).
func (e *Engine) Finalize(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
// No block rewards in Istanbul, so the state remains as is and uncles are dropped
// Accumulate any block and uncle rewards and commit the final state root
e.accumulateRewards(chain, state, header, uncles, e.cfg.GetConfig(header.Number))
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.UncleHash = nilUncleHash
}

// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
// nor block rewards given, and returns the final block.
func (e *Engine) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
/// No block rewards in Istanbul, so the state remains as is and uncles are dropped
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
header.UncleHash = nilUncleHash

e.Finalize(chain, header, state, txs, uncles)
// Assemble and return the final block for sealing
return types.NewBlock(header, txs, nil, receipts, new(trie.Trie)), nil
}
Expand Down Expand Up @@ -532,3 +534,75 @@ func setExtra(h *types.Header, qbftExtra *types.QBFTExtra) error {
h.Extra = payload
return nil
}

func (e *Engine) validatorsList(genesis *types.Header, config istanbul.Config) ([]common.Address, error) {
var validators []common.Address
if config.ValidatorContract != (common.Address{}) && config.GetValidatorSelectionMode(big.NewInt(0)) == params.ContractMode {
log.Info("Initialising snap with contract validators", "address", config.ValidatorContract, "client", config.Client)

validatorContractCaller, err := contract.NewValidatorContractInterfaceCaller(config.ValidatorContract, config.Client)

if err != nil {
return nil, fmt.Errorf("invalid smart contract in genesis alloc: %w", err)
}

opts := bind.CallOpts{
Pending: false,
BlockNumber: big.NewInt(0),
}
validators, err = validatorContractCaller.GetValidators(&opts)
if err != nil {
log.Error("QBFT: invalid smart contract in genesis alloc", "err", err)
return nil, err
}
} else {
// Get the validators from genesis to create a snapshot
var err error
validators, err = e.ExtractGenesisValidators(genesis)
if err != nil {
log.Error("BFT: invalid genesis block", "err", err)
return nil, err
}
}
return validators, nil
}

// AccumulateRewards credits the beneficiary of the given block with a reward.
func (e *Engine) accumulateRewards(chain consensus.ChainHeaderReader, state *state.StateDB, header *types.Header, uncles []*types.Header, cfg istanbul.Config) {
blockReward := cfg.BlockReward
if blockReward == nil {
return // no reward
}
// Accumulate the rewards for a beneficiary
reward := big.Int(*blockReward)
if cfg.BeneficiaryMode == nil || *cfg.BeneficiaryMode == "" {
if cfg.MiningBeneficiary != nil {
state.AddBalance(*cfg.MiningBeneficiary, &reward) // implicit besu compatible mode
}
return
}
switch strings.ToLower(*cfg.BeneficiaryMode) {
case "fixed":
if cfg.MiningBeneficiary != nil {
state.AddBalance(*cfg.MiningBeneficiary, &reward)
}
case "list":
for _, b := range cfg.BeneficiaryList {
state.AddBalance(b, &reward)
}
case "validators":
genesis := chain.GetHeaderByNumber(0)
if err := e.VerifyHeader(chain, genesis, nil, nil); err != nil {
log.Error("BFT: invalid genesis block", "err", err)
return
}
list, err := e.validatorsList(genesis, cfg)
if err != nil {
log.Error("get validators list", "err", err)
return
}
for _, b := range list {
state.AddBalance(b, &reward)
}
}
}
129 changes: 129 additions & 0 deletions consensus/istanbul/qbft/engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,22 @@ package qbftengine

import (
"bytes"
"fmt"
"math/big"
"reflect"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/istanbul"
istanbulcommon "github.com/ethereum/go-ethereum/consensus/istanbul/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestPrepareExtra(t *testing.T) {
Expand Down Expand Up @@ -159,3 +167,124 @@ func TestWriteValidatorVote(t *testing.T) {
t.Errorf("extra data mismatch: have %v, want %v", istExtra, expectedIstExtra)
}
}

func TestAccumulateRewards(t *testing.T) {
addr := common.HexToAddress("0xed9d02e382b34818e88b88a309c7fe71e65f419d")
fixedMode := "fixed"
listMode := "list"
validatorsMode := "validators"
emptyMode := ""
dummyMode := "dummy"
m := []struct {
addr common.Address
miningBeneficiary *common.Address
balance *big.Int
blockReward *math.HexOrDecimal256
mode *string
list []common.Address
expectedBalance *big.Int
}{
{ // off
addr: addr,
miningBeneficiary: nil,
balance: big.NewInt(1),
blockReward: math.NewHexOrDecimal256(1),
mode: nil,
list: nil,
expectedBalance: big.NewInt(1),
},
{ // auto/default
addr: addr,
miningBeneficiary: &addr,
balance: big.NewInt(1),
blockReward: math.NewHexOrDecimal256(1),
mode: nil,
list: nil,
expectedBalance: big.NewInt(2),
},
{ // failing
addr: addr,
miningBeneficiary: nil,
balance: big.NewInt(1),
blockReward: math.NewHexOrDecimal256(1),
mode: &fixedMode,
list: nil,
expectedBalance: big.NewInt(1),
},
{
addr: addr,
miningBeneficiary: &addr,
balance: big.NewInt(1),
blockReward: math.NewHexOrDecimal256(1),
mode: &fixedMode,
list: nil,
expectedBalance: big.NewInt(2),
},
{ // failing
addr: addr,
miningBeneficiary: nil,
balance: big.NewInt(1),
blockReward: math.NewHexOrDecimal256(1),
mode: &listMode,
list: nil,
expectedBalance: big.NewInt(1),
},
{
addr: addr,
miningBeneficiary: nil,
balance: big.NewInt(1),
blockReward: math.NewHexOrDecimal256(1),
mode: &listMode,
list: []common.Address{addr},
expectedBalance: big.NewInt(2),
},
{
addr: addr,
miningBeneficiary: nil,
balance: big.NewInt(1),
blockReward: math.NewHexOrDecimal256(1),
mode: &validatorsMode,
expectedBalance: big.NewInt(1),
},
{
addr: addr,
miningBeneficiary: nil,
balance: big.NewInt(1),
blockReward: math.NewHexOrDecimal256(1),
mode: &emptyMode,
expectedBalance: big.NewInt(1),
},
{
addr: addr,
miningBeneficiary: nil,
balance: big.NewInt(1),
blockReward: math.NewHexOrDecimal256(1),
mode: &dummyMode,
expectedBalance: big.NewInt(1),
},
}
var e *Engine
chain := &core.BlockChain{}
db := state.NewDatabaseWithConfig(rawdb.NewMemoryDatabase(), nil)
state, err := state.New(common.Hash{}, db, nil)
require.NoError(t, err)

header := &types.Header{
Number: big.NewInt(1),
}
for idx, te := range m {
if te.mode == &validatorsMode {
continue // skip, it's not testable yet
}
state.SetBalance(te.addr, te.balance)
cfg := istanbul.Config{
BlockReward: te.blockReward,
BeneficiaryMode: te.mode,
BeneficiaryList: te.list,
MiningBeneficiary: te.miningBeneficiary,
}
e.accumulateRewards(chain, state, header, nil, cfg)
balance := state.GetBalance(te.addr)
assert.Equal(t, te.expectedBalance, balance, fmt.Sprintf("index: %d", idx), te)
}
}