penumbra_sdk_ibc/component/msg_handler/
channel_open_try.rs

1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use cnidarium::{StateRead, StateWrite};
4use ibc_types::core::{
5    channel::{
6        channel::State as ChannelState, events, msgs::MsgChannelOpenTry, ChannelEnd, Counterparty,
7        PortId,
8    },
9    connection::{ConnectionEnd, State as ConnectionState},
10};
11
12use crate::component::{
13    app_handler::{AppHandlerCheck, AppHandlerExecute},
14    channel::StateWriteExt,
15    connection::StateReadExt,
16    proof_verification::ChannelProofVerifier,
17    HostInterface, MsgHandler,
18};
19
20#[async_trait]
21impl MsgHandler for MsgChannelOpenTry {
22    async fn check_stateless<H: AppHandlerCheck>(&self) -> Result<()> {
23        connection_hops_eq_1(self)?;
24
25        Ok(())
26    }
27
28    async fn try_execute<
29        S: StateWrite,
30        AH: AppHandlerCheck + AppHandlerExecute,
31        HI: HostInterface,
32    >(
33        &self,
34        mut state: S,
35    ) -> Result<()> {
36        tracing::debug!(msg = ?self);
37        let connection_on_b = verify_connections_open(&state, self).await?;
38
39        // TODO: do we want to do capability authentication?
40        // TODO: version intersection
41
42        let expected_channel_on_a = ChannelEnd {
43            state: ChannelState::Init,
44            ordering: self.ordering,
45            remote: Counterparty::new(self.port_id_on_b.clone(), None),
46            connection_hops: vec![connection_on_b
47                .counterparty
48                .connection_id
49                .clone()
50                .ok_or_else(|| anyhow::anyhow!("no counterparty connection id provided"))?],
51            version: self.version_supported_on_a.clone(),
52            ..ChannelEnd::default()
53        };
54
55        tracing::debug!(?self, ?expected_channel_on_a);
56
57        state
58            .verify_channel_proof(
59                &connection_on_b,
60                &self.proof_chan_end_on_a,
61                &self.proof_height_on_a,
62                &self.chan_id_on_a,
63                &self.port_id_on_a,
64                &expected_channel_on_a,
65            )
66            .await?;
67
68        let transfer = PortId::transfer();
69        if self.port_id_on_b == transfer {
70            AH::chan_open_try_check(&mut state, self).await?;
71        } else {
72            anyhow::bail!("invalid port id");
73        }
74
75        let channel_id = state
76            .next_channel_id()
77            .await
78            .context("unable to retrieve next channel id")?;
79        let new_channel = ChannelEnd {
80            state: ChannelState::TryOpen,
81            ordering: self.ordering,
82            remote: Counterparty::new(self.port_id_on_a.clone(), Some(self.chan_id_on_a.clone())),
83            connection_hops: self.connection_hops_on_b.clone(),
84            version: self.version_supported_on_a.clone(),
85            ..ChannelEnd::default()
86        };
87
88        state.put_channel(&channel_id, &self.port_id_on_b, new_channel.clone());
89        state.put_send_sequence(&channel_id, &self.port_id_on_b, 1);
90        state.put_recv_sequence(&channel_id, &self.port_id_on_b, 1);
91        state.put_ack_sequence(&channel_id, &self.port_id_on_b, 1);
92
93        state.record(
94            events::channel::OpenTry {
95                port_id: self.port_id_on_b.clone(),
96                channel_id: channel_id.clone(),
97                counterparty_port_id: new_channel.counterparty().port_id().clone(),
98                counterparty_channel_id: new_channel
99                    .counterparty()
100                    .channel_id
101                    .clone()
102                    .unwrap_or_default(),
103                connection_id: new_channel.connection_hops[0].clone(),
104                version: new_channel.version.clone(),
105            }
106            .into(),
107        );
108
109        let transfer = PortId::transfer();
110        if self.port_id_on_b == transfer {
111            AH::chan_open_try_execute(state, self).await;
112        } else {
113            anyhow::bail!("invalid port id");
114        }
115
116        Ok(())
117    }
118}
119
120pub fn connection_hops_eq_1(msg: &MsgChannelOpenTry) -> anyhow::Result<()> {
121    if msg.connection_hops_on_b.len() != 1 {
122        anyhow::bail!("currently only channels with one connection hop are supported");
123    }
124    Ok(())
125}
126
127async fn verify_connections_open<S: StateRead>(
128    state: S,
129    msg: &MsgChannelOpenTry,
130) -> anyhow::Result<ConnectionEnd> {
131    let connection = state
132        .get_connection(&msg.connection_hops_on_b[0])
133        .await?
134        .ok_or_else(|| anyhow::anyhow!("connection not found"))?;
135
136    if connection.state != ConnectionState::Open {
137        Err(anyhow::anyhow!("connection for channel is not open"))
138    } else {
139        Ok(connection)
140    }
141}