ibc_client_tendermint/client_state/
validation.rs

1use ibc_client_tendermint_types::{
2    ClientState as ClientStateType, ConsensusState as ConsensusStateType, Header as TmHeader,
3    Misbehaviour as TmMisbehaviour, TENDERMINT_HEADER_TYPE_URL, TENDERMINT_MISBEHAVIOUR_TYPE_URL,
4};
5use ibc_core_client::context::client_state::ClientStateValidation;
6use ibc_core_client::context::{Convertible, ExtClientValidationContext};
7use ibc_core_client::types::error::ClientError;
8use ibc_core_client::types::Status;
9use ibc_core_host::types::identifiers::ClientId;
10use ibc_core_host::types::path::ClientConsensusStatePath;
11use ibc_primitives::prelude::*;
12use ibc_primitives::proto::Any;
13use tendermint::crypto::default::Sha256;
14use tendermint::crypto::Sha256 as Sha256Trait;
15use tendermint::merkle::MerkleHash;
16use tendermint_light_client_verifier::{ProdVerifier, Verifier};
17
18use super::{
19    check_for_misbehaviour_on_misbehavior, check_for_misbehaviour_on_update,
20    consensus_state_status, ClientState,
21};
22use crate::client_state::{verify_header, verify_misbehaviour};
23
24impl<V> ClientStateValidation<V> for ClientState
25where
26    V: ExtClientValidationContext,
27    ConsensusStateType: Convertible<V::ConsensusStateRef>,
28    <ConsensusStateType as TryFrom<V::ConsensusStateRef>>::Error: Into<ClientError>,
29{
30    /// The default verification logic exposed by ibc-rs simply delegates to a
31    /// standalone `verify_client_message` function. This is to make it as
32    /// simple as possible for those who merely need the default
33    /// [`ProdVerifier`] behaviour, as well as those who require custom
34    /// verification logic.
35    ///
36    /// In a situation where the Tendermint [`ProdVerifier`] doesn't provide the
37    /// desired outcome, users should define a custom verifier struct and then
38    /// implement the [`Verifier`] trait for it.
39    ///
40    /// In order to wire up the custom verifier, create a newtype `ClientState`
41    /// wrapper similar to [`ClientState`] and implement all client state traits
42    /// for it. For method implementation, the simplest way is to import and
43    /// call their analogous standalone versions under the
44    /// [`crate::client_state`] module, unless bespoke logic is desired for any
45    /// of those functions. Then, when it comes to implementing the
46    /// `verify_client_message` method, use the [`verify_client_message`]
47    /// function and pass your custom verifier object as the `verifier`
48    /// parameter.
49    fn verify_client_message(
50        &self,
51        ctx: &V,
52        client_id: &ClientId,
53        client_message: Any,
54    ) -> Result<(), ClientError> {
55        verify_client_message::<V, Sha256>(
56            self.inner(),
57            ctx,
58            client_id,
59            client_message,
60            &ProdVerifier::default(),
61        )
62    }
63
64    fn check_for_misbehaviour(
65        &self,
66        ctx: &V,
67        client_id: &ClientId,
68        client_message: Any,
69    ) -> Result<bool, ClientError> {
70        check_for_misbehaviour(self.inner(), ctx, client_id, client_message)
71    }
72
73    fn status(&self, ctx: &V, client_id: &ClientId) -> Result<Status, ClientError> {
74        status(self.inner(), ctx, client_id)
75    }
76
77    fn check_substitute(&self, _ctx: &V, substitute_client_state: Any) -> Result<(), ClientError> {
78        check_substitute::<V>(self.inner(), substitute_client_state)
79    }
80}
81
82/// Verify the client message as part of the client state validation process.
83///
84/// Note that this function is typically implemented as part of the
85/// [`ClientStateValidation`] trait, but has been made a standalone function in
86/// order to make the ClientState APIs more flexible. It mostly adheres to the
87/// same signature as the `ClientStateValidation::verify_client_message`
88/// function, except for an additional `verifier` parameter that allows users
89/// who require custom verification logic to easily pass in their own verifier
90/// implementation.
91pub fn verify_client_message<V, H>(
92    client_state: &ClientStateType,
93    ctx: &V,
94    client_id: &ClientId,
95    client_message: Any,
96    verifier: &impl Verifier,
97) -> Result<(), ClientError>
98where
99    V: ExtClientValidationContext,
100    ConsensusStateType: Convertible<V::ConsensusStateRef>,
101    <ConsensusStateType as TryFrom<V::ConsensusStateRef>>::Error: Into<ClientError>,
102    H: MerkleHash + Sha256Trait + Default,
103{
104    match client_message.type_url.as_str() {
105        TENDERMINT_HEADER_TYPE_URL => {
106            let header = TmHeader::try_from(client_message)?;
107            verify_header::<V, H>(
108                ctx,
109                &header,
110                client_id,
111                client_state.chain_id(),
112                &client_state.as_light_client_options()?,
113                verifier,
114            )
115        }
116        TENDERMINT_MISBEHAVIOUR_TYPE_URL => {
117            let misbehaviour = TmMisbehaviour::try_from(client_message)?;
118            verify_misbehaviour::<V, H>(
119                ctx,
120                &misbehaviour,
121                client_id,
122                client_state.chain_id(),
123                &client_state.as_light_client_options()?,
124                verifier,
125            )
126        }
127        _ => Err(ClientError::InvalidUpdateClientMessage),
128    }
129}
130
131/// Check for misbehaviour on the client state as part of the client state
132/// validation process.
133///
134/// Note that this function is typically implemented as part of the
135/// [`ClientStateValidation`] trait, but has been made a standalone function
136/// in order to make the ClientState APIs more flexible.
137///
138/// This method covers the following cases:
139///
140/// 1 - fork:
141/// Assumes at least one consensus state before the fork point exists. Let
142/// existing consensus states on chain B be: [Sn,.., Sf, Sf-1, S0] with
143/// `Sf-1` being the most recent state before the fork. Chain A is queried for
144/// a header `Hf'` at `Sf.height` and if it is different from the `Hf` in the
145/// event for the client update (the one that has generated `Sf` on chain),
146/// then the two headers are included in the evidence and submitted. Note
147/// that in this case the headers are different but have the same height.
148///
149/// 2 - BFT time violation for unavailable header (a.k.a. Future Lunatic
150/// Attack or FLA):
151/// Some header with a height that is higher than the latest height on A has
152/// been accepted and a consensus state was created on B. Note that this
153/// implies that the timestamp of this header must be within the
154/// `clock_drift` of the client. Assume the client on B has been updated
155/// with `h2`(not present on/ produced by chain A) and it has a timestamp of
156/// `t2` that is at most `clock_drift` in the future. Then the latest header
157/// from A is fetched, let it be `h1`, with a timestamp of `t1`. If `t1 >=
158/// t2` then evidence of misbehavior is submitted to A.
159///
160/// 3 - BFT time violation for existing headers:
161/// Ensure that consensus state times are monotonically increasing with
162/// height.
163pub fn check_for_misbehaviour<V>(
164    client_state: &ClientStateType,
165    ctx: &V,
166    client_id: &ClientId,
167    client_message: Any,
168) -> Result<bool, ClientError>
169where
170    V: ExtClientValidationContext,
171    ConsensusStateType: Convertible<V::ConsensusStateRef>,
172    <ConsensusStateType as TryFrom<V::ConsensusStateRef>>::Error: Into<ClientError>,
173{
174    match client_message.type_url.as_str() {
175        TENDERMINT_HEADER_TYPE_URL => {
176            let header = TmHeader::try_from(client_message)?;
177            check_for_misbehaviour_on_update(ctx, header, client_id, &client_state.latest_height)
178        }
179        TENDERMINT_MISBEHAVIOUR_TYPE_URL => {
180            let misbehaviour = TmMisbehaviour::try_from(client_message)?;
181            check_for_misbehaviour_on_misbehavior(misbehaviour.header1(), misbehaviour.header2())
182        }
183        _ => Err(ClientError::InvalidUpdateClientMessage),
184    }
185}
186
187/// Query the status of the client state.
188///
189/// Note that this function is typically implemented as part of the
190/// [`ClientStateValidation`] trait, but has been made a standalone function
191/// in order to make the ClientState APIs more flexible.
192pub fn status<V>(
193    client_state: &ClientStateType,
194    ctx: &V,
195    client_id: &ClientId,
196) -> Result<Status, ClientError>
197where
198    V: ExtClientValidationContext,
199    ConsensusStateType: Convertible<V::ConsensusStateRef>,
200    <ConsensusStateType as TryFrom<V::ConsensusStateRef>>::Error: Into<ClientError>,
201{
202    if client_state.is_frozen() {
203        return Ok(Status::Frozen);
204    }
205
206    let latest_consensus_state: ConsensusStateType = {
207        match ctx.consensus_state(&ClientConsensusStatePath::new(
208            client_id.clone(),
209            client_state.latest_height.revision_number(),
210            client_state.latest_height.revision_height(),
211        )) {
212            Ok(cs) => cs.try_into().map_err(Into::into)?,
213            // if the client state does not have an associated consensus state for its latest height
214            // then it must be expired
215            Err(_) => return Ok(Status::Expired),
216        }
217    };
218
219    // Note: if the `duration_since()` is `None`, indicating that the latest
220    // consensus state is in the future, then we don't consider the client
221    // to be expired.
222    let now = ctx.host_timestamp()?;
223
224    let status = consensus_state_status(
225        &latest_consensus_state.into(),
226        &now,
227        client_state.trusting_period,
228    )?;
229
230    Ok(status)
231}
232
233/// Check that the subject and substitute client states match as part of
234/// the client recovery validation step.
235///
236/// The subject and substitute client states match if all their respective
237/// client state parameters match except for frozen height, latest height,
238/// trusting period, and chain ID.
239pub fn check_substitute<V>(
240    subject_client_state: &ClientStateType,
241    substitute_client_state: Any,
242) -> Result<(), ClientError>
243where
244    V: ExtClientValidationContext,
245    ConsensusStateType: Convertible<V::ConsensusStateRef>,
246{
247    let ClientStateType {
248        latest_height: _,
249        frozen_height: _,
250        trusting_period: _,
251        chain_id: _,
252        allow_update: _,
253        trust_level: subject_trust_level,
254        unbonding_period: subject_unbonding_period,
255        max_clock_drift: subject_max_clock_drift,
256        proof_specs: subject_proof_specs,
257        upgrade_path: subject_upgrade_path,
258    } = subject_client_state;
259
260    let substitute_client_state = ClientStateType::try_from(substitute_client_state)?;
261
262    let ClientStateType {
263        latest_height: _,
264        frozen_height: _,
265        trusting_period: _,
266        chain_id: _,
267        allow_update: _,
268        trust_level: substitute_trust_level,
269        unbonding_period: substitute_unbonding_period,
270        max_clock_drift: substitute_max_clock_drift,
271        proof_specs: substitute_proof_specs,
272        upgrade_path: substitute_upgrade_path,
273    } = substitute_client_state;
274
275    (subject_trust_level == &substitute_trust_level
276        && subject_unbonding_period == &substitute_unbonding_period
277        && subject_max_clock_drift == &substitute_max_clock_drift
278        && subject_proof_specs == &substitute_proof_specs
279        && subject_upgrade_path == &substitute_upgrade_path)
280        .then_some(())
281        .ok_or(ClientError::FailedToVerifyClientRecoveryStates)
282}