hermes_cli/commands/query/
clients.rs

1use std::fmt;
2
3use cgp::core::error::HasErrorType;
4use hermes_any_counterparty::contexts::any_counterparty::AnyCounterparty;
5use hermes_any_counterparty::types::client_state::AnyClientState;
6use hermes_cli_components::traits::build::CanLoadBuilder;
7use hermes_cli_framework::command::CommandRunner;
8use hermes_cli_framework::output::{json, Output};
9use hermes_cosmos_chain_components::types::tendermint::TendermintClientState;
10use hermes_cosmos_relayer::contexts::chain::CosmosChain;
11use hermes_error::types::Error;
12use hermes_relayer_components::chain::traits::queries::client_state::CanQueryAllClientStatesWithLatestHeight;
13use hermes_relayer_components::chain::traits::types::client_state::HasClientStateType;
14use hermes_relayer_components::chain::traits::types::ibc::HasIbcChainTypes;
15use ibc_relayer_types::core::ics02_client::client_state::ClientState;
16use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ClientId};
17use tracing::info;
18
19use crate::contexts::app::HermesApp;
20use crate::impls::error_wrapper::ErrorWrapper;
21use crate::Result;
22
23#[derive(Debug, clap::Parser)]
24pub struct QueryClients {
25    #[clap(
26        long = "host-chain",
27        required = true,
28        value_name = "HOST_CHAIN_ID",
29        help_heading = "REQUIRED",
30        help = "Identifier of the host chain to query"
31    )]
32    host_chain_id: ChainId,
33
34    #[clap(
35        long = "reference-chain",
36        value_name = "REFERENCE_CHAIN_ID",
37        help = "Only show clients that reference this chain"
38    )]
39    reference_chain_id: Option<ChainId>,
40
41    #[clap(
42        long = "verbose",
43        help = "Enable verbose output, displaying each's client state"
44    )]
45    verbose: bool,
46}
47
48impl CommandRunner<HermesApp> for QueryClients {
49    async fn run(&self, app: &HermesApp) -> Result<Output> {
50        let builder = app.load_builder().await?;
51
52        let chain = builder.build_chain(&self.host_chain_id).await?;
53        let clients = query_all_client_states::<CosmosChain, AnyCounterparty>(
54            &chain,
55            &self.host_chain_id,
56            self.reference_chain_id.as_ref(),
57        )
58        .await?;
59
60        if !json() {
61            clients.iter().for_each(|client| {
62                if self.verbose {
63                    info!("- {client:#?}",);
64                } else {
65                    info!(
66                        "- {}: {} -> {}",
67                        client.client_id,
68                        self.host_chain_id,
69                        client.client_state.chain_id()
70                    );
71                }
72            });
73        }
74
75        if json() {
76            if self.verbose {
77                Ok(Output::success(clients))
78            } else {
79                let client_ids = clients
80                    .into_iter()
81                    .map(|client| {
82                        serde_json::json!({
83                            "client_id": client.client_id,
84                            "host_chain_id": self.host_chain_id,
85                            "reference_chain_id": client.client_state.chain_id(),
86                        })
87                    })
88                    .collect::<Vec<_>>();
89
90                Ok(Output::success(client_ids))
91            }
92        } else {
93            Ok(Output::success_msg(format!(
94                "Total: {} clients",
95                clients.len()
96            )))
97        }
98    }
99}
100
101#[derive(serde::Serialize)]
102struct Client<Chain, Counterparty>
103where
104    Chain: HasIbcChainTypes<Counterparty>,
105    Counterparty: HasClientStateType<Chain>,
106{
107    client_id: Chain::ClientId,
108    client_state: Counterparty::ClientState,
109}
110
111impl<Chain, Counterparty> fmt::Debug for Client<Chain, Counterparty>
112where
113    Chain: HasIbcChainTypes<Counterparty>,
114    Counterparty: HasClientStateType<Chain>,
115    Counterparty::ClientState: fmt::Debug,
116    for<'a> Pretty<'a, Counterparty::ClientState>: fmt::Debug,
117{
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        f.debug_struct("Client")
120            .field("client_id", &self.client_id)
121            .field("client_state", &Pretty(&self.client_state))
122            .finish()
123    }
124}
125
126async fn query_all_client_states<Chain, Counterparty>(
127    chain: &Chain,
128    host_chain_id: &ChainId,
129    reference_chain_id: Option<&ChainId>,
130) -> Result<Vec<Client<Chain, Counterparty>>>
131where
132    Chain: HasIbcChainTypes<Counterparty, ClientId = ClientId>
133        + CanQueryAllClientStatesWithLatestHeight<Counterparty>
134        + HasErrorType<Error = Error>,
135    Counterparty: HasClientStateType<Chain, ClientState = AnyClientState>,
136{
137    let mut clients = chain
138        .query_all_client_states_with_latest_height()
139        .await
140        .wrap_error("Failed to query clients")?
141        .into_iter()
142        .map(|(client_id, client_state)| Client::<Chain, Counterparty> {
143            client_id,
144            client_state,
145        })
146        .collect::<Vec<_>>();
147
148    info!("Found {} clients on chain `{host_chain_id}`", clients.len());
149
150    if let Some(reference_chain_id) = reference_chain_id {
151        clients.retain(|client| client.client_state.chain_id() == reference_chain_id);
152
153        info!(
154            "Found {} clients that reference `{reference_chain_id}`",
155            clients.len()
156        );
157    }
158
159    Ok(clients)
160}
161
162pub struct Pretty<'a, A: ?Sized>(&'a A);
163
164impl<'a> fmt::Debug for Pretty<'a, TendermintClientState> {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        let client_state = self.0;
167
168        f.debug_struct("ClientState")
169            .field("chain_id", &client_state.chain_id.to_string())
170            .field("client_type", &client_state.client_type())
171            .field("latest_height", &client_state.latest_height.to_string())
172            .field("trust_threshold", &client_state.trust_threshold.to_string())
173            .field("trusting_period", &client_state.trusting_period)
174            .field("unbonding_period", &client_state.unbonding_period)
175            .field("max_clock_drift", &client_state.max_clock_drift)
176            .field("frozen_height", &client_state.frozen_height)
177            .finish()
178    }
179}
180
181impl<'a> fmt::Debug for Pretty<'a, AnyClientState> {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self.0 {
184            AnyClientState::Tendermint(ref client_state) => Pretty(client_state).fmt(f),
185            // AnyClientState::Sovereign(ref client_state) => Pretty(client_state).fmt(f),
186        }
187    }
188}