ibc_core_channel/handler/
timeout.rs

1use ibc_core_channel_types::channel::{Counterparty, Order, State};
2use ibc_core_channel_types::commitment::compute_packet_commitment;
3use ibc_core_channel_types::error::ChannelError;
4use ibc_core_channel_types::events::{ChannelClosed, TimeoutPacket};
5use ibc_core_channel_types::msgs::{MsgTimeout, MsgTimeoutOnClose};
6use ibc_core_client::context::prelude::*;
7use ibc_core_connection::delay::verify_conn_delay_passed;
8use ibc_core_handler_types::events::{IbcEvent, MessageEvent};
9use ibc_core_host::types::path::{
10    ChannelEndPath, ClientConsensusStatePath, CommitmentPath, Path, ReceiptPath, SeqRecvPath,
11};
12use ibc_core_host::{ExecutionContext, ValidationContext};
13use ibc_core_router::module::Module;
14use ibc_primitives::prelude::*;
15
16use super::timeout_on_close;
17
18pub enum TimeoutMsgType {
19    Timeout(MsgTimeout),
20    TimeoutOnClose(MsgTimeoutOnClose),
21}
22
23pub fn timeout_packet_validate<ValCtx>(
24    ctx_a: &ValCtx,
25    module: &dyn Module,
26    timeout_msg_type: TimeoutMsgType,
27) -> Result<(), ChannelError>
28where
29    ValCtx: ValidationContext,
30{
31    match &timeout_msg_type {
32        TimeoutMsgType::Timeout(msg) => validate(ctx_a, msg),
33        TimeoutMsgType::TimeoutOnClose(msg) => timeout_on_close::validate(ctx_a, msg),
34    }?;
35
36    let (packet, signer) = match timeout_msg_type {
37        TimeoutMsgType::Timeout(msg) => (msg.packet, msg.signer),
38        TimeoutMsgType::TimeoutOnClose(msg) => (msg.packet, msg.signer),
39    };
40
41    module.on_timeout_packet_validate(&packet, &signer)
42}
43
44pub fn timeout_packet_execute<ExecCtx>(
45    ctx_a: &mut ExecCtx,
46    module: &mut dyn Module,
47    timeout_msg_type: TimeoutMsgType,
48) -> Result<(), ChannelError>
49where
50    ExecCtx: ExecutionContext,
51{
52    let (packet, signer) = match timeout_msg_type {
53        TimeoutMsgType::Timeout(msg) => (msg.packet, msg.signer),
54        TimeoutMsgType::TimeoutOnClose(msg) => (msg.packet, msg.signer),
55    };
56    let chan_end_path_on_a = ChannelEndPath::new(&packet.port_id_on_a, &packet.chan_id_on_a);
57    let chan_end_on_a = ctx_a.channel_end(&chan_end_path_on_a)?;
58
59    // In all cases, this event is emitted
60    let event = IbcEvent::TimeoutPacket(TimeoutPacket::new(packet.clone(), chan_end_on_a.ordering));
61    ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel))?;
62    ctx_a.emit_ibc_event(event)?;
63
64    let commitment_path_on_a =
65        CommitmentPath::new(&packet.port_id_on_a, &packet.chan_id_on_a, packet.seq_on_a);
66
67    // check if we're in the NO-OP case
68    if ctx_a.get_packet_commitment(&commitment_path_on_a).is_err() {
69        // This error indicates that the timeout has already been relayed
70        // or there is a misconfigured relayer attempting to prove a timeout
71        // for a packet never sent. Core IBC will treat this error as a no-op in order to
72        // prevent an entire relay transaction from failing and consuming unnecessary fees.
73        return Ok(());
74    };
75
76    let (extras, cb_result) = module.on_timeout_packet_execute(&packet, &signer);
77
78    cb_result?;
79
80    // apply state changes
81    let chan_end_on_a = {
82        ctx_a.delete_packet_commitment(&commitment_path_on_a)?;
83
84        if let Order::Ordered = chan_end_on_a.ordering {
85            let mut chan_end_on_a = chan_end_on_a;
86            chan_end_on_a.state = State::Closed;
87            ctx_a.store_channel(&chan_end_path_on_a, chan_end_on_a.clone())?;
88
89            chan_end_on_a
90        } else {
91            chan_end_on_a
92        }
93    };
94
95    // emit events and logs
96    {
97        ctx_a.log_message("success: packet timeout".to_string())?;
98
99        if let Order::Ordered = chan_end_on_a.ordering {
100            let conn_id_on_a = chan_end_on_a.connection_hops()[0].clone();
101
102            let event = IbcEvent::ChannelClosed(ChannelClosed::new(
103                packet.port_id_on_a.clone(),
104                packet.chan_id_on_a.clone(),
105                chan_end_on_a.counterparty().port_id.clone(),
106                chan_end_on_a.counterparty().channel_id.clone(),
107                conn_id_on_a,
108                chan_end_on_a.ordering,
109            ));
110            ctx_a.emit_ibc_event(IbcEvent::Message(MessageEvent::Channel))?;
111            ctx_a.emit_ibc_event(event)?;
112        }
113
114        for module_event in extras.events {
115            ctx_a.emit_ibc_event(IbcEvent::Module(module_event))?;
116        }
117
118        for log_message in extras.log {
119            ctx_a.log_message(log_message)?;
120        }
121    }
122
123    Ok(())
124}
125
126fn validate<Ctx>(ctx_a: &Ctx, msg: &MsgTimeout) -> Result<(), ChannelError>
127where
128    Ctx: ValidationContext,
129{
130    ctx_a.validate_message_signer(&msg.signer)?;
131
132    let chan_end_on_a = ctx_a.channel_end(&ChannelEndPath::new(
133        &msg.packet.port_id_on_a,
134        &msg.packet.chan_id_on_a,
135    ))?;
136
137    chan_end_on_a.verify_state_matches(&State::Open)?;
138
139    let counterparty = Counterparty::new(
140        msg.packet.port_id_on_b.clone(),
141        Some(msg.packet.chan_id_on_b.clone()),
142    );
143
144    chan_end_on_a.verify_counterparty_matches(&counterparty)?;
145
146    let conn_id_on_a = chan_end_on_a.connection_hops()[0].clone();
147    let conn_end_on_a = ctx_a.connection_end(&conn_id_on_a)?;
148
149    //verify packet commitment
150    let commitment_path_on_a = CommitmentPath::new(
151        &msg.packet.port_id_on_a,
152        &msg.packet.chan_id_on_a,
153        msg.packet.seq_on_a,
154    );
155    let Ok(commitment_on_a) = ctx_a.get_packet_commitment(&commitment_path_on_a) else {
156        // This error indicates that the timeout has already been relayed
157        // or there is a misconfigured relayer attempting to prove a timeout
158        // for a packet never sent. Core IBC will treat this error as a no-op in order to
159        // prevent an entire relay transaction from failing and consuming unnecessary fees.
160        return Ok(());
161    };
162
163    let expected_commitment_on_a = compute_packet_commitment(
164        &msg.packet.data,
165        &msg.packet.timeout_height_on_b,
166        &msg.packet.timeout_timestamp_on_b,
167    );
168
169    if commitment_on_a != expected_commitment_on_a {
170        return Err(ChannelError::MismatchedPacketCommitment {
171            expected: expected_commitment_on_a,
172            actual: commitment_on_a,
173        });
174    }
175
176    // Verify proofs
177    {
178        let client_id_on_a = conn_end_on_a.client_id();
179        let client_val_ctx_a = ctx_a.get_client_validation_context();
180        let client_state_of_b_on_a = client_val_ctx_a.client_state(client_id_on_a)?;
181
182        client_state_of_b_on_a
183            .status(ctx_a.get_client_validation_context(), client_id_on_a)?
184            .verify_is_active()?;
185
186        client_state_of_b_on_a.validate_proof_height(msg.proof_height_on_b)?;
187
188        // check that timeout height or timeout timestamp has passed on the other end
189        let client_cons_state_path_on_a = ClientConsensusStatePath::new(
190            client_id_on_a.clone(),
191            msg.proof_height_on_b.revision_number(),
192            msg.proof_height_on_b.revision_height(),
193        );
194        let consensus_state_of_b_on_a =
195            client_val_ctx_a.consensus_state(&client_cons_state_path_on_a)?;
196        let timestamp_of_b = consensus_state_of_b_on_a.timestamp()?;
197
198        if !msg.packet.timed_out(&timestamp_of_b, msg.proof_height_on_b) {
199            return Err(ChannelError::InsufficientPacketTimeout {
200                timeout_height: msg.packet.timeout_height_on_b,
201                chain_height: msg.proof_height_on_b,
202                timeout_timestamp: msg.packet.timeout_timestamp_on_b,
203                chain_timestamp: timestamp_of_b,
204            });
205        }
206
207        verify_conn_delay_passed(ctx_a, msg.proof_height_on_b, &conn_end_on_a)?;
208
209        let next_seq_recv_verification_result = match chan_end_on_a.ordering {
210            Order::Ordered => {
211                if msg.packet.seq_on_a < msg.next_seq_recv_on_b {
212                    return Err(ChannelError::MismatchedPacketSequence {
213                        actual: msg.packet.seq_on_a,
214                        expected: msg.next_seq_recv_on_b,
215                    });
216                }
217                let seq_recv_path_on_b =
218                    SeqRecvPath::new(&msg.packet.port_id_on_b, &msg.packet.chan_id_on_b);
219
220                client_state_of_b_on_a.verify_membership(
221                    conn_end_on_a.counterparty().prefix(),
222                    &msg.proof_unreceived_on_b,
223                    consensus_state_of_b_on_a.root(),
224                    Path::SeqRecv(seq_recv_path_on_b),
225                    msg.packet.seq_on_a.to_vec(),
226                )
227            }
228            Order::Unordered => {
229                let receipt_path_on_b = ReceiptPath::new(
230                    &msg.packet.port_id_on_b,
231                    &msg.packet.chan_id_on_b,
232                    msg.packet.seq_on_a,
233                );
234
235                client_state_of_b_on_a.verify_non_membership(
236                    conn_end_on_a.counterparty().prefix(),
237                    &msg.proof_unreceived_on_b,
238                    consensus_state_of_b_on_a.root(),
239                    Path::Receipt(receipt_path_on_b),
240                )
241            }
242            Order::None => {
243                return Err(ChannelError::InvalidState {
244                    expected: "Channel ordering to not be None".to_string(),
245                    actual: chan_end_on_a.ordering.to_string(),
246                })
247            }
248        };
249
250        next_seq_recv_verification_result?;
251    }
252
253    Ok(())
254}