diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 5265db52bc..24b56d406b 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -345,6 +345,7 @@ func (h Hooks) OnValidatorBonded(ctx sdk.Context, addr sdk.ConsAddress) { h.sh.OnValidatorBonded(ctx, addr) } func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, addr sdk.ConsAddress, operator sdk.ValAddress) { + h.dh.OnValidatorBeginUnbonding(ctx, addr, operator) h.sh.OnValidatorBeginUnbonding(ctx, addr, operator) } func (h Hooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { diff --git a/types/decimal.go b/types/decimal.go index e9623995f0..05dd97795a 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -247,6 +247,11 @@ func (d Dec) QuoInt(i Int) Dec { return Dec{mul} } +// is integer, e.g. decimals are zero. +func (d Dec) IsInteger() bool { + return new(big.Int).Rem(d.Int, precisionReuse).Sign() == 0 +} + func (d Dec) String() string { str := d.ToLeftPaddedWithDecimals(Precision) placement := len(str) - Precision diff --git a/types/stake.go b/types/stake.go index 25b8979946..1818919ab8 100644 --- a/types/stake.go +++ b/types/stake.go @@ -85,9 +85,9 @@ type ValidatorSet interface { // delegation bond for a delegated proof of stake system type Delegation interface { - GetDelegator() AccAddress // delegator AccAddress for the bond - GetValidator() ValAddress // validator operator address - GetShares() Dec // amount of validator's shares held in this delegation + GetDelegatorAddr() AccAddress // delegator AccAddress for the bond + GetValidatorAddr() ValAddress // validator operator address + GetShares() Dec // amount of validator's shares held in this delegation } // properties for the set of all delegations for a particular diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index 4132976ef6..784a75c797 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -69,25 +69,28 @@ func (k Keeper) RemoveDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAd //___________________________________________________________________________________________ -// withdraw all the rewards for a single delegation +// Withdraw all the rewards for a single delegation. +// NOTE: This gets called "onDelegationSharesModified", +// meaning any changes to bonded coins. func (k Keeper) WithdrawDelegationReward(ctx sdk.Context, delegatorAddr sdk.AccAddress, - validatorAddr sdk.ValAddress) sdk.Error { + valAddr sdk.ValAddress) sdk.Error { - if !k.HasDelegationDistInfo(ctx, delegatorAddr, validatorAddr) { + if !k.HasDelegationDistInfo(ctx, delegatorAddr, valAddr) { return types.ErrNoDelegationDistInfo(k.codespace) } // TODO: Reconcile with duplicate code in getDelegatorRewardsAll. height := ctx.BlockHeight() - bondedTokens := k.stakeKeeper.TotalPower(ctx) + lastTotalPower := k.stakeKeeper.GetLastTotalPower(ctx) + lastValPower := k.stakeKeeper.GetLastValidatorPower(ctx, valAddr) feePool := k.GetFeePool(ctx) - delInfo := k.GetDelegationDistInfo(ctx, delegatorAddr, validatorAddr) - valInfo := k.GetValidatorDistInfo(ctx, validatorAddr) - validator := k.stakeKeeper.Validator(ctx, validatorAddr) - delegation := k.stakeKeeper.Delegation(ctx, delegatorAddr, validatorAddr) + delInfo := k.GetDelegationDistInfo(ctx, delegatorAddr, valAddr) + valInfo := k.GetValidatorDistInfo(ctx, valAddr) + validator := k.stakeKeeper.Validator(ctx, valAddr) + delegation := k.stakeKeeper.Delegation(ctx, delegatorAddr, valAddr) - delInfo, valInfo, feePool, withdraw := delInfo.WithdrawRewards(feePool, valInfo, height, bondedTokens, - validator.GetPower(), validator.GetDelegatorShares(), delegation.GetShares(), validator.GetCommission()) + delInfo, valInfo, feePool, withdraw := delInfo.WithdrawRewards(feePool, valInfo, height, lastTotalPower, + lastValPower, validator.GetDelegatorShares(), delegation.GetShares(), validator.GetCommission()) k.SetValidatorDistInfo(ctx, valInfo) k.SetDelegationDistInfo(ctx, delInfo) @@ -123,20 +126,21 @@ func (k Keeper) WithdrawDelegationRewardsAll(ctx sdk.Context, delegatorAddr sdk. func (k Keeper) getDelegatorRewardsAll(ctx sdk.Context, delAddr sdk.AccAddress, height int64) types.DecCoins { withdraw := types.DecCoins{} - bondedTokens := k.stakeKeeper.TotalPower(ctx) + lastTotalPower := k.stakeKeeper.GetLastTotalPower(ctx) // iterate over all the delegations // TODO: Reconcile with duplicate code in WithdrawDelegationReward. operationAtDelegation := func(_ int64, del sdk.Delegation) (stop bool) { feePool := k.GetFeePool(ctx) - valAddr := del.GetValidator() + valAddr := del.GetValidatorAddr() + lastValPower := k.stakeKeeper.GetLastValidatorPower(ctx, valAddr) delInfo := k.GetDelegationDistInfo(ctx, delAddr, valAddr) valInfo := k.GetValidatorDistInfo(ctx, valAddr) validator := k.stakeKeeper.Validator(ctx, valAddr) delegation := k.stakeKeeper.Delegation(ctx, delAddr, valAddr) - delInfo, valInfo, feePool, diWithdraw := delInfo.WithdrawRewards(feePool, valInfo, height, bondedTokens, - validator.GetPower(), validator.GetDelegatorShares(), delegation.GetShares(), validator.GetCommission()) + delInfo, valInfo, feePool, diWithdraw := delInfo.WithdrawRewards(feePool, valInfo, height, lastTotalPower, + lastValPower, validator.GetDelegatorShares(), delegation.GetShares(), validator.GetCommission()) withdraw = withdraw.Plus(diWithdraw) k.SetFeePool(ctx, feePool) k.SetValidatorDistInfo(ctx, valInfo) diff --git a/x/distribution/keeper/delegation_test.go b/x/distribution/keeper/delegation_test.go index 3455d48c81..8060fcdf2d 100644 --- a/x/distribution/keeper/delegation_test.go +++ b/x/distribution/keeper/delegation_test.go @@ -34,6 +34,8 @@ func TestWithdrawDelegationRewardBasic(t *testing.T) { // withdraw delegation ctx = ctx.WithBlockHeight(1) + sk.SetLastTotalPower(ctx, sdk.NewDec(10)) + sk.SetLastValidatorPower(ctx, valOpAddr1, sdk.NewDec(10)) keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) @@ -204,8 +206,6 @@ func TestWithdrawDelegationRewardsAll(t *testing.T) { got = stakeHandler(ctx, msgCreateValidator) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) - _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) - // delegate to all the validators msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) @@ -214,6 +214,9 @@ func TestWithdrawDelegationRewardsAll(t *testing.T) { msgDelegate = stake.NewTestMsgDelegate(delAddr1, valOpAddr3, 30) require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) + // Update sk's LastValidatorPower/LastTotalPowers. + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + // 40 tokens left after delegating 60 of them amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) require.Equal(t, int64(40), amt.Int64()) diff --git a/x/distribution/keeper/keeper_test.go b/x/distribution/keeper/keeper_test.go index 8244305113..f8eb0925d5 100644 --- a/x/distribution/keeper/keeper_test.go +++ b/x/distribution/keeper/keeper_test.go @@ -29,9 +29,9 @@ func TestSetGetFeePool(t *testing.T) { ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0) fp := types.InitialFeePool() - fp.ValAccum.UpdateHeight = 777 + fp.TotalValAccum.UpdateHeight = 777 keeper.SetFeePool(ctx, fp) res := keeper.GetFeePool(ctx) - require.Equal(t, fp.ValAccum, res.ValAccum) + require.Equal(t, fp.TotalValAccum, res.TotalValAccum) } diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go index 59b615ec8a..42f3861fbe 100644 --- a/x/distribution/keeper/test_common.go +++ b/x/distribution/keeper/test_common.go @@ -114,7 +114,6 @@ func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, pk.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) sk.SetPool(ctx, stake.InitialPool()) sk.SetParams(ctx, stake.DefaultParams()) - sk.InitIntraTxCounter(ctx) // fill all the addresses with some coins, set the loose pool tokens simultaneously for _, addr := range addrs { diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index 0d34533874..d8ec7ea7fd 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -50,15 +50,16 @@ func (k Keeper) WithdrawValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.Va // withdraw self-delegation height := ctx.BlockHeight() validator := k.stakeKeeper.Validator(ctx, operatorAddr) + lastValPower := k.stakeKeeper.GetLastValidatorPower(ctx, operatorAddr) accAddr := sdk.AccAddress(operatorAddr.Bytes()) withdraw := k.getDelegatorRewardsAll(ctx, accAddr, height) // withdrawal validator commission rewards - bondedTokens := k.stakeKeeper.TotalPower(ctx) + lastTotalPower := k.stakeKeeper.GetLastTotalPower(ctx) valInfo := k.GetValidatorDistInfo(ctx, operatorAddr) feePool := k.GetFeePool(ctx) - valInfo, feePool, commission := valInfo.WithdrawCommission(feePool, height, bondedTokens, - validator.GetPower(), validator.GetCommission()) + valInfo, feePool, commission := valInfo.WithdrawCommission(feePool, height, lastTotalPower, + lastValPower, validator.GetCommission()) withdraw = withdraw.Plus(commission) k.SetValidatorDistInfo(ctx, valInfo) diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index f5b8978e5f..4667d66e00 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -21,7 +21,13 @@ func NewDelegationDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.Val } } -// withdraw rewards from delegator +// Withdraw rewards from delegator. +// Among many things, it does: +// * updates validator info's total del accum. +// * calls vi.TakeFeePoolRewards, which: +// * updates validator info's FeePoolWithdrawalHeight, thus setting accum to 0. +// * updates fee pool to latest height and total val accum w/ given totalBonded. +// (see comment on TakeFeePoolRewards for more info). func (di DelegationDistInfo) WithdrawRewards(fp FeePool, vi ValidatorDistInfo, height int64, totalBonded, vdTokens, totalDelShares, delegatorShares, commissionRate sdk.Dec) (DelegationDistInfo, ValidatorDistInfo, FeePool, DecCoins) { diff --git a/x/distribution/types/delegator_info_test.go b/x/distribution/types/delegator_info_test.go index 516cbf99df..4af7f0a8f1 100644 --- a/x/distribution/types/delegator_info_test.go +++ b/x/distribution/types/delegator_info_test.go @@ -33,7 +33,7 @@ func TestWithdrawRewards(t *testing.T) { validatorTokens, validatorDelShares, di1Shares, commissionRate) assert.Equal(t, height, di1.WithdrawalHeight) - assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum)) assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.PoolCommission[0].Amount)) @@ -48,7 +48,7 @@ func TestWithdrawRewards(t *testing.T) { validatorTokens, validatorDelShares, di2Shares, commissionRate) assert.Equal(t, height, di2.WithdrawalHeight) - assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValAccum.Accum)) + assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum)) assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(4), vi.PoolCommission[0].Amount)) diff --git a/x/distribution/types/fee_pool.go b/x/distribution/types/fee_pool.go index 66731cb197..a792a888f2 100644 --- a/x/distribution/types/fee_pool.go +++ b/x/distribution/types/fee_pool.go @@ -1,7 +1,10 @@ package types import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + cmn "github.com/tendermint/tendermint/libs/common" ) // total accumulation tracker @@ -29,25 +32,50 @@ func (ta TotalAccum) UpdateForNewHeight(height int64, accumCreatedPerBlock sdk.D return ta } +// update total validator accumulation factor for the new height +// CONTRACT: height should be greater than the old height +func (ta TotalAccum) UpdateForNewHeight_DEBUG(height int64, accumCreatedPerBlock sdk.Dec) TotalAccum { + blocks := height - ta.UpdateHeight + if blocks < 0 { + panic("reverse updated for new height") + } + if !accumCreatedPerBlock.IsZero() && blocks != 0 { + fmt.Println( + cmn.Blue( + fmt.Sprintf("FP Add %v * %v = %v, + %v (old) => %v (new)", + accumCreatedPerBlock.String(), sdk.NewInt(blocks), + accumCreatedPerBlock.MulInt(sdk.NewInt(blocks)).String(), + ta.Accum.String(), + ta.Accum.Add(accumCreatedPerBlock.MulInt(sdk.NewInt(blocks))).String(), + ), + ), + ) + } + ta.Accum = ta.Accum.Add(accumCreatedPerBlock.MulInt(sdk.NewInt(blocks))) + ta.UpdateHeight = height + return ta +} + //___________________________________________________________________________________________ // global fee pool for distribution type FeePool struct { - ValAccum TotalAccum `json:"val_accum"` // total valdator accum held by validators + TotalValAccum TotalAccum `json:"val_accum"` // total valdator accum held by validators Pool DecCoins `json:"pool"` // funds for all validators which have yet to be withdrawn CommunityPool DecCoins `json:"community_pool"` // pool for community funds yet to be spent } // update total validator accumulation factor +// NOTE: Do not call this except from ValidatorDistInfo.TakeFeePoolRewards(). func (f FeePool) UpdateTotalValAccum(height int64, totalBondedTokens sdk.Dec) FeePool { - f.ValAccum = f.ValAccum.UpdateForNewHeight(height, totalBondedTokens) + f.TotalValAccum = f.TotalValAccum.UpdateForNewHeight_DEBUG(height, totalBondedTokens) return f } // zero fee pool func InitialFeePool() FeePool { return FeePool{ - ValAccum: NewTotalAccum(0), + TotalValAccum: NewTotalAccum(0), Pool: DecCoins{}, CommunityPool: DecCoins{}, } diff --git a/x/distribution/types/fee_pool_test.go b/x/distribution/types/fee_pool_test.go index e39fb09c94..478ec7539b 100644 --- a/x/distribution/types/fee_pool_test.go +++ b/x/distribution/types/fee_pool_test.go @@ -23,8 +23,8 @@ func TestUpdateTotalValAccum(t *testing.T) { fp := InitialFeePool() fp = fp.UpdateTotalValAccum(5, sdk.NewDec(3)) - require.True(sdk.DecEq(t, sdk.NewDec(15), fp.ValAccum.Accum)) + require.True(sdk.DecEq(t, sdk.NewDec(15), fp.TotalValAccum.Accum)) fp = fp.UpdateTotalValAccum(8, sdk.NewDec(2)) - require.True(sdk.DecEq(t, sdk.NewDec(21), fp.ValAccum.Accum)) + require.True(sdk.DecEq(t, sdk.NewDec(21), fp.TotalValAccum.Accum)) } diff --git a/x/distribution/types/keepers.go b/x/distribution/types/keepers.go index 31a68a5da9..5009c6b5af 100644 --- a/x/distribution/types/keepers.go +++ b/x/distribution/types/keepers.go @@ -10,6 +10,8 @@ type StakeKeeper interface { Validator(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Validator ValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) sdk.Validator TotalPower(ctx sdk.Context) sdk.Dec + GetLastTotalPower(ctx sdk.Context) sdk.Dec + GetLastValidatorPower(ctx sdk.Context, valAddr sdk.ValAddress) sdk.Dec } // expected coin keeper diff --git a/x/distribution/types/validator_info.go b/x/distribution/types/validator_info.go index ae80d8aa71..df1062e5c4 100644 --- a/x/distribution/types/validator_info.go +++ b/x/distribution/types/validator_info.go @@ -1,7 +1,10 @@ package types import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + cmn "github.com/tendermint/tendermint/libs/common" ) // distribution info for a particular validator @@ -19,9 +22,9 @@ func NewValidatorDistInfo(operatorAddr sdk.ValAddress, currentHeight int64) Vali return ValidatorDistInfo{ OperatorAddr: operatorAddr, FeePoolWithdrawalHeight: currentHeight, - Pool: DecCoins{}, - PoolCommission: DecCoins{}, - DelAccum: NewTotalAccum(currentHeight), + Pool: DecCoins{}, + PoolCommission: DecCoins{}, + DelAccum: NewTotalAccum(currentHeight), } } @@ -31,13 +34,21 @@ func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares sdk return vi } -// move any available accumulated fees in the FeePool to the validator's pool +// Move any available accumulated fees in the FeePool to the validator's pool. +// * updates validator info's FeePoolWithdrawalHeight, thus setting accum to 0. +// * updates fee pool to latest height and total val accum w/ given totalBonded. +// This is the only way to update the FeePool's validator TotalAccum. +// NOTE: This algorithm works as long as TakeFeePoolRewards is called after every power change. +// - called in ValidationDistInfo.WithdrawCommission. +// - called in DelegationDistInfo.WithdrawRewards. +// NOTE: When a delegator unbonds, say, onDelegationSharesModified -> +// WithdrawDelegationReward -> WithdrawRewards. func (vi ValidatorDistInfo) TakeFeePoolRewards(fp FeePool, height int64, totalBonded, vdTokens, commissionRate sdk.Dec) (ValidatorDistInfo, FeePool) { fp = fp.UpdateTotalValAccum(height, totalBonded) - if fp.ValAccum.Accum.IsZero() { + if fp.TotalValAccum.Accum.IsZero() { return vi, fp } @@ -45,16 +56,30 @@ func (vi ValidatorDistInfo) TakeFeePoolRewards(fp FeePool, height int64, totalBo blocks := height - vi.FeePoolWithdrawalHeight vi.FeePoolWithdrawalHeight = height accum := vdTokens.MulInt(sdk.NewInt(blocks)) - if accum.GT(fp.ValAccum.Accum) { + + if !accum.IsZero() { + fmt.Println( + cmn.Red( + fmt.Sprintf("FP Sub %v * %v = %v, %v - _ => %v", + vdTokens.String(), sdk.NewInt(blocks), + accum.String(), + fp.TotalValAccum.Accum.String(), + fp.TotalValAccum.Accum.Sub(accum).String(), + ), + ), + ) + } + + if accum.GT(fp.TotalValAccum.Accum) { panic("individual accum should never be greater than the total") } - withdrawalTokens := fp.Pool.MulDec(accum).QuoDec(fp.ValAccum.Accum) + withdrawalTokens := fp.Pool.MulDec(accum).QuoDec(fp.TotalValAccum.Accum) remainingTokens := fp.Pool.Minus(withdrawalTokens) commission := withdrawalTokens.MulDec(commissionRate) afterCommission := withdrawalTokens.Minus(commission) - fp.ValAccum.Accum = fp.ValAccum.Accum.Sub(accum) + fp.TotalValAccum.Accum = fp.TotalValAccum.Accum.Sub(accum) fp.Pool = remainingTokens vi.PoolCommission = vi.PoolCommission.Plus(commission) vi.Pool = vi.Pool.Plus(afterCommission) diff --git a/x/distribution/types/validator_info_test.go b/x/distribution/types/validator_info_test.go index afa6d8c763..9d1e39fa6a 100644 --- a/x/distribution/types/validator_info_test.go +++ b/x/distribution/types/validator_info_test.go @@ -29,13 +29,13 @@ func TestTakeFeePoolRewards(t *testing.T) { fp.Pool = DecCoins{NewDecCoin("stake", 1000)} vi1, fp = vi1.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens1, commissionRate1) - require.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum)) + require.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum)) assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi1.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(2), vi1.PoolCommission[0].Amount)) vi2, fp = vi2.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens2, commissionRate2) - require.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValAccum.Accum)) + require.True(sdk.DecEq(t, sdk.NewDec(500), fp.TotalValAccum.Accum)) assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(400-12), vi2.Pool[0].Amount)) assert.True(sdk.DecEq(t, vi2.PoolCommission[0].Amount, sdk.NewDec(12))) @@ -45,7 +45,7 @@ func TestTakeFeePoolRewards(t *testing.T) { fp.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000)) vi3, fp = vi3.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens3, commissionRate3) - require.True(sdk.DecEq(t, sdk.NewDec(500), fp.ValAccum.Accum)) + require.True(sdk.DecEq(t, sdk.NewDec(500), fp.TotalValAccum.Accum)) assert.True(sdk.DecEq(t, sdk.NewDec(500), fp.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(1000-40), vi3.Pool[0].Amount)) assert.True(sdk.DecEq(t, vi3.PoolCommission[0].Amount, sdk.NewDec(40))) @@ -67,7 +67,7 @@ func TestWithdrawCommission(t *testing.T) { // for a more fun staring condition, have an non-withdraw update vi, fp = vi.TakeFeePoolRewards(fp, height, totalBondedTokens, validatorTokens, commissionRate) - require.True(sdk.DecEq(t, sdk.NewDec(900), fp.ValAccum.Accum)) + require.True(sdk.DecEq(t, sdk.NewDec(900), fp.TotalValAccum.Accum)) assert.True(sdk.DecEq(t, sdk.NewDec(900), fp.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(100-2), vi.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(2), vi.PoolCommission[0].Amount)) @@ -77,7 +77,7 @@ func TestWithdrawCommission(t *testing.T) { fp.Pool[0].Amount = fp.Pool[0].Amount.Add(sdk.NewDec(1000)) vi, fp, commissionRecv := vi.WithdrawCommission(fp, height, totalBondedTokens, validatorTokens, commissionRate) - require.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValAccum.Accum)) + require.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum)) assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.Pool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(200-4), vi.Pool[0].Amount)) assert.Zero(t, len(vi.PoolCommission)) diff --git a/x/gov/tally.go b/x/gov/tally.go index c5751258a0..b6e42c4b5a 100644 --- a/x/gov/tally.go +++ b/x/gov/tally.go @@ -50,7 +50,7 @@ func tally(ctx sdk.Context, keeper Keeper, proposal Proposal) (passes bool, tall } else { keeper.ds.IterateDelegations(ctx, vote.Voter, func(index int64, delegation sdk.Delegation) (stop bool) { - valAddrStr := delegation.GetValidator().String() + valAddrStr := delegation.GetValidatorAddr().String() if val, ok := currValidators[valAddrStr]; ok { val.Minus = val.Minus.Add(delegation.GetShares()) diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index 861391ac2a..4cd8ed91f3 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -37,7 +37,8 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, vs sdk.ValidatorSet, paramspa return keeper } -// handle a validator signing two blocks at the same height +// handle a validator signing two blocks at the same height. +// power: power of the double-signing validator at the height of infraction. func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractionHeight int64, timestamp time.Time, power int64) { logger := ctx.Logger().With("module", "x/slashing") time := ctx.BlockHeader().Time @@ -70,7 +71,12 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio revisedFraction := k.capBySlashingPeriod(ctx, consAddr, fraction, distributionHeight) logger.Info(fmt.Sprintf("Fraction slashed capped by slashing period from %v to %v", fraction, revisedFraction)) - // Slash validator + // Slash validator. + // `power` is the int64 power of the validator as provided to/by + // Tendermint. This value is validator.Tokens as sent to Tendermint via + // ABCI, and now received as evidence. + // The revisedFraction (which is the new fraction to be slashed) is passed + // in separately to separately slash unbonding and rebonding delegations. k.validatorSet.Slash(ctx, consAddr, distributionHeight, power, revisedFraction) // Jail validator if not already jailed diff --git a/x/slashing/slashing_period.go b/x/slashing/slashing_period.go index 0595d5eeb2..4b13288588 100644 --- a/x/slashing/slashing_period.go +++ b/x/slashing/slashing_period.go @@ -37,6 +37,7 @@ func (k Keeper) capBySlashingPeriod(ctx sdk.Context, address sdk.ConsAddress, fr // This function retrieves the most recent slashing period starting // before a particular height - so the slashing period that was "in effect" // at the time of an infraction committed at that height. +// Slashing periods are created upon validator bonding. func (k Keeper) getValidatorSlashingPeriodForHeight(ctx sdk.Context, address sdk.ConsAddress, height int64) (slashingPeriod ValidatorSlashingPeriod) { store := ctx.KVStore(k.storeKey) // Get the most recent slashing period at or before the infraction height diff --git a/x/stake/genesis.go b/x/stake/genesis.go index ff8f59d443..2fed877414 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -26,7 +26,6 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ keeper.SetPool(ctx, data.Pool) keeper.SetParams(ctx, data.Params) - keeper.InitIntraTxCounter(ctx) for i, validator := range data.Validators { validator.BondIntraTxCounter = int16(i) // set the intra-tx counter to the order the validators are presented diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index 3cd81202ae..c4a558b916 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -889,21 +889,21 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 1, len(keeper.GetValidatorsBonded(ctx))) + require.Equal(t, 1, len(keeper.GetLastValidators(ctx))) msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 2, len(keeper.GetValidatorsBonded(ctx))) + require.Equal(t, 2, len(keeper.GetLastValidators(ctx))) msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) - require.Equal(t, 2, len(keeper.GetValidatorsBonded(ctx))) + require.Equal(t, 2, len(keeper.GetLastValidators(ctx))) // unbond the valdator-2 msgBeginUnbonding := NewMsgBeginUnbonding(sdk.AccAddress(validatorAddr2), validatorAddr2, sdk.NewDec(30)) @@ -916,7 +916,7 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { // because there are extra validators waiting to get in, the queued // validator (aka. validator-1) should make it into the bonded group, thus // the total number of validators should stay the same - vals := keeper.GetValidatorsBonded(ctx) + vals := keeper.GetLastValidators(ctx) require.Equal(t, 2, len(vals), "vals %v", vals) val1, found := keeper.GetValidator(ctx, validatorAddr1) require.True(t, found) diff --git a/x/stake/keeper/keeper.go b/x/stake/keeper/keeper.go index 2092786986..26c686bc90 100644 --- a/x/stake/keeper/keeper.go +++ b/x/stake/keeper/keeper.go @@ -53,12 +53,12 @@ func (k Keeper) Codespace() sdk.CodespaceType { //_______________________________________________________________________ -// load/save the pool +// load the pool func (k Keeper) GetPool(ctx sdk.Context) (pool types.Pool) { store := ctx.KVStore(k.storeKey) b := store.Get(PoolKey) if b == nil { - panic("Stored pool should not have been nil") + panic("stored pool should not have been nil") } k.cdc.MustUnmarshalBinary(b, &pool) return @@ -71,21 +71,73 @@ func (k Keeper) SetPool(ctx sdk.Context, pool types.Pool) { store.Set(PoolKey, b) } -//__________________________________________________________________________ +//_______________________________________________________________________ -// get the current in-block validator operation counter -func (k Keeper) InitIntraTxCounter(ctx sdk.Context) { +// Load the last total validator power. +func (k Keeper) GetLastTotalPower(ctx sdk.Context) (power sdk.Dec) { store := ctx.KVStore(k.storeKey) - b := store.Get(IntraTxCounterKey) + b := store.Get(LastTotalPowerKey) if b == nil { - k.SetIntraTxCounter(ctx, 0) + panic("stored last total power should not have been nil") } + k.cdc.MustUnmarshalBinary(b, &power) + return } +// Set the last total validator power. +func (k Keeper) SetLastTotalPower(ctx sdk.Context, power sdk.Dec) { + if !power.IsInteger() { + panic("input power must be whole integer") + } + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(power) + store.Set(LastTotalPowerKey, b) +} + +//_______________________________________________________________________ + +// 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 sdk.Dec) { + store := ctx.KVStore(k.storeKey) + bz := store.Get(GetLastValidatorPowerKey(operator)) + if bz == nil { + return sdk.ZeroDec() + } + k.cdc.MustUnmarshalBinary(bz, &power) + return +} + +func (k Keeper) powerToBytes(power sdk.Dec) []byte { + bz := k.cdc.MustMarshalBinary(power) + return bz +} + +// Set the last validator power. +func (k Keeper) SetLastValidatorPower(ctx sdk.Context, operator sdk.ValAddress, power sdk.Dec) { + if !power.IsInteger() { + panic("input power must be whole integer") + } + store := ctx.KVStore(k.storeKey) + bz := k.powerToBytes(power) + store.Set(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(GetLastValidatorPowerKey(operator)) +} + +//__________________________________________________________________________ + // get the current in-block validator operation counter func (k Keeper) GetIntraTxCounter(ctx sdk.Context) int16 { store := ctx.KVStore(k.storeKey) b := store.Get(IntraTxCounterKey) + if b == nil { + return 0 + } var counter int16 k.cdc.MustUnmarshalBinary(b, &counter) return counter diff --git a/x/stake/keeper/key.go b/x/stake/keeper/key.go index 91ea7d709c..243fe34ec5 100644 --- a/x/stake/keeper/key.go +++ b/x/stake/keeper/key.go @@ -15,21 +15,27 @@ var ( // Keys for store prefixes // TODO DEPRECATED: delete in next release and reorder keys // ParamKey = []byte{0x00} // key for parameters relating to staking - PoolKey = []byte{0x01} // key for the staking pools - ValidatorsKey = []byte{0x02} // prefix for each key to a validator - ValidatorsByConsAddrKey = []byte{0x03} // prefix for each key to a validator index, by pubkey - ValidatorsBondedIndexKey = []byte{0x04} // prefix for each key to a validator index, for bonded validators - ValidatorsByPowerIndexKey = []byte{0x05} // prefix for each key to a validator index, sorted by power - IntraTxCounterKey = []byte{0x06} // key for intra-block tx index - DelegationKey = []byte{0x07} // key for a delegation - UnbondingDelegationKey = []byte{0x08} // key for an unbonding-delegation - UnbondingDelegationByValIndexKey = []byte{0x09} // prefix for each key for an unbonding-delegation, by validator operator - RedelegationKey = []byte{0x0A} // key for a redelegation - RedelegationByValSrcIndexKey = []byte{0x0B} // prefix for each key for an redelegation, by source validator operator - RedelegationByValDstIndexKey = []byte{0x0C} // prefix for each key for an redelegation, by destination validator operator - UnbondingQueueKey = []byte{0x0D} // prefix for the timestamps in unbonding queue - RedelegationQueueKey = []byte{0x0E} // prefix for the timestamps in redelegations queue - ValidatorQueueKey = []byte{0x0F} // prefix for the timestamps in validator queue + PoolKey = []byte{0x01} // key for the staking pools + IntraTxCounterKey = []byte{0x02} // key for intra-block tx index + + // Last* values are const during a block. + LastValidatorPowerKey = []byte{0x11} // prefix for each key to a validator index, for bonded validators + LastTotalPowerKey = []byte{0x12} // prefix for the total power + + ValidatorsKey = []byte{0x21} // prefix for each key to a validator + ValidatorsByConsAddrKey = []byte{0x22} // prefix for each key to a validator index, by pubkey + ValidatorsByPowerIndexKey = []byte{0x23} // prefix for each key to a validator index, sorted by power + + DelegationKey = []byte{0x31} // key for a delegation + UnbondingDelegationKey = []byte{0x32} // key for an unbonding-delegation + UnbondingDelegationByValIndexKey = []byte{0x33} // prefix for each key for an unbonding-delegation, by validator operator + RedelegationKey = []byte{0x34} // key for a redelegation + RedelegationByValSrcIndexKey = []byte{0x35} // prefix for each key for an redelegation, by source validator operator + RedelegationByValDstIndexKey = []byte{0x36} // prefix for each key for an redelegation, by destination validator operator + + UnbondingQueueKey = []byte{0x41} // prefix for the timestamps in unbonding queue + RedelegationQueueKey = []byte{0x42} // prefix for the timestamps in redelegations queue + ValidatorQueueKey = []byte{0x43} // prefix for the timestamps in validator queue ) const maxDigitsForAccount = 12 // ~220,000,000 atoms created at launch @@ -46,9 +52,9 @@ func GetValidatorByConsAddrKey(addr sdk.ConsAddress) []byte { return append(ValidatorsByConsAddrKey, addr.Bytes()...) } -// Get the validator operator address from ValBondedIndexKey -func GetAddressFromValBondedIndexKey(IndexKey []byte) []byte { - return IndexKey[1:] // remove prefix bytes +// Get the validator operator address from LastValidatorPowerKey +func AddressFromLastValidatorPowerKey(key []byte) []byte { + return key[1:] // remove prefix bytes } // get the validator by power index. @@ -61,8 +67,8 @@ func GetValidatorsByPowerIndexKey(validator types.Validator, pool types.Pool) [] } // get the bonded validator index key for an operator address -func GetValidatorsBondedIndexKey(operator sdk.ValAddress) []byte { - return append(ValidatorsBondedIndexKey, operator...) +func GetLastValidatorPowerKey(operator sdk.ValAddress) []byte { + return append(LastValidatorPowerKey, operator...) } // get the power ranking of a validator diff --git a/x/stake/keeper/key_test.go b/x/stake/keeper/key_test.go index dfd6a44e79..385f313c15 100644 --- a/x/stake/keeper/key_test.go +++ b/x/stake/keeper/key_test.go @@ -35,10 +35,10 @@ func TestGetValidatorPowerRank(t *testing.T) { validator types.Validator wantHex string }{ - {val1, "050000000000000000ffffffffffffffffffff"}, - {val2, "050000000000000001ffffffffffffffffffff"}, - {val3, "05000000000000000affffffffffffffffffff"}, - {val4, "050000010000000000ffffffffffffffffffff"}, + {val1, "230000000000000000ffffffffffffffffffff"}, + {val2, "230000000000000001ffffffffffffffffffff"}, + {val3, "23000000000000000affffffffffffffffffff"}, + {val4, "230000010000000000ffffffffffffffffffff"}, } for i, tt := range tests { got := hex.EncodeToString(getValidatorPowerRank(tt.validator)) @@ -55,11 +55,11 @@ func TestGetREDByValDstIndexKey(t *testing.T) { wantHex string }{ {sdk.AccAddress(addr1), sdk.ValAddress(addr1), sdk.ValAddress(addr1), - "0c63d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, + "3663d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, {sdk.AccAddress(addr1), sdk.ValAddress(addr2), sdk.ValAddress(addr3), - "0c3ab62f0d93849be495e21e3e9013a517038f45bd63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f2"}, + "363ab62f0d93849be495e21e3e9013a517038f45bd63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f2"}, {sdk.AccAddress(addr2), sdk.ValAddress(addr1), sdk.ValAddress(addr3), - "0c3ab62f0d93849be495e21e3e9013a517038f45bd5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f08609"}, + "363ab62f0d93849be495e21e3e9013a517038f45bd5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f08609"}, } for i, tt := range tests { got := hex.EncodeToString(GetREDByValDstIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr)) @@ -76,11 +76,11 @@ func TestGetREDByValSrcIndexKey(t *testing.T) { wantHex string }{ {sdk.AccAddress(addr1), sdk.ValAddress(addr1), sdk.ValAddress(addr1), - "0b63d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, + "3563d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"}, {sdk.AccAddress(addr1), sdk.ValAddress(addr2), sdk.ValAddress(addr3), - "0b5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f086093ab62f0d93849be495e21e3e9013a517038f45bd"}, + "355ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f086093ab62f0d93849be495e21e3e9013a517038f45bd"}, {sdk.AccAddress(addr2), sdk.ValAddress(addr1), sdk.ValAddress(addr3), - "0b63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f23ab62f0d93849be495e21e3e9013a517038f45bd"}, + "3563d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f23ab62f0d93849be495e21e3e9013a517038f45bd"}, } for i, tt := range tests { got := hex.EncodeToString(GetREDByValSrcIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr)) diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index 6672843566..4e859a42a8 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -30,10 +30,10 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato // iterate through the active validator set and perform the provided function func (k Keeper) IterateValidatorsBonded(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + iterator := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) i := int64(0) for ; iterator.Valid(); iterator.Next() { - address := GetAddressFromValBondedIndexKey(iterator.Key()) + address := AddressFromLastValidatorPowerKey(iterator.Key()) validator, found := k.GetValidator(ctx, address) if !found { panic(fmt.Sprintf("validator record not found for address: %v\n", address)) @@ -66,7 +66,7 @@ func (k Keeper) ValidatorByConsAddr(ctx sdk.Context, addr sdk.ConsAddress) sdk.V return val } -// total power from the bond +// total power from the bond (not last, but current) func (k Keeper) TotalPower(ctx sdk.Context) sdk.Dec { pool := k.GetPool(ctx) return pool.BondedTokens diff --git a/x/stake/keeper/slash.go b/x/stake/keeper/slash.go index eafb3cca32..294ae9a9fd 100644 --- a/x/stake/keeper/slash.go +++ b/x/stake/keeper/slash.go @@ -98,10 +98,13 @@ func (k Keeper) Slash(ctx sdk.Context, consAddr sdk.ConsAddress, infractionHeigh // cannot decrease balance below zero tokensToBurn := sdk.MinDec(remainingSlashAmount, validator.Tokens) + tokensToBurn = sdk.MaxDec(tokensToBurn, sdk.ZeroDec()) // defensive. - // burn validator's tokens and update the validator + // Deduct from validator's bonded tokens and update the validator. + // The deducted tokens are returned to pool.LooseTokens. validator = k.RemoveValidatorTokens(ctx, validator, tokensToBurn) pool := k.GetPool(ctx) + // Burn the slashed tokens, which are now loose. pool.LooseTokens = pool.LooseTokens.Sub(tokensToBurn) k.SetPool(ctx, pool) diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index d0ac4a2825..13e2ca2c59 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -106,7 +106,6 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initCoins int64) (sdk.Context keeper := NewKeeper(cdc, keyStake, tkeyStake, ck, pk.Subspace(DefaultParamspace), types.DefaultCodespace) keeper.SetPool(ctx, types.InitialPool()) keeper.SetParams(ctx, types.DefaultParams()) - keeper.InitIntraTxCounter(ctx) // fill all the addresses with some coins, set the loose pool tokens simultaneously for _, addr := range Addrs { diff --git a/x/stake/keeper/val_state_change.go b/x/stake/keeper/val_state_change.go index 2c832a8ad2..cfa381973d 100644 --- a/x/stake/keeper/val_state_change.go +++ b/x/stake/keeper/val_state_change.go @@ -12,10 +12,13 @@ import ( ) // Apply and return accumulated updates to the bonded validator set. Also, -// * Updates the active bonded valset as keyed by GetValidatorsBondedIndexKey(). +// * Updates the active valset as keyed by LastValidatorPowerKey. +// * Updates the total power as keyed by LastTotalPowerKey. // * Updates validator status' according to updated powers. // * Updates the fee pool bonded vs loose tokens. // * Updates relevant indices. +// It gets called once after genesis, another time maybe after genesis transactions, +// then once at every EndBlock. // // CONTRACT: Only validators with non-zero power or zero-power that were bonded // at the previous block height or were removed from the validator set entirely @@ -24,13 +27,14 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab store := ctx.KVStore(k.storeKey) maxValidators := k.GetParams(ctx).MaxValidators + totalPower := int64(0) // Retrieve the last validator set. - // This persistent set is updated later in this function. - // (see GetValidatorsBondedIndexKey()). - last := k.retrieveLastValidatorSet(ctx) + // The persistent set is updated later in this function. + // (see LastValidatorPowerKey). + last := k.getLastValidatorsByAddr(ctx) - // iterate over validators, highest power to lowest + // Iterate over validators, highest power to lowest. iterator := sdk.KVStoreReversePrefixIterator(store, ValidatorsByPowerIndexKey) count := 0 for ; iterator.Valid() && count < int(maxValidators); iterator.Next() { @@ -68,22 +72,22 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab oldPowerBytes, found := last[operatorBytes] // calculate the new power bytes - newPowerBytes := validator.ABCIValidatorPowerBytes(k.cdc) - + newPower := validator.BondedTokens().RoundInt64() + newPowerBytes := k.powerToBytes(sdk.NewDec(newPower)) // update the validator set if power has changed if !found || !bytes.Equal(oldPowerBytes, newPowerBytes) { updates = append(updates, validator.ABCIValidatorUpdate()) + + // set validator power on lookup index. + k.SetLastValidatorPower(ctx, operator, sdk.NewDec(newPower)) } // validator still in the validator set, so delete from the copy delete(last, operatorBytes) - // set the bonded validator index - store.Set(GetValidatorsBondedIndexKey(operator), newPowerBytes) - // keep count count++ - + totalPower += newPower } // sort the no-longer-bonded validators @@ -104,11 +108,15 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab } // delete from the bonded validator index - store.Delete(GetValidatorsBondedIndexKey(operator)) + k.DeleteLastValidatorPower(ctx, operator) // update the validator set updates = append(updates, validator.ABCIValidatorUpdateZero()) + } + // set total power on lookup index if there are any updates + if len(updates) > 0 { + k.SetLastTotalPower(ctx, sdk.NewDec(totalPower)) } return updates @@ -243,11 +251,11 @@ func (k Keeper) completeUnbondingValidator(ctx sdk.Context, validator types.Vali // map of operator addresses to serialized power type validatorsByAddr map[[sdk.AddrLen]byte][]byte -// retrieve the last validator set -func (k Keeper) retrieveLastValidatorSet(ctx sdk.Context) validatorsByAddr { +// get the last validator set +func (k Keeper) getLastValidatorsByAddr(ctx sdk.Context) validatorsByAddr { last := make(validatorsByAddr) store := ctx.KVStore(k.storeKey) - iterator := sdk.KVStorePrefixIterator(store, ValidatorsBondedIndexKey) + iterator := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) for ; iterator.Valid(); iterator.Next() { var operator [sdk.AddrLen]byte copy(operator[:], iterator.Key()[1:]) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 95e5f00012..9fd7434d31 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -235,24 +235,24 @@ func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve uint16) (validators [ } // get the group of the bonded validators -func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validator) { +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, ValidatorsBondedIndexKey) + iterator := sdk.KVStorePrefixIterator(store, LastValidatorPowerKey) defer iterator.Close() i := 0 for ; iterator.Valid(); iterator.Next() { // sanity check - if i > int(maxValidators-1) { - panic("maxValidators is less than the number of records in ValidatorsBonded Store, store should have been updated") + if i >= int(maxValidators) { + panic("more validators than maxValidators found") } - address := GetAddressFromValBondedIndexKey(iterator.Key()) + address := AddressFromLastValidatorPowerKey(iterator.Key()) validator := k.mustGetValidator(ctx, address) validators[i] = validator @@ -261,7 +261,7 @@ func (k Keeper) GetValidatorsBonded(ctx sdk.Context) (validators []types.Validat return validators[:i] // trim } -// get the group of bonded validators sorted by power-rank +// get the current group of bonded validators sorted by power-rank func (k Keeper) GetBondedValidatorsByPower(ctx sdk.Context) []types.Validator { store := ctx.KVStore(k.storeKey) maxValidators := k.MaxValidators(ctx) diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 6f14ba7a5d..7acf1cc02d 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -49,7 +49,7 @@ func TestSetValidator(t *testing.T) { assert.True(ValEq(t, validator, resVal)) require.True(t, found) - resVals := keeper.GetValidatorsBonded(ctx) + resVals := keeper.GetLastValidators(ctx) require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validator, resVals[0])) @@ -191,7 +191,7 @@ func TestSlashToZeroPowerRemoved(t *testing.T) { require.False(t, found) } -// This function tests UpdateValidator, GetValidator, GetValidatorsBonded, RemoveValidator +// This function tests UpdateValidator, GetValidator, GetLastValidators, RemoveValidator func TestValidatorBasics(t *testing.T) { ctx, _, keeper := CreateTestInput(t, false, 1000) pool := keeper.GetPool(ctx) @@ -213,7 +213,7 @@ func TestValidatorBasics(t *testing.T) { // check the empty keeper first _, found := keeper.GetValidator(ctx, addrVals[0]) require.False(t, found) - resVals := keeper.GetValidatorsBonded(ctx) + resVals := keeper.GetLastValidators(ctx) require.Zero(t, len(resVals)) resVals = keeper.GetValidators(ctx, 2) @@ -237,7 +237,7 @@ func TestValidatorBasics(t *testing.T) { require.True(t, found) assert.True(ValEq(t, validators[0], resVal)) - resVals = keeper.GetValidatorsBonded(ctx) + resVals = keeper.GetLastValidators(ctx) require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validators[0], resVals[0])) assert.Equal(t, sdk.Bonded, validators[0].Status) @@ -255,7 +255,7 @@ func TestValidatorBasics(t *testing.T) { require.True(t, found) assert.True(ValEq(t, validators[0], resVal)) - resVals = keeper.GetValidatorsBonded(ctx) + resVals = keeper.GetLastValidators(ctx) require.Equal(t, 1, len(resVals)) assert.True(ValEq(t, validators[0], resVals[0])) @@ -269,7 +269,7 @@ func TestValidatorBasics(t *testing.T) { require.True(t, found) assert.True(ValEq(t, validators[2], resVal)) - resVals = keeper.GetValidatorsBonded(ctx) + resVals = keeper.GetLastValidators(ctx) require.Equal(t, 3, len(resVals)) assert.True(ValEq(t, validators[0], resVals[0])) // order doesn't matter here assert.True(ValEq(t, validators[1], resVals[1])) diff --git a/x/stake/stake.go b/x/stake/stake.go index c4aa547022..c755e352aa 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -39,12 +39,13 @@ var ( GetDelegationKey = keeper.GetDelegationKey GetDelegationsKey = keeper.GetDelegationsKey PoolKey = keeper.PoolKey + IntraTxCounterKey = keeper.IntraTxCounterKey + LastValidatorPowerKey = keeper.LastValidatorPowerKey + LastTotalPowerKey = keeper.LastTotalPowerKey ValidatorsKey = keeper.ValidatorsKey ValidatorsByConsAddrKey = keeper.ValidatorsByConsAddrKey - ValidatorsBondedIndexKey = keeper.ValidatorsBondedIndexKey ValidatorsByPowerIndexKey = keeper.ValidatorsByPowerIndexKey DelegationKey = keeper.DelegationKey - IntraTxCounterKey = keeper.IntraTxCounterKey GetUBDKey = keeper.GetUBDKey GetUBDByValIndexKey = keeper.GetUBDByValIndexKey GetUBDsKey = keeper.GetUBDsKey diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index d389115217..628edd45f4 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -104,9 +104,9 @@ func (d Delegation) Equal(d2 Delegation) bool { var _ sdk.Delegation = Delegation{} // nolint - for sdk.Delegation -func (d Delegation) GetDelegator() sdk.AccAddress { return d.DelegatorAddr } -func (d Delegation) GetValidator() sdk.ValAddress { return d.ValidatorAddr } -func (d Delegation) GetShares() sdk.Dec { return d.Shares } +func (d Delegation) GetDelegatorAddr() sdk.AccAddress { return d.DelegatorAddr } +func (d Delegation) GetValidatorAddr() sdk.ValAddress { return d.ValidatorAddr } +func (d Delegation) GetShares() sdk.Dec { return d.Shares } // HumanReadableString returns a human readable string representation of a // Delegation. An error is returned if the Delegation's delegator or validator diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index d774f761bd..52d30f0a31 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -314,12 +314,6 @@ func (v Validator) ABCIValidatorUpdate() abci.ValidatorUpdate { } } -// ABCIValidatorPowerBytes -func (v Validator) ABCIValidatorPowerBytes(cdc *codec.Codec) []byte { - power := v.BondedTokens().RoundInt64() - return cdc.MustMarshalBinary(power) -} - // ABCIValidatorUpdateZero returns an abci.ValidatorUpdate from a staked validator type // with zero power used for validator updates. func (v Validator) ABCIValidatorUpdateZero() abci.ValidatorUpdate {