pcli/command/query/
ibc_query.rs

1use 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/// Queries the chain for IBC data. Results will be printed in JSON.
23/// The singular subcommands require identifiers, whereas the plural subcommands
24/// return all results.
25#[derive(Debug, clap::Subcommand)]
26pub enum IbcCmd {
27    /// Queries for info on a specific IBC client.
28    /// Requires client identifier string, e.g. "07-tendermint-0".
29    Client { client_id: String },
30    /// Queries for info on all IBC clients.
31    Clients {},
32    /// Queries for info on a specific IBC connection.
33    /// Requires the numeric identifier for the connection, e.g. "0".
34    Connection { connection_id: u64 },
35    /// Queries for info on all IBC connections.
36    Connections {},
37    /// Queries for info on a specific IBC channel.
38    /// Requires the numeric identifier for the channel, e.g. "0".
39    Channel {
40        /// The designation of the ICS port used for channel.
41        /// In the context of IBC, this is usually "transfer".
42        #[clap(long, default_value = "transfer")]
43        port: String,
44
45        /// The numeric id of the ICS channel to query for. This number was assigned
46        /// during channel creation by a relaying client. Refer to the documentation
47        /// for the relayer provider to understand which counterparty chain the
48        /// channel id refers to.
49        channel_id: u64,
50    },
51    /// Queries for info on all IBC channels.
52    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                    // TODO: support pagination
88                    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                    // TODO: support pagination
120                    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                    // TODO: support pagination
217                    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}