Skip to content

Commit

Permalink
add IBCSystem & ICS02 state and action handlers
Browse files Browse the repository at this point in the history
  • Loading branch information
andrey-kuprianov committed Apr 7, 2021
1 parent 11f9d19 commit 94bd12f
Show file tree
Hide file tree
Showing 2 changed files with 308 additions and 0 deletions.
125 changes: 125 additions & 0 deletions modules/tests/runner/ibcsystem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use std::fmt::{Debug, Display};
use std::{any::Any, collections::HashMap};
use std::error::Error;

use ibc::{ics02_client::client_def::{AnyClientState, AnyConsensusState, AnyHeader}};
use ibc::ics02_client::client_type::ClientType;
use ibc::ics03_connection::connection::{Counterparty};
use ibc::ics03_connection::version::Version;
use ibc::ics23_commitment::commitment::{CommitmentPrefix, CommitmentProofBytes};
use ibc::ics24_host::identifier::{ChainId, ClientId, ConnectionId};
use ibc::mock::client_state::{MockClientState, MockConsensusState};
use ibc::mock::context::MockContext;
use ibc::mock::header::MockHeader;
use ibc::proofs::{ConsensusProof, Proofs};
use ibc::signer::Signer;
use ibc::Height;
use ibc::ics18_relayer::error::{Error as ICS18Error, Kind as ICS18ErrorKind};
use ibc::ics26_routing::error::{Error as ICS26Error, Kind as ICS26ErrorKind};

use modelator::{Recipe};

#[derive(Debug)]
pub struct IBCSystem {
// mapping from abstract chain identifier to its context
pub contexts: HashMap<String, MockContext>,
pub recipe: Recipe,
}


impl IBCSystem {
pub fn make<From: Sized + Any, To: Sized + Any>(&self, x: From) -> To {
self.recipe.make(x)
}

pub fn take<T: Sized + Any>(&self) -> T {
self.recipe.take()
}


/// Returns a reference to the `MockContext` of a given `chain_id`.
/// Panic if the context for `chain_id` is not found.
pub fn chain_context(&self, chain_id: String) -> &MockContext {
self.contexts
.get(&chain_id)
.expect("chain context should have been initialized")
}

/// Returns a mutable reference to the `MockContext` of a given `chain_id`.
/// Panic if the context for `chain_id` is not found.
pub fn chain_context_mut(&mut self, chain_id: &str) -> &mut MockContext {
self.contexts
.get_mut(chain_id)
.expect("chain context should have been initialized")
}

pub fn extract_handler_error<K>(ics18_result: &Result<(), ICS18Error>) -> Option<K>
where
K: Clone + Debug + Display + Into<anomaly::BoxError> + 'static,
{
let ics18_error = ics18_result.as_ref().expect_err("ICS18 error expected");
assert!(matches!(
ics18_error.kind(),
ICS18ErrorKind::TransactionFailed
));
let ics26_error = ics18_error
.source()
.expect("expected source in ICS18 error")
.downcast_ref::<ICS26Error>()
.expect("ICS18 source should be an ICS26 error");
assert!(matches!(
ics26_error.kind(),
ICS26ErrorKind::HandlerRaisedError,
));
ics26_error
.source()
.expect("expected source in ICS26 error")
.downcast_ref::<anomaly::Error<K>>()
.map(|e| e.kind().clone())
}

pub fn make_recipe() -> Recipe {
let mut r = Recipe::new();
r.add(|r, chain_id: String| ChainId::new(chain_id, r.take_as("revision")));
r.put_as("revision", |_| 0u64);
r.put(|_| Version::default());
r.put::<Vec<Version>>(|r| vec![r.take()]);
r.add(|_, client_id: u64| {
ClientId::new(ClientType::Mock, client_id)
.expect("it should be possible to create the client identifier")
});
r.add(|_, connection_id: u64| ConnectionId::new(connection_id));

r.add(|_, height: u64| Height::new(0, height));
r.add(|r, height: u64| MockHeader::new(r.make(height)));
r.add(|r, height: u64| AnyHeader::Mock(r.make(height)));
r.add(|r, height: u64| AnyClientState::Mock(MockClientState(r.make(height))));
r.add(|r, height: u64| AnyConsensusState::Mock(MockConsensusState(r.make(height))));
r.put(|_| Signer::new(""));
r.add(|r, (client_id, connection_id): (u64, Option<u64>)| {
Counterparty::new(
r.make(client_id),
connection_id.map(|id| r.make(id)),
r.take(),
)
});
r.put_as("delay_period", |_| 0u64);
r.put::<CommitmentPrefix>(|_| vec![0].into());
r.put::<CommitmentProofBytes>(|_| vec![0].into());
r.add(|r, height: u64| {
ConsensusProof::new(r.take(), r.make(height))
.expect("it should be possible to create the consensus proof")
});
r.add(|r, height: u64| {
Proofs::new(
r.take(),
None,
Some(r.make(height)),
None,
r.make(height),
)
.expect("it should be possible to create the proofs")
});
r
}
}
183 changes: 183 additions & 0 deletions modules/tests/runner/ics02.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
use std::fmt::Debug;
use std::collections::HashMap;

use ibc::{ics03_connection::context::ConnectionReader};
use ibc::ics02_client::context::ClientReader;
use ibc::ics02_client::msgs::create_client::MsgCreateAnyClient;
use ibc::ics02_client::msgs::update_client::MsgUpdateAnyClient;
use ibc::ics02_client::msgs::ClientMsg;
use ibc::ics26_routing::msgs::Ics26Envelope;
use ibc::mock::context::MockContext;
use ibc::mock::host::HostType;
use serde::{Deserialize, Serialize};
use ibc::ics02_client::error::Kind as ICS02ErrorKind;
use ibc::ics18_relayer::error::Error as ICS18Error;

use modelator::{StateHandler, ActionHandler};

use super::ibcsystem::IBCSystem;

#[derive(Debug, Clone, Deserialize, PartialEq)]
pub struct ClientsState {
pub chains: HashMap<String, Chain>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct Chain {
pub height: u64,
pub clients: HashMap<u64, Client>,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct Client {
pub heights: Vec<u64>,
}

impl StateHandler<ClientsState> for IBCSystem {
fn init(&mut self, state: ClientsState) {
// initiliaze all chains
self.contexts.clear();
for (chain_id, chain) in state.chains {
// never GC blocks
let max_history_size = usize::MAX;
let ctx = MockContext::new(
self.make(chain_id.clone()),
HostType::Mock,
max_history_size,
self.make(chain.height),
);
assert!(self.contexts.insert(chain_id, ctx).is_none());
}
}

fn read(&self) -> ClientsState {
let mut chains = HashMap::new();
for (chain_id, ctx) in &self.contexts {
let mut clients = HashMap::new();
let ctr = ClientReader::client_counter(ctx);
for client_id in 0..ctr {
if let Some(states) = ctx.consensus_states(&self.make(client_id)) {
let mut heights: Vec<u64> = states.keys().into_iter().map(|h|h.revision_height).collect();
heights.sort();
clients.insert(client_id, Client { heights });
}
}
chains.insert(chain_id.clone(), Chain {
height: ctx.host_current_height().revision_height,
clients
});
}
ClientsState {
chains
}
}
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub struct ChainClientAction {
pub chain_id: String,
#[serde(flatten)]
pub client_action: ClientAction,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(tag = "type")]
pub enum ClientAction {
ICS02CreateClient(ICS02CreateClient),
ICS02UpdateClient(ICS02UpdateClient),
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ICS02CreateClient {
pub client_state: u64,
pub consensus_state: u64,
}

#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ICS02UpdateClient {
pub client_id: u64,
pub header: u64,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
pub enum ClientActionOutcome {
OK,
ICS02ClientNotFound,
ICS02HeaderVerificationFailure,
}



#[test]
pub fn test() {
let x = ICS02CreateClient {
client_state: 10,
consensus_state: 20,

};
let m = ChainClientAction {
chain_id: "1".to_string(),
client_action: ClientAction::ICS02CreateClient(x),

};



let s = serde_json::to_string_pretty(&m).unwrap();
println!("{}", &s);

let m2: ChainClientAction = serde_json::from_str(&s).unwrap();
println!("{:?}", m2);


}

impl IBCSystem {
fn encode_client_action(&self, action: ClientAction) -> ClientMsg {
match action {
ClientAction::ICS02CreateClient(action) =>
ClientMsg::CreateClient(MsgCreateAnyClient {
client_state: self.make(action.client_state),
consensus_state: self.make(action.consensus_state),
signer: self.take(),
}),
ClientAction::ICS02UpdateClient(action) =>
ClientMsg::UpdateClient(MsgUpdateAnyClient {
client_id: self.make(action.client_id),
header: self.make(action.header),
signer: self.take(),
})
}
}

fn decode_client_outcome(&self, ics18_result: Result<(), ICS18Error>) -> ClientActionOutcome {
if ics18_result.is_ok() {
ClientActionOutcome::OK
}
else if let Some(kind) = Self::extract_handler_error::<ICS02ErrorKind>(&ics18_result) {
match kind {
ICS02ErrorKind::ClientNotFound(_) => ClientActionOutcome::ICS02ClientNotFound,
ICS02ErrorKind::HeaderVerificationFailure => ClientActionOutcome::ICS02HeaderVerificationFailure,
_ => panic!("unexpected ICS02ErrorKind"),
}
}
else {
panic!("unexpected error")
}

}
}

impl ActionHandler<ChainClientAction> for IBCSystem {
type Outcome = ClientActionOutcome;

fn handle(&mut self, action: ChainClientAction) -> Self::Outcome {
let msg = self.encode_client_action(action.client_action.clone());
let ctx = self.chain_context_mut(&action.chain_id);
let result = ctx.deliver(Ics26Envelope::Ics2Msg(msg));
self.decode_client_outcome(result)
}
}

0 comments on commit 94bd12f

Please sign in to comment.