This is the part three of the series of blog posts explaining how to build a full stack application using Tendermint and Cosmos-SDK to build the blockchain backend. The previous part can be found here
This part will focus on building the core software, the application-specific blockchain. This is the code that will be run by the validators and full nodes in the network. Since the implementation is a bit lengthy, we will split it into separate blog posts. This one will cover the first part of the module.
We will go step by step explaining how to build the whole application. To keep the blog post short enough, only the most crucial parts will be shown here, and the whole code will be available on the Github: https://github.com/Sentrylink/tic_tac_toe/tree/basic
An application-specific blockchain, in contrast to a general-purpose platforms like Ethereum, is a blockchain with more narrow focus, built to support a single application. By using Tendermint and Cosmos-SDK it is simple to create application-specific blockchains, with any arbitrary logic that one would want to have on top of the blockchain. To showcase that, we are going to build something very arbitrary, allowing us not to focus on the business logic. We are going to build the tic-tac-toe game on top of a blockchain. It will be as simple as possible, intended to demonstrate how to code arbitrary logic on top of Tendermint, using all of the utilities of Cosmos-SDK, as well as following all of the best practices developed by the Cosmos team and community.
To build something with Cosmos-SDK, following the architectural principles set by it, we begin by dividing our application into appropriate modules. The question of what exactly should compose a single module, and where its borders should lie, can be a tricky one. The good rule of thumb to follow is to keep modules fairly independent, that each one has its own purpose and clear interface with other modules.
In this case, since the application is simple, it will only contain a single module.
As we already stated, the modules built with Cosmos-SDK are usually split into following parts:
- Keeper — this is the central component of module, which should contain the core business logic. The blockchain-specific application consists of a set of keepers that interact with each other, each one handling the logic of its own module.
- Messages — this defines the messages that exist in this module. Messages are what users send, packaged in transactions, when they want to interact with the blockchain by writing to it. This part of the module contains the structure of messages, their basic validation, routing information, etc
- Handler — serves to receive the incoming messages, kick off arbitrary preprocessing logic, and hand them to the keeper which should execute the main business logic of the module
- Querier — serves to handle the queries to read the application state. We can implement arbitrary queries of our application state that will be invoked from the client side
- Codec — registers all of the types that belong to the module, that are going to be encoded using Cosmos-SDK utilities (go-amino)
One of the most important steps in building a module is to figure out how will our application state look like inside of it. This is what will be stored in our key-value store, on every full node, and it will be modified by new transactions inside of the blocks.
First, we need to decide how to represent the game state. In the simplest way, we can use the map to hold all of the nine fields that exist in the game, with each field being in one of three states:
- Taken by player 1
- Taken by player 2
This is how the game state is represented in Go:
We will identify every game by its unique game id, which will be an increasing integer. That will also be a part of our application state.
The whole application state will be a map in which the keys are game ids and the values are JSON encoded games.
We have two types of messages in our tic-tac-toe module that will be packaged into transaction sent by the users to the blockchain:
- Start Game — this message creates new game for two players, provided as addresses in the message. Player one is the signer of the message, and he will take the first turn in the game
- Play — this message represents a single move in the game. It is necessary that we ensure that the game rules are respected: player can only play if it is his turn, player can only select a field and one that has not been taken yet
The messages fit into the framework created by the Cosmos-SDK. We only need to implement a certain interface, and can avoid some of the lower level details that are handled by the SDK. The standard transaction in Cosmos-SDK consists of a list of messages (usually only one exists), that are packed and all signed individually.
Lets see how we can fit into the Cosmos-SDK framework
The message to start a game:
The message to play a move:
These messages are what will serve as an user input to kick off the core logic in the keeper, signed and packaged into transactions at the client side.
Once a transaction arrives at the blockchain, it is processed by the Cosmos-SDK logic. It is first decoded, after which all of the messages are taken out (in this example, always just one) and processed one by one.
First the validation logic for each message is kicked off. In our case it is very simple. This is how the validation logic for MsgPlay looks like:
It only checks that the player is not empty, and that the field is from 0 to 8.
Not that if a message successfully passes this validation logic, it still doesn’t mean that the application processing of it will go through correctly, since this only concerns the basic validation rules, without taking into consideration the application state. For example, the game might already be finished.
Every message contains a routing information that is used by Cosmos-SDK in order to decide how to find where to pass it off, to which part of our custom logic. We implement it like this:
The other crucial part of implementing the message interface is the way that it is encoded and signed. Those methods are called GetSignBytes, where the encoding is implemented and GetSigners, which decides who are the signers for that message. The implementation is available here: https://github.com/Sentrylink/tic_tac_toe/blob/basic/x/tic_tac_toe/msgs.go
The handler is the piece of logic responsible for deciding what to do with those messages. It is registered with Cosmos-SDK, and it will invoke the handler once a new message that matches the route we registered is included in one of the transactions one the blockchain.
This is the central part of our handler:
It is a simple switch statement that calls the appropriate function for each message, and has a default clause to handle all of the unknown messages.
This is how the handleMsgStartGame looks like:
It delegates all of the logic to keeper, just performing the encoding of the newly created game state in order to return it to the user. In handlers we can also do some additional validation or authorization according to the needs of our application, before handing all the data to keeper to execute the main logic
The benefit of using Cosmos-SDK is obvious. We only need to provide a handler for our messages, and rely on it to perform all of the routing according to the routing info we coded for our messages, after which it will call our handler.
This concludes the blog post number three. The following blog post will contain the rest of implementation of the module