diff --git a/modules/src/address.rs b/modules/src/address.rs new file mode 100644 index 0000000000..276562d4ed --- /dev/null +++ b/modules/src/address.rs @@ -0,0 +1 @@ +pub struct AccAddress(Vec; diff --git a/modules/src/context.rs b/modules/src/context.rs new file mode 100644 index 0000000000..7360a387ca --- /dev/null +++ b/modules/src/context.rs @@ -0,0 +1,36 @@ +use serde_derive::{Deserialize, Serialize}; +use tendermint::block::Height; + +#[cfg(test)] +use crate::ics02_client::client_def::AnyConsensusState; +#[cfg(test)] +use crate::mock_client::header::MockHeader; +#[cfg(test)] +use crate::mock_client::state::MockConsensusState; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum SelfHeader { + // Tendermint(tendermint::header::Header), + #[cfg(test)] + Mock(MockHeader), +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct HistoricalInfo { + pub header: SelfHeader, +} + +#[cfg(test)] +impl From for AnyConsensusState { + fn from(h: MockHeader) -> Self { + AnyConsensusState::Mock(MockConsensusState(h)) + } +} + +pub trait ChainReader { + fn self_historical_info(&self, height: Height) -> Option<&HistoricalInfo>; +} + +pub trait ChainKeeper { + fn store_historical_info(&mut self, height: Height, info: HistoricalInfo); +} diff --git a/modules/src/context_mock.rs b/modules/src/context_mock.rs new file mode 100644 index 0000000000..5b6db01848 --- /dev/null +++ b/modules/src/context_mock.rs @@ -0,0 +1,142 @@ +use crate::context::{ChainKeeper, ChainReader, HistoricalInfo, SelfHeader}; +use crate::mock_client::header::MockHeader; +use serde_derive::{Deserialize, Serialize}; +use std::error::Error; +use tendermint::block::Height; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct MockChainContext { + pub max_size: usize, + pub latest: Height, + pub history: Vec, +} + +impl MockChainContext { + pub fn new(max_size: usize, n: Height) -> Self { + Self { + max_size, + latest: n, + history: (0..n.value()) + .map(|i| HistoricalInfo { + header: SelfHeader::Mock(MockHeader(Height(i).increment())), + }) + .collect(), + } + } + + pub fn max_size(&self) -> usize { + self.max_size + } + + /// Used for testing + pub fn populate(&mut self, hs: Vec) { + for h in hs { + self.store_historical_info( + Height(h), + HistoricalInfo { + header: SelfHeader::Mock(MockHeader(Height(h))), + }, + ); + } + } + + /// Used for testing + pub fn validate(&self) -> Result<(), Box> { + // check that the number of entries is not higher than max_size + if self.history.len() > self.max_size { + return Err("too many entries".to_string().into()); + } + + // check latest is properly updated with highest header height + let SelfHeader::Mock(lh) = self.history[self.history.len() - 1].header; + if lh.height() != self.latest { + return Err("latest height is not updated".to_string().into()); + } + + // check that all headers are in sequential order + for i in 1..self.history.len() { + let SelfHeader::Mock(ph) = self.history[i - 1].header; + let SelfHeader::Mock(h) = self.history[i].header; + if ph.height().increment() != h.height() { + return Err("headers in history not sequential".to_string().into()); + } + } + Ok(()) + } +} + +impl ChainReader for MockChainContext { + fn self_historical_info(&self, height: Height) -> Option<&HistoricalInfo> { + let l = height.value() as usize; + let h = self.latest.value() as usize; + + if l <= h - self.max_size { + // header with height not in the history + None + } else { + Some(&self.history[h - l]) + } + } +} + +impl ChainKeeper for MockChainContext { + fn store_historical_info(&mut self, height: Height, info: HistoricalInfo) { + if height != self.latest.increment() { + return; + } + let mut history = self.history.clone(); + if history.len() >= self.max_size { + history.rotate_left(1); + history[self.max_size - 1] = info; + } else { + history.push(info); + } + //history.insert(height, info); + self.history = history; + self.latest = height; + } +} + +#[cfg(test)] +mod tests { + use crate::context_mock::MockChainContext; + use tendermint::block::Height; + + #[test] + fn test_store_historical_info() { + pub struct Test { + name: String, + ctx: MockChainContext, + args: Vec, + } + + impl Test { + pub fn apply(&mut self, hs: Vec) { + self.ctx.populate(hs); + } + } + + let tests: Vec = vec![ + Test { + name: "Add no prune".to_string(), + ctx: MockChainContext::new(3, Height(0)), + args: [1].to_vec(), + }, + Test { + name: "Add with prune".to_string(), + ctx: MockChainContext::new(3, Height(2)), + args: [3, 4].to_vec(), + }, + Test { + name: "Attempt to add non sequential headers".to_string(), + ctx: MockChainContext::new(3, Height(2)), + args: [3, 5, 7].to_vec(), + }, + ]; + + for mut test in tests { + test.apply(test.args.clone()); + assert!(test.ctx.validate().is_ok()); + } + } +} diff --git a/modules/src/events.rs b/modules/src/events.rs index fb42fd0e88..534e5b3d85 100644 --- a/modules/src/events.rs +++ b/modules/src/events.rs @@ -11,6 +11,7 @@ use tendermint::block; use tendermint_rpc::event_listener::{ResultEvent, TMEventData}; +/// Events created by the IBC component of a chain, destined for a relayer. #[derive(Debug, Clone, Deserialize, Serialize)] pub enum IBCEvent { NewBlock(NewBlock), diff --git a/modules/src/handler.rs b/modules/src/handler.rs index 05448f412f..22846862e5 100644 --- a/modules/src/handler.rs +++ b/modules/src/handler.rs @@ -20,8 +20,8 @@ pub enum EventType { #[derive(Clone, Debug, PartialEq, Eq)] pub struct Event { - tpe: EventType, - attributes: Vec, + pub tpe: EventType, + pub attributes: Vec, } impl Event { diff --git a/modules/src/ics02_client/client_def.rs b/modules/src/ics02_client/client_def.rs index 592281dbc0..80e86a151e 100644 --- a/modules/src/ics02_client/client_def.rs +++ b/modules/src/ics02_client/client_def.rs @@ -3,12 +3,20 @@ use serde_derive::{Deserialize, Serialize}; use crate::ics02_client::client_type::ClientType; use crate::ics02_client::header::Header; use crate::ics02_client::state::{ClientState, ConsensusState}; -use crate::ics23_commitment::CommitmentRoot; +use crate::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProof, CommitmentRoot}; use crate::Height; -use crate::ics02_client::mocks; +use crate::ics03_connection::connection::ConnectionEnd; use crate::ics07_tendermint as tendermint; use crate::ics07_tendermint::client_def::TendermintClient; +use crate::ics24_host::identifier::{ClientId, ConnectionId}; + +#[cfg(test)] +use crate::mock_client::client_def::MockClient; +#[cfg(test)] +use crate::mock_client::header::MockHeader; +#[cfg(test)] +use crate::mock_client::state::{MockClientState, MockConsensusState}; pub trait ClientDef: Clone { type Header: Header; @@ -19,30 +27,62 @@ pub trait ClientDef: Clone { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] // TODO: Add Eq #[allow(clippy::large_enum_variant)] pub enum AnyHeader { - Mock(mocks::MockHeader), Tendermint(tendermint::header::Header), + + #[cfg(test)] + Mock(MockHeader), } impl Header for AnyHeader { fn client_type(&self) -> ClientType { match self { - Self::Mock(header) => header.client_type(), Self::Tendermint(header) => header.client_type(), + #[cfg(test)] + Self::Mock(header) => header.client_type(), } } fn height(&self) -> Height { match self { - Self::Mock(header) => header.height(), Self::Tendermint(header) => header.height(), + #[cfg(test)] + Self::Mock(header) => header.height(), } } } #[derive(Clone, Debug, PartialEq)] pub enum AnyClientState { - Mock(mocks::MockClientState), Tendermint(crate::ics07_tendermint::client_state::ClientState), + + #[cfg(test)] + Mock(MockClientState), +} + +impl AnyClientState { + pub fn check_header_and_update_state( + &self, + header: AnyHeader, + ) -> Result<(AnyClientState, AnyConsensusState), Box> { + match self { + AnyClientState::Tendermint(tm_state) => { + let (new_state, new_consensus) = tm_state.check_header_and_update_state(header)?; + Ok(( + AnyClientState::Tendermint(new_state), + AnyConsensusState::Tendermint(new_consensus), + )) + } + #[cfg(test)] + AnyClientState::Mock(mock_state) => { + let (new_state, new_consensus) = + mock_state.check_header_and_update_state(header)?; + Ok(( + AnyClientState::Mock(new_state), + AnyConsensusState::Mock(new_consensus), + )) + } + } + } } impl ClientState for AnyClientState { @@ -54,29 +94,100 @@ impl ClientState for AnyClientState { todo!() } - fn get_latest_height(&self) -> Height { + fn latest_height(&self) -> Height { match self { - AnyClientState::Tendermint(tm_state) => tm_state.get_latest_height(), - AnyClientState::Mock(mock_state) => mock_state.get_latest_height(), + AnyClientState::Tendermint(tm_state) => tm_state.latest_height(), + + #[cfg(test)] + AnyClientState::Mock(mock_state) => mock_state.latest_height(), } } fn is_frozen(&self) -> bool { - todo!() + match self { + AnyClientState::Tendermint(tm_state) => tm_state.is_frozen(), + + #[cfg(test)] + AnyClientState::Mock(mock_state) => mock_state.is_frozen(), + } } + // fn check_header_and_update_state( + // &self, + // header: &dyn Header, + // ) -> Result<(Box, Box), Box> { + // match self { + // AnyClientState::Tendermint(tm_state) => tm_state.check_header_and_update_state(header), + // AnyClientState::Mock(mock_state) => mock_state.check_header_and_update_state(header), + // } + // } + fn verify_client_consensus_state( &self, - _root: &CommitmentRoot, + height: Height, + prefix: &CommitmentPrefix, + proof: &CommitmentProof, + client_id: &ClientId, + consensus_height: Height, + expected_consensus_state: &dyn ConsensusState, ) -> Result<(), Box> { - todo!() + match self { + AnyClientState::Tendermint(tm_state) => tm_state.verify_client_consensus_state( + height, + prefix, + proof, + client_id, + consensus_height, + expected_consensus_state, + ), + + #[cfg(test)] + AnyClientState::Mock(mock_state) => mock_state.verify_client_consensus_state( + height, + prefix, + proof, + client_id, + consensus_height, + expected_consensus_state, + ), + } + } + + fn verify_connection_state( + &self, + height: Height, + prefix: &CommitmentPrefix, + proof: &CommitmentProof, + connection_id: &ConnectionId, + expected_connection_end: &ConnectionEnd, + ) -> Result<(), Box> { + match self { + AnyClientState::Tendermint(tm_state) => tm_state.verify_connection_state( + height, + prefix, + proof, + connection_id, + expected_connection_end, + ), + + #[cfg(test)] + AnyClientState::Mock(mock_state) => mock_state.verify_connection_state( + height, + prefix, + proof, + connection_id, + expected_connection_end, + ), + } } } #[derive(Clone, Debug, PartialEq)] pub enum AnyConsensusState { - Mock(mocks::MockConsensusState), Tendermint(crate::ics07_tendermint::consensus_state::ConsensusState), + + #[cfg(test)] + Mock(MockConsensusState), } impl ConsensusState for AnyConsensusState { @@ -99,8 +210,10 @@ impl ConsensusState for AnyConsensusState { #[derive(Clone, Debug, PartialEq, Eq)] pub enum AnyClient { - Mock(mocks::MockClient), Tendermint(TendermintClient), + + #[cfg(test)] + Mock(MockClient), } impl ClientDef for AnyClient { diff --git a/modules/src/ics02_client/client_type.rs b/modules/src/ics02_client/client_type.rs index a9d1e7ab60..bd152872ea 100644 --- a/modules/src/ics02_client/client_type.rs +++ b/modules/src/ics02_client/client_type.rs @@ -6,6 +6,9 @@ use serde_derive::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub enum ClientType { Tendermint = 1, + + #[cfg(test)] + Mock = 9999, } impl ClientType { @@ -13,6 +16,9 @@ impl ClientType { pub fn as_string(&self) -> &'static str { match self { Self::Tendermint => "tendermint", + + #[cfg(test)] + Self::Mock => "mock", } } } diff --git a/modules/src/ics02_client/context.rs b/modules/src/ics02_client/context.rs new file mode 100644 index 0000000000..ea14a8c9f7 --- /dev/null +++ b/modules/src/ics02_client/context.rs @@ -0,0 +1,31 @@ +use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState}; +use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::error::Error; +use crate::ics24_host::identifier::ClientId; +use crate::Height; + +pub trait ClientReader { + fn client_type(&self, client_id: &ClientId) -> Option; + fn client_state(&self, client_id: &ClientId) -> Option; + fn consensus_state(&self, client_id: &ClientId, height: Height) -> Option; +} + +pub trait ClientKeeper { + fn store_client_type( + &mut self, + client_id: ClientId, + client_type: ClientType, + ) -> Result<(), Error>; + + fn store_client_state( + &mut self, + client_id: ClientId, + client_state: AnyClientState, + ) -> Result<(), Error>; + + fn store_consensus_state( + &mut self, + client_id: ClientId, + consensus_state: AnyConsensusState, + ) -> Result<(), Error>; +} diff --git a/modules/src/ics02_client/context_mock.rs b/modules/src/ics02_client/context_mock.rs new file mode 100644 index 0000000000..e415b90fe6 --- /dev/null +++ b/modules/src/ics02_client/context_mock.rs @@ -0,0 +1,103 @@ +use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState}; +use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::context::{ClientKeeper, ClientReader}; +use crate::ics02_client::error::Error; +use crate::ics24_host::identifier::ClientId; +use crate::mock_client::header::MockHeader; +use crate::mock_client::state::{MockClientState, MockConsensusState}; +use tendermint::block::Height; + +#[derive(Clone, Debug, PartialEq)] +pub struct MockClientContext { + pub client_id: ClientId, + pub client_state: Option, + pub client_type: Option, + pub consensus_state: Option, +} + +impl Default for MockClientContext { + fn default() -> Self { + MockClientContext { + client_id: "defaultclientid".to_string().parse().unwrap(), + client_state: None, + client_type: None, + consensus_state: None, + } + } +} + +impl MockClientContext { + pub fn new(client_id: &ClientId) -> Self { + MockClientContext { + client_id: client_id.clone(), + client_type: None, + client_state: None, + consensus_state: None, + } + } + + pub fn with_client_type(&mut self, client_type: ClientType) { + self.client_type = Option::from(client_type); + } + + pub fn with_client_state(&mut self, client_id: &ClientId, h: u64) { + self.client_id = client_id.clone(); + self.client_type = Option::from(ClientType::Mock); + self.client_state = Option::from(MockClientState(MockHeader(Height(h)))); + self.consensus_state = Option::from(MockConsensusState(MockHeader(Height(h)))); + } +} + +impl ClientReader for MockClientContext { + fn client_type(&self, client_id: &ClientId) -> Option { + if client_id == &self.client_id { + self.client_type.clone() + } else { + None + } + } + + #[allow(trivial_casts)] + fn client_state(&self, client_id: &ClientId) -> Option { + if client_id == &self.client_id { + self.client_state.map(Into::into) + } else { + None + } + } + + #[allow(trivial_casts)] + fn consensus_state(&self, client_id: &ClientId, _height: Height) -> Option { + if client_id == &self.client_id { + self.consensus_state.map(Into::into) + } else { + None + } + } +} + +impl ClientKeeper for MockClientContext { + fn store_client_type( + &mut self, + _client_id: ClientId, + _client_type: ClientType, + ) -> Result<(), Error> { + todo!() + } + + fn store_client_state( + &mut self, + _client_id: ClientId, + _client_state: AnyClientState, + ) -> Result<(), Error> { + todo!() + } + + fn store_consensus_state( + &mut self, + _client_id: ClientId, + _consensus_state: AnyConsensusState, + ) -> Result<(), Error> { + todo!() + } +} diff --git a/modules/src/ics02_client/error.rs b/modules/src/ics02_client/error.rs index 7c1acb0b4b..c3dcf9b6e7 100644 --- a/modules/src/ics02_client/error.rs +++ b/modules/src/ics02_client/error.rs @@ -22,6 +22,9 @@ pub enum Kind { #[error("implementation specific")] ImplementationSpecific, + + #[error("header verification failed")] + HeaderVerificationFailure, } impl Kind { diff --git a/modules/src/ics02_client/handler.rs b/modules/src/ics02_client/handler.rs index cb80a63919..2a8b3bf996 100644 --- a/modules/src/ics02_client/handler.rs +++ b/modules/src/ics02_client/handler.rs @@ -1,44 +1,14 @@ -#![allow(unused_imports)] - use crate::handler::{Event, EventType, HandlerOutput}; -use crate::ics02_client::client_def::{AnyClient, AnyClientState, AnyConsensusState, ClientDef}; -use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::client_def::AnyClient; use crate::ics02_client::error::Error; -use crate::ics02_client::msgs::{MsgCreateAnyClient, MsgUpdateAnyClient}; -use crate::ics02_client::state::{ClientState, ConsensusState}; +use crate::ics02_client::msgs::ClientMsg; use crate::ics24_host::identifier::ClientId; -use crate::Height; +use crate::ics02_client::context::{ClientKeeper, ClientReader}; pub mod create_client; pub mod update_client; -pub trait ClientReader { - fn client_type(&self, client_id: &ClientId) -> Option; - fn client_state(&self, client_id: &ClientId) -> Option; - fn consensus_state(&self, client_id: &ClientId, height: Height) -> Option; -} - -pub trait ClientKeeper { - fn store_client_type( - &mut self, - client_id: ClientId, - client_type: ClientType, - ) -> Result<(), Error>; - - fn store_client_state( - &mut self, - client_id: ClientId, - client_state: AnyClientState, - ) -> Result<(), Error>; - - fn store_consensus_state( - &mut self, - client_id: ClientId, - consensus_state: AnyConsensusState, - ) -> Result<(), Error>; -} - #[derive(Clone, Debug, PartialEq, Eq)] pub enum ClientEvent { ClientCreated(ClientId), @@ -60,11 +30,6 @@ impl From for Event { } } -pub enum ClientMsg { - CreateClient(MsgCreateAnyClient), - UpdateClient(MsgUpdateAnyClient), -} - pub fn dispatch(ctx: &mut Ctx, msg: ClientMsg) -> Result, Error> where Ctx: ClientReader + ClientKeeper, diff --git a/modules/src/ics02_client/handler/create_client.rs b/modules/src/ics02_client/handler/create_client.rs index 45f19200b9..e7c1eed5f4 100644 --- a/modules/src/ics02_client/handler/create_client.rs +++ b/modules/src/ics02_client/handler/create_client.rs @@ -1,14 +1,11 @@ -#![allow(unreachable_code, unused_variables)] - use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics02_client::client_def::{AnyClient, ClientDef}; use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::context::{ClientKeeper, ClientReader}; use crate::ics02_client::error::{Error, Kind}; -use crate::ics02_client::handler::{ClientEvent, ClientKeeper, ClientReader}; +use crate::ics02_client::handler::ClientEvent; use crate::ics02_client::msgs::MsgCreateAnyClient; -use crate::ics02_client::state::{ClientState, ConsensusState}; use crate::ics24_host::identifier::ClientId; -use std::time::Duration; #[derive(Debug)] pub struct CreateClientResult { @@ -67,33 +64,25 @@ pub fn keep( #[cfg(test)] mod tests { use super::*; - use crate::ics02_client::header::Header; - use crate::ics02_client::mocks::*; - use crate::ics02_client::state::{ClientState, ConsensusState}; - use crate::ics07_tendermint::client_def::TendermintClient; + use crate::ics02_client::context_mock::MockClientContext; use crate::ics07_tendermint::header::test_util::get_dummy_header; use crate::ics07_tendermint::msgs::create_client::MsgCreateClient; - use crate::ics23_commitment::CommitmentRoot; - use crate::Height; + use crate::mock_client::header::MockHeader; + use crate::mock_client::state::{MockClientState, MockConsensusState}; use std::str::FromStr; - use thiserror::Error; + use std::time::Duration; + use tendermint::block::Height; #[test] fn test_create_client_ok() { let client_id: ClientId = "mockclient".parse().unwrap(); - - let reader = MockClientReader { - client_id: client_id.clone(), - client_type: None, - client_state: None, - consensus_state: None, - }; + let reader = MockClientContext::new(&client_id); let msg = MsgCreateAnyClient { client_id, - client_type: ClientType::Tendermint, - client_state: MockClientState(42).into(), - consensus_state: MockConsensusState(42).into(), + client_type: ClientType::Mock, + client_state: MockClientState(MockHeader(Height(42))).into(), + consensus_state: MockConsensusState(MockHeader(Height(42))).into(), }; let output = process(&reader, msg.clone()); @@ -104,7 +93,7 @@ mod tests { events, log, }) => { - assert_eq!(result.client_type, ClientType::Tendermint); + assert_eq!(result.client_type, ClientType::Mock); assert_eq!( events, vec![ClientEvent::ClientCreated(msg.client_id).into()] @@ -126,19 +115,14 @@ mod tests { #[test] fn test_create_client_existing_client_type() { let client_id: ClientId = "mockclient".parse().unwrap(); - - let reader = MockClientReader { - client_id: client_id.clone(), - client_type: Some(ClientType::Tendermint), - client_state: None, - consensus_state: None, - }; + let mut reader = MockClientContext::new(&client_id); + reader.with_client_type(ClientType::Mock); let msg = MsgCreateAnyClient { client_id, - client_type: ClientType::Tendermint, - client_state: MockClientState(42).into(), - consensus_state: MockConsensusState(42).into(), + client_type: ClientType::Mock, + client_state: MockClientState(MockHeader(Height(42))).into(), + consensus_state: MockConsensusState(MockHeader(Height(42))).into(), }; let output = process(&reader, msg.clone()); @@ -153,19 +137,14 @@ mod tests { #[test] fn test_create_client_existing_client_state() { let client_id: ClientId = "mockclient".parse().unwrap(); - - let reader = MockClientReader { - client_id: client_id.clone(), - client_type: None, - client_state: Some(MockClientState(0)), - consensus_state: None, - }; + let mut reader = MockClientContext::new(&client_id); + reader.with_client_state(&client_id, 30); let msg = MsgCreateAnyClient { client_id, client_type: ClientType::Tendermint, - client_state: MockClientState(42).into(), - consensus_state: MockConsensusState(42).into(), + client_state: MockClientState(MockHeader(Height(42))).into(), + consensus_state: MockConsensusState(MockHeader(Height(42))).into(), }; let output = process(&reader, msg.clone()); @@ -179,15 +158,8 @@ mod tests { #[test] fn test_tm_create_client_ok() { use tendermint::account::Id as AccountId; - let client_id: ClientId = "tendermint".parse().unwrap(); - - let reader = MockClientReader { - client_id: client_id.clone(), - client_type: None, - client_state: None, - consensus_state: None, - }; + let reader = MockClientContext::new(&client_id); let ics_msg = MsgCreateClient { client_id, diff --git a/modules/src/ics02_client/handler/update_client.rs b/modules/src/ics02_client/handler/update_client.rs index d548bfed9e..b288026c4b 100644 --- a/modules/src/ics02_client/handler/update_client.rs +++ b/modules/src/ics02_client/handler/update_client.rs @@ -2,10 +2,12 @@ use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics02_client::client_def::{AnyClient, ClientDef}; +use crate::ics02_client::context::{ClientKeeper, ClientReader}; use crate::ics02_client::error::{Error, Kind}; -use crate::ics02_client::handler::{ClientEvent, ClientKeeper, ClientReader}; +use crate::ics02_client::handler::ClientEvent; + use crate::ics02_client::msgs::MsgUpdateAnyClient; -use crate::ics02_client::state::{ClientState, ConsensusState}; +use crate::ics02_client::state::ClientState; use crate::ics24_host::identifier::ClientId; #[derive(Debug)] @@ -31,7 +33,7 @@ pub fn process( .client_state(&client_id) .ok_or_else(|| Kind::ClientNotFound(client_id.clone()))?; - let latest_height = client_state.get_latest_height(); + let latest_height = client_state.latest_height(); let consensus_state = ctx .consensus_state(&client_id, latest_height) .ok_or_else(|| Kind::ConsensusStateNotFound(client_id.clone(), latest_height))?; @@ -39,16 +41,16 @@ pub fn process( // Use client_state to validate the new header against the latest consensus_state. // This function will return the new client_state (its latest_height changed) and a // consensus_state obtained from header. These will be later persisted by the keeper. - // FIXME - // (new_client_state, new_consensus_state) = - // CD::check_validity_and_update_state(client_state, consensus_state, &header)?; + let (new_client_state, new_consensus_state) = client_state + .check_header_and_update_state(header) + .map_err(|_| Kind::HeaderVerificationFailure)?; output.emit(ClientEvent::ClientUpdated(client_id.clone())); Ok(output.with_result(UpdateClientResult { client_id, - client_state, // new_client_state - consensus_state, // new_consensus_state + client_state: new_client_state, + consensus_state: new_consensus_state, })) } @@ -66,25 +68,23 @@ pub fn keep( mod tests { use super::*; use crate::ics02_client::client_type::ClientType; - use crate::ics02_client::header::Header; - use crate::ics02_client::mocks::*; - use crate::ics02_client::state::{ClientState, ConsensusState}; - use crate::ics23_commitment::CommitmentRoot; - use crate::Height; - use thiserror::Error; + use crate::ics02_client::context_mock::MockClientContext; + use crate::mock_client::header::MockHeader; + use crate::mock_client::state::{MockClientState, MockConsensusState}; + use tendermint::block::Height; #[test] fn test_update_client_ok() { - let mock = MockClientReader { + let mock = MockClientContext { client_id: "mockclient".parse().unwrap(), client_type: Some(ClientType::Tendermint), - client_state: MockClientState(42).into(), - consensus_state: MockConsensusState(42).into(), + client_state: MockClientState(MockHeader(Height(42))).into(), + consensus_state: MockConsensusState(MockHeader(Height(42))).into(), }; let msg = MsgUpdateAnyClient { client_id: "mockclient".parse().unwrap(), - header: MockHeader(46).into(), + header: MockHeader(Height(46)).into(), }; let output = process(&mock, msg.clone()); diff --git a/modules/src/ics02_client/mocks.rs b/modules/src/ics02_client/mocks.rs deleted file mode 100644 index 1900fc379f..0000000000 --- a/modules/src/ics02_client/mocks.rs +++ /dev/null @@ -1,185 +0,0 @@ -use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader, ClientDef}; -use crate::ics02_client::client_type::ClientType; -use crate::ics02_client::error::Error; -use crate::ics02_client::handler::{ClientKeeper, ClientReader}; -use crate::ics02_client::header::Header; -use crate::ics02_client::state::{ClientState, ConsensusState}; -use crate::ics23_commitment::CommitmentRoot; -use crate::ics24_host::identifier::ClientId; -use crate::Height; - -use serde_derive::{Deserialize, Serialize}; -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum MockError {} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MockHeader(pub u32); - -impl From for AnyHeader { - fn from(mh: MockHeader) -> Self { - Self::Mock(mh) - } -} - -impl Header for MockHeader { - fn client_type(&self) -> ClientType { - todo!() - } - - fn height(&self) -> Height { - todo!() - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MockClientState(pub u32); - -impl From for AnyClientState { - fn from(mcs: MockClientState) -> Self { - Self::Mock(mcs) - } -} - -impl ClientState for MockClientState { - fn chain_id(&self) -> String { - todo!() - } - - fn client_type(&self) -> ClientType { - todo!() - } - - fn get_latest_height(&self) -> Height { - Height::from(self.0 as u64) - } - - fn is_frozen(&self) -> bool { - todo!() - } - - fn verify_client_consensus_state( - &self, - _root: &CommitmentRoot, - ) -> Result<(), Box> { - todo!() - } -} - -impl From for MockClientState { - fn from(cs: MockConsensusState) -> Self { - Self(cs.0) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct MockConsensusState(pub u32); - -impl From for AnyConsensusState { - fn from(mcs: MockConsensusState) -> Self { - Self::Mock(mcs) - } -} - -impl ConsensusState for MockConsensusState { - fn client_type(&self) -> ClientType { - todo!() - } - - fn height(&self) -> Height { - todo!() - } - - fn root(&self) -> &CommitmentRoot { - todo!() - } - - fn validate_basic(&self) -> Result<(), Box> { - todo!() - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct MockClient; - -impl ClientDef for MockClient { - type Header = MockHeader; - type ClientState = MockClientState; - type ConsensusState = MockConsensusState; -} - -#[derive(Clone, Debug, PartialEq)] -pub struct MockClientContext { - reader: MockClientReader, - keeper: MockClientKeeper, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct MockClientReader { - pub client_id: ClientId, - pub client_state: Option, - pub client_type: Option, - pub consensus_state: Option, -} - -impl ClientReader for MockClientReader { - fn client_type(&self, client_id: &ClientId) -> Option { - if client_id == &self.client_id { - self.client_type.clone() - } else { - None - } - } - - #[allow(trivial_casts)] - fn client_state(&self, client_id: &ClientId) -> Option { - if client_id == &self.client_id { - self.client_state.map(Into::into) - } else { - None - } - } - - #[allow(trivial_casts)] - fn consensus_state(&self, client_id: &ClientId, _height: Height) -> Option { - if client_id == &self.client_id { - self.consensus_state.map(Into::into) - } else { - None - } - } -} - -#[derive(Clone, Debug, PartialEq, Default)] -pub struct MockClientKeeper { - pub client_state: Option, - pub client_type: Option, - pub consensus_state: Option, -} - -impl ClientKeeper for MockClientKeeper { - fn store_client_type( - &mut self, - _client_id: ClientId, - _client_type: ClientType, - ) -> Result<(), Error> { - todo!() - } - - fn store_client_state( - &mut self, - _client_id: ClientId, - _client_state: AnyClientState, - ) -> Result<(), Error> { - todo!() - } - - fn store_consensus_state( - &mut self, - _client_id: ClientId, - _consensus_state: AnyConsensusState, - ) -> Result<(), Error> { - todo!() - } -} diff --git a/modules/src/ics02_client/mod.rs b/modules/src/ics02_client/mod.rs index 9f5caea073..3931ace660 100644 --- a/modules/src/ics02_client/mod.rs +++ b/modules/src/ics02_client/mod.rs @@ -2,11 +2,14 @@ pub mod client_def; pub mod client_type; +pub mod context; pub mod error; pub mod events; pub mod handler; pub mod header; -pub mod mocks; pub mod msgs; pub mod raw; pub mod state; + +#[cfg(test)] +pub mod context_mock; diff --git a/modules/src/ics02_client/msgs.rs b/modules/src/ics02_client/msgs.rs index 6ba0bda9be..25e99bfe01 100644 --- a/modules/src/ics02_client/msgs.rs +++ b/modules/src/ics02_client/msgs.rs @@ -8,6 +8,11 @@ use crate::ics02_client::client_def::ClientDef; use crate::ics02_client::client_type::ClientType; use crate::ics24_host::identifier::ClientId; +pub enum ClientMsg { + CreateClient(MsgCreateAnyClient), + UpdateClient(MsgUpdateAnyClient), +} + /// A type of message that triggers the creation of a new on-chain (IBC) client. #[derive(Clone, Debug)] pub struct MsgCreateAnyClient { diff --git a/modules/src/ics02_client/state.rs b/modules/src/ics02_client/state.rs index eede4f6ec1..335565ee78 100644 --- a/modules/src/ics02_client/state.rs +++ b/modules/src/ics02_client/state.rs @@ -1,5 +1,7 @@ use super::client_type::ClientType; -use crate::ics23_commitment::CommitmentRoot; +use crate::ics03_connection::connection::ConnectionEnd; +use crate::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProof, CommitmentRoot}; +use crate::ics24_host::identifier::{ClientId, ConnectionId}; use crate::Height; #[dyn_clonable::clonable] @@ -26,16 +28,44 @@ pub trait ClientState: Clone + std::fmt::Debug { fn client_type(&self) -> ClientType; /// Latest height of consensus state - fn get_latest_height(&self) -> Height; + fn latest_height(&self) -> Height; /// Freeze status of the client fn is_frozen(&self) -> bool; - /// Verifies a proof of the consensus state of the specified client stored on the target machine. - /// FIXME: Definition is incomplete. - /// See https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics#required-functions + // TODO - to be cleaned up by Romain + // fn check_header_and_update_state( + // &self, + // _header: &dyn Header, + // ) -> Result<(Box, Box), Box> { + // todo!() + // } + + /// Verification functions as specified in: + /// https://github.com/cosmos/ics/tree/master/spec/ics-002-client-semantics + /// + /// Verify a `proof` that the consensus state of a given client (at height `consensus_height`) + /// matches the input `consensus_state`. The parameter `counterparty_height` represent the + /// height of the counterparty chain that this proof assumes (i.e., the height at which this + /// proof was computed). fn verify_client_consensus_state( &self, - root: &CommitmentRoot, + height: Height, + prefix: &CommitmentPrefix, + proof: &CommitmentProof, + client_id: &ClientId, + consensus_height: Height, + expected_consensus_state: &dyn ConsensusState, + ) -> Result<(), Box>; + + /// Verify a `proof` that a connection state matches that of the input `connection_end`. + // TODO: ValidationError seems wrong here. + fn verify_connection_state( + &self, + height: Height, + prefix: &CommitmentPrefix, + proof: &CommitmentProof, + connection_id: &ConnectionId, + expected_connection_end: &ConnectionEnd, ) -> Result<(), Box>; } diff --git a/modules/src/ics03_connection/connection.rs b/modules/src/ics03_connection/connection.rs index 087de2cfa6..dc535fc0c1 100644 --- a/modules/src/ics03_connection/connection.rs +++ b/modules/src/ics03_connection/connection.rs @@ -1,13 +1,13 @@ use crate::ics03_connection::error::{Error, Kind}; -use crate::ics23_commitment::CommitmentPrefix; +use crate::ics23_commitment::commitment::CommitmentPrefix; use crate::ics24_host::identifier::{ClientId, ConnectionId}; use crate::try_from_raw::TryFromRaw; use serde_derive::{Deserialize, Serialize}; // Import proto declarations. -use ibc_proto::connection::ConnectionEnd as RawConnectionEnd; -use ibc_proto::connection::Counterparty as RawCounterparty; -use std::convert::TryFrom; +use crate::ics24_host::error::ValidationError; +use ibc_proto::connection::{ConnectionEnd as RawConnectionEnd, Counterparty as RawCounterparty}; +use std::convert::{TryFrom, TryInto}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ConnectionEnd { @@ -21,71 +21,89 @@ impl TryFromRaw for ConnectionEnd { type RawType = RawConnectionEnd; type Error = anomaly::Error; fn try_from(value: RawConnectionEnd) -> Result { - // Todo: Is validation complete here? (Code was moved from `from_proto_connection_end`.) if value.id == "" { return Err(Kind::ConnectionNotFound.into()); } - // The Counterparty field is an Option, may be missing. - match value.counterparty { - Some(cp) => { - let mut conn = ConnectionEnd::new( - value - .client_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, - Counterparty::try_from(cp)?, - value.versions, - ) - .unwrap(); - - // Set the state. - conn.set_state(State::from_i32(value.state)); - Ok(conn) - } - - // If no counterparty was set, signal the error. - None => Err(Kind::MissingCounterparty.into()), - } + Ok(Self::new( + State::from_i32(value.state), + value + .client_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + value + .counterparty + .ok_or_else(|| Kind::MissingCounterparty)? + .try_into()?, + value.versions, + )?) } } impl ConnectionEnd { pub fn new( + state: State, client_id: ClientId, counterparty: Counterparty, - versions: Vec, + versions: Vec, // TODO: Use Newtype for aliasing the version to a string ) -> Result { Ok(Self { - state: State::Uninitialized, + state, client_id, counterparty, versions: validate_versions(versions).map_err(|e| Kind::InvalidVersion.context(e))?, }) } + /// Setter for the `state` field. pub fn set_state(&mut self, new_state: State) { self.state = new_state; } - pub fn state(&self) -> &State { - &self.state + /// Setter for the `version` field. + /// TODO: A ConnectionEnd should only store one version. + pub fn set_version(&mut self, new_version: String) { + self.versions.insert(0, new_version) } - pub fn client_id(&self) -> String { - self.client_id.as_str().into() + /// Helper function to compare the counterparty of this end with another counterparty. + pub fn counterparty_matches(&self, other: &Counterparty) -> bool { + self.counterparty.eq(other) } - pub fn counterparty(&self) -> Counterparty { - self.counterparty.clone() + /// Helper function to compare the client id of this end with another client identifier. + pub fn client_id_matches(&self, other: &ClientId) -> bool { + self.client_id.eq(other) + } + + /// Helper function to compare the state of this end with another state. + pub fn state_matches(&self, other: &State) -> bool { + 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 + } + + /// Getter for the list of versions in this connection end. pub fn versions(&self) -> Vec { self.versions.clone() } - fn validate_basic(&self) -> Result<(), Error> { - self.counterparty().validate_basic() + /// Getter for the counterparty. Returns a `clone()`. + pub fn counterparty(&self) -> Counterparty { + self.counterparty.clone() + } + + /// TODO: Clean this up, probably not necessary. + pub fn validate_basic(&self) -> Result<(), ValidationError> { + self.counterparty.validate_basic() } } @@ -96,52 +114,58 @@ pub struct Counterparty { prefix: CommitmentPrefix, } +// Converts from the wire format RawCounterparty. Typically used from the relayer side +// during queries for response validation and to extract the Counterparty structure. impl TryFrom for Counterparty { type Error = anomaly::Error; fn try_from(value: RawCounterparty) -> Result { - // Todo: Is validation complete here? (code was moved from `from_proto_counterparty`) - match value.prefix { - Some(prefix) => Counterparty::new( - value.client_id, - value.connection_id, - CommitmentPrefix::new(prefix.key_prefix), - ), - None => Err(Kind::MissingCounterpartyPrefix.into()), - } + Ok(Counterparty::new( + value + .client_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + value + .connection_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + value + .prefix + .ok_or_else(|| Kind::MissingCounterparty)? + .key_prefix + .into(), + )?) } } impl Counterparty { pub fn new( - client_id: String, - connection_id: String, + client_id: ClientId, + connection_id: ConnectionId, prefix: CommitmentPrefix, ) -> Result { Ok(Self { - client_id: client_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, - connection_id: connection_id - .parse() - .map_err(|e| Kind::IdentifierError.context(e))?, + client_id, + connection_id, prefix, }) } - pub fn client_id(&self) -> String { - self.client_id.as_str().into() + /// Getter for the client id. + pub fn client_id(&self) -> &ClientId { + &self.client_id } - pub fn connection_id(&self) -> String { - self.connection_id.as_str().into() + /// Getter for connection id. + pub fn connection_id(&self) -> &ConnectionId { + &self.connection_id } pub fn prefix(&self) -> &CommitmentPrefix { &self.prefix } - pub fn validate_basic(&self) -> Result<(), Error> { + pub fn validate_basic(&self) -> Result<(), ValidationError> { Ok(()) } } @@ -165,6 +189,24 @@ pub fn validate_version(version: String) -> Result { Ok(version) } +/// Function required by ICS 03. Returns the list of all possible versions that the connection +/// handshake protocol supports. +/// TODO: What are the precise values for the versions which this function returns? Perhaps encode the versions as constants. +pub fn get_compatible_versions() -> Vec { + vec!["test".to_string()] +} + +/// Function required by ICS 03. Returns one version out of the supplied list of versions, which the +/// connection handshake protocol prefers. +/// TODO: Fix this with proper code. +pub fn pick_version(candidates: Vec) -> Option { + let selection: String = candidates + .get(0) + .unwrap_or(&String::from("none")) + .to_string(); + Some(selection) +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum State { Uninitialized = 0, diff --git a/modules/src/ics03_connection/context.rs b/modules/src/ics03_connection/context.rs new file mode 100644 index 0000000000..6da0c61abf --- /dev/null +++ b/modules/src/ics03_connection/context.rs @@ -0,0 +1,41 @@ +use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState}; +use crate::ics03_connection::connection::ConnectionEnd; +use crate::ics03_connection::error::Error; +use crate::ics23_commitment::commitment::CommitmentPrefix; +use crate::ics24_host::identifier::{ClientId, ConnectionId}; +use crate::Height; + +/// A context supplying all the necessary dependencies for processing any `ICS3Msg`. +pub trait ConnectionReader { + /// Returns the ConnectionEnd for the given identifier `conn_id`. + fn fetch_connection_end(&self, conn_id: &ConnectionId) -> Option<&ConnectionEnd>; + + /// Returns the ClientState for the given identifier `client_id`. + fn fetch_client_state(&self, client_id: &ClientId) -> Option; + + /// Returns the current height of the local chain. + fn chain_current_height(&self) -> Height; + + /// Returns the number of consensus state historical entries for the local chain. + fn chain_consensus_states_history_size(&self) -> usize; + + /// Returns the prefix that the local chain uses in the KV store. + fn commitment_prefix(&self) -> CommitmentPrefix; + + /// Returns the ConsensusState of the local chain at a specific height. + fn fetch_client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Option; + + fn fetch_self_consensus_state(&self, height: Height) -> Option; +} + +pub trait ConnectionKeeper { + fn store_connection( + &mut self, + connection_id: ConnectionId, + connection_end: ConnectionEnd, + ) -> Result<(), Error>; +} diff --git a/modules/src/ics03_connection/context_mock.rs b/modules/src/ics03_connection/context_mock.rs new file mode 100644 index 0000000000..a4be82d707 --- /dev/null +++ b/modules/src/ics03_connection/context_mock.rs @@ -0,0 +1,97 @@ +use crate::context::{ChainReader, SelfHeader}; +use crate::context_mock::MockChainContext; +use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState}; +use crate::ics02_client::context::ClientReader; +use crate::ics02_client::context_mock::MockClientContext; +use crate::ics03_connection::connection::ConnectionEnd; +use crate::ics03_connection::context::{ConnectionKeeper, ConnectionReader}; +use crate::ics03_connection::error::Error; +use crate::ics23_commitment::commitment::CommitmentPrefix; +use crate::ics24_host::identifier::{ClientId, ConnectionId}; +use std::collections::HashMap; +use tendermint::block::Height; + +#[derive(Clone, Debug, PartialEq)] +pub struct MockConnectionContext { + chain_context: MockChainContext, + client_context: MockClientContext, + connections: HashMap, +} + +impl MockConnectionContext { + pub fn new(chain_height: u64, max_history_size: usize) -> Self { + MockConnectionContext { + chain_context: MockChainContext::new(max_history_size, Height(chain_height)), + client_context: Default::default(), + connections: Default::default(), + } + } + + pub fn with_client_state(&mut self, client_id: &ClientId, latest_client_height: u64) { + self.client_context + .with_client_state(client_id, latest_client_height) + } + + pub fn max_size(&self) -> usize { + self.chain_context.max_size() + } + + pub fn add_connection(self, id: ConnectionId, end: ConnectionEnd) -> Self { + let mut connections = self.connections.clone(); + connections.insert(id, end); + Self { + connections, + ..self + } + } +} + +impl ConnectionReader for MockConnectionContext { + fn fetch_connection_end(&self, cid: &ConnectionId) -> Option<&ConnectionEnd> { + self.connections.get(cid) + } + + fn fetch_client_state(&self, client_id: &ClientId) -> Option { + self.client_context.client_state(client_id) + } + + fn chain_current_height(&self) -> Height { + self.chain_context.latest + } + + /// Returns the number of consensus state historical entries for the local chain. + fn chain_consensus_states_history_size(&self) -> usize { + self.chain_context.max_size() + } + + fn commitment_prefix(&self) -> CommitmentPrefix { + unimplemented!() + } + + fn fetch_client_consensus_state( + &self, + client_id: &ClientId, + height: Height, + ) -> Option { + self.client_context.consensus_state(client_id, height) + } + + fn fetch_self_consensus_state(&self, height: Height) -> Option { + let hi = self.chain_context.self_historical_info(height)?.header; + match hi { + #[cfg(test)] + SelfHeader::Mock(h) => Some(h.into()), + } + } +} + +impl ConnectionKeeper for MockConnectionContext { + fn store_connection( + &mut self, + connection_id: ConnectionId, + connection_end: ConnectionEnd, + ) -> Result<(), Error> { + self.connections.insert(connection_id, connection_end); + Ok(()) + } +} diff --git a/modules/src/ics03_connection/error.rs b/modules/src/ics03_connection/error.rs index 36c5e1c0ff..08ad2b5c34 100644 --- a/modules/src/ics03_connection/error.rs +++ b/modules/src/ics03_connection/error.rs @@ -1,5 +1,6 @@ // TODO: Update error types for Connection!! +use crate::ics24_host::identifier::ConnectionId; use anomaly::{BoxError, Context}; use thiserror::Error; @@ -10,6 +11,21 @@ pub enum Kind { #[error("connection state unknown")] UnknownState, + #[error("connection exists (was initialized) already: {0}")] + ConnectionExistsAlready(ConnectionId), + + #[error("a different connection exists (was initialized) already for the same connection identifier")] + ConnectionMismatch, + + #[error("connection end for this identifier was never initialized")] + UninitializedConnection, + + #[error("consensus height claimed by the client on the other party is too advanced")] + InvalidConsensusHeight, + + #[error("consensus height claimed by the client on the other party falls outside of trusting period")] + StaleConsensusHeight, + #[error("identifier error")] IdentifierError, @@ -19,17 +35,41 @@ pub enum Kind { #[error("invalid address")] InvalidAddress, - #[error("invalid proof")] + #[error("invalid connection proof")] InvalidProof, + #[error("invalid signer")] + InvalidSigner, + #[error("queried for a non-existing connection")] ConnectionNotFound, + #[error("invalid counterparty")] + InvalidCounterparty, + #[error("missing counterparty")] MissingCounterparty, #[error("missing counterparty prefix")] MissingCounterpartyPrefix, + + #[error("the client id does not match any client state")] + MissingClient, + + #[error("the client is frozen")] + FrozenClient, + + #[error("the connection proof verification failed")] + ConnectionVerificationFailure, + + #[error("the expected consensus state could not be retrieved")] + MissingClientConsensusState, + + #[error("the local consensus state could not be retrieved")] + MissingLocalConsensusState, + + #[error("the consensus proof verification failed")] + ConsensusStateVerificationFailure, } impl Kind { diff --git a/modules/src/ics03_connection/handler.rs b/modules/src/ics03_connection/handler.rs new file mode 100644 index 0000000000..e259ffb91f --- /dev/null +++ b/modules/src/ics03_connection/handler.rs @@ -0,0 +1,107 @@ +//! This module implements the protocol for ICS3, that is, the processing logic for ICS3 +//! connection open handshake messages. +use crate::handler::{Event, EventType, HandlerOutput}; +use crate::ics03_connection::connection::ConnectionEnd; +use crate::ics03_connection::context::{ConnectionKeeper, ConnectionReader}; +use crate::ics03_connection::error::Error; +use crate::ics03_connection::msgs::ConnectionMsg; +use crate::ics24_host::identifier::ConnectionId; + +pub mod conn_open_ack; +pub mod conn_open_confirm; +pub mod conn_open_init; +pub mod conn_open_try; +pub mod verify; + +#[derive(Clone, Debug)] +pub struct ConnectionResult { + connection_id: ConnectionId, + connection_end: ConnectionEnd, +} + +#[derive(Clone, Debug)] +pub enum ConnectionEvent { + ConnOpenInit(ConnectionResult), + ConnOpenTry(ConnectionResult), +} + +impl From for Event { + fn from(ev: ConnectionEvent) -> Event { + match ev { + ConnectionEvent::ConnOpenInit(conn) => Event::new( + EventType::Custom("connection_open_init".to_string()), + vec![("connection_id".to_string(), conn.connection_id.to_string())], + ), + ConnectionEvent::ConnOpenTry(conn) => Event::new( + EventType::Custom("connection_open_try".to_string()), + vec![("connection_id".to_string(), conn.connection_id.to_string())], + ), + } + } +} + +// The outcome after processing (delivering) a specific ICS3 message. +type Object = ConnectionEnd; + +/// General entry point for delivering (i.e., processing) any type of message related to the ICS3 +/// connection open handshake protocol. +// pub fn process_ics3_msg(ctx: &dyn ConnectionReader, message: &ConnectionMsg) -> ProtocolResult { +// // Process each message with the corresponding process_*_msg function. +// // After processing a specific message, the output consists of a ConnectionEnd. +// let conn_object = match message { +// ConnectionMsg::ConnectionOpenInit(msg) => conn_open_init::process(ctx, msg), +// ConnectionMsg::ConnectionOpenTry(msg) => conn_open_try::process(ctx, msg), +// ConnectionMsg::ConnectionOpenAck(msg) => conn_open_ack::process(ctx, msg), +// ConnectionMsg::ConnectionOpenConfirm(msg) => conn_open_confirm::process(ctx, msg), +// }?; +// +// // Post-processing: emit events. +// let mut events = produce_events(ctx, message); +// +// Ok(ProtocolOutput::new() +// .set_object(conn_object) +// .add_events(&mut events)) +// } + +pub fn keep(keeper: &mut dyn ConnectionKeeper, result: ConnectionResult) -> Result<(), Error> { + keeper.store_connection(result.connection_id, result.connection_end)?; + // TODO - associate connection with client + Ok(()) +} + +pub fn dispatch( + ctx: &mut Ctx, + msg: ConnectionMsg, +) -> Result, Error> +where + Ctx: ConnectionReader + ConnectionKeeper, +{ + match msg { + ConnectionMsg::ConnectionOpenInit(msg) => { + let HandlerOutput { + result, + log, + events, + } = conn_open_init::process(ctx, msg)?; + + keep(ctx, result.clone())?; + Ok(HandlerOutput::builder() + .with_log(log) + .with_events(events) + .with_result(result)) + } + ConnectionMsg::ConnectionOpenTry(msg) => { + let HandlerOutput { + result, + log, + events, + } = conn_open_try::process(ctx, msg)?; + + keep(ctx, result.clone())?; + Ok(HandlerOutput::builder() + .with_log(log) + .with_events(events) + .with_result(result)) + } + } +} diff --git a/modules/src/ics03_connection/handler/conn_open_ack.rs b/modules/src/ics03_connection/handler/conn_open_ack.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/modules/src/ics03_connection/handler/conn_open_ack.rs @@ -0,0 +1 @@ + diff --git a/modules/src/ics03_connection/handler/conn_open_confirm.rs b/modules/src/ics03_connection/handler/conn_open_confirm.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/modules/src/ics03_connection/handler/conn_open_confirm.rs @@ -0,0 +1 @@ + diff --git a/modules/src/ics03_connection/handler/conn_open_init.rs b/modules/src/ics03_connection/handler/conn_open_init.rs new file mode 100644 index 0000000000..a39c81d533 --- /dev/null +++ b/modules/src/ics03_connection/handler/conn_open_init.rs @@ -0,0 +1,135 @@ +use crate::handler::{HandlerOutput, HandlerResult}; +use crate::ics03_connection::connection::{get_compatible_versions, ConnectionEnd, State}; +use crate::ics03_connection::context::ConnectionReader; +use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::handler::ConnectionEvent::ConnOpenInit; +use crate::ics03_connection::handler::ConnectionResult; +use crate::ics03_connection::msgs::MsgConnectionOpenInit; + +/// Protocol logic specific to ICS3 messages of type `MsgConnectionOpenInit`. +pub(crate) fn process( + ctx: &dyn ConnectionReader, + msg: MsgConnectionOpenInit, +) -> HandlerResult { + let mut output = HandlerOutput::builder(); + + // No connection should exist. + if ctx.fetch_connection_end(msg.connection_id()).is_some() { + return Err(Kind::ConnectionExistsAlready(msg.connection_id().clone()).into()); + } + + let new_connection_end = ConnectionEnd::new( + State::Init, + msg.client_id().clone(), + msg.counterparty().clone(), + get_compatible_versions(), + )?; + + output.log("success: no connection found"); + + let result = ConnectionResult { + connection_id: msg.connection_id().clone(), + connection_end: new_connection_end, + }; + + output.emit(ConnOpenInit(result.clone())); + + Ok(output.with_result(result)) +} + +#[cfg(test)] +mod tests { + use crate::handler::EventType; + use crate::ics03_connection::connection::{get_compatible_versions, ConnectionEnd, State}; + use crate::ics03_connection::context_mock::MockConnectionContext; + use crate::ics03_connection::handler::{dispatch, ConnectionResult}; + use crate::ics03_connection::msgs::test_util::get_dummy_msg_conn_open_init; + use crate::ics03_connection::msgs::{ConnectionMsg, MsgConnectionOpenInit}; + use crate::try_from_raw::TryFromRaw; + + #[test] + fn conn_open_init_msg_processing() { + #[derive(Clone, Debug)] + struct ConnOpenInitProcessParams { + ctx: MockConnectionContext, + msg: ConnectionMsg, + } + + struct Test { + name: String, + ctx: MockConnectionContext, + msg: ConnectionMsg, + want_pass: bool, + } + + let dummy_msg = MsgConnectionOpenInit::try_from(get_dummy_msg_conn_open_init()).unwrap(); + let mut default_context = MockConnectionContext::new(34, 3); + default_context.with_client_state(dummy_msg.client_id(), 10); + + let init_conn_end = &ConnectionEnd::new( + State::Init, + dummy_msg.client_id().clone(), + dummy_msg.counterparty().clone(), + get_compatible_versions(), + ) + .unwrap(); + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + ctx: default_context.clone(), + msg: ConnectionMsg::ConnectionOpenInit(dummy_msg.clone()), + want_pass: true, + }, + Test { + name: "Protocol fails because connection exists in the store already".to_string(), + ctx: default_context + .add_connection(dummy_msg.connection_id().clone(), init_conn_end.clone()), + msg: ConnectionMsg::ConnectionOpenInit(dummy_msg.clone()), + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for mut test in tests { + // TODO - this is an example for testing with dispatch + // TODO - the client tests use the process only. Need to select one approach and use the same across. + let res = dispatch(&mut test.ctx, test.msg.clone()); + // Additionally check the events and the output objects in the result. + match res { + Ok(proto_output) => { + assert_eq!( + test.want_pass, + true, + "process_ics3_msg() test passed but was supposed to fail for test: {}, \nparams {:?} {:?}", + test.name, + test.msg.clone(), + test.ctx.clone() + ); + assert_ne!(proto_output.events.is_empty(), true); // Some events must exist. + + // The object in the output is a ConnectionEnd, should have init state. + let res: ConnectionResult = proto_output.result; + assert_eq!(res.connection_id, dummy_msg.connection_id().clone()); + assert_eq!(res.connection_end.state().clone(), State::Init); + + for e in proto_output.events.iter() { + assert_eq!(e.tpe, EventType::Custom("connection_open_init".to_string())); + } + } + Err(e) => { + assert_eq!( + test.want_pass, + false, + "process_ics3_msg() failed for test: {}, \nparams {:?} {:?} error: {:?}", + test.name, + test.msg, + test.ctx.clone(), + e, + ); + } + } + } + } +} diff --git a/modules/src/ics03_connection/handler/conn_open_try.rs b/modules/src/ics03_connection/handler/conn_open_try.rs new file mode 100644 index 0000000000..9a3e2c41b9 --- /dev/null +++ b/modules/src/ics03_connection/handler/conn_open_try.rs @@ -0,0 +1,179 @@ +use crate::handler::{HandlerOutput, HandlerResult}; +use crate::ics03_connection::connection::{pick_version, ConnectionEnd, Counterparty, State}; +use crate::ics03_connection::context::ConnectionReader; +use crate::ics03_connection::error::{Error, Kind}; +use crate::ics03_connection::handler::verify::{check_client_consensus_height, verify_proofs}; +use crate::ics03_connection::handler::ConnectionEvent::ConnOpenTry; +use crate::ics03_connection::handler::ConnectionResult; +use crate::ics03_connection::msgs::MsgConnectionOpenTry; + +/// Protocol logic specific to delivering ICS3 messages of type `MsgConnectionOpenTry`. +pub(crate) fn process( + ctx: &dyn ConnectionReader, + msg: MsgConnectionOpenTry, +) -> HandlerResult { + let mut output = HandlerOutput::builder(); + + // Check that consensus height (for client proof) in message is not too advanced nor too old. + check_client_consensus_height(ctx, msg.consensus_height())?; + + // Unwrap the old connection end (if any) and validate it against the message. + let mut new_connection_end = match ctx.fetch_connection_end(msg.connection_id()) { + Some(old_conn_end) => { + // Validate that existing connection end matches with the one we're trying to establish. + if old_conn_end.state_matches(&State::Init) + && old_conn_end.counterparty_matches(&msg.counterparty()) + && old_conn_end.client_id_matches(msg.client_id()) + { + // A ConnectionEnd already exists and all validation passed. + Ok(old_conn_end.clone()) + } else { + // A ConnectionEnd already exists and validation failed. + Err(Into::::into( + Kind::ConnectionMismatch.context(msg.connection_id().to_string()), + )) + } + } + // No ConnectionEnd exists for this ConnectionId. Create & return a new one. + None => Ok(ConnectionEnd::new( + State::Init, + msg.client_id().clone(), + msg.counterparty(), + msg.counterparty_versions(), + )?), + }?; + + // Proof verification in two steps: + // 1. Setup: build the ConnectionEnd as we expect to find it on the other party. + let expected_conn = ConnectionEnd::new( + State::Init, + msg.counterparty().client_id().clone(), + Counterparty::new( + msg.client_id().clone(), + msg.connection_id().clone(), + msg.counterparty().prefix().clone(), + )?, + msg.counterparty_versions(), + )?; + + // 2. Pass the details to the verification function. + verify_proofs( + ctx, + msg.connection_id(), + &new_connection_end, + &expected_conn, + msg.proofs(), + )?; + + // Transition the connection end to the new state & pick a version. + new_connection_end.set_state(State::TryOpen); + new_connection_end.set_version(pick_version(msg.counterparty_versions()).unwrap()); + // TODO: fix version unwrap above. + + output.log("success: connection verification passed"); + + let result = ConnectionResult { + connection_id: msg.connection_id().clone(), + connection_end: new_connection_end, + }; + + output.emit(ConnOpenTry(result.clone())); + + Ok(output.with_result(result)) +} + +#[cfg(test)] +mod tests { + use crate::handler::EventType; + use crate::ics03_connection::connection::{get_compatible_versions, ConnectionEnd, State}; + use crate::ics03_connection::context_mock::MockConnectionContext; + use crate::ics03_connection::handler::{dispatch, ConnectionResult}; + use crate::ics03_connection::msgs::test_util::get_dummy_msg_conn_open_try; + use crate::ics03_connection::msgs::{ConnectionMsg, MsgConnectionOpenTry}; + use crate::try_from_raw::TryFromRaw; + + #[test] + fn conn_open_try_msg_processing() { + #[derive(Clone, Debug)] + struct ConnOpenTryProcessParams { + ctx: MockConnectionContext, + msg: ConnectionMsg, + } + + struct Test { + name: String, + ctx: MockConnectionContext, + msg: ConnectionMsg, + want_pass: bool, + } + + let dummy_msg = + MsgConnectionOpenTry::try_from(get_dummy_msg_conn_open_try(10, 34)).unwrap(); + let mut default_context = MockConnectionContext::new(34, 3); + default_context.with_client_state(dummy_msg.client_id(), 10); + + let try_conn_end = &ConnectionEnd::new( + State::TryOpen, + dummy_msg.client_id().clone(), + dummy_msg.counterparty(), + get_compatible_versions(), + ) + .unwrap(); + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + ctx: default_context.clone(), + msg: ConnectionMsg::ConnectionOpenTry(dummy_msg.clone()), + want_pass: true, + }, + Test { + name: "Protocol fails because connection exists in the store already".to_string(), + ctx: default_context + .add_connection(dummy_msg.connection_id().clone(), try_conn_end.clone()), + msg: ConnectionMsg::ConnectionOpenTry(dummy_msg.clone()), + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for mut test in tests { + let res = dispatch(&mut test.ctx, test.msg.clone()); + // Additionally check the events and the output objects in the result. + match res { + Ok(proto_output) => { + assert_eq!( + test.want_pass, + true, + "process_ics3_msg() test passed but was supposed to fail for test: {}, \nparams {:?} {:?}", + test.name, + test.msg.clone(), + test.ctx.clone() + ); + assert_ne!(proto_output.events.is_empty(), true); // Some events must exist. + + // The object in the output is a ConnectionEnd, should have TryOpen state. + let res: ConnectionResult = proto_output.result; + assert_eq!(res.connection_id, dummy_msg.connection_id().clone()); + assert_eq!(res.connection_end.state().clone(), State::TryOpen); + + for e in proto_output.events.iter() { + assert_eq!(e.tpe, EventType::Custom("connection_open_try".to_string())); + } + } + Err(e) => { + assert_eq!( + test.want_pass, + false, + "process_ics3_msg() failed for test: {}, \nparams {:?} {:?} error: {:?}", + test.name, + test.msg, + test.ctx.clone(), + e, + ); + } + } + } + } +} diff --git a/modules/src/ics03_connection/handler/verify.rs b/modules/src/ics03_connection/handler/verify.rs new file mode 100644 index 0000000000..cada9caf74 --- /dev/null +++ b/modules/src/ics03_connection/handler/verify.rs @@ -0,0 +1,123 @@ +use crate::ics02_client::state::ClientState; +use crate::ics03_connection::connection::ConnectionEnd; +use crate::ics03_connection::context::ConnectionReader; +use crate::ics03_connection::error::{Error, Kind}; +use crate::ics23_commitment::commitment::CommitmentProof; +use crate::ics24_host::identifier::ConnectionId; +use crate::proofs::{ConsensusProof, Proofs}; +use tendermint::block::Height; + +pub fn verify_proofs( + ctx: &dyn ConnectionReader, + id: &ConnectionId, + connection_end: &ConnectionEnd, + expected_conn: &ConnectionEnd, + proofs: &Proofs, +) -> Result<(), Error> { + verify_connection_proof( + ctx, + id, + connection_end, + expected_conn, + proofs.height(), + proofs.object_proof(), + )?; + + // If a consensus proof is present verify it. + match proofs.consensus_proof() { + None => Ok(()), + Some(proof) => Ok(verify_consensus_proof( + ctx, + connection_end, + proofs.height(), + &proof, + )?), + } +} + +pub fn verify_connection_proof( + ctx: &dyn ConnectionReader, + id: &ConnectionId, + connection_end: &ConnectionEnd, + expected_conn: &ConnectionEnd, + proof_height: Height, + proof: &CommitmentProof, +) -> Result<(), Error> { + // Fetch the client state (IBC client on the local chain). + let client = ctx + .fetch_client_state(connection_end.client_id()) + .ok_or_else(|| Kind::MissingClient.context(connection_end.client_id().to_string()))?; + if client.is_frozen() { + return Err(Kind::FrozenClient + .context(connection_end.client_id().to_string()) + .into()); + } + + // Verify the proof for the connection state against the expected connection end. + Ok(client + .verify_connection_state( + proof_height, + connection_end.counterparty().prefix(), + proof, + connection_end.counterparty().connection_id(), + expected_conn, + ) + .map_err(|_| Kind::InvalidProof.context(id.to_string()))?) +} + +pub fn verify_consensus_proof( + ctx: &dyn ConnectionReader, + connection_end: &ConnectionEnd, + proof_height: Height, + proof: &ConsensusProof, +) -> Result<(), Error> { + // Fetch the client state (IBC client on the local chain). + let client = ctx + .fetch_client_state(connection_end.client_id()) + .ok_or_else(|| Kind::MissingClient.context(connection_end.client_id().to_string()))?; + + if client.is_frozen() { + return Err(Kind::FrozenClient + .context(connection_end.client_id().to_string()) + .into()); + } + + // Fetch the expected consensus state from the historical (local) header data. + let expected_consensus = ctx + .fetch_self_consensus_state(proof.height()) + .ok_or_else(|| Kind::MissingLocalConsensusState.context(proof.height().to_string()))?; + + Ok(client + .verify_client_consensus_state( + proof_height, + connection_end.counterparty().prefix(), + proof.proof(), + connection_end.counterparty().client_id(), + proof.height(), + &expected_consensus, + ) + .map_err(|_| Kind::ConsensusStateVerificationFailure.context(proof.height().to_string()))?) +} + +pub fn check_client_consensus_height( + ctx: &dyn ConnectionReader, + claimed_height: Height, +) -> Result<(), Error> { + // Fail if the consensus height is too advanced. + if claimed_height > ctx.chain_current_height() { + return Err(Kind::InvalidConsensusHeight + .context(claimed_height.to_string()) + .into()); + } + + // Fail if the consensus height is too old (outside of trusting period). + if claimed_height.value() + < (ctx.chain_current_height().value() - ctx.chain_consensus_states_history_size() as u64) + { + return Err(Kind::StaleConsensusHeight + .context(claimed_height.to_string()) + .into()); + } + + Ok(()) +} diff --git a/modules/src/ics03_connection/mod.rs b/modules/src/ics03_connection/mod.rs index bfa77bde53..80203e86ee 100644 --- a/modules/src/ics03_connection/mod.rs +++ b/modules/src/ics03_connection/mod.rs @@ -1,6 +1,13 @@ //! ICS 03: IBC Connection implementation pub mod connection; +/// Context definitions (dependencies for the protocol). +pub mod context; pub mod error; pub mod events; +/// Message processing logic (protocol) for ICS 03. +pub mod handler; pub mod msgs; + +#[cfg(test)] +pub mod context_mock; diff --git a/modules/src/ics03_connection/msgs.rs b/modules/src/ics03_connection/msgs.rs index 1a5192e8b0..1703914ba0 100644 --- a/modules/src/ics03_connection/msgs.rs +++ b/modules/src/ics03_connection/msgs.rs @@ -1,15 +1,60 @@ +//! Message definitions for the connection handshake datagrams. +//! +//! We define each of the four messages in the connection handshake protocol as a `struct`. +//! Each such message comprises the same fields as the datagrams defined in ICS3 English spec: +//! https://github.com/cosmos/ics/tree/master/spec/ics-003-connection-semantics. +//! +//! One departure from ICS3 is that we abstract the three counterparty fields (connection id, +//! prefix, and client id) into a single field of type `Counterparty`; this applies to messages +//! `MsgConnectionOpenInit` and `MsgConnectionOpenTry`. One other difference with regards to +//! abstraction is that all proof-related attributes in a message are encapsulated in `Proofs` type. +//! +//! Another difference to ICS3 specs is that each message comprises an additional field called +//! `signer` which is specific to Cosmos-SDK. +//! TODO: Separate the Cosmos-SDK specific functionality from canonical ICS types. Decorators? + #![allow(clippy::too_many_arguments)] use crate::ics03_connection::connection::{validate_version, validate_versions, Counterparty}; use crate::ics03_connection::error::{Error, Kind}; -use crate::ics23_commitment::{CommitmentPrefix, CommitmentProof}; use crate::ics24_host::identifier::{ClientId, ConnectionId}; use crate::proofs::{ConsensusProof, Proofs}; +use crate::try_from_raw::TryFromRaw; use crate::tx_msg::Msg; +use ibc_proto::connection::MsgConnectionOpenAck as RawMsgConnectionOpenAck; +use ibc_proto::connection::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm; +use ibc_proto::connection::MsgConnectionOpenInit as RawMsgConnectionOpenInit; +use ibc_proto::connection::MsgConnectionOpenTry as RawMsgConnectionOpenTry; + use serde_derive::{Deserialize, Serialize}; +use std::convert::TryInto; +use std::str::{from_utf8, FromStr}; use tendermint::account::Id as AccountId; +use tendermint::block::Height; +/// Message type for the `MsgConnectionOpenInit` message. pub const TYPE_MSG_CONNECTION_OPEN_INIT: &str = "connection_open_init"; +/// Message type for the `MsgConnectionOpenTry` message. +pub const TYPE_MSG_CONNECTION_OPEN_TRY: &str = "connection_open_try"; + +/// Message type for the `MsgConnectionOpenAck` message. +pub const TYPE_MSG_CONNECTION_OPEN_ACK: &str = "connection_open_ack"; + +/// Message type for the `MsgConnectionOpenConfirm` message. +pub const TYPE_MSG_CONNECTION_OPEN_CONFIRM: &str = "connection_open_confirm"; + +/// Enumeration of all possible messages that the ICS3 protocol processes. +#[derive(Clone, Debug)] +pub enum ConnectionMsg { + ConnectionOpenInit(MsgConnectionOpenInit), + ConnectionOpenTry(MsgConnectionOpenTry), + // ConnectionOpenAck(MsgConnectionOpenAck), + // ConnectionOpenConfirm(MsgConnectionOpenConfirm), +} + +/// +/// Message definition `MsgConnectionOpenInit` (i.e., the `ConnOpenInit` datagram). +/// #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MsgConnectionOpenInit { connection_id: ConnectionId, @@ -19,28 +64,43 @@ pub struct MsgConnectionOpenInit { } impl MsgConnectionOpenInit { - pub fn new( - connection_id: String, - client_id: String, - counterparty_connection_id: String, - counterparty_client_id: String, - counterparty_commitment_prefix: CommitmentPrefix, - signer: AccountId, - ) -> Result { + /// Getter: borrow the `connection_id` from this message. + pub fn connection_id(&self) -> &ConnectionId { + &self.connection_id + } + + /// Getter: borrow the `client_id` from this message. + pub fn client_id(&self) -> &ClientId { + &self.client_id + } + + /// Getter: borrow the `counterparty` from this message. + pub fn counterparty(&self) -> &Counterparty { + &self.counterparty + } +} + +impl TryFromRaw for MsgConnectionOpenInit { + type RawType = RawMsgConnectionOpenInit; + type Error = anomaly::Error; + fn try_from(msg: RawMsgConnectionOpenInit) -> Result { Ok(Self { - connection_id: connection_id + connection_id: msg + .connection_id .parse() .map_err(|e| Kind::IdentifierError.context(e))?, - client_id: client_id + client_id: msg + .client_id .parse() .map_err(|e| Kind::IdentifierError.context(e))?, - counterparty: Counterparty::new( - counterparty_client_id, - counterparty_connection_id, - counterparty_commitment_prefix, + counterparty: msg + .counterparty + .ok_or_else(|| Kind::MissingCounterparty)? + .try_into()?, + signer: AccountId::from_str( + from_utf8(&msg.signer).map_err(|e| Kind::InvalidSigner.context(e))?, ) - .map_err(|e| Kind::IdentifierError.context(e))?, - signer, + .map_err(|e| Kind::InvalidSigner.context(e))?, }) } } @@ -58,7 +118,9 @@ impl Msg for MsgConnectionOpenInit { fn validate_basic(&self) -> Result<(), Self::ValidationError> { // All the validation is performed on creation - self.counterparty.validate_basic() + self.counterparty + .validate_basic() + .map_err(|e| Kind::InvalidCounterparty.context(e).into()) } fn get_sign_bytes(&self) -> Vec { @@ -70,8 +132,9 @@ impl Msg for MsgConnectionOpenInit { } } -pub const TYPE_MSG_CONNECTION_OPEN_TRY: &str = "connection_open_try"; - +/// +/// Message definition `MsgConnectionOpenTry` (i.e., `ConnOpenTry` datagram). +/// #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MsgConnectionOpenTry { connection_id: ConnectionId, @@ -83,41 +146,38 @@ pub struct MsgConnectionOpenTry { } 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))?; + /// Getter for accessing the `consensus_height` field from this message. Returns the special + /// value `0` if this field is not set. + pub fn consensus_height(&self) -> Height { + match self.proofs.consensus_proof() { + None => Height(0), + Some(p) => p.height(), + } + } - 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, - }) + /// Getter for accesing the whole counterparty of this message. Returns a `clone()`. + pub fn counterparty(&self) -> Counterparty { + self.counterparty.clone() + } + + /// Getter for accessing the client identifier from this message. + pub fn client_id(&self) -> &ClientId { + &self.client_id + } + + /// Getter for accessing the connection identifier of this message. + pub fn connection_id(&self) -> &ConnectionId { + &self.connection_id + } + + /// Getter for accessing the proofs in this message. + pub fn proofs(&self) -> &Proofs { + &self.proofs + } + + /// Getter for accessing the versions from this message. Returns a `clone()`. + pub fn counterparty_versions(&self) -> Vec { + self.counterparty_versions.clone() } } @@ -133,7 +193,9 @@ impl Msg for MsgConnectionOpenTry { } fn validate_basic(&self) -> Result<(), Self::ValidationError> { - self.counterparty.validate_basic() + self.counterparty + .validate_basic() + .map_err(|e| Kind::InvalidCounterparty.context(e).into()) } fn get_sign_bytes(&self) -> Vec { @@ -145,8 +207,46 @@ impl Msg for MsgConnectionOpenTry { } } -pub const TYPE_MSG_CONNECTION_OPEN_ACK: &str = "connection_open_ack"; +impl TryFromRaw for MsgConnectionOpenTry { + type RawType = RawMsgConnectionOpenTry; + type Error = anomaly::Error; + fn try_from(msg: RawMsgConnectionOpenTry) -> Result { + let consensus_proof_obj = + ConsensusProof::new(msg.proof_consensus.into(), msg.consensus_height) + .map_err(|e| Kind::InvalidProof.context(e))?; + Ok(Self { + connection_id: msg + .connection_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + client_id: msg + .client_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + counterparty: msg + .counterparty + .ok_or_else(|| Kind::MissingCounterparty)? + .try_into()?, + counterparty_versions: validate_versions(msg.counterparty_versions) + .map_err(|e| Kind::InvalidVersion.context(e))?, + proofs: Proofs::new( + msg.proof_init.into(), + Some(consensus_proof_obj), + msg.proof_height, + ) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer: AccountId::from_str( + from_utf8(&msg.signer).map_err(|e| Kind::InvalidSigner.context(e))?, + ) + .map_err(|e| Kind::InvalidSigner.context(e))?, + }) + } +} + +/// +/// Message definition `MsgConnectionOpenAck` (i.e., `ConnOpenAck` datagram). +/// #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MsgConnectionOpenAck { connection_id: ConnectionId, @@ -156,27 +256,28 @@ pub struct MsgConnectionOpenAck { } 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))?; + /// Getter for accessing the `consensus_height` field from this message. Returns the special + /// value `0` if this field is not set. + pub fn consensus_height(&self) -> Height { + match self.proofs.consensus_proof() { + None => Height(0), + Some(p) => p.height(), + } + } - 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, - }) + /// Getter for accessing the connection identifier of this message. + pub fn connection_id(&self) -> &ConnectionId { + &self.connection_id + } + + /// Getter for the version field. + pub fn version(&self) -> &String { + &self.version + } + + /// Getter for accessing (borrow) the proofs in this message. + pub fn proofs(&self) -> &Proofs { + &self.proofs } } @@ -204,8 +305,38 @@ impl Msg for MsgConnectionOpenAck { } } -pub const TYPE_MSG_CONNECTION_OPEN_CONFIRM: &str = "connection_open_confirm"; +impl TryFromRaw for MsgConnectionOpenAck { + type RawType = RawMsgConnectionOpenAck; + type Error = anomaly::Error; + + fn try_from(msg: RawMsgConnectionOpenAck) -> Result { + let consensus_proof_obj = + ConsensusProof::new(msg.proof_consensus.into(), msg.consensus_height) + .map_err(|e| Kind::InvalidProof.context(e))?; + + Ok(Self { + connection_id: msg + .connection_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + version: validate_version(msg.version).map_err(|e| Kind::InvalidVersion.context(e))?, + proofs: Proofs::new( + msg.proof_try.into(), + Option::from(consensus_proof_obj), + msg.proof_height, + ) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer: AccountId::from_str( + from_utf8(&msg.signer).map_err(|e| Kind::InvalidSigner.context(e))?, + ) + .map_err(|e| Kind::InvalidSigner.context(e))?, + }) + } +} +/// +/// Message definition for `MsgConnectionOpenConfirm` (i.e., `ConnOpenConfirm` datagram). +/// #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct MsgConnectionOpenConfirm { connection_id: ConnectionId, @@ -214,20 +345,14 @@ pub struct MsgConnectionOpenConfirm { } 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, - }) + /// Getter for accessing the connection identifier of this message. + pub fn connection_id(&self) -> &ConnectionId { + &self.connection_id + } + + /// Getter for accessing (borrow) the proofs in this message. + pub fn proofs(&self) -> &Proofs { + &self.proofs } } @@ -242,7 +367,7 @@ impl Msg for MsgConnectionOpenConfirm { TYPE_MSG_CONNECTION_OPEN_CONFIRM.to_string() } - fn validate_basic(&self) -> Result<(), Self::ValidationError> { + fn validate_basic(&self) -> Result<(), Error> { Ok(()) } @@ -255,90 +380,168 @@ impl Msg for MsgConnectionOpenConfirm { } } +impl TryFromRaw for MsgConnectionOpenConfirm { + type RawType = RawMsgConnectionOpenConfirm; + type Error = anomaly::Error; + + fn try_from(msg: RawMsgConnectionOpenConfirm) -> Result { + Ok(Self { + connection_id: msg + .connection_id + .parse() + .map_err(|e| Kind::IdentifierError.context(e))?, + proofs: Proofs::new(msg.proof_ack.into(), None, msg.proof_height) + .map_err(|e| Kind::InvalidProof.context(e))?, + signer: AccountId::from_str( + from_utf8(&msg.signer).map_err(|e| Kind::InvalidSigner.context(e))?, + ) + .map_err(|e| Kind::InvalidSigner.context(e))?, + }) + } +} + #[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], + use ibc_proto::connection::Counterparty as RawCounterparty; + use ibc_proto::connection::MsgConnectionOpenAck as RawMsgConnectionOpenAck; + use ibc_proto::connection::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm; + use ibc_proto::connection::MsgConnectionOpenInit as RawMsgConnectionOpenInit; + use ibc_proto::connection::MsgConnectionOpenTry as RawMsgConnectionOpenTry; + + use ibc_proto::commitment::MerklePrefix; + + pub fn get_dummy_proof() -> Vec { + "Y29uc2Vuc3VzU3RhdGUvaWJjb25lY2xpZW50LzIy" + .as_bytes() + .to_vec() + } + + pub fn get_dummy_account_id() -> Vec { + "0CDA3F47EF3C4906693B170EF650EB968C5F4B2C" + .as_bytes() + .to_vec() + } + + pub fn get_dummy_counterparty() -> RawCounterparty { + RawCounterparty { + client_id: "destclient".to_string(), + connection_id: "destconnection".to_string(), + prefix: Some(MerklePrefix { + key_prefix: b"ibc".to_vec(), + }), + } + } + + /// Returns a dummy message, for testing only. + /// Other unit tests may import this if they depend on a MsgConnectionOpenInit. + pub fn get_dummy_msg_conn_open_init() -> RawMsgConnectionOpenInit { + RawMsgConnectionOpenInit { + client_id: "srcclient".to_string(), + connection_id: "srcconnection".to_string(), + counterparty: Some(get_dummy_counterparty()), + signer: get_dummy_account_id(), + } + } + + pub fn get_dummy_msg_conn_open_try( + proof_height: u64, + consensus_height: u64, + ) -> RawMsgConnectionOpenTry { + RawMsgConnectionOpenTry { + client_id: "srcclient".to_string(), + connection_id: "srcconnection".to_string(), + counterparty: Some(get_dummy_counterparty()), + counterparty_versions: vec!["1.0.0".to_string()], + proof_init: get_dummy_proof(), + proof_height, + proof_consensus: get_dummy_proof(), + consensus_height, + signer: get_dummy_account_id(), + } + } + + pub fn get_dummy_msg_conn_open_ack() -> RawMsgConnectionOpenAck { + RawMsgConnectionOpenAck { + connection_id: "srcconnection".to_string(), + version: "1.0.0".to_string(), + proof_try: get_dummy_proof(), + proof_height: 10, + proof_consensus: get_dummy_proof(), + consensus_height: 10, + signer: get_dummy_account_id(), + } + } + + pub fn get_dummy_msg_conn_open_confirm() -> RawMsgConnectionOpenConfirm { + RawMsgConnectionOpenConfirm { + connection_id: "srcconnection".to_string(), + proof_ack: get_dummy_proof(), + proof_height: 10, + signer: get_dummy_account_id(), } } } #[cfg(test)] mod tests { - use super::test_util::get_dummy_proof; use super::MsgConnectionOpenInit; + use crate::ics03_connection::msgs::test_util::{ + get_dummy_counterparty, get_dummy_msg_conn_open_ack, get_dummy_msg_conn_open_confirm, + get_dummy_msg_conn_open_init, get_dummy_msg_conn_open_try, + }; use crate::ics03_connection::msgs::{ MsgConnectionOpenAck, MsgConnectionOpenConfirm, MsgConnectionOpenTry, }; - use crate::ics23_commitment::{CommitmentPrefix, CommitmentProof}; - use std::str::FromStr; - use tendermint::account::Id as AccountId; + use crate::try_from_raw::TryFromRaw; + use ibc_proto::connection::Counterparty as RawCounterparty; + use ibc_proto::connection::MsgConnectionOpenAck as RawMsgConnectionOpenAck; + use ibc_proto::connection::MsgConnectionOpenConfirm as RawMsgConnectionOpenConfirm; + use ibc_proto::connection::MsgConnectionOpenInit as RawMsgConnectionOpenInit; + use ibc_proto::connection::MsgConnectionOpenTry as RawMsgConnectionOpenTry; #[test] fn parse_connection_open_init_msg() { #[derive(Clone, Debug, PartialEq)] - struct ConOpenInitParams { - connection_id: String, - client_id: String, - counterparty_connection_id: String, - counterparty_client_id: String, - counterparty_commitment_prefix: CommitmentPrefix, - } - struct Test { name: String, - params: ConOpenInitParams, + raw: RawMsgConnectionOpenInit, want_pass: bool, } - let default_con_params = ConOpenInitParams { - 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::new(vec![]), - }; + let default_init_msg = get_dummy_msg_conn_open_init(); let tests: Vec = vec![ Test { name: "Good parameters".to_string(), - params: default_con_params.clone(), + raw: default_init_msg.clone(), want_pass: true, }, Test { name: "Bad connection id, non-alpha".to_string(), - params: ConOpenInitParams { + raw: RawMsgConnectionOpenInit { connection_id: "con007".to_string(), - ..default_con_params.clone() + ..default_init_msg.clone() }, want_pass: false, }, Test { name: "Bad client id, name too short".to_string(), - params: ConOpenInitParams { + raw: RawMsgConnectionOpenInit { client_id: "client".to_string(), - ..default_con_params.clone() + ..default_init_msg.clone() }, want_pass: false, }, Test { name: "Bad destination connection id, name too long".to_string(), - params: ConOpenInitParams { - counterparty_connection_id: - "abcdefghijksdffjssdkflweldflsfladfsfwjkrekcmmsdfsdfjflddmnopqrstu" - .to_string(), - ..default_con_params + raw: RawMsgConnectionOpenInit { + counterparty: Some(RawCounterparty { + connection_id: + "abcdefghijksdffjssdkflweldflsfladfsfwjkrekcmmsdfsdfjflddmnopqrstu" + .to_string(), + ..get_dummy_counterparty() + }), + ..default_init_msg }, want_pass: false, }, @@ -347,26 +550,14 @@ mod tests { .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, - ); + let msg = MsgConnectionOpenInit::try_from(test.raw.clone()); assert_eq!( test.want_pass, msg.is_ok(), "MsgConnOpenInit::new failed for test {}, \nmsg {:?} with error {:?}", test.name, - test.params.clone(), + test.raw, msg.err(), ); } @@ -375,116 +566,98 @@ mod tests { #[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: ConOpenTryParams, + raw: RawMsgConnectionOpenTry, 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::new(vec![]), - 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 default_try_msg = get_dummy_msg_conn_open_try(10, 34); let tests: Vec = vec![ Test { name: "Good parameters".to_string(), - params: default_con_params.clone(), + raw: default_try_msg.clone(), want_pass: true, }, Test { name: "Bad connection id, non-alpha".to_string(), - params: ConOpenTryParams { + raw: RawMsgConnectionOpenTry { connection_id: "con007".to_string(), - ..default_con_params.clone() + ..default_try_msg.clone() }, want_pass: false, }, Test { name: "Bad client id, name too short".to_string(), - params: ConOpenTryParams { + raw: RawMsgConnectionOpenTry { client_id: "client".to_string(), - ..default_con_params.clone() + ..default_try_msg.clone() }, want_pass: false, }, Test { name: "Bad destination connection id, name too long".to_string(), - params: ConOpenTryParams { - counterparty_connection_id: - "abcdasdfasdfsdfasfdwefwfsdfsfsfasfwewvxcvdvwgadvaadsefghijklmnopqrstu" - .to_string(), - ..default_con_params.clone() + raw: RawMsgConnectionOpenTry { + counterparty: Some(RawCounterparty { + connection_id: + "abcdasdfasdfsdfasfdwefwfsdfsfsfasfwewvxcvdvwgadvaadsefghijklmnopqrstu" + .to_string(), + ..get_dummy_counterparty() + }), + ..default_try_msg.clone() }, want_pass: false, }, Test { name: "Correct destination client id with lower/upper case and special chars" .to_string(), - params: ConOpenTryParams { - counterparty_client_id: "ClientId_".to_string(), - ..default_con_params.clone() + raw: RawMsgConnectionOpenTry { + counterparty: Some(RawCounterparty { + client_id: "ClientId_".to_string(), + ..get_dummy_counterparty() + }), + ..default_try_msg.clone() }, want_pass: true, }, Test { name: "Bad counterparty versions, empty versions vec".to_string(), - params: ConOpenTryParams { + raw: RawMsgConnectionOpenTry { counterparty_versions: vec![], - ..default_con_params.clone() + ..default_try_msg.clone() }, want_pass: false, }, Test { name: "Bad counterparty versions, empty version string".to_string(), - params: ConOpenTryParams { + raw: RawMsgConnectionOpenTry { counterparty_versions: vec!["".to_string()], - ..default_con_params.clone() + ..default_try_msg.clone() }, want_pass: false, }, Test { name: "Bad proof height, height is 0".to_string(), - params: ConOpenTryParams { + raw: RawMsgConnectionOpenTry { proof_height: 0, - ..default_con_params.clone() + ..default_try_msg.clone() }, want_pass: false, }, Test { name: "Bad consensus height, height is 0".to_string(), - params: ConOpenTryParams { + raw: RawMsgConnectionOpenTry { consensus_height: 0, - ..default_con_params.clone() + ..default_try_msg.clone() }, want_pass: false, }, Test { name: "Empty proof".to_string(), - params: ConOpenTryParams { - proof_init: CommitmentProof { ops: vec![] }, - ..default_con_params + raw: RawMsgConnectionOpenTry { + proof_init: b"".to_vec(), + ..default_try_msg }, want_pass: false, }, @@ -493,31 +666,14 @@ mod tests { .collect(); for test in tests { - let p = test.params.clone(); - - 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, - ); + let msg = MsgConnectionOpenTry::try_from(test.raw.clone()); assert_eq!( test.want_pass, msg.is_ok(), - "MsgConnOpenTry::new failed for test {}, \nmsg {:?} \nwith error {:?}", + "MsgConnOpenTry::new failed for test {}, \nmsg {:?} with error {:?}", test.name, - test.params.clone(), + test.raw, msg.err(), ); } @@ -526,65 +682,49 @@ mod tests { #[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, + raw: RawMsgConnectionOpenAck, 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 default_ack_msg = get_dummy_msg_conn_open_ack(); let tests: Vec = vec![ Test { name: "Good parameters".to_string(), - params: default_con_params.clone(), + raw: default_ack_msg.clone(), want_pass: true, }, Test { name: "Bad connection id, non-alpha".to_string(), - params: ConOpenAckParams { + raw: RawMsgConnectionOpenAck { connection_id: "con007".to_string(), - ..default_con_params.clone() + ..default_ack_msg.clone() }, want_pass: false, }, Test { name: "Bad version, empty version string".to_string(), - params: ConOpenAckParams { + raw: RawMsgConnectionOpenAck { version: "".to_string(), - ..default_con_params.clone() + ..default_ack_msg.clone() }, want_pass: false, }, Test { name: "Bad proof height, height is 0".to_string(), - params: ConOpenAckParams { + raw: RawMsgConnectionOpenAck { proof_height: 0, - ..default_con_params.clone() + ..default_ack_msg.clone() }, want_pass: false, }, Test { name: "Bad consensus height, height is 0".to_string(), - params: ConOpenAckParams { + raw: RawMsgConnectionOpenAck { consensus_height: 0, - ..default_con_params + ..default_ack_msg }, want_pass: false, }, @@ -593,28 +733,15 @@ mod tests { .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, - ); + let msg = MsgConnectionOpenAck::try_from(test.raw.clone()); assert_eq!( test.want_pass, msg.is_ok(), - "MsgConnOpenAck::new failed for test {}, \nmsg {:?} \nwith error {:?}", + "MsgConnOpenTry::new failed for test {}, \nmsg {:?} with error {:?}", test.name, - test.params.clone(), - msg.err() + test.raw, + msg.err(), ); } } @@ -622,43 +749,32 @@ mod tests { #[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, + raw: RawMsgConnectionOpenConfirm, want_pass: bool, } - let default_con_params = ConOpenConfirmParams { - connection_id: "srcconnection".to_string(), - proof_ack: get_dummy_proof(), - proof_height: 10, - }; - + let default_ack_msg = get_dummy_msg_conn_open_confirm(); let tests: Vec = vec![ Test { name: "Good parameters".to_string(), - params: default_con_params.clone(), + raw: default_ack_msg.clone(), want_pass: true, }, Test { name: "Bad connection id, non-alpha".to_string(), - params: ConOpenConfirmParams { + raw: RawMsgConnectionOpenConfirm { connection_id: "con007".to_string(), - ..default_con_params.clone() + ..default_ack_msg.clone() }, want_pass: false, }, Test { name: "Bad proof height, height is 0".to_string(), - params: ConOpenConfirmParams { + raw: RawMsgConnectionOpenConfirm { proof_height: 0, - ..default_con_params + ..default_ack_msg }, want_pass: false, }, @@ -667,21 +783,15 @@ mod tests { .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); + let msg = MsgConnectionOpenConfirm::try_from(test.raw.clone()); assert_eq!( test.want_pass, msg.is_ok(), - "MsgConnOpenConfirm::new failed for test {}, \nmsg {:?} \nwith error {:?}", + "MsgConnOpenTry::new failed for test {}, \nmsg {:?} with error {:?}", test.name, - test.params.clone(), - msg.err() + test.raw, + msg.err(), ); } } diff --git a/modules/src/ics04_channel/msgs.rs b/modules/src/ics04_channel/msgs.rs index 6b5f4d8b2b..93107d1246 100644 --- a/modules/src/ics04_channel/msgs.rs +++ b/modules/src/ics04_channel/msgs.rs @@ -3,7 +3,7 @@ use super::channel::{ChannelEnd, Counterparty, Order}; 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::ics23_commitment::commitment::CommitmentProof; use crate::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; use crate::proofs::Proofs; use crate::tx_msg::Msg; @@ -576,7 +576,7 @@ mod tests { MsgChannelCloseConfirm, MsgChannelCloseInit, MsgChannelOpenAck, MsgChannelOpenConfirm, MsgChannelOpenTry, }; - use crate::ics23_commitment::CommitmentProof; + use crate::ics23_commitment::commitment::CommitmentProof; use std::str::FromStr; use tendermint::account::Id as AccountId; @@ -723,7 +723,7 @@ mod tests { counterparty_port_id: "destport".to_string(), counterparty_channel_id: "testdestchannel".to_string(), counterparty_version: "1.0".to_string(), - proof_init: get_dummy_proof(), + proof_init: get_dummy_proof().into(), proof_height: 10, }; @@ -920,7 +920,7 @@ mod tests { port_id: "port".to_string(), channel_id: "testchannel".to_string(), counterparty_version: "1.0".to_string(), - proof_try: get_dummy_proof(), + proof_try: get_dummy_proof().into(), proof_height: 10, }; @@ -1043,7 +1043,7 @@ mod tests { let default_params = OpenConfirmParams { port_id: "port".to_string(), channel_id: "testchannel".to_string(), - proof_ack: get_dummy_proof(), + proof_ack: get_dummy_proof().into(), proof_height: 10, }; @@ -1253,7 +1253,7 @@ mod tests { let default_params = CloseConfirmParams { port_id: "port".to_string(), channel_id: "testchannel".to_string(), - proof_init: get_dummy_proof(), + proof_init: get_dummy_proof().into(), proof_height: 10, }; diff --git a/modules/src/ics07_tendermint/client_state.rs b/modules/src/ics07_tendermint/client_state.rs index e223867dea..1d6c4bc1c6 100644 --- a/modules/src/ics07_tendermint/client_state.rs +++ b/modules/src/ics07_tendermint/client_state.rs @@ -1,8 +1,15 @@ +#![allow(unreachable_code, unused_variables)] +// TODO -- clean this up use crate::ics02_client::client_type::ClientType; -use crate::ics07_tendermint::consensus_state::ConsensusState; +use crate::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProof}; + +use crate::ics03_connection::connection::ConnectionEnd; use crate::ics07_tendermint::error::{Error, Kind}; -use crate::ics23_commitment::CommitmentRoot; +use crate::ics24_host::identifier::{ClientId, ConnectionId}; +use crate::ics02_client::client_def::AnyHeader; +use crate::ics02_client::state::ConsensusState; +use crate::ics07_tendermint::consensus_state::ConsensusState as tmConsensusState; use serde_derive::{Deserialize, Serialize}; use std::time::Duration; use tendermint::block::Height; @@ -14,9 +21,8 @@ pub struct ClientState { pub trusting_period: Duration, pub unbonding_period: Duration, pub max_clock_drift: Duration, - pub latest_height: crate::Height, - pub frozen_height: crate::Height, - //pub proof_specs: Specs + pub latest_height: Height, + pub frozen_height: Height, } impl ClientState { @@ -71,10 +77,10 @@ impl ClientState { latest_height, }) } -} - -impl From for ClientState { - fn from(_: ConsensusState) -> Self { + pub fn check_header_and_update_state( + &self, + header: AnyHeader, + ) -> Result<(ClientState, tmConsensusState), Box> { todo!() } } @@ -88,7 +94,7 @@ impl crate::ics02_client::state::ClientState for ClientState { ClientType::Tendermint } - fn get_latest_height(&self) -> crate::Height { + fn latest_height(&self) -> Height { self.latest_height } @@ -97,11 +103,40 @@ impl crate::ics02_client::state::ClientState for ClientState { self.frozen_height != Height(0) } + // fn check_header_and_update_state( + // &self, + // header: &dyn Header, + // ) -> Result< + // ( + // Box, + // Box, + // ), + // Box, + // > { + // todo!() + // } + fn verify_client_consensus_state( &self, - _root: &CommitmentRoot, + height: Height, + prefix: &CommitmentPrefix, + proof: &CommitmentProof, + client_id: &ClientId, + consensus_height: Height, + expected_consensus_state: &dyn ConsensusState, + ) -> Result<(), Box> { + Ok(()) + } + + fn verify_connection_state( + &self, + height: Height, + prefix: &CommitmentPrefix, + proof: &CommitmentProof, + connection_id: &ConnectionId, + expected_connection_end: &ConnectionEnd, ) -> Result<(), Box> { - unimplemented!() + Ok(()) } } diff --git a/modules/src/ics07_tendermint/consensus_state.rs b/modules/src/ics07_tendermint/consensus_state.rs index 1b6d0ab8bb..c6a9f9ea24 100644 --- a/modules/src/ics07_tendermint/consensus_state.rs +++ b/modules/src/ics07_tendermint/consensus_state.rs @@ -1,5 +1,5 @@ use crate::ics02_client::client_type::ClientType; -use crate::ics23_commitment::CommitmentRoot; +use crate::ics23_commitment::commitment::CommitmentRoot; use serde_derive::{Deserialize, Serialize}; use tendermint::Hash; diff --git a/modules/src/ics07_tendermint/header.rs b/modules/src/ics07_tendermint/header.rs index d86d13de67..a6a453d9fb 100644 --- a/modules/src/ics07_tendermint/header.rs +++ b/modules/src/ics07_tendermint/header.rs @@ -6,7 +6,7 @@ use tendermint::validator::Set as ValidatorSet; use crate::ics02_client::client_type::ClientType; use crate::ics07_tendermint::consensus_state::ConsensusState; -use crate::ics23_commitment::CommitmentRoot; +use crate::ics23_commitment::commitment::CommitmentRoot; use crate::Height; /// Tendermint consensus header diff --git a/modules/src/ics07_tendermint/state.rs b/modules/src/ics07_tendermint/state.rs deleted file mode 100644 index 4021407baf..0000000000 --- a/modules/src/ics07_tendermint/state.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod consensus; diff --git a/modules/src/ics23_commitment/commitment.rs b/modules/src/ics23_commitment/commitment.rs new file mode 100644 index 0000000000..bcf3e134bd --- /dev/null +++ b/modules/src/ics23_commitment/commitment.rs @@ -0,0 +1,54 @@ +use serde_derive::{Deserialize, Serialize}; + +use std::fmt; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CommitmentRoot; +impl CommitmentRoot { + pub fn from_bytes(_bytes: &[u8]) -> Self { + // TODO + CommitmentRoot {} + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CommitmentPath; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct CommitmentProof(Vec); +impl CommitmentProof { + pub fn is_empty(&self) -> bool { + self.0.len() == 0 + } +} + +impl From> for CommitmentProof { + fn from(v: Vec) -> Self { + Self { 0: v } + } +} + +#[derive(Clone, PartialEq, Serialize, Deserialize)] +pub struct CommitmentPrefix(Vec); + +impl CommitmentPrefix { + pub fn is_empty(&self) -> bool { + self.0.len() == 0 + } +} + +impl From> for CommitmentPrefix { + fn from(v: Vec) -> Self { + Self { 0: v } + } +} + +impl fmt::Debug for CommitmentPrefix { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let converted = std::str::from_utf8(&self.0); + match converted { + Ok(s) => write!(f, "{}", s), + Err(_e) => write!(f, "{:?}", &self.0), + } + } +} diff --git a/modules/src/ics23_commitment/merkle.rs b/modules/src/ics23_commitment/merkle.rs new file mode 100644 index 0000000000..aab52d43d3 --- /dev/null +++ b/modules/src/ics23_commitment/merkle.rs @@ -0,0 +1,14 @@ +use crate::ics23_commitment::commitment::CommitmentPrefix; +use ibc_proto::commitment::MerklePath; + +pub fn apply_prefix( + prefix: &CommitmentPrefix, + _path: String, +) -> Result> { + if prefix.is_empty() { + return Err("empty prefix".into()); + } + + // TODO + Ok(MerklePath { key_path: None }) +} diff --git a/modules/src/ics23_commitment/mock.rs b/modules/src/ics23_commitment/mock.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/modules/src/ics23_commitment/mock.rs @@ -0,0 +1 @@ + diff --git a/modules/src/ics23_commitment/mod.rs b/modules/src/ics23_commitment/mod.rs index ced4f91e13..12c7c57ed5 100644 --- a/modules/src/ics23_commitment/mod.rs +++ b/modules/src/ics23_commitment/mod.rs @@ -1,48 +1,3 @@ -use serde_derive::{Deserialize, Serialize}; - -use std::fmt; -use tendermint::merkle::proof::Proof; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct CommitmentRoot; -impl CommitmentRoot { - pub fn from_bytes(_bytes: &[u8]) -> Self { - // TODO - CommitmentRoot {} - } -} - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct CommitmentPath; - -pub type CommitmentProof = Proof; -/* -impl CommitmentProof { - pub fn from_bytes(_bytes: &[u8]) -> Self { - todo!() - } - - pub fn validate_basic() -> Result { - todo!() - } -} -*/ - -#[derive(Clone, PartialEq, Serialize, Deserialize)] -pub struct CommitmentPrefix(Vec); - -impl CommitmentPrefix { - pub fn new(content: Vec) -> Self { - Self { 0: content } - } -} - -impl fmt::Debug for CommitmentPrefix { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let converted = String::from_utf8(self.clone().0); - match converted { - Ok(s) => write!(f, "{}", s), - Err(_e) => write!(f, "{:?}", &self.0), - } - } -} +pub mod commitment; +pub mod merkle; +pub mod mock; diff --git a/modules/src/ics24_host/identifier.rs b/modules/src/ics24_host/identifier.rs index 7f03c756cd..878f4aa007 100644 --- a/modules/src/ics24_host/identifier.rs +++ b/modules/src/ics24_host/identifier.rs @@ -34,6 +34,20 @@ impl FromStr for ClientId { } } +/// Equality check against string literal (satisfies &ClientId == &str). +/// ``` +/// use std::str::FromStr; +/// use ibc::ics24_host::identifier::ClientId; +/// let client_id = ClientId::from_str("clientidtwo"); +/// assert!(client_id.is_ok()); +/// client_id.map(|id| {assert_eq!(&id, "clientidtwo")}); +/// ``` +impl PartialEq for ClientId { + fn eq(&self, other: &str) -> bool { + self.as_str().eq(other) + } +} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct ConnectionId(String); @@ -64,6 +78,20 @@ impl FromStr for ConnectionId { } } +/// Equality check against string literal (satisfies &ConnectionId == &str). +/// ``` +/// use std::str::FromStr; +/// use ibc::ics24_host::identifier::ConnectionId; +/// let conn_id = ConnectionId::from_str("connectionId"); +/// assert!(conn_id.is_ok()); +/// conn_id.map(|id| {assert_eq!(&id, "connectionId")}); +/// ``` +impl PartialEq for ConnectionId { + fn eq(&self, other: &str) -> bool { + self.as_str().eq(other) + } +} + #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct PortId(String); diff --git a/modules/src/lib.rs b/modules/src/lib.rs index 618dda4c8e..ca0c1cb074 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -19,6 +19,7 @@ //! - ICS 23: Vector Commitment Scheme //! - ICS 24: Host Requirements +pub mod context; pub mod events; pub mod handler; pub mod ics02_client; @@ -33,6 +34,12 @@ pub mod proofs; pub mod try_from_raw; pub mod tx_msg; +#[cfg(test)] +pub mod context_mock; + +#[cfg(test)] +pub mod mock_client; + /// Height of a block, same as in `tendermint` crate pub type Height = tendermint::block::Height; diff --git a/modules/src/mock_client/client_def.rs b/modules/src/mock_client/client_def.rs new file mode 100644 index 0000000000..b0ef32e6a9 --- /dev/null +++ b/modules/src/mock_client/client_def.rs @@ -0,0 +1,12 @@ +use crate::ics02_client::client_def::ClientDef; +use crate::mock_client::header::MockHeader; +use crate::mock_client::state::{MockClientState, MockConsensusState}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MockClient; + +impl ClientDef for MockClient { + type Header = MockHeader; + type ClientState = MockClientState; + type ConsensusState = MockConsensusState; +} diff --git a/modules/src/mock_client/error.rs b/modules/src/mock_client/error.rs new file mode 100644 index 0000000000..96028d69d8 --- /dev/null +++ b/modules/src/mock_client/error.rs @@ -0,0 +1,4 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum MockError {} \ No newline at end of file diff --git a/modules/src/mock_client/header.rs b/modules/src/mock_client/header.rs new file mode 100644 index 0000000000..22d173346a --- /dev/null +++ b/modules/src/mock_client/header.rs @@ -0,0 +1,30 @@ +use crate::ics02_client::client_def::AnyHeader; +use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::header::Header; +use serde_derive::{Deserialize, Serialize}; +use tendermint::block::Height; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct MockHeader(pub Height); + +impl MockHeader { + pub fn height(&self) -> Height { + self.0 + } +} + +impl From for AnyHeader { + fn from(mh: MockHeader) -> Self { + Self::Mock(mh) + } +} + +impl Header for MockHeader { + fn client_type(&self) -> ClientType { + todo!() + } + + fn height(&self) -> Height { + todo!() + } +} diff --git a/modules/src/mock_client/mod.rs b/modules/src/mock_client/mod.rs new file mode 100644 index 0000000000..dbccf94e54 --- /dev/null +++ b/modules/src/mock_client/mod.rs @@ -0,0 +1,5 @@ +//! Mock client implementation, used for testing + +pub mod client_def; +pub mod header; +pub mod state; diff --git a/modules/src/mock_client/state.rs b/modules/src/mock_client/state.rs new file mode 100644 index 0000000000..73a8b17b98 --- /dev/null +++ b/modules/src/mock_client/state.rs @@ -0,0 +1,143 @@ +#![allow(unreachable_code, unused_variables)] + +use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader}; +use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::header::Header; +use crate::ics02_client::state::{ClientState, ConsensusState}; +use crate::ics03_connection::connection::ConnectionEnd; +use crate::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProof, CommitmentRoot}; +use crate::ics23_commitment::merkle::apply_prefix; +use crate::ics24_host::identifier::{ClientId, ConnectionId}; +use crate::ics24_host::Path; +use crate::mock_client::header::MockHeader; +use serde_derive::{Deserialize, Serialize}; +use tendermint::block::Height; + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct MockClientState(pub MockHeader); + +impl MockClientState { + pub fn check_header_and_update_state( + &self, + header: AnyHeader, + ) -> Result<(MockClientState, MockConsensusState), Box> { + match header { + #[cfg(test)] + AnyHeader::Mock(mock_header) => { + if self.latest_height() >= header.height() { + return Err("header height is lower than client latest".into()); + } + + Ok(( + MockClientState(mock_header), + MockConsensusState(mock_header), + )) + } + _ => Err("bad header type for mock client state".into()), + } + } +} + +#[cfg(test)] +impl From for AnyClientState { + fn from(mcs: MockClientState) -> Self { + Self::Mock(mcs) + } +} + +impl ClientState for MockClientState { + fn chain_id(&self) -> String { + todo!() + } + + fn client_type(&self) -> ClientType { + todo!() + } + + fn latest_height(&self) -> Height { + self.0.height() + } + + fn is_frozen(&self) -> bool { + // TODO + false + } + + // fn check_header_and_update_state( + // &self, + // header: &dyn Header, + // ) -> Result<(Box, Box), Box> { + // if self.latest_height() >= header.height() { + // return Err("header height is lower than client latest".into()); + // } + // + // Ok(( + // Box::new(AnyClientState::Mock(MockClientState(header.height().value() as u32))), + // Box::new(AnyConsensusState::Mock(MockConsensusState(header.height().value() as u32))), + // )) + // } + + fn verify_client_consensus_state( + &self, + height: Height, + prefix: &CommitmentPrefix, + proof: &CommitmentProof, + client_id: &ClientId, + consensus_height: Height, + expected_consensus_state: &dyn ConsensusState, + ) -> Result<(), Box> { + let client_prefixed_path = + Path::ConsensusState(client_id.clone(), height.value()).to_string(); + let _path = apply_prefix(prefix, client_prefixed_path)?; + // TODO - add ctx to all client verification functions + // let cs = ctx.fetch_self_consensus_state(height); + // TODO - implement this + // proof.verify_membership(cs.root(), path, expected_consensus_state) + Ok(()) + } + + fn verify_connection_state( + &self, + height: Height, + prefix: &CommitmentPrefix, + proof: &CommitmentProof, + connection_id: &ConnectionId, + expected_connection_end: &ConnectionEnd, + ) -> Result<(), Box> { + Ok(()) + } +} + +impl From for MockClientState { + fn from(cs: MockConsensusState) -> Self { + Self(cs.0) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct MockConsensusState(pub MockHeader); + +#[cfg(test)] +impl From for AnyConsensusState { + fn from(mcs: MockConsensusState) -> Self { + Self::Mock(mcs) + } +} + +impl ConsensusState for MockConsensusState { + fn client_type(&self) -> ClientType { + todo!() + } + + fn height(&self) -> Height { + todo!() + } + + fn root(&self) -> &CommitmentRoot { + todo!() + } + + fn validate_basic(&self) -> Result<(), Box> { + todo!() + } +} diff --git a/modules/src/proofs.rs b/modules/src/proofs.rs index ee5313fd10..6e9df14d22 100644 --- a/modules/src/proofs.rs +++ b/modules/src/proofs.rs @@ -1,40 +1,57 @@ -use crate::ics23_commitment::CommitmentProof; +use crate::ics23_commitment::commitment::CommitmentProof; use serde_derive::{Deserialize, Serialize}; +use tendermint::block::Height; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Proofs { object_proof: CommitmentProof, consensus_proof: Option, /// Height for both the above proofs - proofs_height: u64, + height: Height, } impl Proofs { pub fn new( object_proof: CommitmentProof, consensus_proof: Option, - proofs_height: u64, + height: u64, ) -> Result { - if proofs_height == 0 { + if height == 0 { return Err("Proofs height cannot be zero".to_string()); } - if object_proof.ops.is_empty() { + if object_proof.is_empty() { return Err("Proof cannot be empty".to_string()); } Ok(Self { object_proof, consensus_proof, - proofs_height, + height: Height(height), }) } + + /// Getter for the consensus_proof field of this proof. + pub fn consensus_proof(&self) -> Option { + self.consensus_proof.clone() + } + + /// Getter for the height field of this proof (i.e., the consensus height where this proof was + /// created). + pub fn height(&self) -> Height { + self.height + } + + /// Getter for the object-specific proof (e.g., proof for connection state or channel state). + pub fn object_proof(&self) -> &CommitmentProof { + &self.object_proof + } } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct ConsensusProof { - consensus_proof: CommitmentProof, - consensus_height: u64, + proof: CommitmentProof, + height: Height, } impl ConsensusProof { @@ -42,12 +59,23 @@ impl ConsensusProof { if consensus_height == 0 { return Err("Consensus height cannot be zero".to_string()); } - if consensus_proof.ops.is_empty() { + if consensus_proof.is_empty() { return Err("Proof cannot be empty".to_string()); } + Ok(Self { - consensus_proof, - consensus_height, + proof: consensus_proof, + height: Height(consensus_height), }) } + + /// Getter for the height field of this consensus proof. + pub fn height(&self) -> Height { + self.height + } + + /// Getter for the proof (CommitmentProof) field of this consensus proof. + pub fn proof(&self) -> &CommitmentProof { + &self.proof + } } diff --git a/relayer-cli/tests/integration.rs b/relayer-cli/tests/integration.rs index 16de6a4001..d393103c90 100644 --- a/relayer-cli/tests/integration.rs +++ b/relayer-cli/tests/integration.rs @@ -14,7 +14,6 @@ use ibc::ics03_connection::connection::ConnectionEnd; use ibc::ics03_connection::connection::State as ConnectionState; use ibc::ics04_channel::channel::{ChannelEnd, Order, State as ChannelState}; -use ibc::ics23_commitment::CommitmentPrefix; use ibc::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; use ibc::ics24_host::Path::{ChannelEnds, ClientConnections, Connections}; use relayer::chain::{Chain, CosmosSDKChain}; @@ -61,10 +60,7 @@ fn query_connection_id() { assert_eq!(query.client_id(), "clientidone"); assert_eq!(query.counterparty().client_id(), "clientidtwo"); assert_eq!(query.counterparty().connection_id(), "connectionidtwo"); - assert_eq!( - query.counterparty().prefix(), - &CommitmentPrefix::new(b"prefix".to_vec()) - ); + assert_eq!(query.counterparty().prefix(), &b"prefix".to_vec().into()); assert_eq!( query.versions(), vec!["(1,[ORDER_ORDERED,ORDER_UNORDERED])"]