Skip to content

Commit 7533499

Browse files
feat: add simulator test wiring (#12)
* 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]>
1 parent ec1c4ee commit 7533499

File tree

3 files changed

+415
-22
lines changed

3 files changed

+415
-22
lines changed

app/sim_test.go

Lines changed: 385 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
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

Comments
 (0)