为什么需要模块
一个区块链应用需要管理许多彼此独立的关注点(账户、余额、验证者管理等)。Cosmos SDK 不会把所有逻辑都放进一个单体状态机中,而是将应用拆分为多个模块。SDK 提供了一个基础层,使这些模块能够协同运作,组成一条统一的区块链。 每个模块拥有自己的一部分状态,定义自己的消息和查询,实现自己的业务规则,并在需要时接入区块生命周期和创世流程。这样可以让应用结构更清晰、组合性更强,也更容易推理。同时,它还能将不同模块之间的安全关注点隔离开来,从而构建出更安全的系统。模块定义了什么
模块是一个自包含的状态与逻辑单元。 从高层看,一个模块会定义:- 状态:包含模块数据的
KVStore命名空间 - 消息:模块允许执行的操作
- 查询:对模块状态的只读访问
MsgServer:负责校验、应用业务逻辑,并委托给 keeper- Keeper:状态访问层,也是访问存储的唯一受认可路径
- 参数:由治理控制并存储在链上的配置
状态
状态是持久化在链上的数据,也就是交易可以读取或写入的一切内容。当交易执行时,模块会对这些存储数据应用状态转换或确定性更新。 每个模块都拥有自己那一部分区块链状态。例如:x/auth模块存储账户元数据。x/bank模块存储账户余额。
multistore 中都有自己独立的键值存储命名空间。例如,bank 模块的存储中可能包含如下条目:
消息
正如你在交易、消息与查询一节中学到的,每个模块通过消息(sdk.Msg)来定义它允许执行的操作。
消息定义在模块的 tx.proto 文件中的 service Msg 代码块下,并由该模块的 MsgServer 实现。下面是一个来自 bank 模块的简化示例:
service Msg 代码块被称为 Msg service。protobuf 编译器会基于它生成一个 MsgServer 接口,由模块使用 Go 实现。BaseApp 会根据每条传入消息的 type URL(例如 /cosmos.bank.v1beta1.MsgSend)将其路由到正确的实现。
上面的 MsgSend 就是一个消息类型定义示例。它表示一个将代币从一个账户转移到另一个账户的请求。当它被包含进一笔交易并执行时:
- 检查发送方余额。
- 从
from_address中扣除金额。 - 将金额记入
to_address。
查询
模块通过定义在query.proto 中的查询服务,向外暴露对其状态的只读访问。查询不会修改状态,也不会经过区块执行流程。例如:
业务逻辑
消息定义的是意图,而MsgServer 和 Keeper 会协同工作来执行该意图,并将状态转换应用到模块中。
从概念上看,业务逻辑分布在两个层次中:
MsgServer处理面向交易的逻辑:它会校验输入、应用消息级别的业务规则,并在需要时检查授权。完成这些后,它会将状态转换委托给 keeper。Keeper拥有模块状态:它定义存储结构,并提供唯一被授权的读写路径。消息处理器、区块钩子和治理提案都会通过 keeper 方法来修改状态。所有状态变更都必须经过 keeper,不能直接访问底层存储。
消息执行(MsgServer)
每个模块都会实现一个 MsgServer,当消息被路由到该模块时,它会由 BaseApp 的消息路由器调用。
MsgServer 负责:
- 在需要时检查授权
- 委托给 keeper 方法,由 keeper 校验输入、执行业务规则并完成状态转换
- 返回响应
Add 处理器,也就是处理计数器递增请求的 MsgServer 方法:
Add 不需要权限控制,并且会直接委托给 keeper。处理器本身几乎不包含业务逻辑。这样可以让 msg_server.go 专注于请求处理,而 keeper 则负责实际的状态转换。
完整的 counter 模块示例则在这个最小结构之上增加了更丰富的模式。对于像 MsgUpdateParams 这样的特权消息,MsgServer 会在继续之前,将调用者与已存储的 authority 进行比对。参见完整 Counter 模块详解。
Keeper
模块的Keeper 是它的状态访问层。它拥有模块的 KVStore,并提供带类型的方法来读取和写入状态。存储字段是不导出的,因此 keeper 包之外的任何代码都不能直接访问它们。
MsgServer 和 QueryServer 都会嵌入 keeper。最佳实践是将业务逻辑实现为 keeper 方法,而不是写在 MsgServer 中,这样无论调用方是消息处理器、区块钩子还是治理提案,都能应用同一套规则。
下面的 keeper 示例来自最小 counter 模块示例。它只持有一个状态项,展示了一个最小但有用的 keeper 结构。参见教程中的步骤 5:Keeper。
counter 使用的是 collections.Item[uint64],它是一个带类型的单值条目,底层由模块的 KV 存储通过 Collections API 支撑。AddCount 会读取当前值,将其递增后再写回。所有状态访问都必须经过 keeper:keeper 包之外的任何代码都不能直接触达 counter。关于 keeper 如何从收到的上下文中打开自己的存储,请参阅执行上下文。
跨模块访问
默认情况下,模块彼此隔离。每个模块都拥有自己的状态,而对这部分状态的直接访问会通过模块的 keeper 受到限制。其他模块不能任意修改另一个模块的存储。 相反,模块之间通过显式定义的 keeper 接口进行交互。 例如:- staking 模块调用 bank keeper 上的方法来转移代币。
- governance 模块调用其他模块上的参数更新方法。
expected_keepers.go 文件,用来声明它依赖其他模块提供的接口。这使跨模块依赖关系变得明确:一个模块只能调用另一个模块选择对外暴露的方法。
这种设计让依赖关系更易于审计,并能防止意外或不安全的跨模块状态修改。
如果你想查看 expected keepers 和跨模块手续费收集的实际用法,请参阅完整 Counter 模块详解中的Expected keepers and fee collection。
参数
大多数模块都会暴露一个Params 结构体:它是一组存储在链上的配置值,用于控制模块行为。与普通状态不同,参数被刻意设计为稳定配置:它们只会通过治理发生变化,而不会被用户交易修改。示例包括最小治理押金、验证者最大数量,或 mint 模块的通胀边界。参数通常存储在一个单独的键下(一般是一个 collections.Item[Params]),并通过提交治理提案来更新。
要更新参数,治理提案会提交一条 MsgUpdateParams 消息。MsgServer 会先检查调用方是否为指定的 authority 地址(通常是治理模块账户),然后才会把新值写入存储。关于这种模式的代码示例,请参阅消息执行(MsgServer)。
authority 地址会在 app.go 中构造 keeper 时设置。
对于链级共识参数,由 x/consensus 模块统一管理;它还支持 AuthorityParams,使治理能够在链上更新 authority 地址,而无需软件升级。
如果你想查看参数在实际模块中的实现,请参阅完整 Counter 模块详解中的参数与 authority。
区块钩子
模块可以通过在module.go 的 AppModule 结构体中实现可选的钩子接口,在区块生命周期的特定阶段执行逻辑:
HasBeginBlocker— 在每个区块开始时运行逻辑HasEndBlocker— 在每个区块结束时运行逻辑HasPreBlocker— 在BeginBlock之前运行逻辑,用于共识参数变更
BaseApp 中的 ModuleManager 按配置顺序调用各个已注册模块的钩子。关于应用接线侧的内容,参见 app.go 中的 Module Manager。
模块通过在 module.go 的 AppModule 结构体上定义对应方法来实现钩子:
app.go 中注册到 ModuleManager,这样这些钩子才会被调用。
创世初始化
模块定义了链启动时其状态如何初始化。 每个模块都会实现:DefaultGenesis:返回模块的默认创世状态ValidateGenesis:在链启动前校验创世状态InitGenesis:在链启动时将创世状态写入模块存储ExportGenesis:读取模块当前状态并将其序列化为创世数据
InitChain 期间,BaseApp 会调用每个模块的 InitGenesis,使用 genesis.json 填充其状态。
关于这在区块生命周期中的位置,参见 InitChain(仅创世)。
关于创世实现的演练,参见“构建模块”教程中的 第 2 步:Proto 文件 和 第 8 步:module.go。
内置模块与自定义模块
Cosmos SDK 自带一组大多数链都会使用的核心模块。点击任意模块了解更多:| 模块 | 功能 |
|---|---|
| x/auth | 账户、身份验证与交易签名 |
| x/bank | 代币余额与转账 |
| x/staking | 验证者集合与委托 |
| x/gov | 链上治理与提案 |
| x/distribution | 质押奖励分发 |
| x/slashing | 验证者惩罚执行 |
| x/mint | 代币发行 |
| x/evidence | 提交与处理验证者不当行为 |
| x/upgrade | 协调链升级 |
| x/authz | 委托消息授权 |
| x/feegrant | 账户间的手续费额度 |
| x/consensus | 链上管理 CometBFT 共识参数 |
| 模块 | 功能 |
|---|---|
| Permissioned Consensus | 由链上权威管理的许可型验证者集合,替代基于代币的质押 |
| Multi-sig | 具备可配置投票策略的链上多签账户与集体决策 |
模块剖析(高层视角)
模块位于 SDK 应用的x/ 目录下:
x/ 下的每个子目录都是一个自包含模块。
应用将多个模块组合在一起,形成完整的区块链。
在一个模块内部,你通常会看到如下结构:
x/ 内部:
keeper/:包含Keeper结构体(状态访问)以及MsgServer和QueryServer接口的实现。types/:定义模块的公共类型,包括生成的 protobuf 结构体、存储键,以及expected_keepers.go中声明本模块对其他模块需求的接口。module.go:将模块连接到应用,并注册创世处理器、区块钩子以及消息和查询服务。.proto文件:定义消息、查询、状态模式和创世状态。Go 代码由这些文件生成,并在整个模块中使用。
模块所处的上下文
把你目前学到的内容串起来:- 账户 授权交易。
- 交易 携带消息和执行约束。
- 区块 对交易排序并定义提交边界。
- 模块 定义执行的业务规则。
- MsgServer 校验消息并编排状态转换。
- Keeper 对模块状态执行受控读写。
- 状态 持久化执行的确定性结果。
In the previous section, you saw how blocks and transactions are processed. But where does the actual application logic of a blockchain live? In the Cosmos SDK, modules define that logic. Modules are the fundamental building blocks of a Cosmos SDK application. Each module encapsulates a specific piece of functionality, such as accounts, token transfers, validator management, governance, or any custom logic you define. To see a complete working module, follow the Build a module tutorial series.
Why modules exist
A blockchain application needs to manage many independent concerns (accounts, balances, validator management, etc). Instead of placing all logic in a single monolithic state machine, the Cosmos SDK divides the application into modules. The SDK provides a base layer that allows these modules to operate together as a cohesive blockchain. Each module owns a slice of state, defines its messages and queries, implements its business rules, and hooks into the block lifecycle and genesis as needed. This keeps the application organized, composable, and easier to reason about. It also separates safety concerns between modules, creating a more secure system.What a module defines
A module is a self-contained unit of state and logic. At a high level, a module defines:- State: a
KVStorenamespace that contains the module’s data - Messages: the actions the module allows
- Queries: read-only access to the module’s state
MsgServer: validates, applies business logic, and delegates to the keeper- Keeper: the state access layer: the only sanctioned path to the store
- Params: governance-controlled configuration stored on-chain
State
State is the data persisted on the chain: everything that transactions can read from or write to. When a transaction is executed, modules apply state transitions or deterministic updates to this stored data. Each module owns its own part of the blockchain state. For example:- The
x/authmodule stores account metadata. - The
x/bankmodule stores account balances.
multistore. For example, the bank module’s store might contain entries like:
Messages
As you learned in the Transactions, Messages, and Queries section, each module defines the actions it allows via messages (sdk.Msg).
Messages are defined in the module’s tx.proto file under a service Msg block, and implemented by that module’s MsgServer. Here is a simplified example from the bank module:
service Msg block is referred to as the Msg service in the Cosmos SDK. The protobuf compiler generates a MsgServer interface from it, which the module implements in Go. BaseApp routes each incoming message by its type URL (for example, /cosmos.bank.v1beta1.MsgSend) to the correct implementation.
MsgSend above is an example of a message type definition. It represents a request to transfer tokens from one account to another. When included in a transaction and executed:
- The sender’s balance is checked.
- The amount is deducted from
from_address. - The amount is credited to
to_address.
Queries
Modules expose read-only access to their state through query services, defined inquery.proto. Queries do not modify state and do not go through block execution. For example:
Business logic
While messages define intent, theMsgServer and Keeper work together to execute that intent and apply state transitions to the module.
Business logic is conceptually split across two layers:
- The
MsgServerhandles the transaction-facing logic: it validates inputs, applies message-level business rules, and where required checks authorization. Once satisfied, it delegates state transitions to the keeper. - The
Keeperowns the module’s state: it defines the storage schema and provides the only authorized path for reading and writing it. Message handlers, block hooks, and governance proposals all go through keeper methods to make state changes. All state changes must go through the keeper, nothing accesses the store directly.
Message execution (MsgServer)
Each module implements a MsgServer, which is invoked by BaseApp’s message router when a message is routed to that module.
The MsgServer is responsible for:
- Checking authorization when required
- Delegating to keeper methods that validate inputs, enforce business rules, and perform state transitions
- Returning a response
Add handler which is the MsgServer method that processes a request to increment the counter:
Add is permissionless and delegates directly to the keeper. The handler itself contains almost no business logic. That keeps msg_server.go focused on request handling, while the keeper owns the actual state transition.
The full counter module example adds richer patterns on top of that minimal shape. For privileged messages like MsgUpdateParams, the MsgServer checks the caller against the stored authority before proceeding. See the Full Counter Module Walkthrough.
Keeper
A module’sKeeper is its state access layer. It owns the module’s KVStore and provides typed methods for reading and writing state. The store fields are unexported, so nothing outside the keeper package can access them directly.
The MsgServer and QueryServer both embed the keeper. It is best practice for business logic to be implemented in keeper methods rather than the MsgServer, so the same rules apply whether the caller is a message handler, a block hook, or a governance proposal.
The following keeper example is from the minimal counter module example. It holds a single state item and shows the smallest useful keeper shape. See Step 5: Keeper in the tutorial.
counter uses collections.Item[uint64], a typed single-value entry backed by the module’s KV store using the Collections API. AddCount reads the current value, increments it, and writes it back. All state access goes through the keeper: nothing outside the keeper package can reach counter directly. For details on how the keeper opens its store from the context it receives, see Execution Context.
Inter-module access
Modules are isolated by default. Each module owns its state, and direct access to that state is restricted through the module’s keeper. Other modules cannot arbitrarily mutate another module’s storage. Instead, modules interact through explicitly defined keeper interfaces. For example:- The staking module calls methods on the bank keeper to transfer tokens.
- The governance module calls parameter update methods on other modules.
expected_keepers.go file that declares the interfaces it requires from other modules. This makes cross-module dependencies explicit: a module can only call methods the other module has chosen to expose.
This design keeps dependencies auditable and prevents accidental or unsafe cross-module state mutation.
To see expected keepers and cross-module fee collection in practice, see Expected keepers and fee collection in the Full Counter Module Walkthrough.
Params
Most modules expose aParams struct: a set of configuration values stored on-chain that control the module’s behavior. Unlike regular state, params are intentionally stable: they only change through governance, not through user transactions. Examples include the minimum governance deposit, the maximum number of validators, or mint module inflation bounds. Params are stored under a single key (typically a collections.Item[Params]) and updated by submitting a governance proposal.
To update params, a governance proposal submits a MsgUpdateParams message. The MsgServer checks that the caller is the designated authority address (usually the governance module account) before writing the new values to the store. See Message execution (MsgServer) for a code example of this pattern.
The authority address is set at keeper construction time in app.go.
For chain-level consensus parameters, the x/consensus module manages them centrally; it also supports AuthorityParams, which lets governance update the authority address on-chain without a software upgrade.
To see params implemented in a working module, see Params and authority in the Full Counter Module Walkthrough.
Block hooks
Modules may execute logic at specific points in the block lifecycle by implementing optional hook interfaces in theAppModule struct in module.go:
HasBeginBlocker— runs logic at the start of each blockHasEndBlocker— runs logic at the end of each blockHasPreBlocker— runs logic beforeBeginBlock, used for consensus parameter changes
ModuleManager in BaseApp, which calls each registered module’s hooks in a configured order. For the application wiring side, see Module Manager in app.go.
A module implements a hook by defining the corresponding method on its AppModule struct in module.go:
ModuleManager in app.go for the hooks to be called.
Genesis initialization
Modules define how their state is initialized when the chain starts. Each module implements:DefaultGenesis: returns the module’s default genesis stateValidateGenesis: validates the genesis state before the chain startsInitGenesis: writes the genesis state into the module’s store at chain startExportGenesis: reads the module’s current state and serializes it as genesis data
InitChain, BaseApp calls each module’s InitGenesis to populate its state from genesis.json.
For where this happens in the block lifecycle, see InitChain (genesis only).
For a walkthrough of genesis implementation, see Step 2: Proto files and Step 8: module.go in the Build a Module tutorial.
Built-in and custom modules
The Cosmos SDK ships with a set of core modules that most chains use. Click any module to learn more:| Module | What it does |
|---|---|
| x/auth | Accounts, authentication, and transaction signing |
| x/bank | Token balances and transfers |
| x/staking | Validator set and delegations |
| x/gov | On-chain governance and proposals |
| x/distribution | Staking reward distribution |
| x/slashing | Validator penalty enforcement |
| x/mint | Token issuance |
| x/evidence | Submission and handling of validator misbehavior |
| x/upgrade | Coordinated chain upgrades |
| x/authz | Delegated message authorization |
| x/feegrant | Fee allowances between accounts |
| x/consensus | On-chain management of CometBFT consensus params |
| Module | What it does |
|---|---|
| Permissioned Consensus | Permissioned validator set managed by an on-chain authority, replacing token-based staking |
| Multi-sig | On-chain multisig accounts and collective decision-making with configurable voting policies |
Anatomy of a module (high-level)
Modules live under thex/ directory of an SDK application:
x/ is a self-contained module.
An application composes multiple modules together to form a complete blockchain.
Inside a module, you will typically see a structure like this:
x/:
keeper/: Contains theKeeperstruct (state access) and implementations of theMsgServerandQueryServerinterfaces.types/: Defines the module’s public types: generated protobuf structs, store keys, and theexpected_keepers.gointerfaces that declare what this module needs from other modules.module.go: Connects the module to the application and registers genesis handlers, block hooks, and message and query services..protofiles: Define messages, queries, state schemas, and genesis state. Go code is generated from these files and used throughout the module.
Modules in context
Putting everything together you’ve learned so far:- Accounts authorize transactions.
- Transactions carry messages and execution constraints.
- Blocks order transactions and define commit boundaries.
- Modules define the business rules of execution.
- MsgServer validates messages and orchestrates state transitions.
- Keeper performs controlled reads and writes to module state.
- State persists the deterministic result of execution.