CometBFT 允许你为交易和区块建立索引,并在之后查询或订阅它们的结果。交易通过 ResponseFinalizeBlock.tx_results.events 建立索引,区块通过 ResponseFinalizeBlock.events 建立索引。不过,交易还会通过一个主键建立索引,该主键包含交易哈希,并映射到且存储相应的交易结果。区块也会通过一个主键建立索引,该主键包含区块高度,并映射到且存储区块高度,也就是说,区块本身永远不会被存储。 每个事件都包含一个类型和一组属性,属性是键值对,用来表示该方法执行期间发生了什么。有关 Events 的更多细节,请参阅 [ABCI][/cometbft/latest/spec/abci/Outline#events] 文档。 一个 Event 会关联一个复合键。compositeKey 由事件类型和键组成,中间用点号分隔。 例如:
"jack": [
  "account.number": 100
]
它等价于复合键 jack.account.number。 默认情况下,CometBFT 会按各自的哈希和高度为所有交易建立索引,并按高度为区块建立索引。 CometBFT 允许同一高度内的不同事件拥有相同的属性。

配置

运维人员可以通过 [tx_index] 配置索引。indexer 字段接受一组受支持的索引器。如果其中包含 null,无论同时提供了什么其他值,索引都会被关闭。
[tx-index]

# The backend database to back the indexer.
# If indexer is "null", no indexer service will be used.
#
# The application will set which txs to index. In some cases a node operator will be able
# to decide which txs to index based on configuration set in the application.
#
# Options:
#   1) "null"
#   2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
#     - When "kv" is chosen "tx.height" and "tx.hash" will always be indexed.
#   3) "psql" - the indexer services backed by PostgreSQL.
# indexer = "kv"

支持的索引器

KV

kv 索引器类型是一个嵌入式键值存储,由 CometBFT 底层主数据库支持。使用 kv 索引器类型时,你可以直接通过 CometBFT 的 RPC 查询区块和交易事件。不过,查询语法比较受限,因此这种索引器类型未来可能会被弃用或完全移除。 实现与数据布局 kv 索引器会为事件的每个属性分别存储一条记录,方法是创建一个包含以下部分的复合键:
  • 事件类型
  • 属性键
  • 属性值
  • 事件生成器(例如 FinalizeBlock)
  • 高度
  • 事件计数器
例如,下面这些事件:
Type: "transfer",
  Attributes: []abci.EventAttribute{
   {Key: "sender", Value: "Bob", Index: true},
   {Key: "recipient", Value: "Alice", Index: true},
   {Key: "balance", Value: "100", Index: true},
   {Key: "note", Value: "nothing", Index: true},
   },

Type: "transfer",
  Attributes: []abci.EventAttribute{
   {Key: "sender", Value: "Tom", Index: true},
   {Key: "recipient", Value: "Alice", Index: true},
   {Key: "balance", Value: "200", Index: true},
   {Key: "note", Value: "nothing", Index: true},
   },
如果这些事件来自高度 1 的 FinalizeBlock 调用,那么它们会在存储中表示为:
Key                                 value
---- event1 ------
transferSenderBobFinalizeBlock11           1
transferRecipientAliceFinalizeBlock11      1
transferBalance100FinalizeBlock11          1
transferNoteNothingFinalizeBlock11         1
---- event2 ------
transferSenderTomFinalizeBlock12           1
transferRecipientAliceFinalizeBlock12      1
transferBalance200FinalizeBlock12          1
transferNoteNothingFinalizeBlock12         1

事件编号是索引器维护的一个局部变量,每处理一个新事件就会递增一次。 它是一个 int64 变量,除了用于在同一高度内关联属于同一事件的属性之外,没有其他语义。 由于事件索引是确定性的,这个变量不会以原子方式递增。如果这一点将来发生变化,事件 ID 的生成就会失效。

PostgreSQL

psql 索引器类型允许运维人员通过代理到外部 PostgreSQL 实例来启用区块和交易事件索引,从而以关系模型的形式存储这些事件。由于事件被存储在关系型数据库管理系统中,运维人员可以利用 SQL 执行一系列 kv 索引器类型不支持的丰富而复杂的查询。由于运维人员可以直接使用 SQL,CometBFT 的 RPC 不支持通过 psql 索引器类型进行搜索,任何此类查询都会失败。 请注意,SQL schema 存放在 state/indexer/sink/psql/schema.sql 中,运维人员必须在启动 CometBFT 并启用 psql 索引器类型之前显式创建这些关系。 示例:
psql ... -f state/indexer/sink/psql/schema.sql

默认索引

CometBFT 的交易和区块事件索引器默认会为少量保留事件建立索引。

交易

默认会建立以下索引:
  • tx.height
  • tx.hash

区块

默认会建立以下索引:
  • block.height

添加事件

应用可以自由定义要建立索引的事件。CometBFT 本身不提供用于定义哪些事件应建立索引、哪些事件应忽略的功能。在你的应用 FinalizeBlock 方法中,添加 Events 字段,并填入 UTF-8 编码字符串的键值对(例如,“transfer.sender”: “Bob”、“transfer.recipient”: “Alice”、“transfer.balance”: “100”)。 示例:
func (app *Application) FinalizeBlock(_ context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {

    //...
  tx_results[0] := &types.ExecTxResult{
			Code: CodeTypeOK,
			// With every transaction we can emit a series of events. To make it simple, we just emit the same events.
			Events: []types.Event{
				{
					Type: "app",
					Attributes: []types.EventAttribute{
						{Key: "creator", Value: "Cosmoshi Netowoko", Index: true},
						{Key: "key", Value: key, Index: true},
						{Key: "index_key", Value: "index is working", Index: true},
						{Key: "noindex_key", Value: "index is working", Index: false},
					},
				},
				{
					Type: "app",
					Attributes: []types.EventAttribute{
						{Key: "creator", Value: "Cosmoshi", Index: true},
						{Key: "key", Value: value, Index: true},
						{Key: "index_key", Value: "index is working", Index: true},
						{Key: "noindex_key", Value: "index is working", Index: false},
					},
				},
			},
		}

    block_events = []types.Event{
			{
				Type: "loan",
				Attributes: []types.EventAttribute{
					{	Key:   "account_no", Value: "1", Index: true},
					{ Key:   "amount", Value: "200", Index: true },
				},
			},
			{
				Type: "loan",
				Attributes: []types.EventAttribute{
					{ Key:   "account_no", Value: "2",	Index: true },
					{ Key:   "amount", Value: "300", Index: true},
				},
			},
		}
    return &types.ResponseFinalizeBlock{TxResults: tx_results, Events: block_events}
}
如果索引器不是 null,该交易就会被建立索引。每个事件都会使用如下形式的复合键建立索引:{eventType}.{eventAttribute}={eventValue},例如 transfer.sender=bob。

查询交易事件

你可以通过调用 /tx_search RPC 端点,根据事件查询一组分页交易:
curl "localhost:26657/tx_search?query=\"message.sender='cosmos1...'\"&prove=true"
有关查询语法和其他选项的更多信息,请参阅 tx_search 端点的 RPC API reference。

订阅交易

客户端可以通过 WebSocket 向 /subscribe RPC 端点提供查询条件,以订阅带有指定标签的交易。
{
  "jsonrpc": "2.0",
  "method": "subscribe",
  "id": "0",
  "params": {
    "query": "message.sender='cosmos1...'"
  }
}
有关查询语法和其他选项的更多信息,请参阅 RPC API documentation。

查询区块事件

你可以通过调用 /block_search RPC 端点,根据事件查询一组分页区块:
curl "localhost:26657/block_search?query=\"block.height > 10\""
事件序列的存储是在 CometBFT 0.34.26 中引入的。在此之前,一直到 Tendermint Core 0.34.26,事件序列都不会存储在 kvstore 中,事件仅按高度存储。这意味着,查询返回的区块和交易只要事件属性在同一高度内匹配即可,即使这些属性实际上可能来自该高度内不同的事件。 这一行为在 CometBFT 0.34.26+ 中已被修复。不过,如果数据是在更早版本的 Tendermint Core 中建立索引且未重新建立索引,那么查询这些数据时,会把同一高度内的所有属性视为发生在同一个事件中。

事件属性值类型

用户可以将任意内容用作事件值。不过,如果事件属性值是数字,则需要注意以下几点:
  • 负数在查询索引器时无法被正确检索。
  • 事件值会被转换为大浮点数(来自 big/math 包)。浮点数的精度会设置为它所表示整数的位长度,以确保不会因为精度不足而丢失信息。在 CometBFT v0.38.x 之前并没有这一行为,当时所有浮点值都会被忽略。
  • 从 CometBFT v0.38.x 开始,查询中也可以包含浮点数。
  • 需要注意的是,当小数位很多时,与浮点数进行比较可能并不精确。

事件类型与属性键格式

事件类型/属性键是一个字符串,可以包含任意 Unicode 字母或数字,以及以下字符:.(点号)、-(短横线)、_(下划线)。事件类型/属性键不能以 -(短横线)或 .(点号)开头。
^[\w]+[\.-\w]?$

CometBFT allows you to index transactions and blocks and later query or subscribe to their results. Transactions are indexed by ResponseFinalizeBlock.tx_results.events and blocks are indexed by ResponseFinalizeBlock.events. However, transactions are also indexed by a primary key which includes the transaction hash and maps to and stores the corresponding transaction results. Blocks are indexed by a primary key which includes the block height and maps to and stores the block height, i.e., the block itself is never stored. Each event contains a type and a list of attributes, which are key-value pairs denoting something about what happened during the method’s execution. For more details on Events, see the [ABCI][/cometbft/latest/spec/abci/Outline#events] documentation. An Event has a composite key associated with it. A compositeKey is constructed by its type and key separated by a dot. For example:
"jack": [
  "account.number": 100
]
would be equal to the composite key of jack.account.number. By default, CometBFT will index all transactions by their respective hashes and height and blocks by their height. CometBFT allows for different events within the same height to have equal attributes.

Configuration

Operators can configure indexing via the [tx_index] section. The indexer field takes a series of supported indexers. If null is included, indexing will be turned off regardless of other values provided.
[tx-index]

# The backend database to back the indexer.
# If indexer is "null", no indexer service will be used.
#
# The application will set which txs to index. In some cases a node operator will be able
# to decide which txs to index based on configuration set in the application.
#
# Options:
#   1) "null"
#   2) "kv" (default) - the simplest possible indexer, backed by key-value storage (defaults to levelDB; see DBBackend).
#     - When "kv" is chosen "tx.height" and "tx.hash" will always be indexed.
#   3) "psql" - the indexer services backed by PostgreSQL.
# indexer = "kv"

Supported Indexers

KV

The kv indexer type is an embedded key-value store supported by the main underlying CometBFT database. Using the kv indexer type allows you to query for block and transaction events directly against CometBFT’s RPC. However, the query syntax is limited, and so this indexer type might be deprecated or removed entirely in the future. Implementation and data layout The kv indexer stores each attribute of an event individually by creating a composite key with:
  • event type,
  • attribute key,
  • attribute value,
  • event generator (e.g., FinalizeBlock),
  • the height, and
  • event counter.
For example, the following events:
Type: "transfer",
  Attributes: []abci.EventAttribute{
   {Key: "sender", Value: "Bob", Index: true},
   {Key: "recipient", Value: "Alice", Index: true},
   {Key: "balance", Value: "100", Index: true},
   {Key: "note", Value: "nothing", Index: true},
   },

Type: "transfer",
  Attributes: []abci.EventAttribute{
   {Key: "sender", Value: "Tom", Index: true},
   {Key: "recipient", Value: "Alice", Index: true},
   {Key: "balance", Value: "200", Index: true},
   {Key: "note", Value: "nothing", Index: true},
   },
will be represented as follows in the store, assuming these events result from the FinalizeBlock call for height 1:
Key                                 value
---- event1 ------
transferSenderBobFinalizeBlock11           1
transferRecipientAliceFinalizeBlock11      1
transferBalance100FinalizeBlock11          1
transferNoteNothingFinalizeBlock11         1
---- event2 ------
transferSenderTomFinalizeBlock12           1
transferRecipientAliceFinalizeBlock12      1
transferBalance200FinalizeBlock12          1
transferNoteNothingFinalizeBlock12         1

The event number is a local variable kept by the indexer and incremented when a new event is processed. It is an int64 variable and has no other semantics besides being used to associate attributes belonging to the same events within a height. This variable is not atomically incremented as event indexing is deterministic. Should this ever change, the event ID generation will be broken.

PostgreSQL

The psql indexer type allows an operator to enable block and transaction event indexing by proxying it to an external PostgreSQL instance, allowing for the events to be stored in relational models. Since the events are stored in an RDBMS, operators can leverage SQL to perform a series of rich and complex queries that are not supported by the kv indexer type. Since operators can leverage SQL directly, searching is not enabled for the psql indexer type via CometBFT’s RPC—any such query will fail. Note that the SQL schema is stored in state/indexer/sink/psql/schema.sql, and operators must explicitly create the relations prior to starting CometBFT and enabling the psql indexer type. Example:
psql ... -f state/indexer/sink/psql/schema.sql

Default Indexes

The CometBFT transaction and block event indexer indexes a few select reserved events by default.

Transactions

The following indexes are indexed by default:
  • tx.height
  • tx.hash

Blocks

The following indexes are indexed by default:
  • block.height

Adding Events

Applications are free to define which events to index. CometBFT does not expose functionality to define which events to index and which to ignore. In your application’s FinalizeBlock method, add the Events field with pairs of UTF-8 encoded strings (e.g., “transfer.sender”: “Bob”, “transfer.recipient”: “Alice”, “transfer.balance”: “100”). Example:
func (app *Application) FinalizeBlock(_ context.Context, req *types.RequestFinalizeBlock) (*types.ResponseFinalizeBlock, error) {

    //...
  tx_results[0] := &types.ExecTxResult{
			Code: CodeTypeOK,
			// With every transaction we can emit a series of events. To make it simple, we just emit the same events.
			Events: []types.Event{
				{
					Type: "app",
					Attributes: []types.EventAttribute{
						{Key: "creator", Value: "Cosmoshi Netowoko", Index: true},
						{Key: "key", Value: key, Index: true},
						{Key: "index_key", Value: "index is working", Index: true},
						{Key: "noindex_key", Value: "index is working", Index: false},
					},
				},
				{
					Type: "app",
					Attributes: []types.EventAttribute{
						{Key: "creator", Value: "Cosmoshi", Index: true},
						{Key: "key", Value: value, Index: true},
						{Key: "index_key", Value: "index is working", Index: true},
						{Key: "noindex_key", Value: "index is working", Index: false},
					},
				},
			},
		}

    block_events = []types.Event{
			{
				Type: "loan",
				Attributes: []types.EventAttribute{
					{	Key:   "account_no", Value: "1", Index: true},
					{ Key:   "amount", Value: "200", Index: true },
				},
			},
			{
				Type: "loan",
				Attributes: []types.EventAttribute{
					{ Key:   "account_no", Value: "2",	Index: true },
					{ Key:   "amount", Value: "300", Index: true},
				},
			},
		}
    return &types.ResponseFinalizeBlock{TxResults: tx_results, Events: block_events}
}
If the indexer is not null, the transaction will be indexed. Each event is indexed using a composite key in the form of {eventType}.{eventAttribute}={eventValue}, e.g., transfer.sender=bob.

Querying Transaction Events

You can query for a paginated set of transactions by their events by calling the /tx_search RPC endpoint:
curl "localhost:26657/tx_search?query=\"message.sender='cosmos1...'\"&prove=true"
Check out the RPC API reference for the tx_search endpoint for more information on query syntax and other options.

Subscribing to Transactions

Clients can subscribe to transactions with the given tags via WebSocket by providing a query to the /subscribe RPC endpoint.
{
  "jsonrpc": "2.0",
  "method": "subscribe",
  "id": "0",
  "params": {
    "query": "message.sender='cosmos1...'"
  }
}
Check out the RPC API documentation for more information on query syntax and other options.

Querying Block Events

You can query for a paginated set of blocks by their events by calling the /block_search RPC endpoint:
curl "localhost:26657/block_search?query=\"block.height > 10\""
Storing the event sequence was introduced in CometBFT 0.34.26. Before that, up until Tendermint Core 0.34.26, the event sequence was not stored in the kvstore, and events were stored only by height. That means that queries returned blocks and transactions whose event attributes matched within the height but could match across different events at that height. This behavior was fixed with CometBFT 0.34.26+. However, if the data was indexed with earlier versions of Tendermint Core and not re-indexed, that data will be queried as if all the attributes within a height occurred within the same event.

Event Attribute Value Types

Users can use anything as an event value. However, if the event attribute value is a number, the following needs to be taken into account:
  • Negative numbers will not be properly retrieved when querying the indexer.
  • Event values are converted to big floats (from the big/math package). The precision of the floating-point number is set to the bit length of the integer it is supposed to represent, so that there is no loss of information due to insufficient precision. This was not present before CometBFT v0.38.x, and all float values were ignored.
  • As of CometBFT v0.38.x, queries can contain floating-point numbers as well.
  • Note that comparing to floats can be imprecise with a high number of decimals.

Event Type and Attribute Key Format

An event type/attribute key is a string that can contain any Unicode letter or digit, as well as the following characters: . (dot), - (dash), _ (underscore). The event type/attribute key must not start with - (dash) or . (dot).
^[\w]+[\.-\w]?$