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)