diff --git a/modules/src/ics03_connection/connection.rs b/modules/src/ics03_connection/connection.rs index 66add5491..0ba81d047 100644 --- a/modules/src/ics03_connection/connection.rs +++ b/modules/src/ics03_connection/connection.rs @@ -1,6 +1,5 @@ use super::exported::*; -use crate::ics03_connection::error; -use crate::ics03_connection::error::Kind; +use crate::ics03_connection::error::{Error, Kind}; use crate::ics23_commitment::CommitmentPrefix; use crate::ics24_host::identifier::{ClientId, ConnectionId}; use serde_derive::{Deserialize, Serialize}; @@ -14,18 +13,22 @@ pub struct ConnectionEnd { } impl ConnectionEnd { - pub fn new(client_id: ClientId, counterparty: Counterparty, versions: Vec) -> Self { - ConnectionEnd { + pub fn new( + client_id: ClientId, + counterparty: Counterparty, + versions: Vec, + ) -> Result { + Ok(Self { state: State::Uninitialized, client_id, counterparty, - versions, - } + versions: validate_versions(versions).map_err(|e| Kind::InvalidVersion.context(e))?, + }) } } impl Connection for ConnectionEnd { - type ValidationError = error::Error; + type ValidationError = Error; fn state(&self) -> &State { &self.state @@ -46,19 +49,6 @@ impl Connection for ConnectionEnd { } fn validate_basic(&self) -> Result<(), Self::ValidationError> { - if self.versions.is_empty() { - return Err(error::Kind::InvalidVersion - .context("missing connection versions") - .into()); - } - - for v in self.versions().iter() { - if v.trim().is_empty() { - return Err(error::Kind::InvalidVersion - .context("empty version string") - .into()); - } - } self.counterparty().validate_basic() } } @@ -75,7 +65,7 @@ impl Counterparty { client_id: String, connection_id: String, prefix: CommitmentPrefix, - ) -> Result { + ) -> Result { Ok(Self { client_id: client_id .parse() @@ -89,7 +79,7 @@ impl Counterparty { } impl ConnectionCounterparty for Counterparty { - type ValidationError = error::Error; + type ValidationError = Error; fn client_id(&self) -> String { self.client_id.as_str().into() @@ -104,6 +94,26 @@ impl ConnectionCounterparty for Counterparty { } fn validate_basic(&self) -> Result<(), Self::ValidationError> { - todo!() + // todo!() + Ok(()) + } +} + +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) } diff --git a/modules/src/ics03_connection/error.rs b/modules/src/ics03_connection/error.rs index 76b60ad11..fc41f5655 100644 --- a/modules/src/ics03_connection/error.rs +++ b/modules/src/ics03_connection/error.rs @@ -15,6 +15,12 @@ pub enum Kind { #[error("invalid version")] InvalidVersion, + + #[error("invalid address")] + InvalidAddress, + + #[error("invalid proof")] + InvalidProof, } impl Kind { diff --git a/modules/src/ics03_connection/msgs.rs b/modules/src/ics03_connection/msgs.rs index e1857bdb2..e7947da08 100644 --- a/modules/src/ics03_connection/msgs.rs +++ b/modules/src/ics03_connection/msgs.rs @@ -1,9 +1,10 @@ -use crate::ics03_connection::connection::Counterparty; -use crate::ics03_connection::error::Kind; +#![allow(clippy::too_many_arguments)] +use crate::ics03_connection::connection::{validate_version, validate_versions, Counterparty}; +use crate::ics03_connection::error::{Error, Kind}; use crate::ics03_connection::exported::ConnectionCounterparty; -use crate::ics23_commitment::CommitmentPrefix; +use crate::ics23_commitment::{CommitmentPrefix, CommitmentProof}; use crate::ics24_host::identifier::{ClientId, ConnectionId}; -use crate::ics24_host::validate::{validate_client_identifier, validate_connection_identifier}; +use crate::proofs::{ConsensusProof, Proofs}; use crate::tx_msg::Msg; use serde_derive::{Deserialize, Serialize}; use tendermint::account::Id as AccountId; @@ -26,7 +27,7 @@ impl MsgConnectionOpenInit { counterparty_client_id: String, counterparty_commitment_prefix: CommitmentPrefix, signer: AccountId, - ) -> Result { + ) -> Result { Ok(Self { connection_id: connection_id .parse() @@ -46,7 +47,7 @@ impl MsgConnectionOpenInit { } impl Msg for MsgConnectionOpenInit { - type ValidationError = crate::ics03_connection::error::Error; + type ValidationError = Error; fn route(&self) -> String { crate::keys::ROUTER_KEY.to_string() @@ -57,15 +58,8 @@ impl Msg for MsgConnectionOpenInit { } fn validate_basic(&self) -> Result<(), Self::ValidationError> { - validate_connection_identifier(self.connection_id.as_str()) - .map_err(|e| Kind::IdentifierError.context(e))?; - validate_client_identifier(self.client_id.as_str()) - .map_err(|e| Kind::IdentifierError.context(e))?; - self.counterparty - .validate_basic() - .map_err(|e| Kind::IdentifierError.context(e))?; - //todo: validate signer! - Ok(()) + // All the validation is performed on creation + self.counterparty.validate_basic() } fn get_sign_bytes(&self) -> Vec { @@ -77,18 +71,223 @@ impl Msg for MsgConnectionOpenInit { } } +pub const TYPE_MSG_CONNECTION_OPEN_TRY: &str = "connection_open_try"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgConnectionOpenTry { + connection_id: ConnectionId, + client_id: ClientId, + counterparty: Counterparty, + counterparty_versions: Vec, + proofs: Proofs, + signer: AccountId, +} + +impl MsgConnectionOpenTry { + pub fn new( + connection_id: String, + client_id: String, + counterparty_connection_id: String, + counterparty_client_id: String, + counterparty_commitment_prefix: CommitmentPrefix, + counterparty_versions: Vec, + init_proof: CommitmentProof, + consensus_proof: CommitmentProof, + proofs_height: u64, + consensus_height: u64, + signer: AccountId, + ) -> Result { + let consensus_proof_obj = ConsensusProof::new(consensus_proof, consensus_height) + .map_err(|e| Kind::InvalidProof.context(e))?; + + Ok(Self { + connection_id: connection_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + client_id: client_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + counterparty: Counterparty::new( + counterparty_client_id, + counterparty_connection_id, + counterparty_commitment_prefix, + ) + .map_err(|e| Kind::IdentifierError.context(e))?, + counterparty_versions: validate_versions(counterparty_versions) + .map_err(|e| Kind::InvalidVersion.context(e))?, + proofs: Proofs::new(init_proof, Option::from(consensus_proof_obj), proofs_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer, + }) + } +} + +impl Msg for MsgConnectionOpenTry { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_CONNECTION_OPEN_TRY.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + self.counterparty.validate_basic() + } + + fn get_sign_bytes(&self) -> Vec { + unimplemented!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +pub const TYPE_MSG_CONNECTION_OPEN_ACK: &str = "connection_open_ack"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgConnectionOpenAck { + connection_id: ConnectionId, + proofs: Proofs, + version: String, + signer: AccountId, +} + +impl MsgConnectionOpenAck { + pub fn new( + connection_id: String, + proof_try: CommitmentProof, + proof_consensus: CommitmentProof, + proofs_height: u64, + consensus_height: u64, + version: String, + signer: AccountId, + ) -> Result { + let consensus_proof_obj = ConsensusProof::new(proof_consensus, consensus_height) + .map_err(|e| Kind::InvalidProof.context(e))?; + + Ok(Self { + connection_id: connection_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + proofs: Proofs::new(proof_try, Option::from(consensus_proof_obj), proofs_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + version: validate_version(version).map_err(|e| Kind::InvalidVersion.context(e))?, + signer, + }) + } +} + +impl Msg for MsgConnectionOpenAck { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_CONNECTION_OPEN_ACK.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + Ok(()) + } + + fn get_sign_bytes(&self) -> Vec { + unimplemented!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +pub const TYPE_MSG_CONNECTION_OPEN_CONFIRM: &str = "connection_open_confirm"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgConnectionOpenConfirm { + connection_id: ConnectionId, + proofs: Proofs, + signer: AccountId, +} + +impl MsgConnectionOpenConfirm { + pub fn new( + connection_id: String, + proof_ack: CommitmentProof, + proofs_height: u64, + signer: AccountId, + ) -> Result { + Ok(Self { + connection_id: connection_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + proofs: Proofs::new(proof_ack, None, proofs_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer, + }) + } +} + +impl Msg for MsgConnectionOpenConfirm { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_CONNECTION_OPEN_CONFIRM.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + Ok(()) + } + + fn get_sign_bytes(&self) -> Vec { + unimplemented!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +#[cfg(test)] +pub mod test_util { + use crate::ics23_commitment::CommitmentProof; + use tendermint::merkle::proof::ProofOp; + + pub fn get_dummy_proof() -> CommitmentProof { + let proof_op = ProofOp { + field_type: "iavl:v".to_string(), + key: "Y29uc2Vuc3VzU3RhdGUvaWJjb25lY2xpZW50LzIy".as_bytes().to_vec(), + data: "8QEK7gEKKAgIEAwYHCIgG9RAkJgHlxNjmyzOW6bUAidhiRSja0x6+GXCVENPG1oKKAgGEAUYFyIgwRns+dJvjf1Zk2BaFrXz8inPbvYHB7xx2HCy9ima5f8KKAgEEAMYFyogOr8EGajEV6fG5fzJ2fAAvVMgRLhdMJTzCPlogl9rxlIKKAgCEAIYFyIgcjzX/a+2bFbnNldpawQqZ+kYhIwz5r4wCUzuu1IFW04aRAoeY29uc2Vuc3VzU3RhdGUvaWJjb25lY2xpZW50LzIyEiAZ1uuG60K4NHJZZMuS9QX6o4eEhica5jIHYwflRiYkDBgX" + .as_bytes().to_vec() + }; + + CommitmentProof { + ops: vec![proof_op], + } + } +} + #[cfg(test)] mod tests { - use crate::ics23_commitment::CommitmentPrefix; + use super::test_util::get_dummy_proof; + use super::MsgConnectionOpenInit; + use crate::ics03_connection::msgs::{ + MsgConnectionOpenAck, MsgConnectionOpenConfirm, MsgConnectionOpenTry, + }; + use crate::ics23_commitment::{CommitmentPrefix, CommitmentProof}; use std::str::FromStr; use tendermint::account::Id as AccountId; #[test] fn parse_connection_open_init_msg() { - use super::MsgConnectionOpenInit; - let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; - let acc = AccountId::from_str(id_hex).unwrap(); - #[derive(Clone, Debug, PartialEq)] struct ConOpenInitParams { connection_id: String, @@ -98,6 +297,12 @@ mod tests { counterparty_commitment_prefix: CommitmentPrefix, } + struct Test { + name: String, + params: ConOpenInitParams, + want_pass: bool, + } + let default_con_params = ConOpenInitParams { connection_id: "srcconnection".to_string(), client_id: "srcclient".to_string(), @@ -106,12 +311,101 @@ mod tests { counterparty_commitment_prefix: CommitmentPrefix {}, }; + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + params: default_con_params.clone(), + want_pass: true, + }, + Test { + name: "Bad connection id, non-alpha".to_string(), + params: ConOpenInitParams { + connection_id: "con007".to_string(), + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad client id, name too short".to_string(), + params: ConOpenInitParams { + client_id: "client".to_string(), + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad destination connection id, name too long".to_string(), + params: ConOpenInitParams { + counterparty_connection_id: "abcdefghijklmnopqrstu".to_string(), + ..default_con_params.clone() + }, + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for test in tests { + let p = test.params.clone(); + + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + let acc = AccountId::from_str(id_hex).unwrap(); + + let msg = MsgConnectionOpenInit::new( + p.connection_id, + p.client_id, + p.counterparty_connection_id, + p.counterparty_client_id, + p.counterparty_commitment_prefix, + acc, + ); + + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgConnOpenInit::new failed for test {}, \nmsg {:?} with error {:?}", + test.name, + test.params.clone(), + msg.err(), + ); + } + } + + #[test] + fn parse_connection_open_try_msg() { + #[derive(Clone, Debug, PartialEq)] + struct ConOpenTryParams { + connection_id: String, + client_id: String, + counterparty_connection_id: String, + counterparty_client_id: String, + counterparty_commitment_prefix: CommitmentPrefix, + counterparty_versions: Vec, + proof_init: CommitmentProof, + proof_consensus: CommitmentProof, + proof_height: u64, + consensus_height: u64, + } + struct Test { name: String, - params: ConOpenInitParams, + params: ConOpenTryParams, want_pass: bool, } + let default_con_params = ConOpenTryParams { + connection_id: "srcconnection".to_string(), + client_id: "srcclient".to_string(), + counterparty_connection_id: "destconnection".to_string(), + counterparty_client_id: "destclient".to_string(), + counterparty_commitment_prefix: CommitmentPrefix {}, + counterparty_versions: vec!["1.0.0".to_string()], + proof_init: get_dummy_proof(), + proof_consensus: get_dummy_proof(), + proof_height: 10, + consensus_height: 10, + }; + let tests: Vec = vec![ Test { name: "Good parameters".to_string(), @@ -120,7 +414,7 @@ mod tests { }, Test { name: "Bad connection id, non-alpha".to_string(), - params: ConOpenInitParams { + params: ConOpenTryParams { connection_id: "con007".to_string(), ..default_con_params.clone() }, @@ -128,7 +422,7 @@ mod tests { }, Test { name: "Bad client id, name too short".to_string(), - params: ConOpenInitParams { + params: ConOpenTryParams { client_id: "client".to_string(), ..default_con_params.clone() }, @@ -136,12 +430,60 @@ mod tests { }, Test { name: "Bad destination connection id, name too long".to_string(), - params: ConOpenInitParams { + params: ConOpenTryParams { counterparty_connection_id: "abcdefghijklmnopqrstu".to_string(), ..default_con_params.clone() }, want_pass: false, }, + Test { + name: "Bad destination client id, name in uppercase".to_string(), + params: ConOpenTryParams { + counterparty_client_id: "BadClientId".to_string(), + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad counterparty versions, empty versions vec".to_string(), + params: ConOpenTryParams { + counterparty_versions: vec![], + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad counterparty versions, empty version string".to_string(), + params: ConOpenTryParams { + counterparty_versions: vec!["".to_string()], + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad proof height, height is 0".to_string(), + params: ConOpenTryParams { + proof_height: 0, + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad consensus height, height is 0".to_string(), + params: ConOpenTryParams { + consensus_height: 0, + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Empty proof".to_string(), + params: ConOpenTryParams { + proof_init: CommitmentProof { ops: vec![] }, + ..default_con_params.clone() + }, + want_pass: false, + }, ] .into_iter() .collect(); @@ -149,33 +491,194 @@ mod tests { for test in tests { let p = test.params.clone(); - let msg = MsgConnectionOpenInit::new( + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + let acc = AccountId::from_str(id_hex).unwrap(); + + let msg = MsgConnectionOpenTry::new( p.connection_id, p.client_id, p.counterparty_connection_id, p.counterparty_client_id, p.counterparty_commitment_prefix, + p.counterparty_versions, + p.proof_init, + p.proof_consensus, + p.proof_height, + p.consensus_height, acc, ); - match msg { - Ok(_res) => { - assert!( - test.want_pass, - "MsgConnOpenInit::new should have failed for test {}, \nmsg {:?}", - test.name, - test.params.clone() - ); - } - Err(_err) => { - assert!( - !test.want_pass, - "MsgConnOpenInit::new failed for test {}, \nmsg {:?}", - test.name, - test.params.clone() - ); - } - } + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgConnOpenTry::new failed for test {}, \nmsg {:?} \nwith error {:?}", + test.name, + test.params.clone(), + msg.err(), + ); + } + } + + #[test] + fn parse_connection_open_ack_msg() { + #[derive(Clone, Debug, PartialEq)] + struct ConOpenAckParams { + connection_id: String, + proof_try: CommitmentProof, + proof_consensus: CommitmentProof, + proof_height: u64, + consensus_height: u64, + version: String, + } + + struct Test { + name: String, + params: ConOpenAckParams, + want_pass: bool, + } + + let default_con_params = ConOpenAckParams { + connection_id: "srcconnection".to_string(), + proof_try: get_dummy_proof(), + proof_consensus: get_dummy_proof(), + proof_height: 10, + consensus_height: 10, + version: "1.0.0".to_string(), + }; + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + params: default_con_params.clone(), + want_pass: true, + }, + Test { + name: "Bad connection id, non-alpha".to_string(), + params: ConOpenAckParams { + connection_id: "con007".to_string(), + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad version, empty version string".to_string(), + params: ConOpenAckParams { + version: "".to_string(), + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad proof height, height is 0".to_string(), + params: ConOpenAckParams { + proof_height: 0, + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad consensus height, height is 0".to_string(), + params: ConOpenAckParams { + consensus_height: 0, + ..default_con_params.clone() + }, + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for test in tests { + let p = test.params.clone(); + + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + let acc = AccountId::from_str(id_hex).unwrap(); + + let msg = MsgConnectionOpenAck::new( + p.connection_id, + p.proof_try, + p.proof_consensus, + p.proof_height, + p.consensus_height, + p.version, + acc, + ); + + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgConnOpenAck::new failed for test {}, \nmsg {:?} \nwith error {:?}", + test.name, + test.params.clone(), + msg.err() + ); + } + } + + #[test] + fn parse_connection_open_confirm_msg() { + #[derive(Clone, Debug, PartialEq)] + struct ConOpenConfirmParams { + connection_id: String, + proof_ack: CommitmentProof, + proof_height: u64, + } + + struct Test { + name: String, + params: ConOpenConfirmParams, + want_pass: bool, + } + + let default_con_params = ConOpenConfirmParams { + connection_id: "srcconnection".to_string(), + proof_ack: get_dummy_proof(), + proof_height: 10, + }; + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + params: default_con_params.clone(), + want_pass: true, + }, + Test { + name: "Bad connection id, non-alpha".to_string(), + params: ConOpenConfirmParams { + connection_id: "con007".to_string(), + ..default_con_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad proof height, height is 0".to_string(), + params: ConOpenConfirmParams { + proof_height: 0, + ..default_con_params.clone() + }, + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for test in tests { + let p = test.params.clone(); + + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + let acc = AccountId::from_str(id_hex).unwrap(); + + let msg = + MsgConnectionOpenConfirm::new(p.connection_id, p.proof_ack, p.proof_height, acc); + + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgConnOpenConfirm::new failed for test {}, \nmsg {:?} \nwith error {:?}", + test.name, + test.params.clone(), + msg.err() + ); } } } diff --git a/modules/src/ics04_channel/error.rs b/modules/src/ics04_channel/error.rs index a36686288..5fb078bff 100644 --- a/modules/src/ics04_channel/error.rs +++ b/modules/src/ics04_channel/error.rs @@ -19,6 +19,15 @@ pub enum Kind { #[error("invalid version")] InvalidVersion, + + #[error("invalid proof")] + InvalidProof, + + #[error("invalid packet")] + InvalidPacket, + + #[error("acknowledgement too long")] + AcknowledgementTooLong, } impl Kind { diff --git a/modules/src/ics04_channel/mod.rs b/modules/src/ics04_channel/mod.rs index 07f5679f8..1f5b28720 100644 --- a/modules/src/ics04_channel/mod.rs +++ b/modules/src/ics04_channel/mod.rs @@ -5,4 +5,5 @@ pub mod error; pub mod events; pub mod exported; pub mod msgs; +pub mod packet; pub mod query; diff --git a/modules/src/ics04_channel/msgs.rs b/modules/src/ics04_channel/msgs.rs index e6385128d..c31e4a101 100644 --- a/modules/src/ics04_channel/msgs.rs +++ b/modules/src/ics04_channel/msgs.rs @@ -1,8 +1,12 @@ #![allow(clippy::too_many_arguments)] use super::channel::{ChannelEnd, Counterparty}; use super::exported::*; -use crate::ics04_channel::error::Kind; +use crate::ics03_connection::connection::validate_version; +use crate::ics04_channel::error::{Error, Kind}; +use crate::ics04_channel::packet::Packet; +use crate::ics23_commitment::CommitmentProof; use crate::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; +use crate::proofs::Proofs; use crate::tx_msg::Msg; use serde_derive::{Deserialize, Serialize}; use std::str::FromStr; @@ -28,7 +32,7 @@ impl MsgChannelOpenInit { counterparty_port_id: String, counterparty_channel_id: String, signer: AccountId, - ) -> Result { + ) -> Result { let connection_hops: Result, _> = connection_hops .into_iter() .map(|s| ConnectionId::from_str(s.as_str())) @@ -54,7 +58,7 @@ impl MsgChannelOpenInit { } impl Msg for MsgChannelOpenInit { - type ValidationError = crate::ics04_channel::error::Error; + type ValidationError = Error; fn route(&self) -> String { crate::keys::ROUTER_KEY.to_string() @@ -77,41 +81,967 @@ impl Msg for MsgChannelOpenInit { } } +pub const TYPE_MSG_CHANNEL_OPEN_TRY: &str = "channel_open_try"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgChannelOpenTry { + port_id: PortId, + channel_id: ChannelId, + channel: ChannelEnd, + counterparty_version: String, + proofs: Proofs, + signer: AccountId, +} + +impl MsgChannelOpenTry { + pub fn new( + port_id: String, + channel_id: String, + channel_version: String, + order: String, + connection_hops: Vec, + counterparty_port_id: String, + counterparty_channel_id: String, + counterparty_version: String, + proof_init: CommitmentProof, + proofs_height: u64, + signer: AccountId, + ) -> Result { + let connection_hops: Result, _> = connection_hops + .into_iter() + .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() + .map_err(|e| Kind::IdentifierError.context(e))?, + channel_id: channel_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + channel: ChannelEnd::new( + order.parse()?, + 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, + ), + counterparty_version: validate_version(counterparty_version) + .map_err(|e| Kind::InvalidVersion.context(e))?, + proofs: Proofs::new(proof_init, None, proofs_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer, + }) + } +} + +impl Msg for MsgChannelOpenTry { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_CHANNEL_OPEN_TRY.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + self.channel.validate_basic() + } + + fn get_sign_bytes(&self) -> Vec { + todo!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +pub const TYPE_MSG_CHANNEL_OPEN_ACK: &str = "channel_open_ack"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgChannelOpenAck { + port_id: PortId, + channel_id: ChannelId, + counterparty_version: String, + proofs: Proofs, + signer: AccountId, +} + +impl MsgChannelOpenAck { + pub fn new( + port_id: String, + channel_id: String, + counterparty_version: String, + proof_try: CommitmentProof, + proofs_height: u64, + signer: AccountId, + ) -> Result { + Ok(Self { + port_id: port_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + 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))?, + proofs: Proofs::new(proof_try, None, proofs_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer, + }) + } +} + +impl Msg for MsgChannelOpenAck { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_CHANNEL_OPEN_ACK.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + // Nothing to validate + // All the validation is performed on creation + Ok(()) + } + + fn get_sign_bytes(&self) -> Vec { + todo!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +pub const TYPE_MSG_CHANNEL_OPEN_CONFIRM: &str = "channel_open_confirm"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgChannelOpenConfirm { + port_id: PortId, + channel_id: ChannelId, + proofs: Proofs, + signer: AccountId, +} + +impl MsgChannelOpenConfirm { + pub fn new( + port_id: String, + channel_id: String, + proof_ack: CommitmentProof, + proofs_height: u64, + signer: AccountId, + ) -> Result { + Ok(Self { + port_id: port_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + channel_id: channel_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + proofs: Proofs::new(proof_ack, None, proofs_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer, + }) + } +} + +impl Msg for MsgChannelOpenConfirm { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_CHANNEL_OPEN_CONFIRM.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + // Nothing to validate + // All the validation is performed on creation + Ok(()) + } + + fn get_sign_bytes(&self) -> Vec { + todo!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +pub const TYPE_MSG_CHANNEL_CLOSE_INIT: &str = "channel_close_init"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgChannelCloseInit { + port_id: PortId, + channel_id: ChannelId, + signer: AccountId, +} + +impl MsgChannelCloseInit { + pub fn new( + port_id: String, + channel_id: String, + signer: AccountId, + ) -> Result { + Ok(Self { + port_id: port_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + channel_id: channel_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + signer, + }) + } +} + +impl Msg for MsgChannelCloseInit { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_CHANNEL_CLOSE_INIT.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + // Nothing to validate + // All the validation is performed on creation + Ok(()) + } + + fn get_sign_bytes(&self) -> Vec { + todo!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +pub const TYPE_MSG_CHANNEL_CLOSE_CONFIRM: &str = "channel_close_confirm"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgChannelCloseConfirm { + port_id: PortId, + channel_id: ChannelId, + proofs: Proofs, + signer: AccountId, +} + +impl MsgChannelCloseConfirm { + pub fn new( + port_id: String, + channel_id: String, + proof_init: CommitmentProof, + proofs_height: u64, + signer: AccountId, + ) -> Result { + Ok(Self { + port_id: port_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + channel_id: channel_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + proofs: Proofs::new(proof_init, None, proofs_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer, + }) + } +} + +impl Msg for MsgChannelCloseConfirm { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_CHANNEL_CLOSE_CONFIRM.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + // Nothing to validate + // All the validation is performed on creation + Ok(()) + } + + fn get_sign_bytes(&self) -> Vec { + todo!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +pub const TYPE_MSG_PACKET: &str = "ics04/opaque"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgPacket { + packet: Packet, + proofs: Proofs, + signer: AccountId, +} + +impl MsgPacket { + pub fn new( + packet: Packet, + proof: CommitmentProof, + proof_height: u64, + signer: AccountId, + ) -> Result { + Ok(Self { + packet: packet + .validate() + .map_err(|e| Kind::InvalidPacket.context(e))?, + proofs: Proofs::new(proof, None, proof_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer, + }) + } + + // returns the base64-encoded bytes used for the + // data field when signing the packet + pub fn get_data_bytes() -> Vec { + todo!() + } +} + +impl Msg for MsgPacket { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_PACKET.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + // Nothing to validate + // All the validation is performed on creation + Ok(()) + } + + fn get_sign_bytes(&self) -> Vec { + todo!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +pub const TYPE_MSG_TIMEOUT: &str = "ics04/timeout"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgTimeout { + packet: Packet, + next_sequence_recv: Option, + proofs: Proofs, + signer: AccountId, +} + +impl MsgTimeout { + pub fn new( + packet: Packet, + next_sequence_recv: Option, + proof: CommitmentProof, + proof_height: u64, + signer: AccountId, + ) -> Result { + Ok(Self { + packet: packet + .validate() + .map_err(|e| Kind::InvalidPacket.context(e))?, + next_sequence_recv, + proofs: Proofs::new(proof, None, proof_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer, + }) + } +} + +impl Msg for MsgTimeout { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_TIMEOUT.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + // Nothing to validate + // All the validation is performed on creation + Ok(()) + } + + fn get_sign_bytes(&self) -> Vec { + todo!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + +pub const TYPE_MSG_ACKNOWLEDGEMENT: &str = "ics04/opaque"; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct MsgAcknowledgement { + packet: Packet, + acknowledgement: Vec, + proofs: Proofs, + signer: AccountId, +} + +impl MsgAcknowledgement { + pub fn new( + packet: Packet, + acknowledgement: Vec, + proof: CommitmentProof, + proof_height: u64, + signer: AccountId, + ) -> Result { + if acknowledgement.len() > 100 { + return Err(Kind::AcknowledgementTooLong.into()); + } + + Ok(Self { + packet: packet + .validate() + .map_err(|e| Kind::InvalidPacket.context(e))?, + acknowledgement, + proofs: Proofs::new(proof, None, proof_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer, + }) + } +} + +impl Msg for MsgAcknowledgement { + type ValidationError = Error; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn get_type(&self) -> String { + TYPE_MSG_ACKNOWLEDGEMENT.to_string() + } + + fn validate_basic(&self) -> Result<(), Self::ValidationError> { + // Nothing to validate + // All the validation is performed on creation + Ok(()) + } + + fn get_sign_bytes(&self) -> Vec { + todo!() + } + + fn get_signers(&self) -> Vec { + vec![self.signer] + } +} + #[cfg(test)] mod tests { + use super::MsgChannelOpenInit; + use crate::ics03_connection::msgs::test_util::get_dummy_proof; + use crate::ics04_channel::msgs::{ + MsgChannelCloseConfirm, MsgChannelCloseInit, MsgChannelOpenAck, MsgChannelOpenConfirm, + MsgChannelOpenTry, + }; + use crate::ics23_commitment::CommitmentProof; use std::str::FromStr; use tendermint::account::Id as AccountId; #[test] - fn parse_channel_open_init_msg() { - use super::MsgChannelOpenInit; + fn parse_channel_open_init_msg() { + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + let acc = AccountId::from_str(id_hex).unwrap(); + + #[derive(Clone, Debug, PartialEq)] + struct OpenInitParams { + port_id: String, + channel_id: String, + version: String, + order: String, + connection_hops: Vec, + counterparty_port_id: String, + counterparty_channel_id: String, + } + + let default_params = OpenInitParams { + port_id: "port".to_string(), + channel_id: "testchannel".to_string(), + version: "1.0".to_string(), + order: "ORDERED".to_string(), + connection_hops: vec!["connectionhop".to_string()].into_iter().collect(), + counterparty_port_id: "destport".to_string(), + counterparty_channel_id: "testdestchannel".to_string(), + }; + + struct Test { + name: String, + params: OpenInitParams, + want_pass: bool, + } + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + params: default_params.clone(), + want_pass: true, + }, + Test { + name: "Bad port, non-alpha".to_string(), + params: OpenInitParams { + port_id: "p34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name too short".to_string(), + params: OpenInitParams { + channel_id: "chshort".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad version".to_string(), + params: OpenInitParams { + version: " . ".to_string(), + ..default_params.clone() + }, + want_pass: true, // verified in validate_basic() + }, + Test { + name: "Bad order".to_string(), + params: OpenInitParams { + order: "MYORER".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad connection hops".to_string(), + params: OpenInitParams { + connection_hops: vec!["conn124".to_string()].into_iter().collect(), + ..default_params.clone() + }, + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for test in tests { + let p = test.params.clone(); + + let msg = MsgChannelOpenInit::new( + p.port_id, + p.channel_id, + p.version, + p.order, + p.connection_hops, + p.counterparty_port_id, + p.counterparty_channel_id, + acc, + ); + + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgChanOpenInit::new failed for test {}, \nmsg {:?} with error {:?}", + test.name, + test.params.clone(), + msg.err(), + ); + } + } + + #[test] + fn parse_channel_open_try_msg() { + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + let acc = AccountId::from_str(id_hex).unwrap(); + + #[derive(Clone, Debug, PartialEq)] + struct OpenTryParams { + port_id: String, + channel_id: String, + channel_version: String, + order: String, + connection_hops: Vec, + counterparty_port_id: String, + counterparty_channel_id: String, + counterparty_version: String, + proof_init: CommitmentProof, + proof_height: u64, + } + + let default_params = OpenTryParams { + port_id: "port".to_string(), + channel_id: "testchannel".to_string(), + channel_version: "1.0".to_string(), + order: "ORDERED".to_string(), + connection_hops: vec!["connectionhop".to_string()].into_iter().collect(), + counterparty_port_id: "destport".to_string(), + counterparty_channel_id: "testdestchannel".to_string(), + counterparty_version: "1.0".to_string(), + proof_init: get_dummy_proof(), + proof_height: 10, + }; + + struct Test { + name: String, + params: OpenTryParams, + want_pass: bool, + } + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + params: default_params.clone(), + want_pass: true, + }, + Test { + name: "Bad port, name non-alpha".to_string(), + params: OpenTryParams { + port_id: "p34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad port, name too short".to_string(), + params: OpenTryParams { + port_id: "p".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad port, name too long".to_string(), + params: OpenTryParams { + port_id: "abcdefghijklmnopqrstu".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name non-alpha".to_string(), + params: OpenTryParams { + channel_id: "channelid34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name too short".to_string(), + params: OpenTryParams { + channel_id: "chshort".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name too long".to_string(), + params: OpenTryParams { + channel_id: "abcdefghijklmnopqrstu".to_string(), + ..default_params.clone() + }, + 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 { + proof_height: 0, + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad order".to_string(), + params: OpenTryParams { + order: "MYORER".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad connection hops, non-alpha connection id".to_string(), + params: OpenTryParams { + connection_hops: vec!["connection124".to_string()].into_iter().collect(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad connection hops, connection id too long".to_string(), + params: OpenTryParams { + connection_hops: vec!["abcdefghijklmnopqrstu".to_string()] + .into_iter() + .collect(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad connection hops, connection id too short".to_string(), + params: OpenTryParams { + connection_hops: vec!["connid".to_string()].into_iter().collect(), + ..default_params.clone() + }, + want_pass: false, + }, + // Currently failing because we don't validate connection hops + // Test { + // name: "Bad connection hops, more than 1".to_string(), + // params: OpenTryParams { + // connection_hops: vec!["connectionhop".to_string(), "connectionhopnext".to_string()].into_iter().collect(), + // ..default_params.clone() + // }, + // 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 { + counterparty_port_id: "abcdefghijklmnopqrstu".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad counterparty channel, name non-alpha".to_string(), + params: OpenTryParams { + counterparty_channel_id: "channelid34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for test in tests { + let p = test.params.clone(); + + let msg = MsgChannelOpenTry::new( + p.port_id, + p.channel_id, + p.channel_version, + p.order, + p.connection_hops, + p.counterparty_port_id, + p.counterparty_channel_id, + p.counterparty_version, + p.proof_init, + p.proof_height, + acc, + ); + + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgChanOpenTry::new failed for test {}, \nmsg {:?} with error {:?}", + test.name, + test.params.clone(), + msg.err(), + ); + } + } + + #[test] + fn parse_channel_open_ack_msg() { + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + let acc = AccountId::from_str(id_hex).unwrap(); + + #[derive(Clone, Debug, PartialEq)] + struct OpenAckParams { + port_id: String, + channel_id: String, + counterparty_version: String, + proof_try: CommitmentProof, + proof_height: u64, + } + + let default_params = OpenAckParams { + port_id: "port".to_string(), + channel_id: "testchannel".to_string(), + counterparty_version: "1.0".to_string(), + proof_try: get_dummy_proof(), + proof_height: 10, + }; + + struct Test { + name: String, + params: OpenAckParams, + want_pass: bool, + } + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + params: default_params.clone(), + want_pass: true, + }, + Test { + name: "Bad port, name non-alpha".to_string(), + params: OpenAckParams { + port_id: "p34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad port, name too short".to_string(), + params: OpenAckParams { + port_id: "p".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad port, name too long".to_string(), + params: OpenAckParams { + port_id: "abcdefghijklmnopqrstu".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name non-alpha".to_string(), + params: OpenAckParams { + channel_id: "channelid34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name too short".to_string(), + params: OpenAckParams { + channel_id: "chshort".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name too long".to_string(), + params: OpenAckParams { + channel_id: "abcdefghijklmnopqrstu".to_string(), + ..default_params.clone() + }, + 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 { + proof_height: 0, + ..default_params.clone() + }, + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for test in tests { + let p = test.params.clone(); + + let msg = MsgChannelOpenAck::new( + p.port_id, + p.channel_id, + p.counterparty_version, + p.proof_try, + p.proof_height, + acc, + ); + + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgChanOpenAck::new failed for test {}, \nmsg {:?} with error {:?}", + test.name, + test.params.clone(), + msg.err(), + ); + } + } + + #[test] + fn parse_channel_open_confirm_msg() { let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; let acc = AccountId::from_str(id_hex).unwrap(); #[derive(Clone, Debug, PartialEq)] - struct OpenInitParams { + struct OpenConfirmParams { port_id: String, channel_id: String, - version: String, - order: String, - connection_hops: Vec, - counterparty_port_id: String, - counterparty_channel_id: String, + proof_ack: CommitmentProof, + proof_height: u64, } - let default_params = OpenInitParams { + let default_params = OpenConfirmParams { port_id: "port".to_string(), channel_id: "testchannel".to_string(), - version: "1.0".to_string(), - order: "ORDERED".to_string(), - connection_hops: vec!["connectionhop".to_string()].into_iter().collect(), - counterparty_port_id: "destport".to_string(), - counterparty_channel_id: "testdestchannel".to_string(), + proof_ack: get_dummy_proof(), + proof_height: 10, }; struct Test { name: String, - params: OpenInitParams, + params: OpenConfirmParams, want_pass: bool, } @@ -122,41 +1052,159 @@ mod tests { want_pass: true, }, Test { - name: "Bad port, non-alpha".to_string(), - params: OpenInitParams { + name: "Bad port, name non-alpha".to_string(), + params: OpenConfirmParams { port_id: "p34".to_string(), ..default_params.clone() }, want_pass: false, }, + Test { + name: "Bad port, name too short".to_string(), + params: OpenConfirmParams { + port_id: "p".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad port, name too long".to_string(), + params: OpenConfirmParams { + port_id: "abcdefghijklmnopqrstu".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name non-alpha".to_string(), + params: OpenConfirmParams { + channel_id: "channelid34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, Test { name: "Bad channel, name too short".to_string(), - params: OpenInitParams { + params: OpenConfirmParams { channel_id: "chshort".to_string(), ..default_params.clone() }, want_pass: false, }, Test { - name: "Bad version".to_string(), - params: OpenInitParams { - version: " . ".to_string(), + name: "Bad channel, name too long".to_string(), + params: OpenConfirmParams { + channel_id: "abcdefghijklmnopqrstu".to_string(), ..default_params.clone() }, - want_pass: true, // verified in validate_basic() + want_pass: false, }, Test { - name: "Bad order".to_string(), - params: OpenInitParams { - order: "MYORER".to_string(), + name: "Bad proof height, height = 0".to_string(), + params: OpenConfirmParams { + proof_height: 0, + ..default_params.clone() + }, + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for test in tests { + let p = test.params.clone(); + + let msg = MsgChannelOpenConfirm::new( + p.port_id, + p.channel_id, + p.proof_ack, + p.proof_height, + acc, + ); + + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgChanOpenConfirm::new failed for test {}, \nmsg {:?} with error {:?}", + test.name, + test.params.clone(), + msg.err(), + ); + } + } + + #[test] + fn parse_channel_close_init_msg() { + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + let acc = AccountId::from_str(id_hex).unwrap(); + + #[derive(Clone, Debug, PartialEq)] + struct CloseInitParams { + port_id: String, + channel_id: String, + } + + let default_params = CloseInitParams { + port_id: "port".to_string(), + channel_id: "testchannel".to_string(), + }; + + struct Test { + name: String, + params: CloseInitParams, + want_pass: bool, + } + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + params: default_params.clone(), + want_pass: true, + }, + Test { + name: "Bad port, name non-alpha".to_string(), + params: CloseInitParams { + port_id: "p34".to_string(), ..default_params.clone() }, want_pass: false, }, Test { - name: "Bad connection hops".to_string(), - params: OpenInitParams { - connection_hops: vec!["conn124".to_string()].into_iter().collect(), + name: "Bad port, name too short".to_string(), + params: CloseInitParams { + port_id: "p".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad port, name too long".to_string(), + params: CloseInitParams { + port_id: "abcdefghijklmnopqrstu".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name non-alpha".to_string(), + params: CloseInitParams { + channel_id: "channelid34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name too short".to_string(), + params: CloseInitParams { + channel_id: "chshort".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name too long".to_string(), + params: CloseInitParams { + channel_id: "abcdefghijklmnopqrstu".to_string(), ..default_params.clone() }, want_pass: false, @@ -168,36 +1216,130 @@ mod tests { for test in tests { let p = test.params.clone(); - let msg = MsgChannelOpenInit::new( + let msg = MsgChannelCloseInit::new(p.port_id, p.channel_id, acc); + + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgChanCloseInit::new failed for test {}, \nmsg {:?} with error {:?}", + test.name, + test.params.clone(), + msg.err(), + ); + } + } + + #[test] + fn parse_channel_close_confirm_msg() { + let id_hex = "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C"; + let acc = AccountId::from_str(id_hex).unwrap(); + + #[derive(Clone, Debug, PartialEq)] + struct CloseConfirmParams { + port_id: String, + channel_id: String, + proof_init: CommitmentProof, + proof_height: u64, + } + + let default_params = CloseConfirmParams { + port_id: "port".to_string(), + channel_id: "testchannel".to_string(), + proof_init: get_dummy_proof(), + proof_height: 10, + }; + + struct Test { + name: String, + params: CloseConfirmParams, + want_pass: bool, + } + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + params: default_params.clone(), + want_pass: true, + }, + Test { + name: "Bad port, name non-alpha".to_string(), + params: CloseConfirmParams { + port_id: "p34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad port, name too short".to_string(), + params: CloseConfirmParams { + port_id: "p".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad port, name too long".to_string(), + params: CloseConfirmParams { + port_id: "abcdefghijklmnopqrstu".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name non-alpha".to_string(), + params: CloseConfirmParams { + channel_id: "channelid34".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name too short".to_string(), + params: CloseConfirmParams { + channel_id: "chshort".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad channel, name too long".to_string(), + params: CloseConfirmParams { + channel_id: "abcdefghijklmnopqrstu".to_string(), + ..default_params.clone() + }, + want_pass: false, + }, + Test { + name: "Bad proof height, height = 0".to_string(), + params: CloseConfirmParams { + proof_height: 0, + ..default_params.clone() + }, + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for test in tests { + let p = test.params.clone(); + + let msg = MsgChannelCloseConfirm::new( p.port_id, p.channel_id, - p.version, - p.order, - p.connection_hops, - p.counterparty_port_id, - p.counterparty_channel_id, + p.proof_init, + p.proof_height, acc, ); - match msg { - Ok(_res) => { - assert!( - test.want_pass, - "MsgChanOpenInit::new should have failed for test {}, \nmsg {:?}", - test.name, - test.params.clone() - ); - } - Err(err) => { - assert!( - !test.want_pass, - "MsgChanOpenInit::new failed for test {}, \nmsg {:?} with err {:?}", - test.name, - test.params.clone(), - err - ); - } - } + assert_eq!( + test.want_pass, + msg.is_ok(), + "MsgChanCloseConfirm::new failed for test {}, \nmsg {:?} with error {:?}", + test.name, + test.params.clone(), + msg.err(), + ); } } } diff --git a/modules/src/ics04_channel/packet.rs b/modules/src/ics04_channel/packet.rs new file mode 100644 index 000000000..8d1689aac --- /dev/null +++ b/modules/src/ics04_channel/packet.rs @@ -0,0 +1,14 @@ +use crate::ics04_channel::error::Error; +use serde_derive::{Deserialize, Serialize}; + +// TODO: Packet needs to be implemented +// This is only a workaround for MsgPacket for now! + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Packet; + +impl Packet { + pub fn validate(&self) -> Result { + Ok(self.clone()) + } +} diff --git a/modules/src/ics23_commitment/mod.rs b/modules/src/ics23_commitment/mod.rs index cd8c9864f..508a35102 100644 --- a/modules/src/ics23_commitment/mod.rs +++ b/modules/src/ics23_commitment/mod.rs @@ -24,6 +24,10 @@ impl CommitmentProof { pub fn from_bytes(_bytes: &[u8]) -> Self { todo!() } + + pub fn validate_basic() -> Result { + todo!() + } } */ diff --git a/modules/src/lib.rs b/modules/src/lib.rs index ce823c623..f677a36c5 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -30,6 +30,7 @@ pub mod ics23_commitment; pub mod ics24_host; pub mod keys; pub mod path; +pub mod proofs; pub mod query; pub mod tx_msg; diff --git a/modules/src/proofs.rs b/modules/src/proofs.rs new file mode 100644 index 000000000..ee5313fd1 --- /dev/null +++ b/modules/src/proofs.rs @@ -0,0 +1,53 @@ +use crate::ics23_commitment::CommitmentProof; +use serde_derive::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Proofs { + object_proof: CommitmentProof, + consensus_proof: Option, + /// Height for both the above proofs + proofs_height: u64, +} + +impl Proofs { + pub fn new( + object_proof: CommitmentProof, + consensus_proof: Option, + proofs_height: u64, + ) -> Result { + if proofs_height == 0 { + return Err("Proofs height cannot be zero".to_string()); + } + + if object_proof.ops.is_empty() { + return Err("Proof cannot be empty".to_string()); + } + + Ok(Self { + object_proof, + consensus_proof, + proofs_height, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ConsensusProof { + consensus_proof: CommitmentProof, + consensus_height: u64, +} + +impl ConsensusProof { + pub fn new(consensus_proof: CommitmentProof, consensus_height: u64) -> Result { + if consensus_height == 0 { + return Err("Consensus height cannot be zero".to_string()); + } + if consensus_proof.ops.is_empty() { + return Err("Proof cannot be empty".to_string()); + } + Ok(Self { + consensus_proof, + consensus_height, + }) + } +}