Skip to content

Commit

Permalink
Inflation bug fixes (#2982)
Browse files Browse the repository at this point in the history
* PENDING.md; swap BeginBlocker ordering
* Calculate inflation every block
* Update x/mint spec
* Reset distribution info bond height instead
  • Loading branch information
cwgoes authored and mircea-c committed Dec 5, 2018
1 parent a7b5596 commit 743fc83
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 72 deletions.
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@ BUG FIXES
* Gaia

* SDK
* \#2967 Change ordering of `mint.BeginBlocker` and `distr.BeginBlocker`, recalculate inflation each block

* Tendermint
8 changes: 4 additions & 4 deletions cmd/gaia/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,12 +189,12 @@ func MakeCodec() *codec.Codec {
// application updates every end block
func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {

// distribute rewards from previous block
distr.BeginBlocker(ctx, req, app.distrKeeper)

// mint new tokens for this new block
// mint new tokens for the previous block
mint.BeginBlocker(ctx, app.mintKeeper)

// distribute rewards for the previous block
distr.BeginBlocker(ctx, req, app.distrKeeper)

// slash anyone who double signed.
// NOTE: This should happen after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool,
Expand Down
46 changes: 17 additions & 29 deletions cmd/gaia/app/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,34 +56,13 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (
// prepare for fresh start at zero height
func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {

/* TODO XXX check some invariants */

height := ctx.BlockHeight()

valAccum := sdk.ZeroDec()
vdiIter := func(_ int64, vdi distr.ValidatorDistInfo) bool {
lastValPower := app.stakeKeeper.GetLastValidatorPower(ctx, vdi.OperatorAddr)
valAccum = valAccum.Add(vdi.GetValAccum(height, sdk.NewDecFromInt(lastValPower)))
return false
}
app.distrKeeper.IterateValidatorDistInfos(ctx, vdiIter)

lastTotalPower := sdk.NewDecFromInt(app.stakeKeeper.GetLastTotalPower(ctx))
totalAccum := app.distrKeeper.GetFeePool(ctx).GetTotalValAccum(height, lastTotalPower)

if !totalAccum.Equal(valAccum) {
panic(fmt.Errorf("validator accum invariance: \n\tfee pool totalAccum: %v"+
"\n\tvalidator accum \t%v\n", totalAccum.String(), valAccum.String()))
}

fmt.Printf("accum invariant ok!\n")

/* END TODO XXX */
/* Just to be safe, assert the invariants on current state. */
app.assertRuntimeInvariantsOnContext(ctx)

/* Handle fee distribution state. */

// withdraw all delegator & validator rewards
vdiIter = func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) {
vdiIter := func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) {
err := app.distrKeeper.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr)
if err != nil {
panic(err)
Expand All @@ -102,10 +81,19 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
}
app.distrKeeper.IterateDelegationDistInfos(ctx, ddiIter)

// delete all distribution infos
// these will be recreated in InitGenesis
app.distrKeeper.RemoveValidatorDistInfos(ctx)
app.distrKeeper.RemoveDelegationDistInfos(ctx)
app.assertRuntimeInvariantsOnContext(ctx)

// set distribution info withdrawal heights to 0
app.distrKeeper.IterateDelegationDistInfos(ctx, func(_ int64, delInfo distr.DelegationDistInfo) (stop bool) {
delInfo.DelPoolWithdrawalHeight = 0
app.distrKeeper.SetDelegationDistInfo(ctx, delInfo)
return false
})
app.distrKeeper.IterateValidatorDistInfos(ctx, func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) {
valInfo.FeePoolWithdrawalHeight = 0
app.distrKeeper.SetValidatorDistInfo(ctx, valInfo)
return false
})

// assert that the fee pool is empty
feePool := app.distrKeeper.GetFeePool(ctx)
Expand All @@ -119,7 +107,7 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) {
}

// reset fee pool height, save fee pool
feePool.TotalValAccum.UpdateHeight = 0
feePool.TotalValAccum = distr.NewTotalAccum(0)
app.distrKeeper.SetFeePool(ctx, feePool)

/* Handle stake state. */
Expand Down
9 changes: 7 additions & 2 deletions cmd/gaia/app/invariants.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation"
distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation"
"github.com/cosmos/cosmos-sdk/x/mock/simulation"
Expand All @@ -22,9 +23,13 @@ func (app *GaiaApp) runtimeInvariants() []simulation.Invariant {
}

func (app *GaiaApp) assertRuntimeInvariants() {
invariants := app.runtimeInvariants()
start := time.Now()
ctx := app.NewContext(false, abci.Header{Height: app.LastBlockHeight() + 1})
app.assertRuntimeInvariantsOnContext(ctx)
}

func (app *GaiaApp) assertRuntimeInvariantsOnContext(ctx sdk.Context) {
start := time.Now()
invariants := app.runtimeInvariants()
for _, inv := range invariants {
if err := inv(ctx); err != nil {
panic(fmt.Errorf("invariant broken: %s", err))
Expand Down
10 changes: 5 additions & 5 deletions docs/spec/mint/begin_block.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# Begin-Block

Inflation occurs at the beginning of each block, however minting parameters
are only calculated once per hour.
Minting parameters are recalculated and inflation
paid at the beginning of each block.

## NextInflationRate

The target annual inflation rate is recalculated at the first block of each new
hour. The inflation is also subject to a rate change (positive or negative)
The target annual inflation rate is recalculated each block.
The inflation is also subject to a rate change (positive or negative)
depending on the distance from the desired ratio (67%). The maximum rate change
possible is defined to be 13% per year, however the annual inflation is capped
as between 7% and 20%.

```
NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) {
inflationRateChangePerYear = (1 - bondedRatio/params.GoalBonded) * params.InflationRateChange
inflationRateChange = inflationRateChangePerYear/hrsPerYr
inflationRateChange = inflationRateChangePerYear/blocksPerYr
// increase the new annual inflation for this next cycle
inflation += inflationRateChange
Expand Down
2 changes: 0 additions & 2 deletions docs/spec/mint/state.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ The minter is a space for holding current inflation information.

```golang
type Minter struct {
LastUpdate time.Time // time which the last update was made to the minter
Inflation sdk.Dec // current annual inflation rate
AnnualProvisions sdk.Dec // current annual exptected provisions
}
Expand All @@ -30,4 +29,3 @@ type Params struct {
BlocksPerYear uint64 // expected blocks per year
}
```

2 changes: 2 additions & 0 deletions x/distribution/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ var (
NewMsgWithdrawValidatorRewardsAll = types.NewMsgWithdrawValidatorRewardsAll

NewDecCoins = types.NewDecCoins

NewTotalAccum = types.NewTotalAccum
)

const (
Expand Down
10 changes: 10 additions & 0 deletions x/distribution/types/dec_coin.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,13 @@ func (coins DecCoins) HasNegative() bool {
}
return false
}

// return whether all coins are zero
func (coins DecCoins) IsZero() bool {
for _, coin := range coins {
if !coin.Amount.IsZero() {
return false
}
}
return true
}
21 changes: 8 additions & 13 deletions x/mint/abci_app.go
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
package mint

import (
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// Inflate every block, update inflation parameters once per hour
func BeginBlocker(ctx sdk.Context, k Keeper) {

blockTime := ctx.BlockHeader().Time
// fetch stored minter & params
minter := k.GetMinter(ctx)
params := k.GetParams(ctx)

mintedCoin := minter.BlockProvision(params)
k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin})
k.sk.InflateSupply(ctx, sdk.NewDecFromInt(mintedCoin.Amount))

if blockTime.Sub(minter.LastUpdate) < time.Hour {
return
}

// adjust the inflation, hourly-provision rate every hour
// recalculate inflation rate
totalSupply := k.sk.TotalPower(ctx)
bondedRatio := k.sk.BondedRatio(ctx)
minter.Inflation = minter.NextInflationRate(params, bondedRatio)
minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalSupply)
minter.LastUpdate = blockTime
k.SetMinter(ctx, minter)

// mint coins, add to collected fees, update supply
mintedCoin := minter.BlockProvision(params)
k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin})
k.sk.InflateSupply(ctx, sdk.NewDecFromInt(mintedCoin.Amount))

}
15 changes: 4 additions & 11 deletions x/mint/minter.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,20 @@ package mint

import (
"fmt"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// Minter represents the minting state
type Minter struct {
LastUpdate time.Time `json:"last_update"` // time which the last update was made to the minter
Inflation sdk.Dec `json:"inflation"` // current annual inflation rate
AnnualProvisions sdk.Dec `json:"annual_provisions"` // current annual expected provisions
Inflation sdk.Dec `json:"inflation"` // current annual inflation rate
AnnualProvisions sdk.Dec `json:"annual_provisions"` // current annual expected provisions
}

// Create a new minter object
func NewMinter(lastUpdate time.Time, inflation,
annualProvisions sdk.Dec) Minter {
func NewMinter(inflation, annualProvisions sdk.Dec) Minter {

return Minter{
LastUpdate: lastUpdate,
Inflation: inflation,
AnnualProvisions: annualProvisions,
}
Expand All @@ -28,7 +24,6 @@ func NewMinter(lastUpdate time.Time, inflation,
// minter object for a new chain
func InitialMinter(inflation sdk.Dec) Minter {
return NewMinter(
time.Unix(0, 0),
inflation,
sdk.NewDec(0),
)
Expand All @@ -50,8 +45,6 @@ func validateMinter(minter Minter) error {
return nil
}

var hrsPerYr = sdk.NewDec(8766) // as defined by a julian year of 365.25 days

// get the new inflation rate for the next hour
func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) (
inflation sdk.Dec) {
Expand All @@ -66,7 +59,7 @@ func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) (
inflationRateChangePerYear := sdk.OneDec().
Sub(bondedRatio.Quo(params.GoalBonded)).
Mul(params.InflationRateChange)
inflationRateChange := inflationRateChangePerYear.Quo(hrsPerYr)
inflationRateChange := inflationRateChangePerYear.Quo(sdk.NewDec(int64(params.BlocksPerYear)))

// increase the new annual inflation for this next cycle
inflation = m.Inflation.Add(inflationRateChange)
Expand Down
41 changes: 35 additions & 6 deletions x/mint/minter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
func TestNextInflation(t *testing.T) {
minter := DefaultInitialMinter()
params := DefaultParams()
blocksPerYr := sdk.NewDec(int64(params.BlocksPerYear))

// Governing Mechanism:
// inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange
Expand All @@ -20,24 +21,24 @@ func TestNextInflation(t *testing.T) {
bondedRatio, setInflation, expChange sdk.Dec
}{
// with 0% bonded atom supply the inflation should increase by InflationRateChange
{sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), params.InflationRateChange.Quo(hrsPerYr)},
{sdk.ZeroDec(), sdk.NewDecWithPrec(7, 2), params.InflationRateChange.Quo(blocksPerYr)},

// 100% bonded, starting at 20% inflation and being reduced
// (1 - (1/0.67))*(0.13/8667)
{sdk.OneDec(), sdk.NewDecWithPrec(20, 2),
sdk.OneDec().Sub(sdk.OneDec().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
sdk.OneDec().Sub(sdk.OneDec().Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(blocksPerYr)},

// 50% bonded, starting at 10% inflation and being increased
{sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(10, 2),
sdk.OneDec().Sub(sdk.NewDecWithPrec(5, 1).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(hrsPerYr)},
sdk.OneDec().Sub(sdk.NewDecWithPrec(5, 1).Quo(params.GoalBonded)).Mul(params.InflationRateChange).Quo(blocksPerYr)},

// test 7% minimum stop (testing with 100% bonded)
{sdk.OneDec(), sdk.NewDecWithPrec(7, 2), sdk.ZeroDec()},
{sdk.OneDec(), sdk.NewDecWithPrec(70001, 6), sdk.NewDecWithPrec(-1, 6)},
{sdk.OneDec(), sdk.NewDecWithPrec(700000001, 10), sdk.NewDecWithPrec(-1, 10)},

// test 20% maximum stop (testing with 0% bonded)
{sdk.ZeroDec(), sdk.NewDecWithPrec(20, 2), sdk.ZeroDec()},
{sdk.ZeroDec(), sdk.NewDecWithPrec(199999, 6), sdk.NewDecWithPrec(1, 6)},
{sdk.ZeroDec(), sdk.NewDecWithPrec(1999999999, 10), sdk.NewDecWithPrec(1, 10)},

// perfect balance shouldn't change inflation
{sdk.NewDecWithPrec(67, 2), sdk.NewDecWithPrec(15, 2), sdk.ZeroDec()},
Expand Down Expand Up @@ -95,8 +96,36 @@ func BenchmarkBlockProvision(b *testing.B) {
r1 := rand.New(s1)
minter.AnnualProvisions = sdk.NewDec(r1.Int63n(1000000))

// run the Fib function b.N times
// run the BlockProvision function b.N times
for n := 0; n < b.N; n++ {
minter.BlockProvision(params)
}
}

// Next inflation benchmarking
// BenchmarkNextInflation-4 1000000 1828 ns/op
func BenchmarkNextInflation(b *testing.B) {
minter := InitialMinter(sdk.NewDecWithPrec(1, 1))
params := DefaultParams()
bondedRatio := sdk.NewDecWithPrec(1, 1)

// run the NextInflationRate function b.N times
for n := 0; n < b.N; n++ {
minter.NextInflationRate(params, bondedRatio)
}

}

// Next annual provisions benchmarking
// BenchmarkNextAnnualProvisions-4 5000000 251 ns/op
func BenchmarkNextAnnualProvisions(b *testing.B) {
minter := InitialMinter(sdk.NewDecWithPrec(1, 1))
params := DefaultParams()
totalSupply := sdk.NewDec(100000000000000)

// run the NextAnnualProvisions function b.N times
for n := 0; n < b.N; n++ {
minter.NextAnnualProvisions(params, totalSupply)
}

}

0 comments on commit 743fc83

Please sign in to comment.