github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-033-protobuf-inter-module-comm.md (about) 1 # ADR 033: Protobuf-based Inter-Module Communication 2 3 ## Changelog 4 5 * 2020-10-05: Initial Draft 6 7 ## Status 8 9 Proposed 10 11 ## Abstract 12 13 This ADR introduces a system for permissioned inter-module communication leveraging the protobuf `Query` and `Msg` 14 service definitions defined in [ADR 021](./adr-021-protobuf-query-encoding.md) and 15 [ADR 031](./adr-031-msg-service.md) which provides: 16 17 * stable protobuf based module interfaces to potentially later replace the keeper paradigm 18 * stronger inter-module object capabilities (OCAPs) guarantees 19 * module accounts and sub-account authorization 20 21 ## Context 22 23 In the current Cosmos SDK documentation on the [Object-Capability Model](../../learn/advanced/10-ocap.md), it is stated that: 24 25 > We assume that a thriving ecosystem of Cosmos SDK modules that are easy to compose into a blockchain application will contain faulty or malicious modules. 26 27 There is currently not a thriving ecosystem of Cosmos SDK modules. We hypothesize that this is in part due to: 28 29 1. lack of a stable v1.0 Cosmos SDK to build modules off of. Module interfaces are changing, sometimes dramatically, from 30 point release to point release, often for good reasons, but this does not create a stable foundation to build on. 31 2. lack of a properly implemented object capability or even object-oriented encapsulation system which makes refactors 32 of module keeper interfaces inevitable because the current interfaces are poorly constrained. 33 34 ### `x/bank` Case Study 35 36 Currently the `x/bank` keeper gives pretty much unrestricted access to any module which references it. For instance, the 37 `SetBalance` method allows the caller to set the balance of any account to anything, bypassing even proper tracking of supply. 38 39 There appears to have been some later attempts to implement some semblance of OCAPs using module-level minting, staking 40 and burning permissions. These permissions allow a module to mint, burn or delegate tokens with reference to the module’s 41 own account. These permissions are actually stored as a `[]string` array on the `ModuleAccount` type in state. 42 43 However, these permissions don’t really do much. They control what modules can be referenced in the `MintCoins`, 44 `BurnCoins` and `DelegateCoins***` methods, but for one there is no unique object capability token that controls access — 45 just a simple string. So the `x/upgrade` module could mint tokens for the `x/staking` module simple by calling 46 `MintCoins(“staking”)`. Furthermore, all modules which have access to these keeper methods, also have access to 47 `SetBalance` negating any other attempt at OCAPs and breaking even basic object-oriented encapsulation. 48 49 ## Decision 50 51 Based on [ADR-021](./adr-021-protobuf-query-encoding.md) and [ADR-031](./adr-031-msg-service.md), we introduce the 52 Inter-Module Communication framework for secure module authorization and OCAPs. 53 When implemented, this could also serve as an alternative to the existing paradigm of passing keepers between 54 modules. The approach outlined here-in is intended to form the basis of a Cosmos SDK v1.0 that provides the necessary 55 stability and encapsulation guarantees that allow a thriving module ecosystem to emerge. 56 57 Of particular note — the decision is to _enable_ this functionality for modules to adopt at their own discretion. 58 Proposals to migrate existing modules to this new paradigm will have to be a separate conversation, potentially 59 addressed as amendments to this ADR. 60 61 ### New "Keeper" Paradigm 62 63 In [ADR 021](./adr-021-protobuf-query-encoding.md), a mechanism for using protobuf service definitions to define queriers 64 was introduced and in [ADR 31](./adr-031-msg-service.md), a mechanism for using protobuf service to define `Msg`s was added. 65 Protobuf service definitions generate two golang interfaces representing the client and server sides of a service plus 66 some helper code. Here is a minimal example for the bank `cosmos.bank.Msg/Send` message type: 67 68 ```go 69 package bank 70 71 type MsgClient interface { 72 Send(context.Context, *MsgSend, opts ...grpc.CallOption) (*MsgSendResponse, error) 73 } 74 75 type MsgServer interface { 76 Send(context.Context, *MsgSend) (*MsgSendResponse, error) 77 } 78 ``` 79 80 [ADR 021](./adr-021-protobuf-query-encoding.md) and [ADR 31](./adr-031-msg-service.md) specifies how modules can implement the generated `QueryServer` 81 and `MsgServer` interfaces as replacements for the legacy queriers and `Msg` handlers respectively. 82 83 In this ADR we explain how modules can make queries and send `Msg`s to other modules using the generated `QueryClient` 84 and `MsgClient` interfaces and propose this mechanism as a replacement for the existing `Keeper` paradigm. To be clear, 85 this ADR does not necessitate the creation of new protobuf definitions or services. Rather, it leverages the same proto 86 based service interfaces already used by clients for inter-module communication. 87 88 Using this `QueryClient`/`MsgClient` approach has the following key benefits over exposing keepers to external modules: 89 90 1. Protobuf types are checked for breaking changes using [buf](https://buf.build/docs/breaking-overview) and because of 91 the way protobuf is designed this will give us strong backwards compatibility guarantees while allowing for forward 92 evolution. 93 2. The separation between the client and server interfaces will allow us to insert permission checking code in between 94 the two which checks if one module is authorized to send the specified `Msg` to the other module providing a proper 95 object capability system (see below). 96 3. The router for inter-module communication gives us a convenient place to handle rollback of transactions, 97 enabling atomicy of operations ([currently a problem](https://github.com/cosmos/cosmos-sdk/issues/8030)). Any failure within a module-to-module call would result in a failure of the entire 98 transaction 99 100 This mechanism has the added benefits of: 101 102 * reducing boilerplate through code generation, and 103 * allowing for modules in other languages either via a VM like CosmWasm or sub-processes using gRPC 104 105 ### Inter-module Communication 106 107 To use the `Client` generated by the protobuf compiler we need a `grpc.ClientConn` [interface](https://github.com/grpc/grpc-go/blob/v1.49.x/clientconn.go#L441-L450) 108 implementation. For this we introduce 109 a new type, `ModuleKey`, which implements the `grpc.ClientConn` interface. `ModuleKey` can be thought of as the "private 110 key" corresponding to a module account, where authentication is provided through use of a special `Invoker()` function, 111 described in more detail below. 112 113 Blockchain users (external clients) use their account's private key to sign transactions containing `Msg`s where they are listed as signers (each 114 message specifies required signers with `Msg.GetSigner`). The authentication checks is performed by `AnteHandler`. 115 116 Here, we extend this process, by allowing modules to be identified in `Msg.GetSigners`. When a module wants to trigger the execution a `Msg` in another module, 117 its `ModuleKey` acts as the sender (through the `ClientConn` interface we describe below) and is set as a sole "signer". It's worth to note 118 that we don't use any cryptographic signature in this case. 119 For example, module `A` could use its `A.ModuleKey` to create `MsgSend` object for `/cosmos.bank.Msg/Send` transaction. `MsgSend` validation 120 will assure that the `from` account (`A.ModuleKey` in this case) is the signer. 121 122 Here's an example of a hypothetical module `foo` interacting with `x/bank`: 123 124 ```go 125 package foo 126 127 128 type FooMsgServer { 129 // ... 130 131 bankQuery bank.QueryClient 132 bankMsg bank.MsgClient 133 } 134 135 func NewFooMsgServer(moduleKey RootModuleKey, ...) FooMsgServer { 136 // ... 137 138 return FooMsgServer { 139 // ... 140 modouleKey: moduleKey, 141 bankQuery: bank.NewQueryClient(moduleKey), 142 bankMsg: bank.NewMsgClient(moduleKey), 143 } 144 } 145 146 func (foo *FooMsgServer) Bar(ctx context.Context, req *MsgBarRequest) (*MsgBarResponse, error) { 147 balance, err := foo.bankQuery.Balance(&bank.QueryBalanceRequest{Address: fooMsgServer.moduleKey.Address(), Denom: "foo"}) 148 149 ... 150 151 res, err := foo.bankMsg.Send(ctx, &bank.MsgSendRequest{FromAddress: fooMsgServer.moduleKey.Address(), ...}) 152 153 ... 154 } 155 ``` 156 157 This design is also intended to be extensible to cover use cases of more fine grained permissioning like minting by 158 denom prefix being restricted to certain modules (as discussed in 159 [#7459](https://github.com/cosmos/cosmos-sdk/pull/7459#discussion_r529545528)). 160 161 ### `ModuleKey`s and `ModuleID`s 162 163 A `ModuleKey` can be thought of as a "private key" for a module account and a `ModuleID` can be thought of as the 164 corresponding "public key". From the [ADR 028](./adr-028-public-key-addresses.md), modules can have both a root module account and any number of sub-accounts 165 or derived accounts that can be used for different pools (ex. staking pools) or managed accounts (ex. group 166 accounts). We can also think of module sub-accounts as similar to derived keys - there is a root key and then some 167 derivation path. `ModuleID` is a simple struct which contains the module name and optional "derivation" path, 168 and forms its address based on the `AddressHash` method from [the ADR-028](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-028-public-key-addresses.md): 169 170 ```go 171 type ModuleID struct { 172 ModuleName string 173 Path []byte 174 } 175 176 func (key ModuleID) Address() []byte { 177 return AddressHash(key.ModuleName, key.Path) 178 } 179 ``` 180 181 In addition to being able to generate a `ModuleID` and address, a `ModuleKey` contains a special function called 182 `Invoker` which is the key to safe inter-module access. The `Invoker` creates an `InvokeFn` closure which is used as an `Invoke` method in 183 the `grpc.ClientConn` interface and under the hood is able to route messages to the appropriate `Msg` and `Query` handlers 184 performing appropriate security checks on `Msg`s. This allows for even safer inter-module access than keeper's whose 185 private member variables could be manipulated through reflection. Golang does not support reflection on a function 186 closure's captured variables and direct manipulation of memory would be needed for a truly malicious module to bypass 187 the `ModuleKey` security. 188 189 The two `ModuleKey` types are `RootModuleKey` and `DerivedModuleKey`: 190 191 ```go 192 type Invoker func(callInfo CallInfo) func(ctx context.Context, request, response interface{}, opts ...interface{}) error 193 194 type CallInfo { 195 Method string 196 Caller ModuleID 197 } 198 199 type RootModuleKey struct { 200 moduleName string 201 invoker Invoker 202 } 203 204 func (rm RootModuleKey) Derive(path []byte) DerivedModuleKey { /* ... */} 205 206 type DerivedModuleKey struct { 207 moduleName string 208 path []byte 209 invoker Invoker 210 } 211 ``` 212 213 A module can get access to a `DerivedModuleKey`, using the `Derive(path []byte)` method on `RootModuleKey` and then 214 would use this key to authenticate `Msg`s from a sub-account. Ex: 215 216 ```go 217 package foo 218 219 func (fooMsgServer *MsgServer) Bar(ctx context.Context, req *MsgBar) (*MsgBarResponse, error) { 220 derivedKey := fooMsgServer.moduleKey.Derive(req.SomePath) 221 bankMsgClient := bank.NewMsgClient(derivedKey) 222 res, err := bankMsgClient.Balance(ctx, &bank.MsgSend{FromAddress: derivedKey.Address(), ...}) 223 ... 224 } 225 ``` 226 227 In this way, a module can gain permissioned access to a root account and any number of sub-accounts and send 228 authenticated `Msg`s from these accounts. The `Invoker` `callInfo.Caller` parameter is used under the hood to 229 distinguish between different module accounts, but either way the function returned by `Invoker` only allows `Msg`s 230 from either the root or a derived module account to pass through. 231 232 Note that `Invoker` itself returns a function closure based on the `CallInfo` passed in. This will allow client implementations 233 in the future that cache the invoke function for each method type avoiding the overhead of hash table lookup. 234 This would reduce the performance overhead of this inter-module communication method to the bare minimum required for 235 checking permissions. 236 237 To re-iterate, the closure only allows access to authorized calls. There is no access to anything else regardless of any 238 name impersonation. 239 240 Below is a rough sketch of the implementation of `grpc.ClientConn.Invoke` for `RootModuleKey`: 241 242 ```go 243 func (key RootModuleKey) Invoke(ctx context.Context, method string, args, reply interface{}, opts ...grpc.CallOption) error { 244 f := key.invoker(CallInfo {Method: method, Caller: ModuleID {ModuleName: key.moduleName}}) 245 return f(ctx, args, reply) 246 } 247 ``` 248 249 ### `AppModule` Wiring and Requirements 250 251 In [ADR 031](./adr-031-msg-service.md), the `AppModule.RegisterService(Configurator)` method was introduced. To support 252 inter-module communication, we extend the `Configurator` interface to pass in the `ModuleKey` and to allow modules to 253 specify their dependencies on other modules using `RequireServer()`: 254 255 ```go 256 type Configurator interface { 257 MsgServer() grpc.Server 258 QueryServer() grpc.Server 259 260 ModuleKey() ModuleKey 261 RequireServer(msgServer interface{}) 262 } 263 ``` 264 265 The `ModuleKey` is passed to modules in the `RegisterService` method itself so that `RegisterServices` serves as a single 266 entry point for configuring module services. This is intended to also have the side-effect of greatly reducing boilerplate in 267 `app.go`. For now, `ModuleKey`s will be created based on `AppModuleBasic.Name()`, but a more flexible system may be 268 introduced in the future. The `ModuleManager` will handle creation of module accounts behind the scenes. 269 270 Because modules do not get direct access to each other anymore, modules may have unfulfilled dependencies. To make sure 271 that module dependencies are resolved at startup, the `Configurator.RequireServer` method should be added. The `ModuleManager` 272 will make sure that all dependencies declared with `RequireServer` can be resolved before the app starts. An example 273 module `foo` could declare it's dependency on `x/bank` like this: 274 275 ```go 276 package foo 277 278 func (am AppModule) RegisterServices(cfg Configurator) { 279 cfg.RequireServer((*bank.QueryServer)(nil)) 280 cfg.RequireServer((*bank.MsgServer)(nil)) 281 } 282 ``` 283 284 ### Security Considerations 285 286 In addition to checking for `ModuleKey` permissions, a few additional security precautions will need to be taken by 287 the underlying router infrastructure. 288 289 #### Recursion and Re-entry 290 291 Recursive or re-entrant method invocations pose a potential security threat. This can be a problem if Module A 292 calls Module B and Module B calls module A again in the same call. 293 294 One basic way for the router system to deal with this is to maintain a call stack which prevents a module from 295 being referenced more than once in the call stack so that there is no re-entry. A `map[string]interface{}` table 296 in the router could be used to perform this security check. 297 298 #### Queries 299 300 Queries in Cosmos SDK are generally un-permissioned so allowing one module to query another module should not pose 301 any major security threats assuming basic precautions are taken. The basic precaution that the router system will 302 need to take is making sure that the `sdk.Context` passed to query methods does not allow writing to the store. This 303 can be done for now with a `CacheMultiStore` as is currently done for `BaseApp` queries. 304 305 ### Internal Methods 306 307 In many cases, we may wish for modules to call methods on other modules which are not exposed to clients at all. For this 308 purpose, we add the `InternalServer` method to `Configurator`: 309 310 ```go 311 type Configurator interface { 312 MsgServer() grpc.Server 313 QueryServer() grpc.Server 314 InternalServer() grpc.Server 315 } 316 ``` 317 318 As an example, x/slashing's Slash must call x/staking's Slash, but we don't want to expose x/staking's Slash to end users 319 and clients. 320 321 Internal protobuf services will be defined in a corresponding `internal.proto` file in the given module's 322 proto package. 323 324 Services registered against `InternalServer` will be callable from other modules but not by external clients. 325 326 An alternative solution to internal-only methods could involve hooks / plugins as discussed [here](https://github.com/cosmos/cosmos-sdk/pull/7459#issuecomment-733807753). 327 A more detailed evaluation of a hooks / plugin system will be addressed later in follow-ups to this ADR or as a separate 328 ADR. 329 330 ### Authorization 331 332 By default, the inter-module router requires that messages are sent by the first signer returned by `GetSigners`. The 333 inter-module router should also accept authorization middleware such as that provided by [ADR 030](https://github.com/cosmos/cosmos-sdk/blob/main/docs/architecture/adr-030-authz-module.md). 334 This middleware will allow accounts to otherwise specific module accounts to perform actions on their behalf. 335 Authorization middleware should take into account the need to grant certain modules effectively "admin" privileges to 336 other modules. This will be addressed in separate ADRs or updates to this ADR. 337 338 ### Future Work 339 340 Other future improvements may include: 341 342 * custom code generation that: 343 * simplifies interfaces (ex. generates code with `sdk.Context` instead of `context.Context`) 344 * optimizes inter-module calls - for instance caching resolved methods after first invocation 345 * combining `StoreKey`s and `ModuleKey`s into a single interface so that modules have a single OCAPs handle 346 * code generation which makes inter-module communication more performant 347 * decoupling `ModuleKey` creation from `AppModuleBasic.Name()` so that app's can override root module account names 348 * inter-module hooks and plugins 349 350 ## Alternatives 351 352 ### MsgServices vs `x/capability` 353 354 The `x/capability` module does provide a proper object-capability implementation that can be used by any module in the 355 Cosmos SDK and could even be used for inter-module OCAPs as described in [\#5931](https://github.com/cosmos/cosmos-sdk/issues/5931). 356 357 The advantages of the approach described in this ADR are mostly around how it integrates with other parts of the Cosmos SDK, 358 specifically: 359 360 * protobuf so that: 361 * code generation of interfaces can be leveraged for a better dev UX 362 * module interfaces are versioned and checked for breakage using [buf](https://docs.buf.build/breaking-overview) 363 * sub-module accounts as per ADR 028 364 * the general `Msg` passing paradigm and the way signers are specified by `GetSigners` 365 366 Also, this is a complete replacement for keepers and could be applied to _all_ inter-module communication whereas the 367 `x/capability` approach in #5931 would need to be applied method by method. 368 369 ## Consequences 370 371 ### Backwards Compatibility 372 373 This ADR is intended to provide a pathway to a scenario where there is greater long term compatibility between modules. 374 In the short-term, this will likely result in breaking certain `Keeper` interfaces which are too permissive and/or 375 replacing `Keeper` interfaces altogether. 376 377 ### Positive 378 379 * an alternative to keepers which can more easily lead to stable inter-module interfaces 380 * proper inter-module OCAPs 381 * improved module developer DevX, as commented on by several particpants on 382 [Architecture Review Call, Dec 3](https://hackmd.io/E0wxxOvRQ5qVmTf6N_k84Q) 383 * lays the groundwork for what can be a greatly simplified `app.go` 384 * router can be setup to enforce atomic transactions for module-to-module calls 385 386 ### Negative 387 388 * modules which adopt this will need significant refactoring 389 390 ### Neutral 391 392 ## Test Cases [optional] 393 394 ## References 395 396 * [ADR 021](./adr-021-protobuf-query-encoding.md) 397 * [ADR 031](./adr-031-msg-service.md) 398 * [ADR 028](./adr-028-public-key-addresses.md) 399 * [ADR 030 draft](https://github.com/cosmos/cosmos-sdk/pull/7105) 400 * [Object-Capability Model](https://docs.network.com/main/core/ocap)