Skip to content

Commit

Permalink
feat: add valset change and block info service to runtime (#15811)
Browse files Browse the repository at this point in the history
Co-authored-by: Aaron Craelius <aaron@regen.network>
  • Loading branch information
tac0turtle and aaronc committed Apr 12, 2023
1 parent 70436fa commit f82cbbb
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 22 deletions.
65 changes: 43 additions & 22 deletions docs/architecture/adr-063-core-module-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -261,32 +270,42 @@ 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.

#### Remaining Parts of AppModule

The current `AppModule` framework handles a number of additional concerns which aren't addressed by this core API.
These include:

* gas
* block headers
* upgrades
Expand All @@ -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
Expand All @@ -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 {
Expand Down Expand Up @@ -373,15 +394,15 @@ 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

### Backwards Compatibility

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).
Expand Down
24 changes: 24 additions & 0 deletions runtime/services.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
}

0 comments on commit f82cbbb

Please sign in to comment.