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

This commit is contained in:
Facundo Medica
2023-01-31 19:17:04 +00:00
committed by GitHub
parent 8eec98dcc5
commit deeb4bd362
14 changed files with 125 additions and 74 deletions
+3 -3
View File
@@ -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
+37 -24
View File
@@ -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
}
channels := make(map[string]chan json.RawMessage)
type genesisResult struct {
bz json.RawMessage
err error
}
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
+40 -17
View File
@@ -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) {