Skip to content

Instantly share code, notes, and snippets.

@fadeev
Last active May 12, 2021 08:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fadeev/a1f1f76e647450963b5f8ff19b28304f to your computer and use it in GitHub Desktop.
Save fadeev/a1f1f76e647450963b5f8ff19b28304f to your computer and use it in GitHub Desktop.

IBC Hello World

The Hello World example is a time-honored tradition in computer programming.

The Inter-Blockchain Communication protocol (IBC) is an important part of the Cosmos SDK ecosystem.

This tutorial builds an understanding of how to create and send packets across blockchain. This foundational knowledge helps you navigate between blockchains with the Cosmos SDK.

You will learn how to

  • Use IBC to create and send packets between blockchains.
  • Navigate between blockchains using the Cosmos SDK and the Starport Relayer.
  • Create a basic blog post and save the post on another blockchain.

Follow this tutorial on YouTube

IBC Hello World Tutorial

What is Cosmos SDK and IBC?

The Cosmos SDK is a framework to create a blockchain app. The Cosmos SDK allows developers to easily create custom blockchains that can natively interoperate with other blockchains.

The IBC module in the Cosmos SDK is the standard for the interaction between two blockchains. The IBC module defines how packets and messages are constructed to be interpreted by the sending and the receiving blockchain.

The Cosmos IBC relayer package lets you can connect between sets of IBC-enabled chains. This tutorial teaches you how to create two blockchains and then start and use the relayer with Starport to connect two blockchains.

This tutorial covers essentials like modules, IBC packets, relayer, and the lifecycle of packets routed through IBC.

Prerequisites

This tutorial uses Starport v0.15.1. The Starport tool is the easiest way to build a blockchain.

To install starport into /usr/local/bin, run the following command:

curl https://get.starport.network/starport@v0.15.1! | bash

When the installation succeeds, you see this message:

Installed at /usr/local/bin/starport

You can use Starport in a browser-based IDE, but this tutorial assumes you are using a local Starport installation. See Install Starport.

Create a Blockchain App

Create a blockchain app with a blog module to write posts on other blockchains that contain the Hello World message. For this tutorial, you can write posts for the Cosmos SDK universe that contain Hello Mars, Hello Cosmos, and Hello Earth messages.

For this simple example, create an app that contains a blog module that has a post transaction with title and text.

After you define the logic, run two blockchains that have this module installed.

  • The chains can send posts between each other using IBC.

  • On the sending chain, save the acknowledged and timed out posts.

After the transaction is acknowledged by the receiving chain, you know that the post is saved on both blockchains.

  • The sending chain has the additional data postID.

  • Sent posts that are acknowledged and timed out contain the title and the target chain of the post. These identifiers are visible on the parameter chain. The following chart shows the lifecycle of a packet that travels through IBC.

The Lifecycle of an IBC packet in the Blog Module

Build your Blockchain App

Use Starport to scaffold the blockchain app and the blog module.

1. Build the new blockchain

To scaffold a new blockchain named planet:

starport app github.com/user/planet
cd planet

A new directory named planet is created in your home directory. The planet directory contains a working blockchain app.cd

2. Scaffold the blog module inside your blockchain

Next, use Starport to scaffold a blog module with IBC capabilities. The blog module contains the logic for creating blog posts and routing them through IBC to the second blockchain.

To scaffold a module named blog:

starport module create blog --ibc

A new directory with the code for an IBC module is created in planet/x/blog. Modules scaffolded with the --ibc flag include all the logic for the scaffolded IBC module.

3. Generate CRUD actions for types

Next, create the CRUD actions for the blog module types.

Use the starport type command to scaffold the boilerplate code for the create, read, update, and delete (CRUD) actions.

These starport type commands create CRUD code for the following transactions:

  • Creating blog posts

    starport type post title content --module blog
    
  • Processing acknowledgments for sent posts

    starport type sentPost postID title chain --module blog
    
  • Managing post timeouts

    starport type timedoutPost title chain --module blog
    

The scaffolded code includes proto files for defining data structures, messages, messages handlers, keepers for modifying the state, and CLI commands.

Starport type command overview

starport type [typeName] [field1] [field2] ... [flags]

The first argument of the starport type [typeName] command specifies the name of the type being created. For the blog app, you created post, sentPost, and timedoutPost types.

The next arguments define the fields that are associated with the type. For the blog app, you created title, content, postID, and chain fields.

The --module flag defines which module the new transaction type is added to. This optional flag lets you manage multiple modules within your Starport app. The --module specifies which module the type is scaffolded in. When the flag is not present, the type is scaffolded in the module that matches the name of the repo).

4. Scaffold a sendable and interpretable IBC packet

Now you need to generate packet code that contains the title and the content of the blog post.

The starport packet command creates the logic for an IBC packet that can be sent to another blockchain.

  • The title and content are stored on the target chain.

  • The postID is acknowledged on the sending chain.

To scaffold a sendable and interpretable IBC packet:

starport packet ibcPost title content --ack postID --module blog

Notice the fields in the ibcPost packet match the fields in the post type that you created earlier.

  • The --ack flag defines which identifier is returned to the sending blockchain.

  • The --module flag specifies to create the packet in a particular IBC module.

The starport packet command also scaffolds the CLI command that is capable of sending an IBC packet:

planetd tx blog send-ibcPost [portID] [channelD] [title] [content]

Modify the Source Code

After you create the types and transactions, you must manually insert the logic to manage updates in the data tables. Modify the source code to save the data as specified earlier in this tutorial.

1. Add creator to the blog post packet

Start with the proto file that defines the structure of the IBC packet.

To identify the creator of the post in the receiving blockchain, add the creator field inside the packet. This field was not specified directly in the command because it would automatically become a parameter in the SendIbcPost CLI command.

proto/blog/packet.proto
@@ -19,6 +19,7 @@ message NoData {
 message IbcPostPacketData {
          string title = 1;
   string content = 2;
+  string creator = 3;
 }
 
 // IbcPostPacketAck defines a struct for the packet acknowledgment

To make sure the receiving chain has content on the creator of a blog post, add this value to the IBC packet. The content of the sender of the message is automatically included in SendIbcPost message. The sender is verified as the signer of the message, so you can add the msg.Sender as the creator to the new packet before it is sent over IBC.

x/blog/keeper/msg_server_ibcPost.go
@@ -18,6 +18,7 @@ func (k msgServer) SendIbcPost(goCtx context.Context, msg *types.MsgSendIbcPost)
 
        packet.Title = msg.Title
        packet.Content = msg.Content
+       packet.Creator = msg.Sender
 
        // Transmit the packet
        err := k.TransmitIbcPostPacket(

2. Receive the post

The methods for primary transaction logic are in the planet/x/blog/keeper/ibcPost.go file. Use these methods to manage IBC packets:

  • TransmitIbcPostPacket is called manually to send the packet over IBC. This method also defines the logic before the packet is sent over IBC to another blockchain app.
  • OnRecvIbcPostPacket hook is automatically called when a packet is received on the chain. This method defines the packet reception logic.
  • OnAcknowledgementIbcPostPacket hook is called when a sent packet is acknowledged on the source chain. This method defines the logic when the packet has been received.
  • OnTimeoutIbcPostPacket hook is called when a sent packet times out. This method defines the logic when the packet is not received on the target chain

You must modify the source code to add the logic inside those functions so that the data tables are modified accordingly.

On reception of the post message, create a new post with the title and the content on the receiving chain.

To identify the blockchain app that a message is originating from and who created the message, use an identifier in the following format:

<portID>-<channelID>-<creatorAddress>

Finally, the Starport-generated AppendPost function returns the ID of the new appended post. You can return this value to the source chain through acknowledgment.

Append the type instance as PostID on receiving the packet:

In the ibcPost.go file, make sure to import "strconv" below "errors", and then modify OnRecvIbcPostPacket with the following code:

x/blog/keeper/ibcPost.go
@@ -2,6 +2,7 @@ package keeper
 
 import (
        "errors"
+       "strconv"
 
        sdk "github.com/cosmos/cosmos-sdk/types"
        sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@@ -73,7 +74,13 @@ func (k Keeper) OnRecvIbcPostPacket(ctx sdk.Context, packet channeltypes.Packet,
                return packetAck, err
        }
 
-       // TODO: packet reception logic
+       id := k.AppendPost(
+               ctx,
+               packet.SourcePort+"-"+packet.SourceChannel+"-"+data.Creator,
+               data.Title,
+               data.Content,
+       )
+       packetAck.PostID = strconv.FormatUint(id, 10)
 
        return packetAck, nil
 }

3. Receive the post acknowledgement

On the sending blockchain, store a sentPost so you know that the post has been received on the target chain.

Store the title and the target to identify the post.

When a packet is scaffolded, the default type for the received acknowledgment data is a type that identifies if the packet treatment has failed. The Acknowledgement_Error type is set if OnRecvIbcPostPacket returns an error from the packet.

x/blog/keeper/ibcPost.go
@@ -90,10 +90,7 @@ func (k Keeper) OnRecvIbcPostPacket(ctx sdk.Context, packet channeltypes.Packet,
 func (k Keeper) OnAcknowledgementIbcPostPacket(ctx sdk.Context, packet channeltypes.Packet, data types.IbcPostPacketData, ack channeltypes.Acknowledgement) error {
        switch dispatchedAck := ack.Response.(type) {
        case *channeltypes.Acknowledgement_Error:
-
-               // TODO: failed acknowledgement logic
-               _ = dispatchedAck.Error
-
+               // We will not treat acknowledgment error in this tutorial
                return nil
        case *channeltypes.Acknowledgement_Result:
                // Decode the packet acknowledgment
@@ -104,7 +101,13 @@ func (k Keeper) OnAcknowledgementIbcPostPacket(ctx sdk.Context, packet channelty
                        return errors.New("cannot unmarshal acknowledgment")
                }
 
-               // TODO: successful acknowledgement logic
+               k.AppendSentPost(
+                       ctx,
+                       data.Creator,
+                       packetAck.PostID,
+                       data.Title,
+                       packet.DestinationPort+"-"+packet.DestinationChannel,
+               )
 
                return nil
        default:

4. Store information about the timed-out packet

Store posts that have not been received by target chains in timedoutPost posts. This logic follows the same format as sentPost.

x/blog/keeper/ibcPost.go
@@ -119,7 +119,12 @@ func (k Keeper) OnAcknowledgementIbcPostPacket(ctx sdk.Context, packet channelty
 // OnTimeoutIbcPostPacket responds to the case where a packet has not been transmitted because of a timeout
 func (k Keeper) OnTimeoutIbcPostPacket(ctx sdk.Context, packet channeltypes.Packet, data types.IbcPostPacketData) error {
 
-       // TODO: packet timeout logic
+       k.AppendTimedoutPost(
+               ctx,
+               data.Creator,
+               data.Title,
+               packet.DestinationPort+"-"+packet.DestinationChannel,
+       )
 
        return nil
 }

This last step completes the basic blog module setup. The blockchain is now ready!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment