diff --git a/.changelog/unreleased/breaking-changes/539-separate-handler-functions.md b/.changelog/unreleased/breaking-changes/539-separate-handler-functions.md new file mode 100644 index 000000000..05e34a8de --- /dev/null +++ b/.changelog/unreleased/breaking-changes/539-separate-handler-functions.md @@ -0,0 +1,2 @@ +- Separate validation/execution handlers from context API + ([#539](https://github.com/cosmos/ibc-rs/issues/539)) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index dd43dd8a1..740d384c1 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -44,7 +44,7 @@ jobs: command: fmt args: --all -- --check - doc: + doc_all_features: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -56,6 +56,19 @@ jobs: with: command: doc args: --all-features --release + + doc_no_default_features: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: doc + args: --no-default-features --release clippy_all_features: runs-on: ubuntu-latest diff --git a/crates/ibc/src/core/context.rs b/crates/ibc/src/core/context.rs index 471deb727..5a477c76a 100644 --- a/crates/ibc/src/core/context.rs +++ b/crates/ibc/src/core/context.rs @@ -1,74 +1,35 @@ -mod chan_close_confirm; -mod chan_close_init; -mod chan_open_ack; -mod chan_open_confirm; -mod chan_open_init; -mod chan_open_try; - -mod acknowledgement; -mod recv_packet; -mod timeout; +use core::time::Duration; +use displaydoc::Display; +use ibc_proto::google::protobuf::Any; use crate::prelude::*; use crate::signer::Signer; -use self::chan_close_confirm::{chan_close_confirm_execute, chan_close_confirm_validate}; -use self::chan_close_init::{chan_close_init_execute, chan_close_init_validate}; -use self::chan_open_ack::{chan_open_ack_execute, chan_open_ack_validate}; -use self::chan_open_confirm::{chan_open_confirm_execute, chan_open_confirm_validate}; -use self::chan_open_init::{chan_open_init_execute, chan_open_init_validate}; -use self::chan_open_try::{chan_open_try_execute, chan_open_try_validate}; - -use self::acknowledgement::{acknowledgement_packet_execute, acknowledgement_packet_validate}; -use self::recv_packet::{recv_packet_execute, recv_packet_validate}; -use self::timeout::{timeout_packet_execute, timeout_packet_validate, TimeoutMsgType}; - -use super::ics02_client::msgs::MsgUpdateOrMisbehaviour; -use super::router::Router; -use super::{ - ics02_client::error::ClientError, - ics03_connection::error::ConnectionError, - ics04_channel::error::{ChannelError, PacketError}, -}; -use core::time::Duration; - -use ibc_proto::google::protobuf::Any; - use crate::core::events::IbcEvent; use crate::core::ics02_client::client_state::ClientState; use crate::core::ics02_client::consensus_state::ConsensusState; +use crate::core::ics02_client::error::ClientError; use crate::core::ics03_connection::connection::ConnectionEnd; +use crate::core::ics03_connection::error::ConnectionError; use crate::core::ics03_connection::version::{ get_compatible_versions, pick_version, Version as ConnectionVersion, }; use crate::core::ics04_channel::channel::ChannelEnd; use crate::core::ics04_channel::commitment::{AcknowledgementCommitment, PacketCommitment}; use crate::core::ics04_channel::context::calculate_block_delay; -use crate::core::ics04_channel::msgs::{ChannelMsg, PacketMsg}; +use crate::core::ics04_channel::error::{ChannelError, PacketError}; use crate::core::ics04_channel::packet::{Receipt, Sequence}; use crate::core::ics23_commitment::commitment::CommitmentPrefix; +use crate::core::ics24_host::identifier::ClientId; use crate::core::ics24_host::identifier::ConnectionId; use crate::core::ics24_host::path::{ AckPath, ChannelEndPath, ClientConnectionPath, ClientConsensusStatePath, ClientStatePath, CommitmentPath, ConnectionPath, ReceiptPath, SeqAckPath, SeqRecvPath, SeqSendPath, }; +use crate::core::router::Router; use crate::core::timestamp::Timestamp; -use crate::core::{ - ics02_client::{ - handler::{create_client, update_client, upgrade_client}, - msgs::ClientMsg, - }, - ics03_connection::{ - handler::{conn_open_ack, conn_open_confirm, conn_open_init, conn_open_try}, - msgs::ConnectionMsg, - }, - ics24_host::identifier::ClientId, - msgs::MsgEnvelope, -}; use crate::Height; -use displaydoc::Display; - /// Top-level error #[derive(Debug, Display)] pub enum ContextError { @@ -151,81 +112,6 @@ impl std::error::Error for RouterError { /// /// Trait used for the top-level [`validate`](crate::core::validate) pub trait ValidationContext: Router { - /// Validation entrypoint. - fn validate(&self, msg: MsgEnvelope) -> Result<(), RouterError> - where - Self: Sized, - { - match msg { - MsgEnvelope::Client(msg) => match msg { - ClientMsg::CreateClient(msg) => create_client::validate(self, msg), - ClientMsg::UpdateClient(msg) => { - update_client::validate(self, MsgUpdateOrMisbehaviour::UpdateClient(msg)) - } - ClientMsg::Misbehaviour(msg) => { - update_client::validate(self, MsgUpdateOrMisbehaviour::Misbehaviour(msg)) - } - ClientMsg::UpgradeClient(msg) => upgrade_client::validate(self, msg), - } - .map_err(RouterError::ContextError), - MsgEnvelope::Connection(msg) => match msg { - ConnectionMsg::OpenInit(msg) => conn_open_init::validate(self, msg), - ConnectionMsg::OpenTry(msg) => conn_open_try::validate(self, msg), - ConnectionMsg::OpenAck(msg) => conn_open_ack::validate(self, msg), - ConnectionMsg::OpenConfirm(ref msg) => conn_open_confirm::validate(self, msg), - } - .map_err(RouterError::ContextError), - MsgEnvelope::Channel(msg) => { - let module_id = self - .lookup_module_channel(&msg) - .map_err(ContextError::from)?; - if !self.has_route(&module_id) { - return Err(ChannelError::RouteNotFound) - .map_err(ContextError::ChannelError) - .map_err(RouterError::ContextError); - } - - match msg { - ChannelMsg::OpenInit(msg) => chan_open_init_validate(self, module_id, msg), - ChannelMsg::OpenTry(msg) => chan_open_try_validate(self, module_id, msg), - ChannelMsg::OpenAck(msg) => chan_open_ack_validate(self, module_id, msg), - ChannelMsg::OpenConfirm(msg) => { - chan_open_confirm_validate(self, module_id, msg) - } - ChannelMsg::CloseInit(msg) => chan_close_init_validate(self, module_id, msg), - ChannelMsg::CloseConfirm(msg) => { - chan_close_confirm_validate(self, module_id, msg) - } - } - .map_err(RouterError::ContextError) - } - MsgEnvelope::Packet(msg) => { - let module_id = self - .lookup_module_packet(&msg) - .map_err(ContextError::from)?; - if !self.has_route(&module_id) { - return Err(ChannelError::RouteNotFound) - .map_err(ContextError::ChannelError) - .map_err(RouterError::ContextError); - } - - match msg { - PacketMsg::Recv(msg) => recv_packet_validate(self, msg), - PacketMsg::Ack(msg) => acknowledgement_packet_validate(self, module_id, msg), - PacketMsg::Timeout(msg) => { - timeout_packet_validate(self, module_id, TimeoutMsgType::Timeout(msg)) - } - PacketMsg::TimeoutOnClose(msg) => timeout_packet_validate( - self, - module_id, - TimeoutMsgType::TimeoutOnClose(msg), - ), - } - .map_err(RouterError::ContextError) - } - } - } - /// Returns the ClientState for the given identifier `client_id`. fn client_state(&self, client_id: &ClientId) -> Result, ContextError>; @@ -379,77 +265,6 @@ pub trait ValidationContext: Router { /// /// Trait used for the top-level [`execute`](crate::core::execute) and [`dispatch`](crate::core::dispatch) pub trait ExecutionContext: ValidationContext { - /// Execution entrypoint - fn execute(&mut self, msg: MsgEnvelope) -> Result<(), RouterError> - where - Self: Sized, - { - match msg { - MsgEnvelope::Client(msg) => match msg { - ClientMsg::CreateClient(msg) => create_client::execute(self, msg), - ClientMsg::UpdateClient(msg) => { - update_client::execute(self, MsgUpdateOrMisbehaviour::UpdateClient(msg)) - } - ClientMsg::Misbehaviour(msg) => { - update_client::execute(self, MsgUpdateOrMisbehaviour::Misbehaviour(msg)) - } - ClientMsg::UpgradeClient(msg) => upgrade_client::execute(self, msg), - } - .map_err(RouterError::ContextError), - MsgEnvelope::Connection(msg) => match msg { - ConnectionMsg::OpenInit(msg) => conn_open_init::execute(self, msg), - ConnectionMsg::OpenTry(msg) => conn_open_try::execute(self, msg), - ConnectionMsg::OpenAck(msg) => conn_open_ack::execute(self, msg), - ConnectionMsg::OpenConfirm(ref msg) => conn_open_confirm::execute(self, msg), - } - .map_err(RouterError::ContextError), - MsgEnvelope::Channel(msg) => { - let module_id = self - .lookup_module_channel(&msg) - .map_err(ContextError::from)?; - if !self.has_route(&module_id) { - return Err(ChannelError::RouteNotFound) - .map_err(ContextError::ChannelError) - .map_err(RouterError::ContextError); - } - - match msg { - ChannelMsg::OpenInit(msg) => chan_open_init_execute(self, module_id, msg), - ChannelMsg::OpenTry(msg) => chan_open_try_execute(self, module_id, msg), - ChannelMsg::OpenAck(msg) => chan_open_ack_execute(self, module_id, msg), - ChannelMsg::OpenConfirm(msg) => chan_open_confirm_execute(self, module_id, msg), - ChannelMsg::CloseInit(msg) => chan_close_init_execute(self, module_id, msg), - ChannelMsg::CloseConfirm(msg) => { - chan_close_confirm_execute(self, module_id, msg) - } - } - .map_err(RouterError::ContextError) - } - MsgEnvelope::Packet(msg) => { - let module_id = self - .lookup_module_packet(&msg) - .map_err(ContextError::from)?; - if !self.has_route(&module_id) { - return Err(ChannelError::RouteNotFound) - .map_err(ContextError::ChannelError) - .map_err(RouterError::ContextError); - } - - match msg { - PacketMsg::Recv(msg) => recv_packet_execute(self, module_id, msg), - PacketMsg::Ack(msg) => acknowledgement_packet_execute(self, module_id, msg), - PacketMsg::Timeout(msg) => { - timeout_packet_execute(self, module_id, TimeoutMsgType::Timeout(msg)) - } - PacketMsg::TimeoutOnClose(msg) => { - timeout_packet_execute(self, module_id, TimeoutMsgType::TimeoutOnClose(msg)) - } - } - .map_err(RouterError::ContextError) - } - } - } - /// Called upon successful client creation and update fn store_client_state( &mut self, diff --git a/crates/ibc/src/core/context/acknowledgement.rs b/crates/ibc/src/core/context/acknowledgement.rs deleted file mode 100644 index 3121c0b0c..000000000 --- a/crates/ibc/src/core/context/acknowledgement.rs +++ /dev/null @@ -1,296 +0,0 @@ -use crate::core::events::MessageEvent; -use crate::core::ics24_host::path::{ChannelEndPath, CommitmentPath, SeqAckPath}; -use crate::prelude::*; - -use crate::core::{ - events::IbcEvent, - ics04_channel::{ - channel::Order, error::ChannelError, events::AcknowledgePacket, handler::acknowledgement, - msgs::MsgAcknowledgement, - }, - router::ModuleId, -}; - -use super::{ContextError, ExecutionContext, ValidationContext}; - -pub(super) fn acknowledgement_packet_validate( - ctx_a: &ValCtx, - module_id: ModuleId, - msg: MsgAcknowledgement, -) -> Result<(), ContextError> -where - ValCtx: ValidationContext, -{ - acknowledgement::validate(ctx_a, &msg)?; - - let module = ctx_a - .get_route(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - - module - .on_acknowledgement_packet_validate(&msg.packet, &msg.acknowledgement, &msg.signer) - .map_err(ContextError::PacketError) -} - -pub(super) fn acknowledgement_packet_execute( - ctx_a: &mut ExecCtx, - module_id: ModuleId, - msg: MsgAcknowledgement, -) -> Result<(), ContextError> -where - ExecCtx: ExecutionContext, -{ - let chan_end_path_on_a = - ChannelEndPath::new(&msg.packet.port_id_on_a, &msg.packet.chan_id_on_a); - let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?; - let conn_id_on_a = &chan_end_on_a.connection_hops()[0]; - - // In all cases, this event is emitted - let event = IbcEvent::AcknowledgePacket(AcknowledgePacket::new( - msg.packet.clone(), - chan_end_on_a.ordering, - conn_id_on_a.clone(), - )); - ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_a.emit_ibc_event(event); - - let commitment_path_on_a = CommitmentPath::new( - &msg.packet.port_id_on_a, - &msg.packet.chan_id_on_a, - msg.packet.seq_on_a, - ); - - // check if we're in the NO-OP case - if ctx_a.get_packet_commitment(&commitment_path_on_a).is_err() { - // This error indicates that the timeout has already been relayed - // or there is a misconfigured relayer attempting to prove a timeout - // for a packet never sent. Core IBC will treat this error as a no-op in order to - // prevent an entire relay transaction from failing and consuming unnecessary fees. - return Ok(()); - }; - - let module = ctx_a - .get_route_mut(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - - let (extras, cb_result) = - module.on_acknowledgement_packet_execute(&msg.packet, &msg.acknowledgement, &msg.signer); - - cb_result?; - - // apply state changes - { - let commitment_path_on_a = CommitmentPath { - port_id: msg.packet.port_id_on_a.clone(), - channel_id: msg.packet.chan_id_on_a.clone(), - sequence: msg.packet.seq_on_a, - }; - ctx_a.delete_packet_commitment(&commitment_path_on_a)?; - - if let Order::Ordered = chan_end_on_a.ordering { - // Note: in validation, we verified that `msg.packet.sequence == nextSeqRecv` - // (where `nextSeqRecv` is the value in the store) - let seq_ack_path_on_a = - SeqAckPath::new(&msg.packet.port_id_on_a, &msg.packet.chan_id_on_a); - ctx_a.store_next_sequence_ack(&seq_ack_path_on_a, msg.packet.seq_on_a.increment())?; - } - } - - // emit events and logs - { - ctx_a.log_message("success: packet acknowledgement".to_string()); - - // Note: Acknowledgement event was emitted at the beginning - - for module_event in extras.events { - ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); - } - - for log_message in extras.log { - ctx_a.log_message(log_message); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use rstest::*; - - use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; - use crate::core::ics03_connection::connection::State as ConnectionState; - use crate::core::ics04_channel::channel::Counterparty; - use crate::core::ics04_channel::channel::State; - use crate::core::ics04_channel::commitment::compute_packet_commitment; - use crate::core::ics04_channel::Version; - use crate::core::ics24_host::identifier::ChannelId; - use crate::core::ics24_host::identifier::PortId; - use crate::core::timestamp::ZERO_DURATION; - use crate::{ - applications::transfer::MODULE_ID_STR, - core::{ - ics03_connection::{connection::ConnectionEnd, version::get_compatible_versions}, - ics04_channel::{ - channel::ChannelEnd, commitment::PacketCommitment, - msgs::acknowledgement::test_util::get_dummy_raw_msg_acknowledgement, - }, - ics24_host::identifier::{ClientId, ConnectionId}, - }, - mock::context::MockContext, - test_utils::DummyTransferModule, - Height, - }; - - struct Fixture { - ctx: MockContext, - module_id: ModuleId, - msg: MsgAcknowledgement, - packet_commitment: PacketCommitment, - conn_end_on_a: ConnectionEnd, - chan_end_on_a_ordered: ChannelEnd, - chan_end_on_a_unordered: ChannelEnd, - } - - #[fixture] - fn fixture() -> Fixture { - let client_height = Height::new(0, 2).unwrap(); - let mut ctx = MockContext::default().with_client(&ClientId::default(), client_height); - - let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); - let module = DummyTransferModule::new(); - ctx.add_route(module_id.clone(), module).unwrap(); - - let msg = MsgAcknowledgement::try_from(get_dummy_raw_msg_acknowledgement( - client_height.revision_height(), - )) - .unwrap(); - - let packet = msg.packet.clone(); - - let packet_commitment = compute_packet_commitment( - &msg.packet.data, - &msg.packet.timeout_height_on_b, - &msg.packet.timeout_timestamp_on_b, - ); - - let chan_end_on_a_unordered = ChannelEnd::new( - State::Open, - Order::Unordered, - Counterparty::new( - packet.port_id_on_b.clone(), - Some(packet.chan_id_on_b.clone()), - ), - vec![ConnectionId::default()], - Version::new("ics20-1".to_string()), - ) - .unwrap(); - - let chan_end_on_a_ordered = ChannelEnd::new( - State::Open, - Order::Ordered, - Counterparty::new(packet.port_id_on_b.clone(), Some(packet.chan_id_on_b)), - vec![ConnectionId::default()], - Version::new("ics20-1".to_string()), - ) - .unwrap(); - - let conn_end_on_a = ConnectionEnd::new( - ConnectionState::Open, - ClientId::default(), - ConnectionCounterparty::new( - ClientId::default(), - Some(ConnectionId::default()), - Default::default(), - ), - get_compatible_versions(), - ZERO_DURATION, - ) - .unwrap(); - - Fixture { - ctx, - module_id, - msg, - packet_commitment, - conn_end_on_a, - chan_end_on_a_unordered, - chan_end_on_a_ordered, - } - } - - #[rstest] - fn ack_unordered_chan_execute(fixture: Fixture) { - let Fixture { - ctx, - module_id, - msg, - packet_commitment, - conn_end_on_a, - chan_end_on_a_unordered, - .. - } = fixture; - let mut ctx = ctx - .with_channel( - PortId::default(), - ChannelId::default(), - chan_end_on_a_unordered, - ) - .with_connection(ConnectionId::default(), conn_end_on_a) - .with_packet_commitment( - msg.packet.port_id_on_a.clone(), - msg.packet.chan_id_on_a.clone(), - msg.packet.seq_on_a, - packet_commitment, - ); - - let res = acknowledgement_packet_execute(&mut ctx, module_id, msg); - - assert!(res.is_ok()); - - assert_eq!(ctx.events.len(), 2); - assert!(matches!( - ctx.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(ctx.events[1], IbcEvent::AcknowledgePacket(_))); - } - - #[rstest] - fn ack_ordered_chan_execute(fixture: Fixture) { - let Fixture { - ctx, - module_id, - msg, - packet_commitment, - conn_end_on_a, - chan_end_on_a_ordered, - .. - } = fixture; - let mut ctx = ctx - .with_channel( - PortId::default(), - ChannelId::default(), - chan_end_on_a_ordered, - ) - .with_connection(ConnectionId::default(), conn_end_on_a) - .with_packet_commitment( - msg.packet.port_id_on_a.clone(), - msg.packet.chan_id_on_a.clone(), - msg.packet.seq_on_a, - packet_commitment, - ); - - let res = acknowledgement_packet_execute(&mut ctx, module_id, msg); - - assert!(res.is_ok()); - - assert_eq!(ctx.events.len(), 2); - assert!(matches!( - ctx.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(ctx.events[1], IbcEvent::AcknowledgePacket(_))); - } -} diff --git a/crates/ibc/src/core/context/chan_close_confirm.rs b/crates/ibc/src/core/context/chan_close_confirm.rs deleted file mode 100644 index 9f9fd68cd..000000000 --- a/crates/ibc/src/core/context/chan_close_confirm.rs +++ /dev/null @@ -1,184 +0,0 @@ -use crate::{core::ics24_host::path::ChannelEndPath, prelude::*}; - -use crate::core::ics04_channel::channel::State; -use crate::core::ics04_channel::error::ChannelError; -use crate::core::ics04_channel::events::CloseConfirm; -use crate::core::ics04_channel::handler::chan_close_confirm; -use crate::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; -use crate::core::router::ModuleId; - -use crate::core::events::{IbcEvent, MessageEvent}; - -use super::{ContextError, ExecutionContext, ValidationContext}; - -pub(super) fn chan_close_confirm_validate( - ctx_b: &ValCtx, - module_id: ModuleId, - msg: MsgChannelCloseConfirm, -) -> Result<(), ContextError> -where - ValCtx: ValidationContext, -{ - chan_close_confirm::validate(ctx_b, &msg)?; - - let module = ctx_b - .get_route(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - module.on_chan_close_confirm_validate(&msg.port_id_on_b, &msg.chan_id_on_b)?; - - Ok(()) -} - -pub(super) fn chan_close_confirm_execute( - ctx_b: &mut ExecCtx, - module_id: ModuleId, - msg: MsgChannelCloseConfirm, -) -> Result<(), ContextError> -where - ExecCtx: ExecutionContext, -{ - let module = ctx_b - .get_route_mut(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - let extras = module.on_chan_close_confirm_execute(&msg.port_id_on_b, &msg.chan_id_on_b)?; - let chan_end_path_on_b = ChannelEndPath::new(&msg.port_id_on_b, &msg.chan_id_on_b); - let chan_end_on_b = ctx_b.channel_end(&chan_end_path_on_b)?; - - // state changes - { - let chan_end_on_b = { - let mut chan_end_on_b = chan_end_on_b.clone(); - chan_end_on_b.set_state(State::Closed); - chan_end_on_b - }; - ctx_b.store_channel(&chan_end_path_on_b, chan_end_on_b)?; - } - - // emit events and logs - { - ctx_b.log_message("success: channel close confirm".to_string()); - - let core_event = { - let port_id_on_a = chan_end_on_b.counterparty().port_id.clone(); - let chan_id_on_a = chan_end_on_b - .counterparty() - .channel_id - .clone() - .ok_or(ContextError::ChannelError(ChannelError::Other { - description: - "internal error: ChannelEnd doesn't have a counterparty channel id in CloseInit" - .to_string(), - }))?; - let conn_id_on_b = chan_end_on_b.connection_hops[0].clone(); - - IbcEvent::CloseConfirmChannel(CloseConfirm::new( - msg.port_id_on_b.clone(), - msg.chan_id_on_b.clone(), - port_id_on_a, - chan_id_on_a, - conn_id_on_b, - )) - }; - ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_b.emit_ibc_event(core_event); - - for module_event in extras.events { - ctx_b.emit_ibc_event(IbcEvent::Module(module_event)); - } - - for log_message in extras.log { - ctx_b.log_message(log_message); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::applications::transfer::MODULE_ID_STR; - use crate::core::context::chan_close_confirm::chan_close_confirm_execute; - use crate::core::events::{IbcEvent, MessageEvent}; - use crate::core::ics04_channel::msgs::chan_close_confirm::test_util::get_dummy_raw_msg_chan_close_confirm; - use crate::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; - use crate::core::router::ModuleId; - use crate::core::ValidationContext; - - use crate::core::ics03_connection::connection::ConnectionEnd; - use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; - use crate::core::ics03_connection::connection::State as ConnectionState; - use crate::core::ics03_connection::msgs::test_util::get_dummy_raw_counterparty; - use crate::core::ics03_connection::version::get_compatible_versions; - use crate::core::ics04_channel::channel::{ - ChannelEnd, Counterparty, Order, State as ChannelState, - }; - use crate::core::ics04_channel::Version; - use crate::core::ics24_host::identifier::{ClientId, ConnectionId}; - - use crate::core::timestamp::ZERO_DURATION; - use crate::mock::client_state::client_type as mock_client_type; - use crate::mock::context::MockContext; - use crate::test_utils::DummyTransferModule; - - #[test] - fn chan_close_confirm_event_height() { - let client_id = ClientId::new(mock_client_type(), 24).unwrap(); - let conn_id = ConnectionId::new(2); - let default_context = MockContext::default(); - let client_consensus_state_height = default_context.host_height().unwrap(); - - let conn_end = ConnectionEnd::new( - ConnectionState::Open, - client_id.clone(), - ConnectionCounterparty::try_from(get_dummy_raw_counterparty(Some(0))).unwrap(), - get_compatible_versions(), - ZERO_DURATION, - ) - .unwrap(); - - let msg_chan_close_confirm = MsgChannelCloseConfirm::try_from( - get_dummy_raw_msg_chan_close_confirm(client_consensus_state_height.revision_height()), - ) - .unwrap(); - - let chan_end = ChannelEnd::new( - ChannelState::Open, - Order::default(), - Counterparty::new( - msg_chan_close_confirm.port_id_on_b.clone(), - Some(msg_chan_close_confirm.chan_id_on_b.clone()), - ), - vec![conn_id.clone()], - Version::default(), - ) - .unwrap(); - - let mut context = default_context - .with_client(&client_id, client_consensus_state_height) - .with_connection(conn_id, conn_end) - .with_channel( - msg_chan_close_confirm.port_id_on_b.clone(), - msg_chan_close_confirm.chan_id_on_b.clone(), - chan_end, - ); - - let module = DummyTransferModule::new(); - let module_id = ModuleId::new(MODULE_ID_STR.to_string()); - context.add_route(module_id.clone(), module).unwrap(); - - let res = chan_close_confirm_execute(&mut context, module_id, msg_chan_close_confirm); - assert!(res.is_ok(), "Execution success: happy path"); - - assert_eq!(context.events.len(), 2); - assert!(matches!( - context.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!( - context.events[1], - IbcEvent::CloseConfirmChannel(_) - )); - } -} diff --git a/crates/ibc/src/core/context/chan_close_init.rs b/crates/ibc/src/core/context/chan_close_init.rs deleted file mode 100644 index 1dfa4b239..000000000 --- a/crates/ibc/src/core/context/chan_close_init.rs +++ /dev/null @@ -1,186 +0,0 @@ -use crate::core::ics04_channel::events::CloseInit; -use crate::core::ics04_channel::handler::chan_close_init; -use crate::core::ics24_host::path::ChannelEndPath; -use crate::{core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit, prelude::*}; - -use crate::core::ics04_channel::channel::State; -use crate::core::ics04_channel::error::ChannelError; -use crate::core::router::ModuleId; - -use crate::core::events::{IbcEvent, MessageEvent}; - -use super::{ContextError, ExecutionContext, ValidationContext}; -pub(super) fn chan_close_init_validate( - ctx_a: &ValCtx, - module_id: ModuleId, - msg: MsgChannelCloseInit, -) -> Result<(), ContextError> -where - ValCtx: ValidationContext, -{ - chan_close_init::validate(ctx_a, &msg)?; - - let module = ctx_a - .get_route(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - module.on_chan_close_init_validate(&msg.port_id_on_a, &msg.chan_id_on_a)?; - - Ok(()) -} - -pub(super) fn chan_close_init_execute( - ctx_a: &mut ExecCtx, - module_id: ModuleId, - msg: MsgChannelCloseInit, -) -> Result<(), ContextError> -where - ExecCtx: ExecutionContext, -{ - let module = ctx_a - .get_route_mut(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - let extras = module.on_chan_close_init_execute(&msg.port_id_on_a, &msg.chan_id_on_a)?; - let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &msg.chan_id_on_a); - let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?; - - // state changes - { - let chan_end_on_a = { - let mut chan_end_on_a = chan_end_on_a.clone(); - chan_end_on_a.set_state(State::Closed); - chan_end_on_a - }; - - ctx_a.store_channel(&chan_end_path_on_a, chan_end_on_a)?; - } - - // emit events and logs - { - ctx_a.log_message("success: channel close init".to_string()); - - let core_event = { - let port_id_on_b = chan_end_on_a.counterparty().port_id.clone(); - let chan_id_on_b = chan_end_on_a - .counterparty() - .channel_id - .clone() - .ok_or(ContextError::ChannelError(ChannelError::Other { - description: - "internal error: ChannelEnd doesn't have a counterparty channel id in CloseInit" - .to_string(), - }))?; - let conn_id_on_a = chan_end_on_a.connection_hops[0].clone(); - - IbcEvent::CloseInitChannel(CloseInit::new( - msg.port_id_on_a.clone(), - msg.chan_id_on_a.clone(), - port_id_on_b, - chan_id_on_b, - conn_id_on_a, - )) - }; - ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_a.emit_ibc_event(core_event); - - for module_event in extras.events { - ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); - } - - for log_message in extras.log { - ctx_a.log_message(log_message); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::applications::transfer::MODULE_ID_STR; - use crate::core::events::IbcEvent; - use crate::core::ics04_channel::msgs::chan_close_init::test_util::get_dummy_raw_msg_chan_close_init; - use crate::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; - use crate::core::router::ModuleId; - use crate::core::ValidationContext; - - use crate::core::ics03_connection::connection::ConnectionEnd; - use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; - use crate::core::ics03_connection::connection::State as ConnectionState; - use crate::core::ics03_connection::msgs::test_util::get_dummy_raw_counterparty; - use crate::core::ics03_connection::version::get_compatible_versions; - use crate::core::ics04_channel::channel::{ - ChannelEnd, Counterparty, Order, State as ChannelState, - }; - use crate::core::ics04_channel::Version; - use crate::core::ics24_host::identifier::{ClientId, ConnectionId}; - - use crate::core::timestamp::ZERO_DURATION; - use crate::mock::client_state::client_type as mock_client_type; - use crate::mock::context::MockContext; - use crate::test_utils::DummyTransferModule; - - use super::chan_close_init_execute; - #[test] - fn chan_close_init_event_height() { - let client_id = ClientId::new(mock_client_type(), 24).unwrap(); - let conn_id = ConnectionId::new(2); - - let conn_end = ConnectionEnd::new( - ConnectionState::Open, - client_id.clone(), - ConnectionCounterparty::try_from(get_dummy_raw_counterparty(Some(0))).unwrap(), - get_compatible_versions(), - ZERO_DURATION, - ) - .unwrap(); - - let msg_chan_close_init = - MsgChannelCloseInit::try_from(get_dummy_raw_msg_chan_close_init()).unwrap(); - - let chan_end = ChannelEnd::new( - ChannelState::Open, - Order::default(), - Counterparty::new( - msg_chan_close_init.port_id_on_a.clone(), - Some(msg_chan_close_init.chan_id_on_a.clone()), - ), - vec![conn_id.clone()], - Version::default(), - ) - .unwrap(); - - let mut context = { - let mut default_context = MockContext::default(); - let client_consensus_state_height = default_context.host_height().unwrap(); - - let module = DummyTransferModule::new(); - let module_id = ModuleId::new(MODULE_ID_STR.to_string()); - default_context.add_route(module_id, module).unwrap(); - - default_context - .with_client(&client_id, client_consensus_state_height) - .with_connection(conn_id, conn_end) - .with_channel( - msg_chan_close_init.port_id_on_a.clone(), - msg_chan_close_init.chan_id_on_a.clone(), - chan_end, - ) - }; - - let res = chan_close_init_execute( - &mut context, - ModuleId::new(MODULE_ID_STR.to_string()), - msg_chan_close_init, - ); - assert!(res.is_ok(), "Execution happy path"); - - assert_eq!(context.events.len(), 2); - assert!(matches!( - context.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(context.events[1], IbcEvent::CloseInitChannel(_))); - } -} diff --git a/crates/ibc/src/core/context/chan_open_ack.rs b/crates/ibc/src/core/context/chan_open_ack.rs deleted file mode 100644 index b3cf6267c..000000000 --- a/crates/ibc/src/core/context/chan_open_ack.rs +++ /dev/null @@ -1,215 +0,0 @@ -use crate::core::ics24_host::path::ChannelEndPath; -use crate::{core::ics04_channel::events::OpenAck, prelude::*}; - -use crate::core::ics04_channel::handler::chan_open_ack; -use crate::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; - -use crate::core::ics04_channel::channel::State; -use crate::core::ics04_channel::error::ChannelError; -use crate::core::router::ModuleId; - -use crate::core::events::{IbcEvent, MessageEvent}; - -use super::{ContextError, ExecutionContext, ValidationContext}; - -pub(super) fn chan_open_ack_validate( - ctx_a: &ValCtx, - module_id: ModuleId, - msg: MsgChannelOpenAck, -) -> Result<(), ContextError> -where - ValCtx: ValidationContext, -{ - chan_open_ack::validate(ctx_a, &msg)?; - - let module = ctx_a - .get_route(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - module.on_chan_open_ack_validate(&msg.port_id_on_a, &msg.chan_id_on_a, &msg.version_on_b)?; - - Ok(()) -} - -pub(super) fn chan_open_ack_execute( - ctx_a: &mut ExecCtx, - module_id: ModuleId, - msg: MsgChannelOpenAck, -) -> Result<(), ContextError> -where - ExecCtx: ExecutionContext, -{ - let module = ctx_a - .get_route_mut(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - let extras = - module.on_chan_open_ack_execute(&msg.port_id_on_a, &msg.chan_id_on_a, &msg.version_on_b)?; - let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &msg.chan_id_on_a); - let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?; - - // state changes - { - let chan_end_on_a = { - let mut chan_end_on_a = chan_end_on_a.clone(); - - chan_end_on_a.set_state(State::Open); - chan_end_on_a.set_version(msg.version_on_b.clone()); - chan_end_on_a.set_counterparty_channel_id(msg.chan_id_on_b.clone()); - - chan_end_on_a - }; - ctx_a.store_channel(&chan_end_path_on_a, chan_end_on_a)?; - } - - // emit events and logs - { - ctx_a.log_message("success: channel open ack".to_string()); - - let core_event = { - let port_id_on_b = chan_end_on_a.counterparty().port_id.clone(); - let conn_id_on_a = chan_end_on_a.connection_hops[0].clone(); - - IbcEvent::OpenAckChannel(OpenAck::new( - msg.port_id_on_a.clone(), - msg.chan_id_on_a.clone(), - port_id_on_b, - msg.chan_id_on_b, - conn_id_on_a, - )) - }; - ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_a.emit_ibc_event(core_event); - - for module_event in extras.events { - ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); - } - - for log_message in extras.log { - ctx_a.log_message(log_message); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - core::{context::chan_open_ack::chan_open_ack_execute, timestamp::ZERO_DURATION}, - Height, - }; - use rstest::*; - - use crate::{ - applications::transfer::MODULE_ID_STR, - core::{ - ics03_connection::{ - connection::ConnectionEnd, msgs::test_util::get_dummy_raw_counterparty, - version::get_compatible_versions, - }, - ics04_channel::{ - channel::{ChannelEnd, Counterparty, Order, State}, - msgs::chan_open_ack::{ - test_util::get_dummy_raw_msg_chan_open_ack, MsgChannelOpenAck, - }, - }, - ics24_host::identifier::{ClientId, ConnectionId}, - router::ModuleId, - }, - mock::context::MockContext, - test_utils::DummyTransferModule, - }; - - use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; - use crate::core::ics03_connection::connection::State as ConnectionState; - use crate::mock::client_state::client_type as mock_client_type; - - pub struct Fixture { - pub context: MockContext, - pub module_id: ModuleId, - pub msg: MsgChannelOpenAck, - pub client_id_on_a: ClientId, - pub conn_id_on_a: ConnectionId, - pub conn_end_on_a: ConnectionEnd, - pub chan_end_on_a: ChannelEnd, - pub proof_height: u64, - } - - #[fixture] - fn fixture() -> Fixture { - let proof_height = 10; - let mut context = MockContext::default(); - let module = DummyTransferModule::new(); - let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); - context.add_route(module_id.clone(), module).unwrap(); - - let client_id_on_a = ClientId::new(mock_client_type(), 45).unwrap(); - let conn_id_on_a = ConnectionId::new(2); - let conn_end_on_a = ConnectionEnd::new( - ConnectionState::Open, - client_id_on_a.clone(), - ConnectionCounterparty::try_from(get_dummy_raw_counterparty(Some(0))).unwrap(), - get_compatible_versions(), - ZERO_DURATION, - ) - .unwrap(); - - let msg = - MsgChannelOpenAck::try_from(get_dummy_raw_msg_chan_open_ack(proof_height)).unwrap(); - - let chan_end_on_a = ChannelEnd::new( - State::Init, - Order::Unordered, - Counterparty::new(msg.port_id_on_a.clone(), Some(msg.chan_id_on_b.clone())), - vec![conn_id_on_a.clone()], - msg.version_on_b.clone(), - ) - .unwrap(); - - Fixture { - context, - module_id, - msg, - client_id_on_a, - conn_id_on_a, - conn_end_on_a, - chan_end_on_a, - proof_height, - } - } - - #[rstest] - fn chan_open_ack_execute_happy_path(fixture: Fixture) { - let Fixture { - context, - module_id, - msg, - client_id_on_a, - conn_id_on_a, - conn_end_on_a, - chan_end_on_a, - proof_height, - .. - } = fixture; - - let mut context = context - .with_client(&client_id_on_a, Height::new(0, proof_height).unwrap()) - .with_connection(conn_id_on_a, conn_end_on_a) - .with_channel( - msg.port_id_on_a.clone(), - msg.chan_id_on_a.clone(), - chan_end_on_a, - ); - - let res = chan_open_ack_execute(&mut context, module_id, msg); - - assert!(res.is_ok(), "Execution happy path"); - - assert_eq!(context.events.len(), 2); - assert!(matches!( - context.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(context.events[1], IbcEvent::OpenAckChannel(_))); - } -} diff --git a/crates/ibc/src/core/context/chan_open_confirm.rs b/crates/ibc/src/core/context/chan_open_confirm.rs deleted file mode 100644 index 5cb6e2a03..000000000 --- a/crates/ibc/src/core/context/chan_open_confirm.rs +++ /dev/null @@ -1,222 +0,0 @@ -use crate::core::ics04_channel::events::OpenConfirm; -use crate::core::ics04_channel::handler::chan_open_confirm; -use crate::core::ics24_host::path::ChannelEndPath; -use crate::{core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm, prelude::*}; - -use crate::core::ics04_channel::channel::State; -use crate::core::ics04_channel::error::ChannelError; -use crate::core::router::ModuleId; - -use crate::core::events::{IbcEvent, MessageEvent}; - -use super::{ContextError, ExecutionContext, ValidationContext}; - -pub(super) fn chan_open_confirm_validate( - ctx_b: &ValCtx, - module_id: ModuleId, - msg: MsgChannelOpenConfirm, -) -> Result<(), ContextError> -where - ValCtx: ValidationContext, -{ - chan_open_confirm::validate(ctx_b, &msg)?; - - let module = ctx_b - .get_route(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - module.on_chan_open_confirm_validate(&msg.port_id_on_b, &msg.chan_id_on_b)?; - - Ok(()) -} - -pub(super) fn chan_open_confirm_execute( - ctx_b: &mut ExecCtx, - module_id: ModuleId, - msg: MsgChannelOpenConfirm, -) -> Result<(), ContextError> -where - ExecCtx: ExecutionContext, -{ - let module = ctx_b - .get_route_mut(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - - let extras = module.on_chan_open_confirm_execute(&msg.port_id_on_b, &msg.chan_id_on_b)?; - let chan_end_path_on_b = ChannelEndPath::new(&msg.port_id_on_b, &msg.chan_id_on_b); - let chan_end_on_b = ctx_b.channel_end(&chan_end_path_on_b)?; - - // state changes - { - let chan_end_on_b = { - let mut chan_end_on_b = chan_end_on_b.clone(); - chan_end_on_b.set_state(State::Open); - - chan_end_on_b - }; - ctx_b.store_channel(&chan_end_path_on_b, chan_end_on_b)?; - } - - // emit events and logs - { - ctx_b.log_message("success: channel open confirm".to_string()); - - let conn_id_on_b = chan_end_on_b.connection_hops[0].clone(); - let port_id_on_a = chan_end_on_b.counterparty().port_id.clone(); - let chan_id_on_a = chan_end_on_b - .counterparty() - .channel_id - .clone() - .ok_or(ContextError::ChannelError(ChannelError::Other { - description: - "internal error: ChannelEnd doesn't have a counterparty channel id in OpenConfirm" - .to_string(), - }))?; - - let core_event = IbcEvent::OpenConfirmChannel(OpenConfirm::new( - msg.port_id_on_b.clone(), - msg.chan_id_on_b.clone(), - port_id_on_a, - chan_id_on_a, - conn_id_on_b, - )); - ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_b.emit_ibc_event(core_event); - - for module_event in extras.events { - ctx_b.emit_ibc_event(IbcEvent::Module(module_event)); - } - - for log_message in extras.log { - ctx_b.log_message(log_message); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - core::{ - context::chan_open_confirm::chan_open_confirm_execute, ics04_channel::Version, - timestamp::ZERO_DURATION, - }, - Height, - }; - use rstest::*; - - use crate::{ - applications::transfer::MODULE_ID_STR, - core::{ - ics03_connection::{ - connection::ConnectionEnd, msgs::test_util::get_dummy_raw_counterparty, - version::get_compatible_versions, - }, - ics04_channel::{ - channel::{ChannelEnd, Counterparty, Order, State}, - msgs::chan_open_confirm::{ - test_util::get_dummy_raw_msg_chan_open_confirm, MsgChannelOpenConfirm, - }, - }, - ics24_host::identifier::{ChannelId, ClientId, ConnectionId}, - router::ModuleId, - }, - mock::context::MockContext, - test_utils::DummyTransferModule, - }; - - use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; - use crate::core::ics03_connection::connection::State as ConnectionState; - use crate::mock::client_state::client_type as mock_client_type; - - pub struct Fixture { - pub context: MockContext, - pub module_id: ModuleId, - pub msg: MsgChannelOpenConfirm, - pub client_id_on_b: ClientId, - pub conn_id_on_b: ConnectionId, - pub conn_end_on_b: ConnectionEnd, - pub chan_end_on_b: ChannelEnd, - pub proof_height: u64, - } - - #[fixture] - fn fixture() -> Fixture { - let proof_height = 10; - let mut context = MockContext::default(); - let module = DummyTransferModule::new(); - let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); - context.add_route(module_id.clone(), module).unwrap(); - - let client_id_on_b = ClientId::new(mock_client_type(), 45).unwrap(); - let conn_id_on_b = ConnectionId::new(2); - let conn_end_on_b = ConnectionEnd::new( - ConnectionState::Open, - client_id_on_b.clone(), - ConnectionCounterparty::try_from(get_dummy_raw_counterparty(Some(0))).unwrap(), - get_compatible_versions(), - ZERO_DURATION, - ) - .unwrap(); - - let msg = - MsgChannelOpenConfirm::try_from(get_dummy_raw_msg_chan_open_confirm(proof_height)) - .unwrap(); - - let chan_end_on_b = ChannelEnd::new( - State::TryOpen, - Order::Unordered, - Counterparty::new(msg.port_id_on_b.clone(), Some(ChannelId::default())), - vec![conn_id_on_b.clone()], - Version::default(), - ) - .unwrap(); - - Fixture { - context, - module_id, - msg, - client_id_on_b, - conn_id_on_b, - conn_end_on_b, - chan_end_on_b, - proof_height, - } - } - - #[rstest] - fn chan_open_confirm_execute_happy_path(fixture: Fixture) { - let Fixture { - context, - module_id, - msg, - client_id_on_b, - conn_id_on_b, - conn_end_on_b, - chan_end_on_b, - proof_height, - .. - } = fixture; - - let mut context = context - .with_client(&client_id_on_b, Height::new(0, proof_height).unwrap()) - .with_connection(conn_id_on_b, conn_end_on_b) - .with_channel( - msg.port_id_on_b.clone(), - ChannelId::default(), - chan_end_on_b, - ); - - let res = chan_open_confirm_execute(&mut context, module_id, msg); - - assert!(res.is_ok(), "Execution happy path"); - - assert_eq!(context.events.len(), 2); - assert!(matches!( - context.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(context.events[1], IbcEvent::OpenConfirmChannel(_))); - } -} diff --git a/crates/ibc/src/core/context/chan_open_init.rs b/crates/ibc/src/core/context/chan_open_init.rs deleted file mode 100644 index 813fd95d3..000000000 --- a/crates/ibc/src/core/context/chan_open_init.rs +++ /dev/null @@ -1,198 +0,0 @@ -use crate::core::ics04_channel::events::OpenInit; -use crate::core::ics04_channel::handler::chan_open_init; -use crate::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; -use crate::core::ics24_host::path::{ChannelEndPath, SeqAckPath, SeqRecvPath, SeqSendPath}; -use crate::prelude::*; - -use crate::core::ics24_host::identifier::ChannelId; - -use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State}; -use crate::core::ics04_channel::error::ChannelError; -use crate::core::router::ModuleId; - -use crate::core::events::{IbcEvent, MessageEvent}; - -use super::{ContextError, ExecutionContext, ValidationContext}; -pub(super) fn chan_open_init_validate( - ctx_a: &ValCtx, - module_id: ModuleId, - msg: MsgChannelOpenInit, -) -> Result<(), ContextError> -where - ValCtx: ValidationContext, -{ - chan_open_init::validate(ctx_a, &msg)?; - let chan_id_on_a = ChannelId::new(ctx_a.channel_counter()?); - - let module = ctx_a - .get_route(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - module.on_chan_open_init_validate( - msg.ordering, - &msg.connection_hops_on_a, - &msg.port_id_on_a, - &chan_id_on_a, - &Counterparty::new(msg.port_id_on_b.clone(), None), - &msg.version_proposal, - )?; - - Ok(()) -} - -pub(super) fn chan_open_init_execute( - ctx_a: &mut ExecCtx, - module_id: ModuleId, - msg: MsgChannelOpenInit, -) -> Result<(), ContextError> -where - ExecCtx: ExecutionContext, -{ - let chan_id_on_a = ChannelId::new(ctx_a.channel_counter()?); - let module = ctx_a - .get_route_mut(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - let (extras, version) = module.on_chan_open_init_execute( - msg.ordering, - &msg.connection_hops_on_a, - &msg.port_id_on_a, - &chan_id_on_a, - &Counterparty::new(msg.port_id_on_b.clone(), None), - &msg.version_proposal, - )?; - - let conn_id_on_a = msg.connection_hops_on_a[0].clone(); - - // state changes - { - let chan_end_on_a = ChannelEnd::new( - State::Init, - msg.ordering, - Counterparty::new(msg.port_id_on_b.clone(), None), - msg.connection_hops_on_a.clone(), - msg.version_proposal.clone(), - )?; - let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &chan_id_on_a); - ctx_a.store_channel(&chan_end_path_on_a, chan_end_on_a)?; - - ctx_a.increase_channel_counter(); - - // Initialize send, recv, and ack sequence numbers. - let seq_send_path = SeqSendPath::new(&msg.port_id_on_a, &chan_id_on_a); - ctx_a.store_next_sequence_send(&seq_send_path, 1.into())?; - - let seq_recv_path = SeqRecvPath::new(&msg.port_id_on_a, &chan_id_on_a); - ctx_a.store_next_sequence_recv(&seq_recv_path, 1.into())?; - - let seq_ack_path = SeqAckPath::new(&msg.port_id_on_a, &chan_id_on_a); - ctx_a.store_next_sequence_ack(&seq_ack_path, 1.into())?; - } - - // emit events and logs - { - ctx_a.log_message(format!( - "success: channel open init with channel identifier: {chan_id_on_a}" - )); - let core_event = IbcEvent::OpenInitChannel(OpenInit::new( - msg.port_id_on_a.clone(), - chan_id_on_a.clone(), - msg.port_id_on_b, - conn_id_on_a, - version, - )); - ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_a.emit_ibc_event(core_event); - - for module_event in extras.events { - ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); - } - - for log_message in extras.log { - ctx_a.log_message(log_message); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use crate::applications::transfer::MODULE_ID_STR; - use crate::core::ics03_connection::connection::State as ConnectionState; - use crate::core::ics24_host::identifier::ConnectionId; - use crate::test_utils::DummyTransferModule; - use crate::{ - core::{ - ics03_connection::{ - connection::ConnectionEnd, - msgs::conn_open_init::{ - test_util::get_dummy_raw_msg_conn_open_init, MsgConnectionOpenInit, - }, - version::get_compatible_versions, - }, - ics04_channel::msgs::chan_open_init::test_util::get_dummy_raw_msg_chan_open_init, - }, - mock::context::MockContext, - }; - - use super::*; - use rstest::*; - - pub struct Fixture { - pub context: MockContext, - pub module_id: ModuleId, - pub msg: MsgChannelOpenInit, - pub conn_end_on_a: ConnectionEnd, - } - - #[fixture] - fn fixture() -> Fixture { - let msg = MsgChannelOpenInit::try_from(get_dummy_raw_msg_chan_open_init(None)).unwrap(); - - let mut context = MockContext::default(); - let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); - let module = DummyTransferModule::new(); - context.add_route(module_id.clone(), module).unwrap(); - - let msg_conn_init = - MsgConnectionOpenInit::try_from(get_dummy_raw_msg_conn_open_init()).unwrap(); - - let conn_end_on_a = ConnectionEnd::new( - ConnectionState::Init, - msg_conn_init.client_id_on_a.clone(), - msg_conn_init.counterparty.clone(), - get_compatible_versions(), - msg_conn_init.delay_period, - ) - .unwrap(); - - Fixture { - context, - module_id, - msg, - conn_end_on_a, - } - } - - #[rstest] - fn chan_open_init_execute_events(fixture: Fixture) { - let Fixture { - context, - module_id, - msg, - conn_end_on_a, - } = fixture; - - let mut context = context.with_connection(ConnectionId::default(), conn_end_on_a); - - let res = chan_open_init_execute(&mut context, module_id, msg); - - assert!(res.is_ok(), "Execution succeeds; good parameters"); - - assert_eq!(context.events.len(), 2); - assert!(matches!( - context.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(context.events[1], IbcEvent::OpenInitChannel(_))); - } -} diff --git a/crates/ibc/src/core/context/chan_open_try.rs b/crates/ibc/src/core/context/chan_open_try.rs deleted file mode 100644 index 4b05ad4e6..000000000 --- a/crates/ibc/src/core/context/chan_open_try.rs +++ /dev/null @@ -1,229 +0,0 @@ -use crate::core::ics04_channel::events::OpenTry; -use crate::core::ics04_channel::handler::chan_open_try; -use crate::core::ics24_host::identifier::ChannelId; -use crate::core::ics24_host::path::{ChannelEndPath, SeqAckPath, SeqRecvPath, SeqSendPath}; -use crate::{core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry, prelude::*}; - -use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State}; -use crate::core::ics04_channel::error::ChannelError; -use crate::core::router::ModuleId; - -use crate::core::events::{IbcEvent, MessageEvent}; - -use super::{ContextError, ExecutionContext, ValidationContext}; - -pub(super) fn chan_open_try_validate( - ctx_b: &ValCtx, - module_id: ModuleId, - msg: MsgChannelOpenTry, -) -> Result<(), ContextError> -where - ValCtx: ValidationContext, -{ - chan_open_try::validate(ctx_b, &msg)?; - let chan_id_on_b = ChannelId::new(ctx_b.channel_counter()?); - - let module = ctx_b - .get_route(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - module.on_chan_open_try_validate( - msg.ordering, - &msg.connection_hops_on_b, - &msg.port_id_on_b, - &chan_id_on_b, - &Counterparty::new(msg.port_id_on_a.clone(), Some(msg.chan_id_on_a.clone())), - &msg.version_supported_on_a, - )?; - - Ok(()) -} - -pub(super) fn chan_open_try_execute( - ctx_b: &mut ExecCtx, - module_id: ModuleId, - msg: MsgChannelOpenTry, -) -> Result<(), ContextError> -where - ExecCtx: ExecutionContext, -{ - let chan_id_on_b = ChannelId::new(ctx_b.channel_counter()?); - let module = ctx_b - .get_route_mut(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - - let (extras, version) = module.on_chan_open_try_execute( - msg.ordering, - &msg.connection_hops_on_b, - &msg.port_id_on_b, - &chan_id_on_b, - &Counterparty::new(msg.port_id_on_a.clone(), Some(msg.chan_id_on_a.clone())), - &msg.version_supported_on_a, - )?; - - let conn_id_on_b = msg.connection_hops_on_b[0].clone(); - - // state changes - { - let chan_end_on_b = ChannelEnd::new( - State::TryOpen, - msg.ordering, - Counterparty::new(msg.port_id_on_a.clone(), Some(msg.chan_id_on_a.clone())), - msg.connection_hops_on_b.clone(), - version.clone(), - )?; - - let chan_end_path_on_b = ChannelEndPath::new(&msg.port_id_on_b, &chan_id_on_b); - ctx_b.store_channel(&chan_end_path_on_b, chan_end_on_b)?; - ctx_b.increase_channel_counter(); - - // Initialize send, recv, and ack sequence numbers. - let seq_send_path = SeqSendPath::new(&msg.port_id_on_b, &chan_id_on_b); - ctx_b.store_next_sequence_send(&seq_send_path, 1.into())?; - - let seq_recv_path = SeqRecvPath::new(&msg.port_id_on_b, &chan_id_on_b); - ctx_b.store_next_sequence_recv(&seq_recv_path, 1.into())?; - - let seq_ack_path = SeqAckPath::new(&msg.port_id_on_b, &chan_id_on_b); - ctx_b.store_next_sequence_ack(&seq_ack_path, 1.into())?; - } - - // emit events and logs - { - ctx_b.log_message(format!( - "success: channel open try with channel identifier: {chan_id_on_b}" - )); - - let core_event = IbcEvent::OpenTryChannel(OpenTry::new( - msg.port_id_on_b.clone(), - chan_id_on_b.clone(), - msg.port_id_on_a.clone(), - msg.chan_id_on_a.clone(), - conn_id_on_b, - version, - )); - ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_b.emit_ibc_event(core_event); - - for module_event in extras.events { - ctx_b.emit_ibc_event(IbcEvent::Module(module_event)); - } - - for log_message in extras.log { - ctx_b.log_message(log_message); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - applications::transfer::MODULE_ID_STR, - core::{ - context::chan_open_try::chan_open_try_execute, router::ModuleId, - timestamp::ZERO_DURATION, - }, - test_utils::DummyTransferModule, - Height, - }; - use rstest::*; - - use crate::{ - core::{ - ics03_connection::{ - connection::ConnectionEnd, msgs::test_util::get_dummy_raw_counterparty, - version::get_compatible_versions, - }, - ics04_channel::msgs::chan_open_try::{ - test_util::get_dummy_raw_msg_chan_open_try, MsgChannelOpenTry, - }, - ics24_host::identifier::{ClientId, ConnectionId}, - }, - mock::context::MockContext, - }; - - use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; - use crate::core::ics03_connection::connection::State as ConnectionState; - use crate::mock::client_state::client_type as mock_client_type; - - pub struct Fixture { - pub context: MockContext, - pub module_id: ModuleId, - pub msg: MsgChannelOpenTry, - pub client_id_on_b: ClientId, - pub conn_id_on_b: ConnectionId, - pub conn_end_on_b: ConnectionEnd, - pub proof_height: u64, - } - - #[fixture] - fn fixture() -> Fixture { - let proof_height = 10; - let conn_id_on_b = ConnectionId::new(2); - let client_id_on_b = ClientId::new(mock_client_type(), 45).unwrap(); - - // This is the connection underlying the channel we're trying to open. - let conn_end_on_b = ConnectionEnd::new( - ConnectionState::Open, - client_id_on_b.clone(), - ConnectionCounterparty::try_from(get_dummy_raw_counterparty(Some(0))).unwrap(), - get_compatible_versions(), - ZERO_DURATION, - ) - .unwrap(); - - // We're going to test message processing against this message. - // Note: we make the counterparty's channel_id `None`. - let mut msg = - MsgChannelOpenTry::try_from(get_dummy_raw_msg_chan_open_try(proof_height)).unwrap(); - - let hops = vec![conn_id_on_b.clone()]; - msg.connection_hops_on_b = hops; - - let mut context = MockContext::default(); - let module = DummyTransferModule::new(); - let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); - context.add_route(module_id.clone(), module).unwrap(); - - Fixture { - context, - module_id, - msg, - client_id_on_b, - conn_id_on_b, - conn_end_on_b, - proof_height, - } - } - - #[rstest] - fn chan_open_try_execute_events(fixture: Fixture) { - let Fixture { - context, - module_id, - msg, - client_id_on_b, - conn_id_on_b, - conn_end_on_b, - proof_height, - .. - } = fixture; - - let mut context = context - .with_client(&client_id_on_b, Height::new(0, proof_height).unwrap()) - .with_connection(conn_id_on_b, conn_end_on_b); - - let res = chan_open_try_execute(&mut context, module_id, msg); - - assert!(res.is_ok(), "Execution success: happy path"); - - assert_eq!(context.events.len(), 2); - assert!(matches!( - context.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(context.events[1], IbcEvent::OpenTryChannel(_))); - } -} diff --git a/crates/ibc/src/core/context/recv_packet.rs b/crates/ibc/src/core/context/recv_packet.rs deleted file mode 100644 index 9aa92ae5b..000000000 --- a/crates/ibc/src/core/context/recv_packet.rs +++ /dev/null @@ -1,274 +0,0 @@ -use crate::{ - core::{ - events::{IbcEvent, MessageEvent}, - ics04_channel::{ - channel::Order, - commitment::compute_ack_commitment, - error::ChannelError, - events::{ReceivePacket, WriteAcknowledgement}, - handler::recv_packet, - msgs::recv_packet::MsgRecvPacket, - packet::Receipt, - }, - ics24_host::path::{AckPath, ChannelEndPath, ReceiptPath, SeqRecvPath}, - router::ModuleId, - }, - prelude::*, -}; - -use super::{ContextError, ExecutionContext, ValidationContext}; - -pub(super) fn recv_packet_validate( - ctx_b: &ValCtx, - msg: MsgRecvPacket, -) -> Result<(), ContextError> -where - ValCtx: ValidationContext, -{ - // Note: this contains the validation for `write_acknowledgement` as well. - recv_packet::validate(ctx_b, &msg) - - // nothing to validate with the module, since `onRecvPacket` cannot fail. - // If any error occurs, then an "error acknowledgement" must be returned. -} - -pub(super) fn recv_packet_execute( - ctx_b: &mut ExecCtx, - module_id: ModuleId, - msg: MsgRecvPacket, -) -> Result<(), ContextError> -where - ExecCtx: ExecutionContext, -{ - let chan_end_path_on_b = - ChannelEndPath::new(&msg.packet.port_id_on_b, &msg.packet.chan_id_on_b); - let chan_end_on_b = ctx_b.channel_end(&chan_end_path_on_b)?; - - // Check if another relayer already relayed the packet. - // We don't want to fail the transaction in this case. - { - let packet_already_received = match chan_end_on_b.ordering { - // Note: ibc-go doesn't make the check for `Order::None` channels - Order::None => false, - Order::Unordered => { - let packet = msg.packet.clone(); - let receipt_path_on_b = - ReceiptPath::new(&packet.port_id_on_b, &packet.chan_id_on_b, packet.seq_on_a); - ctx_b.get_packet_receipt(&receipt_path_on_b).is_ok() - } - Order::Ordered => { - let seq_recv_path_on_b = - SeqRecvPath::new(&msg.packet.port_id_on_b, &msg.packet.chan_id_on_b); - let next_seq_recv = ctx_b.get_next_sequence_recv(&seq_recv_path_on_b)?; - - // the sequence number has already been incremented, so - // another relayer already relayed the packet - msg.packet.seq_on_a < next_seq_recv - } - }; - - if packet_already_received { - return Ok(()); - } - } - - let module = ctx_b - .get_route_mut(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - - let (extras, acknowledgement) = module.on_recv_packet_execute(&msg.packet, &msg.signer); - - // state changes - { - // `recvPacket` core handler state changes - match chan_end_on_b.ordering { - Order::Unordered => { - let receipt_path_on_b = ReceiptPath { - port_id: msg.packet.port_id_on_b.clone(), - channel_id: msg.packet.chan_id_on_b.clone(), - sequence: msg.packet.seq_on_a, - }; - - ctx_b.store_packet_receipt(&receipt_path_on_b, Receipt::Ok)?; - } - Order::Ordered => { - let seq_recv_path_on_b = - SeqRecvPath::new(&msg.packet.port_id_on_b, &msg.packet.chan_id_on_b); - let next_seq_recv = ctx_b.get_next_sequence_recv(&seq_recv_path_on_b)?; - ctx_b.store_next_sequence_recv(&seq_recv_path_on_b, next_seq_recv.increment())?; - } - _ => {} - } - let ack_path_on_b = AckPath::new( - &msg.packet.port_id_on_b, - &msg.packet.chan_id_on_b, - msg.packet.seq_on_a, - ); - // `writeAcknowledgement` handler state changes - ctx_b.store_packet_acknowledgement( - &ack_path_on_b, - compute_ack_commitment(&acknowledgement), - )?; - } - - // emit events and logs - { - ctx_b.log_message("success: packet receive".to_string()); - ctx_b.log_message("success: packet write acknowledgement".to_string()); - - let conn_id_on_b = &chan_end_on_b.connection_hops()[0]; - let event = IbcEvent::ReceivePacket(ReceivePacket::new( - msg.packet.clone(), - chan_end_on_b.ordering, - conn_id_on_b.clone(), - )); - ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_b.emit_ibc_event(event); - let event = IbcEvent::WriteAcknowledgement(WriteAcknowledgement::new( - msg.packet, - acknowledgement, - conn_id_on_b.clone(), - )); - ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_b.emit_ibc_event(event); - - for module_event in extras.events { - ctx_b.emit_ibc_event(IbcEvent::Module(module_event)); - } - - for log_message in extras.log { - ctx_b.log_message(log_message); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::{ - applications::transfer::MODULE_ID_STR, - core::{ - context::recv_packet::recv_packet_execute, - events::IbcEvent, - ics03_connection::version::get_compatible_versions, - ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}, - router::ModuleId, - timestamp::ZERO_DURATION, - }, - test_utils::DummyTransferModule, - }; - use rstest::*; - - use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; - use crate::core::ics03_connection::connection::State as ConnectionState; - use crate::{ - core::{ - ics03_connection::connection::ConnectionEnd, - ics04_channel::{ - channel::{ChannelEnd, Counterparty, Order, State}, - msgs::recv_packet::{test_util::get_dummy_raw_msg_recv_packet, MsgRecvPacket}, - Version, - }, - }, - mock::{context::MockContext, ics18_relayer::context::RelayerContext}, - Height, - }; - - pub struct Fixture { - pub context: MockContext, - pub module_id: ModuleId, - pub client_height: Height, - pub host_height: Height, - pub msg: MsgRecvPacket, - pub conn_end_on_b: ConnectionEnd, - pub chan_end_on_b: ChannelEnd, - } - - #[fixture] - fn fixture() -> Fixture { - let mut context = MockContext::default(); - - let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); - let module = DummyTransferModule::new(); - context.add_route(module_id.clone(), module).unwrap(); - - let host_height = context.query_latest_height().unwrap().increment(); - - let client_height = host_height.increment(); - - let msg = MsgRecvPacket::try_from(get_dummy_raw_msg_recv_packet( - client_height.revision_height(), - )) - .unwrap(); - - let packet = msg.packet.clone(); - - let chan_end_on_b = ChannelEnd::new( - State::Open, - Order::default(), - Counterparty::new(packet.port_id_on_a, Some(packet.chan_id_on_a)), - vec![ConnectionId::default()], - Version::new("ics20-1".to_string()), - ) - .unwrap(); - - let conn_end_on_b = ConnectionEnd::new( - ConnectionState::Open, - ClientId::default(), - ConnectionCounterparty::new( - ClientId::default(), - Some(ConnectionId::default()), - Default::default(), - ), - get_compatible_versions(), - ZERO_DURATION, - ) - .unwrap(); - - Fixture { - context, - module_id, - client_height, - host_height, - msg, - conn_end_on_b, - chan_end_on_b, - } - } - - #[rstest] - fn recv_packet_execute_test(fixture: Fixture) { - let Fixture { - context, - module_id, - msg, - conn_end_on_b, - chan_end_on_b, - client_height, - .. - } = fixture; - let mut ctx = context - .with_client(&ClientId::default(), client_height) - .with_connection(ConnectionId::default(), conn_end_on_b) - .with_channel(PortId::default(), ChannelId::default(), chan_end_on_b); - - let res = recv_packet_execute(&mut ctx, module_id, msg); - - assert!(res.is_ok()); - - assert_eq!(ctx.events.len(), 4); - assert!(matches!( - &ctx.events[0], - &IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(&ctx.events[1], &IbcEvent::ReceivePacket(_))); - assert!(matches!( - &ctx.events[2], - &IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(&ctx.events[3], &IbcEvent::WriteAcknowledgement(_))); - } -} diff --git a/crates/ibc/src/core/context/timeout.rs b/crates/ibc/src/core/context/timeout.rs deleted file mode 100644 index 08d29973f..000000000 --- a/crates/ibc/src/core/context/timeout.rs +++ /dev/null @@ -1,335 +0,0 @@ -use crate::core::events::MessageEvent; -use crate::core::ics04_channel::events::ChannelClosed; -use crate::core::ics04_channel::msgs::timeout::MsgTimeout; -use crate::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; -use crate::core::ics24_host::path::{ChannelEndPath, CommitmentPath}; -use crate::prelude::*; - -use crate::core::{ - events::IbcEvent, - ics04_channel::{ - channel::{Order, State}, - error::ChannelError, - events::TimeoutPacket, - handler::{timeout, timeout_on_close}, - }, - router::ModuleId, -}; - -use super::{ContextError, ExecutionContext, ValidationContext}; - -pub(super) enum TimeoutMsgType { - Timeout(MsgTimeout), - TimeoutOnClose(MsgTimeoutOnClose), -} - -pub(super) fn timeout_packet_validate( - ctx_a: &ValCtx, - module_id: ModuleId, - timeout_msg_type: TimeoutMsgType, -) -> Result<(), ContextError> -where - ValCtx: ValidationContext, -{ - match &timeout_msg_type { - TimeoutMsgType::Timeout(msg) => timeout::validate(ctx_a, msg), - TimeoutMsgType::TimeoutOnClose(msg) => timeout_on_close::validate(ctx_a, msg), - }?; - - let module = ctx_a - .get_route(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - - let (packet, signer) = match timeout_msg_type { - TimeoutMsgType::Timeout(msg) => (msg.packet, msg.signer), - TimeoutMsgType::TimeoutOnClose(msg) => (msg.packet, msg.signer), - }; - - module - .on_timeout_packet_validate(&packet, &signer) - .map_err(ContextError::PacketError) -} - -pub(super) fn timeout_packet_execute( - ctx_a: &mut ExecCtx, - module_id: ModuleId, - timeout_msg_type: TimeoutMsgType, -) -> Result<(), ContextError> -where - ExecCtx: ExecutionContext, -{ - let (packet, signer) = match timeout_msg_type { - TimeoutMsgType::Timeout(msg) => (msg.packet, msg.signer), - TimeoutMsgType::TimeoutOnClose(msg) => (msg.packet, msg.signer), - }; - let chan_end_path_on_a = ChannelEndPath::new(&packet.port_id_on_a, &packet.chan_id_on_a); - let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?; - - // In all cases, this event is emitted - let event = IbcEvent::TimeoutPacket(TimeoutPacket::new(packet.clone(), chan_end_on_a.ordering)); - ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_a.emit_ibc_event(event); - - let commitment_path_on_a = - CommitmentPath::new(&packet.port_id_on_a, &packet.chan_id_on_a, packet.seq_on_a); - - // check if we're in the NO-OP case - if ctx_a.get_packet_commitment(&commitment_path_on_a).is_err() { - // This error indicates that the timeout has already been relayed - // or there is a misconfigured relayer attempting to prove a timeout - // for a packet never sent. Core IBC will treat this error as a no-op in order to - // prevent an entire relay transaction from failing and consuming unnecessary fees. - return Ok(()); - }; - - let module = ctx_a - .get_route_mut(&module_id) - .ok_or(ChannelError::RouteNotFound)?; - - let (extras, cb_result) = module.on_timeout_packet_execute(&packet, &signer); - - cb_result?; - - // apply state changes - let chan_end_on_a = { - let commitment_path_on_a = CommitmentPath { - port_id: packet.port_id_on_a.clone(), - channel_id: packet.chan_id_on_a.clone(), - sequence: packet.seq_on_a, - }; - ctx_a.delete_packet_commitment(&commitment_path_on_a)?; - - if let Order::Ordered = chan_end_on_a.ordering { - let mut chan_end_on_a = chan_end_on_a; - chan_end_on_a.state = State::Closed; - ctx_a.store_channel(&chan_end_path_on_a, chan_end_on_a.clone())?; - - chan_end_on_a - } else { - chan_end_on_a - } - }; - - // emit events and logs - { - ctx_a.log_message("success: packet timeout".to_string()); - - if let Order::Ordered = chan_end_on_a.ordering { - let conn_id_on_a = chan_end_on_a.connection_hops()[0].clone(); - - let event = IbcEvent::ChannelClosed(ChannelClosed::new( - packet.port_id_on_a.clone(), - packet.chan_id_on_a.clone(), - chan_end_on_a.counterparty().port_id.clone(), - chan_end_on_a.counterparty().channel_id.clone(), - conn_id_on_a, - chan_end_on_a.ordering, - )); - ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); - ctx_a.emit_ibc_event(event); - } - - for module_event in extras.events { - ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); - } - - for log_message in extras.log { - ctx_a.log_message(log_message); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - use rstest::*; - - use crate::applications::transfer::MODULE_ID_STR; - use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; - use crate::core::ics03_connection::connection::State as ConnectionState; - use crate::core::ics03_connection::version::get_compatible_versions; - use crate::core::ics04_channel::commitment::compute_packet_commitment; - use crate::core::ics04_channel::commitment::PacketCommitment; - use crate::core::ics24_host::identifier::ChannelId; - use crate::core::ics24_host::identifier::PortId; - use crate::core::timestamp::ZERO_DURATION; - use crate::test_utils::DummyTransferModule; - use crate::Height; - use crate::{ - core::{ - ics03_connection::connection::ConnectionEnd, - ics04_channel::{ - channel::{ChannelEnd, Counterparty}, - msgs::timeout_on_close::test_util::get_dummy_raw_msg_timeout_on_close, - Version, - }, - ics24_host::identifier::{ClientId, ConnectionId}, - }, - mock::context::MockContext, - }; - - struct Fixture { - ctx: MockContext, - module_id: ModuleId, - msg: MsgTimeoutOnClose, - packet_commitment: PacketCommitment, - conn_end_on_a: ConnectionEnd, - chan_end_on_a_ordered: ChannelEnd, - chan_end_on_a_unordered: ChannelEnd, - } - - #[fixture] - fn fixture() -> Fixture { - let client_height = Height::new(0, 2).unwrap(); - let mut ctx = MockContext::default().with_client(&ClientId::default(), client_height); - - let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); - let module = DummyTransferModule::new(); - ctx.add_route(module_id.clone(), module).unwrap(); - - let height = 2; - let timeout_timestamp = 5; - - let msg = MsgTimeoutOnClose::try_from(get_dummy_raw_msg_timeout_on_close( - height, - timeout_timestamp, - )) - .unwrap(); - - let packet = msg.packet.clone(); - - let packet_commitment = compute_packet_commitment( - &msg.packet.data, - &msg.packet.timeout_height_on_b, - &msg.packet.timeout_timestamp_on_b, - ); - - let chan_end_on_a_ordered = ChannelEnd::new( - State::Open, - Order::Ordered, - Counterparty::new( - packet.port_id_on_b.clone(), - Some(packet.chan_id_on_b.clone()), - ), - vec![ConnectionId::default()], - Version::new("ics20-1".to_string()), - ) - .unwrap(); - - let chan_end_on_a_unordered = ChannelEnd::new( - State::Open, - Order::Unordered, - Counterparty::new(packet.port_id_on_b.clone(), Some(packet.chan_id_on_b)), - vec![ConnectionId::default()], - Version::new("ics20-1".to_string()), - ) - .unwrap(); - - let conn_end_on_a = ConnectionEnd::new( - ConnectionState::Open, - ClientId::default(), - ConnectionCounterparty::new( - ClientId::default(), - Some(ConnectionId::default()), - Default::default(), - ), - get_compatible_versions(), - ZERO_DURATION, - ) - .unwrap(); - - Fixture { - ctx, - module_id, - msg, - packet_commitment, - conn_end_on_a, - chan_end_on_a_ordered, - chan_end_on_a_unordered, - } - } - - #[rstest] - fn timeout_unordered_chan_execute(fixture: Fixture) { - let Fixture { - ctx, - module_id, - msg, - packet_commitment, - conn_end_on_a, - chan_end_on_a_unordered, - .. - } = fixture; - let mut ctx = ctx - .with_channel( - PortId::default(), - ChannelId::default(), - chan_end_on_a_unordered, - ) - .with_connection(ConnectionId::default(), conn_end_on_a) - .with_packet_commitment( - msg.packet.port_id_on_a.clone(), - msg.packet.chan_id_on_a.clone(), - msg.packet.seq_on_a, - packet_commitment, - ); - - let res = timeout_packet_execute(&mut ctx, module_id, TimeoutMsgType::TimeoutOnClose(msg)); - - assert!(res.is_ok()); - - // Unordered channels only emit one event - assert_eq!(ctx.events.len(), 2); - assert!(matches!( - ctx.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(ctx.events[1], IbcEvent::TimeoutPacket(_))); - } - - #[rstest] - fn timeout_ordered_chan_execute(fixture: Fixture) { - let Fixture { - ctx, - module_id, - msg, - packet_commitment, - conn_end_on_a, - chan_end_on_a_ordered, - .. - } = fixture; - let mut ctx = ctx - .with_channel( - PortId::default(), - ChannelId::default(), - chan_end_on_a_ordered, - ) - .with_connection(ConnectionId::default(), conn_end_on_a) - .with_packet_commitment( - msg.packet.port_id_on_a.clone(), - msg.packet.chan_id_on_a.clone(), - msg.packet.seq_on_a, - packet_commitment, - ); - - let res = timeout_packet_execute(&mut ctx, module_id, TimeoutMsgType::TimeoutOnClose(msg)); - - assert!(res.is_ok()); - - // Ordered channels emit 2 events - assert_eq!(ctx.events.len(), 4); - assert!(matches!( - ctx.events[0], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(ctx.events[1], IbcEvent::TimeoutPacket(_))); - assert!(matches!( - ctx.events[2], - IbcEvent::Message(MessageEvent::Channel) - )); - assert!(matches!(ctx.events[3], IbcEvent::ChannelClosed(_))); - } -} diff --git a/crates/ibc/src/core/handler.rs b/crates/ibc/src/core/handler.rs index 451aa4128..768ba2df1 100644 --- a/crates/ibc/src/core/handler.rs +++ b/crates/ibc/src/core/handler.rs @@ -1,7 +1,41 @@ -use ibc_proto::google::protobuf::Any; - +use super::ics02_client::handler::{create_client, update_client, upgrade_client}; +use super::ics02_client::msgs::{ClientMsg, MsgUpdateOrMisbehaviour}; +use super::ics03_connection::handler::{ + conn_open_ack, conn_open_confirm, conn_open_init, conn_open_try, +}; +use super::ics03_connection::msgs::ConnectionMsg; +use super::ics04_channel::error::ChannelError; +use super::ics04_channel::handler::acknowledgement::{ + acknowledgement_packet_execute, acknowledgement_packet_validate, +}; +use super::ics04_channel::handler::chan_close_confirm::{ + chan_close_confirm_execute, chan_close_confirm_validate, +}; +use super::ics04_channel::handler::chan_close_init::{ + chan_close_init_execute, chan_close_init_validate, +}; +use super::ics04_channel::handler::chan_open_ack::{chan_open_ack_execute, chan_open_ack_validate}; +use super::ics04_channel::handler::chan_open_confirm::{ + chan_open_confirm_execute, chan_open_confirm_validate, +}; +use super::ics04_channel::handler::chan_open_init::{ + chan_open_init_execute, chan_open_init_validate, +}; +use super::ics04_channel::handler::chan_open_try::{chan_open_try_execute, chan_open_try_validate}; +use super::ics04_channel::handler::recv_packet::{recv_packet_execute, recv_packet_validate}; +use super::ics04_channel::handler::timeout::{ + timeout_packet_execute, timeout_packet_validate, TimeoutMsgType, +}; +use super::ics04_channel::msgs::{ChannelMsg, PacketMsg}; +use super::ContextError; use super::{msgs::MsgEnvelope, ExecutionContext, RouterError, ValidationContext}; +/// Entrypoint which performs both validation and message execution +pub fn dispatch(ctx: &mut impl ExecutionContext, msg: MsgEnvelope) -> Result<(), RouterError> { + validate(ctx, msg.clone())?; + execute(ctx, msg) +} + /// Entrypoint which only performs message validation /// /// If a transaction contains `n` messages `m_1` ... `m_n`, then @@ -10,27 +44,129 @@ use super::{msgs::MsgEnvelope, ExecutionContext, RouterError, ValidationContext} /// That is, the state transition of message `i` must be applied before /// message `i+1` is validated. This is equivalent to calling /// `dispatch()` on each successively. -pub fn validate(ctx: &Ctx, message: Any) -> Result<(), RouterError> +pub fn validate(ctx: &Ctx, msg: MsgEnvelope) -> Result<(), RouterError> where Ctx: ValidationContext, { - let envelope: MsgEnvelope = message.try_into()?; - ctx.validate(envelope) + match msg { + MsgEnvelope::Client(msg) => match msg { + ClientMsg::CreateClient(msg) => create_client::validate(ctx, msg), + ClientMsg::UpdateClient(msg) => { + update_client::validate(ctx, MsgUpdateOrMisbehaviour::UpdateClient(msg)) + } + ClientMsg::Misbehaviour(msg) => { + update_client::validate(ctx, MsgUpdateOrMisbehaviour::Misbehaviour(msg)) + } + ClientMsg::UpgradeClient(msg) => upgrade_client::validate(ctx, msg), + } + .map_err(RouterError::ContextError), + MsgEnvelope::Connection(msg) => match msg { + ConnectionMsg::OpenInit(msg) => conn_open_init::validate(ctx, msg), + ConnectionMsg::OpenTry(msg) => conn_open_try::validate(ctx, msg), + ConnectionMsg::OpenAck(msg) => conn_open_ack::validate(ctx, msg), + ConnectionMsg::OpenConfirm(ref msg) => conn_open_confirm::validate(ctx, msg), + } + .map_err(RouterError::ContextError), + MsgEnvelope::Channel(msg) => { + let module_id = ctx + .lookup_module_channel(&msg) + .map_err(ContextError::from)?; + if !ctx.has_route(&module_id) { + return Err(ChannelError::RouteNotFound).map_err(ContextError::from)?; + } + + match msg { + ChannelMsg::OpenInit(msg) => chan_open_init_validate(ctx, module_id, msg), + ChannelMsg::OpenTry(msg) => chan_open_try_validate(ctx, module_id, msg), + ChannelMsg::OpenAck(msg) => chan_open_ack_validate(ctx, module_id, msg), + ChannelMsg::OpenConfirm(msg) => chan_open_confirm_validate(ctx, module_id, msg), + ChannelMsg::CloseInit(msg) => chan_close_init_validate(ctx, module_id, msg), + ChannelMsg::CloseConfirm(msg) => chan_close_confirm_validate(ctx, module_id, msg), + } + .map_err(RouterError::ContextError) + } + MsgEnvelope::Packet(msg) => { + let module_id = ctx.lookup_module_packet(&msg).map_err(ContextError::from)?; + if !ctx.has_route(&module_id) { + return Err(ChannelError::RouteNotFound).map_err(ContextError::from)?; + } + + match msg { + PacketMsg::Recv(msg) => recv_packet_validate(ctx, msg), + PacketMsg::Ack(msg) => acknowledgement_packet_validate(ctx, module_id, msg), + PacketMsg::Timeout(msg) => { + timeout_packet_validate(ctx, module_id, TimeoutMsgType::Timeout(msg)) + } + PacketMsg::TimeoutOnClose(msg) => { + timeout_packet_validate(ctx, module_id, TimeoutMsgType::TimeoutOnClose(msg)) + } + } + .map_err(RouterError::ContextError) + } + } } /// Entrypoint which only performs message execution -pub fn execute(ctx: &mut Ctx, message: Any) -> Result<(), RouterError> +pub fn execute(ctx: &mut Ctx, msg: MsgEnvelope) -> Result<(), RouterError> where Ctx: ExecutionContext, { - let envelope: MsgEnvelope = message.try_into()?; - ctx.execute(envelope) -} + match msg { + MsgEnvelope::Client(msg) => match msg { + ClientMsg::CreateClient(msg) => create_client::execute(ctx, msg), + ClientMsg::UpdateClient(msg) => { + update_client::execute(ctx, MsgUpdateOrMisbehaviour::UpdateClient(msg)) + } + ClientMsg::Misbehaviour(msg) => { + update_client::execute(ctx, MsgUpdateOrMisbehaviour::Misbehaviour(msg)) + } + ClientMsg::UpgradeClient(msg) => upgrade_client::execute(ctx, msg), + } + .map_err(RouterError::ContextError), + MsgEnvelope::Connection(msg) => match msg { + ConnectionMsg::OpenInit(msg) => conn_open_init::execute(ctx, msg), + ConnectionMsg::OpenTry(msg) => conn_open_try::execute(ctx, msg), + ConnectionMsg::OpenAck(msg) => conn_open_ack::execute(ctx, msg), + ConnectionMsg::OpenConfirm(ref msg) => conn_open_confirm::execute(ctx, msg), + } + .map_err(RouterError::ContextError), + MsgEnvelope::Channel(msg) => { + let module_id = ctx + .lookup_module_channel(&msg) + .map_err(ContextError::from)?; + if !ctx.has_route(&module_id) { + return Err(ChannelError::RouteNotFound).map_err(ContextError::from)?; + } -/// Entrypoint which performs both validation and message execution -pub fn dispatch(ctx: &mut impl ExecutionContext, msg: MsgEnvelope) -> Result<(), RouterError> { - ctx.validate(msg.clone())?; - ctx.execute(msg) + match msg { + ChannelMsg::OpenInit(msg) => chan_open_init_execute(ctx, module_id, msg), + ChannelMsg::OpenTry(msg) => chan_open_try_execute(ctx, module_id, msg), + ChannelMsg::OpenAck(msg) => chan_open_ack_execute(ctx, module_id, msg), + ChannelMsg::OpenConfirm(msg) => chan_open_confirm_execute(ctx, module_id, msg), + ChannelMsg::CloseInit(msg) => chan_close_init_execute(ctx, module_id, msg), + ChannelMsg::CloseConfirm(msg) => chan_close_confirm_execute(ctx, module_id, msg), + } + .map_err(RouterError::ContextError) + } + MsgEnvelope::Packet(msg) => { + let module_id = ctx.lookup_module_packet(&msg).map_err(ContextError::from)?; + if !ctx.has_route(&module_id) { + return Err(ChannelError::RouteNotFound).map_err(ContextError::from)?; + } + + match msg { + PacketMsg::Recv(msg) => recv_packet_execute(ctx, module_id, msg), + PacketMsg::Ack(msg) => acknowledgement_packet_execute(ctx, module_id, msg), + PacketMsg::Timeout(msg) => { + timeout_packet_execute(ctx, module_id, TimeoutMsgType::Timeout(msg)) + } + PacketMsg::TimeoutOnClose(msg) => { + timeout_packet_execute(ctx, module_id, TimeoutMsgType::TimeoutOnClose(msg)) + } + } + .map_err(RouterError::ContextError) + } + } } #[cfg(test)] diff --git a/crates/ibc/src/core/ics04_channel/handler/acknowledgement.rs b/crates/ibc/src/core/ics04_channel/handler/acknowledgement.rs index 477871f18..c880859b5 100644 --- a/crates/ibc/src/core/ics04_channel/handler/acknowledgement.rs +++ b/crates/ibc/src/core/ics04_channel/handler/acknowledgement.rs @@ -1,3 +1,6 @@ +use crate::prelude::*; + +use crate::core::events::MessageEvent; use crate::core::ics03_connection::connection::State as ConnectionState; use crate::core::ics03_connection::delay::verify_conn_delay_passed; use crate::core::ics04_channel::channel::{Counterparty, Order, State as ChannelState}; @@ -9,11 +12,111 @@ use crate::core::ics24_host::path::Path; use crate::core::ics24_host::path::{ AckPath, ChannelEndPath, ClientConsensusStatePath, CommitmentPath, SeqAckPath, }; -use crate::prelude::*; +use crate::core::{events::IbcEvent, ics04_channel::events::AcknowledgePacket, router::ModuleId}; +use crate::core::{ContextError, ExecutionContext, ValidationContext}; + +pub(crate) fn acknowledgement_packet_validate( + ctx_a: &ValCtx, + module_id: ModuleId, + msg: MsgAcknowledgement, +) -> Result<(), ContextError> +where + ValCtx: ValidationContext, +{ + validate(ctx_a, &msg)?; + + let module = ctx_a + .get_route(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + + module + .on_acknowledgement_packet_validate(&msg.packet, &msg.acknowledgement, &msg.signer) + .map_err(ContextError::PacketError) +} + +pub(crate) fn acknowledgement_packet_execute( + ctx_a: &mut ExecCtx, + module_id: ModuleId, + msg: MsgAcknowledgement, +) -> Result<(), ContextError> +where + ExecCtx: ExecutionContext, +{ + let chan_end_path_on_a = + ChannelEndPath::new(&msg.packet.port_id_on_a, &msg.packet.chan_id_on_a); + let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?; + let conn_id_on_a = &chan_end_on_a.connection_hops()[0]; + + // In all cases, this event is emitted + let event = IbcEvent::AcknowledgePacket(AcknowledgePacket::new( + msg.packet.clone(), + chan_end_on_a.ordering, + conn_id_on_a.clone(), + )); + ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_a.emit_ibc_event(event); + + let commitment_path_on_a = CommitmentPath::new( + &msg.packet.port_id_on_a, + &msg.packet.chan_id_on_a, + msg.packet.seq_on_a, + ); + + // check if we're in the NO-OP case + if ctx_a.get_packet_commitment(&commitment_path_on_a).is_err() { + // This error indicates that the timeout has already been relayed + // or there is a misconfigured relayer attempting to prove a timeout + // for a packet never sent. Core IBC will treat this error as a no-op in order to + // prevent an entire relay transaction from failing and consuming unnecessary fees. + return Ok(()); + }; + + let module = ctx_a + .get_route_mut(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + + let (extras, cb_result) = + module.on_acknowledgement_packet_execute(&msg.packet, &msg.acknowledgement, &msg.signer); + + cb_result?; + + // apply state changes + { + let commitment_path_on_a = CommitmentPath { + port_id: msg.packet.port_id_on_a.clone(), + channel_id: msg.packet.chan_id_on_a.clone(), + sequence: msg.packet.seq_on_a, + }; + ctx_a.delete_packet_commitment(&commitment_path_on_a)?; + + if let Order::Ordered = chan_end_on_a.ordering { + // Note: in validation, we verified that `msg.packet.sequence == nextSeqRecv` + // (where `nextSeqRecv` is the value in the store) + let seq_ack_path_on_a = + SeqAckPath::new(&msg.packet.port_id_on_a, &msg.packet.chan_id_on_a); + ctx_a.store_next_sequence_ack(&seq_ack_path_on_a, msg.packet.seq_on_a.increment())?; + } + } + + // emit events and logs + { + ctx_a.log_message("success: packet acknowledgement".to_string()); -use crate::core::{ContextError, ValidationContext}; + // Note: Acknowledgement event was emitted at the beginning -pub fn validate(ctx_a: &Ctx, msg: &MsgAcknowledgement) -> Result<(), ContextError> + for module_event in extras.events { + ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); + } + + for log_message in extras.log { + ctx_a.log_message(log_message); + } + } + + Ok(()) +} + +fn validate(ctx_a: &Ctx, msg: &MsgAcknowledgement) -> Result<(), ContextError> where Ctx: ValidationContext, { @@ -114,13 +217,7 @@ where #[cfg(test)] mod tests { - use crate::core::ics04_channel::commitment::compute_packet_commitment; - use crate::core::ics04_channel::handler::acknowledgement::validate; - use crate::core::ics24_host::identifier::ChannelId; - use crate::core::ics24_host::identifier::PortId; - use crate::core::timestamp::Timestamp; - use crate::core::ExecutionContext; - use crate::prelude::*; + use super::*; use rstest::*; use test_log::test; @@ -134,27 +231,40 @@ mod tests { use crate::core::ics04_channel::msgs::acknowledgement::test_util::get_dummy_raw_msg_acknowledgement; use crate::core::ics04_channel::msgs::acknowledgement::MsgAcknowledgement; use crate::core::ics04_channel::Version; + use crate::core::ics24_host::identifier::ChannelId; + use crate::core::ics24_host::identifier::PortId; use crate::core::ics24_host::identifier::{ClientId, ConnectionId}; + use crate::core::timestamp::Timestamp; use crate::core::timestamp::ZERO_DURATION; - use crate::mock::context::MockContext; - pub struct Fixture { - pub context: MockContext, - pub client_height: Height, - pub msg: MsgAcknowledgement, - pub packet_commitment: PacketCommitment, - pub conn_end_on_a: ConnectionEnd, - pub chan_end_on_a: ChannelEnd, + use crate::mock::context::MockContext; + use crate::{applications::transfer::MODULE_ID_STR, test_utils::DummyTransferModule}; + + struct Fixture { + ctx: MockContext, + client_height: Height, + module_id: ModuleId, + msg: MsgAcknowledgement, + packet_commitment: PacketCommitment, + conn_end_on_a: ConnectionEnd, + chan_end_on_a_ordered: ChannelEnd, + chan_end_on_a_unordered: ChannelEnd, } #[fixture] fn fixture() -> Fixture { - let context = MockContext::default(); let client_height = Height::new(0, 2).unwrap(); + let mut ctx = MockContext::default().with_client(&ClientId::default(), client_height); + + let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); + let module = DummyTransferModule::new(); + ctx.add_route(module_id.clone(), module).unwrap(); + let msg = MsgAcknowledgement::try_from(get_dummy_raw_msg_acknowledgement( client_height.revision_height(), )) .unwrap(); + let packet = msg.packet.clone(); let packet_commitment = compute_packet_commitment( @@ -163,15 +273,18 @@ mod tests { &packet.timeout_timestamp_on_b, ); - let chan_end_on_a = ChannelEnd::new( + let chan_end_on_a_unordered = ChannelEnd::new( State::Open, - Order::default(), - Counterparty::new(packet.port_id_on_b.clone(), Some(packet.chan_id_on_b)), + Order::Unordered, + Counterparty::new(packet.port_id_on_b, Some(packet.chan_id_on_b)), vec![ConnectionId::default()], Version::new("ics20-1".to_string()), ) .unwrap(); + let mut chan_end_on_a_ordered = chan_end_on_a_unordered.clone(); + chan_end_on_a_ordered.ordering = Order::Ordered; + let conn_end_on_a = ConnectionEnd::new( ConnectionState::Open, ClientId::default(), @@ -186,20 +299,22 @@ mod tests { .unwrap(); Fixture { - context, + ctx, client_height, + module_id, msg, packet_commitment, conn_end_on_a, - chan_end_on_a, + chan_end_on_a_unordered, + chan_end_on_a_ordered, } } #[rstest] fn ack_fail_no_channel(fixture: Fixture) { - let Fixture { context, msg, .. } = fixture; + let Fixture { ctx, msg, .. } = fixture; - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!( res.is_err(), @@ -211,19 +326,23 @@ mod tests { #[rstest] fn ack_success_no_packet_commitment(fixture: Fixture) { let Fixture { - context, + ctx, msg, conn_end_on_a, - chan_end_on_a, + chan_end_on_a_unordered, client_height, .. } = fixture; - let context = context + let ctx = ctx .with_client(&ClientId::default(), client_height) - .with_channel(PortId::default(), ChannelId::default(), chan_end_on_a) + .with_channel( + PortId::default(), + ChannelId::default(), + chan_end_on_a_unordered, + ) .with_connection(ConnectionId::default(), conn_end_on_a); - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!( res.is_ok(), @@ -234,17 +353,21 @@ mod tests { #[rstest] fn ack_success_happy_path(fixture: Fixture) { let Fixture { - context, + ctx, msg, packet_commitment, conn_end_on_a, - chan_end_on_a, + chan_end_on_a_unordered, client_height, .. } = fixture; - let mut context = context + let mut ctx: MockContext = ctx .with_client(&ClientId::default(), client_height) - .with_channel(PortId::default(), ChannelId::default(), chan_end_on_a) + .with_channel( + PortId::default(), + ChannelId::default(), + chan_end_on_a_unordered, + ) .with_connection(ConnectionId::default(), conn_end_on_a) .with_packet_commitment( msg.packet.port_id_on_a.clone(), @@ -252,26 +375,98 @@ mod tests { msg.packet.seq_on_a, packet_commitment, ); - context - .store_update_time( - ClientId::default(), - client_height, - Timestamp::from_nanoseconds(1000).unwrap(), - ) - .unwrap(); - context - .store_update_height( - ClientId::default(), - client_height, - Height::new(0, 4).unwrap(), - ) - .unwrap(); + ctx.store_update_time( + ClientId::default(), + client_height, + Timestamp::from_nanoseconds(1000).unwrap(), + ) + .unwrap(); + ctx.store_update_height( + ClientId::default(), + client_height, + Height::new(0, 4).unwrap(), + ) + .unwrap(); - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!( res.is_ok(), "Happy path: validation should succeed. err: {res:?}" ) } + + #[rstest] + fn ack_unordered_chan_execute(fixture: Fixture) { + let Fixture { + ctx, + module_id, + msg, + packet_commitment, + conn_end_on_a, + chan_end_on_a_unordered, + .. + } = fixture; + let mut ctx = ctx + .with_channel( + PortId::default(), + ChannelId::default(), + chan_end_on_a_unordered, + ) + .with_connection(ConnectionId::default(), conn_end_on_a) + .with_packet_commitment( + msg.packet.port_id_on_a.clone(), + msg.packet.chan_id_on_a.clone(), + msg.packet.seq_on_a, + packet_commitment, + ); + + let res = acknowledgement_packet_execute(&mut ctx, module_id, msg); + + assert!(res.is_ok()); + + assert_eq!(ctx.events.len(), 2); + assert!(matches!( + ctx.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(ctx.events[1], IbcEvent::AcknowledgePacket(_))); + } + + #[rstest] + fn ack_ordered_chan_execute(fixture: Fixture) { + let Fixture { + ctx, + module_id, + msg, + packet_commitment, + conn_end_on_a, + chan_end_on_a_ordered, + .. + } = fixture; + let mut ctx = ctx + .with_channel( + PortId::default(), + ChannelId::default(), + chan_end_on_a_ordered, + ) + .with_connection(ConnectionId::default(), conn_end_on_a) + .with_packet_commitment( + msg.packet.port_id_on_a.clone(), + msg.packet.chan_id_on_a.clone(), + msg.packet.seq_on_a, + packet_commitment, + ); + + let res = acknowledgement_packet_execute(&mut ctx, module_id, msg); + + assert!(res.is_ok()); + + assert_eq!(ctx.events.len(), 2); + assert!(matches!( + ctx.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(ctx.events[1], IbcEvent::AcknowledgePacket(_))); + } } diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_close_confirm.rs b/crates/ibc/src/core/ics04_channel/handler/chan_close_confirm.rs index 3d879fb73..82c2df7e5 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_close_confirm.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_close_confirm.rs @@ -1,17 +1,104 @@ //! Protocol logic specific to ICS4 messages of type `MsgChannelCloseConfirm`. +use crate::prelude::*; use ibc_proto::protobuf::Protobuf; +use crate::core::events::{IbcEvent, MessageEvent}; use crate::core::ics03_connection::connection::State as ConnectionState; +use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State as ChannelState}; use crate::core::ics04_channel::error::ChannelError; +use crate::core::ics04_channel::events::CloseConfirm; use crate::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; use crate::core::ics24_host::path::Path; use crate::core::ics24_host::path::{ChannelEndPath, ClientConsensusStatePath}; -use crate::core::{ContextError, ValidationContext}; -use crate::prelude::*; +use crate::core::router::ModuleId; +use crate::core::{ContextError, ExecutionContext, ValidationContext}; + +pub(crate) fn chan_close_confirm_validate( + ctx_b: &ValCtx, + module_id: ModuleId, + msg: MsgChannelCloseConfirm, +) -> Result<(), ContextError> +where + ValCtx: ValidationContext, +{ + validate(ctx_b, &msg)?; + + let module = ctx_b + .get_route(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + module.on_chan_close_confirm_validate(&msg.port_id_on_b, &msg.chan_id_on_b)?; + + Ok(()) +} + +pub(crate) fn chan_close_confirm_execute( + ctx_b: &mut ExecCtx, + module_id: ModuleId, + msg: MsgChannelCloseConfirm, +) -> Result<(), ContextError> +where + ExecCtx: ExecutionContext, +{ + let module = ctx_b + .get_route_mut(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + let extras = module.on_chan_close_confirm_execute(&msg.port_id_on_b, &msg.chan_id_on_b)?; + let chan_end_path_on_b = ChannelEndPath::new(&msg.port_id_on_b, &msg.chan_id_on_b); + let chan_end_on_b = ctx_b.channel_end(&chan_end_path_on_b)?; + + // state changes + { + let chan_end_on_b = { + let mut chan_end_on_b = chan_end_on_b.clone(); + chan_end_on_b.set_state(State::Closed); + chan_end_on_b + }; + ctx_b.store_channel(&chan_end_path_on_b, chan_end_on_b)?; + } + + // emit events and logs + { + ctx_b.log_message("success: channel close confirm".to_string()); -pub fn validate(ctx_b: &Ctx, msg: &MsgChannelCloseConfirm) -> Result<(), ContextError> + let core_event = { + let port_id_on_a = chan_end_on_b.counterparty().port_id.clone(); + let chan_id_on_a = chan_end_on_b + .counterparty() + .channel_id + .clone() + .ok_or(ContextError::ChannelError(ChannelError::Other { + description: + "internal error: ChannelEnd doesn't have a counterparty channel id in CloseInit" + .to_string(), + }))?; + let conn_id_on_b = chan_end_on_b.connection_hops[0].clone(); + + IbcEvent::CloseConfirmChannel(CloseConfirm::new( + msg.port_id_on_b.clone(), + msg.chan_id_on_b.clone(), + port_id_on_a, + chan_id_on_a, + conn_id_on_b, + )) + }; + ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_b.emit_ibc_event(core_event); + + for module_event in extras.events { + ctx_b.emit_ibc_event(IbcEvent::Module(module_event)); + } + + for log_message in extras.log { + ctx_b.log_message(log_message); + } + } + + Ok(()) +} + +fn validate(ctx_b: &Ctx, msg: &MsgChannelCloseConfirm) -> Result<(), ContextError> where Ctx: ValidationContext, { @@ -78,10 +165,7 @@ where #[cfg(test)] mod tests { - use crate::core::ics04_channel::msgs::chan_close_confirm::test_util::get_dummy_raw_msg_chan_close_confirm; - use crate::core::ics04_channel::msgs::chan_close_confirm::MsgChannelCloseConfirm; - use crate::core::ValidationContext; - use crate::prelude::*; + use super::*; use crate::core::ics03_connection::connection::ConnectionEnd; use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; @@ -91,17 +175,20 @@ mod tests { use crate::core::ics04_channel::channel::{ ChannelEnd, Counterparty, Order, State as ChannelState, }; + use crate::core::ics04_channel::msgs::chan_close_confirm::test_util::get_dummy_raw_msg_chan_close_confirm; use crate::core::ics04_channel::Version; use crate::core::ics24_host::identifier::{ClientId, ConnectionId}; - + use crate::core::router::ModuleId; use crate::core::timestamp::ZERO_DURATION; + use crate::mock::client_state::client_type as mock_client_type; use crate::mock::context::MockContext; - use super::validate; + use crate::applications::transfer::MODULE_ID_STR; + use crate::test_utils::DummyTransferModule; #[test] - fn chan_close_confirm_event_height() { + fn test_chan_close_confirm_validate() { let client_id = ClientId::new(mock_client_type(), 24).unwrap(); let conn_id = ConnectionId::new(2); let default_context = MockContext::default(); @@ -148,4 +235,64 @@ mod tests { "Validation expected to succeed (happy path). Error: {res:?}" ); } + + #[test] + fn test_chan_close_confirm_execute() { + let client_id = ClientId::new(mock_client_type(), 24).unwrap(); + let conn_id = ConnectionId::new(2); + let default_context = MockContext::default(); + let client_consensus_state_height = default_context.host_height().unwrap(); + + let conn_end = ConnectionEnd::new( + ConnectionState::Open, + client_id.clone(), + ConnectionCounterparty::try_from(get_dummy_raw_counterparty(Some(0))).unwrap(), + get_compatible_versions(), + ZERO_DURATION, + ) + .unwrap(); + + let msg_chan_close_confirm = MsgChannelCloseConfirm::try_from( + get_dummy_raw_msg_chan_close_confirm(client_consensus_state_height.revision_height()), + ) + .unwrap(); + + let chan_end = ChannelEnd::new( + ChannelState::Open, + Order::default(), + Counterparty::new( + msg_chan_close_confirm.port_id_on_b.clone(), + Some(msg_chan_close_confirm.chan_id_on_b.clone()), + ), + vec![conn_id.clone()], + Version::default(), + ) + .unwrap(); + + let mut context = default_context + .with_client(&client_id, client_consensus_state_height) + .with_connection(conn_id, conn_end) + .with_channel( + msg_chan_close_confirm.port_id_on_b.clone(), + msg_chan_close_confirm.chan_id_on_b.clone(), + chan_end, + ); + + let module = DummyTransferModule::new(); + let module_id = ModuleId::new(MODULE_ID_STR.to_string()); + context.add_route(module_id.clone(), module).unwrap(); + + let res = chan_close_confirm_execute(&mut context, module_id, msg_chan_close_confirm); + assert!(res.is_ok(), "Execution success: happy path"); + + assert_eq!(context.events.len(), 2); + assert!(matches!( + context.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!( + context.events[1], + IbcEvent::CloseConfirmChannel(_) + )); + } } diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_close_init.rs b/crates/ibc/src/core/ics04_channel/handler/chan_close_init.rs index 184e1d247..b3be6c82d 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_close_init.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_close_init.rs @@ -1,11 +1,101 @@ //! Protocol logic specific to ICS4 messages of type `MsgChannelCloseInit`. +use crate::prelude::*; + +use crate::core::events::{IbcEvent, MessageEvent}; use crate::core::ics03_connection::connection::State as ConnectionState; +use crate::core::ics04_channel::channel::State; +use crate::core::ics04_channel::error::ChannelError; +use crate::core::ics04_channel::events::CloseInit; use crate::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; use crate::core::ics24_host::path::ChannelEndPath; +use crate::core::router::ModuleId; +use crate::core::{ContextError, ExecutionContext, ValidationContext}; + +pub(crate) fn chan_close_init_validate( + ctx_a: &ValCtx, + module_id: ModuleId, + msg: MsgChannelCloseInit, +) -> Result<(), ContextError> +where + ValCtx: ValidationContext, +{ + validate(ctx_a, &msg)?; + + let module = ctx_a + .get_route(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + module.on_chan_close_init_validate(&msg.port_id_on_a, &msg.chan_id_on_a)?; + + Ok(()) +} + +pub(crate) fn chan_close_init_execute( + ctx_a: &mut ExecCtx, + module_id: ModuleId, + msg: MsgChannelCloseInit, +) -> Result<(), ContextError> +where + ExecCtx: ExecutionContext, +{ + let module = ctx_a + .get_route_mut(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + let extras = module.on_chan_close_init_execute(&msg.port_id_on_a, &msg.chan_id_on_a)?; + let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &msg.chan_id_on_a); + let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?; + + // state changes + { + let chan_end_on_a = { + let mut chan_end_on_a = chan_end_on_a.clone(); + chan_end_on_a.set_state(State::Closed); + chan_end_on_a + }; + + ctx_a.store_channel(&chan_end_path_on_a, chan_end_on_a)?; + } + + // emit events and logs + { + ctx_a.log_message("success: channel close init".to_string()); + + let core_event = { + let port_id_on_b = chan_end_on_a.counterparty().port_id.clone(); + let chan_id_on_b = chan_end_on_a + .counterparty() + .channel_id + .clone() + .ok_or(ContextError::ChannelError(ChannelError::Other { + description: + "internal error: ChannelEnd doesn't have a counterparty channel id in CloseInit" + .to_string(), + }))?; + let conn_id_on_a = chan_end_on_a.connection_hops[0].clone(); -use crate::core::{ContextError, ValidationContext}; + IbcEvent::CloseInitChannel(CloseInit::new( + msg.port_id_on_a.clone(), + msg.chan_id_on_a.clone(), + port_id_on_b, + chan_id_on_b, + conn_id_on_a, + )) + }; + ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_a.emit_ibc_event(core_event); + + for module_event in extras.events { + ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); + } + + for log_message in extras.log { + ctx_a.log_message(log_message); + } + } + + Ok(()) +} -pub fn validate(ctx_a: &Ctx, msg: &MsgChannelCloseInit) -> Result<(), ContextError> +fn validate(ctx_a: &Ctx, msg: &MsgChannelCloseInit) -> Result<(), ContextError> where Ctx: ValidationContext, { @@ -33,10 +123,7 @@ where #[cfg(test)] mod tests { - use crate::core::ics04_channel::msgs::chan_close_init::test_util::get_dummy_raw_msg_chan_close_init; - use crate::core::ics04_channel::msgs::chan_close_init::MsgChannelCloseInit; - use crate::core::ValidationContext; - use crate::prelude::*; + use super::*; use crate::core::ics03_connection::connection::ConnectionEnd; use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; @@ -46,17 +133,18 @@ mod tests { use crate::core::ics04_channel::channel::{ ChannelEnd, Counterparty, Order, State as ChannelState, }; + use crate::core::ics04_channel::msgs::chan_close_init::test_util::get_dummy_raw_msg_chan_close_init; use crate::core::ics04_channel::Version; use crate::core::ics24_host::identifier::{ClientId, ConnectionId}; - use crate::core::timestamp::ZERO_DURATION; + + use crate::applications::transfer::MODULE_ID_STR; use crate::mock::client_state::client_type as mock_client_type; use crate::mock::context::MockContext; - - use super::validate; + use crate::test_utils::DummyTransferModule; #[test] - fn chan_close_init_event_height() { + fn test_chan_close_init_validate() { let client_id = ClientId::new(mock_client_type(), 24).unwrap(); let conn_id = ConnectionId::new(2); @@ -104,4 +192,66 @@ mod tests { "Validation expected to succeed (happy path). Error: {res:?}" ); } + + #[test] + fn test_chan_close_init_execute() { + let client_id = ClientId::new(mock_client_type(), 24).unwrap(); + let conn_id = ConnectionId::new(2); + + let conn_end = ConnectionEnd::new( + ConnectionState::Open, + client_id.clone(), + ConnectionCounterparty::try_from(get_dummy_raw_counterparty(Some(0))).unwrap(), + get_compatible_versions(), + ZERO_DURATION, + ) + .unwrap(); + + let msg_chan_close_init = + MsgChannelCloseInit::try_from(get_dummy_raw_msg_chan_close_init()).unwrap(); + + let chan_end = ChannelEnd::new( + ChannelState::Open, + Order::default(), + Counterparty::new( + msg_chan_close_init.port_id_on_a.clone(), + Some(msg_chan_close_init.chan_id_on_a.clone()), + ), + vec![conn_id.clone()], + Version::default(), + ) + .unwrap(); + + let mut context = { + let mut default_context = MockContext::default(); + let client_consensus_state_height = default_context.host_height().unwrap(); + + let module = DummyTransferModule::new(); + let module_id = ModuleId::new(MODULE_ID_STR.to_string()); + default_context.add_route(module_id, module).unwrap(); + + default_context + .with_client(&client_id, client_consensus_state_height) + .with_connection(conn_id, conn_end) + .with_channel( + msg_chan_close_init.port_id_on_a.clone(), + msg_chan_close_init.chan_id_on_a.clone(), + chan_end, + ) + }; + + let res = chan_close_init_execute( + &mut context, + ModuleId::new(MODULE_ID_STR.to_string()), + msg_chan_close_init, + ); + assert!(res.is_ok(), "Execution happy path"); + + assert_eq!(context.events.len(), 2); + assert!(matches!( + context.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(context.events[1], IbcEvent::CloseInitChannel(_))); + } } diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_open_ack.rs b/crates/ibc/src/core/ics04_channel/handler/chan_open_ack.rs index fa5c1f103..ca2007a85 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_open_ack.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_open_ack.rs @@ -1,17 +1,100 @@ //! Protocol logic specific to ICS4 messages of type `MsgChannelOpenAck`. +use crate::prelude::*; use ibc_proto::protobuf::Protobuf; +use crate::core::events::{IbcEvent, MessageEvent}; use crate::core::ics03_connection::connection::State as ConnectionState; +use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State as ChannelState}; use crate::core::ics04_channel::error::ChannelError; +use crate::core::ics04_channel::events::OpenAck; use crate::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; use crate::core::ics24_host::path::Path; use crate::core::ics24_host::path::{ChannelEndPath, ClientConsensusStatePath}; -use crate::core::{ContextError, ValidationContext}; -use crate::prelude::*; +use crate::core::router::ModuleId; +use crate::core::{ContextError, ExecutionContext, ValidationContext}; + +pub(crate) fn chan_open_ack_validate( + ctx_a: &ValCtx, + module_id: ModuleId, + msg: MsgChannelOpenAck, +) -> Result<(), ContextError> +where + ValCtx: ValidationContext, +{ + validate(ctx_a, &msg)?; + + let module = ctx_a + .get_route(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + module.on_chan_open_ack_validate(&msg.port_id_on_a, &msg.chan_id_on_a, &msg.version_on_b)?; -pub fn validate(ctx_a: &Ctx, msg: &MsgChannelOpenAck) -> Result<(), ContextError> + Ok(()) +} + +pub(crate) fn chan_open_ack_execute( + ctx_a: &mut ExecCtx, + module_id: ModuleId, + msg: MsgChannelOpenAck, +) -> Result<(), ContextError> +where + ExecCtx: ExecutionContext, +{ + let module = ctx_a + .get_route_mut(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + let extras = + module.on_chan_open_ack_execute(&msg.port_id_on_a, &msg.chan_id_on_a, &msg.version_on_b)?; + let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &msg.chan_id_on_a); + let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?; + + // state changes + { + let chan_end_on_a = { + let mut chan_end_on_a = chan_end_on_a.clone(); + + chan_end_on_a.set_state(State::Open); + chan_end_on_a.set_version(msg.version_on_b.clone()); + chan_end_on_a.set_counterparty_channel_id(msg.chan_id_on_b.clone()); + + chan_end_on_a + }; + ctx_a.store_channel(&chan_end_path_on_a, chan_end_on_a)?; + } + + // emit events and logs + { + ctx_a.log_message("success: channel open ack".to_string()); + + let core_event = { + let port_id_on_b = chan_end_on_a.counterparty().port_id.clone(); + let conn_id_on_a = chan_end_on_a.connection_hops[0].clone(); + + IbcEvent::OpenAckChannel(OpenAck::new( + msg.port_id_on_a.clone(), + msg.chan_id_on_a.clone(), + port_id_on_b, + msg.chan_id_on_b, + conn_id_on_a, + )) + }; + ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_a.emit_ibc_event(core_event); + + for module_event in extras.events { + ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); + } + + for log_message in extras.log { + ctx_a.log_message(log_message); + } + } + + Ok(()) +} + +fn validate(ctx_a: &Ctx, msg: &MsgChannelOpenAck) -> Result<(), ContextError> where Ctx: ValidationContext, { @@ -79,29 +162,32 @@ where #[cfg(test)] mod tests { - use crate::core::ics03_connection::msgs::test_util::get_dummy_raw_counterparty; - use crate::core::ics04_channel::channel::Order; - use crate::core::ics04_channel::handler::chan_open_ack::validate; - use crate::core::ics24_host::identifier::ClientId; - use crate::core::timestamp::ZERO_DURATION; - use crate::prelude::*; + use super::*; use rstest::*; use test_log::test; + use crate::applications::transfer::MODULE_ID_STR; use crate::core::ics03_connection::connection::ConnectionEnd; use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; use crate::core::ics03_connection::connection::State as ConnectionState; + use crate::core::ics03_connection::msgs::test_util::get_dummy_raw_counterparty; use crate::core::ics03_connection::version::get_compatible_versions; + use crate::core::ics04_channel::channel::Order; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State}; use crate::core::ics04_channel::msgs::chan_open_ack::test_util::get_dummy_raw_msg_chan_open_ack; use crate::core::ics04_channel::msgs::chan_open_ack::MsgChannelOpenAck; + use crate::core::ics24_host::identifier::ClientId; use crate::core::ics24_host::identifier::ConnectionId; + use crate::core::timestamp::ZERO_DURATION; + use crate::Height; + use crate::mock::client_state::client_type as mock_client_type; use crate::mock::context::MockContext; - use crate::Height; + use crate::test_utils::DummyTransferModule; pub struct Fixture { pub context: MockContext, + pub module_id: ModuleId, pub msg: MsgChannelOpenAck, pub client_id_on_a: ClientId, pub conn_id_on_a: ConnectionId, @@ -113,7 +199,10 @@ mod tests { #[fixture] fn fixture() -> Fixture { let proof_height = 10; - let context = MockContext::default(); + let mut context = MockContext::default(); + let module = DummyTransferModule::new(); + let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); + context.add_route(module_id.clone(), module).unwrap(); let client_id_on_a = ClientId::new(mock_client_type(), 45).unwrap(); let conn_id_on_a = ConnectionId::new(2); @@ -140,6 +229,7 @@ mod tests { Fixture { context, + module_id, msg, client_id_on_a, conn_id_on_a, @@ -262,4 +352,39 @@ mod tests { assert!(res.is_ok(), "Validation happy path") } + + #[rstest] + fn chan_open_ack_execute_happy_path(fixture: Fixture) { + let Fixture { + context, + module_id, + msg, + client_id_on_a, + conn_id_on_a, + conn_end_on_a, + chan_end_on_a, + proof_height, + .. + } = fixture; + + let mut context = context + .with_client(&client_id_on_a, Height::new(0, proof_height).unwrap()) + .with_connection(conn_id_on_a, conn_end_on_a) + .with_channel( + msg.port_id_on_a.clone(), + msg.chan_id_on_a.clone(), + chan_end_on_a, + ); + + let res = chan_open_ack_execute(&mut context, module_id, msg); + + assert!(res.is_ok(), "Execution happy path"); + + assert_eq!(context.events.len(), 2); + assert!(matches!( + context.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(context.events[1], IbcEvent::OpenAckChannel(_))); + } } diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_open_confirm.rs b/crates/ibc/src/core/ics04_channel/handler/chan_open_confirm.rs index bc3958786..89807457d 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_open_confirm.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_open_confirm.rs @@ -1,17 +1,104 @@ //! Protocol logic specific to ICS4 messages of type `MsgChannelOpenConfirm`. +use crate::prelude::*; use ibc_proto::protobuf::Protobuf; +use crate::core::events::{IbcEvent, MessageEvent}; use crate::core::ics03_connection::connection::State as ConnectionState; +use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State as ChannelState}; use crate::core::ics04_channel::error::ChannelError; +use crate::core::ics04_channel::events::OpenConfirm; use crate::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; use crate::core::ics24_host::path::Path; use crate::core::ics24_host::path::{ChannelEndPath, ClientConsensusStatePath}; -use crate::core::{ContextError, ValidationContext}; -use crate::prelude::*; +use crate::core::router::ModuleId; +use crate::core::{ContextError, ExecutionContext, ValidationContext}; + +pub(crate) fn chan_open_confirm_validate( + ctx_b: &ValCtx, + module_id: ModuleId, + msg: MsgChannelOpenConfirm, +) -> Result<(), ContextError> +where + ValCtx: ValidationContext, +{ + validate(ctx_b, &msg)?; + + let module = ctx_b + .get_route(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + module.on_chan_open_confirm_validate(&msg.port_id_on_b, &msg.chan_id_on_b)?; + + Ok(()) +} -pub fn validate(ctx_b: &Ctx, msg: &MsgChannelOpenConfirm) -> Result<(), ContextError> +pub(crate) fn chan_open_confirm_execute( + ctx_b: &mut ExecCtx, + module_id: ModuleId, + msg: MsgChannelOpenConfirm, +) -> Result<(), ContextError> +where + ExecCtx: ExecutionContext, +{ + let module = ctx_b + .get_route_mut(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + + let extras = module.on_chan_open_confirm_execute(&msg.port_id_on_b, &msg.chan_id_on_b)?; + let chan_end_path_on_b = ChannelEndPath::new(&msg.port_id_on_b, &msg.chan_id_on_b); + let chan_end_on_b = ctx_b.channel_end(&chan_end_path_on_b)?; + + // state changes + { + let chan_end_on_b = { + let mut chan_end_on_b = chan_end_on_b.clone(); + chan_end_on_b.set_state(State::Open); + + chan_end_on_b + }; + ctx_b.store_channel(&chan_end_path_on_b, chan_end_on_b)?; + } + + // emit events and logs + { + ctx_b.log_message("success: channel open confirm".to_string()); + + let conn_id_on_b = chan_end_on_b.connection_hops[0].clone(); + let port_id_on_a = chan_end_on_b.counterparty().port_id.clone(); + let chan_id_on_a = chan_end_on_b + .counterparty() + .channel_id + .clone() + .ok_or(ContextError::ChannelError(ChannelError::Other { + description: + "internal error: ChannelEnd doesn't have a counterparty channel id in OpenConfirm" + .to_string(), + }))?; + + let core_event = IbcEvent::OpenConfirmChannel(OpenConfirm::new( + msg.port_id_on_b.clone(), + msg.chan_id_on_b.clone(), + port_id_on_a, + chan_id_on_a, + conn_id_on_b, + )); + ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_b.emit_ibc_event(core_event); + + for module_event in extras.events { + ctx_b.emit_ibc_event(IbcEvent::Module(module_event)); + } + + for log_message in extras.log { + ctx_b.log_message(log_message); + } + } + + Ok(()) +} + +fn validate(ctx_b: &Ctx, msg: &MsgChannelOpenConfirm) -> Result<(), ContextError> where Ctx: ValidationContext, { @@ -81,9 +168,7 @@ where #[cfg(test)] mod tests { - use crate::core::ics04_channel::handler::chan_open_confirm::validate; - use crate::core::ics24_host::identifier::ChannelId; - use crate::prelude::*; + use super::*; use rstest::*; use test_log::test; @@ -96,14 +181,18 @@ mod tests { use crate::core::ics04_channel::msgs::chan_open_confirm::test_util::get_dummy_raw_msg_chan_open_confirm; use crate::core::ics04_channel::msgs::chan_open_confirm::MsgChannelOpenConfirm; use crate::core::ics04_channel::Version; + use crate::core::ics24_host::identifier::ChannelId; use crate::core::ics24_host::identifier::{ClientId, ConnectionId}; use crate::core::timestamp::ZERO_DURATION; + use crate::Height; + use crate::mock::client_state::client_type as mock_client_type; use crate::mock::context::MockContext; - use crate::Height; + use crate::{applications::transfer::MODULE_ID_STR, test_utils::DummyTransferModule}; pub struct Fixture { pub context: MockContext, + pub module_id: ModuleId, pub msg: MsgChannelOpenConfirm, pub client_id_on_b: ClientId, pub conn_id_on_b: ConnectionId, @@ -115,7 +204,10 @@ mod tests { #[fixture] fn fixture() -> Fixture { let proof_height = 10; - let context = MockContext::default(); + let mut context = MockContext::default(); + let module = DummyTransferModule::new(); + let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); + context.add_route(module_id.clone(), module).unwrap(); let client_id_on_b = ClientId::new(mock_client_type(), 45).unwrap(); let conn_id_on_b = ConnectionId::new(2); @@ -143,6 +235,7 @@ mod tests { Fixture { context, + module_id, msg, client_id_on_b, conn_id_on_b, @@ -213,7 +306,7 @@ mod tests { } #[rstest] - fn chan_open_confirm_happy_path(fixture: Fixture) { + fn chan_open_confirm_validate_happy_path(fixture: Fixture) { let Fixture { context, msg, @@ -238,4 +331,39 @@ mod tests { assert!(res.is_ok(), "Validation happy path") } + + #[rstest] + fn chan_open_confirm_execute_happy_path(fixture: Fixture) { + let Fixture { + context, + module_id, + msg, + client_id_on_b, + conn_id_on_b, + conn_end_on_b, + chan_end_on_b, + proof_height, + .. + } = fixture; + + let mut context = context + .with_client(&client_id_on_b, Height::new(0, proof_height).unwrap()) + .with_connection(conn_id_on_b, conn_end_on_b) + .with_channel( + msg.port_id_on_b.clone(), + ChannelId::default(), + chan_end_on_b, + ); + + let res = chan_open_confirm_execute(&mut context, module_id, msg); + + assert!(res.is_ok(), "Execution happy path"); + + assert_eq!(context.events.len(), 2); + assert!(matches!( + context.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(context.events[1], IbcEvent::OpenConfirmChannel(_))); + } } diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_open_init.rs b/crates/ibc/src/core/ics04_channel/handler/chan_open_init.rs index ee4c67439..0408e1173 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_open_init.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_open_init.rs @@ -1,11 +1,119 @@ //! Protocol logic specific to ICS4 messages of type `MsgChannelOpenInit`. -use crate::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; use crate::prelude::*; -use crate::core::{ContextError, ValidationContext}; +use crate::core::events::{IbcEvent, MessageEvent}; +use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State}; +use crate::core::ics04_channel::error::ChannelError; +use crate::core::ics04_channel::events::OpenInit; +use crate::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; +use crate::core::ics24_host::identifier::ChannelId; +use crate::core::ics24_host::path::{ChannelEndPath, SeqAckPath, SeqRecvPath, SeqSendPath}; +use crate::core::router::ModuleId; +use crate::core::{ContextError, ExecutionContext, ValidationContext}; + +pub(crate) fn chan_open_init_validate( + ctx_a: &ValCtx, + module_id: ModuleId, + msg: MsgChannelOpenInit, +) -> Result<(), ContextError> +where + ValCtx: ValidationContext, +{ + validate(ctx_a, &msg)?; + let chan_id_on_a = ChannelId::new(ctx_a.channel_counter()?); + + let module = ctx_a + .get_route(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + module.on_chan_open_init_validate( + msg.ordering, + &msg.connection_hops_on_a, + &msg.port_id_on_a, + &chan_id_on_a, + &Counterparty::new(msg.port_id_on_b.clone(), None), + &msg.version_proposal, + )?; + + Ok(()) +} + +pub(crate) fn chan_open_init_execute( + ctx_a: &mut ExecCtx, + module_id: ModuleId, + msg: MsgChannelOpenInit, +) -> Result<(), ContextError> +where + ExecCtx: ExecutionContext, +{ + let chan_id_on_a = ChannelId::new(ctx_a.channel_counter()?); + let module = ctx_a + .get_route_mut(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + let (extras, version) = module.on_chan_open_init_execute( + msg.ordering, + &msg.connection_hops_on_a, + &msg.port_id_on_a, + &chan_id_on_a, + &Counterparty::new(msg.port_id_on_b.clone(), None), + &msg.version_proposal, + )?; + + let conn_id_on_a = msg.connection_hops_on_a[0].clone(); + + // state changes + { + let chan_end_on_a = ChannelEnd::new( + State::Init, + msg.ordering, + Counterparty::new(msg.port_id_on_b.clone(), None), + msg.connection_hops_on_a.clone(), + msg.version_proposal.clone(), + )?; + let chan_end_path_on_a = ChannelEndPath::new(&msg.port_id_on_a, &chan_id_on_a); + ctx_a.store_channel(&chan_end_path_on_a, chan_end_on_a)?; + + ctx_a.increase_channel_counter(); + + // Initialize send, recv, and ack sequence numbers. + let seq_send_path = SeqSendPath::new(&msg.port_id_on_a, &chan_id_on_a); + ctx_a.store_next_sequence_send(&seq_send_path, 1.into())?; + + let seq_recv_path = SeqRecvPath::new(&msg.port_id_on_a, &chan_id_on_a); + ctx_a.store_next_sequence_recv(&seq_recv_path, 1.into())?; + + let seq_ack_path = SeqAckPath::new(&msg.port_id_on_a, &chan_id_on_a); + ctx_a.store_next_sequence_ack(&seq_ack_path, 1.into())?; + } + + // emit events and logs + { + ctx_a.log_message(format!( + "success: channel open init with channel identifier: {chan_id_on_a}" + )); + let core_event = IbcEvent::OpenInitChannel(OpenInit::new( + msg.port_id_on_a.clone(), + chan_id_on_a.clone(), + msg.port_id_on_b, + conn_id_on_a, + version, + )); + ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_a.emit_ibc_event(core_event); + + for module_event in extras.events { + ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); + } + + for log_message in extras.log { + ctx_a.log_message(log_message); + } + } + + Ok(()) +} -pub fn validate(ctx_a: &Ctx, msg: &MsgChannelOpenInit) -> Result<(), ContextError> +fn validate(ctx_a: &Ctx, msg: &MsgChannelOpenInit) -> Result<(), ContextError> where Ctx: ValidationContext, { @@ -30,11 +138,9 @@ where #[cfg(test)] mod tests { - use crate::prelude::*; + use super::*; use rstest::*; - use test_log::test; - use crate::clients::ics07_tendermint::client_type as tm_client_type; use crate::core::ics02_client::height::Height; use crate::core::ics03_connection::connection::ConnectionEnd; @@ -46,20 +152,32 @@ mod tests { use crate::core::ics04_channel::msgs::chan_open_init::MsgChannelOpenInit; use crate::core::ics24_host::identifier::ClientId; use crate::core::ics24_host::identifier::ConnectionId; + + use crate::applications::transfer::MODULE_ID_STR; use crate::mock::context::MockContext; + use crate::test_utils::DummyTransferModule; + use test_log::test; pub struct Fixture { - pub context: MockContext, + pub ctx: MockContext, + pub module_id: ModuleId, pub msg: MsgChannelOpenInit, } #[fixture] fn fixture() -> Fixture { let msg = MsgChannelOpenInit::try_from(get_dummy_raw_msg_chan_open_init(None)).unwrap(); - let default_context = MockContext::default(); + + let mut default_ctx = MockContext::default(); + let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); + let module = DummyTransferModule::new(); + default_ctx.add_route(module_id.clone(), module).unwrap(); let msg_conn_init = MsgConnectionOpenInit::new_dummy(); + let client_id_on_a = ClientId::new(tm_client_type(), 0).unwrap(); + let client_height = Height::new(0, 10).unwrap(); + let conn_end_on_a = ConnectionEnd::new( ConnectionState::Init, msg_conn_init.client_id_on_a.clone(), @@ -69,14 +187,15 @@ mod tests { ) .unwrap(); - let client_id_on_a = ClientId::new(tm_client_type(), 0).unwrap(); - let client_height = Height::new(0, 10).unwrap(); - - let context = default_context + let ctx = default_ctx .with_client(&client_id_on_a, client_height) .with_connection(ConnectionId::default(), conn_end_on_a); - Fixture { context, msg } + Fixture { + ctx, + module_id, + msg, + } } #[rstest] @@ -92,25 +211,45 @@ mod tests { } #[rstest] - fn chan_open_init_success_happy_path(fixture: Fixture) { - let Fixture { context, msg } = fixture; + fn chan_open_init_validate_happy_path(fixture: Fixture) { + let Fixture { ctx, msg, .. } = fixture; - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!(res.is_ok(), "Validation succeeds; good parameters") } #[rstest] - fn chan_open_init_success_counterparty_chan_id_set(fixture: Fixture) { - let Fixture { context, .. } = fixture; + fn chan_open_init_validate_counterparty_chan_id_set(fixture: Fixture) { + let Fixture { ctx, .. } = fixture; let msg = MsgChannelOpenInit::try_from(get_dummy_raw_msg_chan_open_init(None)).unwrap(); - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!( res.is_ok(), "Validation succeeds even if counterparty channel id is set by relayer" ) } + + #[rstest] + fn chan_open_init_execute_happy_path(fixture: Fixture) { + let Fixture { + mut ctx, + module_id, + msg, + } = fixture; + + let res = chan_open_init_execute(&mut ctx, module_id, msg); + + assert!(res.is_ok(), "Execution succeeds; good parameters"); + + assert_eq!(ctx.events.len(), 2); + assert!(matches!( + ctx.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(ctx.events[1], IbcEvent::OpenInitChannel(_))); + } } diff --git a/crates/ibc/src/core/ics04_channel/handler/chan_open_try.rs b/crates/ibc/src/core/ics04_channel/handler/chan_open_try.rs index fc90f04fb..819b8cc46 100644 --- a/crates/ibc/src/core/ics04_channel/handler/chan_open_try.rs +++ b/crates/ibc/src/core/ics04_channel/handler/chan_open_try.rs @@ -1,17 +1,128 @@ //! Protocol logic specific to ICS4 messages of type `MsgChannelOpenTry`. +use crate::prelude::*; use ibc_proto::protobuf::Protobuf; +use crate::core::events::{IbcEvent, MessageEvent}; use crate::core::ics03_connection::connection::State as ConnectionState; +use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{ChannelEnd, Counterparty, State as ChannelState}; use crate::core::ics04_channel::error::ChannelError; +use crate::core::ics04_channel::events::OpenTry; use crate::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; +use crate::core::ics24_host::identifier::ChannelId; use crate::core::ics24_host::path::Path; use crate::core::ics24_host::path::{ChannelEndPath, ClientConsensusStatePath}; -use crate::core::{ContextError, ValidationContext}; -use crate::prelude::*; +use crate::core::ics24_host::path::{SeqAckPath, SeqRecvPath, SeqSendPath}; +use crate::core::router::ModuleId; +use crate::core::{ContextError, ExecutionContext, ValidationContext}; + +pub(crate) fn chan_open_try_validate( + ctx_b: &ValCtx, + module_id: ModuleId, + msg: MsgChannelOpenTry, +) -> Result<(), ContextError> +where + ValCtx: ValidationContext, +{ + validate(ctx_b, &msg)?; + + let chan_id_on_b = ChannelId::new(ctx_b.channel_counter()?); + + let module = ctx_b + .get_route(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + module.on_chan_open_try_validate( + msg.ordering, + &msg.connection_hops_on_b, + &msg.port_id_on_b, + &chan_id_on_b, + &Counterparty::new(msg.port_id_on_a.clone(), Some(msg.chan_id_on_a.clone())), + &msg.version_supported_on_a, + )?; + + Ok(()) +} + +pub(crate) fn chan_open_try_execute( + ctx_b: &mut ExecCtx, + module_id: ModuleId, + msg: MsgChannelOpenTry, +) -> Result<(), ContextError> +where + ExecCtx: ExecutionContext, +{ + let chan_id_on_b = ChannelId::new(ctx_b.channel_counter()?); + let module = ctx_b + .get_route_mut(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + + let (extras, version) = module.on_chan_open_try_execute( + msg.ordering, + &msg.connection_hops_on_b, + &msg.port_id_on_b, + &chan_id_on_b, + &Counterparty::new(msg.port_id_on_a.clone(), Some(msg.chan_id_on_a.clone())), + &msg.version_supported_on_a, + )?; + + let conn_id_on_b = msg.connection_hops_on_b[0].clone(); + + // state changes + { + let chan_end_on_b = ChannelEnd::new( + State::TryOpen, + msg.ordering, + Counterparty::new(msg.port_id_on_a.clone(), Some(msg.chan_id_on_a.clone())), + msg.connection_hops_on_b.clone(), + version.clone(), + )?; + + let chan_end_path_on_b = ChannelEndPath::new(&msg.port_id_on_b, &chan_id_on_b); + ctx_b.store_channel(&chan_end_path_on_b, chan_end_on_b)?; + ctx_b.increase_channel_counter(); + + // Initialize send, recv, and ack sequence numbers. + let seq_send_path = SeqSendPath::new(&msg.port_id_on_b, &chan_id_on_b); + ctx_b.store_next_sequence_send(&seq_send_path, 1.into())?; + + let seq_recv_path = SeqRecvPath::new(&msg.port_id_on_b, &chan_id_on_b); + ctx_b.store_next_sequence_recv(&seq_recv_path, 1.into())?; + + let seq_ack_path = SeqAckPath::new(&msg.port_id_on_b, &chan_id_on_b); + ctx_b.store_next_sequence_ack(&seq_ack_path, 1.into())?; + } + + // emit events and logs + { + ctx_b.log_message(format!( + "success: channel open try with channel identifier: {chan_id_on_b}" + )); + + let core_event = IbcEvent::OpenTryChannel(OpenTry::new( + msg.port_id_on_b.clone(), + chan_id_on_b.clone(), + msg.port_id_on_a.clone(), + msg.chan_id_on_a.clone(), + conn_id_on_b, + version, + )); + ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_b.emit_ibc_event(core_event); + + for module_event in extras.events { + ctx_b.emit_ibc_event(IbcEvent::Module(module_event)); + } + + for log_message in extras.log { + ctx_b.log_message(log_message); + } + } + + Ok(()) +} -pub fn validate(ctx_b: &Ctx, msg: &MsgChannelOpenTry) -> Result<(), ContextError> +fn validate(ctx_b: &Ctx, msg: &MsgChannelOpenTry) -> Result<(), ContextError> where Ctx: ValidationContext, { @@ -74,12 +185,10 @@ where #[cfg(test)] mod tests { - use crate::prelude::*; + use super::*; use rstest::*; use test_log::test; - use crate::core::ics04_channel::handler::chan_open_try; - use crate::core::ics03_connection::connection::ConnectionEnd; use crate::core::ics03_connection::connection::Counterparty as ConnectionCounterparty; use crate::core::ics03_connection::connection::State as ConnectionState; @@ -89,12 +198,16 @@ mod tests { use crate::core::ics04_channel::msgs::chan_open_try::MsgChannelOpenTry; use crate::core::ics24_host::identifier::{ClientId, ConnectionId}; use crate::core::timestamp::ZERO_DURATION; + use crate::Height; + + use crate::applications::transfer::MODULE_ID_STR; use crate::mock::client_state::client_type as mock_client_type; use crate::mock::context::MockContext; - use crate::Height; + use crate::test_utils::DummyTransferModule; pub struct Fixture { - pub context: MockContext, + pub ctx: MockContext, + pub module_id: ModuleId, pub msg: MsgChannelOpenTry, pub client_id_on_b: ClientId, pub conn_id_on_b: ConnectionId, @@ -126,10 +239,14 @@ mod tests { let hops = vec![conn_id_on_b.clone()]; msg.connection_hops_on_b = hops; - let context = MockContext::default(); + let mut ctx = MockContext::default(); + let module = DummyTransferModule::new(); + let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); + ctx.add_route(module_id.clone(), module).unwrap(); Fixture { - context, + ctx, + module_id, msg, client_id_on_b, conn_id_on_b, @@ -140,9 +257,9 @@ mod tests { #[rstest] fn chan_open_try_fail_no_connection(fixture: Fixture) { - let Fixture { context, msg, .. } = fixture; + let Fixture { ctx, msg, .. } = fixture; - let res = chan_open_try::validate(&context, &msg); + let res = validate(&ctx, &msg); assert!( res.is_err(), @@ -153,15 +270,15 @@ mod tests { #[rstest] fn chan_open_try_fail_no_client_state(fixture: Fixture) { let Fixture { - context, + ctx, msg, conn_id_on_b, conn_end_on_b, .. } = fixture; - let context = context.with_connection(conn_id_on_b, conn_end_on_b); + let ctx = ctx.with_connection(conn_id_on_b, conn_end_on_b); - let res = chan_open_try::validate(&context, &msg); + let res = validate(&ctx, &msg); assert!( res.is_err(), @@ -170,9 +287,9 @@ mod tests { } #[rstest] - fn chan_open_try_happy_path(fixture: Fixture) { + fn chan_open_try_validate_happy_path(fixture: Fixture) { let Fixture { - context, + ctx, msg, client_id_on_b, conn_id_on_b, @@ -181,12 +298,41 @@ mod tests { .. } = fixture; - let context = context + let ctx = ctx .with_client(&client_id_on_b, Height::new(0, proof_height).unwrap()) .with_connection(conn_id_on_b, conn_end_on_b); - let res = chan_open_try::validate(&context, &msg); + let res = validate(&ctx, &msg); assert!(res.is_ok(), "Validation success: happy path") } + + #[rstest] + fn chan_open_try_execute_happy_path(fixture: Fixture) { + let Fixture { + ctx, + module_id, + msg, + client_id_on_b, + conn_id_on_b, + conn_end_on_b, + proof_height, + .. + } = fixture; + + let mut ctx = ctx + .with_client(&client_id_on_b, Height::new(0, proof_height).unwrap()) + .with_connection(conn_id_on_b, conn_end_on_b); + + let res = chan_open_try_execute(&mut ctx, module_id, msg); + + assert!(res.is_ok(), "Execution success: happy path"); + + assert_eq!(ctx.events.len(), 2); + assert!(matches!( + ctx.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(ctx.events[1], IbcEvent::OpenTryChannel(_))); + } } diff --git a/crates/ibc/src/core/ics04_channel/handler/recv_packet.rs b/crates/ibc/src/core/ics04_channel/handler/recv_packet.rs index 526065e03..d622122f1 100644 --- a/crates/ibc/src/core/ics04_channel/handler/recv_packet.rs +++ b/crates/ibc/src/core/ics04_channel/handler/recv_packet.rs @@ -1,19 +1,150 @@ +use crate::prelude::*; + +use crate::core::events::{IbcEvent, MessageEvent}; use crate::core::ics03_connection::connection::State as ConnectionState; use crate::core::ics03_connection::delay::verify_conn_delay_passed; use crate::core::ics04_channel::channel::{Counterparty, Order, State as ChannelState}; -use crate::core::ics04_channel::commitment::compute_packet_commitment; +use crate::core::ics04_channel::commitment::{compute_ack_commitment, compute_packet_commitment}; use crate::core::ics04_channel::error::ChannelError; use crate::core::ics04_channel::error::PacketError; +use crate::core::ics04_channel::events::{ReceivePacket, WriteAcknowledgement}; use crate::core::ics04_channel::msgs::recv_packet::MsgRecvPacket; +use crate::core::ics04_channel::packet::Receipt; use crate::core::ics24_host::path::Path; use crate::core::ics24_host::path::{ AckPath, ChannelEndPath, ClientConsensusStatePath, CommitmentPath, ReceiptPath, SeqRecvPath, }; +use crate::core::router::ModuleId; use crate::core::timestamp::Expiry; +use crate::core::{ContextError, ExecutionContext, ValidationContext}; + +pub(crate) fn recv_packet_validate( + ctx_b: &ValCtx, + msg: MsgRecvPacket, +) -> Result<(), ContextError> +where + ValCtx: ValidationContext, +{ + // Note: this contains the validation for `write_acknowledgement` as well. + validate(ctx_b, &msg) + + // nothing to validate with the module, since `onRecvPacket` cannot fail. + // If any error occurs, then an "error acknowledgement" must be returned. +} + +pub(crate) fn recv_packet_execute( + ctx_b: &mut ExecCtx, + module_id: ModuleId, + msg: MsgRecvPacket, +) -> Result<(), ContextError> +where + ExecCtx: ExecutionContext, +{ + let chan_end_path_on_b = + ChannelEndPath::new(&msg.packet.port_id_on_b, &msg.packet.chan_id_on_b); + let chan_end_on_b = ctx_b.channel_end(&chan_end_path_on_b)?; + + // Check if another relayer already relayed the packet. + // We don't want to fail the transaction in this case. + { + let packet_already_received = match chan_end_on_b.ordering { + // Note: ibc-go doesn't make the check for `Order::None` channels + Order::None => false, + Order::Unordered => { + let packet = msg.packet.clone(); + let receipt_path_on_b = + ReceiptPath::new(&packet.port_id_on_b, &packet.chan_id_on_b, packet.seq_on_a); + ctx_b.get_packet_receipt(&receipt_path_on_b).is_ok() + } + Order::Ordered => { + let seq_recv_path_on_b = + SeqRecvPath::new(&msg.packet.port_id_on_b, &msg.packet.chan_id_on_b); + let next_seq_recv = ctx_b.get_next_sequence_recv(&seq_recv_path_on_b)?; + + // the sequence number has already been incremented, so + // another relayer already relayed the packet + msg.packet.seq_on_a < next_seq_recv + } + }; + + if packet_already_received { + return Ok(()); + } + } + + let module = ctx_b + .get_route_mut(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + + let (extras, acknowledgement) = module.on_recv_packet_execute(&msg.packet, &msg.signer); + + // state changes + { + // `recvPacket` core handler state changes + match chan_end_on_b.ordering { + Order::Unordered => { + let receipt_path_on_b = ReceiptPath { + port_id: msg.packet.port_id_on_b.clone(), + channel_id: msg.packet.chan_id_on_b.clone(), + sequence: msg.packet.seq_on_a, + }; + + ctx_b.store_packet_receipt(&receipt_path_on_b, Receipt::Ok)?; + } + Order::Ordered => { + let seq_recv_path_on_b = + SeqRecvPath::new(&msg.packet.port_id_on_b, &msg.packet.chan_id_on_b); + let next_seq_recv = ctx_b.get_next_sequence_recv(&seq_recv_path_on_b)?; + ctx_b.store_next_sequence_recv(&seq_recv_path_on_b, next_seq_recv.increment())?; + } + _ => {} + } + let ack_path_on_b = AckPath::new( + &msg.packet.port_id_on_b, + &msg.packet.chan_id_on_b, + msg.packet.seq_on_a, + ); + // `writeAcknowledgement` handler state changes + ctx_b.store_packet_acknowledgement( + &ack_path_on_b, + compute_ack_commitment(&acknowledgement), + )?; + } + + // emit events and logs + { + ctx_b.log_message("success: packet receive".to_string()); + ctx_b.log_message("success: packet write acknowledgement".to_string()); + + let conn_id_on_b = &chan_end_on_b.connection_hops()[0]; + let event = IbcEvent::ReceivePacket(ReceivePacket::new( + msg.packet.clone(), + chan_end_on_b.ordering, + conn_id_on_b.clone(), + )); + ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_b.emit_ibc_event(event); + let event = IbcEvent::WriteAcknowledgement(WriteAcknowledgement::new( + msg.packet, + acknowledgement, + conn_id_on_b.clone(), + )); + ctx_b.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_b.emit_ibc_event(event); + + for module_event in extras.events { + ctx_b.emit_ibc_event(IbcEvent::Module(module_event)); + } + + for log_message in extras.log { + ctx_b.log_message(log_message); + } + } -use crate::core::{ContextError, ValidationContext}; + Ok(()) +} -pub fn validate(ctx_b: &Ctx, msg: &MsgRecvPacket) -> Result<(), ContextError> +fn validate(ctx_b: &Ctx, msg: &MsgRecvPacket) -> Result<(), ContextError> where Ctx: ValidationContext, { @@ -148,12 +279,8 @@ where #[cfg(test)] mod tests { - use crate::core::ics04_channel::handler::recv_packet::validate; - use crate::core::ExecutionContext; - use crate::prelude::*; - use crate::Height; + use super::*; use rstest::*; - use test_log::test; use crate::core::ics03_connection::connection::ConnectionEnd; @@ -168,12 +295,16 @@ mod tests { use crate::core::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; use crate::core::timestamp::Timestamp; use crate::core::timestamp::ZERO_DURATION; + use crate::Height; + use crate::mock::context::MockContext; use crate::mock::ics18_relayer::context::RelayerContext; use crate::test_utils::get_dummy_account_id; + use crate::{applications::transfer::MODULE_ID_STR, test_utils::DummyTransferModule}; pub struct Fixture { pub context: MockContext, + pub module_id: ModuleId, pub client_height: Height, pub host_height: Height, pub msg: MsgRecvPacket, @@ -183,7 +314,11 @@ mod tests { #[fixture] fn fixture() -> Fixture { - let context = MockContext::default(); + let mut context = MockContext::default(); + + let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); + let module = DummyTransferModule::new(); + context.add_route(module_id.clone(), module).unwrap(); let host_height = context.query_latest_height().unwrap().increment(); @@ -220,6 +355,7 @@ mod tests { Fixture { context, + module_id, client_height, host_height, msg, @@ -241,7 +377,7 @@ mod tests { } #[rstest] - fn recv_packet_happy_path(fixture: Fixture) { + fn recv_packet_validate_happy_path(fixture: Fixture) { let Fixture { context, msg, @@ -340,4 +476,37 @@ mod tests { "recv_packet validation should fail when the packet has timed out" ) } + + #[rstest] + fn recv_packet_execute_happy_path(fixture: Fixture) { + let Fixture { + context, + module_id, + msg, + conn_end_on_b, + chan_end_on_b, + client_height, + .. + } = fixture; + let mut ctx = context + .with_client(&ClientId::default(), client_height) + .with_connection(ConnectionId::default(), conn_end_on_b) + .with_channel(PortId::default(), ChannelId::default(), chan_end_on_b); + + let res = recv_packet_execute(&mut ctx, module_id, msg); + + assert!(res.is_ok()); + + assert_eq!(ctx.events.len(), 4); + assert!(matches!( + &ctx.events[0], + &IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(&ctx.events[1], &IbcEvent::ReceivePacket(_))); + assert!(matches!( + &ctx.events[2], + &IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(&ctx.events[3], &IbcEvent::WriteAcknowledgement(_))); + } } diff --git a/crates/ibc/src/core/ics04_channel/handler/send_packet.rs b/crates/ibc/src/core/ics04_channel/handler/send_packet.rs index 10807b938..f1367e637 100644 --- a/crates/ibc/src/core/ics04_channel/handler/send_packet.rs +++ b/crates/ibc/src/core/ics04_channel/handler/send_packet.rs @@ -1,3 +1,5 @@ +use crate::prelude::*; + use crate::core::events::IbcEvent; use crate::core::events::MessageEvent; use crate::core::ics04_channel::channel::Counterparty; @@ -13,7 +15,6 @@ use crate::core::ics24_host::path::CommitmentPath; use crate::core::ics24_host::path::SeqSendPath; use crate::core::timestamp::Expiry; use crate::core::ContextError; -use crate::prelude::*; /// Send the given packet, including all necessary validation. /// diff --git a/crates/ibc/src/core/ics04_channel/handler/timeout.rs b/crates/ibc/src/core/ics04_channel/handler/timeout.rs index fdd618581..91cf4bca1 100644 --- a/crates/ibc/src/core/ics04_channel/handler/timeout.rs +++ b/crates/ibc/src/core/ics04_channel/handler/timeout.rs @@ -1,20 +1,151 @@ +use crate::prelude::*; use prost::Message; +use crate::core::events::MessageEvent; use crate::core::ics03_connection::delay::verify_conn_delay_passed; use crate::core::ics04_channel::channel::State; use crate::core::ics04_channel::channel::{Counterparty, Order}; use crate::core::ics04_channel::commitment::compute_packet_commitment; use crate::core::ics04_channel::error::ChannelError; use crate::core::ics04_channel::error::PacketError; +use crate::core::ics04_channel::events::ChannelClosed; use crate::core::ics04_channel::msgs::timeout::MsgTimeout; +use crate::core::ics04_channel::msgs::timeout_on_close::MsgTimeoutOnClose; use crate::core::ics24_host::path::Path; use crate::core::ics24_host::path::{ ChannelEndPath, ClientConsensusStatePath, CommitmentPath, ReceiptPath, SeqRecvPath, }; -use crate::core::{ContextError, ValidationContext}; -use crate::prelude::*; +use crate::core::{ + events::IbcEvent, + ics04_channel::{events::TimeoutPacket, handler::timeout_on_close}, + router::ModuleId, +}; +use crate::core::{ContextError, ExecutionContext, ValidationContext}; + +pub(crate) enum TimeoutMsgType { + Timeout(MsgTimeout), + TimeoutOnClose(MsgTimeoutOnClose), +} -pub fn validate(ctx_a: &Ctx, msg: &MsgTimeout) -> Result<(), ContextError> +pub(crate) fn timeout_packet_validate( + ctx_a: &ValCtx, + module_id: ModuleId, + timeout_msg_type: TimeoutMsgType, +) -> Result<(), ContextError> +where + ValCtx: ValidationContext, +{ + match &timeout_msg_type { + TimeoutMsgType::Timeout(msg) => validate(ctx_a, msg), + TimeoutMsgType::TimeoutOnClose(msg) => timeout_on_close::validate(ctx_a, msg), + }?; + + let module = ctx_a + .get_route(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + + let (packet, signer) = match timeout_msg_type { + TimeoutMsgType::Timeout(msg) => (msg.packet, msg.signer), + TimeoutMsgType::TimeoutOnClose(msg) => (msg.packet, msg.signer), + }; + + module + .on_timeout_packet_validate(&packet, &signer) + .map_err(ContextError::PacketError) +} + +pub(crate) fn timeout_packet_execute( + ctx_a: &mut ExecCtx, + module_id: ModuleId, + timeout_msg_type: TimeoutMsgType, +) -> Result<(), ContextError> +where + ExecCtx: ExecutionContext, +{ + let (packet, signer) = match timeout_msg_type { + TimeoutMsgType::Timeout(msg) => (msg.packet, msg.signer), + TimeoutMsgType::TimeoutOnClose(msg) => (msg.packet, msg.signer), + }; + let chan_end_path_on_a = ChannelEndPath::new(&packet.port_id_on_a, &packet.chan_id_on_a); + let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?; + + // In all cases, this event is emitted + let event = IbcEvent::TimeoutPacket(TimeoutPacket::new(packet.clone(), chan_end_on_a.ordering)); + ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_a.emit_ibc_event(event); + + let commitment_path_on_a = + CommitmentPath::new(&packet.port_id_on_a, &packet.chan_id_on_a, packet.seq_on_a); + + // check if we're in the NO-OP case + if ctx_a.get_packet_commitment(&commitment_path_on_a).is_err() { + // This error indicates that the timeout has already been relayed + // or there is a misconfigured relayer attempting to prove a timeout + // for a packet never sent. Core IBC will treat this error as a no-op in order to + // prevent an entire relay transaction from failing and consuming unnecessary fees. + return Ok(()); + }; + + let module = ctx_a + .get_route_mut(&module_id) + .ok_or(ChannelError::RouteNotFound)?; + + let (extras, cb_result) = module.on_timeout_packet_execute(&packet, &signer); + + cb_result?; + + // apply state changes + let chan_end_on_a = { + let commitment_path_on_a = CommitmentPath { + port_id: packet.port_id_on_a.clone(), + channel_id: packet.chan_id_on_a.clone(), + sequence: packet.seq_on_a, + }; + ctx_a.delete_packet_commitment(&commitment_path_on_a)?; + + if let Order::Ordered = chan_end_on_a.ordering { + let mut chan_end_on_a = chan_end_on_a; + chan_end_on_a.state = State::Closed; + ctx_a.store_channel(&chan_end_path_on_a, chan_end_on_a.clone())?; + + chan_end_on_a + } else { + chan_end_on_a + } + }; + + // emit events and logs + { + ctx_a.log_message("success: packet timeout".to_string()); + + if let Order::Ordered = chan_end_on_a.ordering { + let conn_id_on_a = chan_end_on_a.connection_hops()[0].clone(); + + let event = IbcEvent::ChannelClosed(ChannelClosed::new( + packet.port_id_on_a.clone(), + packet.chan_id_on_a.clone(), + chan_end_on_a.counterparty().port_id.clone(), + chan_end_on_a.counterparty().channel_id.clone(), + conn_id_on_a, + chan_end_on_a.ordering, + )); + ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel)); + ctx_a.emit_ibc_event(event); + } + + for module_event in extras.events { + ctx_a.emit_ibc_event(IbcEvent::Module(module_event)); + } + + for log_message in extras.log { + ctx_a.log_message(log_message); + } + } + + Ok(()) +} + +fn validate(ctx_a: &Ctx, msg: &MsgTimeout) -> Result<(), ContextError> where Ctx: ValidationContext, { @@ -142,10 +273,7 @@ where #[cfg(test)] mod tests { - use crate::core::ics04_channel::commitment::compute_packet_commitment; - use crate::core::timestamp::Timestamp; - use crate::core::ExecutionContext; - use crate::prelude::*; + use super::*; use rstest::*; use crate::core::ics02_client::height::Height; @@ -160,24 +288,35 @@ mod tests { use crate::core::ics04_channel::msgs::timeout::MsgTimeout; use crate::core::ics04_channel::Version; use crate::core::ics24_host::identifier::{ChannelId, ClientId, ConnectionId, PortId}; + use crate::core::timestamp::Timestamp; use crate::core::timestamp::ZERO_DURATION; - use crate::mock::context::MockContext; - pub struct Fixture { - pub context: MockContext, - pub client_height: Height, - pub msg: MsgTimeout, - pub packet_commitment: PacketCommitment, - pub conn_end_on_a: ConnectionEnd, - pub chan_end_on_a_unordered: ChannelEnd, - pub chan_end_on_a_ordered: ChannelEnd, + use crate::applications::transfer::MODULE_ID_STR; + use crate::mock::context::MockContext; + use crate::test_utils::DummyTransferModule; + + struct Fixture { + ctx: MockContext, + client_height: Height, + module_id: ModuleId, + msg: MsgTimeout, + packet_commitment: PacketCommitment, + conn_end_on_a: ConnectionEnd, + chan_end_on_a_ordered: ChannelEnd, + chan_end_on_a_unordered: ChannelEnd, } #[fixture] fn fixture() -> Fixture { - let context = MockContext::default(); + let client_height = Height::new(0, 2).unwrap(); + let mut ctx = MockContext::default().with_client(&ClientId::default(), client_height); let client_height = Height::new(0, 2).unwrap(); + + let module_id: ModuleId = ModuleId::new(MODULE_ID_STR.to_string()); + let module = DummyTransferModule::new(); + ctx.add_route(module_id.clone(), module).unwrap(); + let msg_proof_height = 2; let msg_timeout_height = 5; let timeout_timestamp = Timestamp::now().nanoseconds(); @@ -188,6 +327,7 @@ mod tests { timeout_timestamp, )) .unwrap(); + let packet = msg.packet.clone(); let packet_commitment = compute_packet_commitment( @@ -222,26 +362,27 @@ mod tests { .unwrap(); Fixture { - context, + ctx, client_height, + module_id, msg, packet_commitment, conn_end_on_a, - chan_end_on_a_unordered, chan_end_on_a_ordered, + chan_end_on_a_unordered, } } #[rstest] fn timeout_fail_no_channel(fixture: Fixture) { let Fixture { - context, + ctx, msg, client_height, .. } = fixture; - let context = context.with_client(&ClientId::default(), client_height); - let res = validate(&context, &msg); + let ctx = ctx.with_client(&ClientId::default(), client_height); + let res = validate(&ctx, &msg); assert!( res.is_err(), @@ -252,7 +393,7 @@ mod tests { #[rstest] fn timeout_fail_no_consensus_state_for_height(fixture: Fixture) { let Fixture { - context, + ctx, msg, chan_end_on_a_unordered, conn_end_on_a, @@ -262,7 +403,7 @@ mod tests { let packet = msg.packet.clone(); - let context = context + let ctx = ctx .with_channel( PortId::default(), ChannelId::default(), @@ -276,7 +417,7 @@ mod tests { packet_commitment, ); - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!( res.is_err(), @@ -287,7 +428,7 @@ mod tests { #[rstest] fn timeout_fail_proof_timeout_not_reached(fixture: Fixture) { let Fixture { - context, + ctx, mut msg, chan_end_on_a_unordered, conn_end_on_a, @@ -307,7 +448,7 @@ mod tests { let packet = msg.packet.clone(); - let mut context = context + let mut ctx = ctx .with_client(&ClientId::default(), client_height) .with_connection(ConnectionId::default(), conn_end_on_a) .with_channel( @@ -322,22 +463,20 @@ mod tests { packet_commitment, ); - context - .store_update_time( - ClientId::default(), - client_height, - Timestamp::from_nanoseconds(5).unwrap(), - ) - .unwrap(); - context - .store_update_height( - ClientId::default(), - client_height, - Height::new(0, 4).unwrap(), - ) - .unwrap(); + ctx.store_update_time( + ClientId::default(), + client_height, + Timestamp::from_nanoseconds(5).unwrap(), + ) + .unwrap(); + ctx.store_update_height( + ClientId::default(), + client_height, + Height::new(0, 4).unwrap(), + ) + .unwrap(); - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!( res.is_err(), @@ -349,13 +488,13 @@ mod tests { #[rstest] fn timeout_success_no_packet_commitment(fixture: Fixture) { let Fixture { - context, + ctx, msg, conn_end_on_a, chan_end_on_a_unordered, .. } = fixture; - let context = context + let ctx = ctx .with_channel( PortId::default(), ChannelId::default(), @@ -363,7 +502,7 @@ mod tests { ) .with_connection(ConnectionId::default(), conn_end_on_a); - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!( res.is_ok(), @@ -372,9 +511,9 @@ mod tests { } #[rstest] - fn timeout_success_unordered_channel(fixture: Fixture) { + fn timeout_unordered_channel_validate(fixture: Fixture) { let Fixture { - context, + ctx, msg, chan_end_on_a_unordered, conn_end_on_a, @@ -385,7 +524,7 @@ mod tests { let packet = msg.packet.clone(); - let mut context = context + let mut ctx = ctx .with_client(&ClientId::default(), client_height) .with_connection(ConnectionId::default(), conn_end_on_a) .with_channel( @@ -400,30 +539,28 @@ mod tests { packet_commitment, ); - context - .store_update_time( - ClientId::default(), - client_height, - Timestamp::from_nanoseconds(1000).unwrap(), - ) - .unwrap(); - context - .store_update_height( - ClientId::default(), - client_height, - Height::new(0, 5).unwrap(), - ) - .unwrap(); + ctx.store_update_time( + ClientId::default(), + client_height, + Timestamp::from_nanoseconds(1000).unwrap(), + ) + .unwrap(); + ctx.store_update_height( + ClientId::default(), + client_height, + Height::new(0, 5).unwrap(), + ) + .unwrap(); - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!(res.is_ok(), "Good parameters for unordered channels") } #[rstest] - fn timeout_success_ordered_channel(fixture: Fixture) { + fn timeout_ordered_channel_validate(fixture: Fixture) { let Fixture { - context, + ctx, msg, chan_end_on_a_ordered, conn_end_on_a, @@ -434,7 +571,7 @@ mod tests { let packet = msg.packet.clone(); - let mut context = context + let mut ctx = ctx .with_client(&ClientId::default(), client_height) .with_connection(ConnectionId::default(), conn_end_on_a) .with_channel( @@ -449,23 +586,102 @@ mod tests { packet_commitment, ); - context - .store_update_time( - ClientId::default(), - client_height, - Timestamp::from_nanoseconds(1000).unwrap(), - ) - .unwrap(); - context - .store_update_height( - ClientId::default(), - client_height, - Height::new(0, 4).unwrap(), - ) - .unwrap(); + ctx.store_update_time( + ClientId::default(), + client_height, + Timestamp::from_nanoseconds(1000).unwrap(), + ) + .unwrap(); + ctx.store_update_height( + ClientId::default(), + client_height, + Height::new(0, 4).unwrap(), + ) + .unwrap(); - let res = validate(&context, &msg); + let res = validate(&ctx, &msg); assert!(res.is_ok(), "Good parameters for unordered channels") } + + #[rstest] + fn timeout_unordered_chan_execute(fixture: Fixture) { + let Fixture { + ctx, + module_id, + msg, + packet_commitment, + conn_end_on_a, + chan_end_on_a_unordered, + .. + } = fixture; + let mut ctx = ctx + .with_channel( + PortId::default(), + ChannelId::default(), + chan_end_on_a_unordered, + ) + .with_connection(ConnectionId::default(), conn_end_on_a) + .with_packet_commitment( + msg.packet.port_id_on_a.clone(), + msg.packet.chan_id_on_a.clone(), + msg.packet.seq_on_a, + packet_commitment, + ); + + let res = timeout_packet_execute(&mut ctx, module_id, TimeoutMsgType::Timeout(msg)); + + assert!(res.is_ok()); + + // Unordered channels only emit one event + assert_eq!(ctx.events.len(), 2); + assert!(matches!( + ctx.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(ctx.events[1], IbcEvent::TimeoutPacket(_))); + } + + #[rstest] + fn timeout_ordered_chan_execute(fixture: Fixture) { + let Fixture { + ctx, + module_id, + msg, + packet_commitment, + conn_end_on_a, + chan_end_on_a_ordered, + .. + } = fixture; + let mut ctx = ctx + .with_channel( + PortId::default(), + ChannelId::default(), + chan_end_on_a_ordered, + ) + .with_connection(ConnectionId::default(), conn_end_on_a) + .with_packet_commitment( + msg.packet.port_id_on_a.clone(), + msg.packet.chan_id_on_a.clone(), + msg.packet.seq_on_a, + packet_commitment, + ); + + let res = timeout_packet_execute(&mut ctx, module_id, TimeoutMsgType::Timeout(msg)); + + assert!(res.is_ok()); + + // Ordered channels emit 2 events + assert_eq!(ctx.events.len(), 4); + assert!(matches!( + ctx.events[0], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(ctx.events[1], IbcEvent::TimeoutPacket(_))); + assert!(matches!( + ctx.events[2], + IbcEvent::Message(MessageEvent::Channel) + )); + assert!(matches!(ctx.events[3], IbcEvent::ChannelClosed(_))); + } } diff --git a/crates/ibc/src/core/ics04_channel/handler/timeout_on_close.rs b/crates/ibc/src/core/ics04_channel/handler/timeout_on_close.rs index efaaea48e..d7540375a 100644 --- a/crates/ibc/src/core/ics04_channel/handler/timeout_on_close.rs +++ b/crates/ibc/src/core/ics04_channel/handler/timeout_on_close.rs @@ -1,3 +1,4 @@ +use crate::prelude::*; use ibc_proto::protobuf::Protobuf; use prost::Message; @@ -12,7 +13,6 @@ use crate::core::ics24_host::path::{ ChannelEndPath, ClientConsensusStatePath, CommitmentPath, ReceiptPath, SeqRecvPath, }; use crate::core::{ContextError, ValidationContext}; -use crate::prelude::*; pub fn validate(ctx_a: &Ctx, msg: &MsgTimeoutOnClose) -> Result<(), ContextError> where