hermes_cli/commands/query/
clients.rs1use 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 }
187 }
188}