Skip to content

Commit

Permalink
Merge PR #2597: Add distribution accum invariants
Browse files Browse the repository at this point in the history
  • Loading branch information
rigelrozanski authored and cwgoes committed Oct 26, 2018
1 parent 0f1fb17 commit d71f38b
Show file tree
Hide file tree
Showing 24 changed files with 491 additions and 179 deletions.
3 changes: 3 additions & 0 deletions PENDING.md
Expand Up @@ -35,6 +35,7 @@ IMPROVEMENTS
* Gaia

* SDK
- #2573 [x/distribution] add accum invariance

* Tendermint

Expand All @@ -48,5 +49,7 @@ BUG FIXES
* Gaia

* SDK
- #2573 [x/distribution] accum invariance bugfix
- #2573 [x/slashing] unbonding-delegation slashing invariance bugfix

* Tendermint
4 changes: 3 additions & 1 deletion cmd/gaia/app/sim_test.go
Expand Up @@ -127,7 +127,9 @@ func invariants(app *GaiaApp) []simulation.Invariant {
return []simulation.Invariant{
banksim.NonnegativeBalanceInvariant(app.accountKeeper),
govsim.AllInvariants(),
stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper),
distrsim.AllInvariants(app.distrKeeper, app.stakeKeeper),
stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper,
app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper),
slashingsim.AllInvariants(),
}
}
Expand Down
4 changes: 2 additions & 2 deletions x/bank/simulation/invariants.go
Expand Up @@ -14,7 +14,7 @@ import (

// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances
func NonnegativeBalanceInvariant(mapper auth.AccountKeeper) simulation.Invariant {
return func(app *baseapp.BaseApp) error {
return func(app *baseapp.BaseApp, _ abci.Header) error {
ctx := app.NewContext(false, abci.Header{})
accts := mock.GetAllAccounts(mapper, ctx)
for _, acc := range accts {
Expand All @@ -32,7 +32,7 @@ func NonnegativeBalanceInvariant(mapper auth.AccountKeeper) simulation.Invariant
// TotalCoinsInvariant checks that the sum of the coins across all accounts
// is what is expected
func TotalCoinsInvariant(mapper auth.AccountKeeper, totalSupplyFn func() sdk.Coins) simulation.Invariant {
return func(app *baseapp.BaseApp) error {
return func(app *baseapp.BaseApp, _ abci.Header) error {
ctx := app.NewContext(false, abci.Header{})
totalCoins := sdk.Coins{}

Expand Down
7 changes: 5 additions & 2 deletions x/distribution/alias.go
Expand Up @@ -23,6 +23,11 @@ type (
MsgWithdrawValidatorRewardsAll = types.MsgWithdrawValidatorRewardsAll

GenesisState = types.GenesisState

// expected keepers
StakeKeeper = types.StakeKeeper
BankKeeper = types.BankKeeper
FeeCollectionKeeper = types.FeeCollectionKeeper
)

var (
Expand Down Expand Up @@ -62,9 +67,7 @@ var (
ErrNilDelegatorAddr = types.ErrNilDelegatorAddr
ErrNilWithdrawAddr = types.ErrNilWithdrawAddr
ErrNilValidatorAddr = types.ErrNilValidatorAddr
)

var (
ActionModifyWithdrawAddress = tags.ActionModifyWithdrawAddress
ActionWithdrawDelegatorRewardsAll = tags.ActionWithdrawDelegatorRewardsAll
ActionWithdrawDelegatorReward = tags.ActionWithdrawDelegatorReward
Expand Down
142 changes: 95 additions & 47 deletions x/distribution/keeper/delegation.go
Expand Up @@ -69,78 +69,110 @@ func (k Keeper) RemoveDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAd

//___________________________________________________________________________________________

// 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,
valAddr sdk.ValAddress) sdk.Error {
// return all rewards for a delegation
func (k Keeper) withdrawDelegationReward(ctx sdk.Context,
delAddr sdk.AccAddress, valAddr sdk.ValAddress) (types.FeePool,
types.ValidatorDistInfo, types.DelegationDistInfo, types.DecCoins) {

if !k.HasDelegationDistInfo(ctx, delegatorAddr, valAddr) {
return types.ErrNoDelegationDistInfo(k.codespace)
}
wc := k.GetWithdrawContext(ctx, valAddr)
valInfo := k.GetValidatorDistInfo(ctx, valAddr)
delInfo := k.GetDelegationDistInfo(ctx, delAddr, valAddr)
validator := k.stakeKeeper.Validator(ctx, valAddr)
delegation := k.stakeKeeper.Delegation(ctx, delAddr, valAddr)

delInfo, valInfo, feePool, withdraw := delInfo.WithdrawRewards(wc, valInfo,
validator.GetDelegatorShares(), delegation.GetShares())

return feePool, valInfo, delInfo, withdraw
}

// get all rewards for all delegations of a delegator
func (k Keeper) currentDelegationReward(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) types.DecCoins {

wc := k.GetWithdrawContext(ctx, valAddr)

// TODO: Reconcile with duplicate code in getDelegatorRewardsAll.
height := ctx.BlockHeight()
lastTotalPower := sdk.NewDecFromInt(k.stakeKeeper.GetLastTotalPower(ctx))
lastValPower := sdk.NewDecFromInt(k.stakeKeeper.GetLastValidatorPower(ctx, valAddr))
feePool := k.GetFeePool(ctx)
delInfo := k.GetDelegationDistInfo(ctx, delegatorAddr, valAddr)
valInfo := k.GetValidatorDistInfo(ctx, valAddr)
delInfo := k.GetDelegationDistInfo(ctx, delAddr, valAddr)
validator := k.stakeKeeper.Validator(ctx, valAddr)
delegation := k.stakeKeeper.Delegation(ctx, delegatorAddr, valAddr)
delegation := k.stakeKeeper.Delegation(ctx, delAddr, valAddr)

delInfo, valInfo, feePool, withdraw := delInfo.WithdrawRewards(feePool, valInfo, height, lastTotalPower,
lastValPower, validator.GetDelegatorShares(), delegation.GetShares(), validator.GetCommission())
estimation := delInfo.CurrentRewards(wc, valInfo,
validator.GetDelegatorShares(), delegation.GetShares())

k.SetValidatorDistInfo(ctx, valInfo)
k.SetDelegationDistInfo(ctx, delInfo)
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delegatorAddr)
coinsToAdd, change := withdraw.TruncateDecimal()
return estimation
}

//___________________________________________________________________________________________

// withdraw all rewards for a single delegation
// NOTE: This gets called "onDelegationSharesModified",
// meaning any changes to bonded coins
func (k Keeper) WithdrawToDelegator(ctx sdk.Context, feePool types.FeePool,
delAddr sdk.AccAddress, amount types.DecCoins) {

withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delAddr)
coinsToAdd, change := amount.TruncateDecimal()
feePool.CommunityPool = feePool.CommunityPool.Plus(change)
k.SetFeePool(ctx, feePool)
_, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coinsToAdd)
if err != nil {
panic(err)
}
return nil
}

//___________________________________________________________________________________________

// return all rewards for all delegations of a delegator
func (k Keeper) WithdrawDelegationRewardsAll(ctx sdk.Context, delegatorAddr sdk.AccAddress) {
height := ctx.BlockHeight()
withdraw := k.getDelegatorRewardsAll(ctx, delegatorAddr, height)
feePool := k.GetFeePool(ctx)
withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delegatorAddr)
coinsToAdd, change := withdraw.TruncateDecimal()
feePool.CommunityPool = feePool.CommunityPool.Plus(change)
k.SetFeePool(ctx, feePool)
_, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, coinsToAdd)
if err != nil {
panic(err)
// withdraw all rewards for a single delegation
// NOTE: This gets called "onDelegationSharesModified",
// meaning any changes to bonded coins
func (k Keeper) WithdrawDelegationReward(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) sdk.Error {

if !k.HasDelegationDistInfo(ctx, delAddr, valAddr) {
return types.ErrNoDelegationDistInfo(k.codespace)
}

feePool, valInfo, delInfo, withdraw :=
k.withdrawDelegationReward(ctx, delAddr, valAddr)

k.SetValidatorDistInfo(ctx, valInfo)
k.SetDelegationDistInfo(ctx, delInfo)
k.WithdrawToDelegator(ctx, feePool, delAddr, withdraw)
return nil
}

// current rewards for a single delegation
func (k Keeper) CurrentDelegationReward(ctx sdk.Context, delAddr sdk.AccAddress,
valAddr sdk.ValAddress) (sdk.Coins, sdk.Error) {

if !k.HasDelegationDistInfo(ctx, delAddr, valAddr) {
return sdk.Coins{}, types.ErrNoDelegationDistInfo(k.codespace)
}
estCoins := k.currentDelegationReward(ctx, delAddr, valAddr)
trucate, _ := estCoins.TruncateDecimal()
return trucate, nil
}

//___________________________________________________________________________________________

// return all rewards for all delegations of a delegator
func (k Keeper) getDelegatorRewardsAll(ctx sdk.Context, delAddr sdk.AccAddress, height int64) types.DecCoins {
func (k Keeper) WithdrawDelegationRewardsAll(ctx sdk.Context, delAddr sdk.AccAddress) {
withdraw := k.withdrawDelegationRewardsAll(ctx, delAddr)
feePool := k.GetFeePool(ctx)
k.WithdrawToDelegator(ctx, feePool, delAddr, withdraw)
}

withdraw := types.DecCoins{}
lastTotalPower := sdk.NewDecFromInt(k.stakeKeeper.GetLastTotalPower(ctx))
func (k Keeper) withdrawDelegationRewardsAll(ctx sdk.Context,
delAddr sdk.AccAddress) types.DecCoins {

// iterate over all the delegations
// TODO: Reconcile with duplicate code in WithdrawDelegationReward.
withdraw := types.DecCoins{}
operationAtDelegation := func(_ int64, del sdk.Delegation) (stop bool) {
feePool := k.GetFeePool(ctx)

valAddr := del.GetValidatorAddr()
lastValPower := sdk.NewDecFromInt(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, lastTotalPower,
lastValPower, validator.GetDelegatorShares(), delegation.GetShares(), validator.GetCommission())
feePool, valInfo, delInfo, diWithdraw :=
k.withdrawDelegationReward(ctx, delAddr, valAddr)
withdraw = withdraw.Plus(diWithdraw)
k.SetFeePool(ctx, feePool)
k.SetValidatorDistInfo(ctx, valInfo)
Expand All @@ -150,3 +182,19 @@ func (k Keeper) getDelegatorRewardsAll(ctx sdk.Context, delAddr sdk.AccAddress,
k.stakeKeeper.IterateDelegations(ctx, delAddr, operationAtDelegation)
return withdraw
}

// get all rewards for all delegations of a delegator
func (k Keeper) CurrentDelegationRewardsAll(ctx sdk.Context,
delAddr sdk.AccAddress) types.DecCoins {

// iterate over all the delegations
total := types.DecCoins{}
operationAtDelegation := func(_ int64, del sdk.Delegation) (stop bool) {
valAddr := del.GetValidatorAddr()
est := k.currentDelegationReward(ctx, delAddr, valAddr)
total = total.Plus(est)
return false
}
k.stakeKeeper.IterateDelegations(ctx, delAddr, operationAtDelegation)
return total
}
11 changes: 10 additions & 1 deletion x/distribution/keeper/hooks.go
Expand Up @@ -29,6 +29,15 @@ func (k Keeper) onValidatorModified(ctx sdk.Context, valAddr sdk.ValAddress) {
}
}

// Withdraw all validator rewards
func (k Keeper) onValidatorBonded(ctx sdk.Context, valAddr sdk.ValAddress) {
lastPower := k.stakeKeeper.GetLastValidatorPower(ctx, valAddr)
if !lastPower.Equal(sdk.ZeroInt()) {
panic("expected last power to be 0 for validator entering bonded state")
}
k.onValidatorModified(ctx, valAddr)
}

// XXX Consider removing this after debugging.
func (k Keeper) onValidatorPowerDidChange(ctx sdk.Context, valAddr sdk.ValAddress) {
vi := k.GetValidatorDistInfo(ctx, valAddr)
Expand Down Expand Up @@ -109,7 +118,7 @@ func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, _ sdk.ConsAddress, val
h.k.onValidatorModified(ctx, valAddr)
}
func (h Hooks) OnValidatorBonded(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) {
h.k.onValidatorModified(ctx, valAddr)
h.k.onValidatorBonded(ctx, valAddr)
}
func (h Hooks) OnValidatorPowerDidChange(ctx sdk.Context, _ sdk.ConsAddress, valAddr sdk.ValAddress) {
h.k.onValidatorPowerDidChange(ctx, valAddr)
Expand Down
28 changes: 28 additions & 0 deletions x/distribution/keeper/keeper.go
Expand Up @@ -55,6 +55,17 @@ func (k Keeper) SetFeePool(ctx sdk.Context, feePool types.FeePool) {
store.Set(FeePoolKey, b)
}

// get the total validator accum for the ctx height
// in the fee pool
func (k Keeper) GetFeePoolValAccum(ctx sdk.Context) sdk.Dec {

// withdraw self-delegation
height := ctx.BlockHeight()
totalPower := sdk.NewDecFromInt(k.stakeKeeper.GetLastTotalPower(ctx))
fp := k.GetFeePool(ctx)
return fp.GetTotalValAccum(height, totalPower)
}

//______________________________________________________________________

// set the proposer public key for this block
Expand All @@ -77,6 +88,23 @@ func (k Keeper) SetPreviousProposerConsAddr(ctx sdk.Context, consAddr sdk.ConsAd
store.Set(ProposerKey, b)
}

//______________________________________________________________________

// get context required for withdraw operations
func (k Keeper) GetWithdrawContext(ctx sdk.Context,
valOperatorAddr sdk.ValAddress) types.WithdrawContext {

feePool := k.GetFeePool(ctx)
height := ctx.BlockHeight()
validator := k.stakeKeeper.Validator(ctx, valOperatorAddr)
lastValPower := k.stakeKeeper.GetLastValidatorPower(ctx, valOperatorAddr)
lastTotalPower := sdk.NewDecFromInt(k.stakeKeeper.GetLastTotalPower(ctx))

return types.NewWithdrawContext(
feePool, height, lastTotalPower, sdk.NewDecFromInt(lastValPower),
validator.GetCommission())
}

//______________________________________________________________________
// PARAM STORE

Expand Down
21 changes: 21 additions & 0 deletions x/distribution/keeper/test_common.go
Expand Up @@ -158,3 +158,24 @@ func (fck DummyFeeCollectionKeeper) SetCollectedFees(in sdk.Coins) {
func (fck DummyFeeCollectionKeeper) ClearCollectedFees(_ sdk.Context) {
heldFees = sdk.Coins{}
}

//__________________________________________________________________________________
// used in simulation

// iterate over all the validator distribution infos (inefficient, just used to check invariants)
func (k Keeper) IterateValidatorDistInfos(ctx sdk.Context,
fn func(index int64, distInfo types.ValidatorDistInfo) (stop bool)) {

store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, ValidatorDistInfoKey)
defer iter.Close()
index := int64(0)
for ; iter.Valid(); iter.Next() {
var vdi types.ValidatorDistInfo
k.cdc.MustUnmarshalBinary(iter.Value(), &vdi)
if fn(index, vdi) {
return
}
index++
}
}

0 comments on commit d71f38b

Please sign in to comment.