referrerpolicy=no-referrer-when-downgrade

pallet_bridge_messages/
call_ext.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Parity Bridges Common.
3
4// Parity Bridges Common is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Parity Bridges Common is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
16
17//! Helpers for easier manipulation of call processing with signed extensions.
18
19use 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
30/// Helper struct that provides methods for working with a call supported by `MessagesCallInfo`.
31pub 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	/// Returns true if:
37	///
38	/// - call is `receive_messages_proof` and all messages have been delivered;
39	///
40	/// - call is `receive_messages_delivery_proof` and all messages confirmations have been
41	///   received.
42	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					// we don't care about `free_relayer_slots` here - it is checked in
53					// `is_obsolete` and every relayer has delivered at least one message,
54					// so if relayer slots are released, then message slots are also
55					// released
56					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
73/// Trait representing a call that is a sub type of `pallet_bridge_messages::Call`.
74pub trait CallSubType<T: Config<I, RuntimeCall = Self>, I: 'static>:
75	IsSubType<CallableCallFor<Pallet<T, I>, T>>
76{
77	/// Create a new instance of `ReceiveMessagesProofInfo` from a `ReceiveMessagesProof` call.
78	fn receive_messages_proof_info(&self) -> Option<ReceiveMessagesProofInfo<T::LaneId>>;
79
80	/// Create a new instance of `ReceiveMessagesDeliveryProofInfo` from
81	/// a `ReceiveMessagesDeliveryProof` call.
82	fn receive_messages_delivery_proof_info(
83		&self,
84	) -> Option<ReceiveMessagesDeliveryProofInfo<T::LaneId>>;
85
86	/// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof`
87	/// or a `ReceiveMessagesDeliveryProof` call.
88	fn call_info(&self) -> Option<MessagesCallInfo<T::LaneId>>;
89
90	/// Create a new instance of `MessagesCallInfo` from a `ReceiveMessagesProof`
91	/// or a `ReceiveMessagesDeliveryProof` call, if the call is for the provided lane.
92	fn call_info_for(&self, lane_id: T::LaneId) -> Option<MessagesCallInfo<T::LaneId>>;
93
94	/// Ensures that a `ReceiveMessagesProof` or a `ReceiveMessagesDeliveryProof` call:
95	///
96	/// - does not deliver already delivered messages. We require all messages in the
97	///   `ReceiveMessagesProof` call to be undelivered;
98	///
99	/// - does not submit empty `ReceiveMessagesProof` call with zero messages, unless the lane
100	///   needs to be unblocked by providing relayer rewards proof;
101	///
102	/// - brings no new delivery confirmations in a `ReceiveMessagesDeliveryProof` call. We require
103	///   at least one new delivery confirmation in the unrewarded relayers set;
104	///
105	/// - does not violate some basic (easy verifiable) messages pallet rules obsolete (like
106	///   submitting a call when a pallet is halted or delivering messages when a dispatcher is
107	///   inactive).
108	///
109	/// If one of above rules is violated, the transaction is treated as invalid.
110	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					// we want all messages in this range to be new for us. Otherwise transaction
129					// will be considered obsolete.
130					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				// there's a time frame between message delivery, message confirmation and reward
154				// confirmation. Because of that, we can't assume that our state has been confirmed
155				// to the bridged chain. So we are accepting any proof that brings new
156				// confirmations.
157				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
230/// Returns occupation state of unrewarded relayers vector.
231fn 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			// when current best delivered is message#10 and we're trying to deliver messages 8..=9
324			// => tx is rejected
325			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			// when current best delivered is message#10 and we're trying to import messages 10..=10
334			// => tx is rejected
335			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			// when current best delivered is message#10 and we're trying to deliver messages
344			// 10..=15 => tx is rejected
345			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			// when current best delivered is message#10 and we're trying to deliver messages
354			// 13..=15 => tx is rejected
355			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			// when current best delivered is message#10 and we're trying to deliver message 11..=15
364			// => tx is accepted, but we have inactive dispatcher, so...
365			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			// when current best delivered is message#10 and we're trying to deliver message 11..=15
407			// => tx is accepted
408			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			// when current best confirmed is message#10 and we're trying to confirm message#5 => tx
442			// is rejected
443			confirm_message_10();
444			assert!(!validate_message_confirmation(5));
445		});
446	}
447
448	#[test]
449	fn extension_rejects_same_confirmation() {
450		run_test(|| {
451			// when current best confirmed is message#10 and we're trying to confirm message#10 =>
452			// tx is rejected
453			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			// when current best confirmed is message#10 and we're trying to confirm message#15 =>
471			// tx is accepted
472			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, // doesn't matter for `was_successful`
487				},
488				unrewarded_relayers: UnrewardedRelayerOccupation {
489					free_relayer_slots: 0, // doesn't matter for `was_successful`
490					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, // doesn't matter for `was_successful`
548				},
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}