diff --git a/modules/src/ics03_connection/connection.rs b/modules/src/ics03_connection/connection.rs index 4c5393c6eb..af10f23280 100644 --- a/modules/src/ics03_connection/connection.rs +++ b/modules/src/ics03_connection/connection.rs @@ -7,6 +7,7 @@ use ibc_proto::ibc::core::connection::v1::{ use tendermint_proto::DomainType; use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::version::validate_versions; use crate::ics23_commitment::commitment::CommitmentPrefix; use crate::ics24_host::error::ValidationError; use crate::ics24_host::identifier::{ClientId, ConnectionId}; @@ -45,7 +46,7 @@ impl From for RawConnectionEnd { client_id: value.client_id.to_string(), versions: value.versions, state: value.state as i32, - counterparty: Some(RawCounterparty::from(value.counterparty)), + counterparty: Some(value.counterparty.into()), } } } @@ -65,6 +66,11 @@ impl ConnectionEnd { }) } + /// Getter for the state of this connection end. + pub fn state(&self) -> &State { + &self.state + } + /// Setter for the `state` field. pub fn set_state(&mut self, new_state: State) { self.state = new_state; @@ -91,11 +97,6 @@ impl ConnectionEnd { self.state.eq(other) } - /// Getter for the state of this connection end. - pub fn state(&self) -> &State { - &self.state - } - /// Getter for the client id on the local party of this connection end. pub fn client_id(&self) -> &ClientId { &self.client_id @@ -196,25 +197,6 @@ impl Counterparty { } } -pub fn validate_versions(versions: Vec) -> Result, String> { - let v: Vec = versions.to_vec(); - if v.is_empty() { - return Err("missing versions".to_string()); - } - - for v in versions.into_iter() { - validate_version(v)?; - } - Ok(v) -} - -pub fn validate_version(version: String) -> Result { - if version.trim().is_empty() { - return Err("empty version string".to_string()); - } - Ok(version) -} - #[derive(Clone, Debug, PartialEq)] pub enum State { Init = 1, diff --git a/modules/src/ics03_connection/context.rs b/modules/src/ics03_connection/context.rs index fc2c28d165..c4fc29b117 100644 --- a/modules/src/ics03_connection/context.rs +++ b/modules/src/ics03_connection/context.rs @@ -6,6 +6,7 @@ use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState}; use crate::ics03_connection::connection::{ConnectionEnd, State}; use crate::ics03_connection::error::Error; use crate::ics03_connection::handler::ConnectionResult; +use crate::ics03_connection::version::{get_compatible_versions, pick_version}; use crate::ics23_commitment::commitment::CommitmentPrefix; use crate::ics24_host::identifier::{ClientId, ConnectionId}; use crate::Height; @@ -40,11 +41,19 @@ pub trait ConnectionReader { /// Function required by ICS 03. Returns the list of all possible versions that the connection /// handshake protocol supports. - fn get_compatible_versions(&self) -> Vec; + fn get_compatible_versions(&self) -> Vec { + get_compatible_versions() + } /// Function required by ICS 03. Returns one version out of the supplied list of versions, which the /// connection handshake protocol prefers. - fn pick_version(&self, counterparty_candidate_versions: Vec) -> String; + fn pick_version( + &self, + supported_versions: Vec, + counterparty_candidate_versions: Vec, + ) -> Result { + pick_version(supported_versions, counterparty_candidate_versions) + } } /// A context supplying all the necessary write-only dependencies (i.e., storage writing facility) diff --git a/modules/src/ics03_connection/error.rs b/modules/src/ics03_connection/error.rs index fb3ad80c5c..31e8db3e8d 100644 --- a/modules/src/ics03_connection/error.rs +++ b/modules/src/ics03_connection/error.rs @@ -32,6 +32,9 @@ pub enum Kind { #[error("invalid version")] InvalidVersion, + #[error("no commong version")] + NoCommonVersion, + #[error("invalid address")] InvalidAddress, diff --git a/modules/src/ics03_connection/handler/conn_open_try.rs b/modules/src/ics03_connection/handler/conn_open_try.rs index d812b60924..8915453f51 100644 --- a/modules/src/ics03_connection/handler/conn_open_try.rs +++ b/modules/src/ics03_connection/handler/conn_open_try.rs @@ -82,14 +82,8 @@ pub(crate) fn process( new_connection_end.set_state(State::TryOpen); // Pick the version. - let local_versions = ctx.get_compatible_versions(); - let intersection: Vec = msg - .counterparty_versions() - .iter() - .filter(|cv| local_versions.contains(cv)) - .cloned() - .collect(); - new_connection_end.set_version(ctx.pick_version(intersection)); + new_connection_end + .set_version(ctx.pick_version(ctx.get_compatible_versions(), msg.counterparty_versions())?); output.log("success: connection verification passed"); diff --git a/modules/src/ics03_connection/mod.rs b/modules/src/ics03_connection/mod.rs index 9232ccc293..696f43e071 100644 --- a/modules/src/ics03_connection/mod.rs +++ b/modules/src/ics03_connection/mod.rs @@ -8,3 +8,4 @@ pub mod events; /// Message processing logic (protocol) for ICS 03. pub mod handler; pub mod msgs; +pub mod version; diff --git a/modules/src/ics03_connection/msgs.rs b/modules/src/ics03_connection/msgs.rs index 43eda92b05..68a4ca5158 100644 --- a/modules/src/ics03_connection/msgs.rs +++ b/modules/src/ics03_connection/msgs.rs @@ -22,6 +22,15 @@ 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 6c8ae2d4c2..62ac0c300c 100644 --- a/modules/src/ics03_connection/msgs/conn_open_ack.rs +++ b/modules/src/ics03_connection/msgs/conn_open_ack.rs @@ -6,9 +6,10 @@ use tendermint_proto::DomainType; use tendermint::account::Id as AccountId; +use crate::address::{account_to_string, string_to_account}; use crate::ics02_client::client_def::AnyClientState; -use crate::ics03_connection::connection::validate_version; use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::version::validate_version; use crate::ics23_commitment::commitment::CommitmentProof; use crate::ics24_host::identifier::ConnectionId; use crate::proofs::{ConsensusProof, Proofs}; @@ -91,6 +92,8 @@ impl TryFrom for MsgConnectionOpenAck { type Error = anomaly::Error; fn try_from(msg: RawMsgConnectionOpenAck) -> Result { + let signer = string_to_account(msg.signer).map_err(|e| Kind::InvalidAddress.context(e))?; + let consensus_height = msg .consensus_height .ok_or_else(|| Kind::MissingConsensusHeight)? @@ -134,8 +137,7 @@ impl TryFrom for MsgConnectionOpenAck { proof_height, ) .map_err(|e| Kind::InvalidProof.context(e))?, - signer: AccountId::from_str(msg.signer.as_str()) - .map_err(|e| Kind::InvalidSigner.context(e))?, + signer, }) } } @@ -166,14 +168,15 @@ impl From for RawMsgConnectionOpenAck { .consensus_proof() .map_or_else(|| None, |h| Some(h.height().into())), version: ics_msg.version, - signer: ics_msg.signer.to_string(), + signer: account_to_string(ics_msg.signer).unwrap(), } } } #[cfg(test)] pub mod test_util { - use crate::test_utils::{get_dummy_account_id_raw, get_dummy_proof}; + use crate::ics03_connection::version::default_version_string; + use crate::test_utils::{get_dummy_bech32_account, get_dummy_proof}; use ibc_proto::ibc::core::client::v1::Height; use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenAck as RawMsgConnectionOpenAck; @@ -193,8 +196,8 @@ pub mod test_util { }), client_state: None, proof_client: vec![], - version: "1.0.0".to_string(), - signer: get_dummy_account_id_raw(), + version: default_version_string(), + signer: get_dummy_bech32_account(), } } } diff --git a/modules/src/ics03_connection/msgs/conn_open_confirm.rs b/modules/src/ics03_connection/msgs/conn_open_confirm.rs index af465e729d..88203445b8 100644 --- a/modules/src/ics03_connection/msgs/conn_open_confirm.rs +++ b/modules/src/ics03_connection/msgs/conn_open_confirm.rs @@ -5,10 +5,10 @@ use tendermint_proto::DomainType; use tendermint::account::Id as AccountId; +use crate::address::{account_to_string, string_to_account}; use crate::ics03_connection::error::{Error, Kind}; use crate::ics24_host::identifier::ConnectionId; use crate::{proofs::Proofs, tx_msg::Msg}; -use std::str::FromStr; /// Message type for the `MsgConnectionOpenConfirm` message. pub const TYPE_MSG_CONNECTION_OPEN_CONFIRM: &str = "connection_open_confirm"; @@ -61,6 +61,8 @@ impl TryFrom for MsgConnectionOpenConfirm { type Error = anomaly::Error; fn try_from(msg: RawMsgConnectionOpenConfirm) -> Result { + let signer = string_to_account(msg.signer).map_err(|e| Kind::InvalidAddress.context(e))?; + let proof_height = msg .proof_height .ok_or_else(|| Kind::MissingProofHeight)? @@ -73,8 +75,7 @@ impl TryFrom for MsgConnectionOpenConfirm { .map_err(|e| Kind::IdentifierError.context(e))?, proofs: Proofs::new(msg.proof_ack.into(), None, None, proof_height) .map_err(|e| Kind::InvalidProof.context(e))?, - signer: AccountId::from_str(msg.signer.as_str()) - .map_err(|e| Kind::InvalidSigner.context(e))?, + signer, }) } } @@ -85,7 +86,7 @@ impl From for RawMsgConnectionOpenConfirm { connection_id: ics_msg.connection_id.as_str().to_string(), proof_ack: ics_msg.proofs.object_proof().clone().into(), proof_height: Some(ics_msg.proofs.height().into()), - signer: ics_msg.signer.to_string(), + signer: account_to_string(ics_msg.signer).unwrap(), } } } @@ -95,7 +96,7 @@ pub mod test_util { use ibc_proto::ibc::core::client::v1::Height; use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm; - use crate::test_utils::{get_dummy_account_id_raw, get_dummy_proof}; + use crate::test_utils::{get_dummy_bech32_account, get_dummy_proof}; pub fn get_dummy_msg_conn_open_confirm() -> RawMsgConnectionOpenConfirm { RawMsgConnectionOpenConfirm { @@ -105,7 +106,7 @@ pub mod test_util { version_number: 0, version_height: 10, }), - signer: get_dummy_account_id_raw(), + signer: get_dummy_bech32_account(), } } } diff --git a/modules/src/ics03_connection/msgs/conn_open_init.rs b/modules/src/ics03_connection/msgs/conn_open_init.rs index 38aace9b22..fbacad0a4f 100644 --- a/modules/src/ics03_connection/msgs/conn_open_init.rs +++ b/modules/src/ics03_connection/msgs/conn_open_init.rs @@ -6,8 +6,9 @@ use tendermint_proto::DomainType; use tendermint::account::Id as AccountId; use crate::address::{account_to_string, string_to_account}; -use crate::ics03_connection::connection::{validate_version, Counterparty}; +use crate::ics03_connection::connection::Counterparty; use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::version::validate_version; use crate::ics24_host::identifier::{ClientId, ConnectionId}; use crate::tx_msg::Msg; @@ -107,8 +108,8 @@ impl From for RawMsgConnectionOpenInit { client_id: ics_msg.client_id.as_str().to_string(), connection_id: ics_msg.connection_id.as_str().to_string(), counterparty: Some(ics_msg.counterparty.into()), - signer: account_to_string(ics_msg.signer).unwrap(), version: ics_msg.version, + signer: account_to_string(ics_msg.signer).unwrap(), } } } @@ -118,6 +119,7 @@ pub mod test_util { use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenInit as RawMsgConnectionOpenInit; use crate::ics03_connection::msgs::test_util::get_dummy_counterparty; + use crate::ics03_connection::version::default_version_string; use crate::test_utils::get_dummy_bech32_account; /// Returns a dummy message, for testing only. @@ -127,7 +129,7 @@ pub mod test_util { client_id: "srcclient".to_string(), connection_id: "srcconnection".to_string(), counterparty: Some(get_dummy_counterparty()), - version: "1.0.0".to_string(), + version: default_version_string(), signer: get_dummy_bech32_account(), } } diff --git a/modules/src/ics03_connection/msgs/conn_open_try.rs b/modules/src/ics03_connection/msgs/conn_open_try.rs index b1eedb4614..49381b2332 100644 --- a/modules/src/ics03_connection/msgs/conn_open_try.rs +++ b/modules/src/ics03_connection/msgs/conn_open_try.rs @@ -6,9 +6,11 @@ use tendermint_proto::DomainType; use tendermint::account::Id as AccountId; +use crate::address::{account_to_string, string_to_account}; use crate::ics02_client::client_def::AnyClientState; -use crate::ics03_connection::connection::{validate_versions, Counterparty}; +use crate::ics03_connection::connection::Counterparty; use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::version::validate_versions; use crate::ics23_commitment::commitment::CommitmentProof; use crate::ics24_host::identifier::{ClientId, ConnectionId}; use crate::proofs::{ConsensusProof, Proofs}; @@ -23,14 +25,14 @@ pub const TYPE_MSG_CONNECTION_OPEN_TRY: &str = "connection_open_try"; /// #[derive(Clone, Debug, PartialEq, Eq)] pub struct MsgConnectionOpenTry { - connection_id: ConnectionId, - client_id: ClientId, - client_state: Option, - counterparty_chosen_connection_id: Option, - counterparty: Counterparty, - counterparty_versions: Vec, - proofs: Proofs, - signer: AccountId, + pub connection_id: ConnectionId, + pub client_id: ClientId, + pub client_state: Option, + pub counterparty_chosen_connection_id: Option, + pub counterparty: Counterparty, + pub counterparty_versions: Vec, + pub proofs: Proofs, + pub signer: AccountId, } impl MsgConnectionOpenTry { @@ -99,6 +101,10 @@ impl Msg for MsgConnectionOpenTry { fn get_signers(&self) -> Vec { vec![self.signer] } + + fn type_url(&self) -> String { + "/ibc.core.connection.v1.MsgConnectionOpenTry".to_string() + } } impl DomainType for MsgConnectionOpenTry {} @@ -160,8 +166,7 @@ impl TryFrom for MsgConnectionOpenTry { proof_height, ) .map_err(|e| Kind::InvalidProof.context(e))?, - signer: AccountId::from_str(msg.signer.as_str()) - .map_err(|e| Kind::InvalidSigner.context(e))?, + signer: string_to_account(msg.signer).map_err(|e| Kind::InvalidAddress.context(e))?, }) } } @@ -194,7 +199,7 @@ impl From for RawMsgConnectionOpenTry { .proofs .consensus_proof() .map_or_else(|| None, |h| Some(h.height().into())), - signer: ics_msg.signer.to_string(), + signer: account_to_string(ics_msg.signer).unwrap(), } } } @@ -202,7 +207,8 @@ impl From for RawMsgConnectionOpenTry { #[cfg(test)] pub mod test_util { use crate::ics03_connection::msgs::test_util::get_dummy_counterparty; - use crate::test_utils::{get_dummy_account_id_raw, get_dummy_proof}; + use crate::ics03_connection::version::get_compatible_versions; + use crate::test_utils::{get_dummy_bech32_account, get_dummy_proof}; use ibc_proto::ibc::core::client::v1::Height; use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenTry as RawMsgConnectionOpenTry; @@ -219,7 +225,7 @@ pub mod test_util { desired_connection_id: "srcconnection".to_string(), client_state: None, counterparty: Some(get_dummy_counterparty()), - counterparty_versions: vec!["1.0.0".to_string()], + counterparty_versions: get_compatible_versions(), counterparty_chosen_connection_id: "srcconnection".to_string(), proof_init: get_dummy_proof(), proof_height: Some(Height { @@ -232,7 +238,7 @@ pub mod test_util { version_height: consensus_height, }), proof_client: vec![], - signer: get_dummy_account_id_raw(), + signer: get_dummy_bech32_account(), } } } diff --git a/modules/src/ics03_connection/version.rs b/modules/src/ics03_connection/version.rs new file mode 100644 index 0000000000..6c8e9c7a60 --- /dev/null +++ b/modules/src/ics03_connection/version.rs @@ -0,0 +1,332 @@ +use std::convert::TryFrom; + +use ibc_proto::ibc::core::connection::v1::Version as RawVersion; +use tendermint_proto::DomainType; + +use crate::ics03_connection::error::{Error, Kind}; +use std::str::FromStr; + +#[derive(Clone, Debug, PartialEq, Eq)] +struct Version { + /// unique version identifier + identifier: String, + /// list of features compatible with the specified identifier + features: Vec, +} + +impl DomainType for Version {} + +impl TryFrom for Version { + type Error = anomaly::Error; + fn try_from(value: RawVersion) -> Result { + Ok(Version { + identifier: value.identifier, + features: value.features, + }) + } +} + +impl From for RawVersion { + fn from(value: Version) -> Self { + Self { + identifier: value.identifier, + features: value.features, + } + } +} + +impl Default for Version { + fn default() -> Self { + Version { + identifier: "1".to_string(), + features: vec!["ORDER_ORDERED".to_string(), "ORDER_UNORDERED".to_string()], + } + } +} + +impl FromStr for Version { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok(Version::decode(s.as_bytes()).map_err(|e| Kind::InvalidVersion.context(e))?) + } +} + +impl std::fmt::Display for Version { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + write!( + f, + "{}", + String::from_utf8(Version::encode_vec(&self).unwrap()).unwrap() + ) + } +} + +pub fn default_version_string() -> String { + Version::default().to_string() +} + +pub fn get_compatible_versions() -> Vec { + vec![default_version_string()] +} + +pub fn pick_version( + supported_versions: Vec, + counterparty_versions: Vec, +) -> Result { + let mut intersection: Vec = vec![]; + for s in supported_versions.iter() { + let supported_version = + Version::decode(s.as_bytes()).map_err(|e| Kind::InvalidVersion.context(e))?; + for c in counterparty_versions.iter() { + let counterparty_version = Version::from_str(c.as_str())?; + if supported_version.identifier != counterparty_version.identifier { + continue; + } + // TODO - perform feature intersection and error if empty + intersection.append(&mut vec![supported_version.clone()]); + } + } + intersection.sort_by(|a, b| a.identifier.cmp(&b.identifier)); + if intersection.is_empty() { + return Err(Kind::NoCommonVersion.into()); + } + Ok(intersection[0].to_string()) +} + +pub fn validate_versions(versions: Vec) -> Result, Error> { + if versions.is_empty() { + return Err(Kind::InvalidVersion + .context("no versions".to_string()) + .into()); + } + for version_str in versions.iter() { + validate_version(version_str.clone())?; + } + Ok(versions) +} + +pub fn validate_version(raw_version: String) -> Result { + let version = + Version::from_str(raw_version.as_ref()).map_err(|e| Kind::InvalidVersion.context(e))?; + + if version.identifier.trim().is_empty() { + return Err(Kind::InvalidVersion + .context("empty version string".to_string()) + .into()); + } + for feature in version.features { + if feature.trim().is_empty() { + return Err(Kind::InvalidVersion + .context("empty feature string".to_string()) + .into()); + } + } + Ok(raw_version) +} + +#[cfg(test)] +mod tests { + use crate::ics03_connection::version::{ + default_version_string, get_compatible_versions, pick_version, validate_versions, Version, + }; + use std::str::FromStr; + + fn good_versions() -> Vec { + vec![ + Version::default(), + Version { + identifier: "2".to_string(), + features: vec!["ORDER_RANDOM".to_string(), "ORDER_UNORDERED".to_string()], + }, + ] + .into_iter() + .map(|v| v.to_string()) + .collect() + } + + fn bad_versions_identifier() -> Vec { + vec![Version { + identifier: "".to_string(), + features: vec!["ORDER_RANDOM".to_string(), "ORDER_UNORDERED".to_string()], + }] + .into_iter() + .map(|v| v.to_string()) + .collect() + } + + fn bad_versions_features() -> Vec { + vec![Version { + identifier: "2".to_string(), + features: vec!["".to_string()], + }] + .into_iter() + .map(|v| v.to_string()) + .collect() + } + + fn overlapping() -> (Vec, Vec, String) { + ( + vec![ + Version::default(), + Version { + identifier: "3".to_string(), + features: vec![], + }, + Version { + identifier: "4".to_string(), + features: vec![], + }, + ] + .into_iter() + .map(|v| v.to_string()) + .collect(), + vec![ + Version { + identifier: "2".to_string(), + features: vec![], + }, + Version { + identifier: "4".to_string(), + features: vec![], + }, + Version { + identifier: "3".to_string(), + features: vec![], + }, + ] + .into_iter() + .map(|v| v.to_string()) + .collect(), + // Should pick version 3 as it's the lowest of the intersection {3, 4} + Version { + identifier: "3".to_string(), + features: vec![], + } + .to_string(), + ) + } + + fn disjoint() -> (Vec, Vec, String) { + ( + vec![Version { + identifier: "1".to_string(), + features: vec![], + }] + .into_iter() + .map(|v| v.to_string()) + .collect(), + vec![Version { + identifier: "2".to_string(), + features: vec![], + }] + .into_iter() + .map(|v| v.to_string()) + .collect(), + "".to_string(), + ) + } + + #[test] + fn verify() { + struct Test { + name: String, + versions: Vec, + want_pass: bool, + } + let tests: Vec = vec![ + Test { + name: "Compatible versions".to_string(), + versions: get_compatible_versions(), + want_pass: true, + }, + Test { + name: "Multiple versions".to_string(), + versions: good_versions(), + want_pass: true, + }, + Test { + name: "Bad version identifier".to_string(), + versions: bad_versions_identifier(), + want_pass: false, + }, + Test { + name: "Bad version feature".to_string(), + versions: bad_versions_features(), + want_pass: false, + }, + Test { + name: "Bad versions empty".to_string(), + versions: vec![], + want_pass: false, + }, + ]; + + for test in tests { + let versions = validate_versions(test.versions); + + assert_eq!( + test.want_pass, + versions.is_ok(), + "Validate versions failed for test {} with error {:?}", + test.name, + versions.err(), + ); + } + } + #[test] + fn pick() { + struct Test { + name: String, + supported: Vec, + counterparty: Vec, + picked: String, + want_pass: bool, + } + let tests: Vec = vec![ + Test { + name: "Compatible versions".to_string(), + supported: get_compatible_versions(), + counterparty: get_compatible_versions(), + picked: default_version_string(), + want_pass: true, + }, + Test { + name: "Overlapping versions".to_string(), + supported: overlapping().0, + counterparty: overlapping().1, + picked: overlapping().2, + want_pass: true, + }, + Test { + name: "Disjoint versions".to_string(), + supported: disjoint().0, + counterparty: disjoint().1, + picked: disjoint().2, + want_pass: false, + }, + ]; + + for test in tests { + let version = pick_version(test.supported, test.counterparty); + + assert_eq!( + test.want_pass, + version.is_ok(), + "Validate versions failed for test {}", + test.name, + ); + + if test.want_pass { + assert_eq!(version.unwrap(), test.picked); + } + } + } + #[test] + fn serialize() { + let def = Version::default(); + let def_raw = def.to_string(); + let def_back = Version::from_str(def_raw.as_ref()).unwrap(); + assert_eq!(def, def_back); + } +} diff --git a/modules/src/ics04_channel/msgs/chan_open_ack.rs b/modules/src/ics04_channel/msgs/chan_open_ack.rs index 2db84515d3..dba0111f17 100644 --- a/modules/src/ics04_channel/msgs/chan_open_ack.rs +++ b/modules/src/ics04_channel/msgs/chan_open_ack.rs @@ -1,4 +1,3 @@ -use crate::ics03_connection::connection::validate_version; use crate::ics04_channel::error::{Error, Kind}; use crate::ics23_commitment::commitment::CommitmentProof; use crate::ics24_host::identifier::{ChannelId, PortId}; @@ -37,8 +36,7 @@ impl MsgChannelOpenAck { channel_id: channel_id .parse() .map_err(|e| Kind::IdentifierError.context(e))?, - counterparty_version: validate_version(counterparty_version) - .map_err(|e| Kind::InvalidVersion.context(e))?, + counterparty_version, proofs: Proofs::new(proof_try, None, None, proofs_height) .map_err(|e| Kind::InvalidProof.context(e))?, signer, @@ -162,14 +160,6 @@ mod tests { }, want_pass: false, }, - Test { - name: "Empty counterparty version".to_string(), - params: OpenAckParams { - counterparty_version: " ".to_string(), - ..default_params.clone() - }, - want_pass: false, - }, Test { name: "Bad proof height, height = 0".to_string(), params: OpenAckParams { diff --git a/modules/src/ics04_channel/msgs/chan_open_try.rs b/modules/src/ics04_channel/msgs/chan_open_try.rs index b42d059561..afa2567681 100644 --- a/modules/src/ics04_channel/msgs/chan_open_try.rs +++ b/modules/src/ics04_channel/msgs/chan_open_try.rs @@ -1,6 +1,4 @@ #![allow(clippy::too_many_arguments)] - -use crate::ics03_connection::connection::validate_version; use crate::ics04_channel::channel::{ChannelEnd, Counterparty, Order}; use crate::ics04_channel::error::{Error, Kind}; use crate::ics23_commitment::commitment::CommitmentProof; @@ -46,9 +44,6 @@ impl MsgChannelOpenTry { .map(|s| ConnectionId::from_str(s.as_str())) .collect(); - let version = - validate_version(channel_version).map_err(|e| Kind::InvalidVersion.context(e))?; - Ok(Self { port_id: port_id .parse() @@ -61,10 +56,9 @@ impl MsgChannelOpenTry { Counterparty::new(counterparty_port_id, counterparty_channel_id) .map_err(|e| Kind::IdentifierError.context(e))?, connection_hops.map_err(|e| Kind::IdentifierError.context(e))?, - version, + channel_version, ), - counterparty_version: validate_version(counterparty_version) - .map_err(|e| Kind::InvalidVersion.context(e))?, + counterparty_version, proofs: Proofs::new(proof_init, None, None, proofs_height) .map_err(|e| Kind::InvalidProof.context(e))?, signer, @@ -197,14 +191,6 @@ mod tests { }, want_pass: false, }, - Test { - name: "Empty counterparty version".to_string(), - params: OpenTryParams { - counterparty_version: " ".to_string(), - ..default_params.clone() - }, - want_pass: false, - }, Test { name: "Bad proof height, height = 0".to_string(), params: OpenTryParams { @@ -259,14 +245,6 @@ mod tests { // }, // want_pass: false, // }, - Test { - name: "Empty channel version".to_string(), - params: OpenTryParams { - channel_version: " ".to_string(), - ..default_params.clone() - }, - want_pass: false, - }, Test { name: "Bad counterparty port, name too long".to_string(), params: OpenTryParams { diff --git a/modules/src/ics23_commitment/commitment.rs b/modules/src/ics23_commitment/commitment.rs index bcb1580d4e..e647875519 100644 --- a/modules/src/ics23_commitment/commitment.rs +++ b/modules/src/ics23_commitment/commitment.rs @@ -1,5 +1,10 @@ +use std::convert::TryFrom; use std::fmt; +use ibc_proto::ibc::core::commitment::v1::MerkleProof as RawMerkleProof; + +use crate::ics23_commitment::error::{Error, Kind}; + #[derive(Clone, Debug, PartialEq, Eq)] pub struct CommitmentRoot(pub Vec); // Todo: write constructor impl CommitmentRoot { @@ -40,6 +45,25 @@ impl From for Vec { } } +impl From for CommitmentProof { + fn from(proof: RawMerkleProof) -> Self { + let mut buf = Vec::new(); + prost::Message::encode(&proof, &mut buf).unwrap(); + buf.into() + } +} + +impl TryFrom for RawMerkleProof { + type Error = Error; + + fn try_from(value: CommitmentProof) -> Result { + let value: Vec = value.into(); + let res: RawMerkleProof = prost::Message::decode(value.as_ref()) + .map_err(|e| Kind::InvalidRawMerkleProof.context(e))?; + Ok(res) + } +} + #[derive(Clone, PartialEq, Eq)] pub struct CommitmentPrefix(pub Vec); // Todo: decent getter or DomainType trait implementation diff --git a/modules/src/ics23_commitment/error.rs b/modules/src/ics23_commitment/error.rs new file mode 100644 index 0000000000..96ef1dc79a --- /dev/null +++ b/modules/src/ics23_commitment/error.rs @@ -0,0 +1,16 @@ +use anomaly::{BoxError, Context}; +use thiserror::Error; + +pub type Error = anomaly::Error; + +#[derive(Clone, Debug, Error)] +pub enum Kind { + #[error("invalid raw merkle proof")] + InvalidRawMerkleProof, +} + +impl Kind { + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) + } +} diff --git a/modules/src/ics23_commitment/merkle.rs b/modules/src/ics23_commitment/merkle.rs index f732652c50..33c4fc2530 100644 --- a/modules/src/ics23_commitment/merkle.rs +++ b/modules/src/ics23_commitment/merkle.rs @@ -1,6 +1,10 @@ -use crate::ics23_commitment::commitment::CommitmentPrefix; +use std::convert::TryFrom; use ibc_proto::ibc::core::commitment::v1::MerklePath; +use ibc_proto::ibc::core::commitment::v1::MerkleProof as RawMerkleProof; + +use crate::ics23_commitment::commitment::CommitmentPrefix; +use crate::ics23_commitment::error::Error; pub fn apply_prefix( prefix: &CommitmentPrefix, @@ -24,7 +28,7 @@ pub fn cosmos_specs() -> Vec { prehash_key: 0, prehash_value: 1, length: 1, - prefix: vec![], + prefix: vec![0], }), inner_spec: Some(ibc_proto::ics23::InnerSpec { child_order: vec![0, 1], @@ -44,7 +48,7 @@ pub fn cosmos_specs() -> Vec { prehash_key: 0, prehash_value: 1, length: 1, - prefix: vec![], + prefix: vec![0], }), inner_spec: Some(ibc_proto::ics23::InnerSpec { child_order: vec![0, 1], @@ -59,3 +63,60 @@ pub fn cosmos_specs() -> Vec { }, ] } + +#[derive(Clone, Debug, PartialEq)] +pub struct MerkleProof { + pub proof: Option, +} + +// Merkle Proof serialization notes: +// "Proof" id currently defined in a number of forms and included in a number of places +// - TmProof: in tendermint-rs/src/merkle/proof.rs:Proof +// - RawProofOps: in tendermint-proto/tendermint.cyrpto.rs:ProofOps +// - RawMerkleProof: in ibc-proto/ibc.core.commitment.v1.rs:MerkleProof +// - structure that includes a RawProofOps in its only `proof` field. +// #[derive(Clone, PartialEq, ::prost::Message)] +// pub struct MerkleProof { +// #[prost(message, optional, tag="1")] +// pub proof: ::std::option::Option<::tendermint_proto::crypto::ProofOps>, +// } +// - Vec: RawMerkleProof is not explicitly used but, serialized as Vec, it is +// included in all handshake messages that require proofs (i.e. all except the two `OpenInit`), +// and also in all queries that require proofs +// - MerkleProof: Domain type for RawMerkleProof, currently not used and identical to RawMerkleProof. +// This will change with verification implementation. +// - CommitmentProof: Defined in ibc-rs as Vec and currently used in all its messages +// +// Here are a couple of flows that illustrate the different conversions: +// IBC Messages and Handlers: sink happens in the handle verification +// Vec -> CommitmentProof -> RawMerkleProof -> MerkleProof +// +// Relayer: from the proof in the query response to the proof being included in a message +// TmProof -> RawProofOps => RawMerkleProof -> MerkleProof -> verify() +// -> MerkleProof -> RawMerkleProof -> CommitmentProof -> Vec +// Note: current implementation for ^ is simplified since verification is not yet implemented: +// TmProof -> RawProofOps => RawMerkleProof -> CommitmentProof -> Vec +// +// Implementations of (de)serializers and conversions: +// - commitment.rs: +// Vec <-> CommitmentProof +// CommitmentProof <-> RawMerkleProof +// - merkle.rs: +// RawMerkleProof <-> MerkleProof +// - tendermint-rs/src/merkle/proof.rs: +// TmProof <-> RawProofOps +// - cosmos.rs:abci_query() converts from query proof to Merkle proof: +// RawProofOps => RawMerkleProof +// +impl TryFrom for MerkleProof { + type Error = Error; + fn try_from(value: RawMerkleProof) -> Result { + Ok(MerkleProof { proof: value.proof }) + } +} + +impl From for RawMerkleProof { + fn from(value: MerkleProof) -> Self { + RawMerkleProof { proof: value.proof } + } +} diff --git a/modules/src/ics23_commitment/mod.rs b/modules/src/ics23_commitment/mod.rs index 12c7c57ed5..dbd7f99ea2 100644 --- a/modules/src/ics23_commitment/mod.rs +++ b/modules/src/ics23_commitment/mod.rs @@ -1,3 +1,4 @@ pub mod commitment; +pub mod error; pub mod merkle; pub mod mock; diff --git a/modules/src/mock_context.rs b/modules/src/mock_context.rs index f87a2d3deb..3a0b9a9086 100644 --- a/modules/src/mock_context.rs +++ b/modules/src/mock_context.rs @@ -237,17 +237,6 @@ impl ConnectionReader for MockContext { let hi = self.host_header(height)?; Some(hi.into()) } - - fn get_compatible_versions(&self) -> Vec { - vec!["test".to_string()] - } - - fn pick_version(&self, counterparty_candidate_versions: Vec) -> String { - counterparty_candidate_versions - .get(0) - .unwrap_or(&String::from("none")) - .to_string() - } } impl ConnectionKeeper for MockContext { diff --git a/modules/src/proofs.rs b/modules/src/proofs.rs index 5b2af1f1a7..2d814c553a 100644 --- a/modules/src/proofs.rs +++ b/modules/src/proofs.rs @@ -9,7 +9,8 @@ pub struct Proofs { object_proof: CommitmentProof, client_proof: Option, consensus_proof: Option, - /// Height for the proofs above. When creating these proofs, the chain was at `height`. + /// Height for the commitment root for proving the proofs above. + /// When creating these proofs, the chain is queried at `height-1`. height: Height, } diff --git a/modules/src/test_utils.rs b/modules/src/test_utils.rs index fc5eb3321c..d952588dd3 100644 --- a/modules/src/test_utils.rs +++ b/modules/src/test_utils.rs @@ -26,7 +26,7 @@ pub fn get_dummy_proof() -> Vec { .to_vec() } -pub fn get_dummy_account_id_raw() -> String { +fn get_dummy_account_id_raw() -> String { "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C".to_string() } diff --git a/proto-compiler/src/cmd/compile.rs b/proto-compiler/src/cmd/compile.rs index 4eb17ca659..13feae16dc 100644 --- a/proto-compiler/src/cmd/compile.rs +++ b/proto-compiler/src/cmd/compile.rs @@ -7,7 +7,6 @@ use tempdir::TempDir; use walkdir::WalkDir; use argh::FromArgs; - #[derive(Debug, FromArgs)] #[argh(subcommand, name = "compile")] /// Compile @@ -53,6 +52,7 @@ impl CompileCmd { format!("{}/proto/ibc", sdk_dir.display()), format!("{}/proto/cosmos/tx", sdk_dir.display()), format!("{}/proto/cosmos/base", sdk_dir.display()), + format!("{}/proto/cosmos/staking", sdk_dir.display()), ]; let proto_includes_paths = [ @@ -103,7 +103,8 @@ impl CompileCmd { let includes = proto_includes_paths.iter().map(|p| p.as_os_str().to_os_string()).collect::>(); let proto_services_path = [ - sdk_dir.join("proto/cosmos/auth/v1beta1/query.proto") + sdk_dir.join("proto/cosmos/auth/v1beta1/query.proto"), + sdk_dir.join("proto/cosmos/staking/v1beta1/query.proto"), ]; // List available paths for dependencies diff --git a/proto/src/lib.rs b/proto/src/lib.rs index a49e63535d..407baf4755 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -17,6 +17,11 @@ pub mod cosmos { include!("prost/cosmos.auth.v1beta1.rs"); } } + pub mod staking { + pub mod v1beta1 { + include!("prost/cosmos.staking.v1beta1.rs"); + } + } pub mod base { pub mod abci { pub mod v1beta1 { diff --git a/proto/src/prost/cosmos.staking.v1beta1.rs b/proto/src/prost/cosmos.staking.v1beta1.rs new file mode 100644 index 0000000000..afcdfbd6b6 --- /dev/null +++ b/proto/src/prost/cosmos.staking.v1beta1.rs @@ -0,0 +1,494 @@ +/// HistoricalInfo contains header and validator information for a given block. +/// It is stored as part of staking module's state, which persists the `n` most +/// recent HistoricalInfo +/// (`n` is set by the staking module's `historical_entries` parameter). +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct HistoricalInfo { + #[prost(message, optional, tag="1")] + pub header: ::std::option::Option, + #[prost(message, repeated, tag="2")] + pub valset: ::std::vec::Vec, +} +/// CommissionRates defines the initial commission rates to be used for creating +/// a validator. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct CommissionRates { + #[prost(string, tag="1")] + pub rate: std::string::String, + #[prost(string, tag="2")] + pub max_rate: std::string::String, + #[prost(string, tag="3")] + pub max_change_rate: std::string::String, +} +/// Commission defines commission parameters for a given validator. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Commission { + #[prost(message, optional, tag="1")] + pub commission_rates: ::std::option::Option, + #[prost(message, optional, tag="2")] + pub update_time: ::std::option::Option<::prost_types::Timestamp>, +} +/// Description defines a validator description. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Description { + #[prost(string, tag="1")] + pub moniker: std::string::String, + #[prost(string, tag="2")] + pub identity: std::string::String, + #[prost(string, tag="3")] + pub website: std::string::String, + #[prost(string, tag="4")] + pub security_contact: std::string::String, + #[prost(string, tag="5")] + pub details: std::string::String, +} +/// Validator defines a validator, together with the total amount of the +/// Validator's bond shares and their exchange rate to coins. Slashing results in +/// a decrease in the exchange rate, allowing correct calculation of future +/// undelegations without iterating over delegators. When coins are delegated to +/// this validator, the validator is credited with a delegation whose number of +/// bond shares is based on the amount of coins delegated divided by the current +/// exchange rate. Voting power can be calculated as total bonded shares +/// multiplied by exchange rate. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Validator { + #[prost(string, tag="1")] + pub operator_address: std::string::String, + #[prost(string, tag="2")] + pub consensus_pubkey: std::string::String, + #[prost(bool, tag="3")] + pub jailed: bool, + #[prost(int32, tag="4")] + pub status: i32, + #[prost(string, tag="5")] + pub tokens: std::string::String, + #[prost(string, tag="6")] + pub delegator_shares: std::string::String, + #[prost(message, optional, tag="7")] + pub description: ::std::option::Option, + #[prost(int64, tag="8")] + pub unbonding_height: i64, + #[prost(message, optional, tag="9")] + pub unbonding_time: ::std::option::Option<::prost_types::Timestamp>, + #[prost(message, optional, tag="10")] + pub commission: ::std::option::Option, + #[prost(string, tag="11")] + pub min_self_delegation: std::string::String, +} +/// ValAddresses defines a repeated set of validator addresses. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct ValAddresses { + #[prost(string, repeated, tag="1")] + pub addresses: ::std::vec::Vec, +} +/// DVPair is struct that just has a delegator-validator pair with no other data. +/// It is intended to be used as a marshalable pointer. For example, a DVPair can +/// be used to construct the key to getting an UnbondingDelegation from state. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DvPair { + #[prost(string, tag="1")] + pub delegator_address: std::string::String, + #[prost(string, tag="2")] + pub validator_address: std::string::String, +} +/// DVPairs defines an array of DVPair objects. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DvPairs { + #[prost(message, repeated, tag="1")] + pub pairs: ::std::vec::Vec, +} +/// DVVTriplet is struct that just has a delegator-validator-validator triplet +/// with no other data. It is intended to be used as a marshalable pointer. For +/// example, a DVVTriplet can be used to construct the key to getting a +/// Redelegation from state. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DvvTriplet { + #[prost(string, tag="1")] + pub delegator_address: std::string::String, + #[prost(string, tag="2")] + pub validator_src_address: std::string::String, + #[prost(string, tag="3")] + pub validator_dst_address: std::string::String, +} +/// DVVTriplets defines an array of DVVTriplet objects. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DvvTriplets { + #[prost(message, repeated, tag="1")] + pub triplets: ::std::vec::Vec, +} +/// Delegation represents the bond with tokens held by an account. It is +/// owned by one delegator, and is associated with the voting power of one +/// validator. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Delegation { + #[prost(string, tag="1")] + pub delegator_address: std::string::String, + #[prost(string, tag="2")] + pub validator_address: std::string::String, + #[prost(string, tag="3")] + pub shares: std::string::String, +} +/// UnbondingDelegation stores all of a single delegator's unbonding bonds +/// for a single validator in an time-ordered list. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UnbondingDelegation { + #[prost(string, tag="1")] + pub delegator_address: std::string::String, + #[prost(string, tag="2")] + pub validator_address: std::string::String, + /// unbonding delegation entries + #[prost(message, repeated, tag="3")] + pub entries: ::std::vec::Vec, +} +/// UnbondingDelegationEntry defines an unbonding object with relevant metadata. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct UnbondingDelegationEntry { + #[prost(int64, tag="1")] + pub creation_height: i64, + #[prost(message, optional, tag="2")] + pub completion_time: ::std::option::Option<::prost_types::Timestamp>, + #[prost(string, tag="3")] + pub initial_balance: std::string::String, + #[prost(string, tag="4")] + pub balance: std::string::String, +} +/// RedelegationEntry defines a redelegation object with relevant metadata. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RedelegationEntry { + #[prost(int64, tag="1")] + pub creation_height: i64, + #[prost(message, optional, tag="2")] + pub completion_time: ::std::option::Option<::prost_types::Timestamp>, + #[prost(string, tag="3")] + pub initial_balance: std::string::String, + #[prost(string, tag="4")] + pub shares_dst: std::string::String, +} +/// Redelegation contains the list of a particular delegator's redelegating bonds +/// from a particular source validator to a particular destination validator. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Redelegation { + #[prost(string, tag="1")] + pub delegator_address: std::string::String, + #[prost(string, tag="2")] + pub validator_src_address: std::string::String, + #[prost(string, tag="3")] + pub validator_dst_address: std::string::String, + /// redelegation entries + #[prost(message, repeated, tag="4")] + pub entries: ::std::vec::Vec, +} +/// Params defines the parameters for the staking module. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Params { + #[prost(message, optional, tag="1")] + pub unbonding_time: ::std::option::Option<::prost_types::Duration>, + #[prost(uint32, tag="2")] + pub max_validators: u32, + #[prost(uint32, tag="3")] + pub max_entries: u32, + #[prost(uint32, tag="4")] + pub historical_entries: u32, + #[prost(string, tag="5")] + pub bond_denom: std::string::String, +} +/// DelegationResponse is equivalent to Delegation except that it contains a +/// balance in addition to shares which is more suitable for client responses. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct DelegationResponse { + #[prost(message, optional, tag="1")] + pub delegation: ::std::option::Option, + #[prost(message, optional, tag="2")] + pub balance: ::std::option::Option, +} +/// RedelegationEntryResponse is equivalent to a RedelegationEntry except that it +/// contains a balance in addition to shares which is more suitable for client +/// responses. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RedelegationEntryResponse { + #[prost(message, optional, tag="1")] + pub redelegation_entry: ::std::option::Option, + #[prost(string, tag="4")] + pub balance: std::string::String, +} +/// RedelegationResponse is equivalent to a Redelegation except that its entries +/// contain a balance in addition to shares which is more suitable for client +/// responses. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct RedelegationResponse { + #[prost(message, optional, tag="1")] + pub redelegation: ::std::option::Option, + #[prost(message, repeated, tag="2")] + pub entries: ::std::vec::Vec, +} +/// Pool is used for tracking bonded and not-bonded token supply of the bond +/// denomination. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct Pool { + #[prost(string, tag="1")] + pub not_bonded_tokens: std::string::String, + #[prost(string, tag="2")] + pub bonded_tokens: std::string::String, +} +/// QueryValidatorsRequest is request type for Query/Validators RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryValidatorsRequest { + /// status enables to query for validators matching a given status. + #[prost(string, tag="1")] + pub status: std::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryValidatorsResponse is response type for the Query/Validators RPC method +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryValidatorsResponse { + /// validators contains all the queried validators. + #[prost(message, repeated, tag="1")] + pub validators: ::std::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryValidatorRequest is response type for the Query/Validator RPC method +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryValidatorRequest { + /// validator_addr defines the validator address to query for. + #[prost(string, tag="1")] + pub validator_addr: std::string::String, +} +/// QueryValidatorResponse is response type for the Query/Validator RPC method +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryValidatorResponse { + /// validator defines the the validator info. + #[prost(message, optional, tag="1")] + pub validator: ::std::option::Option, +} +/// QueryValidatorDelegationsRequest is request type for the +/// Query/ValidatorDelegations RPC method +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryValidatorDelegationsRequest { + /// validator_addr defines the validator address to query for. + #[prost(string, tag="1")] + pub validator_addr: std::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryValidatorDelegationsResponse is response type for the +/// Query/ValidatorDelegations RPC method +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryValidatorDelegationsResponse { + #[prost(message, repeated, tag="1")] + pub delegation_responses: ::std::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryValidatorUnbondingDelegationsRequest is required type for the +/// Query/ValidatorUnbondingDelegations RPC method +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryValidatorUnbondingDelegationsRequest { + /// validator_addr defines the validator address to query for. + #[prost(string, tag="1")] + pub validator_addr: std::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryValidatorUnbondingDelegationsResponse is response type for the +/// Query/ValidatorUnbondingDelegations RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryValidatorUnbondingDelegationsResponse { + #[prost(message, repeated, tag="1")] + pub unbonding_responses: ::std::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryDelegationRequest is request type for the Query/Delegation RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegationRequest { + /// delegator_addr defines the delegator address to query for. + #[prost(string, tag="1")] + pub delegator_addr: std::string::String, + /// validator_addr defines the validator address to query for. + #[prost(string, tag="2")] + pub validator_addr: std::string::String, +} +/// QueryDelegationResponse is response type for the Query/Delegation RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegationResponse { + /// delegation_responses defines the delegation info of a delegation. + #[prost(message, optional, tag="1")] + pub delegation_response: ::std::option::Option, +} +/// QueryUnbondingDelegationRequest is request type for the +/// Query/UnbondingDelegation RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryUnbondingDelegationRequest { + /// delegator_addr defines the delegator address to query for. + #[prost(string, tag="1")] + pub delegator_addr: std::string::String, + /// validator_addr defines the validator address to query for. + #[prost(string, tag="2")] + pub validator_addr: std::string::String, +} +/// QueryDelegationResponse is response type for the Query/UnbondingDelegation +/// RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryUnbondingDelegationResponse { + /// unbond defines the unbonding information of a delegation. + #[prost(message, optional, tag="1")] + pub unbond: ::std::option::Option, +} +/// QueryDelegatorDelegationsRequest is request type for the +/// Query/DelegatorDelegations RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegatorDelegationsRequest { + /// delegator_addr defines the delegator address to query for. + #[prost(string, tag="1")] + pub delegator_addr: std::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryDelegatorDelegationsResponse is response type for the +/// Query/DelegatorDelegations RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegatorDelegationsResponse { + /// delegation_responses defines all the delegations' info of a delegator. + #[prost(message, repeated, tag="1")] + pub delegation_responses: ::std::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryDelegatorUnbondingDelegationsRequest is request type for the +/// Query/DelegatorUnbondingDelegations RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegatorUnbondingDelegationsRequest { + /// delegator_addr defines the delegator address to query for. + #[prost(string, tag="1")] + pub delegator_addr: std::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryUnbondingDelegatorDelegationsResponse is response type for the +/// Query/UnbondingDelegatorDelegations RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegatorUnbondingDelegationsResponse { + #[prost(message, repeated, tag="1")] + pub unbonding_responses: ::std::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryRedelegationsRequest is request type for the Query/Redelegations RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryRedelegationsRequest { + /// delegator_addr defines the delegator address to query for. + #[prost(string, tag="1")] + pub delegator_addr: std::string::String, + /// src_validator_addr defines the validator address to redelegate from. + #[prost(string, tag="2")] + pub src_validator_addr: std::string::String, + /// dst_validator_addr defines the validator address to redelegate to. + #[prost(string, tag="3")] + pub dst_validator_addr: std::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="4")] + pub pagination: ::std::option::Option, +} +/// QueryRedelegationsResponse is response type for the Query/Redelegations RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryRedelegationsResponse { + #[prost(message, repeated, tag="1")] + pub redelegation_responses: ::std::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryDelegatorValidatorsRequest is request type for the +/// Query/DelegatorValidators RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegatorValidatorsRequest { + /// delegator_addr defines the delegator address to query for. + #[prost(string, tag="1")] + pub delegator_addr: std::string::String, + /// pagination defines an optional pagination for the request. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryDelegatorValidatorsResponse is response type for the +/// Query/DelegatorValidators RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegatorValidatorsResponse { + /// validators defines the the validators' info of a delegator. + #[prost(message, repeated, tag="1")] + pub validators: ::std::vec::Vec, + /// pagination defines the pagination in the response. + #[prost(message, optional, tag="2")] + pub pagination: ::std::option::Option, +} +/// QueryDelegatorValidatorRequest is request type for the +/// Query/DelegatorValidator RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegatorValidatorRequest { + /// delegator_addr defines the delegator address to query for. + #[prost(string, tag="1")] + pub delegator_addr: std::string::String, + /// validator_addr defines the validator address to query for. + #[prost(string, tag="2")] + pub validator_addr: std::string::String, +} +/// QueryDelegatorValidatorResponse response type for the +/// Query/DelegatorValidator RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryDelegatorValidatorResponse { + /// validator defines the the validator info. + #[prost(message, optional, tag="1")] + pub validator: ::std::option::Option, +} +/// QueryHistoricalInfoRequest is request type for the Query/HistoricalInfo RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryHistoricalInfoRequest { + /// height defines at which height to query the historical info. + #[prost(int64, tag="1")] + pub height: i64, +} +/// QueryHistoricalInfoResponse is response type for the Query/HistoricalInfo RPC +/// method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryHistoricalInfoResponse { + /// hist defines the historical info at the given height. + #[prost(message, optional, tag="1")] + pub hist: ::std::option::Option, +} +/// QueryPoolRequest is request type for the Query/Pool RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryPoolRequest { +} +/// QueryPoolResponse is response type for the Query/Pool RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryPoolResponse { + /// pool defines the pool info. + #[prost(message, optional, tag="1")] + pub pool: ::std::option::Option, +} +/// QueryParamsRequest is request type for the Query/Params RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryParamsRequest { +} +/// QueryParamsResponse is response type for the Query/Params RPC method. +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct QueryParamsResponse { + /// params holds all the parameters of this module. + #[prost(message, optional, tag="1")] + pub params: ::std::option::Option, +} +# [doc = r" Generated client implementations."] pub mod query_client { # ! [allow (unused_variables , dead_code , missing_docs)] use tonic :: codegen :: * ; # [doc = " Query defines the gRPC querier service."] pub struct QueryClient < T > { inner : tonic :: client :: Grpc < T > , } impl QueryClient < tonic :: transport :: Channel > { # [doc = r" Attempt to create a new client by connecting to a given endpoint."] pub async fn connect < D > (dst : D) -> Result < Self , tonic :: transport :: Error > where D : std :: convert :: TryInto < tonic :: transport :: Endpoint > , D :: Error : Into < StdError > , { let conn = tonic :: transport :: Endpoint :: new (dst) ? . connect () . await ? ; Ok (Self :: new (conn)) } } impl < T > QueryClient < T > where T : tonic :: client :: GrpcService < tonic :: body :: BoxBody > , T :: ResponseBody : Body + HttpBody + Send + 'static , T :: Error : Into < StdError > , < T :: ResponseBody as HttpBody > :: Error : Into < StdError > + Send , { pub fn new (inner : T) -> Self { let inner = tonic :: client :: Grpc :: new (inner) ; Self { inner } } pub fn with_interceptor (inner : T , interceptor : impl Into < tonic :: Interceptor >) -> Self { let inner = tonic :: client :: Grpc :: with_interceptor (inner , interceptor) ; Self { inner } } # [doc = " Validators queries all validators that match the given status."] pub async fn validators (& mut self , request : impl tonic :: IntoRequest < super :: QueryValidatorsRequest > ,) -> Result < tonic :: Response < super :: QueryValidatorsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/Validators") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Validator queries validator info for given validator address."] pub async fn validator (& mut self , request : impl tonic :: IntoRequest < super :: QueryValidatorRequest > ,) -> Result < tonic :: Response < super :: QueryValidatorResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/Validator") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " ValidatorDelegations queries delegate info for given validator."] pub async fn validator_delegations (& mut self , request : impl tonic :: IntoRequest < super :: QueryValidatorDelegationsRequest > ,) -> Result < tonic :: Response < super :: QueryValidatorDelegationsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/ValidatorDelegations") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " ValidatorUnbondingDelegations queries unbonding delegations of a validator."] pub async fn validator_unbonding_delegations (& mut self , request : impl tonic :: IntoRequest < super :: QueryValidatorUnbondingDelegationsRequest > ,) -> Result < tonic :: Response < super :: QueryValidatorUnbondingDelegationsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/ValidatorUnbondingDelegations") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Delegation queries delegate info for given validator delegator pair."] pub async fn delegation (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegationRequest > ,) -> Result < tonic :: Response < super :: QueryDelegationResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/Delegation") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " UnbondingDelegation queries unbonding info for given validator delegator"] # [doc = " pair."] pub async fn unbonding_delegation (& mut self , request : impl tonic :: IntoRequest < super :: QueryUnbondingDelegationRequest > ,) -> Result < tonic :: Response < super :: QueryUnbondingDelegationResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/UnbondingDelegation") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " DelegatorDelegations queries all delegations of a given delegator address."] pub async fn delegator_delegations (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegatorDelegationsRequest > ,) -> Result < tonic :: Response < super :: QueryDelegatorDelegationsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/DelegatorDelegations") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " DelegatorUnbondingDelegations queries all unbonding delegations of a given"] # [doc = " delegator address."] pub async fn delegator_unbonding_delegations (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegatorUnbondingDelegationsRequest > ,) -> Result < tonic :: Response < super :: QueryDelegatorUnbondingDelegationsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Redelegations queries redelegations of given address."] pub async fn redelegations (& mut self , request : impl tonic :: IntoRequest < super :: QueryRedelegationsRequest > ,) -> Result < tonic :: Response < super :: QueryRedelegationsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/Redelegations") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " DelegatorValidators queries all validators info for given delegator"] # [doc = " address."] pub async fn delegator_validators (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegatorValidatorsRequest > ,) -> Result < tonic :: Response < super :: QueryDelegatorValidatorsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/DelegatorValidators") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " DelegatorValidator queries validator info for given delegator validator"] # [doc = " pair."] pub async fn delegator_validator (& mut self , request : impl tonic :: IntoRequest < super :: QueryDelegatorValidatorRequest > ,) -> Result < tonic :: Response < super :: QueryDelegatorValidatorResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/DelegatorValidator") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " HistoricalInfo queries the historical info for given height."] pub async fn historical_info (& mut self , request : impl tonic :: IntoRequest < super :: QueryHistoricalInfoRequest > ,) -> Result < tonic :: Response < super :: QueryHistoricalInfoResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/HistoricalInfo") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Pool queries the pool info."] pub async fn pool (& mut self , request : impl tonic :: IntoRequest < super :: QueryPoolRequest > ,) -> Result < tonic :: Response < super :: QueryPoolResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/Pool") ; self . inner . unary (request . into_request () , path , codec) . await } # [doc = " Parameters queries the staking parameters."] pub async fn params (& mut self , request : impl tonic :: IntoRequest < super :: QueryParamsRequest > ,) -> Result < tonic :: Response < super :: QueryParamsResponse > , tonic :: Status > { self . inner . ready () . await . map_err (| e | { tonic :: Status :: new (tonic :: Code :: Unknown , format ! ("Service was not ready: {}" , e . into ())) }) ? ; let codec = tonic :: codec :: ProstCodec :: default () ; let path = http :: uri :: PathAndQuery :: from_static ("/cosmos.staking.v1beta1.Query/Params") ; self . inner . unary (request . into_request () , path , codec) . await } } impl < T : Clone > Clone for QueryClient < T > { fn clone (& self) -> Self { Self { inner : self . inner . clone () , } } } impl < T > std :: fmt :: Debug for QueryClient < T > { fn fmt (& self , f : & mut std :: fmt :: Formatter < '_ >) -> std :: fmt :: Result { write ! (f , "QueryClient {{ ... }}") } } } \ No newline at end of file diff --git a/proto/src/prost/ibc.applications.transfer.v1.rs b/proto/src/prost/ibc.applications.transfer.v1.rs index cce5db71ec..6ab1a8b8bd 100644 --- a/proto/src/prost/ibc.applications.transfer.v1.rs +++ b/proto/src/prost/ibc.applications.transfer.v1.rs @@ -72,16 +72,6 @@ pub struct Params { #[prost(bool, tag="2")] pub receive_enabled: bool, } -/// GenesisState defines the ibc-transfer genesis state -#[derive(Clone, PartialEq, ::prost::Message)] -pub struct GenesisState { - #[prost(string, tag="1")] - pub port_id: std::string::String, - #[prost(message, repeated, tag="2")] - pub denom_traces: ::std::vec::Vec, - #[prost(message, optional, tag="3")] - pub params: ::std::option::Option, -} /// QueryDenomTraceRequest is the request type for the Query/DenomTrace RPC /// method #[derive(Clone, PartialEq, ::prost::Message)] @@ -128,3 +118,13 @@ pub struct QueryParamsResponse { #[prost(message, optional, tag="1")] pub params: ::std::option::Option, } +/// GenesisState defines the ibc-transfer genesis state +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GenesisState { + #[prost(string, tag="1")] + pub port_id: std::string::String, + #[prost(message, repeated, tag="2")] + pub denom_traces: ::std::vec::Vec, + #[prost(message, optional, tag="3")] + pub params: ::std::option::Option, +} diff --git a/proto/src/prost/tendermint.abci.rs b/proto/src/prost/tendermint.abci.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/proto/src/prost/tendermint.crypto.rs b/proto/src/prost/tendermint.crypto.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/proto/src/prost/tendermint.libs.bits.rs b/proto/src/prost/tendermint.libs.bits.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/proto/src/prost/tendermint.types.rs b/proto/src/prost/tendermint.types.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/proto/src/prost/tendermint.version.rs b/proto/src/prost/tendermint.version.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/relayer-cli/README.md b/relayer-cli/README.md index 43290889c7..7f9690ab5f 100644 --- a/relayer-cli/README.md +++ b/relayer-cli/README.md @@ -19,6 +19,9 @@ relayer-cli -c config.toml tx raw create-client dest_chain_id src_chain_id dest_ relayer-cli -c config.toml tx raw conn-init dest_chain_id src_chain_id dest_client_id src_client_id dest_connection_id -d src_connection_id -k seed_file.json + +relayer-cli -c config.toml tx raw conn-try dest_chain_id src_chain_id dest_client_id src_client_id dest_connection_id src_connection_id + -k seed_file.json ``` Note: This is work in progress, more commands will be implemented and tested with gaia stargate-4 chains. diff --git a/relayer-cli/src/commands/query/channel.rs b/relayer-cli/src/commands/query/channel.rs index 40193362e8..aee3d1ca09 100644 --- a/relayer-cli/src/commands/query/channel.rs +++ b/relayer-cli/src/commands/query/channel.rs @@ -102,7 +102,9 @@ impl Runnable for QueryChannelEndCmd { opts.proof, ) .map_err(|e| Kind::Query.context(e).into()) - .and_then(|v| ChannelEnd::decode_vec(&v).map_err(|e| Kind::Query.context(e).into())); + .and_then(|v| { + ChannelEnd::decode_vec(&v.value).map_err(|e| Kind::Query.context(e).into()) + }); match res { Ok(cs) => status_info!("Result for channel end query: ", "{:?}", cs), diff --git a/relayer-cli/src/commands/query/client.rs b/relayer-cli/src/commands/query/client.rs index 47139a306a..20ec1dc53d 100644 --- a/relayer-cli/src/commands/query/client.rs +++ b/relayer-cli/src/commands/query/client.rs @@ -85,7 +85,7 @@ impl Runnable for QueryClientStateCmd { ) .map_err(|e| Kind::Query.context(e).into()) .and_then(|v| { - AnyClientState::decode_vec(&v).map_err(|e| Kind::Query.context(e).into()) + AnyClientState::decode_vec(&v.value).map_err(|e| Kind::Query.context(e).into()) }); match res { Ok(cs) => status_info!("client state query result: ", "{:?}", cs), @@ -183,7 +183,7 @@ impl Runnable for QueryClientConsensusCmd { ) .map_err(|e| Kind::Query.context(e).into()) .and_then(|v| { - AnyConsensusState::decode_vec(&v).map_err(|e| Kind::Query.context(e).into()) + AnyConsensusState::decode_vec(&v.value).map_err(|e| Kind::Query.context(e).into()) }); match res { @@ -289,7 +289,9 @@ impl Runnable for QueryClientConnectionsCmd { false, ) .map_err(|e| Kind::Query.context(e).into()) - .and_then(|v| ConnectionIDs::decode_vec(&v).map_err(|e| Kind::Query.context(e).into())); + .and_then(|v| { + ConnectionIDs::decode_vec(&v.value).map_err(|e| Kind::Query.context(e).into()) + }); match res { Ok(cs) => status_info!("client connections query result: ", "{:?}", cs), Err(e) => status_info!("client connections query error", "{}", e), diff --git a/relayer-cli/src/commands/query/connection.rs b/relayer-cli/src/commands/query/connection.rs index 111c803e3b..6ce3ab02de 100644 --- a/relayer-cli/src/commands/query/connection.rs +++ b/relayer-cli/src/commands/query/connection.rs @@ -1,9 +1,7 @@ -use std::convert::TryInto; - use abscissa_core::{Command, Options, Runnable}; use ibc::ics03_connection::connection::ConnectionEnd; use ibc::ics24_host::error::ValidationError; -use ibc::ics24_host::identifier::ConnectionId; +use ibc::ics24_host::identifier::{ChainId as ICSChainId, ConnectionId}; use relayer::chain::{Chain, CosmosSDKChain}; use relayer::config::{ChainConfig, Config}; use tendermint::chain::Id as ChainId; @@ -64,6 +62,7 @@ impl QueryConnectionEndCmd { } } +// cargo run --bin relayer -- -c relayer/tests/config/fixtures/simple_config.toml query connection end ibc-test connectionidone --height 3 impl Runnable for QueryConnectionEndCmd { fn run(&self) { let config = app_config(); @@ -78,14 +77,14 @@ impl Runnable for QueryConnectionEndCmd { status_info!("Options", "{:?}", opts); let chain = CosmosSDKChain::from_config(chain_config).unwrap(); - // run without proof: - // cargo run --bin relayer -- -c relayer/tests/config/fixtures/simple_config.toml query connection end ibc-test connectionidone --height 3 -p false + let height = ibc::Height::new( + ICSChainId::chain_version(chain.id().to_string()), + opts.height, + ); + + // TODO - any value in querying with proof from the CLI? let res: Result = chain - .query_connection( - &opts.connection_id, - opts.height.try_into().unwrap(), - opts.proof, - ) + .query_connection(&opts.connection_id, height) .map_err(|e| Kind::Query.context(e).into()); match res { diff --git a/relayer-cli/src/commands/tx.rs b/relayer-cli/src/commands/tx.rs index 445ad8660e..c347d172c3 100644 --- a/relayer-cli/src/commands/tx.rs +++ b/relayer-cli/src/commands/tx.rs @@ -24,10 +24,6 @@ pub enum TxRawCommands { #[options(help = "get usage information")] Help(Help), - /// The `tx raw conn-init` subcommand - #[options(help = "tx raw conn-init")] - ConnInit(connection::TxRawConnInitCmd), - /// The `tx raw client-create` subcommand submits a MsgCreateClient in a transaction to a chain #[options(help = "tx raw create-client")] CreateClient(TxCreateClientCmd), @@ -35,4 +31,12 @@ pub enum TxRawCommands { /// The `tx raw client-update` subcommand submits a MsgUpdateClient in a transaction to a chain #[options(help = "tx raw update-client")] UpdateClient(TxUpdateClientCmd), + + /// The `tx raw conn-init` subcommand + #[options(help = "tx raw conn-init")] + ConnInit(connection::TxRawConnInitCmd), + + /// The `tx raw conn-try` subcommand + #[options(help = "tx raw conn-try")] + ConnTry(connection::TxRawConnTryCmd), } diff --git a/relayer-cli/src/commands/tx/client.rs b/relayer-cli/src/commands/tx/client.rs index 3b26fc1947..ffb2bdd604 100644 --- a/relayer-cli/src/commands/tx/client.rs +++ b/relayer-cli/src/commands/tx/client.rs @@ -2,7 +2,9 @@ use abscissa_core::{Command, Options, Runnable}; use ibc::ics24_host::identifier::ClientId; -use relayer::tx::client::{create_client, update_client, ClientOptions}; +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}; @@ -22,9 +24,6 @@ pub struct TxCreateClientCmd { )] dest_client_id: ClientId, - #[options(help = "account sequence of the signer", short = "s")] - account_sequence: u64, - #[options( help = "json key file for the signer, must include mnemonic", short = "k" @@ -38,7 +37,6 @@ impl Runnable for TxCreateClientCmd { &self.dest_chain_id, &self.src_chain_id, &self.dest_client_id, - self.account_sequence, &self.seed_file, ) { Err(err) => { @@ -49,8 +47,8 @@ impl Runnable for TxCreateClientCmd { }; status_info!("Message", "{:?}", opts); - let res: Result, Error> = - create_client(opts).map_err(|e| Kind::Tx.context(e).into()); + let res: Result = + build_create_client_and_send(opts).map_err(|e| Kind::Tx.context(e).into()); match res { Ok(receipt) => status_ok!("Success", "client created: {:?}", receipt), @@ -73,9 +71,6 @@ pub struct TxUpdateClientCmd { )] dest_client_id: ClientId, - #[options(help = "account sequence of the signer", short = "s")] - account_sequence: u64, - #[options( help = "json key file for the signer, must include mnemonic", short = "k" @@ -89,7 +84,6 @@ impl Runnable for TxUpdateClientCmd { &self.dest_chain_id, &self.src_chain_id, &self.dest_client_id, - self.account_sequence, &self.seed_file, ) { Err(err) => { @@ -100,8 +94,8 @@ impl Runnable for TxUpdateClientCmd { }; status_info!("Message", "{:?}", opts); - let res: Result, Error> = - update_client(opts).map_err(|e| Kind::Tx.context(e).into()); + let res: Result = + build_update_client_and_send(opts).map_err(|e| Kind::Tx.context(e).into()); match res { Ok(receipt) => status_ok!("Success", "client updated: {:?}", receipt), @@ -114,7 +108,6 @@ fn validate_common_options( dest_chain_id: &str, src_chain_id: &str, dest_client_id: &ClientId, - account_sequence: u64, seed_file: &str, ) -> Result { let config = app_config(); @@ -150,6 +143,5 @@ fn validate_common_options( dest_chain_config: dest_chain_config.clone(), src_chain_config: src_chain_config.clone(), signer_seed, - account_sequence, }) } diff --git a/relayer-cli/src/commands/tx/connection.rs b/relayer-cli/src/commands/tx/connection.rs index c7091533c5..61c3a4628d 100644 --- a/relayer-cli/src/commands/tx/connection.rs +++ b/relayer-cli/src/commands/tx/connection.rs @@ -5,7 +5,10 @@ use abscissa_core::{Command, Options, Runnable}; use ibc::ics24_host::identifier::{ClientId, ConnectionId}; use relayer::config::Config; -use relayer::tx::connection::{conn_init, ConnectionOpenInitOptions}; +use relayer::tx::connection::{ + build_conn_init_and_send, build_conn_try_and_send, ConnectionOpenInitOptions, + ConnectionOpenTryOptions, +}; use crate::error::{Error, Kind}; @@ -43,6 +46,7 @@ impl TxRawConnInitCmd { .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() @@ -54,12 +58,12 @@ impl TxRawConnInitCmd { })?; let opts = ConnectionOpenInitOptions { + dest_chain_config: dest_chain_config.clone(), + src_chain_config: src_chain_config.clone(), dest_client_id: self.dest_client_id.clone(), src_client_id: self.src_client_id.clone(), dest_connection_id: self.dest_connection_id.clone(), src_connection_id: self.src_connection_id.clone(), - dest_chain_config: dest_chain_config.clone(), - src_chain_config: src_chain_config.clone(), signer_seed, }; @@ -80,7 +84,8 @@ impl Runnable for TxRawConnInitCmd { }; status_info!("Message", "{:?}", opts); - let res: Result, Error> = conn_init(&opts).map_err(|e| Kind::Tx.context(e).into()); + 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), @@ -88,3 +93,85 @@ impl Runnable for TxRawConnInitCmd { } } } + +#[derive(Clone, Command, Debug, Options)] +pub struct TxRawConnTryCmd { + #[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 TxRawConnTryCmd { + 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 = ConnectionOpenTryOptions { + 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 TxRawConnTryCmd { + 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_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), + } + } +} diff --git a/relayer-cli/tests/integration.rs b/relayer-cli/tests/integration.rs index 6084e8708f..b486a29592 100644 --- a/relayer-cli/tests/integration.rs +++ b/relayer-cli/tests/integration.rs @@ -93,7 +93,8 @@ fn query_channel_id() { Height::from(0_u32), false, ) - .unwrap(), + .unwrap() + .value, ) .unwrap(); @@ -117,7 +118,8 @@ fn query_client_id() { Height::from(0_u32), false, ) - .unwrap(), + .unwrap() + .value, ) .unwrap(); diff --git a/relayer/src/chain.rs b/relayer/src/chain.rs index 7d8dc20d13..314dd0f093 100644 --- a/relayer/src/chain.rs +++ b/relayer/src/chain.rs @@ -8,7 +8,8 @@ use serde::{de::DeserializeOwned, Serialize}; use tendermint_proto::DomainType; // TODO - tendermint deps should not be here -use tendermint::account::Id as AccountId; +//use tendermint::account::Id as AccountId; +use ibc_proto::ibc::core::commitment::v1::MerkleProof; use tendermint::block::Height; use tendermint::chain::Id as ChainId; use tendermint_light_client::types::TrustThreshold; @@ -17,27 +18,40 @@ use tendermint_rpc::Client as RpcClient; use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader}; use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient; use ibc::ics02_client::msgs::update_client::MsgUpdateAnyClient; +use ibc::Height as ICSHeight; + use ibc::ics02_client::state::{ClientState, ConsensusState}; -use ibc::ics03_connection::connection::{ConnectionEnd, Counterparty}; +use ibc::ics03_connection::connection::{ConnectionEnd, Counterparty, State}; use ibc::ics03_connection::msgs::conn_open_init::MsgConnectionOpenInit; -use ibc::ics23_commitment::commitment::CommitmentPrefix; +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}; use ibc::ics24_host::Path; +use ibc::ics24_host::Path::ClientConsensusState as ClientConsensusPath; +use ibc::proofs::{ConsensusProof, Proofs}; use ibc::tx_msg::Msg; -use ibc::Height as ICSHeight; use crate::client::LightClient; use crate::config::ChainConfig; use crate::error::{Error, Kind}; use crate::keyring::store::{KeyEntry, KeyRing}; -use crate::tx::connection::ConnectionOpenInitOptions; +use crate::tx::connection::{ConnectionOpenInitOptions, ConnectionOpenTryOptions}; use crate::util::block_on; pub(crate) mod cosmos; pub use cosmos::CosmosSDKChain; - pub mod handle; +/// Generic query response type +/// TODO - will slowly move to GRPC protobuf specs for queries +pub struct QueryResponse { + pub value: Vec, + pub proof: MerkleProof, + pub height: Height, +} + /// Defines a blockchain as understood by the relayer pub trait Chain { /// TODO - Should these be part of the Chain trait? @@ -61,7 +75,16 @@ pub trait Chain { type Error: Into>; /// Perform a generic `query`, and return the corresponding response data. - fn query(&self, data: Path, height: Height, prove: bool) -> Result, Self::Error>; + // TODO - migrate callers to use ics_query() and then remove this + fn query(&self, data: Path, height: Height, prove: bool) -> Result; + + /// Perform a generic ICS `query`, and return the corresponding response data. + fn ics_query( + &self, + data: Path, + height: ICSHeight, + prove: bool, + ) -> Result; /// send a transaction with `msgs` to chain. fn send( @@ -70,9 +93,10 @@ pub trait Chain { key: KeyEntry, memo: String, timeout_height: u64, - ) -> Result, Self::Error>; + ) -> Result; /// Returns the chain's identifier + /// TODO - move to ICS Chain Id fn id(&self) -> &ChainId { &self.config().id } @@ -98,14 +122,56 @@ pub trait Chain { fn query_client_state( &self, client_id: &ClientId, - height: Height, - proof: bool, + height: ICSHeight, ) -> Result; + fn proven_client_state( + &self, + client_id: &ClientId, + height: ICSHeight, + ) -> Result<(AnyClientState, MerkleProof), Error>; + fn build_client_state(&self, height: ICSHeight) -> Result; fn build_consensus_state(&self, height: ICSHeight) -> Result; + fn proven_connection( + &self, + connection_id: &ConnectionId, + height: ICSHeight, + ) -> Result<(ConnectionEnd, MerkleProof), Error> { + let res = self + .ics_query(Path::Connections(connection_id.clone()), height, true) + .map_err(|e| Kind::Query.context(e))?; + let connection_end = + ConnectionEnd::decode_vec(&res.value).map_err(|e| Kind::Query.context(e))?; + + Ok((connection_end, res.proof)) + } + + fn proven_client_consensus( + &self, + client_id: &ClientId, + consensus_height: ICSHeight, + height: ICSHeight, + ) -> Result<(AnyConsensusState, MerkleProof), Error> { + let res = self + .ics_query( + ClientConsensusPath { + client_id: client_id.clone(), + epoch: consensus_height.version_number, + height: consensus_height.version_height, + }, + height, + true, + ) + .map_err(|e| Kind::Query.context(e))?; + let consensus_state = + AnyConsensusState::decode_vec(&res.value).map_err(|e| Kind::Query.context(e))?; + + Ok((consensus_state, res.proof)) + } + fn build_header( &self, trusted_height: ICSHeight, @@ -113,20 +179,85 @@ pub trait Chain { ) -> Result; fn query_commitment_prefix(&self) -> Result { + // TODO - do a real chain query Ok(CommitmentPrefix::from( self.config().store_prefix.as_bytes().to_vec(), )) } + fn query_compatible_versions(&self) -> Result, Error> { + // TODO - do a real chain query + Ok(get_compatible_versions()) + } + fn query_connection( &self, connection_id: &ConnectionId, - height: Height, - proof: bool, + height: ICSHeight, ) -> Result { Ok(self - .query(Path::Connections(connection_id.clone()), height, proof) + .ics_query(Path::Connections(connection_id.clone()), height, false) .map_err(|e| Kind::Query.context(e)) - .and_then(|v| ConnectionEnd::decode_vec(&v).map_err(|e| Kind::Query.context(e)))?) + .and_then(|v| { + ConnectionEnd::decode_vec(&v.value).map_err(|e| Kind::Query.context(e)) + })?) + } + + /// Build the required proofs for connection handshake messages. The proofs are obtained from + /// queries at height - 1 + fn build_connection_proofs( + &self, + message_type: ConnectionMsgType, + connection_id: &ConnectionId, + client_id: &ClientId, + height: ICSHeight, + ) -> Result { + // Set the height of the queries at height - 1 + let query_height = height + .decrement() + .map_err(|e| Kind::InvalidHeight.context(e))?; + + let connection_proof = + CommitmentProof::from(self.proven_connection(&connection_id, query_height)?.1); + + let mut client_proof: Option = None; + let mut consensus_proof = None; + + match message_type { + ConnectionMsgType::OpenTry | ConnectionMsgType::OpenAck => { + let (client_state, client_state_proof) = + self.proven_client_state(&client_id, query_height)?; + + client_proof = Some(CommitmentProof::from(client_state_proof)); + + let consensus_state_proof = self + .proven_client_consensus( + &client_id, + client_state.latest_height(), + query_height, + )? + .1; + + consensus_proof = Some( + ConsensusProof::new( + CommitmentProof::from(consensus_state_proof), + client_state.latest_height(), + ) + .map_err(|e| { + Kind::ConnOpenTry( + connection_id.clone(), + "failed to build consensus proof".to_string(), + ) + .context(e) + })?, + ); + } + _ => {} + } + + Ok( + Proofs::new(connection_proof, client_proof, consensus_proof, height) + .map_err(|e| Kind::MalformedProof)?, + ) } } diff --git a/relayer/src/chain/cosmos.rs b/relayer/src/chain/cosmos.rs index 5a3c624b1e..5fec24365b 100644 --- a/relayer/src/chain/cosmos.rs +++ b/relayer/src/chain/cosmos.rs @@ -10,7 +10,10 @@ use prost_types::Any; use bitcoin::hashes::hex::ToHex; use k256::ecdsa::{SigningKey, VerifyKey}; +use tendermint_proto::crypto::ProofOps; use tendermint_proto::DomainType; +use tendermint_rpc::endpoint::abci_query::AbciQuery; +use tendermint_rpc::endpoint::broadcast; use tendermint::abci::{Path as TendermintABCIPath, Transaction}; use tendermint::account::Id as AccountId; @@ -22,8 +25,17 @@ use tendermint_light_client::types::{LightBlock, SignedHeader, TrustThreshold, V use tendermint_rpc::Client; use tendermint_rpc::HttpClient; +// Support for GRPC +use ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient; +use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest}; +use ibc_proto::cosmos::staking::v1beta1::Params as StakingParams; + +use tonic::codegen::http::Uri; + use ibc_proto::cosmos::base::v1beta1::Coin; use ibc_proto::cosmos::tx::v1beta1::mode_info::{Single, Sum}; +use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, Fee, ModeInfo, SignDoc, SignerInfo, TxBody, TxRaw}; +use ibc_proto::ibc::core::commitment::v1::MerkleProof; use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader}; use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient; @@ -35,24 +47,20 @@ use ibc::ics07_tendermint::consensus_state::ConsensusState; use ibc::ics07_tendermint::header::Header as TendermintHeader; use ibc::ics23_commitment::commitment::CommitmentPrefix; use ibc::ics24_host::identifier::{ChainId, ClientId, ConnectionId}; +use ibc::ics24_host::Path::ClientConsensusState as ClientConsensusPath; use ibc::ics24_host::Path::ClientState as ClientStatePath; use ibc::ics24_host::{Path, IBC_QUERY_PATH}; use ibc::tx_msg::Msg; use ibc::Height as ICSHeight; -use ibc_proto::cosmos::tx::v1beta1::{AuthInfo, Fee, ModeInfo, SignDoc, SignerInfo, TxBody, TxRaw}; use super::Chain; +use crate::chain::QueryResponse; use crate::client::tendermint::LightClient; use crate::config::ChainConfig; use crate::error::{Error, Kind}; use crate::keyring::store::{KeyEntry, KeyRing, KeyRingOperations, StoreBackend}; use crate::util::block_on; -// Support for GRPC -use ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient; -use ibc_proto::cosmos::auth::v1beta1::{BaseAccount, QueryAccountRequest}; -use tonic::codegen::http::Uri; - pub struct CosmosSDKChain { config: ChainConfig, rpc_client: HttpClient, @@ -80,9 +88,30 @@ impl CosmosSDKChain { } /// The unbonding period of this chain - fn unbonding_period(&self) -> Duration { - // TODO - query chain - Duration::from_secs(24 * 7 * 3 * 3600) + async fn unbonding_period(&self) -> Result { + // TODO - generalize this + let grpc_addr = + Uri::from_str(&self.config().grpc_addr).map_err(|e| Kind::Grpc.context(e))?; + let mut client = + ibc_proto::cosmos::staking::v1beta1::query_client::QueryClient::connect(grpc_addr) + .await + .map_err(|e| Kind::Grpc.context(e))?; + + let request = + tonic::Request::new(ibc_proto::cosmos::staking::v1beta1::QueryParamsRequest {}); + + let response = client + .params(request) + .await + .map_err(|e| Kind::Grpc.context(e))?; + + let res = response + .into_inner() + .params + .ok_or_else(|| Kind::Grpc.context("none staking params".to_string()))? + .unbonding_time + .ok_or_else(|| Kind::Grpc.context("none unbonding time".to_string()))?; + Ok(Duration::from_secs(res.seconds as u64)) } /// Query the consensus parameters via an RPC query @@ -158,7 +187,18 @@ impl Chain for CosmosSDKChain { type ClientState = ClientState; type Error = Error; - fn query(&self, data: Path, height: Height, prove: bool) -> Result, Self::Error> { + fn ics_query( + &self, + data: Path, + height: ICSHeight, + prove: bool, + ) -> Result { + let height = + Height::try_from(height.version_height).map_err(|e| Kind::InvalidHeight.context(e))?; + self.query(data, height, prove) + } + + fn query(&self, data: Path, height: Height, prove: bool) -> Result { let path = TendermintABCIPath::from_str(IBC_QUERY_PATH).unwrap(); if !data.is_provable() & prove { @@ -178,13 +218,14 @@ impl Chain for CosmosSDKChain { } /// Send a transaction that includes the specified messages + /// TODO - split the messages in multiple Tx-es such that they don't exceed some max size fn send( &mut self, proto_msgs: Vec, key: KeyEntry, memo: String, timeout_height: u64, - ) -> Result, Error> { + ) -> Result { // Create TxBody let body = TxBody { messages: proto_msgs.to_vec(), @@ -230,7 +271,7 @@ impl Chain for CosmosSDKChain { let fee = Some(Fee { amount: vec![coin], - gas_limit: 100000, + gas_limit: 150000, payer: "".to_string(), granter: "".to_string(), }); @@ -266,10 +307,9 @@ impl Chain for CosmosSDKChain { let mut txraw_buf = Vec::new(); prost::Message::encode(&tx_raw, &mut txraw_buf).unwrap(); - //println!("TxRAW {:?}", hex::encode(txraw_buf.clone())); - //let signed = sign(sign_doc); - let response = block_on(broadcast_tx(self, txraw_buf)).map_err(|e| Kind::Rpc.context(e))?; + let response = + block_on(broadcast_tx_commit(self, txraw_buf)).map_err(|e| Kind::Rpc.context(e))?; Ok(response) } @@ -312,13 +352,28 @@ impl Chain for CosmosSDKChain { fn query_client_state( &self, client_id: &ClientId, - height: Height, - proof: bool, + height: ICSHeight, ) -> Result { Ok(self - .query(ClientStatePath(client_id.clone()), height, proof) + .ics_query(ClientStatePath(client_id.clone()), height, false) .map_err(|e| Kind::Query.context(e)) - .and_then(|v| AnyClientState::decode_vec(&v).map_err(|e| Kind::Query.context(e)))?) + .and_then(|v| { + AnyClientState::decode_vec(&v.value).map_err(|e| Kind::Query.context(e)) + })?) + } + + fn proven_client_state( + &self, + client_id: &ClientId, + height: ICSHeight, + ) -> Result<(AnyClientState, MerkleProof), Error> { + let res = self + .ics_query(ClientStatePath(client_id.clone()), height, true) + .map_err(|e| Kind::Query.context(e))?; + + let state = AnyClientState::decode_vec(&res.value).map_err(|e| Kind::Query.context(e))?; + + Ok((state, res.proof)) } fn build_client_state(&self, height: ICSHeight) -> Result { @@ -327,7 +382,7 @@ impl Chain for CosmosSDKChain { self.id().to_string(), self.config.trust_threshold, self.config.trusting_period, - self.unbonding_period(), + block_on(self.unbonding_period())?, Duration::from_millis(3000), // TODO - get it from src config when avail height, ICSHeight::zero(), @@ -396,7 +451,7 @@ async fn abci_query( data: String, height: Height, prove: bool, -) -> Result, anomaly::Error> { +) -> Result> { let height = if height.value() == 0 { None } else { @@ -418,16 +473,33 @@ async fn abci_query( // Fail due to empty response value (nothing to decode). return Err(Kind::EmptyResponseValue.into()); } + if prove && response.proof.is_none() { + // Fail due to empty proof + return Err(Kind::EmptyResponseProof.into()); + } + + let raw_proof_ops = response + .proof + .map(ProofOps::try_from) + .transpose() + .map_err(|e| Kind::MalformedProof.context(e))?; + + let response = QueryResponse { + value: response.value, + proof: MerkleProof { + proof: raw_proof_ops, + }, + height: response.height, + }; - Ok(response.value) + Ok(response) } -/// Perform a generic `broadcast_tx`, and return the corresponding deserialized response data. -async fn broadcast_tx( +/// Perform a `broadcast_tx_sync`, and return the corresponding deserialized response data. +async fn broadcast_tx_sync( chain: &CosmosSDKChain, data: Vec, -) -> Result, anomaly::Error> { - // Use the Tendermint-rs RPC client to do the query. +) -> Result> { let response = chain .rpc_client() .broadcast_tx_sync(data.into()) @@ -440,7 +512,22 @@ async fn broadcast_tx( return Err(Kind::Rpc.context(response.log.to_string()).into()); } - Ok(response.data.as_bytes().to_vec()) + Ok(serde_json::to_string_pretty(&response).unwrap()) +} + +/// Perform a `broadcast_tx_commit`, and return the corresponding deserialized response data. +/// TODO - move send() to this once RPC tendermint response is fixed +async fn broadcast_tx_commit( + chain: &CosmosSDKChain, + data: Vec, +) -> Result> { + let response = chain + .rpc_client() + .broadcast_tx_commit(data.into()) + .await + .map_err(|e| Kind::Rpc.context(e))?; + + Ok(serde_json::to_string(&response).unwrap()) } fn fetch_signed_header(client: &HttpClient, height: Height) -> Result { @@ -462,9 +549,10 @@ fn fetch_validator_set(client: &HttpClient, height: Height) -> Result Result { let grpc_addr = Uri::from_str(&chain.config().grpc_addr).map_err(|e| Kind::Grpc.context(e))?; - let mut client = QueryClient::connect(grpc_addr) - .await - .map_err(|e| Kind::Grpc.context(e))?; + let mut client = + ibc_proto::cosmos::auth::v1beta1::query_client::QueryClient::connect(grpc_addr) + .await + .map_err(|e| Kind::Grpc.context(e))?; let request = tonic::Request::new(QueryAccountRequest { address }); diff --git a/relayer/src/error.rs b/relayer/src/error.rs index 1c0a76d85b..e3311fcd80 100644 --- a/relayer/src/error.rs +++ b/relayer/src/error.rs @@ -42,6 +42,14 @@ pub enum Kind { #[error("Empty response value")] EmptyResponseValue, + /// Response does not contain a proof + #[error("Empty response proof")] + EmptyResponseProof, + + /// Response does not contain a proof + #[error("Malformed proof")] + MalformedProof, + /// Invalid height #[error("Invalid height")] InvalidHeight, @@ -58,6 +66,10 @@ pub enum Kind { #[error("Failed to build conn open init {0}: {1}")] ConnOpenInit(ConnectionId, String), + /// Connection open try failure + #[error("Failed to build conn open try {0}: {1}")] + ConnOpenTry(ConnectionId, String), + /// A message transaction failure #[error("Message transaction failure: {0}")] MessageTransaction(String), diff --git a/relayer/src/keyring/store.rs b/relayer/src/keyring/store.rs index 7deee972c6..dce105c7b4 100644 --- a/relayer/src/keyring/store.rs +++ b/relayer/src/keyring/store.rs @@ -68,8 +68,8 @@ impl KeyRingOperations for KeyRing { /// Get key from seed file fn key_from_seed_file(&mut self, key_file_content: &str) -> Result { - let key_json: Value = serde_json::from_str(key_file_content) - .map_err(|e| Kind::InvalidKey.context("failed to parse key seed file"))?; + let key_json: Value = + serde_json::from_str(key_file_content).map_err(|e| Kind::InvalidKey.context(e))?; let signer: AccountId; let key: KeyEntry; diff --git a/relayer/src/tx/client.rs b/relayer/src/tx/client.rs index 87888d62bd..1fb78cd4c0 100644 --- a/relayer/src/tx/client.rs +++ b/relayer/src/tx/client.rs @@ -1,20 +1,19 @@ -use bitcoin::hashes::hex::ToHex; -use prost_types::Any; use std::convert::TryInto; +use std::str::FromStr; use std::time::Duration; -use ibc::downcast; -use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader}; -use std::str::FromStr; +use bitcoin::hashes::hex::ToHex; +use prost_types::Any; use tendermint::account::Id as AccountId; use tendermint_light_client::types::TrustThreshold; +use tendermint_proto::DomainType; use ibc_proto::ibc::core::client::v1::MsgCreateClient as RawMsgCreateClient; use ibc_proto::ibc::core::client::v1::MsgUpdateClient as RawMsgUpdateClient; +use ibc::ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader}; use ibc::ics02_client::client_type::ClientType; -use ibc::ics02_client::height::Height; use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient; use ibc::ics02_client::msgs::update_client::MsgUpdateAnyClient; use ibc::ics07_tendermint::header::Header as TendermintHeader; @@ -22,13 +21,12 @@ use ibc::ics24_host::identifier::{ChainId, ClientId}; use ibc::ics24_host::Path::ClientConsensusState; use ibc::ics24_host::Path::ClientState as ClientStatePath; use ibc::tx_msg::Msg; +use ibc::Height; use crate::chain::{Chain, CosmosSDKChain}; use crate::config::ChainConfig; use crate::error::{Error, Kind}; - use crate::keyring::store::{KeyEntry, KeyRingOperations}; -use tendermint_proto::DomainType; #[derive(Clone, Debug)] pub struct ClientOptions { @@ -36,68 +34,99 @@ pub struct ClientOptions { pub dest_chain_config: ChainConfig, pub src_chain_config: ChainConfig, pub signer_seed: String, - pub account_sequence: u64, } -pub fn create_client(opts: ClientOptions) -> Result, Error> { - // Get the source and destination chains. - let src_chain = CosmosSDKChain::from_config(opts.clone().src_chain_config)?; - let mut dest_chain = CosmosSDKChain::from_config(opts.clone().dest_chain_config)?; - +pub fn build_create_client( + dest_chain: &mut CosmosSDKChain, + src_chain: &CosmosSDKChain, + dest_client_id: ClientId, + signer_seed: &str, +) -> 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 = dest_chain.query_client_state(&opts.dest_client_id, 0_u32.into(), false); + let client_state = dest_chain.query_client_state(&dest_client_id, Height::default()); if client_state.is_ok() { return Err(Into::::into(Kind::CreateClient( - opts.dest_client_id, + dest_client_id, "client already exists".into(), ))); } // Get the key and signer from key seed file. - let (key, signer) = dest_chain.key_and_signer(&opts.signer_seed)?; + let (key, signer) = dest_chain.key_and_signer(signer_seed)?; - // Build client create message with the data from the source chain at latest height. + // Build client create message with the data from source chain at latest height. let latest_height = src_chain.query_latest_height()?; - let new_msg = MsgCreateAnyClient::new( - opts.dest_client_id, + Ok(MsgCreateAnyClient::new( + dest_client_id, src_chain.build_client_state(latest_height)?, src_chain.build_consensus_state(latest_height)?, signer, ) .map_err(|e| { Kind::MessageTransaction("failed to build the create client message".into()).context(e) - })?; + })?) +} - let proto_msgs: Vec = vec![new_msg.to_any::()]; +pub fn build_create_client_and_send(opts: ClientOptions) -> Result { + // Get the source and destination chains. + let src_chain = &CosmosSDKChain::from_config(opts.clone().src_chain_config)?; + let dest_chain = &mut CosmosSDKChain::from_config(opts.clone().dest_chain_config)?; - // Send the transaction to the destination chain - Ok(dest_chain.send(proto_msgs, key, "".to_string(), 0)?) + let new_msg = build_create_client( + dest_chain, + src_chain, + opts.dest_client_id, + &opts.signer_seed, + )?; + let (key, _) = dest_chain.key_and_signer(&opts.signer_seed)?; + + Ok(dest_chain.send( + vec![new_msg.to_any::()], + key, + "".to_string(), + 0, + )?) } -pub fn update_client(opts: ClientOptions) -> Result, Error> { - // Get the source and destination chains - let src_chain = CosmosSDKChain::from_config(opts.clone().src_chain_config)?; - let mut dest_chain = CosmosSDKChain::from_config(opts.clone().dest_chain_config)?; - +pub fn build_update_client( + dest_chain: &mut CosmosSDKChain, + src_chain: &CosmosSDKChain, + dest_client_id: ClientId, + target_height: Height, + signer_seed: &str, +) -> Result, Error> { // Get the latest trusted height from the client state on destination. let trusted_height = dest_chain - .query_client_state(&opts.dest_client_id, 0_u32.into(), false)? + .query_client_state(&dest_client_id, Height::default())? .latest_height(); - // Set the target height to latest. - let target_height = src_chain.query_latest_height()?; - // Get the key and signer from key seed file. - let (key, signer) = dest_chain.key_and_signer(&opts.signer_seed)?; + let (key, signer) = dest_chain.key_and_signer(signer_seed)?; let new_msg = MsgUpdateAnyClient { - client_id: opts.dest_client_id, + client_id: dest_client_id, header: src_chain.build_header(trusted_height, target_height)?, signer, }; - let proto_msgs: Vec = vec![new_msg.to_any::()]; + Ok(vec![new_msg.to_any::()]) +} + +pub fn build_update_client_and_send(opts: ClientOptions) -> Result { + // Get the source and destination chains. + let src_chain = &CosmosSDKChain::from_config(opts.clone().src_chain_config)?; + let dest_chain = &mut CosmosSDKChain::from_config(opts.clone().dest_chain_config)?; + + let target_height = src_chain.query_latest_height()?; + let new_msgs = build_update_client( + dest_chain, + src_chain, + opts.dest_client_id, + target_height, + &opts.signer_seed, + )?; + let (key, _) = dest_chain.key_and_signer(&opts.signer_seed)?; - Ok(dest_chain.send(proto_msgs, key, "".to_string(), 0)?) + Ok(dest_chain.send(new_msgs, key, "".to_string(), 0)?) } diff --git a/relayer/src/tx/connection.rs b/relayer/src/tx/connection.rs index 54297fd98a..821c7af7a7 100644 --- a/relayer/src/tx/connection.rs +++ b/relayer/src/tx/connection.rs @@ -1,23 +1,32 @@ +use std::convert::{TryFrom, TryInto}; +use std::str::FromStr; +use std::thread; +use std::time::Duration; + use prost_types::Any; use serde_json::Value; -use std::str::FromStr; use bitcoin::hashes::hex::ToHex; -use tendermint::account::Id as AccountId; -use tendermint_rpc::Id; +use ibc_proto::ibc::core::client::v1::MsgUpdateClient as RawMsgUpdateClient; +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::Counterparty; +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}; use ibc::tx_msg::Msg; -use ibc_proto::ibc::core::connection::v1::MsgConnectionOpenInit as RawMsgConnectionOpenInit; +use ibc::Height as ICSHeight; use crate::chain::{Chain, CosmosSDKChain}; 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}; #[derive(Clone, Debug)] pub struct ConnectionOpenInitOptions { @@ -30,14 +39,14 @@ pub struct ConnectionOpenInitOptions { pub signer_seed: String, } -pub fn conn_init(opts: &ConnectionOpenInitOptions) -> Result, Error> { - // Get the source and destination chains - let src_chain = CosmosSDKChain::from_config(opts.src_chain_config.clone())?; - let mut dest_chain = CosmosSDKChain::from_config(opts.dest_chain_config.clone())?; - +pub fn build_conn_init( + dest_chain: &mut CosmosSDKChain, + src_chain: &CosmosSDKChain, + opts: &ConnectionOpenInitOptions, +) -> Result, Error> { // Check that the destination chain will accept the message, i.e. it does not have the connection if dest_chain - .query_connection(&opts.dest_connection_id, 0_u32.into(), false) + .query_connection(&opts.dest_connection_id, ICSHeight::default()) .is_ok() { return Err(Kind::ConnOpenInit( @@ -63,11 +72,162 @@ pub fn conn_init(opts: &ConnectionOpenInitOptions) -> Result, Error> { client_id: opts.dest_client_id.clone(), connection_id: opts.dest_connection_id.clone(), counterparty, - version: "".to_string(), + version: dest_chain.query_compatible_versions()?[0].clone(), + signer, + }; + + Ok(vec![new_msg.to_any::()]) +} + +pub fn build_conn_init_and_send(opts: &ConnectionOpenInitOptions) -> Result { + // Get the source and destination chains. + let src_chain = &CosmosSDKChain::from_config(opts.clone().src_chain_config)?; + let dest_chain = &mut CosmosSDKChain::from_config(opts.clone().dest_chain_config)?; + + let new_msgs = build_conn_init(dest_chain, src_chain, opts)?; + let (key, _) = dest_chain.key_and_signer(&opts.signer_seed)?; + + Ok(dest_chain.send(new_msgs, key, "".to_string(), 0)?) +} + +#[derive(Clone, Debug)] +pub struct ConnectionOpenTryOptions { + pub dest_chain_config: ChainConfig, + pub src_chain_config: ChainConfig, + pub dest_client_id: ClientId, + pub src_client_id: ClientId, + pub dest_connection_id: ConnectionId, + pub src_connection_id: ConnectionId, + pub signer_seed: String, +} + +fn check_connection_state_for_try( + connection_id: ConnectionId, + existing_connection: ConnectionEnd, + expected_connection: ConnectionEnd, +) -> 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() + { + 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( + 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 + let counterparty = Counterparty::new( + opts.src_client_id.clone(), + Some(opts.src_connection_id.clone()), + src_chain.query_commitment_prefix()?, + ); + let dest_expected_connection = ConnectionEnd::new( + State::Init, + opts.dest_client_id.clone(), + counterparty.clone(), + 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, + )? + } + + 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(), + ) + })?; + // 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 (key, signer) = src_chain.key_and_signer(&opts.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 key and signer from key seed file + let (key, 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 = src_chain.query_client_state(&opts.src_client_id, ics_target_height)?; + + let proofs = src_chain.build_connection_proofs( + 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.dest_connection_id.clone(), + client_id: opts.dest_client_id.clone(), + client_state: Some(client_state), + counterparty_chosen_connection_id: src_connection.counterparty().connection_id().cloned(), + counterparty, + counterparty_versions, + proofs, signer, }; - let proto_msgs: Vec = vec![new_msg.to_any::()]; + let mut new_msgs = vec![new_msg.to_any::()]; + + msgs.append(&mut new_msgs); + + Ok(msgs) +} + +pub fn build_conn_try_and_send(opts: ConnectionOpenTryOptions) -> 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_try(dest_chain, src_chain, &opts)?; + let (key, _) = dest_chain.key_and_signer(&opts.signer_seed)?; - Ok(dest_chain.send(proto_msgs, key, "".to_string(), 0)?) + Ok(dest_chain.send(dest_msgs, key, "".to_string(), 0)?) }