hermes_cli/commands/query/
channels.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_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}