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

Add query client status command #3044

Merged
merged 7 commits into from
Mar 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Everything seems good to me. Just missing the 3 unit tests for the new CLI

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