-
Notifications
You must be signed in to change notification settings - Fork 359
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add IBCSystem & ICS02 state and action handlers
- Loading branch information
1 parent
11f9d19
commit 94bd12f
Showing
2 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
|