diff --git a/crates/relayer-cli/src/commands/tx.rs b/crates/relayer-cli/src/commands/tx.rs index 52c24b4f30..a50ce867e0 100644 --- a/crates/relayer-cli/src/commands/tx.rs +++ b/crates/relayer-cli/src/commands/tx.rs @@ -56,6 +56,9 @@ pub enum TxCmd { /// Relay the channel upgrade attempt (ChannelUpgradeOpen) ChanUpgradeOpen(channel::TxChanUpgradeOpenCmd), + /// Relay the channel upgrade cancellation (ChannelUpgradeCancel) + ChanUpgradeCancel(channel::TxChanUpgradeCancelCmd), + /// Send a fungible token transfer test transaction (ICS20 MsgTransfer) FtTransfer(transfer::TxIcs20MsgTransferCmd), diff --git a/crates/relayer-cli/src/commands/tx/channel.rs b/crates/relayer-cli/src/commands/tx/channel.rs index b37b42040c..103a123927 100644 --- a/crates/relayer-cli/src/commands/tx/channel.rs +++ b/crates/relayer-cli/src/commands/tx/channel.rs @@ -782,7 +782,7 @@ impl Runnable for TxChanUpgradeTryCmd { info!("message ChanUpgradeTry: {}", channel); - let res: Result = channel + let res: Result, Error> = channel .build_chan_upgrade_try_and_send() .map_err(Error::channel); @@ -913,7 +913,7 @@ impl Runnable for TxChanUpgradeAckCmd { info!("message ChanUpgradeAck: {}", channel); - let res: Result = channel + let res: Result, Error> = channel .build_chan_upgrade_ack_and_send() .map_err(Error::channel); @@ -1044,7 +1044,7 @@ impl Runnable for TxChanUpgradeConfirmCmd { info!("message ChanUpgradeConfirm: {}", channel); - let res: Result = channel + let res: Result, Error> = channel .build_chan_upgrade_confirm_and_send() .map_err(Error::channel); @@ -1185,6 +1185,136 @@ impl Runnable for TxChanUpgradeOpenCmd { } } +/// Relay channel upgrade cancel when counterparty has aborted the upgrade (ChannelUpgradeCancel) +/// +/// Build and send a `ChannelUpgradeCancel` message to cancel +/// the channel upgrade handshake given that the counterparty has aborted. +#[derive(Clone, Command, Debug, Parser, PartialEq, Eq)] +pub struct TxChanUpgradeCancelCmd { + #[clap( + long = "dst-chain", + required = true, + value_name = "DST_CHAIN_ID", + help_heading = "REQUIRED", + help = "Identifier of the destination chain" + )] + dst_chain_id: ChainId, + + #[clap( + long = "src-chain", + required = true, + value_name = "SRC_CHAIN_ID", + help_heading = "REQUIRED", + help = "Identifier of the source chain" + )] + src_chain_id: ChainId, + + #[clap( + long = "dst-connection", + visible_alias = "dst-conn", + required = true, + value_name = "DST_CONNECTION_ID", + help_heading = "REQUIRED", + help = "Identifier of the destination connection" + )] + dst_conn_id: ConnectionId, + + #[clap( + long = "dst-port", + required = true, + value_name = "DST_PORT_ID", + help_heading = "REQUIRED", + help = "Identifier of the destination port" + )] + dst_port_id: PortId, + + #[clap( + long = "src-port", + required = true, + value_name = "SRC_PORT_ID", + help_heading = "REQUIRED", + help = "Identifier of the source port" + )] + src_port_id: PortId, + + #[clap( + long = "src-channel", + visible_alias = "src-chan", + required = true, + value_name = "SRC_CHANNEL_ID", + help_heading = "REQUIRED", + help = "Identifier of the source channel (required)" + )] + src_chan_id: ChannelId, + + #[clap( + long = "dst-channel", + visible_alias = "dst-chan", + required = true, + help_heading = "REQUIRED", + value_name = "DST_CHANNEL_ID", + help = "Identifier of the destination channel (optional)" + )] + dst_chan_id: Option, +} + +impl Runnable for TxChanUpgradeCancelCmd { + fn run(&self) { + let config = app_config(); + + let chains = match ChainHandlePair::spawn(&config, &self.src_chain_id, &self.dst_chain_id) { + Ok(chains) => chains, + Err(e) => Output::error(format!("{}", e)).exit(), + }; + + // Retrieve the connection + let dst_connection = match chains.dst.query_connection( + QueryConnectionRequest { + connection_id: self.dst_conn_id.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) { + Ok((connection, _)) => connection, + Err(e) => Output::error(format!("{}", e)).exit(), + }; + + // Fetch the Channel that will facilitate the communication between the channel ends + // being upgraded. This channel is assumed to already exist on the destination chain. + let channel = Channel { + connection_delay: Default::default(), + ordering: Ordering::default(), + a_side: ChannelSide::new( + chains.src, + ClientId::default(), + ConnectionId::default(), + self.src_port_id.clone(), + Some(self.src_chan_id.clone()), + None, + ), + b_side: ChannelSide::new( + chains.dst, + dst_connection.client_id().clone(), + self.dst_conn_id.clone(), + self.dst_port_id.clone(), + self.dst_chan_id.clone(), + None, + ), + }; + + info!("message ChanUpgradeCancel: {}", channel); + + let res: Result = channel + .build_chan_upgrade_cancel_and_send() + .map_err(Error::channel); + + match res { + Ok(receipt) => Output::success(receipt).exit(), + Err(e) => Output::error(e).exit(), + } + } +} + #[cfg(test)] mod tests { use abscissa_core::clap::Parser; diff --git a/crates/relayer-types/src/core/ics04_channel/error.rs b/crates/relayer-types/src/core/ics04_channel/error.rs index 3006423d05..57c5e6fc35 100644 --- a/crates/relayer-types/src/core/ics04_channel/error.rs +++ b/crates/relayer-types/src/core/ics04_channel/error.rs @@ -126,6 +126,9 @@ define_error! { MissingUpgradeFields | _ | { "missing upgrade fields" }, + MissingUpgradeErrorReceipt + | _ | { "missing upgrade error receipt" }, + MissingProposedUpgradeChannel | _ | { "missing proposed upgrade channel" }, diff --git a/crates/relayer-types/src/core/ics04_channel/events.rs b/crates/relayer-types/src/core/ics04_channel/events.rs index 0e466e17d2..090c77ed33 100644 --- a/crates/relayer-types/src/core/ics04_channel/events.rs +++ b/crates/relayer-types/src/core/ics04_channel/events.rs @@ -1078,6 +1078,108 @@ impl EventType for UpgradeOpen { } } +#[derive(Clone, Debug, PartialEq, Eq, Serialize)] +pub struct UpgradeCancel { + pub port_id: PortId, + pub channel_id: ChannelId, + pub counterparty_port_id: PortId, + pub counterparty_channel_id: Option, + pub upgrade_connection_hops: Vec, + pub upgrade_version: Version, + pub upgrade_sequence: Sequence, + pub upgrade_ordering: Ordering, +} + +impl Display for UpgradeCancel { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> { + if let Some(counterparty_channel_id) = &self.counterparty_channel_id { + write!(f, "UpgradeAttributes {{ port_id: {}, channel_id: {}, counterparty_port_id: {}, counterparty_channel_id: {counterparty_channel_id}, upgrade_connection_hops: [", self.port_id, self.channel_id, self.counterparty_port_id)?; + } else { + write!(f, "UpgradeAttributes {{ port_id: {}, channel_id: {}, counterparty_port_id: {}, counterparty_channel_id: None, upgrade_connection_hops: [", self.port_id, self.channel_id, self.counterparty_port_id)?; + } + for hop in self.upgrade_connection_hops.iter() { + write!(f, " {} ", hop)?; + } + write!( + f, + "], upgrade_version: {}, upgrade_sequence: {}, upgrade_ordering: {} }}", + self.upgrade_version, self.upgrade_sequence, self.upgrade_ordering + ) + } +} + +impl From for UpgradeAttributes { + fn from(ev: UpgradeCancel) -> Self { + Self { + port_id: ev.port_id, + channel_id: ev.channel_id, + counterparty_port_id: ev.counterparty_port_id, + counterparty_channel_id: ev.counterparty_channel_id, + upgrade_connection_hops: ev.upgrade_connection_hops, + upgrade_version: ev.upgrade_version, + upgrade_sequence: ev.upgrade_sequence, + upgrade_ordering: ev.upgrade_ordering, + } + } +} + +impl From for abci::Event { + fn from(value: UpgradeCancel) -> Self { + let kind = UpgradeCancel::event_type().as_str().to_owned(); + Self { + kind, + attributes: UpgradeAttributes::from(value).into(), + } + } +} + +impl UpgradeCancel { + pub fn channel_id(&self) -> &ChannelId { + &self.channel_id + } + + pub fn port_id(&self) -> &PortId { + &self.port_id + } + + pub fn counterparty_port_id(&self) -> &PortId { + &self.counterparty_port_id + } + + pub fn counterparty_channel_id(&self) -> Option<&ChannelId> { + self.counterparty_channel_id.as_ref() + } +} + +impl TryFrom for UpgradeCancel { + type Error = EventError; + + fn try_from(attrs: UpgradeAttributes) -> Result { + Ok(Self { + port_id: attrs.port_id, + channel_id: attrs.channel_id, + counterparty_port_id: attrs.counterparty_port_id, + counterparty_channel_id: attrs.counterparty_channel_id, + upgrade_connection_hops: attrs.upgrade_connection_hops, + upgrade_version: attrs.upgrade_version, + upgrade_sequence: attrs.upgrade_sequence, + upgrade_ordering: attrs.upgrade_ordering, + }) + } +} + +impl From for IbcEvent { + fn from(v: UpgradeCancel) -> Self { + IbcEvent::UpgradeCancelChannel(v) + } +} + +impl EventType for UpgradeCancel { + fn event_type() -> IbcEventType { + IbcEventType::UpgradeCancelChannel + } +} + macro_rules! impl_try_from_attribute_for_event { ($($event:ty),+) => { $(impl TryFrom for $event { diff --git a/crates/relayer-types/src/core/ics04_channel/msgs.rs b/crates/relayer-types/src/core/ics04_channel/msgs.rs index 03f4b7eff8..6a79031c28 100644 --- a/crates/relayer-types/src/core/ics04_channel/msgs.rs +++ b/crates/relayer-types/src/core/ics04_channel/msgs.rs @@ -24,6 +24,7 @@ pub mod chan_close_init; // Upgrade handshake messages. pub mod chan_upgrade_ack; +pub mod chan_upgrade_cancel; pub mod chan_upgrade_confirm; pub mod chan_upgrade_init; pub mod chan_upgrade_open; diff --git a/crates/relayer-types/src/core/ics04_channel/msgs/chan_upgrade_cancel.rs b/crates/relayer-types/src/core/ics04_channel/msgs/chan_upgrade_cancel.rs new file mode 100644 index 0000000000..f2e33039b1 --- /dev/null +++ b/crates/relayer-types/src/core/ics04_channel/msgs/chan_upgrade_cancel.rs @@ -0,0 +1,241 @@ +use ibc_proto::ibc::core::channel::v1::MsgChannelUpgradeCancel as RawMsgChannelUpgradeCancel; +use ibc_proto::Protobuf; + +use crate::core::ics04_channel::error::Error; +use crate::core::ics04_channel::upgrade::ErrorReceipt; +use crate::core::ics23_commitment::commitment::CommitmentProofBytes; +use crate::core::ics24_host::identifier::{ChannelId, PortId}; +use crate::signer::Signer; +use crate::tx_msg::Msg; +use crate::Height; + +pub const TYPE_URL: &str = "/ibc.core.channel.v1.MsgChannelUpgradeCancel"; + +/// Message definition the `ChanUpgradeCancel` datagram. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MsgChannelUpgradeCancel { + pub port_id: PortId, + pub channel_id: ChannelId, + pub error_receipt: ErrorReceipt, + /// The proof of the counterparty error receipt + pub proof_error_receipt: CommitmentProofBytes, + /// The height at which the proofs were queried. + pub proof_height: Height, + pub signer: Signer, +} + +impl MsgChannelUpgradeCancel { + #[allow(clippy::too_many_arguments)] + pub fn new( + port_id: PortId, + channel_id: ChannelId, + error_receipt: ErrorReceipt, + proof_error_receipt: CommitmentProofBytes, + proof_height: Height, + signer: Signer, + ) -> Self { + Self { + port_id, + channel_id, + error_receipt, + proof_error_receipt, + proof_height, + signer, + } + } +} + +impl Msg for MsgChannelUpgradeCancel { + type ValidationError = Error; + type Raw = RawMsgChannelUpgradeCancel; + + fn route(&self) -> String { + crate::keys::ROUTER_KEY.to_string() + } + + fn type_url(&self) -> String { + TYPE_URL.to_string() + } +} + +impl Protobuf for MsgChannelUpgradeCancel {} + +impl TryFrom for MsgChannelUpgradeCancel { + type Error = Error; + + fn try_from(raw_msg: RawMsgChannelUpgradeCancel) -> Result { + let raw_error_receipt = raw_msg + .error_receipt + .ok_or(Error::missing_upgrade_error_receipt())?; + let error_receipt = ErrorReceipt::try_from(raw_error_receipt)?; + + let proof_height = raw_msg + .proof_height + .ok_or_else(Error::missing_proof_height)? + .try_into() + .map_err(|_| Error::invalid_proof_height())?; + + Ok(MsgChannelUpgradeCancel { + port_id: raw_msg.port_id.parse().map_err(Error::identifier)?, + channel_id: raw_msg.channel_id.parse().map_err(Error::identifier)?, + error_receipt, + proof_error_receipt: raw_msg + .proof_error_receipt + .try_into() + .map_err(Error::invalid_proof)?, + proof_height, + signer: raw_msg.signer.parse().map_err(Error::signer)?, + }) + } +} + +impl From for RawMsgChannelUpgradeCancel { + fn from(domain_msg: MsgChannelUpgradeCancel) -> Self { + RawMsgChannelUpgradeCancel { + port_id: domain_msg.port_id.to_string(), + channel_id: domain_msg.channel_id.to_string(), + error_receipt: Some(domain_msg.error_receipt.into()), + proof_error_receipt: domain_msg.proof_error_receipt.into(), + proof_height: Some(domain_msg.proof_height.into()), + signer: domain_msg.signer.to_string(), + } + } +} + +#[cfg(test)] +pub mod test_util { + use ibc_proto::ibc::core::channel::v1::ErrorReceipt as RawErrorReceipt; + use ibc_proto::ibc::core::channel::v1::MsgChannelUpgradeCancel as RawMsgChannelUpgradeCancel; + use ibc_proto::ibc::core::client::v1::Height as RawHeight; + + use crate::core::ics24_host::identifier::{ChannelId, PortId}; + use crate::test_utils::{get_dummy_bech32_account, get_dummy_proof}; + + /// Returns a dummy `RawMsgChannelUpgradeCnacel`, for testing only! + pub fn get_dummy_raw_msg_chan_upgrade_cancel() -> RawMsgChannelUpgradeCancel { + RawMsgChannelUpgradeCancel { + port_id: PortId::default().to_string(), + channel_id: ChannelId::default().to_string(), + error_receipt: Some(RawErrorReceipt { + sequence: 1, + message: "error message".to_string(), + }), + proof_error_receipt: get_dummy_proof(), + proof_height: Some(RawHeight { + revision_number: 1, + revision_height: 1, + }), + signer: get_dummy_bech32_account(), + } + } +} + +#[cfg(test)] +mod tests { + use test_log::test; + + use ibc_proto::ibc::core::channel::v1::MsgChannelUpgradeCancel as RawMsgChannelUpgradeCancel; + + use crate::core::ics04_channel::msgs::chan_upgrade_cancel::test_util::get_dummy_raw_msg_chan_upgrade_cancel; + use crate::core::ics04_channel::msgs::chan_upgrade_cancel::MsgChannelUpgradeCancel; + + #[test] + fn parse_channel_upgrade_try_msg() { + struct Test { + name: String, + raw: RawMsgChannelUpgradeCancel, + want_pass: bool, + } + + let default_raw_msg = get_dummy_raw_msg_chan_upgrade_cancel(); + + let tests: Vec = vec![ + Test { + name: "Good parameters".to_string(), + raw: default_raw_msg.clone(), + want_pass: true, + }, + Test { + name: "Correct port ID".to_string(), + raw: RawMsgChannelUpgradeCancel { + port_id: "p36".to_string(), + ..default_raw_msg.clone() + }, + want_pass: true, + }, + Test { + name: "Port too short".to_string(), + raw: RawMsgChannelUpgradeCancel { + port_id: "p".to_string(), + ..default_raw_msg.clone() + }, + want_pass: false, + }, + Test { + name: "Port too long".to_string(), + raw: RawMsgChannelUpgradeCancel { + port_id: "abcdefsdfasdfasdfasdfasdfasdfadsfasdgafsgadfasdfasdfasdfsdfasdfaghijklmnopqrstuabcdefsdfasdfasdfasdfasdfasdfadsfasdgafsgadfasdfasdfasdfsdfasdfaghijklmnopqrstu".to_string(), + ..default_raw_msg.clone() + }, + want_pass: false, + }, + Test { + name: "Correct channel ID".to_string(), + raw: RawMsgChannelUpgradeCancel { + channel_id: "channel-2".to_string(), + ..default_raw_msg.clone() + }, + want_pass: true, + }, + Test { + name: "Channel name too short".to_string(), + raw: RawMsgChannelUpgradeCancel { + channel_id: "c".to_string(), + ..default_raw_msg.clone() + }, + want_pass: false, + }, + Test { + name: "Channel name too long".to_string(), + raw: RawMsgChannelUpgradeCancel { + channel_id: "channel-128391283791827398127398791283912837918273981273987912839".to_string(), + ..default_raw_msg.clone() + }, + want_pass: false, + }, + Test { + name: "Empty proof channel".to_string(), + raw: RawMsgChannelUpgradeCancel { + proof_error_receipt: vec![], + ..default_raw_msg + }, + want_pass: false, + }, + ] + .into_iter() + .collect(); + + for test in tests { + let res = MsgChannelUpgradeCancel::try_from(test.raw.clone()); + + assert_eq!( + test.want_pass, + res.is_ok(), + "RawMsgChannelUpgradeCancel::try_from failed for test {}, \nraw msg {:?} with err {:?}", + test.name, + test.raw, + res.err() + ); + } + } + + #[test] + fn to_and_from() { + let raw = get_dummy_raw_msg_chan_upgrade_cancel(); + let msg = MsgChannelUpgradeCancel::try_from(raw.clone()).unwrap(); + let raw_back = RawMsgChannelUpgradeCancel::from(msg.clone()); + let msg_back = MsgChannelUpgradeCancel::try_from(raw_back.clone()).unwrap(); + assert_eq!(raw, raw_back); + assert_eq!(msg, msg_back); + } +} diff --git a/crates/relayer-types/src/core/ics04_channel/timeout.rs b/crates/relayer-types/src/core/ics04_channel/timeout.rs index f0877ccae0..2d53a7c2e3 100644 --- a/crates/relayer-types/src/core/ics04_channel/timeout.rs +++ b/crates/relayer-types/src/core/ics04_channel/timeout.rs @@ -256,7 +256,7 @@ impl From for RawUpgradeTimeout { fn from(value: UpgradeTimeout) -> Self { match value { UpgradeTimeout::Height(height) => Self { - height: RawHeight::try_from(height).ok(), + height: Some(RawHeight::from(height)), timestamp: 0, }, UpgradeTimeout::Timestamp(timestamp) => Self { @@ -264,7 +264,7 @@ impl From for RawUpgradeTimeout { timestamp: timestamp.nanoseconds(), }, UpgradeTimeout::Both(height, timestamp) => Self { - height: RawHeight::try_from(height).ok(), + height: Some(RawHeight::from(height)), timestamp: timestamp.nanoseconds(), }, } diff --git a/crates/relayer-types/src/core/ics04_channel/upgrade.rs b/crates/relayer-types/src/core/ics04_channel/upgrade.rs index 9973a7ff74..e46e9937ae 100644 --- a/crates/relayer-types/src/core/ics04_channel/upgrade.rs +++ b/crates/relayer-types/src/core/ics04_channel/upgrade.rs @@ -1,3 +1,4 @@ +use ibc_proto::ibc::core::channel::v1::ErrorReceipt as RawErrorReceipt; use ibc_proto::ibc::core::channel::v1::Upgrade as RawUpgrade; use ibc_proto::Protobuf; @@ -49,6 +50,34 @@ impl From for RawUpgrade { } } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ErrorReceipt { + pub sequence: Sequence, + pub message: String, +} + +impl Protobuf for ErrorReceipt {} + +impl TryFrom for ErrorReceipt { + type Error = ChannelError; + + fn try_from(value: RawErrorReceipt) -> Result { + Ok(Self { + sequence: value.sequence.into(), + message: value.message, + }) + } +} + +impl From for RawErrorReceipt { + fn from(value: ErrorReceipt) -> Self { + Self { + sequence: value.sequence.into(), + message: value.message, + } + } +} + #[cfg(test)] pub mod test_util { use crate::core::ics04_channel::{ diff --git a/crates/relayer-types/src/core/ics24_host/path.rs b/crates/relayer-types/src/core/ics24_host/path.rs index 5efcc62536..41d76d4012 100644 --- a/crates/relayer-types/src/core/ics24_host/path.rs +++ b/crates/relayer-types/src/core/ics24_host/path.rs @@ -43,6 +43,14 @@ pub enum Path { Receipts(ReceiptsPath), Upgrade(ClientUpgradePath), ChannelUpgrade(ChannelUpgradePath), + ChannelUpgradeError(ChannelUpgradeErrorPath), +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)] +#[display(fmt = "channelUpgrades/upgradeError/ports/{port_id}/channels/{channel_id}")] +pub struct ChannelUpgradeErrorPath { + pub port_id: PortId, + pub channel_id: ChannelId, } #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Display)] diff --git a/crates/relayer-types/src/events.rs b/crates/relayer-types/src/events.rs index caff7a421a..c3db57d264 100644 --- a/crates/relayer-types/src/events.rs +++ b/crates/relayer-types/src/events.rs @@ -134,6 +134,7 @@ const CHANNEL_UPGRADE_TRY_EVENT: &str = "channel_upgrade_try"; const CHANNEL_UPGRADE_ACK_EVENT: &str = "channel_upgrade_ack"; const CHANNEL_UPGRADE_CONFIRM_EVENT: &str = "channel_upgrade_confirm"; const CHANNEL_UPGRADE_OPEN_EVENT: &str = "channel_upgrade_open"; +const CHANNEL_UPGRADE_CANCEL_EVENT: &str = "channel_upgrade_cancelled"; /// Packet event types const SEND_PACKET_EVENT: &str = "send_packet"; const RECEIVE_PACKET_EVENT: &str = "receive_packet"; @@ -170,6 +171,7 @@ pub enum IbcEventType { UpgradeAckChannel, UpgradeConfirmChannel, UpgradeOpenChannel, + UpgradeCancelChannel, SendPacket, ReceivePacket, WriteAck, @@ -207,6 +209,7 @@ impl IbcEventType { IbcEventType::UpgradeAckChannel => CHANNEL_UPGRADE_ACK_EVENT, IbcEventType::UpgradeConfirmChannel => CHANNEL_UPGRADE_CONFIRM_EVENT, IbcEventType::UpgradeOpenChannel => CHANNEL_UPGRADE_OPEN_EVENT, + IbcEventType::UpgradeCancelChannel => CHANNEL_UPGRADE_CANCEL_EVENT, IbcEventType::SendPacket => SEND_PACKET_EVENT, IbcEventType::ReceivePacket => RECEIVE_PACKET_EVENT, IbcEventType::WriteAck => WRITE_ACK_EVENT, @@ -248,6 +251,7 @@ impl FromStr for IbcEventType { CHANNEL_UPGRADE_ACK_EVENT => Ok(IbcEventType::UpgradeAckChannel), CHANNEL_UPGRADE_CONFIRM_EVENT => Ok(IbcEventType::UpgradeConfirmChannel), CHANNEL_UPGRADE_OPEN_EVENT => Ok(IbcEventType::UpgradeOpenChannel), + CHANNEL_UPGRADE_CANCEL_EVENT => Ok(IbcEventType::UpgradeCancelChannel), SEND_PACKET_EVENT => Ok(IbcEventType::SendPacket), RECEIVE_PACKET_EVENT => Ok(IbcEventType::ReceivePacket), WRITE_ACK_EVENT => Ok(IbcEventType::WriteAck), @@ -291,6 +295,7 @@ pub enum IbcEvent { UpgradeAckChannel(ChannelEvents::UpgradeAck), UpgradeConfirmChannel(ChannelEvents::UpgradeConfirm), UpgradeOpenChannel(ChannelEvents::UpgradeOpen), + UpgradeCancelChannel(ChannelEvents::UpgradeCancel), SendPacket(ChannelEvents::SendPacket), ReceivePacket(ChannelEvents::ReceivePacket), @@ -335,6 +340,7 @@ impl Display for IbcEvent { IbcEvent::UpgradeAckChannel(ev) => write!(f, "UpgradeAckChannel({ev})"), IbcEvent::UpgradeConfirmChannel(ev) => write!(f, "UpgradeConfirmChannel({ev})"), IbcEvent::UpgradeOpenChannel(ev) => write!(f, "UpgradeOpenChannel({ev})"), + IbcEvent::UpgradeCancelChannel(ev) => write!(f, "UpgradeCancelChannel({ev})"), IbcEvent::SendPacket(ev) => write!(f, "SendPacket({ev})"), IbcEvent::ReceivePacket(ev) => write!(f, "ReceivePacket({ev})"), @@ -379,6 +385,7 @@ impl TryFrom for abci::Event { IbcEvent::UpgradeAckChannel(event) => event.into(), IbcEvent::UpgradeConfirmChannel(event) => event.into(), IbcEvent::UpgradeOpenChannel(event) => event.into(), + IbcEvent::UpgradeCancelChannel(event) => event.into(), IbcEvent::SendPacket(event) => event.try_into().map_err(Error::channel)?, IbcEvent::ReceivePacket(event) => event.try_into().map_err(Error::channel)?, IbcEvent::WriteAcknowledgement(event) => event.try_into().map_err(Error::channel)?, @@ -426,6 +433,7 @@ impl IbcEvent { IbcEvent::UpgradeAckChannel(_) => IbcEventType::UpgradeAckChannel, IbcEvent::UpgradeConfirmChannel(_) => IbcEventType::UpgradeConfirmChannel, IbcEvent::UpgradeOpenChannel(_) => IbcEventType::UpgradeOpenChannel, + IbcEvent::UpgradeCancelChannel(_) => IbcEventType::UpgradeCancelChannel, IbcEvent::SendPacket(_) => IbcEventType::SendPacket, IbcEvent::ReceivePacket(_) => IbcEventType::ReceivePacket, IbcEvent::WriteAcknowledgement(_) => IbcEventType::WriteAck, @@ -457,6 +465,7 @@ impl IbcEvent { IbcEvent::UpgradeAckChannel(ev) => Some(ev.into()), IbcEvent::UpgradeConfirmChannel(ev) => Some(ev.into()), IbcEvent::UpgradeOpenChannel(ev) => Some(ev.into()), + IbcEvent::UpgradeCancelChannel(ev) => Some(ev.into()), _ => None, } } diff --git a/crates/relayer/src/chain/cosmos.rs b/crates/relayer/src/chain/cosmos.rs index 7db0b0b7c3..2025715cd6 100644 --- a/crates/relayer/src/chain/cosmos.rs +++ b/crates/relayer/src/chain/cosmos.rs @@ -20,7 +20,7 @@ use ibc_proto::cosmos::staking::v1beta1::Params as StakingParams; use ibc_proto::ibc::apps::fee::v1::{ QueryIncentivizedPacketRequest, QueryIncentivizedPacketResponse, }; -use ibc_proto::ibc::core::channel::v1::QueryUpgradeRequest; +use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; use ibc_proto::interchain_security::ccv::v1::ConsumerParams as CcvConsumerParams; use ibc_proto::Protobuf; use ibc_relayer_types::applications::ics31_icq::response::CrossChainQueryResponse; @@ -43,13 +43,17 @@ use ibc_relayer_types::core::ics24_host::identifier::{ ChainId, ChannelId, ClientId, ConnectionId, PortId, }; use ibc_relayer_types::core::ics24_host::path::{ - AcksPath, ChannelEndsPath, ChannelUpgradePath, ClientConsensusStatePath, ClientStatePath, - CommitmentsPath, ConnectionsPath, ReceiptsPath, SeqRecvsPath, + AcksPath, ChannelEndsPath, ChannelUpgradeErrorPath, ChannelUpgradePath, + ClientConsensusStatePath, ClientStatePath, CommitmentsPath, ConnectionsPath, ReceiptsPath, + SeqRecvsPath, }; use ibc_relayer_types::core::ics24_host::{ ClientUpgradePath, Path, IBC_QUERY_PATH, SDK_UPGRADE_QUERY_PATH, }; -use ibc_relayer_types::core::{ics02_client::height::Height, ics04_channel::upgrade::Upgrade}; +use ibc_relayer_types::core::{ + ics02_client::height::Height, ics04_channel::upgrade::ErrorReceipt, + ics04_channel::upgrade::Upgrade, +}; use ibc_relayer_types::signer::Signer; use ibc_relayer_types::Height as ICSHeight; @@ -2313,6 +2317,29 @@ impl ChainEndpoint for CosmosSdkChain { Ok((upgrade, Some(proof))) } + + fn query_upgrade_error( + &self, + request: QueryUpgradeErrorRequest, + height: Height, + ) -> Result<(ErrorReceipt, Option), Error> { + let port_id = PortId::from_str(&request.port_id) + .map_err(|_| Error::invalid_port_string(request.port_id))?; + let channel_id = ChannelId::from_str(&request.channel_id) + .map_err(|_| Error::invalid_channel_string(request.channel_id))?; + let res = self.query( + ChannelUpgradeErrorPath { + port_id, + channel_id, + }, + QueryHeight::Specific(height), + true, + )?; + let proof = res.proof.ok_or_else(Error::empty_response_proof)?; + let error_receipt = ErrorReceipt::decode_vec(&res.value).map_err(Error::decode)?; + + Ok((error_receipt, Some(proof))) + } } fn sort_events_by_sequence(events: &mut [IbcEventWithHeight]) { diff --git a/crates/relayer/src/chain/endpoint.rs b/crates/relayer/src/chain/endpoint.rs index 300fa6df0e..acbf6406ec 100644 --- a/crates/relayer/src/chain/endpoint.rs +++ b/crates/relayer/src/chain/endpoint.rs @@ -1,8 +1,8 @@ use alloc::sync::Arc; use core::convert::TryFrom; -use ibc_proto::ibc::core::channel::v1::QueryUpgradeRequest; +use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; use ibc_relayer_types::core::ics02_client::height::Height; -use ibc_relayer_types::core::ics04_channel::upgrade::Upgrade; +use ibc_relayer_types::core::ics04_channel::upgrade::{ErrorReceipt, Upgrade}; use tokio::runtime::Runtime as TokioRuntime; @@ -696,4 +696,10 @@ pub trait ChainEndpoint: Sized { request: QueryUpgradeRequest, height: Height, ) -> Result<(Upgrade, Option), Error>; + + fn query_upgrade_error( + &self, + request: QueryUpgradeErrorRequest, + height: Height, + ) -> Result<(ErrorReceipt, Option), Error>; } diff --git a/crates/relayer/src/chain/handle.rs b/crates/relayer/src/chain/handle.rs index 0d08c112ec..750a083e73 100644 --- a/crates/relayer/src/chain/handle.rs +++ b/crates/relayer/src/chain/handle.rs @@ -1,6 +1,6 @@ use alloc::sync::Arc; use core::fmt::{self, Debug, Display}; -use ibc_relayer_types::core::ics04_channel::upgrade::Upgrade; +use ibc_relayer_types::core::ics04_channel::upgrade::{ErrorReceipt, Upgrade}; use crossbeam_channel as channel; use tracing::Span; @@ -8,7 +8,7 @@ use tracing::Span; use ibc_proto::ibc::apps::fee::v1::{ QueryIncentivizedPacketRequest, QueryIncentivizedPacketResponse, }; -use ibc_proto::ibc::core::channel::v1::QueryUpgradeRequest; +use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; use ibc_relayer_types::{ applications::ics31_icq::response::CrossChainQueryResponse, core::{ @@ -379,6 +379,12 @@ pub enum ChainRequest { height: Height, reply_to: ReplyTo<(Upgrade, Option)>, }, + + QueryUpgradeError { + request: QueryUpgradeErrorRequest, + height: Height, + reply_to: ReplyTo<(ErrorReceipt, Option)>, + }, } pub trait ChainHandle: Clone + Display + Send + Sync + Debug + 'static { @@ -698,4 +704,10 @@ pub trait ChainHandle: Clone + Display + Send + Sync + Debug + 'static { request: QueryUpgradeRequest, height: Height, ) -> Result<(Upgrade, Option), Error>; + + fn query_upgrade_error( + &self, + request: QueryUpgradeErrorRequest, + height: Height, + ) -> Result<(ErrorReceipt, Option), Error>; } diff --git a/crates/relayer/src/chain/handle/base.rs b/crates/relayer/src/chain/handle/base.rs index e202f7170a..4c612212f4 100644 --- a/crates/relayer/src/chain/handle/base.rs +++ b/crates/relayer/src/chain/handle/base.rs @@ -5,7 +5,7 @@ use tracing::Span; use ibc_proto::ibc::{ apps::fee::v1::{QueryIncentivizedPacketRequest, QueryIncentivizedPacketResponse}, - core::channel::v1::QueryUpgradeRequest, + core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}, }; use ibc_relayer_types::{ applications::ics31_icq::response::CrossChainQueryResponse, @@ -16,7 +16,7 @@ use ibc_relayer_types::{ ics04_channel::channel::{ChannelEnd, IdentifiedChannelEnd}, ics04_channel::{ packet::{PacketMsgType, Sequence}, - upgrade::Upgrade, + upgrade::{ErrorReceipt, Upgrade}, }, ics23_commitment::{commitment::CommitmentPrefix, merkle::MerkleProof}, ics24_host::identifier::ChainId, @@ -537,4 +537,16 @@ impl ChainHandle for BaseChainHandle { reply_to, }) } + + fn query_upgrade_error( + &self, + request: QueryUpgradeErrorRequest, + height: Height, + ) -> Result<(ErrorReceipt, Option), Error> { + self.send(|reply_to| ChainRequest::QueryUpgradeError { + request, + height, + reply_to, + }) + } } diff --git a/crates/relayer/src/chain/handle/cache.rs b/crates/relayer/src/chain/handle/cache.rs index 92029fc53f..26fa5636fa 100644 --- a/crates/relayer/src/chain/handle/cache.rs +++ b/crates/relayer/src/chain/handle/cache.rs @@ -1,11 +1,12 @@ use core::fmt::{Display, Error as FmtError, Formatter}; use crossbeam_channel as channel; use ibc_relayer_types::core::ics02_client::header::AnyHeader; +use ibc_relayer_types::core::ics04_channel::upgrade::ErrorReceipt; use tracing::Span; use ibc_proto::ibc::apps::fee::v1::QueryIncentivizedPacketRequest; use ibc_proto::ibc::apps::fee::v1::QueryIncentivizedPacketResponse; -use ibc_proto::ibc::core::channel::v1::QueryUpgradeRequest; +use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; use ibc_relayer_types::applications::ics31_icq::response::CrossChainQueryResponse; use ibc_relayer_types::core::ics02_client::events::UpdateClient; use ibc_relayer_types::core::ics03_connection::connection::ConnectionEnd; @@ -525,4 +526,12 @@ impl ChainHandle for CachingChainHandle { ) -> Result<(Upgrade, Option), Error> { self.inner.query_upgrade(request, height) } + + fn query_upgrade_error( + &self, + request: QueryUpgradeErrorRequest, + height: Height, + ) -> Result<(ErrorReceipt, Option), Error> { + self.inner.query_upgrade_error(request, height) + } } diff --git a/crates/relayer/src/chain/handle/counting.rs b/crates/relayer/src/chain/handle/counting.rs index ebc0a9c88f..053d289ef8 100644 --- a/crates/relayer/src/chain/handle/counting.rs +++ b/crates/relayer/src/chain/handle/counting.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use std::sync::{Arc, RwLock, RwLockReadGuard}; use crossbeam_channel as channel; -use ibc_proto::ibc::core::channel::v1::QueryUpgradeRequest; -use ibc_relayer_types::core::ics04_channel::upgrade::Upgrade; +use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; +use ibc_relayer_types::core::ics04_channel::upgrade::{ErrorReceipt, Upgrade}; use tracing::{debug, Span}; use ibc_proto::ibc::apps::fee::v1::{ @@ -520,4 +520,13 @@ impl ChainHandle for CountingChainHandle { self.inc_metric("query_upgrade"); self.inner.query_upgrade(request, height) } + + fn query_upgrade_error( + &self, + request: QueryUpgradeErrorRequest, + height: Height, + ) -> Result<(ErrorReceipt, Option), Error> { + self.inc_metric("query_upgrade_error"); + self.inner.query_upgrade_error(request, height) + } } diff --git a/crates/relayer/src/chain/runtime.rs b/crates/relayer/src/chain/runtime.rs index a084bf5967..6732c01368 100644 --- a/crates/relayer/src/chain/runtime.rs +++ b/crates/relayer/src/chain/runtime.rs @@ -7,7 +7,7 @@ use tracing::{error, Span}; use ibc_proto::ibc::{ apps::fee::v1::{QueryIncentivizedPacketRequest, QueryIncentivizedPacketResponse}, - core::channel::v1::QueryUpgradeRequest, + core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}, }; use ibc_relayer_types::{ applications::ics31_icq::response::CrossChainQueryResponse, @@ -21,7 +21,7 @@ use ibc_relayer_types::{ ics04_channel::{ channel::{ChannelEnd, IdentifiedChannelEnd}, packet::{PacketMsgType, Sequence}, - upgrade::Upgrade, + upgrade::{ErrorReceipt, Upgrade}, }, ics23_commitment::{commitment::CommitmentPrefix, merkle::MerkleProof}, ics24_host::identifier::{ChainId, ChannelId, ClientId, ConnectionId, PortId}, @@ -360,6 +360,10 @@ where ChainRequest::QueryUpgrade { request, height, reply_to } => { self.query_upgrade(request, height, reply_to)? }, + + ChainRequest::QueryUpgradeError { request, height, reply_to } => { + self.query_upgrade_error(request, height, reply_to)? + }, } }, } @@ -883,4 +887,16 @@ where Ok(()) } + + fn query_upgrade_error( + &self, + request: QueryUpgradeErrorRequest, + height: Height, + reply_to: ReplyTo<(ErrorReceipt, Option)>, + ) -> Result<(), Error> { + let result = self.chain.query_upgrade_error(request, height); + reply_to.send(result).map_err(Error::send)?; + + Ok(()) + } } diff --git a/crates/relayer/src/channel.rs b/crates/relayer/src/channel.rs index 5d56d161eb..7e0ede2bf8 100644 --- a/crates/relayer/src/channel.rs +++ b/crates/relayer/src/channel.rs @@ -1,6 +1,7 @@ pub use error::ChannelError; -use ibc_proto::ibc::core::channel::v1::QueryUpgradeRequest; +use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; use ibc_relayer_types::core::ics04_channel::msgs::chan_upgrade_ack::MsgChannelUpgradeAck; +use ibc_relayer_types::core::ics04_channel::msgs::chan_upgrade_cancel::MsgChannelUpgradeCancel; use ibc_relayer_types::core::ics04_channel::msgs::chan_upgrade_confirm::MsgChannelUpgradeConfirm; use ibc_relayer_types::core::ics04_channel::msgs::chan_upgrade_open::MsgChannelUpgradeOpen; use ibc_relayer_types::core::ics04_channel::packet::Sequence; @@ -812,16 +813,28 @@ impl Channel { // Channel Upgrade handshake steps (State::Open(UpgradeState::Upgrading), State::Open(UpgradeState::NotUpgrading)) => { - Some(self.build_chan_upgrade_try_and_send()?) + match self.build_chan_upgrade_try_and_send()? { + Some(event) => Some(event), + None => Some(self.flipped().build_chan_upgrade_cancel_and_send()?), + } } (State::Open(UpgradeState::NotUpgrading), State::Open(UpgradeState::Upgrading)) => { - Some(self.flipped().build_chan_upgrade_try_and_send()?) + match self.flipped().build_chan_upgrade_try_and_send()? { + Some(event) => Some(event), + None => Some(self.build_chan_upgrade_cancel_and_send()?), + } } (State::Flushing, State::Open(UpgradeState::Upgrading)) => { - Some(self.build_chan_upgrade_ack_and_send()?) + match self.build_chan_upgrade_ack_and_send()? { + Some(event) => Some(event), + None => Some(self.flipped().build_chan_upgrade_cancel_and_send()?), + } } (State::Flushcomplete, State::Flushing) => { - Some(self.build_chan_upgrade_confirm_and_send()?) + match self.build_chan_upgrade_confirm_and_send()? { + Some(event) => Some(event), + None => Some(self.flipped().build_chan_upgrade_cancel_and_send()?), + } } (State::Flushcomplete, State::Open(UpgradeState::Upgrading)) => { Some(self.flipped().build_chan_upgrade_open_and_send()?) @@ -1775,7 +1788,7 @@ impl Channel { Ok(chain_a_msgs) } - pub fn build_chan_upgrade_try_and_send(&self) -> Result { + pub fn build_chan_upgrade_try_and_send(&self) -> Result, ChannelError> { let dst_msgs = self.build_chan_upgrade_try()?; let tm = TrackedMsgs::new_static(dst_msgs, "ChannelUpgradeTry"); @@ -1785,26 +1798,22 @@ impl Channel { .send_messages_and_wait_commit(tm) .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; - // Find the relevant event for channel upgrade try - let result = events - .into_iter() - .find(|event_with_height| { - matches!(event_with_height.event, IbcEvent::UpgradeTryChannel(_)) - || matches!(event_with_height.event, IbcEvent::ChainError(_)) - }) - .ok_or_else(|| { - ChannelError::missing_event( - "no channel upgrade try event was in the response".to_string(), - ) - })?; - - match &result.event { - IbcEvent::UpgradeTryChannel(_) => { - info!("👋 {} => {}", self.dst_chain().id(), result); - Ok(result.event) + // If the Channel Upgrade Try times out, there will be no events in the response + if let Some(event_with_height) = events.into_iter().find(|event_with_height| { + matches!(event_with_height.event, IbcEvent::UpgradeTryChannel(_)) + || matches!(event_with_height.event, IbcEvent::ChainError(_)) + }) { + match &event_with_height.event { + IbcEvent::UpgradeTryChannel(_) => { + info!("👋 {} => {}", self.dst_chain().id(), event_with_height); + Ok(Some(event_with_height.event.clone())) + } + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e.clone())), + _ => Err(ChannelError::invalid_event(event_with_height.event.clone())), } - IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e.clone())), - _ => Err(ChannelError::invalid_event(result.event)), + } else { + warn!("no channel upgrade try event found, this might be due to the channel upgrade having timed out"); + Ok(None) } } @@ -1877,7 +1886,7 @@ impl Channel { Ok(chain_a_msgs) } - pub fn build_chan_upgrade_ack_and_send(&self) -> Result { + pub fn build_chan_upgrade_ack_and_send(&self) -> Result, ChannelError> { let dst_msgs = self.build_chan_upgrade_ack()?; let tm = TrackedMsgs::new_static(dst_msgs, "ChannelUpgradeAck"); @@ -1887,25 +1896,22 @@ impl Channel { .send_messages_and_wait_commit(tm) .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; - let result = events - .into_iter() - .find(|event_with_height| { - matches!(event_with_height.event, IbcEvent::UpgradeAckChannel(_)) - || matches!(event_with_height.event, IbcEvent::ChainError(_)) - }) - .ok_or_else(|| { - ChannelError::missing_event( - "no channel upgrade ack event was in the response".to_string(), - ) - })?; - - match &result.event { - IbcEvent::UpgradeAckChannel(_) => { - info!("👋 {} => {}", self.dst_chain().id(), result); - Ok(result.event) + // If the Channel Upgrade Ack times out, there will be no events in the response + if let Some(event_with_height) = events.into_iter().find(|event_with_height| { + matches!(event_with_height.event, IbcEvent::UpgradeAckChannel(_)) + || matches!(event_with_height.event, IbcEvent::ChainError(_)) + }) { + match &event_with_height.event { + IbcEvent::UpgradeAckChannel(_) => { + info!("👋 {} => {}", self.dst_chain().id(), event_with_height); + Ok(Some(event_with_height.event.clone())) + } + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e.clone())), + _ => Err(ChannelError::invalid_event(event_with_height.event.clone())), } - IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e.clone())), - _ => Err(ChannelError::invalid_event(result.event)), + } else { + warn!("no channel upgrade ack event found, this might be due to the channel upgrade having timed out"); + Ok(None) } } @@ -1993,7 +1999,7 @@ impl Channel { Ok(chain_a_msgs) } - pub fn build_chan_upgrade_confirm_and_send(&self) -> Result { + pub fn build_chan_upgrade_confirm_and_send(&self) -> Result, ChannelError> { let dst_msgs = self.build_chan_upgrade_confirm()?; let tm = TrackedMsgs::new_static(dst_msgs, "ChannelUpgradeConfirm"); @@ -2003,25 +2009,22 @@ impl Channel { .send_messages_and_wait_commit(tm) .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; - let result = events - .into_iter() - .find(|event_with_height| { - matches!(event_with_height.event, IbcEvent::UpgradeConfirmChannel(_)) - || matches!(event_with_height.event, IbcEvent::ChainError(_)) - }) - .ok_or_else(|| { - ChannelError::missing_event( - "no channel upgrade confirm event was in the response".to_string(), - ) - })?; - - match &result.event { - IbcEvent::UpgradeConfirmChannel(_) => { - info!("👋 {} => {}", self.dst_chain().id(), result); - Ok(result.event) + // If the Channel Upgrade Confirm times out, there will be no events in the response + if let Some(event_with_height) = events.into_iter().find(|event_with_height| { + matches!(event_with_height.event, IbcEvent::UpgradeConfirmChannel(_)) + || matches!(event_with_height.event, IbcEvent::ChainError(_)) + }) { + match &event_with_height.event { + IbcEvent::UpgradeConfirmChannel(_) => { + info!("👋 {} => {}", self.dst_chain().id(), event_with_height); + Ok(Some(event_with_height.event.clone())) + } + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e.clone())), + _ => Err(ChannelError::invalid_event(event_with_height.event.clone())), } - IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e.clone())), - _ => Err(ChannelError::invalid_event(result.event)), + } else { + warn!("no channel upgrade confirm event found, this might be due to the channel upgrade having timed out"); + Ok(None) } } @@ -2106,7 +2109,7 @@ impl Channel { }) .ok_or_else(|| { ChannelError::missing_event( - "no channel upgrade ack event was in the response".to_string(), + "no channel upgrade open event was in the response".to_string(), ) })?; @@ -2120,6 +2123,106 @@ impl Channel { } } + pub fn build_chan_upgrade_cancel(&self) -> Result, ChannelError> { + // Destination channel ID must exist + let src_channel_id = self + .src_channel_id() + .ok_or_else(ChannelError::missing_counterparty_channel_id)?; + + let dst_channel_id = self + .dst_channel_id() + .ok_or_else(ChannelError::missing_counterparty_channel_id)?; + + let src_port_id = self.src_port_id(); + + let dst_port_id = self.dst_port_id(); + + let src_latest_height = self + .src_chain() + .query_latest_height() + .map_err(|e| ChannelError::chain_query(self.src_chain().id(), e))?; + + let (error_receipt, maybe_error_receipt_proof) = self + .src_chain() + .query_upgrade_error( + QueryUpgradeErrorRequest { + port_id: src_port_id.to_string(), + channel_id: src_channel_id.to_string(), + }, + src_latest_height, + ) + .map_err(|e| ChannelError::chain_query(self.src_chain().id(), e))?; + + let error_receipt_proof = + maybe_error_receipt_proof.ok_or(ChannelError::missing_upgrade_error_receipt_proof())?; + + let proof_error_receipt = CommitmentProofBytes::try_from(error_receipt_proof) + .map_err(ChannelError::malformed_proof)?; + + // Building the channel proof at the queried height + let proofs = self + .src_chain() + .build_channel_proofs( + &src_port_id.clone(), + &src_channel_id.clone(), + src_latest_height, + ) + .map_err(ChannelError::channel_proof)?; + + // Build message(s) to update client on destination + let mut msgs = self.build_update_client_on_dst(proofs.height())?; + + let signer = self + .dst_chain() + .get_signer() + .map_err(|e| ChannelError::fetch_signer(self.dst_chain().id(), e))?; + + // Build the domain type message + let new_msg = MsgChannelUpgradeCancel { + port_id: dst_port_id.clone(), + channel_id: dst_channel_id.clone(), + error_receipt, + proof_error_receipt, + proof_height: proofs.height(), + signer, + }; + + msgs.push(new_msg.to_any()); + Ok(msgs) + } + + pub fn build_chan_upgrade_cancel_and_send(&self) -> Result { + let dst_msgs = self.build_chan_upgrade_cancel()?; + + let tm = TrackedMsgs::new_static(dst_msgs, "ChannelUpgradeCancel"); + + let events = self + .dst_chain() + .send_messages_and_wait_commit(tm) + .map_err(|e| ChannelError::submit(self.dst_chain().id(), e))?; + + let result = events + .into_iter() + .find(|event_with_height| { + matches!(event_with_height.event, IbcEvent::UpgradeCancelChannel(_)) + || matches!(event_with_height.event, IbcEvent::ChainError(_)) + }) + .ok_or_else(|| { + ChannelError::missing_event( + "no channel upgrade cancel event was in the response".to_string(), + ) + })?; + + match &result.event { + IbcEvent::UpgradeCancelChannel(_) => { + info!("👋 {} => {}", self.dst_chain().id(), result); + Ok(result.event) + } + IbcEvent::ChainError(e) => Err(ChannelError::tx_response(e.clone())), + _ => Err(ChannelError::invalid_event(result.event)), + } + } + pub fn map_chain( self, mapper_a: impl Fn(ChainA) -> ChainC, diff --git a/crates/relayer/src/channel/error.rs b/crates/relayer/src/channel/error.rs index 895065db0e..e25b1898bf 100644 --- a/crates/relayer/src/channel/error.rs +++ b/crates/relayer/src/channel/error.rs @@ -70,6 +70,9 @@ define_error! { MissingUpgradeProof |_| { "missing upgrade proof" }, + MissingUpgradeErrorReceiptProof + |_| { "missing upgrade error receipt proof" }, + MalformedProof [ ProofError ] |_| { "malformed proof" }, diff --git a/crates/relayer/src/event.rs b/crates/relayer/src/event.rs index f3a9794c90..8697e25f78 100644 --- a/crates/relayer/src/event.rs +++ b/crates/relayer/src/event.rs @@ -132,6 +132,10 @@ pub fn ibc_event_try_from_abci_event(abci_event: &AbciEvent) -> Result Ok(IbcEvent::UpgradeOpenChannel( channel_upgrade_open_try_from_abci_event(abci_event).map_err(IbcEventError::channel)?, )), + Ok(IbcEventType::UpgradeCancelChannel) => Ok(IbcEvent::UpgradeCancelChannel( + channel_upgrade_cancelled_try_from_abci_event(abci_event) + .map_err(IbcEventError::channel)?, + )), Ok(IbcEventType::SendPacket) => Ok(IbcEvent::SendPacket( send_packet_try_from_abci_event(abci_event).map_err(IbcEventError::channel)?, )), @@ -323,6 +327,16 @@ pub fn channel_upgrade_open_try_from_abci_event( } } +pub fn channel_upgrade_cancelled_try_from_abci_event( + abci_event: &AbciEvent, +) -> Result { + match channel_upgrade_extract_attributes_from_tx(abci_event) { + Ok(attrs) => channel_events::UpgradeCancel::try_from(attrs) + .map_err(|_| ChannelError::implementation_specific()), + Err(e) => Err(e), + } +} + pub fn send_packet_try_from_abci_event( abci_event: &AbciEvent, ) -> Result { diff --git a/guide/src/templates/commands/hermes/tx/chan-upgrade-cancel_1.md b/guide/src/templates/commands/hermes/tx/chan-upgrade-cancel_1.md new file mode 100644 index 0000000000..5f7fc7e0e1 --- /dev/null +++ b/guide/src/templates/commands/hermes/tx/chan-upgrade-cancel_1.md @@ -0,0 +1 @@ +[[#BINARY hermes]][[#GLOBALOPTIONS]] tx chan-upgrade-cancel --dst-chain [[#DST_CHAIN_ID]] --src-chain [[#SRC_CHAIN_ID]] --dst-connection [[#DST_CONNECTION_ID]] --dst-port [[#DST_PORT_ID]] --src-port [[#SRC_PORT_ID]] --src-channel [[#SRC_CHANNEL_ID]] --dst-channel [[#DST_CHANNEL_ID]] diff --git a/guide/src/templates/help_templates/tx.md b/guide/src/templates/help_templates/tx.md index 3549c78e34..8fc53c2405 100644 --- a/guide/src/templates/help_templates/tx.md +++ b/guide/src/templates/help_templates/tx.md @@ -15,6 +15,7 @@ SUBCOMMANDS: chan-open-init Initialize a channel (ChannelOpenInit) chan-open-try Relay the channel attempt (ChannelOpenTry) chan-upgrade-ack Relay the channel upgrade attempt (ChannelUpgradeAck) + chan-upgrade-cancel Relay the channel upgrade cancellation (ChannelUpgradeCancel) chan-upgrade-confirm Relay the channel upgrade attempt (ChannelUpgradeConfirm) chan-upgrade-open Relay the channel upgrade attempt (ChannelUpgradeOpen) chan-upgrade-try Relay the channel upgrade attempt (ChannelUpgradeTry) diff --git a/guide/src/templates/help_templates/tx/chan-upgrade-cancel.md b/guide/src/templates/help_templates/tx/chan-upgrade-cancel.md new file mode 100644 index 0000000000..ceb38a5f12 --- /dev/null +++ b/guide/src/templates/help_templates/tx/chan-upgrade-cancel.md @@ -0,0 +1,30 @@ +DESCRIPTION: +Relay the channel upgrade cancellation (ChannelUpgradeCancel) + +USAGE: + hermes tx chan-upgrade-cancel --dst-chain --src-chain --dst-connection --dst-port --src-port --src-channel --dst-channel + +OPTIONS: + -h, --help Print help information + +REQUIRED: + --dst-chain + Identifier of the destination chain + + --dst-channel + Identifier of the destination channel (optional) [aliases: dst-chan] + + --dst-connection + Identifier of the destination connection [aliases: dst-conn] + + --dst-port + Identifier of the destination port + + --src-chain + Identifier of the source chain + + --src-channel + Identifier of the source channel (required) [aliases: src-chan] + + --src-port + Identifier of the source port diff --git a/tools/integration-test/src/tests/async_icq/simple_query.rs b/tools/integration-test/src/tests/async_icq/simple_query.rs index 4d2824d344..f11877f53a 100644 --- a/tools/integration-test/src/tests/async_icq/simple_query.rs +++ b/tools/integration-test/src/tests/async_icq/simple_query.rs @@ -109,7 +109,7 @@ impl BinaryConnectionTest for AsyncIcqTest { "1", )?; - driver.vote_proposal(&fee_denom_a.with_amount(381000000u64).to_string())?; + driver.vote_proposal(&fee_denom_a.with_amount(381000000u64).to_string(), "1")?; info!("Assert that the update oracle proposal is eventually passed"); diff --git a/tools/integration-test/src/tests/channel_upgrade/ics29.rs b/tools/integration-test/src/tests/channel_upgrade/ics29.rs index fefa554ac2..881e331649 100644 --- a/tools/integration-test/src/tests/channel_upgrade/ics29.rs +++ b/tools/integration-test/src/tests/channel_upgrade/ics29.rs @@ -1,10 +1,11 @@ -//! Tests channel upgrade fetures: +//! Tests channel upgrade features: //! //! - `ChannelUpgradeICS29` tests that only after the upgrade handshake is completed //! and the channel version has been updated to ICS29 can Incentivized packets be //! relayed. use ibc_relayer::chain::requests::{IncludeProof, QueryChannelRequest, QueryHeight}; +use ibc_relayer_types::core::ics04_channel::packet::Sequence; use ibc_relayer_types::core::ics04_channel::version::Version; use ibc_test_framework::chain::config::{set_max_deposit_period, set_voting_period}; use ibc_test_framework::prelude::*; @@ -140,6 +141,7 @@ impl BinaryChannelTest for ChannelUpgradeICS29 { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b, + Sequence::from(1), ); info!("Will initialise upgrade handshake with governance proposal..."); @@ -151,6 +153,7 @@ impl BinaryChannelTest for ChannelUpgradeICS29 { old_connection_hops_a.first().unwrap().as_str(), &serde_json::to_string(&new_version.0).unwrap(), chains.handle_a().get_signer().unwrap().as_ref(), + "1", )?; info!("Check that the channel upgrade successfully upgraded the version..."); diff --git a/tools/integration-test/src/tests/channel_upgrade/upgrade_handshake.rs b/tools/integration-test/src/tests/channel_upgrade/upgrade_handshake.rs index 7108e3cb5f..c2871b528f 100644 --- a/tools/integration-test/src/tests/channel_upgrade/upgrade_handshake.rs +++ b/tools/integration-test/src/tests/channel_upgrade/upgrade_handshake.rs @@ -6,12 +6,14 @@ use std::thread::sleep; use ibc_relayer::chain::requests::{IncludeProof, QueryChannelRequest, QueryHeight}; +use ibc_relayer_types::core::ics04_channel::packet::Sequence; use ibc_relayer_types::core::ics04_channel::version::Version; use ibc_test_framework::chain::config::{set_max_deposit_period, set_voting_period}; use ibc_test_framework::prelude::*; use ibc_test_framework::relayer::channel::{ - assert_eventually_channel_established, assert_eventually_channel_upgrade_open, - ChannelUpgradableAttributes, + assert_eventually_channel_established, assert_eventually_channel_upgrade_ack, + assert_eventually_channel_upgrade_cancel, assert_eventually_channel_upgrade_open, + assert_eventually_channel_upgrade_try, ChannelUpgradableAttributes, }; #[test] @@ -24,6 +26,16 @@ fn test_channel_upgrade_clear_handshake() -> Result<(), Error> { run_binary_channel_test(&ChannelUpgradeClearHandshake) } +#[test] +fn test_channel_upgrade_timeout_ack_handshake() -> Result<(), Error> { + run_binary_channel_test(&ChannelUpgradeTimeoutAckHandshake) +} + +#[test] +fn test_channel_upgrade_timeout_confirm_handshake() -> Result<(), Error> { + run_binary_channel_test(&ChannelUpgradeTimeoutConfirmHandshake) +} + const MAX_DEPOSIT_PERIOD: &str = "10s"; const VOTING_PERIOD: u64 = 10; @@ -97,6 +109,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshake { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b, + Sequence::from(1), ); info!("Will initialise upgrade handshake with governance proposal..."); @@ -108,6 +121,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshake { old_connection_hops_a.first().unwrap().as_str(), &serde_json::to_string(&new_version.0).unwrap(), chains.handle_a().get_signer().unwrap().as_ref(), + "1", )?; info!("Check that the channel upgrade successfully upgraded the version..."); @@ -202,6 +216,7 @@ impl BinaryChannelTest for ChannelUpgradeClearHandshake { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b, + Sequence::from(1), ); info!("Will initialise upgrade handshake with governance proposal..."); @@ -213,6 +228,7 @@ impl BinaryChannelTest for ChannelUpgradeClearHandshake { old_connection_hops_a.first().unwrap().as_str(), &serde_json::to_string(&new_version.0).unwrap(), chains.handle_a().get_signer().unwrap().as_ref(), + "1", )?; // After the governance proposal, wait a few blocks before starting the Hermes instance @@ -236,3 +252,281 @@ impl BinaryChannelTest for ChannelUpgradeClearHandshake { }) } } + +pub struct ChannelUpgradeTimeoutAckHandshake; + +impl TestOverrides for ChannelUpgradeTimeoutAckHandshake { + fn modify_relayer_config(&self, config: &mut Config) { + config.mode.channels.enabled = true; + } + + fn modify_genesis_file(&self, genesis: &mut serde_json::Value) -> Result<(), Error> { + set_max_deposit_period(genesis, MAX_DEPOSIT_PERIOD)?; + set_voting_period(genesis, VOTING_PERIOD)?; + Ok(()) + } + + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl BinaryChannelTest for ChannelUpgradeTimeoutAckHandshake { + fn run( + &self, + _config: &TestConfig, + relayer: RelayerDriver, + chains: ConnectedChains, + channels: ConnectedChannel, + ) -> Result<(), Error> { + info!("Check that channels are both in OPEN State"); + + assert_eventually_channel_established( + &chains.handle_b, + &chains.handle_a, + &channels.channel_id_b.as_ref(), + &channels.port_b.as_ref(), + )?; + + let channel_end_a = chains + .handle_a + .query_channel( + QueryChannelRequest { + port_id: channels.port_a.0.clone(), + channel_id: channels.channel_id_a.0.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map(|(channel_end, _)| channel_end) + .map_err(|e| eyre!("Error querying ChannelEnd A: {e}"))?; + + let channel_end_b = chains + .handle_b + .query_channel( + QueryChannelRequest { + port_id: channels.port_b.0.clone(), + channel_id: channels.channel_id_b.0.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map(|(channel_end, _)| channel_end) + .map_err(|e| eyre!("Error querying ChannelEnd B: {e}"))?; + + let old_version = channel_end_a.version; + let old_ordering = channel_end_a.ordering; + let old_connection_hops_a = channel_end_a.connection_hops; + let old_connection_hops_b = channel_end_b.connection_hops; + + let channel = channels.channel; + let new_version = Version::ics20_with_fee(); + + let old_attrs = ChannelUpgradableAttributes::new( + old_version.clone(), + old_version.clone(), + old_ordering, + old_connection_hops_a.clone(), + old_connection_hops_b.clone(), + Sequence::from(1), + ); + + info!("Will update channel params to set a short upgrade timeout..."); + + chains.node_b.chain_driver().update_channel_params( + 5000000000, + chains.handle_b().get_signer().unwrap().as_ref(), + "1", + )?; + + info!("Will initialise upgrade handshake with governance proposal..."); + + chains.node_a.chain_driver().initialise_channel_upgrade( + channel.src_port_id().as_str(), + channel.src_channel_id().unwrap().as_str(), + old_ordering.as_str(), + old_connection_hops_a.first().unwrap().as_str(), + &serde_json::to_string(&new_version.0).unwrap(), + chains.handle_a().get_signer().unwrap().as_ref(), + "1", + )?; + + info!("Will run ChanUpgradeTry step..."); + + channel.build_chan_upgrade_try_and_send()?; + + info!("Check that the step ChanUpgradeTry was correctly executed..."); + + assert_eventually_channel_upgrade_try( + &chains.handle_b, + &chains.handle_a, + &channels.channel_id_b.as_ref(), + &channels.port_b.as_ref(), + &old_attrs.flipped(), + )?; + + std::thread::sleep(Duration::from_secs(10)); + + info!("Check that the channel upgrade was successfully cancelled..."); + + // This will assert that both channel ends are eventually + // in Open state, and that the fields have not changed. + relayer.with_supervisor(|| { + assert_eventually_channel_upgrade_cancel( + &chains.handle_a, + &chains.handle_b, + &channels.channel_id_a.as_ref(), + &channels.port_a.as_ref(), + &old_attrs, + )?; + + Ok(()) + }) + } +} + +pub struct ChannelUpgradeTimeoutConfirmHandshake; + +impl TestOverrides for ChannelUpgradeTimeoutConfirmHandshake { + fn modify_relayer_config(&self, config: &mut Config) { + config.mode.channels.enabled = true; + } + + fn modify_genesis_file(&self, genesis: &mut serde_json::Value) -> Result<(), Error> { + set_max_deposit_period(genesis, MAX_DEPOSIT_PERIOD)?; + set_voting_period(genesis, VOTING_PERIOD)?; + Ok(()) + } + + fn should_spawn_supervisor(&self) -> bool { + false + } +} + +impl BinaryChannelTest for ChannelUpgradeTimeoutConfirmHandshake { + fn run( + &self, + _config: &TestConfig, + relayer: RelayerDriver, + chains: ConnectedChains, + channels: ConnectedChannel, + ) -> Result<(), Error> { + info!("Check that channels are both in OPEN State"); + + assert_eventually_channel_established( + &chains.handle_b, + &chains.handle_a, + &channels.channel_id_b.as_ref(), + &channels.port_b.as_ref(), + )?; + + let channel_end_a = chains + .handle_a + .query_channel( + QueryChannelRequest { + port_id: channels.port_a.0.clone(), + channel_id: channels.channel_id_a.0.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map(|(channel_end, _)| channel_end) + .map_err(|e| eyre!("Error querying ChannelEnd A: {e}"))?; + + let channel_end_b = chains + .handle_b + .query_channel( + QueryChannelRequest { + port_id: channels.port_b.0.clone(), + channel_id: channels.channel_id_b.0.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map(|(channel_end, _)| channel_end) + .map_err(|e| eyre!("Error querying ChannelEnd B: {e}"))?; + + let old_version = channel_end_a.version; + let old_ordering = channel_end_a.ordering; + let old_connection_hops_a = channel_end_a.connection_hops; + let old_connection_hops_b = channel_end_b.connection_hops; + + let channel = channels.channel; + let new_version = Version::ics20_with_fee(); + + let old_attrs = ChannelUpgradableAttributes::new( + old_version.clone(), + old_version.clone(), + old_ordering, + old_connection_hops_a.clone(), + old_connection_hops_b.clone(), + Sequence::from(1), + ); + + info!("Will update channel params to set a short upgrade timeout..."); + + chains.node_a.chain_driver().update_channel_params( + 5000000000, + chains.handle_a().get_signer().unwrap().as_ref(), + "1", + )?; + + info!("Will initialise upgrade handshake with governance proposal..."); + + chains.node_a.chain_driver().initialise_channel_upgrade( + channel.src_port_id().as_str(), + channel.src_channel_id().unwrap().as_str(), + old_ordering.as_str(), + old_connection_hops_a.first().unwrap().as_str(), + &serde_json::to_string(&new_version.0).unwrap(), + chains.handle_a().get_signer().unwrap().as_ref(), + "2", + )?; + + info!("Will run ChanUpgradeTry step..."); + + channel.build_chan_upgrade_try_and_send()?; + + info!("Check that the step ChanUpgradeTry was correctly executed..."); + + assert_eventually_channel_upgrade_try( + &chains.handle_b, + &chains.handle_a, + &channels.channel_id_b.as_ref(), + &channels.port_b.as_ref(), + &old_attrs.flipped(), + )?; + + info!("Will run ChanUpgradeAck step..."); + + channel.flipped().build_chan_upgrade_ack_and_send()?; + + info!("Check that the step ChanUpgradeAck was correctly executed..."); + + assert_eventually_channel_upgrade_ack( + &chains.handle_a, + &chains.handle_b, + &channels.channel_id_a.as_ref(), + &channels.port_a.as_ref(), + &old_attrs, + )?; + + std::thread::sleep(Duration::from_secs(10)); + + info!("Check that the channel upgrade was successfully cancelled..."); + + // This will assert that both channel ends are eventually + // in Open state, and that the fields have not changed. + relayer.with_supervisor(|| { + assert_eventually_channel_upgrade_cancel( + &chains.handle_a, + &chains.handle_b, + &channels.channel_id_a.as_ref(), + &channels.port_a.as_ref(), + &old_attrs, + )?; + + Ok(()) + }) + } +} diff --git a/tools/integration-test/src/tests/channel_upgrade/upgrade_handshake_steps.rs b/tools/integration-test/src/tests/channel_upgrade/upgrade_handshake_steps.rs index c0992af356..7c1c59865d 100644 --- a/tools/integration-test/src/tests/channel_upgrade/upgrade_handshake_steps.rs +++ b/tools/integration-test/src/tests/channel_upgrade/upgrade_handshake_steps.rs @@ -11,16 +11,21 @@ //! //! - `ChannelUpgradeHandshakeFromConfirm` tests that the channel worker will finish the //! upgrade handshake if the channel is being upgraded and is at the Confirm step. +//! +//! - `ChannelUpgradeHandshakeTimeoutOnAck` tests that the channel worker will finish the +//! cancel the upgrade handshake if the Ack step fails due to an upgrade timeout. use ibc_relayer::chain::requests::{IncludeProof, QueryChannelRequest, QueryHeight}; +use ibc_relayer_types::core::ics04_channel::packet::Sequence; use ibc_relayer_types::core::ics04_channel::version::Version; +use ibc_relayer_types::events::IbcEventType; use ibc_test_framework::chain::config::{set_max_deposit_period, set_voting_period}; use ibc_test_framework::prelude::*; use ibc_test_framework::relayer::channel::{ assert_eventually_channel_established, assert_eventually_channel_upgrade_ack, - assert_eventually_channel_upgrade_confirm, assert_eventually_channel_upgrade_init, - assert_eventually_channel_upgrade_open, assert_eventually_channel_upgrade_try, - ChannelUpgradableAttributes, + assert_eventually_channel_upgrade_cancel, assert_eventually_channel_upgrade_confirm, + assert_eventually_channel_upgrade_init, assert_eventually_channel_upgrade_open, + assert_eventually_channel_upgrade_try, ChannelUpgradableAttributes, }; #[test] @@ -43,6 +48,11 @@ fn test_channel_upgrade_handshake_from_confirm() -> Result<(), Error> { run_binary_channel_test(&ChannelUpgradeHandshakeFromConfirm) } +#[test] +fn test_channel_upgrade_handshake_timeout_on_ack() -> Result<(), Error> { + run_binary_channel_test(&ChannelUpgradeHandshakeTimeoutOnAck) +} + const MAX_DEPOSIT_PERIOD: &str = "10s"; const VOTING_PERIOD: u64 = 10; @@ -123,6 +133,7 @@ impl BinaryChannelTest for ChannelUpgradeManualHandshake { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b.clone(), + Sequence::from(1), ); let interm_attrs = ChannelUpgradableAttributes::new( @@ -131,6 +142,7 @@ impl BinaryChannelTest for ChannelUpgradeManualHandshake { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b.clone(), + Sequence::from(1), ); let upgraded_attrs = ChannelUpgradableAttributes::new( @@ -139,6 +151,7 @@ impl BinaryChannelTest for ChannelUpgradeManualHandshake { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b, + Sequence::from(1), ); info!("Will initialise upgrade handshake with governance proposal..."); @@ -150,6 +163,7 @@ impl BinaryChannelTest for ChannelUpgradeManualHandshake { old_connection_hops_a.first().unwrap().as_str(), &serde_json::to_string(&new_version.0).unwrap(), chains.handle_a().get_signer().unwrap().as_ref(), + "1", )?; info!("Check that the step ChanUpgradeInit was correctly executed..."); @@ -284,6 +298,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromTry { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b.clone(), + Sequence::from(1), ); let upgraded_attrs = ChannelUpgradableAttributes::new( @@ -292,6 +307,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromTry { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b, + Sequence::from(1), ); info!("Will initialise upgrade handshake with governance proposal..."); @@ -303,6 +319,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromTry { old_connection_hops_a.first().unwrap().as_str(), &serde_json::to_string(&new_version.0).unwrap(), chains.handle_a().get_signer().unwrap().as_ref(), + "1", )?; info!("Will run ChanUpgradeTry step..."); @@ -397,6 +414,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromAck { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b.clone(), + Sequence::from(1), ); let upgraded_attrs = ChannelUpgradableAttributes::new( @@ -405,6 +423,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromAck { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b, + Sequence::from(1), ); info!("Will initialise upgrade handshake with governance proposal..."); @@ -416,6 +435,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromAck { old_connection_hops_a.first().unwrap().as_str(), &serde_json::to_string(&new_version.0).unwrap(), chains.handle_a().get_signer().unwrap().as_ref(), + "1", )?; info!("Will run ChanUpgradeTry step..."); @@ -523,6 +543,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromConfirm { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b.clone(), + Sequence::from(1), ); let interm_attrs = ChannelUpgradableAttributes::new( @@ -531,6 +552,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromConfirm { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b.clone(), + Sequence::from(1), ); let upgraded_attrs = ChannelUpgradableAttributes::new( @@ -539,6 +561,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromConfirm { old_ordering, old_connection_hops_a.clone(), old_connection_hops_b, + Sequence::from(1), ); info!("Will initialise upgrade handshake with governance proposal..."); @@ -550,6 +573,7 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromConfirm { old_connection_hops_a.first().unwrap().as_str(), &serde_json::to_string(&new_version.0).unwrap(), chains.handle_a().get_signer().unwrap().as_ref(), + "1", )?; info!("Will run ChanUpgradeTry step..."); @@ -613,6 +637,152 @@ impl BinaryChannelTest for ChannelUpgradeHandshakeFromConfirm { } } +struct ChannelUpgradeHandshakeTimeoutOnAck; + +impl BinaryChannelTest for ChannelUpgradeHandshakeTimeoutOnAck { + fn run( + &self, + _config: &TestConfig, + _relayer: RelayerDriver, + chains: ConnectedChains, + channels: ConnectedChannel, + ) -> Result<(), Error> { + info!("Check that channels are both in OPEN State"); + + assert_eventually_channel_established( + &chains.handle_b, + &chains.handle_a, + &channels.channel_id_b.as_ref(), + &channels.port_b.as_ref(), + )?; + + let channel_end_a = chains + .handle_a + .query_channel( + QueryChannelRequest { + port_id: channels.port_a.0.clone(), + channel_id: channels.channel_id_a.0.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map(|(channel_end, _)| channel_end) + .map_err(|e| eyre!("Error querying ChannelEnd A: {e}"))?; + + let channel_end_b = chains + .handle_b + .query_channel( + QueryChannelRequest { + port_id: channels.port_b.0.clone(), + channel_id: channels.channel_id_b.0.clone(), + height: QueryHeight::Latest, + }, + IncludeProof::No, + ) + .map(|(channel_end, _)| channel_end) + .map_err(|e| eyre!("Error querying ChannelEnd B: {e}"))?; + + let old_version = channel_end_a.version; + let old_ordering = channel_end_a.ordering; + let old_connection_hops_a = channel_end_a.connection_hops; + let old_connection_hops_b = channel_end_b.connection_hops; + + let channel = channels.channel; + let new_version = Version::ics20_with_fee(); + + let old_attrs = ChannelUpgradableAttributes::new( + old_version.clone(), + old_version.clone(), + old_ordering, + old_connection_hops_a.clone(), + old_connection_hops_b.clone(), + Sequence::from(1), + ); + + info!("Will update channel params to set a short upgrade timeout..."); + + chains.node_b.chain_driver().update_channel_params( + 5000000000, + chains.handle_b().get_signer().unwrap().as_ref(), + "1", + )?; + + info!("Will initialise upgrade handshake with governance proposal..."); + + chains.node_a.chain_driver().initialise_channel_upgrade( + channel.src_port_id().as_str(), + channel.src_channel_id().unwrap().as_str(), + old_ordering.as_str(), + old_connection_hops_a.first().unwrap().as_str(), + &serde_json::to_string(&new_version.0).unwrap(), + chains.handle_a().get_signer().unwrap().as_ref(), + "1", + )?; + + info!("Check that the step ChanUpgradeInit was correctly executed..."); + + assert_eventually_channel_upgrade_init( + &chains.handle_a, + &chains.handle_b, + &channels.channel_id_a.as_ref(), + &channels.port_a.as_ref(), + &old_attrs, + )?; + + info!("Will run ChanUpgradeTry step..."); + + channel.build_chan_upgrade_try_and_send()?; + + info!("Check that the step ChanUpgradeTry was correctly executed..."); + + assert_eventually_channel_upgrade_try( + &chains.handle_b, + &chains.handle_a, + &channels.channel_id_b.as_ref(), + &channels.port_b.as_ref(), + &old_attrs.flipped(), + )?; + + // wait enough time so that ACK fails due to upgrade timeout + sleep(Duration::from_secs(10)); + + info!("Will run ChanUpgradeAck step..."); + + let ack_event = channel.flipped().build_chan_upgrade_ack_and_send()?; + + info!("Check that the step ChanUpgradeAck timed out..."); + + // ACK should fail because the upgrade has timed out + assert!( + ack_event.is_none(), + "channel upgrade ack should have failed due to timeout" + ); + + info!("Will run ChanUpgradeCancel step..."); + + // Since the following assertion checks that the fields of channel ends + // have not been updated, asserting there is a `UpgradeCancelChannel` event + // avoids having a passing test due to the Upgrade Init step failing + let cancel_event = channel.build_chan_upgrade_cancel_and_send()?; + assert_eq!( + cancel_event.event_type(), + IbcEventType::UpgradeCancelChannel + ); + + info!("Check that the step ChanUpgradeCancel was correctly executed..."); + + assert_eventually_channel_upgrade_cancel( + &chains.handle_b, + &chains.handle_a, + &channels.channel_id_b.as_ref(), + &channels.port_b.as_ref(), + &old_attrs.flipped(), + )?; + + Ok(()) + } +} + impl HasOverrides for ChannelUpgradeManualHandshake { type Overrides = ChannelUpgradeTestOverrides; @@ -644,3 +814,11 @@ impl HasOverrides for ChannelUpgradeHandshakeFromConfirm { &ChannelUpgradeTestOverrides } } + +impl HasOverrides for ChannelUpgradeHandshakeTimeoutOnAck { + type Overrides = ChannelUpgradeTestOverrides; + + fn get_overrides(&self) -> &ChannelUpgradeTestOverrides { + &ChannelUpgradeTestOverrides + } +} diff --git a/tools/integration-test/src/tests/client_upgrade.rs b/tools/integration-test/src/tests/client_upgrade.rs index 01731e6d8b..9387321cc6 100644 --- a/tools/integration-test/src/tests/client_upgrade.rs +++ b/tools/integration-test/src/tests/client_upgrade.rs @@ -121,7 +121,7 @@ impl BinaryChainTest for ClientUpgradeTest { .map_err(handle_generic_error)?; // Vote on the proposal so the chain will upgrade - driver.vote_proposal(&fee_denom_a.with_amount(381000000u64).to_string())?; + driver.vote_proposal(&fee_denom_a.with_amount(381000000u64).to_string(), "1")?; info!("Assert that the chain upgrade proposal is eventually passed"); @@ -267,7 +267,7 @@ impl BinaryChainTest for HeightTooHighClientUpgradeTest { .map_err(handle_generic_error)?; // Vote on the proposal so the chain will upgrade - driver.vote_proposal(&fee_denom_a.with_amount(381000000u64).to_string())?; + driver.vote_proposal(&fee_denom_a.with_amount(381000000u64).to_string(), "1")?; // The application height reports a height of 1 less than the height according to Tendermint client_upgrade_height.increment(); @@ -364,7 +364,7 @@ impl BinaryChainTest for HeightTooLowClientUpgradeTest { .map_err(handle_generic_error)?; // Vote on the proposal so the chain will upgrade - driver.vote_proposal(&fee_denom_a.with_amount(381000000u64).to_string())?; + driver.vote_proposal(&fee_denom_a.with_amount(381000000u64).to_string(), "1")?; // The application height reports a height of 1 less than the height according to Tendermint client_upgrade_height diff --git a/tools/test-framework/src/chain/cli/upgrade.rs b/tools/test-framework/src/chain/cli/upgrade.rs index 57c0a055c1..172391bf05 100644 --- a/tools/test-framework/src/chain/cli/upgrade.rs +++ b/tools/test-framework/src/chain/cli/upgrade.rs @@ -1,8 +1,11 @@ /*! Methods for voting on a proposal. */ +use eyre::eyre; + use crate::chain::exec::simple_exec; use crate::error::Error; +use crate::prelude::*; pub fn vote_proposal( chain_id: &str, @@ -10,6 +13,7 @@ pub fn vote_proposal( home_path: &str, rpc_listen_address: &str, fees: &str, + proposal_id: &str, ) -> Result<(), Error> { simple_exec( chain_id, @@ -20,7 +24,7 @@ pub fn vote_proposal( "tx", "gov", "vote", - "1", + proposal_id, "yes", "--chain-id", chain_id, @@ -48,7 +52,7 @@ pub fn submit_gov_proposal( proposal_file: &str, ) -> Result<(), Error> { let proposal_file = format!("{}/{}", home_path, proposal_file); - simple_exec( + let output = simple_exec( chain_id, command_path, &[ @@ -74,5 +78,48 @@ pub fn submit_gov_proposal( ], )?; + let json_output: serde_json::Value = + serde_json::from_str(&output.stdout).map_err(handle_generic_error)?; + + if json_output + .get("code") + .ok_or_else(|| eyre!("expected `code` field in output"))? + .as_u64() + .ok_or_else(|| eyre!("expected `code` to be a u64"))? + != 0 + { + let raw_log = json_output + .get("raw_log") + .ok_or_else(|| eyre!("expected `code` field in output"))? + .as_str() + .ok_or_else(|| eyre!("expected `raw_log` to be a str"))?; + warn!("failed to submit governance proposal due to `{raw_log}`. Will retry..."); + simple_exec( + chain_id, + command_path, + &[ + "--node", + rpc_listen_address, + "tx", + "gov", + "submit-proposal", + &proposal_file, + "--chain-id", + chain_id, + "--home", + home_path, + "--keyring-backend", + "test", + "--gas", + "20000000", + "--from", + signer, + "--output", + "json", + "--yes", + ], + )?; + } + Ok(()) } diff --git a/tools/test-framework/src/chain/ext/proposal.rs b/tools/test-framework/src/chain/ext/proposal.rs index 689b693d55..c42531462c 100644 --- a/tools/test-framework/src/chain/ext/proposal.rs +++ b/tools/test-framework/src/chain/ext/proposal.rs @@ -23,7 +23,7 @@ pub trait ChainProposalMethodsExt { proposal_id: u64, ) -> Result; - fn vote_proposal(&self, fees: &str) -> Result<(), Error>; + fn vote_proposal(&self, fees: &str, proposal_id: &str) -> Result<(), Error>; fn initialise_channel_upgrade( &self, @@ -33,6 +33,14 @@ pub trait ChainProposalMethodsExt { connection_hops: &str, version: &str, signer: &str, + proposal_id: &str, + ) -> Result<(), Error>; + + fn update_channel_params( + &self, + timestamp: u64, + signer: &str, + proposal_id: &str, ) -> Result<(), Error>; } @@ -47,13 +55,14 @@ impl<'a, Chain: Send> ChainProposalMethodsExt for MonoTagged Result<(), Error> { + fn vote_proposal(&self, fees: &str, proposal_id: &str) -> Result<(), Error> { vote_proposal( self.value().chain_id.as_str(), &self.value().command_path, &self.value().home_path, &self.value().rpc_listen_address(), fees, + proposal_id, )?; Ok(()) } @@ -66,6 +75,7 @@ impl<'a, Chain: Send> ChainProposalMethodsExt for MonoTagged Result<(), Error> { let gov_address = self.query_auth_module("gov")?; let channel_upgrade_proposal = create_channel_upgrade_proposal( @@ -92,7 +102,56 @@ impl<'a, Chain: Send> ChainProposalMethodsExt for MonoTagged Result<(), Error> { + let gov_address = self.query_auth_module("gov")?; + let channel_update_params_proposal = + create_channel_update_params_proposal(self.value(), timestamp, &gov_address)?; + submit_gov_proposal( + self.value().chain_id.as_str(), + &self.value().command_path, + &self.value().home_path, + &self.value().rpc_listen_address(), + signer, + &channel_update_params_proposal, + )?; + + self.value().assert_proposal_status( + self.value().chain_id.as_str(), + &self.value().command_path, + &self.value().home_path, + &self.value().rpc_listen_address(), + ProposalStatus::VotingPeriod, + proposal_id, )?; vote_proposal( @@ -101,6 +160,7 @@ impl<'a, Chain: Send> ChainProposalMethodsExt for MonoTagged ChainProposalMethodsExt for MonoTagged Result { + let raw_proposal = r#" + { + "messages": [ + { + "@type": "/ibc.core.channel.v1.MsgUpdateParams", + "params": { + "upgrade_timeout": { + "timestamp": {timestamp} + } + }, + "authority":"{signer}" + } + ], + "deposit": "10000001stake", + "title": "Channel update params", + "summary": "Update channel params", + "expedited": false + }"#; + + let proposal = raw_proposal.replace("{timestamp}", ×tamp.to_string()); + let proposal = proposal.replace("{signer}", gov_address); + + let output_file = "channel_update_params_proposal.json"; + + chain_driver.write_file(output_file, &proposal)?; + Ok(output_file.to_owned()) +} diff --git a/tools/test-framework/src/framework/binary/ics.rs b/tools/test-framework/src/framework/binary/ics.rs index 8125a541f9..d11c8ff3e8 100644 --- a/tools/test-framework/src/framework/binary/ics.rs +++ b/tools/test-framework/src/framework/binary/ics.rs @@ -90,6 +90,7 @@ where &node_a.chain_driver.home_path, &node_a.chain_driver.rpc_listen_address(), &provider_fee, + "1", )?; node_a.chain_driver.assert_proposal_status( diff --git a/tools/test-framework/src/relayer/chain.rs b/tools/test-framework/src/relayer/chain.rs index e64bdf9938..9178fce0a1 100644 --- a/tools/test-framework/src/relayer/chain.rs +++ b/tools/test-framework/src/relayer/chain.rs @@ -21,9 +21,9 @@ */ use crossbeam_channel as channel; -use ibc_proto::ibc::core::channel::v1::QueryUpgradeRequest; +use ibc_proto::ibc::core::channel::v1::{QueryUpgradeErrorRequest, QueryUpgradeRequest}; use ibc_relayer::chain::cosmos::version::Specs; -use ibc_relayer_types::core::ics04_channel::upgrade::Upgrade; +use ibc_relayer_types::core::ics04_channel::upgrade::{ErrorReceipt, Upgrade}; use tracing::Span; use ibc_proto::ibc::apps::fee::v1::{ @@ -445,4 +445,12 @@ where ) -> Result<(Upgrade, Option), Error> { self.value().query_upgrade(request, height) } + + fn query_upgrade_error( + &self, + request: QueryUpgradeErrorRequest, + height: Height, + ) -> Result<(ErrorReceipt, Option), Error> { + self.value().query_upgrade_error(request, height) + } } diff --git a/tools/test-framework/src/relayer/channel.rs b/tools/test-framework/src/relayer/channel.rs index 9b8f6de1db..e16dbf90ac 100644 --- a/tools/test-framework/src/relayer/channel.rs +++ b/tools/test-framework/src/relayer/channel.rs @@ -7,6 +7,7 @@ use ibc_relayer::channel::{extract_channel_id, Channel, ChannelSide}; use ibc_relayer_types::core::ics04_channel::channel::{ ChannelEnd, IdentifiedChannelEnd, Ordering, State as ChannelState, UpgradeState, }; +use ibc_relayer_types::core::ics04_channel::packet::Sequence; use ibc_relayer_types::core::ics04_channel::version::Version; use ibc_relayer_types::core::ics24_host::identifier::ConnectionId; @@ -43,6 +44,7 @@ pub struct ChannelUpgradableAttributes { ordering: Ordering, connection_hops_a: Vec, connection_hops_b: Vec, + upgrade_sequence: Sequence, } impl ChannelUpgradableAttributes { @@ -52,6 +54,7 @@ impl ChannelUpgradableAttributes { ordering: Ordering, connection_hops_a: Vec, connection_hops_b: Vec, + upgrade_sequence: Sequence, ) -> Self { Self { version_a, @@ -59,6 +62,7 @@ impl ChannelUpgradableAttributes { ordering, connection_hops_a, connection_hops_b, + upgrade_sequence, } } @@ -69,6 +73,7 @@ impl ChannelUpgradableAttributes { ordering: self.ordering, connection_hops_a: self.connection_hops_b.clone(), connection_hops_b: self.connection_hops_a.clone(), + upgrade_sequence: self.upgrade_sequence, } } @@ -91,6 +96,10 @@ impl ChannelUpgradableAttributes { pub fn connection_hops_b(&self) -> &Vec { &self.connection_hops_b } + + pub fn upgrade_sequence(&self) -> &Sequence { + &self.upgrade_sequence + } } pub fn init_channel( @@ -338,6 +347,8 @@ pub fn assert_eventually_channel_upgrade_init( + handle_a: &ChainA, + handle_b: &ChainB, + channel_id_a: &TaggedChannelIdRef, + port_id_a: &TaggedPortIdRef, + upgrade_attrs: &ChannelUpgradableAttributes, +) -> Result, Error> { + assert_eventually_succeed( + "channel upgrade cancel step should be done", + 20, + Duration::from_secs(1), + || { + assert_channel_upgrade_state( + ChannelState::Open(UpgradeState::NotUpgrading), + ChannelState::Open(UpgradeState::NotUpgrading), + handle_a, + handle_b, + channel_id_a, + port_id_a, + upgrade_attrs, + &Sequence::from(1), + &Sequence::from(1), ) }, ) @@ -455,6 +501,8 @@ fn assert_channel_upgrade_state( channel_id_a: &TaggedChannelIdRef, port_id_a: &TaggedPortIdRef, upgrade_attrs: &ChannelUpgradableAttributes, + upgrade_sequence_a: &Sequence, + upgrade_sequence_b: &Sequence, ) -> Result, Error> { let channel_end_a = query_channel_end(handle_a, channel_id_a, port_id_a)?; @@ -499,6 +547,18 @@ fn assert_channel_upgrade_state( ))); } + if !channel_end_a + .value() + .upgraded_sequence + .eq(upgrade_sequence_a) + { + return Err(Error::generic(eyre!( + "expected channel end A upgrade sequence to be `{}`, but it is instead `{}`", + upgrade_sequence_a, + channel_end_a.value().upgraded_sequence + ))); + } + let channel_id_b = channel_end_a .tagged_counterparty_channel_id() .ok_or_else(|| eyre!("expected counterparty channel id to present on open channel"))?; @@ -548,5 +608,17 @@ fn assert_channel_upgrade_state( ))); } + if !channel_end_b + .value() + .upgraded_sequence + .eq(upgrade_sequence_b) + { + return Err(Error::generic(eyre!( + "expected channel end B upgrade sequence to be `{}`, but it is instead `{}`", + upgrade_sequence_b, + channel_end_b.value().upgraded_sequence + ))); + } + Ok(channel_id_b) }