penumbra_sdk_ibc/component/
packet.rs1use 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 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 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 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 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 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#[async_trait]
187pub trait SendPacketWrite: StateWrite {
188 async fn send_packet_execute(&mut self, packet: IBCPacket<Checked>) {
191 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 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 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 {}