penumbra_sdk_ibc/component/msg_handler/
connection_open_confirm.rs

1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use cnidarium::{StateRead, StateWrite};
4use ibc_types::{
5    core::connection::{
6        events, msgs::MsgConnectionOpenConfirm, ConnectionEnd, Counterparty, State,
7    },
8    path::ConnectionPath,
9};
10
11use crate::{
12    component::{
13        client::StateReadExt as _,
14        connection::{StateReadExt as _, StateWriteExt as _},
15        proof_verification, MsgHandler,
16    },
17    IBC_COMMITMENT_PREFIX,
18};
19
20#[async_trait]
21impl MsgHandler for MsgConnectionOpenConfirm {
22    async fn check_stateless<H>(&self) -> Result<()> {
23        // NOTE: other than that the message is a well formed ConnectionOpenConfirm,
24        // there is no other stateless validation to perform.
25
26        Ok(())
27    }
28
29    async fn try_execute<S: StateWrite, AH, HI>(&self, mut state: S) -> Result<()> {
30        tracing::debug!(msg = ?self);
31        // Validate a ConnectionOpenConfirm message, completing the IBC connection handshake.
32        //
33        // Verify that we have a connection in the correct state (TRYOPEN), and that the
34        // counterparty has committed a connection with the expected state (OPEN) to their state
35        // store.
36        //
37        // Here we are Chain B.
38        // CHAINS:          (A, B)
39        // PRIOR STATE:     (OPEN, TRYOPEN)
40        // POSTERIOR STATE: (OPEN, OPEN)
41        //
42        // verify that a connection with the provided ID exists and is in the correct state
43        // (TRYOPEN)
44        let connection = verify_previous_connection(&state, self).await?;
45
46        let expected_conn = ConnectionEnd {
47            state: State::Open,
48            client_id: connection.counterparty.client_id.clone(),
49            counterparty: Counterparty {
50                client_id: connection.client_id.clone(),
51                connection_id: Some(self.conn_id_on_b.clone()),
52                prefix: IBC_COMMITMENT_PREFIX.clone(),
53            },
54            versions: connection.versions.to_vec(),
55            delay_period: connection.delay_period,
56        };
57
58        // get the trusted client state for the counterparty
59        let trusted_client_state = state.get_client_state(&connection.client_id).await?;
60
61        // check if the client is frozen
62        // TODO: should we also check if the client is expired here?
63        if trusted_client_state.is_frozen() {
64            anyhow::bail!("client is frozen");
65        }
66
67        // get the stored consensus state for the counterparty
68        let trusted_consensus_state = state
69            .get_verified_consensus_state(&self.proof_height_on_a, &connection.client_id)
70            .await?;
71
72        // PROOF VERIFICATION
73        // in connectionOpenConfirm, only the inclusion of the connection state must be
74        // verified, not the client or consensus states.
75
76        let proof_conn_end_on_a = self.proof_conn_end_on_a.clone();
77        proof_verification::verify_connection_state(
78            &trusted_client_state,
79            self.proof_height_on_a,
80            &connection.counterparty.prefix,
81            &proof_conn_end_on_a,
82            &trusted_consensus_state.root,
83            &ConnectionPath::new(connection.counterparty.connection_id.as_ref().ok_or_else(
84                || anyhow::anyhow!("missing counterparty in connection open confirm"),
85            )?),
86            &expected_conn,
87        )?;
88
89        // VERIFICATION SUCCESSFUL. now execute
90        let mut connection = state
91            .get_connection(&self.conn_id_on_b)
92            .await?
93            .ok_or_else(|| anyhow::anyhow!("no connection with the given ID"))
94            .context("unable to get connection")?;
95
96        connection.state = State::Open;
97
98        state.update_connection(&self.conn_id_on_b, connection.clone());
99
100        state.record(
101            events::ConnectionOpenConfirm {
102                conn_id_on_b: self.conn_id_on_b.clone(),
103                client_id_on_b: connection.client_id.clone(),
104                conn_id_on_a: connection
105                    .counterparty
106                    .connection_id
107                    .clone()
108                    .unwrap_or_default(),
109                client_id_on_a: connection.counterparty.client_id,
110            }
111            .into(),
112        );
113
114        Ok(())
115    }
116}
117
118async fn verify_previous_connection<S: StateRead>(
119    state: S,
120    msg: &MsgConnectionOpenConfirm,
121) -> anyhow::Result<ConnectionEnd> {
122    let connection = state
123        .get_connection(&msg.conn_id_on_b)
124        .await?
125        .ok_or_else(|| anyhow::anyhow!("connection not found"))?;
126
127    if !connection.state_matches(&State::TryOpen) {
128        Err(anyhow::anyhow!("connection not in correct state"))
129    } else {
130        Ok(connection)
131    }
132}