x/counter 的整体结构,但使用了一个精简版本,这样你可以专注于亲手构建并接入模块的核心步骤。
完成后,你将拥有一个可工作的模块,并将它接入正在运行的链。想更深入了解 Cosmos SDK 中模块的工作方式,请参阅模块简介。
继续之前,你必须先完成前置条件指南,确保所有内容都已安装。
构建模块
Cosmos SDK 让你可以通过模块,直接将自定义业务逻辑构建到链中。每个模块都遵循相同的整体模式:- 定义消息:用户可以发送
Add来增加计数器 - 定义查询:用户可以查询
Count来读取当前值 - 定义创世状态:模块初始计数为
0
- 运行
proto-gen生成 Go 类型和接口 - 在
keeper中实现业务逻辑,用于存储计数并更新它 - 实现
MsgServer和QueryServer,将消息和查询传递给 keeper - 在
module.go中注册模块 - 在
app.go中将其接入链
步骤 1:准备
本教程使用tutorial/start 分支,它是一个空白模板,供你从零创建模块并将其接入 app.go。
- 如果你还没有克隆仓库,请先执行:
- 切换到
tutorial/start分支,并创建新模块目录:
x/counter/ 和 proto/example/counter/v1/ 看到空的占位目录。
步骤 2:Proto 文件
Proto 文件是模块公共 API 的权威来源。你会在这里定义消息和服务。若想更深入了解 protobuf 如何在模块之间使用,请参阅编码与 Protobuf。 在本教程中,counter 模块存储一个数字,Add 会按用户提交的数量增加它,而查询会返回当前值。
首先,创建这三个 proto 文件:
tx.proto
这是你定义的第一个模块文件。它声明了Add 的交易消息结构:用户发送什么来递增计数器,以及模块在处理后返回什么。若想进一步了解消息的定义与路由方式,请参阅消息。将以下代码添加到 tx.proto。
query.proto
这个文件定义了只读的 gRPC 查询服务,以及用于获取当前计数的响应类型。若想进一步了解查询与交易的区别,请参阅查询。将以下代码添加到query.proto。
genesis.proto
这个文件定义了模块在创世中存储的数据,以便链启动时能够初始化计数器。将以下代码添加到genesis.proto。
步骤 3:生成代码
- 确保 Docker 已在运行。
-
第一次运行
proto-gen时,你需要先构建 builder 镜像。运行以下命令:
x/counter/types/:
不要编辑生成文件。 公共类型的变更应当修改 proto 文件。每次修改 proto 后,都重新运行 make proto-gen。
最重要的生成结果是 MsgServer 和 QueryServer 接口。在步骤 5 和 6 中,你会分别在 keeper/msg_server.go 和 keeper/query_server.go 中实现它们。
步骤 4:Types
接下来,你将在x/counter/types 中定义模块类型和标识符,供模块其余部分依赖。
为本节创建这两个文件:
keys.go
这个文件定义了模块的基础标识符:一个是在整个 SDK 中使用的模块名,另一个是用于声明该模块 KV store 命名空间的 store key。想进一步了解模块如何通过 store key 访问状态,请参阅模块如何访问状态。ModuleName 在整个 SDK 中标识该模块(路由、事件、治理)。StoreKey 是模块在链的 KV store 中声明其隔离命名空间所使用的键(按约定它等于 ModuleName)。
接口注册
这个文件会将你生成的消息类型注册到 SDK 的接口注册表中,以便应用能够正确解码并路由你模块的交易。_Msg_serviceDesc 由 make proto-gen 生成,它描述了在 tx.proto 中定义的 Msg gRPC 服务。
第 5 步:Keeper
在这一步中,你将创建 keeper。它是模块中负责持有计数器状态并提供模块其他部分调用方法的组件。关于 keeper 角色的概念性概览,参见 Keeper。 创建 keeper 文件:collections.Item[uint64] 是一个带类型的 KV 存储条目;collections 包负责处理编码和命名空间。GetCount 将 ErrNotFound 视为零,因此计数器在没有显式初始化的情况下也会从零开始。
状态布局
StoreKey("counter")是该模块在链全局 KV 存储中的独立命名空间。其他模块都不能读取或写入这个命名空间。collections.NewPrefix(0)是一个单字节前缀,用于在模块命名空间内标识counter这一项。若模块包含多个存储项,通常会使用NewPrefix(0)、NewPrefix(1)等将它们彼此分隔。- 将
ErrNotFound视为零,意味着 keeper 无需显式设置初始值;在一条全新链上,第一次调用GetCount时按约定会返回0。
第 6 步:MsgServer
在这一步中,你将为生成出来的MsgServer 接口实现交易处理器。当用户提交 tx counter add 时,就会执行这段代码路径。关于消息执行的概念性概览,参见 Message execution。
创建消息服务器文件:
MsgServer 接口,并将 Add 交易转发给 keeper 的 AddCount 方法。
msgServer 内嵌了 *Keeper,并直接委托给 AddCount。处理器本身不包含任何业务逻辑。
第 7 步:QueryServer
在这一步中,你将为生成出来的QueryServer 接口实现只读查询处理器。当有人查询当前计数器值时,就会执行这段代码路径。关于模块如何暴露查询的更多内容,参见 Queries。
创建查询服务器文件:
QueryServer 接口,并从 keeper 返回当前计数器值。
第 8 步:module.go
在这一步中,你将把 keeper 和生成出的服务连接到 Cosmos SDK 模块框架中,这样应用就知道如何初始化模块、暴露其查询路由,以及注册其交易处理器。 创建模块文件:var _ interface = Struct{} 代码块是 Go 的编译期检查机制:如果结构体缺少任何必需的方法,构建会立即失败。
RegisterServices 是最重要的方法。它会将生成的服务端接口连接到你的实现上,使其可以通过 SDK 的消息路由器和查询路由器访问。
第 9 步:AutoCLI
在这一步中,你将为模块定义 CLI 元数据。AutoCLI 会结合这份配置和你的 proto 服务,自动生成exampled query counter 和 exampled tx counter 命令。
创建 AutoCLI 文件:
AutoCLI 如何将 Count 查询和 Add 交易暴露为简单的命令行命令。
PositionalArgs 会将第一个 CLI 参数映射到 MsgAddRequest 中的 add 字段,因此可以使用 add 4,而不是 add --add 4。
第 10 步:接入 app.go
在这一步中,你将把新模块接入应用,使链能够创建其存储、构造其 keeper,并在模块启动和创世处理时将其纳入其中。关于app.go 的作用以及接入顺序为何重要的完整说明,参见 app.go Overview。
打开 app.go 并找到每个标记注释。将代码直接粘贴到注释下方。
1. 导入
将 counter 模块、keeper 和共享类型的导入添加到app.go。
在 app.go 中找到对应注释,并将代码直接添加到它下方。
2. Keeper 字段
将计数器 keeper 存储在ExampleApp 上,以便应用其余部分可以引用它。
3. Store Key
为计数器模块提供它自己的 KV 存储命名空间。4. Keeper 实例化
使用模块存储和应用 codec 构造计数器 keeper。5. 模块管理器
将计数器模块注册到应用的模块管理器中。6. Genesis 顺序
在应用从 genesis 初始化状态时包含计数器模块。7. 导出顺序
在应用将状态导出回 genesis 时包含计数器模块。第 11 步:构建
运行以下命令来编译应用,并在尝试运行链之前确认新的模块接线是有效的。第 12 步:测试你的模块
现在你将在本地运行应用,并通过一笔交易加一次查询来确认该模块可以端到端正常工作。启动链
首先,安装二进制并启动演示链。exampled,然后运行 scripts/local_node.sh,该脚本会:
- 重置本地链数据
- 初始化 genesis
- 创建并为
alice和bob测试账户注资 - 创建一笔验证者交易
- 启动链
提交一笔交易
打开第二个终端,并提交一笔向计数器增加4 的交易:
code: 0,这表示链已接受并执行该交易,且没有发生应用错误:
查询链
使用之前由AutoCLI 生成的查询命令查询计数器,以确认已存储的值发生了变化:
后续步骤
你在这里构建的简单计数器模块,遵循了main 分支中完整 x/counter 示例相同的结构。接下来,你将看到完整模块如何在这个基础上扩展出参数、费用收集、测试等功能。
下一步:完整 Counter 模块演练 →
In quickstart, you started a chain and submitted a transaction to increase the counter. In this tutorial, you’ll build a simple counter module from scratch. It follows the same overall structure as the full
x/counter, but uses a stripped-down version so you can focus on the core steps of building and wiring a module yourself.
By the end, you’ll have built a working module and wired it into a running chain. For a deeper dive into how modules work in the Cosmos SDK, see Intro to Modules.
Before continuing, you must follow the Prerequisites guide to make sure everything is installed.
Making modules
The Cosmos SDK makes it easy to build custom business logic directly into your chain through modules. Every module follows the same overall pattern:- Define messages: users can send
Addto increase the counter - Define queries: users can query
Countto read the current value - Define genesis state: the module starts with a count of
0
- Run
proto-gento generate the Go types and interfaces - Implement your business logic in a
keeperto store the count and update it - Implement
MsgServerandQueryServerto pass messages and queries into the keeper - Register the module in
module.go - Wire it into the chain in
app.go
Step 1: Setup
This tutorial uses thetutorial/start branch, which is a blank template for you to create the module from scratch and wire it into app.go.
- Clone the repo if you haven’t already:
- Check out the
tutorial/startbranch and make the new module directories:
x/counter/ and proto/example/counter/v1/.
Step 2: Proto files
Proto files are the source of truth for the module’s public API. You define messages and services here. For a deeper look at how protobuf is used across modules, see Encoding and Protobuf. In this tutorial, the counter module stores one number,Add increases it by the amount the user submits, and the query returns the current value.
First, create the three proto files:
tx.proto
This is the first module file you define. It declares the transaction message shape forAdd: what the user sends to increment the counter, and what the module returns after handling it. To learn more about how messages are defined and routed, see Messages. Add the following code to tx.proto.
query.proto
This file defines the read-only gRPC query service and the response type for fetching the current count. To learn more about how queries differ from transactions, see Queries. Add the following code toquery.proto.
genesis.proto
This file defines the data the module stores in genesis so the counter can be initialized when the chain starts. Add the following code togenesis.proto.
Step 3: Generate Code
- Make sure Docker is running.
- The first time you run proto-gen you need to build the builder image. Run the following commands:
x/counter/types/:
Do not edit generated files. Changes to public types belong in the proto files. Re-run make proto-gen after any proto change.
The most important generated output is the MsgServer and QueryServer interfaces. In Steps 5 and 6, you’ll implement them in keeper/msg_server.go and keeper/query_server.go.
Step 4: Types
Next, you’ll define the module types and identifiers inx/counter/types that the rest of the module depends on.
Create the two files for this section:
keys.go
This file defines the module’s basic identifiers: the module name used throughout the SDK, and the store key used to claim the module’s KV store namespace. For more on how modules access state through store keys, see How modules access state.ModuleName identifies the module throughout the SDK (routing, events, governance). StoreKey is the key used to claim the module’s isolated namespace in the chain’s KV store (set equal to ModuleName by convention).
Interface Registration
This file registers your generated message types with the SDK interface registry so the application can decode and route your module’s transactions correctly._Msg_serviceDesc is generated by make proto-gen — it describes the Msg gRPC service defined in tx.proto.
Step 5: Keeper
In this step, you create the keeper, which is the part of the module that owns the counter state and provides the methods the rest of the module will call. For a conceptual overview of the keeper’s role, see Keeper. Create the keeper file:collections.Item[uint64] is a typed KV store entry; the collections package handles encoding and namespacing. GetCount treats ErrNotFound as zero so the counter starts at zero without explicit initialization.
State layout
StoreKey("counter") is the module’s isolated namespace within the chain’s global KV store. No other module can read or write this namespace.collections.NewPrefix(0)is a single-byte prefix that identifies thecounteritem within the module’s namespace. A module with multiple items would useNewPrefix(0),NewPrefix(1), etc. to keep them separate.ErrNotFoundtreated as zero means the keeper never needs to explicitly set an initial value — the firstGetCountcall on a fresh chain returns0by convention.
Step 6: MsgServer
In this step, you implement the transaction handler for the generatedMsgServer interface. This is the code path that runs when a user submits tx counter add. For a conceptual overview of message execution, see Message execution.
Create the message server file:
MsgServer interface and forwards the Add transaction to the keeper’s AddCount method.
msgServer embeds *Keeper and delegates directly to AddCount. The handler itself contains no business logic.
Step 7: QueryServer
In this step, you implement the read-only query handler for the generatedQueryServer interface. This is the code path that runs when someone queries the current counter value. For more on how modules expose queries, see Queries.
Create the query server file:
QueryServer interface and returns the current counter value from the keeper.
Step 8: module.go
In this step, you connect your keeper and generated services to the Cosmos SDK module framework so the application knows how to initialize the module, expose its query routes, and register its transaction handlers. Create the module file:var _ interface = Struct{} block at the top is a Go compile-time check — if the struct is missing any required method, the build fails immediately.
RegisterServices is the most important method. It connects the generated server interfaces to your implementations, making them reachable from the SDK’s message and query routers.
Step 9: AutoCLI
In this step, you define the CLI metadata for your module. AutoCLI reads this configuration together with your proto services and generates theexampled query counter and exampled tx counter commands automatically.
Create the AutoCLI file:
AutoCLI how to expose the Count query and Add transaction as simple command-line commands.
PositionalArgs maps the first CLI argument to the add field in MsgAddRequest, so add 4 works instead of add --add 4.
Step 10: Wire into app.go
In this step, you wire your new module into the application so the chain creates its store, constructs its keeper, and includes it in module startup and genesis handling. For a full explanation of whatapp.go does and why the wiring order matters, see app.go Overview.
Open app.go and find each marker comment. Paste the code directly below it.
1. Imports
Add the counter module, keeper, and shared types imports toapp.go.
Find the comment in app.go and add the code directly below it.
2. Keeper Field
Store the counter keeper onExampleApp so the rest of the app can reference it.
3. Store Key
Give the counter module its own KV store namespace.4. Keeper Instantiation
Construct the counter keeper using the module store and app codec.5. Module Manager
Register the counter module with the app’s module manager.6. Genesis Order
Include the counter module when the app initializes state from genesis.7. Export Order
Include the counter module when the app exports state back out to genesis.Step 11: Build
Run the following to compile the app and make sure the new module wiring is valid before you try to run the chain.Step 12: Test your module
Now you’ll run the app locally and use one transaction plus one query to confirm the module works end-to-end.Start the chain
First, install the binary and start the demo chain.exampled and then runs scripts/local_node.sh, which:
- resets the local chain data
- initializes genesis
- creates and funds the
aliceandbobtest accounts - creates a validator transaction
- starts the chain
Submit a transaction
Open a second terminal and submit a transaction that adds4 to the counter:
code: 0, which means the chain accepted and executed the transaction without an application error:
Query the chain
Query the counter to confirm the stored value changed using the query command thatAutoCLI generated earlier:
Next steps
The simple counter module you built here follows the same structure as the fullx/counter example in the main branch. Next, you’ll see how the full module extends that foundation with features like params, fee collection, tests, and more.
Next: Full Counter Module Walkthrough →