Merge PR #1858: Fix Cliff Validator Update Bugs

This commit is contained in:
Christopher Goes 2018-08-09 00:57:59 +02:00 committed by GitHub
parent 0600d7356d
commit ac26d33547
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 146 additions and 17 deletions

View File

@ -73,6 +73,7 @@ BUG FIXES
* \#1799 Fix `gaiad export`
* \#1828 Force user to specify amount on create-validator command by removing default
* \#1839 Fixed bug where intra-tx counter wasn't set correctly for genesis validators
* [staking] [#1858](https://github.com/cosmos/cosmos-sdk/pull/1858) Fixed bug where the cliff validator was not be updated correctly
* [tests] \#1675 Fix non-deterministic `test_cover`
* [client] \#1551: Refactored `CoreContext`
* Renamed `CoreContext` to `QueryContext`
@ -80,9 +81,8 @@ BUG FIXES
structure `TxContext` in `x/auth/client/context`
* Cleaned up documentation and API of what used to be `CoreContext`
* Implemented `KeyType` enum for key info
BUG FIXES
* \#1666 Add intra-tx counter to the genesis validators
* [tests] \#1551: Fixed invalid LCD test JSON payload in `doIBCTransfer`
* \#1787 Fixed bug where Tally fails due to revoked/unbonding validator
* [basecoin] Fixes coin transaction failure and account query [discussion](https://forum.cosmos.network/t/unmarshalbinarybare-expected-to-read-prefix-bytes-75fbfab8-since-it-is-registered-concrete-but-got-0a141dfa/664/6)
* \#1787 Fixed bug where Tally fails due to revoked/unbonding validator
* [basecoin] Fixes coin transaction failure and account query [discussion](https://forum.cosmos.network/t/unmarshalbinarybare-expected-to-read-prefix-bytes-75fbfab8-since-it-is-registered-concrete-but-got-0a141dfa/664/6)

View File

@ -134,6 +134,8 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat
}
// get the group of bonded validators sorted by power-rank
//
// TODO: Rename to GetBondedValidatorsByPower or GetValidatorsByPower(ctx, status)
func (k Keeper) GetValidatorsByPower(ctx sdk.Context) []types.Validator {
store := ctx.KVStore(k.storeKey)
maxValidators := k.GetParams(ctx).MaxValidators
@ -191,13 +193,13 @@ func (k Keeper) ClearTendermintUpdates(ctx sdk.Context) {
//___________________________________________________________________________
// Perfom all the necessary steps for when a validator changes its power. This
// Perform all the necessary steps for when a validator changes its power. This
// function updates all validator stores as well as tendermint update store.
// It may kick out validators if a new validator is entering the bonded validator
// group.
//
// nolint: gocyclo
// TODO: Remove above nolint, function needs to be simplified
// TODO: Remove above nolint, function needs to be simplified!
func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) types.Validator {
store := ctx.KVStore(k.storeKey)
pool := k.GetPool(ctx)
@ -210,19 +212,29 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
cliffPower := k.GetCliffValidatorPower(ctx)
switch {
// if already bonded and power increasing only need to update tendermint
// if the validator is already bonded and the power is increasing, we need
// perform the following:
// a) update Tendermint
// b) check if the cliff validator needs to be updated
case powerIncreasing && !validator.Revoked &&
(oldFound && oldValidator.Status == sdk.Bonded):
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator())
store.Set(GetTendermintUpdatesKey(validator.Owner), bz)
if cliffPower != nil {
cliffAddr := sdk.AccAddress(k.GetCliffValidator(ctx))
if bytes.Equal(cliffAddr, validator.Owner) {
k.updateCliffValidator(ctx, validator)
}
}
// if is a new validator and the new power is less than the cliff validator
case cliffPower != nil && !oldFound &&
bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower
// skip to completion
// if was unbonded and the new power is less than the cliff validator
// if was unbonded and the new power is less than the cliff validator
case cliffPower != nil &&
(oldFound && oldValidator.Status == sdk.Unbonded) &&
bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower
@ -232,11 +244,10 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
// a) not-bonded and now has power-rank greater than cliff validator
// b) bonded and now has decreased in power
default:
// update the validator set for this validator
// if updated, the validator has changed bonding status
updatedVal, updated := k.UpdateBondedValidators(ctx, validator)
if updated { // updates to validator occurred to be updated
if updated {
// the validator has changed bonding status
validator = updatedVal
break
}
@ -246,13 +257,69 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
bz := k.cdc.MustMarshalBinary(validator.ABCIValidator())
store.Set(GetTendermintUpdatesKey(validator.Owner), bz)
}
}
k.SetValidator(ctx, validator)
return validator
}
// updateCliffValidator determines if the current cliff validator needs to be
// updated or swapped. If the provided affected validator is the current cliff
// validator before it's power was increased, either the cliff power key will
// be updated or if it's power is greater than the next bonded validator by
// power, it'll be swapped.
func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validator) {
var newCliffVal types.Validator
store := ctx.KVStore(k.storeKey)
pool := k.GetPool(ctx)
cliffAddr := sdk.AccAddress(k.GetCliffValidator(ctx))
oldCliffVal, found := k.GetValidator(ctx, cliffAddr)
if !found {
panic(fmt.Sprintf("cliff validator record not found for address: %v\n", cliffAddr))
}
// NOTE: We get the power via affectedVal since the store (by power key)
// has yet to be updated.
affectedValPower := affectedVal.GetPower()
// Create a validator iterator ranging from smallest to largest by power
// starting the current cliff validator's power.
start := GetValidatorsByPowerIndexKey(oldCliffVal, pool)
end := sdk.PrefixEndBytes(ValidatorsByPowerIndexKey)
iterator := store.Iterator(start, end)
if iterator.Valid() {
ownerAddr := iterator.Value()
currVal, found := k.GetValidator(ctx, ownerAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
}
if currVal.Status != sdk.Bonded || currVal.Revoked {
panic(fmt.Sprintf("unexpected revoked or unbonded validator for address: %s\n", ownerAddr))
}
newCliffVal = currVal
iterator.Close()
} else {
panic("failed to create valid validator power iterator")
}
if bytes.Equal(affectedVal.Owner, newCliffVal.Owner) {
// The affected validator remains the cliff validator, however, since
// the store does not contain the new power, set the new cliff
// validator to the affected validator.
bz := GetValidatorsByPowerIndexKey(affectedVal, pool)
store.Set(ValidatorPowerCliffKey, bz)
} else if affectedValPower.GT(newCliffVal.GetPower()) {
// The affected validator no longer remains the cliff validator as it's
// power is greater than the new current cliff validator.
k.setCliffValidator(ctx, newCliffVal, pool)
}
}
func (k Keeper) updateForRevoking(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator {
if newValidator.Revoked && oldFound && oldValidator.Status == sdk.Bonded {
newValidator = k.unbondValidator(ctx, newValidator)
@ -317,8 +384,9 @@ func (k Keeper) updateValidatorPower(ctx sdk.Context, oldFound bool, oldValidato
//
// nolint: gocyclo
// TODO: Remove the above golint
func (k Keeper) UpdateBondedValidators(ctx sdk.Context,
affectedValidator types.Validator) (updatedVal types.Validator, updated bool) {
func (k Keeper) UpdateBondedValidators(
ctx sdk.Context, affectedValidator types.Validator) (
updatedVal types.Validator, updated bool) {
store := ctx.KVStore(k.storeKey)
@ -328,7 +396,8 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context,
var validator, validatorToBond types.Validator
newValidatorBonded := false
iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) // largest to smallest
// create a validator iterator ranging from largest to smallest by power
iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey)
for {
if !iterator.Valid() || bondedValidatorsCount > int(maxValidators-1) {
break
@ -354,6 +423,7 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context,
validatorToBond = validator
newValidatorBonded = true
}
bondedValidatorsCount++
// sanity check
@ -363,6 +433,7 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context,
iterator.Next()
}
iterator.Close()
// clear or set the cliff validator
@ -372,17 +443,17 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context,
k.clearCliffValidator(ctx)
}
// swap the cliff validator for a new validator if the affected validator was bonded
// swap the cliff validator for a new validator if the affected validator
// was bonded
if newValidatorBonded {
// unbond the cliff validator
if oldCliffValidatorAddr != nil {
cliffVal, found := k.GetValidator(ctx, oldCliffValidatorAddr)
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", oldCliffValidatorAddr))
}
k.unbondValidator(ctx, cliffVal)
k.unbondValidator(ctx, cliffVal)
}
// bond the new validator
@ -391,6 +462,7 @@ func (k Keeper) UpdateBondedValidators(ctx sdk.Context,
return validator, true
}
}
return types.Validator{}, false
}

View File

@ -1,6 +1,7 @@
package keeper
import (
"fmt"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -88,6 +89,62 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) {
require.True(t, keeper.validatorByPowerIndexExists(ctx, power))
}
func TestCliffValidatorChange(t *testing.T) {
numVals := 10
maxVals := 5
// create context, keeper, and pool for tests
ctx, _, keeper := CreateTestInput(t, false, 0)
pool := keeper.GetPool(ctx)
// create keeper parameters
params := keeper.GetParams(ctx)
params.MaxValidators = uint16(maxVals)
keeper.SetParams(ctx, params)
// create a random pool
pool.LooseTokens = sdk.NewRat(10000)
pool.BondedTokens = sdk.NewRat(1234)
keeper.SetPool(ctx, pool)
validators := make([]types.Validator, numVals)
for i := 0; i < len(validators); i++ {
moniker := fmt.Sprintf("val#%d", int64(i))
val := types.NewValidator(Addrs[i], PKs[i], types.Description{Moniker: moniker})
val.BondHeight = int64(i)
val.BondIntraTxCounter = int16(i)
val, pool, _ = val.AddTokensFromDel(pool, int64((i+1)*10))
keeper.SetPool(ctx, pool)
val = keeper.UpdateValidator(ctx, val)
validators[i] = val
}
// add a large amount of tokens to current cliff validator
currCliffVal := validators[numVals-maxVals]
currCliffVal, pool, _ = currCliffVal.AddTokensFromDel(pool, 200)
keeper.SetPool(ctx, pool)
currCliffVal = keeper.UpdateValidator(ctx, currCliffVal)
// assert new cliff validator to be set to the second lowest bonded validator by power
newCliffVal := validators[numVals-maxVals+1]
require.Equal(t, newCliffVal.Owner, sdk.AccAddress(keeper.GetCliffValidator(ctx)))
// assert cliff validator power should have been updated
cliffPower := keeper.GetCliffValidatorPower(ctx)
require.Equal(t, GetValidatorsByPowerIndexKey(newCliffVal, pool), cliffPower)
// add small amount of tokens to new current cliff validator
newCliffVal, pool, _ = newCliffVal.AddTokensFromDel(pool, 1)
keeper.SetPool(ctx, pool)
newCliffVal = keeper.UpdateValidator(ctx, newCliffVal)
// assert cliff validator has not change but increased in power
cliffPower = keeper.GetCliffValidatorPower(ctx)
require.Equal(t, newCliffVal.Owner, sdk.AccAddress(keeper.GetCliffValidator(ctx)))
require.Equal(t, GetValidatorsByPowerIndexKey(newCliffVal, pool), cliffPower)
}
func TestSlashToZeroPowerRemoved(t *testing.T) {
// initialize setup
ctx, _, keeper := CreateTestInput(t, false, 100)