Skip to content

Commit

Permalink
doc: Updates related to ServiceMsg, sdk.Msg and Msg service (#9294)
Browse files Browse the repository at this point in the history
* doc: Updates related to ServiceMsg, sdk.Msg and Msg service

* Apply suggestions from code review

Co-authored-by: Ryan Christoffersen <12519942+ryanchrypto@users.noreply.github.com>

* remove one more ServiceMsg

* Use service method rathr than service RPC

Co-authored-by: Ryan Christoffersen <12519942+ryanchrypto@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored May 12, 2021
1 parent d7dd1d7 commit f2cea6a
Show file tree
Hide file tree
Showing 15 changed files with 117 additions and 111 deletions.
12 changes: 7 additions & 5 deletions docs/basics/app-anatomy.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,22 +139,24 @@ Modules must implement [interfaces](../building-modules/module-manager.md#applic

### `Msg` Services

Each module defines two [Protobuf services](https://developers.google.com/protocol-buffers/docs/proto#services): one `Msg` service to handle messages, and one gRPC `Query` service to handle queries. If we consider the module as a state-machine, then a `Msg` is a state transition. A `Msg` service is a Protobuf service defining all possible `Msg`s a module exposes. Note that `Msg`s are bundled in [`transactions`](../core/transactions.md), and each transaction contains one or multiple `messages`.
Each module defines two [Protobuf services](https://developers.google.com/protocol-buffers/docs/proto#services): one `Msg` service to handle messages, and one gRPC `Query` service to handle queries. If we consider the module as a state-machine, then a `Msg` service is a set of state transition RPC methods.
Each Protobuf `Msg` service method is 1:1 related to a Protobuf request type, which must implement `sdk.Msg` interface.
Note that `sdk.Msg`s are bundled in [transactions](../core/transactions.md), and each transaction contains one or multiple messages.

When a valid block of transactions is received by the full-node, Tendermint relays each one to the application via [`DeliverTx`](https://tendermint.com/docs/app-dev/abci-spec.html#delivertx). Then, the application handles the transaction:

1. Upon receiving the transaction, the application first unmarshalls it from `[]bytes`.
2. Then, it verifies a few things about the transaction like [fee payment and signatures](#gas-fees.md#antehandler) before extracting the `Msg`(s) contained in the transaction.
3. `Msg`s are encoded as Protobuf [`Any`s](#register-codec) via the `sdk.ServiceMsg` struct. By analyzing each `Any`'s `type_url`, baseapp's `msgServiceRouter` routes the `Msg` to the corresponding module's `Msg` service.
3. `sdk.Msg`s are encoded using Protobuf [`Any`s](#register-codec). By analyzing each `Any`'s `type_url`, baseapp's `msgServiceRouter` routes the `sdk.Msg` to the corresponding module's `Msg` service.
4. If the message is successfully processed, the state is updated.

For a more detailed look at a transaction lifecycle, click [here](./tx-lifecycle.md).
For a more details look at a transaction [lifecycle](./tx-lifecycle.md).

Module developers create custom `Msg`s when they build their own module. The general practice is to define all `Msg`s in a Protobuf service called `service Msg {}`, and define each `Msg` as a Protobuf service method, using the `rpc` keyword. These definitions usually reside in a `tx.proto` file. For example, the `x/bank` module defines two `Msg`s to allows users to transfer tokens:
Module developers create custom `Msg` services when they build their own module. The general practice is to define the `Msg` Protobuf service in a `tx.proto` file. For example, the `x/bank` module defines a service with two methods to transfer tokens:

+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0-rc3/proto/cosmos/bank/v1beta1/tx.proto#L10-L17

These two `Msg`s are processed by the `Msg` service of the `x/bank` module, which ultimately calls the `keeper` of the `x/auth` module in order to update the state.
Service methods use `keeper` in order to update the module state.

Each module should also implement the `RegisterServices` method as part of the [`AppModule` interface](#application-module-interface). This method should call the `RegisterMsgServer` function provided by the generated Protobuf code.

Expand Down
2 changes: 1 addition & 1 deletion docs/basics/gas-fees.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ ctx.BlockGasMeter().ConsumeGas(

## AnteHandler

The `AnteHandler` is run for every transaction during `CheckTx` and `DeliverTx`, before the `Msg` service of each `Msg` in the transaction. `AnteHandler`s have the following signature:
The `AnteHandler` is run for every transaction during `CheckTx` and `DeliverTx`, before a Protobuf `Msg` service method for each `sdk.Msg` in the transaction. `AnteHandler`s have the following signature:

```go
// AnteHandler authenticates transactions, before their internal messages are handled.
Expand Down
11 changes: 5 additions & 6 deletions docs/basics/tx-lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ When `Tx` is received by the application from the underlying consensus engine (e

### ValidateBasic

[`Msg`s](../core/transactions.md#messages) are extracted from `Tx` and `ValidateBasic`, a method of the `Msg` interface implemented by the module developer, is run for each one. It should include basic **stateless** sanity checks. For example, if the message is to send coins from one address to another, `ValidateBasic` likely checks for nonempty addresses and a nonnegative coin amount, but does not require knowledge of state such as account balance of an address.
[`sdk.Msg`s](../core/transactions.md#messages) are extracted from `Tx`, and `ValidateBasic`, a method of the `sdk.Msg` interface implemented by the module developer, is run for each one. `ValidateBasic` should include basic **stateless** sanity checks. For example, if the message is to send coins from one address to another, `ValidateBasic` likely checks for nonempty addresses and a nonnegative coin amount, but does not require knowledge of state such as the account balance of an address.

### AnteHandler

Expand Down Expand Up @@ -199,12 +199,11 @@ Instead of using their `checkState`, full-nodes use `deliverState`:
- **`MsgServiceRouter`:** While `CheckTx` would have exited, `DeliverTx` continues to run
[`runMsgs`](../core/baseapp.md#runtx-and-runmsgs) to fully execute each `Msg` within the transaction.
Since the transaction may have messages from different modules, `BaseApp` needs to know which module
to find the appropriate handler. This is achieved using `BaseApp`'s `MsgServiceRouter` so that it can be processed by the module's [`Msg` service](../building-modules/msg-services.md).
For legacy `Msg` routing, the `Route` function is called via the [module manager](../building-modules/module-manager.md) to retrieve the route name and find the legacy [`Handler`](../building-modules/msg-services.md#handler-type) within the module.
to find the appropriate handler. This is achieved using `BaseApp`'s `MsgServiceRouter` so that it can be processed by the module's Protobuf [`Msg` service](../building-modules/msg-services.md).
For `LegacyMsg` routing, the `Route` function is called via the [module manager](../building-modules/module-manager.md) to retrieve the route name and find the legacy [`Handler`](../building-modules/msg-services.md#handler-type) within the module.

- **`Msg` service:** The `Msg` service, a step up from `AnteHandler`, is responsible for executing each
message in the `Tx` and causes state transitions to persist in `deliverTxState`. It is defined
within a module `Msg` protobuf service and writes to the appropriate stores within the module.
- **`Msg` service:** a Protobuf `Msg` service, a step up from `AnteHandler`, is responsible for executing each
message in the `Tx` and causes state transitions to persist in `deliverTxState`.

- **Gas:** While a `Tx` is being delivered, a `GasMeter` is used to keep track of how much
gas is being used; if execution completes, `GasUsed` is set and returned in the
Expand Down
26 changes: 13 additions & 13 deletions docs/building-modules/intro.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!--
order: 1
order: 1
-->

# Introduction to SDK Modules
Expand All @@ -13,18 +13,18 @@ Modules define most of the logic of SDK applications. Developers compose modules

## Role of Modules in an SDK Application

The Cosmos SDK can be thought of as the Ruby-on-Rails of blockchain development. It comes with a core that provides the basic functionalities every blockchain application needs, like a [boilerplate implementation of the ABCI](../core/baseapp.md) to communicate with the underlying consensus engine, a [`multistore`](../core/store.md#multistore) to persist state, a [server](../core/node.md) to form a full-node and [interfaces](./module-interfaces.md) to handle queries.
The Cosmos SDK can be thought of as the Ruby-on-Rails of blockchain development. It comes with a core that provides the basic functionalities every blockchain application needs, like a [boilerplate implementation of the ABCI](../core/baseapp.md) to communicate with the underlying consensus engine, a [`multistore`](../core/store.md#multistore) to persist state, a [server](../core/node.md) to form a full-node and [interfaces](./module-interfaces.md) to handle queries.

On top of this core, the Cosmos SDK enables developers to build modules that implement the business logic of their application. In other words, SDK modules implement the bulk of the logic of applications, while the core does the wiring and enables modules to be composed together. The end goal is to build a robust ecosystem of open-source SDK modules, making it increasingly easier to build complex blockchain applications.
On top of this core, the Cosmos SDK enables developers to build modules that implement the business logic of their application. In other words, SDK modules implement the bulk of the logic of applications, while the core does the wiring and enables modules to be composed together. The end goal is to build a robust ecosystem of open-source SDK modules, making it increasingly easier to build complex blockchain applications.

SDK modules can be seen as little state-machines within the state-machine. They generally define a subset of the state using one or more `KVStore`s in the [main multistore](../core/store.md), as well as a subset of [message types](./messages-and-queries.md#messages). These messages are routed by one of the main components of SDK core, [`BaseApp`](../core/baseapp.md), to the [`Msg` service](./msg-services.md) of the module that defines them.
SDK modules can be seen as little state-machines within the state-machine. They generally define a subset of the state using one or more `KVStore`s in the [main multistore](../core/store.md), as well as a subset of [message types](./messages-and-queries.md#messages). These messages are routed by one of the main components of SDK core, [`BaseApp`](../core/baseapp.md), to a module Protobuf [`Msg` service](./msg-services.md) that defines them.

```
+
|
| Transaction relayed from the full-node's consensus engine
| Transaction relayed from the full-node's consensus engine
| to the node's application via DeliverTx
|
|
|
|
+---------------------v--------------------------+
Expand Down Expand Up @@ -64,28 +64,28 @@ SDK modules can be seen as little state-machines within the state-machine. They
v
```

As a result of this architecture, building an SDK application usually revolves around writing modules to implement the specialized logic of the application, and composing them with existing modules to complete the application. Developers will generally work on modules that implement logic needed for their specific use case that do not exist yet, and will use existing modules for more generic functionalities like staking, accounts or token management.
As a result of this architecture, building an SDK application usually revolves around writing modules to implement the specialized logic of the application, and composing them with existing modules to complete the application. Developers will generally work on modules that implement logic needed for their specific use case that do not exist yet, and will use existing modules for more generic functionalities like staking, accounts or token management.

## How to Approach Building Modules as a Developer

While there are no definitive guidelines for writing modules, here are some important design principles developers should keep in mind when building them:

- **Composability**: SDK applications are almost always composed of multiple modules. This means developers need to carefully consider the integration of their module not only with the core of the Cosmos SDK, but also with other modules. The former is achieved by following standard design patterns outlined [here](#main-components-of-sdk-modules), while the latter is achieved by properly exposing the store(s) of the module via the [`keeper`](./keeper.md).
- **Specialization**: A direct consequence of the **composability** feature is that modules should be **specialized**. Developers should carefully establish the scope of their module and not batch multiple functionalities into the same module. This separation of concerns enables modules to be re-used in other projects and improves the upgradability of the application. **Specialization** also plays an important role in the [object-capabilities model](../core/ocap.md) of the Cosmos SDK.
- **Capabilities**: Most modules need to read and/or write to the store(s) of other modules. However, in an open-source environment, it is possible for some modules to be malicious. That is why module developers need to carefully think not only about how their module interacts with other modules, but also about how to give access to the module's store(s). The Cosmos SDK takes a capabilities-oriented approach to inter-module security. This means that each store defined by a module is accessed by a `key`, which is held by the module's [`keeper`](./keeper.md). This `keeper` defines how to access the store(s) and under what conditions. Access to the module's store(s) is done by passing a reference to the module's `keeper`.
- **Composability**: SDK applications are almost always composed of multiple modules. This means developers need to carefully consider the integration of their module not only with the core of the Cosmos SDK, but also with other modules. The former is achieved by following standard design patterns outlined [here](#main-components-of-sdk-modules), while the latter is achieved by properly exposing the store(s) of the module via the [`keeper`](./keeper.md).
- **Specialization**: A direct consequence of the **composability** feature is that modules should be **specialized**. Developers should carefully establish the scope of their module and not batch multiple functionalities into the same module. This separation of concerns enables modules to be re-used in other projects and improves the upgradability of the application. **Specialization** also plays an important role in the [object-capabilities model](../core/ocap.md) of the Cosmos SDK.
- **Capabilities**: Most modules need to read and/or write to the store(s) of other modules. However, in an open-source environment, it is possible for some modules to be malicious. That is why module developers need to carefully think not only about how their module interacts with other modules, but also about how to give access to the module's store(s). The Cosmos SDK takes a capabilities-oriented approach to inter-module security. This means that each store defined by a module is accessed by a `key`, which is held by the module's [`keeper`](./keeper.md). This `keeper` defines how to access the store(s) and under what conditions. Access to the module's store(s) is done by passing a reference to the module's `keeper`.

## Main Components of SDK Modules

Modules are by convention defined in the `./x/` subfolder (e.g. the `bank` module will be defined in the `./x/bank` folder). They generally share the same core components:

- A [`keeper`](./keeper.md), used to access the module's store(s) and update the state.
- A [`Msg` service](./messages-and-queries.md#messages) used to process messages when they are routed to the module by [`BaseApp`](../core/baseapp.md#message-routing) and trigger state-transitions.
- A [`Msg` service](./messages-and-queries.md#messages), used to process messages when they are routed to the module by [`BaseApp`](../core/baseapp.md#message-routing) and trigger state-transitions.
- A [query service](./query-services.md), used to process user queries when they are routed to the module by [`BaseApp`](../core/baseapp.md#query-routing).
- Interfaces, for end users to query the subset of the state defined by the module and create `message`s of the custom types defined in the module.

In addition to these components, modules implement the `AppModule` interface in order to be managed by the [`module manager`](./module-manager.md).
In addition to these components, modules implement the `AppModule` interface in order to be managed by the [`module manager`](./module-manager.md).

Please refer to the [structure document](./structure.md) to learn about the recommended structure of a module's directory.
Please refer to the [structure document](./structure.md) to learn about the recommended structure of a module's directory.

## Next {hide}

Expand Down
Loading

0 comments on commit f2cea6a

Please sign in to comment.