referrerpolicy=no-referrer-when-downgrade

pallet_bridge_messages/
benchmarking.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//! Messages pallet benchmarking.
18
19#![cfg(feature = "runtime-benchmarks")]
20
21use crate::{
22	active_outbound_lane, weights_ext::EXPECTED_DEFAULT_MESSAGE_LENGTH, BridgedChainOf, Call,
23	InboundLanes, OutboundLanes,
24};
25
26use bp_messages::{
27	source_chain::FromBridgedChainMessagesDeliveryProof,
28	target_chain::FromBridgedChainMessagesProof, ChainWithMessages, DeliveredMessages,
29	InboundLaneData, LaneState, MessageNonce, OutboundLaneData, UnrewardedRelayer,
30	UnrewardedRelayersState,
31};
32use bp_runtime::{AccountIdOf, HashOf, UnverifiedStorageProofParams};
33use codec::Decode;
34use frame_benchmarking::{account, v2::*};
35use frame_support::weights::Weight;
36use frame_system::RawOrigin;
37use sp_runtime::{traits::TrailingZeroInput, BoundedVec};
38use sp_std::{ops::RangeInclusive, prelude::*};
39
40const SEED: u32 = 0;
41
42/// Pallet we're benchmarking here.
43pub struct Pallet<T: Config<I>, I: 'static = ()>(crate::Pallet<T, I>);
44
45/// Benchmark-specific message proof parameters.
46#[derive(Debug)]
47pub struct MessageProofParams<LaneId> {
48	/// Id of the lane.
49	pub lane: LaneId,
50	/// Range of messages to include in the proof.
51	pub message_nonces: RangeInclusive<MessageNonce>,
52	/// If `Some`, the proof needs to include this outbound lane data.
53	pub outbound_lane_data: Option<OutboundLaneData>,
54	/// If `true`, the caller expects that the proof will contain correct messages that will
55	/// be successfully dispatched. This is only called from the "optional"
56	/// `receive_single_message_proof_with_dispatch` benchmark. If you don't need it, just
57	/// return `true` from the `is_message_successfully_dispatched`.
58	pub is_successful_dispatch_expected: bool,
59	/// Proof size requirements.
60	pub proof_params: UnverifiedStorageProofParams,
61}
62
63/// Benchmark-specific message delivery proof parameters.
64#[derive(Debug)]
65pub struct MessageDeliveryProofParams<ThisChainAccountId, LaneId> {
66	/// Id of the lane.
67	pub lane: LaneId,
68	/// The proof needs to include this inbound lane data.
69	pub inbound_lane_data: InboundLaneData<ThisChainAccountId>,
70	/// Proof size requirements.
71	pub proof_params: UnverifiedStorageProofParams,
72}
73
74/// Trait that must be implemented by runtime.
75pub trait Config<I: 'static>: crate::Config<I> {
76	/// Lane id to use in benchmarks.
77	fn bench_lane_id() -> Self::LaneId {
78		Self::LaneId::default()
79	}
80
81	/// Return id of relayer account at the bridged chain.
82	///
83	/// By default, zero account is returned.
84	fn bridged_relayer_id() -> AccountIdOf<BridgedChainOf<Self, I>> {
85		Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap()
86	}
87
88	/// Create given account and give it enough balance for test purposes. Used to create
89	/// relayer account at the target chain. Is strictly necessary when your rewards scheme
90	/// assumes that the relayer account must exist.
91	///
92	/// Does nothing by default.
93	fn endow_account(_account: &Self::AccountId) {}
94
95	/// Prepare messages proof to receive by the module.
96	fn prepare_message_proof(
97		params: MessageProofParams<Self::LaneId>,
98	) -> (FromBridgedChainMessagesProof<HashOf<BridgedChainOf<Self, I>>, Self::LaneId>, Weight);
99	/// Prepare messages delivery proof to receive by the module.
100	fn prepare_message_delivery_proof(
101		params: MessageDeliveryProofParams<Self::AccountId, Self::LaneId>,
102	) -> FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<Self, I>>, Self::LaneId>;
103
104	/// Returns true if message has been successfully dispatched or not.
105	fn is_message_successfully_dispatched(_nonce: MessageNonce) -> bool {
106		true
107	}
108
109	/// Returns true if given relayer has been rewarded for some of its actions.
110	fn is_relayer_rewarded(relayer: &Self::AccountId) -> bool;
111}
112
113fn send_regular_message<T: Config<I>, I: 'static>() {
114	OutboundLanes::<T, I>::insert(
115		T::bench_lane_id(),
116		OutboundLaneData {
117			state: LaneState::Opened,
118			latest_generated_nonce: 1,
119			..Default::default()
120		},
121	);
122
123	let mut outbound_lane = active_outbound_lane::<T, I>(T::bench_lane_id()).unwrap();
124	outbound_lane.send_message(BoundedVec::try_from(vec![]).expect("We craft valid messages"));
125}
126
127fn receive_messages<T: Config<I>, I: 'static>(nonce: MessageNonce) {
128	InboundLanes::<T, I>::insert(
129		T::bench_lane_id(),
130		InboundLaneData {
131			state: LaneState::Opened,
132			relayers: vec![UnrewardedRelayer {
133				relayer: T::bridged_relayer_id(),
134				messages: DeliveredMessages::new(nonce),
135			}]
136			.into(),
137			last_confirmed_nonce: 0,
138		},
139	);
140}
141
142struct ReceiveMessagesProofSetup<T: Config<I>, I: 'static> {
143	relayer_id_on_src: AccountIdOf<BridgedChainOf<T, I>>,
144	relayer_id_on_tgt: T::AccountId,
145	msgs_count: u32,
146	_phantom_data: sp_std::marker::PhantomData<I>,
147}
148
149impl<T: Config<I>, I: 'static> ReceiveMessagesProofSetup<T, I> {
150	const LATEST_RECEIVED_NONCE: MessageNonce = 20;
151
152	fn new(msgs_count: u32) -> Self {
153		let setup = Self {
154			relayer_id_on_src: T::bridged_relayer_id(),
155			relayer_id_on_tgt: account("relayer", 0, SEED),
156			msgs_count,
157			_phantom_data: Default::default(),
158		};
159		T::endow_account(&setup.relayer_id_on_tgt);
160		// mark messages 1..=latest_recvd_nonce as delivered
161		receive_messages::<T, I>(Self::LATEST_RECEIVED_NONCE);
162
163		setup
164	}
165
166	fn relayer_id_on_src(&self) -> AccountIdOf<BridgedChainOf<T, I>> {
167		self.relayer_id_on_src.clone()
168	}
169
170	fn relayer_id_on_tgt(&self) -> T::AccountId {
171		self.relayer_id_on_tgt.clone()
172	}
173
174	fn last_nonce(&self) -> MessageNonce {
175		Self::LATEST_RECEIVED_NONCE + self.msgs_count as u64
176	}
177
178	fn nonces(&self) -> RangeInclusive<MessageNonce> {
179		(Self::LATEST_RECEIVED_NONCE + 1)..=self.last_nonce()
180	}
181
182	fn check_last_nonce(&self) {
183		assert_eq!(
184			crate::InboundLanes::<T, I>::get(&T::bench_lane_id()).map(|d| d.last_delivered_nonce()),
185			Some(self.last_nonce()),
186		);
187	}
188}
189
190#[instance_benchmarks]
191mod benchmarks {
192	use super::*;
193
194	// Benchmarks that are used directly by the runtime calls weight formulae.
195	//
196
197	fn max_msgs<T: Config<I>, I: 'static>() -> u32 {
198		T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX as u32 -
199			ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE as u32
200	}
201
202	// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
203	// conditions:
204	// * proof does not include outbound lane state proof;
205	// * inbound lane already has state, so it needs to be read and decoded;
206	// * message is dispatched (reminder: dispatch weight should be minimal);
207	// * message requires all heavy checks done by dispatcher.
208	#[benchmark]
209	fn receive_single_message_proof() {
210		// setup code
211		let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
212		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
213			lane: T::bench_lane_id(),
214			message_nonces: setup.nonces(),
215			outbound_lane_data: None,
216			is_successful_dispatch_expected: false,
217			proof_params: UnverifiedStorageProofParams::from_db_size(
218				EXPECTED_DEFAULT_MESSAGE_LENGTH,
219			),
220		});
221
222		#[extrinsic_call]
223		receive_messages_proof(
224			RawOrigin::Signed(setup.relayer_id_on_tgt()),
225			setup.relayer_id_on_src(),
226			Box::new(proof),
227			setup.msgs_count,
228			dispatch_weight,
229		);
230
231		// verification code
232		setup.check_last_nonce();
233	}
234
235	// Benchmark `receive_messages_proof` extrinsic with `n` minimal-weight messages and following
236	// conditions:
237	// * proof does not include outbound lane state proof;
238	// * inbound lane already has state, so it needs to be read and decoded;
239	// * message is dispatched (reminder: dispatch weight should be minimal);
240	// * message requires all heavy checks done by dispatcher.
241	#[benchmark]
242	fn receive_n_messages_proof(n: Linear<1, { max_msgs::<T, I>() }>) {
243		// setup code
244		let setup = ReceiveMessagesProofSetup::<T, I>::new(n);
245		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
246			lane: T::bench_lane_id(),
247			message_nonces: setup.nonces(),
248			outbound_lane_data: None,
249			is_successful_dispatch_expected: false,
250			proof_params: UnverifiedStorageProofParams::from_db_size(
251				EXPECTED_DEFAULT_MESSAGE_LENGTH,
252			),
253		});
254
255		#[extrinsic_call]
256		receive_messages_proof(
257			RawOrigin::Signed(setup.relayer_id_on_tgt()),
258			setup.relayer_id_on_src(),
259			Box::new(proof),
260			setup.msgs_count,
261			dispatch_weight,
262		);
263
264		// verification code
265		setup.check_last_nonce();
266	}
267
268	// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
269	// conditions:
270	// * proof includes outbound lane state proof;
271	// * inbound lane already has state, so it needs to be read and decoded;
272	// * message is successfully dispatched (reminder: dispatch weight should be minimal);
273	// * message requires all heavy checks done by dispatcher.
274	//
275	// The weight of outbound lane state delivery would be
276	// `weight(receive_single_message_proof_with_outbound_lane_state) -
277	// weight(receive_single_message_proof)`. This won't be super-accurate if message has non-zero
278	// dispatch weight, but estimation should be close enough to real weight.
279	#[benchmark]
280	fn receive_single_message_proof_with_outbound_lane_state() {
281		// setup code
282		let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
283		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
284			lane: T::bench_lane_id(),
285			message_nonces: setup.nonces(),
286			outbound_lane_data: Some(OutboundLaneData {
287				state: LaneState::Opened,
288				oldest_unpruned_nonce: setup.last_nonce(),
289				latest_received_nonce: ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE,
290				latest_generated_nonce: setup.last_nonce(),
291			}),
292			is_successful_dispatch_expected: false,
293			proof_params: UnverifiedStorageProofParams::from_db_size(
294				EXPECTED_DEFAULT_MESSAGE_LENGTH,
295			),
296		});
297
298		#[extrinsic_call]
299		receive_messages_proof(
300			RawOrigin::Signed(setup.relayer_id_on_tgt()),
301			setup.relayer_id_on_src(),
302			Box::new(proof),
303			setup.msgs_count,
304			dispatch_weight,
305		);
306
307		// verification code
308		setup.check_last_nonce();
309	}
310
311	// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
312	// conditions:
313	// * the proof has large leaf with total size ranging between 1KB and 16KB;
314	// * proof does not include outbound lane state proof;
315	// * inbound lane already has state, so it needs to be read and decoded;
316	// * message is dispatched (reminder: dispatch weight should be minimal);
317	// * message requires all heavy checks done by dispatcher.
318	#[benchmark]
319	fn receive_single_n_bytes_message_proof(
320		/// Proof size in KB
321		n: Linear<1, { 16 * 1024 }>,
322	) {
323		// setup code
324		let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
325		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
326			lane: T::bench_lane_id(),
327			message_nonces: setup.nonces(),
328			outbound_lane_data: None,
329			is_successful_dispatch_expected: false,
330			proof_params: UnverifiedStorageProofParams::from_db_size(n),
331		});
332
333		#[extrinsic_call]
334		receive_messages_proof(
335			RawOrigin::Signed(setup.relayer_id_on_tgt()),
336			setup.relayer_id_on_src(),
337			Box::new(proof),
338			setup.msgs_count,
339			dispatch_weight,
340		);
341
342		// verification code
343		setup.check_last_nonce();
344	}
345
346	// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
347	// * single relayer is rewarded for relaying single message;
348	// * relayer account does not exist (in practice it needs to exist in production environment).
349	//
350	// This is base benchmark for all other confirmations delivery benchmarks.
351	#[benchmark]
352	fn receive_delivery_proof_for_single_message() {
353		let relayer_id: T::AccountId = account("relayer", 0, SEED);
354
355		// send message that we're going to confirm
356		send_regular_message::<T, I>();
357
358		let relayers_state = UnrewardedRelayersState {
359			unrewarded_relayer_entries: 1,
360			messages_in_oldest_entry: 1,
361			total_messages: 1,
362			last_delivered_nonce: 1,
363		};
364		let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
365			lane: T::bench_lane_id(),
366			inbound_lane_data: InboundLaneData {
367				state: LaneState::Opened,
368				relayers: vec![UnrewardedRelayer {
369					relayer: relayer_id.clone(),
370					messages: DeliveredMessages::new(1),
371				}]
372				.into_iter()
373				.collect(),
374				last_confirmed_nonce: 0,
375			},
376			proof_params: UnverifiedStorageProofParams::default(),
377		});
378
379		#[extrinsic_call]
380		receive_messages_delivery_proof(
381			RawOrigin::Signed(relayer_id.clone()),
382			proof,
383			relayers_state,
384		);
385
386		assert_eq!(
387			OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
388			Some(1)
389		);
390		assert!(T::is_relayer_rewarded(&relayer_id));
391	}
392
393	// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
394	// * single relayer is rewarded for relaying two messages;
395	// * relayer account does not exist (in practice it needs to exist in production environment).
396	//
397	// Additional weight for paying single-message reward to the same relayer could be computed
398	// as `weight(receive_delivery_proof_for_two_messages_by_single_relayer)
399	//   - weight(receive_delivery_proof_for_single_message)`.
400	#[benchmark]
401	fn receive_delivery_proof_for_two_messages_by_single_relayer() {
402		let relayer_id: T::AccountId = account("relayer", 0, SEED);
403
404		// send message that we're going to confirm
405		send_regular_message::<T, I>();
406		send_regular_message::<T, I>();
407
408		let relayers_state = UnrewardedRelayersState {
409			unrewarded_relayer_entries: 1,
410			messages_in_oldest_entry: 2,
411			total_messages: 2,
412			last_delivered_nonce: 2,
413		};
414		let mut delivered_messages = DeliveredMessages::new(1);
415		delivered_messages.note_dispatched_message();
416		let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
417			lane: T::bench_lane_id(),
418			inbound_lane_data: InboundLaneData {
419				state: LaneState::Opened,
420				relayers: vec![UnrewardedRelayer {
421					relayer: relayer_id.clone(),
422					messages: delivered_messages,
423				}]
424				.into_iter()
425				.collect(),
426				last_confirmed_nonce: 0,
427			},
428			proof_params: UnverifiedStorageProofParams::default(),
429		});
430
431		#[extrinsic_call]
432		receive_messages_delivery_proof(
433			RawOrigin::Signed(relayer_id.clone()),
434			proof,
435			relayers_state,
436		);
437
438		assert_eq!(
439			OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
440			Some(2)
441		);
442		assert!(T::is_relayer_rewarded(&relayer_id));
443	}
444
445	// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
446	// * two relayers are rewarded for relaying single message each;
447	// * relayer account does not exist (in practice it needs to exist in production environment).
448	//
449	// Additional weight for paying reward to the next relayer could be computed
450	// as `weight(receive_delivery_proof_for_two_messages_by_two_relayers)
451	//   - weight(receive_delivery_proof_for_two_messages_by_single_relayer)`.
452	#[benchmark]
453	fn receive_delivery_proof_for_two_messages_by_two_relayers() {
454		let relayer1_id: T::AccountId = account("relayer1", 1, SEED);
455		let relayer2_id: T::AccountId = account("relayer2", 2, SEED);
456
457		// send message that we're going to confirm
458		send_regular_message::<T, I>();
459		send_regular_message::<T, I>();
460
461		let relayers_state = UnrewardedRelayersState {
462			unrewarded_relayer_entries: 2,
463			messages_in_oldest_entry: 1,
464			total_messages: 2,
465			last_delivered_nonce: 2,
466		};
467		let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
468			lane: T::bench_lane_id(),
469			inbound_lane_data: InboundLaneData {
470				state: LaneState::Opened,
471				relayers: vec![
472					UnrewardedRelayer {
473						relayer: relayer1_id.clone(),
474						messages: DeliveredMessages::new(1),
475					},
476					UnrewardedRelayer {
477						relayer: relayer2_id.clone(),
478						messages: DeliveredMessages::new(2),
479					},
480				]
481				.into_iter()
482				.collect(),
483				last_confirmed_nonce: 0,
484			},
485			proof_params: UnverifiedStorageProofParams::default(),
486		});
487
488		#[extrinsic_call]
489		receive_messages_delivery_proof(
490			RawOrigin::Signed(relayer1_id.clone()),
491			proof,
492			relayers_state,
493		);
494
495		assert_eq!(
496			OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
497			Some(2)
498		);
499		assert!(T::is_relayer_rewarded(&relayer1_id));
500		assert!(T::is_relayer_rewarded(&relayer2_id));
501	}
502
503	// Benchmarks that the runtime developers may use for proper pallet configuration.
504	//
505
506	// This benchmark is optional and may be used when runtime developer need a way to compute
507	// message dispatch weight. In this case, he needs to provide messages that can go the whole
508	// dispatch
509	//
510	// Benchmark `receive_messages_proof` extrinsic with single message and following conditions:
511	//
512	// * proof does not include outbound lane state proof;
513	// * inbound lane already has state, so it needs to be read and decoded;
514	// * message is **SUCCESSFULLY** dispatched;
515	// * message requires all heavy checks done by dispatcher.
516	#[benchmark]
517	fn receive_single_n_bytes_message_proof_with_dispatch(
518		/// Proof size in KB
519		n: Linear<1, { 16 * 1024 }>,
520	) {
521		// setup code
522		let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
523		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
524			lane: T::bench_lane_id(),
525			message_nonces: setup.nonces(),
526			outbound_lane_data: None,
527			is_successful_dispatch_expected: true,
528			proof_params: UnverifiedStorageProofParams::from_db_size(n),
529		});
530
531		#[extrinsic_call]
532		receive_messages_proof(
533			RawOrigin::Signed(setup.relayer_id_on_tgt()),
534			setup.relayer_id_on_src(),
535			Box::new(proof),
536			setup.msgs_count,
537			dispatch_weight,
538		);
539
540		// verification code
541		setup.check_last_nonce();
542		assert!(T::is_message_successfully_dispatched(setup.last_nonce()));
543	}
544
545	impl_benchmark_test_suite!(
546		Pallet,
547		crate::tests::mock::new_test_ext(),
548		crate::tests::mock::TestRuntime
549	);
550}