对于 COSMOS 新手而言,下面有一些有用的链接:

COSMOS 建立在 CometBFT 之上,提供了身份验证、账户管理、防重放攻击、多签名账户等功能。同时还提供了 ignite cli 工具来简化开发流程。

COSMOS 基础架构如下:

Diagram

模块

要实现 COSMOS 模块,需要实现 AppModule 接口。AppModule 接口定义如下:

type AppModule interface {
	appmodule.AppModule

	AppModuleBasic
}

将 AppModule 展开,其定义如下:

type AppModule interface {
    // IsOnePerModuleType is a marker function just indicates that this is a one-per-module type.
	IsOnePerModuleType()
    // IsAppModule is a dummy method to tag a struct as implementing an AppModule.
	IsAppModule()
    // HasName allows the module to provide its own name for legacy purposes. Newer apps should specify the name for their modules using a map using NewManagerFromMap.
    Name() string
    RegisterLegacyAminoCodec(*codec.LegacyAmino)
	RegisterInterfaces(types.InterfaceRegistry)
	RegisterGRPCGatewayRoutes(client.Context, *runtime.ServeMux)
}

对于一个 Module 而言,其生命周期如下:

Diagram

IBC 使用

要使用 COSMOS,最简单的方法是使用 ignite cli 工具。

ignite scaffold 提供了一系列的命令来生成模块、消息、查询等代码:

命令作用用法

chain

生成一个区块链项目

$ ignite scaffold chain [name] [flags]

module

生成一个新的模块

$ ignite scaffold module [name] [flags]

query

生成一个 Query 服务

$ ignite scaffold query [name] [field1:type1] [field2:type2] ... [flags]

list

生成一个 List 数据结构

$ ignite scaffold list NAME [field]... [flags]

map

生成一个 Map 数据结构

$ ignite scaffold map NAME [field]... [flags]

single

生成一个单例数据结构

$ ignite scaffold single NAME [field:type]... [flags]

IBC 基础

下面的例子在 IBC 的基础上,修复了官方的 Bug。

创建区块链

首先,我们需要创建一个区块链:

$ ignite scaffold chain planet --no-module
$ cd planet

创建模块

然后,我们创建一个模块:

$ ignite scaffold module blog --ibc

为类型生成 CRUD 操作

创建 blog posts
$ ignite scaffold list post title content creator --no-message --module blog
处理 ack 消息
$ ignite scaffold list sentPost postID:uint title chain creator --no-message --module blog
处理 post timeout 信息
$ ignite scaffold list timeoutPost title chain creator --no-message --module blog

创建 IBC 包

$ ignite scaffold packet ibcPost title content --ack postID:uint --module blog

修改源码

上面 我们为 blog 模块生成了具体的 CRUD 操作,但是具体的代码需要我们自己实现。

proto/planet/blog/packet.proto
message IbcPostPacketData {
  string title = 1;
  string content = 2;
  string creator = 3;
}
x/blog/keeper/msg_server_ibc_post.go
func (k msgServer) SendIbcPost(goCtx context.Context, msg *types.MsgSendIbcPost) (*types.MsgSendIbcPostResponse, error) {
	ctx := sdk.UnwrapSDKContext(goCtx)

	// TODO: logic before transmitting the packet

	// Construct the packet
	var packet types.IbcPostPacketData

	packet.Title = msg.Title
	packet.Content = msg.Content
	packet.Creator = msg.Creator

	// Transmit the packet
	_, err := k.TransmitIbcPostPacket(
		ctx,
		packet,
		msg.Port,
		msg.ChannelID,
		clienttypes.ZeroHeight(),
		msg.TimeoutTimestamp,
	)
	if err != nil {
		return nil, err
	}

	return &types.MsgSendIbcPostResponse{}, nil
}
x/blog/keeper/ibc_post.go
func (k Keeper) OnRecvIbcPostPacket(ctx sdk.Context, packet channeltypes.Packet, data types.IbcPostPacketData) (packetAck types.IbcPostPacketAck, err error) {
	// validate packet data upon receiving
	if err := data.ValidateBasic(); err != nil {
		return packetAck, err
	}
	k.SetPost(ctx, types.Post{
		Title:   data.Title,
		Content: data.Content,
	})

	// TODO: packet reception logic

	return packetAck, nil
}
x/blog/keeper/ibc_post.go
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

		return nil
	case *channeltypes.Acknowledgement_Result:
		// Decode the packet acknowledgment
		var packetAck types.IbcPostPacketAck

		if err := types.ModuleCdc.UnmarshalJSON(dispatchedAck.Result, &packetAck); err != nil {
			// The counter-party module doesn't implement the correct acknowledgment format
			return errors.New("cannot unmarshal acknowledgment")
		}

		// TODO: successful acknowledgement logic
		k.SetSentPost(ctx, types.SentPost{
			Id:    packetAck.PostId,
			Title: data.Title,
			Chain: packet.DestinationPort + "-" + packet.DestinationChannel,
		})

		return nil
	default:
		// The counter-party module doesn't implement the correct acknowledgment format
		return errors.New("invalid acknowledgment format")
	}
}
x/blog/keeper/ibc_post.go
func (k Keeper) OnTimeoutIbcPostPacket(ctx sdk.Context, packet channeltypes.Packet, data types.IbcPostPacketData) error {

	// TODO: packet timeout logic
	k.SetTimeoutPost(ctx, types.TimeoutPost{
		Title: data.Title,
		Chain: packet.DestinationChannel + "-" + packet.DestinationPort,
	})

	return nil
}

测试 IBC 模块

要测试 IBC 模块,首先需要在项目根路径下创建两个配置文件:

earth.yml
version: 1
build:
  proto:
    path: proto
    third_party_paths:
    - third_party/proto
    - proto_vendor
accounts:
- name: alice
  coins:
  - 1000token
  - 100000000stake
- name: bob
  coins:
  - 500token
  - 100000000stake
faucet:
  name: bob
  coins:
  - 5token
  - 100000stake
  host: 0.0.0.0:4500
genesis:
  chain_id: earth
validators:
- name: alice
  bonded: 100000000stake
  home: $HOME/.earth
mars.yml
version: 1
build:
  proto:
    path: proto
    third_party_paths:
    - third_party/proto
    - proto_vendor
accounts:
- name: alice
  coins:
  - 1000token
  - 1000000000stake
- name: bob
  coins:
  - 500token
  - 100000000stake
faucet:
  name: bob
  coins:
  - 5token
  - 100000stake
  host: :4501
genesis:
  chain_id: mars
validators:
- name: alice
  bonded: 100000000stake
  app:
    api:
      address: :1318
    grpc:
      address: :9092
    grpc-web:
      address: :9093
  config:
    p2p:
      laddr: :26658
    rpc:
      laddr: :26659
      pprof_laddr: :6061
  home: $HOME/.mars

然后运行:

$ ignite chain serve -c earth.yml
$ ignite chain serve -c mars.yml
$ rm -rf ~/.ignite/relayer

还需要安装 hermes:

$ ignite app install -g github.com/ignite/apps/hermes

然后运行:

$ ignite relayer hermes configure \
"earth" "http://localhost:26657" "http://localhost:9090" \
"mars" "http://localhost:26659" "http://localhost:9092" \
--chain-a-faucet "http://0.0.0.0:4500" \
--chain-b-faucet "http://0.0.0.0:4501" \
--chain-a-port-id "blog" \
--chain-b-port-id "blog" \
--channel-version "blog-1"

这次运行会失败,但是不用担心,将 ~/.ignite/relayer/hermes/earth_mars 修改为下面的内容:

Details
~/.ignite/relayer/hermes/earth_mars
[global]
log_level = 'error'

[mode]
[mode.clients]
enabled = true
refresh = true
misbehaviour = true

[mode.connections]
enabled = true

[mode.channels]
enabled = true

[mode.packets]
enabled = true
clear_interval = 100
clear_on_start = true
tx_confirmation = true
auto_register_counterparty_payee = false

[rest]
enabled = false
host = '127.0.0.1'
port = 3000

[telemetry]
enabled = false
host = '127.0.0.1'
port = 3001

[[chains]]
id = 'earth'
type = 'CosmosSdk'
ccv_consumer_chain = false
rpc_addr = 'http://localhost:26657'
grpc_addr = 'http://localhost:9090'
event_source = {batch_delay = '500ms', mode = 'push', url = 'ws://localhost:26657/websocket'}
rpc_timeout = '10s'
trusted_node = true
account_prefix = 'cosmos'
key_name = 'wallet'
address_type = {derivation = 'cosmos'}
key_store_type = 'Test'
store_prefix = 'ibc'
default_gas = 1000000
max_gas = 10000000
gas_price = {denom = 'stake', price = 0.001}
gas_multiplier = 1.2
max_msg_num = 30
max_tx_size = 2097152
clock_drift = '5s'
max_block_time = '30s'
trusting_period = '14days'
trust_threshold = {denominator = '3', numerator = '2'}
memo_prefix = ''
sequential_batch_tx = false

[[chains]]
id = 'mars'
type = 'CosmosSdk'
ccv_consumer_chain = false
rpc_addr = 'http://localhost:26659'
grpc_addr = 'http://localhost:9092'
event_source = {batch_delay = '500ms', mode = 'push', url = 'ws://localhost:26659/websocket'}
rpc_timeout = '10s'
trusted_node = true
account_prefix = 'cosmos'
key_name = 'wallet'
address_type = {derivation = 'cosmos'}
key_store_type = 'Test'
store_prefix = 'ibc'
default_gas = 1000000
max_gas = 10000000
gas_price = {denom = 'stake', price = 0.001}
gas_multiplier = 1.2
max_msg_num = 30
max_tx_size = 2097152
clock_drift = '5s'
max_block_time = '30s'
trusting_period = '14days'
trust_threshold = {denominator = '3', numerator = '2'}
memo_prefix = ''
sequential_batch_tx = false

然后重新运行上面的命令。运行过程中碰到的所有选项都置空即可。

开始测试

使用下面的命令启动测试:

$ ignite relayer hermes start "earth" "mars"

发送包:

$ planetd tx blog send-ibc-post blog channel-0 "Hello" "Hello Mars, I'm Alice from Earth" --from alice --chain-id earth --home ~/.earth
$ planetd q blog list-post --node tcp://localhost:26659

API 服务器

Cosmos 提供了 gRPC, RESTful 和 CometBFT 三种 API 服务器。REST 接口定义于 OpenAPI 规范文件中,调用时转发到 gRPC 服务器。因此 OpenAPI 可以访问所有 gRPC 存在的服务。

默认情况下 gRPC 服务器监听在 localhost:9090 端口,REST 服务器监听在 http://0.0.0.0:1317 端口。

Query 服务会通过 gRPC 接口暴露出去,但是 Msg 服务只能通过编程的形式访问[1]

API 使用

由于 MSG 相关的 API 只能通过编程的形式访问,这里以 blog 教程为例了解其 API 的用法:

首先添加依赖项:

module blogclient

go 1.20

require (
    blog v0.0.0-00010101000000-000000000000
    github.com/ignite/cli v0.25.2
)

replace blog => ../blog
replace github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1

然后创建一个 main.go 文件:

main.go
package main

import (
	"context"
	"fmt"
	"log"

	// Importing the general purpose Cosmos blockchain client
	"github.com/ignite/cli/v28/ignite/pkg/cosmosclient"

	// Importing the types package of your blog blockchain
	"blog/x/blog/types"
)

func main() {
	ctx := context.Background()
	addressPrefix := "cosmos"

	// Create a Cosmos client instance
	client, err := cosmosclient.New(ctx, cosmosclient.WithAddressPrefix(addressPrefix))
	if err != nil {
		log.Fatal(err)
	}

	// Account `alice` was initialized during `ignite chain serve`
	accountName := "alice"

	// Get account from the keyring
	account, err := client.Account(accountName)
	if err != nil {
		log.Fatal(err)
	}

	addr, err := account.Address(addressPrefix)
	if err != nil {
		log.Fatal(err)
	}

	// Define a message to create a post
	msg := &types.MsgCreatePost{
		Creator: addr,
		Title:   "Hello!",
		Body:    "This is the first post",
	}

	// Broadcast a transaction from account `alice` with the message
	// to create a post store response in txResp
	txResp, err := client.BroadcastTx(ctx, account, msg)
	if err != nil {
		log.Fatal(err)
	}

	// Print response from broadcasting a transaction
	fmt.Print("MsgCreatePost:\n\n")
	fmt.Println(txResp)

	// Instantiate a query client for your `blog` blockchain
	queryClient := types.NewQueryClient(client.Context())

	// Query the blockchain using the client's `PostAll` method
	// to get all posts store all posts in queryResp
	queryResp, err := queryClient.ListPost(ctx, &types.QueryListPostRequest{})
	if err != nil {
		log.Fatal(err)
	}

	// Print response from querying all the posts
	fmt.Print("\n\nAll posts:\n\n")
	fmt.Println(queryResp)
}
Last moify: 2024-12-20 07:00:17
Build time:2025-07-18 09:41:42
Powered By asphinx