github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-063-core-module-api.md (about) 1 # ADR 063: Core Module API 2 3 ## Changelog 4 5 * 2022-08-18 First Draft 6 * 2022-12-08 First Draft 7 * 2023-01-24 Updates 8 9 ## Status 10 11 ACCEPTED Partially Implemented 12 13 ## Abstract 14 15 A new core API is proposed as a way to develop cosmos-sdk applications that will eventually replace the existing 16 `AppModule` and `sdk.Context` frameworks a set of core services and extension interfaces. This core API aims to: 17 18 * be simpler 19 * more extensible 20 * more stable than the current framework 21 * enable deterministic events and queries, 22 * support event listeners 23 * [ADR 033: Protobuf-based Inter-Module Communication](./adr-033-protobuf-inter-module-comm.md) clients. 24 25 ## Context 26 27 Historically modules have exposed their functionality to the framework via the `AppModule` and `AppModuleBasic` 28 interfaces which have the following shortcomings: 29 30 * both `AppModule` and `AppModuleBasic` need to be defined and registered which is counter-intuitive 31 * apps need to implement the full interfaces, even parts they don't need (although there are workarounds for this), 32 * interface methods depend heavily on unstable third party dependencies, in particular Comet, 33 * legacy required methods have littered these interfaces for far too long 34 35 In order to interact with the state machine, modules have needed to do a combination of these things: 36 37 * get store keys from the app 38 * call methods on `sdk.Context` which contains more or less the full set of capability available to modules. 39 40 By isolating all the state machine functionality into `sdk.Context`, the set of functionalities available to 41 modules are tightly coupled to this type. If there are changes to upstream dependencies (such as Comet) 42 or new functionalities are desired (such as alternate store types), the changes need impact `sdk.Context` and all 43 consumers of it (basically all modules). Also, all modules now receive `context.Context` and need to convert these 44 to `sdk.Context`'s with a non-ergonomic unwrapping function. 45 46 Any breaking changes to these interfaces, such as ones imposed by third-party dependencies like Comet, have the 47 side effect of forcing all modules in the ecosystem to update in lock-step. This means it is almost impossible to have 48 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 49 another module. This lock-step coupling slows down overall development within the ecosystem and causes updates to 50 components to be delayed longer than they would if things were more stable and loosely coupled. 51 52 ## Decision 53 54 The `core` API proposes a set of core APIs that modules can rely on to interact with the state machine and expose their 55 functionalities to it that are designed in a principled way such that: 56 57 * tight coupling of dependencies and unrelated functionalities is minimized or eliminated 58 * APIs can have long-term stability guarantees 59 * the SDK framework is extensible in a safe and straightforward way 60 61 The design principles of the core API are as follows: 62 63 * everything that a module wants to interact with in the state machine is a service 64 * all services coordinate state via `context.Context` and don't try to recreate the "bag of variables" approach of `sdk.Context` 65 * all independent services are isolated in independent packages with minimal APIs and minimal dependencies 66 * the core API should be minimalistic and designed for long-term support (LTS) 67 * a "runtime" module will implement all the "core services" defined by the core API and can handle all module 68 functionalities exposed by core extension interfaces 69 * other non-core and/or non-LTS services can be exposed by specific versions of runtime modules or other modules 70 following the same design principles, this includes functionality that interacts with specific non-stable versions of 71 third party dependencies such as Comet 72 * the core API doesn't implement *any* functionality, it just defines types 73 * go stable API compatibility guidelines are followed: https://go.dev/blog/module-compatibility 74 75 A "runtime" module is any module which implements the core functionality of composing an ABCI app, which is currently 76 handled by `BaseApp` and the `ModuleManager`. Runtime modules which implement the core API are *intentionally* separate 77 from the core API in order to enable more parallel versions and forks of the runtime module than is possible with the 78 SDK's current tightly coupled `BaseApp` design while still allowing for a high degree of composability and 79 compatibility. 80 81 Modules which are built only against the core API don't need to know anything about which version of runtime, 82 `BaseApp` or Comet in order to be compatible. Modules from the core mainline SDK could be easily composed 83 with a forked version of runtime with this pattern. 84 85 This design is intended to enable matrices of compatible dependency versions. Ideally a given version of any module 86 is compatible with multiple versions of the runtime module and other compatible modules. This will allow dependencies 87 to be selectively updated based on battle-testing. More conservative projects may want to update some dependencies 88 slower than more fast moving projects. 89 90 ### Core Services 91 92 The following "core services" are defined by the core API. All valid runtime module implementations should provide 93 implementations of these services to modules via both [dependency injection](./adr-057-app-wiring.md) and 94 manual wiring. The individual services described below are all bundled in a convenient `appmodule.Service` 95 "bundle service" so that for simplicity modules can declare a dependency on a single service. 96 97 #### Store Services 98 99 Store services will be defined in the `cosmossdk.io/core/store` package. 100 101 The generic `store.KVStore` interface is the same as current SDK `KVStore` interface. Store keys have been refactored 102 into store services which, instead of expecting the context to know about stores, invert the pattern and allow 103 retrieving a store from a generic context. There are three store services for the three types of currently supported 104 stores - regular kv-store, memory, and transient: 105 106 ```go 107 type KVStoreService interface { 108 OpenKVStore(context.Context) KVStore 109 } 110 111 type MemoryStoreService interface { 112 OpenMemoryStore(context.Context) KVStore 113 } 114 type TransientStoreService interface { 115 OpenTransientStore(context.Context) KVStore 116 } 117 ``` 118 119 Modules can use these services like this: 120 121 ```go 122 func (k msgServer) Send(ctx context.Context, msg *types.MsgSend) (*types.MsgSendResponse, error) { 123 store := k.kvStoreSvc.OpenKVStore(ctx) 124 } 125 ``` 126 127 Just as with the current runtime module implementation, modules will not need to explicitly name these store keys, 128 but rather the runtime module will choose an appropriate name for them and modules just need to request the 129 type of store they need in their dependency injection (or manual) constructors. 130 131 #### Event Service 132 133 The event `Service` will be defined in the `cosmossdk.io/core/event` package. 134 135 The event `Service` allows modules to emit typed and legacy untyped events: 136 137 ```go 138 package event 139 140 type Service interface { 141 // EmitProtoEvent emits events represented as a protobuf message (as described in ADR 032). 142 // 143 // Callers SHOULD assume that these events may be included in consensus. These events 144 // MUST be emitted deterministically and adding, removing or changing these events SHOULD 145 // be considered state-machine breaking. 146 EmitProtoEvent(ctx context.Context, event protoiface.MessageV1) error 147 148 // EmitKVEvent emits an event based on an event and kv-pair attributes. 149 // 150 // These events will not be part of consensus and adding, removing or changing these events is 151 // not a state-machine breaking change. 152 EmitKVEvent(ctx context.Context, eventType string, attrs ...KVEventAttribute) error 153 154 // EmitProtoEventNonConsensus emits events represented as a protobuf message (as described in ADR 032), without 155 // including it in blockchain consensus. 156 // 157 // These events will not be part of consensus and adding, removing or changing events is 158 // not a state-machine breaking change. 159 EmitProtoEventNonConsensus(ctx context.Context, event protoiface.MessageV1) error 160 } 161 ``` 162 163 Typed events emitted with `EmitProto` should be assumed to be part of blockchain consensus (whether they are part of 164 the block or app hash is left to the runtime to specify). 165 166 Events emitted by `EmitKVEvent` and `EmitProtoEventNonConsensus` are not considered to be part of consensus and cannot be observed 167 by other modules. If there is a client-side need to add events in patch releases, these methods can be used. 168 169 #### Logger 170 171 A logger (`cosmossdk.io/log`) must be supplied using `depinject`, and will 172 be made available for modules to use via `depinject.In`. 173 Modules using it should follow the current pattern in the SDK by adding the module name before using it. 174 175 ```go 176 type ModuleInputs struct { 177 depinject.In 178 179 Logger log.Logger 180 } 181 182 func ProvideModule(in ModuleInputs) ModuleOutputs { 183 keeper := keeper.NewKeeper( 184 in.logger, 185 ) 186 } 187 188 func NewKeeper(logger log.Logger) Keeper { 189 return Keeper{ 190 logger: logger.With(log.ModuleKey, "x/"+types.ModuleName), 191 } 192 } 193 ``` 194 195 ``` 196 197 ### Core `AppModule` extension interfaces 198 199 200 Modules will provide their core services to the runtime module via extension interfaces built on top of the 201 `cosmossdk.io/core/appmodule.AppModule` tag interface. This tag interface requires only two empty methods which 202 allow `depinject` to identify implementors as `depinject.OnePerModule` types and as app module implementations: 203 204 ```go 205 type AppModule interface { 206 depinject.OnePerModuleType 207 208 // IsAppModule is a dummy method to tag a struct as implementing an AppModule. 209 IsAppModule() 210 } 211 ``` 212 213 Other core extension interfaces will be defined in `cosmossdk.io/core` should be supported by valid runtime 214 implementations. 215 216 #### `MsgServer` and `QueryServer` registration 217 218 `MsgServer` and `QueryServer` registration is done by implementing the `HasServices` extension interface: 219 220 ```go 221 type HasServices interface { 222 AppModule 223 224 RegisterServices(grpc.ServiceRegistrar) 225 } 226 227 ``` 228 229 Because of the `cosmos.msg.v1.service` protobuf option, required for `Msg` services, the same `ServiceRegitrar` can be 230 used to register both `Msg` and query services. 231 232 #### Genesis 233 234 The genesis `Handler` functions - `DefaultGenesis`, `ValidateGenesis`, `InitGenesis` and `ExportGenesis` - are specified 235 against the `GenesisSource` and `GenesisTarget` interfaces which will abstract over genesis sources which may be a single 236 JSON object or collections of JSON objects that can be efficiently streamed. 237 238 ```go 239 // GenesisSource is a source for genesis data in JSON format. It may abstract over a 240 // single JSON object or separate files for each field in a JSON object that can 241 // be streamed over. Modules should open a separate io.ReadCloser for each field that 242 // is required. When fields represent arrays they can efficiently be streamed 243 // over. If there is no data for a field, this function should return nil, nil. It is 244 // important that the caller closes the reader when done with it. 245 type GenesisSource = func(field string) (io.ReadCloser, error) 246 247 // GenesisTarget is a target for writing genesis data in JSON format. It may 248 // abstract over a single JSON object or JSON in separate files that can be 249 // streamed over. Modules should open a separate io.WriteCloser for each field 250 // and should prefer writing fields as arrays when possible to support efficient 251 // iteration. It is important the caller closers the writer AND checks the error 252 // when done with it. It is expected that a stream of JSON data is written 253 // to the writer. 254 type GenesisTarget = func(field string) (io.WriteCloser, error) 255 ``` 256 257 All genesis objects for a given module are expected to conform to the semantics of a JSON object. 258 Each field in the JSON object should be read and written separately to support streaming genesis. 259 The [ORM](./adr-055-orm.md) and [collections](./adr-062-collections-state-layer.md) both support 260 streaming genesis and modules using these frameworks generally do not need to write any manual 261 genesis code. 262 263 To support genesis, modules should implement the `HasGenesis` extension interface: 264 265 ```go 266 type HasGenesis interface { 267 AppModule 268 269 // DefaultGenesis writes the default genesis for this module to the target. 270 DefaultGenesis(GenesisTarget) error 271 272 // ValidateGenesis validates the genesis data read from the source. 273 ValidateGenesis(GenesisSource) error 274 275 // InitGenesis initializes module state from the genesis source. 276 InitGenesis(context.Context, GenesisSource) error 277 278 // ExportGenesis exports module state to the genesis target. 279 ExportGenesis(context.Context, GenesisTarget) error 280 } 281 ``` 282 283 #### Pre Blockers 284 285 Modules that have functionality that runs before BeginBlock and should implement the has `HasPreBlocker` interfaces: 286 287 ```go 288 type HasPreBlocker interface { 289 AppModule 290 PreBlock(context.Context) error 291 } 292 ``` 293 294 #### Begin and End Blockers 295 296 Modules that have functionality that runs before transactions (begin blockers) or after transactions 297 (end blockers) should implement the has `HasBeginBlocker` and/or `HasEndBlocker` interfaces: 298 299 ```go 300 type HasBeginBlocker interface { 301 AppModule 302 BeginBlock(context.Context) error 303 } 304 305 type HasEndBlocker interface { 306 AppModule 307 EndBlock(context.Context) error 308 } 309 ``` 310 311 The `BeginBlock` and `EndBlock` methods will take a `context.Context`, because: 312 313 * most modules don't need Comet information other than `BlockInfo` so we can eliminate dependencies on specific 314 Comet versions 315 * for the few modules that need Comet block headers and/or return validator updates, specific versions of the 316 runtime module will provide specific functionality for interacting with the specific version(s) of Comet 317 supported 318 319 In order for `BeginBlock`, `EndBlock` and `InitGenesis` to send back validator updates and retrieve full Comet 320 block headers, the runtime module for a specific version of Comet could provide services like this: 321 322 ```go 323 type ValidatorUpdateService interface { 324 SetValidatorUpdates(context.Context, []abci.ValidatorUpdate) 325 } 326 ``` 327 328 Header Service defines a way to get header information about a block. This information is generalized for all implementations: 329 330 ```go 331 332 type Service interface { 333 GetHeaderInfo(context.Context) Info 334 } 335 336 type Info struct { 337 Height int64 // Height returns the height of the block 338 Hash []byte // Hash returns the hash of the block header 339 Time time.Time // Time returns the time of the block 340 ChainID string // ChainId returns the chain ID of the block 341 } 342 ``` 343 344 Comet Service provides a way to get comet specific information: 345 346 ```go 347 type Service interface { 348 GetCometInfo(context.Context) Info 349 } 350 351 type CometInfo struct { 352 Evidence []abci.Misbehavior // Misbehavior returns the misbehavior of the block 353 // ValidatorsHash returns the hash of the validators 354 // For Comet, it is the hash of the next validators 355 ValidatorsHash []byte 356 ProposerAddress []byte // ProposerAddress returns the address of the block proposer 357 DecidedLastCommit abci.CommitInfo // DecidedLastCommit returns the last commit info 358 } 359 ``` 360 361 If a user would like to provide a module other information they would need to implement another service like: 362 363 ```go 364 type RollKit Interface { 365 ... 366 } 367 ``` 368 369 We know these types will change at the Comet level and that also a very limited set of modules actually need this 370 functionality, so they are intentionally kept out of core to keep core limited to the necessary, minimal set of stable 371 APIs. 372 373 #### Remaining Parts of AppModule 374 375 The current `AppModule` framework handles a number of additional concerns which aren't addressed by this core API. 376 These include: 377 378 * gas 379 * block headers 380 * upgrades 381 * registration of gogo proto and amino interface types 382 * cobra query and tx commands 383 * gRPC gateway 384 * crisis module invariants 385 * simulations 386 387 Additional `AppModule` extension interfaces either inside or outside of core will need to be specified to handle 388 these concerns. 389 390 In the case of gogo proto and amino interfaces, the registration of these generally should happen as early 391 as possible during initialization and in [ADR 057: App Wiring](./adr-057-app-wiring-1.md), protobuf type registration 392 happens before dependency injection (although this could alternatively be done dedicated DI providers). 393 394 gRPC gateway registration should probably be handled by the runtime module, but the core API shouldn't depend on gRPC 395 gateway types as 1) we are already using an older version and 2) it's possible the framework can do this registration 396 automatically in the future. So for now, the runtime module should probably provide some sort of specific type for doing 397 this registration ex: 398 399 ```go 400 type GrpcGatewayInfo struct { 401 Handlers []GrpcGatewayHandler 402 } 403 404 type GrpcGatewayHandler func(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error 405 ``` 406 407 which modules can return in a provider: 408 409 ```go 410 func ProvideGrpcGateway() GrpcGatewayInfo { 411 return GrpcGatewayinfo { 412 Handlers: []Handler {types.RegisterQueryHandlerClient} 413 } 414 } 415 ``` 416 417 Crisis module invariants and simulations are subject to potential redesign and should be managed with types 418 defined in the crisis and simulation modules respectively. 419 420 Extension interface for CLI commands will be provided via the `cosmossdk.io/client/v2` module and its 421 [autocli](./adr-058-auto-generated-cli.md) framework. 422 423 #### Example Usage 424 425 Here is an example of setting up a hypothetical `foo` v2 module which uses the [ORM](./adr-055-orm.md) for its state 426 management and genesis. 427 428 ```go 429 430 type Keeper struct { 431 db orm.ModuleDB 432 evtSrv event.Service 433 } 434 435 func (k Keeper) RegisterServices(r grpc.ServiceRegistrar) { 436 foov1.RegisterMsgServer(r, k) 437 foov1.RegisterQueryServer(r, k) 438 } 439 440 func (k Keeper) BeginBlock(context.Context) error { 441 return nil 442 } 443 444 func ProvideApp(config *foomodulev2.Module, evtSvc event.EventService, db orm.ModuleDB) (Keeper, appmodule.AppModule){ 445 k := &Keeper{db: db, evtSvc: evtSvc} 446 return k, k 447 } 448 ``` 449 450 ### Runtime Compatibility Version 451 452 The `core` module will define a static integer var, `cosmossdk.io/core.RuntimeCompatibilityVersion`, which is 453 a minor version indicator of the core module that is accessible at runtime. Correct runtime module implementations 454 should check this compatibility version and return an error if the current `RuntimeCompatibilityVersion` is higher 455 than the version of the core API that this runtime version can support. When new features are adding to the `core` 456 module API that runtime modules are required to support, this version should be incremented. 457 458 ### Runtime Modules 459 460 The initial `runtime` module will simply be created within the existing `github.com/cosmos/cosmos-sdk` go module 461 under the `runtime` package. This module will be a small wrapper around the existing `BaseApp`, `sdk.Context` and 462 module manager and follow the Cosmos SDK's existing [0-based versioning](https://0ver.org). To move to semantic 463 versioning as well as runtime modularity, new officially supported runtime modules will be created under the 464 `cosmossdk.io/runtime` prefix. For each supported consensus engine a semantically-versioned go module should be created 465 with a runtime implementation for that consensus engine. For example: 466 - `cosmossdk.io/runtime/comet` 467 - `cosmossdk.io/runtime/comet/v2` 468 - `cosmossdk.io/runtime/rollkit` 469 - etc. 470 471 These runtime modules should attempt to be semantically versioned even if the underlying consensus engine is not. Also, 472 because a runtime module is also a first class Cosmos SDK module, it should have a protobuf module config type. 473 A new semantically versioned module config type should be created for each of these runtime module such that there is a 474 1:1 correspondence between the go module and module config type. This is the same practice should be followed for every 475 semantically versioned Cosmos SDK module as described in [ADR 057: App Wiring](./adr-057-app-wiring.md). 476 477 Currently, `github.com/cosmos/cosmos-sdk/runtime` uses the protobuf config type `cosmos.app.runtime.v1alpha1.Module`. 478 When we have a standalone v1 comet runtime, we should use a dedicated protobuf module config type such as 479 `cosmos.runtime.comet.v1.Module1`. When we release v2 of the comet runtime (`cosmossdk.io/runtime/comet/v2`) we should 480 have a corresponding `cosmos.runtime.comet.v2.Module` protobuf type. 481 482 In order to make it easier to support different consensus engines that support the same core module functionality as 483 described in this ADR, a common go module should be created with shared runtime components. The easiest runtime components 484 to share initially are probably the message/query router, inter-module client, service register, and event router. 485 This common runtime module should be created initially as the `cosmossdk.io/runtime/common` go module. 486 487 When this new architecture has been implemented, the main dependency for a Cosmos SDK module would be 488 `cosmossdk.io/core` and that module should be able to be used with any supported consensus engine (to the extent 489 that it does not explicitly depend on consensus engine specific functionality such as Comet's block headers). An 490 app developer would then be able to choose which consensus engine they want to use by importing the corresponding 491 runtime module. The current `BaseApp` would be refactored into the `cosmossdk.io/runtime/comet` module, the router 492 infrastructure in `baseapp/` would be refactored into `cosmossdk.io/runtime/common` and support ADR 033, and eventually 493 a dependency on `github.com/cosmos/cosmos-sdk` would no longer be required. 494 495 In short, modules would depend primarily on `cosmossdk.io/core`, and each `cosmossdk.io/runtime/{consensus-engine}` 496 would implement the `cosmossdk.io/core` functionality for that consensus engine. 497 498 On additional piece that would need to be resolved as part of this architecture is how runtimes relate to the server. 499 Likely it would make sense to modularize the current server architecture so that it can be used with any runtime even 500 if that is based on a consensus engine besides Comet. This means that eventually the Comet runtime would need to 501 encapsulate the logic for starting Comet and the ABCI app. 502 503 ### Testing 504 505 A mock implementation of all services should be provided in core to allow for unit testing of modules 506 without needing to depend on any particular version of runtime. Mock services should 507 allow tests to observe service behavior or provide a non-production implementation - for instance memory 508 stores can be used to mock stores. 509 510 For integration testing, a mock runtime implementation should be provided that allows composing different app modules 511 together for testing without a dependency on runtime or Comet. 512 513 ## Consequences 514 515 ### Backwards Compatibility 516 517 Early versions of runtime modules should aim to support as much as possible modules built with the existing 518 `AppModule`/`sdk.Context` framework. As the core API is more widely adopted, later runtime versions may choose to 519 drop support and only support the core API plus any runtime module specific APIs (like specific versions of Comet). 520 521 The core module itself should strive to remain at the go semantic version `v1` as long as possible and follow design 522 principles that allow for strong long-term support (LTS). 523 524 Older versions of the SDK can support modules built against core with adaptors that convert wrap core `AppModule` 525 implementations in implementations of `AppModule` that conform to that version of the SDK's semantics as well 526 as by providing service implementations by wrapping `sdk.Context`. 527 528 ### Positive 529 530 * better API encapsulation and separation of concerns 531 * more stable APIs 532 * more framework extensibility 533 * deterministic events and queries 534 * event listeners 535 * inter-module msg and query execution support 536 * more explicit support for forking and merging of module versions (including runtime) 537 538 ### Negative 539 540 ### Neutral 541 542 * modules will need to be refactored to use this API 543 * some replacements for `AppModule` functionality still need to be defined in follow-ups 544 (type registration, commands, invariants, simulations) and this will take additional design work 545 546 ## Further Discussions 547 548 * gas 549 * block headers 550 * upgrades 551 * registration of gogo proto and amino interface types 552 * cobra query and tx commands 553 * gRPC gateway 554 * crisis module invariants 555 * simulations 556 557 ## References 558 559 * [ADR 033: Protobuf-based Inter-Module Communication](./adr-033-protobuf-inter-module-comm.md) 560 * [ADR 057: App Wiring](./adr-057-app-wiring-1.md) 561 * [ADR 055: ORM](./adr-055-orm.md) 562 * [ADR 028: Public Key Addresses](./adr-028-public-key-addresses.md) 563 * [Keeping Your Modules Compatible](https://go.dev/blog/module-compatibility)