layer_climb_core/events/
ibc.rs

1use crate::ibc_types::{IbcChannelId, IbcConnectionId, IbcPortId};
2use crate::prelude::*;
3
4use crate::events::{
5    EVENT_ATTR_IBC_CONNECTION_ID, EVENT_ATTR_IBC_PACKET_ACK_HEX, EVENT_ATTR_IBC_PACKET_DATA_HEX,
6    EVENT_ATTR_IBC_PACKET_DST_CHANNEL, EVENT_ATTR_IBC_PACKET_DST_PORT,
7    EVENT_ATTR_IBC_PACKET_SEQUENCE, EVENT_ATTR_IBC_PACKET_SRC_CHANNEL,
8    EVENT_ATTR_IBC_PACKET_SRC_PORT, EVENT_ATTR_IBC_PACKET_TIMEOUT_HEIGHT,
9    EVENT_ATTR_IBC_PACKET_TIMEOUT_TIMESTAMP,
10};
11
12use super::{
13    Event, EVENT_TYPE_IBC_ACK_PACKET, EVENT_TYPE_IBC_RECV_PACKET, EVENT_TYPE_IBC_SEND_PACKET,
14    EVENT_TYPE_IBC_TIMEOUT_PACKET, EVENT_TYPE_IBC_WRITE_ACK,
15};
16
17#[derive(Clone)]
18pub struct IbcPacket {
19    pub src_port_id: IbcPortId,
20    pub src_channel_id: IbcChannelId,
21    pub dst_port_id: IbcPortId,
22    pub dst_channel_id: IbcChannelId,
23    pub src_connection_id: IbcConnectionId,
24    pub dst_connection_id: IbcConnectionId,
25    pub sequence: u64,
26    pub timeout_height: IbcPacketTimeoutHeight,
27    pub timeout_timestamp: u64,
28    pub data: Option<Vec<u8>>,
29    pub ack: Option<Vec<u8>>,
30    pub kind: IbcPacketKind,
31}
32
33impl std::fmt::Debug for IbcPacket {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        // write each field line by line, but hex encode data and ack
36        writeln!(f, "src_port_id: {}", self.src_port_id)?;
37        writeln!(f, "src_channel_id: {}", self.src_channel_id)?;
38        writeln!(f, "dst_port_id: {}", self.dst_port_id)?;
39        writeln!(f, "dst_channel_id: {}", self.dst_channel_id)?;
40        writeln!(f, "src_connection_id: {}", self.src_connection_id)?;
41        writeln!(f, "dst_connection_id: {}", self.dst_connection_id)?;
42        writeln!(f, "sequence: {}", self.sequence)?;
43        writeln!(f, "timeout_height: {:?}", self.timeout_height)?;
44        writeln!(f, "timeout_timestamp: {}", self.timeout_timestamp)?;
45        if let Some(data) = &self.data {
46            writeln!(f, "data: {}", const_hex::encode(data))?;
47        }
48        if let Some(ack) = &self.ack {
49            writeln!(f, "ack: {}", const_hex::encode(ack))?;
50        }
51        writeln!(f, "kind: {:?}", self.kind)
52    }
53}
54
55impl IbcPacket {
56    pub fn invert(&mut self) {
57        std::mem::swap(&mut self.src_port_id, &mut self.dst_port_id);
58        std::mem::swap(&mut self.src_channel_id, &mut self.dst_channel_id);
59        std::mem::swap(&mut self.src_connection_id, &mut self.dst_connection_id);
60    }
61}
62
63#[derive(Clone, Debug)]
64pub enum IbcPacketTimeoutHeight {
65    None,
66    Revision { height: u64, revision: u64 },
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub enum IbcPacketKind {
71    Send,
72    Receive,
73    WriteAck,
74    Ack,
75    Timeout,
76}
77
78impl TryFrom<&Event<'_>> for IbcPacketKind {
79    type Error = anyhow::Error;
80
81    fn try_from(event: &Event) -> Result<Self> {
82        if event.is_type(EVENT_TYPE_IBC_SEND_PACKET) {
83            Ok(IbcPacketKind::Send)
84        } else if event.is_type(EVENT_TYPE_IBC_RECV_PACKET) {
85            Ok(IbcPacketKind::Receive)
86        } else if event.is_type(EVENT_TYPE_IBC_ACK_PACKET) {
87            Ok(IbcPacketKind::Ack)
88        } else if event.is_type(EVENT_TYPE_IBC_WRITE_ACK) {
89            Ok(IbcPacketKind::WriteAck)
90        } else if event.is_type(EVENT_TYPE_IBC_TIMEOUT_PACKET) {
91            Ok(IbcPacketKind::Timeout)
92        } else {
93            Err(anyhow!("not an IBC packet event type: {}", event.ty()))
94        }
95    }
96}
97
98impl<'a> TryFrom<&Event<'a>> for IbcPacket {
99    type Error = anyhow::Error;
100
101    fn try_from(event: &Event<'a>) -> Result<Self> {
102        let kind: IbcPacketKind = event.try_into()?;
103
104        #[derive(Default)]
105        struct IbcPacketBuilder {
106            pub src_port_id: Option<IbcPortId>,
107            pub src_channel_id: Option<IbcChannelId>,
108            pub dst_port_id: Option<IbcPortId>,
109            pub dst_channel_id: Option<IbcChannelId>,
110            pub connection_id: Option<IbcConnectionId>,
111            pub sequence: Option<u64>,
112            pub timeout_height: Option<IbcPacketTimeoutHeight>,
113            pub timeout_timestamp: Option<u64>,
114            pub data: Option<Vec<u8>>,
115            pub ack: Option<Vec<u8>>,
116        }
117
118        let mut builder = IbcPacketBuilder::default();
119
120        // https://github.com/cosmos/relayer/blob/16a64aaac1839cd799c5b6e99458a644f68d788c/relayer/chains/parsing.go#L216
121        for attribute in event.attributes() {
122            if attribute.key() == EVENT_ATTR_IBC_PACKET_SRC_PORT {
123                builder.src_port_id = Some(IbcPortId::new(attribute.value()));
124            }
125
126            if attribute.key() == EVENT_ATTR_IBC_PACKET_SRC_CHANNEL {
127                builder.src_channel_id = Some(IbcChannelId::new(attribute.value()));
128            }
129
130            if attribute.key() == EVENT_ATTR_IBC_PACKET_DST_PORT {
131                builder.dst_port_id = Some(IbcPortId::new(attribute.value()));
132            }
133
134            if attribute.key() == EVENT_ATTR_IBC_PACKET_DST_CHANNEL {
135                builder.dst_channel_id = Some(IbcChannelId::new(attribute.value()));
136            }
137
138            if attribute.key() == EVENT_ATTR_IBC_CONNECTION_ID {
139                builder.connection_id = Some(IbcConnectionId::new(attribute.value()));
140            }
141
142            if attribute.key() == EVENT_ATTR_IBC_PACKET_SEQUENCE {
143                builder.sequence = Some(attribute.value().parse()?);
144            }
145
146            if attribute.key() == EVENT_ATTR_IBC_PACKET_TIMEOUT_HEIGHT {
147                // "{revision}-{height}"
148                let mut s = attribute.value().split('-');
149                let revision: u64 = s
150                    .next()
151                    .ok_or_else(|| anyhow!("missing revision"))?
152                    .parse()?;
153                let height: u64 = s.next().ok_or_else(|| anyhow!("missing height"))?.parse()?;
154                // uggggggh https://github.com/informalsystems/hermes/blob/1ee344fe5be1670fcb629e01b3ccb08c9e1ad9c2/crates/relayer-types/src/core/ics04_channel/timeout.rs#L22
155                if revision == 0 && height == 0 {
156                    builder.timeout_height = Some(IbcPacketTimeoutHeight::None);
157                } else {
158                    builder.timeout_height =
159                        Some(IbcPacketTimeoutHeight::Revision { revision, height });
160                }
161            }
162
163            if attribute.key() == EVENT_ATTR_IBC_PACKET_TIMEOUT_TIMESTAMP {
164                builder.timeout_timestamp = Some(attribute.value().parse()?);
165            }
166
167            if attribute.key() == EVENT_ATTR_IBC_PACKET_DATA_HEX {
168                let data = const_hex::decode(attribute.value())?;
169                builder.data = Some(data);
170            }
171            if attribute.key() == EVENT_ATTR_IBC_PACKET_ACK_HEX {
172                let ack = const_hex::decode(attribute.value())?;
173                builder.ack = Some(ack);
174            }
175        }
176
177        // connection_id from the event is just one field, doesn't know about src/dst
178        // this will be corrected in packet "normalization" in the relayer
179        let connection_id = builder
180            .connection_id
181            .ok_or_else(|| anyhow!("missing connection id"))?;
182
183        Ok(IbcPacket {
184            src_port_id: builder
185                .src_port_id
186                .ok_or_else(|| anyhow!("missing src port"))?,
187            src_channel_id: builder
188                .src_channel_id
189                .ok_or_else(|| anyhow!("missing src channel"))?,
190            dst_port_id: builder
191                .dst_port_id
192                .ok_or_else(|| anyhow!("missing dst port"))?,
193            dst_channel_id: builder
194                .dst_channel_id
195                .ok_or_else(|| anyhow!("missing dst channel"))?,
196            src_connection_id: connection_id.clone(),
197            dst_connection_id: connection_id.clone(),
198            sequence: builder
199                .sequence
200                .ok_or_else(|| anyhow!("missing sequence"))?,
201            timeout_height: builder
202                .timeout_height
203                .ok_or_else(|| anyhow!("missing timeout height"))?,
204            timeout_timestamp: builder
205                .timeout_timestamp
206                .ok_or_else(|| anyhow!("missing timeout timestamp"))?,
207            data: builder.data,
208            ack: builder.ack,
209            kind,
210        })
211    }
212}