Skip to content
This repository has been archived by the owner on Feb 18, 2025. It is now read-only.

Commit

Permalink
consortium-v2: make finality vote weight proportional to staked amount
Browse files Browse the repository at this point in the history
Currently, the finality vote weights of all validators are the same. After this
commit, the finality vote weight is based on the staked amount of the validator.
The rule of finality vote weight follows what is stated in REP-0010: Introducing
Rotating Validators

	- If the staked amount of a validator is smaller than or equal to 1/22
	  of total stake, the weight is directly proportional to the staked
amount.
	- If the staked amount of a validator is bigger than 1/22 of total
	  stake, the weight equals 1/22 of total stake.
  • Loading branch information
minh-bq committed Mar 11, 2024
1 parent 40b4198 commit 496520a
Show file tree
Hide file tree
Showing 11 changed files with 578 additions and 21 deletions.
5 changes: 3 additions & 2 deletions consensus/consortium/common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
)

const (
ExtraSeal = crypto.SignatureLength
ExtraVanity = 32
ExtraSeal = crypto.SignatureLength
ExtraVanity = 32
MaxFinalityVotePercentage uint16 = 10_000
)

var (
Expand Down
23 changes: 23 additions & 0 deletions consensus/consortium/common/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/consortium/generated_contracts/profile"
roninValidatorSet "github.com/ethereum/go-ethereum/consensus/consortium/generated_contracts/ronin_validator_set"
slashIndicator "github.com/ethereum/go-ethereum/consensus/consortium/generated_contracts/slash_indicator"
"github.com/ethereum/go-ethereum/consensus/consortium/generated_contracts/staking"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
Expand Down Expand Up @@ -61,6 +62,7 @@ type ContractInteraction interface {
Slash(opts *ApplyTransactOpts, spoiledValidator common.Address) error
FinalityReward(opts *ApplyTransactOpts, votedValidators []common.Address) error
GetBlsPublicKey(blockNumber *big.Int, validator common.Address) (blsCommon.PublicKey, error)
GetStakedAmount(blockNumber *big.Int, validators []common.Address) ([]*big.Int, error)
}

// ContractIntegrator is a contract facing to interact with smart contract that supports DPoS
Expand All @@ -71,6 +73,7 @@ type ContractIntegrator struct {
slashIndicatorSC *slashIndicator.SlashIndicator
profileSC *profile.Profile
finalityTrackingSC *finalityTracking.FinalityTracking
stakingSC *staking.Staking
signTxFn SignerTxFn
coinbase common.Address
}
Expand Down Expand Up @@ -101,12 +104,19 @@ func NewContractIntegrator(config *chainParams.ChainConfig, backend bind.Contrac
return nil, err
}

// Create Staking contract instance
stakingSC, err := staking.NewStaking(config.ConsortiumV2Contracts.StakingContract, backend)
if err != nil {
return nil, err
}

return &ContractIntegrator{
chainId: config.ChainID,
roninValidatorSetSC: roninValidatorSetSC,
slashIndicatorSC: slashIndicatorSC,
profileSC: profileSC,
finalityTrackingSC: finalityTrackingSC,
stakingSC: stakingSC,
signTxFn: signTxFn,
signer: types.LatestSignerForChainID(config.ChainID),
coinbase: coinbase,
Expand Down Expand Up @@ -265,6 +275,19 @@ func (c *ContractIntegrator) GetBlsPublicKey(blockNumber *big.Int, validator com
return blsPublicKey, nil
}

func (c *ContractIntegrator) GetStakedAmount(blockNumber *big.Int, validators []common.Address) ([]*big.Int, error) {
callOpts := bind.CallOpts{
BlockNumber: blockNumber,
}

stakedAmount, err := c.stakingSC.GetManyStakingTotals(&callOpts, validators)
if err != nil {
return nil, err
}

return stakedAmount, nil
}

// ApplyMessageOpts is the collection of options to fine tune a contract call request.
type ApplyMessageOpts struct {
State *state.StateDB
Expand Down
8 changes: 6 additions & 2 deletions consensus/consortium/common/mock_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
var Validators *MockValidators

type MockValidators struct {
validators []common.Address
validators []common.Address
blsPublicKeys map[common.Address]blsCommon.PublicKey
}

Expand All @@ -25,7 +25,7 @@ func SetMockValidators(validators, publicKeys string) error {
return errors.New("mismatch length between mock validators and mock blsPubKey")
}
Validators = &MockValidators{
validators: make([]common.Address, len(vals)),
validators: make([]common.Address, len(vals)),
blsPublicKeys: make(map[common.Address]blsCommon.PublicKey),
}
for i, val := range vals {
Expand Down Expand Up @@ -80,3 +80,7 @@ func (contract *MockContract) FinalityReward(*ApplyTransactOpts, []common.Addres
func (contract *MockContract) GetBlsPublicKey(_ *big.Int, addr common.Address) (blsCommon.PublicKey, error) {
return Validators.GetPublicKey(addr)
}

func (contract *MockContract) GetStakedAmount(_ *big.Int, _ []common.Address) ([]*big.Int, error) {
return nil, nil
}
51 changes: 50 additions & 1 deletion consensus/consortium/common/utils.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package common

import (
"github.com/ethereum/go-ethereum/common"
"math/big"
"sort"

"github.com/ethereum/go-ethereum/common"
)

// ExtractAddressFromBytes extracts validators' address from extra data in header
Expand Down Expand Up @@ -60,3 +62,50 @@ func RemoveOutdatedRecents(recents map[uint64]common.Address, currentBlock uint6

return newRecents
}

// 1. The vote weight of each validator is validator pool's staked amount / total staked of all validator's pools
// 2. If the vote weight of a validator is higher than 1 / n, then the vote weight is 1 / n with n is the number
// of validators
// 3. After the step 2, the total vote weight might be lower than 1. Normalize the vote weight to make total vote
// weight is 1 (new vote weight = current vote weight / current total vote weight) (after this step, the total vote
// weight might not be 1 due to precision problem, but it is neglectible with small n)
//
// For vote weight, we don't use floating pointer number but multiply the vote weight with MaxFinalityVotePercentage
// and store vote weight in integer type. The precision of calculation is based on MaxFinalityVotePercentage.
func NormalizeFinalityVoteWeight(stakedAmounts []*big.Int) []uint16 {
var (
totalStakedAmount = big.NewInt(0)
finalityVoteWeight []uint16
maxVoteWeight uint16
totalVoteWeight uint
)

// Calculate the maximum vote weight of each validator for step 2
// 1 * MaxFinalityVotePercentage / n
maxVoteWeight = MaxFinalityVotePercentage / uint16(len(stakedAmounts))

for _, stakedAmount := range stakedAmounts {
totalStakedAmount.Add(totalStakedAmount, stakedAmount)
}

// Step 1, 2
for _, stakedAmount := range stakedAmounts {
weight := new(big.Int).Mul(stakedAmount, big.NewInt(int64(MaxFinalityVotePercentage)))
weight.Div(weight, totalStakedAmount)

w := uint16(weight.Uint64())
if w > maxVoteWeight {
w = maxVoteWeight
}
totalVoteWeight += uint(w)
finalityVoteWeight = append(finalityVoteWeight, w)
}

// Step 3
for i, weight := range finalityVoteWeight {
normalizedWeight := uint16(uint(weight) * uint(MaxFinalityVotePercentage) / totalVoteWeight)
finalityVoteWeight[i] = normalizedWeight
}

return finalityVoteWeight
}
59 changes: 59 additions & 0 deletions consensus/consortium/common/utils_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package common

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

Expand Down Expand Up @@ -65,3 +66,61 @@ func TestRemoveInvalidRecents(t *testing.T) {
t.Errorf("Expect %v but got %v", expected, actual)
}
}

func TestNormalizeFinalityVoteWeight(t *testing.T) {
// All staked amounts are equal
var stakedAmounts []*big.Int
for i := 0; i < 22; i++ {
stakedAmounts = append(stakedAmounts, big.NewInt(1_000_000))
}

voteWeights := NormalizeFinalityVoteWeight(stakedAmounts)
for _, voteWeight := range voteWeights {
if voteWeight != 454 {
t.Fatalf("Incorrect vote weight, expect %d got %d", 454, voteWeight)
}
}

// All staked amount differs
for i := 0; i < 22; i++ {
stakedAmounts[i] = big.NewInt(int64(i) + 1)
}
voteWeights = NormalizeFinalityVoteWeight(stakedAmounts)
expectedVoteWeights := []uint16{51, 103, 155, 207, 259, 311, 363, 415, 467, 519, 571, 597, 597, 597, 597, 597, 597, 597, 597, 597, 597, 597}

for i := range voteWeights {
if voteWeights[i] != expectedVoteWeights[i] {
t.Fatalf("Incorrect vote weight, expect %d got %d", expectedVoteWeights[i], voteWeights[i])
}
}

// Staked amount differences are small
for i := 0; i < 22; i++ {
stakedAmounts[i] = big.NewInt(int64(i) + 1_000_000)
}
voteWeights = NormalizeFinalityVoteWeight(stakedAmounts)
for i := range voteWeights {
if voteWeights[i] != 454 {
t.Fatalf("Incorrect vote weight, expect %d got %d", 454, voteWeights[i])
}
}

// Some staked amounts differ greatly
for i := 0; i < 20; i++ {
stakedAmounts[i] = big.NewInt(1_000_000)
}
stakedAmounts[20] = big.NewInt(1000)
stakedAmounts[21] = big.NewInt(2500)
voteWeights = NormalizeFinalityVoteWeight(stakedAmounts)
for i := 0; i < 20; i++ {
if voteWeights[i] != 499 {
t.Fatalf("Incorrect vote weight, expect %d got %d", 499, voteWeights[i])
}
}
if voteWeights[20] != 0 {
t.Fatalf("Incorrect vote weight, expect %d got %d", 0, voteWeights[20])
}
if voteWeights[21] != 1 {
t.Fatalf("Incorrect vote weight, expect %d got %d", 1, voteWeights[21])
}
}
Loading

0 comments on commit 496520a

Please sign in to comment.