Add min commission rate logic (#3715)

* Add min commission rate logic
pull/3718/head
Rongjian Lan 4 years ago committed by GitHub
parent 4360503bd3
commit b0bf39861d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      core/evm.go
  2. 10
      core/staking_verifier.go
  3. 63
      core/staking_verifier_test.go
  4. 17
      core/state/statedb.go
  5. 1
      core/state_transition.go
  6. 1
      core/vm/interface.go
  7. 19
      internal/chain/engine.go
  8. 27
      internal/params/config.go
  9. 35
      staking/availability/measure.go
  10. 2
      staking/params.go
  11. 4
      staking/types/test/prototype.go

@ -19,6 +19,8 @@ package core
import (
"math/big"
"github.com/harmony-one/harmony/internal/params"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/block"
consensus_engine "github.com/harmony-one/harmony/consensus/engine"
@ -44,6 +46,9 @@ type ChainContext interface {
// ReadValidatorList returns the list of all validators
ReadValidatorList() ([]common.Address, error)
// Config returns chain config
Config() *params.ChainConfig
}
// NewEVMContext creates a new context for use in the EVM.

@ -4,6 +4,8 @@ import (
"bytes"
"math/big"
"github.com/harmony-one/harmony/staking/availability"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/crypto/bls"
@ -166,6 +168,14 @@ func VerifyAndEditValidatorFromMsg(
return nil, errCommissionRateChangeTooHigh
}
if chainContext.Config().IsMinCommissionRate(epoch) && newRate.LT(availability.MinCommissionRate) {
firstEpoch := stateDB.GetValidatorFirstElectionEpoch(msg.ValidatorAddress)
promoPeriod := chainContext.Config().MinCommissionPromoPeriod.Int64()
if firstEpoch.Uint64() != 0 && big.NewInt(0).Sub(epoch, firstEpoch).Int64() >= promoPeriod {
return nil, errCommissionRateChangeTooLow
}
}
snapshotValidator, err := chainContext.ReadValidatorSnapshot(wrapper.Address)
if err != nil {
return nil, errors.WithMessage(err, "validator snapshot not found.")

@ -55,8 +55,10 @@ var (
hundredKOnes = new(big.Int).Mul(big.NewInt(100000), oneBig)
negRate = numeric.NewDecWithPrec(-1, 10)
pointZeroOneDec = numeric.NewDecWithPrec(1, 2)
pointOneDec = numeric.NewDecWithPrec(1, 1)
pointTwoDec = numeric.NewDecWithPrec(2, 1)
pointFourDec = numeric.NewDecWithPrec(4, 1)
pointFiveDec = numeric.NewDecWithPrec(5, 1)
pointSevenDec = numeric.NewDecWithPrec(7, 1)
pointEightFiveDec = numeric.NewDecWithPrec(85, 2)
@ -465,7 +467,7 @@ func TestVerifyAndEditValidatorFromMsg(t *testing.T) {
expWrapper: func() staking.ValidatorWrapper {
vw := defaultExpWrapperEditValidator()
vw.UpdateHeight = big.NewInt(defaultSnapBlockNumber)
vw.Rate = pointFiveDec
vw.Rate = pointFourDec
return vw
}(),
},
@ -655,6 +657,51 @@ func TestVerifyAndEditValidatorFromMsg(t *testing.T) {
expErr: errors.New("banned status"),
},
{
// 14: Rate is lower than min rate of 5%
sdb: makeStateDBForStake(t),
bc: func() *fakeChainContext {
chain := makeFakeChainContextForStake()
vw := chain.vWrappers[validatorAddr]
vw.Rate = pointFourDec
chain.vWrappers[validatorAddr] = vw
return chain
}(),
epoch: big.NewInt(20),
blockNum: big.NewInt(defaultBlockNumber),
msg: func() staking.EditValidator {
msg := defaultMsgEditValidator()
msg.CommissionRate = &pointZeroOneDec
return msg
}(),
expErr: errCommissionRateChangeTooLow,
},
{
// 15: Rate is ok within the promo period
sdb: makeStateDBForStake(t),
bc: func() *fakeChainContext {
chain := makeFakeChainContextForStake()
vw := chain.vWrappers[validatorAddr]
vw.Rate = pointFourDec
chain.vWrappers[validatorAddr] = vw
return chain
}(),
epoch: big.NewInt(15),
blockNum: big.NewInt(defaultBlockNumber),
msg: func() staking.EditValidator {
msg := defaultMsgEditValidator()
msg.CommissionRate = &pointZeroOneDec
return msg
}(),
expWrapper: func() staking.ValidatorWrapper {
vw := defaultExpWrapperEditValidator()
vw.UpdateHeight = big.NewInt(defaultBlockNumber)
vw.Rate = pointZeroOneDec
return vw
}(),
},
}
for i, test := range tests {
w, err := VerifyAndEditValidatorFromMsg(test.sdb, test.bc, test.epoch, test.blockNum,
@ -1588,6 +1635,7 @@ func updateStateValidators(sdb *state.DB, ws []*staking.ValidatorWrapper) error
for i, w := range ws {
sdb.SetValidatorFlag(w.Address)
sdb.AddBalance(w.Address, hundredKOnes)
sdb.SetValidatorFirstElectionEpoch(w.Address, big.NewInt(10))
if err := sdb.UpdateValidatorWrapper(w.Address, w); err != nil {
return fmt.Errorf("update %v vw error: %v", i, err)
}
@ -1675,6 +1723,13 @@ func (chain *fakeChainContext) Engine() consensus_engine.Engine {
return nil
}
func (chain *fakeChainContext) Config() *params.ChainConfig {
config := &params.ChainConfig{}
config.MinCommissionRateEpoch = big.NewInt(0)
config.MinCommissionPromoPeriod = big.NewInt(10)
return config
}
func (chain *fakeChainContext) GetHeader(common.Hash, uint64) *block.Header {
return nil
}
@ -1709,6 +1764,12 @@ func (chain *fakeErrChainContext) GetHeader(common.Hash, uint64) *block.Header {
return nil
}
func (chain *fakeErrChainContext) Config() *params.ChainConfig {
config := &params.ChainConfig{}
config.MinCommissionRateEpoch = big.NewInt(10)
return config
}
func (chain *fakeErrChainContext) ReadDelegationsByDelegator(common.Address) (staking.DelegationIndexes, error) {
return nil, nil
}

@ -859,6 +859,23 @@ func (db *DB) UpdateValidatorWrapper(
return nil
}
// SetValidatorFirstElectionEpoch sets the epoch when the validator is first elected
func (db *DB) SetValidatorFirstElectionEpoch(addr common.Address, epoch *big.Int) {
firstEpoch := db.GetValidatorFirstElectionEpoch(addr)
if firstEpoch.Uint64() == 0 {
// Set only when it's not set (or it's 0)
bytes := common.BigToHash(epoch)
db.SetState(addr, staking.FirstElectionEpochKey, bytes)
}
}
// GetValidatorFirstElectionEpoch gets the epoch when the validator was first elected
func (db *DB) GetValidatorFirstElectionEpoch(addr common.Address) *big.Int {
so := db.getStateObject(addr)
value := so.GetState(db.db, staking.FirstElectionEpochKey)
return value.Big()
}
// SetValidatorFlag checks whether it is a validator object
func (db *DB) SetValidatorFlag(addr common.Address) {
db.SetState(addr, staking.IsValidatorKey, staking.IsValidator)

@ -43,6 +43,7 @@ var (
errNoDelegationToUndelegate = errors.New("no delegation to undelegate")
errCommissionRateChangeTooFast = errors.New("change on commission rate can not be more than max change rate within the same epoch")
errCommissionRateChangeTooHigh = errors.New("commission rate can not be higher than maximum commission rate")
errCommissionRateChangeTooLow = errors.New("commission rate can not be lower than min rate of 5%")
errNoRewardsToCollect = errors.New("no rewards to collect")
errNegativeAmount = errors.New("amount can not be negative")
errDupIdentity = errors.New("validator identity exists")

@ -47,6 +47,7 @@ type StateDB interface {
SetValidatorFlag(common.Address)
UnsetValidatorFlag(common.Address)
IsValidator(common.Address) bool
GetValidatorFirstElectionEpoch(addr common.Address) *big.Int
AddReward(*staking.ValidatorWrapper, *big.Int, map[common.Address]numeric.Dec) error
AddRefund(uint64)

@ -5,6 +5,8 @@ import (
"math/big"
"sort"
"github.com/harmony-one/harmony/internal/params"
bls2 "github.com/harmony-one/bls/ffi/go/bls"
blsvrf "github.com/harmony-one/harmony/crypto/vrf/bls"
@ -290,7 +292,7 @@ func (e *engineImpl) Finalize(
// Needs to be after payoutUndelegations because payoutUndelegations
// depends on the old LastEpochInCommittee
if err := setLastEpochInCommittee(header, state); err != nil {
if err := setElectionEpochAndMinFee(header, state, chain.Config()); err != nil {
return nil, nil, err
}
@ -392,7 +394,7 @@ func IsCommitteeSelectionBlock(chain engine.ChainReader, header *block.Header) b
return isBeaconChain && header.IsLastBlockInEpoch() && inPreStakingEra
}
func setLastEpochInCommittee(header *block.Header, state *state.DB) error {
func setElectionEpochAndMinFee(header *block.Header, state *state.DB, config *params.ChainConfig) error {
newShardState, err := header.GetShardState()
if err != nil {
const msg = "[Finalize] failed to read shard state"
@ -405,7 +407,20 @@ func setLastEpochInCommittee(header *block.Header, state *state.DB) error {
"[Finalize] failed to get validator from state to finalize",
)
}
// Set last epoch in committee
wrapper.LastEpochInCommittee = newShardState.Epoch
if config.IsMinCommissionRate(newShardState.Epoch) {
// Set first election epoch
state.SetValidatorFirstElectionEpoch(addr, newShardState.Epoch)
// Update minimum commission fee
if err := availability.UpdateMinimumCommissionFee(
newShardState.Epoch, state, addr, config.MinCommissionPromoPeriod.Int64(),
); err != nil {
return err
}
}
}
return nil
}

@ -52,6 +52,8 @@ var (
NoEarlyUnlockEpoch: big.NewInt(530), // Around Monday Apr 12th 2021, 22:30 UTC
VRFEpoch: EpochTBD,
MinDelegation100Epoch: EpochTBD,
MinCommissionRateEpoch: EpochTBD,
MinCommissionPromoPeriod: big.NewInt(100),
EPoSBound35Epoch: EpochTBD,
EIP155Epoch: big.NewInt(28),
S3Epoch: big.NewInt(28),
@ -77,6 +79,8 @@ var (
NoEarlyUnlockEpoch: big.NewInt(73580),
VRFEpoch: EpochTBD,
MinDelegation100Epoch: EpochTBD,
MinCommissionRateEpoch: EpochTBD,
MinCommissionPromoPeriod: big.NewInt(10),
EPoSBound35Epoch: EpochTBD,
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
@ -103,6 +107,8 @@ var (
NoEarlyUnlockEpoch: big.NewInt(0),
VRFEpoch: big.NewInt(0),
MinDelegation100Epoch: big.NewInt(0),
MinCommissionRateEpoch: big.NewInt(0),
MinCommissionPromoPeriod: big.NewInt(10),
EPoSBound35Epoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
@ -129,6 +135,8 @@ var (
NoEarlyUnlockEpoch: big.NewInt(0),
VRFEpoch: big.NewInt(0),
MinDelegation100Epoch: big.NewInt(0),
MinCommissionRateEpoch: big.NewInt(0),
MinCommissionPromoPeriod: big.NewInt(10),
EPoSBound35Epoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
@ -155,6 +163,8 @@ var (
NoEarlyUnlockEpoch: big.NewInt(0),
VRFEpoch: big.NewInt(0),
MinDelegation100Epoch: big.NewInt(0),
MinCommissionRateEpoch: big.NewInt(0),
MinCommissionPromoPeriod: big.NewInt(10),
EPoSBound35Epoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
@ -180,6 +190,8 @@ var (
NoEarlyUnlockEpoch: big.NewInt(0),
VRFEpoch: big.NewInt(0),
MinDelegation100Epoch: big.NewInt(0),
MinCommissionRateEpoch: big.NewInt(0),
MinCommissionPromoPeriod: big.NewInt(10),
EPoSBound35Epoch: big.NewInt(0),
EIP155Epoch: big.NewInt(0),
S3Epoch: big.NewInt(0),
@ -207,6 +219,8 @@ var (
big.NewInt(0), // NoEarlyUnlockEpoch
big.NewInt(0), // VRFEpoch
big.NewInt(0), // MinDelegation100Epoch
big.NewInt(0), // MinCommissionRateEpoch
big.NewInt(10), // MinCommissionPromoPeriod
big.NewInt(0), // EPoSBound35Epoch
big.NewInt(0), // EIP155Epoch
big.NewInt(0), // S3Epoch
@ -234,6 +248,8 @@ var (
big.NewInt(0), // NoEarlyUnlockEpoch
big.NewInt(0), // VRFEpoch
big.NewInt(0), // MinDelegation100Epoch
big.NewInt(0), // MinCommissionRateEpoch
big.NewInt(10), // MinCommissionPromoPeriod
big.NewInt(0), // EPoSBound35Epoch
big.NewInt(0), // EIP155Epoch
big.NewInt(0), // S3Epoch
@ -318,6 +334,12 @@ type ChainConfig struct {
// MinDelegation100Epoch is the epoch when min delegation is reduced from 1000 ONE to 100 ONE
MinDelegation100Epoch *big.Int `json:"min-delegation-100-epoch,omitempty"`
// MinCommissionRateEpoch is the epoch when policy for minimum comission rate of 5% is started
MinCommissionRateEpoch *big.Int `json:"min-commission-rate-epoch,omitempty"`
// MinCommissionPromoPeriod is the number of epochs when newly elected validators can have 0% commission
MinCommissionPromoPeriod *big.Int `json:"commission-promo-period,omitempty"`
// EPoSBound35Epoch is the epoch when the EPoS bound parameter c is changed from 15% to 35%
EPoSBound35Epoch *big.Int `json:"epos-bound-35-epoch,omitempty"`
@ -417,6 +439,11 @@ func (c *ChainConfig) IsMinDelegation100(epoch *big.Int) bool {
return isForked(c.MinDelegation100Epoch, epoch)
}
// IsMinCommissionRate determines whether it is the epoch to start the policy of 5% min commission
func (c *ChainConfig) IsMinCommissionRate(epoch *big.Int) bool {
return isForked(c.MinCommissionRateEpoch, epoch)
}
// IsEPoSBound35 determines whether it is the epoch to extend the EPoS bound to 35%
func (c *ChainConfig) IsEPoSBound35(epoch *big.Int) bool {
return isForked(c.EPoSBound35Epoch, epoch)

@ -3,6 +3,8 @@ package availability
import (
"math/big"
"github.com/harmony-one/harmony/core/state"
"github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/crypto/bls"
"github.com/harmony-one/harmony/internal/utils"
@ -15,6 +17,8 @@ import (
var (
measure = numeric.NewDec(2).Quo(numeric.NewDec(3))
MinCommissionRate = numeric.MustNewDecFromStr("0.05")
// ErrDivByZero ..
ErrDivByZero = errors.New("toSign of availability cannot be 0, mistake in protocol")
)
@ -215,3 +219,34 @@ func ComputeAndMutateEPOSStatus(
return nil
}
// UpdateMinimumCommissionFee update the validator commission fee to the minimum 5%
// if the validator has a lower commission rate and 100 epochs have passed after
// the validator was first elected.
func UpdateMinimumCommissionFee(
electionEpoch *big.Int,
state *state.DB,
addr common.Address,
promoPeriod int64,
) error {
utils.Logger().Info().Msg("begin update min commission fee")
wrapper, err := state.ValidatorWrapper(addr)
if err != nil {
return err
}
firstElectionEpoch := state.GetValidatorFirstElectionEpoch(addr)
if firstElectionEpoch.Uint64() != 0 && big.NewInt(0).Sub(electionEpoch, firstElectionEpoch).Int64() >= int64(promoPeriod) {
if wrapper.Rate.LT(MinCommissionRate) {
utils.Logger().Info().
Str("addr", addr.Hex()).
Str("old rate", wrapper.Rate.String()).
Str("firstElectionEpoch", firstElectionEpoch.String()).
Msg("updating min commission rate")
wrapper.Rate.SetBytes(MinCommissionRate.Bytes())
}
}
return nil
}

@ -9,6 +9,7 @@ const (
isValidatorStr = "Harmony/IsValidator/Value/v1"
collectRewardsStr = "Harmony/CollectRewards"
delegateStr = "Harmony/Delegate"
firstElectionEpochStr = "Harmony/FirstElectionEpoch/Key/v1"
)
// keys used to retrieve staking related informatio
@ -17,4 +18,5 @@ var (
IsValidator = crypto.Keccak256Hash([]byte(isValidatorStr))
CollectRewardsTopic = crypto.Keccak256Hash([]byte(collectRewardsStr))
DelegateTopic = crypto.Keccak256Hash([]byte(delegateStr))
FirstElectionEpochKey = crypto.Keccak256Hash([]byte(firstElectionEpochStr))
)

@ -59,9 +59,9 @@ var (
}
commissionRates = staking.CommissionRates{
Rate: numeric.NewDecWithPrec(5, 1),
Rate: numeric.NewDecWithPrec(4, 1),
MaxRate: numeric.NewDecWithPrec(9, 1),
MaxChangeRate: numeric.NewDecWithPrec(3, 1),
MaxChangeRate: numeric.NewDecWithPrec(4, 1),
}
commission = staking.Commission{

Loading…
Cancel
Save