hermes_cli/commands/query/
connections.rs

1use 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}