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: introduce PreBlock #186

Merged
merged 25 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/gov,x/group) [#17220](https://github.com/cosmos/cosmos-sdk/pull/17220) Do not try validate `msgURL` as web URL in `draft-proposal` command.
* (cli) [#17188](https://github.com/cosmos/cosmos-sdk/pull/17188) Fix `--output-document` flag in `tx multi-sign`.
* (x/auth) [#17209](https://github.com/cosmos/cosmos-sdk/pull/17209) Internal error on AccountInfo when account's public key is not set.
* (types) [#16583](https://github.com/cosmos/cosmos-sdk/pull/16583) Add `PreBlocker`, which runs before begin blocker other modules, and allows to modify consensus parameters, and the changes are visible to the following state machine logics.

## [v0.47.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.4) - 2023-07-17

Expand Down
17 changes: 17 additions & 0 deletions UPGRADING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,23 @@ This guide provides instructions for upgrading to specific versions of Cosmos SD

## [v0.47.x](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.0)

### BaseApp

#### Set PreBlocker

**Users using `depinject` / app v2 do not need any changes, this is abstracted for them.**

```diff
+ app.SetPreBlocker(app.PreBlocker)
```
```diff
+func (app *SimApp) PreBlocker(ctx sdk.Context, req abci.RequestBeginBlock) (sdk.ResponsePreBlock, error) {
+ return app.ModuleManager.PreBlock(ctx, req)
+}
```

BaseApp added `SetPreBlocker` for apps. This is essential for BaseApp to run `PreBlock` which runs before begin blocker other modules, and allows to modify consensus parameters, and the changes are visible to the following state machine logics.

### Migration to CometBFT (Part 1)

The Cosmos SDK has migrated to CometBFT, as its default consensus engine.
Expand Down
19 changes: 18 additions & 1 deletion baseapp/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,25 @@ func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeg
WithHeaderHash(req.Hash)
}

ctx := app.deliverState.ctx
if app.preBlocker != nil {
rsp, err := app.preBlocker(ctx, req)
if err != nil {
panic(fmt.Errorf("preBlock failed, height: %d, err: %w", req.Header.Height, err))

Check warning

Code scanning / CodeQL

Panic in BeginBock or EndBlock consensus methods

path flow from Begin/EndBlock to a panic call

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
}
// rsp.ConsensusParamsChanged is true from preBlocker means ConsensusParams in store get changed
// write the consensus parameters in store to context
if rsp.ConsensusParamsChanged {
ctx = ctx.WithConsensusParams(app.GetConsensusParams(ctx))
Fixed Show fixed Hide fixed
// GasMeter must be set after we get a context with updated consensus params.
gasMeter := app.getBlockGasMeter(ctx)
ctx = ctx.WithBlockGasMeter(gasMeter)
app.deliverState.ctx = ctx
}
}

if app.beginBlocker != nil {
res = app.beginBlocker(app.deliverState.ctx, req)
res = app.beginBlocker(ctx, req)
res.Events = sdk.MarkEventsToIndex(res.Events, app.indexEvents)
}
// set the signed validators for addition to context in deliverTx
Expand Down
1 change: 1 addition & 0 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type BaseApp struct { //nolint: maligned
anteHandler sdk.AnteHandler // ante handler for fee and auth
postHandler sdk.PostHandler // post handler, optional, e.g. for tips
initChainer sdk.InitChainer // initialize state with validators and state blob
preBlocker sdk.PreBlocker // logic to run before BeginBlocker
beginBlocker sdk.BeginBlocker // logic to run before any txs
processProposal sdk.ProcessProposalHandler // the handler which runs on ABCI ProcessProposal
prepareProposal sdk.PrepareProposalHandler // the handler which runs on ABCI PrepareProposal
Expand Down
3 changes: 3 additions & 0 deletions baseapp/baseapp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,9 @@ func TestBaseAppOptionSeal(t *testing.T) {
require.Panics(t, func() {
suite.baseApp.SetInitChainer(nil)
})
require.Panics(t, func() {
suite.baseApp.SetPreBlocker(nil)
})
require.Panics(t, func() {
suite.baseApp.SetBeginBlocker(nil)
})
Expand Down
8 changes: 8 additions & 0 deletions baseapp/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ func (app *BaseApp) SetInitChainer(initChainer sdk.InitChainer) {
app.initChainer = initChainer
}

func (app *BaseApp) SetPreBlocker(preBlocker sdk.PreBlocker) {
if app.sealed {
panic("SetPreBlocker() on sealed BaseApp")
}

app.preBlocker = preBlocker
}

func (app *BaseApp) SetBeginBlocker(beginBlocker sdk.BeginBlocker) {
if app.sealed {
panic("SetBeginBlocker() on sealed BaseApp")
Expand Down
18 changes: 18 additions & 0 deletions docs/docs/building-apps/03-app-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ This document describes how to upgrade your application. If you are looking spec
This section is currently incomplete. Track the progress of this document [here](https://github.com/cosmos/cosmos-sdk/issues/11504).
:::

## Set PreBlocker

:::tip
Users using `depinject` / app v2 do not need any changes, this is abstracted for them.
:::

Call `SetPreBlocker` to run `PreBlock`:

```go
app.SetPreBlocker(app.PreBlocker)
```

```go
func (app *SimApp) PreBlocker(ctx sdk.Context, req abci.RequestBeginBlock) (sdk.ResponsePreBlock, error) {
return app.ModuleManager.PreBlock(ctx, req)
}
```

## Pre-Upgrade Handling

Cosmovisor supports custom pre-upgrade handling. Use pre-upgrade handling when you need to implement application config changes that are required in the newer version before you perform the upgrade.
Expand Down
1 change: 1 addition & 0 deletions docs/docs/building-modules/01-module-manager.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ The module manager is used throughout the application whenever an action on a co
* `InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, genesisData map[string]json.RawMessage)`: Calls the [`InitGenesis`](./08-genesis.md#initgenesis) function of each module when the application is first started, in the order defined in `OrderInitGenesis`. Returns an `abci.ResponseInitChain` to the underlying consensus engine, which can contain validator updates.
* `ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec)`: Calls the [`ExportGenesis`](./08-genesis.md#exportgenesis) function of each module, in the order defined in `OrderExportGenesis`. The export constructs a genesis file from a previously existing state, and is mainly used when a hard-fork upgrade of the chain is required.
* `ExportGenesisForModules(ctx sdk.Context, cdc codec.JSONCodec, modulesToExport []string)`: Behaves the same as `ExportGenesis`, except takes a list of modules to export.
* `PreBlock(ctx sdk.Context, req abci.RequestBeginBlock)`: At the beginning of each block, this function is called from [`BaseApp`](../core/00-baseapp.md#beginblock) and, in turn, calls the `PreBlock` function of each modules implementing the `PreBlockAppModule` interface. The function returns a `sdk.ResponsePreBlock{ConsensusParamsChanged: bool}` which indicates whether the consensus parameters are changed or not, if `ConsensusParamsChanged` is true, the caller will refresh the context's consensus parameters, and make the changes visible to followed code. The main use case is for upgrade module to do the state migration before all other code logics.
* `BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock)`: At the beginning of each block, this function is called from [`BaseApp`](../core/00-baseapp.md#beginblock) and, in turn, calls the [`BeginBlock`](./05-beginblock-endblock.md) function of each modules implementing the `BeginBlockAppModule` interface, in the order defined in `OrderBeginBlockers`. It creates a child [context](../core/02-context.md) with an event manager to aggregate [events](../core/08-events.md) emitted from all modules. The function returns an `abci.ResponseBeginBlock` which contains the aforementioned events.
* `EndBlock(ctx sdk.Context, req abci.RequestEndBlock)`: At the end of each block, this function is called from [`BaseApp`](../core/00-baseapp.md#endblock) and, in turn, calls the [`EndBlock`](./05-beginblock-endblock.md) function of each modules implementing the `EndBlockAppModule` interface, in the order defined in `OrderEndBlockers`. It creates a child [context](../core/02-context.md) with an event manager to aggregate [events](../core/08-events.md) emitted from all modules. The function returns an `abci.ResponseEndBlock` which contains the aforementioned events, as well as validator set updates (if any).

Expand Down
1 change: 1 addition & 0 deletions runtime/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (a *AppBuilder) Build(
bApp.SetVersion(version.Version)
bApp.SetInterfaceRegistry(a.app.interfaceRegistry)
bApp.MountStores(a.app.storeKeys...)
bApp.SetPreBlocker(a.app.ModuleManager.PreBlock)

a.app.BaseApp = bApp
a.app.configurator = module.NewConfigurator(a.app.cdc, a.app.MsgServiceRouter(), a.app.GRPCQueryRouter())
Expand Down
10 changes: 10 additions & 0 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,10 @@ func NewSimApp(
nftmodule.NewAppModule(appCodec, app.NFTKeeper, app.AccountKeeper, app.BankKeeper, app.interfaceRegistry),
consensus.NewAppModule(appCodec, app.ConsensusParamsKeeper),
)
// NOTE: upgrade module is required to be prioritized
app.ModuleManager.SetOrderPreBlockers(
upgradetypes.ModuleName,
)

// During begin block slashing happens after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool, so as to keep the
Expand Down Expand Up @@ -496,6 +500,7 @@ func NewSimApp(

// initialize BaseApp
app.SetInitChainer(app.InitChainer)
app.SetPreBlocker(app.PreBlocker)
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)
app.setAnteHandler(encodingConfig.TxConfig)
Expand Down Expand Up @@ -561,6 +566,11 @@ func (app *SimApp) setPostHandler() {
// Name returns the name of the App
func (app *SimApp) Name() string { return app.BaseApp.Name() }

// PreBlocker application updates every begin block
func (app *SimApp) PreBlocker(ctx sdk.Context, req abci.RequestBeginBlock) (sdk.ResponsePreBlock, error) {
return app.ModuleManager.PreBlock(ctx, req)
}

// BeginBlocker application updates every begin block
func (app *SimApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
return app.ModuleManager.BeginBlock(ctx, req)
Expand Down
1 change: 1 addition & 0 deletions simapp/app_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ var (
Name: "runtime",
Config: appconfig.WrapAny(&runtimev1alpha1.Module{
AppName: "SimApp",
// NOTE: upgrade module is required to be prioritized
// During begin block slashing happens after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool, so as to keep the
// CanWithdrawInvariant invariant.
Expand Down
10 changes: 10 additions & 0 deletions testutil/configurator/configurator.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
// Config should never need to be instantiated manually and is solely used for ModuleOption.
type Config struct {
ModuleConfigs map[string]*appv1alpha1.ModuleConfig
PreBlockersOrder []string
BeginBlockersOrder []string
EndBlockersOrder []string
InitGenesisOrder []string
Expand All @@ -36,6 +37,9 @@ type Config struct {
func defaultConfig() *Config {
return &Config{
ModuleConfigs: make(map[string]*appv1alpha1.ModuleConfig),
PreBlockersOrder: []string{
"upgrade",
},
BeginBlockersOrder: []string{
"upgrade",
"mint",
Expand Down Expand Up @@ -102,6 +106,12 @@ func defaultConfig() *Config {

type ModuleOption func(config *Config)

func WithCustomPreBlockersOrder(preBlockOrder ...string) ModuleOption {
return func(config *Config) {
config.PreBlockersOrder = preBlockOrder
}
}

func WithCustomBeginBlockersOrder(beginBlockOrder ...string) ModuleOption {
return func(config *Config) {
config.BeginBlockersOrder = beginBlockOrder
Expand Down
2 changes: 1 addition & 1 deletion testutil/mock/types_module_module.go

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

6 changes: 6 additions & 0 deletions types/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import (
// InitChainer initializes application state at genesis
type InitChainer func(ctx Context, req abci.RequestInitChain) abci.ResponseInitChain

// PreBlocker runs code before the `BeginBlocker`.
type ResponsePreBlock struct {
ConsensusParamsChanged bool
}
type PreBlocker func(ctx Context, req abci.RequestBeginBlock) (ResponsePreBlock, 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
Expand Down
71 changes: 67 additions & 4 deletions types/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@ type HasConsensusVersion interface {
ConsensusVersion() uint64
}

type PreBlockAppModule interface {
AppModule
PreBlock(sdk.Context, abci.RequestBeginBlock) (sdk.ResponsePreBlock, error)
}

// BeginBlockAppModule is an extension interface that contains information about the AppModule and BeginBlock.
type BeginBlockAppModule interface {
AppModule
Expand Down Expand Up @@ -249,6 +254,7 @@ type Manager struct {
Modules map[string]interface{} // interface{} is used now to support the legacy AppModule as well as new core appmodule.AppModule.
OrderInitGenesis []string
OrderExportGenesis []string
OrderPreBlockers []string
OrderBeginBlockers []string
OrderEndBlockers []string
OrderMigrations []string
Expand All @@ -258,15 +264,20 @@ type Manager struct {
func NewManager(modules ...AppModule) *Manager {
moduleMap := make(map[string]interface{})
modulesStr := make([]string, 0, len(modules))
preBlockModulesStr := make([]string, 0)
for _, module := range modules {
moduleMap[module.Name()] = module
modulesStr = append(modulesStr, module.Name())
if _, ok := module.(PreBlockAppModule); ok {
preBlockModulesStr = append(preBlockModulesStr, module.Name())
}
}

return &Manager{
Modules: moduleMap,
OrderInitGenesis: modulesStr,
OrderExportGenesis: modulesStr,
OrderPreBlockers: preBlockModulesStr,
OrderBeginBlockers: modulesStr,
OrderEndBlockers: modulesStr,
}
Expand All @@ -277,15 +288,20 @@ func NewManager(modules ...AppModule) *Manager {
func NewManagerFromMap(moduleMap map[string]appmodule.AppModule) *Manager {
simpleModuleMap := make(map[string]interface{})
modulesStr := make([]string, 0, len(simpleModuleMap))
preBlockModulesStr := make([]string, 0)
for name, module := range moduleMap {
simpleModuleMap[name] = module
modulesStr = append(modulesStr, name)
if _, ok := module.(PreBlockAppModule); ok {
preBlockModulesStr = append(preBlockModulesStr, name)
}
}

return &Manager{
Modules: simpleModuleMap,
OrderInitGenesis: modulesStr,
OrderExportGenesis: modulesStr,
OrderPreBlockers: preBlockModulesStr,
OrderBeginBlockers: modulesStr,
OrderEndBlockers: modulesStr,
}
Expand All @@ -303,6 +319,12 @@ func (m *Manager) SetOrderExportGenesis(moduleNames ...string) {
m.OrderExportGenesis = moduleNames
}

// SetOrderPreBlockers sets the order of set pre-blocker calls
func (m *Manager) SetOrderPreBlockers(moduleNames ...string) {
m.assertNoForgottenPreBlockModules("SetOrderPreBlockers", moduleNames)
m.OrderPreBlockers = moduleNames
}

// SetOrderBeginBlockers sets the order of set begin-blocker calls
func (m *Manager) SetOrderBeginBlockers(moduleNames ...string) {
m.assertNoForgottenModules("SetOrderBeginBlockers", moduleNames)
Expand Down Expand Up @@ -443,6 +465,28 @@ func (m *Manager) assertNoForgottenModules(setOrderFnName string, moduleNames []
}
}

// assertNoForgottenPreBlockModules checks that we didn't forget any preblock modules in the
// SetOrder* functions.
func (m *Manager) assertNoForgottenPreBlockModules(setOrderFnName string, moduleNames []string) {
ms := make(map[string]bool)
for _, m := range moduleNames {
ms[m] = true
}
var missing []string
for m, module := range m.Modules {
if _, ok := module.(PreBlockAppModule); ok {
if !ms[m] {
missing = append(missing, m)
}
}
}
if len(missing) != 0 {
sort.Strings(missing)
panic(fmt.Sprintf(
"%s: all preblock modules must be defined when setting %s, missing: %v", setOrderFnName, setOrderFnName, missing))
}
}

// MigrationHandler is the migration function that each module registers.
type MigrationHandler func(sdk.Context) error

Expand Down Expand Up @@ -550,19 +594,38 @@ func (m Manager) RunMigrations(ctx sdk.Context, cfg Configurator, fromVM Version
return updatedVM, nil
}

// PreBlock performs begin block functionality for upgrade module.
// It takes the current context as a parameter and returns a boolean value
// indicating whether the migration was successfully executed or not.
func (m *Manager) PreBlock(ctx sdk.Context, req abci.RequestBeginBlock) (sdk.ResponsePreBlock, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())
paramsChanged := false
for _, moduleName := range m.OrderPreBlockers {
if module, ok := m.Modules[moduleName].(PreBlockAppModule); ok {
rsp, err := module.PreBlock(ctx, req)
if err != nil {
return sdk.ResponsePreBlock{}, err
}
if rsp.ConsensusParamsChanged {
paramsChanged = true
}
}
}
return sdk.ResponsePreBlock{
ConsensusParamsChanged: paramsChanged,
}, nil
}

// 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 {
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)
}
}

return abci.ResponseBeginBlock{
Events: ctx.EventManager().ABCIEvents(),
}
Expand Down
Loading
Loading