From b2b026b5e076b47442e2e1064f289b37f51645f4 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 29 Nov 2018 16:17:10 +0100 Subject: [PATCH] Merge PR #2930: Simulation spring cleaning * Update PENDING.md * Add simple period for expensive invariants * Remove individual module simulations * Simulate a few more blocks * Add README explaining reason for shell scripts * Deduplicate scripts, log exact replication command on failure * Refactor invariants to take sdk.Context instead of baseapp.BaseApp * Reference all issues in PENDING.md entry * Remove no longer used simulation.RandSetup * Bug fixes * Address @rigelrozanski comments * Fix typo --- .circleci/config.yml | 21 ------ Makefile | 12 ++-- PENDING.md | 1 + cmd/gaia/app/invariants.go | 4 +- cmd/gaia/app/sim_test.go | 21 +++--- scripts/README.md | 3 + scripts/import-export-sim.sh | 54 ---------------- scripts/multisim.sh | 7 +- scripts/simulation-after-import.sh | 54 ---------------- x/bank/simulation/invariants.go | 8 +-- x/bank/simulation/sim_test.go | 46 ------------- x/distribution/simulation/invariants.go | 23 ++----- x/gov/simulation/invariants.go | 4 +- x/gov/simulation/sim_test.go | 86 ------------------------- x/mock/simulation/invariants.go | 8 ++- x/mock/simulation/simulate.go | 16 ++--- x/mock/simulation/util.go | 8 +-- x/slashing/simulation/invariants.go | 4 +- x/stake/simulation/invariants.go | 38 +++-------- x/stake/simulation/msgs.go | 25 ------- x/stake/simulation/sim_test.go | 72 --------------------- 21 files changed, 62 insertions(+), 453 deletions(-) create mode 100644 scripts/README.md delete mode 100755 scripts/import-export-sim.sh delete mode 100755 scripts/simulation-after-import.sh delete mode 100644 x/bank/simulation/sim_test.go delete mode 100644 x/gov/simulation/sim_test.go delete mode 100644 x/stake/simulation/sim_test.go diff --git a/.circleci/config.yml b/.circleci/config.yml index a0bab3e46..17442318f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -90,24 +90,6 @@ jobs: make test_cli make test_examples - test_sim_modules: - <<: *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 individual module simulations - command: | - export PATH="$GOBIN:$PATH" - make test_sim_modules - test_sim_gaia_nondeterminism: <<: *defaults parallelism: 1 @@ -307,9 +289,6 @@ workflows: - integration_tests: requires: - setup_dependencies - - test_sim_modules: - requires: - - setup_dependencies - test_sim_gaia_nondeterminism: requires: - setup_dependencies diff --git a/Makefile b/Makefile index 4fdb5a30f..58394a554 100644 --- a/Makefile +++ b/Makefile @@ -164,29 +164,25 @@ test_unit: test_race: @VERSION=$(VERSION) go test -race $(PACKAGES_NOSIMULATION) -test_sim_modules: - @echo "Running individual module simulations..." - @go test $(PACKAGES_SIMTEST) - test_sim_gaia_nondeterminism: @echo "Running nondeterminism test..." @go test ./cmd/gaia/app -run TestAppStateDeterminism -SimulationEnabled=true -v -timeout 10m test_sim_gaia_fast: @echo "Running quick Gaia simulation. This may take several minutes..." - @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=500 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -v -timeout 24h + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=1000 -SimulationBlockSize=200 -SimulationCommit=true -SimulationSeed=99 -v -timeout 24h test_sim_gaia_import_export: @echo "Running Gaia import/export simulation. This may take several minutes..." - @bash scripts/import-export-sim.sh 50 + @bash scripts/multisim.sh 50 TestGaiaImportExport test_sim_gaia_simulation_after_import: @echo "Running Gaia simulation-after-import. This may take several minutes..." - @bash scripts/simulation-after-import.sh 50 + @bash scripts/multisim.sh 50 TestGaiaSimulationAfterImport test_sim_gaia_multi_seed: @echo "Running multi-seed Gaia simulation. This may take awhile!" - @bash scripts/multisim.sh 25 + @bash scripts/multisim.sh 50 TestFullGaiaSimulation SIM_NUM_BLOCKS ?= 500 SIM_BLOCK_SIZE ?= 200 diff --git a/PENDING.md b/PENDING.md index 12a856730..b9b7e28c7 100644 --- a/PENDING.md +++ b/PENDING.md @@ -74,6 +74,7 @@ IMPROVEMENTS - [#110](https://github.com/tendermint/devops/issues/110) Updated CircleCI job to trigger website build when cosmos docs are updated. * SDK + - [x/mock/simulation] \#2832, \#2885, \#2873, \#2902 Simulation cleanup - [x/mock/simulation] [\#2720] major cleanup, introduction of helper objects, reorganization - \#2821 Codespaces are now strings - [types] #2776 Improve safety of `Coin` and `Coins` types. Various functions diff --git a/cmd/gaia/app/invariants.go b/cmd/gaia/app/invariants.go index 67ec1c714..e67e5a16d 100644 --- a/cmd/gaia/app/invariants.go +++ b/cmd/gaia/app/invariants.go @@ -8,6 +8,7 @@ import ( distrsim "github.com/cosmos/cosmos-sdk/x/distribution/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation" stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" + abci "github.com/tendermint/tendermint/abci/types" ) func (app *GaiaApp) runtimeInvariants() []simulation.Invariant { @@ -23,8 +24,9 @@ func (app *GaiaApp) runtimeInvariants() []simulation.Invariant { func (app *GaiaApp) assertRuntimeInvariants() { invariants := app.runtimeInvariants() start := time.Now() + ctx := app.NewContext(false, abci.Header{Height: app.LastBlockHeight() + 1}) for _, inv := range invariants { - if err := inv(app.BaseApp); err != nil { + if err := inv(ctx); err != nil { panic(fmt.Errorf("invariant broken: %s", err)) } } diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 97ea568fd..52ac2ed1a 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -40,6 +40,7 @@ var ( enabled bool verbose bool commit bool + period int ) func init() { @@ -49,6 +50,7 @@ func init() { flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation") flag.BoolVar(&verbose, "SimulationVerbose", false, "Verbose log output") flag.BoolVar(&commit, "SimulationCommit", false, "Have the simulation commit") + flag.IntVar(&period, "SimulationPeriod", 100, "Run slow invariants only once every period assertions") } func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { @@ -185,12 +187,12 @@ func testAndRunTxs(app *GaiaApp) []simulation.WeightedOperation { func invariants(app *GaiaApp) []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(), + simulation.PeriodicInvariant(banksim.NonnegativeBalanceInvariant(app.accountKeeper), period, 0), + simulation.PeriodicInvariant(govsim.AllInvariants(), period, 0), + simulation.PeriodicInvariant(distrsim.AllInvariants(app.distrKeeper, app.stakeKeeper), period, 0), + simulation.PeriodicInvariant(stakesim.AllInvariants(app.bankKeeper, app.stakeKeeper, + app.feeCollectionKeeper, app.distrKeeper, app.accountKeeper), period, 0), + simulation.PeriodicInvariant(slashingsim.AllInvariants(), period, 0), } } @@ -219,7 +221,6 @@ func BenchmarkFullGaiaSimulation(b *testing.B) { _, err := simulation.SimulateFromSeed( b, app.BaseApp, appStateFn, seed, testAndRunTxs(app), - []simulation.RandSetup{}, invariants(app), // these shouldn't get ran numBlocks, blockSize, @@ -262,7 +263,6 @@ func TestFullGaiaSimulation(t *testing.T) { _, err := simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, testAndRunTxs(app), - []simulation.RandSetup{}, invariants(app), numBlocks, blockSize, @@ -304,7 +304,6 @@ func TestGaiaImportExport(t *testing.T) { _, err := simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, testAndRunTxs(app), - []simulation.RandSetup{}, invariants(app), numBlocks, blockSize, @@ -401,7 +400,6 @@ func TestGaiaSimulationAfterImport(t *testing.T) { stopEarly, err := simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, testAndRunTxs(app), - []simulation.RandSetup{}, invariants(app), numBlocks, blockSize, @@ -447,7 +445,6 @@ func TestGaiaSimulationAfterImport(t *testing.T) { _, err = simulation.SimulateFromSeed( t, newApp.BaseApp, appStateFn, seed, testAndRunTxs(newApp), - []simulation.RandSetup{}, invariants(newApp), numBlocks, blockSize, @@ -479,13 +476,11 @@ func TestAppStateDeterminism(t *testing.T) { simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, testAndRunTxs(app), - []simulation.RandSetup{}, []simulation.Invariant{}, 50, 100, true, ) - //app.Commit() appHash := app.LastCommitID().Hash appHashList[j] = appHash } diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 000000000..f213124c8 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,3 @@ +Generally we should avoid shell scripting and write tests purely in Golang. +However, some libraries are not Goroutine-safe (e.g. app simulations cannot be run safely in parallel), +and OS-native threading may be more efficient for many parallel simulations, so we use shell scripts here. diff --git a/scripts/import-export-sim.sh b/scripts/import-export-sim.sh deleted file mode 100755 index 8ace8e0d5..000000000 --- a/scripts/import-export-sim.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/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/import-export-sim.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 import/export Gaia simulation 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 TestGaiaImportExport -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 diff --git a/scripts/multisim.sh b/scripts/multisim.sh index 2376eb3a8..028b6563f 100755 --- a/scripts/multisim.sh +++ b/scripts/multisim.sh @@ -3,9 +3,11 @@ 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 +testname=$2 echo "Running multi-seed simulation with seeds ${seeds[@]}" echo "Running $blocks blocks per seed" +echo "Running test $testname" echo "Edit scripts/multisim.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)." @@ -16,10 +18,10 @@ echo "Using temporary log directory: $tmpdir" sim() { seed=$1 - echo "Running full Gaia simulation with seed $seed. This may take awhile!" + echo "Running Gaia simulation with seed $seed. This may take awhile!" file="$tmpdir/gaia-simulation-seed-$seed-date-$(date -u +"%Y-%m-%dT%H:%M:%S+00:00").stdout" echo "Writing stdout to $file..." - go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationNumBlocks=$blocks \ + go test ./cmd/gaia/app -run $testname -SimulationEnabled=true -SimulationNumBlocks=$blocks \ -SimulationVerbose=true -SimulationCommit=true -SimulationSeed=$seed -v -timeout 24h > $file } @@ -44,6 +46,7 @@ for pid in ${pids[*]}; do if [ $last -ne 0 ] then echo "Simulation with seed $seed failed!" + echo "To replicate, run 'go test ./cmd/gaia/app -run $testname -SimulationEnabled=true -SimulationNumBlocks=$blocks -SimulationVerbose=true -SimulationCommit=true -SimulationSeed=$seed -v -timeout 24h'" code=1 else echo "Simulation with seed $seed OK" diff --git a/scripts/simulation-after-import.sh b/scripts/simulation-after-import.sh deleted file mode 100755 index 2e90d1234..000000000 --- a/scripts/simulation-after-import.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/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 diff --git a/x/bank/simulation/invariants.go b/x/bank/simulation/invariants.go index f0cd5ba63..ee826c0e3 100644 --- a/x/bank/simulation/invariants.go +++ b/x/bank/simulation/invariants.go @@ -4,18 +4,15 @@ import ( "errors" "fmt" - "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/mock/simulation" - abci "github.com/tendermint/tendermint/abci/types" ) // NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances func NonnegativeBalanceInvariant(mapper auth.AccountKeeper) simulation.Invariant { - return func(app *baseapp.BaseApp) error { - ctx := app.NewContext(false, abci.Header{}) + return func(ctx sdk.Context) error { accts := mock.GetAllAccounts(mapper, ctx) for _, acc := range accts { coins := acc.GetCoins() @@ -32,8 +29,7 @@ func NonnegativeBalanceInvariant(mapper auth.AccountKeeper) simulation.Invariant // TotalCoinsInvariant checks that the sum of the coins across all accounts // is what is expected func TotalCoinsInvariant(mapper auth.AccountKeeper, totalSupplyFn func() sdk.Coins) simulation.Invariant { - return func(app *baseapp.BaseApp) error { - ctx := app.NewContext(false, abci.Header{}) + return func(ctx sdk.Context) error { totalCoins := sdk.Coins{} chkAccount := func(acc auth.Account) bool { diff --git a/x/bank/simulation/sim_test.go b/x/bank/simulation/sim_test.go deleted file mode 100644 index 514156228..000000000 --- a/x/bank/simulation/sim_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package simulation - -import ( - "encoding/json" - "math/rand" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/cosmos/cosmos-sdk/x/mock/simulation" -) - -func TestBankWithRandomMessages(t *testing.T) { - mapp := mock.NewApp() - - bank.RegisterCodec(mapp.Cdc) - mapper := mapp.AccountKeeper - bankKeeper := bank.NewBaseKeeper(mapper) - mapp.Router().AddRoute("bank", bank.NewHandler(bankKeeper)) - - err := mapp.CompleteSetup() - if err != nil { - panic(err) - } - - appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { - simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) - return json.RawMessage("{}") - } - - simulation.Simulate( - t, mapp.BaseApp, appStateFn, - []simulation.WeightedOperation{ - {1, SingleInputSendTx(mapper)}, - {1, SingleInputSendMsg(mapper, bankKeeper)}, - }, - []simulation.RandSetup{}, - []simulation.Invariant{ - NonnegativeBalanceInvariant(mapper), - TotalCoinsInvariant(mapper, func() sdk.Coins { return mapp.TotalCoinsSupply }), - }, - 30, 60, - false, - ) -} diff --git a/x/distribution/simulation/invariants.go b/x/distribution/simulation/invariants.go index 5e995978c..bf808905a 100644 --- a/x/distribution/simulation/invariants.go +++ b/x/distribution/simulation/invariants.go @@ -3,28 +3,26 @@ package simulation import ( "fmt" - "github.com/cosmos/cosmos-sdk/baseapp" 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, stk stake.Keeper) simulation.Invariant { sk := distr.StakeKeeper(stk) - return func(app *baseapp.BaseApp) error { - err := ValAccumInvariants(d, sk)(app) + return func(ctx sdk.Context) error { + err := ValAccumInvariants(d, sk)(ctx) if err != nil { return err } - err = DelAccumInvariants(d, sk)(app) + err = DelAccumInvariants(d, sk)(ctx) if err != nil { return err } - err = CanWithdrawInvariant(d, stk)(app) + err = CanWithdrawInvariant(d, stk)(ctx) if err != nil { return err } @@ -35,9 +33,7 @@ func AllInvariants(d distr.Keeper, stk stake.Keeper) simulation.Invariant { // ValAccumInvariants checks that the fee pool accum == sum all validators' accum func ValAccumInvariants(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) + return func(ctx sdk.Context) error { height := ctx.BlockHeight() valAccum := sdk.ZeroDec() @@ -62,9 +58,7 @@ func ValAccumInvariants(k distr.Keeper, sk distr.StakeKeeper) simulation.Invaria // 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) + return func(ctx sdk.Context) error { height := ctx.BlockHeight() totalDelAccumFromVal := make(map[string]sdk.Dec) // key is the valOpAddr string @@ -139,10 +133,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 { - mockHeader := abci.Header{Height: app.LastBlockHeight() + 1} - ctx := app.NewContext(false, mockHeader) - + return func(ctx sdk.Context) error { // we don't want to write the changes ctx, _ = ctx.CacheContext() diff --git a/x/gov/simulation/invariants.go b/x/gov/simulation/invariants.go index 6d5f41918..06f8e9774 100644 --- a/x/gov/simulation/invariants.go +++ b/x/gov/simulation/invariants.go @@ -1,13 +1,13 @@ package simulation import ( - "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock/simulation" ) // AllInvariants tests all governance invariants func AllInvariants() simulation.Invariant { - return func(app *baseapp.BaseApp) error { + return func(ctx sdk.Context) error { // TODO Add some invariants! // Checking proposal queues, no passed-but-unexecuted proposals, etc. return nil diff --git a/x/gov/simulation/sim_test.go b/x/gov/simulation/sim_test.go deleted file mode 100644 index 5b6068c57..000000000 --- a/x/gov/simulation/sim_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package simulation - -import ( - "encoding/json" - "math/rand" - "testing" - - abci "github.com/tendermint/tendermint/abci/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/gov" - "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/cosmos/cosmos-sdk/x/mock/simulation" - "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/stake" -) - -// TestGovWithRandomMessages -func TestGovWithRandomMessages(t *testing.T) { - mapp := mock.NewApp() - - bank.RegisterCodec(mapp.Cdc) - gov.RegisterCodec(mapp.Cdc) - mapper := mapp.AccountKeeper - - bankKeeper := bank.NewBaseKeeper(mapper) - stakeKey := sdk.NewKVStoreKey("stake") - stakeTKey := sdk.NewTransientStoreKey("transient_stake") - paramKey := sdk.NewKVStoreKey("params") - paramTKey := sdk.NewTransientStoreKey("transient_params") - paramKeeper := params.NewKeeper(mapp.Cdc, paramKey, paramTKey) - stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, paramKeeper.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) - govKey := sdk.NewKVStoreKey("gov") - govKeeper := gov.NewKeeper(mapp.Cdc, govKey, paramKeeper, paramKeeper.Subspace(gov.DefaultParamspace), bankKeeper, stakeKeeper, gov.DefaultCodespace) - mapp.Router().AddRoute("gov", gov.NewHandler(govKeeper)) - mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - gov.EndBlocker(ctx, govKeeper) - return abci.ResponseEndBlock{} - }) - - err := mapp.CompleteSetup(stakeKey, stakeTKey, paramKey, paramTKey, govKey) - if err != nil { - panic(err) - } - - appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { - simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) - return json.RawMessage("{}") - } - - setup := func(r *rand.Rand, accs []simulation.Account) { - ctx := mapp.NewContext(false, abci.Header{}) - stake.InitGenesis(ctx, stakeKeeper, stake.DefaultGenesisState()) - gov.InitGenesis(ctx, govKeeper, gov.DefaultGenesisState()) - } - - // Test with unscheduled votes - simulation.Simulate( - t, mapp.BaseApp, appStateFn, - []simulation.WeightedOperation{ - {2, SimulateMsgSubmitProposal(govKeeper)}, - {3, SimulateMsgDeposit(govKeeper)}, - {20, SimulateMsgVote(govKeeper)}, - }, []simulation.RandSetup{ - setup, - }, []simulation.Invariant{ - AllInvariants(), - }, 10, 100, - false, - ) - - // Test with scheduled votes - simulation.Simulate( - t, mapp.BaseApp, appStateFn, - []simulation.WeightedOperation{ - {10, SimulateSubmittingVotingAndSlashingForProposal(govKeeper, stakeKeeper)}, - {5, SimulateMsgDeposit(govKeeper)}, - }, []simulation.RandSetup{ - setup, - }, []simulation.Invariant{ - AllInvariants(), - }, 10, 100, - false, - ) -} diff --git a/x/mock/simulation/invariants.go b/x/mock/simulation/invariants.go index 23686005d..a3ae5f3c3 100644 --- a/x/mock/simulation/invariants.go +++ b/x/mock/simulation/invariants.go @@ -5,13 +5,15 @@ import ( "testing" "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" ) // An Invariant is a function which tests a particular invariant. // If the invariant has been broken, it should return an error // containing a descriptive message about what happened. // The simulator will then halt and print the logs. -type Invariant func(app *baseapp.BaseApp) error +type Invariant func(ctx sdk.Context) error // group of Invarient type Invariants []Invariant @@ -20,8 +22,10 @@ type Invariants []Invariant func (invs Invariants) assertAll(t *testing.T, app *baseapp.BaseApp, event string, displayLogs func()) { + ctx := app.NewContext(false, abci.Header{Height: app.LastBlockHeight() + 1}) + for i := 0; i < len(invs); i++ { - if err := invs[i](app); err != nil { + if err := invs[i](ctx); err != nil { fmt.Printf("Invariants broken after %s\n%s\n", event, err.Error()) displayLogs() t.Fatal() diff --git a/x/mock/simulation/simulate.go b/x/mock/simulation/simulate.go index 21caecef2..d2836bc7b 100644 --- a/x/mock/simulation/simulate.go +++ b/x/mock/simulation/simulate.go @@ -18,25 +18,22 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// RandSetup performs the random setup the mock module needs. -type RandSetup func(r *rand.Rand, accounts []Account) - // AppStateFn returns the app state json bytes 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, + appStateFn AppStateFn, ops WeightedOperations, invariants Invariants, numBlocks int, blockSize int, commit bool) (bool, error) { time := time.Now().UnixNano() return SimulateFromSeed(t, app, appStateFn, time, ops, - setups, invariants, numBlocks, blockSize, commit) + invariants, numBlocks, blockSize, commit) } // initialize the chain for the simulation func initChain(r *rand.Rand, params Params, accounts []Account, - setups []RandSetup, app *baseapp.BaseApp, + app *baseapp.BaseApp, appStateFn AppStateFn) mockValidators { req := abci.RequestInitChain{ @@ -45,9 +42,6 @@ func initChain(r *rand.Rand, params Params, accounts []Account, res := app.InitChain(req) validators := newMockValidators(r, res.Validators, params) - for i := 0; i < len(setups); i++ { - setups[i](r, accounts) - } return validators } @@ -56,7 +50,7 @@ func initChain(r *rand.Rand, params Params, accounts []Account, // TODO split this monster function up func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, appStateFn AppStateFn, seed int64, ops WeightedOperations, - setups []RandSetup, invariants Invariants, + invariants Invariants, 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. @@ -78,7 +72,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // Second variable to keep pending validator set (delayed one block since // TM 0.24) Initially this is the same as the initial validator set - validators := initChain(r, params, accs, setups, app, appStateFn) + validators := initChain(r, params, accs, app, appStateFn) nextValidators := validators header := abci.Header{ diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index df2b6dae4..7d127131b 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" ) func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B) { @@ -113,9 +113,9 @@ func getBlockSize(r *rand.Rand, params Params, // computationally heavy simulations. // TODO reference this function in the codebase probably through use of a switch func PeriodicInvariant(invariant Invariant, period int, offset int) Invariant { - return func(app *baseapp.BaseApp) error { - if int(app.LastBlockHeight())%period == offset { - return invariant(app) + return func(ctx sdk.Context) error { + if int(ctx.BlockHeight())%period == offset { + return invariant(ctx) } return nil } diff --git a/x/slashing/simulation/invariants.go b/x/slashing/simulation/invariants.go index 0aa0ed1e5..c140a5eb9 100644 --- a/x/slashing/simulation/invariants.go +++ b/x/slashing/simulation/invariants.go @@ -1,14 +1,14 @@ package simulation import ( - "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock/simulation" ) // TODO Any invariants to check here? // AllInvariants tests all slashing invariants func AllInvariants() simulation.Invariant { - return func(_ *baseapp.BaseApp) error { + return func(_ sdk.Context) error { return nil } } diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 44348c673..8fac4df15 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" - "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" @@ -13,7 +12,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake/keeper" stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types" - abci "github.com/tendermint/tendermint/abci/types" ) // AllInvariants runs all invariants of the stake module. @@ -22,29 +20,28 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper, f auth.FeeCollectionKeeper, d distribution.Keeper, am auth.AccountKeeper) simulation.Invariant { - return func(app *baseapp.BaseApp) error { - err := SupplyInvariants(ck, k, f, d, am)(app) + return func(ctx sdk.Context) error { + err := SupplyInvariants(ck, k, f, d, am)(ctx) if err != nil { return err } - err = PositivePowerInvariant(k)(app) + err = PositivePowerInvariant(k)(ctx) if err != nil { return err } - err = PositiveDelegationInvariant(k)(app) + err = PositiveDelegationInvariant(k)(ctx) if err != nil { return err } - err = DelegatorSharesInvariant(k)(app) + err = DelegatorSharesInvariant(k)(ctx) if err != nil { return err } - err = ValidatorSetInvariant(k)(app) - return err + return nil } } @@ -52,8 +49,7 @@ func AllInvariants(ck bank.Keeper, k stake.Keeper, // nolint: unparam func SupplyInvariants(ck bank.Keeper, k stake.Keeper, f auth.FeeCollectionKeeper, d distribution.Keeper, am auth.AccountKeeper) simulation.Invariant { - return func(app *baseapp.BaseApp) error { - ctx := app.NewContext(false, abci.Header{}) + return func(ctx sdk.Context) error { pool := k.GetPool(ctx) loose := sdk.ZeroDec() @@ -117,9 +113,7 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, // PositivePowerInvariant checks that all stored validators have > 0 power. func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { - return func(app *baseapp.BaseApp) error { - ctx := app.NewContext(false, abci.Header{}) - + return func(ctx sdk.Context) error { iterator := k.ValidatorsPowerStoreIterator(ctx) pool := k.GetPool(ctx) @@ -143,9 +137,7 @@ 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{}) - + return func(ctx sdk.Context) error { delegations := k.GetAllDelegations(ctx) for _, delegation := range delegations { if delegation.Shares.IsNegative() { @@ -164,9 +156,7 @@ func PositiveDelegationInvariant(k stake.Keeper) simulation.Invariant { // 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{}) - + return func(ctx sdk.Context) error { validators := k.GetAllValidators(ctx) for _, validator := range validators { @@ -187,11 +177,3 @@ func DelegatorSharesInvariant(k stake.Keeper) simulation.Invariant { 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 { - // TODO - return nil - } -} diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index dda344ffb..9fa469d6c 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -7,11 +7,9 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/cosmos/cosmos-sdk/x/stake/keeper" - abci "github.com/tendermint/tendermint/abci/types" ) // SimulateMsgCreateValidator @@ -228,26 +226,3 @@ func SimulateMsgBeginRedelegate(m auth.AccountKeeper, k stake.Keeper) simulation return action, nil, nil } } - -// Setup -// nolint: errcheck -func Setup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { - return func(r *rand.Rand, accs []simulation.Account) { - ctx := mapp.NewContext(false, abci.Header{}) - gen := stake.DefaultGenesisState() - stake.InitGenesis(ctx, k, gen) - params := k.GetParams(ctx) - denom := params.BondDenom - loose := sdk.ZeroInt() - mapp.AccountKeeper.IterateAccounts(ctx, func(acc auth.Account) bool { - balance := simulation.RandomAmount(r, sdk.NewInt(1000000)) - acc.SetCoins(acc.GetCoins().Plus(sdk.Coins{sdk.NewCoin(denom, balance)})) - mapp.AccountKeeper.SetAccount(ctx, acc) - loose = loose.Add(balance) - return false - }) - pool := k.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDec(loose.Int64())) - k.SetPool(ctx, pool) - } -} diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go deleted file mode 100644 index 58ec360ac..000000000 --- a/x/stake/simulation/sim_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package simulation - -import ( - "encoding/json" - "math/rand" - "testing" - - abci "github.com/tendermint/tendermint/abci/types" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/bank" - "github.com/cosmos/cosmos-sdk/x/distribution" - "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/cosmos/cosmos-sdk/x/mock/simulation" - "github.com/cosmos/cosmos-sdk/x/params" - "github.com/cosmos/cosmos-sdk/x/stake" -) - -// TestStakeWithRandomMessages -func TestStakeWithRandomMessages(t *testing.T) { - mapp := mock.NewApp() - - bank.RegisterCodec(mapp.Cdc) - mapper := mapp.AccountKeeper - bankKeeper := bank.NewBaseKeeper(mapper) - feeKey := sdk.NewKVStoreKey("fee") - stakeKey := sdk.NewKVStoreKey("stake") - stakeTKey := sdk.NewTransientStoreKey("transient_stake") - paramsKey := sdk.NewKVStoreKey("params") - paramsTKey := sdk.NewTransientStoreKey("transient_params") - distrKey := sdk.NewKVStoreKey("distr") - - feeCollectionKeeper := auth.NewFeeCollectionKeeper(mapp.Cdc, feeKey) - paramstore := params.NewKeeper(mapp.Cdc, paramsKey, paramsTKey) - stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, stakeTKey, bankKeeper, paramstore.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) - distrKeeper := distribution.NewKeeper(mapp.Cdc, distrKey, paramstore.Subspace(distribution.DefaultParamspace), bankKeeper, stakeKeeper, feeCollectionKeeper, distribution.DefaultCodespace) - mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) - mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - validatorUpdates, tags := stake.EndBlocker(ctx, stakeKeeper) - return abci.ResponseEndBlock{ - ValidatorUpdates: validatorUpdates, - Tags: tags, - } - }) - - err := mapp.CompleteSetup(stakeKey, stakeTKey, paramsKey, paramsTKey) - if err != nil { - panic(err) - } - - appStateFn := func(r *rand.Rand, accs []simulation.Account) json.RawMessage { - simulation.RandomSetGenesis(r, mapp, accs, []string{"stake"}) - return json.RawMessage("{}") - } - - simulation.Simulate( - t, mapp.BaseApp, appStateFn, - []simulation.WeightedOperation{ - {10, SimulateMsgCreateValidator(mapper, stakeKeeper)}, - {5, SimulateMsgEditValidator(stakeKeeper)}, - {15, SimulateMsgDelegate(mapper, stakeKeeper)}, - {10, SimulateMsgBeginUnbonding(mapper, stakeKeeper)}, - {10, SimulateMsgBeginRedelegate(mapper, stakeKeeper)}, - }, []simulation.RandSetup{ - Setup(mapp, stakeKeeper), - }, []simulation.Invariant{ - AllInvariants(bankKeeper, stakeKeeper, feeCollectionKeeper, distrKeeper, mapp.AccountKeeper), - }, 10, 100, - false, - ) -}