github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/app/simulation_test.go (about) 1 package app 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "math/rand" 7 "os" 8 "testing" 9 "time" 10 11 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec" 12 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/prefix" 13 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/module" 14 "github.com/fibonacci-chain/fbc/x/wasm" 15 wasmtypes "github.com/fibonacci-chain/fbc/x/wasm/types" 16 17 "github.com/stretchr/testify/require" 18 19 abci "github.com/fibonacci-chain/fbc/libs/tendermint/abci/types" 20 "github.com/fibonacci-chain/fbc/libs/tendermint/libs/log" 21 dbm "github.com/fibonacci-chain/fbc/libs/tm-db" 22 23 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/baseapp" 24 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/simapp" 25 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/simapp/helpers" 26 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store" 27 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 28 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth" 29 distr "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/distribution" 30 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/mint" 31 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/simulation" 32 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/slashing" 33 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/supply" 34 "github.com/fibonacci-chain/fbc/x/gov" 35 "github.com/fibonacci-chain/fbc/x/params" 36 "github.com/fibonacci-chain/fbc/x/staking" 37 ) 38 39 func init() { 40 simapp.GetSimulatorFlags() 41 } 42 43 type storeKeysPrefixes struct { 44 A sdk.StoreKey 45 B sdk.StoreKey 46 Prefixes [][]byte 47 } 48 49 // fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of 50 // an IAVLStore for faster simulation speed. 51 func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { 52 bapp.SetFauxMerkleMode() 53 } 54 55 // interBlockCacheOpt returns a BaseApp option function that sets the persistent 56 // inter-block write-through cache. 57 func interBlockCacheOpt() func(*baseapp.BaseApp) { 58 return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) 59 } 60 61 func TestFullAppSimulation(t *testing.T) { 62 config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") 63 if skip { 64 t.Skip("skipping application simulation") 65 } 66 require.NoError(t, err, "simulation setup failed") 67 68 defer func() { 69 db.Close() 70 require.NoError(t, os.RemoveAll(dir)) 71 }() 72 73 app := NewFBChainApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt) 74 require.Equal(t, appName, app.Name()) 75 76 // run randomized simulation 77 _, simParams, simErr := simulation.SimulateFromSeed( 78 t, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()), 79 simapp.SimulationOperations(app, app.Codec(), config), 80 app.ModuleAccountAddrs(), config, 81 ) 82 83 // export state and simParams before the simulation error is checked 84 err = simapp.CheckExportSimulation(app, config, simParams) 85 require.NoError(t, err) 86 require.NoError(t, simErr) 87 88 if config.Commit { 89 simapp.PrintStats(db) 90 } 91 } 92 93 func TestAppImportExport(t *testing.T) { 94 config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") 95 if skip { 96 t.Skip("skipping application import/export simulation") 97 } 98 require.NoError(t, err, "simulation setup failed") 99 100 defer func() { 101 db.Close() 102 require.NoError(t, os.RemoveAll(dir)) 103 }() 104 105 app := NewFBChainApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt) 106 require.Equal(t, appName, app.Name()) 107 108 // Run randomized simulation 109 _, simParams, simErr := simulation.SimulateFromSeed( 110 t, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()), 111 simapp.SimulationOperations(app, app.Codec(), config), 112 app.ModuleAccountAddrs(), config, 113 ) 114 115 // export state and simParams before the simulation error is checked 116 err = simapp.CheckExportSimulation(app, config, simParams) 117 require.NoError(t, err) 118 require.NoError(t, simErr) 119 120 if config.Commit { 121 simapp.PrintStats(db) 122 } 123 124 fmt.Printf("exporting genesis...\n") 125 126 appState, _, err := app.ExportAppStateAndValidators(false, []string{}) 127 require.NoError(t, err) 128 129 fmt.Printf("importing genesis...\n") 130 131 // nolint: dogsled 132 _, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2") 133 require.NoError(t, err, "simulation setup failed") 134 135 defer func() { 136 newDB.Close() 137 require.NoError(t, os.RemoveAll(newDir)) 138 }() 139 140 newApp := NewFBChainApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt) 141 require.Equal(t, appName, newApp.Name()) 142 143 var genesisState map[string]json.RawMessage 144 err = app.Codec().UnmarshalJSON(appState, &genesisState) 145 require.NoError(t, err) 146 147 ctxA := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) 148 ctxB := newApp.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) 149 newApp.mm.InitGenesis(ctxB, genesisState) 150 151 fmt.Printf("comparing stores...\n") 152 153 storeKeysPrefixes := []storeKeysPrefixes{ 154 {app.keys[baseapp.MainStoreKey], newApp.keys[baseapp.MainStoreKey], [][]byte{}}, 155 {app.keys[auth.StoreKey], newApp.keys[auth.StoreKey], [][]byte{}}, 156 {app.keys[staking.StoreKey], newApp.keys[staking.StoreKey], 157 [][]byte{}}, // ordering may change but it doesn't matter 158 {app.keys[slashing.StoreKey], newApp.keys[slashing.StoreKey], [][]byte{}}, 159 {app.keys[mint.StoreKey], newApp.keys[mint.StoreKey], [][]byte{}}, 160 {app.keys[distr.StoreKey], newApp.keys[distr.StoreKey], [][]byte{}}, 161 {app.keys[supply.StoreKey], newApp.keys[supply.StoreKey], [][]byte{}}, 162 {app.keys[params.StoreKey], newApp.keys[params.StoreKey], [][]byte{}}, 163 {app.keys[gov.StoreKey], newApp.keys[gov.StoreKey], [][]byte{}}, 164 } 165 166 // reset contract code index in source DB for comparison with dest DB 167 dropContractHistory := func(s store.KVStore, keys ...[]byte) { 168 for _, key := range keys { 169 prefixStore := prefix.NewStore(s, key) 170 iter := prefixStore.Iterator(nil, nil) 171 for ; iter.Valid(); iter.Next() { 172 prefixStore.Delete(iter.Key()) 173 } 174 iter.Close() 175 } 176 } 177 prefixes := [][]byte{wasmtypes.ContractCodeHistoryElementPrefix, wasmtypes.ContractByCodeIDAndCreatedSecondaryIndexPrefix} 178 dropContractHistory(ctxA.KVStore(app.keys[wasm.StoreKey]), prefixes...) 179 dropContractHistory(ctxB.KVStore(newApp.keys[wasm.StoreKey]), prefixes...) 180 181 normalizeContractInfo := func(ctx sdk.Context, app *FBChainApp) { 182 var index uint64 183 app.WasmKeeper.IterateContractInfo(ctx, func(address sdk.AccAddress, info wasmtypes.ContractInfo) bool { 184 created := &wasmtypes.AbsoluteTxPosition{ 185 BlockHeight: uint64(0), 186 TxIndex: index, 187 } 188 info.Created = created 189 store := ctx.KVStore(app.keys[wasm.StoreKey]) 190 store.Set(wasmtypes.GetContractAddressKey(address), app.marshal.GetProtocMarshal().MustMarshal(&info)) 191 index++ 192 return false 193 }) 194 } 195 normalizeContractInfo(ctxA, app) 196 normalizeContractInfo(ctxB, newApp) 197 198 for _, skp := range storeKeysPrefixes { 199 storeA := ctxA.KVStore(skp.A) 200 storeB := ctxB.KVStore(skp.B) 201 202 failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) 203 require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") 204 205 fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) 206 require.Equal(t, len(failedKVAs), 0, simapp.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, app.Codec(), failedKVAs, failedKVBs)) 207 } 208 } 209 210 func TestAppSimulationAfterImport(t *testing.T) { 211 config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") 212 if skip { 213 t.Skip("skipping application simulation after import") 214 } 215 require.NoError(t, err, "simulation setup failed") 216 217 defer func() { 218 db.Close() 219 require.NoError(t, os.RemoveAll(dir)) 220 }() 221 222 app := NewFBChainApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt) 223 require.Equal(t, appName, app.Name()) 224 225 // Run randomized simulation 226 stopEarly, simParams, simErr := simulation.SimulateFromSeed( 227 t, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()), 228 simapp.SimulationOperations(app, app.Codec(), config), 229 app.ModuleAccountAddrs(), config, 230 ) 231 232 // export state and simParams before the simulation error is checked 233 err = simapp.CheckExportSimulation(app, config, simParams) 234 require.NoError(t, err) 235 require.NoError(t, simErr) 236 237 if config.Commit { 238 simapp.PrintStats(db) 239 } 240 241 if stopEarly { 242 fmt.Println("can't export or import a zero-validator genesis, exiting test...") 243 return 244 } 245 246 fmt.Printf("exporting genesis...\n") 247 248 appState, _, err := app.ExportAppStateAndValidators(true, []string{}) 249 require.NoError(t, err) 250 251 fmt.Printf("importing genesis...\n") 252 253 // nolint: dosgsled 254 _, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2") 255 require.NoError(t, err, "simulation setup failed") 256 257 defer func() { 258 newDB.Close() 259 require.NoError(t, os.RemoveAll(newDir)) 260 }() 261 262 newApp := NewFBChainApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt) 263 require.Equal(t, appName, newApp.Name()) 264 265 newApp.InitChain(abci.RequestInitChain{ 266 AppStateBytes: appState, 267 }) 268 269 _, _, err = simulation.SimulateFromSeed( 270 t, os.Stdout, newApp.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()), 271 simapp.SimulationOperations(newApp, newApp.Codec(), config), 272 newApp.ModuleAccountAddrs(), config, 273 ) 274 require.NoError(t, err) 275 } 276 277 func TestAppStateDeterminism(t *testing.T) { 278 if !simapp.FlagEnabledValue { 279 t.Skip("skipping application simulation") 280 } 281 282 config := simapp.NewConfigFromFlags() 283 config.InitialBlockHeight = 1 284 config.ExportParamsPath = "" 285 config.OnOperation = false 286 config.AllInvariants = false 287 config.ChainID = helpers.SimAppChainID 288 289 numTimesToRunPerSeed := 2 290 appHashList := make([]json.RawMessage, numTimesToRunPerSeed) 291 292 config.Seed = rand.Int63() 293 294 for i := 0; i < numTimesToRunPerSeed; i++ { 295 var logger log.Logger 296 if simapp.FlagVerboseValue { 297 logger = log.TestingLogger() 298 } else { 299 logger = log.NewNopLogger() 300 } 301 302 db := dbm.NewMemDB() 303 304 app := NewFBChainApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, interBlockCacheOpt()) 305 306 fmt.Printf( 307 "running non-determinism simulation; seed %d: attempt: %d/%d\n", 308 config.Seed, i+1, numTimesToRunPerSeed, 309 ) 310 311 _, _, err := simulation.SimulateFromSeed( 312 t, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()), 313 simapp.SimulationOperations(app, app.Codec(), config), 314 app.ModuleAccountAddrs(), config, 315 ) 316 require.NoError(t, err) 317 318 if config.Commit { 319 simapp.PrintStats(db) 320 } 321 322 appHash := app.LastCommitID().Hash 323 appHashList[i] = appHash 324 325 if i != 0 { 326 require.Equal( 327 t, appHashList[0], appHashList[i], 328 "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numTimesToRunPerSeed, 329 ) 330 } 331 } 332 } 333 334 // AppStateFn returns the initial application state using a genesis or the simulation parameters. 335 // It panics if the user provides files for both of them. 336 // If a file is not given for the genesis or the sim params, it creates a randomized one. 337 func AppStateFn(codec *codec.Codec, manager *module.SimulationManager) simulation.AppStateFn { 338 // quick hack to setup app state genesis with our app modules 339 simapp.ModuleBasics = ModuleBasics 340 if simapp.FlagGenesisTimeValue == 0 { // always set to have a block time 341 simapp.FlagGenesisTimeValue = time.Now().Unix() 342 } 343 return simapp.AppStateFn(codec, manager) 344 }