penumbra_sdk_ibc/component/msg_handler/
channel_open_init.rs

1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use cnidarium::{StateRead, StateWrite};
4use ibc_types::core::channel::msgs::MsgChannelOpenInit;
5use ibc_types::core::channel::{
6    channel::State, events, ChannelEnd, ChannelId, Counterparty, PortId,
7};
8
9use crate::component::HostInterface;
10use crate::component::{
11    app_handler::{AppHandlerCheck, AppHandlerExecute},
12    channel::{StateReadExt as _, StateWriteExt as _},
13    connection::StateReadExt as _,
14    MsgHandler,
15};
16
17#[async_trait]
18impl MsgHandler for MsgChannelOpenInit {
19    async fn check_stateless<H: AppHandlerCheck>(&self) -> Result<()> {
20        connection_hops_eq_1(self)?;
21
22        Ok(())
23    }
24
25    async fn try_execute<
26        S: StateWrite,
27        AH: AppHandlerCheck + AppHandlerExecute,
28        HI: HostInterface,
29    >(
30        &self,
31        mut state: S,
32    ) -> Result<()> {
33        tracing::debug!(msg = ?self);
34        let channel_id = get_channel_id(&state).await?;
35
36        verify_channel_does_not_exist(&state, &channel_id, &self.port_id_on_a).await?;
37
38        // NOTE: optimistic channel handshakes are allowed, so we don't check if the connection
39        // is open here.
40        verify_connections_exist(&state, self).await?;
41
42        // TODO: do we want to do capability authentication?
43
44        let transfer = PortId::transfer();
45        if self.port_id_on_a == transfer {
46            AH::chan_open_init_check(&mut state, self).await?;
47        } else {
48            anyhow::bail!("invalid port id");
49        }
50        let channel_id = state
51            .next_channel_id()
52            .await
53            .context("unable to get next channel id")?;
54        let new_channel = ChannelEnd {
55            state: State::Init,
56            ordering: self.ordering,
57            remote: Counterparty::new(self.port_id_on_b.clone(), None),
58            connection_hops: self.connection_hops_on_a.clone(),
59            version: self.version_proposal.clone(),
60            ..ChannelEnd::default()
61        };
62
63        state.put_channel(&channel_id, &self.port_id_on_a, new_channel.clone());
64        state.put_send_sequence(&channel_id, &self.port_id_on_a, 1);
65        state.put_recv_sequence(&channel_id, &self.port_id_on_a, 1);
66        state.put_ack_sequence(&channel_id, &self.port_id_on_a, 1);
67
68        state.record(
69            events::channel::OpenInit {
70                port_id: self.port_id_on_a.clone(),
71                channel_id: channel_id.clone(),
72                counterparty_port_id: new_channel.counterparty().port_id().clone(),
73                connection_id: new_channel.connection_hops[0].clone(),
74                version: new_channel.version.clone(),
75            }
76            .into(),
77        );
78
79        let transfer = PortId::transfer();
80        if self.port_id_on_a == transfer {
81            AH::chan_open_init_execute(state, self).await;
82        } else {
83            anyhow::bail!("invalid port id");
84        }
85
86        Ok(())
87    }
88}
89
90fn connection_hops_eq_1(msg: &MsgChannelOpenInit) -> anyhow::Result<()> {
91    if msg.connection_hops_on_a.len() != 1 {
92        anyhow::bail!("currently only channels with one connection hop are supported");
93    }
94    Ok(())
95}
96async fn verify_connections_exist<S: StateRead>(
97    state: S,
98    msg: &MsgChannelOpenInit,
99) -> anyhow::Result<()> {
100    state
101        .get_connection(&msg.connection_hops_on_a[0])
102        .await?
103        .ok_or_else(|| anyhow::anyhow!("connection not found"))
104        .map(|_| ())
105}
106
107async fn get_channel_id<S: StateRead>(state: S) -> anyhow::Result<ChannelId> {
108    let counter = state.get_channel_counter().await?;
109
110    Ok(ChannelId::new(counter))
111}
112
113async fn verify_channel_does_not_exist<S: StateRead>(
114    state: S,
115    channel_id: &ChannelId,
116    port_id: &PortId,
117) -> anyhow::Result<()> {
118    let channel = state.get_channel(channel_id, port_id).await?;
119    if channel.is_some() {
120        anyhow::bail!("channel already exists");
121    }
122    Ok(())
123}