1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
use ibc_client_tendermint_types::error::{Error, IntoResult};
use ibc_client_tendermint_types::{
    ConsensusState as ConsensusStateType, Header as TmHeader, Misbehaviour as TmMisbehaviour,
};
use ibc_core_client::context::{Convertible, ExtClientValidationContext};
use ibc_core_client::types::error::ClientError;
use ibc_core_host::types::identifiers::{ChainId, ClientId};
use ibc_core_host::types::path::ClientConsensusStatePath;
use ibc_primitives::prelude::*;
use ibc_primitives::Timestamp;
use tendermint::crypto::Sha256;
use tendermint::merkle::MerkleHash;
use tendermint::{Hash, Time};
use tendermint_light_client_verifier::options::Options;
use tendermint_light_client_verifier::Verifier;

use crate::types::Header;

/// Determines whether or not two conflicting headers at the same height would
/// have convinced the light client.
pub fn verify_misbehaviour<V, H>(
    ctx: &V,
    misbehaviour: &TmMisbehaviour,
    client_id: &ClientId,
    chain_id: &ChainId,
    options: &Options,
    verifier: &impl Verifier,
) -> Result<(), ClientError>
where
    V: ExtClientValidationContext,
    ConsensusStateType: Convertible<V::ConsensusStateRef>,
    <ConsensusStateType as TryFrom<V::ConsensusStateRef>>::Error: Into<ClientError>,
    H: MerkleHash + Sha256 + Default,
{
    misbehaviour.validate_basic::<H>()?;

    let header_1 = misbehaviour.header1();
    let trusted_consensus_state_1: ConsensusStateType = {
        let consensus_state_path = ClientConsensusStatePath::new(
            client_id.clone(),
            header_1.trusted_height.revision_number(),
            header_1.trusted_height.revision_height(),
        );
        let consensus_state = ctx.consensus_state(&consensus_state_path)?;

        consensus_state.try_into().map_err(Into::into)?
    };

    let header_2 = misbehaviour.header2();
    let trusted_consensus_state_2: ConsensusStateType = {
        let consensus_state_path = ClientConsensusStatePath::new(
            client_id.clone(),
            header_2.trusted_height.revision_number(),
            header_2.trusted_height.revision_height(),
        );
        let consensus_state = ctx.consensus_state(&consensus_state_path)?;

        consensus_state.try_into().map_err(Into::into)?
    };

    let current_timestamp = ctx.host_timestamp()?;

    verify_misbehaviour_header::<H>(
        header_1,
        chain_id,
        options,
        trusted_consensus_state_1.timestamp(),
        trusted_consensus_state_1.next_validators_hash,
        current_timestamp,
        verifier,
    )?;
    verify_misbehaviour_header::<H>(
        header_2,
        chain_id,
        options,
        trusted_consensus_state_2.timestamp(),
        trusted_consensus_state_2.next_validators_hash,
        current_timestamp,
        verifier,
    )
}

pub fn verify_misbehaviour_header<H>(
    header: &TmHeader,
    chain_id: &ChainId,
    options: &Options,
    trusted_timestamp: Time,
    trusted_next_validator_hash: Hash,
    current_timestamp: Timestamp,
    verifier: &impl Verifier,
) -> Result<(), ClientError>
where
    H: MerkleHash + Sha256 + Default,
{
    // ensure correctness of the trusted next validator set provided by the relayer
    header.check_trusted_next_validator_set::<H>(&trusted_next_validator_hash)?;

    // ensure trusted consensus state is within trusting period
    {
        let duration_since_consensus_state = current_timestamp
            .duration_since(&trusted_timestamp.into())
            .ok_or_else(|| ClientError::InvalidConsensusStateTimestamp {
                time1: trusted_timestamp.into(),
                time2: current_timestamp,
            })?;

        if duration_since_consensus_state >= options.trusting_period {
            return Err(Error::ConsensusStateTimestampGteTrustingPeriod {
                duration_since_consensus_state,
                trusting_period: options.trusting_period,
            }
            .into());
        }
    }

    // main header verification, delegated to the tendermint-light-client crate.
    let untrusted_state = header.as_untrusted_block_state();

    let tm_chain_id = &chain_id
        .as_str()
        .try_into()
        .map_err(|e| ClientError::Other {
            description: format!("failed to parse chain id: {e}"),
        })?;

    let trusted_state = header.as_trusted_block_state(
        tm_chain_id,
        trusted_timestamp,
        trusted_next_validator_hash,
    )?;

    let current_timestamp = current_timestamp.into_tm_time().ok_or(ClientError::Other {
        description: "host timestamp must not be zero".to_string(),
    })?;

    verifier
        .verify_misbehaviour_header(untrusted_state, trusted_state, options, current_timestamp)
        .into_result()?;

    Ok(())
}

pub fn check_for_misbehaviour_on_misbehavior(
    header_1: &Header,
    header_2: &Header,
) -> Result<bool, ClientError> {
    if header_1.height() == header_2.height() {
        // when the height of the 2 headers are equal, we only have evidence
        // of misbehaviour in the case where the headers are different
        // (otherwise, the same header was added twice in the message,
        // and this is evidence of nothing)
        Ok(header_1.signed_header.commit.block_id.hash
            != header_2.signed_header.commit.block_id.hash)
    } else {
        // header_1 is at greater height than header_2, therefore
        // header_1 time must be less than or equal to
        // header_2 time in order to be valid misbehaviour (violation of
        // monotonic time).
        Ok(header_1.signed_header.header.time <= header_2.signed_header.header.time)
    }
}