cosmos-sdk/x/staking/keeper/validator.go
Marko c455e5e0bd
feat: Min commission upgrade 0.44 (#10529)
<!--
The default pull request template is for types feat, fix, or refactor.
For other templates, add one of the following parameters to the url:
- template=docs.md
- template=other.md
-->

## Description

Added the ability to set a minimum commission rate that all validators cannot set their commission rate below.

replaces https://github.com/cosmos/cosmos-sdk/pull/10422

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
2021-11-12 11:55:55 +00:00

452 lines
15 KiB
Go

package keeper
import (
"fmt"
"time"
gogotypes "github.com/gogo/protobuf/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
// get a single validator
func (k Keeper) GetValidator(ctx sdk.Context, addr sdk.ValAddress) (validator types.Validator, found bool) {
store := ctx.KVStore(k.storeKey)
value := store.Get(types.GetValidatorKey(addr))
if value == nil {
return validator, false
}
validator = types.MustUnmarshalValidator(k.cdc, value)
return validator, true
}
func (k Keeper) mustGetValidator(ctx sdk.Context, addr sdk.ValAddress) types.Validator {
validator, found := k.GetValidator(ctx, addr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %X\n", addr))
}
return validator
}
// get a single validator by consensus address
func (k Keeper) GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator types.Validator, found bool) {
store := ctx.KVStore(k.storeKey)
opAddr := store.Get(types.GetValidatorByConsAddrKey(consAddr))
if opAddr == nil {
return validator, false
}
return k.GetValidator(ctx, opAddr)
}
func (k Keeper) mustGetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) types.Validator {
validator, found := k.GetValidatorByConsAddr(ctx, consAddr)
if !found {
panic(fmt.Errorf("validator with consensus-Address %s not found", consAddr))
}
return validator
}
// set the main record holding validator details
func (k Keeper) SetValidator(ctx sdk.Context, validator types.Validator) {
store := ctx.KVStore(k.storeKey)
bz := types.MustMarshalValidator(k.cdc, &validator)
store.Set(types.GetValidatorKey(validator.GetOperator()), bz)
}
// validator index
func (k Keeper) SetValidatorByConsAddr(ctx sdk.Context, validator types.Validator) error {
consPk, err := validator.GetConsAddr()
if err != nil {
return err
}
store := ctx.KVStore(k.storeKey)
store.Set(types.GetValidatorByConsAddrKey(consPk), validator.GetOperator())
return nil
}
// validator index
func (k Keeper) SetValidatorByPowerIndex(ctx sdk.Context, validator types.Validator) {
// jailed validators are not kept in the power index
if validator.Jailed {
return
}
store := ctx.KVStore(k.storeKey)
store.Set(types.GetValidatorsByPowerIndexKey(validator, k.PowerReduction(ctx)), validator.GetOperator())
}
// validator index
func (k Keeper) DeleteValidatorByPowerIndex(ctx sdk.Context, validator types.Validator) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.GetValidatorsByPowerIndexKey(validator, k.PowerReduction(ctx)))
}
// validator index
func (k Keeper) SetNewValidatorByPowerIndex(ctx sdk.Context, validator types.Validator) {
store := ctx.KVStore(k.storeKey)
store.Set(types.GetValidatorsByPowerIndexKey(validator, k.PowerReduction(ctx)), validator.GetOperator())
}
// Update the tokens of an existing validator, update the validators power index key
func (k Keeper) AddValidatorTokensAndShares(ctx sdk.Context, validator types.Validator,
tokensToAdd sdk.Int) (valOut types.Validator, addedShares sdk.Dec) {
k.DeleteValidatorByPowerIndex(ctx, validator)
validator, addedShares = validator.AddTokensFromDel(tokensToAdd)
k.SetValidator(ctx, validator)
k.SetValidatorByPowerIndex(ctx, validator)
return validator, addedShares
}
// Update the tokens of an existing validator, update the validators power index key
func (k Keeper) RemoveValidatorTokensAndShares(ctx sdk.Context, validator types.Validator,
sharesToRemove sdk.Dec) (valOut types.Validator, removedTokens sdk.Int) {
k.DeleteValidatorByPowerIndex(ctx, validator)
validator, removedTokens = validator.RemoveDelShares(sharesToRemove)
k.SetValidator(ctx, validator)
k.SetValidatorByPowerIndex(ctx, validator)
return validator, removedTokens
}
// Update the tokens of an existing validator, update the validators power index key
func (k Keeper) RemoveValidatorTokens(ctx sdk.Context,
validator types.Validator, tokensToRemove sdk.Int) types.Validator {
k.DeleteValidatorByPowerIndex(ctx, validator)
validator = validator.RemoveTokens(tokensToRemove)
k.SetValidator(ctx, validator)
k.SetValidatorByPowerIndex(ctx, validator)
return validator
}
// UpdateValidatorCommission attempts to update a validator's commission rate.
// An error is returned if the new commission rate is invalid.
func (k Keeper) UpdateValidatorCommission(ctx sdk.Context,
validator types.Validator, newRate sdk.Dec) (types.Commission, error) {
commission := validator.Commission
blockTime := ctx.BlockHeader().Time
if err := commission.ValidateNewRate(newRate, blockTime); err != nil {
return commission, err
}
if newRate.LT(k.MinCommissionRate(ctx)) {
return commission, fmt.Errorf("cannot set validator commission to less than minimum rate of %s", k.MinCommissionRate(ctx))
}
commission.Rate = newRate
commission.UpdateTime = blockTime
return commission, nil
}
// remove the validator record and associated indexes
// except for the bonded validator index which is only handled in ApplyAndReturnTendermintUpdates
// TODO, this function panics, and it's not good.
func (k Keeper) RemoveValidator(ctx sdk.Context, address sdk.ValAddress) {
// first retrieve the old validator record
validator, found := k.GetValidator(ctx, address)
if !found {
return
}
if !validator.IsUnbonded() {
panic("cannot call RemoveValidator on bonded or unbonding validators")
}
if validator.Tokens.IsPositive() {
panic("attempting to remove a validator which still contains tokens")
}
valConsAddr, err := validator.GetConsAddr()
if err != nil {
panic(err)
}
// delete the old validator record
store := ctx.KVStore(k.storeKey)
store.Delete(types.GetValidatorKey(address))
store.Delete(types.GetValidatorByConsAddrKey(valConsAddr))
store.Delete(types.GetValidatorsByPowerIndexKey(validator, k.PowerReduction(ctx)))
// call hooks
k.AfterValidatorRemoved(ctx, valConsAddr, validator.GetOperator())
}
// get groups of validators
// get the set of all validators with no limits, used during genesis dump
func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.ValidatorsKey)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
validator := types.MustUnmarshalValidator(k.cdc, iterator.Value())
validators = append(validators, validator)
}
return validators
}
// return a given amount of all the validators
func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve uint32) (validators []types.Validator) {
store := ctx.KVStore(k.storeKey)
validators = make([]types.Validator, maxRetrieve)
iterator := sdk.KVStorePrefixIterator(store, types.ValidatorsKey)
defer iterator.Close()
i := 0
for ; iterator.Valid() && i < int(maxRetrieve); iterator.Next() {
validator := types.MustUnmarshalValidator(k.cdc, iterator.Value())
validators[i] = validator
i++
}
return validators[:i] // trim if the array length < maxRetrieve
}
// get the current group of bonded validators sorted by power-rank
func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator {
maxValidators := k.MaxValidators(ctx)
validators := make([]types.Validator, maxValidators)
iterator := k.ValidatorsPowerStoreIterator(ctx)
defer iterator.Close()
i := 0
for ; iterator.Valid() && i < int(maxValidators); iterator.Next() {
address := iterator.Value()
validator := k.mustGetValidator(ctx, address)
if validator.IsBonded() {
validators[i] = validator
i++
}
}
return validators[:i] // trim
}
// returns an iterator for the current validator power store
func (k Keeper) ValidatorsPowerStoreIterator(ctx sdk.Context) sdk.Iterator {
store := ctx.KVStore(k.storeKey)
return sdk.KVStoreReversePrefixIterator(store, types.ValidatorsByPowerIndexKey)
}
// Last Validator Index
// Load the last validator power.
// Returns zero if the operator was not a validator last block.
func (k Keeper) GetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress) (power int64) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.GetLastValidatorPowerKey(operator))
if bz == nil {
return 0
}
intV := gogotypes.Int64Value{}
k.cdc.MustUnmarshal(bz, &intV)
return intV.GetValue()
}
// Set the last validator power.
func (k Keeper) SetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress, power int64) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshal(&gogotypes.Int64Value{Value: power})
store.Set(types.GetLastValidatorPowerKey(operator), bz)
}
// Delete the last validator power.
func (k Keeper) DeleteLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.GetLastValidatorPowerKey(operator))
}
// returns an iterator for the consensus validators in the last block
func (k Keeper) LastValidatorsIterator(ctx sdk.Context) (iterator sdk.Iterator) {
store := ctx.KVStore(k.storeKey)
iterator = sdk.KVStorePrefixIterator(store, types.LastValidatorPowerKey)
return iterator
}
// Iterate over last validator powers.
func (k Keeper) IterateLastValidatorPowers(ctx sdk.Context, handler func(operator sdk.ValAddress, power int64) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, types.LastValidatorPowerKey)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
addr := sdk.ValAddress(types.AddressFromLastValidatorPowerKey(iter.Key()))
intV := &gogotypes.Int64Value{}
k.cdc.MustUnmarshal(iter.Value(), intV)
if handler(addr, intV.GetValue()) {
break
}
}
}
// get the group of the bonded validators
func (k Keeper) GetLastValidators(ctx sdk.Context) (validators []types.Validator) {
store := ctx.KVStore(k.storeKey)
// add the actual validator power sorted store
maxValidators := k.MaxValidators(ctx)
validators = make([]types.Validator, maxValidators)
iterator := sdk.KVStorePrefixIterator(store, types.LastValidatorPowerKey)
defer iterator.Close()
i := 0
for ; iterator.Valid(); iterator.Next() {
// sanity check
if i >= int(maxValidators) {
panic("more validators than maxValidators found")
}
address := types.AddressFromLastValidatorPowerKey(iterator.Key())
validator := k.mustGetValidator(ctx, address)
validators[i] = validator
i++
}
return validators[:i] // trim
}
// GetUnbondingValidators returns a slice of mature validator addresses that
// complete their unbonding at a given time and height.
func (k Keeper) GetUnbondingValidators(ctx sdk.Context, endTime time.Time, endHeight int64) []string {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.GetValidatorQueueKey(endTime, endHeight))
if bz == nil {
return []string{}
}
addrs := types.ValAddresses{}
k.cdc.MustUnmarshal(bz, &addrs)
return addrs.Addresses
}
// SetUnbondingValidatorsQueue sets a given slice of validator addresses into
// the unbonding validator queue by a given height and time.
func (k Keeper) SetUnbondingValidatorsQueue(ctx sdk.Context, endTime time.Time, endHeight int64, addrs []string) {
store := ctx.KVStore(k.storeKey)
bz := k.cdc.MustMarshal(&types.ValAddresses{Addresses: addrs})
store.Set(types.GetValidatorQueueKey(endTime, endHeight), bz)
}
// InsertUnbondingValidatorQueue inserts a given unbonding validator address into
// the unbonding validator queue for a given height and time.
func (k Keeper) InsertUnbondingValidatorQueue(ctx sdk.Context, val types.Validator) {
addrs := k.GetUnbondingValidators(ctx, val.UnbondingTime, val.UnbondingHeight)
addrs = append(addrs, val.OperatorAddress)
k.SetUnbondingValidatorsQueue(ctx, val.UnbondingTime, val.UnbondingHeight, addrs)
}
// DeleteValidatorQueueTimeSlice deletes all entries in the queue indexed by a
// given height and time.
func (k Keeper) DeleteValidatorQueueTimeSlice(ctx sdk.Context, endTime time.Time, endHeight int64) {
store := ctx.KVStore(k.storeKey)
store.Delete(types.GetValidatorQueueKey(endTime, endHeight))
}
// DeleteValidatorQueue removes a validator by address from the unbonding queue
// indexed by a given height and time.
func (k Keeper) DeleteValidatorQueue(ctx sdk.Context, val types.Validator) {
addrs := k.GetUnbondingValidators(ctx, val.UnbondingTime, val.UnbondingHeight)
newAddrs := []string{}
for _, addr := range addrs {
if addr != val.OperatorAddress {
newAddrs = append(newAddrs, addr)
}
}
if len(newAddrs) == 0 {
k.DeleteValidatorQueueTimeSlice(ctx, val.UnbondingTime, val.UnbondingHeight)
} else {
k.SetUnbondingValidatorsQueue(ctx, val.UnbondingTime, val.UnbondingHeight, newAddrs)
}
}
// ValidatorQueueIterator returns an interator ranging over validators that are
// unbonding whose unbonding completion occurs at the given height and time.
func (k Keeper) ValidatorQueueIterator(ctx sdk.Context, endTime time.Time, endHeight int64) sdk.Iterator {
store := ctx.KVStore(k.storeKey)
return store.Iterator(types.ValidatorQueueKey, sdk.InclusiveEndBytes(types.GetValidatorQueueKey(endTime, endHeight)))
}
// UnbondAllMatureValidators unbonds all the mature unbonding validators that
// have finished their unbonding period.
func (k Keeper) UnbondAllMatureValidators(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
blockTime := ctx.BlockTime()
blockHeight := ctx.BlockHeight()
// unbondingValIterator will contains all validator addresses indexed under
// the ValidatorQueueKey prefix. Note, the entire index key is composed as
// ValidatorQueueKey | timeBzLen (8-byte big endian) | timeBz | heightBz (8-byte big endian),
// so it may be possible that certain validator addresses that are iterated
// over are not ready to unbond, so an explicit check is required.
unbondingValIterator := k.ValidatorQueueIterator(ctx, blockTime, blockHeight)
defer unbondingValIterator.Close()
for ; unbondingValIterator.Valid(); unbondingValIterator.Next() {
key := unbondingValIterator.Key()
keyTime, keyHeight, err := types.ParseValidatorQueueKey(key)
if err != nil {
panic(fmt.Errorf("failed to parse unbonding key: %w", err))
}
// All addresses for the given key have the same unbonding height and time.
// We only unbond if the height and time are less than the current height
// and time.
if keyHeight <= blockHeight && (keyTime.Before(blockTime) || keyTime.Equal(blockTime)) {
addrs := types.ValAddresses{}
k.cdc.MustUnmarshal(unbondingValIterator.Value(), &addrs)
for _, valAddr := range addrs.Addresses {
addr, err := sdk.ValAddressFromBech32(valAddr)
if err != nil {
panic(err)
}
val, found := k.GetValidator(ctx, addr)
if !found {
panic("validator in the unbonding queue was not found")
}
if !val.IsUnbonding() {
panic("unexpected validator in unbonding queue; status was not unbonding")
}
val = k.UnbondingToUnbonded(ctx, val)
if val.GetDelegatorShares().IsZero() {
k.RemoveValidator(ctx, val.GetOperator())
}
}
store.Delete(key)
}
}
}