Changelog
- 2022-08-18 First Draft
- 2022-12-08 First Draft
- 2023-01-24 Updates
Status
ACCEPTED Partially ImplementedAbstract
A new core API is proposed as a way to develop cosmos-sdk applications that will eventually replace the existingAppModule
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
- ADR 033: Protobuf-based Inter-Module Communication clients.
Context
Historically modules have exposed their functionality to the framework via theAppModule
and AppModuleBasic
interfaces which have the following shortcomings:
- both
AppModule
andAppModuleBasic
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 Comet,
- legacy required methods have littered these interfaces for far too long
- get store keys from the app
- call methods on
sdk.Context
which contains more or less the full set of capability available to modules.
sdk.Context
, the set of functionalities available to
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 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
components to be delayed longer than they would if things were more stable and loosely coupled.
Decision
Thecore
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
- 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 ofsdk.Context
- all independent services are isolated in independent packages with minimal APIs and minimal dependencies
- the core API should be minimalistic and designed for long-term support (LTS)
- a “runtime” module will implement all the “core services” defined by the core API and can handle all module 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 Comet
- the core API doesn’t implement any functionality, it just defines types
- go stable API compatibility guidelines are followed: Link
BaseApp
and the ModuleManager
. Runtime modules which implement the core API are intentionally separate
from the core API in order to enable more parallel versions and forks of the runtime module than is possible with the
SDK’s current tightly coupled BaseApp
design while still allowing for a high degree of composability and
compatibility.
Modules which are built only against the core API don’t need to know anything about which version of runtime,
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
is compatible with multiple versions of the runtime module and other compatible modules. This will allow dependencies
to be selectively updated based on battle-testing. More conservative projects may want to update some dependencies
slower than more fast moving projects.
Core Services
The following “core services” are defined by the core API. All valid runtime module implementations should provide implementations of these services to modules via both dependency injection and manual wiring. The individual services described below are all bundled in a convenientappmodule.Service
“bundle service” so that for simplicity modules can declare a dependency on a single service.
Store Services
Store services will be defined in thecosmossdk.io/core/store
package.
The generic store.KVStore
interface is the same as current SDK KVStore
interface. Store keys have been refactored
into store services which, instead of expecting the context to know about stores, invert the pattern and allow
retrieving a store from a generic context. There are three store services for the three types of currently supported
stores - regular kv-store, memory, and transient:
Event Service
The eventService
will be defined in the cosmossdk.io/core/event
package.
The event Service
allows modules to emit typed and legacy untyped events:
EmitProto
should be assumed to be part of blockchain consensus (whether they are part of
the block or app hash is left to the runtime to specify).
Events emitted by EmitKVEvent
and EmitProtoEventNonConsensus
are not considered to be part of consensus and cannot be observed
by other modules. If there is a client-side need to add events in patch releases, these methods can be used.
Logger
A logger (cosmossdk.io/log
) must be supplied using depinject
, and will
be made available for modules to use via depinject.In
.
Modules using it should follow the current pattern in the SDK by adding the module name before using it.
Core AppModule
extension interfaces
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:
cosmossdk.io/core
should be supported by valid runtime
implementations.
MsgServer
and QueryServer
registration
MsgServer
and QueryServer
registration is done by implementing the HasServices
extension interface:
cosmos.msg.v1.service
protobuf option, required for Msg
services, the same ServiceRegitrar
can be
used to register both Msg
and query services.
Genesis
The genesisHandler
functions - DefaultGenesis
, ValidateGenesis
, InitGenesis
and ExportGenesis
- are specified
against the GenesisSource
and GenesisTarget
interfaces which will abstract over genesis sources which may be a single
JSON object or collections of JSON objects that can be efficiently streamed.
HasGenesis
extension interface:
Pre Blockers
Modules that have functionality that runs before BeginBlock and should implement the hasHasPreBlocker
interfaces:
Begin and End Blockers
Modules that have functionality that runs before transactions (begin blockers) or after transactions (end blockers) should implement the hasHasBeginBlocker
and/or HasEndBlocker
interfaces:
BeginBlock
and EndBlock
methods will take a context.Context
, because:
- 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
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:
Remaining Parts of AppModule
The currentAppModule
framework handles a number of additional concerns which aren’t addressed by this core API.
These include:
- gas
- block headers
- upgrades
- registration of gogo proto and amino interface types
- cobra query and tx commands
- gRPC gateway
- crisis module invariants
- simulations
AppModule
extension interfaces either inside or outside of core will need to be specified to handle
these concerns.
In the case of gogo proto and amino interfaces, the registration of these generally should happen as early
as possible during initialization and in ADR 057: App Wiring, protobuf type registrationhappens before dependency injection (although this could alternatively be done dedicated DI providers). gRPC gateway registration should probably be handled by the runtime module, but the core API shouldn’t depend on gRPC 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:
cosmossdk.io/client/v2
module and its
autocli framework.
Example Usage
Here is an example of setting up a hypotheticalfoo
v2 module which uses the ORM for its state
management and genesis.
Runtime Compatibility Version
Thecore
module will define a static integer var, cosmossdk.io/core.RuntimeCompatibilityVersion
, which is
a minor version indicator of the core module that is accessible at runtime. Correct runtime module implementations
should check this compatibility version and return an error if the current RuntimeCompatibilityVersion
is higher
than the version of the core API that this runtime version can support. When new features are adding to the core
module API that runtime modules are required to support, this version should be incremented.
Runtime Modules
The initialruntime
module will simply be created within the existing github.com/cosmos/cosmos-sdk
go module
under the runtime
package. This module will be a small wrapper around the existing BaseApp
, sdk.Context
and
module manager and follow the Cosmos SDK’s existing 0-based versioning. To move to semantic
versioning as well as runtime modularity, new officially supported runtime modules will be created under the
cosmossdk.io/runtime
prefix. For each supported consensus engine a semantically-versioned go module should be created
with a runtime implementation for that consensus engine. For example:
cosmossdk.io/runtime/comet
cosmossdk.io/runtime/comet/v2
cosmossdk.io/runtime/rollkit
- etc.
github.com/cosmos/cosmos-sdk/runtime
uses the protobuf config type cosmos.app.runtime.v1alpha1.Module
.
When we have a standalone v1 comet runtime, we should use a dedicated protobuf module config type such as
cosmos.runtime.comet.v1.Module1
. When we release v2 of the comet runtime (cosmossdk.io/runtime/comet/v2
) we should
have a corresponding cosmos.runtime.comet.v2.Module
protobuf type.
In order to make it easier to support different consensus engines that support the same core module functionality as
described in this ADR, a common go module should be created with shared runtime components. The easiest runtime components
to share initially are probably the message/query router, inter-module client, service register, and event router.
This common runtime module should be created initially as the cosmossdk.io/runtime/common
go module.
When this new architecture has been implemented, the main dependency for a Cosmos SDK module would be
cosmossdk.io/core
and that module should be able to be used with any supported consensus engine (to the extent
that it does not explicitly depend on consensus engine specific functionality such as Comet’s block headers). An
app developer would then be able to choose which consensus engine they want to use by importing the corresponding
runtime module. The current BaseApp
would be refactored into the cosmossdk.io/runtime/comet
module, the router
infrastructure in baseapp/
would be refactored into cosmossdk.io/runtime/common
and support ADR 033, and eventually
a dependency on github.com/cosmos/cosmos-sdk
would no longer be required.
In short, modules would depend primarily on cosmossdk.io/core
, and each cosmossdk.io/runtime/{consensus-engine}
would implement the cosmossdk.io/core
functionality for that consensus engine.
On additional piece that would need to be resolved as part of this architecture is how runtimes relate to the server.
Likely it would make sense to modularize the current server architecture so that it can be used with any runtime even
if that is based on a consensus engine besides Comet. This means that eventually the Comet runtime would need to
encapsulate the logic for starting Comet and the ABCI app.
Testing
A mock implementation of all services should be provided in core to allow for unit testing of modules without needing to depend on any particular version of runtime. Mock services should allow tests to observe service behavior or provide a non-production implementation - for instance memory 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 Comet.Consequences
Backwards Compatibility
Early versions of runtime modules should aim to support as much as possible modules built with the existingAppModule
/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 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).
Older versions of the SDK can support modules built against core with adaptors that convert wrap core AppModule
implementations in implementations of AppModule
that conform to that version of the SDK’s semantics as well
as by providing service implementations by wrapping sdk.Context
.
Positive
- better API encapsulation and separation of concerns
- more stable APIs
- more framework extensibility
- deterministic events and queries
- event listeners
- inter-module msg and query execution support
- more explicit support for forking and merging of module versions (including runtime)
Negative
Neutral
- modules will need to be refactored to use this API
- some replacements for
AppModule
functionality still need to be defined in follow-ups (type registration, commands, invariants, simulations) and this will take additional design work
Further Discussions
- gas
- block headers
- upgrades
- registration of gogo proto and amino interface types
- cobra query and tx commands
- gRPC gateway
- crisis module invariants
- simulations