From 87f401de4962359385d16863a4e862b7a0105b0d Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Wed, 11 May 2022 17:14:39 +0200 Subject: [PATCH 01/18] first draft ibc queries --- spec/app/ics-interchain-queries/README.md | 323 ++++++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 spec/app/ics-interchain-queries/README.md diff --git a/spec/app/ics-interchain-queries/README.md b/spec/app/ics-interchain-queries/README.md new file mode 100644 index 000000000..59e3cc3f5 --- /dev/null +++ b/spec/app/ics-interchain-queries/README.md @@ -0,0 +1,323 @@ +--- +ics: TBA +title: Interchain Query +stage: draft +category: IBC/APP +requires: 2, 18, 23, 24 +kind: instantiation +author: Joe Schnetzler , Manuel Bravo +created: 2022-01-06 +modified: 2022-05-03 +--- + +## Synopsis + +This standard document specifies the data structures and state machine handling logic of the Cross-chain Querying module, which allows for cross-chain querying between IBC enabled chains. + +## Overview and Basic Concepts + +### Motivation + +Interchain Accounts (ICS-27) brings one of the most important features IBC offers, cross-chain transactions (on-chain). Limited in this functionality is the querying of state from one chain, on another chain. Adding cross-chain querying via the Cross-chain Querying module gives unlimited flexibility to chains to build IBC enabled protocols around Interchain Accounts and beyond. + +### 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 Querying 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 Querying 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. + +`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. + +## System Model and Properties + +### Assumptions + +- **Safe chains:** Both the Querying and Queried chains are safe. + +- **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 transactions. + +- **Correct relayer:** There is at least one correct relayer between the Querying and Queried chains. This is required for liveness. + +### Desired Properties + +#### Permissionless + +A Querying Chain can query a chain 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 + +A Queried Chain has to do no implementation work or add any module to enable cross-chain querying. By utilizing an RPC client on a relayer, this is possible. + +#### Modular + +Adding cross-chain querying should be as easy as implementing a module in your chain. + +#### Control Queried Data + +The Querying Chain should have ultimate control over how to handle queried data. Like querying for a certain query form/type. + +#### Incentivization + +A bounty is paid to incentivize relayers for participating in interchain queries. + +## Technical Specification + +### General Design + +The Querying Chain MUST implement the Cross-chain Querying module, which allows the Querying Chain to query state at the Queried Chain. + +Cross-chain querying relies on relayers operating between both chains. When a query request is received by the Cross-chain Querying module of the Querying Chain, this emits a `sendQuery` 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 at the Queried Chain. The relayer then submits (on-chain) the result at the Querying Chain. The result is finally stored at the Querying Chain by the Cross-chain Querying 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. + +### Data Structures + +The Cross-chain Querying 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. + +```typescript +interface CrossChainQuery struct { + id: Identifier + path: CommitmentPath + timeoutHeight: Height + queryHeight: Height + clientId: Identifier + bounty: sdk.Coin +} +``` + +- The `id` field uniquely identifies the query at the Querying Chain. +- The `path` field is the path to be queried at the Queried Chain. +- The `timeoutHeight` field 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 `queryHeigth` field is the height at which the relayer must query the Queried Chain +- The `bounty` field is a bounty that is given to the relayer for participating in the query. +- The `clientId` field identifies the Queried Chain. + +The Cross-chain Querying module stores query results to allow query callers to asynchronously retrieve them. +In this context, this ICS defines the `QueryResult` type as follows: + +```typescript +enum QueryResult { + SUCCESS, + FAILURE, + TIMEOUT, +} +``` +- 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 timeout before a result is committed at the Querying Chain is marked as `TIMEOUT`. + +A CrossChainQueryResult is a particular interface used to represent query replies. + +```typescript +interface CrossChainQueryResult struct { + id: Identifier + result: QueryResult + data: []byte +} +``` + +- The `id` field uniquely identifies the query at the Querying Chain. +- The `result` field indicates whether the query was correctly executed at the Queried Chain and if the queried path exists. +- The `data` field is an opaque bytestring that contains the value associated with the queried path in case `result = SUCCESS`. + +### Store paths + +#### Ongoing query path + +The ongoing query path is a private path that stores the state of ongoing cross-chain queries. + +```typescript +function ongoingQueryPath(id: Identifier): Path { + return "ibcquery/{id}" +} +``` +#### Result query path + +The result query path is a public path that stores the result of completed queries. + +```typescript +function resultQueryPath(id: Identifier): Path { + return "ibcqueryresult/{id}" +} +``` + +### Helper functions + +The Querying Chain MUST implement a function `generateQueryIdentifier`, which generates a unique query identifier: + +```typescript +function generateQueryIdentifier = () -> Identifier +``` + +### Sub-protocols + +#### Query lifecycle + +1) When the Cross-chain Querying module receives a query request, it calls `CrossChainQueryRequest`. This function generates a unique identifier for the query, stores it in its `privateStore` and emits a `sendQuery` event. Query requests can be submitted by other IBC modules as transactions to the Querying Chain or simply executed as part of the `BeginBlock` and `EndBlock` logic. +2) A correct relayer listening to `sendQuery` events from the Querying Chain will eventually pick the query request up and execute it at the Queried Chain. The result is then submitted (on-chain) to the Querying Chain. +3) When the query result is committed at the Querying Chain, it is handed to the Cross-chain Querying module. +4) The Cross-chain Querying module calls `CrossChainQueryResult`. This function first retrieves the query from the `privateStore` using 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 the `privateStore` and stores the result in a public path. +5) The query caller can then asynchronously retrieve the query result. + +#### Normal path methods + +The `CrossChainQueryRequest` function is called when the Cross-chain Querying module at the Querying Chain receives a new query request. + +```typescript +function CrossChainQueryRequest( + path: CommitmentPath, + queryHeigth: Heigth, + timeoutHeigth: Height, + clientId: Identifier, + bounty: sdk.Coin, + ): Identifier { + + // Check that there exists a client of the Queried Chain. The client will be used to verify the query result. + abortTransactionUnless(queryClientState(clientId) !== null) + + // Generate a unique query identifier. + queryIdentifier = generateQueryIdentifier() + + // Create a query request record. + query = CrossChainQuery{queryIdentifier, + path, + queryHeigth, + timeoutHeigth, + clientId, + bounty} + + // Store the query in the local, private store. + privateStore.set(ongoingQueryPath(queryIdentifier), query) + + // Log the query request. + emitLogEntry("sendQuery", query) + + // Returns the query identifier. + return queryIdentifier +} +``` +- **Precondition** + - There exists a client with `clientId` identifier. +- **Postcondition** + - The query request is stored in the `privateStore`. + - A `sendQuery` event is emitted. + +The `CrossChainQueryResult` function is called when the Cross-chain Querying module at the Querying Chain receives a new query reply. + +```typescript +function CrossChainQueryResult( + queryId: Identifier, + data: []byte + proof: CommitmentProof, + proofHeight: Height, + success: boolean, + delayPeriodTime: uint64, + delayPeriodBlocks: uint64 + ) { + + // Retrieve query state from the local, private store using the query's identifier. + query = privateStore.get(queryPath(queryIdentifier)) + abortTransactionUnless(query !== null) + + // Retrieve client state of the Queried Chain. + client = queryClientState(query.clientId) + abortTransactionUnless(client !== null) + + // Check that the relier executed the query at the requested height at the Queried Chain. + abortTransactionUnless(query.queryHeigth !== proofHeight) + + // Verify query result using the local light client of the Queried Chain. If success, then verify that the data is indeed the value associated with query.path at query.queryHeight at the Queried Chain. Otherwise, verify that query.path does not exist at query.queryHeight at the Queried Chain. + if (success) { + abortTransactionUnless(client.verifyMemership( + client, + proofHeight, + delayPeriodTime, + delayPeriodBlocks, + proof, + query.path, + data + )) + result = FOUND + } else { + abortTransactionUnless(client.verifyNonMemership( + client, + proofHeight, + delayPeriodTime, + delayPeriodBlocks, + proof, + query.path, + )) + result = NOTFOUND + } + + // Delete the query from the local, private store. + query = privateStore.delete(ongoingQueryPath(queryId)) + + // Create a query result record. + resultRecord = CrossChainQuery{queryIdentifier, + result, + data} + + // Store the result in a public path. + provableStore.set(resultQueryPath(queryIdentifier), resultRecord) + +} +``` +- **Precondition** + - There exists a client with `clientId` identifier. + - There is a query request stored in the `privateStore` identified by `queryId`. +- **Postcondition** + - The query request identified by `queryId` is deleted from the `privateStore`. + - The query result is stored in the `provableStore`. + +#### Timeouts + +Query requests have associated a `timeoutHeight` field that specifies the height limit at the Querying Chain after which a query is considered to have failed. + +The Querying Chain calls the `checkQueryTimeout` function to check whether a specific query has timeout. + +> There are several alternatives on how to handle timeouts. For instance, the relayer could submit on-chain timeout notifications to the Querying Chain. Since the relayer is untrusted, for each of these notifications the Cross-chain Querying module of the Querying Chain MUST call the `checkQueryTimeout` to check if the query has indeed timeout. An alternative could be to make the Cross-chain Querying module responsible for checking +if any query has timeout by iterating over the ongoing queries at the beginning of a block and calling `checkQueryTimeout`. This is an implementation detail that this specification does not cover. + +```typescript +function checkQueryTimeout( + queryId: Identifier +){ + // Retrieve the query state from the local, private store using the query's identifier. + query = privateStore.get(queryPath(queryIdentifier)) + abortTransactionUnless(query !== null) + + // Get the current height. + currentHeight = getCurrentHeight() + + + if (currentHeight > query.timeoutHeight) { + // Delete the query from the local, private store if it has timeout + query = privateStore.delete(ongoingQueryPath(queryId)) + + // Create a query result record. + resultRecord = CrossChainQuery{queryIdentifier, + TIMEOUT, + null} + + // Store the result in a public path. + provableStore.set(resultQueryPath(queryIdentifier), resultRecord) + } +} +``` +- **Precondition** + - There is a query request stored in the `privateStore` identified by `queryId`. +- **Postcondition** + - If the query has indeed timeout, then + - the query request identified by `queryId` is deleted from the `privateStore`; + - the fact that the query has timeout is recorded in the `provableStore`. \ No newline at end of file From 4cbb46b45231fb5025e596dbd2e1759f998ce09c Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Wed, 11 May 2022 17:27:17 +0200 Subject: [PATCH 02/18] minor fixes --- spec/app/ics-interchain-queries/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/app/ics-interchain-queries/README.md b/spec/app/ics-interchain-queries/README.md index 59e3cc3f5..aba44fa0f 100644 --- a/spec/app/ics-interchain-queries/README.md +++ b/spec/app/ics-interchain-queries/README.md @@ -74,7 +74,7 @@ A bounty is paid to incentivize relayers for participating in interchain queries The Querying Chain MUST implement the Cross-chain Querying module, which allows the Querying Chain to query state at the Queried Chain. -Cross-chain querying relies on relayers operating between both chains. When a query request is received by the Cross-chain Querying module of the Querying Chain, this emits a `sendQuery` 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 at the Queried Chain. The relayer then submits (on-chain) the result at the Querying Chain. The result is finally stored at the Querying Chain by the Cross-chain Querying module. +Cross-chain querying relies on relayers operating between both chains. When a query request is received by the Querying Chain, the Cross-chain Querying module emits a `sendQuery` 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 at the Queried Chain. The relayer then submits (on-chain) the result at the Querying Chain. The result is finally registered at the Querying Chain by the Cross-chain Querying 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. @@ -99,8 +99,8 @@ interface CrossChainQuery struct { - The `path` field is the path to be queried at the Queried Chain. - The `timeoutHeight` field 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 `queryHeigth` field is the height at which the relayer must query the Queried Chain -- The `bounty` field is a bounty that is given to the relayer for participating in the query. - The `clientId` field identifies the Queried Chain. +- The `bounty` field is a bounty that is given to the relayer for participating in the query. The Cross-chain Querying module stores query results to allow query callers to asynchronously retrieve them. In this context, this ICS defines the `QueryResult` type as follows: @@ -116,7 +116,7 @@ enum QueryResult { - 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 timeout before a result is committed at the Querying Chain is marked as `TIMEOUT`. -A CrossChainQueryResult is a particular interface used to represent query replies. +A CrossChainQueryResult is a particular interface used to represent query results. ```typescript interface CrossChainQueryResult struct { @@ -163,10 +163,10 @@ function generateQueryIdentifier = () -> Identifier #### Query lifecycle -1) When the Cross-chain Querying module receives a query request, it calls `CrossChainQueryRequest`. This function generates a unique identifier for the query, stores it in its `privateStore` and emits a `sendQuery` event. Query requests can be submitted by other IBC modules as transactions to the Querying Chain or simply executed as part of the `BeginBlock` and `EndBlock` logic. +1) When the Querying Chain receives a query request, it calls `CrossChainQueryRequest` of the Cross-chain Querying module. This function generates a unique identifier for the query, stores it in its `privateStore` and emits a `sendQuery` event. Query requests can be submitted by other IBC modules as transactions to the Querying Chain or simply executed as part of the `BeginBlock` and `EndBlock` logic. 2) A correct relayer listening to `sendQuery` events from the Querying Chain will eventually pick the query request up and execute it at the Queried Chain. The result is then submitted (on-chain) to the Querying Chain. -3) When the query result is committed at the Querying Chain, it is handed to the Cross-chain Querying module. -4) The Cross-chain Querying module calls `CrossChainQueryResult`. This function first retrieves the query from the `privateStore` using 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 the `privateStore` and stores the result in a public path. +3) When the query result is committed at the Querying Chain, this calls the `CrossChainQueryResult` function of the Cross-chain Querying module. +4) The `CrossChainQueryResult` first retrieves the query from the `privateStore` using 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 the `privateStore` and stores the result in a public path. 5) The query caller can then asynchronously retrieve the query result. #### Normal path methods From 7f382109f877328bb28bfb081fce4aae1df74038 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Fri, 13 May 2022 14:12:43 +0200 Subject: [PATCH 03/18] address Aditya minor comments --- spec/app/ics-interchain-queries/README.md | 30 ++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/spec/app/ics-interchain-queries/README.md b/spec/app/ics-interchain-queries/README.md index aba44fa0f..a18c814b7 100644 --- a/spec/app/ics-interchain-queries/README.md +++ b/spec/app/ics-interchain-queries/README.md @@ -38,7 +38,7 @@ Interchain Accounts (ICS-27) brings one of the most important features IBC offer ### Assumptions -- **Safe chains:** Both the Querying and Queried chains are safe. +- **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. @@ -87,18 +87,18 @@ A CrossChainQuery is a particular interface to represent query requests. A reque ```typescript interface CrossChainQuery struct { id: Identifier - path: CommitmentPath - timeoutHeight: Height + path: CommitmentPath + timeoutHeight: Height queryHeight: Height clientId: Identifier - bounty: sdk.Coin + bounty: sdk.Coin } ``` - The `id` field uniquely identifies the query at the Querying Chain. - The `path` field is the path to be queried at the Queried Chain. - The `timeoutHeight` field 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 `queryHeigth` field is the height at which the relayer must query the Queried Chain +- The `queryHeight` field is the height at which the relayer must query the Queried Chain - The `clientId` field identifies the Queried Chain. - The `bounty` field is a bounty that is given to the relayer for participating in the query. @@ -176,8 +176,8 @@ The `CrossChainQueryRequest` function is called when the Cross-chain Querying mo ```typescript function CrossChainQueryRequest( path: CommitmentPath, - queryHeigth: Heigth, - timeoutHeigth: Height, + queryHeight: Height, + timeoutHeight: Height, clientId: Identifier, bounty: sdk.Coin, ): Identifier { @@ -185,14 +185,17 @@ function CrossChainQueryRequest( // Check that there exists a client of the Queried Chain. The client will be used to verify the query result. abortTransactionUnless(queryClientState(clientId) !== null) + // Check that timeoutHeight is greater than the current height, otherwise the query will always timeout. + abortTransactionUnless(timeoutHeight > getCurrentHeight()) + // Generate a unique query identifier. queryIdentifier = generateQueryIdentifier() // Create a query request record. query = CrossChainQuery{queryIdentifier, path, - queryHeigth, - timeoutHeigth, + queryHeight, + timeoutHeight, clientId, bounty} @@ -220,7 +223,6 @@ function CrossChainQueryResult( data: []byte proof: CommitmentProof, proofHeight: Height, - success: boolean, delayPeriodTime: uint64, delayPeriodBlocks: uint64 ) { @@ -234,10 +236,10 @@ function CrossChainQueryResult( abortTransactionUnless(client !== null) // Check that the relier executed the query at the requested height at the Queried Chain. - abortTransactionUnless(query.queryHeigth !== proofHeight) + abortTransactionUnless(query.queryHeight !== proofHeight) // Verify query result using the local light client of the Queried Chain. If success, then verify that the data is indeed the value associated with query.path at query.queryHeight at the Queried Chain. Otherwise, verify that query.path does not exist at query.queryHeight at the Queried Chain. - if (success) { + if (data == null) { abortTransactionUnless(client.verifyMemership( client, proofHeight, @@ -247,7 +249,7 @@ function CrossChainQueryResult( query.path, data )) - result = FOUND + result = SUCCESS } else { abortTransactionUnless(client.verifyNonMemership( client, @@ -257,7 +259,7 @@ function CrossChainQueryResult( proof, query.path, )) - result = NOTFOUND + result = FAILURE } // Delete the query from the local, private store. From 8c7234d5660047ef86ee50784362fbc4f4afdd69 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Fri, 13 May 2022 14:17:01 +0200 Subject: [PATCH 04/18] fix typo --- spec/app/ics-interchain-queries/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-interchain-queries/README.md b/spec/app/ics-interchain-queries/README.md index a18c814b7..e020f5519 100644 --- a/spec/app/ics-interchain-queries/README.md +++ b/spec/app/ics-interchain-queries/README.md @@ -239,7 +239,7 @@ function CrossChainQueryResult( abortTransactionUnless(query.queryHeight !== proofHeight) // Verify query result using the local light client of the Queried Chain. If success, then verify that the data is indeed the value associated with query.path at query.queryHeight at the Queried Chain. Otherwise, verify that query.path does not exist at query.queryHeight at the Queried Chain. - if (data == null) { + if (data != null) { abortTransactionUnless(client.verifyMemership( client, proofHeight, From 7aa0a808feb166817a79c252e2c5b463a0c7d082 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Fri, 13 May 2022 14:19:55 +0200 Subject: [PATCH 05/18] fix typo --- spec/app/ics-interchain-queries/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-interchain-queries/README.md b/spec/app/ics-interchain-queries/README.md index e020f5519..ca75853ed 100644 --- a/spec/app/ics-interchain-queries/README.md +++ b/spec/app/ics-interchain-queries/README.md @@ -239,7 +239,7 @@ function CrossChainQueryResult( abortTransactionUnless(query.queryHeight !== proofHeight) // Verify query result using the local light client of the Queried Chain. If success, then verify that the data is indeed the value associated with query.path at query.queryHeight at the Queried Chain. Otherwise, verify that query.path does not exist at query.queryHeight at the Queried Chain. - if (data != null) { + if (data !== null) { abortTransactionUnless(client.verifyMemership( client, proofHeight, From bd2b444863c65a55efd52f919b28987c3d961578 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Wed, 18 May 2022 12:26:33 +0200 Subject: [PATCH 06/18] address Chris comments --- spec/app/ics-interchain-queries/README.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/spec/app/ics-interchain-queries/README.md b/spec/app/ics-interchain-queries/README.md index ca75853ed..40aac72ef 100644 --- a/spec/app/ics-interchain-queries/README.md +++ b/spec/app/ics-interchain-queries/README.md @@ -1,5 +1,5 @@ --- -ics: TBA +ics: 31 title: Interchain Query stage: draft category: IBC/APP @@ -66,7 +66,7 @@ The Querying Chain should have ultimate control over how to handle queried data. #### Incentivization -A bounty is paid to incentivize relayers for participating in interchain queries. +A bounty is paid to incentivize relayers for participating in interchain queries: fetching data from the Queried Chain and submitting it (together with proofs) to the Querying ## Technical Specification @@ -74,7 +74,7 @@ A bounty is paid to incentivize relayers for participating in interchain queries The Querying Chain MUST implement the Cross-chain Querying module, which allows the Querying Chain to query state at the Queried Chain. -Cross-chain querying relies on relayers operating between both chains. When a query request is received by the Querying Chain, the Cross-chain Querying module emits a `sendQuery` 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 at the Queried Chain. The relayer then submits (on-chain) the result at the Querying Chain. The result is finally registered at the Querying Chain by the Cross-chain Querying module. +Cross-chain querying relies on relayers operating between both chains. When a query request is received by the Querying Chain, the Cross-chain Querying module emits a `sendQuery` 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 (on-chain) the result at the Querying Chain. The result is finally registered at the Querying Chain by the Cross-chain Querying 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. @@ -114,7 +114,7 @@ enum QueryResult { ``` - 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 timeout before a result is committed at the Querying Chain is marked as `TIMEOUT`. +- A query that timed out before a result is committed at the Querying Chain is marked as `TIMEOUT`. A CrossChainQueryResult is a particular interface used to represent query results. @@ -185,7 +185,7 @@ function CrossChainQueryRequest( // Check that there exists a client of the Queried Chain. The client will be used to verify the query result. abortTransactionUnless(queryClientState(clientId) !== null) - // Check that timeoutHeight is greater than the current height, otherwise the query will always timeout. + // Check that timeoutHeight is greater than the current height, otherwise the query will always time out. abortTransactionUnless(timeoutHeight > getCurrentHeight()) // Generate a unique query identifier. @@ -286,10 +286,10 @@ function CrossChainQueryResult( Query requests have associated a `timeoutHeight` field that specifies the height limit at the Querying Chain after which a query is considered to have failed. -The Querying Chain calls the `checkQueryTimeout` function to check whether a specific query has timeout. +The Querying Chain calls the `checkQueryTimeout` function to check whether a specific query has timed out. -> There are several alternatives on how to handle timeouts. For instance, the relayer could submit on-chain timeout notifications to the Querying Chain. Since the relayer is untrusted, for each of these notifications the Cross-chain Querying module of the Querying Chain MUST call the `checkQueryTimeout` to check if the query has indeed timeout. An alternative could be to make the Cross-chain Querying module responsible for checking -if any query has timeout by iterating over the ongoing queries at the beginning of a block and calling `checkQueryTimeout`. This is an implementation detail that this specification does not cover. +> There are several alternatives on how to handle timeouts. For instance, the relayer could submit on-chain timeout notifications to the Querying Chain. Since the relayer is untrusted, for each of these notifications the Cross-chain Querying 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 Querying 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`. This is an implementation detail that this specification does not cover. ```typescript function checkQueryTimeout( @@ -304,7 +304,7 @@ function checkQueryTimeout( if (currentHeight > query.timeoutHeight) { - // Delete the query from the local, private store if it has timeout + // Delete the query from the local, private store if it has timed out query = privateStore.delete(ongoingQueryPath(queryId)) // Create a query result record. @@ -320,6 +320,6 @@ function checkQueryTimeout( - **Precondition** - There is a query request stored in the `privateStore` identified by `queryId`. - **Postcondition** - - If the query has indeed timeout, then + - If the query has indeed timed out, then - the query request identified by `queryId` is deleted from the `privateStore`; - - the fact that the query has timeout is recorded in the `provableStore`. \ No newline at end of file + - the fact that the query has timed out is recorded in the `provableStore`. \ No newline at end of file From 0a7a8acced52b8f6d1ee34bd001d7f2a3021d4c8 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Wed, 18 May 2022 13:22:15 +0200 Subject: [PATCH 07/18] address Carlos comments --- spec/app/ics-interchain-queries/README.md | 34 +++++++++++++++-------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/spec/app/ics-interchain-queries/README.md b/spec/app/ics-interchain-queries/README.md index 40aac72ef..571d95f66 100644 --- a/spec/app/ics-interchain-queries/README.md +++ b/spec/app/ics-interchain-queries/README.md @@ -7,7 +7,7 @@ requires: 2, 18, 23, 24 kind: instantiation author: Joe Schnetzler , Manuel Bravo created: 2022-01-06 -modified: 2022-05-03 +modified: 2022-05-11 --- ## Synopsis @@ -34,6 +34,8 @@ Interchain Accounts (ICS-27) brings one of the most important features IBC offer `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 @@ -91,7 +93,7 @@ interface CrossChainQuery struct { timeoutHeight: Height queryHeight: Height clientId: Identifier - bounty: sdk.Coin + bounty: Fee } ``` @@ -132,13 +134,13 @@ interface CrossChainQueryResult struct { ### Store paths -#### Ongoing query path +#### Query path -The ongoing query path is a private path that stores the state of ongoing cross-chain queries. +The query path is a private path that stores the state of ongoing cross-chain queries. ```typescript -function ongoingQueryPath(id: Identifier): Path { - return "ibcquery/{id}" +function queryPath(id: Identifier): Path { + return "queries/{id}" } ``` #### Result query path @@ -147,7 +149,7 @@ The result query path is a public path that stores the result of completed queri ```typescript function resultQueryPath(id: Identifier): Path { - return "ibcqueryresult/{id}" + return "queriesresult/{id}" } ``` @@ -200,7 +202,7 @@ function CrossChainQueryRequest( bounty} // Store the query in the local, private store. - privateStore.set(ongoingQueryPath(queryIdentifier), query) + privateStore.set(queryPath(queryIdentifier), query) // Log the query request. emitLogEntry("sendQuery", query) @@ -263,7 +265,7 @@ function CrossChainQueryResult( } // Delete the query from the local, private store. - query = privateStore.delete(ongoingQueryPath(queryId)) + query = privateStore.delete(queryPath(queryId)) // Create a query result record. resultRecord = CrossChainQuery{queryIdentifier, @@ -305,7 +307,7 @@ function checkQueryTimeout( if (currentHeight > query.timeoutHeight) { // Delete the query from the local, private store if it has timed out - query = privateStore.delete(ongoingQueryPath(queryId)) + query = privateStore.delete(queryPath(queryId)) // Create a query result record. resultRecord = CrossChainQuery{queryIdentifier, @@ -322,4 +324,14 @@ function checkQueryTimeout( - **Postcondition** - If the query has indeed timed out, then - the query request identified by `queryId` is deleted from the `privateStore`; - - the fact that the query has timed out is recorded in the `provableStore`. \ No newline at end of file + - the fact that the query has timed out is recorded in the `provableStore`. + +## History + +January 6, 2022 - First draft + +May 11, 2022 - Major revision + +## Copyright + +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). \ No newline at end of file From fac54deb7917e221ed8c3e2738f87bf46057013f Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Wed, 8 Jun 2022 16:14:41 +0200 Subject: [PATCH 08/18] add clean query result function --- spec/app/ics-interchain-queries/README.md | 36 ++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/spec/app/ics-interchain-queries/README.md b/spec/app/ics-interchain-queries/README.md index 571d95f66..4f0c3c1e1 100644 --- a/spec/app/ics-interchain-queries/README.md +++ b/spec/app/ics-interchain-queries/README.md @@ -182,7 +182,7 @@ function CrossChainQueryRequest( timeoutHeight: Height, clientId: Identifier, bounty: sdk.Coin, - ): Identifier { + ): [Identifier, CapabilityKey] { // Check that there exists a client of the Queried Chain. The client will be used to verify the query result. abortTransactionUnless(queryClientState(clientId) !== null) @@ -204,11 +204,13 @@ function CrossChainQueryRequest( // Store the query in the local, private store. privateStore.set(queryPath(queryIdentifier), query) + queryCapability = newCapability(queryIdentifier) + // Log the query request. emitLogEntry("sendQuery", query) // Returns the query identifier. - return queryIdentifier + return [queryIdentifier, queryCapability] } ``` - **Precondition** @@ -265,7 +267,7 @@ function CrossChainQueryResult( } // Delete the query from the local, private store. - query = privateStore.delete(queryPath(queryId)) + privateStore.delete(queryPath(queryId)) // Create a query result record. resultRecord = CrossChainQuery{queryIdentifier, @@ -284,6 +286,31 @@ function CrossChainQueryResult( - The query request identified by `queryId` is deleted from the `privateStore`. - The query result is stored in the `provableStore`. +The `CleanCrossChainQueryResult` function is called when the caller of a query has retrieved the result and wants to delete it. + +```typescript +function CleanCrossChainQueryResult( + queryId: Identifier, + queryCapability: CapabilityKey + ) { + + // Retrieve the query result from the provable store using the query's identifier. + resultRecord = privateStore.get(resultQueryPath(queryIdentifier)) + abortTransactionUnless(resultRecord !== null) + + // Abort the transaction unless the caller has the right to clean the query result + abortTransactionUnless(authenticateCapability(queryId, queryCapability)) + + // Delete the query result from the public store. + privateStore.delete(resultQueryPath(queryId)) +} +``` +- **Precondition** + - There is a query result stored in the `provableStore` identified by `queryId`. + - The caller has the right to clean the query result +- **Postcondition** + - The query result identified by `queryId` is deleted from the `provableStore`. + #### Timeouts Query requests have associated a `timeoutHeight` field that specifies the height limit at the Querying Chain after which a query is considered to have failed. @@ -307,11 +334,12 @@ function checkQueryTimeout( if (currentHeight > query.timeoutHeight) { // Delete the query from the local, private store if it has timed out - query = privateStore.delete(queryPath(queryId)) + privateStore.delete(queryPath(queryId)) // Create a query result record. resultRecord = CrossChainQuery{queryIdentifier, TIMEOUT, + query.caller null} // Store the result in a public path. From fc24959f13f1c9dab1bbb12433cc02af35732147 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Wed, 8 Jun 2022 16:19:50 +0200 Subject: [PATCH 09/18] Rename spec/app/ics-interchain-queries/README.md to spec/app/ics-031-crosschain-queries/README.md --- .../README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename spec/app/{ics-interchain-queries => ics-031-crosschain-queries}/README.md (99%) diff --git a/spec/app/ics-interchain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md similarity index 99% rename from spec/app/ics-interchain-queries/README.md rename to spec/app/ics-031-crosschain-queries/README.md index 4f0c3c1e1..f277e2310 100644 --- a/spec/app/ics-interchain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -362,4 +362,4 @@ May 11, 2022 - Major revision ## Copyright -All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). \ No newline at end of file +All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). From eca64c2206c8c24ada5b58d5c1fd19412ccb3b4a Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Wed, 8 Jun 2022 17:51:14 +0200 Subject: [PATCH 10/18] minor change --- spec/app/ics-031-crosschain-queries/README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/spec/app/ics-031-crosschain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md index f277e2310..6695a6d55 100644 --- a/spec/app/ics-031-crosschain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -1,9 +1,9 @@ --- ics: 31 -title: Interchain Query +title: Cross-chain Queries stage: draft category: IBC/APP -requires: 2, 18, 23, 24 +requires: 2, 5, 18, 23, 24 kind: instantiation author: Joe Schnetzler , Manuel Bravo created: 2022-01-06 @@ -30,6 +30,8 @@ Interchain Accounts (ICS-27) brings one of the most important features IBC offer `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. @@ -169,7 +171,7 @@ function generateQueryIdentifier = () -> Identifier 2) A correct relayer listening to `sendQuery` events from the Querying Chain will eventually pick the query request up and execute it at the Queried Chain. The result is then submitted (on-chain) to the Querying Chain. 3) When the query result is committed at the Querying Chain, this calls the `CrossChainQueryResult` function of the Cross-chain Querying module. 4) The `CrossChainQueryResult` first retrieves the query from the `privateStore` using 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 the `privateStore` and stores the result in a public path. -5) The query caller can then asynchronously retrieve the query result. +5) The query caller can then asynchronously retrieve the query result. The function `PruneCrossChainQueryResult` allows a query caller to prune the result from the store once it retrieves it. #### Normal path methods @@ -286,10 +288,10 @@ function CrossChainQueryResult( - The query request identified by `queryId` is deleted from the `privateStore`. - The query result is stored in the `provableStore`. -The `CleanCrossChainQueryResult` function is called when the caller of a query has retrieved the result and wants to delete it. +The `PruneCrossChainQueryResult` function is called when the caller of a query has retrieved the result and wants to delete it. ```typescript -function CleanCrossChainQueryResult( +function PruneCrossChainQueryResult( queryId: Identifier, queryCapability: CapabilityKey ) { From 4f06748626e36393dde03e720a7193bf1b877eec Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Tue, 14 Jun 2022 18:33:23 +0200 Subject: [PATCH 11/18] add relayer address to functions and localTimeoutTimestamp --- spec/app/ics-031-crosschain-queries/README.md | 64 ++++++++++++------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/spec/app/ics-031-crosschain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md index 6695a6d55..592ea4372 100644 --- a/spec/app/ics-031-crosschain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -92,7 +92,8 @@ A CrossChainQuery is a particular interface to represent query requests. A reque interface CrossChainQuery struct { id: Identifier path: CommitmentPath - timeoutHeight: Height + localTimeoutHeight: Height + localTimeoutTimestamp: Height queryHeight: Height clientId: Identifier bounty: Fee @@ -101,7 +102,8 @@ interface CrossChainQuery struct { - The `id` field uniquely identifies the query at the Querying Chain. - The `path` field is the path to be queried at the Queried Chain. -- The `timeoutHeight` field 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 `localTimeoutHeight` field 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 `localTimeoutTimestamp` field 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 `queryHeight` field is the height at which the relayer must query the Queried Chain - The `clientId` field identifies the Queried Chain. - The `bounty` field is a bounty that is given to the relayer for participating in the query. @@ -181,16 +183,18 @@ The `CrossChainQueryRequest` function is called when the Cross-chain Querying mo function CrossChainQueryRequest( path: CommitmentPath, queryHeight: Height, - timeoutHeight: Height, + localTimeoutHeight: Height, clientId: Identifier, - bounty: sdk.Coin, + bounty: Fee, ): [Identifier, CapabilityKey] { // Check that there exists a client of the Queried Chain. The client will be used to verify the query result. abortTransactionUnless(queryClientState(clientId) !== null) - // Check that timeoutHeight is greater than the current height, otherwise the query will always time out. - abortTransactionUnless(timeoutHeight > getCurrentHeight()) + // Sanity-check that localTimeoutHeight is 0 or greater than the current height, otherwise the query will always time out. + abortTransactionUnless(localTimeoutHeight === 0 || localTimeoutHeight > getCurrentHeight()) + // Sanity-check that localTimeoutTimestamp is 0 or greater than the current timestamp, otherwise the query will always time out. + abortTransactionUnless(localTimeoutTimestamp === 0 || localTimeoutTimestamp > currentTimestamp()) // Generate a unique query identifier. queryIdentifier = generateQueryIdentifier() @@ -199,7 +203,8 @@ function CrossChainQueryRequest( query = CrossChainQuery{queryIdentifier, path, queryHeight, - timeoutHeight, + localTimeoutHeight, + localTimeoutTimestamp, clientId, bounty} @@ -222,6 +227,7 @@ function CrossChainQueryRequest( - A `sendQuery` event is emitted. The `CrossChainQueryResult` function is called when the Cross-chain Querying 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). ```typescript function CrossChainQueryResult( @@ -230,7 +236,8 @@ function CrossChainQueryResult( proof: CommitmentProof, proofHeight: Height, delayPeriodTime: uint64, - delayPeriodBlocks: uint64 + delayPeriodBlocks: uint64, + relayer: string ) { // Retrieve query state from the local, private store using the query's identifier. @@ -244,6 +251,12 @@ function CrossChainQueryResult( // Check that the relier executed the query at the requested height at the Queried Chain. abortTransactionUnless(query.queryHeight !== proofHeight) + // Check that localTimeoutHeight is 0 or greater than the current height. + abortTransactionUnless(query.localTimeoutHeight === 0 || query.localTimeoutHeight > getCurrentHeight()) + // Check that localTimeoutTimestamp is 0 or greater than the current timestamp. + abortTransactionUnless(query.localTimeoutTimestamp === 0 || query.localTimeoutTimestamp > currentTimestamp()) + + // Verify query result using the local light client of the Queried Chain. If success, then verify that the data is indeed the value associated with query.path at query.queryHeight at the Queried Chain. Otherwise, verify that query.path does not exist at query.queryHeight at the Queried Chain. if (data !== null) { abortTransactionUnless(client.verifyMemership( @@ -315,16 +328,19 @@ function PruneCrossChainQueryResult( #### Timeouts -Query requests have associated a `timeoutHeight` field that specifies the height limit at the Querying Chain after which a query is considered to have failed. +Query requests have associated a `localTimeoutHeight` and a `localTimeoutTimestamp` field that specifies the height and timestamp limit at the Querying Chain after which a query is considered to have failed. The Querying Chain calls the `checkQueryTimeout` function to check whether a specific query has timed out. > There are several alternatives on how to handle timeouts. For instance, the relayer could submit on-chain timeout notifications to the Querying Chain. Since the relayer is untrusted, for each of these notifications the Cross-chain Querying 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 Querying 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`. This is an implementation detail that this specification does not cover. +We pass the relayer address just as in `CrossChainQueryResult` to allow for possible incentivization here as well. + ```typescript function checkQueryTimeout( - queryId: Identifier + queryId: Identifier, + relayer: string ){ // Retrieve the query state from the local, private store using the query's identifier. query = privateStore.get(queryPath(queryIdentifier)) @@ -333,20 +349,22 @@ function checkQueryTimeout( // Get the current height. currentHeight = getCurrentHeight() - - if (currentHeight > query.timeoutHeight) { - // Delete the query from the local, private store if it has timed out - privateStore.delete(queryPath(queryId)) + // Check that localTimeoutHeight or localTimeoutTimestamp has passed on the Querying Chain (locally) + abortTransactionUnless( + (query.localTimeoutHeight > 0 && query.localTimeoutHeight < getCurrentHeight()) || + (query.localTimeoutTimestamp > 0 && query.localTimeoutTimestamp < currentTimestamp())) - // Create a query result record. - resultRecord = CrossChainQuery{queryIdentifier, - TIMEOUT, - query.caller - null} + // Delete the query from the local, private store if it has timed out + privateStore.delete(queryPath(queryId)) - // Store the result in a public path. - provableStore.set(resultQueryPath(queryIdentifier), resultRecord) - } + // Create a query result record. + resultRecord = CrossChainQuery{queryIdentifier, + TIMEOUT, + query.caller + null} + + // Store the result in a public path. + provableStore.set(resultQueryPath(queryIdentifier), resultRecord) } ``` - **Precondition** @@ -362,6 +380,8 @@ January 6, 2022 - First draft May 11, 2022 - Major revision +June 14, 2022 - Adds pruning, localTimeoutTimestamp and adds relayer address for incentivization + ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). From ebd7d82b5c7e9e988b0b85fac48ea594afb47f30 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Wed, 15 Jun 2022 14:57:01 +0200 Subject: [PATCH 12/18] add paragraph addition state machine logic --- spec/app/ics-031-crosschain-queries/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/app/ics-031-crosschain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md index 592ea4372..28a93c1f9 100644 --- a/spec/app/ics-031-crosschain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -78,7 +78,7 @@ A bounty is paid to incentivize relayers for participating in interchain queries The Querying Chain MUST implement the Cross-chain Querying module, which allows the Querying Chain to query state at the Queried Chain. -Cross-chain querying relies on relayers operating between both chains. When a query request is received by the Querying Chain, the Cross-chain Querying module emits a `sendQuery` 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 (on-chain) the result at the Querying Chain. The result is finally registered at the Querying Chain by the Cross-chain Querying module. +Cross-chain querying relies on relayers operating between both chains. When a query request is received by the Querying Chain, the Cross-chain Querying module emits a `sendQuery` 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 (on-chain) the result at the Querying Chain. The result is finally registered at the Querying Chain by the Cross-chain Querying 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. @@ -173,6 +173,7 @@ function generateQueryIdentifier = () -> Identifier 2) A correct relayer listening to `sendQuery` events from the Querying Chain will eventually pick the query request up and execute it at the Queried Chain. The result is then submitted (on-chain) to the Querying Chain. 3) When the query result is committed at the Querying Chain, this calls the `CrossChainQueryResult` function of the Cross-chain Querying module. 4) The `CrossChainQueryResult` first retrieves the query from the `privateStore` using 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 the `privateStore` and stores the result in a public path. +> 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 existing `bounty` field of the `CrossChainQuery` interface or extend the interface with an additional field. 5) The query caller can then asynchronously retrieve the query result. The function `PruneCrossChainQueryResult` allows a query caller to prune the result from the store once it retrieves it. #### Normal path methods From aa4a60b1269fe571ad19dbb665070c27bd89702a Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Tue, 28 Jun 2022 12:54:56 +0200 Subject: [PATCH 13/18] query results into private store --- spec/app/ics-031-crosschain-queries/README.md | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/app/ics-031-crosschain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md index 28a93c1f9..259d966f0 100644 --- a/spec/app/ics-031-crosschain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -149,7 +149,7 @@ function queryPath(id: Identifier): Path { ``` #### Result query path -The result query path is a public path that stores the result of completed queries. +The result query path is a private path that stores the result of completed queries. ```typescript function resultQueryPath(id: Identifier): Path { @@ -172,7 +172,7 @@ function generateQueryIdentifier = () -> Identifier 1) When the Querying Chain receives a query request, it calls `CrossChainQueryRequest` of the Cross-chain Querying module. This function generates a unique identifier for the query, stores it in its `privateStore` and emits a `sendQuery` event. Query requests can be submitted by other IBC modules as transactions to the Querying Chain or simply executed as part of the `BeginBlock` and `EndBlock` logic. 2) A correct relayer listening to `sendQuery` events from the Querying Chain will eventually pick the query request up and execute it at the Queried Chain. The result is then submitted (on-chain) to the Querying Chain. 3) When the query result is committed at the Querying Chain, this calls the `CrossChainQueryResult` function of the Cross-chain Querying module. -4) The `CrossChainQueryResult` first retrieves the query from the `privateStore` using 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 the `privateStore` and stores the result in a public path. +4) The `CrossChainQueryResult` first retrieves the query from the `privateStore` using 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 the `privateStore` and 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 existing `bounty` field of the `CrossChainQuery` interface or extend the interface with an additional field. 5) The query caller can then asynchronously retrieve the query result. The function `PruneCrossChainQueryResult` allows a query caller to prune the result from the store once it retrieves it. @@ -290,8 +290,8 @@ function CrossChainQueryResult( result, data} - // Store the result in a public path. - provableStore.set(resultQueryPath(queryIdentifier), resultRecord) + // Store the result in the local, private store. + privateStore.set(resultQueryPath(queryIdentifier), resultRecord) } ``` @@ -300,7 +300,7 @@ function CrossChainQueryResult( - There is a query request stored in the `privateStore` identified by `queryId`. - **Postcondition** - The query request identified by `queryId` is deleted from the `privateStore`. - - The query result is stored in the `provableStore`. + - The query result is stored in the `privateStore`. The `PruneCrossChainQueryResult` function is called when the caller of a query has retrieved the result and wants to delete it. @@ -310,22 +310,22 @@ function PruneCrossChainQueryResult( queryCapability: CapabilityKey ) { - // Retrieve the query result from the provable store using the query's identifier. + // Retrieve the query result from the private store using the query's identifier. resultRecord = privateStore.get(resultQueryPath(queryIdentifier)) abortTransactionUnless(resultRecord !== null) // Abort the transaction unless the caller has the right to clean the query result abortTransactionUnless(authenticateCapability(queryId, queryCapability)) - // Delete the query result from the public store. + // Delete the query result from the the local, private store. privateStore.delete(resultQueryPath(queryId)) } ``` - **Precondition** - - There is a query result stored in the `provableStore` identified by `queryId`. + - There is a query result stored in the `privateStore` identified by `queryId`. - The caller has the right to clean the query result - **Postcondition** - - The query result identified by `queryId` is deleted from the `provableStore`. + - The query result identified by `queryId` is deleted from the `privateStore`. #### Timeouts @@ -364,8 +364,8 @@ function checkQueryTimeout( query.caller null} - // Store the result in a public path. - provableStore.set(resultQueryPath(queryIdentifier), resultRecord) + // Store the result in the local, private store. + privateStore.set(resultQueryPath(queryIdentifier), resultRecord) } ``` - **Precondition** @@ -373,7 +373,7 @@ function checkQueryTimeout( - **Postcondition** - If the query has indeed timed out, then - the query request identified by `queryId` is deleted from the `privateStore`; - - the fact that the query has timed out is recorded in the `provableStore`. + - the fact that the query has timed out is recorded in the `privateStore`. ## History From 9465743728df99939bf2c64ffd09fdf7cad8bf2f Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Tue, 28 Jun 2022 13:10:54 +0200 Subject: [PATCH 14/18] minor change --- spec/app/ics-031-crosschain-queries/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-031-crosschain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md index 259d966f0..7afadad69 100644 --- a/spec/app/ics-031-crosschain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -334,7 +334,7 @@ Query requests have associated a `localTimeoutHeight` and a `localTimeoutTimesta The Querying Chain calls the `checkQueryTimeout` function to check whether a specific query has timed out. > There are several alternatives on how to handle timeouts. For instance, the relayer could submit on-chain timeout notifications to the Querying Chain. Since the relayer is untrusted, for each of these notifications the Cross-chain Querying 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 Querying 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`. This is an implementation detail that this specification does not cover. +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. We pass the relayer address just as in `CrossChainQueryResult` to allow for possible incentivization here as well. From c2b1ee0f4353e10aad533686b568d81ffe019385 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Fri, 29 Jul 2022 18:44:52 +0200 Subject: [PATCH 15/18] major changes to assumptions --- spec/app/ics-031-crosschain-queries/README.md | 129 ++++++++++-------- 1 file changed, 71 insertions(+), 58 deletions(-) diff --git a/spec/app/ics-031-crosschain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md index 7afadad69..fa21c2158 100644 --- a/spec/app/ics-031-crosschain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -7,26 +7,26 @@ requires: 2, 5, 18, 23, 24 kind: instantiation author: Joe Schnetzler , Manuel Bravo created: 2022-01-06 -modified: 2022-05-11 +modified: 2022-07-28 --- ## Synopsis -This standard document specifies the data structures and state machine handling logic of the Cross-chain Querying module, which allows for cross-chain querying between IBC enabled chains. +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 -Interchain Accounts (ICS-27) brings one of the most important features IBC offers, cross-chain transactions (on-chain). Limited in this functionality is the querying of state from one chain, on another chain. Adding cross-chain querying via the Cross-chain Querying module gives unlimited flexibility to chains to build IBC enabled protocols around Interchain Accounts and beyond. +Interchain Accounts (ICS-27) brings one of the most important features IBC offers, cross-chain transactions. Limited in this functionality is the querying of state from one chain, on another chain. Adding cross-chain querying via the Cross-chain Queries module gives unlimited flexibility to chains to build IBC enabled protocols around Interchain Accounts and beyond. ### 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 Querying module. +`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. +`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 Querying Module`: The module that implements the Cross-chain Querying protocol. Only the Querying Chain integrates it. +`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. @@ -42,49 +42,58 @@ Interchain Accounts (ICS-27) brings one of the most important features IBC offer ### 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. +- **Safe chains:** Both the queryin 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. +- **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 transactions. +- **Censorship-resistant querying chain:** The querying chain cannot selectively omit valid transactions. -- **Correct relayer:** There is at least one correct relayer between the Querying and Queried chains. This is required for liveness. +> For example, This means that if a relayer submits a valid transaction to the querying chain, the transaction is guaranteed to be included in a committed block within a bounded time. + +- **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 -A Querying Chain can query a chain 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. +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 +#### Minimal queried chain Work -A Queried Chain has to do no implementation work or add any module to enable cross-chain querying. By utilizing an RPC client on a relayer, this is possible. +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 -Adding cross-chain querying should be as easy as implementing a module in your chain. - -#### Control Queried Data - -The Querying Chain should have ultimate control over how to handle queried data. Like querying for a certain query form/type. +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 interchain queries: fetching data from the Queried Chain and submitting it (together with proofs) to the Querying +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 Querying module, which allows the Querying Chain to query state at the Queried Chain. +The querying chain must implement the Cross-chain Queries module, which allows the querying chain to query state at the queried chain. -Cross-chain querying relies on relayers operating between both chains. When a query request is received by the Querying Chain, the Cross-chain Querying module emits a `sendQuery` 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 (on-chain) the result at the Querying Chain. The result is finally registered at the Querying Chain by the Cross-chain Querying module. +Cross-chain queries relies on relayers operating between both chains. When a query request is received by the querying chain, the Cross-chain Queries module emits a `sendQuery` 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. +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. ### Data Structures -The Cross-chain Querying module stores query requests when it processes them. +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. @@ -93,34 +102,34 @@ interface CrossChainQuery struct { id: Identifier path: CommitmentPath localTimeoutHeight: Height - localTimeoutTimestamp: Height + localTimeoutTimestamp: uint64 queryHeight: Height clientId: Identifier bounty: Fee } ``` -- The `id` field uniquely identifies the query at the Querying Chain. -- The `path` field is the path to be queried at the Queried Chain. -- The `localTimeoutHeight` field 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 `localTimeoutTimestamp` field 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 `queryHeight` field is the height at which the relayer must query the Queried Chain -- The `clientId` field identifies the Queried Chain. +- The `id` field uniquely identifies the query at the querying chain. +- The `path` field is the path to be queried at the queried chain. +- The `localTimeoutHeight` field 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 `localTimeoutTimestamp` field 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 `queryHeight` field is the height at which the relayer must query the queried chain +- The `clientId` field identifies the querying chain's client of the queried chain. - The `bounty` field is a bounty that is given to the relayer for participating in the query. -The Cross-chain Querying module stores query results to allow query callers to asynchronously retrieve them. -In this context, this ICS defines the `QueryResult` type as follows: +The Cross-chain Queries module stores query results to allow query callers to asynchronously retrieve them. +In this context, this standard defines the `QueryResult` type as follows: ```typescript enum QueryResult { SUCCESS, FAILURE, - TIMEOUT, + TIMEOUT } ``` -- 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`. +- 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`. A CrossChainQueryResult is a particular interface used to represent query results. @@ -132,8 +141,8 @@ interface CrossChainQueryResult struct { } ``` -- The `id` field uniquely identifies the query at the Querying Chain. -- The `result` field indicates whether the query was correctly executed at the Queried Chain and if the queried path exists. +- The `id` field uniquely identifies the query at the querying chain. +- The `result` field indicates whether the query was correctly executed at the queried chain and if the queried path exists. - The `data` field is an opaque bytestring that contains the value associated with the queried path in case `result = SUCCESS`. ### Store paths @@ -159,7 +168,7 @@ function resultQueryPath(id: Identifier): Path { ### Helper functions -The Querying Chain MUST implement a function `generateQueryIdentifier`, which generates a unique query identifier: +The querying chain MUST implement a function `generateQueryIdentifier`, which generates a unique query identifier: ```typescript function generateQueryIdentifier = () -> Identifier @@ -169,27 +178,28 @@ function generateQueryIdentifier = () -> Identifier #### Query lifecycle -1) When the Querying Chain receives a query request, it calls `CrossChainQueryRequest` of the Cross-chain Querying module. This function generates a unique identifier for the query, stores it in its `privateStore` and emits a `sendQuery` event. Query requests can be submitted by other IBC modules as transactions to the Querying Chain or simply executed as part of the `BeginBlock` and `EndBlock` logic. -2) A correct relayer listening to `sendQuery` events from the Querying Chain will eventually pick the query request up and execute it at the Queried Chain. The result is then submitted (on-chain) to the Querying Chain. -3) When the query result is committed at the Querying Chain, this calls the `CrossChainQueryResult` function of the Cross-chain Querying module. -4) The `CrossChainQueryResult` first retrieves the query from the `privateStore` using 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 the `privateStore` and 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 existing `bounty` field of the `CrossChainQuery` interface or extend the interface with an additional field. +1) When the querying chain receives a query request, it calls `CrossChainQueryRequest` of the Cross-chain Queries module. This function generates a unique identifier for the query, stores it in its `privateStore` and emits a `sendQuery` event. Query requests can be submitted as transactions to the querying chain or simply executed as part of the `BeginBlock` and `EndBlock` logic. Typically, query requests will be issued by other IBC modules. +2) A correct relayer listening to `sendQuery` events 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. +3) When the query result is committed at the querying chain, this calls the `CrossChainQueryResponse` function of the Cross-chain Queries module. +4) The `CrossChainQueryResponse` first retrieves the query from the `privateStore` using 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 the `privateStore` and 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 existing `bounty` field of the `CrossChainQuery` interface or extend the interface with an additional field. 5) The query caller can then asynchronously retrieve the query result. The function `PruneCrossChainQueryResult` allows a query caller to prune the result from the store once it retrieves it. #### Normal path methods -The `CrossChainQueryRequest` function is called when the Cross-chain Querying module at the Querying Chain receives a new query request. +The `CrossChainQueryRequest` function is called when the Cross-chain Queries module at the querying chain receives a new query request. ```typescript function CrossChainQueryRequest( path: CommitmentPath, queryHeight: Height, localTimeoutHeight: Height, + localTimeoutTimestamp: uint64, clientId: Identifier, bounty: Fee, ): [Identifier, CapabilityKey] { - // Check that there exists a client of the Queried Chain. The client will be used to verify the query result. + // Check that there exists a client of the queried chain. The client will be used to verify the query result. abortTransactionUnless(queryClientState(clientId) !== null) // Sanity-check that localTimeoutHeight is 0 or greater than the current height, otherwise the query will always time out. @@ -227,11 +237,11 @@ function CrossChainQueryRequest( - The query request is stored in the `privateStore`. - A `sendQuery` event is emitted. -The `CrossChainQueryResult` function is called when the Cross-chain Querying 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). +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). ```typescript -function CrossChainQueryResult( +function CrossChainQueryResponse( queryId: Identifier, data: []byte proof: CommitmentProof, @@ -245,11 +255,11 @@ function CrossChainQueryResult( query = privateStore.get(queryPath(queryIdentifier)) abortTransactionUnless(query !== null) - // Retrieve client state of the Queried Chain. + // Retrieve client state of the queried chain. client = queryClientState(query.clientId) abortTransactionUnless(client !== null) - // Check that the relier executed the query at the requested height at the Queried Chain. + // Check that the relier executed the query at the requested height at the queried chain. abortTransactionUnless(query.queryHeight !== proofHeight) // Check that localTimeoutHeight is 0 or greater than the current height. @@ -258,7 +268,8 @@ function CrossChainQueryResult( abortTransactionUnless(query.localTimeoutTimestamp === 0 || query.localTimeoutTimestamp > currentTimestamp()) - // Verify query result using the local light client of the Queried Chain. If success, then verify that the data is indeed the value associated with query.path at query.queryHeight at the Queried Chain. Otherwise, verify that query.path does not exist at query.queryHeight at the Queried Chain. + // Verify query result using the local light client of the queried chain. + // If the reponse carries data, then verify that the data is indeed the value associated with query.path at query.queryHeight at the queried chain. if (data !== null) { abortTransactionUnless(client.verifyMemership( client, @@ -270,6 +281,7 @@ function CrossChainQueryResult( data )) result = SUCCESS + // If there response does not carry any data, verify that query.path does not exist at query.queryHeight at the queried chain. } else { abortTransactionUnless(client.verifyNonMemership( client, @@ -329,14 +341,13 @@ function PruneCrossChainQueryResult( #### Timeouts -Query requests have associated a `localTimeoutHeight` and a `localTimeoutTimestamp` field that specifies the height and timestamp limit at the Querying Chain after which a query is considered to have failed. +Query requests have associated a `localTimeoutHeight` and a `localTimeoutTimestamp` field that specifies the height and timestamp limit at the querying chain after which a query is considered to have failed. -The Querying Chain calls the `checkQueryTimeout` function to check whether a specific query has timed out. +The querying chain calls the `checkQueryTimeout` function to check whether a specific query has timed out. -> There are several alternatives on how to handle timeouts. For instance, the relayer could submit on-chain timeout notifications to the Querying Chain. Since the relayer is untrusted, for each of these notifications the Cross-chain Querying 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 Querying 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. +> 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. -We pass the relayer address just as in `CrossChainQueryResult` to allow for possible incentivization here as well. +We pass the relayer address just as in `CrossChainQueryResponse` to allow for possible incentivization here as well. ```typescript function checkQueryTimeout( @@ -350,7 +361,7 @@ function checkQueryTimeout( // Get the current height. currentHeight = getCurrentHeight() - // Check that localTimeoutHeight or localTimeoutTimestamp has passed on the Querying Chain (locally) + // Check that localTimeoutHeight or localTimeoutTimestamp has passed on the querying chain (locally) abortTransactionUnless( (query.localTimeoutHeight > 0 && query.localTimeoutHeight < getCurrentHeight()) || (query.localTimeoutTimestamp > 0 && query.localTimeoutTimestamp < currentTimestamp())) @@ -383,6 +394,8 @@ May 11, 2022 - Major revision June 14, 2022 - Adds pruning, localTimeoutTimestamp and adds relayer address for incentivization +July 28, 2022 - Revision of the assumptions + ## Copyright All content herein is licensed under [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0). From e9e77b7d82c0c1f565b72310bc96401a74b22f10 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Thu, 4 Aug 2022 17:20:18 +0200 Subject: [PATCH 16/18] new motivation --- spec/app/ics-031-crosschain-queries/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/app/ics-031-crosschain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md index fa21c2158..52b0c3250 100644 --- a/spec/app/ics-031-crosschain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -18,7 +18,7 @@ This standard document specifies the data structures and state machine handling ### Motivation -Interchain Accounts (ICS-27) brings one of the most important features IBC offers, cross-chain transactions. Limited in this functionality is the querying of state from one chain, on another chain. Adding cross-chain querying via the Cross-chain Queries module gives unlimited flexibility to chains to build IBC enabled protocols around Interchain Accounts and beyond. +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 @@ -48,7 +48,7 @@ Interchain Accounts (ICS-27) brings one of the most important features IBC offer - **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 included in a committed block within a bounded time. +> 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 guarantees this. - **Correct relayer:** There is at least one live relayer between the querying and queried chains where the relayer correctly follows the protocol. From 475bc3cad2906f07732d4c8c1a546b365c954f21 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Thu, 4 Aug 2022 17:21:10 +0200 Subject: [PATCH 17/18] typo --- spec/app/ics-031-crosschain-queries/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/app/ics-031-crosschain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md index 52b0c3250..818305d4d 100644 --- a/spec/app/ics-031-crosschain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -48,7 +48,7 @@ We expect on-chain applications to depend on reads from other chains, e.g., a pa - **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 guarantees this. +> 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. From 7f2f5680c01b70abc7e3dd6e9560bd4b288bd777 Mon Sep 17 00:00:00 2001 From: Manuel Bravo Date: Fri, 5 Aug 2022 19:12:52 +0200 Subject: [PATCH 18/18] minor changes --- spec/app/ics-031-crosschain-queries/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spec/app/ics-031-crosschain-queries/README.md b/spec/app/ics-031-crosschain-queries/README.md index 818305d4d..1d437cb30 100644 --- a/spec/app/ics-031-crosschain-queries/README.md +++ b/spec/app/ics-031-crosschain-queries/README.md @@ -91,6 +91,8 @@ Cross-chain queries relies on relayers operating between both chains. When a que 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. @@ -343,11 +345,10 @@ function PruneCrossChainQueryResult( Query requests have associated a `localTimeoutHeight` and a `localTimeoutTimestamp` field that specifies the height and timestamp limit at the querying chain after which a query is considered to have failed. -The querying chain calls the `checkQueryTimeout` function to check whether a specific query has timed out. - -> 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. +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. -We pass the relayer address just as in `CrossChainQueryResponse` to allow for possible incentivization here as well. +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. ```typescript function checkQueryTimeout(