penumbra_sdk_ibc/component/msg_handler/
misbehavior.rs

1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use cnidarium::{StateRead, StateWrite};
4use ibc_types::core::client::{events, ClientType};
5use ibc_types::core::client::{msgs::MsgSubmitMisbehaviour, ClientId};
6use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState;
7use ibc_types::lightclients::tendermint::header::Header as TendermintHeader;
8use ibc_types::lightclients::tendermint::misbehaviour::Misbehaviour as TendermintMisbehavior;
9use ibc_types::lightclients::tendermint::TENDERMINT_CLIENT_TYPE;
10use tendermint_light_client_verifier::{
11    types::{TrustedBlockState, UntrustedBlockState},
12    ProdVerifier, Verdict, Verifier,
13};
14
15use super::update_client::verify_header_validator_set;
16use super::MsgHandler;
17use crate::component::client::StateWriteExt as _;
18use crate::component::HostInterface;
19use crate::component::{ics02_validation, ClientStateReadExt as _};
20
21#[async_trait]
22impl MsgHandler for MsgSubmitMisbehaviour {
23    async fn check_stateless<H>(&self) -> Result<()> {
24        misbehavior_is_tendermint(self)?;
25        let untrusted_misbehavior =
26            ics02_validation::get_tendermint_misbehavior(self.misbehaviour.clone())?;
27        // misbehavior must either contain equivocation or timestamp monotonicity violation
28        if !misbehavior_equivocation_violation(&untrusted_misbehavior)
29            && !misbehavior_timestamp_monotonicity_violation(&untrusted_misbehavior)
30        {
31            anyhow::bail!(
32                "misbehavior must either contain equivocation or timestamp monotonicity violation"
33            );
34        }
35
36        Ok(())
37    }
38
39    async fn try_execute<S: StateWrite, H, HI: HostInterface>(&self, mut state: S) -> Result<()> {
40        tracing::debug!(msg = ?self);
41
42        let untrusted_misbehavior =
43            ics02_validation::get_tendermint_misbehavior(self.misbehaviour.clone())?;
44
45        // misbehavior must either contain equivocation or timestamp monotonicity violation
46        if !misbehavior_equivocation_violation(&untrusted_misbehavior)
47            && !misbehavior_timestamp_monotonicity_violation(&untrusted_misbehavior)
48        {
49            anyhow::bail!(
50                "misbehavior must either contain equivocation or timestamp monotonicity violation"
51            );
52        }
53
54        // verify that both headers verify for an update client on the last trusted header for
55        // client_id
56        let client_state = client_is_present(&state, self).await?;
57
58        // NOTE: we are allowing expired clients here. it seems correct to allow expired clients to
59        // be frozen on evidence of misbehavior.
60        client_is_not_frozen(&client_state)?;
61
62        let trusted_client_state = client_state;
63
64        verify_misbehavior_header::<&S, HI>(
65            &state,
66            &untrusted_misbehavior.client_id,
67            &untrusted_misbehavior.header1,
68            &trusted_client_state,
69        )
70        .await?;
71        verify_misbehavior_header::<&S, HI>(
72            &state,
73            &untrusted_misbehavior.client_id,
74            &untrusted_misbehavior.header2,
75            &trusted_client_state,
76        )
77        .await?;
78
79        tracing::info!(client_id = ?untrusted_misbehavior.client_id, "received valid misbehavior evidence! freezing client");
80
81        // freeze the client
82        let frozen_client =
83            trusted_client_state.with_frozen_height(untrusted_misbehavior.header1.height());
84        state.put_client(&self.client_id, frozen_client);
85
86        state.record(
87            events::ClientMisbehaviour {
88                client_id: self.client_id.clone(),
89                client_type: ClientType::new(TENDERMINT_CLIENT_TYPE.to_string()),
90            }
91            .into(),
92        );
93
94        Ok(())
95    }
96}
97
98async fn client_is_present<S: StateRead>(
99    state: S,
100    msg: &MsgSubmitMisbehaviour,
101) -> anyhow::Result<TendermintClientState> {
102    state.get_client_type(&msg.client_id).await?;
103
104    state.get_client_state(&msg.client_id).await
105}
106
107fn client_is_not_frozen(client: &TendermintClientState) -> anyhow::Result<()> {
108    if client.is_frozen() {
109        Err(anyhow::anyhow!("client is frozen"))
110    } else {
111        Ok(())
112    }
113}
114
115async fn verify_misbehavior_header<S: StateRead, HI: HostInterface>(
116    state: S,
117    client_id: &ClientId,
118    mb_header: &TendermintHeader,
119    trusted_client_state: &TendermintClientState,
120) -> Result<()> {
121    let trusted_height = mb_header.trusted_height;
122    let last_trusted_consensus_state = state
123        .get_verified_consensus_state(&trusted_height, &client_id)
124        .await?;
125
126    let trusted_height = trusted_height
127        .revision_height()
128        .try_into()
129        .context("invalid header height")?;
130
131    let trusted_validator_set =
132        verify_header_validator_set(mb_header, &last_trusted_consensus_state)?;
133
134    let trusted_state = TrustedBlockState {
135        chain_id: &trusted_client_state.chain_id.clone().into(),
136        header_time: last_trusted_consensus_state.timestamp,
137        height: trusted_height,
138        next_validators: trusted_validator_set,
139        next_validators_hash: last_trusted_consensus_state.next_validators_hash,
140    };
141
142    let untrusted_state = UntrustedBlockState {
143        signed_header: &mb_header.signed_header,
144        validators: &mb_header.validator_set,
145        next_validators: None,
146    };
147
148    let options = trusted_client_state.as_light_client_options()?;
149    let verifier = ProdVerifier::default();
150
151    let verdict = verifier.verify_misbehaviour_header(
152        untrusted_state,
153        trusted_state,
154        &options,
155        HI::get_block_timestamp(&state).await?,
156    );
157
158    match verdict {
159        Verdict::Success => Ok(()),
160        Verdict::NotEnoughTrust(voting_power_tally) => Err(anyhow::anyhow!(
161            "not enough trust, voting power tally: {:?}",
162            voting_power_tally
163        )),
164        Verdict::Invalid(detail) => Err(anyhow::anyhow!(
165            "could not verify tendermint header: invalid: {:?}",
166            detail
167        )),
168    }
169}
170
171fn misbehavior_equivocation_violation(misbehavior: &TendermintMisbehavior) -> bool {
172    misbehavior.header1.height() == misbehavior.header2.height()
173        && misbehavior.header1.signed_header.commit.block_id.hash
174            != misbehavior.header2.signed_header.commit.block_id.hash
175}
176
177fn misbehavior_timestamp_monotonicity_violation(misbehavior: &TendermintMisbehavior) -> bool {
178    misbehavior.header1.height() < misbehavior.header2.height()
179        && misbehavior.header1.signed_header.header.time
180            > misbehavior.header2.signed_header.header.time
181}
182
183fn misbehavior_is_tendermint(msg: &MsgSubmitMisbehaviour) -> Result<()> {
184    if ics02_validation::is_tendermint_misbehavior(&msg.misbehaviour) {
185        Ok(())
186    } else {
187        Err(anyhow::anyhow!(
188            "MsgSubmitMisbehaviour is not tendermint misbehavior"
189        ))
190    }
191}