hermes_cli/commands/query/
connections.rs1use hermes_cli_components::traits::build::CanLoadBuilder;
2use hermes_cli_framework::command::CommandRunner;
3use hermes_cli_framework::output::{json, Output};
4use hermes_cosmos_chain_components::traits::chain_handle::HasBlockingChainHandle;
5use hermes_cosmos_relayer::contexts::chain::CosmosChain;
6use hermes_relayer_components::chain::traits::queries::client_state::CanQueryClientStateWithLatestHeight;
7use ibc_relayer::chain::handle::ChainHandle;
8use ibc_relayer::chain::requests::{PageRequest, QueryConnectionsRequest};
9use ibc_relayer_types::core::ics02_client::client_state::ClientState;
10use ibc_relayer_types::core::ics24_host::identifier::ChainId;
11use tracing::{info, warn};
12
13use crate::contexts::app::HermesApp;
14use crate::impls::error_wrapper::ErrorWrapper;
15use crate::Result;
16
17#[derive(Debug, clap::Parser)]
18pub struct QueryConnections {
19 #[clap(
20 long = "chain",
21 required = true,
22 value_name = "CHAIN_ID",
23 help_heading = "REQUIRED",
24 help = "Identifier of the chain to query"
25 )]
26 chain_id: ChainId,
27
28 #[clap(
29 long = "counterparty-chain",
30 value_name = "COUNTERPARTY_CHAIN_ID",
31 help = "Filter the query response by the counterparty chain"
32 )]
33 counterparty_chain_id: Option<ChainId>,
34
35 #[clap(
36 long = "verbose",
37 help = "Enable verbose output, displaying the client for each connection in the response"
38 )]
39 verbose: bool,
40}
41
42impl CommandRunner<HermesApp> for QueryConnections {
43 async fn run(&self, app: &HermesApp) -> Result<Output> {
44 let builder = app.load_builder().await?;
45
46 let chain = builder.build_chain(&self.chain_id).await?;
47 let chain_id = self.chain_id.clone();
48 let counterparty_chain_id = self.counterparty_chain_id.clone();
49 let verbose = self.verbose;
50
51 let all_connections = chain
52 .with_blocking_chain_handle(move |handle| {
53 handle
54 .query_connections(QueryConnectionsRequest {
55 pagination: Some(PageRequest::all()),
56 })
57 .map_err(From::from)
58 })
59 .await
60 .wrap_error("Failed to query connections for host chain")?;
61
62 info!(
63 "Found {} connections on chain `{chain_id}`",
64 all_connections.len()
65 );
66
67 let connections = if let Some(filter_chain_id) = counterparty_chain_id {
68 let mut connections = Vec::new();
69
70 for connection in all_connections {
71 let client_id = connection.end().client_id().to_owned();
72
73 let client_state = <CosmosChain as CanQueryClientStateWithLatestHeight<
74 CosmosChain,
75 >>::query_client_state_with_latest_height(
76 &chain, &client_id
77 )
78 .await;
79
80 let include = match client_state {
81 Ok(client_state) => {
82 let counterparty_chain_id = client_state.chain_id();
83 counterparty_chain_id == filter_chain_id
84 }
85 Err(e) => {
86 warn!("failed to query client state for client `{client_id}`, skipping...");
87 warn!("reason: {e}");
88
89 false
90 }
91 };
92
93 if include {
94 connections.push(connection);
95 }
96 }
97
98 info!(
99 "Found {} connections on chain `{chain_id}` with counterparty chain `{filter_chain_id}`",
100 connections.len()
101 );
102
103 connections
104 } else {
105 all_connections
106 };
107
108 if json() {
109 if verbose {
110 Ok(Output::success(connections))
111 } else {
112 let connection_ids = connections
113 .into_iter()
114 .map(|connection| connection.connection_id)
115 .collect::<Vec<_>>();
116
117 Ok(Output::success(connection_ids))
118 }
119 } else {
120 connections.iter().for_each(|connection| {
121 if verbose {
122 info!("- {connection:#?}",);
123 } else {
124 info!("- {}", connection.connection_id);
125 }
126 });
127
128 Ok(Output::success_msg(format!(
129 "Total: {} connections",
130 connections.len()
131 )))
132 }
133 }
134}