ibc_testkit/hosts/
mod.rs

1pub mod mock;
2pub mod tendermint;
3
4use core::fmt::Debug;
5use core::ops::Add;
6use core::time::Duration;
7
8use ibc::core::client::context::consensus_state::ConsensusState;
9use ibc::core::client::types::Height;
10use ibc::core::primitives::prelude::*;
11use ibc::core::primitives::Timestamp;
12use ibc::primitives::proto::Any;
13
14pub use self::mock::MockHost;
15pub use self::tendermint::TendermintHost;
16use crate::testapp::ibc::clients::{AnyClientState, AnyConsensusState};
17
18pub type HostClientState<H> = <H as TestHost>::ClientState;
19pub type HostBlock<H> = <H as TestHost>::Block;
20pub type HostBlockParams<H> = <H as TestHost>::BlockParams;
21pub type HostLightClientParams<H> = <H as TestHost>::LightClientParams;
22pub type HostHeader<H> = <HostBlock<H> as TestBlock>::Header;
23pub type HostConsensusState<H> = <HostHeader<H> as TestHeader>::ConsensusState;
24
25/// TestHost is a trait that defines the interface for a host blockchain.
26pub trait TestHost: Default + Debug + Sized {
27    /// The type of block produced by the host.
28    type Block: TestBlock;
29
30    /// The type of client state produced by the host.
31    type ClientState: Into<AnyClientState> + Debug;
32
33    /// The type of block parameter to produce a block.
34    type BlockParams: Debug + Default;
35
36    /// The type of light client parameters to produce a light client state.
37    type LightClientParams: Debug + Default;
38
39    /// The history of blocks produced by the host chain.
40    fn history(&self) -> &Vec<Self::Block>;
41
42    /// Returns true if the host chain has no blocks.
43    fn is_empty(&self) -> bool {
44        self.history().is_empty()
45    }
46
47    /// The latest height of the host chain.
48    fn latest_height(&self) -> Height {
49        self.latest_block().height()
50    }
51
52    /// The latest block of the host chain.
53    fn latest_block(&self) -> Self::Block {
54        self.history().last().cloned().expect("no error")
55    }
56
57    /// Get the block at the given height.
58    fn get_block(&self, target_height: &Height) -> Option<Self::Block> {
59        self.history()
60            .get(target_height.revision_height() as usize - 1)
61            .cloned() // indexed from 1
62    }
63
64    /// Add a block to the host chain.
65    fn push_block(&mut self, block: Self::Block);
66
67    /// Commit a block with commitment root to the blockchain, by extending the history of blocks.
68    fn commit_block(
69        &mut self,
70        commitment_root: Vec<u8>,
71        block_time: Duration,
72        params: &Self::BlockParams,
73    ) {
74        let latest_block = self.latest_block();
75
76        let height = TestBlock::height(&latest_block)
77            .increment()
78            .revision_height();
79        let timestamp = TestBlock::timestamp(&latest_block)
80            .add(block_time)
81            .expect("Never fails");
82
83        let new_block = self.generate_block(commitment_root, height, timestamp, params);
84
85        // History is not full yet.
86        self.push_block(new_block);
87    }
88
89    /// Generate a block at the given height and timestamp, using the provided parameters.
90    fn generate_block(
91        &self,
92        commitment_root: Vec<u8>,
93        height: u64,
94        timestamp: Timestamp,
95        params: &Self::BlockParams,
96    ) -> Self::Block;
97
98    /// Generate a client state using the block at the given height and the provided parameters.
99    fn generate_client_state(
100        &self,
101        latest_height: &Height,
102        params: &Self::LightClientParams,
103    ) -> Self::ClientState;
104
105    fn validate(&self) -> Result<(), String> {
106        // Check that headers in the history are in sequential order.
107        let latest_height = self.latest_height();
108        let mut current_height = Height::min(latest_height.revision_number());
109
110        while current_height <= latest_height {
111            if current_height != self.get_block(&current_height).expect("no error").height() {
112                return Err("block height does not match".to_owned());
113            }
114            current_height = current_height.increment();
115        }
116        Ok(())
117    }
118}
119
120/// TestBlock is a trait that defines the interface for a block produced by a host blockchain.
121pub trait TestBlock: Clone + Debug {
122    /// The type of header can be extracted from the block.
123    type Header: TestHeader;
124
125    /// The height of the block.
126    fn height(&self) -> Height;
127
128    /// The timestamp of the block.
129    fn timestamp(&self) -> Timestamp;
130
131    /// Extract the IBC header using the target and trusted blocks.
132    fn into_header_with_trusted(self, trusted_block: &Self) -> Self::Header;
133
134    /// Extract the IBC header only using the target block (sets the trusted
135    /// block to itself).
136    fn into_header(self) -> Self::Header {
137        self.clone().into_header_with_trusted(&self)
138    }
139}
140
141/// TestHeader is a trait that defines the interface for a header
142/// submitted by relayer from the host blockchain.
143pub trait TestHeader: Clone + Debug + Into<Any> {
144    /// The type of consensus state can be extracted from the header.
145    type ConsensusState: ConsensusState + Into<AnyConsensusState> + From<Self> + Clone + Debug;
146
147    /// The height of the block, as recorded in the header.
148    fn height(&self) -> Height;
149
150    /// The timestamp of the block, as recorded in the header.
151    fn timestamp(&self) -> Timestamp;
152
153    /// Extract the consensus state from the header.
154    fn into_consensus_state(self) -> Self::ConsensusState {
155        Self::ConsensusState::from(self)
156    }
157}