From 7c1d37dd19c83dfd1db79fa10cdcf0cfd2957b01 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 24 Jan 2022 18:59:15 +0530 Subject: [PATCH 01/11] Implement conversions for channel events --- modules/src/core/ics04_channel/events.rs | 146 +++++++++++++++-------- 1 file changed, 99 insertions(+), 47 deletions(-) diff --git a/modules/src/core/ics04_channel/events.rs b/modules/src/core/ics04_channel/events.rs index 11efb275b6..9c13951d90 100644 --- a/modules/src/core/ics04_channel/events.rs +++ b/modules/src/core/ics04_channel/events.rs @@ -8,7 +8,10 @@ use crate::core::ics02_client::height::Height; use crate::core::ics04_channel::error::Error; use crate::core::ics04_channel::packet::Packet; use crate::core::ics24_host::identifier::{ChannelId, ConnectionId, PortId}; -use crate::events::{extract_attribute, Error as EventError, IbcEvent, IbcEventType, RawObject}; +use crate::events::{ + extract_attribute, maybe_extract_attribute, Error as EventError, IbcEvent, IbcEventType, + RawObject, +}; use crate::prelude::*; /// Channel event attribute keys @@ -28,7 +31,6 @@ const PKT_DST_PORT_ATTRIBUTE_KEY: &str = "packet_dst_port"; const PKT_DST_CHANNEL_ATTRIBUTE_KEY: &str = "packet_dst_channel"; const PKT_TIMEOUT_HEIGHT_ATTRIBUTE_KEY: &str = "packet_timeout_height"; const PKT_TIMEOUT_TIMESTAMP_ATTRIBUTE_KEY: &str = "packet_timeout_timestamp"; - const PKT_ACK_ATTRIBUTE_KEY: &str = "packet_ack"; pub fn try_from_tx(event: &tendermint::abci::Event) -> Option { @@ -180,6 +182,31 @@ fn extract_packet_and_write_ack_from_tx( Ok((packet, write_ack)) } +fn extract_attributes(object: &RawObject, namespace: &str) -> Result { + Ok(Attributes { + height: object.height, + port_id: extract_attribute(object, &format!("{}.port_id", namespace))? + .parse() + .map_err(EventError::parse)?, + channel_id: maybe_extract_attribute(object, &format!("{}.channel_id", namespace)) + .and_then(|v| v.parse().ok()), + connection_id: extract_attribute(object, &format!("{}.connection_id", namespace))? + .parse() + .map_err(EventError::parse)?, + counterparty_port_id: extract_attribute( + object, + &format!("{}.counterparty_port_id", namespace), + )? + .parse() + .map_err(EventError::parse)?, + counterparty_channel_id: maybe_extract_attribute( + object, + &format!("{}.counterparty_channel_id", namespace), + ) + .and_then(|v| v.parse().ok()), + }) +} + #[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Attributes { pub height: Height, @@ -316,6 +343,10 @@ impl TryFrom for Vec { } } +trait EventType { + fn event_type() -> IbcEventType; +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct OpenInit(pub Attributes); @@ -349,13 +380,9 @@ impl From for IbcEvent { } } -impl From for AbciEvent { - fn from(v: OpenInit) -> Self { - let attributes = Vec::::from(v.0); - AbciEvent { - type_str: IbcEventType::OpenInitChannel.as_str().to_string(), - attributes, - } +impl EventType for OpenInit { + fn event_type() -> IbcEventType { + IbcEventType::OpenInitChannel } } @@ -392,13 +419,9 @@ impl From for IbcEvent { } } -impl From for AbciEvent { - fn from(v: OpenTry) -> Self { - let attributes = Vec::::from(v.0); - AbciEvent { - type_str: IbcEventType::OpenTryChannel.as_str().to_string(), - attributes, - } +impl EventType for OpenTry { + fn event_type() -> IbcEventType { + IbcEventType::OpenTryChannel } } @@ -439,13 +462,9 @@ impl From for IbcEvent { } } -impl From for AbciEvent { - fn from(v: OpenAck) -> Self { - let attributes = Vec::::from(v.0); - AbciEvent { - type_str: IbcEventType::OpenAckChannel.as_str().to_string(), - attributes, - } +impl EventType for OpenAck { + fn event_type() -> IbcEventType { + IbcEventType::OpenAckChannel } } @@ -482,13 +501,9 @@ impl From for IbcEvent { } } -impl From for AbciEvent { - fn from(v: OpenConfirm) -> Self { - let attributes = Vec::::from(v.0); - AbciEvent { - type_str: IbcEventType::OpenConfirmChannel.as_str().to_string(), - attributes, - } +impl EventType for OpenConfirm { + fn event_type() -> IbcEventType { + IbcEventType::OpenConfirmChannel } } @@ -537,16 +552,6 @@ impl From for IbcEvent { } } -impl From for AbciEvent { - fn from(v: CloseInit) -> Self { - let attributes = Vec::::from(v.0); - AbciEvent { - type_str: IbcEventType::CloseInitChannel.as_str().to_string(), - attributes, - } - } -} - impl core::fmt::Display for CloseInit { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { write!( @@ -559,6 +564,12 @@ impl core::fmt::Display for CloseInit { } } +impl EventType for CloseInit { + fn event_type() -> IbcEventType { + IbcEventType::CloseInitChannel + } +} + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct CloseConfirm(pub Attributes); @@ -586,16 +597,57 @@ impl From for IbcEvent { } } -impl From for AbciEvent { - fn from(v: CloseConfirm) -> Self { - let attributes = Vec::::from(v.0); - AbciEvent { - type_str: IbcEventType::CloseConfirmChannel.as_str().to_string(), - attributes, - } +impl EventType for CloseConfirm { + fn event_type() -> IbcEventType { + IbcEventType::CloseConfirmChannel } } +macro_rules! impl_try_from_ev_to_abci_ev { + ($($event:ty),+) => { + $(impl From<$event> for AbciEvent { + fn from(v: $event) -> Self { + let attributes = Vec::::from(v.0); + let type_str = <$event>::event_type().as_str().to_string(); + AbciEvent { + type_str, + attributes, + } + } + })+ + }; +} + +impl_try_from_ev_to_abci_ev!( + OpenInit, + OpenTry, + OpenAck, + OpenConfirm, + CloseInit, + CloseConfirm +); + +macro_rules! impl_try_from_raw_obj_for_event { + ($($event:ty),+) => { + $(impl TryFrom for $event { + type Error = EventError; + + fn try_from(obj: RawObject) -> Result { + Ok(Self(extract_attributes(&obj, Self::event_type().as_str())?)) + } + })+ + }; +} + +impl_try_from_raw_obj_for_event!( + OpenInit, + OpenTry, + OpenAck, + OpenConfirm, + CloseInit, + CloseConfirm +); + #[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct SendPacket { pub height: Height, From 0b93b1f0a4564053ed7b4405e394654646b64da5 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 24 Jan 2022 18:59:29 +0530 Subject: [PATCH 02/11] Implement conversions for packets --- modules/src/core/ics04_channel/events.rs | 61 ++++++++++++++++++------ 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/modules/src/core/ics04_channel/events.rs b/modules/src/core/ics04_channel/events.rs index 9c13951d90..07b4628b54 100644 --- a/modules/src/core/ics04_channel/events.rs +++ b/modules/src/core/ics04_channel/events.rs @@ -675,20 +675,6 @@ impl SendPacket { } } -impl TryFrom for SendPacket { - type Error = EventError; - - fn try_from(obj: RawObject) -> Result { - let height = obj.height; - let data_str: String = extract_attribute(&obj, &format!("{}.packet_data", obj.action))?; - - let mut packet = Packet::try_from(obj)?; - packet.data = Vec::from(data_str.as_str().as_bytes()); - - Ok(SendPacket { height, packet }) - } -} - impl From for IbcEvent { fn from(v: SendPacket) -> Self { IbcEvent::SendPacket(v) @@ -956,6 +942,53 @@ impl core::fmt::Display for TimeoutOnClosePacket { } } +macro_rules! impl_try_from_raw_obj_for_packet { + ($($packet:ty),+) => { + $(impl TryFrom for $packet { + type Error = EventError; + + fn try_from(obj: RawObject) -> Result { + let height = obj.height; + let data_str: String = extract_attribute(&obj, &format!("{}.{}", obj.action, PKT_DATA_ATTRIBUTE_KEY))?; + + let mut packet = Packet::try_from(obj)?; + packet.data = Vec::from(data_str.as_str().as_bytes()); + + Ok(Self { height, packet }) + } + })+ + }; +} + +impl_try_from_raw_obj_for_packet!( + SendPacket, + ReceivePacket, + AcknowledgePacket, + TimeoutPacket, + TimeoutOnClosePacket +); + +impl TryFrom for WriteAcknowledgement { + type Error = EventError; + + fn try_from(obj: RawObject) -> Result { + let height = obj.height; + let data_str: String = + extract_attribute(&obj, &format!("{}.{}", obj.action, PKT_DATA_ATTRIBUTE_KEY))?; + let ack = extract_attribute(&obj, &format!("{}.{}", obj.action, PKT_ACK_ATTRIBUTE_KEY))? + .into_bytes(); + + let mut packet = Packet::try_from(obj)?; + packet.data = Vec::from(data_str.as_str().as_bytes()); + + Ok(Self { + height, + packet, + ack, + }) + } +} + #[cfg(test)] mod tests { use super::*; From c8be5ea01cc548be3749ac4ca184623622e16bc7 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 24 Jan 2022 19:04:08 +0530 Subject: [PATCH 03/11] Resurrect code (from PR #1172) to extract begin/end-block events from a tendermint NewBlock event --- modules/src/applications/mod.rs | 4 + relayer/src/event/rpc.rs | 201 ++++++++++++++++++++++++++++---- 2 files changed, 180 insertions(+), 25 deletions(-) diff --git a/modules/src/applications/mod.rs b/modules/src/applications/mod.rs index e40402d145..1071464754 100644 --- a/modules/src/applications/mod.rs +++ b/modules/src/applications/mod.rs @@ -14,4 +14,8 @@ pub mod ics27_interchain_accounts { /// ICS27 application current version. pub const VERSION: &str = "ics27-1"; + + pub const ICS27_BANK_SEND_TYPE_URL: &str = "/cosmos.bank.v1beta1.MsgSend"; + pub const ICS27_SEND_TYPE_URL: &str = "/intertx.MsgSend"; + pub const ICS27_REGISTER_TYPE_URL: &str = "/intertx.MsgRegister"; } diff --git a/relayer/src/event/rpc.rs b/relayer/src/event/rpc.rs index e3f3ac7640..994451a0ea 100644 --- a/relayer/src/event/rpc.rs +++ b/relayer/src/event/rpc.rs @@ -1,12 +1,13 @@ use alloc::collections::BTreeMap as HashMap; +use core::convert::TryFrom; use tendermint_rpc::{event::Event as RpcEvent, event::EventData as RpcEventData}; use ibc::core::ics02_client::{events as ClientEvents, height::Height}; use ibc::core::ics03_connection::events as ConnectionEvents; -use ibc::core::ics04_channel::events as ChannelEvents; +use ibc::core::ics04_channel::{events as ChannelEvents, msgs as chan_msgs}; use ibc::core::ics24_host::identifier::ChainId; -use ibc::events::{IbcEvent, RawObject}; +use ibc::events::{Error as EventError, IbcEvent, RawObject}; use crate::event::monitor::queries; @@ -15,8 +16,10 @@ pub fn get_all_events( result: RpcEvent, ) -> Result, String> { let mut vals: Vec<(Height, IbcEvent)> = vec![]; + let RpcEvent { data, events, .. } = result; + let events = events.ok_or("missing events")?; - match &result.data { + match data { RpcEventData::NewBlock { block, .. } => { let height = Height::new( ChainId::chain_version(chain_id.to_string().as_str()), @@ -25,11 +28,15 @@ pub fn get_all_events( vals.push((height, ClientEvents::NewBlock::new(height).into())); - if let Some(events) = &result.events { - let ibc_events = - send_packet_from_block_events(height, events.clone().into_iter().collect()); - if !ibc_events.is_empty() { - vals.extend(ibc_events); + let actions_and_indices = extract_helper(&events)?; + for action in actions_and_indices { + if let Ok(event) = build_event(RawObject::new( + height, + action.0, + action.1 as usize, + events.clone(), + )) { + vals.push((height, event)); } } } @@ -62,11 +69,8 @@ pub fn get_all_events( tracing::trace!("extracted {:?}", chan_event); if matches!(chan_event, IbcEvent::SendPacket(_)) { // Should be the same as the hash of tx_result.tx? - if let Some(hash) = result - .events - .as_ref() - .and_then(|events| events.get("tx.hash")) - .and_then(|values| values.get(0)) + if let Some(hash) = + events.get("tx.hash").and_then(|values| values.get(0)) { tracing::trace!(event = "SendPacket", "tx hash: {}", hash); } @@ -82,19 +86,166 @@ pub fn get_all_events( Ok(vals) } -fn send_packet_from_block_events( - height: Height, - events: HashMap>, -) -> Vec<(Height, IbcEvent)> { - let mut vals: Vec<(Height, IbcEvent)> = vec![]; - if let Some(packets) = events.get("send_packet.packet_data") { - for i in 0..packets.len() { - let raw_obj = RawObject::new(height, "send_packet".to_string(), i, events.clone()); +pub fn build_event(mut object: RawObject) -> Result { + match object.action.as_str() { + // Channel events + "channel_open_init" | chan_msgs::chan_open_init::TYPE_URL => { + Ok(IbcEvent::from(ChannelEvents::OpenInit::try_from(object)?)) + } + "channel_open_try" | chan_msgs::chan_open_try::TYPE_URL => { + Ok(IbcEvent::from(ChannelEvents::OpenTry::try_from(object)?)) + } + "channel_open_ack" | chan_msgs::chan_open_ack::TYPE_URL => { + Ok(IbcEvent::from(ChannelEvents::OpenAck::try_from(object)?)) + } + "channel_open_confirm" | chan_msgs::chan_open_confirm::TYPE_URL => Ok(IbcEvent::from( + ChannelEvents::OpenConfirm::try_from(object)?, + )), + "channel_close_init" | chan_msgs::chan_close_init::TYPE_URL => { + Ok(IbcEvent::from(ChannelEvents::CloseInit::try_from(object)?)) + } + "channel_close_confirm" | chan_msgs::chan_close_confirm::TYPE_URL => Ok(IbcEvent::from( + ChannelEvents::CloseConfirm::try_from(object)?, + )), - if let Ok(pkg) = ChannelEvents::SendPacket::try_from(raw_obj) { - vals.push((height, IbcEvent::from(pkg))) - } + // Packet events + // Note: There is no message.action "send_packet", the only one we can hook into is the + // module's action: + // - "transfer" for ICS20 + // - "MsgSend" and "MsgRegister" for ICS27 + // However the attributes are all prefixed with "send_packet" therefore the overwrite here + // TODO: This need to be sorted out + "transfer" + | ibc::applications::ics20_fungible_token_transfer::msgs::transfer::TYPE_URL + | ibc::applications::ics27_interchain_accounts::ICS27_BANK_SEND_TYPE_URL + | ibc::applications::ics27_interchain_accounts::ICS27_SEND_TYPE_URL + | ibc::applications::ics27_interchain_accounts::ICS27_REGISTER_TYPE_URL => { + object.action = "send_packet".to_string(); + Ok(IbcEvent::from(ChannelEvents::SendPacket::try_from(object)?)) + } + // Same here + // TODO: sort this out + "recv_packet" | chan_msgs::recv_packet::TYPE_URL => { + object.action = "write_acknowledgement".to_string(); + Ok(IbcEvent::from( + ChannelEvents::WriteAcknowledgement::try_from(object)?, + )) + } + "write_acknowledgement" => Ok(IbcEvent::from( + ChannelEvents::WriteAcknowledgement::try_from(object)?, + )), + "acknowledge_packet" | chan_msgs::acknowledgement::TYPE_URL => { + object.action = "acknowledge_packet".to_string(); + Ok(IbcEvent::from(ChannelEvents::AcknowledgePacket::try_from( + object, + )?)) + } + "timeout_packet" | chan_msgs::timeout::TYPE_URL => { + object.action = "timeout_packet".to_string(); + Ok(IbcEvent::from(ChannelEvents::TimeoutPacket::try_from( + object, + )?)) } + "timeout_on_close_packet" | chan_msgs::timeout_on_close::TYPE_URL => { + object.action = "timeout_packet".to_string(); + Ok(IbcEvent::from( + ChannelEvents::TimeoutOnClosePacket::try_from(object)?, + )) + } + + event_type => Err(EventError::incorrect_event_type(event_type.to_string())), } - vals +} + +/// Takes events in the form +/// +/// ```json +/// { +/// "events": { +/// "connection_open_init.client_id": [ +/// "testclient", +/// "testclientsec" +/// ], +/// "connection_open_init.connection_id": [ +/// "ancaconnonetest", +/// "ancaconnonetestsec" +/// ], +/// "connection_open_init.counterparty_client_id": [ +/// "testclientsec", +/// "testclientsecsec" +/// ], +/// "create_client.client_id": [ +/// "testclientthird" +/// ], +/// "create_client.client_type": [ +/// "tendermint" +/// ], +/// "message.action": [ +/// "connection_open_init", +/// "create_client", +/// "connection_open_init" +/// ], +/// "message.module": [ +/// "ibc_connection", +/// "ibc_client", +/// "ibc_connection" +/// ], +/// "message.sender": [ +/// "cosmos187xxg4yfkypl05cqylucezpjvycj24nurvm8p9", +/// "cosmos187xxg4yfkypl05cqylucezpjvycj24nurvm8p9", +/// "cosmos187xxg4yfkypl05cqylucezpjvycj24nurvm8p9", +/// "cosmos187xxg4yfkypl05cqylucezpjvycj24nurvm8p9" +/// ], +/// "tm.event": [ +/// "Tx" +/// ], +/// "transfer.amount": [ +/// "5000stake" +/// ], +/// "transfer.recipient": [ +/// "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta" +/// ], +/// "tx.hash": [ +/// "A9E18AE3909F22232F8DBDB1C48F2FECB260A308A2D157E8832E901D45950605" +/// ], +/// "tx.height": [ +/// "35" +/// ] +/// } +/// } +/// ``` +/// +/// and returns: +/// +/// ```rust +/// vec![ +/// ("connection_open_init", 0), +/// ("create_client", 0), +/// ("connection_open_init", 1), +/// ]; +/// ``` +/// +/// where the number in each entry is the index in the matching events that should be used to build the event. +/// +/// e.g. for the last "connection_open_init" in the result +/// +/// ```text +/// "connection_open_init.client_id" -> "testclientsec" +/// "connection_open_init.connection_id" -> "ancaconnonetestsec", +/// "connection_open_init.counterparty_client_id" -> "testclientsec", "testclientsecsec", +/// ``` +fn extract_helper(events: &HashMap>) -> Result, String> { + let actions = events.get("message.action").ok_or("Incorrect Event Type")?; + + let mut val_indices = HashMap::new(); + let mut result = Vec::with_capacity(actions.len()); + + for action in actions { + let idx = val_indices.entry(action.clone()).or_insert_with(|| 0); + result.push((action.clone(), *idx)); + + *val_indices.get_mut(action.as_str()).unwrap() += 1; + } + + Ok(result) } From 7f8066408f3a79176eb5c1738ddc5ee3f892da98 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 24 Jan 2022 20:55:53 +0530 Subject: [PATCH 04/11] Add channel events in the right order --- relayer/src/event/rpc.rs | 42 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/relayer/src/event/rpc.rs b/relayer/src/event/rpc.rs index 994451a0ea..5534ba5ed1 100644 --- a/relayer/src/event/rpc.rs +++ b/relayer/src/event/rpc.rs @@ -28,17 +28,25 @@ pub fn get_all_events( vals.push((height, ClientEvents::NewBlock::new(height).into())); + let mut chan_events = vec![]; let actions_and_indices = extract_helper(&events)?; for action in actions_and_indices { - if let Ok(event) = build_event(RawObject::new( + if let Ok(event) = build_channel_event(RawObject::new( height, action.0, action.1 as usize, events.clone(), )) { - vals.push((height, event)); + chan_events.push((height, event)); } } + chan_events.sort_by(|a, b| { + let a = ChannelEventType::from(&a.1); + let b = ChannelEventType::from(&b.1); + a.cmp(&b) + }); + + vals.append(&mut chan_events); } RpcEventData::Tx { tx_result } => { let height = Height::new( @@ -86,7 +94,7 @@ pub fn get_all_events( Ok(vals) } -pub fn build_event(mut object: RawObject) -> Result { +pub fn build_channel_event(mut object: RawObject) -> Result { match object.action.as_str() { // Channel events "channel_open_init" | chan_msgs::chan_open_init::TYPE_URL => { @@ -249,3 +257,31 @@ fn extract_helper(events: &HashMap>) -> Result `Open > Packet > Close`. +#[derive(Eq, PartialEq, PartialOrd, Ord)] +enum ChannelEventType { + Open, + Packet, + Close, +} + +impl From<&IbcEvent> for ChannelEventType { + fn from(ev: &IbcEvent) -> Self { + match ev { + IbcEvent::OpenInitChannel(_) + | IbcEvent::OpenTryChannel(_) + | IbcEvent::OpenAckChannel(_) + | IbcEvent::OpenConfirmChannel(_) => Self::Open, + IbcEvent::CloseInitChannel(_) | IbcEvent::CloseConfirmChannel(_) => Self::Close, + IbcEvent::SendPacket(_) + | IbcEvent::ReceivePacket(_) + | IbcEvent::WriteAcknowledgement(_) + | IbcEvent::AcknowledgePacket(_) + | IbcEvent::TimeoutPacket(_) + | IbcEvent::TimeoutOnClosePacket(_) => Self::Packet, + _ => unreachable!(), + } + } +} From d4cc05272c5ab6d3e3be90978b1e29ff5a7c4b7a Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 24 Jan 2022 21:32:26 +0530 Subject: [PATCH 05/11] Remove redundant clones --- modules/src/core/ics04_channel/events.rs | 14 +++++++------- modules/src/core/ics04_channel/packet.rs | 4 ++-- modules/src/events.rs | 16 ++++++++-------- relayer/src/event/rpc.rs | 4 ++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/src/core/ics04_channel/events.rs b/modules/src/core/ics04_channel/events.rs index 07b4628b54..e35afa7ab1 100644 --- a/modules/src/core/ics04_channel/events.rs +++ b/modules/src/core/ics04_channel/events.rs @@ -182,7 +182,7 @@ fn extract_packet_and_write_ack_from_tx( Ok((packet, write_ack)) } -fn extract_attributes(object: &RawObject, namespace: &str) -> Result { +fn extract_attributes(object: &RawObject<'_>, namespace: &str) -> Result { Ok(Attributes { height: object.height, port_id: extract_attribute(object, &format!("{}.port_id", namespace))? @@ -629,10 +629,10 @@ impl_try_from_ev_to_abci_ev!( macro_rules! impl_try_from_raw_obj_for_event { ($($event:ty),+) => { - $(impl TryFrom for $event { + $(impl TryFrom> for $event { type Error = EventError; - fn try_from(obj: RawObject) -> Result { + fn try_from(obj: RawObject<'_>) -> Result { Ok(Self(extract_attributes(&obj, Self::event_type().as_str())?)) } })+ @@ -944,10 +944,10 @@ impl core::fmt::Display for TimeoutOnClosePacket { macro_rules! impl_try_from_raw_obj_for_packet { ($($packet:ty),+) => { - $(impl TryFrom for $packet { + $(impl TryFrom> for $packet { type Error = EventError; - fn try_from(obj: RawObject) -> Result { + fn try_from(obj: RawObject<'_>) -> Result { let height = obj.height; let data_str: String = extract_attribute(&obj, &format!("{}.{}", obj.action, PKT_DATA_ATTRIBUTE_KEY))?; @@ -968,10 +968,10 @@ impl_try_from_raw_obj_for_packet!( TimeoutOnClosePacket ); -impl TryFrom for WriteAcknowledgement { +impl TryFrom> for WriteAcknowledgement { type Error = EventError; - fn try_from(obj: RawObject) -> Result { + fn try_from(obj: RawObject<'_>) -> Result { let height = obj.height; let data_str: String = extract_attribute(&obj, &format!("{}.{}", obj.action, PKT_DATA_ATTRIBUTE_KEY))?; diff --git a/modules/src/core/ics04_channel/packet.rs b/modules/src/core/ics04_channel/packet.rs index 6b4cdd1093..5dd90e1e1f 100644 --- a/modules/src/core/ics04_channel/packet.rs +++ b/modules/src/core/ics04_channel/packet.rs @@ -189,9 +189,9 @@ impl TryFrom for Packet { } } -impl TryFrom for Packet { +impl TryFrom> for Packet { type Error = EventError; - fn try_from(obj: RawObject) -> Result { + fn try_from(obj: RawObject<'_>) -> Result { Ok(Packet { sequence: extract_attribute(&obj, &format!("{}.packet_sequence", obj.action))? .parse() diff --git a/modules/src/events.rs b/modules/src/events.rs index 43dcf01789..442be3e604 100644 --- a/modules/src/events.rs +++ b/modules/src/events.rs @@ -463,21 +463,21 @@ impl IbcEvent { } } -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct RawObject { +#[derive(Debug, Clone, Serialize)] +pub struct RawObject<'a> { pub height: Height, pub action: String, pub idx: usize, - pub events: HashMap>, + pub events: &'a HashMap>, } -impl RawObject { +impl<'a> RawObject<'a> { pub fn new( height: Height, action: String, idx: usize, - events: HashMap>, - ) -> RawObject { + events: &'a HashMap>, + ) -> RawObject<'a> { RawObject { height, action, @@ -500,7 +500,7 @@ pub fn extract_events( Err(Error::incorrect_event_type(action_string.to_string())) } -pub fn extract_attribute(object: &RawObject, key: &str) -> Result { +pub fn extract_attribute(object: &RawObject<'_>, key: &str) -> Result { let value = object .events .get(key) @@ -510,6 +510,6 @@ pub fn extract_attribute(object: &RawObject, key: &str) -> Result Ok(value) } -pub fn maybe_extract_attribute(object: &RawObject, key: &str) -> Option { +pub fn maybe_extract_attribute(object: &RawObject<'_>, key: &str) -> Option { object.events.get(key).map(|tags| tags[object.idx].clone()) } diff --git a/relayer/src/event/rpc.rs b/relayer/src/event/rpc.rs index 5534ba5ed1..a2b135ccbb 100644 --- a/relayer/src/event/rpc.rs +++ b/relayer/src/event/rpc.rs @@ -35,7 +35,7 @@ pub fn get_all_events( height, action.0, action.1 as usize, - events.clone(), + &events, )) { chan_events.push((height, event)); } @@ -94,7 +94,7 @@ pub fn get_all_events( Ok(vals) } -pub fn build_channel_event(mut object: RawObject) -> Result { +pub fn build_channel_event(mut object: RawObject<'_>) -> Result { match object.action.as_str() { // Channel events "channel_open_init" | chan_msgs::chan_open_init::TYPE_URL => { From 6dee987fb54765045d03cc9f109c2ff60a1ed784 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 24 Jan 2022 22:47:14 +0530 Subject: [PATCH 06/11] Minor refactoring --- modules/src/core/ics04_channel/events.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/src/core/ics04_channel/events.rs b/modules/src/core/ics04_channel/events.rs index e35afa7ab1..43e22d02c9 100644 --- a/modules/src/core/ics04_channel/events.rs +++ b/modules/src/core/ics04_channel/events.rs @@ -603,7 +603,7 @@ impl EventType for CloseConfirm { } } -macro_rules! impl_try_from_ev_to_abci_ev { +macro_rules! impl_from_ibc_to_abci_event { ($($event:ty),+) => { $(impl From<$event> for AbciEvent { fn from(v: $event) -> Self { @@ -618,7 +618,7 @@ macro_rules! impl_try_from_ev_to_abci_ev { }; } -impl_try_from_ev_to_abci_ev!( +impl_from_ibc_to_abci_event!( OpenInit, OpenTry, OpenAck, From d16bc383fe3ed5344beb7e1654ab06cc2295c767 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 31 Jan 2022 17:01:48 +0530 Subject: [PATCH 07/11] Fix failing CI tests --- relayer/src/event/rpc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/relayer/src/event/rpc.rs b/relayer/src/event/rpc.rs index a2b135ccbb..f094368121 100644 --- a/relayer/src/event/rpc.rs +++ b/relayer/src/event/rpc.rs @@ -29,7 +29,7 @@ pub fn get_all_events( vals.push((height, ClientEvents::NewBlock::new(height).into())); let mut chan_events = vec![]; - let actions_and_indices = extract_helper(&events)?; + let actions_and_indices = extract_helper(&events).unwrap_or_default(); for action in actions_and_indices { if let Ok(event) = build_channel_event(RawObject::new( height, From ca3169ad316365865e4ee98a62652360ea158640 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 31 Jan 2022 21:02:53 +0530 Subject: [PATCH 08/11] Extract block events without depending on message.action --- relayer/src/event/rpc.rs | 281 ++++++++++----------------------------- 1 file changed, 73 insertions(+), 208 deletions(-) diff --git a/relayer/src/event/rpc.rs b/relayer/src/event/rpc.rs index f094368121..300b1f7534 100644 --- a/relayer/src/event/rpc.rs +++ b/relayer/src/event/rpc.rs @@ -5,9 +5,9 @@ use tendermint_rpc::{event::Event as RpcEvent, event::EventData as RpcEventData} use ibc::core::ics02_client::{events as ClientEvents, height::Height}; use ibc::core::ics03_connection::events as ConnectionEvents; -use ibc::core::ics04_channel::{events as ChannelEvents, msgs as chan_msgs}; +use ibc::core::ics04_channel::events as ChannelEvents; use ibc::core::ics24_host::identifier::ChainId; -use ibc::events::{Error as EventError, IbcEvent, RawObject}; +use ibc::events::{IbcEvent, RawObject}; use crate::event::monitor::queries; @@ -27,26 +27,7 @@ pub fn get_all_events( ); vals.push((height, ClientEvents::NewBlock::new(height).into())); - - let mut chan_events = vec![]; - let actions_and_indices = extract_helper(&events).unwrap_or_default(); - for action in actions_and_indices { - if let Ok(event) = build_channel_event(RawObject::new( - height, - action.0, - action.1 as usize, - &events, - )) { - chan_events.push((height, event)); - } - } - chan_events.sort_by(|a, b| { - let a = ChannelEventType::from(&a.1); - let b = ChannelEventType::from(&b.1); - a.cmp(&b) - }); - - vals.append(&mut chan_events); + vals.append(&mut extract_block_events(height, &events)); } RpcEventData::Tx { tx_result } => { let height = Height::new( @@ -94,194 +75,78 @@ pub fn get_all_events( Ok(vals) } -pub fn build_channel_event(mut object: RawObject<'_>) -> Result { - match object.action.as_str() { - // Channel events - "channel_open_init" | chan_msgs::chan_open_init::TYPE_URL => { - Ok(IbcEvent::from(ChannelEvents::OpenInit::try_from(object)?)) - } - "channel_open_try" | chan_msgs::chan_open_try::TYPE_URL => { - Ok(IbcEvent::from(ChannelEvents::OpenTry::try_from(object)?)) - } - "channel_open_ack" | chan_msgs::chan_open_ack::TYPE_URL => { - Ok(IbcEvent::from(ChannelEvents::OpenAck::try_from(object)?)) - } - "channel_open_confirm" | chan_msgs::chan_open_confirm::TYPE_URL => Ok(IbcEvent::from( - ChannelEvents::OpenConfirm::try_from(object)?, - )), - "channel_close_init" | chan_msgs::chan_close_init::TYPE_URL => { - Ok(IbcEvent::from(ChannelEvents::CloseInit::try_from(object)?)) - } - "channel_close_confirm" | chan_msgs::chan_close_confirm::TYPE_URL => Ok(IbcEvent::from( - ChannelEvents::CloseConfirm::try_from(object)?, - )), - - // Packet events - // Note: There is no message.action "send_packet", the only one we can hook into is the - // module's action: - // - "transfer" for ICS20 - // - "MsgSend" and "MsgRegister" for ICS27 - // However the attributes are all prefixed with "send_packet" therefore the overwrite here - // TODO: This need to be sorted out - "transfer" - | ibc::applications::ics20_fungible_token_transfer::msgs::transfer::TYPE_URL - | ibc::applications::ics27_interchain_accounts::ICS27_BANK_SEND_TYPE_URL - | ibc::applications::ics27_interchain_accounts::ICS27_SEND_TYPE_URL - | ibc::applications::ics27_interchain_accounts::ICS27_REGISTER_TYPE_URL => { - object.action = "send_packet".to_string(); - Ok(IbcEvent::from(ChannelEvents::SendPacket::try_from(object)?)) - } - // Same here - // TODO: sort this out - "recv_packet" | chan_msgs::recv_packet::TYPE_URL => { - object.action = "write_acknowledgement".to_string(); - Ok(IbcEvent::from( - ChannelEvents::WriteAcknowledgement::try_from(object)?, - )) - } - "write_acknowledgement" => Ok(IbcEvent::from( - ChannelEvents::WriteAcknowledgement::try_from(object)?, - )), - "acknowledge_packet" | chan_msgs::acknowledgement::TYPE_URL => { - object.action = "acknowledge_packet".to_string(); - Ok(IbcEvent::from(ChannelEvents::AcknowledgePacket::try_from( - object, - )?)) - } - "timeout_packet" | chan_msgs::timeout::TYPE_URL => { - object.action = "timeout_packet".to_string(); - Ok(IbcEvent::from(ChannelEvents::TimeoutPacket::try_from( - object, - )?)) - } - "timeout_on_close_packet" | chan_msgs::timeout_on_close::TYPE_URL => { - object.action = "timeout_packet".to_string(); - Ok(IbcEvent::from( - ChannelEvents::TimeoutOnClosePacket::try_from(object)?, - )) - } - - event_type => Err(EventError::incorrect_event_type(event_type.to_string())), +fn extract_block_events( + height: Height, + block_events: &HashMap>, +) -> Vec<(Height, IbcEvent)> { + #[inline] + fn extract_events<'a, T: TryFrom>>( + height: Height, + block_events: &'a HashMap>, + event_type: &str, + event_field: &str, + ) -> Vec { + block_events + .get(&format!("{}.{}", event_type, event_field)) + .unwrap_or(&vec![]) + .iter() + .enumerate() + .filter_map(|(i, _)| { + let raw_obj = RawObject::new(height, event_type.to_owned(), i, block_events); + T::try_from(raw_obj).ok() + }) + .collect() } -} - -/// Takes events in the form -/// -/// ```json -/// { -/// "events": { -/// "connection_open_init.client_id": [ -/// "testclient", -/// "testclientsec" -/// ], -/// "connection_open_init.connection_id": [ -/// "ancaconnonetest", -/// "ancaconnonetestsec" -/// ], -/// "connection_open_init.counterparty_client_id": [ -/// "testclientsec", -/// "testclientsecsec" -/// ], -/// "create_client.client_id": [ -/// "testclientthird" -/// ], -/// "create_client.client_type": [ -/// "tendermint" -/// ], -/// "message.action": [ -/// "connection_open_init", -/// "create_client", -/// "connection_open_init" -/// ], -/// "message.module": [ -/// "ibc_connection", -/// "ibc_client", -/// "ibc_connection" -/// ], -/// "message.sender": [ -/// "cosmos187xxg4yfkypl05cqylucezpjvycj24nurvm8p9", -/// "cosmos187xxg4yfkypl05cqylucezpjvycj24nurvm8p9", -/// "cosmos187xxg4yfkypl05cqylucezpjvycj24nurvm8p9", -/// "cosmos187xxg4yfkypl05cqylucezpjvycj24nurvm8p9" -/// ], -/// "tm.event": [ -/// "Tx" -/// ], -/// "transfer.amount": [ -/// "5000stake" -/// ], -/// "transfer.recipient": [ -/// "cosmos17xpfvakm2amg962yls6f84z3kell8c5lserqta" -/// ], -/// "tx.hash": [ -/// "A9E18AE3909F22232F8DBDB1C48F2FECB260A308A2D157E8832E901D45950605" -/// ], -/// "tx.height": [ -/// "35" -/// ] -/// } -/// } -/// ``` -/// -/// and returns: -/// -/// ```rust -/// vec![ -/// ("connection_open_init", 0), -/// ("create_client", 0), -/// ("connection_open_init", 1), -/// ]; -/// ``` -/// -/// where the number in each entry is the index in the matching events that should be used to build the event. -/// -/// e.g. for the last "connection_open_init" in the result -/// -/// ```text -/// "connection_open_init.client_id" -> "testclientsec" -/// "connection_open_init.connection_id" -> "ancaconnonetestsec", -/// "connection_open_init.counterparty_client_id" -> "testclientsec", "testclientsecsec", -/// ``` -fn extract_helper(events: &HashMap>) -> Result, String> { - let actions = events.get("message.action").ok_or("Incorrect Event Type")?; - - let mut val_indices = HashMap::new(); - let mut result = Vec::with_capacity(actions.len()); - - for action in actions { - let idx = val_indices.entry(action.clone()).or_insert_with(|| 0); - result.push((action.clone(), *idx)); - *val_indices.get_mut(action.as_str()).unwrap() += 1; + #[inline] + fn append_events>( + events: &mut Vec<(Height, IbcEvent)>, + chan_events: Vec, + height: Height, + ) { + events.append( + &mut chan_events + .into_iter() + .map(|ev| (height, ev.into())) + .collect(), + ); } - Ok(result) -} - -// A type that is used to derive the ordering for channel events. -// Events are ordered in the following order -> `Open > Packet > Close`. -#[derive(Eq, PartialEq, PartialOrd, Ord)] -enum ChannelEventType { - Open, - Packet, - Close, -} - -impl From<&IbcEvent> for ChannelEventType { - fn from(ev: &IbcEvent) -> Self { - match ev { - IbcEvent::OpenInitChannel(_) - | IbcEvent::OpenTryChannel(_) - | IbcEvent::OpenAckChannel(_) - | IbcEvent::OpenConfirmChannel(_) => Self::Open, - IbcEvent::CloseInitChannel(_) | IbcEvent::CloseConfirmChannel(_) => Self::Close, - IbcEvent::SendPacket(_) - | IbcEvent::ReceivePacket(_) - | IbcEvent::WriteAcknowledgement(_) - | IbcEvent::AcknowledgePacket(_) - | IbcEvent::TimeoutPacket(_) - | IbcEvent::TimeoutOnClosePacket(_) => Self::Packet, - _ => unreachable!(), - } - } + let mut events: Vec<(Height, IbcEvent)> = vec![]; + append_events::( + &mut events, + extract_events(height, block_events, "channel_open_init", "channel_id"), + height, + ); + append_events::( + &mut events, + extract_events(height, block_events, "channel_open_try", "channel_id"), + height, + ); + append_events::( + &mut events, + extract_events(height, block_events, "channel_open_ack", "channel_id"), + height, + ); + append_events::( + &mut events, + extract_events(height, block_events, "channel_open_confirm", "channel_id"), + height, + ); + append_events::( + &mut events, + extract_events(height, block_events, "send_packet", "packet_data"), + height, + ); + append_events::( + &mut events, + extract_events(height, block_events, "channel_close_init", "channel_id"), + height, + ); + append_events::( + &mut events, + extract_events(height, block_events, "channel_close_confirm", "channel_id"), + height, + ); + events } From e0ca18443a0d9541e43849660ca0e2ec97218c9c Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 31 Jan 2022 21:40:36 +0530 Subject: [PATCH 09/11] Cleanup --- modules/src/applications/mod.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/src/applications/mod.rs b/modules/src/applications/mod.rs index 1071464754..e40402d145 100644 --- a/modules/src/applications/mod.rs +++ b/modules/src/applications/mod.rs @@ -14,8 +14,4 @@ pub mod ics27_interchain_accounts { /// ICS27 application current version. pub const VERSION: &str = "ics27-1"; - - pub const ICS27_BANK_SEND_TYPE_URL: &str = "/cosmos.bank.v1beta1.MsgSend"; - pub const ICS27_SEND_TYPE_URL: &str = "/intertx.MsgSend"; - pub const ICS27_REGISTER_TYPE_URL: &str = "/intertx.MsgRegister"; } From 9fa2c30c13229ef19040776ae9943ead3af35ed1 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Mon, 31 Jan 2022 21:44:52 +0530 Subject: [PATCH 10/11] Add .changelog entry --- .../ibc-relayer/1793-begin-end-block-chan-events.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/ibc-relayer/1793-begin-end-block-chan-events.md diff --git a/.changelog/unreleased/improvements/ibc-relayer/1793-begin-end-block-chan-events.md b/.changelog/unreleased/improvements/ibc-relayer/1793-begin-end-block-chan-events.md new file mode 100644 index 0000000000..369f3c51da --- /dev/null +++ b/.changelog/unreleased/improvements/ibc-relayer/1793-begin-end-block-chan-events.md @@ -0,0 +1,2 @@ +- Handle channel events originating from Tendermint ABCI's BeginBlock and EndBlock methods + ([#1793](https://github.com/informalsystems/ibc-rs/issues/1793)) \ No newline at end of file From 90a3bc5559c11e4381ebb3f7bf8fc34600740341 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Tue, 1 Feb 2022 17:38:52 +0530 Subject: [PATCH 11/11] Document event extraction --- relayer/src/event/rpc.rs | 100 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/relayer/src/event/rpc.rs b/relayer/src/event/rpc.rs index 300b1f7534..a43008c361 100644 --- a/relayer/src/event/rpc.rs +++ b/relayer/src/event/rpc.rs @@ -11,6 +11,106 @@ use ibc::events::{IbcEvent, RawObject}; use crate::event::monitor::queries; +/// Extract IBC events from Tendermint RPC events +/// +/// Events originate from the following ABCI methods -> +/// 1. `DeliverTx` - these events are generated during the execution of transaction messages. +/// 2. `BeginBlock` +/// 3. `EndBlock` +/// +/// Events originating from `DeliverTx` are currently extracted via the `RpcEvent::data` field as +/// the `EventData::Tx` variant. +/// Here's an example of what these events look like (i.e. `TxInfo::TxResult::events`) - +/// ```ron +/// [ +/// Event { +/// type_str: "channel_open_init", +/// attributes: [ +/// Tag { +/// key: Key( +/// "port_id", +/// ), +/// value: Value( +/// "transfer", +/// ), +/// }, +/// Tag { +/// key: Key( +/// "channel_id", +/// ), +/// value: Value( +/// "channel-1", +/// ), +/// }, +/// Tag { +/// key: Key( +/// "counterparty_port_id", +/// ), +/// value: Value( +/// "transfer", +/// ), +/// }, +/// Tag { +/// key: Key( +/// "counterparty_channel_id", +/// ), +/// value: Value( +/// "", +/// ), +/// }, +/// Tag { +/// key: Key( +/// "connection_id", +/// ), +/// value: Value( +/// "connection-1", +/// ), +/// }, +/// ], +/// }, +/// // ... +/// ] +/// ``` +/// +/// Events originating from `BeginBlock` and `EndBlock` methods are extracted via the +/// `RpcEvent::events` field. Here's an example of what these events look like -> +/// ```json +/// { +/// "channel_open_init.channel_id": [ +/// "channel-0", +/// ], +/// "channel_open_init.connection_id": [ +/// "connection-0", +/// ], +/// "channel_open_init.counterparty_channel_id": [ +/// "channel-0", +/// ], +/// "channel_open_init.counterparty_port_id": [ +/// "transfer", +/// ], +/// "channel_open_init.port_id": [ +/// "transfer", +/// ], +/// // ... +/// } +/// ``` +/// +/// Note: Historically, all events were extracted from the `RpcEvent::events` field. This was +/// possible because these events had a `message.action` field that allowed us to infer the order in +/// which these events were triggered -> +/// ```json +/// "message.action": [ +/// "update_client", +/// "channel_open_ack", +/// ], +/// "message.module": [ +/// "ibc_client", +/// "ibc_channel", +/// ], +/// ``` +/// {Begin,End}Block events however do not have any such `message.action` associated with them, so +/// this doesn't work. For this reason, we extract block events in the following order -> +/// OpenInit -> OpenTry -> OpenAck -> OpenConfirm -> SendPacket -> CloseInit -> CloseConfirm. pub fn get_all_events( chain_id: &ChainId, result: RpcEvent,