From f82cbbbeebf7dfaf1a652c804eadbbb81ec280fa Mon Sep 17 00:00:00 2001 From: Marko Date: Wed, 12 Apr 2023 23:45:02 +0200 Subject: [PATCH] feat: add valset change and block info service to runtime (#15811) Co-authored-by: Aaron Craelius --- docs/architecture/adr-063-core-module-api.md | 65 +++++++++++++------- runtime/services.go | 24 ++++++++ 2 files changed, 67 insertions(+), 22 deletions(-) diff --git a/docs/architecture/adr-063-core-module-api.md b/docs/architecture/adr-063-core-module-api.md index a977688ec7..b10aca7598 100644 --- a/docs/architecture/adr-063-core-module-api.md +++ b/docs/architecture/adr-063-core-module-api.md @@ -14,33 +14,36 @@ ACCEPTED Partially Implemented A new core API is proposed as a way to develop cosmos-sdk applications that will eventually replace the existing `AppModule` and `sdk.Context` frameworks a set of core services and extension interfaces. This core API aims to: -- be simpler, -- more extensible, -- more stable than the current framework, -- enable deterministic events and queries, -- support event listeners and -- [ADR 033: Protobuf-based Inter-Module Communication](./adr-033-protobuf-inter-module-comm.md) clients. + +* be simpler +* more extensible +* more stable than the current framework +* enable deterministic events and queries, +* support event listeners +* [ADR 033: Protobuf-based Inter-Module Communication](./adr-033-protobuf-inter-module-comm.md) clients. ## Context Historically modules have exposed their functionality to the framework via the `AppModule` and `AppModuleBasic` interfaces which have the following shortcomings: + * both `AppModule` and `AppModuleBasic` need to be defined and registered which is counter-intuitive * apps need to implement the full interfaces, even parts they don't need (although there are workarounds for this), -* interface methods depend heavily on unstable third party dependencies, in particular Tendermint, +* interface methods depend heavily on unstable third party dependencies, in particular Comet, * legacy required methods have littered these interfaces for far too long In order to interact with the state machine, modules have needed to do a combination of these things: + * get store keys from the app * call methods on `sdk.Context` which contains more or less the full set of capability available to modules. By isolating all the state machine functionality into `sdk.Context`, the set of functionalities available to -modules are tightly coupled to this type. If there are changes to upstream dependencies (such as Tendermint) +modules are tightly coupled to this type. If there are changes to upstream dependencies (such as Comet) or new functionalities are desired (such as alternate store types), the changes need impact `sdk.Context` and all consumers of it (basically all modules). Also, all modules now receive `context.Context` and need to convert these to `sdk.Context`'s with a non-ergonomic unwrapping function. -Any breaking changes to these interfaces, such as ones imposed by third-party dependencies like Tendermint, have the +Any breaking changes to these interfaces, such as ones imposed by third-party dependencies like Comet, have the side effect of forcing all modules in the ecosystem to update in lock-step. This means it is almost impossible to have a version of the module which can be run with 2 or 3 different versions of the SDK or 2 or 3 different versions of another module. This lock-step coupling slows down overall development within the ecosystem and causes updates to @@ -50,11 +53,13 @@ components to be delayed longer than they would if things were more stable and l The `core` API proposes a set of core APIs that modules can rely on to interact with the state machine and expose their functionalities to it that are designed in a principled way such that: + * tight coupling of dependencies and unrelated functionalities is minimized or eliminated * APIs can have long-term stability guarantees * the SDK framework is extensible in a safe and straightforward way The design principles of the core API are as follows: + * everything that a module wants to interact with in the state machine is a service * all services coordinate state via `context.Context` and don't try to recreate the "bag of variables" approach of `sdk.Context` * all independent services are isolated in independent packages with minimal APIs and minimal dependencies @@ -63,7 +68,7 @@ The design principles of the core API are as follows: functionalities exposed by core extension interfaces * other non-core and/or non-LTS services can be exposed by specific versions of runtime modules or other modules following the same design principles, this includes functionality that interacts with specific non-stable versions of -third party dependencies such as Tendermint +third party dependencies such as Comet * the core API doesn't implement *any* functionality, it just defines types * go stable API compatibility guidelines are followed: https://go.dev/blog/module-compatibility @@ -74,7 +79,7 @@ SDK's current tightly coupled `BaseApp` design while still allowing for a high d compatibility. Modules which are built only against the core API don't need to know anything about which version of runtime, -`BaseApp` or Tendermint in order to be compatible. Modules from the core mainline SDK could be easily composed +`BaseApp` or Comet in order to be compatible. Modules from the core mainline SDK could be easily composed with a forked version of runtime with this pattern. This design is intended to enable matrices of compatible dependency versions. Ideally a given version of any module @@ -112,6 +117,7 @@ type TransientStoreService interface { ``` Modules can use these services like this: + ```go func (k msgServer) Send(ctx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) { store := k.kvStoreSvc.OpenKVStore(ctx) @@ -166,6 +172,7 @@ by other modules. If there is a client-side need to add events in patch releases Modules will provide their core services to the runtime module via extension interfaces built on top of the `cosmossdk.io/core/appmodule.AppModule` tag interface. This tag interface requires only two empty methods which allow `depinject` to identify implementors as `depinject.OnePerModule` types and as app module implementations: + ```go type AppModule interface { depinject.OnePerModuleType @@ -226,6 +233,7 @@ streaming genesis and modules using these frameworks generally do not need to wr genesis code. To support genesis, modules should implement the `HasGenesis` extension interface: + ```go type HasGenesis interface { AppModule @@ -248,6 +256,7 @@ type HasGenesis interface { Modules that have functionality that runs before transactions (begin blockers) or after transactions (end blockers) should implement the has `HasBeginBlocker` and/or `HasEndBlocker` interfaces: + ```go type HasBeginBlocker interface { AppModule @@ -261,25 +270,34 @@ type HasEndBlocker interface { ``` The `BeginBlock` and `EndBlock` methods will take a `context.Context`, because: -* most modules don't need Tendermint information other than `BlockInfo` so we can eliminate dependencies on specific -Tendermint versions -* for the few modules that need Tendermint block headers and/or return validator updates, specific versions of the -runtime module will provide specific functionality for interacting with the specific version(s) of Tendermint + +* most modules don't need Comet information other than `BlockInfo` so we can eliminate dependencies on specific +Comet versions +* for the few modules that need Comet block headers and/or return validator updates, specific versions of the +runtime module will provide specific functionality for interacting with the specific version(s) of Comet supported -In order for `BeginBlock`, `EndBlock` and `InitGenesis` to send back validator updates and retrieve full Tendermint -block headers, the runtime module for a specific version of Tendermint could provide services like this: +In order for `BeginBlock`, `EndBlock` and `InitGenesis` to send back validator updates and retrieve full Comet +block headers, the runtime module for a specific version of Comet could provide services like this: + ```go type ValidatorUpdateService interface { SetValidatorUpdates(context.Context, []abci.ValidatorUpdate) } -type BeginBlockService interface { - GetBeginBlockRequest(context.Context) abci.RequestBeginBlock +type BlockInfoService interface { + GetHeight() int64 // GetHeight returns the height of the block + Misbehavior() []abci.Misbehavior // Misbehavior returns the misbehavior of the block + GetHeaderHash() []byte // GetHeaderHash returns the hash of the block header + // GetValidatorsHash returns the hash of the validators + // For Comet, it is the hash of the next validators + GetValidatorsHash() []byte + GetProposerAddress() []byte // GetProposerAddress returns the address of the block proposer + GetDecidedLastCommit() abci.CommitInfo // GetDecidedLastCommit returns the last commit info } ``` -We know these types will change at the Tendermint level and that also a very limited set of modules actually need this +We know these types will change at the Comet level and that also a very limited set of modules actually need this functionality, so they are intentionally kept out of core to keep core limited to the necessary, minimal set of stable APIs. @@ -287,6 +305,7 @@ APIs. The current `AppModule` framework handles a number of additional concerns which aren't addressed by this core API. These include: + * gas * block headers * upgrades @@ -307,6 +326,7 @@ gRPC gateway registration should probably be handled by the runtime module, but gateway types as 1) we are already using an older version and 2) it's possible the framework can do this registration automatically in the future. So for now, the runtime module should probably provide some sort of specific type for doing this registration ex: + ```go type GrpcGatewayInfo struct { Handlers []GrpcGatewayHandler @@ -316,6 +336,7 @@ type GrpcGatewayHandler func(ctx context.Context, mux *runtime.ServeMux, client ``` which modules can return in a provider: + ```go func ProvideGrpcGateway() GrpcGatewayInfo { return GrpcGatewayinfo { @@ -373,7 +394,7 @@ allow tests to observe service behavior or provide a non-production implementati stores can be used to mock stores. For integration testing, a mock runtime implementation should be provided that allows composing different app modules -together for testing without a dependency on runtime or Tendermint. +together for testing without a dependency on runtime or Comet. ## Consequences @@ -381,7 +402,7 @@ together for testing without a dependency on runtime or Tendermint. Early versions of runtime modules should aim to support as much as possible modules built with the existing `AppModule`/`sdk.Context` framework. As the core API is more widely adopted, later runtime versions may choose to -drop support and only support the core API plus any runtime module specific APIs (like specific versions of Tendermint). +drop support and only support the core API plus any runtime module specific APIs (like specific versions of Comet). The core module itself should strive to remain at the go semantic version `v1` as long as possible and follow design principles that allow for strong long-term support (LTS). diff --git a/runtime/services.go b/runtime/services.go index 0b9223d92a..2de85cdd3a 100644 --- a/runtime/services.go +++ b/runtime/services.go @@ -1,9 +1,12 @@ package runtime import ( + "context" + appv1alpha1 "cosmossdk.io/api/cosmos/app/v1alpha1" autocliv1 "cosmossdk.io/api/cosmos/autocli/v1" reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" + abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/cosmos-sdk/runtime/services" "github.com/cosmos/cosmos-sdk/types/module" @@ -21,3 +24,24 @@ func (a *App) registerRuntimeServices(cfg module.Configurator) error { return nil } + +// ====================================================== +// ValidatorUpdateService & BlockInfoService +// ====================================================== + +// ValidatorUpdateService is the service that runtime will provide to the module that sets validator updates. +type ValidatorUpdateService interface { + SetValidatorUpdates(context.Context, []abci.ValidatorUpdate) +} + +// BlockInfoService is the service that runtime will provide to modules which need Comet block information. +type BlockInfoService interface { + GetHeight() int64 // GetHeight returns the height of the block + Misbehavior() []abci.Misbehavior // Misbehavior returns the misbehavior of the block + GetHeaderHash() []byte // GetHeaderHash returns the hash of the block header + // GetValidatorsHash returns the hash of the validators + // For Comet, it is the hash of the next validators + GetValidatorsHash() []byte + GetProposerAddress() []byte // GetProposerAddress returns the address of the block proposer + GetDecidedLastCommit() abci.CommitInfo // GetDecidedLastCommit returns the last commit info +}