ibc_testkit/fixtures/clients/
tendermint.rs

1use core::str::FromStr;
2use core::time::Duration;
3
4use basecoin_store::avl::get_proof_spec as basecoin_proof_spec;
5use bon::Builder;
6use ibc::clients::tendermint::client_state::ClientState as TmClientState;
7use ibc::clients::tendermint::types::error::TendermintClientError;
8use ibc::clients::tendermint::types::proto::v1::{ClientState as RawTmClientState, Fraction};
9#[cfg(feature = "serde")]
10use ibc::clients::tendermint::types::Header;
11use ibc::clients::tendermint::types::{
12    AllowUpdate, ClientState as ClientStateType, TrustThreshold,
13};
14use ibc::core::client::types::proto::v1::Height as RawHeight;
15use ibc::core::client::types::Height;
16use ibc::core::commitment_types::specs::ProofSpecs;
17use ibc::core::host::types::error::DecodingError;
18use ibc::core::host::types::identifiers::ChainId;
19use ibc::core::primitives::prelude::*;
20use tendermint::block::Header as TmHeader;
21
22/// Returns a dummy tendermint `ClientState` by given `frozen_height`, for testing purposes only!
23pub fn dummy_tm_client_state_from_raw(
24    frozen_height: RawHeight,
25) -> Result<TmClientState, DecodingError> {
26    ClientStateType::try_from(dummy_raw_tm_client_state(frozen_height)).map(TmClientState::from)
27}
28
29/// Returns a dummy tendermint `ClientState` from a `TmHeader`, for testing purposes only!
30pub fn dummy_tm_client_state_from_header(tm_header: TmHeader) -> TmClientState {
31    let chain_id = ChainId::from_str(tm_header.chain_id.as_str()).expect("Never fails");
32    let client_state = ClientStateType::new(
33        chain_id.clone(),
34        TrustThreshold::ONE_THIRD,
35        Duration::from_secs(64000),
36        Duration::from_secs(128_000),
37        Duration::from_millis(3000),
38        Height::new(chain_id.revision_number(), u64::from(tm_header.height)).expect("Never fails"),
39        ProofSpecs::cosmos(),
40        Vec::new(),
41        AllowUpdate {
42            after_expiry: false,
43            after_misbehaviour: false,
44        },
45    )
46    .expect("Never fails");
47
48    TmClientState::from(client_state)
49}
50
51/// Returns a dummy tendermint `RawTmClientState` by given `frozen_height`, for testing purposes only!
52pub fn dummy_raw_tm_client_state(frozen_height: RawHeight) -> RawTmClientState {
53    #[allow(deprecated)]
54    RawTmClientState {
55        chain_id: ChainId::new("ibc-0").expect("Never fails").to_string(),
56        trust_level: Some(Fraction {
57            numerator: 1,
58            denominator: 3,
59        }),
60        trusting_period: Some(Duration::from_secs(64000).try_into().expect("no error")),
61        unbonding_period: Some(Duration::from_secs(128_000).try_into().expect("no error")),
62        max_clock_drift: Some(Duration::from_millis(3000).try_into().expect("no error")),
63        latest_height: Some(Height::new(0, 10).expect("Never fails").into()),
64        proof_specs: ProofSpecs::cosmos().into(),
65        upgrade_path: Vec::new(),
66        frozen_height: Some(frozen_height),
67        allow_update_after_expiry: false,
68        allow_update_after_misbehaviour: false,
69    }
70}
71
72#[derive(Debug, Builder)]
73pub struct ClientStateConfig {
74    #[builder(default = TrustThreshold::ONE_THIRD)]
75    pub trust_level: TrustThreshold,
76    #[builder(default = Duration::from_secs(64000))]
77    pub trusting_period: Duration,
78    #[builder(default = Duration::from_secs(128_000))]
79    pub unbonding_period: Duration,
80    #[builder(default = Duration::from_millis(3000))]
81    pub max_clock_drift: Duration,
82    #[builder(default = vec![basecoin_proof_spec(); 2].try_into().expect("no error"))]
83    pub proof_specs: ProofSpecs,
84    #[builder(default)]
85    pub upgrade_path: Vec<String>,
86    #[builder(default = AllowUpdate { after_expiry: false, after_misbehaviour: false })]
87    allow_update: AllowUpdate,
88}
89
90impl Default for ClientStateConfig {
91    fn default() -> Self {
92        Self::builder().build()
93    }
94}
95
96impl ClientStateConfig {
97    pub fn into_client_state(
98        self,
99        chain_id: ChainId,
100        latest_height: Height,
101    ) -> Result<TmClientState, TendermintClientError> {
102        Ok(ClientStateType::new(
103            chain_id,
104            self.trust_level,
105            self.trusting_period,
106            self.unbonding_period,
107            self.max_clock_drift,
108            latest_height,
109            self.proof_specs,
110            self.upgrade_path,
111            self.allow_update,
112        )?
113        .into())
114    }
115}
116
117#[cfg(feature = "serde")]
118pub fn dummy_valid_tendermint_header() -> tendermint::block::Header {
119    use tendermint::block::signed_header::SignedHeader;
120
121    serde_json::from_str::<SignedHeader>(include_str!(concat!(
122        env!("CARGO_MANIFEST_DIR"),
123        "/src/data/json/valid_signed_header.json"
124    )))
125    .expect("Never fails")
126    .header
127}
128
129#[cfg(feature = "serde")]
130pub fn dummy_expired_tendermint_header() -> tendermint::block::Header {
131    use tendermint::block::signed_header::SignedHeader;
132
133    serde_json::from_str::<SignedHeader>(include_str!(concat!(
134        env!("CARGO_MANIFEST_DIR"),
135        "/src/data/json/expired_signed_header.json"
136    )))
137    .expect("Never fails")
138    .header
139}
140
141// TODO: This should be replaced with a ::default() or ::produce().
142// The implementation of this function comprises duplicate code (code borrowed from
143// `tendermint-rs` for assembling a Header).
144// See https://github.com/informalsystems/tendermint-rs/issues/381.
145//
146// The normal flow is:
147// - get the (trusted) signed header and the `trusted_validator_set` at a `trusted_height`
148// - get the `signed_header` and the `validator_set` at latest height
149// - build the ics07 Header
150// For testing purposes this function does:
151// - get the `signed_header` from a .json file
152// - create the `validator_set` with a single validator that is also the proposer
153// - assume a `trusted_height` of 1 and no change in the validator set since height 1,
154//   i.e. `trusted_validator_set` = `validator_set`
155#[cfg(feature = "serde")]
156pub fn dummy_ics07_header() -> Header {
157    use subtle_encoding::hex;
158    use tendermint::block::signed_header::SignedHeader;
159    use tendermint::validator::{Info as ValidatorInfo, Set as ValidatorSet};
160    use tendermint::PublicKey;
161
162    // Build a SignedHeader from a JSON file.
163    let shdr = serde_json::from_str::<SignedHeader>(include_str!(concat!(
164        env!("CARGO_MANIFEST_DIR"),
165        "/src/data/json/valid_signed_header.json"
166    )))
167    .expect("Never fails");
168
169    // Build a set of validators.
170    // Below are test values inspired form `test_validator_set()` in tendermint-rs.
171    let v1 = ValidatorInfo::new(
172        PublicKey::from_raw_ed25519(
173            &hex::decode_upper("F349539C7E5EF7C49549B09C4BFC2335318AB0FE51FBFAA2433B4F13E816F4A7")
174                .expect("Never fails"),
175        )
176        .expect("Never fails"),
177        281_815_u64.try_into().expect("Never fails"),
178    );
179
180    let vs = ValidatorSet::new(vec![v1.clone()], Some(v1));
181
182    Header {
183        signed_header: shdr,
184        validator_set: vs.clone(),
185        trusted_height: Height::min(0),
186        trusted_next_validator_set: vs,
187    }
188}
189
190#[cfg(all(test, feature = "serde"))]
191mod tests {
192    use ibc::primitives::proto::Any;
193    use rstest::rstest;
194
195    use super::*;
196
197    #[rstest]
198    // try conversions for when the client is not frozen
199    #[case::valid_client(0, 0, false)]
200    // try conversions for when the client is frozen
201    #[case::frozen_client(0, 1, true)]
202    fn tm_client_state_conversions_healthy(
203        #[case] revision_number: u64,
204        #[case] revision_height: u64,
205        #[case] is_frozen: bool,
206    ) {
207        let frozen_height = RawHeight {
208            revision_number,
209            revision_height,
210        };
211
212        // check client state creation path from a proto type
213        let tm_client_state_from_raw = dummy_tm_client_state_from_raw(frozen_height);
214        assert!(tm_client_state_from_raw.is_ok());
215
216        let any_from_tm_client_state = Any::from(
217            tm_client_state_from_raw
218                .as_ref()
219                .expect("Never fails")
220                .clone(),
221        );
222        let tm_client_state_from_any = ClientStateType::try_from(any_from_tm_client_state);
223        assert!(tm_client_state_from_any.is_ok());
224        assert_eq!(
225            Some(is_frozen),
226            tm_client_state_from_any
227                .as_ref()
228                .map(|x| x.is_frozen())
229                .ok()
230        );
231        assert_eq!(
232            tm_client_state_from_raw.expect("Never fails"),
233            tm_client_state_from_any.expect("Never fails").into()
234        );
235    }
236
237    #[test]
238    fn tm_client_state_from_header_healthy() {
239        // check client state creation path from a tendermint header
240        let tm_header = dummy_valid_tendermint_header();
241        let tm_client_state_from_header = dummy_tm_client_state_from_header(tm_header);
242        let any_from_header = Any::from(tm_client_state_from_header.clone());
243        let tm_client_state_from_any = ClientStateType::try_from(any_from_header);
244        assert!(tm_client_state_from_any.is_ok());
245        assert_eq!(
246            tm_client_state_from_header,
247            tm_client_state_from_any.expect("Never fails").into()
248        );
249    }
250}