hermes_cli/commands/query/
channels.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_relayer_components::chain::traits::queries::chain_status::CanQueryChainHeight;
6use ibc_relayer::chain::handle::ChainHandle;
7use ibc_relayer::chain::requests::{
8    IncludeProof, PageRequest, QueryChannelsRequest, QueryClientStateRequest,
9    QueryConnectionRequest, QueryHeight,
10};
11use ibc_relayer_types::core::ics04_channel::channel::State;
12use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ChannelId, PortId};
13use tracing::{info, warn};
14
15use crate::contexts::app::HermesApp;
16use crate::impls::error_wrapper::ErrorWrapper;
17use crate::Result;
18
19#[derive(Debug, clap::Parser)]
20pub struct QueryChannels {
21    #[clap(
22        long = "chain",
23        required = true,
24        value_name = "CHAIN_ID",
25        help_heading = "REQUIRED",
26        help = "Identifier of the chain to query"
27    )]
28    chain_id: ChainId,
29
30    #[clap(
31        long = "counterparty-chain",
32        value_name = "COUNTERPARTY_CHAIN_ID",
33        help = "Filter the query response by the counterparty chain"
34    )]
35    counterparty_chain_id: Option<ChainId>,
36
37    #[clap(
38        long = "show-counterparty",
39        help = "Show the counterparty's chain, port, and channel"
40    )]
41    show_counterparty: bool,
42}
43
44impl CommandRunner<HermesApp> for QueryChannels {
45    async fn run(&self, app: &HermesApp) -> Result<Output> {
46        let builder = app.load_builder().await?;
47
48        let chain = builder.build_chain(&self.chain_id).await?;
49        let chain_id = self.chain_id.clone();
50        let dst_chain_id = self.counterparty_chain_id.clone();
51        let show_counterparty = self.show_counterparty;
52
53        let all_channels = chain
54            .with_blocking_chain_handle(move |chain_handle| {
55                chain_handle
56                    .query_channels(QueryChannelsRequest {
57                        pagination: Some(PageRequest::all()),
58                    })
59                    .map_err(From::from)
60            })
61            .await?;
62
63        let chain_height = chain
64            .query_chain_height()
65            .await
66            .wrap_error("Failed to query latest chain height")?;
67
68        let mut channels = Vec::new();
69
70        for channel in all_channels {
71            let port_id = &channel.port_id;
72            let channel_id = &channel.channel_id;
73            let chain_id = chain_id.clone();
74            let channel_end = &channel.channel_end;
75
76            if channel_end.state_matches(&State::Uninitialized) {
77                warn!(
78                    "channel `{port_id}/{channel_id}` on chain `{chain_id}` at {chain_height} is uninitialized"
79                );
80
81                continue;
82            }
83
84            let Some(connection_id) = channel.channel_end.connection_hops.first() else {
85                warn!(
86                    "missing connection hops for `{port_id}/{channel_id}` on chain `{chain_id}` at `{chain_height}`"
87                );
88
89                continue;
90            };
91
92            let counterparty = if show_counterparty || dst_chain_id.is_some() {
93                let connection_id = connection_id.clone();
94                let connection_end = chain
95                    .with_blocking_chain_handle(move |handle| {
96                        handle
97                            .query_connection(
98                                QueryConnectionRequest {
99                                    connection_id,
100                                    height: QueryHeight::Specific(chain_height),
101                                },
102                                IncludeProof::No,
103                            )
104                            .map_err(From::from)
105                    })
106                    .await;
107
108                let Ok((connection_end, _)) = connection_end else {
109                    warn!(
110                        "missing connection end for `{port_id}/{channel_id}` on chain `{chain_id}` at {chain_height}"
111                    );
112
113                    continue;
114                };
115
116                let client_id = connection_end.client_id().clone();
117                let client_state = chain
118                    .with_blocking_chain_handle(move |handle| {
119                        handle
120                            .query_client_state(
121                                QueryClientStateRequest {
122                                    client_id,
123                                    height: QueryHeight::Specific(chain_height),
124                                },
125                                IncludeProof::No,
126                            )
127                            .map_err(From::from)
128                    })
129                    .await;
130
131                let Ok((client_state, _)) = client_state else {
132                    warn!("missing client state for {port_id}/{channel_id} on chain {chain_id} at {chain_height}");
133
134                    continue;
135                };
136
137                let client_state_chain_id = client_state.chain_id();
138                let client_state_chain_id_matches_dst_chain_id = dst_chain_id
139                    .as_ref()
140                    .map(|dst_chain_id| dst_chain_id == &client_state_chain_id)
141                    .unwrap_or(true);
142
143                if !client_state_chain_id_matches_dst_chain_id {
144                    continue;
145                }
146
147                let counterparty = channel_end.counterparty();
148
149                Some(Counterparty {
150                    chain_id: client_state_chain_id.clone(),
151                    port_id: counterparty.port_id.clone(),
152                    channel_id: counterparty.channel_id.clone(),
153                })
154            } else {
155                None
156            };
157
158            channels.push((channel, counterparty));
159        }
160
161        info!("Found {} channels on chain `{chain_id}`", channels.len());
162
163        if json() {
164            let channels = channels
165                .into_iter()
166                .map(|(channel, counterparty)| {
167                    let (port_id, channel_id) = (channel.port_id, channel.channel_id);
168
169                    let mut result = serde_json::json!({
170                        "port_id": port_id,
171                        "channel_id": channel_id,
172                    });
173
174                    if let Some(counterparty) = counterparty {
175                        result["counterparty"] = serde_json::to_value(counterparty).unwrap();
176                    }
177
178                    result
179                })
180                .collect::<Vec<_>>();
181
182            return Ok(Output::success(channels));
183        }
184
185        channels.iter().for_each(|(channel, counterparty)| {
186            info!("- {}/{}", channel.port_id, channel.channel_id);
187
188            if let Some(counterparty) = counterparty {
189                info!(
190                    "  - counterparty: {}/{} on chain {}",
191                    counterparty.port_id,
192                    counterparty
193                        .channel_id
194                        .as_ref()
195                        .map_or("unknown".to_string(), |c| c.to_string()),
196                    counterparty.chain_id
197                );
198            }
199        });
200
201        Ok(Output::success_msg(format!(
202            "Total: {} channels",
203            channels.len()
204        )))
205    }
206}
207
208#[derive(Debug, serde::Serialize)]
209struct Counterparty {
210    chain_id: ChainId,
211    port_id: PortId,
212    channel_id: Option<ChannelId>,
213}