penumbra_sdk_ibc/component/msg_handler/
timeout.rs

1use anyhow::{Context, Result};
2use async_trait::async_trait;
3use cnidarium::StateWrite;
4use ibc_types::core::channel::{
5    channel::{Order as ChannelOrder, State as ChannelState},
6    events,
7    msgs::MsgTimeout,
8    PortId,
9};
10
11use crate::component::{
12    app_handler::{AppHandlerCheck, AppHandlerExecute},
13    channel::{StateReadExt as _, StateWriteExt},
14    client::StateReadExt,
15    connection::StateReadExt as _,
16    proof_verification::{commit_packet, PacketProofVerifier},
17    HostInterface, MsgHandler,
18};
19
20#[async_trait]
21impl MsgHandler for MsgTimeout {
22    async fn check_stateless<H: AppHandlerCheck>(&self) -> Result<()> {
23        // NOTE: no additional stateless validation is possible
24
25        Ok(())
26    }
27
28    async fn try_execute<
29        S: StateWrite,
30        H: AppHandlerCheck + AppHandlerExecute,
31        HI: HostInterface,
32    >(
33        &self,
34        mut state: S,
35    ) -> Result<()> {
36        tracing::debug!(msg = ?self);
37        let mut channel = state
38            .get_channel(&self.packet.chan_on_a, &self.packet.port_on_a)
39            .await
40            .context("failed to get channel")?
41            .ok_or_else(|| anyhow::anyhow!("channel not found"))?;
42        if !channel.state_matches(&ChannelState::Open) {
43            anyhow::bail!("channel is not open");
44        }
45
46        // TODO: capability authentication?
47        if self.packet.chan_on_b.ne(channel
48            .counterparty()
49            .channel_id()
50            .ok_or_else(|| anyhow::anyhow!("missing channel id"))?)
51        {
52            anyhow::bail!("packet destination channel does not match channel");
53        }
54        if self.packet.port_on_b != channel.counterparty().port_id {
55            anyhow::bail!("packet destination port does not match channel");
56        }
57
58        let connection = state
59            .get_connection(&channel.connection_hops[0])
60            .await
61            .context("failed to get connection")?
62            .ok_or_else(|| anyhow::anyhow!("connection not found for channel"))?;
63
64        let client_state = state.get_client_state(&connection.client_id).await?;
65        let last_consensus_state = state
66            .get_verified_consensus_state(&client_state.latest_height(), &connection.client_id)
67            .await?;
68        let last_update_time = last_consensus_state.timestamp;
69        let proof_update_height = self.proof_height_on_b;
70
71        // check that timeout height or timeout timestamp has passed on the other end
72        if !self
73            .packet
74            .timed_out(&last_update_time.into(), proof_update_height)
75        {
76            anyhow::bail!("packet has not timed out on the counterparty chain");
77        }
78
79        // verify that we actually sent this packet
80        let commitment = state
81            .get_packet_commitment(&self.packet)
82            .await
83            .context("failed to get packet commitment")?
84            .ok_or_else(|| anyhow::anyhow!("packet commitment not found"))?;
85        if commitment != commit_packet(&self.packet) {
86            anyhow::bail!("packet commitment does not match");
87        }
88
89        if channel.ordering == ChannelOrder::Ordered {
90            // ordered channel: check that packet has not been received
91            if self.next_seq_recv_on_b > self.packet.sequence {
92                anyhow::bail!("packet sequence number does not match");
93            }
94
95            // in the case of a timed-out ordered packet, the counterparty should have
96            // committed the next sequence number to their state
97            state
98                .verify_packet_timeout_proof::<HI>(&connection, self)
99                .await
100                .context("failed to verify packet timeout proof")?;
101        } else {
102            // in the case of a timed-out unordered packet, the counterparty should not have
103            // committed a receipt to the state.
104            state
105                .verify_packet_timeout_absence_proof::<HI>(&connection, self)
106                .await
107                .context("failed to verify packet timeout absence proof")?;
108        }
109
110        let transfer = PortId::transfer();
111        if self.packet.port_on_b == transfer {
112            H::timeout_packet_check(&mut state, self)
113                .await
114                .context("failed to execute handler for timeout_packet_check")?;
115        } else {
116            anyhow::bail!("invalid port id");
117        }
118
119        state.delete_packet_commitment(
120            &self.packet.chan_on_a,
121            &self.packet.port_on_a,
122            self.packet.sequence.into(),
123        );
124
125        if channel.ordering == ChannelOrder::Ordered {
126            // if the channel is ordered and we get a timeout packet, close the channel
127            channel.set_state(ChannelState::Closed);
128            state.put_channel(
129                &self.packet.chan_on_a,
130                &self.packet.port_on_a,
131                channel.clone(),
132            );
133        }
134
135        state.record(
136            events::packet::TimeoutPacket {
137                timeout_height: self.packet.timeout_height_on_b,
138                timeout_timestamp: self.packet.timeout_timestamp_on_b,
139                sequence: self.packet.sequence,
140                src_port_id: self.packet.port_on_a.clone(),
141                src_channel_id: self.packet.chan_on_a.clone(),
142                dst_port_id: self.packet.port_on_b.clone(),
143                dst_channel_id: self.packet.chan_on_b.clone(),
144                channel_ordering: channel.ordering,
145            }
146            .into(),
147        );
148
149        let transfer = PortId::transfer();
150        if self.packet.port_on_b == transfer {
151            H::timeout_packet_execute(state, self).await?;
152        } else {
153            anyhow::bail!("invalid port id");
154        }
155
156        Ok(())
157    }
158}