penumbra_sdk_ibc/component/msg_handler/
connection_open_ack.rs1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use cnidarium::{StateRead, StateWrite};
4use ibc_types::core::{
5 client::Height,
6 connection::{events, msgs::MsgConnectionOpenAck, ConnectionEnd, Counterparty, State},
7};
8use ibc_types::lightclients::tendermint::client_state::ClientState as TendermintClientState;
9use ibc_types::path::{ClientConsensusStatePath, ClientStatePath, ConnectionPath};
10
11use crate::{
12 component::{
13 client::StateReadExt as _,
14 connection::{StateReadExt as _, StateWriteExt as _},
15 ics02_validation::validate_penumbra_sdk_client_state,
16 proof_verification, HostInterface, MsgHandler,
17 },
18 IBC_COMMITMENT_PREFIX,
19};
20
21#[async_trait]
22impl MsgHandler for MsgConnectionOpenAck {
23 async fn check_stateless<H>(&self) -> Result<()> {
24 Ok(())
25 }
26
27 async fn try_execute<S: StateWrite, AH, HI: HostInterface>(&self, mut state: S) -> Result<()> {
28 tracing::debug!(msg = ?self);
29 consensus_height_is_correct::<&S, HI>(&state, self).await?;
45
46 penumbra_sdk_client_state_is_well_formed::<&S, HI>(&state, self).await?;
48
49 let connection = verify_previous_connection(&state, self).await?;
51
52 let expected_counterparty = Counterparty {
55 client_id: connection.client_id.clone(), connection_id: Some(self.conn_id_on_a.clone()), prefix: IBC_COMMITMENT_PREFIX.clone(), };
59
60 let expected_conn = ConnectionEnd {
62 state: State::TryOpen,
63 client_id: connection.counterparty.client_id.clone(),
64 counterparty: expected_counterparty.clone(),
65 versions: vec![self.version.clone()],
66 delay_period: connection.delay_period,
67 };
68
69 let trusted_client_state = state.get_client_state(&connection.client_id).await?;
71
72 if trusted_client_state.is_frozen() {
75 anyhow::bail!("client is frozen");
76 }
77
78 let trusted_consensus_state = state
80 .get_verified_consensus_state(&self.proofs_height_on_b, &connection.client_id)
81 .await?;
82
83 tracing::debug!(?trusted_client_state,);
86 tracing::debug!(
87 msg.proofs_height_on_b = ?self.proofs_height_on_b,
88 );
89 tracing::debug!(
90 counterparty_prefix = ?connection.counterparty.prefix,
91 );
92 tracing::debug!(
93 msg.proof_conn_end_on_b = ?self.proof_conn_end_on_b,
94 );
95 tracing::debug!(
96 trusted_consensus_state_root = ?trusted_consensus_state.root,
97 );
98 tracing::debug!(
99 connection_path = %ConnectionPath::new(&self.conn_id_on_b),
100 );
101 tracing::debug!(
102 expected_conn = ?expected_conn,
103 );
104 let conn_end_on_b_proof = self.proof_conn_end_on_b.clone();
105 proof_verification::verify_connection_state(
106 &trusted_client_state,
107 self.proofs_height_on_b,
108 &connection.counterparty.prefix,
109 &conn_end_on_b_proof,
110 &trusted_consensus_state.root,
111 &ConnectionPath::new(&self.conn_id_on_b),
112 &expected_conn,
113 )
114 .context("couldn't verify connection state")?;
115
116 let proof_client_state_of_a_on_b = self.proof_client_state_of_a_on_b.clone();
119 let client_state_of_a_on_b: TendermintClientState =
120 self.client_state_of_a_on_b.clone().try_into()?;
121
122 proof_verification::verify_client_full_state(
123 &trusted_client_state,
124 self.proofs_height_on_b,
125 &connection.counterparty.prefix,
126 &proof_client_state_of_a_on_b,
127 &trusted_consensus_state.root,
128 &ClientStatePath::new(&connection.counterparty.client_id),
129 client_state_of_a_on_b,
130 )
131 .context("couldn't verify client state")?;
132
133 let expected_consensus = state
134 .get_penumbra_sdk_consensus_state(self.consensus_height_of_a_on_b)
135 .await?;
136
137 let proof_consensus_state_of_a_on_b = self.proof_consensus_state_of_a_on_b.clone();
140 proof_verification::verify_client_consensus_state(
141 &trusted_client_state,
142 self.proofs_height_on_b,
143 &connection.counterparty.prefix,
144 &proof_consensus_state_of_a_on_b,
145 &trusted_consensus_state.root,
146 &ClientConsensusStatePath::new(
147 &connection.counterparty.client_id,
148 &self.consensus_height_of_a_on_b,
149 ),
150 expected_consensus,
151 )
152 .context("couldn't verify client consensus state")?;
153
154 let mut connection = state
157 .get_connection(&self.conn_id_on_a)
158 .await
159 .context("should be able to get connection")?
160 .context("missing connection")?;
161
162 let prev_counterparty = connection.counterparty;
164 let counterparty = Counterparty {
165 client_id: prev_counterparty.client_id.clone(),
166 connection_id: Some(self.conn_id_on_b.clone()),
167 prefix: prev_counterparty.prefix,
168 };
169 connection.state = State::Open;
170 connection.versions = vec![self.version.clone()];
171 connection.counterparty = counterparty;
172
173 state.update_connection(&self.conn_id_on_a, connection.clone());
174
175 state.record(
176 events::ConnectionOpenAck {
177 conn_id_on_a: self.conn_id_on_a.clone(),
178 client_id_on_a: connection.client_id.clone(),
179 conn_id_on_b: connection
180 .counterparty
181 .connection_id
182 .clone()
183 .unwrap_or_default(),
184 client_id_on_b: connection.counterparty.client_id,
185 }
186 .into(),
187 );
188
189 Ok(())
190 }
191}
192async fn consensus_height_is_correct<S: StateRead, HI: HostInterface>(
193 state: S,
194 msg: &MsgConnectionOpenAck,
195) -> anyhow::Result<()> {
196 let current_height = Height::new(
197 HI::get_revision_number(&state).await?,
198 HI::get_block_height(&state).await?,
199 )?;
200 if msg.consensus_height_of_a_on_b >= current_height {
201 anyhow::bail!("consensus height is greater than the current block height",);
202 }
203
204 Ok(())
205}
206
207async fn penumbra_sdk_client_state_is_well_formed<S: StateRead, HI: HostInterface>(
208 state: S,
209 msg: &MsgConnectionOpenAck,
210) -> anyhow::Result<()> {
211 let height = HI::get_block_height(&state).await?;
212 let chain_id = HI::get_chain_id(&state).await?;
213 validate_penumbra_sdk_client_state(msg.client_state_of_a_on_b.clone(), &chain_id, height)?;
214
215 Ok(())
216}
217async fn verify_previous_connection<S: StateRead>(
218 state: S,
219 msg: &MsgConnectionOpenAck,
220) -> anyhow::Result<ConnectionEnd> {
221 let connection = state
222 .get_connection(&msg.conn_id_on_a)
223 .await?
224 .ok_or_else(|| {
225 anyhow::anyhow!(
226 "no connection with the specified ID {} exists",
227 msg.conn_id_on_a
228 )
229 })?;
230
231 let state_is_consistent =
236 connection.state_matches(&State::Init) && connection.versions.contains(&msg.version);
237
238 if !state_is_consistent {
239 anyhow::bail!("connection is not in the correct state");
240 } else {
241 Ok(connection)
242 }
243}