In this section, you will take a closer look at messages,
Msg. At the end of the section, you can find a code example that illustrates message creation and the inclusion of messages in transactions for your checkers blockchain.
Msg will help you prepare for the next section, on modules in the Cosmos SDK, as messages are a primary object handled by modules.
Messages are one of two primary objects handled by a module in the Cosmos SDK. The other primary object handled by modules is queries. While messages inform the state and have the potential to alter it, queries inspect the module state and are always read-only.
In the Cosmos SDK, a transaction contains one or more messages. The module processes the messages after the transaction is included in a block by the consensus layer.
Remember from the previous section on transactions that transactions must be signed before a validator includes them in a block. Every message in a transaction must be signed by the addresses as specified by
The Cosmos SDK currently allows signing transactions with either
When an account signs a message it signs an array of bytes. This array of bytes is the outcome of serializing the message. For the signature to be verifiable at a later date, this conversion needs to be deterministic. For this reason, you define a canonical bytes-representation of the message, typically with the parameters ordered alphabetically.
# Messages and the transaction lifecycle
Transactions containing one or more valid messages are serialized and confirmed by CometBFT. As you might recall, CometBFT is agnostic to the transaction interpretation and has absolute finality. When a transaction is included in a block, it is confirmed and finalized with no possibility of chain re-organization or cancellation.
The confirmed transaction is relayed to the Cosmos SDK application for interpretation. Each message is routed to the appropriate module via
BaseApp decodes each message contained in the transaction. Each module has its own
MsgService that processes each received message.
Although it is technically feasible to proceed to create a novel
MsgService, the recommended approach is to define a Protobuf
Msg service. Each module has exactly one Protobuf
Msg service defined in
tx.proto and there is an RPC service method for each message type in the module. The Protobuf message service implicitly defines the interface layer of the state, mutating processes contained within the module.
How does all of this translate into code? Here is an example
MsgService from the
bank module (opens new window):
In this example:
Msgservice method has exactly one argument, such as
MsgSend, which must implement the
sdk.Msginterface and a Protobuf response.
- The standard naming convention is to call the RPC argument
Msg<service-rpc-name>and the RPC response
# Client and server code generation
The Cosmos SDK uses Protobuf definitions to generate client and server code:
MsgServerinterface defines the server API for the
Msgservice. Its implementation is described in the
Msgservices documentation (opens new window).
- Structures are generated for all RPC requests and response types.
# Code example
In the previous design exercise's code examples, the ABCI application was aware of a single transaction type: that of a checkers move with four
int values. With multiple games, this is no longer sufficient. Additionally, you need to conform to the SDK's way of handling
Tx, which means creating messages that are then included in a transaction.
If you want the guided coding exercise instead of design and implementation considerations, see the links at the bottom of the page.
What you need
Begin by describing the messages you need for your checkers application to have a solid starting point before diving into the code:
- In the former Play transaction, your four integers need to move from the transaction to an
sdk.Msg, wrapped in said transaction. Four flat
intvalues are no longer sufficient, as you need to follow the
sdk.Msginterface, identify the game for which a move is meant, and distinguish a move message from other message types.
- You need to add a message type for creating a new game. When this is done, a player can create a new game which mentions other players. A generated (possibly) ID identifies this newly created game and is returned to the message creator.
- It would be a good feature for the other person to be able to reject the challenge. This would have the added benefit of clearing the state of stale, unstarted games.
How to proceed
Focus on the messages around the game creation. There is no single true way of deciding what goes into your messages. The following is one reasonable example.
The message itself is structured like this:
Creatorcontains the address of the message signer.
The corresponding response message would then be:
The idea here is that when the creator does not know the ID of the game that will be created, the ID needs to be returned.
With the messages defined, you need to declare how the message should be handled. This involves:
- Describing how the messages are serialized.
- Writing the code that handles the message and places the new game in the storage.
- Putting hooks and callbacks at the right places in the general message handling.
Thinking from design to implementation, Ignite CLI can help you create these elements, plus the
MsgCreateGameResponse objects, with this command:
Ignite CLI creates a variety of other files. See Run Your Own Cosmos Chain for details, and to make additions to existing files.
A sample of things Ignite CLI did for you
Ignite CLI significantly reduces the amount of work a developer has to do to build an application with the Cosmos SDK. Among others, it assists with:
Getting the signer, the
Creator, of your message:
GetSignersis a requirement of
sdk.Msg(opens new window).
Making sure the message's bytes to sign are deterministic:
Adding a callback for your new message type in your module's message handler
Creating an empty shell of a file (
x/checkers/keeper/msg_server_create_game.go) for you to include your code, and the response message:
Ignite CLI is opinionated in terms of which files it creates to separate which concerns. If you are not using it, you are free to create the files you want.
What is left to do?
Your work is mostly done. You want to create the specific game creation code to replace
// TODO: Handling the message. For this, you need to:
Decide how to create a new and unique game ID:
Extract and verify addresses, such as:
Create a game object with all required parameters - see the modules section for the declaration of this object:
Send the game object to storage - see the modules section for the declaration of this function:
And finally, return the expected message:
Remember, as a part of good design practice:
- If you encounter an internal error (one that denotes an error in logic or catastrophic failure), you should
panic("This situation should not happen").
- If you encounter a user or regular error (like a user not having enough funds), you should return a regular
The other messages
You can also implement other messages:
The play message, which means implicitly accepting the challenge when playing for the first time. If you create it with Ignite CLI, use:
This generates, among others, the object files, callbacks, and a new file for you to write your code:
To jump straight to the corresponding part of the coding exercise, head to Add a Way to Make a Move.
The reject message, which should be valid only if the player never played any moves in this game.
This generates, among others:
This message is not implemented in the coding exercise as it is not necessary if you implement an expiration.
What would happen if one of the two players has accepted the game by playing, but the other player has neither accepted nor rejected the game? You can address this scenario by:
- Having a timeout after which the game is canceled.
- Keeping an index as a First-In-First-Out (FIFO) list, or a list of unstarted games ordered by their cancellation time, so that this automatic trigger does not consume too many resources.
What would happen if a player stops taking turns? To ensure functionality for your checkers application, you can consider:
- Having a timeout after which the game is forfeited. You could also automatically charge the forgetful player, if and when you implement a wager system. For the guided coding exercise on this part, head straight to Keep an Up-To-Date Game Deadline.
- Keeping an index of games that could be forfeited. If both timeouts are the same, you can keep a single FIFO list of games so you can clear them from the top of the list as necessary. For the guided coding exercise on this part, head straight to Put Your Games in Order.
- Handling the cancelation in ABCI's
EndBlock(or rather its equivalent in the Cosmos SDK) without any of the players having to trigger the cancelation. For the guided coding exercise on this part, head straight to Auto-Expiring Games.
In general terms, you could add
timeout: Timestamp to your
StoredGame and update it every time something changes in the game. You can decide on a maximum delay, for example one day.
There are no open challenges, meaning a player cannot create a game where the second player is unknown until someone steps in. Therefore, player matching is left outside of the blockchain. The enterprising student can incorporate it inside the blockchain by changing the necessary models.
If you would like to get started on building your own checkers game, you can go straight to the main exercise in Run Your Own Cosmos Chain to start from scratch.
More specifically, you can jump to:
To summarize, this section has explored:
- Messages, one of two primary objects handled by a module in the Cosmos SDK, which inform the state and have the potential to alter it.
- How one or more messages form a transaction in the Cosmos SDK, and messages are only processed after a transaction is signed by a validator and included in a block by the consensus layer.
- An example of more complex message handling capabilities related to the checkers game blockchain.