|
| 1 | +package app |
| 2 | + |
| 3 | +import ( |
| 4 | + "encoding/json" |
| 5 | + "flag" |
| 6 | + "fmt" |
| 7 | + "math/rand" |
| 8 | + "os" |
| 9 | + "runtime/debug" |
| 10 | + "strings" |
| 11 | + "testing" |
| 12 | + |
| 13 | + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" |
| 14 | + "github.com/spf13/viper" |
| 15 | + "github.com/stretchr/testify/require" |
| 16 | + |
| 17 | + abci "github.com/cometbft/cometbft/abci/types" |
| 18 | + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" |
| 19 | + |
| 20 | + dbm "github.com/cosmos/cosmos-db" |
| 21 | + |
| 22 | + "cosmossdk.io/log" |
| 23 | + storetypes "cosmossdk.io/store/types" |
| 24 | + "cosmossdk.io/x/feegrant" |
| 25 | + |
| 26 | + "github.com/cosmos/cosmos-sdk/baseapp" |
| 27 | + "github.com/cosmos/cosmos-sdk/client/flags" |
| 28 | + "github.com/cosmos/cosmos-sdk/server" |
| 29 | + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" |
| 30 | + simtypes "github.com/cosmos/cosmos-sdk/types/simulation" |
| 31 | + authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper" |
| 32 | + "github.com/cosmos/cosmos-sdk/x/simulation" |
| 33 | + simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli" |
| 34 | + slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" |
| 35 | + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" |
| 36 | +) |
| 37 | + |
| 38 | +var FlagEnableStreamingValue bool |
| 39 | + |
| 40 | +// Get flags every time the simulator is run |
| 41 | +func init() { |
| 42 | + simcli.GetSimulatorFlags() |
| 43 | + flag.BoolVar(&FlagEnableStreamingValue, "EnableStreaming", false, "Enable streaming service") |
| 44 | +} |
| 45 | + |
| 46 | +func TestFullAppSimulation(t *testing.T) { |
| 47 | + config := simcli.NewConfigFromFlags() |
| 48 | + config.ChainID = SimAppChainID |
| 49 | + |
| 50 | + db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue) |
| 51 | + if skip { |
| 52 | + t.Skip("skipping application simulation") |
| 53 | + } |
| 54 | + require.NoError(t, err, "simulation setup failed") |
| 55 | + |
| 56 | + defer func() { |
| 57 | + require.NoError(t, db.Close()) |
| 58 | + require.NoError(t, os.RemoveAll(dir)) |
| 59 | + }() |
| 60 | + |
| 61 | + appOptions := make(simtestutil.AppOptionsMap, 0) |
| 62 | + appOptions[flags.FlagHome] = DefaultNodeHome |
| 63 | + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue |
| 64 | + |
| 65 | + app := NewApp(logger, db, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID)) |
| 66 | + require.Equal(t, "tokenfactory", app.Name()) |
| 67 | + |
| 68 | + // run randomized simulation |
| 69 | + _, simParams, simErr := simulation.SimulateFromSeed( |
| 70 | + t, |
| 71 | + os.Stdout, |
| 72 | + app.BaseApp, |
| 73 | + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()), |
| 74 | + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 |
| 75 | + simtestutil.SimulationOperations(app, app.AppCodec(), config), |
| 76 | + BlockedAddresses(), |
| 77 | + config, |
| 78 | + app.AppCodec(), |
| 79 | + ) |
| 80 | + |
| 81 | + // export state and simParams before the simulation error is checked |
| 82 | + err = simtestutil.CheckExportSimulation(app, config, simParams) |
| 83 | + require.NoError(t, err) |
| 84 | + require.NoError(t, simErr) |
| 85 | + |
| 86 | + if config.Commit { |
| 87 | + simtestutil.PrintStats(db) |
| 88 | + } |
| 89 | +} |
| 90 | + |
| 91 | +func TestAppImportExport(t *testing.T) { |
| 92 | + config := simcli.NewConfigFromFlags() |
| 93 | + config.ChainID = SimAppChainID |
| 94 | + |
| 95 | + db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue) |
| 96 | + if skip { |
| 97 | + t.Skip("skipping application import/export simulation") |
| 98 | + } |
| 99 | + require.NoError(t, err, "simulation setup failed") |
| 100 | + |
| 101 | + defer func() { |
| 102 | + require.NoError(t, db.Close()) |
| 103 | + require.NoError(t, os.RemoveAll(dir)) |
| 104 | + }() |
| 105 | + |
| 106 | + appOptions := make(simtestutil.AppOptionsMap, 0) |
| 107 | + appOptions[flags.FlagHome] = DefaultNodeHome |
| 108 | + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue |
| 109 | + |
| 110 | + app := NewApp(logger, db, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID)) |
| 111 | + require.Equal(t, "tokenfactory", app.Name()) |
| 112 | + |
| 113 | + // Run randomized simulation |
| 114 | + _, simParams, simErr := simulation.SimulateFromSeed( |
| 115 | + t, |
| 116 | + os.Stdout, |
| 117 | + app.BaseApp, |
| 118 | + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()), |
| 119 | + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 |
| 120 | + simtestutil.SimulationOperations(app, app.AppCodec(), config), |
| 121 | + BlockedAddresses(), |
| 122 | + config, |
| 123 | + app.AppCodec(), |
| 124 | + ) |
| 125 | + |
| 126 | + // export state and simParams before the simulation error is checked |
| 127 | + err = simtestutil.CheckExportSimulation(app, config, simParams) |
| 128 | + require.NoError(t, err) |
| 129 | + require.NoError(t, simErr) |
| 130 | + |
| 131 | + if config.Commit { |
| 132 | + simtestutil.PrintStats(db) |
| 133 | + } |
| 134 | + |
| 135 | + fmt.Printf("exporting genesis...\n") |
| 136 | + |
| 137 | + exported, err := app.ExportAppStateAndValidators(false, []string{}, []string{}) |
| 138 | + require.NoError(t, err) |
| 139 | + |
| 140 | + fmt.Printf("importing genesis...\n") |
| 141 | + |
| 142 | + newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue) |
| 143 | + require.NoError(t, err, "simulation setup failed") |
| 144 | + |
| 145 | + defer func() { |
| 146 | + require.NoError(t, newDB.Close()) |
| 147 | + require.NoError(t, os.RemoveAll(newDir)) |
| 148 | + }() |
| 149 | + |
| 150 | + newApp := NewApp(log.NewNopLogger(), newDB, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID)) |
| 151 | + require.Equal(t, "tokenfactory", newApp.Name()) |
| 152 | + |
| 153 | + var genesisState GenesisState |
| 154 | + err = json.Unmarshal(exported.AppState, &genesisState) |
| 155 | + require.NoError(t, err) |
| 156 | + |
| 157 | + ctxA := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()}) |
| 158 | + ctxB := newApp.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()}) |
| 159 | + _, err = newApp.ModuleManager.InitGenesis(ctxB, app.AppCodec(), genesisState) |
| 160 | + |
| 161 | + if err != nil { |
| 162 | + if strings.Contains(err.Error(), "validator set is empty after InitGenesis") { |
| 163 | + logger.Info("Skipping simulation as all validators have been unbonded") |
| 164 | + logger.Info("err", err, "stacktrace", string(debug.Stack())) |
| 165 | + return |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + require.NoError(t, err) |
| 170 | + err = newApp.StoreConsensusParams(ctxB, exported.ConsensusParams) |
| 171 | + require.NoError(t, err) |
| 172 | + fmt.Printf("comparing stores...\n") |
| 173 | + |
| 174 | + // skip certain prefixes |
| 175 | + skipPrefixes := map[string][][]byte{ |
| 176 | + stakingtypes.StoreKey: { |
| 177 | + stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey, |
| 178 | + stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey, |
| 179 | + stakingtypes.UnbondingTypeKey, stakingtypes.ValidatorUpdatesKey, |
| 180 | + }, |
| 181 | + authzkeeper.StoreKey: {authzkeeper.GrantQueuePrefix}, |
| 182 | + feegrant.StoreKey: {feegrant.FeeAllowanceQueueKeyPrefix}, |
| 183 | + slashingtypes.StoreKey: {slashingtypes.ValidatorMissedBlockBitmapKeyPrefix}, |
| 184 | + } |
| 185 | + |
| 186 | + storeKeys := app.GetStoreKeys() |
| 187 | + require.NotEmpty(t, storeKeys) |
| 188 | + |
| 189 | + for _, appKeyA := range storeKeys { |
| 190 | + // only compare kvstores |
| 191 | + if _, ok := appKeyA.(*storetypes.KVStoreKey); !ok { |
| 192 | + continue |
| 193 | + } |
| 194 | + |
| 195 | + keyName := appKeyA.Name() |
| 196 | + appKeyB := newApp.GetKey(keyName) |
| 197 | + |
| 198 | + storeA := ctxA.KVStore(appKeyA) |
| 199 | + storeB := ctxB.KVStore(appKeyB) |
| 200 | + |
| 201 | + failedKVAs, failedKVBs := simtestutil.DiffKVStores(storeA, storeB, skipPrefixes[keyName]) |
| 202 | + require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare %s", keyName) |
| 203 | + |
| 204 | + fmt.Printf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), appKeyA, appKeyB) |
| 205 | + |
| 206 | + require.Equal(t, 0, len(failedKVAs), simtestutil.GetSimulationLog(keyName, app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) |
| 207 | + } |
| 208 | +} |
| 209 | + |
| 210 | +func TestAppSimulationAfterImport(t *testing.T) { |
| 211 | + config := simcli.NewConfigFromFlags() |
| 212 | + config.ChainID = SimAppChainID |
| 213 | + |
| 214 | + db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue) |
| 215 | + if skip { |
| 216 | + t.Skip("skipping application simulation after import") |
| 217 | + } |
| 218 | + require.NoError(t, err, "simulation setup failed") |
| 219 | + |
| 220 | + defer func() { |
| 221 | + require.NoError(t, db.Close()) |
| 222 | + require.NoError(t, os.RemoveAll(dir)) |
| 223 | + }() |
| 224 | + |
| 225 | + appOptions := make(simtestutil.AppOptionsMap, 0) |
| 226 | + appOptions[flags.FlagHome] = DefaultNodeHome |
| 227 | + appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue |
| 228 | + |
| 229 | + app := NewApp(logger, db, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID)) |
| 230 | + require.Equal(t, "tokenfactory", app.Name()) |
| 231 | + |
| 232 | + // Run randomized simulation |
| 233 | + stopEarly, simParams, simErr := simulation.SimulateFromSeed( |
| 234 | + t, |
| 235 | + os.Stdout, |
| 236 | + app.BaseApp, |
| 237 | + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()), |
| 238 | + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 |
| 239 | + simtestutil.SimulationOperations(app, app.AppCodec(), config), |
| 240 | + BlockedAddresses(), |
| 241 | + config, |
| 242 | + app.AppCodec(), |
| 243 | + ) |
| 244 | + |
| 245 | + // export state and simParams before the simulation error is checked |
| 246 | + err = simtestutil.CheckExportSimulation(app, config, simParams) |
| 247 | + require.NoError(t, err) |
| 248 | + require.NoError(t, simErr) |
| 249 | + |
| 250 | + if config.Commit { |
| 251 | + simtestutil.PrintStats(db) |
| 252 | + } |
| 253 | + |
| 254 | + if stopEarly { |
| 255 | + fmt.Println("can't export or import a zero-validator genesis, exiting test...") |
| 256 | + return |
| 257 | + } |
| 258 | + |
| 259 | + fmt.Printf("exporting genesis...\n") |
| 260 | + |
| 261 | + exported, err := app.ExportAppStateAndValidators(true, []string{}, []string{}) |
| 262 | + require.NoError(t, err) |
| 263 | + |
| 264 | + fmt.Printf("importing genesis...\n") |
| 265 | + |
| 266 | + newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue) |
| 267 | + require.NoError(t, err, "simulation setup failed") |
| 268 | + |
| 269 | + defer func() { |
| 270 | + require.NoError(t, newDB.Close()) |
| 271 | + require.NoError(t, os.RemoveAll(newDir)) |
| 272 | + }() |
| 273 | + |
| 274 | + newApp := NewApp(log.NewNopLogger(), newDB, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID)) |
| 275 | + require.Equal(t, "tokenfactory", newApp.Name()) |
| 276 | + |
| 277 | + _, err = newApp.InitChain(&abci.RequestInitChain{ |
| 278 | + AppStateBytes: exported.AppState, |
| 279 | + ChainId: SimAppChainID, |
| 280 | + }) |
| 281 | + require.NoError(t, err) |
| 282 | + |
| 283 | + _, _, err = simulation.SimulateFromSeed( |
| 284 | + t, |
| 285 | + os.Stdout, |
| 286 | + newApp.BaseApp, |
| 287 | + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()), |
| 288 | + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 |
| 289 | + simtestutil.SimulationOperations(newApp, newApp.AppCodec(), config), |
| 290 | + BlockedAddresses(), |
| 291 | + config, |
| 292 | + app.AppCodec(), |
| 293 | + ) |
| 294 | + require.NoError(t, err) |
| 295 | +} |
| 296 | + |
| 297 | +func TestAppStateDeterminism(t *testing.T) { |
| 298 | + if !simcli.FlagEnabledValue { |
| 299 | + t.Skip("skipping application simulation") |
| 300 | + } |
| 301 | + |
| 302 | + config := simcli.NewConfigFromFlags() |
| 303 | + config.InitialBlockHeight = 1 |
| 304 | + config.ExportParamsPath = "" |
| 305 | + config.OnOperation = false |
| 306 | + config.AllInvariants = false |
| 307 | + config.ChainID = SimAppChainID |
| 308 | + |
| 309 | + numSeeds := 3 |
| 310 | + numTimesToRunPerSeed := 3 // This used to be set to 5, but we've temporarily reduced it to 3 for the sake of faster CI. |
| 311 | + appHashList := make([]json.RawMessage, numTimesToRunPerSeed) |
| 312 | + |
| 313 | + // We will be overriding the random seed and just run a single simulation on the provided seed value |
| 314 | + if config.Seed != simcli.DefaultSeedValue { |
| 315 | + numSeeds = 1 |
| 316 | + } |
| 317 | + |
| 318 | + appOptions := viper.New() |
| 319 | + if FlagEnableStreamingValue { |
| 320 | + m := make(map[string]interface{}) |
| 321 | + m["streaming.abci.keys"] = []string{"*"} |
| 322 | + m["streaming.abci.plugin"] = "abci_v1" |
| 323 | + m["streaming.abci.stop-node-on-err"] = true |
| 324 | + for key, value := range m { |
| 325 | + appOptions.SetDefault(key, value) |
| 326 | + } |
| 327 | + } |
| 328 | + appOptions.SetDefault(flags.FlagHome, DefaultNodeHome) |
| 329 | + appOptions.SetDefault(server.FlagInvCheckPeriod, simcli.FlagPeriodValue) |
| 330 | + if simcli.FlagVerboseValue { |
| 331 | + appOptions.SetDefault(flags.FlagLogLevel, "debug") |
| 332 | + } |
| 333 | + |
| 334 | + for i := 0; i < numSeeds; i++ { |
| 335 | + if config.Seed == simcli.DefaultSeedValue { |
| 336 | + config.Seed = rand.Int63() |
| 337 | + } |
| 338 | + |
| 339 | + fmt.Println("config.Seed: ", config.Seed) |
| 340 | + |
| 341 | + for j := 0; j < numTimesToRunPerSeed; j++ { |
| 342 | + var logger log.Logger |
| 343 | + if simcli.FlagVerboseValue { |
| 344 | + logger = log.NewTestLogger(t) |
| 345 | + } else { |
| 346 | + logger = log.NewNopLogger() |
| 347 | + } |
| 348 | + |
| 349 | + db := dbm.NewMemDB() |
| 350 | + app := NewApp(logger, db, nil, true, appOptions, []wasmkeeper.Option{}, baseapp.SetChainID(SimAppChainID)) |
| 351 | + |
| 352 | + fmt.Printf( |
| 353 | + "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", |
| 354 | + config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, |
| 355 | + ) |
| 356 | + |
| 357 | + _, _, err := simulation.SimulateFromSeed( |
| 358 | + t, |
| 359 | + os.Stdout, |
| 360 | + app.BaseApp, |
| 361 | + simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()), |
| 362 | + simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1 |
| 363 | + simtestutil.SimulationOperations(app, app.AppCodec(), config), |
| 364 | + BlockedAddresses(), |
| 365 | + config, |
| 366 | + app.AppCodec(), |
| 367 | + ) |
| 368 | + require.NoError(t, err) |
| 369 | + |
| 370 | + if config.Commit { |
| 371 | + simtestutil.PrintStats(db) |
| 372 | + } |
| 373 | + |
| 374 | + appHash := app.LastCommitID().Hash |
| 375 | + appHashList[j] = appHash |
| 376 | + |
| 377 | + if j != 0 { |
| 378 | + require.Equal( |
| 379 | + t, string(appHashList[0]), string(appHashList[j]), |
| 380 | + "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, |
| 381 | + ) |
| 382 | + } |
| 383 | + } |
| 384 | + } |
| 385 | +} |
0 commit comments