什么是状态?
状态是区块链的持久化数据:账户余额、委托、治理提案、模块参数,以及其他任何能够在区块之间保留下来的数据。当一笔交易执行时,模块会更新状态。当一个区块被提交时,更新后的状态就会成为下一个区块的起点:KVStore 模型
在最底层,Cosmos SDK 将状态存储为键值对。键和值都是字节数组。模块使用 Protocol Buffers 将结构化数据编码为这些字节,并在读取时再将其解码回来。 关于模块如何将数据序列化为字节的细节,请参见 编码与 Protobuf。 下面的示例展示了 bank 模块如何存储余额的一个概念性示例:x/bank/types/keys.go。
每个模块在键值存储中都拥有自己的命名空间。键由模块定义,通常以一个字节前缀开头,用于将它们与其他模块的键区分开来。
多重存储(Multistore)
单个模块存储只是整体图景的一部分。在应用层,所有模块存储会一起提交。 每个模块都有自己的 KVStore,所有模块存储都会挂载到一个 multistore 中,并作为单一状态根一起提交。 模块只能通过自己的 keeper 读取和写入自身的存储。访问由StoreKey 控制,它是在应用启动时注册的一种带类型能力对象。未持有该 key 的模块无法打开这个存储。
这种隔离遵循对象能力模型:
- 模块不能直接修改另一个模块的状态
- 跨模块交互必须通过暴露出来的 keeper 方法进行
状态如何存储(IAVL 与 commit store)
每个模块的 KVStore 都由一个CommitKVStore 支撑。更多细节请参见存储规范。
在这里描述的当前 SDK 存储实现中,Cosmos SDK 使用的是 IAVL,这是一种带版本的 AVL Merkle 树。
IAVL 为树中的每次读写都提供 O(log n) 复杂度,这意味着读取或写入一个键所花费的时间取决于树的高度,而不是键的总数量。它还会在每次区块提交时为状态建立版本,并生成确定性的根哈希,以便为轻客户端生成 Merkle 证明。
每次区块提交都会生成一个新的树版本和新的根哈希:
应用哈希(app hash)
app hash 是应用已提交状态的密码学根哈希。它通过CommitMultiStore 汇总所有模块存储。由于每个验证者都会以确定性的方式执行相同的状态转换,因此对于同一个区块,他们都应计算出相同的 app hash。
数据库后端
IAVL 树不会把数据存储在内存中。它会把带版本的节点写入数据库后端,而数据库后端本身是一个磁盘上的键值存储。 Cosmos SDK 使用 CometBFT 的db 包 对数据库实现做抽象。默认后端是 goleveldb。其他受支持的后端还包括 PebbleDB、RocksDB 和 memDB(内存型,用于测试)。
数据库后端在节点启动时选择,并在 app.toml 中配置。应用代码永远不会直接与它交互;这层边界由 store 层负责管理。
SDK 中的存储类型
除了基础的 KVStore 之外,SDK 还提供了若干专门的存储包装器。CommitKVStore (persistent store)
CommitKVStore 是由 IAVL 支撑的主要持久化存储。它会跨区块持久保留、生成带版本的提交,并参与 app hash 的计算。
CacheMultiStore (transaction isolation)
在执行每笔交易之前,Cosmos SDK 的BaseApp 都会创建一个 CacheMultiStore;它是 multistore 的一个带缓存、写时复制视图。
该交易期间的所有写操作都会发生在这个缓存层中:
- 如果交易成功,变更会写入底层存储。
- 如果交易失败,缓存会被丢弃,不会提交任何状态变更。
Ephemeral store types
Transient store 会在每个区块结束时清空。它们用于存放每区块临时数据,例如计数器或中间计算结果,并且不会影响 app hash。 Memory store 会跨越区块提交而保留,但在节点重启时会重置。它们的Commit() 是空操作,数据永远不会写入磁盘。它们用于在进程内缓存那些每个区块重新计算代价较高、但又不需要在重启后保留的数据。模块通过 MemoryStoreKey 访问它们,并在 app.go 中通过 MountMemoryStores 挂载。
| 存储类型 | 区块提交后保留 | 重启后保留 |
|---|---|---|
| Transient | 否(每个区块结束时清空) | 否 |
| Memory | 是 | 否 |
| IAVL(CommitKVStore) | 是 | 是 |
Gas and trace store wrappers
所有存储访问都会通过GasKVStore 和 TraceKVStore 这两个包装器附加额外行为。
GasKVStore会为每次读写收取 gasTraceKVStore会记录每次存储操作以便调试
Prefix store
prefix store 会包装一个 KVStore,并自动为每个键添加一个固定的字节前缀。这样 keeper 就可以将读写范围限定在某个子命名空间中,而不必在每次调用时手动构造带前缀的键。Collections API(类型化状态访问)
在 Cosmos SDK 中,模块通常会使用 collections API 来定义类型化的状态访问。 模块不会手动构造字节键,而是定义如下这类带类型的 collection:collections.Item[T]collections.Map[K, V]collections.Sequence
collections/collections.go。完整的包指南请参见 Collections。
模块如何访问状态
模块不会直接与 multistore 交互。相反,每个模块都会定义一个 keeper,并通过每次调用时收到的执行Context 来打开自己的 KVStore。关于 Context 如何在运行时携带 store 引用的细节,请参见 执行上下文。
一个 keeper 通常会持有:
- 模块的store key(一种对象能力,用于从
Context中打开模块的KVStore), - 一个 Protobuf codec,用于对以字节形式存储的值进行编码和解码,
- 模块所依赖的其他 keeper 的引用(接口)。
创世与链初始化
在第一个区块执行之前,链必须以一个称为 genesis 的初始状态启动,该状态定义在genesis.json 中。创世是对 KVStore 的第一次写入,它决定了在任何交易运行之前,每个模块的状态如何存在。
在 InitChain 期间,BaseApp 会调用每个模块的 InitGenesis 来填充其存储:
DefaultGenesis、ValidateGenesis、InitGenesis、ExportGenesis)以及初始化顺序的信息,请参见 模块简介 和 交易生命周期。
如需查看模块中创世实现的实践演示,请参见“构建模块”教程中的 步骤 2:Proto 文件 和 步骤 8:module.go。
后续步骤
如需了解有关存储、裁剪策略和存储配置的更多信息,请参见 store 规范。如需查看完整的存储接口定义,请参见 SDK 源码中的store/types/store.go。
由于 KV 存储只保存原始字节,模块在写入结构化数据之前必须先对其进行序列化。下一节 编码与 Protobuf 将说明 Cosmos SDK 如何使用 Protocol Buffers 以确定性方式对这些数据进行编码,以及为什么每个验证者都必须生成完全相同的字节。
In the previous section, you learned that modules define business logic and that keepers are responsible for reading and writing module state. This page explains how that state is actually stored, committed, and made verifiable across the network.
What is state?
State is the persistent data of the blockchain: account balances, delegations, governance proposals, module parameters, and any other data that survives between blocks. When a transaction executes, modules update state. When a block is committed, that updated state becomes the starting point for the next block:The KVStore model
At its lowest level, the Cosmos SDK stores state as key-value pairs. Both keys and values are byte arrays. Modules encode structured data into those bytes using Protocol Buffers, and decode them back when reading. See Encoding and Protobuf for details on how modules serialize data into bytes. The following example shows a conceptual example of how the bank module stores balances:x/bank/types/keys.go for the actual implementation.
Each module owns its own namespace in the key-value store. Keys are defined by the module and typically begin with a byte prefix that distinguishes them from other module keys.
Multistore
A single module store is only part of the picture. At the application level, all module stores are committed together. Every module has its own KVStore, and all module stores are mounted inside a multistore that is committed as a single state root. A module can only read and write to its own store through its keeper. Access is gated by aStoreKey, which is a typed capability object registered at app startup. Modules that don’t hold the key cannot open the store.
This isolation follows an object-capabilities model:
- Modules cannot directly mutate another module’s state
- Cross-module interaction must go through exposed keeper methods
How state is stored (IAVL and commit stores)
Each module’s KVStore is backed by aCommitKVStore. See the store spec for more details.
In the current SDK store implementation described here, the Cosmos SDK uses IAVL, a versioned AVL Merkle tree.
IAVL gives every read and write of the tree O(log n) complexity, meaning the time to read or write a key scales with the height of the tree, not the total number of keys. It also versions state on each block commit, and produces deterministic root hashes that can be used to generate Merkle proofs for light clients.
Each block commit produces a new tree version with a new root hash:
App hash
The app hash is the cryptographic root hash of the application’s committed state. It summarizes all module stores together through theCommitMultiStore. Because every validator executes the same state transitions deterministically, they should all compute the same app hash for a given block.
Database backend
The IAVL tree does not store data in memory. It writes versioned nodes to a database backend, which is a key-value store on disk. The Cosmos SDK uses CometBFT’sdb package to abstract over the database implementation. The default backend is goleveldb. Other supported backends include PebbleDB, RocksDB, and memDB (in-memory, for testing).
The database backend is selected at node startup and configured in app.toml. Application code never interacts with it directly; the store layer owns that boundary.
Store types in the SDK
Beyond the base KVStore, the SDK provides several specialized store wrappers.CommitKVStore (persistent store)
TheCommitKVStore is the main persistent store backed by IAVL. It persists across blocks, produces versioned commits, and contributes to the app hash.
CacheMultiStore (transaction isolation)
Before executing each transaction, the Cosmos SDK’sBaseApp creates a CacheMultiStore — a cached, copy-on-write view of the multistore.
All writes during that transaction occur in this cached layer:
- If the transaction succeeds, changes are written to the underlying store.
- If the transaction fails, the cache is discarded and no state changes are committed.
Ephemeral store types
Transient stores are cleared at the end of each block. They are used for temporary per-block data such as counters or intermediate calculations, and do not affect the app hash. Memory stores survive block commits but reset when the node restarts — theirCommit() is a no-op and data is never written to disk. They are used for in-process caching of data that is expensive to recompute each block but does not need to survive a restart. Modules access them via MemoryStoreKey, mounted with MountMemoryStores in app.go.
| Store type | Survives block commit | Survives restart |
|---|---|---|
| Transient | No (cleared each block) | No |
| Memory | Yes | No |
| IAVL (CommitKVStore) | Yes | Yes |
Gas and trace store wrappers
All store accesses are wrapped with additional behavior by theGasKVStore and TraceKVStore wrappers.
GasKVStorecharges gas for each read and writeTraceKVStorelogs each store operation for debugging
Prefix store
A prefix store wraps a KVStore and automatically prepends a fixed byte prefix to every key. This lets keepers scope their reads and writes to a sub-namespace without manually constructing prefixed keys on every call.Collections API (typed state access)
In the Cosmos SDK, modules commonly use the collections API to define typed state access. Instead of manually constructing byte keys, modules define typed collections such as:collections.Item[T]collections.Map[K, V]collections.Sequence
collections/collections.go for the base interface definitions. For the full package guide, see Collections.
How modules access state
Modules do not interact with the multistore directly. Instead, each module defines a keeper that opens its KVStore through the executionContext it receives on each call. For details on how Context carries the store reference at runtime, see Execution Context.
A keeper typically holds:
- the module’s store key (an object-capability used to open the module’s
KVStorefromContext), - a Protobuf codec used to encode and decode values stored as bytes,
- references (interfaces) to other keepers the module depends on.
Genesis and chain initialization
Before the first block executes, the chain must start with an initial state called genesis, defined ingenesis.json. Genesis is the first write to the KVStores — it is how every module’s state exists before any transaction runs.
During InitChain, BaseApp calls each module’s InitGenesis to populate its store:
DefaultGenesis, ValidateGenesis, InitGenesis, ExportGenesis) and initialization ordering, see Intro to Modules and Transaction Lifecycle.
For a walkthrough of genesis implementation in a module, see Step 2: Proto files and Step 8: module.go in the Build a Module tutorial.
Next steps
For more information on stores, pruning strategies, and store configuration, see the store spec. For the full store interface definitions, seestore/types/store.go in the SDK source.
Because KV stores only hold raw bytes, modules must serialize structured data before writing it. The next section, Encoding and Protobuf, explains how the Cosmos SDK uses Protocol Buffers to encode that data deterministically, and why every validator must produce exactly the same bytes.