diff --git a/Cargo.toml b/Cargo.toml index 0001bad76b..39e6021cfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,10 @@ members = [ exclude = [ "proto-compiler" ] + +[patch.crates-io] + +tendermint = { git = "https://github.com/informalsystems/tendermint-rs", branch = "romac/skip-verif" } +tendermint-rpc = { git = "https://github.com/informalsystems/tendermint-rs", branch = "romac/skip-verif" } +tendermint-proto = { git = "https://github.com/informalsystems/tendermint-rs", branch = "romac/skip-verif" } +tendermint-light-client = { git = "https://github.com/informalsystems/tendermint-rs", branch = "romac/skip-verif" } \ No newline at end of file diff --git a/modules/src/ics04_channel/channel.rs b/modules/src/ics04_channel/channel.rs index c38fe12f3c..b8607057d4 100644 --- a/modules/src/ics04_channel/channel.rs +++ b/modules/src/ics04_channel/channel.rs @@ -126,6 +126,11 @@ impl ChannelEnd { } self.counterparty().validate_basic() } + + /// Helper function to compare the state of this end with another state. + pub fn state_matches(&self, other: &State) -> bool { + self.state.eq(other) + } } #[derive(Clone, Debug, PartialEq)] @@ -236,18 +241,16 @@ impl FromStr for Order { #[derive(Clone, Debug, PartialEq)] pub enum State { - Uninitialized = 0, - Init, - TryOpen, - Open, - Closed, + Init = 1, + TryOpen = 2, + Open = 3, + Closed = 4, } impl State { /// Yields the state as a string pub fn as_string(&self) -> &'static str { match self { - Self::Uninitialized => "UNINITIALIZED", Self::Init => "INIT", Self::TryOpen => "TRYOPEN", Self::Open => "OPEN", @@ -258,7 +261,6 @@ impl State { // Parses the State out from a i32. pub fn from_i32(s: i32) -> Result { match s { - 0 => Ok(Self::Uninitialized), 1 => Ok(Self::Init), 2 => Ok(Self::TryOpen), 3 => Ok(Self::Open), @@ -291,7 +293,7 @@ pub mod test_util { /// Returns a dummy `RawChannel`, for testing only! pub fn get_dummy_raw_channel_end() -> RawChannel { RawChannel { - state: 0, + state: 1, ordering: 0, counterparty: Some(get_dummy_raw_counterparty()), connection_hops: vec![], diff --git a/relayer-cli/src/commands/tx/channel.rs b/relayer-cli/src/commands/tx/channel.rs index 81afbfeee5..4b4d3844af 100644 --- a/relayer-cli/src/commands/tx/channel.rs +++ b/relayer-cli/src/commands/tx/channel.rs @@ -2,344 +2,113 @@ use crate::prelude::*; use abscissa_core::{Command, Options, Runnable}; use ibc::ics04_channel::channel::Order; -use ibc::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; - -use relayer::config::Config; +use ibc::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; use crate::error::{Error, Kind}; -use relayer::tx::channel::{ +use relayer::channel::{ build_chan_ack_and_send, build_chan_confirm_and_send, build_chan_init_and_send, - build_chan_try_and_send, ChannelOpenInitOptions, ChannelOpenOptions, + build_chan_try_and_send, }; -#[derive(Clone, Command, Debug, Options)] -pub struct TxRawChanInitCmd { - #[options(free, help = "identifier of the destination chain")] - dst_chain_id: String, - - #[options(free, help = "identifier of the source chain")] - src_chain_id: String, - - #[options(free, help = "identifier of the destination connection")] - dst_connection_id: ConnectionId, +use relayer::chain::runtime::ChainRuntime; +use relayer::channel::{ChannelConfig, ChannelConfigSide}; - #[options(free, help = "identifier of the destination port")] - dst_port_id: PortId, +macro_rules! chan_open_cmd { + ($chan_open_cmd:ident, $dbg_string:literal, $func:ident) => { + #[derive(Clone, Command, Debug, Options)] + pub struct $chan_open_cmd { + #[options(free, help = "identifier of the destination chain")] + dst_chain_id: String, - #[options(free, help = "identifier of the source port")] - src_port_id: PortId, + #[options(free, help = "identifier of the source chain")] + src_chain_id: String, - #[options(free, help = "identifier of the destination channel")] - dst_channel_id: ChannelId, - - #[options(help = "identifier of the source channel", short = "s")] - src_channel_id: Option, - - #[options(help = "the channel order", short = "o")] - ordering: Order, -} + #[options(free, help = "identifier of the destination connection")] + dst_connection_id: ConnectionId, -impl TxRawChanInitCmd { - fn validate_options(&self, config: &Config) -> Result { - let dst_chain_config = config - .chains - .iter() - .find(|c| c.id == self.dst_chain_id.parse().unwrap()) - .ok_or_else(|| "missing destination chain configuration".to_string())?; + #[options(free, help = "identifier of the destination port")] + dst_port_id: PortId, - let src_chain_config = config - .chains - .iter() - .find(|c| c.id == self.src_chain_id.parse().unwrap()) - .ok_or_else(|| "missing src chain configuration".to_string())?; + #[options(free, help = "identifier of the source port")] + src_port_id: PortId, - let opts = ChannelOpenInitOptions { - dst_chain_config: dst_chain_config.clone(), - src_chain_config: src_chain_config.clone(), + #[options(free, help = "identifier of the destination channel")] + dst_channel_id: ChannelId, - dst_connection_id: self.dst_connection_id.clone(), - - dst_port_id: self.dst_port_id.clone(), - src_port_id: self.src_port_id.clone(), - - dst_channel_id: self.dst_channel_id.clone(), - src_channel_id: self.src_channel_id.clone(), - - ordering: self.ordering, - }; - - Ok(opts) - } -} + #[options(free, help = "identifier of the source channel")] + src_channel_id: ChannelId, -impl Runnable for TxRawChanInitCmd { - fn run(&self) { - let config = app_config(); - - let opts = match self.validate_options(&config) { - Err(err) => { - status_err!("invalid options: {}", err); - return; - } - Ok(result) => result, - }; - status_info!("Message", "{:?}", opts); - - let res: Result = - build_chan_init_and_send(&opts).map_err(|e| Kind::Tx.context(e).into()); - - match res { - Ok(receipt) => status_info!("channel init, result: ", "{:?}", receipt), - Err(e) => status_info!("channel init failed, error: ", "{}", e), + #[options(help = "the channel order", short = "o")] + ordering: Order, } - } -} - -#[derive(Clone, Command, Debug, Options)] -pub struct TxRawChanTryCmd { - #[options(free, help = "identifier of the destination chain")] - dest_chain_id: String, - - #[options(free, help = "identifier of the source chain")] - src_chain_id: String, - - #[options(free, help = "identifier of the destination connection")] - dest_connection_id: ConnectionId, - - #[options(free, help = "identifier of the destination port")] - dest_port_id: PortId, - - #[options(free, help = "identifier of the source port")] - src_port_id: PortId, - #[options(free, help = "identifier of the destination channel")] - dest_channel_id: ChannelId, - - #[options(free, help = "identifier of the source channel")] - src_channel_id: ChannelId, - - #[options(help = "the channel order", short = "o")] - ordering: Order, -} - -impl TxRawChanTryCmd { - fn validate_options(&self, config: &Config) -> Result { - let dest_chain_config = config - .chains - .iter() - .find(|c| c.id == self.dest_chain_id.parse().unwrap()) - .ok_or_else(|| "missing destination chain configuration".to_string())?; - - let src_chain_config = config - .chains - .iter() - .find(|c| c.id == self.src_chain_id.parse().unwrap()) - .ok_or_else(|| "missing src chain configuration".to_string())?; - - let opts = ChannelOpenOptions { - dst_chain_config: dest_chain_config.clone(), - src_chain_config: src_chain_config.clone(), - - dst_connection_id: self.dest_connection_id.clone(), - - dst_port_id: self.dest_port_id.clone(), - src_port_id: self.src_port_id.clone(), - - dst_channel_id: self.dest_channel_id.clone(), - src_channel_id: self.src_channel_id.clone(), - - ordering: self.ordering, - }; - - Ok(opts) - } -} - -impl Runnable for TxRawChanTryCmd { - fn run(&self) { - let config = app_config(); - - let opts = match self.validate_options(&config) { - Err(err) => { - status_err!("invalid options: {}", err); - return; + impl Runnable for $chan_open_cmd { + fn run(&self) { + let config = app_config(); + + let src_config = config + .chains + .iter() + .find(|c| c.id == self.src_chain_id.parse().unwrap()) + .ok_or_else(|| "missing src chain configuration".to_string()); + + let dst_config = config + .chains + .iter() + .find(|c| c.id == self.dst_chain_id.parse().unwrap()) + .ok_or_else(|| "missing src chain configuration".to_string()); + + let (src_chain_config, dst_chain_config) = match (src_config, dst_config) { + (Ok(s), Ok(d)) => (s, d), + (_, _) => { + status_err!("invalid options"); + return; + } + }; + + let opts = ChannelConfig { + ordering: self.ordering, + a_config: ChannelConfigSide::new( + &src_chain_config.id, + &ConnectionId::default(), + &ClientId::default(), + &self.src_port_id, + &self.src_channel_id, + ), + b_config: ChannelConfigSide::new( + &dst_chain_config.id, + &self.dst_connection_id, + &ClientId::default(), + &self.dst_port_id, + &self.dst_channel_id, + ), + }; + + status_info!("Message ", "{}: {:#?}", $dbg_string, opts); + + let (src_chain, _) = ChainRuntime::spawn(src_chain_config.clone()).unwrap(); + let (dst_chain, _) = ChainRuntime::spawn(dst_chain_config.clone()).unwrap(); + + let res: Result = + $func(dst_chain, src_chain, &opts).map_err(|e| Kind::Tx.context(e).into()); + + match res { + Ok(receipt) => status_ok!("Result: ", "{:?} - {:?}", $dbg_string, receipt), + Err(e) => status_err!("Failed with Error: {:?} - {:?}", $dbg_string, e), + } } - Ok(result) => result, - }; - status_info!("Message", "{:?}", opts); - - let res: Result = - build_chan_try_and_send(&opts).map_err(|e| Kind::Tx.context(e).into()); - - match res { - Ok(receipt) => status_info!("channel try, result: ", "{:?}", receipt), - Err(e) => status_info!("channel try failed, error: ", "{}", e), } - } + }; } -#[derive(Clone, Command, Debug, Options)] -pub struct TxRawChanAckCmd { - #[options(free, help = "identifier of the destination chain")] - dest_chain_id: String, +chan_open_cmd!(TxRawChanInitCmd, "ChanOpenInit", build_chan_init_and_send); - #[options(free, help = "identifier of the source chain")] - src_chain_id: String, +chan_open_cmd!(TxRawChanTryCmd, "ChanOpenTry", build_chan_try_and_send); - #[options(free, help = "identifier of the destination connection")] - dest_connection_id: ConnectionId, +chan_open_cmd!(TxRawChanAckCmd, "ChanOpenAck", build_chan_ack_and_send); - #[options(free, help = "identifier of the destination port")] - dest_port_id: PortId, - - #[options(free, help = "identifier of the source port")] - src_port_id: PortId, - - #[options(free, help = "identifier of the destination channel")] - dest_channel_id: ChannelId, - - #[options(free, help = "identifier of the source channel")] - src_channel_id: ChannelId, - - #[options(help = "the channel order", short = "o")] - ordering: Order, -} - -impl TxRawChanAckCmd { - fn validate_options(&self, config: &Config) -> Result { - let dest_chain_config = config - .chains - .iter() - .find(|c| c.id == self.dest_chain_id.parse().unwrap()) - .ok_or_else(|| "missing destination chain configuration".to_string())?; - - let src_chain_config = config - .chains - .iter() - .find(|c| c.id == self.src_chain_id.parse().unwrap()) - .ok_or_else(|| "missing src chain configuration".to_string())?; - - let opts = ChannelOpenOptions { - dst_chain_config: dest_chain_config.clone(), - src_chain_config: src_chain_config.clone(), - - dst_connection_id: self.dest_connection_id.clone(), - - dst_port_id: self.dest_port_id.clone(), - src_port_id: self.src_port_id.clone(), - - dst_channel_id: self.dest_channel_id.clone(), - src_channel_id: self.src_channel_id.clone(), - - ordering: self.ordering, - }; - - Ok(opts) - } -} - -impl Runnable for TxRawChanAckCmd { - fn run(&self) { - let config = app_config(); - - let opts = match self.validate_options(&config) { - Err(err) => { - status_err!("invalid options: {}", err); - return; - } - Ok(result) => result, - }; - status_info!("Message", "{:?}", opts); - - let res: Result = - build_chan_ack_and_send(&opts).map_err(|e| Kind::Tx.context(e).into()); - - match res { - Ok(receipt) => status_info!("channel ack, result: ", "{:?}", receipt), - Err(e) => status_info!("channel ack failed, error: ", "{}", e), - } - } -} - -#[derive(Clone, Command, Debug, Options)] -pub struct TxRawChanConfirmCmd { - #[options(free, help = "identifier of the destination chain")] - dest_chain_id: String, - - #[options(free, help = "identifier of the source chain")] - src_chain_id: String, - - #[options(free, help = "identifier of the destination connection")] - dest_connection_id: ConnectionId, - - #[options(free, help = "identifier of the destination port")] - dest_port_id: PortId, - - #[options(free, help = "identifier of the source port")] - src_port_id: PortId, - - #[options(free, help = "identifier of the destination channel")] - dest_channel_id: ChannelId, - - #[options(free, help = "identifier of the source channel")] - src_channel_id: ChannelId, - - #[options(help = "the channel order", short = "o")] - ordering: Order, -} - -impl TxRawChanConfirmCmd { - fn validate_options(&self, config: &Config) -> Result { - let dest_chain_config = config - .chains - .iter() - .find(|c| c.id == self.dest_chain_id.parse().unwrap()) - .ok_or_else(|| "missing destination chain configuration".to_string())?; - - let src_chain_config = config - .chains - .iter() - .find(|c| c.id == self.src_chain_id.parse().unwrap()) - .ok_or_else(|| "missing src chain configuration".to_string())?; - - let opts = ChannelOpenOptions { - dst_chain_config: dest_chain_config.clone(), - src_chain_config: src_chain_config.clone(), - - dst_connection_id: self.dest_connection_id.clone(), - - dst_port_id: self.dest_port_id.clone(), - src_port_id: self.src_port_id.clone(), - - dst_channel_id: self.dest_channel_id.clone(), - src_channel_id: self.src_channel_id.clone(), - - ordering: self.ordering, - }; - - Ok(opts) - } -} - -impl Runnable for TxRawChanConfirmCmd { - fn run(&self) { - let config = app_config(); - - let opts = match self.validate_options(&config) { - Err(err) => { - status_err!("invalid options: {}", err); - return; - } - Ok(result) => result, - }; - status_info!("Message", "{:?}", opts); - - let res: Result = - build_chan_confirm_and_send(&opts).map_err(|e| Kind::Tx.context(e).into()); - - match res { - Ok(receipt) => status_info!("channel confirm, result: ", "{:?}", receipt), - Err(e) => status_info!("channel confirm failed, error: ", "{}", e), - } - } -} +chan_open_cmd!( + TxRawChanConfirmCmd, + "ChanOpenConfirm", + build_chan_confirm_and_send +); diff --git a/relayer-cli/src/commands/tx/client.rs b/relayer-cli/src/commands/tx/client.rs index cbce5b8a5a..872d7950ca 100644 --- a/relayer-cli/src/commands/tx/client.rs +++ b/relayer-cli/src/commands/tx/client.rs @@ -2,13 +2,14 @@ use abscissa_core::{Command, Options, Runnable}; use ibc::ics24_host::identifier::ClientId; -use relayer::tx::client::{ - build_create_client_and_send, build_update_client_and_send, ClientOptions, -}; - use crate::application::app_config; use crate::error::{Error, Kind}; use crate::prelude::*; +use relayer::chain::runtime::ChainRuntime; +use relayer::config::ChainConfig; +use relayer::foreign_client::{ + build_create_client_and_send, build_update_client_and_send, ForeignClientConfig, +}; #[derive(Clone, Command, Debug, Options)] pub struct TxCreateClientCmd { @@ -27,21 +28,31 @@ pub struct TxCreateClientCmd { impl Runnable for TxCreateClientCmd { fn run(&self) { - let opts = match validate_common_options( + let (dst_chain_config, src_chain_config, opts) = match validate_common_options( &self.dst_chain_id, &self.src_chain_id, &self.dst_client_id, ) { + Ok(result) => result, Err(err) => { status_err!("invalid options: {}", err); return; } - Ok(result) => result, }; - status_info!("Message", "{:?}", opts); - let res: Result = - build_create_client_and_send(opts).map_err(|e| Kind::Tx.context(e).into()); + status_info!( + "Message CreateClient", + "id: {:?}, for chain: {:?}, on chain: {:?}", + opts.client_id(), + src_chain_config.id, + opts.chain_id() + ); + + let (src_chain, _) = ChainRuntime::spawn(src_chain_config).unwrap(); + let (dst_chain, _) = ChainRuntime::spawn(dst_chain_config).unwrap(); + + let res: Result = build_create_client_and_send(dst_chain, src_chain, &opts) + .map_err(|e| Kind::Tx.context(e).into()); match res { Ok(receipt) => status_ok!("Success", "client created: {:?}", receipt), @@ -70,7 +81,7 @@ impl Runnable for TxUpdateClientCmd { let opts = validate_common_options(&self.dst_chain_id, &self.src_chain_id, &self.dst_client_id); - let opts = match opts { + let (dst_chain_config, src_chain_config, opts) = match opts { Ok(result) => result, Err(err) => { status_err!("invalid options: {}", err); @@ -78,13 +89,22 @@ impl Runnable for TxUpdateClientCmd { } }; - status_info!("Message", "{:?}", opts); + status_info!( + "Message UpdateClient", + "id: {:?}, for chain: {:?}, on chain: {:?}", + opts.client_id(), + src_chain_config.id, + opts.chain_id() + ); + + let (src_chain, _) = ChainRuntime::spawn(src_chain_config).unwrap(); + let (dst_chain, _) = ChainRuntime::spawn(dst_chain_config).unwrap(); - let res: Result = - build_update_client_and_send(opts).map_err(|e| Kind::Tx.context(e).into()); + let res: Result = build_update_client_and_send(dst_chain, src_chain, &opts) + .map_err(|e| Kind::Tx.context(e).into()); match res { - Ok(receipt) => status_ok!("Success", "client updated: {:?}", receipt), + Ok(receipt) => status_ok!("Success client updated: {:?}", receipt), Err(e) => status_err!("client update failed: {}", e), } } @@ -94,7 +114,7 @@ fn validate_common_options( dst_chain_id: &str, src_chain_id: &str, dst_client_id: &ClientId, -) -> Result { +) -> Result<(ChainConfig, ChainConfig, ForeignClientConfig), String> { let config = app_config(); // Validate parameters @@ -119,9 +139,9 @@ fn validate_common_options( .find(|c| c.id == src_chain_id) .ok_or_else(|| "missing source chain configuration".to_string())?; - Ok(ClientOptions { - dst_client_id: dst_client_id.clone(), - dst_chain_config: dst_chain_config.clone(), - src_chain_config: src_chain_config.clone(), - }) + Ok(( + dst_chain_config.clone(), + src_chain_config.clone(), + ForeignClientConfig::new(&dst_chain_config.id, &dst_client_id), + )) } diff --git a/relayer-cli/src/commands/tx/connection.rs b/relayer-cli/src/commands/tx/connection.rs index d498f73791..42c9b24d87 100644 --- a/relayer-cli/src/commands/tx/connection.rs +++ b/relayer-cli/src/commands/tx/connection.rs @@ -4,293 +4,100 @@ use abscissa_core::{Command, Options, Runnable}; use ibc::ics24_host::identifier::{ClientId, ConnectionId}; -use relayer::config::Config; -use relayer::tx::connection::{ +use relayer::connection::{ build_conn_ack_and_send, build_conn_confirm_and_send, build_conn_init_and_send, - build_conn_try_and_send, ConnectionOpenInitOptions, ConnectionOpenOptions, + build_conn_try_and_send, }; use crate::error::{Error, Kind}; +use relayer::chain::runtime::ChainRuntime; +use relayer::connection::{ConnectionConfig, ConnectionSideConfig}; -#[derive(Clone, Command, Debug, Options)] -pub struct TxRawConnInitCmd { - #[options(free, help = "identifier of the destination chain")] - dst_chain_id: String, +macro_rules! conn_open_cmd { + ($conn_open_cmd:ident, $dbg_string:literal, $func:ident) => { + #[derive(Clone, Command, Debug, Options)] + pub struct $conn_open_cmd { + #[options(free, help = "identifier of the destination chain")] + dst_chain_id: String, - #[options(free, help = "identifier of the source chain")] - src_chain_id: String, + #[options(free, help = "identifier of the source chain")] + src_chain_id: String, - #[options(free, help = "identifier of the destination client")] - dst_client_id: ClientId, + #[options(free, help = "identifier of the destination client")] + dst_client_id: ClientId, - #[options(free, help = "identifier of the source client")] - src_client_id: ClientId, + #[options(free, help = "identifier of the source client")] + src_client_id: ClientId, - #[options(free, help = "identifier of the destination connection")] - dst_connection_id: ConnectionId, + #[options(free, help = "identifier of the destination connection")] + dst_connection_id: ConnectionId, - #[options(help = "identifier of the source connection", short = "s")] - src_connection_id: Option, -} - -impl TxRawConnInitCmd { - fn validate_options(&self, config: &Config) -> Result { - let dst_chain_config = config - .chains - .iter() - .find(|c| c.id == self.dst_chain_id.parse().unwrap()) - .ok_or_else(|| "missing destination chain configuration".to_string())?; - - let src_chain_config = config - .chains - .iter() - .find(|c| c.id == self.src_chain_id.parse().unwrap()) - .ok_or_else(|| "missing src chain configuration".to_string())?; - - let opts = ConnectionOpenInitOptions { - dst_chain_config: dst_chain_config.clone(), - src_chain_config: src_chain_config.clone(), - dst_client_id: self.dst_client_id.clone(), - src_client_id: self.src_client_id.clone(), - dst_connection_id: self.dst_connection_id.clone(), - src_connection_id: self.src_connection_id.clone(), - }; - - Ok(opts) - } -} - -impl Runnable for TxRawConnInitCmd { - fn run(&self) { - let config = app_config(); - - let opts = match self.validate_options(&config) { - Err(err) => { - status_err!("invalid options: {}", err); - return; - } - Ok(result) => result, - }; - - let res: Result = - build_conn_init_and_send(&opts).map_err(|e| Kind::Tx.context(e).into()); - - match res { - Ok(receipt) => status_info!("conn init, result: ", "{:?}", receipt), - Err(e) => status_info!("conn init failed, error: ", "{}", e), + #[options(free, help = "identifier of the source connection")] + src_connection_id: ConnectionId, } - } -} - -#[derive(Clone, Command, Debug, Options)] -pub struct TxRawConnTryCmd { - #[options(free, help = "identifier of the destination chain")] - dst_chain_id: String, - - #[options(free, help = "identifier of the source chain")] - src_chain_id: String, - - #[options(free, help = "identifier of the destination client")] - dst_client_id: ClientId, - - #[options(free, help = "identifier of the source client")] - src_client_id: ClientId, - #[options(free, help = "identifier of the destination connection")] - dst_connection_id: ConnectionId, - - #[options(free, help = "identifier of the source connection")] - src_connection_id: ConnectionId, -} - -impl TxRawConnTryCmd { - fn validate_options(&self, config: &Config) -> Result { - let dst_chain_config = config - .chains - .iter() - .find(|c| c.id == self.dst_chain_id.parse().unwrap()) - .ok_or_else(|| "missing destination chain configuration".to_string())?; - - let src_chain_config = config - .chains - .iter() - .find(|c| c.id == self.src_chain_id.parse().unwrap()) - .ok_or_else(|| "missing src chain configuration".to_string())?; - - let opts = ConnectionOpenOptions { - src_chain_config: src_chain_config.clone(), - dst_chain_config: dst_chain_config.clone(), - src_client_id: self.src_client_id.clone(), - dst_client_id: self.dst_client_id.clone(), - src_connection_id: self.src_connection_id.clone(), - dst_connection_id: self.dst_connection_id.clone(), - }; - - Ok(opts) - } -} - -impl Runnable for TxRawConnTryCmd { - fn run(&self) { - let config = app_config(); - - let opts = match self.validate_options(&config) { - Err(err) => { - status_err!("invalid options: {}", err); - return; + impl Runnable for $conn_open_cmd { + fn run(&self) { + let config = app_config(); + + let src_config = config + .chains + .iter() + .find(|c| c.id == self.src_chain_id.parse().unwrap()) + .ok_or_else(|| "missing src chain configuration".to_string()); + + let dst_config = config + .chains + .iter() + .find(|c| c.id == self.dst_chain_id.parse().unwrap()) + .ok_or_else(|| "missing src chain configuration".to_string()); + + let (src_chain_config, dst_chain_config) = match (src_config, dst_config) { + (Ok(s), Ok(d)) => (s, d), + (_, _) => { + status_err!("invalid options"); + return; + } + }; + + let opts = ConnectionConfig { + a_config: ConnectionSideConfig::new( + src_chain_config.id.clone(), + self.src_connection_id.clone(), + self.src_client_id.clone(), + ), + b_config: ConnectionSideConfig::new( + dst_chain_config.id.clone(), + self.dst_connection_id.clone(), + self.dst_client_id.clone(), + ), + }; + + status_info!("Message ", "{}: {:#?}", $dbg_string, opts); + + let (src_chain, _) = ChainRuntime::spawn(src_chain_config.clone()).unwrap(); + let (dst_chain, _) = ChainRuntime::spawn(dst_chain_config.clone()).unwrap(); + + let res: Result = + $func(dst_chain, src_chain, &opts).map_err(|e| Kind::Tx.context(e).into()); + + match res { + Ok(receipt) => status_ok!("Result: ", "{:?} - {:?}", $dbg_string, receipt), + Err(e) => status_err!("Failed with Error: {:?} - {:?}", $dbg_string, e), + } } - Ok(result) => result, - }; - status_info!("Message", "{:?}", opts); - - let res: Result = - build_conn_try_and_send(opts).map_err(|e| Kind::Tx.context(e).into()); - - match res { - Ok(receipt) => status_info!("conn try, result: ", "{:?}", receipt), - Err(e) => status_info!("conn try failed, error: ", "{}", e), } - } + }; } -#[derive(Clone, Command, Debug, Options)] -pub struct TxRawConnAckCmd { - #[options(free, help = "identifier of the destination chain")] - dst_chain_id: String, - - #[options(free, help = "identifier of the source chain")] - src_chain_id: String, +conn_open_cmd!(TxRawConnInitCmd, "ConnOpenInit", build_conn_init_and_send); - #[options(free, help = "identifier of the destination client")] - dst_client_id: ClientId, +conn_open_cmd!(TxRawConnTryCmd, "ConnOpenTry", build_conn_try_and_send); - #[options(free, help = "identifier of the source client")] - src_client_id: ClientId, - - #[options(free, help = "identifier of the destination connection")] - dst_connection_id: ConnectionId, - - #[options(free, help = "identifier of the source connection")] - src_connection_id: ConnectionId, -} +conn_open_cmd!(TxRawConnAckCmd, "ConnOpenAck", build_conn_ack_and_send); -impl TxRawConnAckCmd { - fn validate_options(&self, config: &Config) -> Result { - let dst_chain_config = config - .chains - .iter() - .find(|c| c.id == self.dst_chain_id.parse().unwrap()) - .ok_or_else(|| "missing destination chain configuration".to_string())?; - - let src_chain_config = config - .chains - .iter() - .find(|c| c.id == self.src_chain_id.parse().unwrap()) - .ok_or_else(|| "missing src chain configuration".to_string())?; - - let opts = ConnectionOpenOptions { - src_chain_config: src_chain_config.clone(), - dst_chain_config: dst_chain_config.clone(), - src_client_id: self.src_client_id.clone(), - dst_client_id: self.dst_client_id.clone(), - src_connection_id: self.src_connection_id.clone(), - dst_connection_id: self.dst_connection_id.clone(), - }; - - Ok(opts) - } -} - -impl Runnable for TxRawConnAckCmd { - fn run(&self) { - let config = app_config(); - - let opts = match self.validate_options(&config) { - Err(err) => { - status_err!("invalid options: {}", err); - return; - } - Ok(result) => result, - }; - status_info!("Message", "{:?}", opts); - - let res: Result = - build_conn_ack_and_send(opts).map_err(|e| Kind::Tx.context(e).into()); - - match res { - Ok(receipt) => status_info!("conn ack, result: ", "{:?}", receipt), - Err(e) => status_info!("conn ack failed, error: ", "{}", e), - } - } -} - -#[derive(Clone, Command, Debug, Options)] -pub struct TxRawConnConfirmCmd { - #[options(free, help = "identifier of the destination chain")] - dst_chain_id: String, - - #[options(free, help = "identifier of the source chain")] - src_chain_id: String, - - #[options(free, help = "identifier of the destination client")] - dst_client_id: ClientId, - - #[options(free, help = "identifier of the source client")] - src_client_id: ClientId, - - #[options(free, help = "identifier of the destination connection")] - dst_connection_id: ConnectionId, - - #[options(free, help = "identifier of the source connection")] - src_connection_id: ConnectionId, -} - -impl TxRawConnConfirmCmd { - fn validate_options(&self, config: &Config) -> Result { - let dst_chain_config = config - .chains - .iter() - .find(|c| c.id == self.dst_chain_id.parse().unwrap()) - .ok_or_else(|| "missing destination chain configuration".to_string())?; - - let src_chain_config = config - .chains - .iter() - .find(|c| c.id == self.src_chain_id.parse().unwrap()) - .ok_or_else(|| "missing src chain configuration".to_string())?; - - let opts = ConnectionOpenOptions { - src_chain_config: src_chain_config.clone(), - dst_chain_config: dst_chain_config.clone(), - src_client_id: self.src_client_id.clone(), - dst_client_id: self.dst_client_id.clone(), - src_connection_id: self.src_connection_id.clone(), - dst_connection_id: self.dst_connection_id.clone(), - }; - - Ok(opts) - } -} - -impl Runnable for TxRawConnConfirmCmd { - fn run(&self) { - let config = app_config(); - - let opts = match self.validate_options(&config) { - Err(err) => { - status_err!("invalid options: {}", err); - return; - } - Ok(result) => result, - }; - status_info!("Message", "{:?}", opts); - - let res: Result = - build_conn_confirm_and_send(opts).map_err(|e| Kind::Tx.context(e).into()); - - match res { - Ok(receipt) => status_info!("conn confirm, result: ", "{:?}", receipt), - Err(e) => status_info!("conn confirm failed, error: ", "{}", e), - } - } -} +conn_open_cmd!( + TxRawConnConfirmCmd, + "ConnOpenConfirm", + build_conn_confirm_and_send +); diff --git a/relayer-cli/src/commands/v0.rs b/relayer-cli/src/commands/v0.rs index 35546e25fe..728aa9a14e 100644 --- a/relayer-cli/src/commands/v0.rs +++ b/relayer-cli/src/commands/v0.rs @@ -5,9 +5,9 @@ use abscissa_core::{ }; use relayer::chain::runtime::ChainRuntime; - -use ibc::ics24_host::identifier::ClientId; -use std::str::FromStr; +use relayer::channel::ChannelConfig; +use relayer::connection::ConnectionConfig; +use relayer::relay::channel_relay; use crate::config::Config; use crate::prelude::*; @@ -19,7 +19,7 @@ impl V0Cmd { fn cmd(&self) -> Result<(), BoxError> { let config = app_config().clone(); debug!("launching 'v0' command"); - v0_task(config) + v0_task(&config) } } @@ -30,81 +30,40 @@ impl Runnable for V0Cmd { } } -pub fn v0_task(config: Config) -> Result<(), BoxError> { - // Load source and destination chains configurations +pub fn v0_task(config: &Config) -> Result<(), BoxError> { + // Relay for a single channel, first on the first connection in configuration + // TODO - change the config to use typed Ids and same with ConnectionConfig, ClientConfig, ChannelConfig, etc + let conn = &config + .connections + .clone() + .ok_or("No connections configured")?[0]; + + let path = &conn.paths.clone().ok_or("No paths configured")?[0]; + + let connection = ConnectionConfig::new(&conn.clone())?; + let channel = ChannelConfig::new(&connection, &path)?; + let src_chain_config = config .chains - .get(0) - .cloned() - .ok_or("Configuration for source chain (position 0 in chains config) not found")?; + .clone() + .into_iter() + .find(|c| c.id == connection.src().chain_id().clone()) + .ok_or("Configuration for source chain not found")?; let dst_chain_config = config .chains - .get(1) - .cloned() - .ok_or("Configuration for dest. chain (position 1 in chains config) not found")?; - - // Parse & validate client identifiers - let client_src_id = ClientId::from_str( - src_chain_config - .client_ids - .get(0) - .ok_or("Config for client on source chain not found")?, - ) - .map_err(|e| format!("Error validating client identifier for src chain ({:?})", e))?; - - let client_dst_id = ClientId::from_str( - dst_chain_config - .client_ids - .get(0) - .ok_or("Config for client for dest. chain not found")?, - ) - .map_err(|e| format!("Error validating client identifier for dst chain ({:?})", e))?; + .clone() + .into_iter() + .find(|c| c.id == connection.dst().chain_id().clone()) + .ok_or("Configuration for source chain not found")?; - // Initialize the source and destination runtimes and light clients let (src_chain_handle, _) = ChainRuntime::spawn(src_chain_config)?; let (dst_chain_handle, _) = ChainRuntime::spawn(dst_chain_config)?; - // Instantiate the foreign client on the source chain. - // let client_on_src = ForeignClient::new( - // &src_chain_handle, - // &dst_chain_handle, - // ForeignClientConfig::new(client_src_id), - // )?; - - // Instantiate the foreign client on the destination chain. - // let client_on_dst = ForeignClient::new( - // &dst_chain_handle, - // &src_chain_handle, - // ForeignClientConfig::new(client_dst_id), - // )?; - - // Initialize a connection between the two chains - // let connection = Connection::new( - // &src_chain_handle, - // &dst_chain_handle, - // &client_on_src, // Semantic dependency. - // ConnectionConfig::new(todo!(), todo!()), - // )?; - - // Initialize a channel over the connection - // let channel = Channel::new( - // &src_chain_handle, - // &dst_chain_handle, - // connection, // Semantic dependecy - // ChannelConfig::new(todo!(), todo!()), - // )?; - - // TODO: Re-enable `link` module in `relayer/src/lib.rs` - // let link = Link::new( - // src_chain_handle, - // dst_chain_handle, - // client_on_src, // Actual dependecy - // channel, // Semantic dependecy - // LinkConfig::new(todo!(), todo!(), todo!()), - // )?; - - // link.run()?; - - Ok(()) + Ok(channel_relay( + src_chain_handle, + dst_chain_handle, + connection, + channel, + )?) } diff --git a/relayer/src/chain.rs b/relayer/src/chain.rs index 8f02fb8699..27116b9302 100644 --- a/relayer/src/chain.rs +++ b/relayer/src/chain.rs @@ -31,9 +31,9 @@ use ibc::ics23_commitment::merkle::MerkleProof; use ibc::Height as ICSHeight; use crate::config::ChainConfig; +use crate::connection::ConnectionMsgType; use crate::error::{Error, Kind}; use crate::keyring::store::{KeyEntry, KeyRing}; -use crate::tx::connection::ConnectionMsgType; /// Generic query response type /// TODO - will slowly move to GRPC protobuf specs for queries @@ -128,12 +128,8 @@ pub trait Chain { connection_id: &ConnectionId, height: ICSHeight, ) -> Result { - Ok(self - .query(Path::Connections(connection_id.clone()), height, false) - .map_err(|e| Kind::Query.context(e)) - .and_then(|v| { - ConnectionEnd::decode_vec(&v.value).map_err(|e| Kind::Query.context(e)) - })?) + let res = self.query(Path::Connections(connection_id.clone()), height, false)?; + Ok(ConnectionEnd::decode_vec(&res.value).map_err(|e| Kind::Query.context(e))?) } fn query_channel( @@ -142,14 +138,12 @@ pub trait Chain { channel_id: &ChannelId, height: ICSHeight, ) -> Result { - Ok(self - .query( - Path::ChannelEnds(port_id.clone(), channel_id.clone()), - height, - false, - ) - .map_err(|e| Kind::Query.context(e)) - .and_then(|v| ChannelEnd::decode_vec(&v.value).map_err(|e| Kind::Query.context(e)))?) + let res = self.query( + Path::ChannelEnds(port_id.clone(), channel_id.clone()), + height, + false, + )?; + Ok(ChannelEnd::decode_vec(&res.value).map_err(|e| Kind::Query.context(e))?) } // Provable queries diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 1e5f7a1e92..de5f09d5c7 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -156,10 +156,8 @@ impl Chain for CosmosSDKChain { let response = self.block_on(abci_query(&self, path, data.to_string(), height, prove))??; - // Verify response proof, if requested. - if prove { - dbg!("Todo: implement proof verification."); // Todo: Verify proof - } + // TODO - Verify response proof, if requested. + if prove {} Ok(response) } @@ -442,7 +440,7 @@ async fn abci_query( data: String, height: Height, prove: bool, -) -> Result> { +) -> Result { let height = if height.value() == 0 { None } else { diff --git a/relayer/src/chain/handle.rs b/relayer/src/chain/handle.rs index 39ba51b04a..f64951e9ce 100644 --- a/relayer/src/chain/handle.rs +++ b/relayer/src/chain/handle.rs @@ -18,7 +18,7 @@ use ibc::{ // FIXME: the handle should not depend on tendermint-specific types use tendermint::account::Id as AccountId; -use crate::tx::connection::ConnectionMsgType; +use crate::connection::ConnectionMsgType; use crate::{error::Error, event::monitor::EventBatch}; // use crate::foreign_client::ForeignClient; diff --git a/relayer/src/chain/handle/prod.rs b/relayer/src/chain/handle/prod.rs index 4876bca612..a6ae4ec17f 100644 --- a/relayer/src/chain/handle/prod.rs +++ b/relayer/src/chain/handle/prod.rs @@ -20,9 +20,9 @@ use tendermint::account::Id as AccountId; use crate::{ chain::QueryResponse, + connection::ConnectionMsgType, error::{Error, Kind}, keyring::store::KeyEntry, - tx::connection::ConnectionMsgType, }; use super::{reply_channel, ChainHandle, HandleInput, ReplyTo, Subscription}; diff --git a/relayer/src/chain/runtime.rs b/relayer/src/chain/runtime.rs index f3624bbf30..0be8a925d4 100644 --- a/relayer/src/chain/runtime.rs +++ b/relayer/src/chain/runtime.rs @@ -31,6 +31,7 @@ use tendermint::account::Id as AccountId; use crate::{ config::ChainConfig, + connection::ConnectionMsgType, error::{Error, Kind}, event::{ bus::EventBus, @@ -38,7 +39,6 @@ use crate::{ }, keyring::store::KeyEntry, light_client::{tendermint::LightClient as TMLightClient, LightClient}, - tx::connection::ConnectionMsgType, }; use super::{ diff --git a/relayer/src/channel.rs b/relayer/src/channel.rs index 9a8f38a7df..aa3148e6e2 100644 --- a/relayer/src/channel.rs +++ b/relayer/src/channel.rs @@ -1,48 +1,734 @@ -use ibc::ics24_host::identifier::{ChainId, ChannelId, ClientId, PortId}; +use std::str::FromStr; +use std::time::SystemTime; + +use prost_types::Any; use thiserror::Error; -use crate::chain::{handle::ChainHandle, Chain}; -use crate::connection::Connection; +use ibc_proto::ibc::core::channel::v1::MsgChannelOpenAck as RawMsgChannelOpenAck; +use ibc_proto::ibc::core::channel::v1::MsgChannelOpenConfirm as RawMsgChannelOpenConfirm; +use ibc_proto::ibc::core::channel::v1::MsgChannelOpenInit as RawMsgChannelOpenInit; +use ibc_proto::ibc::core::channel::v1::MsgChannelOpenTry as RawMsgChannelOpenTry; + +use ibc::ics04_channel::channel::{ChannelEnd, Counterparty, Order, State}; +use ibc::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; +use ibc::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; +use ibc::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; +use ibc::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; +use ibc::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}; +use ibc::tx_msg::Msg; +use ibc::Height; + +use crate::chain::handle::ChainHandle; +use crate::config::RelayPath; +use crate::connection::{Connection, ConnectionConfig}; +use crate::error::{Error, Kind}; +use crate::foreign_client::build_update_client; +use crate::relay::MAX_ITER; #[derive(Debug, Error)] pub enum ChannelError { #[error("failed")] - Failed, + Failed(String), } +#[derive(Clone, Debug)] pub struct ChannelConfigSide { chain_id: ChainId, + connection_id: ConnectionId, client_id: ClientId, - channel_id: ChannelId, port_id: PortId, + channel_id: ChannelId, } +impl ChannelConfigSide { + pub fn new( + chain_id: &ChainId, + connection_id: &ConnectionId, + client_id: &ClientId, + port_id: &PortId, + channel_id: &ChannelId, + ) -> ChannelConfigSide { + Self { + chain_id: chain_id.clone(), + connection_id: connection_id.clone(), + client_id: client_id.clone(), + port_id: port_id.clone(), + channel_id: channel_id.clone(), + } + } + + pub fn chain_id(&self) -> &ChainId { + &self.chain_id + } + + pub fn connection_id(&self) -> &ConnectionId { + &self.connection_id + } + + pub fn client_id(&self) -> &ClientId { + &self.client_id + } + + pub fn port_id(&self) -> &PortId { + &self.port_id + } + + pub fn channel_id(&self) -> &ChannelId { + &self.channel_id + } +} + +#[derive(Clone, Debug)] + pub struct ChannelConfig { - src_config: ChannelConfigSide, - dst_config: ChannelConfigSide, + pub ordering: Order, + pub a_config: ChannelConfigSide, + pub b_config: ChannelConfigSide, } +impl ChannelConfig { + pub fn src(&self) -> &ChannelConfigSide { + &self.a_config + } + + pub fn dst(&self) -> &ChannelConfigSide { + &self.b_config + } + + pub fn a_end(&self) -> &ChannelConfigSide { + &self.a_config + } + + pub fn b_end(&self) -> &ChannelConfigSide { + &self.b_config + } + + pub fn flipped(&self) -> ChannelConfig { + ChannelConfig { + ordering: self.ordering, + a_config: self.b_config.clone(), + b_config: self.a_config.clone(), + } + } +} + +#[derive(Clone, Debug)] pub struct Channel { config: ChannelConfig, } impl ChannelConfig { - pub fn new(src_config: ChannelConfigSide, dst_config: ChannelConfigSide) -> ChannelConfig { - Self { - src_config, - dst_config, - } + pub fn new(conn: &ConnectionConfig, path: &RelayPath) -> Result { + let a_config = ChannelConfigSide { + chain_id: conn.a_end().chain_id().clone(), + connection_id: conn.a_end().connection_id().clone(), + client_id: conn.a_end().client_id().clone(), + port_id: PortId::from_str(path.a_port.clone().ok_or("Port id not specified")?.as_str()) + .map_err(|e| format!("Invalid port id ({:?})", e))?, + channel_id: ChannelId::from_str( + path.a_channel + .clone() + .ok_or("Channel id not specified")? + .as_str(), + ) + .map_err(|e| format!("Invalid channel id ({:?})", e))?, + }; + + let b_config = ChannelConfigSide { + chain_id: conn.b_end().chain_id().clone(), + connection_id: conn.b_end().connection_id().clone(), + client_id: conn.b_end().client_id().clone(), + port_id: PortId::from_str( + path.b_port + .clone() + .ok_or("Counterparty port id not specified")? + .as_str(), + ) + .map_err(|e| format!("Invalid counterparty port id ({:?})", e))?, + channel_id: ChannelId::from_str( + path.b_channel + .clone() + .ok_or("Counterparty channel id not specified")? + .as_str(), + ) + .map_err(|e| format!("Invalid counterparty channel id ({:?})", e))?, + }; + + Ok(ChannelConfig { + ordering: Default::default(), // TODO - add to config + a_config, + b_config, + }) + } +} + +// temp fix for queries +fn get_channel( + chain: impl ChainHandle, + port: &PortId, + id: &ChannelId, +) -> Result, ChannelError> { + match chain.query_channel(port, id, Height::zero()) { + Err(e) => match e.kind() { + Kind::EmptyResponseValue => Ok(None), + _ => Err(ChannelError::Failed(format!( + "error retrieving channel {:?}", + e + ))), + }, + Ok(chan) => Ok(Some(chan)), } } impl Channel { pub fn new( - src_chain: impl ChainHandle, - dst_chain: impl ChainHandle, - connection: Connection, // Semantic dependency + a_chain: impl ChainHandle, + b_chain: impl ChainHandle, + _connection: Connection, config: ChannelConfig, ) -> Result { - // XXX: Perform the channel handshake - Ok(Channel { config }) + let done = '\u{1F973}'; + + let flipped = config.flipped(); + + let mut counter = 0; + + while counter < MAX_ITER { + counter += 1; + let now = SystemTime::now(); + + // Continue loop if query error + let a_channel = get_channel( + a_chain.clone(), + &config.a_end().port_id, + &config.a_end().channel_id, + ); + if a_channel.is_err() { + continue; + } + let b_channel = get_channel( + b_chain.clone(), + &config.b_end().port_id, + &config.b_end().channel_id, + ); + if b_channel.is_err() { + continue; + } + + match (a_channel?, b_channel?) { + (None, None) => { + // Init to src + match build_chan_init_and_send(a_chain.clone(), b_chain.clone(), &flipped) { + Err(e) => println!("{:?} Failed ChanInit {:?}", e, config.a_end()), + Ok(_) => println!("{} ChanInit {:?}", done, config.a_end()), + } + } + (Some(a_channel), None) => { + // Try to dest + assert!(a_channel.state_matches(&State::Init)); + match build_chan_try_and_send(b_chain.clone(), a_chain.clone(), &config) { + Err(e) => println!("{:?} Failed ChanTry {:?}", e, config.b_end()), + Ok(_) => println!("{} ChanTry {:?}", done, config.b_end()), + } + } + (None, Some(b_channel)) => { + // Try to src + assert!(b_channel.state_matches(&State::Init)); + match build_chan_try_and_send(a_chain.clone(), b_chain.clone(), &flipped) { + Err(e) => println!("{:?} Failed ChanTry {:?}", e, config.a_end()), + Ok(_) => println!("{} ChanTry {:?}", done, config.a_end()), + } + } + (Some(a_channel), Some(b_channel)) => { + match (a_channel.state(), b_channel.state()) { + (&State::Init, &State::Init) => { + // Try to dest + // Try to dest + match build_chan_try_and_send(b_chain.clone(), a_chain.clone(), &config) + { + Err(e) => println!("{:?} Failed ChanTry {:?}", e, config.b_end()), + Ok(_) => println!("{} ChanTry {:?}", done, config.b_end()), + } + } + (&State::TryOpen, &State::Init) => { + // Ack to dest + match build_chan_ack_and_send(b_chain.clone(), a_chain.clone(), &config) + { + Err(e) => println!("{:?} Failed ChanAck {:?}", e, config.b_end()), + Ok(_) => println!("{} ChanAck {:?}", done, config.b_end()), + } + } + (&State::Init, &State::TryOpen) | (&State::TryOpen, &State::TryOpen) => { + // Ack to src + match build_chan_ack_and_send( + a_chain.clone(), + b_chain.clone(), + &flipped, + ) { + Err(e) => println!("{:?} Failed ChanAck {:?}", e, config.a_end()), + Ok(_) => println!("{} ChanAck {:?}", done, config.a_end()), + } + } + (&State::Open, &State::TryOpen) => { + // Confirm to dest + match build_chan_confirm_and_send( + b_chain.clone(), + a_chain.clone(), + &config, + ) { + Err(e) => { + println!("{:?} Failed ChanConfirm {:?}", e, config.b_end()) + } + Ok(_) => println!("{} ChanConfirm {:?}", done, config.b_end()), + } + } + (&State::TryOpen, &State::Open) => { + // Confirm to src + match build_chan_confirm_and_send( + a_chain.clone(), + b_chain.clone(), + &flipped, + ) { + Err(e) => println!("{:?} ChanConfirm {:?}", e, flipped), + Ok(_) => println!("{} ChanConfirm {:?}", done, flipped), + } + } + (&State::Open, &State::Open) => { + println!( + "{} {} {} Channel handshake finished for {:#?}", + done, done, done, config + ); + return Ok(Channel { config }); + } + _ => {} // TODO channel close + } + } + } + println!("elapsed time {:?}\n", now.elapsed().unwrap().as_secs()); + } + + Err(ChannelError::Failed(format!( + "Failed to finish channel handshake in {:?} iterations", + MAX_ITER + ))) + } +} + +/// Enumeration of proof carrying ICS4 message, helper for relayer. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ChannelMsgType { + OpenTry, + OpenAck, + OpenConfirm, +} + +pub fn build_chan_init( + dst_chain: impl ChainHandle, + _src_chain: impl ChainHandle, + opts: &ChannelConfig, +) -> Result, Error> { + // Check that the destination chain will accept the message, i.e. it does not have the channel + if dst_chain + .query_channel( + opts.dst().port_id(), + opts.dst().channel_id(), + Height::default(), + ) + .is_ok() + { + return Err(Kind::ChanOpenInit( + opts.dst().channel_id().clone(), + "channel already exist".into(), + ) + .into()); } + + let signer = dst_chain + .get_signer() + .map_err(|e| Kind::KeyBase.context(e))?; + + let counterparty = Counterparty::new( + opts.src().port_id().clone(), + Some(opts.src().channel_id().clone()), + ); + + let channel = ChannelEnd::new( + State::Init, + opts.ordering, + counterparty, + vec![opts.dst().connection_id().clone()], + dst_chain.module_version(&opts.dst().port_id())?, + ); + + // Build the domain type message + let new_msg = MsgChannelOpenInit { + port_id: opts.dst().port_id().clone(), + channel_id: opts.dst().channel_id().clone(), + channel, + signer, + }; + + Ok(vec![new_msg.to_any::()]) +} + +pub fn build_chan_init_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ChannelConfig, +) -> Result { + let dst_msgs = build_chan_init(dst_chain.clone(), src_chain, &opts)?; + Ok(dst_chain.send_tx(dst_msgs)?) +} + +fn check_destination_channel_state( + channel_id: ChannelId, + existing_channel: ChannelEnd, + expected_channel: ChannelEnd, +) -> Result<(), Error> { + let good_connection_hops = + existing_channel.connection_hops() == expected_channel.connection_hops(); + + let good_state = + existing_channel.state().clone() as u32 <= expected_channel.state().clone() as u32; + + let good_channel_ids = existing_channel.counterparty().channel_id().is_none() + || existing_channel.counterparty().channel_id() + == expected_channel.counterparty().channel_id(); + + // TODO check versions + + if good_state && good_connection_hops && good_channel_ids { + Ok(()) + } else { + Err(Kind::ChanOpenTry( + channel_id, + "channel already exist in an incompatible state".into(), + ) + .into()) + } +} + +/// Retrieves the channel from destination and compares against the expected channel +/// built from the message type (`msg_type`) and options (`opts`). +/// If the expected and the destination channels are compatible, it returns the expected channel +fn validated_expected_channel( + dst_chain: impl ChainHandle, + _src_chain: impl ChainHandle, + msg_type: ChannelMsgType, + opts: &ChannelConfig, +) -> Result { + // If there is a channel present on the destination chain, it should look like this: + let counterparty = Counterparty::new( + opts.src().port_id().clone(), + Option::from(opts.src().channel_id().clone()), + ); + + // The highest expected state, depends on the message type: + let highest_state = match msg_type { + ChannelMsgType::OpenTry => State::Init, + ChannelMsgType::OpenAck => State::TryOpen, + ChannelMsgType::OpenConfirm => State::TryOpen, + }; + + let dest_expected_channel = ChannelEnd::new( + highest_state, + opts.ordering, + counterparty, + vec![opts.dst().connection_id().clone()], + dst_chain.module_version(&opts.dst().port_id())?, + ); + + // Retrieve existing channel if any + let dest_channel = dst_chain.query_channel( + &opts.dst().port_id(), + &opts.dst().channel_id(), + Height::default(), + ); + + // Check if a connection is expected to exist on destination chain + if msg_type == ChannelMsgType::OpenTry { + // TODO - check typed Err, or make query_channel return Option + // It is ok if there is no channel for Try Tx + if dest_channel.is_err() { + return Ok(dest_expected_channel); + } + } else { + // A channel must exist on destination chain for Ack and Confirm Tx-es to succeed + if dest_channel.is_err() { + return Err(Kind::ChanOpenTry( + opts.src().channel_id().clone(), + "missing channel on source chain".to_string(), + ) + .into()); + } + } + + check_destination_channel_state( + opts.dst().channel_id().clone(), + dest_channel?, + dest_expected_channel.clone(), + )?; + + Ok(dest_expected_channel) +} + +pub fn build_chan_try( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ChannelConfig, +) -> Result, Error> { + // Check that the destination chain will accept the message, i.e. it does not have the channel + let _dest_expected_channel = validated_expected_channel( + dst_chain.clone(), + src_chain.clone(), + ChannelMsgType::OpenTry, + opts, + ) + .map_err(|e| { + Kind::ChanOpenTry( + opts.src().channel_id().clone(), + "try options inconsistent with existing channel on destination chain".to_string(), + ) + .context(e) + })?; + + let src_channel = src_chain + .query_channel( + &opts.src().port_id(), + &opts.src().channel_id(), + Height::default(), + ) + .map_err(|e| { + Kind::ChanOpenTry( + opts.dst().channel_id().clone(), + "channel does not exist on source".into(), + ) + .context(e) + })?; + + // Retrieve the connection + let dest_connection = + dst_chain.query_connection(&opts.dst().connection_id().clone(), Height::default())?; + + let ics_target_height = src_chain.query_latest_height()?; + + // Build message to update client on destination + let mut msgs = build_update_client( + dst_chain.clone(), + src_chain.clone(), + &dest_connection.client_id(), + ics_target_height, + )?; + + let counterparty = Counterparty::new( + opts.src().port_id().clone(), + Some(opts.src().channel_id().clone()), + ); + + let channel = ChannelEnd::new( + State::Init, + opts.ordering, + counterparty, + vec![opts.dst().connection_id().clone()], + dst_chain.module_version(&opts.dst().port_id())?, + ); + + // Get signer + let signer = dst_chain + .get_signer() + .map_err(|e| Kind::KeyBase.context(e))?; + + // Build the domain type message + let new_msg = MsgChannelOpenTry { + port_id: opts.dst().port_id().clone(), + channel_id: opts.dst().channel_id().clone(), + counterparty_chosen_channel_id: src_channel.counterparty().channel_id, + channel, + counterparty_version: src_chain.module_version(&opts.src().port_id())?, + proofs: src_chain.build_channel_proofs( + &opts.src().port_id(), + &opts.src().channel_id(), + ics_target_height, + )?, + signer, + }; + + let mut new_msgs = vec![new_msg.to_any::()]; + + msgs.append(&mut new_msgs); + + Ok(msgs) +} + +pub fn build_chan_try_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ChannelConfig, +) -> Result { + let dst_msgs = build_chan_try(dst_chain.clone(), src_chain, &opts)?; + Ok(dst_chain.send_tx(dst_msgs)?) +} + +pub fn build_chan_ack( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ChannelConfig, +) -> Result, Error> { + // Check that the destination chain will accept the message + let _dest_expected_channel = validated_expected_channel( + dst_chain.clone(), + src_chain.clone(), + ChannelMsgType::OpenAck, + opts, + ) + .map_err(|e| { + Kind::ChanOpenAck( + opts.src().channel_id().clone(), + "ack options inconsistent with existing channel on destination chain".to_string(), + ) + .context(e) + })?; + + let _src_channel = src_chain + .query_channel( + &opts.src().port_id(), + &opts.src().channel_id(), + Height::default(), + ) + .map_err(|e| { + Kind::ChanOpenAck( + opts.dst().channel_id().clone(), + "channel does not exist on source".into(), + ) + .context(e) + })?; + + // Retrieve the connection + let dest_connection = + dst_chain.query_connection(&opts.dst().connection_id().clone(), Height::default())?; + + let ics_target_height = src_chain.query_latest_height()?; + + // Build message to update client on destination + let mut msgs = build_update_client( + dst_chain.clone(), + src_chain.clone(), + &dest_connection.client_id(), + ics_target_height, + )?; + + // Get signer + let signer = dst_chain + .get_signer() + .map_err(|e| Kind::KeyBase.context(e))?; + + // Build the domain type message + let new_msg = MsgChannelOpenAck { + port_id: opts.dst().port_id().clone(), + channel_id: opts.dst().channel_id().clone(), + counterparty_channel_id: opts.src().channel_id().clone(), + counterparty_version: src_chain.module_version(&opts.dst().port_id())?, + proofs: src_chain.build_channel_proofs( + &opts.src().port_id(), + &opts.src().channel_id(), + ics_target_height, + )?, + signer, + }; + + let mut new_msgs = vec![new_msg.to_any::()]; + + msgs.append(&mut new_msgs); + + Ok(msgs) +} + +pub fn build_chan_ack_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ChannelConfig, +) -> Result { + let dst_msgs = build_chan_ack(dst_chain.clone(), src_chain, &opts)?; + Ok(dst_chain.send_tx(dst_msgs)?) +} + +pub fn build_chan_confirm( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ChannelConfig, +) -> Result, Error> { + // Check that the destination chain will accept the message + let _dest_expected_channel = validated_expected_channel( + dst_chain.clone(), + src_chain.clone(), + ChannelMsgType::OpenConfirm, + opts, + ) + .map_err(|e| { + Kind::ChanOpenConfirm( + opts.src().channel_id().clone(), + "confirm options inconsistent with existing channel on destination chain".to_string(), + ) + .context(e) + })?; + + let _src_channel = src_chain + .query_channel( + &opts.src().port_id(), + &opts.src().channel_id(), + Height::default(), + ) + .map_err(|e| { + Kind::ChanOpenConfirm( + opts.src().channel_id().clone(), + "channel does not exist on source".into(), + ) + .context(e) + })?; + + // Retrieve the connection + let dest_connection = + dst_chain.query_connection(&opts.dst().connection_id().clone(), Height::default())?; + + let ics_target_height = src_chain.query_latest_height()?; + + // Build message to update client on destination + let mut msgs = build_update_client( + dst_chain.clone(), + src_chain.clone(), + &dest_connection.client_id(), + ics_target_height, + )?; + + // Get signer + let signer = dst_chain + .get_signer() + .map_err(|e| Kind::KeyBase.context(e))?; + + // Build the domain type message + let new_msg = MsgChannelOpenConfirm { + port_id: opts.dst().port_id().clone(), + channel_id: opts.dst().channel_id().clone(), + proofs: src_chain.build_channel_proofs( + &opts.src().port_id(), + &opts.src().channel_id(), + ics_target_height, + )?, + signer, + }; + + let mut new_msgs = vec![new_msg.to_any::()]; + + msgs.append(&mut new_msgs); + + Ok(msgs) +} + +pub fn build_chan_confirm_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ChannelConfig, +) -> Result { + let dst_msgs = build_chan_confirm(dst_chain.clone(), src_chain, &opts)?; + Ok(dst_chain.send_tx(dst_msgs)?) } diff --git a/relayer/src/config.rs b/relayer/src/config.rs index 36a0c0c8a0..c8a943be43 100644 --- a/relayer/src/config.rs +++ b/relayer/src/config.rs @@ -130,8 +130,8 @@ impl ChainConfig { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Connection { - pub src: Option, // use any source - pub dest: Option, // use any destination + pub a_end: Option, // use any source + pub b_end: Option, // use any destination pub paths: Option>, // use any port, direction bidirectional } @@ -158,10 +158,10 @@ impl Default for Direction { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct RelayPath { - pub src_port: Option, // default from any source port - pub dest_port: Option, // default from any dest port - pub src_channel: Option, // default from any source port - pub dest_channel: Option, // default from any dest port + pub a_port: Option, // default from any source port + pub b_port: Option, // default from any dest port + pub a_channel: Option, // default from any source port + pub b_channel: Option, // default from any dest port #[serde(default)] pub direction: Direction, // default bidirectional } diff --git a/relayer/src/connection.rs b/relayer/src/connection.rs index 9dbd93747a..df024cae19 100644 --- a/relayer/src/connection.rs +++ b/relayer/src/connection.rs @@ -1,48 +1,700 @@ -use ibc::ics24_host::identifier::{ChainId, ClientId, ConnectionId}; +use prost_types::Any; +use std::str::FromStr; +use std::time::SystemTime; use thiserror::Error; -use crate::chain::{handle::ChainHandle, Chain}; -// use crate::foreign_client::ForeignClient; +use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenAck as RawMsgConnectionOpenAck; +use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm; +use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenInit as RawMsgConnectionOpenInit; +use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as RawMsgConnectionOpenTry; + +use ibc::ics02_client::height::Height; +use ibc::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; +use ibc::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; +use ibc::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; +use ibc::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; +use ibc::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; +use ibc::ics24_host::identifier::{ChainId, ClientId, ConnectionId}; +use ibc::tx_msg::Msg; +use ibc::Height as ICSHeight; + +use crate::chain::handle::ChainHandle; +use crate::config; +use crate::error::{Error, Kind}; +use crate::foreign_client::{build_update_client, ForeignClient}; +use crate::relay::MAX_ITER; #[derive(Debug, Error)] pub enum ConnectionError { #[error("Failed")] - Failed(), + Failed(String), } +#[derive(Clone, Debug)] pub struct Connection { pub config: ConnectionConfig, } +#[derive(Clone, Debug)] pub struct ConnectionSideConfig { - pub connection_id: ConnectionId, - pub chain_id: ChainId, - pub client_id: ClientId, + chain_id: ChainId, + connection_id: ConnectionId, + client_id: ClientId, +} + +impl ConnectionSideConfig { + pub fn new( + chain_id: ChainId, + connection_id: ConnectionId, + client_id: ClientId, + ) -> ConnectionSideConfig { + Self { + chain_id, + connection_id, + client_id, + } + } + + pub fn chain_id(&self) -> &ChainId { + &self.chain_id + } + + pub fn connection_id(&self) -> &ConnectionId { + &self.connection_id + } + + pub fn client_id(&self) -> &ClientId { + &self.client_id + } } +#[derive(Clone, Debug)] pub struct ConnectionConfig { - pub src_config: ConnectionSideConfig, - pub dst_config: ConnectionSideConfig, + pub a_config: ConnectionSideConfig, + pub b_config: ConnectionSideConfig, } impl ConnectionConfig { - pub fn new(_src: ConnectionSideConfig, _dst: ConnectionSideConfig) -> ConnectionConfig { - todo!() + pub fn src(&self) -> &ConnectionSideConfig { + &self.a_config + } + + pub fn dst(&self) -> &ConnectionSideConfig { + &self.b_config + } + + pub fn a_end(&self) -> &ConnectionSideConfig { + &self.a_config + } + + pub fn b_end(&self) -> &ConnectionSideConfig { + &self.b_config + } + + pub fn flipped(&self) -> ConnectionConfig { + ConnectionConfig { + a_config: self.b_config.clone(), + b_config: self.a_config.clone(), + } + } +} + +impl ConnectionConfig { + pub fn new(conn: &config::Connection) -> Result { + let a_conn_endpoint = conn + .a_end + .clone() + .ok_or("Connection source endpoint not specified")?; + let b_conn_endpoint = conn + .b_end + .clone() + .ok_or("Connection destination endpoint not specified")?; + + let a_config = ConnectionSideConfig { + chain_id: ChainId::from_str(a_conn_endpoint.chain_id.as_str()) + .map_err(|e| format!("Invalid chain id ({:?})", e))?, + connection_id: ConnectionId::from_str( + a_conn_endpoint + .connection_id + .ok_or("Connection id not specified")? + .as_str(), + ) + .map_err(|e| format!("Invalid connection id ({:?})", e))?, + client_id: ClientId::from_str(a_conn_endpoint.client_id.as_str()) + .map_err(|e| format!("Invalid client id ({:?})", e))?, + }; + + let b_config = ConnectionSideConfig { + chain_id: ChainId::from_str(b_conn_endpoint.chain_id.as_str()) + .map_err(|e| format!("Invalid counterparty chain id ({:?})", e))?, + connection_id: ConnectionId::from_str( + b_conn_endpoint + .connection_id + .ok_or("Counterparty connection id not specified")? + .as_str(), + ) + .map_err(|e| format!("Invalid counterparty connection id ({:?})", e))?, + client_id: ClientId::from_str(b_conn_endpoint.client_id.as_str()) + .map_err(|e| format!("Invalid counterparty client id ({:?})", e))?, + }; + + Ok(ConnectionConfig { a_config, b_config }) + } +} + +// temp fix for queries +fn get_connection( + chain: impl ChainHandle, + id: &ConnectionId, +) -> Result, ConnectionError> { + match chain.query_connection(id, Height::zero()) { + Err(e) => match e.kind() { + Kind::EmptyResponseValue => Ok(None), + _ => Err(ConnectionError::Failed(format!( + "error retrieving connection {:?}", + e + ))), + }, + Ok(conn) => Ok(Some(conn)), } } impl Connection { pub fn new( - _src_chain: impl ChainHandle, - _dst_chain: impl ChainHandle, - _foreign_client: &ForeignClient, + a_chain: impl ChainHandle, + b_chain: impl ChainHandle, + _a_client: ForeignClient, + _b_client: ForeignClient, config: ConnectionConfig, ) -> Result { - // Check the status of the established connection - // * query connection on source chain - // * query the destination chain - // * based on the status on the status from src and dest, we know what to do - // * then we proceed with Handshake protocol - Ok(Connection { config }) + let done = '\u{1F942}'; // surprise emoji + + let flipped = config.flipped(); + + let mut counter = 0; + while counter < MAX_ITER { + counter += 1; + let now = SystemTime::now(); + + // Continue loop if query error + let a_connection = get_connection(a_chain.clone(), &config.a_end().connection_id); + if a_connection.is_err() { + continue; + } + let b_connection = get_connection(b_chain.clone(), &config.b_end().connection_id); + if b_connection.is_err() { + continue; + } + + match (a_connection?, b_connection?) { + (None, None) => { + // Init to src + match build_conn_init_and_send(a_chain.clone(), b_chain.clone(), &flipped) { + Err(e) => println!("{:?} Failed ConnInit {:?}", e, config.a_end()), + Ok(_) => println!("{} ConnInit {:?}", done, config.a_end()), + } + } + (Some(a_connection), None) => { + assert!(a_connection.state_matches(&State::Init)); + match build_conn_try_and_send(b_chain.clone(), a_chain.clone(), &config) { + Err(e) => println!("{:?} Failed ConnTry {:?}", e, config.b_end()), + Ok(_) => println!("{} ConnTry {:?}", done, config.b_end()), + } + } + (None, Some(b_connection)) => { + assert!(b_connection.state_matches(&State::Init)); + match build_conn_try_and_send(a_chain.clone(), b_chain.clone(), &flipped) { + Err(e) => println!("{:?} Failed ConnTry {:?}", e, config.a_end()), + Ok(_) => println!("{} ConnTry {:?}", done, config.a_end()), + } + } + (Some(a_connection), Some(b_connection)) => { + match (a_connection.state(), b_connection.state()) { + (&State::Init, &State::Init) => { + // Try to dest + match build_conn_try_and_send(b_chain.clone(), a_chain.clone(), &config) + { + Err(e) => println!("{:?} Failed ConnTry {:?}", e, config.b_end()), + Ok(_) => println!("{} ConnTry {:?}", done, config.b_end()), + } + } + (&State::TryOpen, &State::Init) => { + // Ack to dest + match build_conn_ack_and_send(b_chain.clone(), a_chain.clone(), &config) + { + Err(e) => println!("{:?} Failed ConnAck {:?}", e, config.b_end()), + Ok(_) => println!("{} ConnAck {:?}", done, config.b_end()), + } + } + (&State::Init, &State::TryOpen) | (&State::TryOpen, &State::TryOpen) => { + // Ack to src + match build_conn_ack_and_send( + a_chain.clone(), + b_chain.clone(), + &flipped, + ) { + Err(e) => println!("{:?} Failed ConnAck {:?}", e, config.a_end()), + Ok(_) => println!("{} ConnAck {:?}", done, config.a_end()), + } + } + (&State::Open, &State::TryOpen) => { + // Confirm to dest + match build_conn_confirm_and_send( + b_chain.clone(), + a_chain.clone(), + &config, + ) { + Err(e) => { + println!("{:?} Failed ConnConfirm {:?}", e, config.b_end()) + } + Ok(_) => println!("{} ConnConfirm {:?}", done, config.b_end()), + } + } + (&State::TryOpen, &State::Open) => { + // Confirm to src + match build_conn_confirm_and_send( + a_chain.clone(), + b_chain.clone(), + &flipped, + ) { + Err(e) => println!("{:?} ConnConfirm {:?}", e, config.a_end()), + Ok(_) => println!("{} ConnConfirm {:?}", done, config.a_end()), + } + } + (&State::Open, &State::Open) => { + println!( + "{} {} {} Connection handshake finished for [{:#?}]", + done, done, done, config + ); + return Ok(Connection { config }); + } + _ => {} + } + } + } + println!("elapsed time {:?}\n", now.elapsed().unwrap().as_secs()); + } + + Err(ConnectionError::Failed(format!( + "Failed to finish connection handshake in {:?} iterations", + MAX_ITER + ))) + } +} + +/// Enumeration of proof carrying ICS3 message, helper for relayer. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ConnectionMsgType { + OpenTry, + OpenAck, + OpenConfirm, +} + +pub fn build_conn_init( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ConnectionConfig, +) -> Result, Error> { + // Check that the destination chain will accept the message, i.e. it does not have the connection + if dst_chain + .query_connection(&opts.dst().connection_id(), ICSHeight::default()) + .is_ok() + { + return Err(Kind::ConnOpenInit( + opts.dst().connection_id().clone(), + "connection already exist".into(), + ) + .into()); + } + + // Get signer + let signer = dst_chain + .get_signer() + .map_err(|e| Kind::KeyBase.context(e))?; + + let prefix = src_chain.query_commitment_prefix()?; + + let counterparty = Counterparty::new( + opts.src().client_id().clone(), + Some(opts.src().connection_id().clone()), + prefix, + ); + + // Build the domain type message + let new_msg = MsgConnectionOpenInit { + client_id: opts.dst().client_id().clone(), + connection_id: opts.dst().connection_id().clone(), + counterparty, + version: dst_chain.query_compatible_versions()?[0].clone(), + signer, + }; + + Ok(vec![new_msg.to_any::()]) +} + +pub fn build_conn_init_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ConnectionConfig, +) -> Result { + let dst_msgs = build_conn_init(dst_chain.clone(), src_chain, opts)?; + Ok(dst_chain.send_tx(dst_msgs)?) +} + +fn check_destination_connection_state( + connection_id: ConnectionId, + existing_connection: ConnectionEnd, + expected_connection: ConnectionEnd, +) -> Result<(), Error> { + let good_client_ids = existing_connection.client_id() == expected_connection.client_id() + && existing_connection.counterparty().client_id() + == expected_connection.counterparty().client_id(); + + let good_state = + existing_connection.state().clone() as u32 <= expected_connection.state().clone() as u32; + + let good_connection_ids = existing_connection.counterparty().connection_id().is_none() + || existing_connection.counterparty().connection_id() + == expected_connection.counterparty().connection_id(); + + // TODO check versions and store prefix + + if good_state && good_client_ids && good_connection_ids { + Ok(()) + } else { + Err(Kind::ConnOpenTry( + connection_id, + "connection already exist in an incompatible state".into(), + ) + .into()) + } +} + +/// Retrieves the connection from destination and compares against the expected connection +/// built from the message type (`msg_type`) and options (`opts`). +/// If the expected and the destination connections are compatible, it returns the expected connection +fn validated_expected_connection( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + msg_type: ConnectionMsgType, + opts: &ConnectionConfig, +) -> Result { + // If there is a connection present on the destination chain, it should look like this: + let counterparty = Counterparty::new( + opts.src().client_id().clone(), + Option::from(opts.src().connection_id().clone()), + src_chain.query_commitment_prefix()?, + ); + + // The highest expected state, depends on the message type: + let highest_state = match msg_type { + ConnectionMsgType::OpenTry => State::Init, + ConnectionMsgType::OpenAck => State::TryOpen, + ConnectionMsgType::OpenConfirm => State::TryOpen, + }; + + let dst_expected_connection = ConnectionEnd::new( + highest_state, + opts.dst().client_id().clone(), + counterparty, + src_chain.query_compatible_versions()?, + ) + .unwrap(); + + // Retrieve existing connection if any + let dst_connection = + dst_chain.query_connection(&opts.dst().connection_id().clone(), ICSHeight::default()); + + // Check if a connection is expected to exist on destination chain + if msg_type == ConnectionMsgType::OpenTry { + // TODO - check typed Err, or make query_connection return Option + // It is ok if there is no connection for Try Tx + if dst_connection.is_err() { + return Ok(dst_expected_connection); + } + } else { + // A connection must exist on destination chain for Ack and Confirm Tx-es to succeed + if dst_connection.is_err() { + return Err(Kind::ConnOpenTry( + opts.src().connection_id().clone(), + "missing connection on source chain".to_string(), + ) + .into()); + } } + + check_destination_connection_state( + opts.dst().connection_id().clone(), + dst_connection?, + dst_expected_connection.clone(), + )?; + + Ok(dst_expected_connection) +} + +/// Attempts to build a MsgConnOpenTry. +pub fn build_conn_try( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ConnectionConfig, +) -> Result, Error> { + let dst_expected_connection = validated_expected_connection( + dst_chain.clone(), + src_chain.clone(), + ConnectionMsgType::OpenTry, + opts, + ) + .map_err(|e| { + Kind::ConnOpenTry( + opts.dst().connection_id().clone(), + "try options inconsistent with existing connection on destination chain".to_string(), + ) + .context(e) + })?; + + let src_connection = src_chain + .query_connection(&opts.src().connection_id().clone(), ICSHeight::default()) + .map_err(|e| { + Kind::ConnOpenTry( + opts.src().connection_id().clone(), + "missing connection on source chain".to_string(), + ) + .context(e) + })?; + + // TODO - check that the src connection is consistent with the try options + + // Build add send the message(s) for updating client on source + // TODO - add check if it is required + let src_client_target_height = dst_chain.query_latest_height()?; + let client_msgs = build_update_client( + src_chain.clone(), + dst_chain.clone(), + &opts.src().client_id(), + src_client_target_height, + )?; + src_chain.send_tx(client_msgs)?; + + // Build message(s) for updating client on destination + let ics_target_height = src_chain.query_latest_height()?; + + let mut msgs = build_update_client( + dst_chain.clone(), + src_chain.clone(), + &opts.dst().client_id(), + ics_target_height, + )?; + + let (client_state, proofs) = src_chain.build_connection_proofs_and_client_state( + ConnectionMsgType::OpenTry, + &opts.src().connection_id().clone(), + &opts.src().client_id(), + ics_target_height, + )?; + + let counterparty_versions = if src_connection.versions().is_empty() { + src_chain.query_compatible_versions()? + } else { + src_connection.versions() + }; + + // Get signer + let signer = dst_chain + .get_signer() + .map_err(|e| Kind::KeyBase.context(e))?; + + let new_msg = MsgConnectionOpenTry { + connection_id: opts.dst().connection_id().clone(), + client_id: opts.dst().client_id().clone(), + client_state, + counterparty_chosen_connection_id: src_connection.counterparty().connection_id().cloned(), + counterparty: dst_expected_connection.counterparty(), + counterparty_versions, + proofs, + signer, + }; + + let mut new_msgs = vec![new_msg.to_any::()]; + + msgs.append(&mut new_msgs); + + Ok(msgs) +} + +pub fn build_conn_try_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ConnectionConfig, +) -> Result { + let dst_msgs = build_conn_try(dst_chain.clone(), src_chain, &opts)?; + Ok(dst_chain.send_tx(dst_msgs)?) +} + +/// Attempts to build a MsgConnOpenAck. +pub fn build_conn_ack( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ConnectionConfig, +) -> Result, Error> { + let _expected_dst_connection = validated_expected_connection( + dst_chain.clone(), + src_chain.clone(), + ConnectionMsgType::OpenAck, + opts, + ) + .map_err(|e| { + Kind::ConnOpenAck( + opts.dst().connection_id().clone(), + "ack options inconsistent with existing connection on destination chain".to_string(), + ) + .context(e) + })?; + + let src_connection = src_chain + .query_connection(&opts.src().connection_id().clone(), ICSHeight::default()) + .map_err(|e| { + Kind::ConnOpenAck( + opts.src().connection_id().clone(), + "missing connection on source chain".to_string(), + ) + .context(e) + })?; + + // TODO - check that the src connection is consistent with the ack options + + // Build add **send** the message(s) for updating client on source. + // TODO - add check if it is required + // Build add send the message(s) for updating client on source + // TODO - add check if it is required + let src_client_target_height = dst_chain.query_latest_height()?; + let client_msgs = build_update_client( + src_chain.clone(), + dst_chain.clone(), + &opts.src().client_id(), + src_client_target_height, + )?; + src_chain.send_tx(client_msgs)?; + + // Build message(s) for updating client on destination + let ics_target_height = src_chain.query_latest_height()?; + + let mut msgs = build_update_client( + dst_chain.clone(), + src_chain.clone(), + &opts.dst().client_id(), + ics_target_height, + )?; + + let (client_state, proofs) = src_chain.build_connection_proofs_and_client_state( + ConnectionMsgType::OpenAck, + &opts.src().connection_id().clone(), + &opts.src().client_id(), + ics_target_height, + )?; + + // Get signer + let signer = dst_chain + .get_signer() + .map_err(|e| Kind::KeyBase.context(e))?; + + let new_msg = MsgConnectionOpenAck { + connection_id: opts.dst().connection_id().clone(), + counterparty_connection_id: Option::from(opts.src().connection_id().clone()), + client_state, + proofs, + version: src_connection.versions()[0].clone(), + signer, + }; + + let mut new_msgs = vec![new_msg.to_any::()]; + + msgs.append(&mut new_msgs); + + Ok(msgs) +} + +pub fn build_conn_ack_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ConnectionConfig, +) -> Result { + let dst_msgs = build_conn_ack(dst_chain.clone(), src_chain, opts)?; + Ok(dst_chain.send_tx(dst_msgs)?) +} + +/// Attempts to build a MsgConnOpenConfirm. +pub fn build_conn_confirm( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ConnectionConfig, +) -> Result, Error> { + let _expected_dst_connection = validated_expected_connection( + dst_chain.clone(), + src_chain.clone(), + ConnectionMsgType::OpenAck, + opts, + ) + .map_err(|e| { + Kind::ConnOpenConfirm( + opts.src().connection_id().clone(), + "confirm options inconsistent with existing connection on destination chain" + .to_string(), + ) + .context(e) + })?; + + let _src_connection = src_chain + .query_connection(&opts.src().connection_id().clone(), ICSHeight::default()) + .map_err(|e| { + Kind::ConnOpenAck( + opts.src().connection_id().clone(), + "missing connection on source chain".to_string(), + ) + .context(e) + })?; + + // TODO - check that the src connection is consistent with the confirm options + + // Build message(s) for updating client on destination + let ics_target_height = src_chain.query_latest_height()?; + + let mut msgs = build_update_client( + dst_chain.clone(), + src_chain.clone(), + &opts.dst().client_id(), + ics_target_height, + )?; + + let (_, proofs) = src_chain.build_connection_proofs_and_client_state( + ConnectionMsgType::OpenConfirm, + &opts.src().connection_id().clone(), + &opts.src().client_id(), + ics_target_height, + )?; + + // Get signer + let signer = dst_chain + .get_signer() + .map_err(|e| Kind::KeyBase.context(e))?; + + let new_msg = MsgConnectionOpenConfirm { + connection_id: opts.dst().connection_id().clone(), + proofs, + signer, + }; + + let mut new_msgs = vec![new_msg.to_any::()]; + + msgs.append(&mut new_msgs); + + Ok(msgs) +} + +pub fn build_conn_confirm_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ConnectionConfig, +) -> Result { + let dst_msgs = build_conn_confirm(dst_chain.clone(), src_chain, &opts)?; + Ok(dst_chain.send_tx(dst_msgs)?) } diff --git a/relayer/src/foreign_client.rs b/relayer/src/foreign_client.rs index 18756de5ff..726577a070 100644 --- a/relayer/src/foreign_client.rs +++ b/relayer/src/foreign_client.rs @@ -1,19 +1,20 @@ use prost_types::Any; -use tendermint::account::Id as AccountId; -use tendermint::{block::signed_header::SignedHeader, Hash}; use thiserror::Error; -use ibc::{ - ics02_client::client_def::AnyConsensusState, - ics02_client::header::Header, - ics07_tendermint::consensus_state::ConsensusState, - ics23_commitment::commitment::CommitmentProof, - ics24_host::identifier::{ChainId, ClientId}, - ics24_host::Path::ClientState as ClientStatePath, - Height, -}; +use ibc_proto::ibc::core::client::v1::MsgCreateClient as RawMsgCreateClient; +use ibc_proto::ibc::core::client::v1::MsgUpdateClient as RawMsgUpdateClient; -use crate::{chain::handle::ChainHandle, msgs::Datagram}; +use ibc::ics02_client::header::Header; +use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient; +use ibc::ics02_client::msgs::update_client::MsgUpdateAnyClient; +use ibc::ics02_client::state::ClientState; +use ibc::ics02_client::state::ConsensusState; +use ibc::ics24_host::identifier::{ChainId, ClientId}; +use ibc::tx_msg::Msg; +use ibc::Height; + +use crate::chain::handle::ChainHandle; +use crate::error::{Error, Kind}; #[derive(Debug, Error)] pub enum ForeignClientError { @@ -24,14 +25,22 @@ pub enum ForeignClientError { ClientUpdate(String), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct ForeignClientConfig { + chain: ChainId, id: ClientId, } impl ForeignClientConfig { - pub fn new(client_id: ClientId) -> ForeignClientConfig { - Self { id: client_id } + pub fn new(chain: &ChainId, id: &ClientId) -> ForeignClientConfig { + Self { + chain: chain.clone(), + id: id.clone(), + } + } + + pub fn chain_id(&self) -> &ChainId { + &self.chain } pub fn client_id(&self) -> &ClientId { @@ -45,92 +54,36 @@ pub struct ForeignClient { } impl ForeignClient { - /// Creates a new foreign client. Blocks until the client is created on `host` chain (or - /// panics???). - /// Post-condition: chain `host` will host an IBC client for chain `source`. + /// Creates a new foreign client. Blocks until the client is created on `dst_chain` or + /// an error occurs. + /// Post-condition: `dst_chain` hosts an IBC client for `src_chain`. /// TODO: what are the pre-conditions for success? - /// Is it enough to have a "live" handle to each of `host` and `target` chains? + /// Is it enough to have a "live" handle to each of `dst_chain` and `src_chain` chains? pub fn new( - host: impl ChainHandle, - source: impl ChainHandle, + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, config: ForeignClientConfig, ) -> Result { - // Query the client state on source chain. - let response = host.query(ClientStatePath(config.clone().id), Height::zero(), false); - - // The chain may already host a client with this id. - if response.is_ok() { - Ok(ForeignClient { config }) // Nothing left to do. - } else { - // Create a new client on the host chain. - Self::create_client(host, source, config.client_id())?; - Ok(ForeignClient { config }) - } - } - - /// Creates on the `dst` chain an IBC client which will store headers for `src` chain. - fn create_client( - dst: impl ChainHandle, // The chain that will host the client. - src: impl ChainHandle, // The client will store headers of this chain. - client_id: &ClientId, - ) -> Result<(), ForeignClientError> { - // Fetch latest header of the source chain. - let latest_header = src.get_header(Height::zero()).map_err(|e| { - ForeignClientError::ClientCreate(format!("failed to fetch latest header ({:?})", e)) - })?; - - // Build the client state. The source chain handle will take care of the details. - let client_state = src - .build_client_state(latest_header.height()) - .map_err(|e| { - ForeignClientError::ClientCreate(format!( - "failed to assemble client state ({:?})", - e - )) - })?; + let done = '\u{1F36D}'; - // Build the consensus state. - // The source chain handle knows the internals of assembling this message. - let consensus_state = src - .build_consensus_state(latest_header.height()) - .map_err(|e| { - ForeignClientError::ClientCreate(format!( - "failed to assemble client consensus state ({:?})", - e - )) + // Query the client state on source chain. + let client_state = dst_chain.query_client_state(&config.id, Height::default()); + if client_state.is_err() { + build_create_client_and_send(dst_chain, src_chain, &config).map_err(|e| { + ForeignClientError::ClientCreate(format!("Create client failed ({:?})", e)) })?; - - // Extract the signer from the destination chain handle, for example `dst.get_signer()`. - let signer: AccountId = todo!(); - - // Build the domain type message. - let create_client_msg = unimplemented!(); - // TODO Make the create message public. - // MsgCreateAnyClient::new(client_id.clone(), client_state, consensus_state, signer) - // .map_err(|e| { - // ForeignClientError::ClientCreate(format!( - // "failed to assemble the create client message ({:?})", - // e - // )) - // })?; - - // Create a proto any message. - let proto_msgs = vec![Any { - // TODO - add get_url_type() to prepend proper string to get_type() - type_url: "/ibc.client.MsgCreateClient".to_ascii_lowercase(), - value: vec![], //new_msg.get_sign_bytes(), - }]; - - // TODO: Bridge from Any message into EncodedTransaction. - // dst.submit(&proto_msgs)? - - Ok(()) + } + println!( + "{} Client on {:?} is created {:?}\n", + done, config.chain, config.id + ); + Ok(ForeignClient { config }) } pub fn update( &mut self, - src_chain: impl ChainHandle, - dst_chain: impl ChainHandle, + _src_chain: impl ChainHandle, + _dst_chain: impl ChainHandle, src_target_height: Height, ) -> Result { /* @@ -176,15 +129,85 @@ impl ForeignClient { } } -fn verify_consensus_state_inclusion( - _consensus_state: &ConsensusState, - _membership_proof: &CommitmentProof, - _hash: &Hash, -) -> bool { - true +pub fn build_create_client( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + dst_client_id: &ClientId, +) -> Result { + // Verify that the client has not been created already, i.e the destination chain does not + // have a state for this client. + let client_state = dst_chain.query_client_state(&dst_client_id, Height::default()); + if client_state.is_ok() { + return Err(Into::::into(Kind::CreateClient( + dst_client_id.clone(), + "client already exists".into(), + ))); + } + + // Get signer + let signer = dst_chain + .get_signer() + .map_err(|e| Kind::KeyBase.context(e))?; + + // Build client create message with the data from source chain at latest height. + let latest_height = src_chain.query_latest_height()?; + Ok(MsgCreateAnyClient::new( + dst_client_id.clone(), + src_chain.build_client_state(latest_height)?.wrap_any(), + src_chain.build_consensus_state(latest_height)?.wrap_any(), + signer, + ) + .map_err(|e| { + Kind::MessageTransaction("failed to build the create client message".into()).context(e) + })?) +} + +pub fn build_create_client_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ForeignClientConfig, +) -> Result { + let new_msg = build_create_client(dst_chain.clone(), src_chain, opts.client_id())?; + + Ok(dst_chain.send_tx(vec![new_msg.to_any::()])?) +} + +pub fn build_update_client( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + dst_client_id: &ClientId, + target_height: Height, +) -> Result, Error> { + // Get the latest trusted height from the client state on destination. + let trusted_height = dst_chain + .query_client_state(&dst_client_id, Height::default())? + .latest_height(); + + let header = src_chain + .build_header(trusted_height, target_height)? + .wrap_any(); + + let signer = dst_chain.get_signer()?; + let new_msg = MsgUpdateAnyClient { + client_id: dst_client_id.clone(), + header, + signer, + }; + + Ok(vec![new_msg.to_any::()]) } -// XXX: It's probably the link that can produce this -fn create_client_update_datagram(_header: Vec) -> Datagram { - Datagram::NoOp() +pub fn build_update_client_and_send( + dst_chain: impl ChainHandle, + src_chain: impl ChainHandle, + opts: &ForeignClientConfig, +) -> Result { + let new_msgs = build_update_client( + dst_chain.clone(), + src_chain.clone(), + opts.client_id(), + src_chain.query_latest_height()?, + )?; + + Ok(dst_chain.send_tx(new_msgs)?) } diff --git a/relayer/src/lib.rs b/relayer/src/lib.rs index a0ca9133ad..1a85ec987f 100644 --- a/relayer/src/lib.rs +++ b/relayer/src/lib.rs @@ -14,16 +14,16 @@ pub mod auth; pub mod chain; -// pub mod channel; +pub mod channel; pub mod config; -pub mod event; -// pub mod connection; +pub mod connection; pub mod error; -// pub mod foreign_client; +pub mod event; +pub mod foreign_client; pub mod keyring; pub mod keys; pub mod light_client; // pub mod link; pub mod msgs; -pub mod tx; +pub mod relay; pub mod util; diff --git a/relayer/src/relay.rs b/relayer/src/relay.rs new file mode 100644 index 0000000000..2bdb81ab39 --- /dev/null +++ b/relayer/src/relay.rs @@ -0,0 +1,54 @@ +use anomaly::BoxError; + +use crate::chain::handle::ChainHandle; +use crate::channel::{Channel, ChannelConfig}; +use crate::connection::{Connection, ConnectionConfig}; +use crate::foreign_client::{ForeignClient, ForeignClientConfig}; + +pub(crate) const MAX_ITER: u32 = 10; + +pub fn channel_relay( + a_chain_handle: impl ChainHandle, + b_chain_handle: impl ChainHandle, + conn_cfg: ConnectionConfig, + chan_cfg: ChannelConfig, +) -> Result<(), BoxError> { + // Instantiate the foreign client on the source chain. + let client_on_a = ForeignClient::new( + a_chain_handle.clone(), + b_chain_handle.clone(), + ForeignClientConfig::new(conn_cfg.a_end().chain_id(), conn_cfg.a_end().client_id()), + )?; + + // Instantiate the foreign client on the destination chain. + let client_on_b = ForeignClient::new( + b_chain_handle.clone(), + a_chain_handle.clone(), + ForeignClientConfig::new(conn_cfg.b_end().chain_id(), conn_cfg.b_end().client_id()), + )?; + + // Setup the connection between the two chains + let connection = Connection::new( + a_chain_handle.clone(), + b_chain_handle.clone(), + client_on_a, + client_on_b, + conn_cfg, + )?; + + // Setup the channel over the connection + let _channel = Channel::new(a_chain_handle, b_chain_handle, connection, chan_cfg)?; + + // TODO: Re-enable `link` module in `relayer/src/lib.rs` + // let link = Link::new( + // a_chain_handle, + // b_chain_handle, + // client_on_src, // Actual dependecy + // channel, // Semantic dependecy + // LinkConfig::new(todo!(), todo!(), todo!()), + // )?; + + // link.run()?; + + Ok(()) +} diff --git a/relayer/src/tx.rs b/relayer/src/tx.rs deleted file mode 100644 index 210a35c8ac..0000000000 --- a/relayer/src/tx.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod channel; -pub mod client; -pub mod connection; diff --git a/relayer/src/tx/channel.rs b/relayer/src/tx/channel.rs deleted file mode 100644 index c9011528a0..0000000000 --- a/relayer/src/tx/channel.rs +++ /dev/null @@ -1,462 +0,0 @@ -use prost_types::Any; - -use ibc_proto::ibc::core::channel::v1::MsgChannelOpenAck as RawMsgChannelOpenAck; -use ibc_proto::ibc::core::channel::v1::MsgChannelOpenConfirm as RawMsgChannelOpenConfirm; -use ibc_proto::ibc::core::channel::v1::MsgChannelOpenInit as RawMsgChannelOpenInit; -use ibc_proto::ibc::core::channel::v1::MsgChannelOpenTry as RawMsgChannelOpenTry; - -use ibc::ics04_channel::channel::{ChannelEnd, Counterparty, Order, State}; -use ibc::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; -use ibc::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; -use ibc::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; -use ibc::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; -use ibc::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; -use ibc::tx_msg::Msg; -use ibc::Height as ICSHeight; - -use crate::chain::{handle::ChainHandle, runtime::ChainRuntime}; -use crate::config::ChainConfig; -use crate::error::{Error, Kind}; -use crate::tx::client::build_update_client; - -/// Enumeration of proof carrying ICS4 message, helper for relayer. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ChannelMsgType { - OpenTry, - OpenAck, - OpenConfirm, -} - -#[derive(Clone, Debug)] -pub struct ChannelOpenInitOptions { - pub dst_chain_config: ChainConfig, - pub src_chain_config: ChainConfig, - pub dst_connection_id: ConnectionId, - pub dst_port_id: PortId, - pub src_port_id: PortId, - pub dst_channel_id: ChannelId, - pub src_channel_id: Option, - pub ordering: Order, -} - -pub fn build_chan_init( - dst_chain: impl ChainHandle, - _src_chain: impl ChainHandle, - opts: &ChannelOpenInitOptions, -) -> Result, Error> { - // Check that the destination chain will accept the message, i.e. it does not have the channel - if dst_chain - .query_channel( - &opts.dst_port_id, - &opts.dst_channel_id, - ICSHeight::default(), - ) - .is_ok() - { - return Err(Kind::ChanOpenInit( - opts.dst_channel_id.clone(), - "channel already exist".into(), - ) - .into()); - } - - let signer = dst_chain - .get_signer() - .map_err(|e| Kind::KeyBase.context(e))?; - - let counterparty = Counterparty::new(opts.src_port_id.clone(), opts.src_channel_id.clone()); - - let channel = ChannelEnd::new( - State::Init, - opts.ordering, - counterparty, - vec![opts.dst_connection_id.clone()], - dst_chain.module_version(&opts.dst_port_id)?, - ); - - // Build the domain type message - let new_msg = MsgChannelOpenInit { - port_id: opts.dst_port_id.clone(), - channel_id: opts.dst_channel_id.clone(), - channel, - signer, - }; - - Ok(vec![new_msg.to_any::()]) -} - -pub fn build_chan_init_and_send(opts: &ChannelOpenInitOptions) -> Result { - // Get the source and destination chains. - let (src_chain, _) = ChainRuntime::spawn(opts.clone().src_chain_config)?; - let (dst_chain, _) = ChainRuntime::spawn(opts.clone().dst_chain_config)?; - - let new_msgs = build_chan_init(dst_chain.clone(), src_chain, opts)?; - - Ok(dst_chain.send_tx(new_msgs)?) -} - -#[derive(Clone, Debug)] -pub struct ChannelOpenOptions { - pub dst_chain_config: ChainConfig, - pub src_chain_config: ChainConfig, - pub dst_port_id: PortId, - pub src_port_id: PortId, - pub dst_channel_id: ChannelId, - pub src_channel_id: ChannelId, - pub dst_connection_id: ConnectionId, - pub ordering: Order, -} - -fn check_destination_channel_state( - channel_id: ChannelId, - existing_channel: ChannelEnd, - expected_channel: ChannelEnd, -) -> Result<(), Error> { - let good_connection_hops = - existing_channel.connection_hops() == expected_channel.connection_hops(); - - let good_state = - existing_channel.state().clone() as u32 <= expected_channel.state().clone() as u32; - - let good_channel_ids = existing_channel.counterparty().channel_id().is_none() - || existing_channel.counterparty().channel_id() - == expected_channel.counterparty().channel_id(); - - // TODO check versions - - if good_state && good_connection_hops && good_channel_ids { - Ok(()) - } else { - Err(Kind::ChanOpenTry( - channel_id, - "channel already exist in an incompatible state".into(), - ) - .into()) - } -} - -/// Retrieves the channel from destination and compares against the expected channel -/// built from the message type (`msg_type`) and options (`opts`). -/// If the expected and the destination channels are compatible, it returns the expected channel -fn validated_expected_channel( - dst_chain: impl ChainHandle, - _src_chain: impl ChainHandle, - msg_type: ChannelMsgType, - opts: &ChannelOpenOptions, -) -> Result { - // If there is a channel present on the destination chain, it should look like this: - let counterparty = Counterparty::new( - opts.src_port_id.clone(), - Option::from(opts.src_channel_id.clone()), - ); - - // The highest expected state, depends on the message type: - let highest_state = match msg_type { - ChannelMsgType::OpenTry => State::Init, - ChannelMsgType::OpenAck => State::TryOpen, - ChannelMsgType::OpenConfirm => State::TryOpen, - }; - - let dest_expected_channel = ChannelEnd::new( - highest_state, - opts.ordering, - counterparty, - vec![opts.dst_connection_id.clone()], - dst_chain.module_version(&opts.dst_port_id)?, - ); - - // Retrieve existing channel if any - let dest_channel = dst_chain.query_channel( - &opts.dst_port_id, - &opts.dst_channel_id, - ICSHeight::default(), - ); - - // Check if a connection is expected to exist on destination chain - if msg_type == ChannelMsgType::OpenTry { - // TODO - check typed Err, or make query_channel return Option - // It is ok if there is no channel for Try Tx - if dest_channel.is_err() { - return Ok(dest_expected_channel); - } - } else { - // A channel must exist on destination chain for Ack and Confirm Tx-es to succeed - if dest_channel.is_err() { - return Err(Kind::ChanOpenTry( - opts.src_channel_id.clone(), - "missing channel on source chain".to_string(), - ) - .into()); - } - } - - check_destination_channel_state( - opts.dst_channel_id.clone(), - dest_channel?, - dest_expected_channel.clone(), - )?; - - Ok(dest_expected_channel) -} - -pub fn build_chan_try( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - opts: &ChannelOpenOptions, -) -> Result, Error> { - // Check that the destination chain will accept the message, i.e. it does not have the channel - let _dest_expected_channel = validated_expected_channel( - dst_chain.clone(), - src_chain.clone(), - ChannelMsgType::OpenTry, - opts, - ) - .map_err(|e| { - Kind::ChanOpenTry( - opts.src_channel_id.clone(), - "try options inconsistent with existing channel on destination chain".to_string(), - ) - .context(e) - })?; - - let src_channel = src_chain - .query_channel( - &opts.src_port_id, - &opts.src_channel_id, - ICSHeight::default(), - ) - .map_err(|e| { - Kind::ChanOpenTry( - opts.dst_channel_id.clone(), - "channel does not exist on source".into(), - ) - .context(e) - })?; - - // Retrieve the connection - let dest_connection = - dst_chain.query_connection(&opts.dst_connection_id.clone(), ICSHeight::default())?; - - let ics_target_height = src_chain.query_latest_height()?; - - // Build message to update client on destination - let mut msgs = build_update_client( - dst_chain.clone(), - src_chain.clone(), - dest_connection.client_id().clone(), - ics_target_height, - )?; - - let counterparty = - Counterparty::new(opts.src_port_id.clone(), Some(opts.src_channel_id.clone())); - - let channel = ChannelEnd::new( - State::Init, - opts.ordering, - counterparty, - vec![opts.dst_connection_id.clone()], - dst_chain.module_version(&opts.dst_port_id)?, - ); - - // Get signer - let signer = dst_chain - .get_signer() - .map_err(|e| Kind::KeyBase.context(e))?; - - // Build the domain type message - let new_msg = MsgChannelOpenTry { - port_id: opts.dst_port_id.clone(), - channel_id: opts.dst_channel_id.clone(), - counterparty_chosen_channel_id: src_channel.counterparty().channel_id, - channel, - counterparty_version: src_chain.module_version(&opts.src_port_id)?, - proofs: src_chain.build_channel_proofs( - &opts.src_port_id, - &opts.src_channel_id, - ics_target_height, - )?, - signer, - }; - - let mut new_msgs = vec![new_msg.to_any::()]; - - msgs.append(&mut new_msgs); - - Ok(msgs) -} - -pub fn build_chan_try_and_send(opts: &ChannelOpenOptions) -> Result { - // Get the source and destination chains. - let (src_chain, _) = ChainRuntime::spawn(opts.clone().src_chain_config)?; - let (dst_chain, _) = ChainRuntime::spawn(opts.clone().dst_chain_config)?; - - let new_msgs = build_chan_try(dst_chain.clone(), src_chain, opts)?; - - Ok(dst_chain.send_tx(new_msgs)?) -} - -pub fn build_chan_ack( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - opts: &ChannelOpenOptions, -) -> Result, Error> { - // Check that the destination chain will accept the message - let _dest_expected_channel = validated_expected_channel( - dst_chain.clone(), - src_chain.clone(), - ChannelMsgType::OpenAck, - opts, - ) - .map_err(|e| { - Kind::ChanOpenAck( - opts.src_channel_id.clone(), - "ack options inconsistent with existing channel on destination chain".to_string(), - ) - .context(e) - })?; - - let _src_channel = src_chain - .query_channel( - &opts.src_port_id, - &opts.src_channel_id, - ICSHeight::default(), - ) - .map_err(|e| { - Kind::ChanOpenAck( - opts.dst_channel_id.clone(), - "channel does not exist on source".into(), - ) - .context(e) - })?; - - // Retrieve the connection - let dest_connection = - dst_chain.query_connection(&opts.dst_connection_id.clone(), ICSHeight::default())?; - - let ics_target_height = src_chain.query_latest_height()?; - - // Build message to update client on destination - let mut msgs = build_update_client( - dst_chain.clone(), - src_chain.clone(), - dest_connection.client_id().clone(), - ics_target_height, - )?; - - // Get signer - let signer = dst_chain - .get_signer() - .map_err(|e| Kind::KeyBase.context(e))?; - - // Build the domain type message - let new_msg = MsgChannelOpenAck { - port_id: opts.dst_port_id.clone(), - channel_id: opts.dst_channel_id.clone(), - counterparty_channel_id: opts.src_channel_id.clone(), - counterparty_version: src_chain.module_version(&opts.dst_port_id)?, - proofs: src_chain.build_channel_proofs( - &opts.src_port_id, - &opts.src_channel_id, - ics_target_height, - )?, - signer, - }; - - let mut new_msgs = vec![new_msg.to_any::()]; - - msgs.append(&mut new_msgs); - - Ok(msgs) -} - -pub fn build_chan_ack_and_send(opts: &ChannelOpenOptions) -> Result { - // Get the source and destination chains. - let (src_chain, _) = ChainRuntime::spawn(opts.clone().src_chain_config)?; - let (dst_chain, _) = ChainRuntime::spawn(opts.clone().dst_chain_config)?; - - let new_msgs = build_chan_ack(dst_chain.clone(), src_chain, opts)?; - - Ok(dst_chain.send_tx(new_msgs)?) -} - -pub fn build_chan_confirm( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - opts: &ChannelOpenOptions, -) -> Result, Error> { - // Check that the destination chain will accept the message - let _dest_expected_channel = validated_expected_channel( - dst_chain.clone(), - src_chain.clone(), - ChannelMsgType::OpenConfirm, - opts, - ) - .map_err(|e| { - Kind::ChanOpenConfirm( - opts.src_channel_id.clone(), - "confirm options inconsistent with existing channel on destination chain".to_string(), - ) - .context(e) - })?; - - let _src_channel = src_chain - .query_channel( - &opts.src_port_id, - &opts.src_channel_id, - ICSHeight::default(), - ) - .map_err(|e| { - Kind::ChanOpenConfirm( - opts.dst_channel_id.clone(), - "channel does not exist on source".into(), - ) - .context(e) - })?; - - // Retrieve the connection - let dest_connection = - dst_chain.query_connection(&opts.dst_connection_id.clone(), ICSHeight::default())?; - - let ics_target_height = src_chain.query_latest_height()?; - - // Build message to update client on destination - let mut msgs = build_update_client( - dst_chain.clone(), - src_chain.clone(), - dest_connection.client_id().clone(), - ics_target_height, - )?; - - // Get signer - let signer = dst_chain - .get_signer() - .map_err(|e| Kind::KeyBase.context(e))?; - - // Build the domain type message - let new_msg = MsgChannelOpenConfirm { - port_id: opts.dst_port_id.clone(), - channel_id: opts.dst_channel_id.clone(), - proofs: src_chain.build_channel_proofs( - &opts.src_port_id, - &opts.src_channel_id, - ics_target_height, - )?, - signer, - }; - - let mut new_msgs = vec![new_msg.to_any::()]; - - msgs.append(&mut new_msgs); - - Ok(msgs) -} - -pub fn build_chan_confirm_and_send(opts: &ChannelOpenOptions) -> Result { - // Get the source and destination chains. - let (src_chain, _) = ChainRuntime::spawn(opts.clone().src_chain_config)?; - let (dst_chain, _) = ChainRuntime::spawn(opts.clone().dst_chain_config)?; - - let new_msgs = build_chan_confirm(dst_chain.clone(), src_chain, opts)?; - - Ok(dst_chain.send_tx(new_msgs)?) -} diff --git a/relayer/src/tx/client.rs b/relayer/src/tx/client.rs deleted file mode 100644 index 545d402eaa..0000000000 --- a/relayer/src/tx/client.rs +++ /dev/null @@ -1,110 +0,0 @@ -use prost_types::Any; - -use ibc_proto::ibc::core::client::v1::MsgCreateClient as RawMsgCreateClient; -use ibc_proto::ibc::core::client::v1::MsgUpdateClient as RawMsgUpdateClient; - -use ibc::ics02_client::header::Header; -use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient; -use ibc::ics02_client::msgs::update_client::MsgUpdateAnyClient; -use ibc::ics02_client::state::ClientState; -use ibc::ics02_client::state::ConsensusState; - -use ibc::ics24_host::identifier::ClientId; - -use ibc::tx_msg::Msg; -use ibc::Height; - -use crate::chain::{handle::ChainHandle, runtime::ChainRuntime}; -use crate::config::ChainConfig; -use crate::error::{Error, Kind}; - -#[derive(Clone, Debug)] -pub struct ClientOptions { - pub dst_client_id: ClientId, - pub dst_chain_config: ChainConfig, - pub src_chain_config: ChainConfig, -} - -pub fn build_create_client( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - dst_client_id: ClientId, -) -> Result { - // Verify that the client has not been created already, i.e the destination chain does not - // have a state for this client. - let client_state = dst_chain.query_client_state(&dst_client_id, Height::default()); - if client_state.is_ok() { - return Err(Into::::into(Kind::CreateClient( - dst_client_id, - "client already exists".into(), - ))); - } - - // Get signer - let signer = dst_chain - .get_signer() - .map_err(|e| Kind::KeyBase.context(e))?; - - // Build client create message with the data from source chain at latest height. - let latest_height = src_chain.query_latest_height()?; - Ok(MsgCreateAnyClient::new( - dst_client_id, - src_chain.build_client_state(latest_height)?.wrap_any(), - src_chain.build_consensus_state(latest_height)?.wrap_any(), - signer, - ) - .map_err(|e| { - Kind::MessageTransaction("failed to build the create client message".into()).context(e) - })?) -} - -pub fn build_create_client_and_send(opts: ClientOptions) -> Result { - // Initialize the source and destination runtimes and light clients - let (src_chain, _) = ChainRuntime::spawn(opts.src_chain_config.clone())?; - let (dst_chain, _) = ChainRuntime::spawn(opts.dst_chain_config.clone())?; - - let new_msg = build_create_client(dst_chain.clone(), src_chain, opts.dst_client_id)?; - - Ok(dst_chain.send_tx(vec![new_msg.to_any::()])?) -} - -pub fn build_update_client( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - dst_client_id: ClientId, - target_height: Height, -) -> Result, Error> { - // Get the latest trusted height from the client state on destination. - let trusted_height = dst_chain - .query_client_state(&dst_client_id, Height::default())? - .latest_height(); - - let header = src_chain - .build_header(trusted_height, target_height)? - .wrap_any(); - - let signer = dst_chain.get_signer()?; - let new_msg = MsgUpdateAnyClient { - client_id: dst_client_id, - header, - signer, - }; - - Ok(vec![new_msg.to_any::()]) -} - -pub fn build_update_client_and_send(opts: ClientOptions) -> Result { - // Initialize the source and destination runtimes and light clients - let (src_chain, _) = ChainRuntime::spawn(opts.src_chain_config.clone())?; - let (dst_chain, _) = ChainRuntime::spawn(opts.dst_chain_config.clone())?; - - let target_height = src_chain.query_latest_height()?; - let new_msgs = build_update_client( - dst_chain.clone(), - src_chain, - opts.dst_client_id, - target_height, - )?; - - Ok(dst_chain.send_tx(new_msgs)?) -} diff --git a/relayer/src/tx/connection.rs b/relayer/src/tx/connection.rs deleted file mode 100644 index c267445379..0000000000 --- a/relayer/src/tx/connection.rs +++ /dev/null @@ -1,459 +0,0 @@ -use prost_types::Any; - -use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenAck as RawMsgConnectionOpenAck; -use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm; -use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenInit as RawMsgConnectionOpenInit; -use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as RawMsgConnectionOpenTry; - -use ibc::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; -use ibc::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; -use ibc::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; -use ibc::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; -use ibc::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use ibc::ics24_host::identifier::{ClientId, ConnectionId}; -use ibc::tx_msg::Msg; -use ibc::Height as ICSHeight; - -use crate::chain::{handle::ChainHandle, runtime::ChainRuntime}; -use crate::config::ChainConfig; -use crate::error::{Error, Kind}; -use crate::tx::client::build_update_client; - -#[derive(Clone, Debug)] -pub struct ConnectionOpenInitOptions { - pub dst_chain_config: ChainConfig, - pub src_chain_config: ChainConfig, - pub dst_client_id: ClientId, - pub src_client_id: ClientId, - pub dst_connection_id: ConnectionId, - pub src_connection_id: Option, -} - -/// Enumeration of proof carrying ICS3 message, helper for relayer. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ConnectionMsgType { - OpenTry, - OpenAck, - OpenConfirm, -} - -pub fn build_conn_init( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - opts: &ConnectionOpenInitOptions, -) -> Result, Error> { - // Check that the destination chain will accept the message, i.e. it does not have the connection - if dst_chain - .query_connection(&opts.dst_connection_id, ICSHeight::default()) - .is_ok() - { - return Err(Kind::ConnOpenInit( - opts.dst_connection_id.clone(), - "connection already exist".into(), - ) - .into()); - } - - // Get signer - let signer = dst_chain - .get_signer() - .map_err(|e| Kind::KeyBase.context(e))?; - - let prefix = src_chain.query_commitment_prefix()?; - - let counterparty = Counterparty::new( - opts.src_client_id.clone(), - opts.src_connection_id.clone(), - prefix, - ); - - // Build the domain type message - let new_msg = MsgConnectionOpenInit { - client_id: opts.dst_client_id.clone(), - connection_id: opts.dst_connection_id.clone(), - counterparty, - version: dst_chain.query_compatible_versions()?[0].clone(), - signer, - }; - - Ok(vec![new_msg.to_any::()]) -} - -pub fn build_conn_init_and_send(opts: &ConnectionOpenInitOptions) -> Result { - // Initialize the source and destination runtimes and light clients - let (src_chain, _) = ChainRuntime::spawn(opts.src_chain_config.clone())?; - let (dst_chain, _) = ChainRuntime::spawn(opts.dst_chain_config.clone())?; - - let dst_msgs = build_conn_init(dst_chain.clone(), src_chain, opts)?; - - Ok(dst_chain.send_tx(dst_msgs)?) -} - -#[derive(Clone, Debug)] -pub struct ConnectionOpenOptions { - pub dst_chain_config: ChainConfig, - pub src_chain_config: ChainConfig, - pub dst_client_id: ClientId, - pub src_client_id: ClientId, - pub dst_connection_id: ConnectionId, - pub src_connection_id: ConnectionId, -} - -fn check_destination_connection_state( - connection_id: ConnectionId, - existing_connection: ConnectionEnd, - expected_connection: ConnectionEnd, -) -> Result<(), Error> { - let good_client_ids = existing_connection.client_id() == expected_connection.client_id() - && existing_connection.counterparty().client_id() - == expected_connection.counterparty().client_id(); - - let good_state = - existing_connection.state().clone() as u32 <= expected_connection.state().clone() as u32; - - let good_connection_ids = existing_connection.counterparty().connection_id().is_none() - || existing_connection.counterparty().connection_id() - == expected_connection.counterparty().connection_id(); - - // TODO check versions and store prefix - - if good_state && good_client_ids && good_connection_ids { - Ok(()) - } else { - Err(Kind::ConnOpenTry( - connection_id, - "connection already exist in an incompatible state".into(), - ) - .into()) - } -} - -/// Retrieves the connection from destination and compares against the expected connection -/// built from the message type (`msg_type`) and options (`opts`). -/// If the expected and the destination connections are compatible, it returns the expected connection -fn validated_expected_connection( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - msg_type: ConnectionMsgType, - opts: &ConnectionOpenOptions, -) -> Result { - // If there is a connection present on the destination chain, it should look like this: - let counterparty = Counterparty::new( - opts.src_client_id.clone(), - Option::from(opts.src_connection_id.clone()), - src_chain.query_commitment_prefix()?, - ); - - // The highest expected state, depends on the message type: - let highest_state = match msg_type { - ConnectionMsgType::OpenTry => State::Init, - ConnectionMsgType::OpenAck => State::TryOpen, - ConnectionMsgType::OpenConfirm => State::TryOpen, - }; - - let dst_expected_connection = ConnectionEnd::new( - highest_state, - opts.dst_client_id.clone(), - counterparty, - src_chain.query_compatible_versions()?, - ) - .unwrap(); - - // Retrieve existing connection if any - let dst_connection = - dst_chain.query_connection(&opts.dst_connection_id.clone(), ICSHeight::default()); - - // Check if a connection is expected to exist on destination chain - if msg_type == ConnectionMsgType::OpenTry { - // TODO - check typed Err, or make query_connection return Option - // It is ok if there is no connection for Try Tx - if dst_connection.is_err() { - return Ok(dst_expected_connection); - } - } else { - // A connection must exist on destination chain for Ack and Confirm Tx-es to succeed - if dst_connection.is_err() { - return Err(Kind::ConnOpenTry( - opts.src_connection_id.clone(), - "missing connection on source chain".to_string(), - ) - .into()); - } - } - - check_destination_connection_state( - opts.dst_connection_id.clone(), - dst_connection?, - dst_expected_connection.clone(), - )?; - - Ok(dst_expected_connection) -} - -/// Attempts to build a MsgConnOpenTry. -pub fn build_conn_try( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - opts: &ConnectionOpenOptions, -) -> Result, Error> { - let dst_expected_connection = validated_expected_connection( - dst_chain.clone(), - src_chain.clone(), - ConnectionMsgType::OpenTry, - opts, - ) - .map_err(|e| { - Kind::ConnOpenTry( - opts.src_connection_id.clone(), - "try options inconsistent with existing connection on destination chain".to_string(), - ) - .context(e) - })?; - - let src_connection = src_chain - .query_connection(&opts.src_connection_id.clone(), ICSHeight::default()) - .map_err(|e| { - Kind::ConnOpenTry( - opts.src_connection_id.clone(), - "missing connection on source chain".to_string(), - ) - .context(e) - })?; - - // TODO - check that the src connection is consistent with the try options - - // TODO - Build add send the message(s) for updating client on source (when we don't need the key seed anymore) - // TODO - add check if it is required - // let signer = dst_chain - // .get_signer() - // .map_err(|e| Kind::KeyBase.context(e))?; - // build_update_client_and_send(ClientOptions { - // dst_client_id: opts.src_client_id.clone(), - // dst_chain_config: src_chain.config().clone(), - // src_chain_config: dst_chain.config().clone(), - // })?; - - // Get signer - let signer = dst_chain - .get_signer() - .map_err(|e| Kind::KeyBase.context(e))?; - - // Build message(s) for updating client on destination - let ics_target_height = src_chain.query_latest_height()?; - - let mut msgs = build_update_client( - dst_chain, - src_chain.clone(), - opts.dst_client_id.clone(), - ics_target_height, - )?; - - let (client_state, proofs) = src_chain.build_connection_proofs_and_client_state( - ConnectionMsgType::OpenTry, - &opts.src_connection_id.clone(), - &opts.src_client_id, - ics_target_height, - )?; - - let counterparty_versions = if src_connection.versions().is_empty() { - src_chain.query_compatible_versions()? - } else { - src_connection.versions() - }; - - let new_msg = MsgConnectionOpenTry { - connection_id: opts.dst_connection_id.clone(), - client_id: opts.dst_client_id.clone(), - client_state, - counterparty_chosen_connection_id: src_connection.counterparty().connection_id().cloned(), - counterparty: dst_expected_connection.counterparty(), - counterparty_versions, - proofs, - signer, - }; - - let mut new_msgs = vec![new_msg.to_any::()]; - - msgs.append(&mut new_msgs); - - Ok(msgs) -} - -pub fn build_conn_try_and_send(opts: ConnectionOpenOptions) -> Result { - // Initialize the source and destination chain runtimes - let (src_chain, _) = ChainRuntime::spawn(opts.src_chain_config.clone())?; - let (dst_chain, _) = ChainRuntime::spawn(opts.dst_chain_config.clone())?; - - let dst_msgs = build_conn_try(dst_chain.clone(), src_chain, &opts)?; - - Ok(dst_chain.send_tx(dst_msgs)?) -} - -/// Attempts to build a MsgConnOpenAck. -pub fn build_conn_ack( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - opts: &ConnectionOpenOptions, -) -> Result, Error> { - let _expected_dst_connection = validated_expected_connection( - dst_chain.clone(), - src_chain.clone(), - ConnectionMsgType::OpenAck, - opts, - ) - .map_err(|e| { - Kind::ConnOpenAck( - opts.src_connection_id.clone(), - "ack options inconsistent with existing connection on destination chain".to_string(), - ) - .context(e) - })?; - - let src_connection = src_chain - .query_connection(&opts.src_connection_id.clone(), ICSHeight::default()) - .map_err(|e| { - Kind::ConnOpenAck( - opts.src_connection_id.clone(), - "missing connection on source chain".to_string(), - ) - .context(e) - })?; - - // TODO - check that the src connection is consistent with the ack options - - // TODO - Build add **send** the message(s) for updating client on source (when we don't need the key seed anymore) - // TODO - add check if it is required - // let signer = dst_chain - // .get_signer() - // .map_err(|e| Kind::KeyBase.context(e))?; - // build_update_client_and_send(ClientOptions { - // dst_client_id: opts.src_client_id.clone(), - // dst_chain_config: src_chain.config().clone(), - // src_chain_config: dst_chain.config().clone(), - // })?; - - // Get signer - let signer = dst_chain - .get_signer() - .map_err(|e| Kind::KeyBase.context(e))?; - - // Build message(s) for updating client on destination - let ics_target_height = src_chain.query_latest_height()?; - - let mut msgs = build_update_client( - dst_chain, - src_chain.clone(), - opts.dst_client_id.clone(), - ics_target_height, - )?; - - let (client_state, proofs) = src_chain.build_connection_proofs_and_client_state( - ConnectionMsgType::OpenAck, - &opts.src_connection_id.clone(), - &opts.src_client_id, - ics_target_height, - )?; - - let new_msg = MsgConnectionOpenAck { - connection_id: opts.dst_connection_id.clone(), - counterparty_connection_id: Option::from(opts.src_connection_id.clone()), - client_state, - proofs, - version: src_connection.versions()[0].clone(), - signer, - }; - - let mut new_msgs = vec![new_msg.to_any::()]; - - msgs.append(&mut new_msgs); - - Ok(msgs) -} - -pub fn build_conn_ack_and_send(opts: ConnectionOpenOptions) -> Result { - // Initialize the source and destination chain runtimes - let (src_chain, _) = ChainRuntime::spawn(opts.src_chain_config.clone())?; - let (dst_chain, _) = ChainRuntime::spawn(opts.dst_chain_config.clone())?; - - let dst_msgs = build_conn_ack(dst_chain.clone(), src_chain, &opts)?; - - Ok(dst_chain.send_tx(dst_msgs)?) -} - -/// Attempts to build a MsgConnOpenConfirm. -pub fn build_conn_confirm( - dst_chain: impl ChainHandle, - src_chain: impl ChainHandle, - opts: &ConnectionOpenOptions, -) -> Result, Error> { - let _expected_dst_connection = validated_expected_connection( - dst_chain.clone(), - src_chain.clone(), - ConnectionMsgType::OpenAck, - opts, - ) - .map_err(|e| { - Kind::ConnOpenConfirm( - opts.src_connection_id.clone(), - "confirm options inconsistent with existing connection on destination chain" - .to_string(), - ) - .context(e) - })?; - - let _src_connection = src_chain - .query_connection(&opts.src_connection_id.clone(), ICSHeight::default()) - .map_err(|e| { - Kind::ConnOpenAck( - opts.src_connection_id.clone(), - "missing connection on source chain".to_string(), - ) - .context(e) - })?; - - // TODO - check that the src connection is consistent with the confirm options - - // Get signer - let signer = dst_chain - .get_signer() - .map_err(|e| Kind::KeyBase.context(e))?; - - // Build message(s) for updating client on destination - let ics_target_height = src_chain.query_latest_height()?; - - let mut msgs = build_update_client( - dst_chain, - src_chain.clone(), - opts.dst_client_id.clone(), - ics_target_height, - )?; - - let (_, proofs) = src_chain.build_connection_proofs_and_client_state( - ConnectionMsgType::OpenConfirm, - &opts.src_connection_id.clone(), - &opts.src_client_id, - ics_target_height, - )?; - - let new_msg = MsgConnectionOpenConfirm { - connection_id: opts.dst_connection_id.clone(), - proofs, - signer, - }; - - let mut new_msgs = vec![new_msg.to_any::()]; - - msgs.append(&mut new_msgs); - - Ok(msgs) -} - -pub fn build_conn_confirm_and_send(opts: ConnectionOpenOptions) -> Result { - // Initialize the source and destination runtimes and light clients - let (src_chain, _) = ChainRuntime::spawn(opts.src_chain_config.clone())?; - let (dst_chain, _) = ChainRuntime::spawn(opts.dst_chain_config.clone())?; - - let dst_msgs = build_conn_confirm(dst_chain.clone(), src_chain, &opts)?; - - Ok(dst_chain.send_tx(dst_msgs)?) -} diff --git a/relayer/tests/config/fixtures/relayer_conf_example.toml b/relayer/tests/config/fixtures/relayer_conf_example.toml index 265c0a1a68..3907ad51a1 100644 --- a/relayer/tests/config/fixtures/relayer_conf_example.toml +++ b/relayer/tests/config/fixtures/relayer_conf_example.toml @@ -38,30 +38,30 @@ numerator = '1' denominator = '3' [[connections]] -[connections.src] +[connections.a_end] chain_id = 'chain_A' client_id = 'clB1' connection_id = 'connAtoB' -[connections.dest] +[connections.b_end] chain_id = 'chain_B' client_id = 'clA1' connection_id = 'connBtoA' [[connections.paths]] -src_port = 'portA1' -dest_port = 'portB1' +a_port = 'transfer' +b_port = 'transfer' direction = 'unidirectional' [[connections.paths]] -src_port = 'portA2' -dest_port = 'portB2' +a_port = 'transfer' +b_port = 'transfer' direction = 'bidirectional' [[connections.paths]] -src_port = 'portA3' -dest_port = 'portB3' -src_channel = 'chan3onA' -dest_channel = 'chan3onB' +a_port = 'transfer' +b_port = 'transfer' +a_channel = 'chan3onA' +b_channel = 'chan3onB' direction = 'bidirectional' diff --git a/relayer/tests/config/fixtures/simple_config.toml b/relayer/tests/config/fixtures/simple_config.toml index 7c3e95929b..4a9594ec66 100644 --- a/relayer/tests/config/fixtures/simple_config.toml +++ b/relayer/tests/config/fixtures/simple_config.toml @@ -40,12 +40,12 @@ strategy = "naive" [[connections]] -[connections.src] +[connections.a_end] chain_id = "ibc1" client_id = "ibconeclient" connection_id = "ibconeconnection" -[connections.dest] +[connections.b_end] chain_id = "ibc0" client_id = "ibczeroclientt" connection_id = "ibczeroconnection"