diff --git a/Cargo.lock b/Cargo.lock index 59745e4bd..e9609db29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1504,6 +1504,7 @@ version = "0.1.0" dependencies = [ "cgp", "clap", + "hermes-cosmos-chain-components", "hermes-encoding-components", "hermes-error", "hermes-logging-components", @@ -1511,6 +1512,7 @@ dependencies = [ "hermes-runtime-components", "hermes-test-components", "http 1.2.0", + "ibc", "serde", "toml", ] diff --git a/crates/cli/cli-components/Cargo.toml b/crates/cli/cli-components/Cargo.toml index 4d1da2d9c..33a13c351 100644 --- a/crates/cli/cli-components/Cargo.toml +++ b/crates/cli/cli-components/Cargo.toml @@ -18,9 +18,11 @@ hermes-relayer-components = { workspace = true } hermes-encoding-components = { workspace = true } hermes-logging-components = { workspace = true } hermes-test-components = { workspace = true } +hermes-cosmos-chain-components = { workspace = true } cgp = { workspace = true } http = { workspace = true } +ibc = { workspace = true } serde = { workspace = true, features = ["derive"] } toml = { workspace = true } clap = { workspace = true, features = ["derive"] } diff --git a/crates/cli/cli-components/src/impls/commands/channel/create.rs b/crates/cli/cli-components/src/impls/commands/channel/create.rs new file mode 100644 index 000000000..27842b8bd --- /dev/null +++ b/crates/cli/cli-components/src/impls/commands/channel/create.rs @@ -0,0 +1,176 @@ +use core::fmt::Display; +use core::marker::PhantomData; + +use cgp::core::field::Index; +use cgp::prelude::*; +use hermes_logging_components::traits::has_logger::HasLogger; +use hermes_logging_components::traits::logger::CanLog; +use hermes_logging_components::types::level::LevelInfo; +use hermes_relayer_components::build::traits::builders::relay_builder::CanBuildRelay; +use hermes_relayer_components::chain::traits::types::chain_id::HasChainIdType; +use hermes_relayer_components::chain::traits::types::channel::HasInitChannelOptionsType; +use hermes_relayer_components::chain::traits::types::ibc::{HasClientIdType, HasIbcChainTypes}; +use hermes_relayer_components::multi::traits::chain_at::HasChainTypeAt; +use hermes_relayer_components::multi::traits::relay_at::HasRelayTypeAt; +use hermes_relayer_components::relay::impls::channel::bootstrap::CanBootstrapChannel; +use hermes_relayer_components::relay::traits::chains::HasRelayChains; + +use crate::traits::build::CanLoadBuilder; +use crate::traits::command::CommandRunner; +use crate::traits::output::{CanProduceOutput, HasOutputType}; +use crate::traits::parse::CanParseArg; + +pub struct RunCreateChannelCommand; + +#[derive(Debug, clap::Parser, HasField)] +pub struct CreateChannelArgs { + #[clap( + long = "target-chain-id", + required = true, + value_name = "TARGET_CHAIN_ID", + help_heading = "REQUIRED" + )] + target_chain_id: String, + + #[clap( + long = "target-client-id", + required = true, + value_name = "TARGET_CLIENT_ID", + help_heading = "REQUIRED" + )] + target_client_id: String, + + #[clap( + long = "target-connection-id", + required = true, + value_name = "TARGET_CONNECTION_ID", + help_heading = "REQUIRED" + )] + target_connection_id: String, + + #[clap(long = "target-port-id", value_name = "TARGET_PORT_ID")] + target_port_id: String, + + #[clap( + long = "counterparty-chain-id", + required = true, + value_name = "COUNTERPARTY_CHAIN_ID", + help_heading = "REQUIRED" + )] + counterparty_chain_id: String, + + #[clap( + long = "counterparty-client-id", + required = true, + value_name = "COUNTERPARTY_CLIENT_ID", + help_heading = "REQUIRED" + )] + counterparty_client_id: String, + + #[clap(long = "counterparty-port-id", value_name = "COUNTERPARTY_PORT_ID")] + counterparty_port_id: String, + + #[clap(long = "ordering", value_name = "ORDERING")] + ordering: String, + + #[clap(long = "version", value_name = "VERSION")] + version: String, +} + +impl CommandRunner + for RunCreateChannelCommand +where + App: CanLoadBuilder + + HasOutputType + + HasErrorType + + HasLogger + + CanProduceOutput<&'static str> + + CanRaiseError + + CanRaiseError + + CanParseArg + + CanParseArg + + CanParseArg + + CanParseArg + + CanParseArg + + CanParseArg + + CanParseArg, + App::Logger: CanLog, + Args: Async, + Builder: CanBuildRelay, Index<1>, Relay = Relay> + + HasChainTypeAt, Chain = Chain> + + HasChainTypeAt, Chain = Counterparty> + + HasRelayTypeAt, Index<1>>, + Chain: HasChainIdType + + HasErrorType + + HasClientIdType + + HasInitChannelOptionsType + + HasIbcChainTypes, + Chain::InitChannelOptions: Default, + Chain::ChainId: Display, + Chain::ClientId: Display, + Chain::ChannelId: Display, + Counterparty::ChainId: Display, + Counterparty::ClientId: Display, + Counterparty::ChannelId: Display, + Counterparty: HasChainIdType + HasClientIdType + HasIbcChainTypes + HasErrorType, + Relay: CanBootstrapChannel + HasRelayChains, +{ + async fn run_command(app: &App, args: &Args) -> Result { + let logger = app.logger(); + let builder = app.load_builder().await?; + + let target_chain_id = app.parse_arg(args, PhantomData::)?; + let target_client_id = app.parse_arg(args, PhantomData::)?; + let target_port_id = app.parse_arg(args, PhantomData::)?; + let counterparty_chain_id = + app.parse_arg(args, PhantomData::)?; + let counterparty_client_id = + app.parse_arg(args, PhantomData::)?; + let counterparty_port_id = + app.parse_arg(args, PhantomData::)?; + + let relay = builder + .build_relay( + PhantomData::<(Index<0>, Index<1>)>, + &target_chain_id, + &counterparty_chain_id, + &target_client_id, + &counterparty_client_id, + ) + .await + .map_err(App::raise_error)?; + + logger + .log( + &format!( + "Creating channel between {}:{} and {}:{} ...", + target_chain_id, + target_client_id, + counterparty_chain_id, + counterparty_client_id, + ), + &LevelInfo, + ) + .await; + + let (target_channel_id, counterparty_channel_id) = relay + .bootstrap_channel(&target_port_id, &counterparty_port_id, &Default::default()) + .await + .map_err(App::raise_error)?; + + logger + .log( + &format!( + "Channel {}:{} successfully created between {} and {}", + target_channel_id, + counterparty_channel_id, + target_chain_id, + counterparty_chain_id, + ), + &LevelInfo, + ) + .await; + + Ok(app.produce_output("Done")) + } +} diff --git a/crates/cli/cli-components/src/impls/commands/channel/mod.rs b/crates/cli/cli-components/src/impls/commands/channel/mod.rs new file mode 100644 index 000000000..c5fb369c1 --- /dev/null +++ b/crates/cli/cli-components/src/impls/commands/channel/mod.rs @@ -0,0 +1 @@ +pub mod create; diff --git a/crates/cli/cli-components/src/impls/commands/mod.rs b/crates/cli/cli-components/src/impls/commands/mod.rs index d4bad9cc7..bec9c27b5 100644 --- a/crates/cli/cli-components/src/impls/commands/mod.rs +++ b/crates/cli/cli-components/src/impls/commands/mod.rs @@ -1,4 +1,5 @@ pub mod bootstrap; +pub mod channel; pub mod client; pub mod connection; pub mod queries; diff --git a/crates/cli/cli-components/src/impls/parse/identifier.rs b/crates/cli/cli-components/src/impls/parse/identifier.rs new file mode 100644 index 000000000..c5d6030e2 --- /dev/null +++ b/crates/cli/cli-components/src/impls/parse/identifier.rs @@ -0,0 +1,79 @@ +use core::marker::PhantomData; + +use cgp::prelude::*; +use hermes_cosmos_chain_components::types::channel::CosmosInitChannelOptions; +use ibc::core::channel::types::channel::Order; +use ibc::core::channel::types::Version; +use ibc::core::host::types::error::IdentifierError; +use ibc::core::host::types::identifiers::{ConnectionId, PortId}; + +use crate::traits::parse::ArgParser; + +const DEFAULT_VERSION: &str = "ics20-1"; + +pub struct ParsePortId; + +impl ArgParser for ParsePortId +where + App: CanRaiseAsyncError, + Args: HasField, +{ + type Parsed = PortId; + + fn parse_arg( + _app: &App, + args: &Args, + _tag: PhantomData, + ) -> Result { + if let Ok(port_id) = args.get_field(PhantomData).parse::() { + Ok(port_id) + } else { + Ok(PortId::transfer()) + } + } +} + +pub struct ParseInitCosmosChannelOptions; + +impl ArgParser for ParseInitCosmosChannelOptions +where + App: HasAsyncErrorType, + Args: HasField + + HasField + + HasField, +{ + type Parsed = CosmosInitChannelOptions; + + fn parse_arg( + _app: &App, + args: &Args, + _tag: PhantomData, + ) -> Result { + let connection_hops = if let Ok(conn_id) = args + .get_field(PhantomData::) + .parse::() + { + vec![conn_id] + } else { + Default::default() + }; + + let ordering = + if let Ok(ordering) = args.get_field(PhantomData::).parse() { + ordering + } else { + Order::Unordered + }; + + let channel_version = match args.get_field(PhantomData::).parse() { + Ok(version) => version, + Err(_) => Version::new(DEFAULT_VERSION.to_string()), + }; + + Ok(CosmosInitChannelOptions { + connection_hops, + ordering, + channel_version, + }) + } +} diff --git a/crates/cli/cli-components/src/impls/parse/mod.rs b/crates/cli/cli-components/src/impls/parse/mod.rs index 1101a2e4a..08e6e3d37 100644 --- a/crates/cli/cli-components/src/impls/parse/mod.rs +++ b/crates/cli/cli-components/src/impls/parse/mod.rs @@ -1,2 +1,3 @@ pub mod field; +pub mod identifier; pub mod string; diff --git a/crates/cli/cli/src/commands/channel/create.rs b/crates/cli/cli/src/commands/channel/create.rs deleted file mode 100644 index 9cb777e37..000000000 --- a/crates/cli/cli/src/commands/channel/create.rs +++ /dev/null @@ -1,143 +0,0 @@ -use core::marker::PhantomData; - -use cgp::core::field::Index; -use hermes_cli_components::traits::build::CanLoadBuilder; -use hermes_cli_framework::command::CommandRunner; -use hermes_cli_framework::output::Output; -use hermes_cosmos_chain_components::types::channel::CosmosInitChannelOptions; -use hermes_relayer_components::build::traits::builders::relay_builder::CanBuildRelay; -use hermes_relayer_components::relay::impls::channel::bootstrap::CanBootstrapChannel; -use ibc::core::channel::types::channel::Order; -use ibc::core::channel::types::Version; -use ibc::core::host::types::identifiers::{ChainId, ClientId, ConnectionId, PortId}; -use oneline_eyre::eyre::eyre; -use tracing::info; - -use crate::contexts::app::HermesApp; -use crate::Result; - -#[derive(Debug, clap::Parser)] -pub struct ChannelCreate { - /// Identifier of chain A - #[clap( - long = "chain-a", - required = true, - value_name = "CHAIN_ID_A", - help_heading = "REQUIRED" - )] - chain_id_a: ChainId, - - /// Identifier of client A - #[clap( - long = "client-a", - required = true, - value_name = "CLIENT_ID_A", - help_heading = "REQUIRED" - )] - client_id_a: ClientId, - - /// Identifier of the connection on A - #[clap( - long = "connection-a", - required = true, - value_name = "CONNECTION_ID_A", - help_heading = "REQUIRED" - )] - connection_id_a: ConnectionId, - - /// Port identifier on chain A - #[clap( - long = "port-a", - value_name = "PORT_ID_A", - default_value_t = PortId::transfer(), - )] - port_id_a: PortId, - - /// Identifier of chain B - #[clap( - long = "chain-b", - required = true, - value_name = "CHAIN_ID_B", - help_heading = "REQUIRED" - )] - chain_id_b: ChainId, - - /// Identifier of client B - #[clap( - long = "client-b", - required = true, - value_name = "CLIENT_ID_B", - help_heading = "REQUIRED" - )] - client_id_b: ClientId, - - /// Port identifier on chain B - #[clap( - long = "port-b", - value_name = "PORT_ID_B", - default_value_t = PortId::transfer(), - )] - port_id_b: PortId, - - /// Ordering of the channel - #[clap( - long = "ordering", - value_name = "ORDERING", - default_value_t = Order::Unordered, - )] - ordering: Order, - - /// Version of the channel - #[clap( - long = "version", - value_name = "VERSION", - default_value_t = Version::new("ics20-1".to_string()), - )] - version: Version, -} - -impl CommandRunner for ChannelCreate { - async fn run(&self, app: &HermesApp) -> Result { - let builder = app.load_builder().await?; - - let relay = builder - .build_relay( - PhantomData::<(Index<0>, Index<1>)>, - &self.chain_id_a, - &self.chain_id_b, - &self.client_id_a, - &self.client_id_b, - ) - .await - .map_err(|e| eyre!("Failed to build relay: {e}"))?; - - let options = CosmosInitChannelOptions { - ordering: self.ordering, - connection_hops: vec![self.connection_id_a.clone()], - channel_version: self.version.clone(), - }; - - info!( - ?options, - "Creating channel between {}:{} and {}:{} on connection {}...", - self.chain_id_a, - self.client_id_a, - self.chain_id_b, - self.client_id_b, - self.connection_id_a, - ); - - let (channel_id_a, channel_id_b) = relay - .bootstrap_channel(&self.port_id_a, &self.port_id_b, &options) - .await - .map_err(|e| eyre!("Failed to create channel: channel handshake failed: {e}"))?; - - info!( - %channel_id_a, %channel_id_b, - "Channel successfully created between {} and {}", - self.chain_id_a, self.chain_id_b, - ); - - Ok(Output::success_msg("Done")) - } -} diff --git a/crates/cli/cli/src/commands/channel/mod.rs b/crates/cli/cli/src/commands/channel/mod.rs index 082dc3585..93afc1f94 100644 --- a/crates/cli/cli/src/commands/channel/mod.rs +++ b/crates/cli/cli/src/commands/channel/mod.rs @@ -1,5 +1,5 @@ -pub mod create; -pub use create::ChannelCreate; +use hermes_cli_components::impls::commands::channel::create::CreateChannelArgs; +use hermes_cli_components::traits::command::CanRunCommand; use hermes_cli_framework::command::CommandRunner; use hermes_cli_framework::output::Output; @@ -9,13 +9,13 @@ use crate::Result; #[derive(Debug, clap::Subcommand)] pub enum ChannelCommands { /// Create a new channel - Create(ChannelCreate), + Create(CreateChannelArgs), } impl CommandRunner for ChannelCommands { async fn run(&self, app: &HermesApp) -> Result { match self { - Self::Create(cmd) => cmd.run(app).await, + Self::Create(cmd) => app.run_command(cmd).await, } } } diff --git a/crates/cli/cli/src/commands/connection/mod.rs b/crates/cli/cli/src/commands/connection/mod.rs index d65777f12..831c8abe5 100644 --- a/crates/cli/cli/src/commands/connection/mod.rs +++ b/crates/cli/cli/src/commands/connection/mod.rs @@ -1,6 +1,3 @@ -// mod create; -// pub use create::ConnectionCreate; - use hermes_cli_components::impls::commands::connection::create::CreateConnectionArgs; use hermes_cli_components::traits::command::CanRunCommand; use hermes_cli_framework::command::CommandRunner; diff --git a/crates/cli/cli/src/contexts/app.rs b/crates/cli/cli/src/contexts/app.rs index 64bbebe92..ff7f2b20c 100644 --- a/crates/cli/cli/src/contexts/app.rs +++ b/crates/cli/cli/src/contexts/app.rs @@ -8,6 +8,9 @@ use cgp::core::types::WithType; use cgp::prelude::*; use hermes_any_counterparty::contexts::any_counterparty::AnyCounterparty; use hermes_cli_components::impls::commands::bootstrap::chain::RunBootstrapChainCommand; +use hermes_cli_components::impls::commands::channel::create::{ + CreateChannelArgs, RunCreateChannelCommand, +}; use hermes_cli_components::impls::commands::client::create::{ CreateClientOptionsParser, RunCreateClientCommand, }; @@ -60,6 +63,7 @@ use hermes_cli_components::impls::commands::start::{RunStartRelayerCommand, Star use hermes_cli_components::impls::config::get_config_path::GetDefaultConfigField; use hermes_cli_components::impls::config::load_toml_config::LoadTomlConfig; use hermes_cli_components::impls::config::save_toml_config::WriteTomlConfig; +use hermes_cli_components::impls::parse::identifier::{ParseInitCosmosChannelOptions, ParsePortId}; use hermes_cli_components::impls::parse::string::{ParseFromOptionalString, ParseFromString}; use hermes_cli_components::traits::any_counterparty::ProvideAnyCounterparty; use hermes_cli_components::traits::bootstrap::{BootstrapLoaderComponent, BootstrapTypeComponent}; @@ -198,6 +202,14 @@ delegate_components! { (CreateClientArgs, symbol!("target_chain_id")): ParseFromString, (CreateClientArgs, symbol!("counterparty_chain_id")): ParseFromString, + (CreateChannelArgs, symbol!("target_chain_id")): ParseFromString, + (CreateChannelArgs, symbol!("target_client_id")): ParseFromString, + (CreateChannelArgs, symbol!("target_port_id")): ParsePortId, + (CreateChannelArgs, symbol!("counterparty_chain_id")): ParseFromString, + (CreateChannelArgs, symbol!("counterparty_client_id")): ParseFromString, + (CreateChannelArgs, symbol!("counterparty_port_id")): ParsePortId, + (CreateChannelArgs, symbol!("init_channel_options")): ParseInitCosmosChannelOptions, + (CreateConnectionArgs, symbol!("target_chain_id")): ParseFromString, (CreateConnectionArgs, symbol!("target_client_id")): ParseFromString, (CreateConnectionArgs, symbol!("counterparty_chain_id")): ParseFromString, @@ -224,6 +236,7 @@ delegate_components! { CreateClientArgs: RunCreateClientCommand, CreateConnectionArgs: RunCreateConnectionCommand, + CreateChannelArgs: RunCreateChannelCommand, UpdateClientArgs: RunUpdateClientCommand, BootstrapSubCommand: RunBootstrapSubCommand, @@ -304,6 +317,7 @@ pub trait CanUseHermesApp: + CanRunCommand + CanRunCommand + CanRunCommand + // + CanRunCommand + CanRunCommand + CanRunCommand + CanRunCommand