From 62d405abb2b86b4fd7e911272a71fab3027f8bb0 Mon Sep 17 00:00:00 2001 From: Anca Zamfir Date: Thu, 12 Nov 2020 15:54:48 +0100 Subject: [PATCH] Implement relaying of ConnOpenAck and ConnOpenConfirm (#397) --- CHANGELOG.md | 8 +- modules/src/ics03_connection/connection.rs | 2 +- modules/src/ics03_connection/msgs.rs | 9 - .../ics03_connection/msgs/conn_open_ack.rs | 16 +- .../msgs/conn_open_confirm.rs | 10 +- relayer-cli/README.md | 2 +- relayer-cli/src/commands/tx.rs | 8 + relayer-cli/src/commands/tx/connection.rs | 172 +++++++++- relayer/src/chain.rs | 35 +- relayer/src/chain/cosmos.rs | 3 - relayer/src/error.rs | 8 + relayer/src/tx/client.rs | 8 +- relayer/src/tx/connection.rs | 304 +++++++++++++++--- 13 files changed, 499 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae8e6cafa3..d809dee177 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,10 +12,13 @@ Special thanks to external contributors for this release: @CharlyCst ([#347]). - [modules] - Implement flexible connection id selection ([#332]) - ICS 4 Domain Types for channel handshakes ([#315]) -- [relayer] Implement `query_header_at_height` via plain RPC queries (no light client verification) ([#336]) +- [relayer] + - Implement `query_header_at_height` via plain RPC queries (no light client verification) ([#336]) + - Implement the relayer logic for connection handshake message ([#358], [#359], [#360]) - [relayer-cli] - Merge light clients config in relayer config and add commands to add/remove light clients ([#348]) - CLI for client update message ([#277]) + - Implement the relayer CLI for connection handshake message ([#358], [#359], [#360]) - [proto-compiler] - Refactor and allow specifying a commit at which the Cosmos SDK should be checked out ([#366]) - Add a `--tag` option to the `clone-sdk` command to check out a tag instead of a commit ([#369]) @@ -27,6 +30,9 @@ Special thanks to external contributors for this release: @CharlyCst ([#347]). [#335]: https://github.com/informalsystems/ibc-rs/pulls/335 [#336]: https://github.com/informalsystems/ibc-rs/issues/336 [#348]: https://github.com/informalsystems/ibc-rs/pulls/348 +[#358]: https://github.com/informalsystems/ibc-rs/issues/358 +[#358]: https://github.com/informalsystems/ibc-rs/issues/359 +[#358]: https://github.com/informalsystems/ibc-rs/issues/360 [#366]: https://github.com/informalsystems/ibc-rs/issues/366 [#368]: https://github.com/informalsystems/ibc-rs/issues/368 [#369]: https://github.com/informalsystems/ibc-rs/pulls/369 diff --git a/modules/src/ics03_connection/connection.rs b/modules/src/ics03_connection/connection.rs index af10f23280..c4e8d19c96 100644 --- a/modules/src/ics03_connection/connection.rs +++ b/modules/src/ics03_connection/connection.rs @@ -197,7 +197,7 @@ impl Counterparty { } } -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum State { Init = 1, TryOpen = 2, diff --git a/modules/src/ics03_connection/msgs.rs b/modules/src/ics03_connection/msgs.rs index 68a4ca5158..43eda92b05 100644 --- a/modules/src/ics03_connection/msgs.rs +++ b/modules/src/ics03_connection/msgs.rs @@ -22,15 +22,6 @@ pub mod conn_open_confirm; pub mod conn_open_init; pub mod conn_open_try; -/// Enumeration of all possible message types that the ICS3 protocol processes. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ConnectionMsgType { - OpenInit, - OpenTry, - OpenAck, - OpenConfirm, -} - /// Enumeration of all possible messages that the ICS3 protocol processes. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ConnectionMsg { diff --git a/modules/src/ics03_connection/msgs/conn_open_ack.rs b/modules/src/ics03_connection/msgs/conn_open_ack.rs index 62ac0c300c..ab79ed3ede 100644 --- a/modules/src/ics03_connection/msgs/conn_open_ack.rs +++ b/modules/src/ics03_connection/msgs/conn_open_ack.rs @@ -22,12 +22,12 @@ pub const TYPE_MSG_CONNECTION_OPEN_ACK: &str = "connection_open_ack"; /// Message definition `MsgConnectionOpenAck` (i.e., `ConnOpenAck` datagram). #[derive(Clone, Debug, PartialEq, Eq)] pub struct MsgConnectionOpenAck { - connection_id: ConnectionId, - counterparty_connection_id: Option, - client_state: Option, - proofs: Proofs, - version: String, - signer: AccountId, + pub connection_id: ConnectionId, + pub counterparty_connection_id: Option, + pub client_state: Option, + pub proofs: Proofs, + pub version: String, + pub signer: AccountId, } impl MsgConnectionOpenAck { @@ -84,6 +84,10 @@ impl Msg for MsgConnectionOpenAck { fn get_signers(&self) -> Vec { vec![self.signer] } + + fn type_url(&self) -> String { + "/ibc.core.connection.v1.MsgConnectionOpenAck".to_string() + } } impl DomainType for MsgConnectionOpenAck {} diff --git a/modules/src/ics03_connection/msgs/conn_open_confirm.rs b/modules/src/ics03_connection/msgs/conn_open_confirm.rs index 88203445b8..19eab21213 100644 --- a/modules/src/ics03_connection/msgs/conn_open_confirm.rs +++ b/modules/src/ics03_connection/msgs/conn_open_confirm.rs @@ -18,9 +18,9 @@ pub const TYPE_MSG_CONNECTION_OPEN_CONFIRM: &str = "connection_open_confirm"; /// #[derive(Clone, Debug, PartialEq, Eq)] pub struct MsgConnectionOpenConfirm { - connection_id: ConnectionId, - proofs: Proofs, - signer: AccountId, + pub connection_id: ConnectionId, + pub proofs: Proofs, + pub signer: AccountId, } impl MsgConnectionOpenConfirm { @@ -53,6 +53,10 @@ impl Msg for MsgConnectionOpenConfirm { fn get_signers(&self) -> Vec { vec![self.signer] } + + fn type_url(&self) -> String { + "/ibc.core.connection.v1.MsgConnectionOpenConfirm".to_string() + } } impl DomainType for MsgConnectionOpenConfirm {} diff --git a/relayer-cli/README.md b/relayer-cli/README.md index 7f9690ab5f..72d0a5e302 100644 --- a/relayer-cli/README.md +++ b/relayer-cli/README.md @@ -32,7 +32,7 @@ As shown above the tx commands currently require specifying a seed file: #### Steps to testing the transactions: * Start two chains using the `dev-env` script from the [ovrclk/relayer](https://github.com/ovrclk/relayer) (make sure to checkout stargate-4 version) -* After you run the script, the Go relayer will create a `data` folder for the chains. Open the key seed file `./data/ibc1/key_seed.json` for chain `ibc-1` and look for the account value +* After you run the script, the Go relayer will create a `data` folder for the chains. Copy the key seed file `./data/ibc1/key_seed.json` for chain `ibc-1` to a convenient location. { diff --git a/relayer-cli/src/commands/tx.rs b/relayer-cli/src/commands/tx.rs index c347d172c3..376ddd687e 100644 --- a/relayer-cli/src/commands/tx.rs +++ b/relayer-cli/src/commands/tx.rs @@ -39,4 +39,12 @@ pub enum TxRawCommands { /// The `tx raw conn-try` subcommand #[options(help = "tx raw conn-try")] ConnTry(connection::TxRawConnTryCmd), + + /// The `tx raw conn-ack` subcommand + #[options(help = "tx raw conn-ack")] + ConnAck(connection::TxRawConnAckCmd), + + /// The `tx raw conn-confirm` subcommand + #[options(help = "tx raw conn-confirm")] + ConnConfirm(connection::TxRawConnConfirmCmd), } diff --git a/relayer-cli/src/commands/tx/connection.rs b/relayer-cli/src/commands/tx/connection.rs index 61c3a4628d..c889960988 100644 --- a/relayer-cli/src/commands/tx/connection.rs +++ b/relayer-cli/src/commands/tx/connection.rs @@ -6,8 +6,8 @@ use ibc::ics24_host::identifier::{ClientId, ConnectionId}; use relayer::config::Config; use relayer::tx::connection::{ - build_conn_init_and_send, build_conn_try_and_send, ConnectionOpenInitOptions, - ConnectionOpenTryOptions, + build_conn_ack_and_send, build_conn_confirm_and_send, build_conn_init_and_send, + build_conn_try_and_send, ConnectionOpenInitOptions, ConnectionOpenOptions, }; use crate::error::{Error, Kind}; @@ -122,7 +122,7 @@ pub struct TxRawConnTryCmd { } impl TxRawConnTryCmd { - fn validate_options(&self, config: &Config) -> Result { + fn validate_options(&self, config: &Config) -> Result { let dest_chain_config = config .chains .iter() @@ -139,7 +139,7 @@ impl TxRawConnTryCmd { anomaly::Context::new("invalid signer seed file", Some(e.into())).to_string() })?; - let opts = ConnectionOpenTryOptions { + let opts = ConnectionOpenOptions { src_chain_config: src_chain_config.clone(), dest_chain_config: dest_chain_config.clone(), src_client_id: self.src_client_id.clone(), @@ -175,3 +175,167 @@ impl Runnable for TxRawConnTryCmd { } } } + +#[derive(Clone, Command, Debug, Options)] +pub struct TxRawConnAckCmd { + #[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 client")] + dest_client_id: ClientId, + + #[options(free, help = "identifier of the source client")] + src_client_id: ClientId, + + #[options(free, help = "identifier of the destination connection")] + dest_connection_id: ConnectionId, + + #[options(free, help = "identifier of the source connection")] + src_connection_id: ConnectionId, + + #[options( + help = "json key file for the signer, must include mnemonic", + short = "k" + )] + seed_file: String, +} + +impl TxRawConnAckCmd { + 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 signer_seed = std::fs::read_to_string(&self.seed_file).map_err(|e| { + anomaly::Context::new("invalid signer seed file", Some(e.into())).to_string() + })?; + + let opts = ConnectionOpenOptions { + src_chain_config: src_chain_config.clone(), + dest_chain_config: dest_chain_config.clone(), + src_client_id: self.src_client_id.clone(), + dest_client_id: self.dest_client_id.clone(), + src_connection_id: self.src_connection_id.clone(), + dest_connection_id: self.dest_connection_id.clone(), + signer_seed, + }; + + 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")] + dest_chain_id: String, + + #[options(free, help = "identifier of the source chain")] + src_chain_id: String, + + #[options(free, help = "identifier of the destination client")] + dest_client_id: ClientId, + + #[options(free, help = "identifier of the source client")] + src_client_id: ClientId, + + #[options(free, help = "identifier of the destination connection")] + dest_connection_id: ConnectionId, + + #[options(free, help = "identifier of the source connection")] + src_connection_id: ConnectionId, + + #[options( + help = "json key file for the signer, must include mnemonic", + short = "k" + )] + seed_file: String, +} + +impl TxRawConnConfirmCmd { + 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 signer_seed = std::fs::read_to_string(&self.seed_file).map_err(|e| { + anomaly::Context::new("invalid signer seed file", Some(e.into())).to_string() + })?; + + let opts = ConnectionOpenOptions { + src_chain_config: src_chain_config.clone(), + dest_chain_config: dest_chain_config.clone(), + src_client_id: self.src_client_id.clone(), + dest_client_id: self.dest_client_id.clone(), + src_connection_id: self.src_connection_id.clone(), + dest_connection_id: self.dest_connection_id.clone(), + signer_seed, + }; + + 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), + } + } +} diff --git a/relayer/src/chain.rs b/relayer/src/chain.rs index c6367e5ee2..226186c98a 100644 --- a/relayer/src/chain.rs +++ b/relayer/src/chain.rs @@ -24,7 +24,6 @@ use ibc::ics02_client::state::{ClientState, ConsensusState}; use ibc::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; use ibc::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; use ibc::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use ibc::ics03_connection::msgs::ConnectionMsgType; use ibc::ics03_connection::version::get_compatible_versions; use ibc::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProof}; use ibc::ics24_host::identifier::{ClientId, ConnectionId}; @@ -37,7 +36,7 @@ use crate::client::LightClient; use crate::config::ChainConfig; use crate::error::{Error, Kind}; use crate::keyring::store::{KeyEntry, KeyRing}; -use crate::tx::connection::{ConnectionOpenInitOptions, ConnectionOpenTryOptions}; +use crate::tx::connection::{ConnectionMsgType, ConnectionOpenInitOptions, ConnectionOpenOptions}; use crate::util::block_on; pub(crate) mod cosmos; @@ -204,45 +203,48 @@ pub trait Chain { })?) } - /// Build the required proofs for connection handshake messages. The proofs are obtained from - /// queries at height - 1 - fn build_connection_proofs( + /// Builds the required proofs and the client state for connection handshake messages. + /// The proofs and client state must be obtained from queries at same height with value + /// `height - 1` + fn build_connection_proofs_and_client_state( &self, message_type: ConnectionMsgType, connection_id: &ConnectionId, client_id: &ClientId, height: ICSHeight, - ) -> Result { + ) -> Result<(Option, Proofs), Error> { // Set the height of the queries at height - 1 let query_height = height .decrement() .map_err(|e| Kind::InvalidHeight.context(e))?; + // Collect all proofs as required let connection_proof = CommitmentProof::from(self.proven_connection(&connection_id, query_height)?.1); - let mut client_proof: Option = None; + let mut client_state = None; + let mut client_proof = None; let mut consensus_proof = None; match message_type { ConnectionMsgType::OpenTry | ConnectionMsgType::OpenAck => { - let (client_state, client_state_proof) = + let (client_state_value, client_state_proof) = self.proven_client_state(&client_id, query_height)?; - client_proof = Some(CommitmentProof::from(client_state_proof)); + client_proof = Option::from(CommitmentProof::from(client_state_proof)); let consensus_state_proof = self .proven_client_consensus( &client_id, - client_state.latest_height(), + client_state_value.latest_height(), query_height, )? .1; - consensus_proof = Some( + consensus_proof = Option::from( ConsensusProof::new( CommitmentProof::from(consensus_state_proof), - client_state.latest_height(), + client_state_value.latest_height(), ) .map_err(|e| { Kind::ConnOpenTry( @@ -252,13 +254,16 @@ pub trait Chain { .context(e) })?, ); + + client_state = Option::from(client_state_value); } _ => {} } - Ok( + Ok(( + client_state, Proofs::new(connection_proof, client_proof, consensus_proof, height) - .map_err(|e| Kind::MalformedProof)?, - ) + .map_err(|_| Kind::MalformedProof)?, + )) } } diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 5fec24365b..de13eee614 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -239,9 +239,6 @@ impl Chain for CosmosSDKChain { let mut body_buf = Vec::new(); prost::Message::encode(&body, &mut body_buf).unwrap(); - // let key = self.keybase.get(signer.clone()).map_err(|e| error::Kind::KeyBase.context(e))?; - let pub_key_bytes = key.public_key.public_key.to_bytes(); - let mut pk_buf = Vec::new(); prost::Message::encode(&key.public_key.public_key.to_bytes(), &mut pk_buf).unwrap(); diff --git a/relayer/src/error.rs b/relayer/src/error.rs index e3311fcd80..c874f508d6 100644 --- a/relayer/src/error.rs +++ b/relayer/src/error.rs @@ -70,6 +70,14 @@ pub enum Kind { #[error("Failed to build conn open try {0}: {1}")] ConnOpenTry(ConnectionId, String), + /// Connection open ack failure + #[error("Failed to build conn open ack {0}: {1}")] + ConnOpenAck(ConnectionId, String), + + /// Connection open confirm failure + #[error("Failed to build conn open confirm {0}: {1}")] + ConnOpenConfirm(ConnectionId, String), + /// A message transaction failure #[error("Message transaction failure: {0}")] MessageTransaction(String), diff --git a/relayer/src/tx/client.rs b/relayer/src/tx/client.rs index 1fb78cd4c0..e8992cf422 100644 --- a/relayer/src/tx/client.rs +++ b/relayer/src/tx/client.rs @@ -52,8 +52,8 @@ pub fn build_create_client( ))); } - // Get the key and signer from key seed file. - let (key, signer) = dest_chain.key_and_signer(signer_seed)?; + // Get the signer from key seed file + let (_, signer) = dest_chain.key_and_signer(signer_seed)?; // Build client create message with the data from source chain at latest height. let latest_height = src_chain.query_latest_height()?; @@ -101,8 +101,8 @@ pub fn build_update_client( .query_client_state(&dest_client_id, Height::default())? .latest_height(); - // Get the key and signer from key seed file. - let (key, signer) = dest_chain.key_and_signer(signer_seed)?; + // Get the signer from key seed file + let (_, signer) = dest_chain.key_and_signer(signer_seed)?; let new_msg = MsgUpdateAnyClient { client_id: dest_client_id, diff --git a/relayer/src/tx/connection.rs b/relayer/src/tx/connection.rs index 821c7af7a7..33be6c7e99 100644 --- a/relayer/src/tx/connection.rs +++ b/relayer/src/tx/connection.rs @@ -9,13 +9,14 @@ use serde_json::Value; use bitcoin::hashes::hex::ToHex; use ibc_proto::ibc::core::client::v1::MsgUpdateClient as RawMsgUpdateClient; +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_init::MsgConnectionOpenInit; use ibc::ics03_connection::msgs::conn_open_try::MsgConnectionOpenTry; -use ibc::ics03_connection::msgs::ConnectionMsgType; use ibc::ics03_connection::version::get_compatible_versions; use ibc::ics23_commitment::commitment::CommitmentPrefix; use ibc::ics24_host::identifier::{ClientId, ConnectionId}; @@ -27,6 +28,8 @@ use crate::config::ChainConfig; use crate::error::{Error, Kind}; use crate::keyring::store::{KeyEntry, KeyRingOperations}; use crate::tx::client::{build_update_client, build_update_client_and_send, ClientOptions}; +use ibc::ics03_connection::msgs::conn_open_ack::MsgConnectionOpenAck; +use ibc::ics03_connection::msgs::conn_open_confirm::MsgConnectionOpenConfirm; #[derive(Clone, Debug)] pub struct ConnectionOpenInitOptions { @@ -39,6 +42,14 @@ pub struct ConnectionOpenInitOptions { pub signer_seed: String, } +/// 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( dest_chain: &mut CosmosSDKChain, src_chain: &CosmosSDKChain, @@ -56,8 +67,8 @@ pub fn build_conn_init( .into()); } - // Get the key and signer from key seed file - let (key, signer) = dest_chain.key_and_signer(&opts.signer_seed)?; + // Get the signer from key seed file + let (_, signer) = dest_chain.key_and_signer(&opts.signer_seed)?; let prefix = src_chain.query_commitment_prefix()?; @@ -91,7 +102,7 @@ pub fn build_conn_init_and_send(opts: &ConnectionOpenInitOptions) -> Result Result<(), Error> { - if existing_connection.client_id() != expected_connection.client_id() - || existing_connection.counterparty().client_id() - != expected_connection.counterparty().client_id() - || existing_connection.counterparty().connection_id().is_some() - && existing_connection.counterparty().connection_id() - != expected_connection.counterparty().connection_id() - { + 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()) - } else { - Ok(()) } } -/// Attempts to send a MsgConnOpenTry to the dest_chain. -pub fn build_conn_try( +/// 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( dest_chain: &mut CosmosSDKChain, src_chain: &CosmosSDKChain, - opts: &ConnectionOpenTryOptions, -) -> Result, Error> { - // If there is a connection present on the destination chain it should look like this + 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(), - Some(opts.src_connection_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 dest_expected_connection = ConnectionEnd::new( - State::Init, + highest_state, opts.dest_client_id.clone(), - counterparty.clone(), + counterparty, src_chain.query_compatible_versions()?, ) .unwrap(); - // Check that if a connection exists on destination it is consistent with the try options - if let Ok(dest_connection) = - dest_chain.query_connection(&opts.dest_connection_id.clone(), ICSHeight::default()) - { - check_connection_state_for_try( - opts.dest_connection_id.clone(), - dest_connection, - dest_expected_connection, - )? + // Retrieve existing connection if any + let dest_connection = + dest_chain.query_connection(&opts.dest_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 dest_connection.is_err() { + return Ok(dest_expected_connection); + } + } else { + // A connection must exist on destination chain for Ack and Confirm Tx-es to succeed + if dest_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.dest_connection_id.clone(), + dest_connection?, + dest_expected_connection.clone(), + )?; + + Ok(dest_expected_connection) +} + +/// Attempts to build a MsgConnOpenTry. +pub fn build_conn_try( + dest_chain: &mut CosmosSDKChain, + src_chain: &CosmosSDKChain, + opts: &ConnectionOpenOptions, +) -> Result, Error> { + let dest_expected_connection = + validated_expected_connection(dest_chain, src_chain, 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| { @@ -161,6 +227,7 @@ pub fn build_conn_try( 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 @@ -174,8 +241,8 @@ pub fn build_conn_try( // signer_seed: "".to_string(), // })?; - // Get the key and signer from key seed file - let (key, signer) = dest_chain.key_and_signer(&opts.signer_seed)?; + // Get the signer from key seed file + let (_, signer) = dest_chain.key_and_signer(&opts.signer_seed)?; // Build message(s) for updating client on destination let ics_target_height = src_chain.query_latest_height()?; @@ -188,9 +255,7 @@ pub fn build_conn_try( &opts.signer_seed, )?; - let client_state = src_chain.query_client_state(&opts.src_client_id, ics_target_height)?; - - let proofs = src_chain.build_connection_proofs( + let (client_state, proofs) = src_chain.build_connection_proofs_and_client_state( ConnectionMsgType::OpenTry, &opts.src_connection_id.clone(), &opts.src_client_id, @@ -206,9 +271,9 @@ pub fn build_conn_try( let new_msg = MsgConnectionOpenTry { connection_id: opts.dest_connection_id.clone(), client_id: opts.dest_client_id.clone(), - client_state: Some(client_state), + client_state, counterparty_chosen_connection_id: src_connection.counterparty().connection_id().cloned(), - counterparty, + counterparty: dest_expected_connection.counterparty(), counterparty_versions, proofs, signer, @@ -221,7 +286,7 @@ pub fn build_conn_try( Ok(msgs) } -pub fn build_conn_try_and_send(opts: ConnectionOpenTryOptions) -> Result { +pub fn build_conn_try_and_send(opts: ConnectionOpenOptions) -> Result { // Get the source and destination chains. let src_chain = &CosmosSDKChain::from_config(opts.src_chain_config.clone())?; let dest_chain = &mut CosmosSDKChain::from_config(opts.clone().dest_chain_config)?; @@ -231,3 +296,164 @@ pub fn build_conn_try_and_send(opts: ConnectionOpenTryOptions) -> Result Result, Error> { + let _expected_dest_connection = + validated_expected_connection(dest_chain, src_chain, 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 (key, signer) = src_chain.key_and_signer(&opts.src_signer_seed)?; + // build_update_client_and_send(ClientOptions { + // dest_client_id: opts.src_client_id.clone(), + // dest_chain_config: src_chain.config().clone(), + // src_chain_config: dest_chain.config().clone(), + // signer_seed: "".to_string(), + // })?; + + // Get the signer from key seed file + let (_, signer) = dest_chain.key_and_signer(&opts.signer_seed)?; + + // Build message(s) for updating client on destination + let ics_target_height = src_chain.query_latest_height()?; + + let mut msgs = build_update_client( + dest_chain, + src_chain, + opts.dest_client_id.clone(), + ics_target_height, + &opts.signer_seed, + )?; + + 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.dest_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 { + // Get the source and destination chains. + let src_chain = &CosmosSDKChain::from_config(opts.src_chain_config.clone())?; + let dest_chain = &mut CosmosSDKChain::from_config(opts.clone().dest_chain_config)?; + + let dest_msgs = build_conn_ack(dest_chain, src_chain, &opts)?; + let (key, _) = dest_chain.key_and_signer(&opts.signer_seed)?; + + Ok(dest_chain.send(dest_msgs, key, "".to_string(), 0)?) +} + +/// Attempts to build a MsgConnOpenConfirm. +pub fn build_conn_confirm( + dest_chain: &mut CosmosSDKChain, + src_chain: &CosmosSDKChain, + opts: &ConnectionOpenOptions, +) -> Result, Error> { + let _expected_dest_connection = + validated_expected_connection(dest_chain, src_chain, 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 the signer from key seed file + let (_, signer) = dest_chain.key_and_signer(&opts.signer_seed)?; + + // Build message(s) for updating client on destination + let ics_target_height = src_chain.query_latest_height()?; + + let mut msgs = build_update_client( + dest_chain, + src_chain, + opts.dest_client_id.clone(), + ics_target_height, + &opts.signer_seed, + )?; + + 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.dest_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 { + // Get the source and destination chains. + let src_chain = &CosmosSDKChain::from_config(opts.src_chain_config.clone())?; + let dest_chain = &mut CosmosSDKChain::from_config(opts.clone().dest_chain_config)?; + + let dest_msgs = build_conn_confirm(dest_chain, src_chain, &opts)?; + let (key, _) = dest_chain.key_and_signer(&opts.signer_seed)?; + + Ok(dest_chain.send(dest_msgs, key, "".to_string(), 0)?) +}