penumbra_sdk_ibc/component/msg_handler/
channel_close_confirm.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use ibc_types::core::{
5    channel::{
6        channel::State as ChannelState, events, msgs::MsgChannelCloseConfirm, ChannelEnd,
7        Counterparty, PortId,
8    },
9    connection::State as ConnectionState,
10};
11
12use crate::component::{
13    app_handler::{AppHandlerCheck, AppHandlerExecute},
14    channel::{StateReadExt as _, StateWriteExt as _},
15    connection::StateReadExt as _,
16    proof_verification::ChannelProofVerifier,
17    HostInterface, MsgHandler,
18};
19
20#[async_trait]
21impl MsgHandler for MsgChannelCloseConfirm {
22    async fn check_stateless<AH>(&self) -> Result<()> {
23        // NOTE: no additional stateless validation is possible
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        // TODO: capability authentication?
38        //
39        // we probably do need capability authentication here, or some other authorization
40        // method, to prevent anyone from spuriously closing channels.
41        //
42        let mut channel = state
43            .get_channel(&self.chan_id_on_b, &self.port_id_on_b)
44            .await?
45            .ok_or_else(|| anyhow::anyhow!("channel not found"))?;
46        if channel.state_matches(&ChannelState::Closed) {
47            anyhow::bail!("channel is already closed");
48        }
49
50        let connection = state
51            .get_connection(&channel.connection_hops[0])
52            .await?
53            .ok_or_else(|| anyhow::anyhow!("connection not found for channel"))?;
54        if !connection.state_matches(&ConnectionState::Open) {
55            anyhow::bail!("connection for channel is not open");
56        }
57
58        let expected_connection_hops = vec![connection
59            .counterparty
60            .connection_id
61            .clone()
62            .ok_or_else(|| anyhow::anyhow!("no counterparty connection id provided"))?];
63
64        let expected_counterparty =
65            Counterparty::new(self.port_id_on_b.clone(), Some(self.chan_id_on_b.clone()));
66
67        let expected_channel = ChannelEnd {
68            state: ChannelState::Closed,
69            ordering: channel.ordering,
70            remote: expected_counterparty,
71            connection_hops: expected_connection_hops,
72            version: channel.version.clone(),
73            ..ChannelEnd::default()
74        };
75
76        state
77            .verify_channel_proof(
78                &connection,
79                &self.proof_chan_end_on_a,
80                &self.proof_height_on_a,
81                &channel
82                    .remote
83                    .channel_id
84                    .clone()
85                    .ok_or_else(|| anyhow::anyhow!("no channel id"))?,
86                &channel.remote.port_id.clone(),
87                &expected_channel,
88            )
89            .await?;
90
91        let transfer = PortId::transfer();
92        if self.port_id_on_b == transfer {
93            AH::chan_close_confirm_check(&mut state, self).await?;
94        } else {
95            anyhow::bail!("invalid port id");
96        }
97        channel.set_state(ChannelState::Closed);
98        state.put_channel(&self.chan_id_on_b, &self.port_id_on_b, channel.clone());
99
100        state.record(
101            events::channel::CloseConfirm {
102                port_id: self.port_id_on_b.clone(),
103                channel_id: self.chan_id_on_b.clone(),
104                counterparty_port_id: channel.counterparty().port_id.clone(),
105                counterparty_channel_id: channel
106                    .counterparty()
107                    .channel_id
108                    .clone()
109                    .unwrap_or_default(),
110                connection_id: channel.connection_hops[0].clone(),
111            }
112            .into(),
113        );
114
115        // TODO: should this be part of the handler?
116        let transfer = PortId::transfer();
117        if self.port_id_on_b == transfer {
118            AH::chan_close_confirm_execute(state, self).await;
119        } else {
120            anyhow::bail!("invalid port id");
121        }
122
123        Ok(())
124    }
125}