1use crate::{BridgedChainOf, Config, InboundLanes, OutboundLanes, Pallet, LOG_TARGET};
20
21use bp_messages::{
22 target_chain::MessageDispatch, BaseMessagesProofInfo, ChainWithMessages, InboundLaneData,
23 MessageNonce, MessagesCallInfo, ReceiveMessagesDeliveryProofInfo, ReceiveMessagesProofInfo,
24 UnrewardedRelayerOccupation,
25};
26use bp_runtime::{AccountIdOf, OwnedBridgeModule};
27use frame_support::{dispatch::CallableCallFor, traits::IsSubType};
28use sp_runtime::transaction_validity::TransactionValidity;
29
30pub struct CallHelper<T: Config<I>, I: 'static> {
32 _phantom_data: sp_std::marker::PhantomData<(T, I)>,
33}
34
35impl<T: Config<I>, I: 'static> CallHelper<T, I> {
36 pub fn was_successful(info: &MessagesCallInfo<T::LaneId>) -> bool {
43 match info {
44 MessagesCallInfo::ReceiveMessagesProof(info) => {
45 let inbound_lane_data = match InboundLanes::<T, I>::get(info.base.lane_id) {
46 Some(inbound_lane_data) => inbound_lane_data,
47 None => return false,
48 };
49 if info.base.bundled_range.is_empty() {
50 let post_occupation =
51 unrewarded_relayers_occupation::<T, I>(&inbound_lane_data);
52 return post_occupation.free_message_slots >
57 info.unrewarded_relayers.free_message_slots
58 }
59
60 inbound_lane_data.last_delivered_nonce() == *info.base.bundled_range.end()
61 },
62 MessagesCallInfo::ReceiveMessagesDeliveryProof(info) => {
63 let outbound_lane_data = match OutboundLanes::<T, I>::get(info.0.lane_id) {
64 Some(outbound_lane_data) => outbound_lane_data,
65 None => return false,
66 };
67 outbound_lane_data.latest_received_nonce == *info.0.bundled_range.end()
68 },
69 }
70 }
71}
72
73pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
75 IsSubType<CallableCallFor<Pallet<T, I>, T>>
76{
77 fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo<T::LaneId>>;
79
80 fn receive_messages_delivery_proof_info(
83 &self,
84 ) -> Option<ReceiveMessagesDeliveryProofInfo<T::LaneId>>;
85
86 fn call_info(&self) -> Option<MessagesCallInfo<T::LaneId>>;
89
90 fn call_info_for(&self, lane_id: T::LaneId) -> Option<MessagesCallInfo<T::LaneId>>;
93
94 fn check_obsolete_call(&self) -> TransactionValidity;
111}
112
113impl<
114 Call: IsSubType<CallableCallFor<Pallet<T, I>, T>>,
115 T: frame_system::Config<RuntimeCall = Call> + Config<I>,
116 I: 'static,
117 > CallSubType<T, I> for T::RuntimeCall
118{
119 fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo<T::LaneId>> {
120 if let Some(crate::Call::<T, I>::receive_messages_proof { ref proof, .. }) =
121 self.is_sub_type()
122 {
123 let inbound_lane_data = InboundLanes::<T, I>::get(proof.lane)?;
124
125 return Some(ReceiveMessagesProofInfo {
126 base: BaseMessagesProofInfo {
127 lane_id: proof.lane,
128 bundled_range: proof.nonces_start..=proof.nonces_end,
131 best_stored_nonce: inbound_lane_data.last_delivered_nonce(),
132 },
133 unrewarded_relayers: unrewarded_relayers_occupation::<T, I>(&inbound_lane_data),
134 })
135 }
136
137 None
138 }
139
140 fn receive_messages_delivery_proof_info(
141 &self,
142 ) -> Option<ReceiveMessagesDeliveryProofInfo<T::LaneId>> {
143 if let Some(crate::Call::<T, I>::receive_messages_delivery_proof {
144 ref proof,
145 ref relayers_state,
146 ..
147 }) = self.is_sub_type()
148 {
149 let outbound_lane_data = OutboundLanes::<T, I>::get(proof.lane)?;
150
151 return Some(ReceiveMessagesDeliveryProofInfo(BaseMessagesProofInfo {
152 lane_id: proof.lane,
153 bundled_range: outbound_lane_data.latest_received_nonce + 1..=
158 relayers_state.last_delivered_nonce,
159 best_stored_nonce: outbound_lane_data.latest_received_nonce,
160 }))
161 }
162
163 None
164 }
165
166 fn call_info(&self) -> Option<MessagesCallInfo<T::LaneId>> {
167 if let Some(info) = self.receive_messages_proof_info() {
168 return Some(MessagesCallInfo::ReceiveMessagesProof(info))
169 }
170
171 if let Some(info) = self.receive_messages_delivery_proof_info() {
172 return Some(MessagesCallInfo::ReceiveMessagesDeliveryProof(info))
173 }
174
175 None
176 }
177
178 fn call_info_for(&self, lane_id: T::LaneId) -> Option<MessagesCallInfo<T::LaneId>> {
179 self.call_info().filter(|info| {
180 let actual_lane_id = match info {
181 MessagesCallInfo::ReceiveMessagesProof(info) => info.base.lane_id,
182 MessagesCallInfo::ReceiveMessagesDeliveryProof(info) => info.0.lane_id,
183 };
184 actual_lane_id == lane_id
185 })
186 }
187
188 fn check_obsolete_call(&self) -> TransactionValidity {
189 let is_pallet_halted = Pallet::<T, I>::ensure_not_halted().is_err();
190 match self.call_info() {
191 Some(proof_info) if is_pallet_halted => {
192 tracing::trace!(
193 target: LOG_TARGET,
194 ?proof_info,
195 "Rejecting messages transaction on halted pallet"
196 );
197
198 return sp_runtime::transaction_validity::InvalidTransaction::Call.into()
199 },
200 Some(MessagesCallInfo::ReceiveMessagesProof(proof_info))
201 if proof_info
202 .is_obsolete(T::MessageDispatch::is_active(proof_info.base.lane_id)) =>
203 {
204 tracing::trace!(
205 target: LOG_TARGET,
206 ?proof_info,
207 "Rejecting obsolete messages delivery transaction"
208 );
209
210 return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
211 },
212 Some(MessagesCallInfo::ReceiveMessagesDeliveryProof(proof_info))
213 if proof_info.is_obsolete() =>
214 {
215 tracing::trace!(
216 target: LOG_TARGET,
217 ?proof_info,
218 "Rejecting obsolete messages confirmation transaction"
219 );
220
221 return sp_runtime::transaction_validity::InvalidTransaction::Stale.into()
222 },
223 _ => {},
224 }
225
226 Ok(sp_runtime::transaction_validity::ValidTransaction::default())
227 }
228}
229
230fn unrewarded_relayers_occupation<T: Config<I>, I: 'static>(
232 inbound_lane_data: &InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>,
233) -> UnrewardedRelayerOccupation {
234 UnrewardedRelayerOccupation {
235 free_relayer_slots: T::BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX
236 .saturating_sub(inbound_lane_data.relayers.len() as MessageNonce),
237 free_message_slots: {
238 let unconfirmed_messages = inbound_lane_data
239 .last_delivered_nonce()
240 .saturating_sub(inbound_lane_data.last_confirmed_nonce);
241 T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
242 .saturating_sub(unconfirmed_messages)
243 },
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use crate::tests::mock::*;
251 use bp_messages::{
252 source_chain::FromBridgedChainMessagesDeliveryProof,
253 target_chain::FromBridgedChainMessagesProof, DeliveredMessages, InboundLaneData, LaneState,
254 OutboundLaneData, UnrewardedRelayer, UnrewardedRelayersState,
255 };
256 use sp_std::ops::RangeInclusive;
257
258 fn fill_unrewarded_relayers() {
259 let mut inbound_lane_state = InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap();
260 for n in 0..BridgedChain::MAX_UNREWARDED_RELAYERS_IN_CONFIRMATION_TX {
261 inbound_lane_state.relayers.push_back(UnrewardedRelayer {
262 relayer: Default::default(),
263 messages: DeliveredMessages { begin: n + 1, end: n + 1 },
264 });
265 }
266 InboundLanes::<TestRuntime>::insert(test_lane_id(), inbound_lane_state);
267 }
268
269 fn fill_unrewarded_messages() {
270 let mut inbound_lane_state = InboundLanes::<TestRuntime>::get(test_lane_id()).unwrap();
271 inbound_lane_state.relayers.push_back(UnrewardedRelayer {
272 relayer: Default::default(),
273 messages: DeliveredMessages {
274 begin: 1,
275 end: BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
276 },
277 });
278 InboundLanes::<TestRuntime>::insert(test_lane_id(), inbound_lane_state);
279 }
280
281 fn deliver_message_10() {
282 InboundLanes::<TestRuntime>::insert(
283 test_lane_id(),
284 bp_messages::InboundLaneData {
285 state: LaneState::Opened,
286 relayers: Default::default(),
287 last_confirmed_nonce: 10,
288 },
289 );
290 }
291
292 fn validate_message_delivery(
293 nonces_start: bp_messages::MessageNonce,
294 nonces_end: bp_messages::MessageNonce,
295 ) -> bool {
296 RuntimeCall::Messages(crate::Call::<TestRuntime, ()>::receive_messages_proof {
297 relayer_id_at_bridged_chain: 42,
298 messages_count: nonces_end.checked_sub(nonces_start).map(|x| x + 1).unwrap_or(0) as u32,
299 dispatch_weight: frame_support::weights::Weight::zero(),
300 proof: Box::new(FromBridgedChainMessagesProof {
301 bridged_header_hash: Default::default(),
302 storage_proof: Default::default(),
303 lane: test_lane_id(),
304 nonces_start,
305 nonces_end,
306 }),
307 })
308 .check_obsolete_call()
309 .is_ok()
310 }
311
312 fn run_test<T>(test: impl Fn() -> T) -> T {
313 sp_io::TestExternalities::new(Default::default()).execute_with(|| {
314 InboundLanes::<TestRuntime>::insert(test_lane_id(), InboundLaneData::opened());
315 OutboundLanes::<TestRuntime>::insert(test_lane_id(), OutboundLaneData::opened());
316 test()
317 })
318 }
319
320 #[test]
321 fn extension_rejects_obsolete_messages() {
322 run_test(|| {
323 deliver_message_10();
326 assert!(!validate_message_delivery(8, 9));
327 });
328 }
329
330 #[test]
331 fn extension_rejects_same_message() {
332 run_test(|| {
333 deliver_message_10();
336 assert!(!validate_message_delivery(8, 10));
337 });
338 }
339
340 #[test]
341 fn extension_rejects_call_with_some_obsolete_messages() {
342 run_test(|| {
343 deliver_message_10();
346 assert!(!validate_message_delivery(10, 15));
347 });
348 }
349
350 #[test]
351 fn extension_rejects_call_with_future_messages() {
352 run_test(|| {
353 deliver_message_10();
356 assert!(!validate_message_delivery(13, 15));
357 });
358 }
359
360 #[test]
361 fn extension_reject_call_when_dispatcher_is_inactive() {
362 run_test(|| {
363 deliver_message_10();
366
367 TestMessageDispatch::deactivate(test_lane_id());
368 assert!(!validate_message_delivery(11, 15));
369 });
370 }
371
372 #[test]
373 fn extension_rejects_empty_delivery_with_rewards_confirmations_if_there_are_free_relayer_and_message_slots(
374 ) {
375 run_test(|| {
376 deliver_message_10();
377 assert!(!validate_message_delivery(10, 9));
378 });
379 }
380
381 #[test]
382 fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_relayer_slots(
383 ) {
384 run_test(|| {
385 deliver_message_10();
386 fill_unrewarded_relayers();
387 assert!(validate_message_delivery(10, 9));
388 });
389 }
390
391 #[test]
392 fn extension_accepts_empty_delivery_with_rewards_confirmations_if_there_are_no_free_message_slots(
393 ) {
394 run_test(|| {
395 fill_unrewarded_messages();
396 assert!(validate_message_delivery(
397 BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
398 BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX - 1
399 ));
400 });
401 }
402
403 #[test]
404 fn extension_accepts_new_messages() {
405 run_test(|| {
406 deliver_message_10();
409 assert!(validate_message_delivery(11, 15));
410 });
411 }
412
413 fn confirm_message_10() {
414 OutboundLanes::<TestRuntime>::insert(
415 test_lane_id(),
416 bp_messages::OutboundLaneData {
417 state: LaneState::Opened,
418 oldest_unpruned_nonce: 0,
419 latest_received_nonce: 10,
420 latest_generated_nonce: 10,
421 },
422 );
423 }
424
425 fn validate_message_confirmation(last_delivered_nonce: bp_messages::MessageNonce) -> bool {
426 RuntimeCall::Messages(crate::Call::<TestRuntime>::receive_messages_delivery_proof {
427 proof: FromBridgedChainMessagesDeliveryProof {
428 bridged_header_hash: Default::default(),
429 storage_proof: Default::default(),
430 lane: test_lane_id(),
431 },
432 relayers_state: UnrewardedRelayersState { last_delivered_nonce, ..Default::default() },
433 })
434 .check_obsolete_call()
435 .is_ok()
436 }
437
438 #[test]
439 fn extension_rejects_obsolete_confirmations() {
440 run_test(|| {
441 confirm_message_10();
444 assert!(!validate_message_confirmation(5));
445 });
446 }
447
448 #[test]
449 fn extension_rejects_same_confirmation() {
450 run_test(|| {
451 confirm_message_10();
454 assert!(!validate_message_confirmation(10));
455 });
456 }
457
458 #[test]
459 fn extension_rejects_empty_confirmation_even_if_there_are_no_free_unrewarded_entries() {
460 run_test(|| {
461 confirm_message_10();
462 fill_unrewarded_relayers();
463 assert!(!validate_message_confirmation(10));
464 });
465 }
466
467 #[test]
468 fn extension_accepts_new_confirmation() {
469 run_test(|| {
470 confirm_message_10();
473 assert!(validate_message_confirmation(15));
474 });
475 }
476
477 fn was_message_delivery_successful(
478 bundled_range: RangeInclusive<MessageNonce>,
479 is_empty: bool,
480 ) -> bool {
481 CallHelper::<TestRuntime, ()>::was_successful(&MessagesCallInfo::ReceiveMessagesProof(
482 ReceiveMessagesProofInfo {
483 base: BaseMessagesProofInfo {
484 lane_id: test_lane_id(),
485 bundled_range,
486 best_stored_nonce: 0, },
488 unrewarded_relayers: UnrewardedRelayerOccupation {
489 free_relayer_slots: 0, free_message_slots: if is_empty {
491 0
492 } else {
493 BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX
494 },
495 },
496 },
497 ))
498 }
499
500 #[test]
501 #[allow(clippy::reversed_empty_ranges)]
502 fn was_successful_returns_false_for_failed_reward_confirmation_transaction() {
503 run_test(|| {
504 fill_unrewarded_messages();
505 assert!(!was_message_delivery_successful(10..=9, true));
506 });
507 }
508
509 #[test]
510 #[allow(clippy::reversed_empty_ranges)]
511 fn was_successful_returns_true_for_successful_reward_confirmation_transaction() {
512 run_test(|| {
513 assert!(was_message_delivery_successful(10..=9, true));
514 });
515 }
516
517 #[test]
518 fn was_successful_returns_false_for_failed_delivery() {
519 run_test(|| {
520 deliver_message_10();
521 assert!(!was_message_delivery_successful(10..=12, false));
522 });
523 }
524
525 #[test]
526 fn was_successful_returns_false_for_partially_successful_delivery() {
527 run_test(|| {
528 deliver_message_10();
529 assert!(!was_message_delivery_successful(9..=12, false));
530 });
531 }
532
533 #[test]
534 fn was_successful_returns_true_for_successful_delivery() {
535 run_test(|| {
536 deliver_message_10();
537 assert!(was_message_delivery_successful(9..=10, false));
538 });
539 }
540
541 fn was_message_confirmation_successful(bundled_range: RangeInclusive<MessageNonce>) -> bool {
542 CallHelper::<TestRuntime, ()>::was_successful(
543 &MessagesCallInfo::ReceiveMessagesDeliveryProof(ReceiveMessagesDeliveryProofInfo(
544 BaseMessagesProofInfo {
545 lane_id: test_lane_id(),
546 bundled_range,
547 best_stored_nonce: 0, },
549 )),
550 )
551 }
552
553 #[test]
554 fn was_successful_returns_false_for_failed_confirmation() {
555 run_test(|| {
556 confirm_message_10();
557 assert!(!was_message_confirmation_successful(10..=12));
558 });
559 }
560
561 #[test]
562 fn was_successful_returns_false_for_partially_successful_confirmation() {
563 run_test(|| {
564 confirm_message_10();
565 assert!(!was_message_confirmation_successful(9..=12));
566 });
567 }
568
569 #[test]
570 fn was_successful_returns_true_for_successful_confirmation() {
571 run_test(|| {
572 confirm_message_10();
573 assert!(was_message_confirmation_successful(9..=10));
574 });
575 }
576}