penumbra_sdk_ibc/component/msg_handler/
connection_open_try.rs1use crate::component::{proof_verification, HostInterface};
2use crate::version::pick_connection_version;
3use crate::IBC_COMMITMENT_PREFIX;
4use anyhow::{Context, Result};
5use async_trait::async_trait;
6use cnidarium::{StateRead, StateWrite};
7use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState;
8use ibc_types::path::{ClientConsensusStatePath, ClientStatePath, ConnectionPath};
9use ibc_types::{
10 core::client::Height as IBCHeight,
11 core::connection::{
12 events, msgs::MsgConnectionOpenTry, ConnectionEnd, ConnectionId, Counterparty,
13 State as ConnectionState,
14 },
15};
16
17use crate::component::{
18 client::StateReadExt as _,
19 connection::{StateReadExt as _, StateWriteExt as _},
20 connection_counter::SUPPORTED_VERSIONS,
21 ics02_validation::validate_penumbra_sdk_client_state,
22 MsgHandler,
23};
24
25#[async_trait]
26impl MsgHandler for MsgConnectionOpenTry {
27 async fn check_stateless<H>(&self) -> Result<()> {
28 Ok(())
30 }
31
32 async fn try_execute<S: StateWrite, AH, HI: HostInterface>(&self, mut state: S) -> Result<()> {
33 tracing::debug!(msg = ?self);
34
35 consensus_height_is_correct::<&S, HI>(&mut state, self).await?;
53
54 penumbra_sdk_client_state_is_well_formed::<&S, HI>(&mut state, self).await?;
57
58 let supported_versions = SUPPORTED_VERSIONS.clone();
68
69 pick_connection_version(&supported_versions, &self.versions_on_a.clone())?;
70
71 let expected_conn = ConnectionEnd {
74 state: ConnectionState::Init,
75 client_id: self.counterparty.client_id.clone(),
76 counterparty: Counterparty {
77 client_id: self.client_id_on_b.clone(),
78 connection_id: None,
79 prefix: IBC_COMMITMENT_PREFIX.clone(),
80 },
81 versions: self.versions_on_a.clone(),
82 delay_period: self.delay_period,
83 };
84
85 let trusted_client_state = state.get_client_state(&self.client_id_on_b).await?;
87
88 if trusted_client_state.is_frozen() {
91 anyhow::bail!("client is frozen");
92 }
93
94 let trusted_consensus_state = state
96 .get_verified_consensus_state(&self.proofs_height_on_a, &self.client_id_on_b)
97 .await?;
98
99 let proof_conn_end_on_a = self.proof_conn_end_on_a.clone();
102 proof_verification::verify_connection_state(
103 &trusted_client_state,
104 self.proofs_height_on_a,
105 &self.counterparty.prefix,
106 &proof_conn_end_on_a,
107 &trusted_consensus_state.root,
108 &ConnectionPath::new(
109 self.counterparty
110 .connection_id
111 .as_ref()
112 .ok_or_else(|| anyhow::anyhow!("counterparty connection id is not set"))?,
113 ),
114 &expected_conn,
115 )
116 .context("failed to verify connection state")?;
117
118 let proof_client_state_of_b_on_a = self.proof_client_state_of_b_on_a.clone();
121
122 let client_state_of_b_on_a: TendermintClientState =
123 self.client_state_of_b_on_a.clone().try_into()?;
124
125 proof_verification::verify_client_full_state(
126 &trusted_client_state,
127 self.proofs_height_on_a,
128 &self.counterparty.prefix,
129 &proof_client_state_of_b_on_a,
130 &trusted_consensus_state.root,
131 &ClientStatePath::new(&self.counterparty.client_id),
132 client_state_of_b_on_a,
133 )
134 .context("couldn't verify client state")?;
135
136 let expected_consensus = state
137 .get_penumbra_sdk_consensus_state(self.consensus_height_of_b_on_a)
138 .await?;
139
140 let proof_consensus_state_of_b_on_a = self.proof_consensus_state_of_b_on_a.clone();
143 proof_verification::verify_client_consensus_state(
144 &trusted_client_state,
145 self.proofs_height_on_a,
146 &self.counterparty.prefix,
147 &proof_consensus_state_of_b_on_a,
148 &trusted_consensus_state.root,
149 &ClientConsensusStatePath::new(
150 &self.counterparty.client_id,
151 &self.consensus_height_of_b_on_a,
152 ),
153 expected_consensus,
154 )
155 .context("couldn't verify client consensus state")?;
156
157 let mut new_conn = ConnectionEnd {
161 state: ConnectionState::TryOpen,
162 client_id: self.client_id_on_b.clone(),
163 counterparty: self.counterparty.clone(),
164 versions: self.versions_on_a.clone(),
165 delay_period: self.delay_period,
166 };
167
168 new_conn.versions = vec![pick_connection_version(
169 &SUPPORTED_VERSIONS.to_vec(),
170 &self.versions_on_a.clone(),
171 )?];
172
173 let new_connection_id = ConnectionId::new(
174 state
175 .get_connection_counter()
176 .await
177 .context("unable to get connection counter")?
178 .0,
179 );
180
181 state
182 .put_new_connection(&new_connection_id, new_conn)
183 .await
184 .context("unable to put new connection")?;
185
186 state.record(
187 events::ConnectionOpenTry {
188 conn_id_on_b: new_connection_id.clone(),
189 client_id_on_b: self.client_id_on_b.clone(),
190 conn_id_on_a: self.counterparty.connection_id.clone().unwrap_or_default(),
191 client_id_on_a: self.counterparty.client_id.clone(),
192 }
193 .into(),
194 );
195
196 Ok(())
197 }
198}
199async fn consensus_height_is_correct<S: StateRead, HI: HostInterface>(
200 state: S,
201 msg: &MsgConnectionOpenTry,
202) -> anyhow::Result<()> {
203 let current_height = IBCHeight::new(
204 HI::get_revision_number(&state).await?,
205 HI::get_block_height(&state).await?,
206 )?;
207 if msg.consensus_height_of_b_on_a >= current_height {
208 anyhow::bail!("consensus height is greater than the current block height",);
209 }
210
211 Ok(())
212}
213async fn penumbra_sdk_client_state_is_well_formed<S: StateRead, HI: HostInterface>(
214 state: S,
215 msg: &MsgConnectionOpenTry,
216) -> anyhow::Result<()> {
217 let height = HI::get_block_height(&state).await?;
218 let chain_id = HI::get_chain_id(&state).await?;
219 validate_penumbra_sdk_client_state(msg.client_state_of_b_on_a.clone(), &chain_id, height)?;
220
221 Ok(())
222}