Skip to content

Commit

Permalink
Add query client status command (#3044)
Browse files Browse the repository at this point in the history
  • Loading branch information
romac authored Mar 24, 2023
1 parent d4b1e39 commit fa2c038
Show file tree
Hide file tree
Showing 9 changed files with 174 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Fix `query client consensus` to sort the consensus states by height
([\#3041](https://github.com/informalsystems/hermes/issues/3041))
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Add a `query client status` command to query whether a client is active, frozen
or expired ([\#3124](https://github.com/informalsystems/hermes/issues/3124))
3 changes: 3 additions & 0 deletions crates/relayer-cli/src/commands/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ pub enum QueryClientCmds {
/// Query for the header used in a client update at a certain height
Header(client::QueryClientHeaderCmd),

/// Query the client status (frozen, expired or active)
Status(client::QueryClientStatusCmd),

/// Query the client connections
Connections(client::QueryClientConnectionsCmd),
}
Expand Down
128 changes: 127 additions & 1 deletion crates/relayer-cli/src/commands/query/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use abscissa_core::clap::Parser;
use abscissa_core::{Command, Runnable};
use color_eyre::eyre::eyre;

use ibc_relayer::chain::handle::ChainHandle;
use ibc_relayer::chain::requests::{
Expand Down Expand Up @@ -258,6 +259,104 @@ impl Runnable for QueryClientHeaderCmd {
}
}

/// Query client status command
#[derive(Clone, Command, Debug, Parser, PartialEq, Eq)]
pub struct QueryClientStatusCmd {
#[clap(
long = "chain",
required = true,
value_name = "CHAIN_ID",
help_heading = "REQUIRED",
help = "Identifier of the chain to query"
)]
chain_id: ChainId,

#[clap(
long = "client",
required = true,
value_name = "CLIENT_ID",
help_heading = "REQUIRED",
help = "Identifier of the client to query"
)]
client_id: ClientId,
}

impl Runnable for QueryClientStatusCmd {
fn run(&self) {
let config = app_config();

let chain = spawn_chain_runtime(&config, &self.chain_id)
.unwrap_or_else(exit_with_unrecoverable_error);

let status =
client_status(&chain, &self.client_id).unwrap_or_else(exit_with_unrecoverable_error);

Output::success(status).exit()
}
}

#[derive(Debug, serde::Serialize)]
enum Status {
Frozen,
Expired,
Active,
}

fn client_status(
chain: &impl ChainHandle,
client_id: &ClientId,
) -> Result<Status, color_eyre::Report> {
let (client_state, _) = chain.query_client_state(
QueryClientStateRequest {
client_id: client_id.clone(),
height: QueryHeight::Latest,
},
IncludeProof::No,
)?;

if client_state.is_frozen() {
return Ok(Status::Frozen);
}

let consensus_state_heights =
chain.query_consensus_state_heights(QueryConsensusStateHeightsRequest {
client_id: client_id.clone(),
pagination: Some(PageRequest::all()),
})?;

let latest_consensus_height = consensus_state_heights.last().copied().ok_or_else(|| {
eyre!(
"no consensus state found for client '{}' on chain '{}'",
client_id,
chain.id()
)
})?;

let (latest_consensus_state, _) = chain.query_consensus_state(
QueryConsensusStateRequest {
client_id: client_id.clone(),
consensus_height: latest_consensus_height,
query_height: QueryHeight::Latest,
},
IncludeProof::No,
)?;

// Fetch the application status, for the network time
let app_status = chain.query_application_status()?;
let current_src_network_time = app_status.timestamp;

// Compute the duration of time elapsed since this consensus state was installed
let elapsed = current_src_network_time
.duration_since(&latest_consensus_state.timestamp())
.unwrap_or_default();

if client_state.expired(elapsed) {
Ok(Status::Expired)
} else {
Ok(Status::Active)
}
}

/// Query client connections command
#[derive(Clone, Command, Debug, Parser, PartialEq, Eq)]
pub struct QueryClientConnectionsCmd {
Expand Down Expand Up @@ -310,7 +409,7 @@ impl Runnable for QueryClientConnectionsCmd {
mod tests {
use super::{
QueryClientConnectionsCmd, QueryClientConsensusCmd, QueryClientHeaderCmd,
QueryClientStateCmd,
QueryClientStateCmd, QueryClientStatusCmd,
};

use std::str::FromStr;
Expand Down Expand Up @@ -566,4 +665,31 @@ mod tests {
fn test_query_client_state_no_chain() {
assert!(QueryClientStateCmd::try_parse_from(["test", "--client", "client_id"]).is_err())
}

#[test]
fn test_query_client_status_required_only() {
assert_eq!(
QueryClientStatusCmd {
chain_id: ChainId::from_string("chain_id"),
client_id: ClientId::from_str("client_id").unwrap(),
},
QueryClientStatusCmd::parse_from([
"test",
"--chain",
"chain_id",
"--client",
"client_id"
])
)
}

#[test]
fn test_query_client_status_no_chain() {
assert!(QueryClientStatusCmd::try_parse_from(["test", "--client", "client_id"]).is_err())
}

#[test]
fn test_query_client_status_no_client() {
assert!(QueryClientStatusCmd::try_parse_from(["test", "--chain", "chain_id"]).is_err())
}
}
8 changes: 6 additions & 2 deletions crates/relayer/src/chain/cosmos/query/consensus_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub async fn query_consensus_state_heights(

let response = grpc_response.map_err(Error::grpc_status)?.into_inner();

let heights: Vec<_> = response
let mut heights: Vec<_> = response
.consensus_state_heights
.into_iter()
.filter_map(|h| {
Expand All @@ -74,6 +74,8 @@ pub async fn query_consensus_state_heights(
})
.collect();

heights.sort_unstable();

Ok(heights)
}

Expand All @@ -96,7 +98,7 @@ pub async fn query_consensus_states(
.map_err(Error::grpc_status)?
.into_inner();

let consensus_states: Vec<_> = response
let mut consensus_states: Vec<_> = response
.consensus_states
.into_iter()
.filter_map(|cs| {
Expand All @@ -112,5 +114,7 @@ pub async fn query_consensus_states(
})
.collect();

consensus_states.sort_unstable_by_key(|cs| cs.height);

Ok(consensus_states)
}
20 changes: 20 additions & 0 deletions guide/src/documentation/commands/queries/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,23 @@ Success: [
),
]
```

## Query for the status of client (active, frozen, or expired)

This command queries the status of a client, ie. whether it is active, frozen or expired.

```
{{#include ../../../templates/help_templates/query/client/status.md}}
```

__Example__

Query for the status of the client `07-tendermint-0` on `ibc-0`:

```shell
{{#template ../../../templates/commands/hermes/query/client/status_1.md CHAIN_ID=ibc-0 CLIENT_ID=07-tendermint-0}}
```

```
SUCCESS Active
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[[#BINARY hermes]][[#GLOBALOPTIONS]] query client status --chain [[#CHAIN_ID]] --client [[#CLIENT_ID]]
1 change: 1 addition & 0 deletions guide/src/templates/help_templates/query/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ SUBCOMMANDS:
header Query for the header used in a client update at a certain height
help Print this message or the help of the given subcommand(s)
state Query the client state
status Query the client status (frozen, expired or active)
12 changes: 12 additions & 0 deletions guide/src/templates/help_templates/query/client/status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
DESCRIPTION:
Query the client status (frozen, expired or active)

USAGE:
hermes query client status --chain <CHAIN_ID> --client <CLIENT_ID>

OPTIONS:
-h, --help Print help information

REQUIRED:
--chain <CHAIN_ID> Identifier of the chain to query
--client <CLIENT_ID> Identifier of the client to query

0 comments on commit fa2c038

Please sign in to comment.