diff --git a/app/app.go b/app/app.go index 4bb547d8..6154cb73 100644 --- a/app/app.go +++ b/app/app.go @@ -6,8 +6,8 @@ import ( abci "github.com/tendermint/tendermint/abci/types" cmn "github.com/tendermint/tendermint/libs/common" - dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" @@ -18,7 +18,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/crisis" distr "github.com/cosmos/cosmos-sdk/x/distribution" - distrclient "github.com/cosmos/cosmos-sdk/x/distribution/client" "github.com/cosmos/cosmos-sdk/x/genaccounts" "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/gov" @@ -41,7 +40,20 @@ var ( DefaultNodeHome = os.ExpandEnv("$HOME/.kvd") // ModuleBasics manages simple versions of full app modules. It's used for things such as codec registration and genesis file verification. - ModuleBasics module.BasicManager + ModuleBasics = module.NewBasicManager( + genaccounts.AppModuleBasic{}, + genutil.AppModuleBasic{}, + auth.AppModuleBasic{}, + bank.AppModuleBasic{}, + staking.AppModuleBasic{}, + mint.AppModuleBasic{}, + distr.AppModuleBasic{}, + gov.NewAppModuleBasic(paramsclient.ProposalHandler, distr.ProposalHandler), + params.AppModuleBasic{}, + crisis.AppModuleBasic{}, + slashing.AppModuleBasic{}, + supply.AppModuleBasic{}, + ) // module account permissions mAccPerms = map[string][]string{ @@ -54,23 +66,6 @@ var ( } ) -func init() { - ModuleBasics = module.NewBasicManager( - genaccounts.AppModuleBasic{}, - genutil.AppModuleBasic{}, - auth.AppModuleBasic{}, - bank.AppModuleBasic{}, - staking.AppModuleBasic{}, - mint.AppModuleBasic{}, - distr.AppModuleBasic{}, - gov.NewAppModuleBasic(paramsclient.ProposalHandler, distrclient.ProposalHandler), - params.AppModuleBasic{}, - crisis.AppModuleBasic{}, - slashing.AppModuleBasic{}, - supply.AppModuleBasic{}, - ) -} - // Extended ABCI application type App struct { *bam.BaseApp @@ -79,18 +74,8 @@ type App struct { invCheckPeriod uint // keys to access the substores - keyMain *sdk.KVStoreKey - keyAccount *sdk.KVStoreKey - keySupply *sdk.KVStoreKey - keyStaking *sdk.KVStoreKey - tkeyStaking *sdk.TransientStoreKey - keySlashing *sdk.KVStoreKey - keyMint *sdk.KVStoreKey - keyDistr *sdk.KVStoreKey - tkeyDistr *sdk.TransientStoreKey - keyGov *sdk.KVStoreKey - keyParams *sdk.KVStoreKey - tkeyParams *sdk.TransientStoreKey + keys map[string]*sdk.KVStoreKey + tkeys map[string]*sdk.TransientStoreKey // keepers from all the modules accountKeeper auth.AccountKeeper @@ -106,6 +91,9 @@ type App struct { // the module manager mm *module.Manager + + // simulation manager + sm *module.SimulationManager } // NewApp returns a reference to an initialized App. @@ -119,77 +107,75 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, bApp.SetCommitMultiStoreTracer(traceStore) bApp.SetAppVersion(version.Version) + keys := sdk.NewKVStoreKeys( + bam.MainStoreKey, auth.StoreKey, staking.StoreKey, + supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, + gov.StoreKey, params.StoreKey, + ) + tkeys := sdk.NewTransientStoreKeys(staking.TStoreKey, params.TStoreKey) + var app = &App{ BaseApp: bApp, cdc: cdc, invCheckPeriod: invCheckPeriod, - keyMain: sdk.NewKVStoreKey(bam.MainStoreKey), - keyAccount: sdk.NewKVStoreKey(auth.StoreKey), - keySupply: sdk.NewKVStoreKey(supply.StoreKey), - keyStaking: sdk.NewKVStoreKey(staking.StoreKey), - tkeyStaking: sdk.NewTransientStoreKey(staking.TStoreKey), - keyMint: sdk.NewKVStoreKey(mint.StoreKey), - keyDistr: sdk.NewKVStoreKey(distr.StoreKey), - tkeyDistr: sdk.NewTransientStoreKey(distr.TStoreKey), - keySlashing: sdk.NewKVStoreKey(slashing.StoreKey), - keyGov: sdk.NewKVStoreKey(gov.StoreKey), - keyParams: sdk.NewKVStoreKey(params.StoreKey), - tkeyParams: sdk.NewTransientStoreKey(params.TStoreKey), + keys: keys, + tkeys: tkeys, } // init params keeper and subspaces - app.paramsKeeper = params.NewKeeper(app.cdc, app.keyParams, app.tkeyParams, params.DefaultCodespace) + app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace) authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace) bankSubspace := app.paramsKeeper.Subspace(bank.DefaultParamspace) stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace) mintSubspace := app.paramsKeeper.Subspace(mint.DefaultParamspace) distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace) slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) - govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace) + govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable()) crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) // add keepers app.accountKeeper = auth.NewAccountKeeper( app.cdc, - app.keyAccount, + keys[auth.StoreKey], authSubspace, auth.ProtoBaseAccount) app.bankKeeper = bank.NewBaseKeeper( app.accountKeeper, bankSubspace, - bank.DefaultCodespace) + bank.DefaultCodespace, + app.ModuleAccountAddrs()) app.supplyKeeper = supply.NewKeeper( app.cdc, - app.keySupply, + keys[supply.StoreKey], app.accountKeeper, app.bankKeeper, - supply.DefaultCodespace, mAccPerms) stakingKeeper := staking.NewKeeper( app.cdc, - app.keyStaking, - app.tkeyStaking, + keys[staking.StoreKey], + tkeys[staking.TStoreKey], app.supplyKeeper, stakingSubspace, staking.DefaultCodespace) app.mintKeeper = mint.NewKeeper( app.cdc, - app.keyMint, + keys[mint.StoreKey], mintSubspace, &stakingKeeper, app.supplyKeeper, auth.FeeCollectorName) app.distrKeeper = distr.NewKeeper( app.cdc, - app.keyDistr, + keys[distr.StoreKey], distrSubspace, &stakingKeeper, app.supplyKeeper, distr.DefaultCodespace, - auth.FeeCollectorName) + auth.FeeCollectorName, + app.ModuleAccountAddrs()) app.slashingKeeper = slashing.NewKeeper( app.cdc, - app.keySlashing, + keys[slashing.StoreKey], &stakingKeeper, slashingSubspace, slashing.DefaultCodespace) @@ -205,8 +191,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)) app.govKeeper = gov.NewKeeper( app.cdc, - app.keyGov, - app.paramsKeeper, + keys[gov.StoreKey], govSubspace, app.supplyKeeper, &stakingKeeper, @@ -218,19 +203,20 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, app.stakingKeeper = *stakingKeeper.SetHooks( staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks())) - // create the module manager + // create the module manager (Note: Any module instantiated in the module manager that is later modified + // must be passed by reference here.) app.mm = module.NewManager( genaccounts.NewAppModule(app.accountKeeper), genutil.NewAppModule(app.accountKeeper, app.stakingKeeper, app.BaseApp.DeliverTx), auth.NewAppModule(app.accountKeeper), bank.NewAppModule(app.bankKeeper, app.accountKeeper), - crisis.NewAppModule(app.crisisKeeper), + crisis.NewAppModule(&app.crisisKeeper), supply.NewAppModule(app.supplyKeeper, app.accountKeeper), distr.NewAppModule(app.distrKeeper, app.supplyKeeper), gov.NewAppModule(app.govKeeper, app.supplyKeeper), mint.NewAppModule(app.mintKeeper), slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), - staking.NewAppModule(app.stakingKeeper, app.distrKeeper, app.accountKeeper, app.supplyKeeper), + staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -238,7 +224,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, // CanWithdrawInvariant invariant. app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName) - app.mm.SetOrderEndBlockers(gov.ModuleName, staking.ModuleName) + app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName) // genutils must occur after staking so that pools are properly // initialized with tokens from genesis accounts. @@ -249,10 +235,29 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, app.mm.RegisterInvariants(&app.crisisKeeper) app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) + // create the simulation manager and define the order of the modules for deterministic simulations + // + // NOTE: This is not required for apps that don't use the simulator for fuzz testing + // transactions. + app.sm = module.NewSimulationManager( + genaccounts.NewAppModule(app.accountKeeper), + auth.NewAppModule(app.accountKeeper), + bank.NewAppModule(app.bankKeeper, app.accountKeeper), + supply.NewAppModule(app.supplyKeeper, app.accountKeeper), + gov.NewAppModule(app.govKeeper, app.supplyKeeper), + mint.NewAppModule(app.mintKeeper), + distr.NewAppModule(app.distrKeeper, app.supplyKeeper), + staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), + slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), + ) + + app.sm.RegisterStoreDecoders() + + // initialize stores + app.MountKVStores(keys) + app.MountTransientStores(tkeys) + // initialize the app - app.MountStores(app.keyMain, app.keyAccount, app.keySupply, app.keyStaking, - app.keyMint, app.keyDistr, app.keySlashing, app.keyGov, app.keyParams, - app.tkeyParams, app.tkeyStaking, app.tkeyDistr) app.SetInitChainer(app.InitChainer) app.SetBeginBlocker(app.BeginBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountKeeper, app.supplyKeeper, auth.DefaultSigVerificationGasConsumer)) @@ -260,7 +265,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, // load store if loadLatest { - err := app.LoadLatestVersion(app.keyMain) + err := app.LoadLatestVersion(app.keys[bam.MainStoreKey]) if err != nil { cmn.Exit(err.Error()) } @@ -272,10 +277,13 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, // custom tx codec func MakeCodec() *codec.Codec { var cdc = codec.New() + ModuleBasics.RegisterCodec(cdc) sdk.RegisterCodec(cdc) codec.RegisterCrypto(cdc) - return cdc + codec.RegisterEvidences(cdc) + + return cdc.Seal() } func SetBech32AddressPrefixes(config *sdk.Config) { @@ -303,15 +311,29 @@ func (app *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.Res // load a particular height func (app *App) LoadHeight(height int64) error { - return app.LoadVersion(height, app.keyMain) + return app.LoadVersion(height, app.keys[bam.MainStoreKey]) } // ModuleAccountAddrs returns all the app's module account addresses. func (app *App) ModuleAccountAddrs() map[string]bool { modAccAddrs := make(map[string]bool) for acc := range mAccPerms { - modAccAddrs[app.supplyKeeper.GetModuleAddress(acc).String()] = true + modAccAddrs[supply.NewModuleAddress(acc).String()] = true } return modAccAddrs } + +// Codec returns the application's sealed codec. +func (app *App) Codec() *codec.Codec { + return app.cdc +} + +// GetMaccPerms returns a mapping of the application's module account permissions. +func GetMaccPerms() map[string][]string { + perms := make(map[string][]string) + for k, v := range mAccPerms { + perms[k] = v + } + return perms +} diff --git a/app/app_test.go b/app/app_test.go index ed9f6400..ae3dfbf2 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -5,8 +5,8 @@ import ( "testing" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + db "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/codec" @@ -24,6 +24,16 @@ func TestExport(t *testing.T) { require.NoError(t, err, "ExportAppStateAndValidators should not have an error") } +// ensure that black listed addresses are properly set in bank keeper +func TestBlackListedAddrs(t *testing.T) { + db := db.NewMemDB() + app := NewApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0) + + for acc := range mAccPerms { + require.True(t, app.bankKeeper.BlacklistedAddr(app.supplyKeeper.GetModuleAddress(acc))) + } +} + func setGenesis(app *App) error { genesisState := NewDefaultGenesisState() diff --git a/app/export.go b/app/export.go index 81db5dd1..0b6c150c 100644 --- a/app/export.go +++ b/app/export.go @@ -124,7 +124,7 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string // Iterate through validators by power descending, reset bond heights, and // update bond intra-tx counters. - store := ctx.KVStore(app.keyStaking) + store := ctx.KVStore(app.keys[staking.StoreKey]) iter := sdk.KVStoreReversePrefixIterator(store, staking.ValidatorsKey) counter := int16(0) diff --git a/app/sim_test.go b/app/sim_test.go index 15782411..803dbed0 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -2,33 +2,38 @@ package app import ( "encoding/json" - "flag" "fmt" - "io" "io/ioutil" "math/rand" "os" "testing" - "time" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/simapp" // TODO replace with types from app/genesis.go ? + "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" - authsim "github.com/cosmos/cosmos-sdk/x/auth/simulation" - "github.com/cosmos/cosmos-sdk/x/bank" - distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" - govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" - paramsim "github.com/cosmos/cosmos-sdk/x/params/simulation" + "github.com/cosmos/cosmos-sdk/x/auth" + authsimops "github.com/cosmos/cosmos-sdk/x/auth/simulation/operations" + banksimops "github.com/cosmos/cosmos-sdk/x/bank/simulation/operations" + distr "github.com/cosmos/cosmos-sdk/x/distribution" + distrsimops "github.com/cosmos/cosmos-sdk/x/distribution/simulation/operations" + "github.com/cosmos/cosmos-sdk/x/gov" + govsimops "github.com/cosmos/cosmos-sdk/x/gov/simulation/operations" + "github.com/cosmos/cosmos-sdk/x/mint" + "github.com/cosmos/cosmos-sdk/x/params" + paramsimops "github.com/cosmos/cosmos-sdk/x/params/simulation/operations" "github.com/cosmos/cosmos-sdk/x/simulation" - slashingsim "github.com/cosmos/cosmos-sdk/x/slashing/simulation" + "github.com/cosmos/cosmos-sdk/x/slashing" + slashingsimops "github.com/cosmos/cosmos-sdk/x/slashing/simulation/operations" "github.com/cosmos/cosmos-sdk/x/staking" - stakingsim "github.com/cosmos/cosmos-sdk/x/staking/simulation" + stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations" + "github.com/cosmos/cosmos-sdk/x/supply" ) // Simulation parameter constants @@ -53,372 +58,276 @@ const ( OpWeightMsgUnjail = "op_weight_msg_unjail" ) -var ( - genesisFile string - paramsFile string - seed int64 - numBlocks int - blockSize int - enabled bool - verbose bool - lean bool - commit bool - period int - onOperation bool // TODO Remove in favor of binary search for invariant violation - allInvariants bool -) - func init() { - flag.StringVar(&genesisFile, "SimulationGenesis", "", "custom simulation genesis file; cannot be used with params file") - flag.StringVar(¶msFile, "SimulationParams", "", "custom simulation params file which overrides any random params; cannot be used with genesis") - flag.Int64Var(&seed, "SimulationSeed", 42, "simulation random seed") - flag.IntVar(&numBlocks, "SimulationNumBlocks", 500, "number of blocks") - flag.IntVar(&blockSize, "SimulationBlockSize", 200, "operations per block") - flag.BoolVar(&enabled, "SimulationEnabled", false, "enable the simulation") - flag.BoolVar(&verbose, "SimulationVerbose", false, "verbose log output") - flag.BoolVar(&lean, "SimulationLean", false, "lean simulation log output") - flag.BoolVar(&commit, "SimulationCommit", false, "have the simulation commit") - flag.IntVar(&period, "SimulationPeriod", 1, "run slow invariants only once every period assertions") - flag.BoolVar(&onOperation, "SimulateEveryOperation", false, "run slow invariants every operation") - flag.BoolVar(&allInvariants, "PrintAllInvariants", false, "print all invariants if a broken invariant is found") + simapp.GetSimulatorFlags() } -// helper function for populating input for SimulateFromSeed -func getSimulateFromSeedInput(tb testing.TB, w io.Writer, app *App) ( - testing.TB, io.Writer, *baseapp.BaseApp, simulation.AppStateFn, int64, - simulation.WeightedOperations, sdk.Invariants, int, int, bool, bool, bool, bool, map[string]bool, -) { - - return tb, w, app.BaseApp, appStateFn, seed, - testAndRunTxs(app), invariants(app), numBlocks, blockSize, commit, - lean, onOperation, allInvariants, app.ModuleAccountAddrs() -} - -func appStateFn( - r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time, -) (appState json.RawMessage, simAccs []simulation.Account, chainID string) { - - cdc := MakeCodec() - - switch { - case paramsFile != "" && genesisFile != "": - panic("cannot provide both a genesis file and a params file") - - case genesisFile != "": - appState, simAccs, chainID = simapp.AppStateFromGenesisFileFn(r, accs, genesisTimestamp) - - case paramsFile != "": - appParams := make(simulation.AppParams) - bz, err := ioutil.ReadFile(paramsFile) - if err != nil { - panic(err) - } - - cdc.MustUnmarshalJSON(bz, &appParams) - appState, simAccs, chainID = appStateRandomizedFn(r, accs, genesisTimestamp, appParams) - - default: - appParams := make(simulation.AppParams) - appState, simAccs, chainID = appStateRandomizedFn(r, accs, genesisTimestamp, appParams) - } - - return appState, simAccs, chainID -} - -// TODO refactor out random initialization code to the modules -func appStateRandomizedFn( - r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time, appParams simulation.AppParams, -) (json.RawMessage, []simulation.Account, string) { - - cdc := MakeCodec() - genesisState := simapp.NewDefaultGenesisState() - - var ( - amount int64 - numInitiallyBonded int64 - ) - - appParams.GetOrGenerate(cdc, StakePerAccount, &amount, r, - func(r *rand.Rand) { amount = int64(r.Intn(1e12)) }) - appParams.GetOrGenerate(cdc, InitiallyBondedValidators, &amount, r, - func(r *rand.Rand) { numInitiallyBonded = int64(r.Intn(250)) }) - - numAccs := int64(len(accs)) - if numInitiallyBonded > numAccs { - numInitiallyBonded = numAccs - } - - fmt.Printf( - `Selected randomly generated parameters for simulated genesis: -{ - stake_per_account: "%v", - initially_bonded_validators: "%v" -} -`, amount, numInitiallyBonded, - ) - - simapp.GenGenesisAccounts(cdc, r, accs, genesisTimestamp, amount, numInitiallyBonded, genesisState) - simapp.GenAuthGenesisState(cdc, r, appParams, genesisState) - simapp.GenBankGenesisState(cdc, r, appParams, genesisState) - simapp.GenSupplyGenesisState(cdc, amount, numInitiallyBonded, int64(len(accs)), genesisState) - simapp.GenGovGenesisState(cdc, r, appParams, genesisState) - simapp.GenMintGenesisState(cdc, r, appParams, genesisState) - simapp.GenDistrGenesisState(cdc, r, appParams, genesisState) - stakingGen := simapp.GenStakingGenesisState(cdc, r, accs, amount, numAccs, numInitiallyBonded, appParams, genesisState) - simapp.GenSlashingGenesisState(cdc, r, stakingGen, appParams, genesisState) - - appState, err := MakeCodec().MarshalJSON(genesisState) - if err != nil { - panic(err) - } - - return appState, accs, "simulation" -} - -func testAndRunTxs(app *App) []simulation.WeightedOperation { - cdc := MakeCodec() +func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOperation { ap := make(simulation.AppParams) - if paramsFile != "" { - bz, err := ioutil.ReadFile(paramsFile) + paramChanges := app.sm.GenerateParamChanges(config.Seed) + + if config.ParamsFile != "" { + bz, err := ioutil.ReadFile(config.ParamsFile) if err != nil { panic(err) } - cdc.MustUnmarshalJSON(bz, &ap) + app.cdc.MustUnmarshalJSON(bz, &ap) } + // nolint: govet return []simulation.WeightedOperation{ { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightDeductFee, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightDeductFee, &v, nil, func(_ *rand.Rand) { v = 5 }) return v }(nil), - authsim.SimulateDeductFee(app.accountKeeper, app.supplyKeeper), + authsimops.SimulateDeductFee(app.accountKeeper, app.supplyKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgSend, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgSend, &v, nil, func(_ *rand.Rand) { v = 100 }) return v }(nil), - bank.SimulateMsgSend(app.accountKeeper, app.bankKeeper), + banksimops.SimulateMsgSend(app.accountKeeper, app.bankKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightSingleInputMsgMultiSend, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightSingleInputMsgMultiSend, &v, nil, func(_ *rand.Rand) { v = 10 }) return v }(nil), - bank.SimulateSingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper), + banksimops.SimulateSingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgSetWithdrawAddress, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgSetWithdrawAddress, &v, nil, func(_ *rand.Rand) { v = 50 }) return v }(nil), - distrsim.SimulateMsgSetWithdrawAddress(app.accountKeeper, app.distrKeeper), + distrsimops.SimulateMsgSetWithdrawAddress(app.distrKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgWithdrawDelegationReward, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgWithdrawDelegationReward, &v, nil, func(_ *rand.Rand) { v = 50 }) return v }(nil), - distrsim.SimulateMsgWithdrawDelegatorReward(app.accountKeeper, app.distrKeeper), + distrsimops.SimulateMsgWithdrawDelegatorReward(app.distrKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgWithdrawValidatorCommission, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgWithdrawValidatorCommission, &v, nil, func(_ *rand.Rand) { v = 50 }) return v }(nil), - distrsim.SimulateMsgWithdrawValidatorCommission(app.accountKeeper, app.distrKeeper), + distrsimops.SimulateMsgWithdrawValidatorCommission(app.distrKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightSubmitVotingSlashingTextProposal, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingTextProposal, &v, nil, func(_ *rand.Rand) { v = 5 }) return v }(nil), - govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, govsim.SimulateTextProposalContent), + govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, govsimops.SimulateTextProposalContent), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightSubmitVotingSlashingCommunitySpendProposal, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingCommunitySpendProposal, &v, nil, func(_ *rand.Rand) { v = 5 }) return v }(nil), - govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, distrsim.SimulateCommunityPoolSpendProposalContent(app.distrKeeper)), + govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, distrsimops.SimulateCommunityPoolSpendProposalContent(app.distrKeeper)), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightSubmitVotingSlashingParamChangeProposal, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingParamChangeProposal, &v, nil, func(_ *rand.Rand) { v = 5 }) return v }(nil), - govsim.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, paramsim.SimulateParamChangeProposalContent), + govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, paramsimops.SimulateParamChangeProposalContent(paramChanges)), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgDeposit, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgDeposit, &v, nil, func(_ *rand.Rand) { v = 100 }) return v }(nil), - govsim.SimulateMsgDeposit(app.govKeeper), + govsimops.SimulateMsgDeposit(app.govKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgCreateValidator, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgCreateValidator, &v, nil, func(_ *rand.Rand) { v = 100 }) return v }(nil), - stakingsim.SimulateMsgCreateValidator(app.accountKeeper, app.stakingKeeper), + stakingsimops.SimulateMsgCreateValidator(app.accountKeeper, app.stakingKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgEditValidator, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgEditValidator, &v, nil, func(_ *rand.Rand) { v = 5 }) return v }(nil), - stakingsim.SimulateMsgEditValidator(app.stakingKeeper), + stakingsimops.SimulateMsgEditValidator(app.stakingKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgDelegate, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgDelegate, &v, nil, func(_ *rand.Rand) { v = 100 }) return v }(nil), - stakingsim.SimulateMsgDelegate(app.accountKeeper, app.stakingKeeper), + stakingsimops.SimulateMsgDelegate(app.accountKeeper, app.stakingKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgUndelegate, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgUndelegate, &v, nil, func(_ *rand.Rand) { v = 100 }) return v }(nil), - stakingsim.SimulateMsgUndelegate(app.accountKeeper, app.stakingKeeper), + stakingsimops.SimulateMsgUndelegate(app.accountKeeper, app.stakingKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgBeginRedelegate, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgBeginRedelegate, &v, nil, func(_ *rand.Rand) { v = 100 }) return v }(nil), - stakingsim.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakingKeeper), + stakingsimops.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakingKeeper), }, { func(_ *rand.Rand) int { var v int - ap.GetOrGenerate(cdc, OpWeightMsgUnjail, &v, nil, + ap.GetOrGenerate(app.cdc, OpWeightMsgUnjail, &v, nil, func(_ *rand.Rand) { v = 100 }) return v }(nil), - slashingsim.SimulateMsgUnjail(app.slashingKeeper), + slashingsimops.SimulateMsgUnjail(app.slashingKeeper), }, } } -func invariants(app *App) []sdk.Invariant { - // TODO: fix PeriodicInvariants, it doesn't seem to call individual invariants for a period of 1 - // Ref: https://github.com/cosmos/cosmos-sdk/issues/4631 - if period == 1 { - return app.crisisKeeper.Invariants() - } - return simulation.PeriodicInvariants(app.crisisKeeper.Invariants(), period, 0) -} - -// Pass this in as an option to use a dbStoreAdapter instead of an IAVLStore for simulation speed. +// fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of +// an IAVLStore for faster simulation speed. func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { bapp.SetFauxMerkleMode() } +// interBlockCacheOpt returns a BaseApp option function that sets the persistent +// inter-block write-through cache. +func interBlockCacheOpt() func(*baseapp.BaseApp) { + return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) +} + // Profile with: -// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/GaiaApp -bench ^BenchmarkFullAppSimulation$ -SimulationCommit=true -cpuprofile cpu.out -// TODO does this work +// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/GaiaApp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out func BenchmarkFullAppSimulation(b *testing.B) { logger := log.NewNopLogger() + config := simapp.NewConfigFromFlags() var db dbm.DB dir, _ := ioutil.TempDir("", "goleveldb-app-sim") db, _ = sdk.NewLevelDB("Simulation", dir) defer func() { db.Close() - os.RemoveAll(dir) + _ = os.RemoveAll(dir) }() - app := NewApp(logger, db, nil, true, 0) + + app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt()) // Run randomized simulation - // TODO parameterize numbers, save for a later PR - _, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(b, os.Stdout, app)) - if err != nil { - fmt.Println(err) - b.Fail() + // TODO: parameterize numbers, save for a later PR + _, simParams, simErr := simulation.SimulateFromSeed( + b, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), + testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, + ) + + // export state and params before the simulation error is checked + if config.ExportStatePath != "" { + if err := ExportStateToJSON(app, config.ExportStatePath); err != nil { + fmt.Println(err) + b.Fail() + } } - if commit { - fmt.Println("GoLevelDB Stats") + + if config.ExportParamsPath != "" { + if err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil { + fmt.Println(err) + b.Fail() + } + } + + if simErr != nil { + fmt.Println(simErr) + b.FailNow() + } + + if config.Commit { + fmt.Println("\nGoLevelDB Stats") fmt.Println(db.Stats()["leveldb.stats"]) fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) } } func TestFullAppSimulation(t *testing.T) { - if !enabled { - t.Skip("Skipping application simulation") + if !simapp.FlagEnabledValue { + t.Skip("skipping application simulation") } var logger log.Logger + config := simapp.NewConfigFromFlags() - if verbose { + if simapp.FlagVerboseValue { logger = log.TestingLogger() } else { logger = log.NewNopLogger() @@ -430,32 +339,49 @@ func TestFullAppSimulation(t *testing.T) { defer func() { db.Close() - os.RemoveAll(dir) + _ = os.RemoveAll(dir) }() - app := NewApp(logger, db, nil, true, 0, fauxMerkleModeOpt) - require.Equal(t, "kava", app.Name()) + app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", app.Name()) // Run randomized simulation - _, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, os.Stdout, app)) - if commit { + _, simParams, simErr := simulation.SimulateFromSeed( + t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), + testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, + ) + + // export state and params before the simulation error is checked + if config.ExportStatePath != "" { + err := ExportStateToJSON(app, config.ExportStatePath) + require.NoError(t, err) + } + + if config.ExportParamsPath != "" { + err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath) + require.NoError(t, err) + } + + require.NoError(t, simErr) + + if config.Commit { // for memdb: // fmt.Println("Database Size", db.Stats()["database.size"]) - fmt.Println("GoLevelDB Stats") + fmt.Println("\nGoLevelDB Stats") fmt.Println(db.Stats()["leveldb.stats"]) fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) } - - require.Nil(t, err) } func TestAppImportExport(t *testing.T) { - if !enabled { - t.Skip("Skipping application import/export simulation") + if !simapp.FlagEnabledValue { + t.Skip("skipping application import/export simulation") } var logger log.Logger - if verbose { + config := simapp.NewConfigFromFlags() + + if simapp.FlagVerboseValue { logger = log.TestingLogger() } else { logger = log.NewNopLogger() @@ -467,52 +393,65 @@ func TestAppImportExport(t *testing.T) { defer func() { db.Close() - os.RemoveAll(dir) + _ = os.RemoveAll(dir) }() - app := NewApp(logger, db, nil, true, 0, fauxMerkleModeOpt) - require.Equal(t, "kava", app.Name()) + app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) + require.Equal(t, "SimApp", app.Name()) // Run randomized simulation - _, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, os.Stdout, app)) + _, simParams, simErr := simulation.SimulateFromSeed( + t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), + testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, + ) - if commit { + // export state and simParams before the simulation error is checked + if config.ExportStatePath != "" { + err := ExportStateToJSON(app, config.ExportStatePath) + require.NoError(t, err) + } + + if config.ExportParamsPath != "" { + err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath) + require.NoError(t, err) + } + + require.NoError(t, simErr) + + if config.Commit { // for memdb: // fmt.Println("Database Size", db.Stats()["database.size"]) - fmt.Println("GoLevelDB Stats") + fmt.Println("\nGoLevelDB 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") + fmt.Printf("exporting genesis...\n") appState, _, err := app.ExportAppStateAndValidators(false, []string{}) require.NoError(t, err) - fmt.Printf("Importing genesis...\n") + fmt.Printf("importing genesis...\n") newDir, _ := ioutil.TempDir("", "goleveldb-app-sim-2") newDB, _ := sdk.NewLevelDB("Simulation-2", dir) defer func() { newDB.Close() - os.RemoveAll(newDir) + _ = os.RemoveAll(newDir) }() - newApp := NewApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt) - require.Equal(t, "kava", newApp.Name()) + newApp := NewApp(log.NewNopLogger(), newDB, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) + require.Equal(t, "SimApp", newApp.Name()) var genesisState simapp.GenesisState err = app.cdc.UnmarshalJSON(appState, &genesisState) - if err != nil { - panic(err) - } + require.NoError(t, err) - ctxB := newApp.NewContext(true, abci.Header{}) + ctxB := newApp.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) newApp.mm.InitGenesis(ctxB, genesisState) - fmt.Printf("Comparing stores...\n") - ctxA := app.NewContext(true, abci.Header{}) + fmt.Printf("comparing stores...\n") + ctxA := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) type StoreKeysPrefixes struct { A sdk.StoreKey @@ -521,38 +460,45 @@ func TestAppImportExport(t *testing.T) { } storeKeysPrefixes := []StoreKeysPrefixes{ - {app.keyMain, newApp.keyMain, [][]byte{}}, - {app.keyAccount, newApp.keyAccount, [][]byte{}}, - {app.keyStaking, newApp.keyStaking, [][]byte{staking.UnbondingQueueKey, - staking.RedelegationQueueKey, staking.ValidatorQueueKey}}, // ordering may change but it doesn't matter - {app.keySlashing, newApp.keySlashing, [][]byte{}}, - {app.keyMint, newApp.keyMint, [][]byte{}}, - {app.keyDistr, newApp.keyDistr, [][]byte{}}, - {app.keySupply, newApp.keySupply, [][]byte{}}, - {app.keyParams, newApp.keyParams, [][]byte{}}, - {app.keyGov, newApp.keyGov, [][]byte{}}, + {app.keys[baseapp.MainStoreKey], newApp.keys[baseapp.MainStoreKey], [][]byte{}}, + {app.keys[auth.StoreKey], newApp.keys[auth.StoreKey], [][]byte{}}, + {app.keys[staking.StoreKey], newApp.keys[staking.StoreKey], + [][]byte{ + staking.UnbondingQueueKey, staking.RedelegationQueueKey, staking.ValidatorQueueKey, + }}, // ordering may change but it doesn't matter + {app.keys[slashing.StoreKey], newApp.keys[slashing.StoreKey], [][]byte{}}, + {app.keys[mint.StoreKey], newApp.keys[mint.StoreKey], [][]byte{}}, + {app.keys[distr.StoreKey], newApp.keys[distr.StoreKey], [][]byte{}}, + {app.keys[supply.StoreKey], newApp.keys[supply.StoreKey], [][]byte{}}, + {app.keys[params.StoreKey], newApp.keys[params.StoreKey], [][]byte{}}, + {app.keys[gov.StoreKey], newApp.keys[gov.StoreKey], [][]byte{}}, } for _, storeKeysPrefix := range storeKeysPrefixes { storeKeyA := storeKeysPrefix.A storeKeyB := storeKeysPrefix.B prefixes := storeKeysPrefix.Prefixes + storeA := ctxA.KVStore(storeKeyA) storeB := ctxB.KVStore(storeKeyB) - kvA, kvB, count, equal := sdk.DiffKVStores(storeA, storeB, prefixes) - fmt.Printf("Compared %d key/value pairs between %s and %s\n", count, storeKeyA, storeKeyB) - require.True(t, equal, simapp.GetSimulationLog(storeKeyA.Name(), app.cdc, newApp.cdc, kvA, kvB)) - } + failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, prefixes) + require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") + + fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), storeKeyA, storeKeyB) + require.Len(t, failedKVAs, 0, simapp.GetSimulationLog(storeKeyA.Name(), app.sm.StoreDecoders, app.cdc, failedKVAs, failedKVBs)) + } } func TestAppSimulationAfterImport(t *testing.T) { - if !enabled { - t.Skip("Skipping application simulation after import") + if !simapp.FlagEnabledValue { + t.Skip("skipping application simulation after import") } var logger log.Logger - if verbose { + config := simapp.NewConfigFromFlags() + + if simapp.FlagVerboseValue { logger = log.TestingLogger() } else { logger = log.NewNopLogger() @@ -563,25 +509,40 @@ func TestAppSimulationAfterImport(t *testing.T) { defer func() { db.Close() - os.RemoveAll(dir) + _ = os.RemoveAll(dir) }() - app := NewApp(logger, db, nil, true, 0, fauxMerkleModeOpt) - require.Equal(t, "kava", app.Name()) + app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) + require.Equal(t, "GaiaApp", app.Name()) // Run randomized simulation - stopEarly, err := simulation.SimulateFromSeed(getSimulateFromSeedInput(t, os.Stdout, app)) + // Run randomized simulation + stopEarly, simParams, simErr := simulation.SimulateFromSeed( + t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), + testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, + ) - if commit { + // export state and params before the simulation error is checked + if config.ExportStatePath != "" { + err := ExportStateToJSON(app, config.ExportStatePath) + require.NoError(t, err) + } + + if config.ExportParamsPath != "" { + err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath) + require.NoError(t, err) + } + + require.NoError(t, simErr) + + if config.Commit { // for memdb: // fmt.Println("Database Size", db.Stats()["database.size"]) - fmt.Println("GoLevelDB Stats") + fmt.Println("\nGoLevelDB Stats") fmt.Println(db.Stats()["leveldb.stats"]) fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) } - 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") @@ -602,64 +563,80 @@ func TestAppSimulationAfterImport(t *testing.T) { defer func() { newDB.Close() - os.RemoveAll(newDir) + _ = os.RemoveAll(newDir) }() newApp := NewApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt) - require.Equal(t, "kava", newApp.Name()) + require.Equal(t, "GaiaApp", newApp.Name()) + newApp.InitChain(abci.RequestInitChain{ AppStateBytes: appState, }) // Run randomized simulation on imported app - _, err = simulation.SimulateFromSeed(getSimulateFromSeedInput(t, os.Stdout, newApp)) - require.Nil(t, err) + _, _, err = simulation.SimulateFromSeed( + t, os.Stdout, newApp.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), + testAndRunTxs(newApp, config), newApp.ModuleAccountAddrs(), config, + ) + + require.NoError(t, err) } // TODO: Make another test for the fuzzer itself, which just has noOp txs // and doesn't depend on the application. func TestAppStateDeterminism(t *testing.T) { - if !enabled { - t.Skip("Skipping application simulation") + if !simapp.FlagEnabledValue { + t.Skip("skipping application simulation") } + config := simapp.NewConfigFromFlags() + config.InitialBlockHeight = 1 + config.ExportParamsPath = "" + config.OnOperation = false + config.AllInvariants = false + numSeeds := 3 numTimesToRunPerSeed := 5 appHashList := make([]json.RawMessage, numTimesToRunPerSeed) for i := 0; i < numSeeds; i++ { - seed := rand.Int63() + config.Seed = rand.Int63() + for j := 0; j < numTimesToRunPerSeed; j++ { logger := log.NewNopLogger() db := dbm.NewMemDB() - app := NewApp(logger, db, nil, true, 0) + app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt()) - // run randomized simulation - simulation.SimulateFromSeed( - t, os.Stdout, app.BaseApp, appStateFn, seed, - testAndRunTxs(app), - []sdk.Invariant{}, - 50, - 100, - true, - false, - false, - false, - app.ModuleAccountAddrs(), + fmt.Printf( + "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", + config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, ) + _, _, err := simulation.SimulateFromSeed( + t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), + testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, + ) + require.NoError(t, err) + appHash := app.LastCommitID().Hash appHashList[j] = appHash - } - for k := 1; k < numTimesToRunPerSeed; k++ { - require.Equal(t, appHashList[0], appHashList[k], "appHash list: %v", appHashList) + if j != 0 { + require.Equal( + t, appHashList[0], appHashList[j], + "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, + ) + } } } } func BenchmarkInvariants(b *testing.B) { logger := log.NewNopLogger() + + config := simapp.NewConfigFromFlags() + config.AllInvariants = false + dir, _ := ioutil.TempDir("", "goleveldb-app-invariant-bench") db, _ := sdk.NewLevelDB("simulation", dir) @@ -668,16 +645,31 @@ func BenchmarkInvariants(b *testing.B) { os.RemoveAll(dir) }() - app := NewApp(logger, db, nil, true, 0) + app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt()) // 2. Run parameterized simulation (w/o invariants) - _, err := simulation.SimulateFromSeed( - b, ioutil.Discard, app.BaseApp, appStateFn, seed, testAndRunTxs(app), - []sdk.Invariant{}, numBlocks, blockSize, commit, lean, onOperation, false, - app.ModuleAccountAddrs(), + _, simParams, simErr := simulation.SimulateFromSeed( + b, ioutil.Discard, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), + testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, ) - if err != nil { - fmt.Println(err) + + // export state and params before the simulation error is checked + if config.ExportStatePath != "" { + if err := ExportStateToJSON(app, config.ExportStatePath); err != nil { + fmt.Println(err) + b.Fail() + } + } + + if config.ExportParamsPath != "" { + if err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil { + fmt.Println(err) + b.Fail() + } + } + + if simErr != nil { + fmt.Println(simErr) b.FailNow() } @@ -690,7 +682,7 @@ func BenchmarkInvariants(b *testing.B) { for _, cr := range app.crisisKeeper.Routes() { b.Run(fmt.Sprintf("%s/%s", cr.ModuleName, cr.Route), func(b *testing.B) { if res, stop := cr.Invar(ctx); stop { - fmt.Printf("broken invariant at block %d of %d\n%s", ctx.BlockHeight()-1, numBlocks, res) + fmt.Printf("broken invariant at block %d of %d\n%s", ctx.BlockHeight()-1, config.NumBlocks, res) b.FailNow() } }) diff --git a/app/utils.go b/app/utils.go new file mode 100644 index 00000000..9c5742b4 --- /dev/null +++ b/app/utils.go @@ -0,0 +1,17 @@ +package app + +import ( + "fmt" + "io/ioutil" +) + +// ExportStateToJSON util function to export the app state to JSON +func ExportStateToJSON(app *App, path string) error { + fmt.Println("exporting app state...") + appState, _, err := app.ExportAppStateAndValidators(false, nil) + if err != nil { + return err + } + + return ioutil.WriteFile(path, []byte(appState), 0644) +}