unbonding validators, delegtion effects

This commit is contained in:
rigelrozanski 2018-08-24 21:47:31 -04:00
parent c5d44bcaf0
commit 86f07d271d
4 changed files with 132 additions and 59 deletions

View File

@ -2,6 +2,7 @@ package keeper
import (
"bytes"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/stake/types"
@ -311,6 +312,32 @@ func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddr
//______________________________________________________________________________________________________
// get info for begin functions: MinTime and CreationHeight
func (k Keeper) getBeginInfo(ctx sdk.Context, params types.Params, validatorSrcAddr sdk.AccAddress) (
minTime time.Time, height int64, completeNow bool) {
validator, found := k.GetValidator(ctx, validatorSrcAddr)
switch {
case !found || validator.Status == sdk.Bonded:
// longest wait - just unbonding period from now
minTime = ctx.BlockHeader().Time.Add(params.UnbondingTime)
height = ctx.BlockHeader().Height
return minTime, height, false
case validator.Status == sdk.Unbonding:
minTime = validator.UnbondingMinTime
height = validator.UnbondingHeight
return minTime, height, false
case validator.Status == sdk.Unbonded:
return minTime, height, true
default:
panic("unknown validator status")
}
}
// complete unbonding an unbonding record
func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress, sharesAmount sdk.Dec) sdk.Error {
@ -327,12 +354,22 @@ func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk
// create the unbonding delegation
params := k.GetParams(ctx)
minTime := ctx.BlockHeader().Time.Add(params.UnbondingTime)
minTime, height, completeNow := k.getBeginInfo(ctx, params, validatorAddr)
balance := sdk.Coin{params.BondDenom, returnAmount.RoundInt()}
// no need to create the ubd object just complete now
if completeNow {
_, _, err := k.coinKeeper.AddCoins(ctx, delegatorAddr, sdk.Coins{balance})
if err != nil {
return err
}
return nil
}
ubd := types.UnbondingDelegation{
DelegatorAddr: delegatorAddr,
ValidatorAddr: validatorAddr,
CreationHeight: height,
MinTime: minTime,
Balance: balance,
InitialBalance: balance,
@ -390,11 +427,19 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delegatorAddr, validatorSrcAd
// create the unbonding delegation
minTime := ctx.BlockHeader().Time.Add(params.UnbondingTime)
height := ctx.BlockHeader().Height
minTime, height, completeNow := k.getBeginInfo(ctx, params, validatorSrcAddr)
if completeNow { // no need to create the redelegation object
return nil
}
red := types.Redelegation{
DelegatorAddr: delegatorAddr,
ValidatorSrcAddr: validatorSrcAddr,
ValidatorDstAddr: validatorDstAddr,
CreationHeight: height,
MinTime: minTime,
SharesDst: sharesCreated,
SharesSrc: sharesAmount,

View File

@ -43,6 +43,26 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in
pubkey.Address()))
return
}
// do not slash if unbonded
unbonded := false
if validator.Status == sdk.Unbonded {
unbonded = true
} else if validator.Status == sdk.Unbonding {
ctxTime := ctx.BlockHeader().Time
if ctxTime.After(validator.UnbondingMinTime) {
// TODO should we also just update the
// validator status to unbonded here?
unbonded = true
}
}
if unbonded {
logger.Info(fmt.Sprintf(
"failed attempt to slash an unbonded validator"))
return
}
operatorAddress := validator.GetOperator()
// Track remaining slash amount for the validator
@ -91,17 +111,16 @@ func (k Keeper) Slash(ctx sdk.Context, pubkey crypto.PubKey, infractionHeight in
// Cannot decrease balance below zero
tokensToBurn := sdk.MinDec(remainingSlashAmount, validator.Tokens)
// Get the current pool
// burn validator's tokens
pool := k.GetPool(ctx)
// remove tokens from the validator
validator, pool = validator.RemoveTokens(pool, tokensToBurn)
// burn tokens
pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn)
// update the pool
k.SetPool(ctx, pool)
// update the validator, possibly kicking it out
validator = k.UpdateValidator(ctx, validator)
// remove validator if it has been reduced to zero shares
// remove validator if it has no more tokens
if validator.Tokens.IsZero() {
k.RemoveValidator(ctx, validator.Operator)
}
@ -134,12 +153,12 @@ func (k Keeper) Unjail(ctx sdk.Context, pubkey crypto.PubKey) {
}
// set the jailed flag on a validator
func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, jailed bool) {
func (k Keeper) setJailed(ctx sdk.Context, pubkey crypto.PubKey, isJailed bool) {
validator, found := k.GetValidatorByPubKey(ctx, pubkey)
if !found {
panic(fmt.Errorf("Validator with pubkey %s not found, cannot set jailed to %v", pubkey, jailed))
panic(fmt.Errorf("Validator with pubkey %s not found, cannot set jailed to %v", pubkey, isJailed))
}
validator.Jailed = jailed
validator.Jailed = isJailed
k.UpdateValidator(ctx, validator) // update validator, possibly unbonding or bonding it
return
}
@ -179,6 +198,7 @@ func (k Keeper) slashUnbondingDelegation(ctx sdk.Context, unbondingDelegation ty
unbondingDelegation.Balance.Amount = unbondingDelegation.Balance.Amount.Sub(unbondingSlashAmount)
k.SetUnbondingDelegation(ctx, unbondingDelegation)
pool := k.GetPool(ctx)
// Burn loose tokens
// Ref https://github.com/cosmos/cosmos-sdk/pull/1278#discussion_r198657760
pool.LooseTokens = pool.LooseTokens.Sub(slashAmount)
@ -239,6 +259,7 @@ func (k Keeper) slashRedelegation(ctx sdk.Context, validator types.Validator, re
if err != nil {
panic(fmt.Errorf("error unbonding delegator: %v", err))
}
// Burn loose tokens
pool := k.GetPool(ctx)
pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn)

View File

@ -212,6 +212,7 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
cliffPower := k.GetCliffValidatorPower(ctx)
switch {
// if the validator is already bonded and the power is increasing, we need
// perform the following:
// a) update Tendermint
@ -240,10 +241,11 @@ func (k Keeper) UpdateValidator(ctx sdk.Context, validator types.Validator) type
bytes.Compare(valPower, cliffPower) == -1: //(valPower < cliffPower
// skip to completion
default:
// default case - validator was either:
// 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
updatedVal, updated := k.UpdateBondedValidators(ctx, validator)
if updated {
@ -307,10 +309,13 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato
newCliffValRank := GetValidatorsByPowerIndexKey(newCliffVal, pool)
if bytes.Equal(affectedVal.Operator, newCliffVal.Operator) {
// The affected validator remains the cliff validator, however, since
// the store does not contain the new power, update the new power rank.
store.Set(ValidatorPowerCliffKey, affectedValRank)
} else if bytes.Compare(affectedValRank, newCliffValRank) > 0 {
// The affected validator no longer remains the cliff validator as it's
// power is greater than the new cliff validator.
k.setCliffValidator(ctx, newCliffVal, pool)
@ -321,7 +326,7 @@ func (k Keeper) updateCliffValidator(ctx sdk.Context, affectedVal types.Validato
func (k Keeper) updateForJailing(ctx sdk.Context, oldFound bool, oldValidator, newValidator types.Validator) types.Validator {
if newValidator.Jailed && oldFound && oldValidator.Status == sdk.Bonded {
newValidator = k.unbondValidator(ctx, newValidator)
newValidator = k.beginUnbondingValidator(ctx, newValidator)
// need to also clear the cliff validator spot because the jail has
// opened up a new spot which will be filled when
@ -416,8 +421,12 @@ func (k Keeper) UpdateBondedValidators(
}
}
// if we've reached jailed validators no further bonded validators exist
if validator.Jailed {
break
}
// increment bondedValidatorsCount / get the validator to bond
if !validator.Jailed {
if validator.Status != sdk.Bonded {
validatorToBond = validator
if newValidatorBonded {
@ -425,10 +434,6 @@ func (k Keeper) UpdateBondedValidators(
}
newValidatorBonded = true
}
} else {
// TODO: document why we must break here.
break
}
// increment the total number of bonded validators and potentially mark
// the validator to bond
@ -464,13 +469,15 @@ func (k Keeper) UpdateBondedValidators(
}
if bytes.Equal(validatorToBond.Operator, affectedValidator.Operator) {
// unbond the old cliff validator iff the affected validator was
// newly bonded and has greater power
k.unbondValidator(ctx, oldCliffVal)
// begin unbonding the old cliff validator iff the affected
// validator was newly bonded and has greater power
k.beginUnbondingValidator(ctx, oldCliffVal)
} else {
// otherwise unbond the affected validator, which must have been
// kicked out
affectedValidator = k.unbondValidator(ctx, affectedValidator)
// otherwise begin unbonding the affected validator, which must
// have been kicked out
affectedValidator = k.beginUnbondingValidator(ctx, affectedValidator)
}
}
@ -563,25 +570,30 @@ func kickOutValidators(k Keeper, ctx sdk.Context, toKickOut map[string]byte) {
if !found {
panic(fmt.Sprintf("validator record not found for address: %v\n", ownerAddr))
}
k.unbondValidator(ctx, validator)
k.beginUnbondingValidator(ctx, validator)
}
}
// perform all the store operations for when a validator status becomes unbonded
func (k Keeper) unbondValidator(ctx sdk.Context, validator types.Validator) types.Validator {
func (k Keeper) beginUnbondingValidator(ctx sdk.Context, validator types.Validator) types.Validator {
store := ctx.KVStore(k.storeKey)
pool := k.GetPool(ctx)
params := k.GetParams(ctx)
// sanity check
if validator.Status == sdk.Unbonded {
panic(fmt.Sprintf("should not already be unbonded, validator: %v\n", validator))
if validator.Status == sdk.Unbonded ||
validator.Status == sdk.Unbonding {
panic(fmt.Sprintf("should not already be unbonded or unbonding, validator: %v\n", validator))
}
// set the status
validator, pool = validator.UpdateStatus(pool, sdk.Unbonded)
validator, pool = validator.UpdateStatus(pool, sdk.Unbonding)
k.SetPool(ctx, pool)
validator.UnbondingMinTime = ctx.BlockHeader().Time.Add(params.UnbondingTime)
validator.UnbondingHeight = ctx.BlockHeader().Height
// save the now unbonded validator record
k.SetValidator(ctx, validator)

View File

@ -3,6 +3,7 @@ package types
import (
"bytes"
"fmt"
"time"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
@ -31,15 +32,14 @@ type Validator struct {
Description Description `json:"description"` // description terms for the validator
BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator
BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change
ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer
UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding
UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding
Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators
CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge
CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission
CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time)
// fee related
LastBondedTokens sdk.Dec `json:"prev_bonded_tokens"` // Previous bonded tokens held
}
// NewValidator - initialize a new validator
@ -54,12 +54,12 @@ func NewValidator(operator sdk.AccAddress, pubKey crypto.PubKey, description Des
Description: description,
BondHeight: int64(0),
BondIntraTxCounter: int16(0),
ProposerRewardPool: sdk.Coins{},
UnbondingHeight: int64(0),
UnbondingMinTime: time.Unix(0, 0),
Commission: sdk.ZeroDec(),
CommissionMax: sdk.ZeroDec(),
CommissionChangeRate: sdk.ZeroDec(),
CommissionChangeToday: sdk.ZeroDec(),
LastBondedTokens: sdk.ZeroDec(),
}
}
@ -73,12 +73,12 @@ type validatorValue struct {
Description Description
BondHeight int64
BondIntraTxCounter int16
ProposerRewardPool sdk.Coins
UnbondingHeight int64
UnbondingMinTime time.Time
Commission sdk.Dec
CommissionMax sdk.Dec
CommissionChangeRate sdk.Dec
CommissionChangeToday sdk.Dec
LastBondedTokens sdk.Dec
}
// return the redelegation without fields contained within the key for the store
@ -92,12 +92,12 @@ func MustMarshalValidator(cdc *wire.Codec, validator Validator) []byte {
Description: validator.Description,
BondHeight: validator.BondHeight,
BondIntraTxCounter: validator.BondIntraTxCounter,
ProposerRewardPool: validator.ProposerRewardPool,
UnbondingHeight: validator.UnbondingHeight,
UnbondingMinTime: validator.UnbondingMinTime,
Commission: validator.Commission,
CommissionMax: validator.CommissionMax,
CommissionChangeRate: validator.CommissionChangeRate,
CommissionChangeToday: validator.CommissionChangeToday,
LastBondedTokens: validator.LastBondedTokens,
}
return cdc.MustMarshalBinary(val)
}
@ -108,7 +108,6 @@ func MustUnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) Validat
if err != nil {
panic(err)
}
return validator
}
@ -134,12 +133,12 @@ func UnmarshalValidator(cdc *wire.Codec, operatorAddr, value []byte) (validator
Description: storeValue.Description,
BondHeight: storeValue.BondHeight,
BondIntraTxCounter: storeValue.BondIntraTxCounter,
ProposerRewardPool: storeValue.ProposerRewardPool,
UnbondingHeight: storeValue.UnbondingHeight,
UnbondingMinTime: storeValue.UnbondingMinTime,
Commission: storeValue.Commission,
CommissionMax: storeValue.CommissionMax,
CommissionChangeRate: storeValue.CommissionChangeRate,
CommissionChangeToday: storeValue.CommissionChangeToday,
LastBondedTokens: storeValue.LastBondedTokens,
}, nil
}
@ -161,12 +160,12 @@ func (v Validator) HumanReadableString() (string, error) {
resp += fmt.Sprintf("Delegator Shares: %s\n", v.DelegatorShares.String())
resp += fmt.Sprintf("Description: %s\n", v.Description)
resp += fmt.Sprintf("Bond Height: %d\n", v.BondHeight)
resp += fmt.Sprintf("Proposer Reward Pool: %s\n", v.ProposerRewardPool.String())
resp += fmt.Sprintf("Unbonding Height: %d\n", v.UnbondingHeight)
resp += fmt.Sprintf("Minimum Unbonding Time: %d\n", v.UnbondingMinTime)
resp += fmt.Sprintf("Commission: %s\n", v.Commission.String())
resp += fmt.Sprintf("Max Commission Rate: %s\n", v.CommissionMax.String())
resp += fmt.Sprintf("Commission Change Rate: %s\n", v.CommissionChangeRate.String())
resp += fmt.Sprintf("Commission Change Today: %s\n", v.CommissionChangeToday.String())
resp += fmt.Sprintf("Previous Bonded Tokens: %s\n", v.LastBondedTokens.String())
return resp, nil
}
@ -186,15 +185,14 @@ type BechValidator struct {
Description Description `json:"description"` // description terms for the validator
BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator
BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change
ProposerRewardPool sdk.Coins `json:"proposer_reward_pool"` // XXX reward pool collected from being the proposer
UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding
UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding
Commission sdk.Dec `json:"commission"` // XXX the commission rate of fees charged to any delegators
CommissionMax sdk.Dec `json:"commission_max"` // XXX maximum commission rate which this validator can ever charge
CommissionChangeRate sdk.Dec `json:"commission_change_rate"` // XXX maximum daily increase of the validator commission
CommissionChangeToday sdk.Dec `json:"commission_change_today"` // XXX commission rate change today, reset each day (UTC time)
// fee related
LastBondedTokens sdk.Dec `json:"prev_bonded_shares"` // last bonded token amount
}
// get the bech validator from the the regular validator
@ -216,14 +214,13 @@ func (v Validator) Bech32Validator() (BechValidator, error) {
Description: v.Description,
BondHeight: v.BondHeight,
BondIntraTxCounter: v.BondIntraTxCounter,
ProposerRewardPool: v.ProposerRewardPool,
UnbondingHeight: v.UnbondingHeight,
UnbondingMinTime: v.UnbondingMinTime,
Commission: v.Commission,
CommissionMax: v.CommissionMax,
CommissionChangeRate: v.CommissionChangeRate,
CommissionChangeToday: v.CommissionChangeToday,
LastBondedTokens: v.LastBondedTokens,
}, nil
}
@ -238,12 +235,10 @@ func (v Validator) Equal(c2 Validator) bool {
v.Tokens.Equal(c2.Tokens) &&
v.DelegatorShares.Equal(c2.DelegatorShares) &&
v.Description == c2.Description &&
v.ProposerRewardPool.IsEqual(c2.ProposerRewardPool) &&
v.Commission.Equal(c2.Commission) &&
v.CommissionMax.Equal(c2.CommissionMax) &&
v.CommissionChangeRate.Equal(c2.CommissionChangeRate) &&
v.CommissionChangeToday.Equal(c2.CommissionChangeToday) &&
v.LastBondedTokens.Equal(c2.LastBondedTokens)
v.CommissionChangeToday.Equal(c2.CommissionChangeToday)
}
// constant used in flags to indicate that description field should not be updated