ibc_testkit/hosts/
tendermint.rs

1use core::str::FromStr;
2
3use bon::Builder;
4use ibc::clients::tendermint::client_state::ClientState;
5use ibc::clients::tendermint::consensus_state::ConsensusState;
6use ibc::clients::tendermint::types::proto::v1::Header as RawHeader;
7use ibc::clients::tendermint::types::{Header, TENDERMINT_HEADER_TYPE_URL};
8use ibc::core::client::types::Height;
9use ibc::core::host::types::identifiers::ChainId;
10use ibc::core::primitives::prelude::*;
11use ibc::core::primitives::Timestamp;
12use ibc::primitives::proto::Any;
13use ibc::primitives::{IntoHostTime, IntoTimestamp, ToVec};
14use tendermint::block::Header as TmHeader;
15use tendermint::validator::Set as ValidatorSet;
16use tendermint_testgen::light_block::TmLightBlock;
17use tendermint_testgen::{
18    Generator, Header as TestgenHeader, LightBlock as TestgenLightBlock,
19    Validator as TestgenValidator,
20};
21
22use crate::fixtures::clients::tendermint::ClientStateConfig;
23use crate::hosts::{TestBlock, TestHeader, TestHost};
24
25/// A host that produces Tendermint blocks and interfaces with Tendermint light clients.
26#[derive(Debug, Builder)]
27pub struct TendermintHost {
28    /// Unique identifier for the chain.
29    #[builder(default = ChainId::new("mock-0").expect("Never fails"))]
30    pub chain_id: ChainId,
31    /// The chain of blocks underlying this context.
32    #[builder(default)]
33    pub history: Vec<TmLightBlock>,
34}
35
36impl Default for TendermintHost {
37    fn default() -> Self {
38        Self::builder().build()
39    }
40}
41
42impl TestHost for TendermintHost {
43    type Block = TmLightBlock;
44    type BlockParams = BlockParams;
45    type LightClientParams = ClientStateConfig;
46    type ClientState = ClientState;
47
48    fn history(&self) -> &Vec<Self::Block> {
49        &self.history
50    }
51
52    fn push_block(&mut self, block: Self::Block) {
53        self.history.push(block);
54    }
55
56    fn generate_block(
57        &self,
58        commitment_root: Vec<u8>,
59        height: u64,
60        timestamp: Timestamp,
61        params: &Self::BlockParams,
62    ) -> Self::Block {
63        TestgenLightBlock::new_default_with_header(
64            TestgenHeader::new(&params.validators)
65                .app_hash(commitment_root.try_into().expect("infallible"))
66                .height(height)
67                .chain_id(self.chain_id.as_str())
68                .next_validators(&params.next_validators)
69                .time(timestamp.into_host_time().expect("Never fails")),
70        )
71        .validators(&params.validators)
72        .next_validators(&params.next_validators)
73        .generate()
74        .expect("Never fails")
75    }
76
77    fn generate_client_state(
78        &self,
79        latest_height: &Height,
80        params: &Self::LightClientParams,
81    ) -> Self::ClientState {
82        let client_state = ClientStateConfig::builder()
83            .trusting_period(params.trusting_period)
84            .max_clock_drift(params.max_clock_drift)
85            .unbonding_period(params.unbonding_period)
86            .proof_specs(params.proof_specs.clone())
87            .build()
88            .into_client_state(
89                self.chain_id.clone(),
90                self.get_block(latest_height)
91                    .expect("block exists")
92                    .height(),
93            )
94            .expect("never fails");
95
96        client_state.inner().validate().expect("never fails");
97
98        client_state
99    }
100}
101
102impl TestBlock for TmLightBlock {
103    type Header = TendermintHeader;
104
105    fn height(&self) -> Height {
106        Height::new(
107            ChainId::from_str(self.signed_header.header.chain_id.as_str())
108                .expect("Never fails")
109                .revision_number(),
110            self.signed_header.header.height.value(),
111        )
112        .expect("Never fails")
113    }
114
115    fn timestamp(&self) -> Timestamp {
116        self.signed_header
117            .header
118            .time
119            .into_timestamp()
120            .expect("Never fails")
121    }
122
123    fn into_header_with_trusted(self, trusted_block: &Self) -> Self::Header {
124        let mut header = TendermintHeader::from(self.clone());
125        header.set_trusted_height(trusted_block.height());
126        header.set_trusted_next_validators_set(trusted_block.validators.clone());
127        header
128    }
129}
130
131#[derive(Debug, Builder)]
132pub struct BlockParams {
133    pub validators: Vec<TestgenValidator>,
134    pub next_validators: Vec<TestgenValidator>,
135}
136
137impl BlockParams {
138    pub fn from_validator_history(validator_history: Vec<Vec<TestgenValidator>>) -> Vec<Self> {
139        validator_history
140            .windows(2)
141            .map(|vals| {
142                Self::builder()
143                    .validators(vals[0].clone())
144                    .next_validators(vals[1].clone())
145                    .build()
146            })
147            .collect()
148    }
149}
150
151impl Default for BlockParams {
152    fn default() -> Self {
153        Self::builder()
154            .validators(vec![
155                TestgenValidator::new("1").voting_power(50),
156                TestgenValidator::new("2").voting_power(50),
157            ])
158            .next_validators(vec![
159                TestgenValidator::new("1").voting_power(50),
160                TestgenValidator::new("2").voting_power(50),
161            ])
162            .build()
163    }
164}
165
166/// This wrapper type is needed to implement
167/// [`From`] traits [`Header`] to foreign types.
168#[derive(Debug, Clone)]
169pub struct TendermintHeader(Header);
170
171impl TendermintHeader {
172    pub fn set_trusted_height(&mut self, trusted_height: Height) {
173        self.0.trusted_height = trusted_height
174    }
175
176    pub fn set_trusted_next_validators_set(&mut self, trusted_next_validator_set: ValidatorSet) {
177        self.0.trusted_next_validator_set = trusted_next_validator_set
178    }
179
180    pub fn header(&self) -> &TmHeader {
181        &self.0.signed_header.header
182    }
183}
184
185impl TestHeader for TendermintHeader {
186    type ConsensusState = ConsensusState;
187
188    fn height(&self) -> Height {
189        Height::new(
190            ChainId::from_str(self.0.signed_header.header.chain_id.as_str())
191                .expect("Never fails")
192                .revision_number(),
193            self.0.signed_header.header.height.value(),
194        )
195        .expect("Never fails")
196    }
197
198    fn timestamp(&self) -> Timestamp {
199        self.0
200            .signed_header
201            .header
202            .time
203            .into_timestamp()
204            .expect("Never fails")
205    }
206}
207
208impl From<TendermintHeader> for Header {
209    fn from(header: TendermintHeader) -> Self {
210        header.0
211    }
212}
213
214impl From<TendermintHeader> for ConsensusState {
215    fn from(header: TendermintHeader) -> Self {
216        header.0.signed_header.header.into()
217    }
218}
219
220impl From<TmLightBlock> for TendermintHeader {
221    fn from(block: TmLightBlock) -> Self {
222        let trusted_height = block.height();
223
224        let TmLightBlock {
225            signed_header,
226            validators: validator_set,
227            ..
228        } = block;
229
230        let trusted_next_validator_set = validator_set.clone();
231
232        // by default trust the current height and validators
233        Self(Header {
234            signed_header,
235            validator_set,
236            trusted_height,
237            trusted_next_validator_set,
238        })
239    }
240}
241
242impl From<TendermintHeader> for Any {
243    fn from(value: TendermintHeader) -> Self {
244        Self {
245            type_url: TENDERMINT_HEADER_TYPE_URL.to_string(),
246            value: RawHeader::from(value.0).to_vec(),
247        }
248    }
249}