diff --git a/module/Makefile b/module/Makefile index 9fb06ed3f..e58889389 100644 --- a/module/Makefile +++ b/module/Makefile @@ -169,8 +169,21 @@ proto-update-deps: ############################################################################### SIMAPP = github.com/peggyjv/gravity-bridge/module/v2/app +BINDIR ?= ~/go/bin +runsim: $(BINDIR)/runsim +$(BINDIR)/runsim: + @echo "Installing runsim..." + @(cd /tmp && go install github.com/cosmos/tools/cmd/runsim@v1.0.0) test-sim-nondeterminism: @echo "Running non-determinism test..." @go test -mod=readonly $(SIMAPP) -run TestAppStateDeterminism -Enabled=true \ - -NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h \ No newline at end of file + -NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h + +test-sim-import-export: runsim + @echo "Running application import/export simulation. This may take several minutes..." + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 50 5 TestAppImportExport + +test-sim-after-import: runsim + @echo "Running application simulation-after-import. This may take several minutes..." + @$(BINDIR)/runsim -Jobs=4 -SimAppPkg=$(SIMAPP) -ExitOnFail 50 5 TestAppSimulationAfterImport diff --git a/module/app/export.go b/module/app/export.go index 01d20c712..7bc2e5915 100644 --- a/module/app/export.go +++ b/module/app/export.go @@ -43,7 +43,8 @@ func (app *Gravity) ExportAppStateAndValidators( // prepare for fresh start at zero height // NOTE zero height genesis is a temporary feature which will be deprecated -// in favour of export at a block height +// +// in favour of export at a block height func (app *Gravity) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string) { applyWhiteList := false @@ -69,10 +70,7 @@ func (app *Gravity) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []st // withdraw all validator commission app.stakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) { - _, err := app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) - if err != nil { - log.Fatal(err) - } + _, _ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) return false }) @@ -160,7 +158,7 @@ func (app *Gravity) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []st counter := int16(0) for ; iter.Valid(); iter.Next() { - addr := sdk.ValAddress(iter.Key()[1:]) + addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key())) validator, found := app.stakingKeeper.GetValidator(ctx, addr) if !found { panic("expected validator, not found") diff --git a/module/app/sim_test.go b/module/app/sim_test.go index 8f473e26e..53222f421 100644 --- a/module/app/sim_test.go +++ b/module/app/sim_test.go @@ -5,6 +5,8 @@ import ( "fmt" "math/rand" "os" + "runtime/debug" + "strings" "testing" "github.com/cosmos/cosmos-sdk/baseapp" @@ -71,24 +73,24 @@ func TestFullAppSimulation(t *testing.T) { app.BaseApp, AppStateFn(app.AppCodec(), app.SimulationManager()), simtypes.RandomAccounts, - SimulationOperations(*app, app.AppCodec(), config), + simapp.SimulationOperations(app, app.AppCodec(), config), app.ModuleAccountAddrs(), config, app.AppCodec(), ) // export state and simParams before the simulation error is checked - err = CheckExportSimulation(*app, config, simParams) + err = simapp.CheckExportSimulation(app, config, simParams) require.NoError(t, err) require.NoError(t, simErr) if config.Commit { - PrintStats(db) + simapp.PrintStats(db) } } func TestAppImportExport(t *testing.T) { - config, db, dir, logger, skip, err := SetupSimulation("leveldb-app-sim", "Simulation") + config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") if skip { t.Skip("skipping application import/export simulation") } @@ -109,19 +111,19 @@ func TestAppImportExport(t *testing.T) { app.BaseApp, AppStateFn(app.AppCodec(), app.SimulationManager()), simtypes.RandomAccounts, - SimulationOperations(*app, app.AppCodec(), config), + simapp.SimulationOperations(app, app.AppCodec(), config), app.ModuleAccountAddrs(), config, app.AppCodec(), ) // export state and simParams before the simulation error is checked - err = CheckExportSimulation(*app, config, simParams) + err = simapp.CheckExportSimulation(app, config, simParams) require.NoError(t, err) require.NoError(t, simErr) if config.Commit { - PrintStats(db) + simapp.PrintStats(db) } fmt.Printf("exporting genesis...\n") @@ -139,16 +141,28 @@ func TestAppImportExport(t *testing.T) { require.NoError(t, os.RemoveAll(newDir)) }() - newApp := NewGravityApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + newApp := NewGravityApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, DefaultNodeHome, FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) require.Equal(t, appName, newApp.Name()) var genesisState GenesisState err = json.Unmarshal(appState.AppState, &genesisState) require.NoError(t, err) + defer func() { + if r := recover(); r != nil { + err := fmt.Sprintf("%v", r) + if !strings.Contains(err, "validator set is empty after InitGenesis") { + panic(r) + } + logger.Info("Skipping simulation as all validators have been unbonded") + logger.Info("err", err, "stacktrace", string(debug.Stack())) + } + }() + ctxA := app.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) ctxB := newApp.NewContext(true, tmproto.Header{Height: app.LastBlockHeight()}) newApp.mm.InitGenesis(ctxB, app.AppCodec(), genesisState) + newApp.StoreConsensusParams(ctxB, appState.ConsensusParams) fmt.Printf("comparing stores...\n") @@ -180,7 +194,7 @@ func TestAppImportExport(t *testing.T) { require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) - require.Equal(t, len(failedKVAs), 0, GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) + require.Equal(t, len(failedKVAs), 0, simapp.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) } } @@ -206,19 +220,19 @@ func TestAppSimulationAfterImport(t *testing.T) { app.BaseApp, AppStateFn(app.AppCodec(), app.SimulationManager()), simtypes.RandomAccounts, - SimulationOperations(*app, app.AppCodec(), config), + simapp.SimulationOperations(app, app.AppCodec(), config), app.ModuleAccountAddrs(), config, app.AppCodec(), ) // export state and simParams before the simulation error is checked - err = CheckExportSimulation(*app, config, simParams) + err = simapp.CheckExportSimulation(app, config, simParams) require.NoError(t, err) require.NoError(t, simErr) if config.Commit { - PrintStats(db) + simapp.PrintStats(db) } if stopEarly { @@ -233,28 +247,27 @@ func TestAppSimulationAfterImport(t *testing.T) { fmt.Printf("importing genesis...\n") - _, newDB, newDir, _, _, err := SetupSimulation("leveldb-app-sim-2", "Simulation-2") + _, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2") require.NoError(t, err, "simulation setup failed") defer func() { - newDB.Close() + require.NoError(t, newDB.Close()) require.NoError(t, os.RemoveAll(newDir)) }() - newApp := NewGravityApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, DefaultNodeHome, FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) + newApp := NewGravityApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, DefaultNodeHome, FlagPeriodValue, MakeEncodingConfig(), EmptyAppOptions{}, fauxMerkleModeOpt) require.Equal(t, appName, newApp.Name()) newApp.InitChain(abci.RequestInitChain{ AppStateBytes: appState.AppState, }) - _, _, err = simulation.SimulateFromSeed( t, os.Stdout, newApp.BaseApp, AppStateFn(app.AppCodec(), app.SimulationManager()), simtypes.RandomAccounts, - SimulationOperations(*newApp, newApp.AppCodec(), config), + simapp.SimulationOperations(newApp, newApp.AppCodec(), config), newApp.ModuleAccountAddrs(), config, app.AppCodec(), @@ -311,7 +324,7 @@ func TestAppStateDeterminism(t *testing.T) { require.NoError(t, err) if config.Commit { - PrintStats(db) + simapp.PrintStats(db) } appHash := app.LastCommitID().Hash diff --git a/module/app/state.go b/module/app/state.go index a4c0d6408..ee5299fa2 100644 --- a/module/app/state.go +++ b/module/app/state.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + "github.com/cosmos/cosmos-sdk/simapp" simappparams "github.com/cosmos/cosmos-sdk/simapp/params" sdk "github.com/cosmos/cosmos-sdk/types" tmjson "github.com/tendermint/tendermint/libs/json" @@ -18,6 +19,8 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" simtypes "github.com/cosmos/cosmos-sdk/types/simulation" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // TODO: audit this code when we hook up simulations @@ -29,10 +32,10 @@ func AppStateFn(cdc codec.JSONCodec, simManager *module.SimulationManager) simty return func(r *rand.Rand, accs []simtypes.Account, config simtypes.Config, ) (appState json.RawMessage, simAccs []simtypes.Account, chainID string, genesisTimestamp time.Time) { - if FlagGenesisTimeValue == 0 { + if simapp.FlagGenesisTimeValue == 0 { genesisTimestamp = simtypes.RandTimestamp(r) } else { - genesisTimestamp = time.Unix(FlagGenesisTimeValue, 0) + genesisTimestamp = time.Unix(simapp.FlagGenesisTimeValue, 0) } chainID = config.ChainID @@ -44,7 +47,7 @@ func AppStateFn(cdc codec.JSONCodec, simManager *module.SimulationManager) simty // override the default chain-id from simapp to set it later to the config genesisDoc, accounts := StateFromGenesisFileFn(r, cdc, config.GenesisFile) - if FlagGenesisTimeValue == 0 { + if simapp.FlagGenesisTimeValue == 0 { // use genesis timestamp if no custom timestamp is provided (i.e no random timestamp) genesisTimestamp = genesisDoc.GenesisTime } @@ -71,6 +74,67 @@ func AppStateFn(cdc codec.JSONCodec, simManager *module.SimulationManager) simty appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams) } + rawState := make(map[string]json.RawMessage) + err := json.Unmarshal(appState, &rawState) + if err != nil { + panic(err) + } + + stakingStateBz, ok := rawState[stakingtypes.ModuleName] + if !ok { + panic("staking genesis state is missing") + } + + stakingState := new(stakingtypes.GenesisState) + err = cdc.UnmarshalJSON(stakingStateBz, stakingState) + if err != nil { + panic(err) + } + // compute not bonded balance + notBondedTokens := sdk.ZeroInt() + for _, val := range stakingState.Validators { + if val.Status != stakingtypes.Unbonded { + continue + } + notBondedTokens = notBondedTokens.Add(val.GetTokens()) + } + notBondedCoins := sdk.NewCoin(stakingState.Params.BondDenom, notBondedTokens) + // edit bank state to make it have the not bonded pool tokens + bankStateBz, ok := rawState[banktypes.ModuleName] + // TODO(fdymylja/jonathan): should we panic in this case + if !ok { + panic("bank genesis state is missing") + } + bankState := new(banktypes.GenesisState) + err = cdc.UnmarshalJSON(bankStateBz, bankState) + if err != nil { + panic(err) + } + + stakingAddr := authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String() + var found bool + for _, balance := range bankState.Balances { + if balance.Address == stakingAddr { + found = true + break + } + } + if !found { + bankState.Balances = append(bankState.Balances, banktypes.Balance{ + Address: stakingAddr, + Coins: sdk.NewCoins(notBondedCoins), + }) + } + + // change appState back + rawState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingState) + rawState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankState) + + // replace appstate + appState, err = json.Marshal(rawState) + if err != nil { + panic(err) + } return appState, simAccs, chainID, genesisTimestamp } } diff --git a/module/app/utils.go b/module/app/utils.go deleted file mode 100644 index 9f9883c7d..000000000 --- a/module/app/utils.go +++ /dev/null @@ -1,131 +0,0 @@ -package app - -import ( - "encoding/json" - "fmt" - "io/ioutil" - - "github.com/tendermint/tendermint/libs/log" - dbm "github.com/tendermint/tm-db" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/simapp/helpers" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/kv" - "github.com/cosmos/cosmos-sdk/types/module" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" -) - -// SetupSimulation creates the config, db (levelDB), temporary directory and logger for -// the simulation tests. If `FlagEnabledValue` is false it skips the current test. -// Returns error on an invalid db intantiation or temp dir creation. -func SetupSimulation(dirPrefix, dbName string) (simtypes.Config, dbm.DB, string, log.Logger, bool, error) { - if !FlagEnabledValue { - return simtypes.Config{}, nil, "", nil, true, nil - } - - config := NewConfigFromFlags() - config.ChainID = helpers.SimAppChainID - - var logger log.Logger - if FlagVerboseValue { - logger = log.TestingLogger() - } else { - logger = log.NewNopLogger() - } - - dir, err := ioutil.TempDir("", dirPrefix) - if err != nil { - return simtypes.Config{}, nil, "", nil, false, err - } - - db, err := sdk.NewLevelDB(dbName, dir) - if err != nil { - return simtypes.Config{}, nil, "", nil, false, err - } - - return config, db, dir, logger, false, nil -} - -// SimulationOperations retrieves the simulation params from the provided file path -// and returns all the modules weighted operations -func SimulationOperations(app Gravity, cdc codec.JSONCodec, config simtypes.Config) []simtypes.WeightedOperation { - simState := module.SimulationState{ - AppParams: make(simtypes.AppParams), - Cdc: cdc, - } - - if config.ParamsFile != "" { - bz, err := ioutil.ReadFile(config.ParamsFile) - if err != nil { - panic(err) - } - - err = json.Unmarshal(bz, &simState.AppParams) - if err != nil { - panic(err) - } - } - - simState.ParamChanges = app.SimulationManager().GenerateParamChanges(config.Seed) - simState.Contents = app.SimulationManager().GetProposalContents(simState) - return app.SimulationManager().WeightedOperations(simState) -} - -// CheckExportSimulation exports the app state and simulation parameters to JSON -// if the export paths are defined. -func CheckExportSimulation( - app Gravity, config simtypes.Config, params simtypes.Params, -) error { - if config.ExportStatePath != "" { - fmt.Println("exporting app state...") - exported, err := app.ExportAppStateAndValidators(false, nil) - if err != nil { - return err - } - - if err := ioutil.WriteFile(config.ExportStatePath, []byte(exported.AppState), 0600); err != nil { - return err - } - } - - if config.ExportParamsPath != "" { - fmt.Println("exporting simulation params...") - paramsBz, err := json.MarshalIndent(params, "", " ") - if err != nil { - return err - } - - if err := ioutil.WriteFile(config.ExportParamsPath, paramsBz, 0600); err != nil { - return err - } - } - return nil -} - -// PrintStats prints the corresponding statistics from the app DB. -func PrintStats(db dbm.DB) { - fmt.Println("\nLevelDB Stats") - fmt.Println(db.Stats()["leveldb.stats"]) - fmt.Println("LevelDB cached block size", db.Stats()["leveldb.cachedblock"]) -} - -// GetSimulationLog unmarshals the KVPair's Value to the corresponding type based on the -// each's module store key and the prefix bytes of the KVPair's key. -func GetSimulationLog(storeName string, sdr sdk.StoreDecoderRegistry, kvAs, kvBs []kv.Pair) (log string) { - for i := 0; i < len(kvAs); i++ { - if len(kvAs[i].Value) == 0 && len(kvBs[i].Value) == 0 { - // skip if the value doesn't have any bytes - continue - } - - decoder, ok := sdr[storeName] - if ok { - log += decoder(kvAs[i], kvBs[i]) - } else { - log += fmt.Sprintf("store A %X => %X\nstore B %X => %X\n", kvAs[i].Key, kvAs[i].Value, kvBs[i].Key, kvBs[i].Value) - } - } - - return log -} diff --git a/module/x/gravity/simulation/genesis.go b/module/x/gravity/simulation/genesis.go index 6b63848b8..ce177b08a 100644 --- a/module/x/gravity/simulation/genesis.go +++ b/module/x/gravity/simulation/genesis.go @@ -30,6 +30,10 @@ func genRandomString(r *rand.Rand, minLength, maxLength uint8) string { return hex.EncodeToString(bz) } +func GenBridgeActive(r *rand.Rand) bool { + return true +} + func genRandomParams(r *rand.Rand) types.Params { return types.Params{ GravityId: genRandomString(r, 16, 16), @@ -47,10 +51,10 @@ func genRandomParams(r *rand.Rand) types.Params { SlashFractionEthereumSignature: sdk.NewDec(1).Quo(sdk.NewDec(1000)), SlashFractionConflictingEthereumSignature: sdk.NewDec(1).Quo(sdk.NewDec(1000)), UnbondSlashingSignerSetTxsWindow: uint64(r.Intn(maxBlocksInOneRound)), - BridgeActive: true, - BatchCreationPeriod: uint64(r.Intn(maxBlocksInOneRound)), + BridgeActive: GenBridgeActive(r), + BatchCreationPeriod: uint64(r.Intn(maxBlocksInOneRound)) + 1, BatchMaxElement: uint64(r.Intn(100)), - ObserveEthereumHeightPeriod: r.Uint64(), + ObserveEthereumHeightPeriod: r.Uint64() + 1, } } diff --git a/module/x/gravity/simulation/params.go b/module/x/gravity/simulation/params.go index 61a9b603b..053057543 100644 --- a/module/x/gravity/simulation/params.go +++ b/module/x/gravity/simulation/params.go @@ -46,5 +46,10 @@ func ParamChanges(r *rand.Rand) []simtypes.ParamChange { return fmt.Sprintf("\"%d\"", uint64(r.Intn(100))+1) }, ), + simulation.NewSimParamChange(types.ModuleName, string(types.ParamStoreBridgeActive), + func(r *rand.Rand) string { + return fmt.Sprintf("%v", GenBridgeActive(r)) + }, + ), } }