penumbra_sdk_ibc/component/
packet.rs

1use anyhow::Result;
2use async_trait::async_trait;
3use cnidarium::{StateRead, StateWrite};
4use ibc_types::core::{
5    channel::{channel::State as ChannelState, events, ChannelId, Packet, PortId},
6    client::Height,
7};
8use tendermint::Time;
9
10use crate::component::{
11    channel::{StateReadExt as _, StateWriteExt as _},
12    client::StateReadExt as _,
13    connection::StateReadExt as _,
14};
15
16pub trait CheckStatus: private::Sealed {}
17
18#[derive(Debug, Clone)]
19pub enum Checked {}
20impl CheckStatus for Checked {}
21
22#[derive(Debug, Clone)]
23pub enum Unchecked {}
24impl CheckStatus for Unchecked {}
25
26mod private {
27    use super::*;
28
29    pub trait Sealed {}
30    impl Sealed for Checked {}
31    impl Sealed for Unchecked {}
32}
33
34pub struct IBCPacket<S: CheckStatus> {
35    pub(crate) source_port: PortId,
36    pub(crate) source_channel: ChannelId,
37    pub(crate) timeout_height: Height,
38    pub(crate) timeout_timestamp: u64,
39    pub(crate) data: Vec<u8>,
40
41    m: std::marker::PhantomData<S>,
42}
43
44impl IBCPacket<Unchecked> {
45    pub fn new(
46        source_port: PortId,
47        source_channel: ChannelId,
48        timeout_height: Height,
49        timeout_timestamp: u64,
50        data: Vec<u8>,
51    ) -> Self {
52        Self {
53            source_port,
54            source_channel,
55            timeout_height,
56            timeout_timestamp,
57            data,
58            m: std::marker::PhantomData,
59        }
60    }
61
62    pub fn assume_checked(self) -> IBCPacket<Checked> {
63        IBCPacket {
64            source_port: self.source_port,
65            source_channel: self.source_channel,
66            timeout_height: self.timeout_height,
67            timeout_timestamp: self.timeout_timestamp,
68            data: self.data,
69            m: std::marker::PhantomData,
70        }
71    }
72}
73
74impl<S: CheckStatus> IBCPacket<S> {
75    pub fn source_port(&self) -> &PortId {
76        &self.source_port
77    }
78
79    pub fn source_channel(&self) -> &ChannelId {
80        &self.source_channel
81    }
82
83    pub fn timeout_height(&self) -> &Height {
84        &self.timeout_height
85    }
86
87    pub fn timeout_timestamp(&self) -> u64 {
88        self.timeout_timestamp
89    }
90
91    pub fn data(&self) -> &[u8] {
92        &self.data
93    }
94}
95
96#[async_trait]
97pub trait SendPacketRead: StateRead {
98    /// send_packet_check verifies that a packet can be sent using the provided parameters.
99    async fn send_packet_check(
100        &self,
101        packet: IBCPacket<Unchecked>,
102        current_block_time: Time,
103    ) -> Result<IBCPacket<Checked>> {
104        let channel = self
105            .get_channel(&packet.source_channel, &packet.source_port)
106            .await?
107            .ok_or_else(|| {
108                anyhow::anyhow!(
109                    "channel {} on port {} does not exist",
110                    packet.source_channel,
111                    packet.source_port
112                )
113            })?;
114
115        if channel.state_matches(&ChannelState::Closed) {
116            anyhow::bail!(
117                "channel {} on port {} is closed",
118                packet.source_channel,
119                packet.source_port
120            );
121        }
122
123        // TODO: should we check dest port & channel here?
124        let connection = self
125            .get_connection(&channel.connection_hops[0])
126            .await?
127            .ok_or_else(|| {
128                anyhow::anyhow!("connection {} does not exist", channel.connection_hops[0])
129            })?;
130
131        // check that the client state is active so we don't do accidental sends on frozen clients.
132        let client_state = self.get_client_state(&connection.client_id).await?;
133        if client_state.is_frozen() {
134            anyhow::bail!("client {} is frozen", &connection.client_id);
135        }
136
137        let latest_consensus_state = self
138            .get_verified_consensus_state(&client_state.latest_height(), &connection.client_id)
139            .await?;
140
141        let time_elapsed = current_block_time.duration_since(latest_consensus_state.timestamp)?;
142
143        if client_state.expired(time_elapsed) {
144            anyhow::bail!("client {} is expired", &connection.client_id);
145        }
146
147        let latest_height = client_state.latest_height();
148
149        // check that time timeout height hasn't already passed in the local client tracking the
150        // receiving chain
151        if packet.timeout_height <= latest_height {
152            anyhow::bail!(
153                "timeout height {} is less than the latest height on the counterparty {}",
154                packet.timeout_height,
155                latest_height,
156            );
157        }
158
159        // check that the timeout timestamp hasn't already passed in the local client tracking
160        // the receiving chain
161        let chain_ts = latest_consensus_state.timestamp.unix_timestamp_nanos() as u64;
162        if packet.timeout_timestamp <= chain_ts {
163            anyhow::bail!(
164                "timeout timestamp {} is less than the latest timestamp on the counterparty {}",
165                packet.timeout_timestamp,
166                chain_ts,
167            );
168        }
169
170        Ok(IBCPacket::<Checked> {
171            source_port: packet.source_port.clone(),
172            source_channel: packet.source_channel,
173            timeout_height: packet.timeout_height,
174            timeout_timestamp: packet.timeout_timestamp,
175            data: packet.data,
176
177            m: std::marker::PhantomData,
178        })
179    }
180}
181
182impl<T: StateRead + ?Sized> SendPacketRead for T {}
183
184/// This trait, an extension of the Channel, Connection, and Client views, allows a component to
185/// send a packet.
186#[async_trait]
187pub trait SendPacketWrite: StateWrite {
188    /// Send a packet on a channel. This assumes that send_packet_check has already been called on
189    /// the provided packet.
190    async fn send_packet_execute(&mut self, packet: IBCPacket<Checked>) {
191        // increment the send sequence counter
192        let sequence = self
193            .get_send_sequence(&packet.source_channel, &packet.source_port)
194            .await
195            .expect("able to get send sequence while executing send packet");
196        self.put_send_sequence(&packet.source_channel, &packet.source_port, sequence + 1);
197
198        let channel = self
199            .get_channel(&packet.source_channel, &packet.source_port)
200            .await
201            .expect("should be able to get channel")
202            .ok_or_else(|| {
203                anyhow::anyhow!(
204                    "channel {} on port {} does not exist",
205                    packet.source_channel,
206                    packet.source_port
207                )
208            })
209            .expect("should be able to get channel");
210
211        // store commitment to the packet data & packet timeout
212        let packet = Packet {
213            chan_on_a: packet.source_channel,
214            port_on_a: packet.source_port.clone(),
215            sequence: sequence.into(),
216
217            chan_on_b: channel
218                .counterparty()
219                .channel_id
220                .clone()
221                .expect("should have counterparty channel"),
222            port_on_b: channel.counterparty().port_id.clone(),
223
224            timeout_height_on_b: packet.timeout_height.into(),
225            timeout_timestamp_on_b: ibc_types::timestamp::Timestamp::from_nanoseconds(
226                packet.timeout_timestamp,
227            )
228            .expect("able to parse timeout timestamp from nanoseconds"),
229
230            data: packet.data,
231        };
232
233        self.put_packet_commitment(&packet);
234
235        self.record(
236            events::packet::SendPacket {
237                packet_data: packet.data.clone(),
238                timeout_height: packet.timeout_height_on_b,
239                timeout_timestamp: packet.timeout_timestamp_on_b,
240                sequence: packet.sequence,
241                src_port_id: packet.port_on_a.clone(),
242                src_channel_id: packet.chan_on_a.clone(),
243                dst_port_id: packet.port_on_b.clone(),
244                dst_channel_id: packet.chan_on_b,
245                channel_ordering: channel.ordering,
246                src_connection_id: channel.connection_hops[0].clone(),
247            }
248            .into(),
249        );
250    }
251}
252
253impl<T: StateWrite + ?Sized> SendPacketWrite for T {}
254
255#[async_trait]
256pub trait WriteAcknowledgement: StateWrite {
257    // see: https://github.com/cosmos/ibc/blob/8326e26e7e1188b95c32481ff00348a705b23700/spec/core/ics-004-channel-and-packet-semantics/README.md?plain=1#L779
258    async fn write_acknowledgement(&mut self, packet: &Packet, ack_bytes: &[u8]) -> Result<()> {
259        if ack_bytes.is_empty() {
260            anyhow::bail!("acknowledgement cannot be empty");
261        }
262
263        let exists_prev_ack = self
264            .get_packet_acknowledgement(
265                &packet.port_on_b,
266                &packet.chan_on_b,
267                packet.sequence.into(),
268            )
269            .await?
270            .is_some();
271        if exists_prev_ack {
272            anyhow::bail!("acknowledgement already exists");
273        }
274
275        let channel = self
276            .get_channel(&packet.chan_on_b, &packet.port_on_b)
277            .await?
278            .ok_or_else(|| {
279                anyhow::anyhow!(
280                    "channel {} on port {} does not exist",
281                    packet.chan_on_b,
282                    packet.port_on_b
283                )
284            })?;
285
286        self.put_packet_acknowledgement(
287            &packet.port_on_b,
288            &packet.chan_on_b,
289            packet.sequence.into(),
290            ack_bytes,
291        );
292
293        self.record(
294            events::packet::WriteAcknowledgement {
295                packet_data: packet.data.clone(),
296                timeout_height: packet.timeout_height_on_b,
297                timeout_timestamp: packet.timeout_timestamp_on_b,
298                sequence: packet.sequence,
299                src_port_id: packet.port_on_a.clone(),
300                src_channel_id: packet.chan_on_a.clone(),
301                dst_port_id: packet.port_on_b.clone(),
302                dst_channel_id: packet.chan_on_b.clone(),
303                acknowledgement: ack_bytes.to_vec(),
304                dst_connection_id: channel.connection_hops[0].clone(),
305            }
306            .into(),
307        );
308
309        Ok(())
310    }
311}
312
313impl<T: StateWrite + ?Sized> WriteAcknowledgement for T {}