Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: return errors in module manager ABCI methods #14847

Merged
merged 11 commits into from
Jan 31, 2023
18 changes: 15 additions & 3 deletions baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ 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 {
Expand Down Expand Up @@ -195,7 +199,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)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
}
res.Events = sdk.MarkEventsToIndex(res.Events, app.indexEvents)
}
// set the signed validators for addition to context in deliverTx
Expand All @@ -218,7 +226,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)

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
}
res.Events = sdk.MarkEventsToIndex(res.Events, app.indexEvents)
}

Expand Down
12 changes: 6 additions & 6 deletions baseapp/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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
})
}

Expand Down
6 changes: 3 additions & 3 deletions runtime/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions runtime/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 5 additions & 4 deletions server/mock/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,22 +96,23 @@ 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 abci.ResponseInitChain{}, err
// TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(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
}
}

Expand Down
6 changes: 5 additions & 1 deletion simapp/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions types/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
61 changes: 37 additions & 24 deletions types/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ package module

import (
"encoding/json"
"errors"
"fmt"
"sort"

Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand All @@ -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
}
Expand All @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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{}

Expand All @@ -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")

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods

Possible panics in BeginBock- or EndBlock-related consensus methods could cause a chain halt
}

validatorUpdates = moduleValUpdates
Expand All @@ -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
Expand Down
Loading