use core::fmt::Debug;
use core::time::Duration;
use basecoin_store::context::ProvableStore;
use basecoin_store::impls::InMemoryStore;
use ibc::core::channel::types::channel::ChannelEnd;
use ibc::core::channel::types::commitment::PacketCommitment;
use ibc::core::client::context::client_state::ClientStateValidation;
use ibc::core::client::context::{ClientExecutionContext, ClientValidationContext};
use ibc::core::client::types::Height;
use ibc::core::connection::types::ConnectionEnd;
use ibc::core::entrypoint::{dispatch, execute, validate};
use ibc::core::handler::types::error::HandlerError;
use ibc::core::handler::types::events::IbcEvent;
use ibc::core::handler::types::msgs::MsgEnvelope;
use ibc::core::host::types::identifiers::{ChannelId, ClientId, ConnectionId, PortId, Sequence};
use ibc::core::host::types::path::{
ChannelEndPath, ClientConsensusStatePath, ClientStatePath, CommitmentPath, ConnectionPath,
SeqAckPath, SeqRecvPath, SeqSendPath,
};
use ibc::core::host::{ExecutionContext, ValidationContext};
use ibc::primitives::prelude::*;
use ibc::primitives::Timestamp;
use super::testapp::ibc::core::types::{LightClientState, MockIbcStore};
use crate::fixtures::core::context::dummy_store_generic_test_context;
use crate::hosts::{HostClientState, MockHost, TendermintHost, TestBlock, TestHeader, TestHost};
use crate::testapp::ibc::clients::{AnyClientState, AnyConsensusState};
use crate::testapp::ibc::core::router::MockRouter;
use crate::testapp::ibc::core::types::DEFAULT_BLOCK_TIME_SECS;
#[derive(Debug)]
pub struct StoreGenericTestContext<S, H>
where
S: ProvableStore + Debug,
H: TestHost,
HostClientState<H>: ClientStateValidation<MockIbcStore<S>>,
{
pub multi_store: S,
pub host: H,
pub ibc_store: MockIbcStore<S>,
pub ibc_router: MockRouter,
}
pub type MockStore = InMemoryStore;
pub type TestContext<H> = StoreGenericTestContext<MockStore, H>;
pub type MockContext = TestContext<MockHost>;
pub type TendermintContext = TestContext<TendermintHost>;
impl<S, H> Default for StoreGenericTestContext<S, H>
where
S: ProvableStore + Debug + Default,
H: TestHost,
HostClientState<H>: ClientStateValidation<MockIbcStore<S>>,
{
fn default() -> Self {
dummy_store_generic_test_context().call()
}
}
impl<S, H> StoreGenericTestContext<S, H>
where
S: ProvableStore + Debug,
H: TestHost,
HostClientState<H>: ClientStateValidation<MockIbcStore<S>>,
{
pub fn ibc_store(&self) -> &MockIbcStore<S> {
&self.ibc_store
}
pub fn ibc_store_mut(&mut self) -> &mut MockIbcStore<S> {
&mut self.ibc_store
}
pub fn ibc_router(&self) -> &MockRouter {
&self.ibc_router
}
pub fn ibc_router_mut(&mut self) -> &mut MockRouter {
&mut self.ibc_router
}
pub fn host_block(&self, target_height: &Height) -> Option<H::Block> {
self.host.get_block(target_height)
}
pub fn query_latest_block(&self) -> Option<H::Block> {
self.host.get_block(&self.latest_height())
}
pub fn light_client_latest_height(&self, client_id: &ClientId) -> Height {
self.ibc_store
.client_state(client_id)
.expect("client state exists")
.latest_height()
}
pub fn advance_block_up_to_height(mut self, target_height: Height) -> Self {
let latest_height = self.host.latest_height();
if target_height.revision_number() != latest_height.revision_number() {
panic!("Cannot advance history of the chain to a different 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 {
while self.host.latest_height().revision_height() < target_height.revision_height() {
self.advance_block_height()
}
}
self
}
pub fn advance_genesis_height(&mut self, genesis_time: Timestamp, params: &H::BlockParams) {
self.end_block();
let multi_store_commitment = self.multi_store.commit().expect("no error");
let genesis_block =
self.host
.generate_block(multi_store_commitment, 1, genesis_time, params);
self.host.push_block(genesis_block);
self.begin_block();
}
pub fn begin_block(&mut self) {
let consensus_state = self
.host
.latest_block()
.into_header()
.into_consensus_state()
.into();
let ibc_commitment_proof = self
.multi_store
.get_proof(
self.host.latest_height().revision_height().into(),
&self
.ibc_store
.commitment_prefix()
.as_bytes()
.try_into()
.expect("valid utf8 prefix"),
)
.expect("no error");
self.ibc_store.begin_block(
self.host.latest_height().revision_height(),
consensus_state,
ibc_commitment_proof,
);
}
pub fn end_block(&mut self) {
let ibc_store_commitment = self.ibc_store.end_block().expect("no error");
self.multi_store
.set(
self.ibc_store
.commitment_prefix()
.as_bytes()
.try_into()
.expect("valid utf8 prefix"),
ibc_store_commitment,
)
.expect("no error");
}
pub fn commit_state_to_host(&mut self, block_time: Duration, params: &H::BlockParams) {
let multi_store_commitment = self.multi_store.commit().expect("no error");
self.host
.commit_block(multi_store_commitment, block_time, params);
}
pub fn advance_block_height_with_params(
&mut self,
block_time: Duration,
params: &H::BlockParams,
) {
self.end_block();
self.commit_state_to_host(block_time, params);
self.begin_block();
}
pub fn advance_block_height(&mut self) {
self.advance_block_height_with_params(
Duration::from_secs(DEFAULT_BLOCK_TIME_SECS),
&Default::default(),
)
}
pub fn latest_height(&self) -> Height {
let latest_ibc_height = self.ibc_store.host_height().expect("Never fails");
let latest_host_height = self.host.latest_height();
assert_eq!(
latest_ibc_height, latest_host_height,
"The IBC store and the host chain must have the same height"
);
latest_ibc_height
}
pub fn latest_timestamp(&self) -> Timestamp {
self.host.latest_block().timestamp()
}
pub fn timestamp_at(&self, height: Height) -> Timestamp {
self.host
.get_block(&height)
.expect("block exists")
.timestamp()
}
pub fn with_client_state(mut self, client_id: &ClientId, client_state: AnyClientState) -> Self {
let client_state_path = ClientStatePath::new(client_id.clone());
self.ibc_store
.store_client_state(client_state_path, client_state)
.expect("error writing to store");
self
}
pub fn with_consensus_state(
mut self,
client_id: &ClientId,
height: Height,
consensus_state: AnyConsensusState,
) -> Self {
let consensus_state_path = ClientConsensusStatePath::new(
client_id.clone(),
height.revision_number(),
height.revision_height(),
);
self.ibc_store
.store_consensus_state(consensus_state_path, consensus_state)
.expect("error writing to store");
self
}
pub fn generate_light_client(
&self,
mut consensus_heights: Vec<Height>,
client_params: &H::LightClientParams,
) -> LightClientState<H> {
let client_height = if let Some(&height) = consensus_heights.last() {
height
} else {
consensus_heights.push(self.latest_height());
self.latest_height()
};
let client_state = self
.host
.generate_client_state(&client_height, client_params);
let consensus_states = consensus_heights
.into_iter()
.map(|height| {
(
height,
self.host_block(&height)
.expect("block exists")
.into_header()
.into_consensus_state(),
)
})
.collect();
LightClientState {
client_state,
consensus_states,
}
}
pub fn with_light_client<RH>(
mut self,
client_id: &ClientId,
light_client: LightClientState<RH>,
) -> Self
where
RH: TestHost,
{
self = self.with_client_state(client_id, light_client.client_state.into());
for (height, consensus_state) in light_client.consensus_states {
self = self.with_consensus_state(client_id, height, consensus_state.into());
self.ibc_store
.store_update_meta(
client_id.clone(),
height,
self.latest_timestamp(),
self.latest_height(),
)
.expect("error writing to store");
}
self
}
pub fn with_connection(
mut self,
connection_id: ConnectionId,
connection_end: ConnectionEnd,
) -> Self {
let connection_path = ConnectionPath::new(&connection_id);
self.ibc_store
.store_connection(&connection_path, connection_end)
.expect("error writing to store");
self
}
pub fn with_channel(
mut self,
port_id: PortId,
chan_id: ChannelId,
channel_end: ChannelEnd,
) -> Self {
let channel_end_path = ChannelEndPath::new(&port_id, &chan_id);
self.ibc_store
.store_channel(&channel_end_path, channel_end)
.expect("error writing to store");
self
}
pub fn with_send_sequence(
mut self,
port_id: PortId,
chan_id: ChannelId,
seq_number: Sequence,
) -> Self {
let seq_send_path = SeqSendPath::new(&port_id, &chan_id);
self.ibc_store
.store_next_sequence_send(&seq_send_path, seq_number)
.expect("error writing to store");
self
}
pub fn with_recv_sequence(
mut self,
port_id: PortId,
chan_id: ChannelId,
seq_number: Sequence,
) -> Self {
let seq_recv_path = SeqRecvPath::new(&port_id, &chan_id);
self.ibc_store
.store_next_sequence_recv(&seq_recv_path, seq_number)
.expect("error writing to store");
self
}
pub fn with_ack_sequence(
mut self,
port_id: PortId,
chan_id: ChannelId,
seq_number: Sequence,
) -> Self {
let seq_ack_path = SeqAckPath::new(&port_id, &chan_id);
self.ibc_store
.store_next_sequence_ack(&seq_ack_path, seq_number)
.expect("error writing to store");
self
}
pub fn with_packet_commitment(
mut self,
port_id: PortId,
chan_id: ChannelId,
seq: Sequence,
data: PacketCommitment,
) -> Self {
let commitment_path = CommitmentPath::new(&port_id, &chan_id, seq);
self.ibc_store
.store_packet_commitment(&commitment_path, data)
.expect("error writing to store");
self
}
pub fn validate(&mut self, msg: MsgEnvelope) -> Result<(), HandlerError> {
validate(&self.ibc_store, &self.ibc_router, msg)
}
pub fn execute(&mut self, msg: MsgEnvelope) -> Result<(), HandlerError> {
execute(&mut self.ibc_store, &mut self.ibc_router, msg)
}
pub fn dispatch(&mut self, msg: MsgEnvelope) -> Result<(), HandlerError> {
dispatch(&mut self.ibc_store, &mut self.ibc_router, msg)
}
pub fn deliver(&mut self, msg: MsgEnvelope) -> Result<(), HandlerError> {
self.dispatch(msg)?;
self.advance_block_height();
Ok(())
}
pub fn get_events(&self) -> Vec<IbcEvent> {
self.ibc_store.events.lock().clone()
}
pub fn get_logs(&self) -> Vec<String> {
self.ibc_store.logs.lock().clone()
}
}
#[cfg(test)]
mod tests {
use ibc::core::client::context::consensus_state::ConsensusState;
use super::*;
use crate::hosts::{HostConsensusState, MockHost, TendermintHost};
use crate::testapp::ibc::core::types::DefaultIbcStore;
#[test]
fn test_mock_history_validation() {
pub struct Test<H: TestHost>
where
H: TestHost,
HostConsensusState<H>: ConsensusState,
HostClientState<H>: ClientStateValidation<DefaultIbcStore>,
{
name: String,
ctx: TestContext<H>,
}
fn run_tests<H>(sub_title: &str)
where
H: TestHost,
HostConsensusState<H>: ConsensusState,
HostClientState<H>: ClientStateValidation<DefaultIbcStore>,
{
let cv = 0;
let tests: Vec<Test<H>> = vec![
Test {
name: "Empty history, small pruning window".to_string(),
ctx: dummy_store_generic_test_context()
.latest_height(Height::new(cv, 1).expect("Never fails"))
.call(),
},
Test {
name: "Large pruning window".to_string(),
ctx: dummy_store_generic_test_context()
.latest_height(Height::new(cv, 2).expect("Never fails"))
.call(),
},
Test {
name: "Small pruning window".to_string(),
ctx: dummy_store_generic_test_context()
.latest_height(Height::new(cv, 30).expect("Never fails"))
.call(),
},
Test {
name: "Small pruning window, small starting height".to_string(),
ctx: dummy_store_generic_test_context()
.latest_height(Height::new(cv, 2).expect("Never fails"))
.call(),
},
];
for mut test in tests {
assert!(
test.ctx.host.validate().is_ok(),
"failed in test [{}] {} while validating context {:?}",
sub_title,
test.name,
test.ctx
);
let current_height = test.ctx.latest_height();
test.ctx.advance_block_height();
assert!(
test.ctx.host.validate().is_ok(),
"failed in test [{}] {} while validating context {:?}",
sub_title,
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
.get_block(¤t_height)
.expect("Never fails")
.height(),
current_height,
"failed while fetching height {:?} of context {:?}",
current_height,
test.ctx
);
}
}
run_tests::<MockHost>("Mock Host");
run_tests::<TendermintHost>("Synthetic TM Host");
}
}