# Incentivize Players
Make sure you have everything you need before proceeding:
- You understand the concept of gas.
- Go is installed.
- You have the checkers blockchain codebase with the game wager and its handling. If not, follow the previous steps or check out the relevant version (opens new window).
In this section, you will:
- Add transaction fees.
- Set fees and add metering.
- Do integration tests.
Players can start playing checkers with your Cosmos blockchain. Transaction fees are paid by the players themselves, at least the fee related to transporting the serialized bytes and the other gas-metered parts like
Your blockchain is taking shape, but you need to take care of peripheral concerns. For instance, how do you make sure that participants pay their fair share of the costs they impose on the network?
Next, you should add your own gas metering to reflect the costs that different transactions impose, or you can add costs to discourage spam.
# Some initial thoughts
To continue developing your checkers blockchain:
- At what junctures can you charge gas?
- At what junctures can you not charge gas, and what do you do about it?
- Are there new errors to report back?
- What event should you emit?
# Code needs
Before diving into the specifics, ask yourself:
- What Ignite CLI commands, if any, will assist you?
- How do you adjust what Ignite CLI created for you?
- Where do you make your changes?
- How would you unit-test these new elements?
- How would you use Ignite CLI to locally run a one-node blockchain and interact with it via the CLI to see what you get?
# New data
These values provide examples but you can, and should, set your own. To get a rule-of-thumb idea of how much gas is already consumed without your additions, look back at your previous transactions. Save your pick of the values as new constants:
Here are the debatable rationales for these values:
- Creating a game imposes a large cost because it creates a brand new entry in storage, which contains many fields. This new storage entry is stored on all nodes.
- Playing a game imposes a smaller cost because it makes changes to an existing storage entry, which was already paid for. On the other hand it costs some computation and pushes back the time by when the game expires.
- When a player rejects a game, the storage entry is deleted, which relieves the nodes of the burden of storing it. Hence it makes sense to incentivize players to reject games by refunding some gas. Since some computation was still done between creation and rejection, the refund is less than the cost of creation.
As a checkers blockchain creator, your goal may be to have as many on-going games as possible. Adding costs sounds counter to this goal. However, here the goal is to optimize potential congestion at the margin. If there is little activity, then the gas price will go down, and these additional costs will be trivial for players anyway. Conversely, if there is a lot of network activity, the gas price will go up, and whether you have put additional costs or not players will still be less likely to participate.
# Add handling
Add a line that consumes or refunds the designated amount of gas in each relevant handler:
When handling a game creation:
When handling a move:
When handling a game rejection, you make sure that you are not refunding more than what has already been consumed:
You do not meter gas in your
EndBlock handler because it is not called by a player sending a transaction. Instead, it is a service rendered by the network. If you want to account for the gas cost of a game expiration, you have to devise a way to pre-collect it from players as part of the other messages.
As part of your code optimization, avoid calling
ConsumeGas with a fixed gas cost (for instance
k) from within a loop. Each pass of the loop uses computation resources (
c) on each node. If you know the number of times your code loops (
n), you know that running the full loop will use
n*c computation resources.
Now consider the case of a user who sent a transaction without enough gas. The transaction will fail anyway, but at what point will it fail?
- If you call
ConsumeGas(k)within the loop, the transaction will fail during one of the passes (the
mth pass). This means that the node has already used
- If you call
ConsumeGas(n*k)once before the loop, the transaction will fail immediately, and the node will have used
Choosing option 2 improves the effectiveness of your blockchain, and potentially protects it from spam and denial-of-service attacks.
Additionally, making only a single call to
ConsumeGas slightly saves computation resources of the node.
# Unit tests
Now you must add tests that confirm the gas consumption. However, it is not possible to differentiate the gas cost that BaseApp is incurring on your messages from the gas cost your module imposes on top of it. Also, you cannot distinguish via the descriptor unless it panics (opens new window). Nevertheless, you can add a lame test like:
Now add another test for play (opens new window) and one for reject. Note that
after is much less than
These new tests are lame, because their
25_000 values cannot be predicted but have to be found by trial and error.
# Interact via the CLI
Here, you want to confirm that gas is consumed by different actions. The difficulty is that Alice's and Bob's balances in
stake tokens change not only because of the gas used but also depending on the gas price. An easy measurement is to use
Say this returns
69422, which is the estimated gas used. Now comment out the
.ConsumeGas line in
msg_server_create_game.go, save it, wait a few minutes for Ignite CLI to rebuild, and try again:
Say, this time you get
54422. This is good: the
15000 gas is no longer part of the estimation, as expected. Uncomment the
.ConsumeGas line. You can try
--dry-run on play and reject too.
--dry-run is a good start. Now have Alice create a game and check the gas used in the transaction:
You could impose a
--gas-prices and then check balances, but this would obfuscate the gas consumption which is what you want to confirm.
As before, comment the
msg_server_create_game.go and wait for Ignite CLI to rebuild. Then try again:
There is only a difference of
4000. The rest of the system likely had some under-the-hood initializations, such as Merkle tree creations, which may falsify the early results. Create 10 more games without
.Consumeing gas and only look at the
gas_used. It should stabilize at a certain value:
Put back the
.ConsumeGas line and rebuild. Then try again:
It now consistently mentions a difference of
That is sufficient confirmation.
What about the refund on reject? With the gas refund in place, reject one of the many games you created:
Now comment out the
RefundGas part and reject another game. This shows:
This is close to
14000 more expensive than when there is a refund.
Do not worry if you do not get the same values. At least try multiple times to see if the values look like each other on your system.
To summarize, this section has explored:
- How to add gas metering to your application so participants contribute toward the cost of the work being demanded of the network by gameplay, and add costs to discourage spam.
- What new data constants need to be added, such as fees for creating games or playing moves, and gas consumption lines for handlers relating to these gameplay aspects.
- Best practices for gas metering, including where not to call fixed gas costs and the implications of a user sending transactions without enough gas to process them.
- What texts to add that confirm gas consumption, acknowledging the limitations on precision that the use of BaseApp and your module also imposes on understanding how much gas is used by various transactions.
- How to interact via the CLI to confirm that gas is being consumed by different actions, acknowledging the additional complications arising from variable account balances and gas price.