use crate::applications::transfer::context::{
cosmos_adr028_escrow_address, TokenTransferExecutionContext, TokenTransferValidationContext,
};
use crate::applications::transfer::error::TokenTransferError;
use crate::applications::transfer::PrefixedCoin;
use crate::clients::ics07_tendermint::TENDERMINT_CLIENT_TYPE;
use crate::core::ics24_host::path::{
AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, ClientStatePath,
ClientTypePath, CommitmentPath, ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath,
SeqSendPath,
};
use crate::prelude::*;
use alloc::collections::btree_map::BTreeMap;
use alloc::sync::Arc;
use core::cmp::min;
use core::fmt::Debug;
use core::ops::{Add, Sub};
use core::time::Duration;
use parking_lot::Mutex;
use subtle_encoding::bech32;
use ibc_proto::google::protobuf::Any;
use tracing::debug;
use crate::clients::ics07_tendermint::client_state::ClientState as TmClientState;
use crate::core::context::ContextError;
use crate::core::context::Router;
use crate::core::ics02_client::client_state::ClientState;
use crate::core::ics02_client::client_type::ClientType;
use crate::core::ics02_client::consensus_state::ConsensusState;
use crate::core::ics02_client::error::ClientError;
use crate::core::ics02_client::header::Header;
use crate::core::ics03_connection::connection::ConnectionEnd;
use crate::core::ics03_connection::error::ConnectionError;
use crate::core::ics04_channel::channel::ChannelEnd;
use crate::core::ics04_channel::commitment::{AcknowledgementCommitment, PacketCommitment};
use crate::core::ics04_channel::error::{ChannelError, PacketError};
use crate::core::ics04_channel::packet::{Receipt, Sequence};
use crate::core::ics23_commitment::commitment::CommitmentPrefix;
use crate::core::ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId};
use crate::core::ics26_routing::context::{Module, ModuleId};
use crate::core::ics26_routing::msgs::MsgEnvelope;
use crate::core::{dispatch, ExecutionContext, ValidationContext};
use crate::events::IbcEvent;
use crate::mock::client_state::{
client_type as mock_client_type, MockClientRecord, MockClientState,
};
use crate::mock::consensus_state::MockConsensusState;
use crate::mock::header::MockHeader;
use crate::mock::host::{HostBlock, HostType};
use crate::mock::ics18_relayer::context::RelayerContext;
use crate::mock::ics18_relayer::error::RelayerError;
use crate::signer::Signer;
use crate::timestamp::Timestamp;
use crate::Height;
use super::client_state::MOCK_CLIENT_TYPE;
pub const DEFAULT_BLOCK_TIME_SECS: u64 = 3;
#[derive(Debug)]
pub struct MockContext {
host_chain_type: HostType,
host_chain_id: ChainId,
max_history_size: usize,
history: Vec<HostBlock>,
block_time: Duration,
pub ibc_store: Arc<Mutex<MockIbcStore>>,
router: BTreeMap<ModuleId, Arc<dyn Module>>,
pub events: Vec<IbcEvent>,
pub logs: Vec<String>,
}
impl Default for MockContext {
fn default() -> Self {
Self::new(
ChainId::new("mockgaia".to_string(), 0),
HostType::Mock,
5,
Height::new(0, 5).unwrap(),
)
}
}
impl Clone for MockContext {
fn clone(&self) -> Self {
let ibc_store = {
let ibc_store = self.ibc_store.lock().clone();
Arc::new(Mutex::new(ibc_store))
};
Self {
host_chain_type: self.host_chain_type,
host_chain_id: self.host_chain_id.clone(),
max_history_size: self.max_history_size,
history: self.history.clone(),
block_time: self.block_time,
ibc_store,
router: self.router.clone(),
events: self.events.clone(),
logs: self.logs.clone(),
}
}
}
impl MockContext {
pub fn new(
host_id: ChainId,
host_type: HostType,
max_history_size: usize,
latest_height: Height,
) -> Self {
assert_ne!(
max_history_size, 0,
"The chain must have a non-zero max_history_size"
);
assert_ne!(
latest_height.revision_height(),
0,
"The chain must have a non-zero revision_height"
);
let n = min(max_history_size as u64, latest_height.revision_height());
assert_eq!(
host_id.version(),
latest_height.revision_number(),
"The version in the chain identifier must match the version in the latest height"
);
let block_time = Duration::from_secs(DEFAULT_BLOCK_TIME_SECS);
let next_block_timestamp = Timestamp::now().add(block_time).unwrap();
MockContext {
host_chain_type: host_type,
host_chain_id: host_id.clone(),
max_history_size,
history: (0..n)
.rev()
.map(|i| {
HostBlock::generate_block(
host_id.clone(),
host_type,
latest_height.sub(i).unwrap().revision_height(),
next_block_timestamp
.sub(Duration::from_secs(DEFAULT_BLOCK_TIME_SECS * (i + 1)))
.unwrap(),
)
})
.collect(),
block_time,
ibc_store: Arc::new(Mutex::new(MockIbcStore::default())),
router: BTreeMap::new(),
events: Vec::new(),
logs: Vec::new(),
}
}
pub fn with_client(self, client_id: &ClientId, height: Height) -> Self {
self.with_client_parametrized(client_id, height, Some(mock_client_type()), Some(height))
}
pub fn with_client_parametrized(
self,
client_id: &ClientId,
client_state_height: Height,
client_type: Option<ClientType>,
consensus_state_height: Option<Height>,
) -> Self {
let client_chain_id = self.host_chain_id.clone();
self.with_client_parametrized_with_chain_id(
client_chain_id,
client_id,
client_state_height,
client_type,
consensus_state_height,
)
}
pub fn with_client_parametrized_with_chain_id(
self,
client_chain_id: ChainId,
client_id: &ClientId,
client_state_height: Height,
client_type: Option<ClientType>,
consensus_state_height: Option<Height>,
) -> Self {
let cs_height = consensus_state_height.unwrap_or(client_state_height);
let client_type = client_type.unwrap_or_else(mock_client_type);
let (client_state, consensus_state) = if client_type.as_str() == MOCK_CLIENT_TYPE {
(
Some(MockClientState::new(MockHeader::new(client_state_height)).into_box()),
MockConsensusState::new(MockHeader::new(cs_height)).into_box(),
)
} else if client_type.as_str() == TENDERMINT_CLIENT_TYPE {
let light_block = HostBlock::generate_tm_block(
client_chain_id,
cs_height.revision_height(),
Timestamp::now(),
);
let client_state =
TmClientState::new_dummy_from_header(light_block.header().clone()).into_box();
(Some(client_state), light_block.into())
} else {
panic!("unknown client type")
};
let consensus_states = vec![(cs_height, consensus_state)].into_iter().collect();
debug!("consensus states: {:?}", consensus_states);
let client_record = MockClientRecord {
client_type,
client_state,
consensus_states,
};
self.ibc_store
.lock()
.clients
.insert(client_id.clone(), client_record);
self
}
pub fn with_client_parametrized_history(
self,
client_id: &ClientId,
client_state_height: Height,
client_type: Option<ClientType>,
consensus_state_height: Option<Height>,
) -> Self {
let client_chain_id = self.host_chain_id.clone();
self.with_client_parametrized_history_with_chain_id(
client_chain_id,
client_id,
client_state_height,
client_type,
consensus_state_height,
)
}
pub(crate) fn with_client_parametrized_history_with_chain_id(
self,
client_chain_id: ChainId,
client_id: &ClientId,
client_state_height: Height,
client_type: Option<ClientType>,
consensus_state_height: Option<Height>,
) -> Self {
let cs_height = consensus_state_height.unwrap_or(client_state_height);
let prev_cs_height = cs_height.clone().sub(1).unwrap_or(client_state_height);
let client_type = client_type.unwrap_or_else(mock_client_type);
let now = Timestamp::now();
let (client_state, consensus_state) = if client_type.as_str() == MOCK_CLIENT_TYPE {
(
Some(MockClientState::new(MockHeader::new(client_state_height)).into_box()),
MockConsensusState::new(MockHeader::new(cs_height)).into_box(),
)
} else if client_type.as_str() == TENDERMINT_CLIENT_TYPE {
let light_block =
HostBlock::generate_tm_block(client_chain_id, cs_height.revision_height(), now);
let client_state =
TmClientState::new_dummy_from_header(light_block.header().clone()).into_box();
(Some(client_state), light_block.into())
} else {
panic!("Unknown client type")
};
let prev_consensus_state = if client_type.as_str() == MOCK_CLIENT_TYPE {
MockConsensusState::new(MockHeader::new(prev_cs_height)).into_box()
} else if client_type.as_str() == TENDERMINT_CLIENT_TYPE {
let light_block = HostBlock::generate_tm_block(
self.host_chain_id.clone(),
prev_cs_height.revision_height(),
now.sub(self.block_time).unwrap(),
);
light_block.into()
} else {
panic!("Unknown client type")
};
let consensus_states = vec![
(prev_cs_height, prev_consensus_state),
(cs_height, consensus_state),
]
.into_iter()
.collect();
debug!("consensus states: {:?}", consensus_states);
let client_record = MockClientRecord {
client_type,
client_state,
consensus_states,
};
self.ibc_store
.lock()
.clients
.insert(client_id.clone(), client_record);
self
}
pub fn with_connection(
self,
connection_id: ConnectionId,
connection_end: ConnectionEnd,
) -> Self {
self.ibc_store
.lock()
.connections
.insert(connection_id, connection_end);
self
}
pub fn with_channel(
self,
port_id: PortId,
chan_id: ChannelId,
channel_end: ChannelEnd,
) -> Self {
let mut channels = self.ibc_store.lock().channels.clone();
channels
.entry(port_id)
.or_default()
.insert(chan_id, channel_end);
self.ibc_store.lock().channels = channels;
self
}
pub fn with_send_sequence(
self,
port_id: PortId,
chan_id: ChannelId,
seq_number: Sequence,
) -> Self {
let mut next_sequence_send = self.ibc_store.lock().next_sequence_send.clone();
next_sequence_send
.entry(port_id)
.or_default()
.insert(chan_id, seq_number);
self.ibc_store.lock().next_sequence_send = next_sequence_send;
self
}
pub fn with_recv_sequence(
self,
port_id: PortId,
chan_id: ChannelId,
seq_number: Sequence,
) -> Self {
let mut next_sequence_recv = self.ibc_store.lock().next_sequence_recv.clone();
next_sequence_recv
.entry(port_id)
.or_default()
.insert(chan_id, seq_number);
self.ibc_store.lock().next_sequence_recv = next_sequence_recv;
self
}
pub fn with_ack_sequence(
self,
port_id: PortId,
chan_id: ChannelId,
seq_number: Sequence,
) -> Self {
let mut next_sequence_ack = self.ibc_store.lock().next_sequence_send.clone();
next_sequence_ack
.entry(port_id)
.or_default()
.insert(chan_id, seq_number);
self.ibc_store.lock().next_sequence_ack = next_sequence_ack;
self
}
pub fn with_height(self, target_height: Height) -> Self {
let latest_height = self.latest_height();
if target_height.revision_number() > latest_height.revision_number() {
unimplemented!()
} else if target_height.revision_number() < latest_height.revision_number() {
panic!("Cannot rewind history of the chain to a smaller revision number!")
} else if target_height.revision_height() < latest_height.revision_height() {
panic!("Cannot rewind history of the chain to a smaller revision height!")
} else if target_height.revision_height() > latest_height.revision_height() {
let mut ctx = MockContext { ..self };
while ctx.latest_height().revision_height() < target_height.revision_height() {
ctx.advance_host_chain_height()
}
ctx
} else {
self
}
}
pub fn with_packet_commitment(
self,
port_id: PortId,
chan_id: ChannelId,
seq: Sequence,
data: PacketCommitment,
) -> Self {
let mut packet_commitment = self.ibc_store.lock().packet_commitment.clone();
packet_commitment
.entry(port_id)
.or_default()
.entry(chan_id)
.or_default()
.insert(seq, data);
self.ibc_store.lock().packet_commitment = packet_commitment;
self
}
pub fn add_route(
&mut self,
module_id: ModuleId,
module: impl Module + 'static,
) -> Result<(), String> {
match self.router.insert(module_id, Arc::new(module)) {
None => Ok(()),
Some(_) => Err("Duplicate module_id".to_owned()),
}
}
pub fn host_block(&self, target_height: &Height) -> Option<&HostBlock> {
let target = target_height.revision_height() as usize;
let latest = self.latest_height().revision_height() as usize;
if (target > latest) || (target <= latest - self.history.len()) {
None } else {
Some(&self.history[self.history.len() + target - latest - 1])
}
}
pub fn advance_host_chain_height(&mut self) {
let latest_block = self.history.last().expect("history cannot be empty");
let new_block = HostBlock::generate_block(
self.host_chain_id.clone(),
self.host_chain_type,
latest_block.height().increment().revision_height(),
latest_block.timestamp().add(self.block_time).unwrap(),
);
if self.history.len() >= self.max_history_size {
self.history.rotate_left(1);
self.history[self.max_history_size - 1] = new_block;
} else {
self.history.push(new_block);
}
}
pub fn deliver(&mut self, msg: MsgEnvelope) -> Result<(), RelayerError> {
dispatch(self, msg).map_err(RelayerError::TransactionFailed)?;
self.advance_host_chain_height();
Ok(())
}
pub fn validate(&self) -> Result<(), String> {
if self.history.len() > self.max_history_size {
return Err("too many entries".to_string());
}
if !self.history.is_empty() {
let lh = &self.history[self.history.len() - 1];
if lh.height() != self.latest_height() {
return Err("latest height is not updated".to_string());
}
}
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());
}
}
Ok(())
}
pub fn add_port(&mut self, port_id: PortId) {
let module_id = ModuleId::new(format!("module{port_id}").into()).unwrap();
self.ibc_store
.lock()
.port_to_module
.insert(port_id, module_id);
}
pub fn scope_port_to_module(&mut self, port_id: PortId, module_id: ModuleId) {
self.ibc_store
.lock()
.port_to_module
.insert(port_id, module_id);
}
pub fn latest_client_states(&self, client_id: &ClientId) -> Box<dyn ClientState> {
self.ibc_store.lock().clients[client_id]
.client_state
.as_ref()
.unwrap()
.clone()
}
pub fn latest_consensus_states(
&self,
client_id: &ClientId,
height: &Height,
) -> Box<dyn ConsensusState> {
dyn_clone::clone_box(
self.ibc_store.lock().clients[client_id]
.consensus_states
.get(height)
.unwrap()
.as_ref(),
)
}
#[inline]
fn latest_height(&self) -> Height {
self.history
.last()
.expect("history cannot be empty")
.height()
}
pub fn ibc_store_share(&self) -> Arc<Mutex<MockIbcStore>> {
self.ibc_store.clone()
}
}
type PortChannelIdMap<V> = BTreeMap<PortId, BTreeMap<ChannelId, V>>;
#[derive(Clone, Debug, Default)]
pub struct MockIbcStore {
pub clients: BTreeMap<ClientId, MockClientRecord>,
pub client_processed_times: BTreeMap<(ClientId, Height), Timestamp>,
pub client_processed_heights: BTreeMap<(ClientId, Height), Height>,
pub client_ids_counter: u64,
pub client_connections: BTreeMap<ClientId, ConnectionId>,
pub connections: BTreeMap<ConnectionId, ConnectionEnd>,
pub connection_ids_counter: u64,
pub connection_channels: BTreeMap<ConnectionId, Vec<(PortId, ChannelId)>>,
pub channel_ids_counter: u64,
pub channels: PortChannelIdMap<ChannelEnd>,
pub next_sequence_send: PortChannelIdMap<Sequence>,
pub next_sequence_recv: PortChannelIdMap<Sequence>,
pub next_sequence_ack: PortChannelIdMap<Sequence>,
pub packet_acknowledgement: PortChannelIdMap<BTreeMap<Sequence, AcknowledgementCommitment>>,
pub port_to_module: BTreeMap<PortId, ModuleId>,
pub packet_commitment: PortChannelIdMap<BTreeMap<Sequence, PacketCommitment>>,
pub packet_receipt: PortChannelIdMap<BTreeMap<Sequence, Receipt>>,
}
impl RelayerContext for MockContext {
fn query_latest_height(&self) -> Result<Height, ContextError> {
self.host_height()
}
fn query_client_full_state(&self, client_id: &ClientId) -> Option<Box<dyn ClientState>> {
ValidationContext::client_state(self, client_id).ok()
}
fn query_latest_header(&self) -> Option<Box<dyn Header>> {
let block_ref = self.host_block(&self.host_height().unwrap());
block_ref.cloned().map(Header::into_box)
}
fn signer(&self) -> Signer {
"0CDA3F47EF3C4906693B170EF650EB968C5F4B2C".parse().unwrap()
}
}
impl Router for MockContext {
fn get_route(&self, module_id: &ModuleId) -> Option<&dyn Module> {
self.router.get(module_id).map(Arc::as_ref)
}
fn get_route_mut(&mut self, module_id: &ModuleId) -> Option<&mut dyn Module> {
match self.router.get_mut(module_id) {
Some(arc_mod) => match Arc::get_mut(arc_mod) {
Some(m) => Some(m),
None => None,
},
None => None,
}
}
fn has_route(&self, module_id: &ModuleId) -> bool {
self.router.get(module_id).is_some()
}
fn lookup_module_by_port(&self, port_id: &PortId) -> Option<ModuleId> {
self.ibc_store.lock().port_to_module.get(port_id).cloned()
}
}
impl ValidationContext for MockContext {
fn client_state(&self, client_id: &ClientId) -> Result<Box<dyn ClientState>, ContextError> {
match self.ibc_store.lock().clients.get(client_id) {
Some(client_record) => {
client_record
.client_state
.clone()
.ok_or_else(|| ClientError::ClientStateNotFound {
client_id: client_id.clone(),
})
}
None => Err(ClientError::ClientStateNotFound {
client_id: client_id.clone(),
}),
}
.map_err(ContextError::ClientError)
}
fn decode_client_state(&self, client_state: Any) -> Result<Box<dyn ClientState>, ContextError> {
if let Ok(client_state) = TmClientState::try_from(client_state.clone()) {
Ok(client_state.into_box())
} else if let Ok(client_state) = MockClientState::try_from(client_state.clone()) {
Ok(client_state.into_box())
} else {
Err(ClientError::UnknownClientStateType {
client_state_type: client_state.type_url,
})
}
.map_err(ContextError::ClientError)
}
fn consensus_state(
&self,
client_cons_state_path: &ClientConsensusStatePath,
) -> Result<Box<dyn ConsensusState>, ContextError> {
let client_id = &client_cons_state_path.client_id;
let height = Height::new(client_cons_state_path.epoch, client_cons_state_path.height)?;
match self.ibc_store.lock().clients.get(client_id) {
Some(client_record) => match client_record.consensus_states.get(&height) {
Some(consensus_state) => Ok(consensus_state.clone()),
None => Err(ClientError::ConsensusStateNotFound {
client_id: client_id.clone(),
height,
}),
},
None => Err(ClientError::ConsensusStateNotFound {
client_id: client_id.clone(),
height,
}),
}
.map_err(ContextError::ClientError)
}
fn next_consensus_state(
&self,
client_id: &ClientId,
height: &Height,
) -> Result<Option<Box<dyn ConsensusState>>, ContextError> {
let ibc_store = self.ibc_store.lock();
let client_record =
ibc_store
.clients
.get(client_id)
.ok_or_else(|| ClientError::ClientStateNotFound {
client_id: client_id.clone(),
})?;
let mut heights: Vec<Height> = client_record.consensus_states.keys().cloned().collect();
heights.sort();
for h in heights {
if h > *height {
return Ok(Some(
client_record.consensus_states.get(&h).unwrap().clone(),
));
}
}
Ok(None)
}
fn prev_consensus_state(
&self,
client_id: &ClientId,
height: &Height,
) -> Result<Option<Box<dyn ConsensusState>>, ContextError> {
let ibc_store = self.ibc_store.lock();
let client_record =
ibc_store
.clients
.get(client_id)
.ok_or_else(|| ClientError::ClientStateNotFound {
client_id: client_id.clone(),
})?;
let mut heights: Vec<Height> = client_record.consensus_states.keys().cloned().collect();
heights.sort_by(|a, b| b.cmp(a));
for h in heights {
if h < *height {
return Ok(Some(
client_record.consensus_states.get(&h).unwrap().clone(),
));
}
}
Ok(None)
}
fn host_height(&self) -> Result<Height, ContextError> {
Ok(self.latest_height())
}
fn host_timestamp(&self) -> Result<Timestamp, ContextError> {
Ok(self
.history
.last()
.expect("history cannot be empty")
.timestamp()
.add(self.block_time)
.unwrap())
}
fn host_consensus_state(
&self,
height: &Height,
) -> Result<Box<dyn ConsensusState>, ContextError> {
match self.host_block(height) {
Some(block_ref) => Ok(block_ref.clone().into()),
None => Err(ClientError::MissingLocalConsensusState { height: *height }),
}
.map_err(ConnectionError::Client)
.map_err(ContextError::ConnectionError)
}
fn client_counter(&self) -> Result<u64, ContextError> {
Ok(self.ibc_store.lock().client_ids_counter)
}
fn connection_end(&self, cid: &ConnectionId) -> Result<ConnectionEnd, ContextError> {
match self.ibc_store.lock().connections.get(cid) {
Some(connection_end) => Ok(connection_end.clone()),
None => Err(ConnectionError::ConnectionNotFound {
connection_id: cid.clone(),
}),
}
.map_err(ContextError::ConnectionError)
}
fn validate_self_client(
&self,
client_state_of_host_on_counterparty: Any,
) -> Result<(), ContextError> {
let mock_client_state = MockClientState::try_from(client_state_of_host_on_counterparty)
.map_err(|_| ConnectionError::InvalidClientState {
reason: "client must be a mock client".to_string(),
})
.map_err(ContextError::ConnectionError)?;
mock_client_state.confirm_not_frozen()?;
let self_chain_id = &self.host_chain_id;
let self_revision_number = self_chain_id.version();
if self_revision_number != mock_client_state.latest_height().revision_number() {
return Err(ConnectionError::InvalidClientState {
reason: format!(
"client is not in the same revision as the chain. expected: {}, got: {}",
self_revision_number,
mock_client_state.latest_height().revision_number()
),
})
.map_err(ContextError::ConnectionError);
}
let host_current_height = self.latest_height().increment();
if mock_client_state.latest_height() >= host_current_height {
return Err(ConnectionError::InvalidClientState {
reason: format!(
"client has latest height {} greater than or equal to chain height {}",
mock_client_state.latest_height(),
host_current_height
),
})
.map_err(ContextError::ConnectionError);
}
Ok(())
}
fn commitment_prefix(&self) -> CommitmentPrefix {
CommitmentPrefix::try_from(b"mock".to_vec()).unwrap()
}
fn connection_counter(&self) -> Result<u64, ContextError> {
Ok(self.ibc_store.lock().connection_ids_counter)
}
fn channel_end(&self, chan_end_path: &ChannelEndPath) -> Result<ChannelEnd, ContextError> {
let port_id = &chan_end_path.0;
let channel_id = &chan_end_path.1;
match self
.ibc_store
.lock()
.channels
.get(port_id)
.and_then(|map| map.get(channel_id))
{
Some(channel_end) => Ok(channel_end.clone()),
None => Err(ChannelError::ChannelNotFound {
port_id: port_id.clone(),
channel_id: channel_id.clone(),
}),
}
.map_err(ContextError::ChannelError)
}
fn get_next_sequence_send(
&self,
seq_send_path: &SeqSendPath,
) -> Result<Sequence, ContextError> {
let port_id = &seq_send_path.0;
let channel_id = &seq_send_path.1;
match self
.ibc_store
.lock()
.next_sequence_send
.get(port_id)
.and_then(|map| map.get(channel_id))
{
Some(sequence) => Ok(*sequence),
None => Err(PacketError::MissingNextSendSeq {
port_id: port_id.clone(),
channel_id: channel_id.clone(),
}),
}
.map_err(ContextError::PacketError)
}
fn get_next_sequence_recv(
&self,
seq_recv_path: &SeqRecvPath,
) -> Result<Sequence, ContextError> {
let port_id = &seq_recv_path.0;
let channel_id = &seq_recv_path.1;
match self
.ibc_store
.lock()
.next_sequence_recv
.get(port_id)
.and_then(|map| map.get(channel_id))
{
Some(sequence) => Ok(*sequence),
None => Err(PacketError::MissingNextRecvSeq {
port_id: port_id.clone(),
channel_id: channel_id.clone(),
}),
}
.map_err(ContextError::PacketError)
}
fn get_next_sequence_ack(&self, seq_ack_path: &SeqAckPath) -> Result<Sequence, ContextError> {
let port_id = &seq_ack_path.0;
let channel_id = &seq_ack_path.1;
match self
.ibc_store
.lock()
.next_sequence_ack
.get(port_id)
.and_then(|map| map.get(channel_id))
{
Some(sequence) => Ok(*sequence),
None => Err(PacketError::MissingNextAckSeq {
port_id: port_id.clone(),
channel_id: channel_id.clone(),
}),
}
.map_err(ContextError::PacketError)
}
fn get_packet_commitment(
&self,
commitment_path: &CommitmentPath,
) -> Result<PacketCommitment, ContextError> {
let port_id = &commitment_path.port_id;
let channel_id = &commitment_path.channel_id;
let seq = &commitment_path.sequence;
match self
.ibc_store
.lock()
.packet_commitment
.get(port_id)
.and_then(|map| map.get(channel_id))
.and_then(|map| map.get(seq))
{
Some(commitment) => Ok(commitment.clone()),
None => Err(PacketError::PacketCommitmentNotFound { sequence: *seq }),
}
.map_err(ContextError::PacketError)
}
fn get_packet_receipt(&self, receipt_path: &ReceiptPath) -> Result<Receipt, ContextError> {
let port_id = &receipt_path.port_id;
let channel_id = &receipt_path.channel_id;
let seq = &receipt_path.sequence;
match self
.ibc_store
.lock()
.packet_receipt
.get(port_id)
.and_then(|map| map.get(channel_id))
.and_then(|map| map.get(seq))
{
Some(receipt) => Ok(receipt.clone()),
None => Err(PacketError::PacketReceiptNotFound { sequence: *seq }),
}
.map_err(ContextError::PacketError)
}
fn get_packet_acknowledgement(
&self,
ack_path: &AckPath,
) -> Result<AcknowledgementCommitment, ContextError> {
let port_id = &ack_path.port_id;
let channel_id = &ack_path.channel_id;
let seq = &ack_path.sequence;
match self
.ibc_store
.lock()
.packet_acknowledgement
.get(port_id)
.and_then(|map| map.get(channel_id))
.and_then(|map| map.get(seq))
{
Some(ack) => Ok(ack.clone()),
None => Err(PacketError::PacketAcknowledgementNotFound { sequence: *seq }),
}
.map_err(ContextError::PacketError)
}
fn client_update_time(
&self,
client_id: &ClientId,
height: &Height,
) -> Result<Timestamp, ContextError> {
match self
.ibc_store
.lock()
.client_processed_times
.get(&(client_id.clone(), *height))
{
Some(time) => Ok(*time),
None => Err(ChannelError::ProcessedTimeNotFound {
client_id: client_id.clone(),
height: *height,
}),
}
.map_err(ContextError::ChannelError)
}
fn client_update_height(
&self,
client_id: &ClientId,
height: &Height,
) -> Result<Height, ContextError> {
match self
.ibc_store
.lock()
.client_processed_heights
.get(&(client_id.clone(), *height))
{
Some(height) => Ok(*height),
None => Err(ChannelError::ProcessedHeightNotFound {
client_id: client_id.clone(),
height: *height,
}),
}
.map_err(ContextError::ChannelError)
}
fn channel_counter(&self) -> Result<u64, ContextError> {
Ok(self.ibc_store.lock().channel_ids_counter)
}
fn max_expected_time_per_block(&self) -> Duration {
self.block_time
}
}
impl ExecutionContext for MockContext {
fn store_client_type(
&mut self,
client_type_path: ClientTypePath,
client_type: ClientType,
) -> Result<(), ContextError> {
let mut ibc_store = self.ibc_store.lock();
let client_id = client_type_path.0;
let client_record = ibc_store
.clients
.entry(client_id)
.or_insert(MockClientRecord {
client_type: client_type.clone(),
consensus_states: Default::default(),
client_state: Default::default(),
});
client_record.client_type = client_type;
Ok(())
}
fn store_client_state(
&mut self,
client_state_path: ClientStatePath,
client_state: Box<dyn ClientState>,
) -> Result<(), ContextError> {
let mut ibc_store = self.ibc_store.lock();
let client_id = client_state_path.0;
let client_record = ibc_store
.clients
.entry(client_id)
.or_insert(MockClientRecord {
client_type: client_state.client_type(),
consensus_states: Default::default(),
client_state: Default::default(),
});
client_record.client_state = Some(client_state);
Ok(())
}
fn store_consensus_state(
&mut self,
consensus_state_path: ClientConsensusStatePath,
consensus_state: Box<dyn ConsensusState>,
) -> Result<(), ContextError> {
let mut ibc_store = self.ibc_store.lock();
let client_record = ibc_store
.clients
.entry(consensus_state_path.client_id)
.or_insert(MockClientRecord {
client_type: mock_client_type(),
consensus_states: Default::default(),
client_state: Default::default(),
});
let height = Height::new(consensus_state_path.epoch, consensus_state_path.height).unwrap();
client_record
.consensus_states
.insert(height, consensus_state);
Ok(())
}
fn increase_client_counter(&mut self) {
self.ibc_store.lock().client_ids_counter += 1
}
fn store_update_time(
&mut self,
client_id: ClientId,
height: Height,
timestamp: Timestamp,
) -> Result<(), ContextError> {
let _ = self
.ibc_store
.lock()
.client_processed_times
.insert((client_id, height), timestamp);
Ok(())
}
fn store_update_height(
&mut self,
client_id: ClientId,
height: Height,
host_height: Height,
) -> Result<(), ContextError> {
let _ = self
.ibc_store
.lock()
.client_processed_heights
.insert((client_id, height), host_height);
Ok(())
}
fn store_connection(
&mut self,
connection_path: &ConnectionPath,
connection_end: ConnectionEnd,
) -> Result<(), ContextError> {
let connection_id = connection_path.0.clone();
self.ibc_store
.lock()
.connections
.insert(connection_id, connection_end);
Ok(())
}
fn store_connection_to_client(
&mut self,
client_connection_path: &ClientConnectionPath,
conn_id: ConnectionId,
) -> Result<(), ContextError> {
let client_id = client_connection_path.0.clone();
self.ibc_store
.lock()
.client_connections
.insert(client_id, conn_id);
Ok(())
}
fn increase_connection_counter(&mut self) {
self.ibc_store.lock().connection_ids_counter += 1;
}
fn store_packet_commitment(
&mut self,
commitment_path: &CommitmentPath,
commitment: PacketCommitment,
) -> Result<(), ContextError> {
self.ibc_store
.lock()
.packet_commitment
.entry(commitment_path.port_id.clone())
.or_default()
.entry(commitment_path.channel_id.clone())
.or_default()
.insert(commitment_path.sequence, commitment);
Ok(())
}
fn delete_packet_commitment(
&mut self,
commitment_path: &CommitmentPath,
) -> Result<(), ContextError> {
self.ibc_store
.lock()
.packet_commitment
.get_mut(&commitment_path.port_id)
.and_then(|map| map.get_mut(&commitment_path.channel_id))
.and_then(|map| map.remove(&commitment_path.sequence));
Ok(())
}
fn store_packet_receipt(
&mut self,
path: &ReceiptPath,
receipt: Receipt,
) -> Result<(), ContextError> {
self.ibc_store
.lock()
.packet_receipt
.entry(path.port_id.clone())
.or_default()
.entry(path.channel_id.clone())
.or_default()
.insert(path.sequence, receipt);
Ok(())
}
fn store_packet_acknowledgement(
&mut self,
ack_path: &AckPath,
ack_commitment: AcknowledgementCommitment,
) -> Result<(), ContextError> {
let port_id = ack_path.port_id.clone();
let channel_id = ack_path.channel_id.clone();
let seq = ack_path.sequence;
self.ibc_store
.lock()
.packet_acknowledgement
.entry(port_id)
.or_default()
.entry(channel_id)
.or_default()
.insert(seq, ack_commitment);
Ok(())
}
fn delete_packet_acknowledgement(&mut self, ack_path: &AckPath) -> Result<(), ContextError> {
let port_id = ack_path.port_id.clone();
let channel_id = ack_path.channel_id.clone();
let sequence = ack_path.sequence;
self.ibc_store
.lock()
.packet_acknowledgement
.get_mut(&port_id)
.and_then(|map| map.get_mut(&channel_id))
.and_then(|map| map.remove(&sequence));
Ok(())
}
fn store_channel(
&mut self,
channel_end_path: &ChannelEndPath,
channel_end: ChannelEnd,
) -> Result<(), ContextError> {
let port_id = channel_end_path.0.clone();
let channel_id = channel_end_path.1.clone();
self.ibc_store
.lock()
.channels
.entry(port_id)
.or_default()
.insert(channel_id, channel_end);
Ok(())
}
fn store_next_sequence_send(
&mut self,
seq_send_path: &SeqSendPath,
seq: Sequence,
) -> Result<(), ContextError> {
let port_id = seq_send_path.0.clone();
let channel_id = seq_send_path.1.clone();
self.ibc_store
.lock()
.next_sequence_send
.entry(port_id)
.or_default()
.insert(channel_id, seq);
Ok(())
}
fn store_next_sequence_recv(
&mut self,
seq_recv_path: &SeqRecvPath,
seq: Sequence,
) -> Result<(), ContextError> {
let port_id = seq_recv_path.0.clone();
let channel_id = seq_recv_path.1.clone();
self.ibc_store
.lock()
.next_sequence_recv
.entry(port_id)
.or_default()
.insert(channel_id, seq);
Ok(())
}
fn store_next_sequence_ack(
&mut self,
seq_ack_path: &SeqAckPath,
seq: Sequence,
) -> Result<(), ContextError> {
let port_id = seq_ack_path.0.clone();
let channel_id = seq_ack_path.1.clone();
self.ibc_store
.lock()
.next_sequence_ack
.entry(port_id)
.or_default()
.insert(channel_id, seq);
Ok(())
}
fn increase_channel_counter(&mut self) {
self.ibc_store.lock().channel_ids_counter += 1;
}
fn emit_ibc_event(&mut self, event: IbcEvent) {
self.events.push(event);
}
fn log_message(&mut self, message: String) {
self.logs.push(message);
}
}
impl TokenTransferValidationContext for MockContext {
type AccountId = Signer;
fn get_port(&self) -> Result<PortId, TokenTransferError> {
Ok(PortId::transfer())
}
fn get_escrow_account(
&self,
port_id: &PortId,
channel_id: &ChannelId,
) -> Result<Self::AccountId, TokenTransferError> {
let addr = cosmos_adr028_escrow_address(port_id, channel_id);
Ok(bech32::encode("cosmos", addr).parse().unwrap())
}
fn can_send_coins(&self) -> Result<(), TokenTransferError> {
Ok(())
}
fn can_receive_coins(&self) -> Result<(), TokenTransferError> {
Ok(())
}
fn send_coins_validate(
&self,
_from_account: &Self::AccountId,
_to_account: &Self::AccountId,
_coin: &PrefixedCoin,
) -> Result<(), TokenTransferError> {
Ok(())
}
fn mint_coins_validate(
&self,
_account: &Self::AccountId,
_coin: &PrefixedCoin,
) -> Result<(), TokenTransferError> {
Ok(())
}
fn burn_coins_validate(
&self,
_account: &Self::AccountId,
_coin: &PrefixedCoin,
) -> Result<(), TokenTransferError> {
Ok(())
}
}
impl TokenTransferExecutionContext for MockContext {
fn send_coins_execute(
&mut self,
_from_account: &Self::AccountId,
_to_account: &Self::AccountId,
_coin: &PrefixedCoin,
) -> Result<(), TokenTransferError> {
Ok(())
}
fn mint_coins_execute(
&mut self,
_account: &Self::AccountId,
_coin: &PrefixedCoin,
) -> Result<(), TokenTransferError> {
Ok(())
}
fn burn_coins_execute(
&mut self,
_account: &Self::AccountId,
_coin: &PrefixedCoin,
) -> Result<(), TokenTransferError> {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_log::test;
use alloc::str::FromStr;
use crate::core::ics04_channel::channel::{Counterparty, Order};
use crate::core::ics04_channel::error::ChannelError;
use crate::core::ics04_channel::handler::ModuleExtras;
use crate::core::ics04_channel::msgs::acknowledgement::Acknowledgement;
use crate::core::ics04_channel::packet::Packet;
use crate::core::ics04_channel::Version;
use crate::core::ics24_host::identifier::ChainId;
use crate::core::ics24_host::identifier::{ChannelId, ConnectionId, PortId};
use crate::core::ics26_routing::context::{Module, ModuleId};
use crate::mock::context::MockContext;
use crate::mock::host::HostType;
use crate::signer::Signer;
use crate::test_utils::get_dummy_bech32_account;
use crate::Height;
#[test]
fn test_history_manipulation() {
pub struct Test {
name: String,
ctx: MockContext,
}
let cv = 1; let tests: Vec<Test> = vec![
Test {
name: "Empty history, small pruning window".to_string(),
ctx: MockContext::new(
ChainId::new("mockgaia".to_string(), cv),
HostType::Mock,
2,
Height::new(cv, 1).unwrap(),
),
},
Test {
name: "[Synthetic TM host] Empty history, small pruning window".to_string(),
ctx: MockContext::new(
ChainId::new("mocksgaia".to_string(), cv),
HostType::SyntheticTendermint,
2,
Height::new(cv, 1).unwrap(),
),
},
Test {
name: "Large pruning window".to_string(),
ctx: MockContext::new(
ChainId::new("mockgaia".to_string(), cv),
HostType::Mock,
30,
Height::new(cv, 2).unwrap(),
),
},
Test {
name: "[Synthetic TM host] Large pruning window".to_string(),
ctx: MockContext::new(
ChainId::new("mocksgaia".to_string(), cv),
HostType::SyntheticTendermint,
30,
Height::new(cv, 2).unwrap(),
),
},
Test {
name: "Small pruning window".to_string(),
ctx: MockContext::new(
ChainId::new("mockgaia".to_string(), cv),
HostType::Mock,
3,
Height::new(cv, 30).unwrap(),
),
},
Test {
name: "[Synthetic TM host] Small pruning window".to_string(),
ctx: MockContext::new(
ChainId::new("mockgaia".to_string(), cv),
HostType::SyntheticTendermint,
3,
Height::new(cv, 30).unwrap(),
),
},
Test {
name: "Small pruning window, small starting height".to_string(),
ctx: MockContext::new(
ChainId::new("mockgaia".to_string(), cv),
HostType::Mock,
3,
Height::new(cv, 2).unwrap(),
),
},
Test {
name: "[Synthetic TM host] Small pruning window, small starting height".to_string(),
ctx: MockContext::new(
ChainId::new("mockgaia".to_string(), cv),
HostType::SyntheticTendermint,
3,
Height::new(cv, 2).unwrap(),
),
},
Test {
name: "Large pruning window, large starting height".to_string(),
ctx: MockContext::new(
ChainId::new("mockgaia".to_string(), cv),
HostType::Mock,
50,
Height::new(cv, 2000).unwrap(),
),
},
Test {
name: "[Synthetic TM host] Large pruning window, large starting height".to_string(),
ctx: MockContext::new(
ChainId::new("mockgaia".to_string(), cv),
HostType::SyntheticTendermint,
50,
Height::new(cv, 2000).unwrap(),
),
},
];
for mut test in tests {
assert!(
test.ctx.validate().is_ok(),
"failed in test {} while validating context {:?}",
test.name,
test.ctx
);
let current_height = test.ctx.latest_height();
test.ctx.advance_host_chain_height();
assert!(
test.ctx.validate().is_ok(),
"failed in test {} while validating context {:?}",
test.name,
test.ctx
);
let next_height = current_height.increment();
assert_eq!(
test.ctx.latest_height(),
next_height,
"failed while increasing height for context {:?}",
test.ctx
);
assert_eq!(
test.ctx.host_block(¤t_height).unwrap().height(),
current_height,
"failed while fetching height {:?} of context {:?}",
current_height,
test.ctx
);
}
}
#[test]
fn test_router() {
#[derive(Debug, Default)]
struct FooModule {
counter: usize,
}
impl Module for FooModule {
fn on_chan_open_init_validate(
&self,
_order: Order,
_connection_hops: &[ConnectionId],
_port_id: &PortId,
_channel_id: &ChannelId,
_counterparty: &Counterparty,
version: &Version,
) -> Result<Version, ChannelError> {
Ok(version.clone())
}
fn on_chan_open_init_execute(
&mut self,
_order: Order,
_connection_hops: &[ConnectionId],
_port_id: &PortId,
_channel_id: &ChannelId,
_counterparty: &Counterparty,
version: &Version,
) -> Result<(ModuleExtras, Version), ChannelError> {
Ok((ModuleExtras::empty(), version.clone()))
}
fn on_chan_open_try_validate(
&self,
_order: Order,
_connection_hops: &[ConnectionId],
_port_id: &PortId,
_channel_id: &ChannelId,
_counterparty: &Counterparty,
counterparty_version: &Version,
) -> Result<Version, ChannelError> {
Ok(counterparty_version.clone())
}
fn on_chan_open_try_execute(
&mut self,
_order: Order,
_connection_hops: &[ConnectionId],
_port_id: &PortId,
_channel_id: &ChannelId,
_counterparty: &Counterparty,
counterparty_version: &Version,
) -> Result<(ModuleExtras, Version), ChannelError> {
Ok((ModuleExtras::empty(), counterparty_version.clone()))
}
fn on_recv_packet_execute(
&mut self,
_packet: &Packet,
_relayer: &Signer,
) -> (ModuleExtras, Acknowledgement) {
self.counter += 1;
(
ModuleExtras::empty(),
Acknowledgement::try_from(vec![1u8]).unwrap(),
)
}
fn on_timeout_packet_validate(
&self,
_packet: &Packet,
_relayer: &Signer,
) -> Result<(), PacketError> {
Ok(())
}
fn on_timeout_packet_execute(
&mut self,
_packet: &Packet,
_relayer: &Signer,
) -> (ModuleExtras, Result<(), PacketError>) {
(ModuleExtras::empty(), Ok(()))
}
fn on_acknowledgement_packet_validate(
&self,
_packet: &Packet,
_acknowledgement: &Acknowledgement,
_relayer: &Signer,
) -> Result<(), PacketError> {
Ok(())
}
fn on_acknowledgement_packet_execute(
&mut self,
_packet: &Packet,
_acknowledgement: &Acknowledgement,
_relayer: &Signer,
) -> (ModuleExtras, Result<(), PacketError>) {
(ModuleExtras::empty(), Ok(()))
}
}
#[derive(Debug, Default)]
struct BarModule;
impl Module for BarModule {
fn on_chan_open_init_validate(
&self,
_order: Order,
_connection_hops: &[ConnectionId],
_port_id: &PortId,
_channel_id: &ChannelId,
_counterparty: &Counterparty,
version: &Version,
) -> Result<Version, ChannelError> {
Ok(version.clone())
}
fn on_chan_open_init_execute(
&mut self,
_order: Order,
_connection_hops: &[ConnectionId],
_port_id: &PortId,
_channel_id: &ChannelId,
_counterparty: &Counterparty,
version: &Version,
) -> Result<(ModuleExtras, Version), ChannelError> {
Ok((ModuleExtras::empty(), version.clone()))
}
fn on_chan_open_try_validate(
&self,
_order: Order,
_connection_hops: &[ConnectionId],
_port_id: &PortId,
_channel_id: &ChannelId,
_counterparty: &Counterparty,
counterparty_version: &Version,
) -> Result<Version, ChannelError> {
Ok(counterparty_version.clone())
}
fn on_chan_open_try_execute(
&mut self,
_order: Order,
_connection_hops: &[ConnectionId],
_port_id: &PortId,
_channel_id: &ChannelId,
_counterparty: &Counterparty,
counterparty_version: &Version,
) -> Result<(ModuleExtras, Version), ChannelError> {
Ok((ModuleExtras::empty(), counterparty_version.clone()))
}
fn on_recv_packet_execute(
&mut self,
_packet: &Packet,
_relayer: &Signer,
) -> (ModuleExtras, Acknowledgement) {
(
ModuleExtras::empty(),
Acknowledgement::try_from(vec![1u8]).unwrap(),
)
}
fn on_timeout_packet_validate(
&self,
_packet: &Packet,
_relayer: &Signer,
) -> Result<(), PacketError> {
Ok(())
}
fn on_timeout_packet_execute(
&mut self,
_packet: &Packet,
_relayer: &Signer,
) -> (ModuleExtras, Result<(), PacketError>) {
(ModuleExtras::empty(), Ok(()))
}
fn on_acknowledgement_packet_validate(
&self,
_packet: &Packet,
_acknowledgement: &Acknowledgement,
_relayer: &Signer,
) -> Result<(), PacketError> {
Ok(())
}
fn on_acknowledgement_packet_execute(
&mut self,
_packet: &Packet,
_acknowledgement: &Acknowledgement,
_relayer: &Signer,
) -> (ModuleExtras, Result<(), PacketError>) {
(ModuleExtras::empty(), Ok(()))
}
}
let mut ctx = MockContext::new(
ChainId::new("mockgaia".to_string(), 1),
HostType::Mock,
1,
Height::new(1, 1).unwrap(),
);
ctx.add_route("foomodule".parse().unwrap(), FooModule::default())
.unwrap();
ctx.add_route("barmodule".parse().unwrap(), BarModule::default())
.unwrap();
let mut on_recv_packet_result = |module_id: &'static str| {
let module_id = ModuleId::from_str(module_id).unwrap();
let m = ctx.get_route_mut(&module_id).unwrap();
let result = m.on_recv_packet_execute(
&Packet::default(),
&get_dummy_bech32_account().parse().unwrap(),
);
(module_id, result)
};
let _results = vec![
on_recv_packet_result("foomodule"),
on_recv_packet_result("barmodule"),
];
}
}