# Messages

synopsis

In this section, you will take a closer look at messages, Msg. It is recommended to take a look at the following previous sections to better understand messages:

At the end of the section, you can find a code example illustrating message creation and the inclusion of messages in transactions for your checkers blockchain.

Understanding 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 last 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 GetSigners.

The Cosmos SDK currently allows signing transactions with either SIGN_MODE_DIRECT or SIGN_MODE_LEGACY_AMINO_JSON methods.

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 the Tendermint consensus engine. As you might recall, Tendermint 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’s MsgServiceRouter. BaseApp decodes each message contained in the transaction. Each module has its own MsgService that processes each received message.

# MsgService

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's an example MsgService from the bank module (opens new window):

Example MsgService:

Copy // Msg defines the bank Msg service. service Msg { // Send defines a method for sending coins from one account to another account. rpc Send(MsgSend) returns (MsgSendResponse); // MultiSend defines a method for sending coins from some accounts to other accounts. rpc MultiSend(MsgMultiSend) returns (MsgMultiSendResponse); }

In the above example, we can see that:

  • Each Msg service method has exactly one argument, such as MsgSend, which must implement the sdk.Msg interface and a Protobuf response.
  • The standard naming convention is to call the RPC argument Msg<service-rpc-name> and the RPC response Msg<service-rpc-name>Response.

# Client and server code generation

The Cosmos SDK uses Protobuf definitions to generate client and server code:

If you want to dive deeper when it comes to messages, the Msg service and modules take a look at:

# Next up

Have a look at the code example below to get a better sense of how the above translates in development. If you feel ready to dive into the next main concept of the Cosmos SDK, you can head straight to the next section) to learn more about modules.

In the previous 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 will need to conform to the SDK's way of handling Tx, which means creating messages that are then included in a transaction.

# 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:

  1. In the former Play transaction, your four integers need to move from the transaction to an sdk.Msg, wrapped in said transaction. Four flat int values are no longer sufficient as you need to follow the sdk.Msg interface, identify the game for which a move is meant, and distinguish a move message from other message types.
  2. You need to add a message type for creating a new game. When this is done, a player can create a new game and this new game will mention the other players. A generated ID identifies this newly created game and is returned to the message creator.
  3. It would be a welcomed idea for the other person to be able to reject the challenge. That would have the added benefit of clearing the state of stale un-started games.

# How to proceed

Now, let's have a closer look at the messages around the game creation.

  1. The message itself is structured like this:

    Copy type MsgCreateGame struct { Creator string Red string Black string }

    Where Creator contains the address of the message signer.

  2. The corresponding response message would then be:

    Copy type MsgCreateGameResponse struct { IdValue string }

With the messages defined, you need to declare how the message should be handled. This involves:

  1. Describing how the messages are serialized.
  2. Writing the code that handles the message and places the new game in the storage.
  3. Putting hooks and callbacks at the right places in the general message handling.

Starport can help you create all that plus the MsgCreateGame and MsgCreateGameResponse objects with this command:

Copy $ starport scaffold message createGame red black --module checkers --response idValue

Starport creates a whole lot of other files, see My Own Chain for details and make additions to existing files.

# A sample of things Starport did for you

Starport significantly reduces the amount of work a developer has to do to build an application with the Cosmos SDK. Among others, it assists with:

  1. Getting the signer, the Creator, of your message:

    Copy func (msg *MsgCreateGame) GetSigners() []sdk.AccAddress { creator, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { panic(err) } return []sdk.AccAddress{creator} }

    Where GetSigners is a requirement of sdk.Msg (opens new window).

  2. Making sure the message's bytes to sign are deterministic:

    Copy func (msg *MsgCreateGame) GetSignBytes() []byte { bz := ModuleCdc.MustMarshalJSON(msg) return sdk.MustSortJSON(bz) }
  3. Adding a callback for your new message type in your module's message handler x/checkers/handler.go:

    Copy ... switch msg := msg.(type) { ... case *types.MsgCreateGame: res, err := msgServer.CreateGame(sdk.WrapSDKContext(ctx), msg) return sdk.WrapServiceResult(ctx, res, err) ... }
  4. 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:

    Copy func (k msgServer) CreateGame(goCtx context.Context, msg *types.MsgCreateGame) (*types.MsgCreateGameResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // TODO: Handling the message _ = ctx return &types.MsgCreateGameResponse{}, nil }

# What is left to do?

Your work is mostly done. You will want to create the specific game creation code to replace // TODO: Handling the message. For this, you will need to:

  1. Decide on how to create a new and unique game ID: newIndex.

    For more details and to avoid diving too deep in this section, see My Own Chain.

  2. Extract and verify addresses, such as:

    Copy red, err := sdk.AccAddressFromBech32(msg.Red) if err != nil { return nil, errors.New("invalid address for red") }
  3. Create a game object with all required parameters - see the modules section) for the declaration of this object:

    Copy storedGame := { Creator: creator, Index: newIndex, Game: rules.New().String(), Red: red, Black: black, }
  4. Send it to storage - see the modules section) for the declaration of this function:

    Copy k.Keeper.SetStoredGame(ctx, storedGame)
  5. And finally, return the expected message:

    Copy return &types.MsgCreateGameResponse{ IdValue: newIndex, }, nil

Not to forget:

  • If you encounter an internal error, you should panic("This situation should not happen").
  • If you encounter a user or regular error, like not having enough funds, you should return a regular error.

# The other messages

You can also implement other messages.

  1. The play message, which means implicitly accepting the challenge when playing for the first time. If you create it with Starport, use:

    Copy $ starport scaffold message playMove idValue fromX:uint fromY:uint toX:uint toY:uint --module checkers --response idValue

    Which generates, among others, the object files, callbacks, and a new file for you to write your code:

    Copy func (k msgServer) PlayMove(goCtx context.Context, msg *types.MsgPlayMove) (*types.MsgPlayMoveResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // TODO: Handling the message _ = ctx return &types.MsgPlayMoveResponse{}, nil }
  2. The reject message, which should be valid only if the player never played any moves in this game.

    Copy $ starport scaffold message rejectGame idValue --module checkers

    It generates, among others:

    Copy func (k msgServer) RejectGame(goCtx context.Context, msg *types.MsgRejectGame) (*types.MsgRejectGameResponse, error) { ctx := sdk.UnwrapSDKContext(goCtx) // TODO: Handling the message _ = ctx return &types.MsgRejectGameResponse{}, nil }

# Other considerations

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. This cancelation could be handled automatically in ABCI's EndBlock, or rather its equivalent in the Cosmos SDK, without any of the players having to trigger the cancelation.
  • Keeping an index as a First-In-First-Out (FIFO) list or a list of un-started games ordered by their cancelation 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.
  • 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.

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: what about one day?

Of note is that there are no open challenges, meaning a player cannot create a game where the second player is unknown until someone steps in. So player matching is left outside of the blockchain. It is left to the enterprising student to 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 head straight to the main exercise in My Own Chain.