From 41adc549f33770a3fb0427a52bf0f78d72287f8f Mon Sep 17 00:00:00 2001 From: Facundo Medica <14063057+facundomedica@users.noreply.github.com> Date: Tue, 31 Jan 2023 16:17:04 -0300 Subject: [PATCH] feat!: return errors in module manager ABCI methods (#14847) --- CHANGELOG.md | 3 +- baseapp/abci.go | 17 +++++++++-- baseapp/abci_test.go | 12 ++++---- runtime/app.go | 6 ++-- runtime/types.go | 6 ++-- server/mock/app.go | 9 +++--- simapp/app.go | 9 +++--- simapp/export.go | 6 +++- tests/e2e/params/suite.go | 2 +- tests/e2e/staking/suite.go | 1 - types/abci.go | 6 ++-- types/module/module.go | 61 ++++++++++++++++++++++--------------- types/module/module_test.go | 57 +++++++++++++++++++++++----------- x/upgrade/doc.go | 4 +-- 14 files changed, 125 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce4ca804cb75..a6ac8032dee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -169,8 +169,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking Changes +* [#14847](https://github.com/cosmos/cosmos-sdk/pull/14847) App and ModuleManager methods `InitGenesis`, `ExportGenesis`, `BeginBlock` and `EndBlock` now also return an error. * (simulation) [#14728](https://github.com/cosmos/cosmos-sdk/pull/14728) Rename the `ParamChanges` field to `LegacyParamChange` and `Contents` to `LegacyProposalContents` in `simulation.SimulationState`. Additionally it adds a `ProposalMsgs` field to `simulation.SimulationState`. -* (x/upgrade) [14764](https://github.com/cosmos/cosmos-sdk/pull/14764) The `x/upgrade` module is extracted to have a separate go.mod file which allows it to be a standalone module. +* (x/upgrade) [#14764](https://github.com/cosmos/cosmos-sdk/pull/14764) The `x/upgrade` module is extracted to have a separate go.mod file which allows it to be a standalone module. * (x/gov) [#14782](https://github.com/cosmos/cosmos-sdk/pull/14782) Move the `metadata` argument in `govv1.NewProposal` alongside `title` and `summary`. * (store) [#14746](https://github.com/cosmos/cosmos-sdk/pull/14746) Extract Store in its own go.mod and rename the package to `cosmossdk.io/store`. * (simulation) [#14751](https://github.com/cosmos/cosmos-sdk/pull/14751) Remove the `MsgType` field from `simulation.OperationInput` struct. diff --git a/baseapp/abci.go b/baseapp/abci.go index 9522daf41a5d..310d5b4012d9 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -73,7 +73,10 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC // add block gas meter for any genesis transactions (allow infinite gas) app.deliverState.ctx = app.deliverState.ctx.WithBlockGasMeter(storetypes.NewInfiniteGasMeter()) - res = app.initChainer(app.deliverState.ctx, req) + res, err := app.initChainer(app.deliverState.ctx, req) + if err != nil { + panic(err) + } // sanity check if len(req.Validators) > 0 { @@ -195,7 +198,11 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg } if app.beginBlocker != nil { - res = app.beginBlocker(app.deliverState.ctx, req) + var err error + res, err = app.beginBlocker(app.deliverState.ctx, req) + if err != nil { + panic(err) + } res.Events = sdk.MarkEventsToIndex(res.Events, app.indexEvents) } // set the signed validators for addition to context in deliverTx @@ -218,7 +225,11 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc } if app.endBlocker != nil { - res = app.endBlocker(app.deliverState.ctx, req) + var err error + res, err = app.endBlocker(app.deliverState.ctx, req) + if err != nil { + panic(err) + } res.Events = sdk.MarkEventsToIndex(res.Events, app.indexEvents) } diff --git a/baseapp/abci_test.go b/baseapp/abci_test.go index 8581e0533852..c86fe42bbffc 100644 --- a/baseapp/abci_test.go +++ b/baseapp/abci_test.go @@ -54,10 +54,10 @@ func TestABCI_InitChain(t *testing.T) { // set a value in the store on init chain key, value := []byte("hello"), []byte("goodbye") - var initChainer sdk.InitChainer = func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + var initChainer sdk.InitChainer = func(ctx sdk.Context, req abci.RequestInitChain) (abci.ResponseInitChain, error) { store := ctx.KVStore(capKey) store.Set(key, value) - return abci.ResponseInitChain{} + return abci.ResponseInitChain{}, nil } query := abci.RequestQuery{ @@ -579,12 +579,12 @@ func TestABCI_EndBlock(t *testing.T) { ConsensusParams: cp, }) - app.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + app.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) (abci.ResponseEndBlock, error) { return abci.ResponseEndBlock{ ValidatorUpdates: []abci.ValidatorUpdate{ {Power: 100}, }, - } + }, nil }) app.Seal() @@ -1384,9 +1384,9 @@ func TestABCI_Proposal_Read_State_PrepareProposal(t *testing.T) { someKey := []byte("some-key") setInitChainerOpt := func(bapp *baseapp.BaseApp) { - bapp.SetInitChainer(func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + bapp.SetInitChainer(func(ctx sdk.Context, req abci.RequestInitChain) (abci.ResponseInitChain, error) { ctx.KVStore(capKey1).Set(someKey, []byte("foo")) - return abci.ResponseInitChain{} + return abci.ResponseInitChain{}, nil }) } diff --git a/runtime/app.go b/runtime/app.go index 5bab0c47c817..42d2c2e63af6 100644 --- a/runtime/app.go +++ b/runtime/app.go @@ -116,17 +116,17 @@ func (a *App) Load(loadLatest bool) error { } // BeginBlocker application updates every begin block -func (a *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { +func (a *App) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) (abci.ResponseBeginBlock, error) { return a.ModuleManager.BeginBlock(ctx, req) } // EndBlocker application updates every end block -func (a *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { +func (a *App) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) (abci.ResponseEndBlock, error) { return a.ModuleManager.EndBlock(ctx, req) } // InitChainer initializes the chain. -func (a *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { +func (a *App) InitChainer(ctx sdk.Context, req abci.RequestInitChain) (abci.ResponseInitChain, error) { var genesisState map[string]json.RawMessage if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { panic(err) diff --git a/runtime/types.go b/runtime/types.go index e17872623229..0997a4703166 100644 --- a/runtime/types.go +++ b/runtime/types.go @@ -22,13 +22,13 @@ type AppI interface { LegacyAmino() *codec.LegacyAmino // Application updates every begin block. - BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock + BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) (abci.ResponseBeginBlock, error) // Application updates every end block. - EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock + EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) (abci.ResponseEndBlock, error) // Application update at chain (i.e app) initialization. - InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain + InitChainer(ctx sdk.Context, req abci.RequestInitChain) (abci.ResponseInitChain, error) // Loads the app at a given height. LoadHeight(height int64) error diff --git a/server/mock/app.go b/server/mock/app.go index c4802ed69e47..340cd442bed5 100644 --- a/server/mock/app.go +++ b/server/mock/app.go @@ -96,22 +96,21 @@ type GenesisJSON struct { // InitChainer returns a function that can initialize the chain // with key/value pairs -func InitChainer(key storetypes.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain { - return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { +func InitChainer(key storetypes.StoreKey) func(sdk.Context, abci.RequestInitChain) (abci.ResponseInitChain, error) { + return func(ctx sdk.Context, req abci.RequestInitChain) (abci.ResponseInitChain, error) { stateJSON := req.AppStateBytes genesisState := new(GenesisJSON) err := json.Unmarshal(stateJSON, genesisState) if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") + return abci.ResponseInitChain{}, err } for _, val := range genesisState.Values { store := ctx.KVStore(key) store.Set([]byte(val.Key), []byte(val.Value)) } - return abci.ResponseInitChain{} + return abci.ResponseInitChain{}, nil } } diff --git a/simapp/app.go b/simapp/app.go index c1a257f88490..754a96993172 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -29,11 +29,12 @@ import ( upgradeclient "cosmossdk.io/x/upgrade/client" upgradekeeper "cosmossdk.io/x/upgrade/keeper" upgradetypes "cosmossdk.io/x/upgrade/types" - + storetypes "cosmossdk.io/store/types" "cosmossdk.io/x/feegrant" feegrantkeeper "cosmossdk.io/x/feegrant/keeper" feegrantmodule "cosmossdk.io/x/feegrant/module" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" @@ -559,12 +560,12 @@ func (app *SimApp) setPostHandler() { func (app *SimApp) Name() string { return app.BaseApp.Name() } // BeginBlocker application updates every begin block -func (app *SimApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { +func (app *SimApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) (abci.ResponseBeginBlock, error) { return app.ModuleManager.BeginBlock(ctx, req) } // EndBlocker application updates every end block -func (app *SimApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { +func (app *SimApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) (abci.ResponseEndBlock, error) { return app.ModuleManager.EndBlock(ctx, req) } @@ -573,7 +574,7 @@ func (a *SimApp) Configurator() module.Configurator { } // InitChainer application update at chain initialization -func (app *SimApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { +func (app *SimApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) (abci.ResponseInitChain, error) { var genesisState GenesisState if err := json.Unmarshal(req.AppStateBytes, &genesisState); err != nil { panic(err) diff --git a/simapp/export.go b/simapp/export.go index fc4678a54476..246acbc6b3f1 100644 --- a/simapp/export.go +++ b/simapp/export.go @@ -30,7 +30,11 @@ func (app *SimApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAd app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs) } - genState := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) + genState, err := app.ModuleManager.ExportGenesisForModules(ctx, app.appCodec, modulesToExport) + if err != nil { + return servertypes.ExportedApp{}, err + } + appState, err := json.MarshalIndent(genState, "", " ") if err != nil { return servertypes.ExportedApp{}, err diff --git a/tests/e2e/params/suite.go b/tests/e2e/params/suite.go index 6fc3da9db007..0d4585321da7 100644 --- a/tests/e2e/params/suite.go +++ b/tests/e2e/params/suite.go @@ -78,7 +78,7 @@ func (s *E2ETestSuite) SetupSuite() { // Make sure not to forget to persist `myParams` into the actual store, // this is done in InitChain. - app.SetInitChainer(func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { + app.SetInitChainer(func(ctx sdk.Context, req abci.RequestInitChain) (abci.ResponseInitChain, error) { subspace.SetParamSet(ctx, ¶mSet) return app.InitChainer(ctx, req) diff --git a/tests/e2e/staking/suite.go b/tests/e2e/staking/suite.go index 6bcc5c6a7c6a..91b29041fdd5 100644 --- a/tests/e2e/staking/suite.go +++ b/tests/e2e/staking/suite.go @@ -1497,7 +1497,6 @@ func (s *E2ETestSuite) TestBlockResults() { ) return nil - }, 10) } diff --git a/types/abci.go b/types/abci.go index 2679a5afc0bb..2ea35e62c298 100644 --- a/types/abci.go +++ b/types/abci.go @@ -5,19 +5,19 @@ import ( ) // InitChainer initializes application state at genesis -type InitChainer func(ctx Context, req abci.RequestInitChain) abci.ResponseInitChain +type InitChainer func(ctx Context, req abci.RequestInitChain) (abci.ResponseInitChain, error) // BeginBlocker runs code before the transactions in a block // // Note: applications which set create_empty_blocks=false will not have regular block timing and should use // e.g. BFT timestamps rather than block height for any periodic BeginBlock logic -type BeginBlocker func(ctx Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock +type BeginBlocker func(ctx Context, req abci.RequestBeginBlock) (abci.ResponseBeginBlock, error) // EndBlocker runs code after the transactions in a block and return updates to the validator set // // Note: applications which set create_empty_blocks=false will not have regular block timing and should use // e.g. BFT timestamps rather than block height for any periodic EndBlock logic -type EndBlocker func(ctx Context, req abci.RequestEndBlock) abci.ResponseEndBlock +type EndBlocker func(ctx Context, req abci.RequestEndBlock) (abci.ResponseEndBlock, error) // PeerFilter responds to p2p filtering queries from Tendermint type PeerFilter func(info string) abci.ResponseQuery diff --git a/types/module/module.go b/types/module/module.go index 72b24881c941..2b9ab3eb8424 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -30,6 +30,7 @@ package module import ( "encoding/json" + "errors" "fmt" "sort" @@ -376,7 +377,7 @@ func (m *Manager) RegisterServices(cfg Configurator) { // InitGenesis performs init genesis functionality for modules. Exactly one // module must return a non-empty validator set update to correctly initialize // the chain. -func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage) abci.ResponseInitChain { +func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage) (abci.ResponseInitChain, error) { var validatorUpdates []abci.ValidatorUpdate ctx.Logger().Info("initializing blockchain state from genesis.json") for _, moduleName := range m.OrderInitGenesis { @@ -391,12 +392,12 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData // core API genesis source, err := genesis.SourceFromRawJSON(genesisData[moduleName]) if err != nil { - panic(err) + return abci.ResponseInitChain{}, err } err = module.InitGenesis(ctx, source) if err != nil { - panic(err) + return abci.ResponseInitChain{}, err } } else if module, ok := mod.(HasGenesis); ok { ctx.Logger().Debug("running initialization for module", "module", moduleName) @@ -406,7 +407,7 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData // only one module will update the validator set if len(moduleValUpdates) > 0 { if len(validatorUpdates) > 0 { - panic("validator InitGenesis updates already set by a previous module") + return abci.ResponseInitChain{}, errors.New("validator InitGenesis updates already set by a previous module") } validatorUpdates = moduleValUpdates } @@ -420,60 +421,72 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData return abci.ResponseInitChain{ Validators: validatorUpdates, - } + }, nil } // ExportGenesis performs export genesis functionality for modules -func (m *Manager) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) map[string]json.RawMessage { +func (m *Manager) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) (map[string]json.RawMessage, error) { return m.ExportGenesisForModules(ctx, cdc, []string{}) } // ExportGenesisForModules performs export genesis functionality for modules -func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, modulesToExport []string) map[string]json.RawMessage { +func (m *Manager) ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, modulesToExport []string) (map[string]json.RawMessage, error) { if len(modulesToExport) == 0 { modulesToExport = m.OrderExportGenesis } // verify modules exists in app, so that we don't panic in the middle of an export if err := m.checkModulesExists(modulesToExport); err != nil { - panic(err) + return nil, err + } + + type genesisResult struct { + bz json.RawMessage + err error } - channels := make(map[string]chan json.RawMessage) + channels := make(map[string]chan genesisResult) for _, moduleName := range modulesToExport { mod := m.Modules[moduleName] if module, ok := mod.(appmodule.HasGenesis); ok { // core API genesis - channels[moduleName] = make(chan json.RawMessage) - go func(module appmodule.HasGenesis, ch chan json.RawMessage) { + channels[moduleName] = make(chan genesisResult) + go func(module appmodule.HasGenesis, ch chan genesisResult) { ctx := ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions target := genesis.RawJSONTarget{} err := module.ExportGenesis(ctx, target.Target()) if err != nil { - panic(err) + ch <- genesisResult{nil, err} + return } rawJSON, err := target.JSON() if err != nil { - panic(err) + ch <- genesisResult{nil, err} + return } - ch <- rawJSON + ch <- genesisResult{rawJSON, nil} }(module, channels[moduleName]) } else if module, ok := mod.(HasGenesis); ok { - channels[moduleName] = make(chan json.RawMessage) - go func(module HasGenesis, ch chan json.RawMessage) { + channels[moduleName] = make(chan genesisResult) + go func(module HasGenesis, ch chan genesisResult) { ctx := ctx.WithGasMeter(storetypes.NewInfiniteGasMeter()) // avoid race conditions - ch <- module.ExportGenesis(ctx, cdc) + ch <- genesisResult{module.ExportGenesis(ctx, cdc), nil} }(module, channels[moduleName]) } } genesisData := make(map[string]json.RawMessage) for moduleName := range channels { - genesisData[moduleName] = <-channels[moduleName] + res := <-channels[moduleName] + if res.err != nil { + return nil, res.err + } + + genesisData[moduleName] = res.bz } - return genesisData + return genesisData, nil } // checkModulesExists verifies that all modules in the list exist in the app @@ -623,7 +636,7 @@ func (m Manager) RunMigrations(ctx sdk.Context, cfg Configurator, fromVM Version // BeginBlock performs begin block functionality for all modules. It creates a // child context with an event manager to aggregate events emitted from all // modules. -func (m *Manager) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { +func (m *Manager) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) (abci.ResponseBeginBlock, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) for _, moduleName := range m.OrderBeginBlockers { @@ -635,13 +648,13 @@ func (m *Manager) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) abci.R return abci.ResponseBeginBlock{ Events: ctx.EventManager().ABCIEvents(), - } + }, nil } // EndBlock performs end block functionality for all modules. It creates a // child context with an event manager to aggregate events emitted from all // modules. -func (m *Manager) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { +func (m *Manager) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) (abci.ResponseEndBlock, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) validatorUpdates := []abci.ValidatorUpdate{} @@ -656,7 +669,7 @@ func (m *Manager) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) abci.Respo // only one module will update the validator set if len(moduleValUpdates) > 0 { if len(validatorUpdates) > 0 { - panic("validator EndBlock updates already set by a previous module") + return abci.ResponseEndBlock{}, errors.New("validator EndBlock updates already set by a previous module") } validatorUpdates = moduleValUpdates @@ -666,7 +679,7 @@ func (m *Manager) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) abci.Respo return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, Events: ctx.EventManager().ABCIEvents(), - } + }, nil } // GetVersionMap gets consensus version from all modules diff --git a/types/module/module_test.go b/types/module/module_test.go index 59787b578fc9..7fa51989cc4a 100644 --- a/types/module/module_test.go +++ b/types/module/module_test.go @@ -206,7 +206,8 @@ func TestManager_InitGenesis(t *testing.T) { // panic because more than one module returns validator set updates mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(genesisData["module1"])).Times(1).Return([]abci.ValidatorUpdate{{}}) mockAppModule2.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(genesisData["module2"])).Times(1).Return([]abci.ValidatorUpdate{{}}) - require.Panics(t, func() { mm.InitGenesis(ctx, cdc, genesisData) }) + _, err := mm.InitGenesis(ctx, cdc, genesisData) + require.Error(t, err) // happy path mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(genesisData["module1"])).Times(1).Return([]abci.ValidatorUpdate{{}}) @@ -242,14 +243,24 @@ func TestManager_ExportGenesis(t *testing.T) { }`), } - require.Equal(t, want, mm.ExportGenesis(ctx, cdc)) - require.Equal(t, want, mm.ExportGenesisForModules(ctx, cdc, []string{})) - require.Equal(t, map[string]json.RawMessage{"module1": json.RawMessage(`{"key1": "value1"}`)}, mm.ExportGenesisForModules(ctx, cdc, []string{"module1"})) - require.NotEqual(t, map[string]json.RawMessage{"module1": json.RawMessage(`{"key1": "value1"}`)}, mm.ExportGenesisForModules(ctx, cdc, []string{"module2"})) + res, err := mm.ExportGenesis(ctx, cdc) + require.NoError(t, err) + require.Equal(t, want, res) - require.Panics(t, func() { - mm.ExportGenesisForModules(ctx, cdc, []string{"module1", "modulefoo"}) - }) + res, err = mm.ExportGenesisForModules(ctx, cdc, []string{}) + require.NoError(t, err) + require.Equal(t, want, res) + + res, err = mm.ExportGenesisForModules(ctx, cdc, []string{"module1"}) + require.NoError(t, err) + require.Equal(t, map[string]json.RawMessage{"module1": json.RawMessage(`{"key1": "value1"}`)}, res) + + res, err = mm.ExportGenesisForModules(ctx, cdc, []string{"module2"}) + require.NoError(t, err) + require.NotEqual(t, map[string]json.RawMessage{"module1": json.RawMessage(`{"key1": "value1"}`)}, res) + + _, err = mm.ExportGenesisForModules(ctx, cdc, []string{"module1", "modulefoo"}) + require.Error(t, err) } func TestManager_BeginBlock(t *testing.T) { @@ -287,13 +298,15 @@ func TestManager_EndBlock(t *testing.T) { mockAppModule1.EXPECT().EndBlock(gomock.Any(), gomock.Eq(req)).Times(1).Return([]abci.ValidatorUpdate{{}}) mockAppModule2.EXPECT().EndBlock(gomock.Any(), gomock.Eq(req)).Times(1) - ret := mm.EndBlock(sdk.Context{}, req) + ret, err := mm.EndBlock(sdk.Context{}, req) + require.NoError(t, err) require.Equal(t, []abci.ValidatorUpdate{{}}, ret.ValidatorUpdates) // test panic mockAppModule1.EXPECT().EndBlock(gomock.Any(), gomock.Eq(req)).Times(1).Return([]abci.ValidatorUpdate{{}}) mockAppModule2.EXPECT().EndBlock(gomock.Any(), gomock.Eq(req)).Times(1).Return([]abci.ValidatorUpdate{{}}) - require.Panics(t, func() { mm.EndBlock(sdk.Context{}, req) }) + _, err = mm.EndBlock(sdk.Context{}, req) + require.Error(t, err) } // Core API exclusive tests @@ -355,14 +368,24 @@ func TestCoreAPIManager_ExportGenesis(t *testing.T) { }`), } - require.Equal(t, want, mm.ExportGenesis(ctx, cdc)) - require.Equal(t, want, mm.ExportGenesisForModules(ctx, cdc, []string{})) - require.Equal(t, map[string]json.RawMessage{"module1": want["module1"]}, mm.ExportGenesisForModules(ctx, cdc, []string{"module1"})) - require.NotEqual(t, map[string]json.RawMessage{"module1": want["module1"]}, mm.ExportGenesisForModules(ctx, cdc, []string{"module2"})) + res, err := mm.ExportGenesis(ctx, cdc) + require.NoError(t, err) + require.Equal(t, want, res) - require.Panics(t, func() { - mm.ExportGenesisForModules(ctx, cdc, []string{"module1", "modulefoo"}) - }) + res, err = mm.ExportGenesisForModules(ctx, cdc, []string{}) + require.NoError(t, err) + require.Equal(t, want, res) + + res, err = mm.ExportGenesisForModules(ctx, cdc, []string{"module1"}) + require.NoError(t, err) + require.Equal(t, map[string]json.RawMessage{"module1": want["module1"]}, res) + + res, err = mm.ExportGenesisForModules(ctx, cdc, []string{"module2"}) + require.NoError(t, err) + require.NotEqual(t, map[string]json.RawMessage{"module1": want["module1"]}, res) + + _, err = mm.ExportGenesisForModules(ctx, cdc, []string{"module1", "modulefoo"}) + require.Error(t, err) } func TestCoreAPIManagerOrderSetters(t *testing.T) { diff --git a/x/upgrade/doc.go b/x/upgrade/doc.go index 5589ac6eda92..8b101c39a6b9 100644 --- a/x/upgrade/doc.go +++ b/x/upgrade/doc.go @@ -45,9 +45,9 @@ be a matter of minutes and not even require them to be awake at that time. Setup an upgrade Keeper for the app and then define a BeginBlocker that calls the upgrade keeper's BeginBlocker method: - func (app *myApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { + func (app *myApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) (abci.ResponseBeginBlock, error) { app.upgradeKeeper.BeginBlocker(ctx, req) - return abci.ResponseBeginBlock{} + return abci.ResponseBeginBlock{}, nil } The app must then integrate the upgrade keeper with its governance module as appropriate. The governance module