penumbra_sdk_ibc/component/msg_handler/
misbehavior.rs1use 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 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 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 let client_state = client_is_present(&state, self).await?;
57
58 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 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}