ibc_core_host_cosmos/
validate_self_client.rs

1use core::time::Duration;
2
3use ibc_client_tendermint::types::ClientState as TmClientState;
4use ibc_core_client_types::Height;
5use ibc_core_commitment_types::specs::ProofSpecs;
6use ibc_core_host_types::error::HostError;
7use ibc_core_host_types::identifiers::ChainId;
8use ibc_primitives::prelude::*;
9use tendermint::trust_threshold::TrustThresholdFraction as TendermintTrustThresholdFraction;
10
11/// Provides a default implementation intended for implementing the
12/// `ValidationContext::validate_self_client` API.
13///
14/// This validation logic tailored for Tendermint client states of a host chain
15/// operating across various counterparty chains.
16pub trait ValidateSelfClientContext {
17    fn validate_self_tendermint_client(
18        &self,
19        client_state_of_host_on_counterparty: TmClientState,
20    ) -> Result<(), HostError> {
21        client_state_of_host_on_counterparty
22            .validate()
23            .map_err(|e| {
24                HostError::invalid_state(format!(
25                    "counterparty client state could not be validated: {e}"
26                ))
27            })?;
28
29        if client_state_of_host_on_counterparty.is_frozen() {
30            return Err(HostError::invalid_state("client unexpectedly frozen"));
31        }
32
33        let self_chain_id = self.chain_id();
34
35        if self_chain_id != &client_state_of_host_on_counterparty.chain_id {
36            return Err(HostError::invalid_state(format!(
37                "chain ID: expected `{}`, actual `{}`",
38                self_chain_id, client_state_of_host_on_counterparty.chain_id
39            )));
40        }
41
42        let latest_height = client_state_of_host_on_counterparty.latest_height;
43        let self_revision_number = self_chain_id.revision_number();
44
45        if self_revision_number != latest_height.revision_number() {
46            return Err(HostError::invalid_state(format!(
47                "mismatched client revision numbers; expected `{}`, actual `{}`",
48                self_revision_number,
49                latest_height.revision_number()
50            )));
51        }
52
53        if latest_height >= self.host_current_height() {
54            return Err(HostError::invalid_state(format!(
55                "client latest height `{}` should be less than chain height `{}`",
56                latest_height,
57                self.host_current_height()
58            )));
59        }
60
61        if self.proof_specs() != &client_state_of_host_on_counterparty.proof_specs {
62            return Err(HostError::invalid_state(format!(
63                "client proof specs; expected `{:?}`, actual `{:?}`",
64                self.proof_specs(),
65                client_state_of_host_on_counterparty.proof_specs
66            )));
67        }
68
69        let _ = {
70            let trust_level = client_state_of_host_on_counterparty.trust_level;
71
72            TendermintTrustThresholdFraction::new(
73                trust_level.numerator(),
74                trust_level.denominator(),
75            )
76            .map_err(HostError::invalid_state)?
77        };
78
79        if self.unbonding_period() != client_state_of_host_on_counterparty.unbonding_period {
80            return Err(HostError::invalid_state(format!(
81                "unbonding period; expected `{:?}`, actual `{:?}`",
82                self.unbonding_period(),
83                client_state_of_host_on_counterparty.unbonding_period,
84            )));
85        }
86
87        if client_state_of_host_on_counterparty.unbonding_period
88            < client_state_of_host_on_counterparty.trusting_period
89        {
90            return Err(HostError::invalid_state(format!(
91                "counterparty client state: unbonding period must be greater than trusting period; unbonding period ({:?}) < trusting period ({:?})",
92                client_state_of_host_on_counterparty.unbonding_period,
93                client_state_of_host_on_counterparty.trusting_period
94            )));
95        }
96
97        if !client_state_of_host_on_counterparty.upgrade_path.is_empty()
98            && self.upgrade_path() != client_state_of_host_on_counterparty.upgrade_path
99        {
100            return Err(HostError::invalid_state(format!(
101                "upgrade path; expected `{:?}`, actual `{:?}`",
102                self.upgrade_path(),
103                client_state_of_host_on_counterparty.upgrade_path
104            )));
105        }
106
107        Ok(())
108    }
109
110    /// Returns the chain id of the host
111    fn chain_id(&self) -> &ChainId;
112
113    /// Returns the host current height
114    fn host_current_height(&self) -> Height;
115
116    /// Returns the proof specs of the host
117    fn proof_specs(&self) -> &ProofSpecs;
118
119    /// Returns the unbonding period of the host
120    fn unbonding_period(&self) -> Duration;
121
122    /// Returns the host upgrade path. May be empty.
123    fn upgrade_path(&self) -> &[String];
124}