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: Inflation bug fixes #2982

Merged
merged 13 commits into from
Dec 4, 2018
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,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.assertRuntimeInvariantsWith(ctx)
cwgoes marked this conversation as resolved.
Show resolved Hide resolved

/* 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.assertRuntimeInvariantsWith(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.TotalAccum{0, sdk.ZeroDec()}
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
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.assertRuntimeInvariantsWith(ctx)
}

func (app *GaiaApp) assertRuntimeInvariantsWith(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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this warrant any changes to the distribution spec (e.g. Affects on Staking)?

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
}
```

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))
cwgoes marked this conversation as resolved.
Show resolved Hide resolved

}
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)))
cwgoes marked this conversation as resolved.
Show resolved Hide resolved

// 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
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
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 5000000251 ns/op
cwgoes marked this conversation as resolved.
Show resolved Hide resolved
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)
}

}