ibc_client_tendermint/client_state/
common.rs

1use core::time::Duration;
2
3use ibc_client_tendermint_types::{client_type as tm_client_type, ClientState as ClientStateType};
4use ibc_core_client::context::client_state::ClientStateCommon;
5use ibc_core_client::context::consensus_state::ConsensusState;
6use ibc_core_client::types::error::{ClientError, UpgradeClientError};
7use ibc_core_client::types::{Height, Status};
8use ibc_core_commitment_types::commitment::{
9    CommitmentPrefix, CommitmentProofBytes, CommitmentRoot,
10};
11use ibc_core_commitment_types::error::CommitmentError;
12use ibc_core_commitment_types::merkle::{MerklePath, MerkleProof};
13use ibc_core_commitment_types::proto::ics23::{HostFunctionsManager, HostFunctionsProvider};
14use ibc_core_commitment_types::specs::ProofSpecs;
15use ibc_core_host::types::identifiers::ClientType;
16use ibc_core_host::types::path::{
17    Path, PathBytes, UpgradeClientStatePath, UpgradeConsensusStatePath,
18};
19use ibc_primitives::prelude::*;
20use ibc_primitives::proto::Any;
21use ibc_primitives::{Timestamp, ToVec};
22
23use super::ClientState;
24use crate::consensus_state::ConsensusState as TmConsensusState;
25
26impl ClientStateCommon for ClientState {
27    fn verify_consensus_state(
28        &self,
29        consensus_state: Any,
30        host_timestamp: &Timestamp,
31    ) -> Result<(), ClientError> {
32        verify_consensus_state(
33            consensus_state,
34            host_timestamp,
35            self.inner().trusting_period,
36        )
37    }
38
39    fn client_type(&self) -> ClientType {
40        tm_client_type()
41    }
42
43    fn latest_height(&self) -> Height {
44        self.0.latest_height
45    }
46
47    fn validate_proof_height(&self, proof_height: Height) -> Result<(), ClientError> {
48        validate_proof_height(self.inner(), proof_height)
49    }
50
51    fn verify_upgrade_client(
52        &self,
53        upgraded_client_state: Any,
54        upgraded_consensus_state: Any,
55        proof_upgrade_client: CommitmentProofBytes,
56        proof_upgrade_consensus_state: CommitmentProofBytes,
57        root: &CommitmentRoot,
58    ) -> Result<(), ClientError> {
59        let last_height = self.latest_height().revision_height();
60
61        // The client state's upgrade path vector needs to parsed into a tuple in the form
62        // of `(upgrade_path_prefix, upgrade_path)`. Given the length of the client
63        // state's upgrade path vector, the following determinations are made:
64        // 1: The commitment prefix is left empty and the upgrade path is used as-is.
65        // 2: The commitment prefix and upgrade path are both taken as-is.
66        let upgrade_path = &self.inner().upgrade_path;
67        let (upgrade_path_prefix, upgrade_path) = match upgrade_path.len() {
68            0 => {
69                return Err(UpgradeClientError::MissingUpgradePath.into());
70            }
71            1 => (CommitmentPrefix::empty(), upgrade_path[0].clone()),
72            2 => (
73                upgrade_path[0].as_bytes().to_vec().into(),
74                upgrade_path[1].clone(),
75            ),
76            _ => {
77                return Err(UpgradeClientError::InvalidUpgradePath {
78                    description: "upgrade path is too long".to_string(),
79                }
80                .into());
81            }
82        };
83
84        let upgrade_client_path_bytes =
85            self.serialize_path(Path::UpgradeClientState(UpgradeClientStatePath {
86                upgrade_path: upgrade_path.clone(),
87                height: last_height,
88            }))?;
89
90        let upgrade_consensus_path_bytes =
91            self.serialize_path(Path::UpgradeConsensusState(UpgradeConsensusStatePath {
92                upgrade_path,
93                height: last_height,
94            }))?;
95
96        verify_upgrade_client::<HostFunctionsManager>(
97            self.inner(),
98            upgraded_client_state,
99            upgraded_consensus_state,
100            proof_upgrade_client,
101            proof_upgrade_consensus_state,
102            upgrade_path_prefix,
103            upgrade_client_path_bytes,
104            upgrade_consensus_path_bytes,
105            root,
106        )
107    }
108
109    fn serialize_path(&self, path: Path) -> Result<PathBytes, ClientError> {
110        Ok(path.to_string().into_bytes().into())
111    }
112
113    fn verify_membership_raw(
114        &self,
115        prefix: &CommitmentPrefix,
116        proof: &CommitmentProofBytes,
117        root: &CommitmentRoot,
118        path: PathBytes,
119        value: Vec<u8>,
120    ) -> Result<(), ClientError> {
121        verify_membership::<HostFunctionsManager>(
122            &self.inner().proof_specs,
123            prefix,
124            proof,
125            root,
126            path,
127            value,
128        )
129    }
130
131    fn verify_non_membership_raw(
132        &self,
133        prefix: &CommitmentPrefix,
134        proof: &CommitmentProofBytes,
135        root: &CommitmentRoot,
136        path: PathBytes,
137    ) -> Result<(), ClientError> {
138        verify_non_membership::<HostFunctionsManager>(
139            &self.inner().proof_specs,
140            prefix,
141            proof,
142            root,
143            path,
144        )
145    }
146}
147
148/// Verify an `Any` consensus state by attempting to convert it to a `TmConsensusState`.
149/// Also checks whether the converted consensus state's root is present.
150///
151/// Note that this function is typically implemented as part of the
152/// [`ClientStateCommon`] trait, but has been made a standalone function
153/// in order to make the ClientState APIs more flexible.
154pub fn verify_consensus_state(
155    consensus_state: Any,
156    host_timestamp: &Timestamp,
157    trusting_period: Duration,
158) -> Result<(), ClientError> {
159    let tm_consensus_state = TmConsensusState::try_from(consensus_state)?;
160
161    if tm_consensus_state.root().is_empty() {
162        Err(CommitmentError::MissingCommitmentRoot)?;
163    };
164
165    if consensus_state_status(&tm_consensus_state, host_timestamp, trusting_period)?.is_expired() {
166        return Err(ClientError::InvalidStatus(Status::Expired));
167    }
168
169    Ok(())
170}
171
172/// Determines the `Status`, whether it is `Active` or `Expired`, of a consensus
173/// state, using its timestamp, the host's timestamp, and the trusting period.
174pub fn consensus_state_status<CS: ConsensusState>(
175    consensus_state: &CS,
176    host_timestamp: &Timestamp,
177    trusting_period: Duration,
178) -> Result<Status, ClientError> {
179    // Note: if the `duration_since()` is `None`, indicating that the latest
180    // consensus state is in the future, then we don't consider the client
181    // to be expired.
182    if let Some(elapsed_since_latest_consensus_state) =
183        host_timestamp.duration_since(&consensus_state.timestamp()?)
184    {
185        // Note: The equality is considered as expired to stay consistent with
186        // the check in tendermint-rs, where a header at `trusted_header_time +
187        // trusting_period` is considered expired.
188        if elapsed_since_latest_consensus_state >= trusting_period {
189            return Ok(Status::Expired);
190        }
191    }
192
193    Ok(Status::Active)
194}
195
196/// Validate the given proof height against the client state's latest height, returning
197/// an error if the proof height is greater than the latest height of the client state.
198///
199/// Note that this function is typically implemented as part of the
200/// [`ClientStateCommon`] trait, but has been made a standalone function
201/// in order to make the ClientState APIs more flexible.
202pub fn validate_proof_height(
203    client_state: &ClientStateType,
204    proof_height: Height,
205) -> Result<(), ClientError> {
206    let latest_height = client_state.latest_height;
207
208    if latest_height < proof_height {
209        return Err(ClientError::InsufficientProofHeight {
210            actual: latest_height,
211            expected: proof_height,
212        });
213    }
214
215    Ok(())
216}
217
218/// Perform client-specific verifications and check all data in the new
219/// client state to be the same across all valid Tendermint clients for the
220/// new chain.
221///
222/// You can learn more about how to upgrade IBC-connected SDK chains in
223/// [this](https://ibc.cosmos.network/main/ibc/upgrades/quick-guide.html)
224/// guide.
225///
226/// Note that this function is typically implemented as part of the
227/// [`ClientStateCommon`] trait, but has been made a standalone function
228/// in order to make the ClientState APIs more flexible.
229#[allow(clippy::too_many_arguments)]
230pub fn verify_upgrade_client<H: HostFunctionsProvider>(
231    client_state: &ClientStateType,
232    upgraded_client_state: Any,
233    upgraded_consensus_state: Any,
234    proof_upgrade_client: CommitmentProofBytes,
235    proof_upgrade_consensus_state: CommitmentProofBytes,
236    upgrade_path_prefix: CommitmentPrefix,
237    upgrade_client_path_bytes: PathBytes,
238    upgrade_consensus_path_bytes: PathBytes,
239    root: &CommitmentRoot,
240) -> Result<(), ClientError> {
241    // Make sure that the client type is of Tendermint type `ClientState`
242    let upgraded_tm_client_state = ClientState::try_from(upgraded_client_state.clone())?;
243
244    // Make sure that the consensus type is of Tendermint type `ConsensusState`
245    TmConsensusState::try_from(upgraded_consensus_state.clone())?;
246
247    let latest_height = client_state.latest_height;
248    let upgraded_tm_client_state_height = upgraded_tm_client_state.latest_height();
249
250    // Make sure the latest height of the current client is not greater then
251    // the upgrade height This condition checks both the revision number and
252    // the height
253    if latest_height >= upgraded_tm_client_state_height {
254        Err(UpgradeClientError::InsufficientUpgradeHeight {
255            upgraded_height: upgraded_tm_client_state_height,
256            client_height: latest_height,
257        })?
258    }
259
260    // Verify the proof of the upgraded client state
261    verify_membership::<H>(
262        &client_state.proof_specs,
263        &upgrade_path_prefix,
264        &proof_upgrade_client,
265        root,
266        upgrade_client_path_bytes,
267        upgraded_client_state.to_vec(),
268    )?;
269
270    // Verify the proof of the upgraded consensus state
271    verify_membership::<H>(
272        &client_state.proof_specs,
273        &upgrade_path_prefix,
274        &proof_upgrade_consensus_state,
275        root,
276        upgrade_consensus_path_bytes,
277        upgraded_consensus_state.to_vec(),
278    )?;
279
280    Ok(())
281}
282
283/// Verify membership of the given value against the client's merkle proof.
284///
285/// Note that this function is typically implemented as part of the
286/// [`ClientStateCommon`] trait, but has been made a standalone function
287/// in order to make the ClientState APIs more flexible.
288pub fn verify_membership<H: HostFunctionsProvider>(
289    proof_specs: &ProofSpecs,
290    prefix: &CommitmentPrefix,
291    proof: &CommitmentProofBytes,
292    root: &CommitmentRoot,
293    path: PathBytes,
294    value: Vec<u8>,
295) -> Result<(), ClientError> {
296    if prefix.is_empty() {
297        Err(CommitmentError::MissingCommitmentPrefix)?;
298    }
299
300    let merkle_path = MerklePath::new(vec![prefix.as_bytes().to_vec().into(), path]);
301
302    let merkle_proof = MerkleProof::try_from(proof)?;
303
304    merkle_proof.verify_membership::<H>(proof_specs, root.clone().into(), merkle_path, value, 0)?;
305
306    Ok(())
307}
308
309/// Verify that the given value does not belong in the client's merkle proof.
310///
311/// Note that this function is typically implemented as part of the
312/// [`ClientStateCommon`] trait, but has been made a standalone function
313/// in order to make the ClientState APIs more flexible.
314pub fn verify_non_membership<H: HostFunctionsProvider>(
315    proof_specs: &ProofSpecs,
316    prefix: &CommitmentPrefix,
317    proof: &CommitmentProofBytes,
318    root: &CommitmentRoot,
319    path: PathBytes,
320) -> Result<(), ClientError> {
321    let merkle_path = MerklePath::new(vec![prefix.as_bytes().to_vec().into(), path]);
322
323    let merkle_proof = MerkleProof::try_from(proof)?;
324
325    merkle_proof.verify_non_membership::<H>(proof_specs, root.clone().into(), merkle_path)?;
326
327    Ok(())
328}