hermes-cli 0.1.0

Hermes: IBC Relayer in Rust
Documentation
use hermes_cli_components::traits::build::CanLoadBuilder;
use hermes_cli_framework::command::CommandRunner;
use hermes_cli_framework::output::{json, Output};
use hermes_cosmos_chain_components::traits::chain_handle::HasBlockingChainHandle;
use hermes_relayer_components::chain::traits::queries::chain_status::CanQueryChainHeight;
use ibc_relayer::chain::handle::ChainHandle;
use ibc_relayer::chain::requests::{
    IncludeProof, PageRequest, QueryChannelsRequest, QueryClientStateRequest,
    QueryConnectionRequest, QueryHeight,
};
use ibc_relayer_types::core::ics04_channel::channel::State;
use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ChannelId, PortId};
use tracing::{info, warn};

use crate::contexts::app::HermesApp;
use crate::impls::error_wrapper::ErrorWrapper;
use crate::Result;

#[derive(Debug, clap::Parser)]
pub struct QueryChannels {
    #[clap(
        long = "chain",
        required = true,
        value_name = "CHAIN_ID",
        help_heading = "REQUIRED",
        help = "Identifier of the chain to query"
    )]
    chain_id: ChainId,

    #[clap(
        long = "counterparty-chain",
        value_name = "COUNTERPARTY_CHAIN_ID",
        help = "Filter the query response by the counterparty chain"
    )]
    counterparty_chain_id: Option<ChainId>,

    #[clap(
        long = "show-counterparty",
        help = "Show the counterparty's chain, port, and channel"
    )]
    show_counterparty: bool,
}

impl CommandRunner<HermesApp> for QueryChannels {
    async fn run(&self, app: &HermesApp) -> Result<Output> {
        let builder = app.load_builder().await?;

        let chain = builder.build_chain(&self.chain_id).await?;
        let chain_id = self.chain_id.clone();
        let dst_chain_id = self.counterparty_chain_id.clone();
        let show_counterparty = self.show_counterparty;

        let all_channels = chain
            .with_blocking_chain_handle(move |chain_handle| {
                chain_handle
                    .query_channels(QueryChannelsRequest {
                        pagination: Some(PageRequest::all()),
                    })
                    .map_err(From::from)
            })
            .await?;

        let chain_height = chain
            .query_chain_height()
            .await
            .wrap_error("Failed to query latest chain height")?;

        let mut channels = Vec::new();

        for channel in all_channels {
            let port_id = &channel.port_id;
            let channel_id = &channel.channel_id;
            let chain_id = chain_id.clone();
            let channel_end = &channel.channel_end;

            if channel_end.state_matches(&State::Uninitialized) {
                warn!(
                    "channel `{port_id}/{channel_id}` on chain `{chain_id}` at {chain_height} is uninitialized"
                );

                continue;
            }

            let Some(connection_id) = channel.channel_end.connection_hops.first() else {
                warn!(
                    "missing connection hops for `{port_id}/{channel_id}` on chain `{chain_id}` at `{chain_height}`"
                );

                continue;
            };

            let counterparty = if show_counterparty || dst_chain_id.is_some() {
                let connection_id = connection_id.clone();
                let connection_end = chain
                    .with_blocking_chain_handle(move |handle| {
                        handle
                            .query_connection(
                                QueryConnectionRequest {
                                    connection_id,
                                    height: QueryHeight::Specific(chain_height),
                                },
                                IncludeProof::No,
                            )
                            .map_err(From::from)
                    })
                    .await;

                let Ok((connection_end, _)) = connection_end else {
                    warn!(
                        "missing connection end for `{port_id}/{channel_id}` on chain `{chain_id}` at {chain_height}"
                    );

                    continue;
                };

                let client_id = connection_end.client_id().clone();
                let client_state = chain
                    .with_blocking_chain_handle(move |handle| {
                        handle
                            .query_client_state(
                                QueryClientStateRequest {
                                    client_id,
                                    height: QueryHeight::Specific(chain_height),
                                },
                                IncludeProof::No,
                            )
                            .map_err(From::from)
                    })
                    .await;

                let Ok((client_state, _)) = client_state else {
                    warn!("missing client state for {port_id}/{channel_id} on chain {chain_id} at {chain_height}");

                    continue;
                };

                let client_state_chain_id = client_state.chain_id();
                let client_state_chain_id_matches_dst_chain_id = dst_chain_id
                    .as_ref()
                    .map(|dst_chain_id| dst_chain_id == &client_state_chain_id)
                    .unwrap_or(true);

                if !client_state_chain_id_matches_dst_chain_id {
                    continue;
                }

                let counterparty = channel_end.counterparty();

                Some(Counterparty {
                    chain_id: client_state_chain_id.clone(),
                    port_id: counterparty.port_id.clone(),
                    channel_id: counterparty.channel_id.clone(),
                })
            } else {
                None
            };

            channels.push((channel, counterparty));
        }

        info!("Found {} channels on chain `{chain_id}`", channels.len());

        if json() {
            let channels = channels
                .into_iter()
                .map(|(channel, counterparty)| {
                    let (port_id, channel_id) = (channel.port_id, channel.channel_id);

                    let mut result = serde_json::json!({
                        "port_id": port_id,
                        "channel_id": channel_id,
                    });

                    if let Some(counterparty) = counterparty {
                        result["counterparty"] = serde_json::to_value(counterparty).unwrap();
                    }

                    result
                })
                .collect::<Vec<_>>();

            return Ok(Output::success(channels));
        }

        channels.iter().for_each(|(channel, counterparty)| {
            info!("- {}/{}", channel.port_id, channel.channel_id);

            if let Some(counterparty) = counterparty {
                info!(
                    "  - counterparty: {}/{} on chain {}",
                    counterparty.port_id,
                    counterparty
                        .channel_id
                        .as_ref()
                        .map_or("unknown".to_string(), |c| c.to_string()),
                    counterparty.chain_id
                );
            }
        });

        Ok(Output::success_msg(format!(
            "Total: {} channels",
            channels.len()
        )))
    }
}

#[derive(Debug, serde::Serialize)]
struct Counterparty {
    chain_id: ChainId,
    port_id: PortId,
    channel_id: Option<ChannelId>,
}