摘要
本标准文档规定了跨链查询模块的数据结构和状态机处理逻辑,该模块允许 IBC 启用链之间进行跨链查询。概览与基本概念
动机
我们预期链上应用会依赖于从其他链读取的数据,例如,一条链上的某个应用可能需要知道第二条链上某个代币的当前价格。尽管 IBC 协议使链上应用能够与其他链通信,但如果只是用它来查询链状态,成本会过高:这将要求查询链与任意其他链之间维持一个开放通道,并为每一次查询请求使用完整的 IBC 协议栈。注意,后者意味着链之间需要交换数据包,因此被查询链需要提交交易;如果查询请求负载很高,这可能会扰乱其运行。跨链查询解决了这个问题。它使链上应用能够无缝查询其他链的状态:无需被查询链参与,并且对查询链的要求非常低。定义
Querying chain:希望从另一条链(被查询链)获取数据的链。查询链是实现跨链查询模块的链。
Queried chain:其状态被查询的链。被查询链通过中继器使用其 RPC 客户端进行查询,随后结果会被提交回查询链。
Cross-chain Queries Module:实现跨链查询协议的模块。只有查询链需要集成它。
Height 以及与客户端相关的函数定义见 ICS 2。
newCapability 和 authenticateCapability 定义见 ICS 5。
CommitmentPath 和 CommitmentProof 定义见 ICS 23。
Identifier、get、set、delete、getCurrentHeight 以及与模块系统相关的基础原语定义见 ICS 24。
Fee 定义见 ICS 29。
系统模型与属性
假设
- 安全链: 查询链和被查询链都是安全的。这意味着对每一条链而言,其底层共识引擎满足安全性(例如,链不会分叉),并且状态机的执行遵循本文描述的协议。
- 活跃链: 查询链和被查询链都 MUST 保持活跃,也就是说,链上最终会持续产生新区块。
- 抗审查的查询链: 查询链不能选择性地忽略有效交易。
例如,这意味着如果中继器向查询链提交了一笔有效交易,则该交易最终一定会被包含进某个已提交的区块。注意,Tendermint 当前并不保证这一点。
- 正确的中继器: 在查询链和被查询链之间,至少存在一个活跃的中继器,并且该中继器会正确遵循协议。
在本规范的语境下,这意味着对于来自查询链的每一个查询请求,至少存在一个中继器会:(i) 获取该查询请求,(ii) 在被查询链上执行查询,以及 (iii) 将结果连同有效证明一起,通过交易提交给查询链。上述假设足以保证:如果查询链对查询结果进行无界等待,则查询协议会向应用返回结果。尽管如此,本规范考虑的是查询链在固定时间后超时的情况。因此,为了保证查询协议始终向应用返回查询结果,本规范还要求额外假设:查询链以及至少一个正确的中继器都必须及时运行。
- 及时的查询链: 从交易被提交到该链,到该链提交包含该交易的区块之间,存在一个时间上界。
- 及时的中继器: 对于正确且活跃的中继器,从中继器获取查询请求到提交查询结果之间,存在一个时间上界。
因此请注意,为了保证查询协议始终向应用返回结果,查询链上的超时上界应至少等于 及时的查询链 和 及时的中继器 两项假设时间上界之和。这样可以保证中继器提交查询结果交易且查询链在规定的超时上界内处理该交易。
期望属性
无许可
查询链可以在无需对方许可的情况下查询另一条链,并且无需任何第三方或链治理批准即可实现跨链查询。注意,由于链之间不存在事先协商,查询链不能假设被查询的数据一定符合预期格式。最小化被查询链工作量
任何提供查询支持的链都可以作为被查询链,无需额外的实现工作,也不需要任何额外模块。这是通过利用中继器上的 RPC 客户端实现的。模块化
支持跨链查询应当像在你的链中实现一个模块一样简单。激励机制
系统会支付赏金,以激励中继器参与跨链查询:从被查询链获取数据,并将其(连同证明)提交给查询链。技术规范
总体设计
查询链必须实现跨链查询模块,从而允许查询链查询被查询链上的状态。 跨链查询依赖于在两条链之间运行的中继器。当查询链收到查询请求时,跨链查询模块会发出一个sendQuery 事件。在查询链与被查询链之间运行的中继器必须监听查询链上的 sendQuery 事件。最终,某个中继器会获取该查询请求并在被查询链上执行它,也就是获取数据并生成相应证明。随后,中继器会通过一笔交易将结果提交到查询链。最终,该结果由跨链查询模块在查询链上登记。
查询请求中包含被查询链上必须执行该查询的高度。原因在于,被查询的键在不同高度可能具有不同的值。因此,恶意中继器可能会选择一个对自己有利的高度进行查询。通过让查询链决定查询执行的高度,我们可以防止中继器影响结果数据。
注意,这一机制并不能防止跨链 MEV(最大可提取价值):如果查询高度位于未来,仍然可能通过改变被查询链上的状态来修改查询结果,从而产生可利用空间。
数据结构
跨链查询模块在处理查询请求时会存储这些请求。CrossChainQuery 是用于表示查询请求的一个特定接口。当其结果被提交时,该请求会被取回。
id字段在查询链上唯一标识该查询。path字段是被查询链上要查询的路径。localTimeoutHeight字段指定查询链上的一个高度限制,超过该高度后,该查询会被视为失败,并应向原始调用方返回超时结果。localTimeoutTimestamp字段指定查询链上的一个时间戳限制,超过该时间戳后,该查询会被视为失败,并应向原始调用方返回超时结果。queryHeight字段是中继器必须在被查询链上执行查询的高度。clientId字段标识查询链上对应被查询链的客户端。bounty字段是给予参与该查询的中继器的赏金。
QueryResult 类型定义如下:
- 返回值的查询会被标记为
SUCCESS。这意味着该查询已在被查询链上执行,并且在请求高度下,被查询路径存在对应的值。 - 已执行但未返回值的查询会被标记为
FAILURE。这意味着该查询已在被查询链上执行,但在请求高度下,被查询路径不存在对应的值。 - 在结果被提交到查询链之前已经超时的查询会被标记为
TIMEOUT。
CrossChainQueryResult 是用于表示查询结果的一个特定接口。
id字段在查询链上唯一标识该查询。result字段表示该查询是否已在被查询链上正确执行,以及被查询路径是否存在。- 当
result = SUCCESS时,data字段是一个不透明字节串,包含被查询路径关联的值。
存储路径
查询路径
查询路径是一个私有路径,用于存储进行中的跨链查询状态。结果查询路径
结果查询路径是一个私有路径,用于存储已完成查询的结果。辅助函数
查询链 MUST 实现一个generateIdentifier 函数,用于生成唯一的查询标识符:
子协议
查询生命周期
- 当查询链收到查询请求时,它会调用跨链查询模块的
CrossChainQueryRequest。该函数会为查询生成唯一标识符,将其存储到privateStore中,并发出一个sendQuery事件。查询请求既可以作为交易提交到查询链,也可以直接作为BeginBlock和EndBlock逻辑的一部分执行。通常,查询请求将由其他 IBC 模块发起。 - 一个正确的中继器在监听到查询链发出的
sendQuery事件后,最终会获取该查询请求并在被查询链上执行它。随后,结果会通过交易提交到查询链。 - 当查询结果在查询链上被提交后,会调用跨链查询模块的
CrossChainQueryResponse函数。 CrossChainQueryResponse首先使用查询的唯一标识符从privateStore中取回该查询。然后,它继续使用本地客户端验证结果。如果验证通过,该函数会从privateStore中移除该查询,并将结果存入私有存储。
查询链在收到查询结果时,可能会执行额外的状态机逻辑。为将这部分额外状态机逻辑考虑在内,并向查询调用方收取费用,符合本规范的实现可以使用CrossChainQuery接口中现有的bounty字段,或者为该接口扩展一个额外字段。
- 然后,查询调用方可以异步获取查询结果。
PruneCrossChainQueryResult函数允许查询调用方在取回结果后,将其从存储中清除。
常规路径方法
当发起查询的链上的跨链查询模块接收到新的查询请求时,会调用CrossChainQueryRequest 函数。
- 前置条件
- 存在一个标识符为
clientId的客户端。
- 存在一个标识符为
- 后置条件
- 查询请求被存储在
privateStore中。 - 发出一个
sendQuery事件。
- 查询请求被存储在
CrossChainQueryResponse 函数。
我们传入将查询结果提交到发起查询链上的中继者地址,以便按需提供一些奖励。这为费用支付提供了基础,也可以用于其他机制(例如计算排行榜)。
- 前置条件
- 存在一个标识符为
clientId的客户端。 - 在
privateStore中存在一个由queryId标识的查询请求。
- 存在一个标识符为
- 后置条件
- 由
queryId标识的查询请求会从privateStore中删除。 - 查询结果会存储在
privateStore中。
- 由
PruneCrossChainQueryResult 函数。
- 前置条件
- 在
privateStore中存在一个由queryId标识的查询结果。 - 调用方有权清理该查询结果。
- 在
- 后置条件
- 由
queryId标识的查询结果会从privateStore中删除。
- 由
超时
查询请求带有关联的localTimeoutHeight 和 localTimeoutTimestamp 字段,用于指定发起查询链上的高度和时间戳上限;超过该上限后,查询会被视为失败。
关于如何处理超时,有多种方案。例如,中继者可以将超时通知作为交易提交给发起查询的链。由于中继者不受信任,对于每一个此类通知,发起查询链上的跨链查询模块都必须调用 checkQueryTimeout 来检查该查询是否确实已经超时。另一种方案是由跨链查询模块负责在每个区块开始时遍历进行中的查询并调用 checkQueryTimeout,以检查是否有查询已超时。在这种情况下,进行中的查询应按 localTimeoutTimestamp 和 localTimeoutHeight 建立索引存储,以便更高效地遍历。这些都属于实现细节,不在本规范覆盖范围内。
假设由中继者负责将超时通知作为交易提交。checkQueryTimeout 函数如下所示。注意,
这里和 CrossChainQueryResponse 一样传入中继者地址,以便同样支持可能的激励机制。
- 前置条件
- 在
privateStore中存在一个由queryId标识的查询请求。
- 在
- 后置条件
- 如果查询确实已经超时,则:
- 由
queryId标识的查询请求会从privateStore中删除; - 查询已超时这一事实会记录到
privateStore中。
- 由
- 如果查询确实已经超时,则:
历史
2022 年 1 月 6 日 - 首个草案 2022 年 5 月 11 日 - 重大修订 2022 年 6 月 14 日 - 增加清理机制、localTimeoutTimestamp,并加入用于激励的中继者地址
2022 年 7 月 28 日 - 对假设进行修订
版权
此处所有内容均基于 Apache 2.0 许可。Synopsis
This standard document specifies the data structures and state machine handling logic of the Cross-chain Queries module, which allows for cross-chain querying between IBC enabled chains.Overview and Basic Concepts
Motivation
We expect on-chain applications to depend on reads from other chains, e.g., a particular application on a chain may need to know the current price of the token of a second chain. While the IBC protocol enables on-chain applications to talk to other chains, using it for simply querying the state of chains would be too expensive: it would require to maintain an open channel between the querying chain and any other chain, and use the full IBC stack for every query request. Note that the latter implies exchanging packets between chains and therefore committing transactions at the queried chain, which may disrupt its operation if the load of query requests is high. Cross-chain queries solve this issue. It enables on-chain applications to query the state of other chains seamlessly: without involving the queried chain, and requiring very little from the querying chain.Definitions
Querying chain: The chain that is interested in getting data from another chain (queried chain). The querying chain is the chain that implements the Cross-chain Queries module.
Queried chain: The chain whose state is being queried. The queried chain gets queried via a relayer utilizing its RPC client which is then submitted back to the querying chain.
Cross-chain Queries Module: The module that implements the cross-chain querying protocol. Only the querying chain integrates it.
Height and client-related functions are as defined in ICS 2.
newCapability and authenticateCapability are as defined in ICS 5.
CommitmentPath and CommitmentProof are as defined in ICS 23.
Identifier, get, set, delete, getCurrentHeight, and module-system related primitives are as defined in ICS 24.
Fee is as defined in ICS 29.
System Model and Properties
Assumptions
- Safe chains: Both the querying and queried chains are safe. This means that, for every chain, the underlying consensus engine satisfies safety (e.g., the chain does not fork) and the execution of the state machine follows the described protocol.
- Live chains: Both the querying and queried chains MUST be live, i.e., new blocks are eventually added to the chain.
- Censorship-resistant querying chain: The querying chain cannot selectively omit valid transactions.
For example, this means that if a relayer submits a valid transaction to the querying chain, the transaction is guaranteed to be eventually included in a committed block. Note that Tendermint does not currently guarantee this.
- Correct relayer: There is at least one live relayer between the querying and queried chains where the relayer correctly follows the protocol.
In the context of this specification, this implies that for every query request coming from the querying chain, there is at least one relayer that (i) picks the query request up, (ii) executes the query at the queried chain, and (iii) submits the result in a transaction, together with a valid proof, to the querying chain.The above assumptions are enough to guarantee that the query protocol returns results to the application if the querying chain waits unboundly for query results. Nevertheless, this specification considers the case when the querying chain times out after a fixed period of time. Thus, to guarantee that the query protocol always returns query results to the application, the specification requires additional assumptions: both the querying chain and at least one correct relayer have to behave timely.
- Timely querying chain: There exists an upper-bound in the time elapsed between the moment a transaction is submitted to the chain and when the chain commits a block including it.
- Timely relayer: For correct and live relayers, there exists an upper-bound in the time elapsed between the moment a relayer picks a query request and when the relayer submits the query result.
Note then that to guarantee that the query protocol always returns results to the application, the timeout bound at the querying chain should be at least equal to the sum of the upper-bounds of assumptions Timely querying chain and Timely relayer. This would guarantee that the relayer submits and the querying chain process a query result transaction within the specified timeout bound.
Desired Properties
Permissionless
The querying chain can query a chain without permission from the latter and implement cross-chain querying without any approval from a third party or chain governance. Note that since there is no prior negotiation between chains, the querying chain cannot assume that queried data will be in an expected format.Minimal queried chain Work
Any chain that provides query support can act as a queried chain, requiring no implementation work or any extra module. This is possible by utilizing an RPC client on a relayer.Modular
Supporting cross-chain queries should be as easy as implementing a module in your chain.Incentivization
A bounty is paid to incentivize relayers for participating in cross-chain queries: fetching data from the queried chain and submitting it (together with proofs) to the querying chain.Technical Specification
General Design
The querying chain must implement the Cross-chain Queries module, which allows the querying chain to query state at the queried chain. Cross-chain queries rely on relayers operating between both chains. When a query request is received by the querying chain, the Cross-chain Queries module emits asendQuery event. Relayers operating between the querying and queried chains must monitor the querying chain for sendQuery events. Eventually, a relayer will retrieve the query request and execute it, i.e., fetch the data and generate the corresponding proofs, at the queried chain. The relayer then submits the result in a transaction to the querying chain. The result is finally registered at the querying chain by the Cross-chain Queries module.
A query request includes the height of the queried chain at which the query must be executed. The reason is that the keys being queried can have different values at different heights. Thus, a malicious relayer could choose to query a height that has a value that benefits it somehow. By letting the querying chain decide the height at which the query is executed, we can prevent relayers from affecting the result data.
Note that this mechanism does not prevent cross-chain MEV (maximal extractable value): this still creates an opportunity for altering the state on the queried chain if the height is in the future in order to change the results of the query.
Data Structures
The Cross-chain Queries module stores query requests when it processes them. A CrossChainQuery is a particular interface to represent query requests. A request is retrieved when its result is submitted.- The
idfield uniquely identifies the query at the querying chain. - The
pathfield is the path to be queried at the queried chain. - The
localTimeoutHeightfield specifies a height limit at the querying chain after which a query is considered to have failed and a timeout result should be returned to the original caller. - The
localTimeoutTimestampfield specifies a timestamp limit at the querying chain after which a query is considered to have failed and a timeout result should be returned to the original caller. - The
queryHeightfield is the height at which the relayer must query the queried chain - The
clientIdfield identifies the querying chain’s client of the queried chain. - The
bountyfield is a bounty that is given to the relayer for participating in the query.
QueryResult type as follows:
- A query that returns a value is marked as
SUCCESS. This means that the query has been executed at the queried chain and there was a value associated to the queried path at the requested height. - A query that is executed but does not return a value is marked as
FAILURE. This means that the query has been executed at the queried chain, but there was no value associated to the queried path at the requested height. - A query that timed out before a result is committed at the querying chain is marked as
TIMEOUT.
CrossChainQueryResult is a particular interface used to represent query results.
- The
idfield uniquely identifies the query at the querying chain. - The
resultfield indicates whether the query was correctly executed at the queried chain and if the queried path exists. - The
datafield is an opaque bytestring that contains the value associated with the queried path in caseresult = SUCCESS.
Store paths
Query path
The query path is a private path that stores the state of ongoing cross-chain queries.Result query path
The result query path is a private path that stores the result of completed queries.Helper functions
The querying chain MUST implement a functiongenerateIdentifier, which generates a unique query identifier:
Sub-protocols
Query lifecycle
- When the querying chain receives a query request, it calls
CrossChainQueryRequestof the Cross-chain Queries module. This function generates a unique identifier for the query, stores it in itsprivateStoreand emits asendQueryevent. Query requests can be submitted as transactions to the querying chain or simply executed as part of theBeginBlockandEndBlocklogic. Typically, query requests will be issued by other IBC modules. - A correct relayer listening to
sendQueryevents from the querying chain will eventually pick the query request up and execute it at the queried chain. The result is then submitted in a transaction to the querying chain. - When the query result is committed at the querying chain, this calls the
CrossChainQueryResponsefunction of the Cross-chain Queries module. - The
CrossChainQueryResponsefirst retrieves the query from theprivateStoreusing the query’s unique identifier. It then proceeds to verify the result using its local client. If it passes the verification, the function removes the query from theprivateStoreand stores the result in the private store.
The querying chain may execute additional state machine logic when a query result is received. To account for this additional state machine logic and charge a fee to the query caller, an implementation of this specification could use the already existingbountyfield of theCrossChainQueryinterface or extend the interface with an additional field.
- The query caller can then asynchronously retrieve the query result. The function
PruneCrossChainQueryResultallows a query caller to prune the result from the store once it retrieves it.
Normal path methods
TheCrossChainQueryRequest function is called when the Cross-chain Queries module at the querying chain receives a new query request.
- Precondition
- There exists a client with
clientIdidentifier.
- There exists a client with
- Postcondition
- The query request is stored in the
privateStore. - A
sendQueryevent is emitted.
- The query request is stored in the
CrossChainQueryResponse function is called when the Cross-chain Queries module at the querying chain receives a new query reply.
We pass the address of the relayer that submitted the query result to the querying chain to optionally provide some rewards. This provides a foundation for fee payment, but can be used for other techniques as well (like calculating a leaderboard).
- Precondition
- There exists a client with
clientIdidentifier. - There is a query request stored in the
privateStoreidentified byqueryId.
- There exists a client with
- Postcondition
- The query request identified by
queryIdis deleted from theprivateStore. - The query result is stored in the
privateStore.
- The query request identified by
PruneCrossChainQueryResult function is called when the caller of a query has retrieved the result and wants to delete it.
- Precondition
- There is a query result stored in the
privateStoreidentified byqueryId. - The caller has the right to clean the query result
- There is a query result stored in the
- Postcondition
- The query result identified by
queryIdis deleted from theprivateStore.
- The query result identified by
Timeouts
Query requests have associated alocalTimeoutHeight and a localTimeoutTimestamp field that specifies the height and timestamp limit at the querying chain after which a query is considered to have failed.
There are several alternatives on how to handle timeouts. For instance, the relayer could submit timeout notifications as transactions to the querying chain. Since the relayer is untrusted, for each of these notifications, the Cross-chain Queries module of the querying chain MUST call the checkQueryTimeout to check if the query has indeed timed out. An alternative could be to make the Cross-chain Queries module responsible for checking if any query has timed out by iterating over the ongoing queries at the beginning of a block and calling checkQueryTimeout. In this case, ongoing queries should be stored indexed by localTimeoutTimestamp and localTimeoutHeight to allow iterating over them more efficiently. These are implementation details that this specification does not cover.
Assume that the relayer is in charge of submitting timeout notifications as transactions. The checkQueryTimeout function would look as follows. Note that
we pass the relayer address just as in CrossChainQueryResponse to allow for possible incentivization here as well.
- Precondition
- There is a query request stored in the
privateStoreidentified byqueryId.
- There is a query request stored in the
- Postcondition
- If the query has indeed timed out, then
- the query request identified by
queryIdis deleted from theprivateStore; - the fact that the query has timed out is recorded in the
privateStore.
- the query request identified by
- If the query has indeed timed out, then