ibc_client_tendermint/client_state/
misbehaviour.rs

1use ibc_client_tendermint_types::error::{IntoResult, TendermintClientError};
2use ibc_client_tendermint_types::{
3    ConsensusState as ConsensusStateType, Header as TmHeader, Misbehaviour as TmMisbehaviour,
4};
5use ibc_core_client::context::{Convertible, ExtClientValidationContext};
6use ibc_core_client::types::error::ClientError;
7use ibc_core_host::types::error::IdentifierError;
8use ibc_core_host::types::identifiers::{ChainId, ClientId};
9use ibc_core_host::types::path::ClientConsensusStatePath;
10use ibc_primitives::prelude::*;
11use ibc_primitives::{IntoHostTime, IntoTimestamp, Timestamp};
12use tendermint::crypto::Sha256;
13use tendermint::merkle::MerkleHash;
14use tendermint::{Hash, Time};
15use tendermint_light_client_verifier::options::Options;
16use tendermint_light_client_verifier::Verifier;
17
18use crate::types::Header;
19
20/// Determines if two conflicting headers at the same height would
21/// have convinced the light client.
22pub fn verify_misbehaviour<V, H>(
23    ctx: &V,
24    misbehaviour: &TmMisbehaviour,
25    client_id: &ClientId,
26    chain_id: &ChainId,
27    options: &Options,
28    verifier: &impl Verifier,
29) -> Result<(), ClientError>
30where
31    V: ExtClientValidationContext,
32    ConsensusStateType: Convertible<V::ConsensusStateRef>,
33    <ConsensusStateType as TryFrom<V::ConsensusStateRef>>::Error: Into<ClientError>,
34    H: MerkleHash + Sha256 + Default,
35{
36    misbehaviour.validate_basic::<H>()?;
37
38    let header_1 = misbehaviour.header1();
39    let trusted_consensus_state_1: ConsensusStateType = {
40        let consensus_state_path = ClientConsensusStatePath::new(
41            client_id.clone(),
42            header_1.trusted_height.revision_number(),
43            header_1.trusted_height.revision_height(),
44        );
45        let consensus_state = ctx.consensus_state(&consensus_state_path)?;
46
47        consensus_state.try_into().map_err(Into::into)?
48    };
49
50    let header_2 = misbehaviour.header2();
51    let trusted_consensus_state_2: ConsensusStateType = {
52        let consensus_state_path = ClientConsensusStatePath::new(
53            client_id.clone(),
54            header_2.trusted_height.revision_number(),
55            header_2.trusted_height.revision_height(),
56        );
57        let consensus_state = ctx.consensus_state(&consensus_state_path)?;
58
59        consensus_state.try_into().map_err(Into::into)?
60    };
61
62    let current_timestamp = ctx.host_timestamp()?;
63
64    verify_misbehaviour_header::<H>(
65        header_1,
66        chain_id,
67        options,
68        trusted_consensus_state_1.timestamp(),
69        trusted_consensus_state_1.next_validators_hash,
70        current_timestamp,
71        verifier,
72    )?;
73    verify_misbehaviour_header::<H>(
74        header_2,
75        chain_id,
76        options,
77        trusted_consensus_state_2.timestamp(),
78        trusted_consensus_state_2.next_validators_hash,
79        current_timestamp,
80        verifier,
81    )
82}
83
84pub fn verify_misbehaviour_header<H>(
85    header: &TmHeader,
86    chain_id: &ChainId,
87    options: &Options,
88    trusted_time: Time,
89    trusted_next_validator_hash: Hash,
90    current_timestamp: Timestamp,
91    verifier: &impl Verifier,
92) -> Result<(), ClientError>
93where
94    H: MerkleHash + Sha256 + Default,
95{
96    // ensure correctness of the trusted next validator set provided by the relayer
97    header.check_trusted_next_validator_set::<H>(&trusted_next_validator_hash)?;
98
99    // ensure trusted consensus state is within trusting period
100    {
101        let trusted_timestamp = trusted_time.into_timestamp()?;
102
103        let duration_since_consensus_state =
104            current_timestamp.duration_since(&trusted_timestamp).ok_or(
105                ClientError::InvalidConsensusStateTimestamp(trusted_timestamp),
106            )?;
107
108        if duration_since_consensus_state >= options.trusting_period {
109            return Err(TendermintClientError::InsufficientTrustingPeriod {
110                duration_since_consensus_state,
111                trusting_period: options.trusting_period,
112            }
113            .into());
114        }
115    }
116
117    // main header verification, delegated to the tendermint-light-client crate.
118    let untrusted_state = header.as_untrusted_block_state();
119
120    let tm_chain_id =
121        &chain_id
122            .as_str()
123            .try_into()
124            .map_err(|e| IdentifierError::FailedToParse {
125                description: format!("chain ID `{chain_id}`: {e:?}"),
126            })?;
127
128    let trusted_state =
129        header.as_trusted_block_state(tm_chain_id, trusted_time, trusted_next_validator_hash)?;
130
131    let current_timestamp = current_timestamp.into_host_time()?;
132
133    verifier
134        .verify_misbehaviour_header(untrusted_state, trusted_state, options, current_timestamp)
135        .into_result()?;
136
137    Ok(())
138}
139
140pub fn check_for_misbehaviour_on_misbehavior(
141    header_1: &Header,
142    header_2: &Header,
143) -> Result<bool, ClientError> {
144    if header_1.height() == header_2.height() {
145        // when the height of the 2 headers are equal, we only have evidence
146        // of misbehaviour in the case where the headers are different
147        // (otherwise, the same header was added twice in the message,
148        // and this is evidence of nothing)
149        Ok(header_1.signed_header.commit.block_id.hash
150            != header_2.signed_header.commit.block_id.hash)
151    } else {
152        // header_1 is at greater height than header_2, therefore
153        // header_1 time must be less than or equal to
154        // header_2 time in order to be valid misbehaviour (violation of
155        // monotonic time).
156        Ok(header_1.signed_header.header.time <= header_2.signed_header.header.time)
157    }
158}