diff --git a/modules/src/context.rs b/modules/src/context.rs index 49b3527169..05fa2645fa 100644 --- a/modules/src/context.rs +++ b/modules/src/context.rs @@ -2,11 +2,11 @@ use crate::ics07_tendermint; use serde_derive::{Deserialize, Serialize}; use tendermint::block::Height; +use crate::ics02_client::client_def::AnyConsensusState; +use crate::ics02_client::client_def::AnyHeader; + #[cfg(test)] -use { - crate::ics02_client::client_def::AnyConsensusState, crate::mock_client::header::MockHeader, - crate::mock_client::state::MockConsensusState, -}; +use {crate::mock_client::header::MockHeader, crate::mock_client::state::MockConsensusState}; #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum SelfChainType { @@ -37,6 +37,8 @@ impl From for AnyConsensusState { pub trait ChainReader { fn chain_type(&self) -> SelfChainType; fn self_historical_info(&self, height: Height) -> Option; + fn header(&self, height: Height) -> Option; + fn fetch_self_consensus_state(&self, height: Height) -> Option; } pub trait ChainKeeper { diff --git a/modules/src/context_mock.rs b/modules/src/context_mock.rs index a9b31a2ec1..dd4c695417 100644 --- a/modules/src/context_mock.rs +++ b/modules/src/context_mock.rs @@ -1,23 +1,32 @@ use crate::context::{ChainKeeper, ChainReader, HistoricalInfo, SelfChainType, SelfHeader}; +use crate::ics02_client::client_def::{AnyConsensusState, AnyHeader}; use crate::mock_client::header::MockHeader; use serde_derive::{Deserialize, Serialize}; +use std::cmp::min; use std::error::Error; use tendermint::block::Height; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)] pub struct MockChainContext { + /// Maximum size of the history pub max_size: usize, + /// Highest height of the headers in the history pub latest: Height, + /// A list of `max_size` headers ordered by height pub history: Vec, } impl MockChainContext { - pub fn new(max_size: usize, n: Height) -> Self { + /// Creates a new mock chain with max_size number of headers up to height h + pub fn new(max_size: usize, h: Height) -> Self { + // number of headers to store, if h is 0 nothing is stored + let n = min(max_size as u64, h.value()); Self { max_size, - latest: n, - history: (0..n.value()) - .map(|i| MockHeader(Height(i).increment())) + latest: h, + history: (0..n) + .rev() + .map(|i| MockHeader(Height(h.value() - i))) .collect(), } } @@ -38,29 +47,41 @@ impl MockChainContext { } } + pub fn add_header(&mut self, h: u64) { + let mut new_h = h; + if h == 0 { + new_h = u64::from(self.latest.increment()); + } + self.store_historical_info( + Height(new_h), + HistoricalInfo { + header: SelfHeader::Mock(MockHeader(Height(new_h))), + }, + ); + } + pub fn validate(&self) -> Result<(), Box> { - // TODO + // 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()); + } + + // get the highers header + let lh = self.history[self.history.len() - 1]; + // check latest is properly updated with highest header height + 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 ph = self.history[i - 1]; + let h = self.history[i]; + if ph.height().increment() != h.height() { + return Err("headers in history not sequential".to_string().into()); + } + } Ok(()) - // // 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(()) } } @@ -78,10 +99,28 @@ impl ChainReader for MockChainContext { None } else { Some(HistoricalInfo { - header: SelfHeader::Mock(self.history[h - l]), + header: SelfHeader::Mock(self.history[self.max_size + l - h - 1]), }) } } + + fn header(&self, height: Height) -> Option { + let hi = self.self_historical_info(height)?.header; + match hi { + #[cfg(test)] + SelfHeader::Mock(h) => Some(h.into()), + _ => None, + } + } + + fn fetch_self_consensus_state(&self, height: Height) -> Option { + let hi = self.self_historical_info(height)?.header; + match hi { + #[cfg(test)] + SelfHeader::Mock(h) => Some(h.into()), + _ => None, + } + } } impl ChainKeeper for MockChainContext { @@ -133,6 +172,11 @@ mod tests { ctx: MockChainContext::new(3, Height(2)), args: [3, 4].to_vec(), }, + Test { + name: "Add with initial prune".to_string(), + ctx: MockChainContext::new(3, Height(10)), + args: [11].to_vec(), + }, Test { name: "Attempt to add non sequential headers".to_string(), ctx: MockChainContext::new(3, Height(2)), diff --git a/modules/src/ics02_client/client_def.rs b/modules/src/ics02_client/client_def.rs index 0842204cb8..39377a88f6 100644 --- a/modules/src/ics02_client/client_def.rs +++ b/modules/src/ics02_client/client_def.rs @@ -249,7 +249,12 @@ impl ConsensusState for AnyConsensusState { } fn height(&self) -> Height { - todo!() + match self { + AnyConsensusState::Tendermint(cs) => cs.height(), + + #[cfg(test)] + AnyConsensusState::Mock(cs) => cs.height(), + } } fn root(&self) -> &CommitmentRoot { diff --git a/modules/src/ics02_client/context_mock.rs b/modules/src/ics02_client/context_mock.rs index d8399d5064..48d6065f50 100644 --- a/modules/src/ics02_client/context_mock.rs +++ b/modules/src/ics02_client/context_mock.rs @@ -1,7 +1,9 @@ +use crate::context_mock::MockChainContext; 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::ics02_client::error::{Error, Kind}; +use crate::ics02_client::state::ConsensusState; use crate::ics24_host::identifier::ClientId; use crate::mock_client::header::MockHeader; use crate::mock_client::state::{MockClientRecord, MockClientState, MockConsensusState}; @@ -15,13 +17,31 @@ use tendermint::block::Height; /// the ICS02 handlers (create, update client) and other dependent ICS handlers (e.g., ICS03). #[derive(Clone, Debug)] pub struct MockClientContext { + pub chain_context: MockChainContext, /// The set of all clients, indexed by their id. pub clients: HashMap, } +impl MockClientContext { + pub fn new(chain_height: u64, max_history_size: usize) -> Self { + MockClientContext { + chain_context: MockChainContext::new(max_history_size, Height(chain_height)), + clients: Default::default(), + } + } + pub fn chain_context(&self) -> &MockChainContext { + &self.chain_context + } + + pub fn set_chain_context(&mut self, chain_context: MockChainContext) { + self.chain_context = chain_context + } +} + impl Default for MockClientContext { fn default() -> Self { MockClientContext { + chain_context: Default::default(), clients: Default::default(), } } @@ -101,25 +121,56 @@ impl ClientReader for MockClientContext { impl ClientKeeper for MockClientContext { fn store_client_type( &mut self, - _client_id: ClientId, - _client_type: ClientType, + client_id: ClientId, + client_type: ClientType, ) -> Result<(), Error> { - todo!() + let mut client_record = self.clients.entry(client_id).or_insert(MockClientRecord { + client_type, + consensus_states: Default::default(), + client_state: Default::default(), + }); + + client_record.client_type = client_type; + Ok(()) } fn store_client_state( &mut self, - _client_id: ClientId, - _client_state: AnyClientState, + client_id: ClientId, + client_state: AnyClientState, ) -> Result<(), Error> { - todo!() + match client_state { + AnyClientState::Mock(client_state) => { + let mut client_record = self.clients.entry(client_id).or_insert(MockClientRecord { + client_type: ClientType::Mock, + consensus_states: Default::default(), + client_state, + }); + client_record.client_state = client_state; + Ok(()) + } + _ => Err(Kind::BadClientState.into()), + } } fn store_consensus_state( &mut self, - _client_id: ClientId, - _consensus_state: AnyConsensusState, + client_id: ClientId, + consensus_state: AnyConsensusState, ) -> Result<(), Error> { - todo!() + match consensus_state { + AnyConsensusState::Mock(consensus_state) => { + let client_record = self.clients.entry(client_id).or_insert(MockClientRecord { + client_type: ClientType::Mock, + consensus_states: Default::default(), + client_state: Default::default(), + }); + client_record + .consensus_states + .insert(consensus_state.height(), consensus_state); + Ok(()) + } + _ => Err(Kind::BadClientState.into()), + } } } diff --git a/modules/src/ics02_client/error.rs b/modules/src/ics02_client/error.rs index 3be580296f..6bb89d2f79 100644 --- a/modules/src/ics02_client/error.rs +++ b/modules/src/ics02_client/error.rs @@ -48,6 +48,9 @@ pub enum Kind { #[error("mismatch between client and arguments types, expected: {0:?}")] ClientArgsTypeMismatch(ClientType), + + #[error("bad client state (expected mock client)")] + BadClientState, } impl Kind { diff --git a/modules/src/ics02_client/handler.rs b/modules/src/ics02_client/handler.rs index e5fe050a44..a151db88d9 100644 --- a/modules/src/ics02_client/handler.rs +++ b/modules/src/ics02_client/handler.rs @@ -17,6 +17,7 @@ pub enum ClientEvent { ClientUpdated(ClientId), } +#[derive(Clone, Debug, PartialEq, Eq)] pub enum ClientResult { CreateResult(CreateClientResult), UpdateResult(UpdateClientResult), diff --git a/modules/src/ics02_client/handler/create_client.rs b/modules/src/ics02_client/handler/create_client.rs index 8f5c156578..4971ff8706 100644 --- a/modules/src/ics02_client/handler/create_client.rs +++ b/modules/src/ics02_client/handler/create_client.rs @@ -7,7 +7,7 @@ use crate::ics02_client::handler::ClientEvent; use crate::ics02_client::msgs::MsgCreateAnyClient; use crate::ics24_host::identifier::ClientId; -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct CreateClientResult { pub client_id: ClientId, pub client_type: ClientType, diff --git a/modules/src/ics02_client/handler/update_client.rs b/modules/src/ics02_client/handler/update_client.rs index 5ce05623ba..bac2842acc 100644 --- a/modules/src/ics02_client/handler/update_client.rs +++ b/modules/src/ics02_client/handler/update_client.rs @@ -1,14 +1,14 @@ use crate::handler::{HandlerOutput, HandlerResult}; use crate::ics02_client::client_def::{AnyClient, AnyClientState, AnyConsensusState, ClientDef}; -use crate::ics02_client::context::ClientReader; +use crate::ics02_client::context::{ClientKeeper, ClientReader}; use crate::ics02_client::error::{Error, Kind}; -use crate::ics02_client::handler::ClientEvent; +use crate::ics02_client::handler::{ClientEvent, ClientResult}; use crate::ics02_client::msgs::MsgUpdateAnyClient; use crate::ics02_client::state::ClientState; use crate::ics24_host::identifier::ClientId; -#[derive(Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct UpdateClientResult { pub client_id: ClientId, pub client_state: AnyClientState, @@ -57,6 +57,10 @@ pub fn process( })) } +pub fn keep(keeper: &mut dyn ClientKeeper, result: UpdateClientResult) -> Result<(), Error> { + keeper.store_client_result(ClientResult::UpdateResult(result)) +} + #[cfg(test)] mod tests { use tendermint::block::Height; diff --git a/modules/src/ics03_connection/context_mock.rs b/modules/src/ics03_connection/context_mock.rs index 697d045e33..f54aaee3b9 100644 --- a/modules/src/ics03_connection/context_mock.rs +++ b/modules/src/ics03_connection/context_mock.rs @@ -13,7 +13,6 @@ use tendermint::block::Height; #[derive(Clone, Debug, Default)] pub struct MockConnectionContext { - chain_context: MockChainContext, client_context: MockClientContext, connections: HashMap, client_connections: HashMap, @@ -22,15 +21,22 @@ pub struct MockConnectionContext { 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(), + client_context: MockClientContext::new(chain_height, max_history_size), connections: Default::default(), client_connections: Default::default(), } } + pub fn chain_context(&self) -> &MockChainContext { + &self.client_context.chain_context + } + + pub fn client_context(&self) -> &MockClientContext { + &self.client_context + } + pub fn with_client_state(self, client_id: &ClientId, latest_client_height: u64) -> Self { - let mut client_context = self.client_context.clone(); + let mut client_context = self.client_context().clone(); client_context.with_client_consensus_state(client_id, Height(latest_client_height)); Self { client_context, @@ -39,7 +45,7 @@ impl MockConnectionContext { } pub fn max_size(&self) -> usize { - self.chain_context.max_size() + self.chain_context().max_size() } pub fn add_connection(self, id: ConnectionId, end: ConnectionEnd) -> Self { @@ -58,16 +64,16 @@ impl ConnectionReader for MockConnectionContext { } fn fetch_client_state(&self, client_id: &ClientId) -> Option { - self.client_context.client_state(client_id) + self.client_context().client_state(client_id) } fn chain_current_height(&self) -> Height { - self.chain_context.latest + 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() + self.chain_context().max_size() } fn chain_type(&self) -> SelfChainType { @@ -83,11 +89,11 @@ impl ConnectionReader for MockConnectionContext { client_id: &ClientId, height: Height, ) -> Option { - self.client_context.consensus_state(client_id, height) + 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; + let hi = self.chain_context().self_historical_info(height)?.header; match hi { #[cfg(test)] SelfHeader::Mock(h) => Some(h.into()), diff --git a/modules/src/ics18_relayer/context.rs b/modules/src/ics18_relayer/context.rs new file mode 100644 index 0000000000..b2ff2e5179 --- /dev/null +++ b/modules/src/ics18_relayer/context.rs @@ -0,0 +1,24 @@ +use crate::handler::HandlerOutput; +use crate::ics02_client::client_def::{AnyClientState, AnyHeader}; +use crate::ics18_relayer::error::Error; +use crate::ics24_host::identifier::ClientId; +use crate::ics26_routing::msgs::ICS26Envelope; +use crate::Height; + +/// Trait capturing all dependencies (i.e., the context) which algorithms in ICS18 require to +/// relay packets between chains. This trait comprises the dependencies towards a single chain. +/// TODO -- eventually this trait should mirror the `Chain` trait. +pub trait ICS18Context { + /// Returns the latest height of the chain. + fn query_latest_height(&self) -> Height; + + /// Returns this client state for the given `client_id` on this chain. + fn query_client_full_state(&self, client_id: &ClientId) -> Option; + + /// Returns the most advanced header of this chain. + fn query_latest_header(&self) -> Option; + + /// Interface that the relayer uses to submit a datagram to this chain. + /// TODO: Unclear what should be the return type of this. + fn send(&mut self, msg: ICS26Envelope) -> Result, Error>; +} diff --git a/modules/src/ics18_relayer/context_mock.rs b/modules/src/ics18_relayer/context_mock.rs new file mode 100644 index 0000000000..62151a9aaf --- /dev/null +++ b/modules/src/ics18_relayer/context_mock.rs @@ -0,0 +1,89 @@ +use crate::context::ChainReader; +use crate::handler::HandlerOutput; +use crate::ics02_client::client_def::{AnyClientState, AnyHeader}; +use crate::ics02_client::client_type::ClientType; +use crate::ics02_client::context_mock::MockClientContext; +use crate::ics03_connection::context::ConnectionReader; +use crate::ics18_relayer::context::ICS18Context; +use crate::ics18_relayer::error::{Error, Kind}; +use crate::ics24_host::identifier::ClientId; +use crate::ics26_routing::context_mock::MockICS26Context; +use crate::ics26_routing::handler::dispatch; +use crate::ics26_routing::msgs::ICS26Envelope; +use crate::Height; + +#[derive(Clone, Debug, Default)] +pub struct MockICS18Context { + /// Comprises an ICS26 context. Needed to be able to mock the routing of datagrams to a chain. + pub chain_routing_context: MockICS26Context, +} + +impl MockICS18Context { + /// Create a new ICS18 mock context. This function initializes the context to comprise a chain + /// with current height corresponding to `chain_height`, contain + pub fn new( + chain_height: Height, + max_hist_size: usize, + client_id: &ClientId, + client_height: Height, + ) -> MockICS18Context { + // Create the mock client context. + let mut client_ctx = MockClientContext::new(u64::from(chain_height), max_hist_size); + client_ctx.with_client(client_id, ClientType::Mock, client_height); + + Self { + // Wrap the client context in a ICS26 context, which we wrap in the ICS18 context. + chain_routing_context: MockICS26Context { + client_context: client_ctx, + connection_context: Default::default(), // This parameter is ignored for the purpose of this mock. + }, + } + } + + pub fn routing_context(&self) -> &MockICS26Context { + &self.chain_routing_context + } + + pub fn set_routing_context(&mut self, new_rc: MockICS26Context) { + self.chain_routing_context = new_rc + } + + pub fn advance_chain_height(&mut self) { + let mut underlying_chain_ctx = self.chain_routing_context.chain_context().clone(); + underlying_chain_ctx.add_header(0); + // Replace the chain context with the newer one. + self.chain_routing_context + .set_chain_context(underlying_chain_ctx) + } + + /// Internal interface of the context, for consuming (on the modules side) a datagram. + fn recv(&mut self, msg: ICS26Envelope) -> Result, Error> { + let mut rctx = self.routing_context().clone(); + let res = dispatch(&mut rctx, msg).map_err(|e| Kind::TransactionFailed.context(e))?; + self.set_routing_context(rctx); + // Create new block + self.advance_chain_height(); + Ok(res) + } +} + +impl ICS18Context for MockICS18Context { + fn query_latest_height(&self) -> Height { + self.chain_routing_context.chain_current_height() + } + + fn query_client_full_state(&self, client_id: &ClientId) -> Option { + self.chain_routing_context.fetch_client_state(client_id) + } + + fn query_latest_header(&self) -> Option { + let latest_height = self.chain_routing_context.chain_current_height(); + self.chain_routing_context + .chain_context() + .header(latest_height) + } + + fn send(&mut self, msg: ICS26Envelope) -> Result, Error> { + self.recv(msg) + } +} diff --git a/modules/src/ics18_relayer/error.rs b/modules/src/ics18_relayer/error.rs new file mode 100644 index 0000000000..78f18a03fd --- /dev/null +++ b/modules/src/ics18_relayer/error.rs @@ -0,0 +1,27 @@ +use crate::ics24_host::identifier::ClientId; +use crate::Height; +use anomaly::{BoxError, Context}; +use thiserror::Error; + +pub type Error = anomaly::Error; + +#[derive(Clone, Debug, Error, PartialEq, Eq)] +pub enum Kind { + #[error("client state on destination chain not found, (client id: {0})")] + ClientStateNotFound(ClientId), + + #[error("the client on destination chain is already up-to-date (client id: {0}, source height: {1}, dest height: {2})")] + ClientAlreadyUpToDate(ClientId, Height, Height), + + #[error("the client on destination chain is at a higher height (client id: {0}, source height: {1}, dest height: {2})")] + ClientAtHeigherHeight(ClientId, Height, Height), + + #[error("transaction processing by modules failed")] + TransactionFailed, +} + +impl Kind { + pub fn context(self, source: impl Into) -> Context { + Context::new(self, Some(source.into())) + } +} diff --git a/modules/src/ics18_relayer/mod.rs b/modules/src/ics18_relayer/mod.rs new file mode 100644 index 0000000000..5de23b5ade --- /dev/null +++ b/modules/src/ics18_relayer/mod.rs @@ -0,0 +1,10 @@ +//! - ICS 18: Implementation of basic relayer functions. + +pub mod context; +pub mod error; + +#[cfg(test)] +pub mod utils; // Currently only used in tests. + +#[cfg(test)] +pub mod context_mock; diff --git a/modules/src/ics18_relayer/utils.rs b/modules/src/ics18_relayer/utils.rs new file mode 100644 index 0000000000..db1ce23c98 --- /dev/null +++ b/modules/src/ics18_relayer/utils.rs @@ -0,0 +1,146 @@ +use crate::ics02_client::client_def::AnyHeader; +use crate::ics02_client::header::Header; +use crate::ics02_client::msgs::{ClientMsg, MsgUpdateAnyClient}; +use crate::ics02_client::state::ClientState; +use crate::ics03_connection::msgs::test_util::get_dummy_account_id; +use crate::ics18_relayer::context::ICS18Context; +use crate::ics18_relayer::error::{Error, Kind}; +use crate::ics24_host::identifier::ClientId; + +/// Creates a `ClientMsg::UpdateClient` for a client with id `client_id` running on the `dest` +/// context, assuming that the latest header on the source context is `src_header`. +pub fn create_client_update_datagram( + dest: &Ctx, + client_id: &ClientId, + src_header: AnyHeader, +) -> Result +where + Ctx: ICS18Context, +{ + // Check if client for ibc0 on ibc1 has been updated to latest height: + // - query client state on destination chain + let dest_client_state = dest + .query_client_full_state(&client_id) + .ok_or_else(|| Kind::ClientStateNotFound(client_id.clone()))?; + + let dest_client_latest_height = dest_client_state.latest_height(); + + if src_header.height() == dest_client_latest_height { + return Err(Kind::ClientAlreadyUpToDate( + client_id.clone(), + src_header.height(), + dest_client_latest_height, + ) + .into()); + }; + + if dest_client_latest_height > src_header.height() { + return Err(Kind::ClientAtHeigherHeight( + client_id.clone(), + src_header.height(), + dest_client_latest_height, + ) + .into()); + }; + + // Client on destination chain can be updated. + Ok(ClientMsg::UpdateClient(MsgUpdateAnyClient { + client_id: client_id.clone(), + header: src_header, + signer: get_dummy_account_id(), + })) +} + +#[cfg(test)] +mod tests { + use crate::ics02_client::state::ClientState; + use crate::ics18_relayer::context::ICS18Context; + use crate::ics18_relayer::context_mock::MockICS18Context; + use crate::ics18_relayer::utils::create_client_update_datagram; + use crate::ics24_host::identifier::ClientId; + use crate::ics26_routing::msgs::ICS26Envelope; + use std::str::FromStr; + use tendermint::block::Height; + + #[test] + /// Serves to test both ICS 26 `dispatch` & `create_client_update_datagram` function. + /// Implements a "ping pong" of client update messages, so that two chains repeatedly + /// process a client update message and update their height in succession. + fn client_update_ping_pong() { + let chain_a_start_height = Height(11); + let chain_b_start_height = Height(20); + let client_on_b_for_a_height = Height(10); // Should be smaller than `chain_a_start_height` + let client_on_a_for_b_height = Height(20); // Should be smaller than `chain_b_start_height` + let max_history_size = 3; + let num_iterations = 4; + + let client_on_a_for_b = ClientId::from_str("ibconeclient").unwrap(); + let client_on_b_for_a = ClientId::from_str("ibczeroclient").unwrap(); + + // Create two mock contexts, one for each chain. + let mut ctx_a = MockICS18Context::new( + chain_a_start_height, + max_history_size, + &client_on_a_for_b, + client_on_a_for_b_height, + ); + let mut ctx_b = MockICS18Context::new( + chain_b_start_height, + max_history_size, + &client_on_b_for_a, + client_on_b_for_a_height, + ); + + for _i in 0..num_iterations { + // Update client on chain B to latest height of A. + // - create the client update message with the latest header from A + let a_latest_header = ctx_a.query_latest_header().unwrap(); + let client_msg_b_res = + create_client_update_datagram(&ctx_b, &client_on_b_for_a, a_latest_header); + assert_eq!( + client_msg_b_res.is_ok(), + true, + "create_client_update failed for context destination {:?}, error: {:?}", + ctx_b, + client_msg_b_res + ); + let client_msg_b = client_msg_b_res.unwrap(); + + // - send the message to B + let dispatch_res_b = ctx_b.send(ICS26Envelope::ICS2Msg(client_msg_b)); + + // Check if the update succeeded. + assert!(dispatch_res_b.is_ok()); + let client_height_b = ctx_b + .query_client_full_state(&client_on_b_for_a) + .unwrap() + .latest_height(); + assert_eq!(client_height_b, ctx_a.query_latest_height()); + + // Update client on chain B to latest height of B. + // - create the client update message with the latest header from B + let b_latest_header = ctx_b.query_latest_header().unwrap(); + let client_msg_a_res = + create_client_update_datagram(&ctx_a, &client_on_a_for_b, b_latest_header); + assert_eq!( + client_msg_a_res.is_ok(), + true, + "create_client_update failed for context destination {:?}, error: {:?}", + ctx_a, + client_msg_a_res + ); + let client_msg_a = client_msg_a_res.unwrap(); + + // - send the message to A + let dispatch_res_a = ctx_a.send(ICS26Envelope::ICS2Msg(client_msg_a)); + + // Check if the update succeeded. + assert!(dispatch_res_a.is_ok()); + let client_height_a = ctx_a + .query_client_full_state(&client_on_a_for_b) + .unwrap() + .latest_height(); + assert_eq!(client_height_a, ctx_b.query_latest_height()); + } + } +} diff --git a/modules/src/ics26_routing/context_mock.rs b/modules/src/ics26_routing/context_mock.rs index 9ba07c9566..5ab77348c2 100644 --- a/modules/src/ics26_routing/context_mock.rs +++ b/modules/src/ics26_routing/context_mock.rs @@ -1,4 +1,5 @@ use crate::context::SelfChainType; +use crate::context_mock::MockChainContext; use crate::ics02_client::client_def::{AnyClientState, AnyConsensusState}; use crate::ics02_client::client_type::ClientType; use crate::ics02_client::context::{ClientKeeper, ClientReader}; @@ -20,8 +21,20 @@ use tendermint::block::Height; /// of this mock type. #[derive(Clone, Debug, Default)] pub struct MockICS26Context { - client_context: MockClientContext, - connection_context: MockConnectionContext, + pub client_context: MockClientContext, + pub connection_context: MockConnectionContext, +} + +impl MockICS26Context { + pub fn client_context(&self) -> &MockClientContext { + &self.client_context + } + pub fn chain_context(&self) -> &MockChainContext { + &self.client_context.chain_context + } + pub fn set_chain_context(&mut self, chain_context: MockChainContext) { + self.client_context.chain_context = chain_context; + } } impl ICS26Context for MockICS26Context {} @@ -32,16 +45,15 @@ impl ConnectionReader for MockICS26Context { } fn fetch_client_state(&self, client_id: &ClientId) -> Option { - self.connection_context.fetch_client_state(client_id) + self.client_context().client_state(client_id) } fn chain_current_height(&self) -> Height { - self.connection_context.chain_current_height() + self.chain_context().latest } fn chain_consensus_states_history_size(&self) -> usize { - self.connection_context - .chain_consensus_states_history_size() + self.chain_context().max_size() } fn chain_type(&self) -> SelfChainType { @@ -57,8 +69,7 @@ impl ConnectionReader for MockICS26Context { client_id: &ClientId, height: Height, ) -> Option { - self.connection_context - .fetch_client_consensus_state(client_id, height) + self.client_context().consensus_state(client_id, height) } fn fetch_self_consensus_state(&self, height: Height) -> Option { @@ -77,15 +88,15 @@ impl ConnectionReader for MockICS26Context { impl ClientReader for MockICS26Context { fn client_type(&self, client_id: &ClientId) -> Option { - self.client_context.client_type(client_id) + self.client_context().client_type(client_id) } fn client_state(&self, client_id: &ClientId) -> Option { - self.client_context.client_state(client_id) + self.client_context().client_state(client_id) } fn consensus_state(&self, client_id: &ClientId, height: Height) -> Option { - self.client_context.consensus_state(client_id, height) + self.client_context().consensus_state(client_id, height) } } @@ -110,25 +121,31 @@ impl ConnectionKeeper for MockICS26Context { impl ClientKeeper for MockICS26Context { fn store_client_type( &mut self, - _client_id: ClientId, - _client_type: ClientType, + client_id: ClientId, + client_type: ClientType, ) -> Result<(), ICS2Error> { + self.client_context + .store_client_type(client_id, client_type)?; Ok(()) } fn store_client_state( &mut self, - _client_id: ClientId, - _client_state: AnyClientState, + client_id: ClientId, + client_state: AnyClientState, ) -> Result<(), ICS2Error> { + self.client_context + .store_client_state(client_id, client_state)?; Ok(()) } fn store_consensus_state( &mut self, - _client_id: ClientId, - _consensus_state: AnyConsensusState, + client_id: ClientId, + consensus_state: AnyConsensusState, ) -> Result<(), ICS2Error> { + self.client_context + .store_consensus_state(client_id, consensus_state)?; Ok(()) } } diff --git a/modules/src/ics26_routing/mod.rs b/modules/src/ics26_routing/mod.rs index 087b6a6d47..60e2dbd641 100644 --- a/modules/src/ics26_routing/mod.rs +++ b/modules/src/ics26_routing/mod.rs @@ -6,4 +6,4 @@ pub mod handler; pub mod msgs; #[cfg(test)] -mod context_mock; +pub mod context_mock; diff --git a/modules/src/lib.rs b/modules/src/lib.rs index 3b7c51747e..f8438cb4fe 100644 --- a/modules/src/lib.rs +++ b/modules/src/lib.rs @@ -17,6 +17,7 @@ //! - ICS 03: Connection //! - ICS 04: Channel //! - ICS 07: Tendermint Client +//! - ICS 18: Basic relayer functions //! - ICS 20: Fungible Token //! - ICS 23: Vector Commitment Scheme //! - ICS 24: Host Requirements @@ -29,6 +30,7 @@ pub mod ics02_client; pub mod ics03_connection; pub mod ics04_channel; pub mod ics07_tendermint; +pub mod ics18_relayer; pub mod ics20_fungible_token_transfer; pub mod ics23_commitment; pub mod ics24_host; diff --git a/modules/src/mock_client/header.rs b/modules/src/mock_client/header.rs index 0b6e1b1461..665d13ec35 100644 --- a/modules/src/mock_client/header.rs +++ b/modules/src/mock_client/header.rs @@ -9,7 +9,7 @@ use std::convert::TryFrom; use tendermint::block::Height; use tendermint_proto::DomainType; -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MockHeader(pub Height); impl DomainType for MockHeader {} diff --git a/modules/src/mock_client/state.rs b/modules/src/mock_client/state.rs index 2f9fbf6394..fe0f123565 100644 --- a/modules/src/mock_client/state.rs +++ b/modules/src/mock_client/state.rs @@ -34,7 +34,7 @@ pub struct MockClientRecord { /// A mock of a client state. For an example of a real structure that this mocks, you can see /// `ClientState` of ics07_tendermint/client_state.rs. /// TODO: `MockClientState` should evolve, at the very least needs a `is_frozen` boolean field. -#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct MockClientState(pub MockHeader); impl DomainType for MockClientState {} @@ -156,7 +156,7 @@ impl ConsensusState for MockConsensusState { } fn height(&self) -> Height { - todo!() + self.0.height() } fn root(&self) -> &CommitmentRoot {