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: Implement BeginBlock EndBlock for Core API modules #14819

Merged
merged 12 commits into from
Feb 1, 2023
28 changes: 28 additions & 0 deletions testutil/mock/types_mock_appmodule.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions types/module/mock_appmodule_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ type AppModuleWithAllExtensions interface {
type CoreAppModule interface {
appmodule.AppModule
appmodule.HasGenesis
appmodule.HasBeginBlocker
appmodule.HasEndBlocker
}
39 changes: 24 additions & 15 deletions types/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,7 @@ func (m *Manager) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData

// a chain must initialize with a non-empty validator set
if len(validatorUpdates) == 0 {
panic(fmt.Sprintf("validator set is empty after InitGenesis, please ensure at least one validator is initialized with a delegation greater than or equal to the DefaultPowerReduction (%d)", sdk.DefaultPowerReduction))
return abci.ResponseInitChain{}, fmt.Errorf("validator set is empty after InitGenesis, please ensure at least one validator is initialized with a delegation greater than or equal to the DefaultPowerReduction (%d)", sdk.DefaultPowerReduction)
}

return abci.ResponseInitChain{
Expand Down Expand Up @@ -640,9 +640,13 @@ func (m *Manager) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) (abci.
ctx = ctx.WithEventManager(sdk.NewEventManager())

for _, moduleName := range m.OrderBeginBlockers {
module, ok := m.Modules[moduleName].(BeginBlockAppModule)
if ok {
if module, ok := m.Modules[moduleName].(BeginBlockAppModule); ok {
module.BeginBlock(ctx, req)
} else if module, ok := m.Modules[moduleName].(appmodule.HasBeginBlocker); ok {
err := module.BeginBlock(ctx)
if err != nil {
return abci.ResponseBeginBlock{}, err
}
}
}

Expand All @@ -659,20 +663,25 @@ func (m *Manager) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) (abci.Resp
validatorUpdates := []abci.ValidatorUpdate{}

for _, moduleName := range m.OrderEndBlockers {
module, ok := m.Modules[moduleName].(EndBlockAppModule)
if !ok {
continue
}
moduleValUpdates := module.EndBlock(ctx, req)
if module, ok := m.Modules[moduleName].(EndBlockAppModule); ok {
moduleValUpdates := module.EndBlock(ctx, req)

// use these validator updates if provided, the module manager assumes
// only one module will update the validator set
if len(moduleValUpdates) > 0 {
if len(validatorUpdates) > 0 {
return abci.ResponseEndBlock{}, errors.New("validator EndBlock updates already set by a previous module")
}
// use these validator updates if provided, the module manager assumes
// only one module will update the validator set
if len(moduleValUpdates) > 0 {
if len(validatorUpdates) > 0 {
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
validatorUpdates = moduleValUpdates
}
} else if module, ok := m.Modules[moduleName].(appmodule.HasEndBlocker); ok {
err := module.EndBlock(ctx)
if err != nil {
return abci.ResponseEndBlock{}, err
}
} else {
continue
}
}

Expand Down
79 changes: 70 additions & 9 deletions types/module/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ func TestManager_InitGenesis(t *testing.T) {

// this should panic since the validator set is empty even after init genesis
mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Eq(cdc), gomock.Eq(genesisData["module1"])).Times(1).Return(nil)
require.Panics(t, func() { mm.InitGenesis(ctx, cdc, genesisData) })
_, err := mm.InitGenesis(ctx, cdc, genesisData)
require.ErrorContains(t, err, "validator set is empty after InitGenesis")

// test panic
genesisData = map[string]json.RawMessage{
Expand All @@ -206,14 +207,15 @@ 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{{}})
_, err := mm.InitGenesis(ctx, cdc, genesisData)
require.Error(t, err)
_, err = mm.InitGenesis(ctx, cdc, genesisData)
require.ErrorContains(t, err, "validator InitGenesis updates already set by a previous module")

// happy path
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{})
mockAppModule3.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Any()).Times(1).Return(nil)
mm.InitGenesis(ctx, cdc, genesisData)
_, err = mm.InitGenesis(ctx, cdc, genesisData)
require.NoError(t, err)
}

func TestManager_ExportGenesis(t *testing.T) {
Expand Down Expand Up @@ -279,7 +281,8 @@ func TestManager_BeginBlock(t *testing.T) {

mockAppModule1.EXPECT().BeginBlock(gomock.Any(), gomock.Eq(req)).Times(1)
mockAppModule2.EXPECT().BeginBlock(gomock.Any(), gomock.Eq(req)).Times(1)
mm.BeginBlock(sdk.Context{}, req)
_, err := mm.BeginBlock(sdk.Context{}, req)
require.NoError(t, err)
}

func TestManager_EndBlock(t *testing.T) {
Expand All @@ -288,11 +291,13 @@ func TestManager_EndBlock(t *testing.T) {

mockAppModule1 := mock.NewMockEndBlockAppModule(mockCtrl)
mockAppModule2 := mock.NewMockEndBlockAppModule(mockCtrl)
mockAppModule3 := mock.NewMockAppModule(mockCtrl)
mockAppModule1.EXPECT().Name().Times(2).Return("module1")
mockAppModule2.EXPECT().Name().Times(2).Return("module2")
mm := module.NewManager(mockAppModule1, mockAppModule2)
mockAppModule3.EXPECT().Name().Times(2).Return("module3")
mm := module.NewManager(mockAppModule1, mockAppModule2, mockAppModule3)
require.NotNil(t, mm)
require.Equal(t, 2, len(mm.Modules))
require.Equal(t, 3, len(mm.Modules))

req := abci.RequestEndBlock{Height: 10}

Expand Down Expand Up @@ -341,9 +346,11 @@ func TestCoreAPIManager_InitGenesis(t *testing.T) {

// this should panic since the validator set is empty even after init genesis
mockAppModule1.EXPECT().InitGenesis(gomock.Eq(ctx), gomock.Any()).Times(1).Return(nil)
require.Panics(t, func() { mm.InitGenesis(ctx, cdc, genesisData) })
_, err := mm.InitGenesis(ctx, cdc, genesisData)
require.ErrorContains(t, err, "validator set is empty after InitGenesis, please ensure at least one validator is initialized with a delegation greater than or equal to the DefaultPowerReduction")

// TODO: add happy path test. We are not returning any validator updates
// TODO: add happy path test. We are not returning any validator updates, this will come with the services.
// REF: https://github.com/cosmos/cosmos-sdk/issues/14688
}

func TestCoreAPIManager_ExportGenesis(t *testing.T) {
Expand Down Expand Up @@ -421,6 +428,60 @@ func TestCoreAPIManagerOrderSetters(t *testing.T) {
require.Equal(t, []string{"module2", "module1", "module3"}, mm.OrderEndBlockers)
}

func TestCoreAPIManager_BeginBlock(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

mockAppModule1 := mock.NewMockCoreAppModule(mockCtrl)
mockAppModule2 := mock.NewMockCoreAppModule(mockCtrl)
mm := module.NewManagerFromMap(map[string]appmodule.AppModule{
"module1": mockAppModule1,
"module2": mockAppModule2,
})
require.NotNil(t, mm)
require.Equal(t, 2, len(mm.Modules))

req := abci.RequestBeginBlock{Hash: []byte("test")}

mockAppModule1.EXPECT().BeginBlock(gomock.Any()).Times(1).Return(nil)
mockAppModule2.EXPECT().BeginBlock(gomock.Any()).Times(1).Return(nil)
_, err := mm.BeginBlock(sdk.Context{}, req)
require.NoError(t, err)

// test panic
mockAppModule1.EXPECT().BeginBlock(gomock.Any()).Times(1).Return(errors.New("some error"))
_, err = mm.BeginBlock(sdk.Context{}, req)
require.EqualError(t, err, "some error")

}

func TestCoreAPIManager_EndBlock(t *testing.T) {
mockCtrl := gomock.NewController(t)
t.Cleanup(mockCtrl.Finish)

mockAppModule1 := mock.NewMockCoreAppModule(mockCtrl)
mockAppModule2 := mock.NewMockCoreAppModule(mockCtrl)
mm := module.NewManagerFromMap(map[string]appmodule.AppModule{
"module1": mockAppModule1,
"module2": mockAppModule2,
})
require.NotNil(t, mm)
require.Equal(t, 2, len(mm.Modules))

req := abci.RequestEndBlock{Height: 10}

mockAppModule1.EXPECT().EndBlock(gomock.Any()).Times(1).Return(nil)
mockAppModule2.EXPECT().EndBlock(gomock.Any()).Times(1).Return(nil)
res, err := mm.EndBlock(sdk.Context{}, req)
require.NoError(t, err)
require.Len(t, res.ValidatorUpdates, 0)

// test panic
mockAppModule1.EXPECT().EndBlock(gomock.Any()).Times(1).Return(errors.New("some error"))
_, err = mm.EndBlock(sdk.Context{}, req)
require.EqualError(t, err, "some error")
}

// MockCoreAppModule allows us to test functions like DefaultGenesis
type MockCoreAppModule struct{}

Expand Down