Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: F1 storage efficiency improvements #3333

Merged
merged 21 commits into from
Jan 23, 2019
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ BREAKING CHANGES
* `Delegation` -> `Value` in `MsgCreateValidator` and `MsgDelegate`
* `MsgBeginUnbonding` -> `MsgUndelegate`
* [\#3315] Increase decimal precision to 18
* \#3333 - F1 storage efficiency improvements - automatic withdrawals when unbonded, historical reward reference counting
* [\#3328](https://github.com/cosmos/cosmos-sdk/issues/3328) [x/gov] Remove redundant action tag

* Tendermint
Expand Down
4 changes: 2 additions & 2 deletions cmd/gaia/app/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
}

// clear validator slash events
app.distrKeeper.DeleteValidatorSlashEvents(ctx)
app.distrKeeper.DeleteAllValidatorSlashEvents(ctx)

// clear validator historical rewards
app.distrKeeper.DeleteValidatorHistoricalRewards(ctx)
app.distrKeeper.DeleteAllValidatorHistoricalRewards(ctx)

// set context height to zero
height := ctx.BlockHeight()
Expand Down
23 changes: 19 additions & 4 deletions x/distribution/keeper/delegation.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
// initialize starting info for a new delegation
func (k Keeper) initializeDelegation(ctx sdk.Context, val sdk.ValAddress, del sdk.AccAddress) {
// period has already been incremented
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
period := k.GetValidatorCurrentRewards(ctx, val).Period
previousPeriod := k.GetValidatorCurrentRewards(ctx, val).Period - 1

validator := k.stakingKeeper.Validator(ctx, val)
delegation := k.stakingKeeper.Delegation(ctx, del, val)

// calculate delegation stake in tokens
// we don't store directly, so multiply delegation shares * (tokens per share)
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
Expand All @@ -29,7 +31,7 @@ func (k Keeper) calculateDelegationRewardsBetween(ctx sdk.Context, val sdk.Valid
// return staking * (ending - starting)
starting := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), startingPeriod)
ending := k.GetValidatorHistoricalRewards(ctx, val.GetOperator(), endingPeriod)
difference := ending.Minus(starting)
difference := ending.SeqValue.Minus(starting.SeqValue)
rewards = difference.MulDec(staking)
return
}
Expand All @@ -50,7 +52,7 @@ func (k Keeper) calculateDelegationRewards(ctx sdk.Context, val sdk.Validator, d
if endingHeight >= startingHeight {
k.IterateValidatorSlashEventsBetween(ctx, del.GetValidatorAddr(), startingHeight, endingHeight,
func(height uint64, event types.ValidatorSlashEvent) (stop bool) {
endingPeriod := event.ValidatorPeriod - 1
endingPeriod := event.ValidatorPeriod
rigelrozanski marked this conversation as resolved.
Show resolved Hide resolved
rewards = rewards.Plus(k.calculateDelegationRewardsBetween(ctx, val, startingPeriod, endingPeriod, stake))
stake = stake.Mul(sdk.OneDec().Sub(event.Fraction))
startingPeriod = endingPeriod
Expand All @@ -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 {

// 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
endingPeriod := k.incrementValidatorPeriod(ctx, val)
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
coins, remainder := rewards.TruncateDecimal()
outstanding := k.GetOutstandingRewards(ctx)
Expand All @@ -85,5 +97,8 @@ func (k Keeper) withdrawDelegationRewards(ctx sdk.Context, val sdk.Validator, de
return err
}

// remove delegator starting info
k.DeleteDelegatorStartingInfo(ctx, del.GetValidatorAddr(), del.GetDelegatorAddr())

return nil
}
21 changes: 21 additions & 0 deletions x/distribution/keeper/delegation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,15 @@ func TestCalculateRewardsBasic(t *testing.T) {
val := sk.Validator(ctx, 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
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
rewards := k.calculateDelegationRewards(ctx, val, del, endingPeriod)

Expand Down Expand Up @@ -276,9 +282,15 @@ func TestWithdrawDelegationRewardsBasic(t *testing.T) {
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)

// historical count should be 2 (initial + latest for delegation)
require.Equal(t, uint64(2), k.GetValidatorHistoricalRewardCount(ctx))

// withdraw rewards
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
require.Equal(t, sdk.Coins{{staking.DefaultBondDenom, sdk.NewInt(balance - bond + (initial / 2))}}, ak.GetAccount(ctx, sdk.AccAddress(valOpAddr1)).GetCoins())

Expand Down Expand Up @@ -447,10 +459,16 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
tokens := sdk.DecCoins{{staking.DefaultBondDenom, sdk.NewDec(initial)}}
k.AllocateTokensToValidator(ctx, val, tokens)

// historical count should be 2 (validator init, delegation init)
require.Equal(t, uint64(2), k.GetValidatorHistoricalRewardCount(ctx))

// second delegation
msg2 := staking.NewMsgDelegate(sdk.AccAddress(valOpAddr2), valOpAddr1, sdk.NewCoin(staking.DefaultBondDenom, sdk.NewInt(100)))
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
val = sk.Validator(ctx, valOpAddr1)
del2 := sk.Delegation(ctx, sdk.AccAddress(valOpAddr2), valOpAddr1)
Expand All @@ -467,6 +485,9 @@ func TestCalculateRewardsMultiDelegatorMultWithdraw(t *testing.T) {
// second delegator withdraws
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
k.WithdrawValidatorCommission(ctx, valOpAddr1)

Expand Down
38 changes: 34 additions & 4 deletions x/distribution/keeper/hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,41 @@ func (h Hooks) AfterValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) {
h.k.initializeValidator(ctx, val)
}
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) {
// 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))
rigelrozanski marked this conversation as resolved.
Show resolved Hide resolved

// 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) {
val := h.k.stakingKeeper.Validator(ctx, valAddr)
Expand All @@ -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) {
// 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) {
// create new delegation period record
Expand Down
10 changes: 10 additions & 0 deletions x/distribution/keeper/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ func GetDelegatorStartingInfoKey(v sdk.ValAddress, d sdk.AccAddress) []byte {
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
func GetValidatorHistoricalRewardsKey(v sdk.ValAddress, k uint64) []byte {
b := make([]byte, 8)
Expand All @@ -129,6 +134,11 @@ func GetValidatorAccumulatedCommissionKey(v sdk.ValAddress) []byte {
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
func GetValidatorSlashEventKey(v sdk.ValAddress, height uint64) []byte {
b := make([]byte, 8)
Expand Down
3 changes: 2 additions & 1 deletion x/distribution/keeper/querier.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ package keeper
import (
"fmt"

abci "github.com/tendermint/tendermint/abci/types"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
abci "github.com/tendermint/tendermint/abci/types"
)

// nolint
Expand Down
3 changes: 2 additions & 1 deletion x/distribution/keeper/querier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ import (

"github.com/stretchr/testify/require"

abci "github.com/tendermint/tendermint/abci/types"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/distribution/types"
"github.com/cosmos/cosmos-sdk/x/staking"
abci "github.com/tendermint/tendermint/abci/types"
)

const custom = "custom"
Expand Down
75 changes: 68 additions & 7 deletions x/distribution/keeper/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ func (k Keeper) SetDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr
store.Set(GetDelegatorWithdrawAddrKey(delAddr), withdrawAddr.Bytes())
}

// remove a delegator withdraw addr
func (k Keeper) RemoveDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) {
// delete a delegator withdraw addr
func (k Keeper) DeleteDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) {
store := ctx.KVStore(k.storeKey)
store.Delete(GetDelegatorWithdrawAddrKey(delAddr))
}
Expand Down Expand Up @@ -77,21 +77,33 @@ func (k Keeper) SetPreviousProposerConsAddr(ctx sdk.Context, consAddr sdk.ConsAd
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) {
store := ctx.KVStore(k.storeKey)
b := store.Get(GetDelegatorStartingInfoKey(val, del))
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &period)
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) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(period)
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
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)
Expand Down Expand Up @@ -137,8 +149,24 @@ func (k Keeper) IterateValidatorHistoricalRewards(ctx sdk.Context, handler func(
}
}

// delete historical rewards
func (k Keeper) DeleteValidatorHistoricalRewards(ctx sdk.Context) {
// delete a historical reward
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)
iter := sdk.KVStorePrefixIterator(store, ValidatorHistoricalRewardsPrefix)
defer iter.Close()
Expand All @@ -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
func (k Keeper) GetValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress) (rewards types.ValidatorCurrentRewards) {
store := ctx.KVStore(k.storeKey)
Expand All @@ -162,6 +201,12 @@ func (k Keeper) SetValidatorCurrentRewards(ctx sdk.Context, val sdk.ValAddress,
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
func (k Keeper) IterateValidatorCurrentRewards(ctx sdk.Context, handler func(val sdk.ValAddress, rewards types.ValidatorCurrentRewards) (stop bool)) {
store := ctx.KVStore(k.storeKey)
Expand Down Expand Up @@ -195,6 +240,12 @@ func (k Keeper) SetValidatorAccumulatedCommission(ctx sdk.Context, val sdk.ValAd
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
func (k Keeper) IterateValidatorAccumulatedCommissions(ctx sdk.Context, handler func(val sdk.ValAddress, commission types.ValidatorAccumulatedCommission) (stop bool)) {
store := ctx.KVStore(k.storeKey)
Expand Down Expand Up @@ -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
func (k Keeper) DeleteValidatorSlashEvents(ctx sdk.Context) {
func (k Keeper) DeleteAllValidatorSlashEvents(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorSlashEventPrefix)
defer iter.Close()
Expand Down
Loading