Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cross-chain query spec draft #735

Merged
merged 18 commits into from
Aug 11, 2022
388 changes: 388 additions & 0 deletions spec/app/ics-031-crosschain-queries/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,388 @@
---
ics: 31
title: Cross-chain Queries
stage: draft
category: IBC/APP
requires: 2, 5, 18, 23, 24
kind: instantiation
author: Joe Schnetzler <[email protected]>, Manuel Bravo <[email protected]>
created: 2022-01-06
modified: 2022-05-11
---

## 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: The title of the standard is Cross-chain Queries, but throughout Cross-chain Querying is used instead.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Also, I'd abbreviate Coss-chain Queries to CCQ (similarly to CCV for Cross-Chain Validation).


## 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be useful to expand a bit on the motivation, e.g., what exact problems are solved by cross-chain queries that were not possible to solve with normal queries.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a new motivation now. I think it is better. Thanks for pointing it out :)


### 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.

`newCapability` and `authenticateCapability` are as defined in ICS 5.

`CommitmentPath` and `CommitmentProof` are as defined in ICS 23.

`Identifier`, `get`, `set`, `delete`, `getCurrentHeight`, and module-system related primitives are as defined in ICS 24.

`Fee` is as defined in ICS 29.

## System Model and Properties

### Assumptions

- **Safe chains:** Both the Querying and Queried chains are safe. This means that, for every chain, the underlying consensus engine satisfies safety (e.g., the chain does not fork) and the execution of the state machine follows the described protocol.

- **Live chains:** Both the Querying and Queried chains MUST be live, i.e., new blocks are eventually added to the chain.

- **Censorship-resistant Querying Chain:** The Querying Chain cannot selectively omit transactions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this mean? not a very formal definition.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that all correctly submitted transactions are eventually committed. Unless one considers live chains to already guarantee this, I think this is required to guarantee that the query result submitted by a correct relayer is eventually processed.

Copy link
Contributor

@cwgoes cwgoes Jul 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctly submitted to whom? Any nodes can always selectively omit transactions, since you cannot prove that another party received a message. No blockchain can instantiate this property in the general case.

I get what you're going for, though. I agree that we need an assumption in order to guarantee that the query result submitted by a correct relayer is eventually processed - isn't that assumption stronger (processed within a time bound) if timeouts are involved, anyways? I think you could say something like "the relayer must submit and the querying chain must process a "QueryResult" transaction within the specified timeout bound in order for the query protocol to return results to the application" - would that do?

Copy link
Contributor Author

@angbrav angbrav Jul 26, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you could say something like "the relayer must submit and the querying chain must process a "QueryResult" transaction within the specified timeout bound in order for the query protocol to return results to the application" - would that do?

Yes, that would work. I believe your property would be Censorship-resistant property + a timely property. We can either have one property covering both things or two separate properties. I am fine with both solutions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correctly submitted to whom?

To the Querying Chain.

Any nodes can always selectively omit transactions, since you cannot prove that another party received a message. No blockchain can instantiate this property in the general case.

I think this is an interesting (open) research question. Non-reconfigurable Byz. consensus protocols are able to satisfy this property. PBFT for instance is censorship-resistant by broadcasting requests, setting timeouts per request, and forcing a view change if a timeout expires. When adding reconfiguration to the equation, things get more complicated, but I believe things may be solved by adding extra assumptions.

Copy link
Contributor

@cwgoes cwgoes Jul 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is an interesting (open) research question. Non-reconfigurable Byz. consensus protocols are able to satisfy this property. PBFT for instance is censorship-resistant by broadcasting requests, setting timeouts per request, and forcing a view change if a timeout expires. When adding reconfiguration to the equation, things get more complicated, but I believe things may be solved by adding extra assumptions.

How can a user with a transaction force a view change if a timeout expires? I thought timeouts were for proposals - and block proposers can always choose to omit transactions. Perhaps I don't follow, sorry. Under assumptions of bounded latency message delivery from a user authoring a transaction to a sufficient quorum of validators, and honesty of those validators w.r.t. including all received messages in blocks they propose in a particular order, and limited other transactions competing for block space, I could see some sort of transaction inclusion property holding, but these are far more strict assumptions than IBC in general makes, and should be spelled out in detail imo (if this spec intends to rely on them).

Copy link
Contributor Author

@angbrav angbrav Jul 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can a user with a transaction force a view change if a timeout expires? I thought timeouts were for proposals - and block proposers can always choose to omit transactions. Perhaps I don't follow, sorry.

Let me elaborate a bit. This is roughly how it works. Clients broadcast their requests to all consensus nodes. When a node receives a request, it puts it into its FIFO queue and starts a timer. If the request's timer expires before the request is delivered/committed at a node, it attempts to advance to the next view. Therefore, if the leader of the current view is Byzantine and ignores a request, eventually all correct nodes will attempt to advance and force a leader change (actually we do not need all correct nodes but a majority). Note that if a correct node delivers/commits requests, then all correct ones eventually do. You can find a formal proof at https://arxiv.org/pdf/2202.06679.pdf: Figures 4 and 5 show the pseudocode of a variant of PBFT which includes this mechanism, and Section 5 proves a liveness property that implies censorship-resistance.

Copy link
Contributor

@cwgoes cwgoes Aug 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Makes sense (and very cool research).


- **Correct relayer:** There is at least one correct relayer between the Querying and Queried chains. This is required for liveness.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The correct relayer assumption cannot be defined using correct relayer. In other words, correct relayer should be defined.


### 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the Querying Chain need permission from the Queried Chain? If not, we should make this clearer here.


#### 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say here that any chain that provides query support can act as a Queried Chain.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there chains that do not provide query support? What's required to support queries? This is more for my own education :)

Copy link
Contributor

@mpoke mpoke Jul 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there chains that do not provide query support?

Probably not, but just to be sure since it's a prerequisite of Interchain Queries.

Just to make sure, my comment was as a replacement for the entire line. I think that "query support" is the only requirement. Anyway, not that important.


#### 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.
mpoke marked this conversation as resolved.
Show resolved Hide resolved

#### 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
mpoke marked this conversation as resolved.
Show resolved Hide resolved

## 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I'd remove the capitalization from both Querying Chain and 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.
mpoke marked this conversation as resolved.
Show resolved Hide resolved

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to request a cross-chain query from a given height, i.e., query.H >= h?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth noting, this still creates an opportunity for altering state on the queried chain if the height is in the future in order to change the results of the query

(not a relayer-specific problem, but would be a form of cross-chain MEV)

Copy link
Contributor Author

@angbrav angbrav Jul 5, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to request a cross-chain query from a given height, i.e., query.H >= h?

No. That would allow relayers to query a height that has a value that benefits it somehow

worth noting, this still creates an opportunity for altering state on the queried chain if the height is in the future in order to change the results of the query

Well, at least this prevents relayers from benefiting from it. A solution would be to enforce that the height is not in the future, e.g, we can check at the Querying Chain when the query request is processed that the height at the Queried Chain is already over the queries' height.


### 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe rename this as CCQRequest, and CrossChainQueryResult as CCQResponse (to distinguish between the response of a query and the result of the query).

id: Identifier
path: CommitmentPath
localTimeoutHeight: Height
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming++

localTimeoutTimestamp: Height
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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- The `clientId` field identifies 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:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In this context, this ICS defines the `QueryResult` type as follows:
In this context, this standard 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 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.

```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

#### Query path

The query path is a private path that stores the state of ongoing cross-chain queries.

```typescript
function queryPath(id: Identifier): Path {
return "queries/{id}"
}
```
#### Result query path

The result query path is a private path that stores the result of completed queries.

```typescript
function resultQueryPath(id: Identifier): Path {
return "queriesresult/{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 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Query requests can be submitted by other IBC modules

Why only IBC modules?

Copy link
Contributor Author

@angbrav angbrav Jul 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I discussed this with Aditya. I believe the conclusion was that in most cases, cross-chain queries would be issued by IBC modules, as end users would directly query the queried chain. At least this is what I remember, but maybe @AdityaSripal can elaborate a bit more. In any case, I think that even if that's the most common use case, we should keep the specification as general as possible, so I will fix the text.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this calls the CrossChainQueryResult function

Name collision with the data structure for results / responses.

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.

#### 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,
queryHeight: Height,
localTimeoutHeight: Height,
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.
abortTransactionUnless(queryClientState(clientId) !== null)

// 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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localTimeoutTimestamp is missing from function signature.


// Generate a unique query identifier.
queryIdentifier = generateQueryIdentifier()

// Create a query request record.
query = CrossChainQuery{queryIdentifier,
path,
queryHeight,
localTimeoutHeight,
localTimeoutTimestamp,
clientId,
bounty}

// 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, queryCapability]
}
```
- **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.
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(
queryId: Identifier,
data: []byte
proof: CommitmentProof,
proofHeight: Height,
delayPeriodTime: uint64,
delayPeriodBlocks: uint64,
relayer: string
) {

// 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.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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split on multiple lines

if (data !== null) {
abortTransactionUnless(client.verifyMemership(
client,
proofHeight,
delayPeriodTime,
delayPeriodBlocks,
proof,
query.path,
data
))
result = SUCCESS
} else {
abortTransactionUnless(client.verifyNonMemership(
client,
proofHeight,
delayPeriodTime,
delayPeriodBlocks,
proof,
query.path,
))
result = FAILURE
}

// Delete the query from the local, private store.
privateStore.delete(queryPath(queryId))

// Create a query result record.
resultRecord = CrossChainQuery{queryIdentifier,
result,
data}

// Store the result in the local, private store.
privateStore.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 `privateStore`.

The `PruneCrossChainQueryResult` function is called when the caller of a query has retrieved the result and wants to delete it.

```typescript
function PruneCrossChainQueryResult(
queryId: Identifier,
queryCapability: CapabilityKey
) {

// 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 the local, private store.
privateStore.delete(resultQueryPath(queryId))
}
```
- **Precondition**
- 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 `privateStore`.

#### 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.

The Querying Chain calls the `checkQueryTimeout` function to check whether a specific query has timed out.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand how the "Querying Chain calls the checkQueryTimeout function". Is this a transaction? The pseudocode seems to contain several abortTransactionUnless.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends. The spec discusses two alternatives: in one checkQueryTimeout is a transaction. I understand the confusion though. Ideally, this function (since it can be both a transaction or simply a function call from the module) would not include any abortTransactionUnless. Any suggestion on how to solve this? I can think of two solutions:

  • remove abortTransactionUnless from checkQueryTimeout and use if-else conditions. Probably the best solution.
  • commit to one of the discussed alternatives

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have opted for the second solution.


> 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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove new line (i.e., there are two spaces at the end of the prev line)


We pass the relayer address just as in `CrossChainQueryResult` to allow for possible incentivization here as well.

```typescript
function checkQueryTimeout(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should store state in such a way that we can efficiently iterate over existing queries in timeout order (for timestamp and height), though this may be left as details for implementation

Copy link
Contributor Author

@angbrav angbrav Jun 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is an implementation detail. I have added the following sentence: "In this case, ongoing queries should be stored indexed by localTimeoutTimestamp and localTimeoutHeight to allow iterating over them more efficiently". In any case, if we think we should make this explicit in the spec, I can do the modification.

queryId: Identifier,
relayer: string
){
// 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()

// 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()))

// Delete the query from the local, private store if it has timed out
privateStore.delete(queryPath(queryId))

// Create a query result record.
resultRecord = CrossChainQuery{queryIdentifier,
TIMEOUT,
query.caller
null}

// Store the result in the local, private store.
privateStore.set(resultQueryPath(queryIdentifier), resultRecord)
}
```
- **Precondition**
- There is a query request stored in the `privateStore` identified by `queryId`.
- **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 `privateStore`.

## History

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).