Skip to content

Commit

Permalink
feat: add simulator test wiring (#12)
Browse files Browse the repository at this point in the history
* fix: message type

* feat(simulator): sim tests

* fix: lint

* fix(simulator): scope key to tf module

* rm unused funcs

---------

Co-authored-by: Reece Williams <[email protected]>
  • Loading branch information
fmorency and Reecepbcups authored Jun 26, 2024
1 parent ec1c4ee commit 7533499
Show file tree
Hide file tree
Showing 3 changed files with 415 additions and 22 deletions.
385 changes: 385 additions & 0 deletions app/sim_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,385 @@
package app

import (
"encoding/json"
"flag"
"fmt"
"math/rand"
"os"
"runtime/debug"
"strings"
"testing"

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"

abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"

dbm "github.com/cosmos/cosmos-db"

"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/feegrant"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
"github.com/cosmos/cosmos-sdk/x/simulation"
simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)

var FlagEnableStreamingValue bool

// Get flags every time the simulator is run
func init() {
simcli.GetSimulatorFlags()
flag.BoolVar(&FlagEnableStreamingValue, "EnableStreaming", false, "Enable streaming service")
}

func TestFullAppSimulation(t *testing.T) {
config := simcli.NewConfigFromFlags()
config.ChainID = SimAppChainID

db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
if skip {
t.Skip("skipping application simulation")
}
require.NoError(t, err, "simulation setup failed")

defer func() {
require.NoError(t, db.Close())
require.NoError(t, os.RemoveAll(dir))
}()

appOptions := make(simtestutil.AppOptionsMap, 0)
appOptions[flags.FlagHome] = DefaultNodeHome
appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue

app := NewApp(logger, db, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID))
require.Equal(t, "tokenfactory", app.Name())

// run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
t,
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)

// export state and simParams before the simulation error is checked
err = simtestutil.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)

if config.Commit {
simtestutil.PrintStats(db)
}
}

func TestAppImportExport(t *testing.T) {
config := simcli.NewConfigFromFlags()
config.ChainID = SimAppChainID

db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
if skip {
t.Skip("skipping application import/export simulation")
}
require.NoError(t, err, "simulation setup failed")

defer func() {
require.NoError(t, db.Close())
require.NoError(t, os.RemoveAll(dir))
}()

appOptions := make(simtestutil.AppOptionsMap, 0)
appOptions[flags.FlagHome] = DefaultNodeHome
appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue

app := NewApp(logger, db, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID))
require.Equal(t, "tokenfactory", app.Name())

// Run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
t,
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)

// export state and simParams before the simulation error is checked
err = simtestutil.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)

if config.Commit {
simtestutil.PrintStats(db)
}

fmt.Printf("exporting genesis...\n")

exported, err := app.ExportAppStateAndValidators(false, []string{}, []string{})
require.NoError(t, err)

fmt.Printf("importing genesis...\n")

newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
require.NoError(t, err, "simulation setup failed")

defer func() {
require.NoError(t, newDB.Close())
require.NoError(t, os.RemoveAll(newDir))
}()

newApp := NewApp(log.NewNopLogger(), newDB, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID))
require.Equal(t, "tokenfactory", newApp.Name())

var genesisState GenesisState
err = json.Unmarshal(exported.AppState, &genesisState)
require.NoError(t, err)

ctxA := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
ctxB := newApp.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
_, err = newApp.ModuleManager.InitGenesis(ctxB, app.AppCodec(), genesisState)

if err != nil {
if strings.Contains(err.Error(), "validator set is empty after InitGenesis") {
logger.Info("Skipping simulation as all validators have been unbonded")
logger.Info("err", err, "stacktrace", string(debug.Stack()))
return
}
}

require.NoError(t, err)
err = newApp.StoreConsensusParams(ctxB, exported.ConsensusParams)
require.NoError(t, err)
fmt.Printf("comparing stores...\n")

// skip certain prefixes
skipPrefixes := map[string][][]byte{
stakingtypes.StoreKey: {
stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey,
stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey,
stakingtypes.UnbondingTypeKey, stakingtypes.ValidatorUpdatesKey,
},
authzkeeper.StoreKey: {authzkeeper.GrantQueuePrefix},
feegrant.StoreKey: {feegrant.FeeAllowanceQueueKeyPrefix},
slashingtypes.StoreKey: {slashingtypes.ValidatorMissedBlockBitmapKeyPrefix},
}

storeKeys := app.GetStoreKeys()
require.NotEmpty(t, storeKeys)

for _, appKeyA := range storeKeys {
// only compare kvstores
if _, ok := appKeyA.(*storetypes.KVStoreKey); !ok {
continue
}

keyName := appKeyA.Name()
appKeyB := newApp.GetKey(keyName)

storeA := ctxA.KVStore(appKeyA)
storeB := ctxB.KVStore(appKeyB)

failedKVAs, failedKVBs := simtestutil.DiffKVStores(storeA, storeB, skipPrefixes[keyName])
require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare %s", keyName)

fmt.Printf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), appKeyA, appKeyB)

require.Equal(t, 0, len(failedKVAs), simtestutil.GetSimulationLog(keyName, app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs))
}
}

func TestAppSimulationAfterImport(t *testing.T) {
config := simcli.NewConfigFromFlags()
config.ChainID = SimAppChainID

db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
if skip {
t.Skip("skipping application simulation after import")
}
require.NoError(t, err, "simulation setup failed")

defer func() {
require.NoError(t, db.Close())
require.NoError(t, os.RemoveAll(dir))
}()

appOptions := make(simtestutil.AppOptionsMap, 0)
appOptions[flags.FlagHome] = DefaultNodeHome
appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue

app := NewApp(logger, db, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID))
require.Equal(t, "tokenfactory", app.Name())

// Run randomized simulation
stopEarly, simParams, simErr := simulation.SimulateFromSeed(
t,
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)

// export state and simParams before the simulation error is checked
err = simtestutil.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)

if config.Commit {
simtestutil.PrintStats(db)
}

if stopEarly {
fmt.Println("can't export or import a zero-validator genesis, exiting test...")
return
}

fmt.Printf("exporting genesis...\n")

exported, err := app.ExportAppStateAndValidators(true, []string{}, []string{})
require.NoError(t, err)

fmt.Printf("importing genesis...\n")

newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
require.NoError(t, err, "simulation setup failed")

defer func() {
require.NoError(t, newDB.Close())
require.NoError(t, os.RemoveAll(newDir))
}()

newApp := NewApp(log.NewNopLogger(), newDB, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID))
require.Equal(t, "tokenfactory", newApp.Name())

_, err = newApp.InitChain(&abci.RequestInitChain{
AppStateBytes: exported.AppState,
ChainId: SimAppChainID,
})
require.NoError(t, err)

_, _, err = simulation.SimulateFromSeed(
t,
os.Stdout,
newApp.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(newApp, newApp.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)
require.NoError(t, err)
}

func TestAppStateDeterminism(t *testing.T) {
if !simcli.FlagEnabledValue {
t.Skip("skipping application simulation")
}

config := simcli.NewConfigFromFlags()
config.InitialBlockHeight = 1
config.ExportParamsPath = ""
config.OnOperation = false
config.AllInvariants = false
config.ChainID = SimAppChainID

numSeeds := 3
numTimesToRunPerSeed := 3 // This used to be set to 5, but we've temporarily reduced it to 3 for the sake of faster CI.
appHashList := make([]json.RawMessage, numTimesToRunPerSeed)

// We will be overriding the random seed and just run a single simulation on the provided seed value
if config.Seed != simcli.DefaultSeedValue {
numSeeds = 1
}

appOptions := viper.New()
if FlagEnableStreamingValue {
m := make(map[string]interface{})
m["streaming.abci.keys"] = []string{"*"}
m["streaming.abci.plugin"] = "abci_v1"
m["streaming.abci.stop-node-on-err"] = true
for key, value := range m {
appOptions.SetDefault(key, value)
}
}
appOptions.SetDefault(flags.FlagHome, DefaultNodeHome)
appOptions.SetDefault(server.FlagInvCheckPeriod, simcli.FlagPeriodValue)
if simcli.FlagVerboseValue {
appOptions.SetDefault(flags.FlagLogLevel, "debug")
}

for i := 0; i < numSeeds; i++ {
if config.Seed == simcli.DefaultSeedValue {
config.Seed = rand.Int63()
}

fmt.Println("config.Seed: ", config.Seed)

for j := 0; j < numTimesToRunPerSeed; j++ {
var logger log.Logger
if simcli.FlagVerboseValue {
logger = log.NewTestLogger(t)
} else {
logger = log.NewNopLogger()
}

db := dbm.NewMemDB()
app := NewApp(logger, db, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID))

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,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)
require.NoError(t, err)

if config.Commit {
simtestutil.PrintStats(db)
}

appHash := app.LastCommitID().Hash
appHashList[j] = appHash

if j != 0 {
require.Equal(
t, string(appHashList[0]), string(appHashList[j]),
"non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
)
}
}
}
}
Loading

0 comments on commit 7533499

Please sign in to comment.