1use core::fmt::Debug;
2use core::time::Duration;
3
4use basecoin_store::context::ProvableStore;
5use basecoin_store::impls::InMemoryStore;
6use ibc::core::channel::types::channel::ChannelEnd;
7use ibc::core::channel::types::commitment::PacketCommitment;
8use ibc::core::client::context::client_state::ClientStateValidation;
9use ibc::core::client::context::{ClientExecutionContext, ClientValidationContext};
10use ibc::core::client::types::Height;
11use ibc::core::connection::types::ConnectionEnd;
12use ibc::core::entrypoint::{dispatch, execute, validate};
13use ibc::core::handler::types::error::HandlerError;
14use ibc::core::handler::types::events::IbcEvent;
15use ibc::core::handler::types::msgs::MsgEnvelope;
16use ibc::core::host::types::identifiers::{ChannelId, ClientId, ConnectionId, PortId, Sequence};
17use ibc::core::host::types::path::{
18 ChannelEndPath, ClientConsensusStatePath, ClientStatePath, CommitmentPath, ConnectionPath,
19 SeqAckPath, SeqRecvPath, SeqSendPath,
20};
21use ibc::core::host::{ExecutionContext, ValidationContext};
22use ibc::primitives::prelude::*;
23use ibc::primitives::Timestamp;
24
25use super::testapp::ibc::core::types::{LightClientState, MockIbcStore};
26use crate::fixtures::core::context::dummy_store_generic_test_context;
27use crate::hosts::{HostClientState, MockHost, TendermintHost, TestBlock, TestHeader, TestHost};
28use crate::testapp::ibc::clients::{AnyClientState, AnyConsensusState};
29use crate::testapp::ibc::core::router::MockRouter;
30use crate::testapp::ibc::core::types::DEFAULT_BLOCK_TIME_SECS;
31
32#[derive(Debug)]
34pub struct StoreGenericTestContext<S, H>
35where
36 S: ProvableStore + Debug,
37 H: TestHost,
38 HostClientState<H>: ClientStateValidation<MockIbcStore<S>>,
39{
40 pub multi_store: S,
43
44 pub host: H,
46
47 pub ibc_store: MockIbcStore<S>,
49
50 pub ibc_router: MockRouter,
52}
53
54pub type MockStore = InMemoryStore;
56pub type TestContext<H> = StoreGenericTestContext<MockStore, H>;
58pub type MockContext = TestContext<MockHost>;
60pub type TendermintContext = TestContext<TendermintHost>;
62
63impl<S, H> Default for StoreGenericTestContext<S, H>
67where
68 S: ProvableStore + Debug + Default,
69 H: TestHost,
70 HostClientState<H>: ClientStateValidation<MockIbcStore<S>>,
71{
72 fn default() -> Self {
73 dummy_store_generic_test_context().call()
74 }
75}
76
77impl<S, H> StoreGenericTestContext<S, H>
80where
81 S: ProvableStore + Debug,
82 H: TestHost,
83 HostClientState<H>: ClientStateValidation<MockIbcStore<S>>,
84{
85 pub fn ibc_store(&self) -> &MockIbcStore<S> {
87 &self.ibc_store
88 }
89
90 pub fn ibc_store_mut(&mut self) -> &mut MockIbcStore<S> {
92 &mut self.ibc_store
93 }
94
95 pub fn ibc_router(&self) -> &MockRouter {
97 &self.ibc_router
98 }
99
100 pub fn ibc_router_mut(&mut self) -> &mut MockRouter {
102 &mut self.ibc_router
103 }
104
105 pub fn host_block(&self, target_height: &Height) -> Option<H::Block> {
107 self.host.get_block(target_height)
108 }
109
110 pub fn query_latest_block(&self) -> Option<H::Block> {
112 self.host.get_block(&self.latest_height())
113 }
114
115 pub fn light_client_latest_height(&self, client_id: &ClientId) -> Height {
117 self.ibc_store
118 .client_state(client_id)
119 .expect("client state exists")
120 .latest_height()
121 }
122
123 pub fn advance_block_up_to_height(mut self, target_height: Height) -> Self {
125 let latest_height = self.host.latest_height();
126 if target_height.revision_number() != latest_height.revision_number() {
127 panic!("Cannot advance history of the chain to a different revision number!")
128 } else if target_height.revision_height() < latest_height.revision_height() {
129 panic!("Cannot rewind history of the chain to a smaller revision height!")
130 } else {
131 while self.host.latest_height().revision_height() < target_height.revision_height() {
133 self.advance_block_height()
134 }
135 }
136 self
137 }
138
139 pub fn advance_genesis_height(&mut self, genesis_time: Timestamp, params: &H::BlockParams) {
148 self.end_block();
149
150 let multi_store_commitment = self.multi_store.commit().expect("no error");
152
153 let genesis_block =
157 self.host
158 .generate_block(multi_store_commitment, 1, genesis_time, params);
159
160 self.host.push_block(genesis_block);
162
163 self.begin_block();
164 }
165
166 pub fn begin_block(&mut self) {
172 let consensus_state = self
173 .host
174 .latest_block()
175 .into_header()
176 .into_consensus_state()
177 .into();
178
179 let ibc_commitment_proof = self
180 .multi_store
181 .get_proof(
182 self.host.latest_height().revision_height().into(),
183 &self
184 .ibc_store
185 .commitment_prefix()
186 .as_bytes()
187 .try_into()
188 .expect("valid utf8 prefix"),
189 )
190 .expect("no error");
191
192 self.ibc_store.begin_block(
193 self.host.latest_height().revision_height(),
194 consensus_state,
195 ibc_commitment_proof,
196 );
197 }
198
199 pub fn end_block(&mut self) {
203 let ibc_store_commitment = self.ibc_store.end_block().expect("no error");
205
206 self.multi_store
208 .set(
209 self.ibc_store
210 .commitment_prefix()
211 .as_bytes()
212 .try_into()
213 .expect("valid utf8 prefix"),
214 ibc_store_commitment,
215 )
216 .expect("no error");
217 }
218
219 pub fn commit_state_to_host(&mut self, block_time: Duration, params: &H::BlockParams) {
224 let multi_store_commitment = self.multi_store.commit().expect("no error");
226 self.host
228 .commit_block(multi_store_commitment, block_time, params);
229 }
230
231 pub fn advance_block_height_with_params(
234 &mut self,
235 block_time: Duration,
236 params: &H::BlockParams,
237 ) {
238 self.end_block();
239 self.commit_state_to_host(block_time, params);
240 self.begin_block();
241 }
242
243 pub fn advance_block_height(&mut self) {
245 self.advance_block_height_with_params(
246 Duration::from_secs(DEFAULT_BLOCK_TIME_SECS),
247 &Default::default(),
248 )
249 }
250
251 pub fn latest_height(&self) -> Height {
253 let latest_ibc_height = self.ibc_store.host_height().expect("Never fails");
254 let latest_host_height = self.host.latest_height();
255 assert_eq!(
256 latest_ibc_height, latest_host_height,
257 "The IBC store and the host chain must have the same height"
258 );
259 latest_ibc_height
260 }
261
262 pub fn latest_timestamp(&self) -> Timestamp {
264 self.host.latest_block().timestamp()
265 }
266
267 pub fn timestamp_at(&self, height: Height) -> Timestamp {
269 self.host
270 .get_block(&height)
271 .expect("block exists")
272 .timestamp()
273 }
274
275 pub fn with_client_state(mut self, client_id: &ClientId, client_state: AnyClientState) -> Self {
277 let client_state_path = ClientStatePath::new(client_id.clone());
278 self.ibc_store
279 .store_client_state(client_state_path, client_state)
280 .expect("error writing to store");
281 self
282 }
283
284 pub fn with_consensus_state(
286 mut self,
287 client_id: &ClientId,
288 height: Height,
289 consensus_state: AnyConsensusState,
290 ) -> Self {
291 let consensus_state_path = ClientConsensusStatePath::new(
292 client_id.clone(),
293 height.revision_number(),
294 height.revision_height(),
295 );
296 self.ibc_store
297 .store_consensus_state(consensus_state_path, consensus_state)
298 .expect("error writing to store");
299
300 self
301 }
302
303 pub fn generate_light_client(
307 &self,
308 mut consensus_heights: Vec<Height>,
309 client_params: &H::LightClientParams,
310 ) -> LightClientState<H> {
311 let client_height = if let Some(&height) = consensus_heights.last() {
312 height
313 } else {
314 consensus_heights.push(self.latest_height());
315 self.latest_height()
316 };
317
318 let client_state = self
319 .host
320 .generate_client_state(&client_height, client_params);
321
322 let consensus_states = consensus_heights
323 .into_iter()
324 .map(|height| {
325 (
326 height,
327 self.host_block(&height)
328 .expect("block exists")
329 .into_header()
330 .into_consensus_state(),
331 )
332 })
333 .collect();
334
335 LightClientState {
336 client_state,
337 consensus_states,
338 }
339 }
340
341 pub fn with_light_client<RH>(
343 mut self,
344 client_id: &ClientId,
345 light_client: LightClientState<RH>,
346 ) -> Self
347 where
348 RH: TestHost,
349 {
350 self = self.with_client_state(client_id, light_client.client_state.into());
351
352 for (height, consensus_state) in light_client.consensus_states {
353 self = self.with_consensus_state(client_id, height, consensus_state.into());
354
355 self.ibc_store
356 .store_update_meta(
357 client_id.clone(),
358 height,
359 self.latest_timestamp(),
360 self.latest_height(),
361 )
362 .expect("error writing to store");
363 }
364
365 self
366 }
367
368 pub fn with_connection(
372 mut self,
373 connection_id: ConnectionId,
374 connection_end: ConnectionEnd,
375 ) -> Self {
376 let connection_path = ConnectionPath::new(&connection_id);
377 self.ibc_store
378 .store_connection(&connection_path, connection_end)
379 .expect("error writing to store");
380 self
381 }
382
383 pub fn with_channel(
387 mut self,
388 port_id: PortId,
389 chan_id: ChannelId,
390 channel_end: ChannelEnd,
391 ) -> Self {
392 let channel_end_path = ChannelEndPath::new(&port_id, &chan_id);
393 self.ibc_store
394 .store_channel(&channel_end_path, channel_end)
395 .expect("error writing to store");
396 self
397 }
398
399 pub fn with_send_sequence(
403 mut self,
404 port_id: PortId,
405 chan_id: ChannelId,
406 seq_number: Sequence,
407 ) -> Self {
408 let seq_send_path = SeqSendPath::new(&port_id, &chan_id);
409 self.ibc_store
410 .store_next_sequence_send(&seq_send_path, seq_number)
411 .expect("error writing to store");
412 self
413 }
414
415 pub fn with_recv_sequence(
419 mut self,
420 port_id: PortId,
421 chan_id: ChannelId,
422 seq_number: Sequence,
423 ) -> Self {
424 let seq_recv_path = SeqRecvPath::new(&port_id, &chan_id);
425 self.ibc_store
426 .store_next_sequence_recv(&seq_recv_path, seq_number)
427 .expect("error writing to store");
428 self
429 }
430
431 pub fn with_ack_sequence(
435 mut self,
436 port_id: PortId,
437 chan_id: ChannelId,
438 seq_number: Sequence,
439 ) -> Self {
440 let seq_ack_path = SeqAckPath::new(&port_id, &chan_id);
441 self.ibc_store
442 .store_next_sequence_ack(&seq_ack_path, seq_number)
443 .expect("error writing to store");
444 self
445 }
446
447 pub fn with_packet_commitment(
451 mut self,
452 port_id: PortId,
453 chan_id: ChannelId,
454 seq: Sequence,
455 data: PacketCommitment,
456 ) -> Self {
457 let commitment_path = CommitmentPath::new(&port_id, &chan_id, seq);
458 self.ibc_store
459 .store_packet_commitment(&commitment_path, data)
460 .expect("error writing to store");
461 self
462 }
463
464 pub fn validate(&mut self, msg: MsgEnvelope) -> Result<(), HandlerError> {
466 validate(&self.ibc_store, &self.ibc_router, msg)
467 }
468
469 pub fn execute(&mut self, msg: MsgEnvelope) -> Result<(), HandlerError> {
471 execute(&mut self.ibc_store, &mut self.ibc_router, msg)
472 }
473
474 pub fn dispatch(&mut self, msg: MsgEnvelope) -> Result<(), HandlerError> {
476 dispatch(&mut self.ibc_store, &mut self.ibc_router, msg)
477 }
478
479 pub fn deliver(&mut self, msg: MsgEnvelope) -> Result<(), HandlerError> {
483 self.dispatch(msg)?;
484
485 self.advance_block_height();
487 Ok(())
488 }
489
490 pub fn get_events(&self) -> Vec<IbcEvent> {
492 self.ibc_store.events.lock().clone()
493 }
494
495 pub fn get_logs(&self) -> Vec<String> {
497 self.ibc_store.logs.lock().clone()
498 }
499}
500
501#[cfg(test)]
502mod tests {
503 use ibc::core::client::context::consensus_state::ConsensusState;
504
505 use super::*;
506 use crate::hosts::{HostConsensusState, MockHost, TendermintHost};
507 use crate::testapp::ibc::core::types::DefaultIbcStore;
508
509 #[test]
510 fn test_mock_history_validation() {
511 pub struct Test<H: TestHost>
512 where
513 H: TestHost,
514 HostConsensusState<H>: ConsensusState,
515 HostClientState<H>: ClientStateValidation<DefaultIbcStore>,
516 {
517 name: String,
518 ctx: TestContext<H>,
519 }
520
521 fn run_tests<H>(sub_title: &str)
522 where
523 H: TestHost,
524 HostConsensusState<H>: ConsensusState,
525 HostClientState<H>: ClientStateValidation<DefaultIbcStore>,
526 {
527 let cv = 0; let tests: Vec<Test<H>> = vec![
530 Test {
531 name: "Empty history, small pruning window".to_string(),
532 ctx: dummy_store_generic_test_context()
533 .latest_height(Height::new(cv, 1).expect("Never fails"))
534 .call(),
535 },
536 Test {
537 name: "Large pruning window".to_string(),
538 ctx: dummy_store_generic_test_context()
539 .latest_height(Height::new(cv, 2).expect("Never fails"))
540 .call(),
541 },
542 Test {
543 name: "Small pruning window".to_string(),
544 ctx: dummy_store_generic_test_context()
545 .latest_height(Height::new(cv, 30).expect("Never fails"))
546 .call(),
547 },
548 Test {
549 name: "Small pruning window, small starting height".to_string(),
550 ctx: dummy_store_generic_test_context()
551 .latest_height(Height::new(cv, 2).expect("Never fails"))
552 .call(),
553 },
554 ];
563
564 for mut test in tests {
565 assert!(
567 test.ctx.host.validate().is_ok(),
568 "failed in test [{}] {} while validating context {:?}",
569 sub_title,
570 test.name,
571 test.ctx
572 );
573
574 let current_height = test.ctx.latest_height();
575
576 test.ctx.advance_block_height();
578 assert!(
579 test.ctx.host.validate().is_ok(),
580 "failed in test [{}] {} while validating context {:?}",
581 sub_title,
582 test.name,
583 test.ctx
584 );
585
586 let next_height = current_height.increment();
587 assert_eq!(
588 test.ctx.latest_height(),
589 next_height,
590 "failed while increasing height for context {:?}",
591 test.ctx
592 );
593
594 assert_eq!(
595 test.ctx
596 .host
597 .get_block(¤t_height)
598 .expect("Never fails")
599 .height(),
600 current_height,
601 "failed while fetching height {:?} of context {:?}",
602 current_height,
603 test.ctx
604 );
605 }
606 }
607
608 run_tests::<MockHost>("Mock Host");
609 run_tests::<TendermintHost>("Synthetic TM Host");
610 }
611}