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}