pcli/command/query/
ibc_query.rs1use std::time::SystemTime;
2
3use anyhow::Result;
4use colored_json::ToColoredJson;
5use comfy_table::Table;
6use ibc_proto::ibc::core::channel::v1::query_client::QueryClient as ChannelQueryClient;
7use ibc_proto::ibc::core::channel::v1::{
8 IdentifiedChannel, QueryChannelConsensusStateRequest, QueryChannelRequest, QueryChannelsRequest,
9};
10use ibc_proto::ibc::core::client::v1::query_client::QueryClient as ClientQueryClient;
11use ibc_proto::ibc::core::client::v1::{QueryClientStateRequest, QueryClientStatesRequest};
12use ibc_proto::ibc::core::connection::v1::query_client::QueryClient as ConnectionQueryClient;
13use ibc_proto::ibc::core::connection::v1::{
14 ConnectionEnd, QueryConnectionRequest, QueryConnectionsRequest,
15};
16use ibc_types::core::channel::channel::State;
17use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState;
18use ibc_types::lightclients::tendermint::consensus_state::ConsensusState as TendermintConsensusState;
19
20use crate::App;
21
22#[derive(Debug, clap::Subcommand)]
26pub enum IbcCmd {
27 Client { client_id: String },
30 Clients {},
32 Connection { connection_id: u64 },
35 Connections {},
37 Channel {
40 #[clap(long, default_value = "transfer")]
43 port: String,
44
45 channel_id: u64,
50 },
51 Channels {},
53}
54
55struct ChannelInfo {
56 channel: IdentifiedChannel,
57 connection: ConnectionEnd,
58 client: TendermintClientState,
59 consensus_state: TendermintConsensusState,
60}
61
62impl IbcCmd {
63 pub async fn exec(&self, app: &mut App) -> Result<()> {
64 match self {
65 IbcCmd::Client { client_id } => {
66 let mut ibc_client = ClientQueryClient::new(app.pd_channel().await?);
67 let req = QueryClientStateRequest {
68 client_id: client_id.to_string(),
69 };
70 let client_state = match ibc_client
71 .client_state(req)
72 .await?
73 .into_inner()
74 .client_state
75 {
76 Some(c) => TendermintClientState::try_from(c)?,
77 None => {
78 anyhow::bail!("Client id not found: {}", client_id);
79 }
80 };
81 let client_state_json = serde_json::to_string_pretty(&client_state)?;
82 println!("{}", client_state_json.to_colored_json_auto()?);
83 }
84 IbcCmd::Clients {} => {
85 let mut ibc_client = ClientQueryClient::new(app.pd_channel().await?);
86 let req = QueryClientStatesRequest {
87 pagination: None,
89 };
90 let client_states: Vec<_> = ibc_client
91 .client_states(req)
92 .await?
93 .into_inner()
94 .client_states
95 .into_iter()
96 .filter_map(|s| s.client_state)
97 .map(TendermintClientState::try_from)
98 .collect::<Result<Vec<_>, _>>()?;
99
100 let clients_json = serde_json::to_string_pretty(&client_states)?;
101 println!("{}", clients_json.to_colored_json_auto()?);
102 }
103 IbcCmd::Connection { connection_id } => {
104 let mut ibc_client = ConnectionQueryClient::new(app.pd_channel().await?);
105 let c = format!("connection-{}", connection_id);
106 let req = QueryConnectionRequest {
107 connection_id: c.to_owned(),
108 };
109 let connection = ibc_client.connection(req).await?.into_inner().connection;
110 if connection.is_none() {
111 anyhow::bail!("Could not find '{c}'");
112 }
113 let connection_json = serde_json::to_string_pretty(&connection)?;
114 println!("{}", connection_json.to_colored_json_auto()?);
115 }
116 IbcCmd::Connections {} => {
117 let mut ibc_client = ConnectionQueryClient::new(app.pd_channel().await?);
118 let req = QueryConnectionsRequest {
119 pagination: None,
121 };
122 let connections = ibc_client.connections(req).await?.into_inner().connections;
123 let connections_json = serde_json::to_string_pretty(&connections)?;
124 println!("{}", connections_json.to_colored_json_auto()?);
125 }
126 IbcCmd::Channel { port, channel_id } => {
127 let mut channel_client = ChannelQueryClient::new(app.pd_channel().await?);
128 let mut connection_client = ConnectionQueryClient::new(app.pd_channel().await?);
129 let mut client_client = ClientQueryClient::new(app.pd_channel().await?);
130
131 let channel = channel_client
132 .channel(QueryChannelRequest {
133 port_id: port.to_string(),
134 channel_id: format!("channel-{channel_id}"),
135 })
136 .await?
137 .into_inner()
138 .channel
139 .ok_or_else(|| anyhow::anyhow!("channel not found"))?;
140 let connection = connection_client
141 .connection(QueryConnectionRequest {
142 connection_id: channel.connection_hops[0].clone(),
143 })
144 .await?
145 .into_inner()
146 .connection
147 .ok_or_else(|| anyhow::anyhow!("connection for channel not found"))?;
148 let client_state = client_client
149 .client_state(QueryClientStateRequest {
150 client_id: connection.client_id.clone(),
151 })
152 .await?
153 .into_inner()
154 .client_state
155 .ok_or_else(|| anyhow::anyhow!("client state not found"))?;
156 let client_state = TendermintClientState::try_from(client_state)?;
157 let channel_consensus_state = channel_client
158 .channel_consensus_state(QueryChannelConsensusStateRequest {
159 port_id: port.to_string(),
160 channel_id: format!("channel-{}", channel_id),
161 revision_height: client_state.latest_height().revision_height,
162 revision_number: client_state.latest_height().revision_number,
163 })
164 .await?
165 .into_inner()
166 .consensus_state
167 .ok_or_else(|| anyhow::anyhow!("consensus state not found for channel"))?;
168
169 let tendermint_consensus_state =
170 TendermintConsensusState::try_from(channel_consensus_state)?;
171
172 let mut table = Table::new();
173 table.set_header(vec![
174 "Channel ID",
175 "Port",
176 "Counterparty",
177 "Counterparty Channel ID",
178 "State",
179 "Client ID",
180 "Client Height",
181 ]);
182 let mut state_str = State::from_i32(channel.state)
183 .expect("invalid state value")
184 .to_string();
185
186 let current_time: time::OffsetDateTime = SystemTime::now().into();
187 let current_time_tm: tendermint::Time = current_time.try_into()?;
188
189 let time_elapsed =
190 current_time_tm.duration_since(tendermint_consensus_state.timestamp)?;
191 if client_state.expired(time_elapsed) {
192 state_str = "CLIENT EXPIRED".to_string();
193 }
194 table.add_row(vec![
195 channel_id.to_string(),
196 port.to_string(),
197 client_state.chain_id.to_string(),
198 channel
199 .counterparty
200 .ok_or_else(|| anyhow::anyhow!("counterparty not found"))?
201 .channel_id
202 .to_string(),
203 state_str,
204 connection.client_id.to_string(),
205 client_state.latest_height.to_string(),
206 ]);
207
208 println!("{table}")
209 }
210 IbcCmd::Channels {} => {
211 let mut channel_client = ChannelQueryClient::new(app.pd_channel().await?);
212 let mut connection_client = ConnectionQueryClient::new(app.pd_channel().await?);
213 let mut client_client = ClientQueryClient::new(app.pd_channel().await?);
214
215 let req = QueryChannelsRequest {
216 pagination: None,
218 };
219
220 let mut channel_infos = vec![];
221 let channels = channel_client.channels(req).await?.into_inner().channels;
222 for channel in channels {
223 let connection = connection_client
224 .connection(QueryConnectionRequest {
225 connection_id: channel.connection_hops[0].clone(),
226 })
227 .await?
228 .into_inner()
229 .connection
230 .ok_or_else(|| anyhow::anyhow!("connection for channel not found"))?;
231 let client_state = client_client
232 .client_state(QueryClientStateRequest {
233 client_id: connection.client_id.clone(),
234 })
235 .await?
236 .into_inner()
237 .client_state
238 .ok_or_else(|| anyhow::anyhow!("client state not found"))?;
239 let client_state = TendermintClientState::try_from(client_state.clone())?;
240 let channel_consensus_state = channel_client
241 .channel_consensus_state(QueryChannelConsensusStateRequest {
242 port_id: channel.clone().port_id.to_string(),
243 channel_id: channel.clone().channel_id,
244 revision_height: client_state.latest_height().revision_height,
245 revision_number: client_state.latest_height().revision_number,
246 })
247 .await?
248 .into_inner()
249 .consensus_state
250 .ok_or_else(|| anyhow::anyhow!("consensus state not found for channel"))?;
251
252 let tendermint_consensus_state =
253 TendermintConsensusState::try_from(channel_consensus_state)?;
254
255 channel_infos.push(ChannelInfo {
256 channel,
257 connection,
258 client: client_state,
259 consensus_state: tendermint_consensus_state,
260 });
261 }
262
263 let mut table = Table::new();
264 table.set_header(vec![
265 "Channel ID",
266 "Port",
267 "Counterparty",
268 "Counterparty Channel ID",
269 "State",
270 "Client ID",
271 "Client Height",
272 ]);
273
274 for info in channel_infos {
275 let mut state_str = State::from_i32(info.channel.state)
276 .expect("invalid state value")
277 .to_string();
278 let current_time: time::OffsetDateTime = SystemTime::now().into();
279 let current_time_tm: tendermint::Time = current_time.try_into()?;
280
281 let time_elapsed =
282 current_time_tm.duration_since(info.consensus_state.timestamp)?;
283 if info.client.expired(time_elapsed) {
284 state_str = "CLIENT EXPIRED".to_string();
285 }
286 table.add_row(vec![
287 info.channel.channel_id.to_string(),
288 info.channel.port_id,
289 info.client.chain_id.to_string(),
290 info.channel
291 .counterparty
292 .ok_or_else(|| anyhow::anyhow!("counterparty not found"))?
293 .channel_id
294 .to_string(),
295 state_str,
296 info.connection.client_id.to_string(),
297 info.client.latest_height.to_string(),
298 ]);
299 }
300
301 println!("{table}")
302 }
303 }
304
305 Ok(())
306 }
307}