Skip to content

Latest commit

 

History

History
326 lines (235 loc) · 11 KB

create-message.md

File metadata and controls

326 lines (235 loc) · 11 KB
title order description tag
Message - Create a Message to Create a Game
6
You introduce the message to create a game
deep-dive

Message - Create a Message to Create a Game

Make sure you have everthing you need before proceeding:

In this section, you will:

  • Create a game Protobuf object.
  • Create a game Protobuf service interface.
  • Extend your unit tests.
  • Interact via the CLI.

Currently:

  • Your game objects have been defined in storage.
  • You prevented a simple CRUD to set the objects straight from transactions.

Now you need a message to instruct the checkers blockchain to create a game. This message needs to:

  • Not specify the creator: this is implicit because it shall be the signer of the message.
  • Not specify the ID of the game, because the system uses an incrementing counter. However, the server needs to return the newly created ID value, since the eventual value cannot be known before the transaction is included in a block and the state computed. Call this idValue.
  • Not specify the game board as this is controlled by the checkers rules.
  • Specify who is playing with the red pieces. Call the field red.
  • Specify who is playing with the black pieces. Call the field black.

Instruct Ignite CLI to do all of this:

$ ignite scaffold message createGame red black --module checkers --response idValue

This creates a certain number of files plus some GUI elements.

Protobuf objects

Simple Protobuf objects are created:

message MsgCreateGame {
    string creator = 1;
    string red = 2;
    string black = 3;
}

message MsgCreateGameResponse {
    string idValue = 1;
}

When compiled, for instance with ignite generate proto-go, these yield:

type MsgCreateGame struct {
    Creator string `protobuf:"bytes,1,opt,name=creator,proto3" json:"creator,omitempty"`
    Red     string `protobuf:"bytes,2,opt,name=red,proto3" json:"red,omitempty"`
    Black   string `protobuf:"bytes,3,opt,name=black,proto3" json:"black,omitempty"`
}

And:

type MsgCreateGameResponse struct {
    IdValue string `protobuf:"bytes,1,opt,name=idValue,proto3" json:"idValue,omitempty"`
}

Files were generated to serialize the pair which are named *.pb.go. You should not edit these files.

Ignite CLI also registered MsgCreateGame as a concrete message type with the two (de-)serialization engines:

func RegisterCodec(cdc *codec.LegacyAmino) {
    cdc.RegisterConcrete(&MsgCreateGame{}, "checkers/CreateGame", nil)
}

And:

func RegisterInterfaces(registry cdctypes.InterfaceRegistry) {
    registry.RegisterImplementations((*sdk.Msg)(nil),
        &MsgCreateGame{},
    )
    ...
}

This is code that you probably do not need to change.

Ignite CLI also creates boilerplate code to have the message conform to the sdk.Msg type:

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

This code is created only once. You can modify it as you see fit.

Protobuf service interface

Ignite CLI also adds a new function to your gRPC interface that receives all transaction messages for the module, because the message is meant to be sent and received. The interface is called service Msg and is declared inside proto/checkers/tx.proto.

Ignite CLI creates this tx.proto file at the beginning when you scaffold your project's module. Ignite CLI separates different concerns into different files so that it knows where to add elements according to instructions received. Ignite CLI adds a function to the empty service Msg with your instruction.

The new function receives this MsgCreateGame, namely:

service Msg {
    rpc CreateGame(MsgCreateGame) returns (MsgCreateGameResponse);
}

As an interface, it does not describe what should happen when called. With the help of Protobuf, Ignite CLI compiles the interface and creates a default Go implementation.

Unit tests

The code of this section was created by Ignite CLI, so there is no point in testing it. However, since you are going to adjust the keeper to do what you want, why not add a test file? Add keeper/msg_server_create_game_test.go with:

const (
    alice = "cosmos1jmjfq0tplp9tmx4v9uemw72y4d2wa5nr3xn9d3"
    bob   = "cosmos1xyxs3skf3f4jfqeuv89yyaqvjc6lffavxqhc8g"
    carol = "cosmos1e0w5t53nrq7p66fye6c8p0ynyhf6y24l4yuxd7"
)

func TestCreateGame(t *testing.T) {
    msgServer, context := setupMsgServer(t)
    createResponse, err := msgServer.CreateGame(context, &types.MsgCreateGame{
        Creator: alice,
        Red:     bob,
        Black:   carol,
    })
    require.Nil(t, err)
    require.EqualValues(t, types.MsgCreateGameResponse{
        IdValue: "", // TODO: update with a proper value when updated
    }, *createResponse)
}

You can test this with:

$ go test github.com/alice/checkers/x/checkers/keeper

This convenient setupMsgServer function was created by Ignite CLI. To call this a unit test is a slight misnomer because the msgServer created uses a real context and keeper, although with a memory database, not mocks.

Interact via the CLI

Time to see which new CLI command was created by Ignite CLI:

$ checkersd tx checkers --help

Among other things, this informs you of the following:

...
Available Commands:
  create-game Broadcast message createGame

And also:

$ checkersd tx checkers create-game --help

This returns:

...
Usage:
  checkersd tx checkers create-game [red] [black] [flags]

Flags:
   -a, --account-number uint      The account number of the signing account (offline mode only)
   -b, --broadcast-mode string    Transaction broadcasting mode (sync|async|block) (default "sync")
       --dry-run                  ignore the --gas flag and perform a simulation of a transaction, but don\`t broadcast it
       --fees string              Fees to pay along with transaction; eg: 10uatom
       --from string              Name or address of private key with which to sign
       --gas string               gas limit to set per-transaction; set to "auto" to calculate sufficient gas automatically (default 200000)
...

You kept the two accounts created by Ignite CLI.

Have alice start a game with bob. Instead of having to copy and paste the addresses each time you need them, you can store these as variables:

$ export alice=$(checkersd keys show alice -a)
$ export bob=$(checkersd keys show bob -a)

How much gas is needed? You can get an estimate by dry running the transaction using the --dry-run flag:

$ checkersd tx checkers create-game $alice $bob --from $alice --dry-run

This prints:

gas estimate: 40452

It is hard to assess how much gas that represents. In any case, keep gas on auto:

$ checkersd tx checkers create-game $alice $bob --from $alice --gas auto
{"body":{"messages":[{"@type":"/alice.checkers.checkers.MsgCreateGame","creator":"cosmos1wh7scjfhgzeqxfxhqq6jh59sj2y8d7u97qu7qp","red":"cosmos1wh7scjfhgzeqxfxhqq6jh59sj2y8d7u97qu7qp","black":"cosmos199krg6nz4qgv53nvrx9gj7nrlg48clwurn82jy"}],"memo":"","timeout_height":"0","extension_options":[],"non_critical_extension_options":[]},"auth_info":{"signer_infos":[],"fee":{"amount":[],"gas_limit":"40412","payer":"","granter":""}},"signatures":[]}

confirm transaction before signing and broadcasting [y/N]: y
code: 0
codespace: ""
data: 0A0C0A0A43726561746547616D65
gas_used: "38891"
gas_wanted: "40412"
height: "3905"
info: ""
logs:
- events:
  - attributes:
    - key: action
      value: CreateGame
    type: message
  log: ""
  msg_index: 0
raw_log: '[{"events":[{"type":"message","attributes":[{"key":"action","value":"CreateGame"}]}]}]'
timestamp: ""
tx: null
txhash: 59BC309EF79C354DD46ECE8D882BE133699CC10B165FEFAFF6AF3717507EBB4F

You can query your chain to check if the new game was saved to state:

$ checkersd query checkers show-next-game

This returns:

NextGame:
  creator: ""
  idValue: "0"
$ checkersd query checkers list-stored-game

This returns:

StoredGame: []
pagination:
  next_key: null
  total: "0"

It appears that nothing changed. Ignite CLI created a message, but you have not yet implemented what actions the chain should undertake when it receives this message.

Next up

Ignite CLI separates concerns into different files. The most relevant file currently is x/checkers/keeper/msg_server_create_game.go, which is created once. The creation of the game is coded into this file:

// TODO: Handling the message

You need to code in it the creation of the game proper. This is the object of the next section.