From 8520877281aab3bee542abe60580d2e36752790c Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Wed, 21 Dec 2022 08:50:04 -0800 Subject: [PATCH] Add midblock to sei-cosmos (#118) ## Describe your changes and provide context This adds the midblock interfaces and module manager functionality to sei-cosmos ## Testing performed to validate your change Added unit test for module manager behavior and also tested with local sei with sei-chain changes --- baseapp/abci.go | 18 ++++++++++++++++++ baseapp/baseapp.go | 1 + baseapp/options.go | 8 ++++++++ simapp/app.go | 5 +++++ telemetry/wrapper.go | 1 + tests/mocks/types_module_module.go | 13 +++++++++++++ types/abci.go | 3 +++ types/module/module.go | 30 ++++++++++++++++++++++++++++++ types/module/module_test.go | 25 +++++++++++++++++++++++++ 9 files changed, 104 insertions(+) diff --git a/baseapp/abci.go b/baseapp/abci.go index 3baf806db..a0b467c81 100644 --- a/baseapp/abci.go +++ b/baseapp/abci.go @@ -160,6 +160,24 @@ func (app *BaseApp) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) (res return res } +func (app *BaseApp) MidBlock(ctx sdk.Context, height int64) (events []abci.Event) { + defer telemetry.MeasureSince(time.Now(), "abci", "mid_block") + + if app.midBlocker != nil { + midBlockEvents := app.midBlocker(ctx, height) + events = sdk.MarkEventsToIndex(midBlockEvents, app.indexEvents) + } + // TODO: add listener handling + // // call the streaming service hooks with the EndBlock messages + // for _, streamingListener := range app.abciListeners { + // if err := streamingListener.ListenMidBlock(app.deliverState.ctx, req, res); err != nil { + // app.logger.Error("MidBlock listening hook failed", "height", req.Height, "err", err) + // } + // } + + return events +} + // EndBlock implements the ABCI interface. func (app *BaseApp) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) (res abci.ResponseEndBlock) { defer telemetry.MeasureSince(time.Now(), "abci", "end_block") diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index eafe185c5..f2f59b014 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -165,6 +165,7 @@ type moduleRouter struct { type abciData struct { initChainer sdk.InitChainer // initialize state with validators and state blob beginBlocker sdk.BeginBlocker // logic to run before any txs + midBlocker sdk.MidBlocker // logic to run after all txs, and to determine valset changes endBlocker sdk.EndBlocker // logic to run after all txs, and to determine valset changes // absent validators from begin block diff --git a/baseapp/options.go b/baseapp/options.go index 890606ad6..2cec751e8 100644 --- a/baseapp/options.go +++ b/baseapp/options.go @@ -150,6 +150,14 @@ func (app *BaseApp) SetBeginBlocker(beginBlocker sdk.BeginBlocker) { app.beginBlocker = beginBlocker } +func (app *BaseApp) SetMidBlocker(midBlocker sdk.MidBlocker) { + if app.sealed { + panic("SetMidBlocker() on sealed BaseApp") + } + + app.midBlocker = midBlocker +} + func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) { if app.sealed { panic("SetEndBlocker() on sealed BaseApp") diff --git a/simapp/app.go b/simapp/app.go index 1afa0ca2e..27c0d8a9c 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -568,6 +568,11 @@ func (app *SimApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abc return app.mm.BeginBlock(ctx, req) } +// EndBlocker application updates every end block +func (app *SimApp) MidBlocker(ctx sdk.Context, height int64) []abci.Event { + return app.mm.MidBlock(ctx, height) +} + // EndBlocker application updates every end block func (app *SimApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { return app.mm.EndBlock(ctx, req) diff --git a/telemetry/wrapper.go b/telemetry/wrapper.go index 24722a7d6..0cd7c0bc7 100644 --- a/telemetry/wrapper.go +++ b/telemetry/wrapper.go @@ -9,6 +9,7 @@ import ( // Common metric key constants const ( MetricKeyBeginBlocker = "begin_blocker" + MetricKeyMidBlocker = "mid_blocker" MetricKeyEndBlocker = "end_blocker" MetricLabelNameModule = "module" ) diff --git a/tests/mocks/types_module_module.go b/tests/mocks/types_module_module.go index e2592a0e4..9a7dd5572 100644 --- a/tests/mocks/types_module_module.go +++ b/tests/mocks/types_module_module.go @@ -394,6 +394,19 @@ func (mr *MockAppModuleMockRecorder) DefaultGenesis(arg0 interface{}) *gomock.Ca return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultGenesis", reflect.TypeOf((*MockAppModule)(nil).DefaultGenesis), arg0) } +// EndBlock mocks base method. +func (m *MockAppModule) MidBlock(arg0 types0.Context, arg1 int64) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "MidBlock", arg0, arg1) +} + +// EndBlock indicates an expected call of EndBlock. +func (mr *MockAppModuleMockRecorder) MidBlock(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MidBlock", reflect.TypeOf((*MockAppModule)(nil).MidBlock), arg0, arg1) +} + + // EndBlock mocks base method. func (m *MockAppModule) EndBlock(arg0 types0.Context, arg1 abci.RequestEndBlock) []abci.ValidatorUpdate { m.ctrl.T.Helper() diff --git a/types/abci.go b/types/abci.go index d5f1fe76f..b85ee53f8 100644 --- a/types/abci.go +++ b/types/abci.go @@ -13,6 +13,9 @@ type InitChainer func(ctx Context, req abci.RequestInitChain) abci.ResponseInitC // e.g. BFT timestamps rather than block height for any periodic BeginBlock logic type BeginBlocker func(ctx Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock +// MidBlocker runs code after the early transactions in a block and return any relevant events +type MidBlocker func(ctx Context, height int64) []abci.Event + // 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 diff --git a/types/module/module.go b/types/module/module.go index 2119cd13c..5f5c26f60 100644 --- a/types/module/module.go +++ b/types/module/module.go @@ -186,6 +186,11 @@ type BeginBlockAppModule interface { BeginBlock(sdk.Context, abci.RequestBeginBlock) } +type MidBlockAppModule interface { + AppModule + MidBlock(sdk.Context, int64) +} + // EndBlockAppModule is an extension interface that contains information about the AppModule and EndBlock. type EndBlockAppModule interface { AppModule @@ -237,6 +242,7 @@ type Manager struct { OrderInitGenesis []string OrderExportGenesis []string OrderBeginBlockers []string + OrderMidBlockers []string OrderEndBlockers []string OrderMigrations []string } @@ -278,6 +284,13 @@ func (m *Manager) SetOrderBeginBlockers(moduleNames ...string) { m.OrderBeginBlockers = moduleNames } +// SetOrderMidBlockers sets the order of set mid-blocker calls +func (m *Manager) SetOrderMidBlockers(moduleNames ...string) { + // TODO: do we need this assertion? maybe its ok to not have it to only have some modules have MidBlock implementations + // m.assertNoForgottenModules("SetOrderMidBlockers", moduleNames) + m.OrderMidBlockers = moduleNames +} + // SetOrderEndBlockers sets the order of set end-blocker calls func (m *Manager) SetOrderEndBlockers(moduleNames ...string) { m.assertNoForgottenModules("SetOrderEndBlockers", moduleNames) @@ -498,6 +511,23 @@ func (m *Manager) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) abci.R } } +// 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) MidBlock(ctx sdk.Context, height int64) []abci.Event { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + for _, moduleName := range m.OrderMidBlockers { + module, ok := m.Modules[moduleName].(MidBlockAppModule) + if !ok { + continue + } + module.MidBlock(ctx, height) + } + + return ctx.EventManager().ABCIEvents() +} + // EndBlock performs end block functionality for all modules. It creates a // child context with an event manager to aggregate events emitted from all // modules. diff --git a/types/module/module_test.go b/types/module/module_test.go index ed62d0167..eac0402e2 100644 --- a/types/module/module_test.go +++ b/types/module/module_test.go @@ -108,6 +108,11 @@ func TestManagerOrderSetters(t *testing.T) { mm.SetOrderBeginBlockers("module2", "module1") require.Equal(t, []string{"module2", "module1"}, mm.OrderBeginBlockers) + // we expect none of the modules to be included by default + require.Empty(t, mm.OrderMidBlockers) + mm.SetOrderMidBlockers("module2", "module1") + require.Equal(t, []string{"module2", "module1"}, mm.OrderMidBlockers) + require.Equal(t, []string{"module1", "module2"}, mm.OrderEndBlockers) mm.SetOrderEndBlockers("module2", "module1") require.Equal(t, []string{"module2", "module1"}, mm.OrderEndBlockers) @@ -258,6 +263,26 @@ func TestManager_BeginBlock(t *testing.T) { mm.BeginBlock(sdk.Context{}, req) } +func TestManager_MidBlock(t *testing.T) { + mockCtrl := gomock.NewController(t) + t.Cleanup(mockCtrl.Finish) + + mockAppModule1 := mocks.NewMockAppModule(mockCtrl) + mockAppModule2 := mocks.NewMockAppModule(mockCtrl) + mockAppModule1.EXPECT().Name().Times(2).Return("module1") + mockAppModule2.EXPECT().Name().Times(2).Return("module2") + mm := module.NewManager(mockAppModule1, mockAppModule2) + require.NotNil(t, mm) + require.Equal(t, 2, len(mm.Modules)) + mm.SetOrderMidBlockers("module2", "module1") + + height := int64(10) + + mockAppModule1.EXPECT().MidBlock(gomock.Any(), gomock.Eq(height)).Times(1) + mockAppModule2.EXPECT().MidBlock(gomock.Any(), gomock.Eq(height)).Times(1) + mm.MidBlock(sdk.Context{}, height) +} + func TestManager_EndBlock(t *testing.T) { mockCtrl := gomock.NewController(t) t.Cleanup(mockCtrl.Finish)