Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ICS02 mock extensions + more tests in ICS02 and ICS03 #222

Merged
merged 9 commits into from
Sep 10, 2020
2 changes: 1 addition & 1 deletion modules/src/ics02_client/client_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use anomaly::fail;
use serde_derive::{Deserialize, Serialize};

/// Type of the client, depending on the specific consensus algorithm.
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum ClientType {
Tendermint = 1,

Expand Down
100 changes: 61 additions & 39 deletions modules/src/ics02_client/context_mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,96 @@ 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 crate::mock_client::state::{MockClientRecord, MockClientState, MockConsensusState};
use std::collections::HashMap;
use tendermint::block::Height;

#[derive(Clone, Debug, PartialEq)]
/// A mock implementation of client context. This mocks (i.e., replaces) the functionality of
/// a KV-store holding information related to the various IBC clients running on a chain.
/// Each such client is represented here by a `MockClientRecord`.
/// Implements traits `ClientReader` and `ClientKeeper`, and is therefore useful for testing
/// the ICS02 handlers (create, update client) and other dependent ICS handlers (e.g., ICS03).
#[derive(Clone, Debug)]
pub struct MockClientContext {
pub client_id: ClientId,
pub client_state: Option<MockClientState>,
pub client_type: Option<ClientType>,
pub consensus_state: Option<MockConsensusState>,
/// The set of all clients, indexed by their id.
pub clients: HashMap<ClientId, MockClientRecord>,
}

impl Default for MockClientContext {
fn default() -> Self {
MockClientContext {
client_id: "defaultclientid".to_string().parse().unwrap(),
client_state: None,
client_type: None,
consensus_state: None,
clients: Default::default(),
}
}
}

impl MockClientContext {
pub fn new(client_id: &ClientId) -> Self {
MockClientContext {
client_id: client_id.clone(),
client_type: None,
client_state: None,
consensus_state: None,
}
/// Given a client id, a type, and a height, registers a new client of the given type in the
/// context. The client will have mock client state and a mock consensus state for the given
/// height.
pub fn with_client(&mut self, client_id: &ClientId, client_type: ClientType, h: Height) {
let mut client_record = MockClientRecord {
client_type,
client_state: MockClientState(MockHeader(h)),
consensus_states: HashMap::with_capacity(1),
};
client_record
.consensus_states
.insert(h, MockConsensusState(MockHeader(h)));
self.clients.insert(client_id.clone(), client_record);
}

pub fn with_client_type(&mut self, client_type: ClientType) {
self.client_type = Option::from(client_type);
/// Given a client type, an id, and a height, this function registers in the context a new
/// client record for that type and id. This client record will have no consensus states, and a
/// default client state for the input height.
pub fn with_client_type(&mut self, client_id: &ClientId, client_type: ClientType, h: Height) {
let client_record = MockClientRecord {
client_type,
client_state: MockClientState(MockHeader(h)),
consensus_states: Default::default(),
};
self.clients.insert(client_id.clone(), client_record);
}

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))));
/// Given a client id and a height, registers a new client in the context and also associates
/// to this client a mock client state and a mock consensus state for the input height. The type
/// of this client is implicitly assumed to be Mock.
pub fn with_client_consensus_state(&mut self, client_id: &ClientId, h: Height) {
let mut client_record = MockClientRecord {
client_type: ClientType::Mock,
client_state: MockClientState(MockHeader(h)),
consensus_states: HashMap::with_capacity(1),
};
client_record
.consensus_states
.insert(h, MockConsensusState(MockHeader(h)));
self.clients.insert(client_id.clone(), client_record);
}
}

impl ClientReader for MockClientContext {
fn client_type(&self, client_id: &ClientId) -> Option<ClientType> {
if client_id == &self.client_id {
self.client_type.clone()
} else {
None
match self.clients.get(client_id) {
Some(client_record) => client_record.client_type.into(),
None => None,
}
}

#[allow(trivial_casts)]
fn client_state(&self, client_id: &ClientId) -> Option<AnyClientState> {
if client_id == &self.client_id {
self.client_state.map(Into::into)
} else {
None
match self.clients.get(client_id) {
Some(client_record) => Option::from(AnyClientState::Mock(client_record.client_state)),
None => None,
}
}

#[allow(trivial_casts)]
fn consensus_state(&self, client_id: &ClientId, _height: Height) -> Option<AnyConsensusState> {
if client_id == &self.client_id {
self.consensus_state.map(Into::into)
} else {
None
fn consensus_state(&self, client_id: &ClientId, height: Height) -> Option<AnyConsensusState> {
match self.clients.get(client_id) {
Some(client_record) => match client_record.consensus_states.get(&height) {
Some(consensus_state) => Option::from(AnyConsensusState::Mock(*consensus_state)),
None => None,
},
// client_record.consensus_states.get(&height).map(Option::from),
None => None,
}
}
}
Expand Down
89 changes: 76 additions & 13 deletions modules/src/ics02_client/handler/create_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ mod tests {
#[test]
fn test_create_client_ok() {
let client_id: ClientId = "mockclient".parse().unwrap();
let reader = MockClientContext::new(&client_id);
let ctx = MockClientContext::default();

let msg = MsgCreateAnyClient {
client_id,
Expand All @@ -85,7 +85,7 @@ mod tests {
consensus_state: MockConsensusState(MockHeader(Height(42))).into(),
};

let output = process(&reader, msg.clone());
let output = process(&ctx, msg.clone());

match output {
Ok(HandlerOutput {
Expand Down Expand Up @@ -114,18 +114,21 @@ mod tests {

#[test]
fn test_create_client_existing_client_type() {
let height = Height(42);
let client_id: ClientId = "mockclient".parse().unwrap();
let mut reader = MockClientContext::new(&client_id);
reader.with_client_type(ClientType::Mock);
let mut ctx = MockClientContext::default();
ctx.with_client_type(&client_id, ClientType::Mock, height);

ctx.with_client_type(&client_id, ClientType::Tendermint, height);

let msg = MsgCreateAnyClient {
client_id,
client_type: ClientType::Mock,
client_state: MockClientState(MockHeader(Height(42))).into(),
consensus_state: MockConsensusState(MockHeader(Height(42))).into(),
client_state: MockClientState(MockHeader(height)).into(),
consensus_state: MockConsensusState(MockHeader(height)).into(),
};

let output = process(&reader, msg.clone());
let output = process(&ctx, msg.clone());

if let Err(err) = output {
assert_eq!(err.kind(), &Kind::ClientAlreadyExists(msg.client_id));
Expand All @@ -137,8 +140,9 @@ mod tests {
#[test]
fn test_create_client_existing_client_state() {
let client_id: ClientId = "mockclient".parse().unwrap();
let mut reader = MockClientContext::new(&client_id);
reader.with_client_state(&client_id, 30);
let mut ctx = MockClientContext::default();
let height = Height(30);
ctx.with_client_consensus_state(&client_id, height);

let msg = MsgCreateAnyClient {
client_id,
Expand All @@ -147,19 +151,79 @@ mod tests {
consensus_state: MockConsensusState(MockHeader(Height(42))).into(),
};

let output = process(&reader, msg.clone());
let output = process(&ctx, msg.clone());

if let Err(err) = output {
assert_eq!(err.kind(), &Kind::ClientAlreadyExists(msg.client_id));
} else {
panic!("expected an error");
}
}

#[test]
fn test_create_client_ok_multiple() {
let existing_client_id: ClientId = "existingmockclient".parse().unwrap();
let height = Height(80);
let mut ctx = MockClientContext::default();
ctx.with_client_consensus_state(&existing_client_id, height);

let create_client_msgs: Vec<MsgCreateAnyClient<AnyClient>> = vec![
MsgCreateAnyClient {
client_id: "newmockclient1".parse().unwrap(),
client_type: ClientType::Mock,
client_state: MockClientState(MockHeader(Height(42))).into(),
consensus_state: MockConsensusState(MockHeader(Height(42))).into(),
},
MsgCreateAnyClient {
client_id: "newmockclient2".parse().unwrap(),
client_type: ClientType::Mock,
client_state: MockClientState(MockHeader(Height(42))).into(),
consensus_state: MockConsensusState(MockHeader(Height(42))).into(),
},
MsgCreateAnyClient {
client_id: "newmockclient3".parse().unwrap(),
client_type: ClientType::Tendermint,
client_state: MockClientState(MockHeader(Height(50))).into(),
consensus_state: MockConsensusState(MockHeader(Height(50))).into(),
},
]
.into_iter()
.collect();

for msg in create_client_msgs {
let output = process(&ctx, msg.clone());

match output {
Ok(HandlerOutput {
result,
events,
log,
}) => {
assert_eq!(result.client_type, msg.client_type);
assert_eq!(
events,
vec![ClientEvent::ClientCreated(msg.client_id).into()]
);
assert_eq!(
log,
vec![
"success: no client state found".to_string(),
"success: no client type found".to_string()
]
);
}
Err(err) => {
panic!("unexpected error: {}", err);
}
}
}
}

#[test]
fn test_tm_create_client_ok() {
use tendermint::account::Id as AccountId;
let client_id: ClientId = "tendermint".parse().unwrap();
let reader = MockClientContext::new(&client_id);
let ctx = MockClientContext::default();

let ics_msg = MsgCreateClient {
client_id,
Expand All @@ -170,15 +234,14 @@ mod tests {
signer: AccountId::from_str("7C2BB42A8BE69791EC763E51F5A49BCD41E82237").unwrap(),
};

//let msg = ics_msg.pre_process();
let msg = MsgCreateAnyClient {
client_id: ics_msg.client_id().clone(),
client_type: ics_msg.client_type(),
client_state: ics_msg.client_state(),
consensus_state: ics_msg.consensus_state(),
};

let output = process(&reader, msg.clone());
let output = process(&ctx, msg.clone());

match output {
Ok(HandlerOutput {
Expand Down
Loading