Skip to content

Commit

Permalink
Added basic ICS18 method + context mock for testing. Should fix for #216
Browse files Browse the repository at this point in the history
 (local part).
  • Loading branch information
adizere committed Sep 29, 2020
1 parent 53d2147 commit ad86205
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 8 deletions.
2 changes: 1 addition & 1 deletion modules/src/context_mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use tendermint::block::Height;
pub struct MockChainContext {
/// Maximum size of the history
pub max_size: usize,
/// Heighest height of the headers in the history
/// Highest height of the headers in the history
pub latest: Height,
/// A list of `max_size` headers ordered by height
pub history: Vec<MockHeader>,
Expand Down
14 changes: 14 additions & 0 deletions modules/src/ics18_relayer/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use crate::ics02_client::client_def::AnyClientState;
use crate::ics24_host::identifier::ClientId;
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;

/// MockClientState->latest_height() of this client
fn query_client_full_state(&self, client_id: &ClientId) -> Option<AnyClientState>;
}
63 changes: 63 additions & 0 deletions modules/src/ics18_relayer/context_mock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::ics02_client::client_def::AnyClientState;
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::ics24_host::identifier::ClientId;
use crate::ics26_routing::context_mock::MockICS26Context;
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)
}
}

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<AnyClientState> {
self.chain_routing_context.fetch_client_state(client_id)
}
}
21 changes: 21 additions & 0 deletions modules/src/ics18_relayer/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use crate::ics24_host::identifier::ClientId;
use crate::Height;
use anomaly::{BoxError, Context};
use thiserror::Error;

pub type Error = anomaly::Error<Kind>;

#[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),
}

impl Kind {
pub fn context(self, source: impl Into<BoxError>) -> Context<Self> {
Context::new(self, Some(source.into()))
}
}
10 changes: 10 additions & 0 deletions modules/src/ics18_relayer/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
147 changes: 147 additions & 0 deletions modules/src/ics18_relayer/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
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;
use crate::mock_client::header::MockHeader;

pub fn create_client_update_datagram<Ctx>(
dest: &Ctx,
src: &Ctx,
client_id: &ClientId,
) -> Result<ClientMsg, Error>
where
Ctx: ICS18Context,
{
// Check if client for ibc0 on ibc1 has been updated to latest height:
// - query latest height on source chain
let src_latest_height = src.query_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 dest_client_latest_height < src_latest_height {
// Client on destination chain needs an update.
let msg = MsgUpdateAnyClient {
client_id: client_id.clone(),
header: MockHeader(src_latest_height).into(),
signer: get_dummy_account_id(),
};
Ok(ClientMsg::UpdateClient(msg))
} else {
Err(Kind::ClientAlreadyUpToDate(
client_id.clone(),
src_latest_height,
dest_client_latest_height,
)
.into())
}
}

#[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::handler::dispatch;
use crate::ics26_routing::msgs::ICS26Envelope;
use std::str::FromStr;
use tendermint::block::Height;

#[test]
/// This test was moved here from ICS 26.
/// Serves to test both ICS 26 `dispatch` & `create_client_update` function.
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(19); // 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.
let client_msg_b_res =
create_client_update_datagram(&ctx_b, &ctx_a, &client_on_b_for_a);
assert_eq!(
client_msg_b_res.is_ok(),
true,
"create_client_update failed for context destination {:?}, error: {:?}",
ctx_b,
client_msg_b_res
);
let mut routing_context_b = ctx_b.routing_context().clone();
let client_msg_b = client_msg_b_res.unwrap();
let dispatch_res_b =
dispatch(&mut routing_context_b, ICS26Envelope::ICS2Msg(client_msg_b));
assert!(dispatch_res_b.is_ok());
ctx_b.set_routing_context(routing_context_b); // Replace the original routing context with the updated one.

// Check if the update succeeded.
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());

// Advance height on chain B.
ctx_b.advance_chain_height();

// Update client on chain A.
let client_msg_a_res =
create_client_update_datagram(&ctx_a, &ctx_b, &client_on_a_for_b);
assert_eq!(
client_msg_a_res.is_ok(),
true,
"create_client_update failed for context destination {:?}, error: {:?}",
ctx_a,
client_msg_a_res
);
let mut routing_context_a = ctx_a.routing_context().clone();
let client_msg_a = client_msg_a_res.unwrap();
let dispatch_res_a =
dispatch(&mut routing_context_a, ICS26Envelope::ICS2Msg(client_msg_a));
assert!(dispatch_res_a.is_ok());
ctx_a.set_routing_context(routing_context_a);

// Check if the update in the other direction succeeded.
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());

// Advance height for chain A.
ctx_a.advance_chain_height();
}

// let msg = create_client_update(dest_chain_ctx);
//
// let res = update_client_to_latest(&mut ctx_ibc1, &ctx_a, &client_on_b_for_a);
}
}
8 changes: 5 additions & 3 deletions modules/src/ics26_routing/context_mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,11 @@ impl ClientKeeper for MockICS26Context {
client_id: ClientId,
client_type: ClientType,
) -> Result<(), ICS2Error> {
let mut client_store = self.client_context().clone();
client_store.store_client_type(client_id, client_type)?;
self.client_context = client_store;
self.client_context
.store_client_type(client_id, client_type)?;
// let mut client_store = self.client_context().clone();
// client_store.store_client_type(client_id, client_type)?;
// self.client_context = client_store;
Ok(())
}

Expand Down
16 changes: 13 additions & 3 deletions modules/src/ics26_routing/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,22 @@ mod tests {
) -> Result<HandlerOutput<()>, Error> {
// Check if client for ibc0 on ibc1 has been updated to latest height:
// - query latest height on source chain
// TODO maybe: src.get_latest_height()
let src_latest_height = src.chain_context().latest;
// - query client state on destination chain
// (TODO - simulate relayer by "querying" the client state and get the latest height from there
// then check if that is smaller than the source latest height)
// then check if that is smaller than the source latest height)
// TODO maybe: dest.query_client_state(client_id)
let dest_client = dest.client_context.clients.get(&client_id).unwrap();

// let dest_height = dest.query_client_full_state(client_id).latest_height()?;
// if dest_height > src_latest_height {
// weird
// }
// if dest_height < src_latest_height {
// return new Envelope
// }

// Does the client have a consensus state for the latest height?
if dest_client
.consensus_states
Expand All @@ -109,8 +119,8 @@ mod tests {

#[test]
fn test_update_two_chains() {
let client_on_ibc0_for_ibc1: ClientId = "ibconeclient".parse().unwrap();
let client_on_ibc1_for_ibc0: ClientId = "ibczeroclient".parse().unwrap();
let client_on_ibc0_for_ibc1 = ClientId::from_str("ibconeclient").unwrap();
let client_on_ibc1_for_ibc0 = ClientId::from_str("ibczeroclient").unwrap();

let ibc0_start_height = 11;
let ibc1_start_height = 20;
Expand Down
2 changes: 1 addition & 1 deletion modules/src/ics26_routing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ pub mod handler;
pub mod msgs;

#[cfg(test)]
mod context_mock;
pub mod context_mock;
2 changes: 2 additions & 0 deletions modules/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down

0 comments on commit ad86205

Please sign in to comment.