penumbra_sdk_ibc/component/msg_handler/
upgrade_client.rs1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use ibc_types::{
5 core::{
6 client::{events, msgs::MsgUpgradeClient},
7 commitment::{MerkleProof, MerkleRoot},
8 },
9 lightclients::tendermint::{
10 client_state::ClientState as TendermintClientState,
11 consensus_state::ConsensusState as TendermintConsensusState, TrustThreshold,
12 TENDERMINT_CLIENT_TYPE,
13 },
14 timestamp::ZERO_DURATION,
15};
16
17use crate::component::{
18 client::{ConsensusStateWriteExt as _, StateReadExt as _, StateWriteExt as _},
19 proof_verification::ClientUpgradeProofVerifier,
20 HostInterface, MsgHandler,
21};
22
23static SENTINEL_UPGRADE_ROOT: &str = "sentinel_root";
24
25#[async_trait]
26impl MsgHandler for MsgUpgradeClient {
27 async fn check_stateless<AH>(&self) -> Result<()> {
28 Ok(())
29 }
30
31 async fn try_execute<S: StateWrite, AH, HI: HostInterface>(&self, mut state: S) -> Result<()> {
43 tracing::debug!(msg = ?self);
44
45 let upgraded_client_state_tm = TendermintClientState::try_from(self.client_state.clone())
46 .context("client state is not a Tendermint client state")?;
47
48 let upgraded_consensus_state_tm =
49 TendermintConsensusState::try_from(self.consensus_state.clone())
50 .context("consensus state is not a Tendermint consensus state")?;
51
52 let mut upgraded_client_state_tm_zeroed_fields = upgraded_client_state_tm.clone();
64 upgraded_client_state_tm_zeroed_fields.trusting_period = ZERO_DURATION;
65 upgraded_client_state_tm_zeroed_fields.trust_level = TrustThreshold::ZERO;
66 upgraded_client_state_tm_zeroed_fields
67 .allow_update
68 .after_expiry = false;
69 upgraded_client_state_tm_zeroed_fields
70 .allow_update
71 .after_misbehaviour = false;
72 upgraded_client_state_tm_zeroed_fields.frozen_height = None;
73 upgraded_client_state_tm_zeroed_fields.max_clock_drift = ZERO_DURATION;
74
75 let proof_consensus_state: MerkleProof = self
76 .proof_upgrade_consensus_state
77 .clone()
78 .try_into()
79 .context("couldn't decode proof for upgraded consensus state")?;
80 let proof_client_state: MerkleProof = self
81 .proof_upgrade_client
82 .clone()
83 .try_into()
84 .context("couldn't decode proof for upgraded client state")?;
85
86 state
87 .verify_client_upgrade_proof::<HI>(
88 &self.client_id,
89 &proof_client_state,
90 &proof_consensus_state,
91 upgraded_consensus_state_tm.clone(),
92 upgraded_client_state_tm_zeroed_fields.clone(),
93 )
94 .await?;
95
96 let old_client_state = state.get_client_state(&self.client_id).await?;
97
98 let new_client_state = TendermintClientState::new(
106 upgraded_client_state_tm.chain_id,
107 old_client_state.trust_level,
108 old_client_state.trusting_period,
109 upgraded_client_state_tm.unbonding_period,
110 old_client_state.max_clock_drift,
111 upgraded_client_state_tm.latest_height,
112 upgraded_client_state_tm.proof_specs,
113 upgraded_client_state_tm.upgrade_path,
114 old_client_state.allow_update,
115 old_client_state.frozen_height,
116 )?;
117
118 let new_consensus_state = TendermintConsensusState::new(
119 MerkleRoot {
120 hash: SENTINEL_UPGRADE_ROOT.into(),
121 },
122 upgraded_consensus_state_tm.timestamp,
123 upgraded_consensus_state_tm.next_validators_hash,
124 );
125
126 let latest_height = new_client_state.latest_height();
127
128 state.put_client(&self.client_id, new_client_state);
129 state
130 .put_verified_consensus_state::<HI>(
131 latest_height,
132 self.client_id.clone(),
133 new_consensus_state,
134 )
135 .await?;
136
137 state.record(
138 events::UpgradeClient {
139 client_id: self.client_id.clone(),
140 client_type: ibc_types::core::client::ClientType(
141 TENDERMINT_CLIENT_TYPE.to_string(),
142 ),
143 consensus_height: latest_height,
144 }
145 .into(),
146 );
147
148 Ok(())
149 }
150}