From 1c08b354030e3cec25dab34f6510650550bc3fb3 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 14 Nov 2018 16:00:15 +0100 Subject: [PATCH 01/60] Initial pass --- PENDING.md | 1 + cmd/gaia/app/app.go | 2 ++ cmd/gaia/app/invariants.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 cmd/gaia/app/invariants.go diff --git a/PENDING.md b/PENDING.md index 5480b778017b..b2c315675654 100644 --- a/PENDING.md +++ b/PENDING.md @@ -31,6 +31,7 @@ FEATURES * Gaia * [x/gov] [#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Implemented querier for getting governance parameters. + * [app] \#2663 - Runtime-assertable invariants * SDK * [simulator] \#2682 MsgEditValidator now looks at the validator's max rate, thus it now succeeds a significant portion of the time diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 918bfc67d114..8642da3eeead 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -210,6 +210,8 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R tags := gov.EndBlocker(ctx, app.govKeeper) validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) + app.assertRuntimeInvariants() + return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, Tags: tags, diff --git a/cmd/gaia/app/invariants.go b/cmd/gaia/app/invariants.go new file mode 100644 index 000000000000..774212e9bd38 --- /dev/null +++ b/cmd/gaia/app/invariants.go @@ -0,0 +1,32 @@ +package app + +import ( + "fmt" + + banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" + distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" + govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" + "github.com/cosmos/cosmos-sdk/x/mock/simulation" + slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" + stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" +) + +func (app *GaiaApp) runtimeInvariants() []simulation.Invariant { + return []simulation.Invariant{ + banksim.NonnegativeBalanceInvariant(app.accountKeeper), + govsim.AllInvariants(), + distrsim.AllInvariants(app.distrKeeper, app.stakeKeeper), + stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, + app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper), + slashingsim.AllInvariants(), + } +} + +func (app *GaiaApp) assertRuntimeInvariants() { + invariants := app.runtimeInvariants() + for _, inv := range invariants { + if err := inv(app.BaseApp); err != nil { + panic(fmt.Errorf("invariant broken: %s", err)) + } + } +} From 492e9f0313d1b3abb338e8750b97bed95932ba96 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 14 Nov 2018 16:43:05 +0100 Subject: [PATCH 02/60] Minor cleanup --- cmd/gaia/app/invariants.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/gaia/app/invariants.go b/cmd/gaia/app/invariants.go index 774212e9bd38..67ec1c71414a 100644 --- a/cmd/gaia/app/invariants.go +++ b/cmd/gaia/app/invariants.go @@ -2,31 +2,33 @@ package app import ( "fmt" + "time" banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" - govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation" - slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" ) func (app *GaiaApp) runtimeInvariants() []simulation.Invariant { return []simulation.Invariant{ banksim.NonnegativeBalanceInvariant(app.accountKeeper), - govsim.AllInvariants(), - distrsim.AllInvariants(app.distrKeeper, app.stakeKeeper), - stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, + distrsim.ValAccumInvariants(app.distrKeeper, app.stakeKeeper), + stakesim.SupplyInvariants(app.bankKeeper, app.stakeKeeper, app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper), - slashingsim.AllInvariants(), + stakesim.PositivePowerInvariant(app.stakeKeeper), } } func (app *GaiaApp) assertRuntimeInvariants() { invariants := app.runtimeInvariants() + start := time.Now() for _, inv := range invariants { if err := inv(app.BaseApp); err != nil { panic(fmt.Errorf("invariant broken: %s", err)) } } + end := time.Now() + diff := end.Sub(start) + app.BaseApp.Logger.With("module", "invariants").Info("Asserted all invariants", "duration", diff) } From 93242a2b76fe628ac8496b0dd6b512d33e79f14c Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 14 Nov 2018 22:50:07 -0500 Subject: [PATCH 03/60] untested core minting updates --- x/mint/abci_app.go | 15 +++++++++------ x/mint/minter.go | 26 +++++++++++++++++++------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go index 73491f8081e1..5ea2c3886803 100644 --- a/x/mint/abci_app.go +++ b/x/mint/abci_app.go @@ -11,16 +11,19 @@ func BeginBlocker(ctx sdk.Context, k Keeper) { blockTime := ctx.BlockHeader().Time minter := k.GetMinter(ctx) - if blockTime.Sub(minter.InflationLastTime) < time.Hour { // only mint on the hour! - return - } - params := k.GetParams(ctx) totalSupply := k.sk.TotalPower(ctx) bondedRatio := k.sk.BondedRatio(ctx) - minter.InflationLastTime = blockTime - minter, mintedCoin := minter.ProcessProvisions(params, totalSupply, bondedRatio) + minter, mintedCoin := minter.ProcessProvisions(params, blockTime) k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin}) k.sk.InflateSupply(ctx, sdk.NewDecFromInt(mintedCoin.Amount)) + + // adjust the inflation, hourly-provision rate every hour + if blockTime.Sub(minter.LastInflationChange) >= time.Hour { + minter.Inflation = minter.NextInflation(params, bondedRatio) + minter.LastInflationChange = blockTime + minter.HourlyProvisions = minter.NextHourlyProvisions(params, totalSupply) + } + k.SetMinter(ctx, minter) } diff --git a/x/mint/minter.go b/x/mint/minter.go index 135675887bf0..d114a84e985c 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -9,8 +9,10 @@ import ( // current inflation state type Minter struct { - InflationLastTime time.Time `json:"inflation_last_time"` // block time which the last inflation was processed - Inflation sdk.Dec `json:"inflation"` // current annual inflation rate + LastInflation time.Time `json:"last_inflation"` // time of the last inflation + LastInflationChange time.Time `json:"last_inflation_change"` // time which the last inflation rate change + Inflation sdk.Dec `json:"inflation"` // current annual inflation rate + HourlyProvisions sdk.Int `json:"hourly_provisions"` // current hourly provisions rate } // minter object for a new minter @@ -34,17 +36,21 @@ func validateMinter(minter Minter) error { var hrsPerYr = sdk.NewDec(8766) // as defined by a julian year of 365.25 days // process provisions for an hour period -func (m Minter) ProcessProvisions(params Params, totalSupply, bondedRatio sdk.Dec) ( +func (m Minter) ProcessProvisions(params Params, blockTime time.Time) ( minter Minter, provisions sdk.Coin) { - m.Inflation = m.NextInflation(params, bondedRatio) - provisionsDec := m.Inflation.Mul(totalSupply).Quo(hrsPerYr) - provisions = sdk.NewCoin(params.MintDenom, provisionsDec.TruncateInt()) + dur := m.LastInflation.Sub(blockTime).Nanoseconds() + portionOfHour := dur / time.Hour.Nanoseconds() + + provisionsAmt := m.HourlyProvisions.MulRaw(portionOfHour) + provisions = sdk.NewCoin(params.MintDenom, provisionsAmt) + + minter.LastInflation = blockTime return m, provisions } -// get the next inflation rate for the hour +// get the new inflation rate for the next hour func (m Minter) NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { // The target annual inflation rate is recalculated for each previsions cycle. The @@ -70,3 +76,9 @@ func (m Minter) NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk return inflation } + +// get the new hourly inflation provisions rate +func (m Minter) NextHourlyProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Int) { + provisionsDec := m.Inflation.Mul(totalSupply).Quo(hrsPerYr) + return provisionsDec.TruncateInt() +} From d9a53e531ab737018610a467f93fde9ee0f705e5 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 14 Nov 2018 22:57:05 -0500 Subject: [PATCH 04/60] fix initialMinter --- x/mint/minter.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/x/mint/minter.go b/x/mint/minter.go index d114a84e985c..0ac532267607 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -18,8 +18,10 @@ type Minter struct { // minter object for a new minter func InitialMinter() Minter { return Minter{ - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(13, 2), + LastInflation: time.Unix(0, 0), + LastInflationChange: time.Unix(0, 0), + Inflation: sdk.NewDecWithPrec(13, 2), + HourlyProvisions: sdk.NewInt(0), } } @@ -36,6 +38,10 @@ func validateMinter(minter Minter) error { var hrsPerYr = sdk.NewDec(8766) // as defined by a julian year of 365.25 days // process provisions for an hour period +// NOTE if ProcessProvisions is called for the first time +// from an InitialMinter, ProcessProvisions will +// effectively only set the blocktime as the default +// HourlyProvisions is 0. func (m Minter) ProcessProvisions(params Params, blockTime time.Time) ( minter Minter, provisions sdk.Coin) { From dd4bc57db7d63cb94a23552eb85f45e4b0dfe868 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 13:45:36 +0100 Subject: [PATCH 05/60] Update PENDING.md --- PENDING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/PENDING.md b/PENDING.md index af4a1193b040..f5ee2bb39c26 100644 --- a/PENDING.md +++ b/PENDING.md @@ -33,6 +33,7 @@ FEATURES * [x/gov] [#2479](https://github.com/cosmos/cosmos-sdk/issues/2479) Implemented querier for getting governance parameters. * [app] \#2791 Support export at a specific height, with `gaiad export --height=HEIGHT`. + * [app] \#2812 Support export alterations to prepare for restarting at zero-height * SDK * [simulator] \#2682 MsgEditValidator now looks at the validator's max rate, thus it now succeeds a significant portion of the time From decf1631f71d2941b0edebd66018a8fa750d485c Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 13:54:43 +0100 Subject: [PATCH 06/60] Add flag --- cmd/gaia/cmd/gaiad/main.go | 2 +- docs/examples/basecoin/cmd/basecoind/main.go | 2 +- docs/examples/democoin/cmd/democoind/main.go | 2 +- server/constructors.go | 2 +- server/export.go | 6 ++++-- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index 2a72b42cc6e7..abd01923a4ce 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -64,7 +64,7 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application } func exportAppStateAndTMValidators( - logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, + logger log.Logger, db dbm.DB, traceStore io.Writer, height int64, forZeroHeight bool, ) (json.RawMessage, []tmtypes.GenesisValidator, error) { gApp := app.NewGaiaApp(logger, db, traceStore) if height != -1 { diff --git a/docs/examples/basecoin/cmd/basecoind/main.go b/docs/examples/basecoin/cmd/basecoind/main.go index 731c5135f75d..9a7036295fd1 100644 --- a/docs/examples/basecoin/cmd/basecoind/main.go +++ b/docs/examples/basecoin/cmd/basecoind/main.go @@ -126,7 +126,7 @@ func newApp(logger log.Logger, db dbm.DB, storeTracer io.Writer) abci.Applicatio return app.NewBasecoinApp(logger, db, baseapp.SetPruning(viper.GetString("pruning"))) } -func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, storeTracer io.Writer, _ int64) ( +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, storeTracer io.Writer, _ int64, _ bool) ( json.RawMessage, []tmtypes.GenesisValidator, error) { bapp := app.NewBasecoinApp(logger, db) return bapp.ExportAppStateAndValidators() diff --git a/docs/examples/democoin/cmd/democoind/main.go b/docs/examples/democoin/cmd/democoind/main.go index 506d888a5c56..9ec0a89a7b31 100644 --- a/docs/examples/democoin/cmd/democoind/main.go +++ b/docs/examples/democoin/cmd/democoind/main.go @@ -133,7 +133,7 @@ func newApp(logger log.Logger, db dbm.DB, _ io.Writer) abci.Application { return app.NewDemocoinApp(logger, db) } -func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, _ io.Writer, _ int64) ( +func exportAppStateAndTMValidators(logger log.Logger, db dbm.DB, _ io.Writer, _ int64, _ bool) ( json.RawMessage, []tmtypes.GenesisValidator, error) { dapp := app.NewDemocoinApp(logger, db) return dapp.ExportAppStateAndValidators() diff --git a/server/constructors.go b/server/constructors.go index 9039d8a81d54..909bda8e2535 100644 --- a/server/constructors.go +++ b/server/constructors.go @@ -19,7 +19,7 @@ type ( // AppExporter is a function that dumps all app state to // JSON-serializable structure and returns the current validator set. - AppExporter func(log.Logger, dbm.DB, io.Writer, int64) (json.RawMessage, []tmtypes.GenesisValidator, error) + AppExporter func(log.Logger, dbm.DB, io.Writer, int64, bool) (json.RawMessage, []tmtypes.GenesisValidator, error) ) func openDB(rootDir string) (dbm.DB, error) { diff --git a/server/export.go b/server/export.go index fbe52eef6dbc..233df81800de 100644 --- a/server/export.go +++ b/server/export.go @@ -14,7 +14,8 @@ import ( ) const ( - flagHeight = "height" + flagHeight = "height" + flagForZeroHeight = "for-zero-height" ) // ExportCmd dumps app state to JSON. @@ -50,7 +51,8 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C return err } height := viper.GetInt64(flagHeight) - appState, validators, err := appExporter(ctx.Logger, db, traceWriter, height) + forZeroHeight := viper.GetBool(flagForZeroHeight) + appState, validators, err := appExporter(ctx.Logger, db, traceWriter, height, forZeroHeight) if err != nil { return errors.Errorf("error exporting state: %v\n", err) } From 70d64ecbab5f831b7b2820a72c62d176ddc4fc4e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 15:08:19 +0100 Subject: [PATCH 07/60] Fix flag, update docs --- docs/gaia/join-testnet.md | 8 +++++++- server/export.go | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/gaia/join-testnet.md b/docs/gaia/join-testnet.md index bdbff65e2191..62339c07bf96 100644 --- a/docs/gaia/join-testnet.md +++ b/docs/gaia/join-testnet.md @@ -142,7 +142,13 @@ gaiad export > [filename].json You can also export state from a particular height (at the end of processing the block of that height): ```bash -gaiad export --height=[height] > [filename].json +gaiad export --height [height] > [filename].json +``` + +If you plan to start a new network from the exported state, export with the `--for-zero-height` flag: + +```bash +gaiad export --height [height] --for-zero-height > [filename].json ``` ## Upgrade to Validator Node diff --git a/server/export.go b/server/export.go index 233df81800de..7b5ba4a69ed2 100644 --- a/server/export.go +++ b/server/export.go @@ -75,6 +75,7 @@ func ExportCmd(ctx *Context, cdc *codec.Codec, appExporter AppExporter) *cobra.C }, } cmd.Flags().Int64(flagHeight, -1, "Export state from a particular height (-1 means latest height)") + cmd.Flags().Bool(flagForZeroHeight, false, "Export state to start at height zero (perform preproccessing)") return cmd } From 7feb7aa87bd0467cf9ccc4105e115db387c0306f Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 16:02:32 +0100 Subject: [PATCH 08/60] Update heights across state, add forZeroHeight handling to app.go --- cmd/gaia/app/app.go | 70 ++++++++++++++++++++++++++++- cmd/gaia/cmd/gaiad/main.go | 2 +- x/distribution/keeper/delegation.go | 10 +++++ x/distribution/keeper/validator.go | 10 +++++ x/slashing/genesis.go | 16 +++---- x/slashing/handler_test.go | 2 +- x/slashing/hooks.go | 2 +- x/slashing/keeper.go | 4 +- x/slashing/signing_info.go | 6 +-- x/slashing/signing_info_test.go | 2 +- x/slashing/slashing_period.go | 12 ++++- 11 files changed, 116 insertions(+), 20 deletions(-) diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 25265a3611f2..9fa0af18792b 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -296,8 +296,73 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci } // export the state of gaia for a genesis file -func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { - ctx := app.NewContext(true, abci.Header{}) +func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { + ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) + + // prepare for fresh start at zero height + if forZeroHeight { + + /* Handle fee distribution state. */ + + // withdraw all delegator & validator rewards + app.accountKeeper.IterateAccounts(ctx, func(acc auth.Account) (stop bool) { + app.distrKeeper.WithdrawDelegationRewardsAll(ctx, acc.GetAddress()) + app.distrKeeper.WithdrawValidatorRewardsAll(ctx, sdk.ValAddress(acc.GetAddress())) + return false + }) + + // delete all distribution infos + app.distrKeeper.RemoveValidatorDistInfos(ctx) + app.distrKeeper.RemoveDelegationDistInfos(ctx) + + // assert that the fee pool is empty + feePool := app.distrKeeper.GetFeePool(ctx) + if !feePool.TotalValAccum.Accum.IsZero() { + panic("unexpected leftover validator accum") + } + bondDenom := app.stakeKeeper.GetParams(ctx).BondDenom + if !feePool.ValPool.AmountOf(bondDenom).IsZero() { + panic("unexpected leftover validator pool coins") + } + + // reset fee pool height, save fee pool + feePool.TotalValAccum.UpdateHeight = 0 + app.distrKeeper.SetFeePool(ctx, feePool) + + /* Handle stake state. */ + + // iterate through validators by power descending, reset bond height, update bond intra-tx counter + store := ctx.KVStore(app.keyStake) + iter := sdk.KVStoreReversePrefixIterator(store, stake.ValidatorsByPowerIndexKey) + counter := int16(0) + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(iter.Value()) + validator, found := app.stakeKeeper.GetValidator(ctx, addr) + if !found { + panic("expected validator, not found") + } + validator.BondHeight = 0 + validator.BondIntraTxCounter = counter + // AFAICT we do not need to reset unbonding height since it is not used. + app.stakeKeeper.SetValidator(ctx, validator) + counter++ + } + + // AFAICT we do not need to reset bond heights since they are unused. + + /* Handle slashing state. */ + + // we have to clear the slashing periods, since they reference heights + app.slashingKeeper.DeleteValidatorSlashingPeriods(ctx) + + // reset start height on signing infos + app.slashingKeeper.IterateValidatorSigningInfos(ctx, func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + return false + }) + + } // iterate to get the accounts accounts := []GenesisAccount{} @@ -307,6 +372,7 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val return false } app.accountKeeper.IterateAccounts(ctx, appendAccount) + genState := NewGenesisState( accounts, auth.ExportGenesis(ctx, app.feeCollectionKeeper), diff --git a/cmd/gaia/cmd/gaiad/main.go b/cmd/gaia/cmd/gaiad/main.go index abd01923a4ce..23dfca38659c 100644 --- a/cmd/gaia/cmd/gaiad/main.go +++ b/cmd/gaia/cmd/gaiad/main.go @@ -73,5 +73,5 @@ func exportAppStateAndTMValidators( return nil, nil, err } } - return gApp.ExportAppStateAndValidators() + return gApp.ExportAppStateAndValidators(forZeroHeight) } diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index ac519107daea..de8937d44aa2 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -42,6 +42,16 @@ func (k Keeper) RemoveDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress store.Delete(GetDelegationDistInfoKey(delAddr, valOperatorAddr)) } +// remove all delegation distribution infos +func (k Keeper) RemoveDelegationDistInfos(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} + //___________________________________________________________________________________________ // get the delegator withdraw address, return the delegator address if not set diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index d2c755cfa6b5..34f852242843 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -40,6 +40,16 @@ func (k Keeper) RemoveValidatorDistInfo(ctx sdk.Context, valAddr sdk.ValAddress) store.Delete(GetValidatorDistInfoKey(valAddr)) } +// remove all validator distribution infos +func (k Keeper) RemoveValidatorDistInfos(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorDistInfoKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} + // Get the calculated accum of a validator at the current block // without affecting the state. func (k Keeper) GetValidatorAccum(ctx sdk.Context, operatorAddr sdk.ValAddress) (sdk.Dec, sdk.Error) { diff --git a/x/slashing/genesis.go b/x/slashing/genesis.go index 1d4a44369151..2a921af49a37 100644 --- a/x/slashing/genesis.go +++ b/x/slashing/genesis.go @@ -7,10 +7,10 @@ import ( // GenesisState - all slashing state that must be provided at genesis type GenesisState struct { - Params Params - SigningInfos map[string]ValidatorSigningInfo - MissedBlocks map[string][]MissedBlock - SlashingPeriods []ValidatorSlashingPeriod + Params Params `json:"params"` + SigningInfos map[string]ValidatorSigningInfo `json:"signing_infos"` + MissedBlocks map[string][]MissedBlock `json:"missed_blocks"` + SlashingPeriods []ValidatorSlashingPeriod `json:"slashing_periods"` } // MissedBlock @@ -41,7 +41,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState, sdata types. if err != nil { panic(err) } - keeper.setValidatorSigningInfo(ctx, address, info) + keeper.SetValidatorSigningInfo(ctx, address, info) } for addr, array := range data.MissedBlocks { @@ -70,12 +70,12 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) (data GenesisState) { signingInfos := make(map[string]ValidatorSigningInfo) missedBlocks := make(map[string][]MissedBlock) - keeper.iterateValidatorSigningInfos(ctx, func(address sdk.ConsAddress, info ValidatorSigningInfo) (stop bool) { + keeper.IterateValidatorSigningInfos(ctx, func(address sdk.ConsAddress, info ValidatorSigningInfo) (stop bool) { bechAddr := address.String() signingInfos[bechAddr] = info localMissedBlocks := []MissedBlock{} - keeper.iterateValidatorMissedBlockBitArray(ctx, address, func(index int64, missed bool) (stop bool) { + keeper.IterateValidatorMissedBlockBitArray(ctx, address, func(index int64, missed bool) (stop bool) { localMissedBlocks = append(localMissedBlocks, MissedBlock{index, missed}) return false }) @@ -85,7 +85,7 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) (data GenesisState) { }) slashingPeriods := []ValidatorSlashingPeriod{} - keeper.iterateValidatorSlashingPeriods(ctx, func(slashingPeriod ValidatorSlashingPeriod) (stop bool) { + keeper.IterateValidatorSlashingPeriods(ctx, func(slashingPeriod ValidatorSlashingPeriod) (stop bool) { slashingPeriods = append(slashingPeriods, slashingPeriod) return false }) diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index ef307547c640..b69c3bd6a1e5 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -55,7 +55,7 @@ func TestJailedValidatorDelegations(t *testing.T) { JailedUntil: time.Unix(0, 0), MissedBlocksCounter: int64(0), } - slashingKeeper.setValidatorSigningInfo(ctx, consAddr, newInfo) + slashingKeeper.SetValidatorSigningInfo(ctx, consAddr, newInfo) // delegate tokens to the validator delAddr := sdk.AccAddress(addrs[2]) diff --git a/x/slashing/hooks.go b/x/slashing/hooks.go index e09f6c566e0a..bb7e413242b3 100644 --- a/x/slashing/hooks.go +++ b/x/slashing/hooks.go @@ -18,7 +18,7 @@ func (k Keeper) onValidatorBonded(ctx sdk.Context, address sdk.ConsAddress, _ sd JailedUntil: time.Unix(0, 0), MissedBlocksCounter: 0, } - k.setValidatorSigningInfo(ctx, address, signingInfo) + k.SetValidatorSigningInfo(ctx, address, signingInfo) } // Create a new slashing period when a validator is bonded diff --git a/x/slashing/keeper.go b/x/slashing/keeper.go index fe1531f22818..61d366172038 100644 --- a/x/slashing/keeper.go +++ b/x/slashing/keeper.go @@ -96,7 +96,7 @@ func (k Keeper) handleDoubleSign(ctx sdk.Context, addr crypto.Address, infractio panic(fmt.Sprintf("Expected signing info for validator %s but not found", consAddr)) } signInfo.JailedUntil = time.Add(k.DoubleSignUnbondDuration(ctx)) - k.setValidatorSigningInfo(ctx, consAddr, signInfo) + k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } // handle a validator signature, must be called once per validator per block @@ -168,7 +168,7 @@ func (k Keeper) handleValidatorSignature(ctx sdk.Context, addr crypto.Address, p } // Set the updated signing info - k.setValidatorSigningInfo(ctx, consAddr, signInfo) + k.SetValidatorSigningInfo(ctx, consAddr, signInfo) } func (k Keeper) addPubkey(ctx sdk.Context, pubkey crypto.PubKey) { diff --git a/x/slashing/signing_info.go b/x/slashing/signing_info.go index 291351742f39..f4f2d2fde8cd 100644 --- a/x/slashing/signing_info.go +++ b/x/slashing/signing_info.go @@ -21,7 +21,7 @@ func (k Keeper) getValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress } // Stored by *validator* address (not operator address) -func (k Keeper) iterateValidatorSigningInfos(ctx sdk.Context, handler func(address sdk.ConsAddress, info ValidatorSigningInfo) (stop bool)) { +func (k Keeper) IterateValidatorSigningInfos(ctx sdk.Context, handler func(address sdk.ConsAddress, info ValidatorSigningInfo) (stop bool)) { store := ctx.KVStore(k.storeKey) iter := sdk.KVStorePrefixIterator(store, ValidatorSigningInfoKey) defer iter.Close() @@ -36,7 +36,7 @@ func (k Keeper) iterateValidatorSigningInfos(ctx sdk.Context, handler func(addre } // Stored by *validator* address (not operator address) -func (k Keeper) setValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info ValidatorSigningInfo) { +func (k Keeper) SetValidatorSigningInfo(ctx sdk.Context, address sdk.ConsAddress, info ValidatorSigningInfo) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinaryLengthPrefixed(info) store.Set(GetValidatorSigningInfoKey(address), bz) @@ -56,7 +56,7 @@ func (k Keeper) getValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.Con } // Stored by *validator* address (not operator address) -func (k Keeper) iterateValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, handler func(index int64, missed bool) (stop bool)) { +func (k Keeper) IterateValidatorMissedBlockBitArray(ctx sdk.Context, address sdk.ConsAddress, handler func(index int64, missed bool) (stop bool)) { store := ctx.KVStore(k.storeKey) index := int64(0) // Array may be sparse diff --git a/x/slashing/signing_info_test.go b/x/slashing/signing_info_test.go index 15863ebc70b1..d340eaf7a5ef 100644 --- a/x/slashing/signing_info_test.go +++ b/x/slashing/signing_info_test.go @@ -19,7 +19,7 @@ func TestGetSetValidatorSigningInfo(t *testing.T) { JailedUntil: time.Unix(2, 0), MissedBlocksCounter: int64(10), } - keeper.setValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo) + keeper.SetValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0]), newInfo) info, found = keeper.getValidatorSigningInfo(ctx, sdk.ConsAddress(addrs[0])) require.True(t, found) require.Equal(t, info.StartHeight, int64(4)) diff --git a/x/slashing/slashing_period.go b/x/slashing/slashing_period.go index 4caf5d7c9d36..e597497f3071 100644 --- a/x/slashing/slashing_period.go +++ b/x/slashing/slashing_period.go @@ -54,7 +54,7 @@ func (k Keeper) getValidatorSlashingPeriodForHeight(ctx sdk.Context, address sdk // Iterate over all slashing periods in the store, calling on each // decode slashing period a provided handler function // Stop if the provided handler function returns true -func (k Keeper) iterateValidatorSlashingPeriods(ctx sdk.Context, handler func(slashingPeriod ValidatorSlashingPeriod) (stop bool)) { +func (k Keeper) IterateValidatorSlashingPeriods(ctx sdk.Context, handler func(slashingPeriod ValidatorSlashingPeriod) (stop bool)) { store := ctx.KVStore(k.storeKey) iter := sdk.KVStorePrefixIterator(store, ValidatorSlashingPeriodKey) defer iter.Close() @@ -66,6 +66,16 @@ func (k Keeper) iterateValidatorSlashingPeriods(ctx sdk.Context, handler func(sl } } +// Delete all slashing periods in the store. +func (k Keeper) DeleteValidatorSlashingPeriods(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, ValidatorSlashingPeriodKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} + // Stored by validator Tendermint address (not operator address) // This function sets a validator slashing period for a particular validator, // start height, end height, and current slashed-so-far total, or updates From 7c6147863bd84fa84c07f498256ca252c0931f4e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 16:29:47 +0100 Subject: [PATCH 09/60] Assert runtime invariants at application start, move export code --- cmd/gaia/app/app.go | 100 ++----------------------------------- cmd/gaia/app/export.go | 111 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 97 deletions(-) create mode 100644 cmd/gaia/app/export.go diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index f0152c5356ef..6b6d14082980 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -1,7 +1,6 @@ package app import ( - "encoding/json" "fmt" "io" "os" @@ -22,7 +21,6 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" - tmtypes "github.com/tendermint/tendermint/types" ) const ( @@ -292,106 +290,14 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci } } + // assert runtime invariants + app.assertRuntimeInvariants() + return abci.ResponseInitChain{ Validators: validators, } } -// export the state of gaia for a genesis file -func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { - ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) - - // prepare for fresh start at zero height - if forZeroHeight { - - /* Handle fee distribution state. */ - - // withdraw all delegator & validator rewards - app.accountKeeper.IterateAccounts(ctx, func(acc auth.Account) (stop bool) { - app.distrKeeper.WithdrawDelegationRewardsAll(ctx, acc.GetAddress()) - app.distrKeeper.WithdrawValidatorRewardsAll(ctx, sdk.ValAddress(acc.GetAddress())) - return false - }) - - // delete all distribution infos - app.distrKeeper.RemoveValidatorDistInfos(ctx) - app.distrKeeper.RemoveDelegationDistInfos(ctx) - - // assert that the fee pool is empty - feePool := app.distrKeeper.GetFeePool(ctx) - if !feePool.TotalValAccum.Accum.IsZero() { - panic("unexpected leftover validator accum") - } - bondDenom := app.stakeKeeper.GetParams(ctx).BondDenom - if !feePool.ValPool.AmountOf(bondDenom).IsZero() { - panic("unexpected leftover validator pool coins") - } - - // reset fee pool height, save fee pool - feePool.TotalValAccum.UpdateHeight = 0 - app.distrKeeper.SetFeePool(ctx, feePool) - - /* Handle stake state. */ - - // iterate through validators by power descending, reset bond height, update bond intra-tx counter - store := ctx.KVStore(app.keyStake) - iter := sdk.KVStoreReversePrefixIterator(store, stake.ValidatorsByPowerIndexKey) - counter := int16(0) - for ; iter.Valid(); iter.Next() { - addr := sdk.ValAddress(iter.Value()) - validator, found := app.stakeKeeper.GetValidator(ctx, addr) - if !found { - panic("expected validator, not found") - } - validator.BondHeight = 0 - validator.BondIntraTxCounter = counter - // AFAICT we do not need to reset unbonding height since it is not used. - app.stakeKeeper.SetValidator(ctx, validator) - counter++ - } - - // AFAICT we do not need to reset bond heights since they are unused. - - /* Handle slashing state. */ - - // we have to clear the slashing periods, since they reference heights - app.slashingKeeper.DeleteValidatorSlashingPeriods(ctx) - - // reset start height on signing infos - app.slashingKeeper.IterateValidatorSigningInfos(ctx, func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { - info.StartHeight = 0 - app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) - return false - }) - - } - - // iterate to get the accounts - accounts := []GenesisAccount{} - appendAccount := func(acc auth.Account) (stop bool) { - account := NewGenesisAccountI(acc) - accounts = append(accounts, account) - return false - } - app.accountKeeper.IterateAccounts(ctx, appendAccount) - - genState := NewGenesisState( - accounts, - auth.ExportGenesis(ctx, app.feeCollectionKeeper), - stake.ExportGenesis(ctx, app.stakeKeeper), - mint.ExportGenesis(ctx, app.mintKeeper), - distr.ExportGenesis(ctx, app.distrKeeper), - gov.ExportGenesis(ctx, app.govKeeper), - slashing.ExportGenesis(ctx, app.slashingKeeper), - ) - appState, err = codec.MarshalJSONIndent(app.cdc, genState) - if err != nil { - return nil, nil, err - } - validators = stake.WriteValidators(ctx, app.stakeKeeper) - return appState, validators, nil -} - // load a particular height func (app *GaiaApp) LoadHeight(height int64) error { return app.LoadVersion(height, app.keyMain) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go new file mode 100644 index 000000000000..98b0505c88e1 --- /dev/null +++ b/cmd/gaia/app/export.go @@ -0,0 +1,111 @@ +package app + +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/gov" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/slashing" + stake "github.com/cosmos/cosmos-sdk/x/stake" + abci "github.com/tendermint/tendermint/abci/types" + tmtypes "github.com/tendermint/tendermint/types" +) + +// export the state of gaia for a genesis file +func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { + ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) + + // prepare for fresh start at zero height + if forZeroHeight { + + /* Handle fee distribution state. */ + + // withdraw all delegator & validator rewards + app.accountKeeper.IterateAccounts(ctx, func(acc auth.Account) (stop bool) { + app.distrKeeper.WithdrawDelegationRewardsAll(ctx, acc.GetAddress()) + app.distrKeeper.WithdrawValidatorRewardsAll(ctx, sdk.ValAddress(acc.GetAddress())) + return false + }) + + // delete all distribution infos + app.distrKeeper.RemoveValidatorDistInfos(ctx) + app.distrKeeper.RemoveDelegationDistInfos(ctx) + + // assert that the fee pool is empty + feePool := app.distrKeeper.GetFeePool(ctx) + if !feePool.TotalValAccum.Accum.IsZero() { + panic("unexpected leftover validator accum") + } + bondDenom := app.stakeKeeper.GetParams(ctx).BondDenom + if !feePool.ValPool.AmountOf(bondDenom).IsZero() { + panic("unexpected leftover validator pool coins") + } + + // reset fee pool height, save fee pool + feePool.TotalValAccum.UpdateHeight = 0 + app.distrKeeper.SetFeePool(ctx, feePool) + + /* Handle stake state. */ + + // iterate through validators by power descending, reset bond height, update bond intra-tx counter + store := ctx.KVStore(app.keyStake) + iter := sdk.KVStoreReversePrefixIterator(store, stake.ValidatorsByPowerIndexKey) + counter := int16(0) + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(iter.Value()) + validator, found := app.stakeKeeper.GetValidator(ctx, addr) + if !found { + panic("expected validator, not found") + } + validator.BondHeight = 0 + validator.BondIntraTxCounter = counter + // AFAICT we do not need to reset unbonding height since it is not used. + app.stakeKeeper.SetValidator(ctx, validator) + counter++ + } + + // AFAICT we do not need to reset bond heights since they are unused. + + /* Handle slashing state. */ + + // we have to clear the slashing periods, since they reference heights + app.slashingKeeper.DeleteValidatorSlashingPeriods(ctx) + + // reset start height on signing infos + app.slashingKeeper.IterateValidatorSigningInfos(ctx, func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + return false + }) + + } + + // iterate to get the accounts + accounts := []GenesisAccount{} + appendAccount := func(acc auth.Account) (stop bool) { + account := NewGenesisAccountI(acc) + accounts = append(accounts, account) + return false + } + app.accountKeeper.IterateAccounts(ctx, appendAccount) + + genState := NewGenesisState( + accounts, + auth.ExportGenesis(ctx, app.feeCollectionKeeper), + stake.ExportGenesis(ctx, app.stakeKeeper), + mint.ExportGenesis(ctx, app.mintKeeper), + distr.ExportGenesis(ctx, app.distrKeeper), + gov.ExportGenesis(ctx, app.govKeeper), + slashing.ExportGenesis(ctx, app.slashingKeeper), + ) + appState, err = codec.MarshalJSONIndent(app.cdc, genState) + if err != nil { + return nil, nil, err + } + validators = stake.WriteValidators(ctx, app.stakeKeeper) + return appState, validators, nil +} From bf2e5257f1e1fce5ff6e2d6441f0240fd631db28 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 16:34:55 +0100 Subject: [PATCH 10/60] Test fixes; 'make format' --- cmd/gaia/app/app_test.go | 2 +- cmd/gaia/app/export.go | 2 +- cmd/gaia/app/genesis.go | 2 +- cmd/gaia/app/sim_test.go | 2 +- x/auth/client/txbuilder/txbuilder_test.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index 7023eb09c144..358c3395c726 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -49,6 +49,6 @@ func TestGaiadExport(t *testing.T) { // Making a new app object with the db, so that initchain hasn't been called newGapp := NewGaiaApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil) - _, _, err := newGapp.ExportAppStateAndValidators() + _, _, err := newGapp.ExportAppStateAndValidators(false) require.NoError(t, err, "ExportAppStateAndValidators should not have an error") } diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index 98b0505c88e1..dec1a924aa58 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -27,7 +27,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js // withdraw all delegator & validator rewards app.accountKeeper.IterateAccounts(ctx, func(acc auth.Account) (stop bool) { app.distrKeeper.WithdrawDelegationRewardsAll(ctx, acc.GetAddress()) - app.distrKeeper.WithdrawValidatorRewardsAll(ctx, sdk.ValAddress(acc.GetAddress())) + _ = app.distrKeeper.WithdrawValidatorRewardsAll(ctx, sdk.ValAddress(acc.GetAddress())) return false }) diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index e3c869ada1d6..0a0d8ebd4cb8 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -287,7 +287,7 @@ func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tm func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount { accAuth := auth.NewBaseAccountWithAddress(addr) - coins :=sdk.Coins{ + coins := sdk.Coins{ {"fooToken", sdk.NewInt(1000)}, {bondDenom, freeFermionsAcc}, } diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index e56344554a16..ab146e115843 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -311,7 +311,7 @@ func TestGaiaImportExport(t *testing.T) { fmt.Printf("Exporting genesis...\n") - appState, _, err := app.ExportAppStateAndValidators() + appState, _, err := app.ExportAppStateAndValidators(false) if err != nil { panic(err) } diff --git a/x/auth/client/txbuilder/txbuilder_test.go b/x/auth/client/txbuilder/txbuilder_test.go index 3ad2ad4123d8..22c281709d14 100644 --- a/x/auth/client/txbuilder/txbuilder_test.go +++ b/x/auth/client/txbuilder/txbuilder_test.go @@ -9,8 +9,8 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/tendermint/tendermint/crypto/ed25519" stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" + "github.com/tendermint/tendermint/crypto/ed25519" ) var ( From 9c96e64e5fa6522601fbc052cb9a1e93836d9df6 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 16:58:55 +0100 Subject: [PATCH 11/60] Bugfix height; return validators correctly --- x/stake/genesis.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 7be8eff3a4fc..bdd62d3769d4 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -21,8 +21,8 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ // We need to pretend to be "n blocks before genesis", where "n" is the validator update delay, // so that e.g. slashing periods are correctly initialized for the validator set - // e.g. with a one-block offset - the first TM block is at height 0, so state updates applied from genesis.json are in block -1. - ctx = ctx.WithBlockHeight(-types.ValidatorUpdateDelay) + // e.g. with a one-block offset - the first TM block is at height 1, so state updates applied from genesis.json are in block 0. + ctx = ctx.WithBlockHeight(1 - types.ValidatorUpdateDelay) keeper.SetPool(ctx, data.Pool) keeper.SetParams(ctx, data.Params) @@ -72,6 +72,13 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ if data.Exported { for _, lv := range data.LastValidatorPowers { keeper.SetLastValidatorPower(ctx, lv.Address, lv.Power) + validator, found := keeper.GetValidator(ctx, lv.Address) + if !found { + panic("expected validator, not found") + } + update := validator.ABCIValidatorUpdate() + update.Power = lv.Power.Int64() // keep the next-val-set offset, use the last power for the first block + res = append(res, update) } } else { res = keeper.ApplyAndReturnValidatorSetUpdates(ctx) From 481d8014d34633ebfc72ef034306e7c6cf78ffd8 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 17:08:00 +0100 Subject: [PATCH 12/60] Minor simulation fixes --- x/mock/simulation/mock_tendermint.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/x/mock/simulation/mock_tendermint.go b/x/mock/simulation/mock_tendermint.go index 2ddac2e79421..54e38d5c7830 100644 --- a/x/mock/simulation/mock_tendermint.go +++ b/x/mock/simulation/mock_tendermint.go @@ -167,10 +167,11 @@ func RandomRequestBeginBlock(r *rand.Rand, params Params, time := header.Time vals := voteInfos - if r.Float64() < params.PastEvidenceFraction { - height = int64(r.Intn(int(header.Height) - 1)) - time = pastTimes[height] - vals = pastVoteInfos[height] + if r.Float64() < params.PastEvidenceFraction && header.Height > 1 { + height = int64(r.Intn(int(header.Height)-1)) + 1 // Tendermint starts at height 1 + // array indices offset by one + time = pastTimes[height-1] + vals = pastVoteInfos[height-1] } validator := vals[r.Intn(len(vals))].Validator From e6f05b293e99f0a0c5e33804e3c1461487b1e288 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 17:55:21 +0100 Subject: [PATCH 13/60] Fix import-export tests, which cannot assert invariants --- cmd/gaia/app/app.go | 30 ++++++++++++++++++------------ cmd/gaia/app/sim_test.go | 11 ++++++----- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 6b6d14082980..486a6ff8742a 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -216,18 +216,8 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R } } -// custom logic for gaia initialization -func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - stateJSON := req.AppStateBytes - // TODO is this now the whole genesis file? - - var genesisState GenesisState - err := app.cdc.UnmarshalJSON(stateJSON, &genesisState) - if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") - } - +// initialize store from a genesis state +func (app *GaiaApp) initGenesis(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate { // sort by account number to maintain consistency sort.Slice(genesisState.Accounts, func(i, j int) bool { return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber @@ -274,6 +264,22 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci validators = app.stakeKeeper.ApplyAndReturnValidatorSetUpdates(ctx) } + return validators +} + +// custom logic for gaia initialization +func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + stateJSON := req.AppStateBytes + // TODO is this now the whole genesis file? + + var genesisState GenesisState + err := app.cdc.UnmarshalJSON(stateJSON, &genesisState) + if err != nil { + panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 + // return sdk.ErrGenesisParse("").TraceCause(err, "") + } + + validators := app.initGenesis(ctx, genesisState) // sanity check if len(req.Validators) > 0 { diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index ab146e115843..3042f87e9949 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -326,15 +326,16 @@ func TestGaiaImportExport(t *testing.T) { }() newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil) require.Equal(t, "GaiaApp", newApp.Name()) - request := abci.RequestInitChain{ - AppStateBytes: appState, + var genesisState GenesisState + err = app.cdc.UnmarshalJSON(appState, &genesisState) + if err != nil { + panic(err) } - newApp.InitChain(request) - newApp.Commit() + ctxB := newApp.NewContext(true, abci.Header{}) + newApp.initGenesis(ctxB, genesisState) fmt.Printf("Comparing stores...\n") ctxA := app.NewContext(true, abci.Header{}) - ctxB := newApp.NewContext(true, abci.Header{}) type StoreKeysPrefixes struct { A sdk.StoreKey B sdk.StoreKey From 58e026abfc75a666d77aa687fc40c8ad1fd2c561 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 18:05:49 +0100 Subject: [PATCH 14/60] Simulation after import, include in CI --- .circleci/config.yml | 21 +++++++++ Makefile | 4 ++ cmd/gaia/app/sim_test.go | 76 ++++++++++++++++++++++++++++++ scripts/simulation-after-import.sh | 54 +++++++++++++++++++++ 4 files changed, 155 insertions(+) create mode 100755 scripts/simulation-after-import.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 6be110cf27b6..a0bab3e46e3e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -162,6 +162,24 @@ jobs: export PATH="$GOBIN:$PATH" make test_sim_gaia_import_export + test_sim_gaia_simulation_after_import: + <<: *defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - checkout + - run: + name: dependencies + command: | + export PATH="$GOBIN:$PATH" + make get_vendor_deps + - run: + name: Test Gaia import/export simulation + command: | + export PATH="$GOBIN:$PATH" + make test_sim_gaia_simulation_after_import + test_sim_gaia_multi_seed: <<: *defaults parallelism: 1 @@ -301,6 +319,9 @@ workflows: - test_sim_gaia_import_export: requires: - setup_dependencies + - test_sim_gaia_simulation_after_import: + requires: + - setup_dependencies - test_sim_gaia_multi_seed: requires: - setup_dependencies diff --git a/Makefile b/Makefile index 35e5b50eff1f..d9034db9bce3 100644 --- a/Makefile +++ b/Makefile @@ -184,6 +184,10 @@ test_sim_gaia_import_export: @echo "Running Gaia import/export simulation. This may take several minutes..." @bash scripts/import-export-sim.sh 50 +test_sim_gaia_simulation_after_import: + @echo "Running Gaia simulation-after-import. This may take several minutes..." + @bash scripts/simulation-after-import.sh 50 + test_sim_gaia_multi_seed: @echo "Running multi-seed Gaia simulation. This may take awhile!" @bash scripts/multisim.sh 25 diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 3042f87e9949..a7a74e2c9891 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -366,6 +366,82 @@ func TestGaiaImportExport(t *testing.T) { } +func TestGaiaSimulationAfterImport(t *testing.T) { + if !enabled { + t.Skip("Skipping Gaia simulation after import") + } + + // Setup Gaia application + var logger log.Logger + if verbose { + logger = log.TestingLogger() + } else { + logger = log.NewNopLogger() + } + var db dbm.DB + dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") + db, _ = dbm.NewGoLevelDB("Simulation", dir) + defer func() { + db.Close() + os.RemoveAll(dir) + }() + app := NewGaiaApp(logger, db, nil) + require.Equal(t, "GaiaApp", app.Name()) + + // Run randomized simulation + err := simulation.SimulateFromSeed( + t, app.BaseApp, appStateFn, seed, + testAndRunTxs(app), + []simulation.RandSetup{}, + invariants(app), + numBlocks, + blockSize, + commit, + ) + if commit { + // for memdb: + // fmt.Println("Database Size", db.Stats()["database.size"]) + fmt.Println("GoLevelDB Stats") + fmt.Println(db.Stats()["leveldb.stats"]) + fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + } + require.Nil(t, err) + + fmt.Printf("Exporting genesis...\n") + + appState, _, err := app.ExportAppStateAndValidators(true) + if err != nil { + panic(err) + } + + fmt.Printf("Importing genesis...\n") + + newDir, _ := ioutil.TempDir("", "goleveldb-gaia-sim-2") + newDB, _ := dbm.NewGoLevelDB("Simulation-2", dir) + defer func() { + newDB.Close() + os.RemoveAll(newDir) + }() + newApp := NewGaiaApp(log.NewNopLogger(), newDB, nil) + require.Equal(t, "GaiaApp", newApp.Name()) + newApp.InitChain(abci.RequestInitChain{ + AppStateBytes: appState, + }) + + // Run randomized simulation on imported app + err = simulation.SimulateFromSeed( + t, newApp.BaseApp, appStateFn, seed, + testAndRunTxs(newApp), + []simulation.RandSetup{}, + invariants(newApp), + numBlocks, + blockSize, + commit, + ) + require.Nil(t, err) + +} + // TODO: Make another test for the fuzzer itself, which just has noOp txs // and doesn't depend on gaia func TestAppStateDeterminism(t *testing.T) { diff --git a/scripts/simulation-after-import.sh b/scripts/simulation-after-import.sh new file mode 100755 index 000000000000..2e90d123498c --- /dev/null +++ b/scripts/simulation-after-import.sh @@ -0,0 +1,54 @@ +#!/bin/bash + +seeds=(1 2 4 7 9 20 32 123 124 582 1893 2989 3012 4728 37827 981928 87821 891823782 989182 89182391 \ +11 22 44 77 99 2020 3232 123123 124124 582582 18931893 29892989 30123012 47284728 37827) +blocks=$1 + +echo "Running multi-seed import-export simulation with seeds ${seeds[@]}" +echo "Running $blocks blocks per seed" +echo "Edit scripts/simulation-after-import.sh to add new seeds. Keeping parameters in the file makes failures easy to reproduce." +echo "This script will kill all sub-simulations on SIGINT/SIGTERM (i.e. Ctrl-C)." + +trap 'kill $(jobs -pr)' SIGINT SIGTERM + +tmpdir=$(mktemp -d) +echo "Using temporary log directory: $tmpdir" + +sim() { + seed=$1 + echo "Running simulation after import with seed $seed. This may take awhile!" + file="$tmpdir/gaia-simulation-seed-$seed-date-$(date -Iseconds -u).stdout" + echo "Writing stdout to $file..." + go test ./cmd/gaia/app -run TestGaiaSimulationAfterImport -SimulationEnabled=true -SimulationNumBlocks=$blocks \ + -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=$seed -v -timeout 24h > $file +} + +i=0 +pids=() +for seed in ${seeds[@]}; do + sim $seed & + pids[${i}]=$! + i=$(($i+1)) + sleep 10 # start in order, nicer logs +done + +echo "Simulation processes spawned, waiting for completion..." + +code=0 + +i=0 +for pid in ${pids[*]}; do + wait $pid + last=$? + seed=${seeds[${i}]} + if [ $last -ne 0 ] + then + echo "Import/export simulation with seed $seed failed!" + code=1 + else + echo "Import/export simulation with seed $seed OK" + fi + i=$(($i+1)) +done + +exit $code From e2a21f2f099f8ff687bdfdfc454d6b2e2cd96779 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 18:23:26 +0100 Subject: [PATCH 15/60] Avoid zero-validator case --- cmd/gaia/app/export.go | 3 ++- cmd/gaia/app/sim_test.go | 16 +++++++++++----- x/mock/simulation/simulate.go | 9 ++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index dec1a924aa58..ec4e910f2b37 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -2,6 +2,7 @@ package app import ( "encoding/json" + "fmt" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -42,7 +43,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js } bondDenom := app.stakeKeeper.GetParams(ctx).BondDenom if !feePool.ValPool.AmountOf(bondDenom).IsZero() { - panic("unexpected leftover validator pool coins") + panic(fmt.Sprintf("unexpected leftover validator pool coins: %v", feePool.ValPool.AmountOf(bondDenom))) } // reset fee pool height, save fee pool diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index a7a74e2c9891..5d2b96ef26d7 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -206,7 +206,7 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { // Run randomized simulation // TODO parameterize numbers, save for a later PR - err := simulation.SimulateFromSeed( + _, err := simulation.SimulateFromSeed( b, app.BaseApp, appStateFn, seed, testAndRunTxs(app), []simulation.RandSetup{}, @@ -249,7 +249,7 @@ func TestFullGaiaSimulation(t *testing.T) { require.Equal(t, "GaiaApp", app.Name()) // Run randomized simulation - err := simulation.SimulateFromSeed( + _, err := simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, testAndRunTxs(app), []simulation.RandSetup{}, @@ -291,7 +291,7 @@ func TestGaiaImportExport(t *testing.T) { require.Equal(t, "GaiaApp", app.Name()) // Run randomized simulation - err := simulation.SimulateFromSeed( + _, err := simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, testAndRunTxs(app), []simulation.RandSetup{}, @@ -389,7 +389,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) { require.Equal(t, "GaiaApp", app.Name()) // Run randomized simulation - err := simulation.SimulateFromSeed( + stopEarly, err := simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, testAndRunTxs(app), []simulation.RandSetup{}, @@ -407,6 +407,12 @@ func TestGaiaSimulationAfterImport(t *testing.T) { } require.Nil(t, err) + if stopEarly { + // we can't export or import a zero-validator genesis + fmt.Printf("We can't export or import a zero-validator genesis, exiting test...\n") + return + } + fmt.Printf("Exporting genesis...\n") appState, _, err := app.ExportAppStateAndValidators(true) @@ -429,7 +435,7 @@ func TestGaiaSimulationAfterImport(t *testing.T) { }) // Run randomized simulation on imported app - err = simulation.SimulateFromSeed( + _, err = simulation.SimulateFromSeed( t, newApp.BaseApp, appStateFn, seed, testAndRunTxs(newApp), []simulation.RandSetup{}, diff --git a/x/mock/simulation/simulate.go b/x/mock/simulation/simulate.go index ac0e5d3f7f11..74446e290a3e 100644 --- a/x/mock/simulation/simulate.go +++ b/x/mock/simulation/simulate.go @@ -27,7 +27,7 @@ type AppStateFn func(r *rand.Rand, accs []Account) json.RawMessage // Simulate tests application by sending random messages. func Simulate(t *testing.T, app *baseapp.BaseApp, appStateFn AppStateFn, ops WeightedOperations, setups []RandSetup, - invariants Invariants, numBlocks int, blockSize int, commit bool) error { + invariants Invariants, numBlocks int, blockSize int, commit bool) (bool, error) { time := time.Now().UnixNano() return SimulateFromSeed(t, app, appStateFn, time, ops, @@ -57,10 +57,9 @@ func initChain(r *rand.Rand, params Params, accounts []Account, func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, appStateFn AppStateFn, seed int64, ops WeightedOperations, setups []RandSetup, invariants Invariants, - numBlocks int, blockSize int, commit bool) (simError error) { + numBlocks int, blockSize int, commit bool) (stopEarly bool, simError error) { // in case we have to end early, don't os.Exit so that we can run cleanup code. - stopEarly := false testingMode, t, b := getTestingMode(tb) fmt.Printf("Starting SimulateFromSeed with randomness "+ "created with seed %d\n", int(seed)) @@ -217,14 +216,14 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, if stopEarly { eventStats.Print() - return simError + return true, simError } fmt.Printf("\nSimulation complete. Final height (blocks): %d, "+ "final time (seconds), : %v, operations ran %d\n", header.Height, header.Time, opCount) eventStats.Print() - return nil + return false, nil } //______________________________________________________________________________ From 9a57abcacbd9d58bc1f5b5daadb5e44d9bee41ab Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 15 Nov 2018 18:37:23 +0100 Subject: [PATCH 16/60] Fix height offset --- cmd/gaia/app/export.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index ec4e910f2b37..ad6d3695ad12 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -18,7 +18,8 @@ import ( // export the state of gaia for a genesis file func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { - ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) + // as if they could withdraw from the start of the next block + ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight() + 1}) // prepare for fresh start at zero height if forZeroHeight { From 1cc058f13e8e326b23a3a4c685ce798b6f336d1c Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 15 Nov 2018 14:14:45 -0500 Subject: [PATCH 17/60] fix existing tests --- cmd/gaia/app/sim_test.go | 19 ++++++++----------- x/mint/genesis.go | 2 +- x/mint/minter.go | 40 +++++++++++++++++++++++++++++----------- x/mint/minter_test.go | 24 +++++++++++++++++++++++- x/mint/params.go | 14 ++++++++++++++ 5 files changed, 75 insertions(+), 24 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index e56344554a16..8e2550a30fe2 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -109,17 +109,14 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { } fmt.Printf("Selected randomly generated slashing parameters: %+v\n", slashingGenesis) mintGenesis := mint.GenesisState{ - Minter: mint.Minter{ - InflationLastTime: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(int64(r.Intn(99)), 2), - }, - Params: mint.Params{ - MintDenom: stakeTypes.DefaultBondDenom, - InflationRateChange: sdk.NewDecWithPrec(int64(r.Intn(99)), 2), - InflationMax: sdk.NewDecWithPrec(20, 2), - InflationMin: sdk.NewDecWithPrec(7, 2), - GoalBonded: sdk.NewDecWithPrec(67, 2), - }, + Minter: mint.InitialMinter( + sdk.NewDecWithPrec(int64(r.Intn(99)), 2)), + Params: mint.NewParams( + stakeTypes.DefaultBondDenom, + sdk.NewDecWithPrec(int64(r.Intn(99)), 2), + sdk.NewDecWithPrec(20, 2), + sdk.NewDecWithPrec(7, 2), + sdk.NewDecWithPrec(67, 2)), } fmt.Printf("Selected randomly generated minting parameters: %v\n", mintGenesis) var validators []stake.Validator diff --git a/x/mint/genesis.go b/x/mint/genesis.go index ce375d71e576..27615ed7ee47 100644 --- a/x/mint/genesis.go +++ b/x/mint/genesis.go @@ -20,7 +20,7 @@ func NewGenesisState(minter Minter, params Params) GenesisState { // get raw genesis raw message for testing func DefaultGenesisState() GenesisState { return GenesisState{ - Minter: InitialMinter(), + Minter: DefaultInitialMinter(), Params: DefaultParams(), } } diff --git a/x/mint/minter.go b/x/mint/minter.go index 0ac532267607..88a8ccb00bd0 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -7,7 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// current inflation state +// Minter represents the minting state type Minter struct { LastInflation time.Time `json:"last_inflation"` // time of the last inflation LastInflationChange time.Time `json:"last_inflation_change"` // time which the last inflation rate change @@ -15,16 +15,35 @@ type Minter struct { HourlyProvisions sdk.Int `json:"hourly_provisions"` // current hourly provisions rate } -// minter object for a new minter -func InitialMinter() Minter { +// Create a new minter object +func NewMinter(lastInflation, lastInflationChange time.Time, + inflation sdk.Dec, hourlyProvisions sdk.Int) Minter { + return Minter{ - LastInflation: time.Unix(0, 0), - LastInflationChange: time.Unix(0, 0), - Inflation: sdk.NewDecWithPrec(13, 2), - HourlyProvisions: sdk.NewInt(0), + LastInflation: lastInflation, + LastInflationChange: lastInflationChange, + Inflation: inflation, + HourlyProvisions: hourlyProvisions, } } +// minter object for a new chain +func InitialMinter(inflation sdk.Dec) Minter { + return NewMinter( + time.Unix(0, 0), + time.Unix(0, 0), + sdk.NewDecWithPrec(13, 2), + sdk.NewInt(0), + ) +} + +// default initial minter object for a new chain +func DefaultInitialMinter() Minter { + return InitialMinter( + sdk.NewDecWithPrec(13, 2), + ) +} + func validateMinter(minter Minter) error { if minter.Inflation.LT(sdk.ZeroDec()) { return fmt.Errorf("mint parameter Inflation should be positive, is %s ", minter.Inflation.String()) @@ -38,10 +57,9 @@ func validateMinter(minter Minter) error { var hrsPerYr = sdk.NewDec(8766) // as defined by a julian year of 365.25 days // process provisions for an hour period -// NOTE if ProcessProvisions is called for the first time -// from an InitialMinter, ProcessProvisions will -// effectively only set the blocktime as the default -// HourlyProvisions is 0. +// NOTE if ProcessProvisions is called for the first time from an +// InitialMinter, ProcessProvisions will effectively only set +// the blocktime as the default HourlyProvisions is 0. func (m Minter) ProcessProvisions(params Params, blockTime time.Time) ( minter Minter, provisions sdk.Coin) { diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go index b022b0ec804c..bcf06f720603 100644 --- a/x/mint/minter_test.go +++ b/x/mint/minter_test.go @@ -9,7 +9,7 @@ import ( ) func TestNextInflation(t *testing.T) { - minter := InitialMinter() + minter := DefaultInitialMinter() params := DefaultParams() // Governing Mechanism: @@ -51,3 +51,25 @@ func TestNextInflation(t *testing.T) { "Test Index: %v\nDiff: %v\nExpected: %v\n", i, diffInflation, tc.expChange) } } + +//func TestProcessProvisions(t *testing.T) { + +//minter := InitialMinter() +//params := DefaultParams() + +//// Governing Mechanism: +//// inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange + +//tests := []struct { +//bondedRatio, setInflation, expChange sdk.Dec +//}{} +//for i, tc := range tests { +//minter.Inflation = tc.setInflation + +//inflation := minter.NextInflation(params, tc.bondedRatio) +//diffInflation := inflation.Sub(tc.setInflation) + +//require.True(t, diffInflation.Equal(tc.expChange), +//"Test Index: %v\nDiff: %v\nExpected: %v\n", i, diffInflation, tc.expChange) +//} +//} diff --git a/x/mint/params.go b/x/mint/params.go index 47c9c85480f2..452685c3c4d7 100644 --- a/x/mint/params.go +++ b/x/mint/params.go @@ -2,6 +2,7 @@ package mint import ( "fmt" + stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -16,6 +17,19 @@ type Params struct { GoalBonded sdk.Dec `json:"goal_bonded"` // goal of percent bonded atoms } +func NewParams(mintDenom string, + inflationRateChange, inflationMax, + inflationMin, goalBonded sdk.Dec) Params { + + return Params{ + MintDenom: mintDenom, + InflationRateChange: inflationRateChange, + InflationMax: inflationMax, + InflationMin: inflationMin, + GoalBonded: goalBonded, + } +} + // default minting module parameters func DefaultParams() Params { return Params{ From 32e652f078b7e946d5b4fc4ff6f931dcd427cd4f Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 16 Nov 2018 03:30:13 -0500 Subject: [PATCH 18/60] working writing test --- docs/spec/mint/begin_block.md | 4 ++-- x/mint/abci_app.go | 2 +- x/mint/minter.go | 12 ++++++----- x/mint/minter_test.go | 39 +++++++++++++++++------------------ 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/docs/spec/mint/begin_block.md b/docs/spec/mint/begin_block.md index 7588db38b1b2..957af2b4e8d5 100644 --- a/docs/spec/mint/begin_block.md +++ b/docs/spec/mint/begin_block.md @@ -4,7 +4,7 @@ Inflation occurs at the beginning of each block. -### NextInflation +### NextInflationRate The target annual inflation rate is recalculated for each provisions cycle. The inflation is also subject to a rate change (positive or negative) depending on @@ -12,7 +12,7 @@ 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%. -NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { +NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { inflationRateChangePerYear = (1 - bondedRatio/params.GoalBonded) * params.InflationRateChange inflationRateChange = inflationRateChangePerYear/hrsPerYr diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go index 5ea2c3886803..bea4c1f7a5a9 100644 --- a/x/mint/abci_app.go +++ b/x/mint/abci_app.go @@ -20,7 +20,7 @@ func BeginBlocker(ctx sdk.Context, k Keeper) { // adjust the inflation, hourly-provision rate every hour if blockTime.Sub(minter.LastInflationChange) >= time.Hour { - minter.Inflation = minter.NextInflation(params, bondedRatio) + minter.Inflation = minter.NextInflationRate(params, bondedRatio) minter.LastInflationChange = blockTime minter.HourlyProvisions = minter.NextHourlyProvisions(params, totalSupply) } diff --git a/x/mint/minter.go b/x/mint/minter.go index 88a8ccb00bd0..2439a8be716a 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -46,18 +46,20 @@ func DefaultInitialMinter() Minter { func validateMinter(minter Minter) error { if minter.Inflation.LT(sdk.ZeroDec()) { - return fmt.Errorf("mint parameter Inflation should be positive, is %s ", minter.Inflation.String()) + return fmt.Errorf("mint parameter Inflation should be positive, is %s", + minter.Inflation.String()) } if minter.Inflation.GT(sdk.OneDec()) { - return fmt.Errorf("mint parameter Inflation must be <= 1, is %s", minter.Inflation.String()) + return fmt.Errorf("mint parameter Inflation must be <= 1, is %s", + minter.Inflation.String()) } return nil } var hrsPerYr = sdk.NewDec(8766) // as defined by a julian year of 365.25 days -// process provisions for an hour period -// NOTE if ProcessProvisions is called for the first time from an +// process provisions for the period since the last inflation +// NOTE If ProcessProvisions is called for the first time from an // InitialMinter, ProcessProvisions will effectively only set // the blocktime as the default HourlyProvisions is 0. func (m Minter) ProcessProvisions(params Params, blockTime time.Time) ( @@ -75,7 +77,7 @@ func (m Minter) ProcessProvisions(params Params, blockTime time.Time) ( } // get the new inflation rate for the next hour -func (m Minter) NextInflation(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { +func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { // The target annual inflation rate is recalculated for each previsions cycle. The // inflation is also subject to a rate change (positive or negative) depending on diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go index bcf06f720603..dce73322750c 100644 --- a/x/mint/minter_test.go +++ b/x/mint/minter_test.go @@ -2,6 +2,7 @@ package mint import ( "testing" + "time" "github.com/stretchr/testify/require" @@ -44,7 +45,7 @@ func TestNextInflation(t *testing.T) { for i, tc := range tests { minter.Inflation = tc.setInflation - inflation := minter.NextInflation(params, tc.bondedRatio) + inflation := minter.NextInflationRate(params, tc.bondedRatio) diffInflation := inflation.Sub(tc.setInflation) require.True(t, diffInflation.Equal(tc.expChange), @@ -52,24 +53,22 @@ func TestNextInflation(t *testing.T) { } } -//func TestProcessProvisions(t *testing.T) { - -//minter := InitialMinter() -//params := DefaultParams() - -//// Governing Mechanism: -//// inflationRateChangePerYear = (1- BondedRatio/ GoalBonded) * MaxInflationRateChange - -//tests := []struct { -//bondedRatio, setInflation, expChange sdk.Dec -//}{} -//for i, tc := range tests { -//minter.Inflation = tc.setInflation +func TestProcessProvisions(t *testing.T) { + minter := DefaultInitialMinter() + params := DefaultParams() -//inflation := minter.NextInflation(params, tc.bondedRatio) -//diffInflation := inflation.Sub(tc.setInflation) + tests := []struct { + blockTime time.Time + expProvisions sdk.Coin + }{ + {time.Unix(0, 0), sdk.NewCoin(params.MintDenom, 0)}, + } + for i, tc := range tests { + minter.Inflation = tc.setInflation -//require.True(t, diffInflation.Equal(tc.expChange), -//"Test Index: %v\nDiff: %v\nExpected: %v\n", i, diffInflation, tc.expChange) -//} -//} + minter, provisions := minter.ProcessProvisions(params, expProvisions) + require.True(t, blockTime, minter.LastInflation) + require.True(t, diffInflation.Equal(tc.expChange), + "Test Index: %v\nDiff: %v\nExpected: %v\n", i, diffInflation, tc.expChange) + } +} From 715548340b619a568bba0165ba79b3bd49b65020 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 16 Nov 2018 13:41:27 +0100 Subject: [PATCH 19/60] Remove offset-by-one, must be some other issue --- cmd/gaia/app/export.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index ad6d3695ad12..cd212273c897 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -19,7 +19,7 @@ import ( // export the state of gaia for a genesis file func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { // as if they could withdraw from the start of the next block - ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight() + 1}) + ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) // prepare for fresh start at zero height if forZeroHeight { From 834d47207dee7edfede195714d5a62161297be24 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 16 Nov 2018 14:42:40 +0100 Subject: [PATCH 20/60] 'make format' --- cmd/gaia/app/genesis.go | 1 - cmd/gaia/init/genesis_accts_test.go | 14 +++++++------- docs/examples/democoin/cmd/democoind/main.go | 1 - server/init.go | 2 -- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 06eda890f19b..5ce9ab8b95e0 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -91,7 +91,6 @@ func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) { } } - // Create the core parameters for genesis initialization for gaia // note that the pubkey input is this machines pubkey func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( diff --git a/cmd/gaia/init/genesis_accts_test.go b/cmd/gaia/init/genesis_accts_test.go index 8825a8cd3483..49634a90c4c4 100644 --- a/cmd/gaia/init/genesis_accts_test.go +++ b/cmd/gaia/init/genesis_accts_test.go @@ -25,14 +25,14 @@ func TestAddGenesisAccount(t *testing.T) { }{ { "valid account", - args{ - app.GenesisState{}, - addr1, - sdk.Coins{}, - }, - false}, + args{ + app.GenesisState{}, + addr1, + sdk.Coins{}, + }, + false}, {"dup account", args{ - app.GenesisState{Accounts: []app.GenesisAccount{app.GenesisAccount{Address:addr1}}}, + app.GenesisState{Accounts: []app.GenesisAccount{{Address: addr1}}}, addr1, sdk.Coins{}}, true}, } diff --git a/docs/examples/democoin/cmd/democoind/main.go b/docs/examples/democoin/cmd/democoind/main.go index 2c831ca283b4..8f52340f47ba 100644 --- a/docs/examples/democoin/cmd/democoind/main.go +++ b/docs/examples/democoin/cmd/democoind/main.go @@ -30,7 +30,6 @@ const ( flagClientHome = "home-client" ) - // coolGenAppParams sets up the app_state and appends the cool app state func CoolAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( appState json.RawMessage, err error) { diff --git a/server/init.go b/server/init.go index 3a6c6adae599..e1655c27ea68 100644 --- a/server/init.go +++ b/server/init.go @@ -15,7 +15,6 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) - // SimpleGenTx is a simple genesis tx type SimpleGenTx struct { Addr sdk.AccAddress `json:"addr"` @@ -23,7 +22,6 @@ type SimpleGenTx struct { //_____________________________________________________________________ - // Generate a genesis transaction func SimpleAppGenTx(cdc *codec.Codec, pk crypto.PubKey) ( appGenTx, cliPrint json.RawMessage, validator types.GenesisValidator, err error) { From 7d6ff98c0e019913b39d9b0ba6910108563d338e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 16 Nov 2018 14:54:34 +0100 Subject: [PATCH 21/60] Add iteration functions --- x/distribution/keeper/delegation.go | 18 ++++++++++++++++++ x/distribution/keeper/test_common.go | 21 --------------------- x/distribution/keeper/validator.go | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go index de8937d44aa2..728dfb269f37 100644 --- a/x/distribution/keeper/delegation.go +++ b/x/distribution/keeper/delegation.go @@ -52,6 +52,24 @@ func (k Keeper) RemoveDelegationDistInfos(ctx sdk.Context) { } } +// iterate over all the validator distribution infos +func (k Keeper) IterateDelegationDistInfos(ctx sdk.Context, + fn func(index int64, distInfo types.DelegationDistInfo) (stop bool)) { + + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey) + defer iter.Close() + index := int64(0) + for ; iter.Valid(); iter.Next() { + var ddi types.DelegationDistInfo + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &ddi) + if fn(index, ddi) { + return + } + index++ + } +} + //___________________________________________________________________________________________ // get the delegator withdraw address, return the delegator address if not set diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go index 660abbd0e048..7cc68fcc42d9 100644 --- a/x/distribution/keeper/test_common.go +++ b/x/distribution/keeper/test_common.go @@ -158,24 +158,3 @@ 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.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &vdi) - if fn(index, vdi) { - return - } - index++ - } -} diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index 34f852242843..07b5823fed14 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -50,6 +50,24 @@ func (k Keeper) RemoveValidatorDistInfos(ctx sdk.Context) { } } +// iterate over all the validator distribution infos +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.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &vdi) + if fn(index, vdi) { + return + } + index++ + } +} + // Get the calculated accum of a validator at the current block // without affecting the state. func (k Keeper) GetValidatorAccum(ctx sdk.Context, operatorAddr sdk.ValAddress) (sdk.Dec, sdk.Error) { From c08f90c29c14570ab61425403eaf2512ba8340e0 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 16 Nov 2018 15:07:27 +0100 Subject: [PATCH 22/60] Don't iterate over all accounts --- cmd/gaia/app/export.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index cd212273c897..bbb44f8b5750 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -27,9 +27,15 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js /* Handle fee distribution state. */ // withdraw all delegator & validator rewards - app.accountKeeper.IterateAccounts(ctx, func(acc auth.Account) (stop bool) { - app.distrKeeper.WithdrawDelegationRewardsAll(ctx, acc.GetAddress()) - _ = app.distrKeeper.WithdrawValidatorRewardsAll(ctx, sdk.ValAddress(acc.GetAddress())) + app.distrKeeper.IterateValidatorDistInfos(ctx, func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) { + err := app.distrKeeper.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr) + if err != nil { + panic(err) + } + return false + }) + app.distrKeeper.IterateDelegationDistInfos(ctx, func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) { + app.distrKeeper.WithdrawDelegationRewardsAll(ctx, distInfo.DelegatorAddr) return false }) From b9b979fdce9901256ff7086f5c1c0103d81a0006 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 16 Nov 2018 13:03:47 -0500 Subject: [PATCH 23/60] NextProvision test, some fixes --- x/mint/abci_app.go | 11 +++++++---- x/mint/minter.go | 35 ++++++++++++++++------------------- x/mint/minter_test.go | 32 ++++++++++++++++++++++---------- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go index bea4c1f7a5a9..12b82e87eda8 100644 --- a/x/mint/abci_app.go +++ b/x/mint/abci_app.go @@ -6,20 +6,23 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Called every block, process inflation on the first block of every hour +// Inflate every block, update inflation parameters once per hour func BeginBlocker(ctx sdk.Context, k Keeper) { blockTime := ctx.BlockHeader().Time minter := k.GetMinter(ctx) params := k.GetParams(ctx) - totalSupply := k.sk.TotalPower(ctx) - bondedRatio := k.sk.BondedRatio(ctx) - minter, mintedCoin := minter.ProcessProvisions(params, blockTime) + + mintedCoin := minter.NextProvision(params, blockTime) + minter.LastInflation = blockTime + k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin}) k.sk.InflateSupply(ctx, sdk.NewDecFromInt(mintedCoin.Amount)) // adjust the inflation, hourly-provision rate every hour if blockTime.Sub(minter.LastInflationChange) >= time.Hour { + totalSupply := k.sk.TotalPower(ctx) + bondedRatio := k.sk.BondedRatio(ctx) minter.Inflation = minter.NextInflationRate(params, bondedRatio) minter.LastInflationChange = blockTime minter.HourlyProvisions = minter.NextHourlyProvisions(params, totalSupply) diff --git a/x/mint/minter.go b/x/mint/minter.go index 2439a8be716a..93becdc1fecf 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -58,24 +58,6 @@ func validateMinter(minter Minter) error { var hrsPerYr = sdk.NewDec(8766) // as defined by a julian year of 365.25 days -// process provisions for the period since the last inflation -// NOTE If ProcessProvisions is called for the first time from an -// InitialMinter, ProcessProvisions will effectively only set -// the blocktime as the default HourlyProvisions is 0. -func (m Minter) ProcessProvisions(params Params, blockTime time.Time) ( - minter Minter, provisions sdk.Coin) { - - dur := m.LastInflation.Sub(blockTime).Nanoseconds() - portionOfHour := dur / time.Hour.Nanoseconds() - - provisionsAmt := m.HourlyProvisions.MulRaw(portionOfHour) - provisions = sdk.NewCoin(params.MintDenom, provisionsAmt) - - minter.LastInflation = blockTime - - return m, provisions -} - // get the new inflation rate for the next hour func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { @@ -103,8 +85,23 @@ func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation return inflation } -// get the new hourly inflation provisions rate +// Rather than calculating the relative amount of inflation for each block +// the provisions are calculated once per hour and further divided up each +// block based on this hour value. func (m Minter) NextHourlyProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Int) { provisionsDec := m.Inflation.Mul(totalSupply).Quo(hrsPerYr) return provisionsDec.TruncateInt() } + +// process provisions for the period since the last inflation +// based as a portion of the saved hourly provision +func (m Minter) NextProvision(params Params, blockTime time.Time) sdk.Coin { + + dur := blockTime.Sub(m.LastInflation).Nanoseconds() + provisionAmt := m.HourlyProvisions. + MulRaw(dur). + DivRaw(time.Hour.Nanoseconds()) + + provision := sdk.NewCoin(params.MintDenom, provisionAmt) + return provision +} diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go index dce73322750c..61901497c075 100644 --- a/x/mint/minter_test.go +++ b/x/mint/minter_test.go @@ -53,22 +53,34 @@ func TestNextInflation(t *testing.T) { } } -func TestProcessProvisions(t *testing.T) { - minter := DefaultInitialMinter() +func TestNextProvision(t *testing.T) { + minter := InitialMinter(sdk.NewDecWithPrec(1, 1)) params := DefaultParams() tests := []struct { - blockTime time.Time - expProvisions sdk.Coin + hourlyProvisions int64 + blockTime time.Time + expProvisions sdk.Coin }{ - {time.Unix(0, 0), sdk.NewCoin(params.MintDenom, 0)}, + {3600, time.Unix(0, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(0))}, + {3600, time.Unix(60*60, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(3600))}, + {3600, time.Unix(60, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(60))}, + {3600, time.Unix(1, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(1))}, + + // some truncate cases + {3600, time.Unix(1, 500), sdk.NewCoin(params.MintDenom, sdk.NewInt(1))}, + {3600, time.Unix(1, 999), sdk.NewCoin(params.MintDenom, sdk.NewInt(1))}, + {3601, time.Unix(1, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(1))}, + {3601, time.Unix(1800, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(1800))}, + {3601, time.Unix(3600, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(3601))}, + {3700, time.Unix(1800, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(1850))}, } for i, tc := range tests { - minter.Inflation = tc.setInflation + minter.HourlyProvisions = sdk.NewInt(tc.hourlyProvisions) + provisions := minter.NextProvision(params, tc.blockTime) - minter, provisions := minter.ProcessProvisions(params, expProvisions) - require.True(t, blockTime, minter.LastInflation) - require.True(t, diffInflation.Equal(tc.expChange), - "Test Index: %v\nDiff: %v\nExpected: %v\n", i, diffInflation, tc.expChange) + require.True(t, tc.expProvisions.IsEqual(provisions), + "test: %v\n\tExp: %v\n\tGot: %v\n", + i, tc.expProvisions, provisions) } } From f31a7e825c33f44f88f1480cf4f9efc47842f7a7 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 16 Nov 2018 15:11:56 -0500 Subject: [PATCH 24/60] update mechanism to use average block time, PENDING, wip docs --- PENDING.md | 1 + docs/spec/mint/begin_block.md | 21 +++++++++++++----- docs/spec/mint/state.md | 6 +++-- x/mint/abci_app.go | 10 ++++----- x/mint/minter.go | 42 +++++++++++++++-------------------- x/mint/minter_test.go | 35 +++++++++++++---------------- x/mint/params.go | 8 ++++--- 7 files changed, 62 insertions(+), 61 deletions(-) diff --git a/PENDING.md b/PENDING.md index f699ef4636e5..1502cdf172d0 100644 --- a/PENDING.md +++ b/PENDING.md @@ -10,6 +10,7 @@ BREAKING CHANGES * [cli] [\#2786](https://github.com/cosmos/cosmos-sdk/pull/2786) Fix redelegation command flow * Gaia + * [mint] [\#2825] minting now occurs every block, inflation parameter updates still hourly * SDK * [\#2752](https://github.com/cosmos/cosmos-sdk/pull/2752) Don't hardcode bondable denom. diff --git a/docs/spec/mint/begin_block.md b/docs/spec/mint/begin_block.md index 957af2b4e8d5..50d460e02ade 100644 --- a/docs/spec/mint/begin_block.md +++ b/docs/spec/mint/begin_block.md @@ -2,15 +2,16 @@ ## Inflation -Inflation occurs at the beginning of each block. +Inflation occurs at the beginning of each block, however inflation parameters +are only calculated once per hour. ### NextInflationRate -The target annual inflation rate is recalculated for each provisions cycle. 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%. +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) +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 @@ -26,3 +27,11 @@ NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { } return inflation + +### NextHourlyProvisions + +Rather than directly using the inflation rate to calculate minted tokens for each block, +the minted tokens is calculated once per hour, and then applied to each _________________________________________________________ + +the provisions are calculated once per hour and further divided up each block based +on this hour value. diff --git a/docs/spec/mint/state.md b/docs/spec/mint/state.md index 98e8e63dd237..c267eb5db45d 100644 --- a/docs/spec/mint/state.md +++ b/docs/spec/mint/state.md @@ -8,8 +8,10 @@ The minter is a space for holding current inflation information. ```golang type Minter struct { - InflationLastTime time.Time // block time which the last inflation was processed - Inflation sdk.Dec // current annual inflation rate + LastInflation time.Time // time of the last inflation + LastInflationChange time.Time // time which the last inflation rate change + Inflation sdk.Dec // current annual inflation rate + HourlyProvisions sdk.Int // current hourly provisions rate } ``` diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go index 12b82e87eda8..b58cdcc7b5d9 100644 --- a/x/mint/abci_app.go +++ b/x/mint/abci_app.go @@ -13,19 +13,17 @@ func BeginBlocker(ctx sdk.Context, k Keeper) { minter := k.GetMinter(ctx) params := k.GetParams(ctx) - mintedCoin := minter.NextProvision(params, blockTime) - minter.LastInflation = blockTime - + mintedCoin := minter.BlockProvision(params) k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin}) k.sk.InflateSupply(ctx, sdk.NewDecFromInt(mintedCoin.Amount)) // adjust the inflation, hourly-provision rate every hour - if blockTime.Sub(minter.LastInflationChange) >= time.Hour { + if blockTime.Sub(minter.LastUpdate) >= time.Hour { totalSupply := k.sk.TotalPower(ctx) bondedRatio := k.sk.BondedRatio(ctx) minter.Inflation = minter.NextInflationRate(params, bondedRatio) - minter.LastInflationChange = blockTime - minter.HourlyProvisions = minter.NextHourlyProvisions(params, totalSupply) + minter.AnnualProvisions = minter.NextAnnualProvisions(params, totalSupply) + minter.LastUpdate = blockTime } k.SetMinter(ctx, minter) diff --git a/x/mint/minter.go b/x/mint/minter.go index 93becdc1fecf..00717270287b 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -9,21 +9,19 @@ import ( // Minter represents the minting state type Minter struct { - LastInflation time.Time `json:"last_inflation"` // time of the last inflation - LastInflationChange time.Time `json:"last_inflation_change"` // time which the last inflation rate change - Inflation sdk.Dec `json:"inflation"` // current annual inflation rate - HourlyProvisions sdk.Int `json:"hourly_provisions"` // current hourly provisions rate + 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.Int `json:"annual_provisions"` // current annual exptected provisions } // Create a new minter object -func NewMinter(lastInflation, lastInflationChange time.Time, - inflation sdk.Dec, hourlyProvisions sdk.Int) Minter { +func NewMinter(lastUpdate time.Time, inflation sdk.Dec, + annualProvisions sdk.Int) Minter { return Minter{ - LastInflation: lastInflation, - LastInflationChange: lastInflationChange, - Inflation: inflation, - HourlyProvisions: hourlyProvisions, + LastUpdate: lastUpdate, + Inflation: inflation, + AnnualProvisions: annualProvisions, } } @@ -31,8 +29,7 @@ func NewMinter(lastInflation, lastInflationChange time.Time, func InitialMinter(inflation sdk.Dec) Minter { return NewMinter( time.Unix(0, 0), - time.Unix(0, 0), - sdk.NewDecWithPrec(13, 2), + inflation, sdk.NewInt(0), ) } @@ -59,7 +56,8 @@ func validateMinter(minter Minter) error { 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) { +func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) ( + inflation sdk.Dec) { // The target annual inflation rate is recalculated for each previsions cycle. The // inflation is also subject to a rate change (positive or negative) depending on @@ -85,22 +83,18 @@ func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation return inflation } -// Rather than calculating the relative amount of inflation for each block -// the provisions are calculated once per hour and further divided up each -// block based on this hour value. -func (m Minter) NextHourlyProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Int) { +// calculate the annual provisions based on current total supply and inflation rate +func (m Minter) NextAnnualProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Int) { provisionsDec := m.Inflation.Mul(totalSupply).Quo(hrsPerYr) return provisionsDec.TruncateInt() } -// process provisions for the period since the last inflation -// based as a portion of the saved hourly provision -func (m Minter) NextProvision(params Params, blockTime time.Time) sdk.Coin { +// process provisions for a block based on average +// param block creation rate from provisions +func (m Minter) BlockProvision(params Params) sdk.Coin { - dur := blockTime.Sub(m.LastInflation).Nanoseconds() - provisionAmt := m.HourlyProvisions. - MulRaw(dur). - DivRaw(time.Hour.Nanoseconds()) + provisionAmt := m.AnnualProvisions. + DivRaw(params.BlocksPerYear) provision := sdk.NewCoin(params.MintDenom, provisionAmt) return provision diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go index 61901497c075..e221e62494fc 100644 --- a/x/mint/minter_test.go +++ b/x/mint/minter_test.go @@ -2,7 +2,6 @@ package mint import ( "testing" - "time" "github.com/stretchr/testify/require" @@ -53,33 +52,29 @@ func TestNextInflation(t *testing.T) { } } -func TestNextProvision(t *testing.T) { +func TestBlockProvision(t *testing.T) { minter := InitialMinter(sdk.NewDecWithPrec(1, 1)) params := DefaultParams() + secondsPerYear := int64(60 * 60 * 24 * 365) + tests := []struct { - hourlyProvisions int64 - blockTime time.Time - expProvisions sdk.Coin + annualProvisions int64 + expProvisions int64 }{ - {3600, time.Unix(0, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(0))}, - {3600, time.Unix(60*60, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(3600))}, - {3600, time.Unix(60, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(60))}, - {3600, time.Unix(1, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(1))}, - - // some truncate cases - {3600, time.Unix(1, 500), sdk.NewCoin(params.MintDenom, sdk.NewInt(1))}, - {3600, time.Unix(1, 999), sdk.NewCoin(params.MintDenom, sdk.NewInt(1))}, - {3601, time.Unix(1, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(1))}, - {3601, time.Unix(1800, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(1800))}, - {3601, time.Unix(3600, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(3601))}, - {3700, time.Unix(1800, 0), sdk.NewCoin(params.MintDenom, sdk.NewInt(1850))}, + {secondsPerYear / 5, 1}, + {secondsPerYear/5 + 1, 1}, + {(secondsPerYear / 5) * 2, 2}, + {(secondsPerYear / 5) / 2, 0}, } for i, tc := range tests { - minter.HourlyProvisions = sdk.NewInt(tc.hourlyProvisions) - provisions := minter.NextProvision(params, tc.blockTime) + minter.AnnualProvisions = sdk.NewInt(tc.annualProvisions) + provisions := minter.BlockProvision(params) + + expProvisions := sdk.NewCoin(params.MintDenom, + sdk.NewInt(tc.expProvisions)) - require.True(t, tc.expProvisions.IsEqual(provisions), + require.True(t, expProvisions.IsEqual(provisions), "test: %v\n\tExp: %v\n\tGot: %v\n", i, tc.expProvisions, provisions) } diff --git a/x/mint/params.go b/x/mint/params.go index 452685c3c4d7..039d4b6b24ba 100644 --- a/x/mint/params.go +++ b/x/mint/params.go @@ -15,11 +15,11 @@ type Params struct { InflationMax sdk.Dec `json:"inflation_max"` // maximum inflation rate InflationMin sdk.Dec `json:"inflation_min"` // minimum inflation rate GoalBonded sdk.Dec `json:"goal_bonded"` // goal of percent bonded atoms + BlocksPerYear int64 `json:"blocks_per_year"` // expected blocks per year } -func NewParams(mintDenom string, - inflationRateChange, inflationMax, - inflationMin, goalBonded sdk.Dec) Params { +func NewParams(mintDenom string, inflationRateChange, inflationMax, + inflationMin, goalBonded sdk.Dec, blocksPerYear int64) Params { return Params{ MintDenom: mintDenom, @@ -27,6 +27,7 @@ func NewParams(mintDenom string, InflationMax: inflationMax, InflationMin: inflationMin, GoalBonded: goalBonded, + BlocksPerYear: blocksPerYear, } } @@ -38,6 +39,7 @@ func DefaultParams() Params { InflationMax: sdk.NewDecWithPrec(20, 2), InflationMin: sdk.NewDecWithPrec(7, 2), GoalBonded: sdk.NewDecWithPrec(67, 2), + BlocksPerYear: int64(60 * 60 * 24 * 365 / 5), // assuming 5 second block times } } From 3dc8ab1282c785083b11f128a758ce173211576b Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 16 Nov 2018 15:40:46 -0500 Subject: [PATCH 25/60] 1 line compile fix --- cmd/gaia/app/sim_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 8e2550a30fe2..da6f8794e153 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -116,7 +116,8 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { sdk.NewDecWithPrec(int64(r.Intn(99)), 2), sdk.NewDecWithPrec(20, 2), sdk.NewDecWithPrec(7, 2), - sdk.NewDecWithPrec(67, 2)), + sdk.NewDecWithPrec(67, 2), + int64(60*60*24*365/5)), } fmt.Printf("Selected randomly generated minting parameters: %v\n", mintGenesis) var validators []stake.Validator From 5b0f3c980a212e25b2dcb6feda98abcf74b52334 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 16 Nov 2018 15:52:23 -0500 Subject: [PATCH 26/60] finish updating docs --- docs/spec/mint/begin_block.md | 32 +++++++++++++++++++++++--------- docs/spec/mint/state.md | 8 ++++---- x/mint/minter.go | 17 +++++++---------- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/docs/spec/mint/begin_block.md b/docs/spec/mint/begin_block.md index 50d460e02ade..31c4256556c0 100644 --- a/docs/spec/mint/begin_block.md +++ b/docs/spec/mint/begin_block.md @@ -1,11 +1,9 @@ # Begin-Block -## Inflation - -Inflation occurs at the beginning of each block, however inflation parameters +Inflation occurs at the beginning of each block, however minting parameters are only calculated once per hour. -### NextInflationRate +## 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) @@ -13,6 +11,7 @@ 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 @@ -27,11 +26,26 @@ NextInflationRate(params Params, bondedRatio sdk.Dec) (inflation sdk.Dec) { } return inflation +``` + +## NextAnnualProvisions + +Calculate the annual provisions based on current total supply and inflation +rate. This parameter is calculated once per block. + +``` +NextAnnualProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Int) { + provisionsDec = Inflation * totalSupply + return provisionsDec.Truncate() +``` -### NextHourlyProvisions +## BlockProvision -Rather than directly using the inflation rate to calculate minted tokens for each block, -the minted tokens is calculated once per hour, and then applied to each _________________________________________________________ +Calculate the provisions generated for each block based on current +annual provisions -the provisions are calculated once per hour and further divided up each block based -on this hour value. +``` +BlockProvision(params Params) sdk.Coin { + provisionAmt = AnnualProvisions/ params.BlocksPerYear + return sdk.NewCoin(params.MintDenom, provisionAmt) +``` diff --git a/docs/spec/mint/state.md b/docs/spec/mint/state.md index c267eb5db45d..b0423bdc8472 100644 --- a/docs/spec/mint/state.md +++ b/docs/spec/mint/state.md @@ -8,10 +8,9 @@ The minter is a space for holding current inflation information. ```golang type Minter struct { - LastInflation time.Time // time of the last inflation - LastInflationChange time.Time // time which the last inflation rate change - Inflation sdk.Dec // current annual inflation rate - HourlyProvisions sdk.Int // current hourly provisions rate + LastUpdate time.Time // time which the last update was made to the minter + Inflation sdk.Dec // current annual inflation rate + AnnualProvisions sdk.Int // current annual exptected provisions } ``` @@ -28,6 +27,7 @@ type Params struct { InflationMax sdk.Dec // maximum inflation rate InflationMin sdk.Dec // minimum inflation rate GoalBonded sdk.Dec // goal of percent bonded atoms + BlocksPerYear int64 // expected blocks per year } ``` diff --git a/x/mint/minter.go b/x/mint/minter.go index 00717270287b..423e75ee789a 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -84,18 +84,15 @@ func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) ( } // calculate the annual provisions based on current total supply and inflation rate -func (m Minter) NextAnnualProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Int) { - provisionsDec := m.Inflation.Mul(totalSupply).Quo(hrsPerYr) +func (m Minter) NextAnnualProvisions(params Params, totalSupply sdk.Dec) ( + provisions sdk.Int) { + + provisionsDec := m.Inflation.MulInt(totalSupply) return provisionsDec.TruncateInt() } -// process provisions for a block based on average -// param block creation rate from provisions +// get the provisions for a block based on the annual provisions rate func (m Minter) BlockProvision(params Params) sdk.Coin { - - provisionAmt := m.AnnualProvisions. - DivRaw(params.BlocksPerYear) - - provision := sdk.NewCoin(params.MintDenom, provisionAmt) - return provision + provisionAmt := m.AnnualProvisions.DivRaw(params.BlocksPerYear) + return sdk.NewCoin(params.MintDenom, provisionAmt) } From 5f70992e5cb5c22da7aad9e567be178a3f93290a Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 16 Nov 2018 15:54:36 -0500 Subject: [PATCH 27/60] compile fix --- x/mint/minter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/mint/minter.go b/x/mint/minter.go index 423e75ee789a..4caee2537c01 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -87,7 +87,7 @@ func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) ( func (m Minter) NextAnnualProvisions(params Params, totalSupply sdk.Dec) ( provisions sdk.Int) { - provisionsDec := m.Inflation.MulInt(totalSupply) + provisionsDec := m.Inflation.Mul(totalSupply) return provisionsDec.TruncateInt() } From 01a9246e36b9cb8bb496f642c074bcb9ec0d37c6 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 19 Nov 2018 16:08:20 -0500 Subject: [PATCH 28/60] address @cwgoes comments --- docs/spec/mint/state.md | 2 +- x/mint/abci_app.go | 15 ++++++++------- x/mint/minter.go | 6 +----- x/mint/minter_test.go | 2 +- x/mint/params.go | 4 ++-- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/docs/spec/mint/state.md b/docs/spec/mint/state.md index b0423bdc8472..a2368ba41d52 100644 --- a/docs/spec/mint/state.md +++ b/docs/spec/mint/state.md @@ -27,7 +27,7 @@ type Params struct { InflationMax sdk.Dec // maximum inflation rate InflationMin sdk.Dec // minimum inflation rate GoalBonded sdk.Dec // goal of percent bonded atoms - BlocksPerYear int64 // expected blocks per year + BlocksPerYear uint64 // expected blocks per year } ``` diff --git a/x/mint/abci_app.go b/x/mint/abci_app.go index b58cdcc7b5d9..963039961a6b 100644 --- a/x/mint/abci_app.go +++ b/x/mint/abci_app.go @@ -17,14 +17,15 @@ func BeginBlocker(ctx sdk.Context, k Keeper) { k.fck.AddCollectedFees(ctx, sdk.Coins{mintedCoin}) k.sk.InflateSupply(ctx, sdk.NewDecFromInt(mintedCoin.Amount)) - // adjust the inflation, hourly-provision rate every hour - if blockTime.Sub(minter.LastUpdate) >= time.Hour { - 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 + if blockTime.Sub(minter.LastUpdate) < time.Hour { + return } + // adjust the inflation, hourly-provision rate every hour + 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) } diff --git a/x/mint/minter.go b/x/mint/minter.go index 4caee2537c01..76f2c7dddfee 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -11,7 +11,7 @@ import ( 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.Int `json:"annual_provisions"` // current annual exptected provisions + AnnualProvisions sdk.Int `json:"annual_provisions"` // current annual expected provisions } // Create a new minter object @@ -46,10 +46,6 @@ func validateMinter(minter Minter) error { return fmt.Errorf("mint parameter Inflation should be positive, is %s", minter.Inflation.String()) } - if minter.Inflation.GT(sdk.OneDec()) { - return fmt.Errorf("mint parameter Inflation must be <= 1, is %s", - minter.Inflation.String()) - } return nil } diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go index e221e62494fc..e31b65f35d61 100644 --- a/x/mint/minter_test.go +++ b/x/mint/minter_test.go @@ -56,7 +56,7 @@ func TestBlockProvision(t *testing.T) { minter := InitialMinter(sdk.NewDecWithPrec(1, 1)) params := DefaultParams() - secondsPerYear := int64(60 * 60 * 24 * 365) + secondsPerYear := int64(60 * 60 * 8766) tests := []struct { annualProvisions int64 diff --git a/x/mint/params.go b/x/mint/params.go index 039d4b6b24ba..f6244400748c 100644 --- a/x/mint/params.go +++ b/x/mint/params.go @@ -15,7 +15,7 @@ type Params struct { InflationMax sdk.Dec `json:"inflation_max"` // maximum inflation rate InflationMin sdk.Dec `json:"inflation_min"` // minimum inflation rate GoalBonded sdk.Dec `json:"goal_bonded"` // goal of percent bonded atoms - BlocksPerYear int64 `json:"blocks_per_year"` // expected blocks per year + BlocksPerYear uint64 `json:"blocks_per_year"` // expected blocks per year } func NewParams(mintDenom string, inflationRateChange, inflationMax, @@ -39,7 +39,7 @@ func DefaultParams() Params { InflationMax: sdk.NewDecWithPrec(20, 2), InflationMin: sdk.NewDecWithPrec(7, 2), GoalBonded: sdk.NewDecWithPrec(67, 2), - BlocksPerYear: int64(60 * 60 * 24 * 365 / 5), // assuming 5 second block times + BlocksPerYear: uint64(60 * 60 * 8766 / 5), // assuming 5 second block times } } From bb8c8ed66f74dd388c19c35525ae8e59935ba3f4 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 19 Nov 2018 16:20:10 -0500 Subject: [PATCH 29/60] uint fixes --- cmd/gaia/app/sim_test.go | 2 +- x/mint/minter.go | 2 +- x/mint/params.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index da6f8794e153..c2f8057f42dc 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -117,7 +117,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { sdk.NewDecWithPrec(20, 2), sdk.NewDecWithPrec(7, 2), sdk.NewDecWithPrec(67, 2), - int64(60*60*24*365/5)), + uint64(60*60*8766/5)), } fmt.Printf("Selected randomly generated minting parameters: %v\n", mintGenesis) var validators []stake.Validator diff --git a/x/mint/minter.go b/x/mint/minter.go index 76f2c7dddfee..4097ad60cebb 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -89,6 +89,6 @@ func (m Minter) NextAnnualProvisions(params Params, totalSupply sdk.Dec) ( // get the provisions for a block based on the annual provisions rate func (m Minter) BlockProvision(params Params) sdk.Coin { - provisionAmt := m.AnnualProvisions.DivRaw(params.BlocksPerYear) + provisionAmt := m.AnnualProvisions.DivRaw(int64(params.BlocksPerYear)) return sdk.NewCoin(params.MintDenom, provisionAmt) } diff --git a/x/mint/params.go b/x/mint/params.go index f6244400748c..e1acd3a63014 100644 --- a/x/mint/params.go +++ b/x/mint/params.go @@ -19,7 +19,7 @@ type Params struct { } func NewParams(mintDenom string, inflationRateChange, inflationMax, - inflationMin, goalBonded sdk.Dec, blocksPerYear int64) Params { + inflationMin, goalBonded sdk.Dec, blocksPerYear uint64) Params { return Params{ MintDenom: mintDenom, From fa469c2319b8efe2fbd619a64f2d7991d8bfa3b7 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Nov 2018 01:30:33 -0500 Subject: [PATCH 30/60] benchmark using Int --- x/mint/minter_test.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go index e31b65f35d61..5e062d51a3e0 100644 --- a/x/mint/minter_test.go +++ b/x/mint/minter_test.go @@ -1,6 +1,7 @@ package mint import ( + "math/rand" "testing" "github.com/stretchr/testify/require" @@ -79,3 +80,19 @@ func TestBlockProvision(t *testing.T) { i, tc.expProvisions, provisions) } } + +// using sdk.Int operations: +// BenchmarkBlockProvision-4 5000000 220 ns/op +func BenchmarkBlockProvision(b *testing.B) { + minter := InitialMinter(sdk.NewDecWithPrec(1, 1)) + params := DefaultParams() + + s1 := rand.NewSource(100) + r1 := rand.New(s1) + minter.AnnualProvisions = sdk.NewInt(r1.Int63n(1000000)) + + // run the Fib function b.N times + for n := 0; n < b.N; n++ { + minter.BlockProvision(params) + } +} From e1af6c68ef64444d4557d67db8390749a34bb1bd Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Nov 2018 02:06:15 -0500 Subject: [PATCH 31/60] use decimal --- x/mint/minter.go | 17 ++++++++--------- x/mint/minter_test.go | 10 +++++++--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/x/mint/minter.go b/x/mint/minter.go index 4097ad60cebb..38ef0eedccf1 100644 --- a/x/mint/minter.go +++ b/x/mint/minter.go @@ -11,12 +11,12 @@ import ( 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.Int `json:"annual_provisions"` // current annual expected provisions + AnnualProvisions sdk.Dec `json:"annual_provisions"` // current annual expected provisions } // Create a new minter object -func NewMinter(lastUpdate time.Time, inflation sdk.Dec, - annualProvisions sdk.Int) Minter { +func NewMinter(lastUpdate time.Time, inflation, + annualProvisions sdk.Dec) Minter { return Minter{ LastUpdate: lastUpdate, @@ -30,7 +30,7 @@ func InitialMinter(inflation sdk.Dec) Minter { return NewMinter( time.Unix(0, 0), inflation, - sdk.NewInt(0), + sdk.NewDec(0), ) } @@ -81,14 +81,13 @@ func (m Minter) NextInflationRate(params Params, bondedRatio sdk.Dec) ( // calculate the annual provisions based on current total supply and inflation rate func (m Minter) NextAnnualProvisions(params Params, totalSupply sdk.Dec) ( - provisions sdk.Int) { + provisions sdk.Dec) { - provisionsDec := m.Inflation.Mul(totalSupply) - return provisionsDec.TruncateInt() + return m.Inflation.Mul(totalSupply) } // get the provisions for a block based on the annual provisions rate func (m Minter) BlockProvision(params Params) sdk.Coin { - provisionAmt := m.AnnualProvisions.DivRaw(int64(params.BlocksPerYear)) - return sdk.NewCoin(params.MintDenom, provisionAmt) + provisionAmt := m.AnnualProvisions.QuoInt(sdk.NewInt(int64(params.BlocksPerYear))) + return sdk.NewCoin(params.MintDenom, provisionAmt.TruncateInt()) } diff --git a/x/mint/minter_test.go b/x/mint/minter_test.go index 5e062d51a3e0..a393b3a048cb 100644 --- a/x/mint/minter_test.go +++ b/x/mint/minter_test.go @@ -69,7 +69,7 @@ func TestBlockProvision(t *testing.T) { {(secondsPerYear / 5) / 2, 0}, } for i, tc := range tests { - minter.AnnualProvisions = sdk.NewInt(tc.annualProvisions) + minter.AnnualProvisions = sdk.NewDec(tc.annualProvisions) provisions := minter.BlockProvision(params) expProvisions := sdk.NewCoin(params.MintDenom, @@ -81,15 +81,19 @@ func TestBlockProvision(t *testing.T) { } } -// using sdk.Int operations: +// Benchmarking :) +// previously using sdk.Int operations: // BenchmarkBlockProvision-4 5000000 220 ns/op +// +// using sdk.Dec operations: (current implementation) +// BenchmarkBlockProvision-4 3000000 429 ns/op func BenchmarkBlockProvision(b *testing.B) { minter := InitialMinter(sdk.NewDecWithPrec(1, 1)) params := DefaultParams() s1 := rand.NewSource(100) r1 := rand.New(s1) - minter.AnnualProvisions = sdk.NewInt(r1.Int63n(1000000)) + minter.AnnualProvisions = sdk.NewDec(r1.Int63n(1000000)) // run the Fib function b.N times for n := 0; n < b.N; n++ { From 0f0ad9d973519f33b541a1a9cc7bfe91af6c5540 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 20 Nov 2018 14:41:19 +0100 Subject: [PATCH 32/60] Address @rigelrozanski comments --- cmd/gaia/app/export.go | 3 +-- x/stake/handler_test.go | 2 -- x/stake/keeper/delegation.go | 4 +--- x/stake/types/delegation.go | 6 ------ 4 files changed, 2 insertions(+), 13 deletions(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index bbb44f8b5750..79e049c4713e 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -40,6 +40,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js }) // delete all distribution infos + // these will be recreated in InitGenesis app.distrKeeper.RemoveValidatorDistInfos(ctx) app.distrKeeper.RemoveDelegationDistInfos(ctx) @@ -76,8 +77,6 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js counter++ } - // AFAICT we do not need to reset bond heights since they are unused. - /* Handle slashing state. */ // we have to clear the slashing periods, since they reference heights diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index fcc268f558f5..81d03de0585b 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -341,8 +341,6 @@ func TestIncrementsMsgDelegate(t *testing.T) { expDelegatorShares := int64(i+2) * bondAmount // (1 self delegation) expDelegatorAcc := sdk.NewInt(initBond - expBond) - require.Equal(t, bond.Height, int64(i), "Incorrect bond height") - gotBond := bond.Shares.RoundInt64() gotDelegatorShares := validator.DelegatorShares.RoundInt64() gotDelegatorAcc := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(params.BondDenom) diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 32f63f5ed47a..8b9e38a4f256 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -415,7 +415,6 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co // Update delegation delegation.Shares = delegation.Shares.Add(newShares) - delegation.Height = ctx.BlockHeight() k.SetDelegation(ctx, delegation) return newShares, nil @@ -462,8 +461,7 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA k.RemoveDelegation(ctx, delegation) } else { - // Update height - delegation.Height = ctx.BlockHeight() + // update the delegation k.SetDelegation(ctx, delegation) } diff --git a/x/stake/types/delegation.go b/x/stake/types/delegation.go index e7315542741d..0d49b1db58ad 100644 --- a/x/stake/types/delegation.go +++ b/x/stake/types/delegation.go @@ -33,19 +33,16 @@ type Delegation struct { DelegatorAddr sdk.AccAddress `json:"delegator_addr"` ValidatorAddr sdk.ValAddress `json:"validator_addr"` Shares sdk.Dec `json:"shares"` - Height int64 `json:"height"` // Last height bond updated } type delegationValue struct { Shares sdk.Dec - Height int64 } // return the delegation without fields contained within the key for the store func MustMarshalDelegation(cdc *codec.Codec, delegation Delegation) []byte { val := delegationValue{ delegation.Shares, - delegation.Height, } return cdc.MustMarshalBinaryLengthPrefixed(val) } @@ -81,7 +78,6 @@ func UnmarshalDelegation(cdc *codec.Codec, key, value []byte) (delegation Delega DelegatorAddr: delAddr, ValidatorAddr: valAddr, Shares: storeValue.Shares, - Height: storeValue.Height, }, nil } @@ -89,7 +85,6 @@ func UnmarshalDelegation(cdc *codec.Codec, key, value []byte) (delegation Delega func (d Delegation) Equal(d2 Delegation) bool { return bytes.Equal(d.DelegatorAddr, d2.DelegatorAddr) && bytes.Equal(d.ValidatorAddr, d2.ValidatorAddr) && - d.Height == d2.Height && d.Shares.Equal(d2.Shares) } @@ -109,7 +104,6 @@ func (d Delegation) HumanReadableString() (string, error) { resp += fmt.Sprintf("Delegator: %s\n", d.DelegatorAddr) resp += fmt.Sprintf("Validator: %s\n", d.ValidatorAddr) resp += fmt.Sprintf("Shares: %s\n", d.Shares.String()) - resp += fmt.Sprintf("Height: %d", d.Height) return resp, nil } From 385cb68e1d344ce056559074460dd2e45f3c10c7 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 20 Nov 2018 14:43:46 +0100 Subject: [PATCH 33/60] Fix testcases --- cmd/gaia/app/sim_test.go | 2 +- x/stake/keeper/delegation_test.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 0527c889a1a4..f1bc5f647a74 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -133,7 +133,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { validator := stake.NewValidator(valAddr, accs[i].PubKey, stake.Description{}) validator.Tokens = sdk.NewDec(amount) validator.DelegatorShares = sdk.NewDec(amount) - delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(amount), 0} + delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(amount)} validators = append(validators, validator) delegations = append(delegations, delegation) } diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 3fa641fd2a18..2310f849fd62 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -56,11 +56,11 @@ func TestDelegation(t *testing.T) { require.True(t, bond1to1.Equal(resBond)) // add some more records - bond1to2 := types.Delegation{addrDels[0], addrVals[1], sdk.NewDec(9), 0} - bond1to3 := types.Delegation{addrDels[0], addrVals[2], sdk.NewDec(9), 1} - bond2to1 := types.Delegation{addrDels[1], addrVals[0], sdk.NewDec(9), 2} - bond2to2 := types.Delegation{addrDels[1], addrVals[1], sdk.NewDec(9), 3} - bond2to3 := types.Delegation{addrDels[1], addrVals[2], sdk.NewDec(9), 4} + bond1to2 := types.Delegation{addrDels[0], addrVals[1], sdk.NewDec(9)} + bond1to3 := types.Delegation{addrDels[0], addrVals[2], sdk.NewDec(9)} + bond2to1 := types.Delegation{addrDels[1], addrVals[0], sdk.NewDec(9)} + bond2to2 := types.Delegation{addrDels[1], addrVals[1], sdk.NewDec(9)} + bond2to3 := types.Delegation{addrDels[1], addrVals[2], sdk.NewDec(9)} keeper.SetDelegation(ctx, bond1to2) keeper.SetDelegation(ctx, bond1to3) keeper.SetDelegation(ctx, bond2to1) From 06e78445c9e41b4b5534becd7c30f30f96416867 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 20 Nov 2018 14:58:14 +0100 Subject: [PATCH 34/60] Add accum invariant --- cmd/gaia/app/export.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index 79e049c4713e..a471a9e6d399 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -24,6 +24,29 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js // prepare for fresh start at zero height if forZeroHeight { + /* TODO XXX check some invariants */ + + height := ctx.BlockHeight() + + valAccum := sdk.ZeroDec() + app.distrKeeper.IterateValidatorDistInfos(ctx, func(_ int64, vdi distr.ValidatorDistInfo) bool { + lastValPower := app.stakeKeeper.GetLastValidatorPower(ctx, vdi.OperatorAddr) + valAccum = valAccum.Add(vdi.GetValAccum(height, sdk.NewDecFromInt(lastValPower))) + return false + }) + + 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 */ + /* Handle fee distribution state. */ // withdraw all delegator & validator rewards From 645e02389479fc669b7e67c86c39537798e9ec28 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Nov 2018 10:05:55 -0500 Subject: [PATCH 35/60] minor invar update --- cmd/gaia/app/export.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index a471a9e6d399..53cfc0a8ef13 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -58,7 +58,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js return false }) app.distrKeeper.IterateDelegationDistInfos(ctx, func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) { - app.distrKeeper.WithdrawDelegationRewardsAll(ctx, distInfo.DelegatorAddr) + app.distrKeeper.WithdrawDelegationReward(ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr) return false }) @@ -74,7 +74,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js } bondDenom := app.stakeKeeper.GetParams(ctx).BondDenom if !feePool.ValPool.AmountOf(bondDenom).IsZero() { - panic(fmt.Sprintf("unexpected leftover validator pool coins: %v", feePool.ValPool.AmountOf(bondDenom))) + panic(fmt.Sprintf("unexpected leftover validator pool coins: %v", feePool.ValPool.AmountOf(bondDenom).String())) } // reset fee pool height, save fee pool From 4d1131381053c95d4db8593ebfdb79f3e41c5fc5 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Nov 2018 10:14:39 -0500 Subject: [PATCH 36/60] doc update --- docs/spec/mint/begin_block.md | 7 +++---- docs/spec/mint/state.md | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/spec/mint/begin_block.md b/docs/spec/mint/begin_block.md index 31c4256556c0..9207141472cb 100644 --- a/docs/spec/mint/begin_block.md +++ b/docs/spec/mint/begin_block.md @@ -34,9 +34,8 @@ Calculate the annual provisions based on current total supply and inflation rate. This parameter is calculated once per block. ``` -NextAnnualProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Int) { - provisionsDec = Inflation * totalSupply - return provisionsDec.Truncate() +NextAnnualProvisions(params Params, totalSupply sdk.Dec) (provisions sdk.Dec) { + return Inflation * totalSupply ``` ## BlockProvision @@ -47,5 +46,5 @@ annual provisions ``` BlockProvision(params Params) sdk.Coin { provisionAmt = AnnualProvisions/ params.BlocksPerYear - return sdk.NewCoin(params.MintDenom, provisionAmt) + return sdk.NewCoin(params.MintDenom, provisionAmt.Truncate()) ``` diff --git a/docs/spec/mint/state.md b/docs/spec/mint/state.md index a2368ba41d52..c3133296ea5c 100644 --- a/docs/spec/mint/state.md +++ b/docs/spec/mint/state.md @@ -10,7 +10,7 @@ The minter is a space for holding current inflation information. 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.Int // current annual exptected provisions + AnnualProvisions sdk.Dec // current annual exptected provisions } ``` From 9c74ae117f734c3c70a49090da448ea8917a9772 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Nov 2018 10:53:52 -0500 Subject: [PATCH 37/60] lint --- x/auth/ante.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/auth/ante.go b/x/auth/ante.go index 9a7a15e3e9e9..7ac245cf08a2 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -310,7 +310,7 @@ func ensureSufficientMempoolFees(ctx sdk.Context, stdTx StdTx) sdk.Result { if stdTx.Fee.Gas <= 0 { return sdk.ErrInternal(fmt.Sprintf("invalid gas supplied: %d", stdTx.Fee.Gas)).Result() } - requiredFees := adjustFeesByGas(ctx.MinimumFees(), uint64(stdTx.Fee.Gas)) + requiredFees := adjustFeesByGas(ctx.MinimumFees(), stdTx.Fee.Gas) // NOTE: !A.IsAllGTE(B) is not the same as A.IsAllLT(B). if !ctx.MinimumFees().IsZero() && !stdTx.Fee.Amount.IsAllGTE(requiredFees) { From 00b64974deea042219be1293f8e3ae53194c27ee Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 20 Nov 2018 18:37:39 +0100 Subject: [PATCH 38/60] Linter fix --- cmd/gaia/app/export.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index 53cfc0a8ef13..8c58142085d7 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -58,7 +58,10 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js return false }) app.distrKeeper.IterateDelegationDistInfos(ctx, func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) { - app.distrKeeper.WithdrawDelegationReward(ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr) + err := app.distrKeeper.WithdrawDelegationReward(ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr) + if err != nil { + panic(err) + } return false }) From 8b082e964eea7cddcc2febe0b199254628b2763c Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Nov 2018 14:31:12 -0500 Subject: [PATCH 39/60] defensive checks --- types/coin.go | 2 +- types/decimal.go | 2 ++ x/distribution/keeper/validator.go | 4 ++++ x/distribution/types/dec_coin.go | 10 +++++++++ x/distribution/types/delegator_info.go | 30 +++++++++++++++++++++++++- 5 files changed, 46 insertions(+), 2 deletions(-) diff --git a/types/coin.go b/types/coin.go index 5f9020e84e23..9742545324be 100644 --- a/types/coin.go +++ b/types/coin.go @@ -28,7 +28,7 @@ type Coin struct { // the amount is negative. func NewCoin(denom string, amount Int) Coin { if amount.LT(ZeroInt()) { - panic("negative coin amount") + panic(fmt.Sprintf("negative coin amount: %v\n", amount)) } return Coin{ diff --git a/types/decimal.go b/types/decimal.go index 3c0e89f60ba5..275747b64715 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -176,6 +176,8 @@ func NewDecFromStr(str string) (d Dec, err Error) { //nolint func (d Dec) IsNil() bool { return d.Int == nil } // is decimal nil func (d Dec) IsZero() bool { return (d.Int).Sign() == 0 } // is equal to zero +func (d Dec) IsNegative() bool { return (d.Int).Sign() == -1 } // is negative +func (d Dec) IsPositive() bool { return (d.Int).Sign() == 1 } // is positive func (d Dec) Equal(d2 Dec) bool { return (d.Int).Cmp(d2.Int) == 0 } // equal decimals func (d Dec) GT(d2 Dec) bool { return (d.Int).Cmp(d2.Int) > 0 } // greater than func (d Dec) GTE(d2 Dec) bool { return (d.Int).Cmp(d2.Int) >= 0 } // greater than or equal diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index d2c755cfa6b5..854b1a331884 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -67,6 +67,10 @@ func (k Keeper) WithdrawValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.Va accAddr := sdk.AccAddress(operatorAddr.Bytes()) withdraw := k.withdrawDelegationRewardsAll(ctx, accAddr) + //if withdraw.AmountOf { + //return types.ErrNoValidatorDistInfo(k.codespace) + //} + // withdrawal validator commission rewards valInfo := k.GetValidatorDistInfo(ctx, operatorAddr) wc := k.GetWithdrawContext(ctx, operatorAddr) diff --git a/x/distribution/types/dec_coin.go b/x/distribution/types/dec_coin.go index 5eedad7e36b3..65bb1c07f7e7 100644 --- a/x/distribution/types/dec_coin.go +++ b/x/distribution/types/dec_coin.go @@ -176,3 +176,13 @@ func (coins DecCoins) AmountOf(denom string) sdk.Dec { } } } + +// returns the amount of a denom from deccoins +func (coins DecCoins) HasNegative() bool { + for _, coin := range coins { + if coin.Amount.IsNegative() { + return true + } + } + return false +} diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index 83ca7f8cc95e..f8f102b268a1 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -1,6 +1,8 @@ package types import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -24,7 +26,13 @@ func NewDelegationDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.Val // Get the calculated accum of this delegator at the provided height func (di DelegationDistInfo) GetDelAccum(height int64, delegatorShares sdk.Dec) sdk.Dec { blocks := height - di.DelPoolWithdrawalHeight - return delegatorShares.MulInt(sdk.NewInt(blocks)) + accum := delegatorShares.MulInt(sdk.NewInt(blocks)) + + // defensive check + if accum.IsNegative() { + panic(fmt.Sprintf("negative accum: %v\n", accum.String())) + } + return accum } // Withdraw rewards from delegator. @@ -50,8 +58,28 @@ func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDis accum := di.GetDelAccum(wc.Height, delegatorShares) di.DelPoolWithdrawalHeight = wc.Height withdrawalTokens := vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum) + + // defensive check for impossible accum ratios + if accum.GT(vi.DelAccum.Accum) { + panic(fmt.Sprintf("accum > vi.DelAccum.Accum:\n", + "\taccum\t\t%v\n"+ + "\tvi.DelAccum.Accum\t%v\n", + accum, vi.DelAccum.Accum)) + } + remDelPool := vi.DelPool.Minus(withdrawalTokens) + // defensive check + if remDelPool.HasNegative() { + panic(fmt.Sprintf("negative remDelPool: %v\n"+ + "\tvi.DelPool\t\t%v\n"+ + "\taccum\t\t\t%v\n"+ + "\tvi.DelAccum.Accum\t%v\n"+ + "\twithdrawalTokens\t%v\n", + remDelPool, vi.DelPool, accum, + vi.DelAccum.Accum, withdrawalTokens)) + } + vi.DelPool = remDelPool vi.DelAccum.Accum = vi.DelAccum.Accum.Sub(accum) From 3f8176b9301d169be646b3744446025346dc6a4b Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Tue, 20 Nov 2018 16:03:57 -0500 Subject: [PATCH 40/60] some debugging output --- cmd/gaia/app/sim_test.go | 16 +++++++++++----- x/distribution/types/delegator_info.go | 16 +++++++++++++--- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 53b901bfe75e..bd800b81d71b 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -59,7 +59,9 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { if numInitiallyBonded > numAccs { numInitiallyBonded = numAccs } - fmt.Printf("Selected randomly generated parameters for simulated genesis: {amount of steak per account: %v, initially bonded validators: %v}\n", amount, numInitiallyBonded) + fmt.Printf("Selected randomly generated parameters for simulated genesis:\n"+ + "\t{amount of steak per account: %v, initially bonded validators: %v}\n", + amount, numInitiallyBonded) // Randomly generate some genesis accounts for _, acc := range accs { @@ -86,7 +88,8 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { GovernancePenalty: sdk.NewDecWithPrec(1, 2), }, } - fmt.Printf("Selected randomly generated governance parameters: %+v\n", govGenesis) + fmt.Printf("Selected randomly generated governance parameters:\n\t%+v\n", govGenesis) + stakeGenesis := stake.GenesisState{ Pool: stake.InitialPool(), Params: stake.Params{ @@ -95,7 +98,8 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { BondDenom: stakeTypes.DefaultBondDenom, }, } - fmt.Printf("Selected randomly generated staking parameters: %+v\n", stakeGenesis) + fmt.Printf("Selected randomly generated staking parameters:\n\t%+v\n", stakeGenesis) + slashingGenesis := slashing.GenesisState{ Params: slashing.Params{ MaxEvidenceAge: stakeGenesis.Params.UnbondingTime, @@ -107,7 +111,8 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { SlashFractionDowntime: sdk.NewDec(1).Quo(sdk.NewDec(int64(r.Intn(200) + 1))), }, } - fmt.Printf("Selected randomly generated slashing parameters: %+v\n", slashingGenesis) + fmt.Printf("Selected randomly generated slashing parameters:\n\t%+v\n", slashingGenesis) + mintGenesis := mint.GenesisState{ Minter: mint.InitialMinter( sdk.NewDecWithPrec(int64(r.Intn(99)), 2)), @@ -119,7 +124,8 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { sdk.NewDecWithPrec(67, 2), uint64(60*60*8766/5)), } - fmt.Printf("Selected randomly generated minting parameters: %v\n", mintGenesis) + fmt.Printf("Selected randomly generated minting parameters:\n\t%+v\n", mintGenesis) + var validators []stake.Validator var delegations []stake.Delegation diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index f8f102b268a1..7db71c93e6c6 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -59,11 +59,21 @@ func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDis di.DelPoolWithdrawalHeight = wc.Height withdrawalTokens := vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum) + if di.ValOperatorAddr.String() == "cosmosvaloper1uqzxjhtvkyqpaprlg37kr8a7wwvjsul255r9uj" && + di.DelegatorAddr.String() == "cosmos1kd0jlj0nwstqgthxx86grwzevfy8psehz0gs0w" { + + fmt.Println("______________________") + fmt.Printf("debug Height: %v\n", wc.Height) + fmt.Printf("debug withdrawalTokens: %v\n", withdrawalTokens) + fmt.Printf("debug accum: %v\n", accum) + fmt.Printf("debug vi.DelAccum.Accum: %v\n", vi.DelAccum.Accum) + } + // defensive check for impossible accum ratios if accum.GT(vi.DelAccum.Accum) { - panic(fmt.Sprintf("accum > vi.DelAccum.Accum:\n", - "\taccum\t\t%v\n"+ - "\tvi.DelAccum.Accum\t%v\n", + panic(fmt.Sprintf("accum > vi.DelAccum.Accum:\n"+ + "\taccum\t\t\t%v\n"+ + "\tvi.DelAccum.Accum\t%v\n", accum, vi.DelAccum.Accum)) } From 17e3bf51d62ca38204d449bb8248ecae773dc9d9 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 21 Nov 2018 11:15:51 +0100 Subject: [PATCH 41/60] Rename initGenesis to initFromGenesisState --- cmd/gaia/app/app.go | 4 ++-- cmd/gaia/app/sim_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 6f0c74b464a4..10248b4263af 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -217,7 +217,7 @@ func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.R } // initialize store from a genesis state -func (app *GaiaApp) initGenesis(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate { +func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisState) []abci.ValidatorUpdate { // sort by account number to maintain consistency sort.Slice(genesisState.Accounts, func(i, j int) bool { return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber @@ -279,7 +279,7 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci // return sdk.ErrGenesisParse("").TraceCause(err, "") } - validators := app.initGenesis(ctx, genesisState) + validators := app.initFromGenesisState(ctx, genesisState) // sanity check if len(req.Validators) > 0 { diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index f1bc5f647a74..7694e21ba084 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -332,7 +332,7 @@ func TestGaiaImportExport(t *testing.T) { panic(err) } ctxB := newApp.NewContext(true, abci.Header{}) - newApp.initGenesis(ctxB, genesisState) + newApp.initFromGenesisState(ctxB, genesisState) fmt.Printf("Comparing stores...\n") ctxA := app.NewContext(true, abci.Header{}) From 3cc2495220ef71a1a354f17c531f1a864c31e28b Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 11:11:21 -0500 Subject: [PATCH 42/60] correct the defensive checks addresses --- x/distribution/types/delegator_info.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index 7db71c93e6c6..1262956fe4d7 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -59,8 +59,8 @@ func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDis di.DelPoolWithdrawalHeight = wc.Height withdrawalTokens := vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum) - if di.ValOperatorAddr.String() == "cosmosvaloper1uqzxjhtvkyqpaprlg37kr8a7wwvjsul255r9uj" && - di.DelegatorAddr.String() == "cosmos1kd0jlj0nwstqgthxx86grwzevfy8psehz0gs0w" { + if di.ValOperatorAddr.String() == "cosmosvaloper1ygk3dqu23ruhnskcnd23zlcnlnxy7jy5mhdye5" && + di.DelegatorAddr.String() == "cosmos1yz59zhqxsacqupf8d0y0e2g40e4uu6vnad455w" { fmt.Println("______________________") fmt.Printf("debug Height: %v\n", wc.Height) From 94c888a8634de48e23630c8b860d681d95820106 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 16:45:31 -0500 Subject: [PATCH 43/60] delegation share invariance --- x/distribution/keeper/hooks.go | 5 +++++ x/distribution/keeper/validator.go | 7 +++++++ x/stake/keeper/sdk_types.go | 4 ++-- x/stake/simulation/invariants.go | 33 ++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/x/distribution/keeper/hooks.go b/x/distribution/keeper/hooks.go index a4f4353fa711..b531a40429a9 100644 --- a/x/distribution/keeper/hooks.go +++ b/x/distribution/keeper/hooks.go @@ -10,6 +10,11 @@ import ( // Create a new validator distribution record func (k Keeper) onValidatorCreated(ctx sdk.Context, valAddr sdk.ValAddress) { + // defensive check for existence + if k.HasValidatorDistInfo(ctx, valAddr) { + panic("validator dist info already exists (not cleaned up properly)") + } + height := ctx.BlockHeight() vdi := types.ValidatorDistInfo{ OperatorAddr: valAddr, diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go index 854b1a331884..1a53b19bc0f1 100644 --- a/x/distribution/keeper/validator.go +++ b/x/distribution/keeper/validator.go @@ -36,6 +36,13 @@ func (k Keeper) SetValidatorDistInfo(ctx sdk.Context, vdi types.ValidatorDistInf // remove a validator distribution info func (k Keeper) RemoveValidatorDistInfo(ctx sdk.Context, valAddr sdk.ValAddress) { + + // defensive check + vdi := k.GetValidatorDistInfo(ctx, valAddr) + if vdi.DelAccum.Accum.IsPositive() { + panic("Should not delete validator with unwithdrawn delegator accum") + } + store := ctx.KVStore(k.storeKey) store.Delete(GetValidatorDistInfoKey(valAddr)) } diff --git a/x/stake/keeper/sdk_types.go b/x/stake/keeper/sdk_types.go index 1dea473f892b..f777077d7d1f 100644 --- a/x/stake/keeper/sdk_types.go +++ b/x/stake/keeper/sdk_types.go @@ -10,7 +10,7 @@ import ( // Implements ValidatorSet var _ sdk.ValidatorSet = Keeper{} -// iterate through the active validator set and perform the provided function +// iterate through the validator set and perform the provided function func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { store := ctx.KVStore(k.storeKey) iterator := sdk.KVStorePrefixIterator(store, ValidatorsKey) @@ -27,7 +27,7 @@ func (k Keeper) IterateValidators(ctx sdk.Context, fn func(index int64, validato iterator.Close() } -// iterate through the active validator set and perform the provided function +// iterate through the bonded validator set and perform the provided function func (k Keeper) IterateBondedValidatorsByPower(ctx sdk.Context, fn func(index int64, validator sdk.Validator) (stop bool)) { store := ctx.KVStore(k.storeKey) maxValidators := k.MaxValidators(ctx) diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 439f40de3b05..5bad3010213b 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -33,6 +33,11 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper, return err } + err = DelegatorSharesInvariant(k)(app) + if err != nil { + return err + } + err = ValidatorSetInvariant(k)(app) return err } @@ -131,6 +136,34 @@ func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { } } +// DelegatorSharesInvariant checks whether all the delegator shares which persist +// in the delegator object add up to the correct total delegator shares +// amount stored in each validator +func DelegatorSharesInvariant(k stake.Keeper) simulation.Invariant { + return func(app *baseapp.BaseApp) error { + ctx := app.NewContext(false, abci.Header{}) + + validators := k.GetAllValidators(ctx) + for _, validator := range validators { + + valTotalDelShares := validator.GetDelegatorShares() + + totalDelShares := sdk.ZeroDec() + delegations := k.GetValidatorDelegations(ctx, validator.GetOperator()) + for _, delegation := range delegations { + totalDelShares = totalDelShares.Add(delegation.Shares) + } + + if !valTotalDelShares.Equal(totalDelShares) { + return fmt.Errorf("broken delegator shares invariance:\n"+ + "\tvalidator.DelegatorShares: %v\n"+ + "\tsum of Delegator.Shares: %v", valTotalDelShares, totalDelShares) + } + } + return nil + } +} + // ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set func ValidatorSetInvariant(k stake.Keeper) simulation.Invariant { return func(app *baseapp.BaseApp) error { From 1480510ec6d6ea0525312d4228a67801b6ecbec2 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 16:47:26 -0500 Subject: [PATCH 44/60] ... --- x/stake/keeper/validator.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index c7919537cf9c..2d43ca348c7e 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -226,6 +226,13 @@ func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) return validators } +// returns an iterator for the consensus validators in the last block +func (k Keeper) AllValidatorsIterator(ctx sdk.Context) (iterator sdk.Iterator) { + store := ctx.KVStore(k.storeKey) + iterator = sdk.KVStorePrefixIterator(store, ValidatorsKey) + return iterator +} + // return a given amount of all the validators func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve uint16) (validators []types.Validator) { store := ctx.KVStore(k.storeKey) From eb2bdcceff596a4e4206568a48e7abc25e234f19 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 16:48:57 -0500 Subject: [PATCH 45/60] ... --- x/stake/keeper/validator.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/x/stake/keeper/validator.go b/x/stake/keeper/validator.go index 2d43ca348c7e..c7919537cf9c 100644 --- a/x/stake/keeper/validator.go +++ b/x/stake/keeper/validator.go @@ -226,13 +226,6 @@ func (k Keeper) GetAllValidators(ctx sdk.Context) (validators []types.Validator) return validators } -// returns an iterator for the consensus validators in the last block -func (k Keeper) AllValidatorsIterator(ctx sdk.Context) (iterator sdk.Iterator) { - store := ctx.KVStore(k.storeKey) - iterator = sdk.KVStorePrefixIterator(store, ValidatorsKey) - return iterator -} - // return a given amount of all the validators func (k Keeper) GetValidators(ctx sdk.Context, maxRetrieve uint16) (validators []types.Validator) { store := ctx.KVStore(k.storeKey) From f4782fe632dd126939cd28b19c50cc72107dfe09 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 17:47:46 -0500 Subject: [PATCH 46/60] del accum invariance --- x/distribution/keeper/test_common.go | 22 +++++++++- x/distribution/simulation/invariants.go | 56 +++++++++++++++++++++++++ x/distribution/types/delegator_info.go | 2 +- 3 files changed, 78 insertions(+), 2 deletions(-) diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go index 660abbd0e048..26033432fcbf 100644 --- a/x/distribution/keeper/test_common.go +++ b/x/distribution/keeper/test_common.go @@ -162,7 +162,8 @@ func (fck DummyFeeCollectionKeeper) ClearCollectedFees(_ sdk.Context) { //__________________________________________________________________________________ // used in simulation -// iterate over all the validator distribution infos (inefficient, just used to check invariants) +// 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)) { @@ -179,3 +180,22 @@ func (k Keeper) IterateValidatorDistInfos(ctx sdk.Context, index++ } } + +// iterate over all the delegation distribution infos (inefficient, just used +// to check invariants) +func (k Keeper) IterateDelegationDistInfos(ctx sdk.Context, + fn func(index int64, distInfo types.DelegationDistInfo) (stop bool)) { + + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey) + defer iter.Close() + index := int64(0) + for ; iter.Valid(); iter.Next() { + var ddi types.DelegationDistInfo + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &ddi) + if fn(index, ddi) { + return + } + index++ + } +} diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index 75863bfdb406..6a2455f6a332 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -18,6 +18,10 @@ func AllInvariants(d distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { if err != nil { return err } + err = DelAccumInvariants(d, sk)(app) + if err != nil { + return err + } return nil } } @@ -48,3 +52,55 @@ func ValAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invaria return nil } } + +// DelAccumInvariants checks that each validator del accum == sum all delegators' accum +func DelAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { + + return func(app *baseapp.BaseApp) error { + mockHeader := abci.Header{Height: app.LastBlockHeight() + 1} + ctx := app.NewContext(false, mockHeader) + height := ctx.BlockHeight() + + totalDelAccumFromVal := make(map[string]sdk.Dec) // key is the valOpAddr string + totalDelAccum := make(map[string]sdk.Dec) + + // iterate the validators + iterVal := func(_ int64, vdi distr.ValidatorDistInfo) bool { + key := vdi.OperatorAddr.String() + validator := sk.Validator(ctx, vdi.OperatorAddr) + totalDelAccumFromVal[key] = vdi.GetTotalDelAccum(height, + validator.GetDelegatorShares()) + + // also initialize the delegation map + totalDelAccum[key] = sdk.ZeroDec() + + return false + } + k.IterateValidatorDistInfos(ctx, iterVal) + + // iterate the delegations + iterDel := func(_ int64, ddi distr.DelegationDistInfo) bool { + key := ddi.ValOperatorAddr.String() + delegation := sk.Delegation(ctx, ddi.DelegatorAddr, ddi.ValOperatorAddr) + totalDelAccum[key] = totalDelAccum[key].Add( + ddi.GetDelAccum(height, delegation.GetShares())) + return false + } + k.IterateDelegationDistInfos(ctx, iterDel) + + // compare + for key, delAccumFromVal := range totalDelAccumFromVal { + sumDelAccum := totalDelAccum[key] + + if !sumDelAccum.Equal(delAccumFromVal) { + return fmt.Errorf("delegator accum invariance: \n"+ + "\tvalidator: %v\n"+ + "\tsum delegator accum: %v\n"+ + "\tvalidator's total delegator accum: %v", + key, sumDelAccum.String(), delAccumFromVal.String()) + } + } + + return nil + } +} diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index 1262956fe4d7..6075ecb12a30 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -59,9 +59,9 @@ func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDis di.DelPoolWithdrawalHeight = wc.Height withdrawalTokens := vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum) + // XXX debugging - delete before merge if di.ValOperatorAddr.String() == "cosmosvaloper1ygk3dqu23ruhnskcnd23zlcnlnxy7jy5mhdye5" && di.DelegatorAddr.String() == "cosmos1yz59zhqxsacqupf8d0y0e2g40e4uu6vnad455w" { - fmt.Println("______________________") fmt.Printf("debug Height: %v\n", wc.Height) fmt.Printf("debug withdrawalTokens: %v\n", withdrawalTokens) From d510df14d069194c7d95611c591a0041421f68b6 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 18:34:02 -0500 Subject: [PATCH 47/60] better invar output --- x/distribution/simulation/invariants.go | 22 ++++++++++++++++++++-- x/distribution/types/delegator_info.go | 16 ++++++++-------- x/stake/keeper/delegation.go | 6 ++++++ 3 files changed, 34 insertions(+), 10 deletions(-) diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index 6a2455f6a332..60e7ae2a7569 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -93,11 +93,29 @@ func DelAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invaria sumDelAccum := totalDelAccum[key] if !sumDelAccum.Equal(delAccumFromVal) { + + logDelAccums := "" + iterDel := func(_ int64, ddi distr.DelegationDistInfo) bool { + keyLog := ddi.ValOperatorAddr.String() + if keyLog == key { + delegation := sk.Delegation(ctx, ddi.DelegatorAddr, ddi.ValOperatorAddr) + accum := ddi.GetDelAccum(height, delegation.GetShares()) + if accum.IsPositive() { + logDelAccums += fmt.Sprintf("\n\t\tdel: %v, accum: %v", + ddi.DelegatorAddr.String(), + accum.String()) + } + } + return false + } + k.IterateDelegationDistInfos(ctx, iterDel) + return fmt.Errorf("delegator accum invariance: \n"+ "\tvalidator: %v\n"+ "\tsum delegator accum: %v\n"+ - "\tvalidator's total delegator accum: %v", - key, sumDelAccum.String(), delAccumFromVal.String()) + "\tvalidator's total delegator accum: %v\n"+ + "\tlog of delegations with accum: %v\n", + key, sumDelAccum.String(), delAccumFromVal.String(), logDelAccums) } } diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index 6075ecb12a30..d28a3eb65faa 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -60,14 +60,14 @@ func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDis withdrawalTokens := vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum) // XXX debugging - delete before merge - if di.ValOperatorAddr.String() == "cosmosvaloper1ygk3dqu23ruhnskcnd23zlcnlnxy7jy5mhdye5" && - di.DelegatorAddr.String() == "cosmos1yz59zhqxsacqupf8d0y0e2g40e4uu6vnad455w" { - fmt.Println("______________________") - fmt.Printf("debug Height: %v\n", wc.Height) - fmt.Printf("debug withdrawalTokens: %v\n", withdrawalTokens) - fmt.Printf("debug accum: %v\n", accum) - fmt.Printf("debug vi.DelAccum.Accum: %v\n", vi.DelAccum.Accum) - } + //if di.ValOperatorAddr.String() == "cosmosvaloper1ygk3dqu23ruhnskcnd23zlcnlnxy7jy5mhdye5" && + //di.DelegatorAddr.String() == "cosmos1yz59zhqxsacqupf8d0y0e2g40e4uu6vnad455w" { + //fmt.Println("______________________") + //fmt.Printf("debug Height: %v\n", wc.Height) + //fmt.Printf("debug withdrawalTokens: %v\n", withdrawalTokens) + //fmt.Printf("debug accum: %v\n", accum) + //fmt.Printf("debug vi.DelAccum.Accum: %v\n", vi.DelAccum.Accum) + //} // defensive check for impossible accum ratios if accum.GT(vi.DelAccum.Accum) { diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 32f63f5ed47a..98903019da61 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -2,6 +2,7 @@ package keeper import ( "bytes" + "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -396,6 +397,11 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co } } + //XXX debug code delete before merge + if bondAmt.Amount.Int64() == 532 { + fmt.Printf("\ndebug found: %v\n", found) + } + // call the appropriate hook if present if found { k.OnDelegationSharesModified(ctx, delAddr, validator.OperatorAddr) From fb8aa02776b492e16656674d936fda95222df6d0 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 19:03:15 -0500 Subject: [PATCH 48/60] PositiveDelegationInvariant --- x/distribution/simulation/invariants.go | 12 ++++++++++-- x/stake/keeper/delegation.go | 11 +++++++++++ x/stake/simulation/invariants.go | 24 ++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index 60e7ae2a7569..128e7faa9ad2 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -110,12 +110,20 @@ func DelAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invaria } k.IterateDelegationDistInfos(ctx, iterDel) + operAddr, err := sdk.ValAddressFromBech32(key) + if err != nil { + panic(err) + } + validator := sk.Validator(ctx, operAddr) + return fmt.Errorf("delegator accum invariance: \n"+ - "\tvalidator: %v\n"+ + "\tvalidator key: %v\n"+ + "\tvalidator: %+v\n"+ "\tsum delegator accum: %v\n"+ "\tvalidator's total delegator accum: %v\n"+ "\tlog of delegations with accum: %v\n", - key, sumDelAccum.String(), delAccumFromVal.String(), logDelAccums) + key, validator, sumDelAccum.String(), + delAccumFromVal.String(), logDelAccums) } } diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 98903019da61..213bef5fbb6d 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -400,6 +400,11 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co //XXX debug code delete before merge if bondAmt.Amount.Int64() == 532 { fmt.Printf("\ndebug found: %v\n", found) + fmt.Printf("debug delegation: %+v\n", delegation) + } + if delAddr.String() == "cosmos1ackgp6j7uuved4uhgjrxkklel0zud77qr9kdkx" && + validator.OperatorAddr.String() == "cosmosvaloper1ygk3dqu23ruhnskcnd23zlcnlnxy7jy5mhdye5" { + fmt.Printf("\ndebug delegation: %+v\n", delegation) } // call the appropriate hook if present @@ -456,6 +461,12 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA // subtract shares from delegator delegation.Shares = delegation.Shares.Sub(shares) + // XXX delete before merge + if delAddr.String() == "cosmos1ackgp6j7uuved4uhgjrxkklel0zud77qr9kdkx" && + valAddr.String() == "cosmosvaloper1ygk3dqu23ruhnskcnd23zlcnlnxy7jy5mhdye5" { + fmt.Printf("\nunbond debug delegation: %+v\n", delegation) + } + // remove the delegation if delegation.Shares.IsZero() { diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 5bad3010213b..44348c673f6c 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -33,6 +33,11 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper, return err } + err = PositiveDelegationInvariant(k)(app) + if err != nil { + return err + } + err = DelegatorSharesInvariant(k)(app) if err != nil { return err @@ -136,6 +141,25 @@ func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { } } +// PositiveDelegationInvariant checks that all stored delegations have > 0 shares. +func PositiveDelegationInvariant(k stake.Keeper) simulation.Invariant { + return func(app *baseapp.BaseApp) error { + ctx := app.NewContext(false, abci.Header{}) + + delegations := k.GetAllDelegations(ctx) + for _, delegation := range delegations { + if delegation.Shares.IsNegative() { + return fmt.Errorf("delegation with negative shares: %+v", delegation) + } + if delegation.Shares.IsZero() { + return fmt.Errorf("delegation with zero shares: %+v", delegation) + } + } + + return nil + } +} + // DelegatorSharesInvariant checks whether all the delegator shares which persist // in the delegator object add up to the correct total delegator shares // amount stored in each validator From 2abab5996a2cb81981163d15dac867bc4436f21b Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 20:20:43 -0500 Subject: [PATCH 49/60] resolved Dec bug --- x/distribution/types/delegator_info.go | 12 +----------- x/stake/keeper/delegation.go | 21 ++++----------------- x/stake/types/errors.go | 4 ++++ 3 files changed, 9 insertions(+), 28 deletions(-) diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index d28a3eb65faa..1d507796bcb4 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -57,17 +57,7 @@ func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDis accum := di.GetDelAccum(wc.Height, delegatorShares) di.DelPoolWithdrawalHeight = wc.Height - withdrawalTokens := vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum) - - // XXX debugging - delete before merge - //if di.ValOperatorAddr.String() == "cosmosvaloper1ygk3dqu23ruhnskcnd23zlcnlnxy7jy5mhdye5" && - //di.DelegatorAddr.String() == "cosmos1yz59zhqxsacqupf8d0y0e2g40e4uu6vnad455w" { - //fmt.Println("______________________") - //fmt.Printf("debug Height: %v\n", wc.Height) - //fmt.Printf("debug withdrawalTokens: %v\n", withdrawalTokens) - //fmt.Printf("debug accum: %v\n", accum) - //fmt.Printf("debug vi.DelAccum.Accum: %v\n", vi.DelAccum.Accum) - //} + withdrawalTokens := vi.DelPool.MulDec(accum.Quo(vi.DelAccum.Accum)) // defensive check for impossible accum ratios if accum.GT(vi.DelAccum.Accum) { diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 213bef5fbb6d..e7ece1c0d3c8 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -2,7 +2,6 @@ package keeper import ( "bytes" - "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -397,16 +396,6 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co } } - //XXX debug code delete before merge - if bondAmt.Amount.Int64() == 532 { - fmt.Printf("\ndebug found: %v\n", found) - fmt.Printf("debug delegation: %+v\n", delegation) - } - if delAddr.String() == "cosmos1ackgp6j7uuved4uhgjrxkklel0zud77qr9kdkx" && - validator.OperatorAddr.String() == "cosmosvaloper1ygk3dqu23ruhnskcnd23zlcnlnxy7jy5mhdye5" { - fmt.Printf("\ndebug delegation: %+v\n", delegation) - } - // call the appropriate hook if present if found { k.OnDelegationSharesModified(ctx, delAddr, validator.OperatorAddr) @@ -461,12 +450,6 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA // subtract shares from delegator delegation.Shares = delegation.Shares.Sub(shares) - // XXX delete before merge - if delAddr.String() == "cosmos1ackgp6j7uuved4uhgjrxkklel0zud77qr9kdkx" && - valAddr.String() == "cosmosvaloper1ygk3dqu23ruhnskcnd23zlcnlnxy7jy5mhdye5" { - fmt.Printf("\nunbond debug delegation: %+v\n", delegation) - } - // remove the delegation if delegation.Shares.IsZero() { @@ -617,6 +600,9 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, } rounded := returnAmount.TruncateInt() + if rounded.IsZero() { //TODO design consideration + return types.Redelegation{}, types.ErrVerySmallRedelegation(k.Codespace()) + } returnCoin := sdk.NewCoin(k.BondDenom(ctx), rounded) change := returnAmount.Sub(sdk.NewDecFromInt(rounded)) @@ -629,6 +615,7 @@ func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, if !found { return types.Redelegation{}, types.ErrBadRedelegationDst(k.Codespace()) } + sharesCreated, err := k.Delegate(ctx, delAddr, returnCoin, dstValidator, false) if err != nil { return types.Redelegation{}, err diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index bbef528d5095..139fa46056de 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -159,6 +159,10 @@ func ErrSelfRedelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "cannot redelegate to the same validator") } +func ErrVerySmallRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "to few tokens to redelegate, truncates to zero tokens") +} + func ErrBadRedelegationDst(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "redelegation validator not found") } From d41eeb1a4d952172379e786315e641e932e25ebd Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 20:23:01 -0500 Subject: [PATCH 50/60] PENDING.md --- PENDING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/PENDING.md b/PENDING.md index 472601fc7e65..92344f8f06eb 100644 --- a/PENDING.md +++ b/PENDING.md @@ -67,6 +67,7 @@ IMPROVEMENTS - [types] #2776 Improve safety of `Coin` and `Coins` types. Various functions and methods will panic when a negative amount is discovered. - #2815 Gas unit fields changed from `int64` to `uint64`. + - #2825 More staking and distribution invariants * Tendermint - #2796 Update to go-amino 0.14.1 From fffc100fa268db57b75054a7d05db81290f19e57 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 21:18:48 -0500 Subject: [PATCH 51/60] test cover fix --- types/decimal.go | 9 +++++++++ x/distribution/types/dec_coin.go | 2 +- x/distribution/types/delegator_info.go | 5 +++++ x/distribution/types/delegator_info_test.go | 5 +++-- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/types/decimal.go b/types/decimal.go index 275747b64715..a898035649b5 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -172,6 +172,15 @@ func NewDecFromStr(str string) (d Dec, err Error) { return Dec{combined}, nil } +// Decimal from string, panic on error +func MustNewDecFromStr(s string) Dec { + dec, err := NewDecFromStr(s) + if err != nil { + panic(err) + } + return dec +} + //______________________________________________________________________________________________ //nolint func (d Dec) IsNil() bool { return d.Int == nil } // is decimal nil diff --git a/x/distribution/types/dec_coin.go b/x/distribution/types/dec_coin.go index 65bb1c07f7e7..68c822b9e03e 100644 --- a/x/distribution/types/dec_coin.go +++ b/x/distribution/types/dec_coin.go @@ -140,7 +140,7 @@ func (coins DecCoins) MulDec(d sdk.Dec) DecCoins { return res } -// divide all the coins by a multiple +// divide all the coins by a decimal func (coins DecCoins) QuoDec(d sdk.Dec) DecCoins { res := make([]DecCoin, len(coins)) for i, coin := range coins { diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index 1d507796bcb4..6326f089f336 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -59,6 +59,11 @@ func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDis di.DelPoolWithdrawalHeight = wc.Height withdrawalTokens := vi.DelPool.MulDec(accum.Quo(vi.DelAccum.Accum)) + fmt.Printf("debug vi.DelPool: %v\n", vi.DelPool) + fmt.Printf("debug withdrawalTokens: %v\n", withdrawalTokens) + fmt.Printf("debug vi.DelAccum.Accum: %v\n", vi.DelAccum.Accum) + fmt.Printf("debug accum: %v\n", accum) + // defensive check for impossible accum ratios if accum.GT(vi.DelAccum.Accum) { panic(fmt.Sprintf("accum > vi.DelAccum.Accum:\n"+ diff --git a/x/distribution/types/delegator_info_test.go b/x/distribution/types/delegator_info_test.go index e15ad293038a..1393d9325013 100644 --- a/x/distribution/types/delegator_info_test.go +++ b/x/distribution/types/delegator_info_test.go @@ -54,7 +54,8 @@ func TestWithdrawRewards(t *testing.T) { assert.Equal(t, height, di2.DelPoolWithdrawalHeight) assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum)) assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValPool[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.DelPool[0].Amount)) + // NOTE minor rounding error + assert.True(sdk.DecEq(t, sdk.MustNewDecFromStr("48.9999999951"), vi.DelPool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(4), vi.ValCommission[0].Amount)) - assert.True(sdk.DecEq(t, sdk.NewDec(98), rewardRecv2[0].Amount)) + assert.True(sdk.DecEq(t, sdk.MustNewDecFromStr("98.0000000049"), rewardRecv2[0].Amount)) } From ccfbfcf216078933183d1a8d7577061b6f38b525 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 21:36:30 -0500 Subject: [PATCH 52/60] ... --- x/distribution/types/delegator_info.go | 12 +++++++----- x/distribution/types/delegator_info_test.go | 5 ++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index 6326f089f336..7e3fd1313a5e 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -57,12 +57,14 @@ func (di DelegationDistInfo) WithdrawRewards(wc WithdrawContext, vi ValidatorDis accum := di.GetDelAccum(wc.Height, delegatorShares) di.DelPoolWithdrawalHeight = wc.Height - withdrawalTokens := vi.DelPool.MulDec(accum.Quo(vi.DelAccum.Accum)) - fmt.Printf("debug vi.DelPool: %v\n", vi.DelPool) - fmt.Printf("debug withdrawalTokens: %v\n", withdrawalTokens) - fmt.Printf("debug vi.DelAccum.Accum: %v\n", vi.DelAccum.Accum) - fmt.Printf("debug accum: %v\n", accum) + var withdrawalTokens DecCoins + if accum.Equal(vi.DelAccum.Accum) { + // required due to rounding faults + withdrawalTokens = vi.DelPool + } else { + withdrawalTokens = vi.DelPool.MulDec(accum).QuoDec(vi.DelAccum.Accum) + } // defensive check for impossible accum ratios if accum.GT(vi.DelAccum.Accum) { diff --git a/x/distribution/types/delegator_info_test.go b/x/distribution/types/delegator_info_test.go index 1393d9325013..e15ad293038a 100644 --- a/x/distribution/types/delegator_info_test.go +++ b/x/distribution/types/delegator_info_test.go @@ -54,8 +54,7 @@ func TestWithdrawRewards(t *testing.T) { assert.Equal(t, height, di2.DelPoolWithdrawalHeight) assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.TotalValAccum.Accum)) assert.True(sdk.DecEq(t, sdk.NewDec(1800), fp.ValPool[0].Amount)) - // NOTE minor rounding error - assert.True(sdk.DecEq(t, sdk.MustNewDecFromStr("48.9999999951"), vi.DelPool[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(49), vi.DelPool[0].Amount)) assert.True(sdk.DecEq(t, sdk.NewDec(4), vi.ValCommission[0].Amount)) - assert.True(sdk.DecEq(t, sdk.MustNewDecFromStr("98.0000000049"), rewardRecv2[0].Amount)) + assert.True(sdk.DecEq(t, sdk.NewDec(98), rewardRecv2[0].Amount)) } From 7c975828d5a3e84a4f0953d0acead6c1431bdc01 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 22:57:30 -0500 Subject: [PATCH 53/60] missing iter.Close(), line length, reduce tab --- cmd/gaia/app/export.go | 200 ++++++++++++++++++++++------------------- 1 file changed, 106 insertions(+), 94 deletions(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index 8c58142085d7..e6227dc2d428 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -17,104 +17,14 @@ import ( ) // export the state of gaia for a genesis file -func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { +func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) ( + appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { + // as if they could withdraw from the start of the next block ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) - // prepare for fresh start at zero height if forZeroHeight { - - /* TODO XXX check some invariants */ - - height := ctx.BlockHeight() - - valAccum := sdk.ZeroDec() - app.distrKeeper.IterateValidatorDistInfos(ctx, func(_ int64, vdi distr.ValidatorDistInfo) bool { - lastValPower := app.stakeKeeper.GetLastValidatorPower(ctx, vdi.OperatorAddr) - valAccum = valAccum.Add(vdi.GetValAccum(height, sdk.NewDecFromInt(lastValPower))) - return false - }) - - 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 */ - - /* Handle fee distribution state. */ - - // withdraw all delegator & validator rewards - app.distrKeeper.IterateValidatorDistInfos(ctx, func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) { - err := app.distrKeeper.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr) - if err != nil { - panic(err) - } - return false - }) - app.distrKeeper.IterateDelegationDistInfos(ctx, func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) { - err := app.distrKeeper.WithdrawDelegationReward(ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr) - if err != nil { - panic(err) - } - return false - }) - - // delete all distribution infos - // these will be recreated in InitGenesis - app.distrKeeper.RemoveValidatorDistInfos(ctx) - app.distrKeeper.RemoveDelegationDistInfos(ctx) - - // assert that the fee pool is empty - feePool := app.distrKeeper.GetFeePool(ctx) - if !feePool.TotalValAccum.Accum.IsZero() { - panic("unexpected leftover validator accum") - } - bondDenom := app.stakeKeeper.GetParams(ctx).BondDenom - if !feePool.ValPool.AmountOf(bondDenom).IsZero() { - panic(fmt.Sprintf("unexpected leftover validator pool coins: %v", feePool.ValPool.AmountOf(bondDenom).String())) - } - - // reset fee pool height, save fee pool - feePool.TotalValAccum.UpdateHeight = 0 - app.distrKeeper.SetFeePool(ctx, feePool) - - /* Handle stake state. */ - - // iterate through validators by power descending, reset bond height, update bond intra-tx counter - store := ctx.KVStore(app.keyStake) - iter := sdk.KVStoreReversePrefixIterator(store, stake.ValidatorsByPowerIndexKey) - counter := int16(0) - for ; iter.Valid(); iter.Next() { - addr := sdk.ValAddress(iter.Value()) - validator, found := app.stakeKeeper.GetValidator(ctx, addr) - if !found { - panic("expected validator, not found") - } - validator.BondHeight = 0 - validator.BondIntraTxCounter = counter - // AFAICT we do not need to reset unbonding height since it is not used. - app.stakeKeeper.SetValidator(ctx, validator) - counter++ - } - - /* Handle slashing state. */ - - // we have to clear the slashing periods, since they reference heights - app.slashingKeeper.DeleteValidatorSlashingPeriods(ctx) - - // reset start height on signing infos - app.slashingKeeper.IterateValidatorSigningInfos(ctx, func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { - info.StartHeight = 0 - app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) - return false - }) - + prepForZeroHeightGenesis(ctx) } // iterate to get the accounts @@ -142,3 +52,105 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) (appState js validators = stake.WriteValidators(ctx, app.stakeKeeper) return appState, validators, nil } + +// prepare for fresh start at zero height +func 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 */ + + /* Handle fee distribution state. */ + + // withdraw all delegator & validator rewards + vdiIter = func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) { + err := app.distrKeeper.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr) + if err != nil { + panic(err) + } + return false + } + app.distrKeeper.IterateValidatorDistInfos(ctx, vdiIter) + + ddiIter := func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) { + err := app.distrKeeper.WithdrawDelegationReward( + ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr) + if err != nil { + panic(err) + } + return false + } + app.distrKeeper.IterateDelegationDistInfos(ctx, ddiIter) + + // delete all distribution infos + // these will be recreated in InitGenesis + app.distrKeeper.RemoveValidatorDistInfos(ctx) + app.distrKeeper.RemoveDelegationDistInfos(ctx) + + // assert that the fee pool is empty + feePool := app.distrKeeper.GetFeePool(ctx) + if !feePool.TotalValAccum.Accum.IsZero() { + panic("unexpected leftover validator accum") + } + bondDenom := app.stakeKeeper.GetParams(ctx).BondDenom + if !feePool.ValPool.AmountOf(bondDenom).IsZero() { + panic(fmt.Sprintf("unexpected leftover validator pool coins: %v", + feePool.ValPool.AmountOf(bondDenom).String())) + } + + // reset fee pool height, save fee pool + feePool.TotalValAccum.UpdateHeight = 0 + app.distrKeeper.SetFeePool(ctx, feePool) + + /* Handle stake state. */ + + // iterate through validators by power descending, reset bond height, update bond intra-tx counter + store := ctx.KVStore(app.keyStake) + iter := sdk.KVStoreReversePrefixIterator(store, stake.ValidatorsByPowerIndexKey) + counter := int16(0) + for ; iter.Valid(); iter.Next() { + addr := sdk.ValAddress(iter.Value()) + validator, found := app.stakeKeeper.GetValidator(ctx, addr) + if !found { + panic("expected validator, not found") + } + validator.BondHeight = 0 + validator.BondIntraTxCounter = counter + // AFAICT we do not need to reset unbonding height since it is not used. + app.stakeKeeper.SetValidator(ctx, validator) + counter++ + } + iter.Close() + + /* Handle slashing state. */ + + // we have to clear the slashing periods, since they reference heights + app.slashingKeeper.DeleteValidatorSlashingPeriods(ctx) + + // reset start height on signing infos + app.slashingKeeper.IterateValidatorSigningInfos(ctx, func(addr sdk.ConsAddress, info slashing.ValidatorSigningInfo) (stop bool) { + info.StartHeight = 0 + app.slashingKeeper.SetValidatorSigningInfo(ctx, addr, info) + return false + }) +} From e38af2a70ad12ad9e66e39b080e2ccd082d1c60e Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Wed, 21 Nov 2018 23:44:34 -0500 Subject: [PATCH 54/60] typo --- cmd/gaia/app/export.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index e6227dc2d428..ec375410ab48 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -24,7 +24,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) ( ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) if forZeroHeight { - prepForZeroHeightGenesis(ctx) + app.prepForZeroHeightGenesis(ctx) } // iterate to get the accounts @@ -54,7 +54,7 @@ func (app *GaiaApp) ExportAppStateAndValidators(forZeroHeight bool) ( } // prepare for fresh start at zero height -func prepForZeroHeightGenesis(ctx sdk.Context) { +func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) { /* TODO XXX check some invariants */ From 822944b79221375568efb22aa780c706f8150d91 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 22 Nov 2018 11:48:53 +0100 Subject: [PATCH 55/60] Set validator.UnbondingHeight to 0 --- cmd/gaia/app/export.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/gaia/app/export.go b/cmd/gaia/app/export.go index ec375410ab48..041f4560e486 100644 --- a/cmd/gaia/app/export.go +++ b/cmd/gaia/app/export.go @@ -136,7 +136,7 @@ func (app *GaiaApp) prepForZeroHeightGenesis(ctx sdk.Context) { } validator.BondHeight = 0 validator.BondIntraTxCounter = counter - // AFAICT we do not need to reset unbonding height since it is not used. + validator.UnbondingHeight = 0 app.stakeKeeper.SetValidator(ctx, validator) counter++ } From dbf5a4b3aa21a1a6534d34434008f1f960705fbb Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 22 Nov 2018 11:49:22 +0100 Subject: [PATCH 56/60] Remove defer() usage --- x/slashing/slashing_period.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/slashing/slashing_period.go b/x/slashing/slashing_period.go index e597497f3071..e726453dc213 100644 --- a/x/slashing/slashing_period.go +++ b/x/slashing/slashing_period.go @@ -70,10 +70,10 @@ func (k Keeper) IterateValidatorSlashingPeriods(ctx sdk.Context, handler func(sl func (k Keeper) DeleteValidatorSlashingPeriods(ctx sdk.Context) { store := ctx.KVStore(k.storeKey) iter := sdk.KVStorePrefixIterator(store, ValidatorSlashingPeriodKey) - defer iter.Close() for ; iter.Valid(); iter.Next() { store.Delete(iter.Key()) } + iter.Close() } // Stored by validator Tendermint address (not operator address) From 685c55d4e18d5efca992670915c780aebdec19f6 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 22 Nov 2018 11:50:10 +0100 Subject: [PATCH 57/60] Tiny cleanup --- cmd/gaia/app/sim_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 7694e21ba084..1cb59bcc48e1 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -378,9 +378,8 @@ func TestGaiaSimulationAfterImport(t *testing.T) { } else { logger = log.NewNopLogger() } - var db dbm.DB dir, _ := ioutil.TempDir("", "goleveldb-gaia-sim") - db, _ = dbm.NewGoLevelDB("Simulation", dir) + db, _ := dbm.NewGoLevelDB("Simulation", dir) defer func() { db.Close() os.RemoveAll(dir) From 3094324b23e3d5b869757475a520761a2f159331 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 23 Nov 2018 15:30:45 +0100 Subject: [PATCH 58/60] Turn the ability to withdraw rewards into an invariant --- x/distribution/simulation/invariants.go | 52 ++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index 128e7faa9ad2..d315c370cfae 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -7,12 +7,14 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/mock/simulation" + "github.com/cosmos/cosmos-sdk/x/stake" abci "github.com/tendermint/tendermint/abci/types" ) // AllInvariants runs all invariants of the distribution module // Currently: total supply, positive power -func AllInvariants(d distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { +func AllInvariants(d distr.Keeper, stk stake.Keeper) simulation.Invariant { + sk := distr.StakeKeeper(stk) return func(app *baseapp.BaseApp) error { err := ValAccumInvariants(d, sk)(app) if err != nil { @@ -22,6 +24,10 @@ func AllInvariants(d distr.Keeper, sk distr.StakeKeeper) simulation.Invariant { if err != nil { return err } + err = CanWithdrawInvariant(d, stk)(app) + if err != nil { + return err + } return nil } } @@ -130,3 +136,47 @@ func DelAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invaria return nil } } + +func CanWithdrawInvariant(k distr.Keeper, sk stake.Keeper) simulation.Invariant { + return func(app *baseapp.BaseApp) error { + ctx := app.NewContext(false, abci.Header{}) + + // we don't want to write the changes + ctx, _ = ctx.CacheContext() + + // withdraw all delegator & validator rewards + vdiIter := func(_ int64, valInfo distr.ValidatorDistInfo) (stop bool) { + err := k.WithdrawValidatorRewardsAll(ctx, valInfo.OperatorAddr) + if err != nil { + panic(err) + } + return false + } + k.IterateValidatorDistInfos(ctx, vdiIter) + + ddiIter := func(_ int64, distInfo distr.DelegationDistInfo) (stop bool) { + err := k.WithdrawDelegationReward( + ctx, distInfo.DelegatorAddr, distInfo.ValOperatorAddr) + if err != nil { + panic(err) + } + return false + } + k.IterateDelegationDistInfos(ctx, ddiIter) + + // assert that the fee pool is empty + feePool := k.GetFeePool(ctx) + if !feePool.TotalValAccum.Accum.IsZero() { + return fmt.Errorf("unexpected leftover validator accum") + } + bondDenom := sk.GetParams(ctx).BondDenom + if !feePool.ValPool.AmountOf(bondDenom).IsZero() { + return fmt.Errorf("unexpected leftover validator pool coins: %v", + feePool.ValPool.AmountOf(bondDenom).String()) + } + + // all ok + return nil + + } +} From b1a14c183886a91a17695a3506a81495d7941b90 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 23 Nov 2018 15:38:38 +0100 Subject: [PATCH 59/60] Fix linter nit --- x/distribution/simulation/invariants.go | 1 + 1 file changed, 1 insertion(+) diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index d315c370cfae..4939ffb25ac7 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -137,6 +137,7 @@ func DelAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invaria } } +// CanWithdrawInvariant checks that current rewards can be completely withdrawn func CanWithdrawInvariant(k distr.Keeper, sk stake.Keeper) simulation.Invariant { return func(app *baseapp.BaseApp) error { ctx := app.NewContext(false, abci.Header{}) From 3674cb3f29e1981a290a1430624790844ffc678a Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 23 Nov 2018 17:46:22 +0100 Subject: [PATCH 60/60] Correctly set height --- x/distribution/simulation/invariants.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index 4939ffb25ac7..ae4e74729904 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -140,7 +140,8 @@ func DelAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invaria // CanWithdrawInvariant checks that current rewards can be completely withdrawn func CanWithdrawInvariant(k distr.Keeper, sk stake.Keeper) simulation.Invariant { return func(app *baseapp.BaseApp) error { - ctx := app.NewContext(false, abci.Header{}) + mockHeader := abci.Header{Height: app.LastBlockHeight() + 1} + ctx := app.NewContext(false, mockHeader) // we don't want to write the changes ctx, _ = ctx.CacheContext()