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	//
195	// Benchmarks that are used directly by the runtime calls weight formulae.
196	//
197
198	fn max_msgs<T: Config<I>, I: 'static>() -> u32 {
199		T::BridgedChain::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX as u32 -
200			ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE as u32
201	}
202
203	// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
204	// conditions:
205	// * proof does not include outbound lane state proof;
206	// * inbound lane already has state, so it needs to be read and decoded;
207	// * message is dispatched (reminder: dispatch weight should be minimal);
208	// * message requires all heavy checks done by dispatcher.
209	#[benchmark]
210	fn receive_single_message_proof() {
211		// setup code
212		let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
213		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
214			lane: T::bench_lane_id(),
215			message_nonces: setup.nonces(),
216			outbound_lane_data: None,
217			is_successful_dispatch_expected: false,
218			proof_params: UnverifiedStorageProofParams::from_db_size(
219				EXPECTED_DEFAULT_MESSAGE_LENGTH,
220			),
221		});
222
223		#[extrinsic_call]
224		receive_messages_proof(
225			RawOrigin::Signed(setup.relayer_id_on_tgt()),
226			setup.relayer_id_on_src(),
227			Box::new(proof),
228			setup.msgs_count,
229			dispatch_weight,
230		);
231
232		// verification code
233		setup.check_last_nonce();
234	}
235
236	// Benchmark `receive_messages_proof` extrinsic with `n` minimal-weight messages and following
237	// conditions:
238	// * proof does not include outbound lane state proof;
239	// * inbound lane already has state, so it needs to be read and decoded;
240	// * message is dispatched (reminder: dispatch weight should be minimal);
241	// * message requires all heavy checks done by dispatcher.
242	#[benchmark]
243	fn receive_n_messages_proof(n: Linear<1, { max_msgs::<T, I>() }>) {
244		// setup code
245		let setup = ReceiveMessagesProofSetup::<T, I>::new(n);
246		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
247			lane: T::bench_lane_id(),
248			message_nonces: setup.nonces(),
249			outbound_lane_data: None,
250			is_successful_dispatch_expected: false,
251			proof_params: UnverifiedStorageProofParams::from_db_size(
252				EXPECTED_DEFAULT_MESSAGE_LENGTH,
253			),
254		});
255
256		#[extrinsic_call]
257		receive_messages_proof(
258			RawOrigin::Signed(setup.relayer_id_on_tgt()),
259			setup.relayer_id_on_src(),
260			Box::new(proof),
261			setup.msgs_count,
262			dispatch_weight,
263		);
264
265		// verification code
266		setup.check_last_nonce();
267	}
268
269	// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
270	// conditions:
271	// * proof includes outbound lane state proof;
272	// * inbound lane already has state, so it needs to be read and decoded;
273	// * message is successfully dispatched (reminder: dispatch weight should be minimal);
274	// * message requires all heavy checks done by dispatcher.
275	//
276	// The weight of outbound lane state delivery would be
277	// `weight(receive_single_message_proof_with_outbound_lane_state) -
278	// weight(receive_single_message_proof)`. This won't be super-accurate if message has non-zero
279	// dispatch weight, but estimation should be close enough to real weight.
280	#[benchmark]
281	fn receive_single_message_proof_with_outbound_lane_state() {
282		// setup code
283		let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
284		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
285			lane: T::bench_lane_id(),
286			message_nonces: setup.nonces(),
287			outbound_lane_data: Some(OutboundLaneData {
288				state: LaneState::Opened,
289				oldest_unpruned_nonce: setup.last_nonce(),
290				latest_received_nonce: ReceiveMessagesProofSetup::<T, I>::LATEST_RECEIVED_NONCE,
291				latest_generated_nonce: setup.last_nonce(),
292			}),
293			is_successful_dispatch_expected: false,
294			proof_params: UnverifiedStorageProofParams::from_db_size(
295				EXPECTED_DEFAULT_MESSAGE_LENGTH,
296			),
297		});
298
299		#[extrinsic_call]
300		receive_messages_proof(
301			RawOrigin::Signed(setup.relayer_id_on_tgt()),
302			setup.relayer_id_on_src(),
303			Box::new(proof),
304			setup.msgs_count,
305			dispatch_weight,
306		);
307
308		// verification code
309		setup.check_last_nonce();
310	}
311
312	// Benchmark `receive_messages_proof` extrinsic with single minimal-weight message and following
313	// conditions:
314	// * the proof has large leaf with total size ranging between 1KB and 16KB;
315	// * proof does not include outbound lane state proof;
316	// * inbound lane already has state, so it needs to be read and decoded;
317	// * message is dispatched (reminder: dispatch weight should be minimal);
318	// * message requires all heavy checks done by dispatcher.
319	#[benchmark]
320	fn receive_single_n_bytes_message_proof(
321		/// Proof size in KB
322		n: Linear<1, { 16 * 1024 }>,
323	) {
324		// setup code
325		let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
326		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
327			lane: T::bench_lane_id(),
328			message_nonces: setup.nonces(),
329			outbound_lane_data: None,
330			is_successful_dispatch_expected: false,
331			proof_params: UnverifiedStorageProofParams::from_db_size(n),
332		});
333
334		#[extrinsic_call]
335		receive_messages_proof(
336			RawOrigin::Signed(setup.relayer_id_on_tgt()),
337			setup.relayer_id_on_src(),
338			Box::new(proof),
339			setup.msgs_count,
340			dispatch_weight,
341		);
342
343		// verification code
344		setup.check_last_nonce();
345	}
346
347	// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
348	// * single relayer is rewarded for relaying single message;
349	// * relayer account does not exist (in practice it needs to exist in production environment).
350	//
351	// This is base benchmark for all other confirmations delivery benchmarks.
352	#[benchmark]
353	fn receive_delivery_proof_for_single_message() {
354		let relayer_id: T::AccountId = account("relayer", 0, SEED);
355
356		// send message that we're going to confirm
357		send_regular_message::<T, I>();
358
359		let relayers_state = UnrewardedRelayersState {
360			unrewarded_relayer_entries: 1,
361			messages_in_oldest_entry: 1,
362			total_messages: 1,
363			last_delivered_nonce: 1,
364		};
365		let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
366			lane: T::bench_lane_id(),
367			inbound_lane_data: InboundLaneData {
368				state: LaneState::Opened,
369				relayers: vec![UnrewardedRelayer {
370					relayer: relayer_id.clone(),
371					messages: DeliveredMessages::new(1),
372				}]
373				.into_iter()
374				.collect(),
375				last_confirmed_nonce: 0,
376			},
377			proof_params: UnverifiedStorageProofParams::default(),
378		});
379
380		#[extrinsic_call]
381		receive_messages_delivery_proof(
382			RawOrigin::Signed(relayer_id.clone()),
383			proof,
384			relayers_state,
385		);
386
387		assert_eq!(
388			OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
389			Some(1)
390		);
391		assert!(T::is_relayer_rewarded(&relayer_id));
392	}
393
394	// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
395	// * single relayer is rewarded for relaying two messages;
396	// * relayer account does not exist (in practice it needs to exist in production environment).
397	//
398	// Additional weight for paying single-message reward to the same relayer could be computed
399	// as `weight(receive_delivery_proof_for_two_messages_by_single_relayer)
400	//   - weight(receive_delivery_proof_for_single_message)`.
401	#[benchmark]
402	fn receive_delivery_proof_for_two_messages_by_single_relayer() {
403		let relayer_id: T::AccountId = account("relayer", 0, SEED);
404
405		// send message that we're going to confirm
406		send_regular_message::<T, I>();
407		send_regular_message::<T, I>();
408
409		let relayers_state = UnrewardedRelayersState {
410			unrewarded_relayer_entries: 1,
411			messages_in_oldest_entry: 2,
412			total_messages: 2,
413			last_delivered_nonce: 2,
414		};
415		let mut delivered_messages = DeliveredMessages::new(1);
416		delivered_messages.note_dispatched_message();
417		let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
418			lane: T::bench_lane_id(),
419			inbound_lane_data: InboundLaneData {
420				state: LaneState::Opened,
421				relayers: vec![UnrewardedRelayer {
422					relayer: relayer_id.clone(),
423					messages: delivered_messages,
424				}]
425				.into_iter()
426				.collect(),
427				last_confirmed_nonce: 0,
428			},
429			proof_params: UnverifiedStorageProofParams::default(),
430		});
431
432		#[extrinsic_call]
433		receive_messages_delivery_proof(
434			RawOrigin::Signed(relayer_id.clone()),
435			proof,
436			relayers_state,
437		);
438
439		assert_eq!(
440			OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
441			Some(2)
442		);
443		assert!(T::is_relayer_rewarded(&relayer_id));
444	}
445
446	// Benchmark `receive_messages_delivery_proof` extrinsic with following conditions:
447	// * two relayers are rewarded for relaying single message each;
448	// * relayer account does not exist (in practice it needs to exist in production environment).
449	//
450	// Additional weight for paying reward to the next relayer could be computed
451	// as `weight(receive_delivery_proof_for_two_messages_by_two_relayers)
452	//   - weight(receive_delivery_proof_for_two_messages_by_single_relayer)`.
453	#[benchmark]
454	fn receive_delivery_proof_for_two_messages_by_two_relayers() {
455		let relayer1_id: T::AccountId = account("relayer1", 1, SEED);
456		let relayer2_id: T::AccountId = account("relayer2", 2, SEED);
457
458		// send message that we're going to confirm
459		send_regular_message::<T, I>();
460		send_regular_message::<T, I>();
461
462		let relayers_state = UnrewardedRelayersState {
463			unrewarded_relayer_entries: 2,
464			messages_in_oldest_entry: 1,
465			total_messages: 2,
466			last_delivered_nonce: 2,
467		};
468		let proof = T::prepare_message_delivery_proof(MessageDeliveryProofParams {
469			lane: T::bench_lane_id(),
470			inbound_lane_data: InboundLaneData {
471				state: LaneState::Opened,
472				relayers: vec![
473					UnrewardedRelayer {
474						relayer: relayer1_id.clone(),
475						messages: DeliveredMessages::new(1),
476					},
477					UnrewardedRelayer {
478						relayer: relayer2_id.clone(),
479						messages: DeliveredMessages::new(2),
480					},
481				]
482				.into_iter()
483				.collect(),
484				last_confirmed_nonce: 0,
485			},
486			proof_params: UnverifiedStorageProofParams::default(),
487		});
488
489		#[extrinsic_call]
490		receive_messages_delivery_proof(
491			RawOrigin::Signed(relayer1_id.clone()),
492			proof,
493			relayers_state,
494		);
495
496		assert_eq!(
497			OutboundLanes::<T, I>::get(T::bench_lane_id()).map(|s| s.latest_received_nonce),
498			Some(2)
499		);
500		assert!(T::is_relayer_rewarded(&relayer1_id));
501		assert!(T::is_relayer_rewarded(&relayer2_id));
502	}
503
504	//
505	// Benchmarks that the runtime developers may use for proper pallet configuration.
506	//
507
508	// This benchmark is optional and may be used when runtime developer need a way to compute
509	// message dispatch weight. In this case, he needs to provide messages that can go the whole
510	// dispatch
511	//
512	// Benchmark `receive_messages_proof` extrinsic with single message and following conditions:
513	//
514	// * proof does not include outbound lane state proof;
515	// * inbound lane already has state, so it needs to be read and decoded;
516	// * message is **SUCCESSFULLY** dispatched;
517	// * message requires all heavy checks done by dispatcher.
518	#[benchmark]
519	fn receive_single_n_bytes_message_proof_with_dispatch(
520		/// Proof size in KB
521		n: Linear<1, { 16 * 1024 }>,
522	) {
523		// setup code
524		let setup = ReceiveMessagesProofSetup::<T, I>::new(1);
525		let (proof, dispatch_weight) = T::prepare_message_proof(MessageProofParams {
526			lane: T::bench_lane_id(),
527			message_nonces: setup.nonces(),
528			outbound_lane_data: None,
529			is_successful_dispatch_expected: true,
530			proof_params: UnverifiedStorageProofParams::from_db_size(n),
531		});
532
533		#[extrinsic_call]
534		receive_messages_proof(
535			RawOrigin::Signed(setup.relayer_id_on_tgt()),
536			setup.relayer_id_on_src(),
537			Box::new(proof),
538			setup.msgs_count,
539			dispatch_weight,
540		);
541
542		// verification code
543		setup.check_last_nonce();
544		assert!(T::is_message_successfully_dispatched(setup.last_nonce()));
545	}
546
547	impl_benchmark_test_suite!(
548		Pallet,
549		crate::tests::mock::new_test_ext(),
550		crate::tests::mock::TestRuntime
551	);
552}