Merge PR #3333: F1 storage efficiency improvements

This commit is contained in:
Christopher Goes 2019-01-23 12:37:03 +01:00 committed by GitHub
parent a27ef7f7d1
commit b5e245fee3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 273 additions and 35 deletions

View File

@ -45,6 +45,7 @@ BREAKING CHANGES
* `Delegation` -> `Value` in `MsgCreateValidator` and `MsgDelegate` * `Delegation` -> `Value` in `MsgCreateValidator` and `MsgDelegate`
* `MsgBeginUnbonding` -> `MsgUndelegate` * `MsgBeginUnbonding` -> `MsgUndelegate`
* [\#3315] Increase decimal precision to 18 * [\#3315] Increase decimal precision to 18
* \#3333 - F1 storage efficiency improvements - automatic withdrawals when unbonded, historical reward reference counting
* \#3323 Update to Tendermint 0.29.0 * \#3323 Update to Tendermint 0.29.0
* [\#3328](https://github.com/cosmos/cosmos-sdk/issues/3328) [x/gov] Remove redundant action tag * [\#3328](https://github.com/cosmos/cosmos-sdk/issues/3328) [x/gov] Remove redundant action tag

View File

@ -74,10 +74,10 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
} }
// clear validator slash events // clear validator slash events
app.distrKeeper.DeleteValidatorSlashEvents(ctx) app.distrKeeper.DeleteAllValidatorSlashEvents(ctx)
// clear validator historical rewards // clear validator historical rewards
app.distrKeeper.DeleteValidatorHistoricalRewards(ctx) app.distrKeeper.DeleteAllValidatorHistoricalRewards(ctx)
// set context height to zero // set context height to zero
height := ctx.BlockHeight() height := ctx.BlockHeight()

View File

@ -0,0 +1,18 @@
## Reference Counting in F1 Fee Distribution
In F1 fee distribution, in order to calculate the rewards a delegator ought to receive when they
withdraw their delegation, we must read the terms of the summation of rewards divided by tokens from
the period which they ended when they delegated, and the final period (created when they withdraw).
Additionally, as slashes change the amount of tokens a delegation will have (but we calculate this lazily,
only when a delegator un-delegates), we must calculate rewards in separate periods before / after any slashes
which occurred in between when a delegator delegated and when they withdrew their rewards. Thus slashes, like
delegations, reference the period which was ended by the slash event.
All stored historical rewards records for periods which are no longer referenced by any delegations
or any slashes can thus be safely removed, as they will never be read (future delegations and future
slashes will always reference future periods). This is implemented by tracking a `ReferenceCount`
along with each historical reward storage entry. Each time a new object (delegation or slash)
is created which might need to reference the historical record, the reference count is incremented.
Each time one object which previously needed to reference the historical record is deleted, the reference
count is decremented. If the reference count hits zero, the historical record is deleted.

View File

@ -8,14 +8,16 @@ import (
// initialize starting info for a new delegation // initialize starting info for a new delegation
func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) { func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) {
// period has already been incremented // period has already been incremented - we want to store the period ended by this delegation action
period := k.GetValidatorCurrentRewards(ctx, val).Period previousPeriod := k.GetValidatorCurrentRewards(ctx, val).Period - 1
validator := k.stakingKeeper.Validator(ctx, val) validator := k.stakingKeeper.Validator(ctx, val)
delegation := k.stakingKeeper.Delegation(ctx, del, val) delegation := k.stakingKeeper.Delegation(ctx, del, val)
// calculate delegation stake in tokens // calculate delegation stake in tokens
// we don't store directly, so multiply delegation shares * (tokens per share) // we don't store directly, so multiply delegation shares * (tokens per share)
stake := delegation.GetShares().Mul(validator.GetDelegatorShareExRate()) stake := delegation.GetShares().Mul(validator.GetDelegatorShareExRate())
k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(period-1, stake, uint64(ctx.BlockHeight()))) k.SetDelegatorStartingInfo(ctx, val, del, types.NewDelegatorStartingInfo(previousPeriod, stake, uint64(ctx.BlockHeight())))
} }
// calculate the rewards accrued by a delegation between two periods // calculate the rewards accrued by a delegation between two periods
@ -29,7 +31,7 @@ func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Valid
// return staking * (ending - starting) // return staking * (ending - starting)
starting := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), startingPeriod) starting := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), startingPeriod)
ending := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), endingPeriod) ending := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), endingPeriod)
difference := ending.Minus(starting) difference := ending.CumulativeRewardRatio.Minus(starting.CumulativeRewardRatio)
rewards = difference.MulDec(staking) rewards = difference.MulDec(staking)
return return
} }
@ -50,7 +52,7 @@ func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, d
if endingHeight >= startingHeight { if endingHeight >= startingHeight {
k.IterateValidatorSlashEventsBetween(ctx, del.GetValidatorAddr(), startingHeight, endingHeight, k.IterateValidatorSlashEventsBetween(ctx, del.GetValidatorAddr(), startingHeight, endingHeight,
func(height uint64, event types.ValidatorSlashEvent) (stop bool) { func(height uint64, event types.ValidatorSlashEvent) (stop bool) {
endingPeriod := event.ValidatorPeriod - 1 endingPeriod := event.ValidatorPeriod
rewards = rewards.Plus(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake)) rewards = rewards.Plus(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake))
stake = stake.Mul(sdk.OneDec().Sub(event.Fraction)) stake = stake.Mul(sdk.OneDec().Sub(event.Fraction))
startingPeriod = endingPeriod startingPeriod = endingPeriod
@ -67,10 +69,20 @@ func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, d
func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation) sdk.Error { func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, del sdk.Delegation) sdk.Error {
// check existence of delegator starting info
if !k.HasDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr()) {
return types.ErrNoDelegationDistInfo(k.codespace)
}
// end current period and calculate rewards // end current period and calculate rewards
endingPeriod := k.incrementValidatorPeriod(ctx, val) endingPeriod := k.incrementValidatorPeriod(ctx, val)
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
// decrement reference count of starting period
startingInfo := k.GetDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr())
startingPeriod := startingInfo.PreviousPeriod
k.decrementReferenceCount(ctx, del.GetValidatorAddr(), startingPeriod)
// truncate coins, return remainder to community pool // truncate coins, return remainder to community pool
coins, remainder := rewards.TruncateDecimal() coins, remainder := rewards.TruncateDecimal()
outstanding := k.GetOutstandingRewards(ctx) outstanding := k.GetOutstandingRewards(ctx)
@ -85,5 +97,8 @@ func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, de
return err return err
} }
// remove delegator starting info
k.DeleteDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr())
return nil return nil
} }

View File

@ -29,9 +29,15 @@ func TestCalculateRewardsBasic(t *testing.T) {
val := sk.Validator(ctx, valOpAddr1) val := sk.Validator(ctx, valOpAddr1)
del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1) del := sk.Delegation(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)
// historical count should be 2 (once for validator init, once for delegation init)
require.Equal(t, uint64(2), k.GetValidatorHistoricalRewardCount(ctx))
// end period // end period
endingPeriod := k.incrementValidatorPeriod(ctx, val) endingPeriod := k.incrementValidatorPeriod(ctx, val)
// historical count should be 3 (since we ended the period, and haven't yet decremented a reference)
require.Equal(t, uint64(3), k.GetValidatorHistoricalRewardCount(ctx))
// calculate delegation rewards // calculate delegation rewards
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod) rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod)
@ -276,9 +282,15 @@ func TestWithdrawDelegationRewardsBasic(t *testing.T) {
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens) k.AllocateTokensToValidator(ctx, val, tokens)
// historical count should be 2 (initial + latest for delegation)
require.Equal(t, uint64(2), k.GetValidatorHistoricalRewardCount(ctx))
// withdraw rewards // withdraw rewards
require.Nil(t, k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1)) require.Nil(t, k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr1), valOpAddr1))
// historical count should still be 2 (added one record, cleared one)
require.Equal(t, uint64(2), k.GetValidatorHistoricalRewardCount(ctx))
// assert correct balance // assert correct balance
require.Equal(t, sdk.Coins{{staking.DefaultBondDenom, sdk.NewInt(balance - bond + (initial / 2))}}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins()) require.Equal(t, sdk.Coins{{staking.DefaultBondDenom, sdk.NewInt(balance - bond + (initial / 2))}}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins())
@ -447,10 +459,16 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}} tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens) k.AllocateTokensToValidator(ctx, val, tokens)
// historical count should be 2 (validator init, delegation init)
require.Equal(t, uint64(2), k.GetValidatorHistoricalRewardCount(ctx))
// second delegation // second delegation
msg2 := staking.NewMsgDelegate(sdk.AccAddress(valOpAddr2), valOpAddr1, sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100))) msg2 := staking.NewMsgDelegate(sdk.AccAddress(valOpAddr2), valOpAddr1, sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)))
require.True(t, sh(ctx, msg2).IsOK()) require.True(t, sh(ctx, msg2).IsOK())
// historical count should be 3 (second delegation init)
require.Equal(t, uint64(3), k.GetValidatorHistoricalRewardCount(ctx))
// fetch updated validator // fetch updated validator
val = sk.Validator(ctx, valOpAddr1) val = sk.Validator(ctx, valOpAddr1)
del2 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1) del2 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1)
@ -467,6 +485,9 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
// second delegator withdraws // second delegator withdraws
k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1) k.WithdrawDelegationRewards(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1)
// historical count should be 3 (validator init + two delegations)
require.Equal(t, uint64(3), k.GetValidatorHistoricalRewardCount(ctx))
// validator withdraws commission // validator withdraws commission
k.WithdrawValidatorCommission(ctx, valOpAddr1) k.WithdrawValidatorCommission(ctx, valOpAddr1)

View File

@ -20,11 +20,41 @@ func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {
h.k.initializeValidator(ctx, val) h.k.initializeValidator(ctx, val)
} }
func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) { func (h Hooks) BeforeValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) {
val := h.k.stakingKeeper.Validator(ctx, valAddr)
// increment period
h.k.incrementValidatorPeriod(ctx, val)
} }
func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) { func (h Hooks) AfterValidatorRemoved(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) {
// force-withdraw commission
commission := h.k.GetValidatorAccumulatedCommission(ctx, valAddr)
if !commission.IsZero() {
coins, remainder := commission.TruncateDecimal()
// remainder to community pool
feePool := h.k.GetFeePool(ctx)
feePool.CommunityPool = feePool.CommunityPool.Plus(remainder)
h.k.SetFeePool(ctx, feePool)
// update outstanding
outstanding := h.k.GetOutstandingRewards(ctx)
h.k.SetOutstandingRewards(ctx, outstanding.Minus(commission))
// add to validator account
accAddr := sdk.AccAddress(valAddr)
withdrawAddr := h.k.GetDelegatorWithdrawAddr(ctx, accAddr)
if _, _, err := h.k.bankKeeper.AddCoins(ctx, withdrawAddr, coins); err != nil {
panic(err)
}
}
// remove commission record
h.k.DeleteValidatorAccumulatedCommission(ctx, valAddr)
// clear slashes
h.k.DeleteValidatorSlashEvents(ctx, valAddr)
// clear historical rewards
h.k.DeleteValidatorHistoricalRewards(ctx, valAddr)
// clear current rewards
h.k.DeleteValidatorCurrentRewards(ctx, valAddr)
} }
func (h Hooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { func (h Hooks) BeforeDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
val := h.k.stakingKeeper.Validator(ctx, valAddr) val := h.k.stakingKeeper.Validator(ctx, valAddr)
@ -42,7 +72,7 @@ func (h Hooks) BeforeDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAd
} }
} }
func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { func (h Hooks) BeforeDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
// nothing needed here since OnDelegationSharesModified will always also be called // nothing needed here since BeforeDelegationSharesModified will always also be called
} }
func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { func (h Hooks) AfterDelegationModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) {
// create new delegation period record // create new delegation period record

View File

@ -112,6 +112,11 @@ func GetDelegatorStartingInfoKey(v sdk.ValAddress, d sdk.AccAddress) []byte {
return append(append(DelegatorStartingInfoPrefix, v.Bytes()...), d.Bytes()...) return append(append(DelegatorStartingInfoPrefix, v.Bytes()...), d.Bytes()...)
} }
// gets the prefix key for a validator's historical rewards
func GetValidatorHistoricalRewardsPrefix(v sdk.ValAddress) []byte {
return append(ValidatorHistoricalRewardsPrefix, v.Bytes()...)
}
// gets the key for a validator's historical rewards // gets the key for a validator's historical rewards
func GetValidatorHistoricalRewardsKey(v sdk.ValAddress, k uint64) []byte { func GetValidatorHistoricalRewardsKey(v sdk.ValAddress, k uint64) []byte {
b := make([]byte, 8) b := make([]byte, 8)
@ -129,6 +134,11 @@ func GetValidatorAccumulatedCommissionKey(v sdk.ValAddress) []byte {
return append(ValidatorAccumulatedCommissionPrefix, v.Bytes()...) return append(ValidatorAccumulatedCommissionPrefix, v.Bytes()...)
} }
// gets the prefix key for a validator's slash fractions
func GetValidatorSlashEventPrefix(v sdk.ValAddress) []byte {
return append(ValidatorSlashEventPrefix, v.Bytes()...)
}
// gets the key for a validator's slash fraction // gets the key for a validator's slash fraction
func GetValidatorSlashEventKey(v sdk.ValAddress, height uint64) []byte { func GetValidatorSlashEventKey(v sdk.ValAddress, height uint64) []byte {
b := make([]byte, 8) b := make([]byte, 8)

View File

@ -3,10 +3,11 @@ package keeper
import ( import (
"fmt" "fmt"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/distribution/types"
abci "github.com/tendermint/tendermint/abci/types"
) )
// nolint // nolint

View File

@ -6,11 +6,12 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
abci "github.com/tendermint/tendermint/abci/types"
) )
const custom = "custom" const custom = "custom"

View File

@ -21,8 +21,8 @@ func (k Keeper) SetDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr
store.Set(GetDelegatorWithdrawAddrKey(delAddr), withdrawAddr.Bytes()) store.Set(GetDelegatorWithdrawAddrKey(delAddr), withdrawAddr.Bytes())
} }
// remove a delegator withdraw addr // delete a delegator withdraw addr
func (k Keeper) RemoveDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) { func (k Keeper) DeleteDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
store.Delete(GetDelegatorWithdrawAddrKey(delAddr)) store.Delete(GetDelegatorWithdrawAddrKey(delAddr))
} }
@ -77,7 +77,7 @@ func (k Keeper) SetPreviousProposerConsAddr(ctx sdk.Context, consAddr sdk.ConsAd
store.Set(ProposerKey, b) store.Set(ProposerKey, b)
} }
// get the starting period associated with a delegator // get the starting info associated with a delegator
func (k Keeper) GetDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) (period types.DelegatorStartingInfo) { func (k Keeper) GetDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) (period types.DelegatorStartingInfo) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
b := store.Get(GetDelegatorStartingInfoKey(val, del)) b := store.Get(GetDelegatorStartingInfoKey(val, del))
@ -85,13 +85,25 @@ func (k Keeper) GetDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, de
return return
} }
// set the starting period associated with a delegator // set the starting info associated with a delegator
func (k Keeper) SetDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress, period types.DelegatorStartingInfo) { func (k Keeper) SetDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress, period types.DelegatorStartingInfo) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(period) b := k.cdc.MustMarshalBinaryLengthPrefixed(period)
store.Set(GetDelegatorStartingInfoKey(val, del), b) store.Set(GetDelegatorStartingInfoKey(val, del), b)
} }
// check existence of the starting info associated with a delegator
func (k Keeper) HasDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) bool {
store := ctx.KVStore(k.storeKey)
return store.Has(GetDelegatorStartingInfoKey(val, del))
}
// delete the starting info associated with a delegator
func (k Keeper) DeleteDelegatorStartingInfo(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetDelegatorStartingInfoKey(val, del))
}
// iterate over delegator starting infos // iterate over delegator starting infos
func (k Keeper) IterateDelegatorStartingInfos(ctx sdk.Context, handler func(val sdk.ValAddress, del sdk.AccAddress, info types.DelegatorStartingInfo) (stop bool)) { func (k Keeper) IterateDelegatorStartingInfos(ctx sdk.Context, handler func(val sdk.ValAddress, del sdk.AccAddress, info types.DelegatorStartingInfo) (stop bool)) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
@ -137,8 +149,24 @@ func (k Keeper) IterateValidatorHistoricalRewards(ctx sdk.Context, handler func(
} }
} }
// delete historical rewards // delete a historical reward
func (k Keeper) DeleteValidatorHistoricalRewards(ctx sdk.Context) { func (k Keeper) DeleteValidatorHistoricalReward(ctx sdk.Context, val sdk.ValAddress, period uint64) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetValidatorHistoricalRewardsKey(val, period))
}
// delete historical rewards for a validator
func (k Keeper) DeleteValidatorHistoricalRewards(ctx sdk.Context, val sdk.ValAddress) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, GetValidatorHistoricalRewardsPrefix(val))
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
// delete all historical rewards
func (k Keeper) DeleteAllValidatorHistoricalRewards(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorHistoricalRewardsPrefix) iter := sdk.KVStorePrefixIterator(store, ValidatorHistoricalRewardsPrefix)
defer iter.Close() defer iter.Close()
@ -147,6 +175,17 @@ func (k Keeper) DeleteValidatorHistoricalRewards(ctx sdk.Context) {
} }
} }
// historical record count (used for testcases)
func (k Keeper) GetValidatorHistoricalRewardCount(ctx sdk.Context) (count uint64) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorHistoricalRewardsPrefix)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
count++
}
return
}
// get current rewards for a validator // get current rewards for a validator
func (k Keeper) GetValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress) (rewards types.ValidatorCurrentRewards) { func (k Keeper) GetValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress) (rewards types.ValidatorCurrentRewards) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
@ -162,6 +201,12 @@ func (k Keeper) SetValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress,
store.Set(GetValidatorCurrentRewardsKey(val), b) store.Set(GetValidatorCurrentRewardsKey(val), b)
} }
// delete current rewards for a validator
func (k Keeper) DeleteValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetValidatorCurrentRewardsKey(val))
}
// iterate over current rewards // iterate over current rewards
func (k Keeper) IterateValidatorCurrentRewards(ctx sdk.Context, handler func(val sdk.ValAddress, rewards types.ValidatorCurrentRewards) (stop bool)) { func (k Keeper) IterateValidatorCurrentRewards(ctx sdk.Context, handler func(val sdk.ValAddress, rewards types.ValidatorCurrentRewards) (stop bool)) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
@ -195,6 +240,12 @@ func (k Keeper) SetValidatorAccumulatedCommission(ctx sdk.Context, val sdk.ValAd
store.Set(GetValidatorAccumulatedCommissionKey(val), b) store.Set(GetValidatorAccumulatedCommissionKey(val), b)
} }
// delete accumulated commission for a validator
func (k Keeper) DeleteValidatorAccumulatedCommission(ctx sdk.Context, val sdk.ValAddress) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetValidatorAccumulatedCommissionKey(val))
}
// iterate over accumulated commissions // iterate over accumulated commissions
func (k Keeper) IterateValidatorAccumulatedCommissions(ctx sdk.Context, handler func(val sdk.ValAddress, commission types.ValidatorAccumulatedCommission) (stop bool)) { func (k Keeper) IterateValidatorAccumulatedCommissions(ctx sdk.Context, handler func(val sdk.ValAddress, commission types.ValidatorAccumulatedCommission) (stop bool)) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
@ -277,8 +328,18 @@ func (k Keeper) IterateValidatorSlashEvents(ctx sdk.Context, handler func(val sd
} }
} }
// delete slash events for a particular validator
func (k Keeper) DeleteValidatorSlashEvents(ctx sdk.Context, val sdk.ValAddress) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, GetValidatorSlashEventPrefix(val))
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
// delete all slash events // delete all slash events
func (k Keeper) DeleteValidatorSlashEvents(ctx sdk.Context) { func (k Keeper) DeleteAllValidatorSlashEvents(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorSlashEventPrefix) iter := sdk.KVStorePrefixIterator(store, ValidatorSlashEventPrefix)
defer iter.Close() defer iter.Close()

View File

@ -8,8 +8,8 @@ import (
// initialize rewards for a new validator // initialize rewards for a new validator
func (k Keeper) initializeValidator(ctx sdk.Context, val sdk.Validator) { func (k Keeper) initializeValidator(ctx sdk.Context, val sdk.Validator) {
// set initial historical rewards (period 0) // set initial historical rewards (period 0) with reference count of 1
k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), 0, types.ValidatorHistoricalRewards{}) k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), 0, types.NewValidatorHistoricalRewards(sdk.DecCoins{}, 1))
// set current rewards (starting at period 1) // set current rewards (starting at period 1)
k.SetValidatorCurrentRewards(ctx, val.GetOperator(), types.NewValidatorCurrentRewards(sdk.DecCoins{}, 1)) k.SetValidatorCurrentRewards(ctx, val.GetOperator(), types.NewValidatorCurrentRewards(sdk.DecCoins{}, 1))
@ -42,10 +42,10 @@ func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val sdk.Validator) uin
} }
// fetch historical rewards for last period // fetch historical rewards for last period
historical := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period-1) historical := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period-1).CumulativeRewardRatio
// fet new historical rewards // set new historical rewards with reference count of 1
k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period, historical.Plus(current)) k.SetValidatorHistoricalRewards(ctx, val.GetOperator(), rewards.Period, types.NewValidatorHistoricalRewards(historical.Plus(current), 1))
// set current rewards, incrementing period by 1 // set current rewards, incrementing period by 1
k.SetValidatorCurrentRewards(ctx, val.GetOperator(), types.NewValidatorCurrentRewards(sdk.DecCoins{}, rewards.Period+1)) k.SetValidatorCurrentRewards(ctx, val.GetOperator(), types.NewValidatorCurrentRewards(sdk.DecCoins{}, rewards.Period+1))
@ -53,19 +53,47 @@ func (k Keeper) incrementValidatorPeriod(ctx sdk.Context, val sdk.Validator) uin
return rewards.Period return rewards.Period
} }
// increment the reference count for a historical rewards value
func (k Keeper) incrementReferenceCount(ctx sdk.Context, valAddr sdk.ValAddress, period uint64) {
historical := k.GetValidatorHistoricalRewards(ctx, valAddr, period)
if historical.ReferenceCount > 1 {
panic("reference count should never exceed 1")
}
historical.ReferenceCount++
k.SetValidatorHistoricalRewards(ctx, valAddr, period, historical)
}
// decrement the reference count for a historical rewards value, and delete if zero references remain
func (k Keeper) decrementReferenceCount(ctx sdk.Context, valAddr sdk.ValAddress, period uint64) {
historical := k.GetValidatorHistoricalRewards(ctx, valAddr, period)
if historical.ReferenceCount == 0 {
panic("cannot set negative reference count")
}
historical.ReferenceCount--
if historical.ReferenceCount == 0 {
k.DeleteValidatorHistoricalReward(ctx, valAddr, period)
} else {
k.SetValidatorHistoricalRewards(ctx, valAddr, period, historical)
}
}
func (k Keeper) updateValidatorSlashFraction(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) { func (k Keeper) updateValidatorSlashFraction(ctx sdk.Context, valAddr sdk.ValAddress, fraction sdk.Dec) {
height := uint64(ctx.BlockHeight()) height := uint64(ctx.BlockHeight())
currentFraction := sdk.ZeroDec() currentFraction := sdk.ZeroDec()
currentPeriod := k.GetValidatorCurrentRewards(ctx, valAddr).Period endedPeriod := k.GetValidatorCurrentRewards(ctx, valAddr).Period - 1
current, found := k.GetValidatorSlashEvent(ctx, valAddr, height) current, found := k.GetValidatorSlashEvent(ctx, valAddr, height)
if found { if found {
// there has already been a slash event this height, // there has already been a slash event this height,
// and we don't need to store more than one, // and we don't need to store more than one,
// so just update the current slash fraction // so just update the current slash fraction
currentFraction = current.Fraction currentFraction = current.Fraction
} else {
val := k.stakingKeeper.Validator(ctx, valAddr)
// increment current period
endedPeriod = k.incrementValidatorPeriod(ctx, val)
} }
currentMultiplicand := sdk.OneDec().Sub(currentFraction) currentMultiplicand := sdk.OneDec().Sub(currentFraction)
newMultiplicand := sdk.OneDec().Sub(fraction) newMultiplicand := sdk.OneDec().Sub(fraction)
updatedFraction := sdk.OneDec().Sub(currentMultiplicand.Mul(newMultiplicand)) updatedFraction := sdk.OneDec().Sub(currentMultiplicand.Mul(newMultiplicand))
k.SetValidatorSlashEvent(ctx, valAddr, height, types.NewValidatorSlashEvent(currentPeriod, updatedFraction)) k.SetValidatorSlashEvent(ctx, valAddr, height, types.NewValidatorSlashEvent(endedPeriod, updatedFraction))
} }

View File

@ -5,6 +5,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
distr "github.com/cosmos/cosmos-sdk/x/distribution" distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation"
"github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking"
) )
@ -20,6 +21,10 @@ func AllInvariants(d distr.Keeper, stk staking.Keeper) simulation.Invariant {
if err != nil { if err != nil {
return err return err
} }
err = ReferenceCountInvariant(d, stk)(ctx)
if err != nil {
return err
}
return nil return nil
} }
} }
@ -29,7 +34,7 @@ func NonNegativeOutstandingInvariant(k distr.Keeper) simulation.Invariant {
return func(ctx sdk.Context) error { return func(ctx sdk.Context) error {
outstanding := k.GetOutstandingRewards(ctx) outstanding := k.GetOutstandingRewards(ctx)
if outstanding.HasNegative() { if outstanding.HasNegative() {
return fmt.Errorf("Negative outstanding coins: %v", outstanding) return fmt.Errorf("negative outstanding coins: %v", outstanding)
} }
return nil return nil
} }
@ -57,7 +62,35 @@ func CanWithdrawInvariant(k distr.Keeper, sk staking.Keeper) simulation.Invarian
remaining := k.GetOutstandingRewards(ctx) remaining := k.GetOutstandingRewards(ctx)
if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) { if len(remaining) > 0 && remaining[0].Amount.LT(sdk.ZeroDec()) {
return fmt.Errorf("Negative remaining coins: %v", remaining) return fmt.Errorf("negative remaining coins: %v", remaining)
}
return nil
}
}
// ReferenceCountInvariant checks that the number of historical rewards records is correct
func ReferenceCountInvariant(k distr.Keeper, sk staking.Keeper) simulation.Invariant {
return func(ctx sdk.Context) error {
valCount := uint64(0)
sk.IterateValidators(ctx, func(_ int64, val sdk.Validator) (stop bool) {
valCount++
return false
})
dels := sk.GetAllDelegations(ctx)
slashCount := uint64(0)
k.IterateValidatorSlashEvents(ctx, func(_ sdk.ValAddress, _ uint64, _ types.ValidatorSlashEvent) (stop bool) {
slashCount++
return false
})
// one record per validator (zeroeth period), one record per delegation (previous period), one record per slash (previous period)
expected := valCount + uint64(len(dels)) + slashCount
count := k.GetValidatorHistoricalRewardCount(ctx)
if count != expected {
return fmt.Errorf("unexpected number of historical rewards records: expected %v, got %v", expected, count)
} }
return nil return nil

View File

@ -8,9 +8,28 @@ import (
) )
// historical rewards for a validator // historical rewards for a validator
// TODO add reference counter, ref https://github.com/cosmos/cosmos-sdk/pull/3099#discussion_r245747051
// height is implicit within the store key // height is implicit within the store key
type ValidatorHistoricalRewards = sdk.DecCoins // cumulative reward ratio is the sum from the zeroeth period
// until this period of rewards / tokens, per the spec
// The reference count indicates the number of objects
// which might need to reference this historical entry
// at any point.
// ReferenceCount =
// number of outstanding delegations which ended the associated period (and might need to read that record)
// + number of slashes which ended the associated period (and might need to read that record)
// + one per validator for the zeroeth period, set on initialization
type ValidatorHistoricalRewards struct {
CumulativeRewardRatio sdk.DecCoins `json:"cumulative_reward_ratio"`
ReferenceCount uint16 `json:"reference_count"`
}
// create a new ValidatorHistoricalRewards
func NewValidatorHistoricalRewards(cumulativeRewardRatio sdk.DecCoins, referenceCount uint16) ValidatorHistoricalRewards {
return ValidatorHistoricalRewards{
CumulativeRewardRatio: cumulativeRewardRatio,
ReferenceCount: referenceCount,
}
}
// current rewards and current period for a validator // current rewards and current period for a validator
// kept as a running counter and incremented each block // kept as a running counter and incremented each block

View File

@ -184,8 +184,8 @@ func TestRemoveDelShares(t *testing.T) {
DelegatorShares: delShares, DelegatorShares: delShares,
} }
pool := Pool{ pool := Pool{
BondedTokens: sdk.NewInt(248305), BondedTokens: sdk.NewInt(248305),
NotBondedTokens: sdk.NewInt(232147), NotBondedTokens: sdk.NewInt(232147),
} }
shares := sdk.NewDec(29) shares := sdk.NewDec(29)
_, newPool, tokens := validator.RemoveDelShares(pool, shares) _, newPool, tokens := validator.RemoveDelShares(pool, shares)
@ -232,8 +232,8 @@ func TestPossibleOverflow(t *testing.T) {
DelegatorShares: delShares, DelegatorShares: delShares,
} }
pool := Pool{ pool := Pool{
NotBondedTokens: sdk.NewInt(100), NotBondedTokens: sdk.NewInt(100),
BondedTokens: poolTokens, BondedTokens: poolTokens,
} }
tokens := int64(71) tokens := int64(71)
msg := fmt.Sprintf("validator %#v", validator) msg := fmt.Sprintf("validator %#v", validator)