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

HIP-30: Emission split #4497

Merged
merged 15 commits into from
Sep 2, 2023
Merged
13 changes: 11 additions & 2 deletions core/staking_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,20 @@ func VerifyAndEditValidatorFromMsg(
return nil, errCommissionRateChangeTooHigh
}

if chainContext.Config().IsMinCommissionRate(epoch) && newRate.LT(availability.MinCommissionRate) {
minRate := availability.MinCommissionRate(
chainContext.Config().IsMinCommissionRate(epoch),
chainContext.Config().IsHIP30(epoch),
)
if newRate.LT(minRate) {
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
return nil,
errors.Errorf(
"%s %d%%",
errCommissionRateChangeTooLowT,
minRate.MulInt64(100).Int64(),
)
}
}

Expand Down
2 changes: 1 addition & 1 deletion core/staking_verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,7 +676,7 @@ func TestVerifyAndEditValidatorFromMsg(t *testing.T) {
return msg
}(),

expErr: errCommissionRateChangeTooLow,
expErr: errCommissionRateChangeTooLowT,
},
{
// 15: Rate is ok within the promo period
Expand Down
2 changes: 1 addition & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,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%")
errCommissionRateChangeTooLowT = errors.New("commission rate can not be lower than min rate of ")
errNoRewardsToCollect = errors.New("no rewards to collect")
errNegativeAmount = errors.New("amount can not be negative")
errDupIdentity = errors.New("validator identity exists")
Expand Down
66 changes: 57 additions & 9 deletions internal/chain/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"sort"
"time"

"github.com/harmony-one/harmony/common/denominations"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/numeric"

bls2 "github.com/harmony-one/bls/ffi/go/bls"
blsvrf "github.com/harmony-one/harmony/crypto/vrf/bls"
Expand Down Expand Up @@ -285,7 +287,7 @@ func (e *engineImpl) Finalize(
// depends on the old LastEpochInCommittee

startTime = time.Now()
if err := setElectionEpochAndMinFee(header, state, chain.Config()); err != nil {
if err := setElectionEpochAndMinFee(chain, header, state, chain.Config()); err != nil {
return nil, nil, err
}
utils.Logger().Debug().Int64("elapsed time", time.Now().Sub(startTime).Milliseconds()).Msg("SetElectionEpochAndMinFee")
Expand All @@ -311,7 +313,7 @@ func (e *engineImpl) Finalize(

// Accumulate block rewards and commit the final state root
// Header seems complete, assemble into a block and return
payout, err := AccumulateRewardsAndCountSigs(
remainder, payout, err := AccumulateRewardsAndCountSigs(
chain, state, header, beacon, sigsReady,
)
if err != nil {
Expand All @@ -331,6 +333,23 @@ func (e *engineImpl) Finalize(
// TODO: make the viewID fetch from caller of the block proposal.
header.SetViewID(new(big.Int).SetUint64(viewID()))

// Add the emission recovery split to the balance
if chain.Config().IsHIP30(header.Epoch()) {
// convert to ONE - note that numeric.Dec
// is designed for staking decimals and not
// ONE balances so we use big.Int for this math
remainderOne := new(big.Int).Div(
remainder.Int, big.NewInt(denominations.One),
)
// this goes directly to the balance (on shard 0, of course)
// because the reward mechanism isn't built to handle
// rewards not obtained from any delegations
state.AddBalance(
shard.Schedule.InstanceForEpoch(header.Epoch()).
HIP30RecoveryAddress(),
remainderOne,
)
}
// Finalize the state root
header.SetRoot(state.IntermediateRoot(chain.Config().IsS3(header.Epoch())))
return types.NewBlock(header, txs, receipts, outcxs, incxs, stks), payout, nil
Expand Down Expand Up @@ -390,12 +409,23 @@ func IsCommitteeSelectionBlock(chain engine.ChainReader, header *block.Header) b
return isBeaconChain && header.IsLastBlockInEpoch() && inPreStakingEra
}

func setElectionEpochAndMinFee(header *block.Header, state *state.DB, config *params.ChainConfig) error {
func setElectionEpochAndMinFee(chain engine.ChainReader, 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"
return errors.New(msg)
}
// these 2 should be created outside of loop to optimize
minRate := availability.MinCommissionRate(
config.IsMinCommissionRate(newShardState.Epoch),
config.IsHIP30(newShardState.Epoch),
)
minRateNotZero := !minRate.Equal(numeric.ZeroDec())
// elected validators have their fee updated, if required to do so
isElected := make(
map[common.Address]struct{},
len(newShardState.StakedValidators().Addrs),
)
for _, addr := range newShardState.StakedValidators().Addrs {
wrapper, err := state.ValidatorWrapper(addr, true, false)
if err != nil {
Expand All @@ -405,14 +435,32 @@ func setElectionEpochAndMinFee(header *block.Header, state *state.DB, config *pa
}
// Set last epoch in committee
wrapper.LastEpochInCommittee = newShardState.Epoch

if config.IsMinCommissionRate(newShardState.Epoch) {
// Set first election epoch
if minRateNotZero {
// Set first election epoch (applies only if previously unset)
state.SetValidatorFirstElectionEpoch(addr, newShardState.Epoch)

// Update minimum commission fee
if err := availability.UpdateMinimumCommissionFee(
newShardState.Epoch, state, addr, config.MinCommissionPromoPeriod.Int64(),
if _, err := availability.UpdateMinimumCommissionFee(
newShardState.Epoch, state, addr, minRate,
config.MinCommissionPromoPeriod.Uint64(),
); err != nil {
return err
}
}
isElected[addr] = struct{}{}
}
// due to a bug in the old implementation of the minimum fee,
// unelected validators did not have their fee updated even
// when the protocol required them to do so. here we fix it,
// but only after the HIP-30 hard fork is effective.
if config.IsHIP30(newShardState.Epoch) {
for _, addr := range chain.ValidatorCandidates() {
// skip elected validator
if _, ok := isElected[addr]; ok {
continue
}
if _, err := availability.UpdateMinimumCommissionFee(
newShardState.Epoch, state, addr, minRate,
config.MinCommissionPromoPeriod.Uint64(),
); err != nil {
return err
}
Expand Down
Loading