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
This commit is contained in:
Christopher Goes 2018-11-29 16:17:10 +01:00 committed by GitHub
parent 5d700a597c
commit b2b026b5e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 62 additions and 453 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))
}
}

View File

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

3
scripts/README.md Normal file
View File

@ -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.

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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 {

View File

@ -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,
)
}

View File

@ -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()

View File

@ -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

View File

@ -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,
)
}

View File

@ -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()

View File

@ -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{

View File

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

View File

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

View File

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

View File

@ -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)
}
}

View File

@ -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,
)
}