referrerpolicy=no-referrer-when-downgrade

polkadot_runtime_parachains/hrmp/
benchmarking.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot 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// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17#![cfg(feature = "runtime-benchmarks")]
18
19use crate::{
20	configuration::Pallet as Configuration,
21	hrmp::{Pallet as Hrmp, *},
22	paras::{Pallet as Paras, ParaKind, ParachainsCache},
23	shared::Pallet as Shared,
24};
25use frame_benchmarking::{v2::*, whitelisted_caller};
26use frame_support::{assert_ok, traits::Currency};
27
28type BalanceOf<T> =
29	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
30
31fn register_parachain_with_balance<T: Config>(id: ParaId, balance: BalanceOf<T>) {
32	let mut parachains = ParachainsCache::new();
33	Paras::<T>::initialize_para_now(
34		&mut parachains,
35		id,
36		&crate::paras::ParaGenesisArgs {
37			para_kind: ParaKind::Parachain,
38			genesis_head: vec![1].into(),
39			validation_code: vec![1].into(),
40		},
41	);
42	T::Currency::make_free_balance_be(&id.into_account_truncating(), balance);
43}
44
45fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
46	let events = frame_system::Pallet::<T>::events();
47	let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
48	// compare to the last event record
49	let frame_system::EventRecord { event, .. } = &events[events.len() - 1];
50	assert_eq!(event, &system_event);
51}
52
53fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
54	let events = frame_system::Pallet::<T>::events();
55	let system_event: <T as frame_system::Config>::RuntimeEvent = generic_event.into();
56
57	assert!(events.iter().any(|record| record.event == system_event));
58}
59
60/// Enumerates the phase in the setup process of a channel between two parachains.
61enum ParachainSetupStep {
62	/// A channel open has been requested
63	Requested,
64	/// A channel open has been requested and accepted.
65	Accepted,
66	/// A channel open has been requested and accepted, and a session has passed and is now open.
67	Established,
68	/// A channel open has been requested and accepted, and a session has passed and is now
69	/// open, and now it has been requested to close down.
70	CloseRequested,
71}
72
73fn establish_para_connection<T: Config>(
74	from: u32,
75	to: u32,
76	until: ParachainSetupStep,
77) -> [(ParaId, crate::Origin); 2]
78where
79	<T as frame_system::Config>::RuntimeOrigin: From<crate::Origin>,
80{
81	let config = configuration::ActiveConfig::<T>::get();
82	let ed = T::Currency::minimum_balance();
83	let deposit: BalanceOf<T> = config.hrmp_sender_deposit.unique_saturated_into();
84	let capacity = config.hrmp_channel_max_capacity;
85	let message_size = config.hrmp_channel_max_message_size;
86	assert!(message_size > 0, "Invalid genesis for benchmarking");
87	assert!(capacity > 0, "Invalid genesis for benchmarking");
88
89	let sender: ParaId = from.into();
90	let sender_origin: crate::Origin = from.into();
91
92	let recipient: ParaId = to.into();
93	let recipient_origin: crate::Origin = to.into();
94
95	let output = [(sender, sender_origin.clone()), (recipient, recipient_origin.clone())];
96
97	// Make both a parachain if they are already not.
98	if !Paras::<T>::is_parachain(sender) {
99		register_parachain_with_balance::<T>(sender, deposit + ed);
100	}
101	if !Paras::<T>::is_parachain(recipient) {
102		register_parachain_with_balance::<T>(recipient, deposit + ed);
103	}
104
105	assert_ok!(Hrmp::<T>::hrmp_init_open_channel(
106		sender_origin.clone().into(),
107		recipient,
108		capacity,
109		message_size
110	));
111
112	if matches!(until, ParachainSetupStep::Requested) {
113		return output
114	}
115
116	assert_ok!(Hrmp::<T>::hrmp_accept_open_channel(recipient_origin.into(), sender));
117	if matches!(until, ParachainSetupStep::Accepted) {
118		return output
119	}
120
121	Hrmp::<T>::process_hrmp_open_channel_requests(&configuration::ActiveConfig::<T>::get());
122	if matches!(until, ParachainSetupStep::Established) {
123		return output
124	}
125
126	let channel_id = HrmpChannelId { sender, recipient };
127	assert_ok!(Hrmp::<T>::hrmp_close_channel(sender_origin.clone().into(), channel_id));
128	if matches!(until, ParachainSetupStep::CloseRequested) {
129		// NOTE: this is just for expressiveness, otherwise the if-statement is 100% useless.
130		return output
131	}
132
133	output
134}
135
136/// Prefix value for account generation. These numbers are used as seeds to create distinct (para)
137/// accounts.
138///
139/// To maintain sensibility created accounts should always be unique and never overlap. For example,
140/// if for some benchmarking component `c`, accounts are being created `for s in 0..c` with seed
141/// `PREFIX_0 + s`, then we must assert that `c <= PREFIX_1`, meaning that it won't overlap with
142/// `PREFIX_2`.
143///
144/// These values are chosen large enough so that the likelihood of any clash is already very low.
145const PREFIX_0: u32 = 10_000;
146const PREFIX_1: u32 = PREFIX_0 * 2;
147const MAX_UNIQUE_CHANNELS: u32 = 128;
148
149static_assertions::const_assert!(MAX_UNIQUE_CHANNELS < PREFIX_0);
150static_assertions::const_assert!(HRMP_MAX_INBOUND_CHANNELS_BOUND < PREFIX_0);
151static_assertions::const_assert!(HRMP_MAX_OUTBOUND_CHANNELS_BOUND < PREFIX_0);
152
153#[benchmarks(where <T as frame_system::Config>::RuntimeOrigin: From<crate::Origin>)]
154mod benchmarks {
155	use super::*;
156
157	#[benchmark]
158	fn hrmp_init_open_channel() {
159		let sender_id: ParaId = 1u32.into();
160		let sender_origin: crate::Origin = 1u32.into();
161
162		let recipient_id: ParaId = 2u32.into();
163
164		// make sure para is registered, and has enough balance.
165		let ed = T::Currency::minimum_balance();
166		let deposit: BalanceOf<T> = configuration::ActiveConfig::<T>::get()
167			.hrmp_sender_deposit
168			.unique_saturated_into();
169		register_parachain_with_balance::<T>(sender_id, deposit + ed);
170		register_parachain_with_balance::<T>(recipient_id, deposit + ed);
171
172		let capacity = configuration::ActiveConfig::<T>::get().hrmp_channel_max_capacity;
173		let message_size = configuration::ActiveConfig::<T>::get().hrmp_channel_max_message_size;
174
175		#[extrinsic_call]
176		_(sender_origin, recipient_id, capacity, message_size);
177
178		assert_last_event::<T>(
179			Event::<T>::OpenChannelRequested {
180				sender: sender_id,
181				recipient: recipient_id,
182				proposed_max_capacity: capacity,
183				proposed_max_message_size: message_size,
184			}
185			.into(),
186		);
187	}
188
189	#[benchmark]
190	fn hrmp_accept_open_channel() {
191		let [(sender, _), (recipient, recipient_origin)] =
192			establish_para_connection::<T>(1, 2, ParachainSetupStep::Requested);
193
194		#[extrinsic_call]
195		_(recipient_origin, sender);
196
197		assert_last_event::<T>(Event::<T>::OpenChannelAccepted { sender, recipient }.into());
198	}
199
200	#[benchmark]
201	fn hrmp_close_channel() {
202		let [(sender, sender_origin), (recipient, _)] =
203			establish_para_connection::<T>(1, 2, ParachainSetupStep::Established);
204		let channel_id = HrmpChannelId { sender, recipient };
205
206		#[extrinsic_call]
207		_(sender_origin, channel_id.clone());
208
209		assert_last_event::<T>(
210			Event::<T>::ChannelClosed { by_parachain: sender, channel_id }.into(),
211		);
212	}
213
214	// NOTE: a single parachain should have the maximum number of allowed ingress and egress
215	// channels.
216	#[benchmark]
217	fn force_clean_hrmp(
218		// ingress channels to a single leaving parachain that need to be closed.
219		i: Linear<0, { HRMP_MAX_INBOUND_CHANNELS_BOUND - 1 }>,
220		// egress channels to a single leaving parachain that need to be closed.
221		e: Linear<0, { HRMP_MAX_OUTBOUND_CHANNELS_BOUND - 1 }>,
222	) {
223		// first, update the configs to support this many open channels...
224		assert_ok!(Configuration::<T>::set_hrmp_max_parachain_outbound_channels(
225			frame_system::RawOrigin::Root.into(),
226			e + 1
227		));
228		assert_ok!(Configuration::<T>::set_hrmp_max_parachain_inbound_channels(
229			frame_system::RawOrigin::Root.into(),
230			i + 1
231		));
232		assert_ok!(Configuration::<T>::set_max_downward_message_size(
233			frame_system::RawOrigin::Root.into(),
234			1024
235		));
236		// .. and enact it.
237		Configuration::<T>::initializer_on_new_session(&Shared::<T>::scheduled_session());
238
239		let config = configuration::ActiveConfig::<T>::get();
240		let deposit: BalanceOf<T> = config.hrmp_sender_deposit.unique_saturated_into();
241
242		let para: ParaId = 1u32.into();
243		register_parachain_with_balance::<T>(para, deposit);
244		T::Currency::make_free_balance_be(&para.into_account_truncating(), deposit * 256u32.into());
245
246		for ingress_para_id in 0..i {
247			// establish ingress channels to `para`.
248			let ingress_para_id = ingress_para_id + PREFIX_0;
249			let _ = establish_para_connection::<T>(
250				ingress_para_id,
251				para.into(),
252				ParachainSetupStep::Established,
253			);
254		}
255
256		// nothing should be left unprocessed.
257		assert_eq!(HrmpOpenChannelRequestsList::<T>::decode_len().unwrap_or_default(), 0);
258
259		for egress_para_id in 0..e {
260			// establish egress channels to `para`.
261			let egress_para_id = egress_para_id + PREFIX_1;
262			let _ = establish_para_connection::<T>(
263				para.into(),
264				egress_para_id,
265				ParachainSetupStep::Established,
266			);
267		}
268
269		// nothing should be left unprocessed.
270		assert_eq!(HrmpOpenChannelRequestsList::<T>::decode_len().unwrap_or_default(), 0);
271
272		// all in all, we have created this many channels.
273		assert_eq!(HrmpChannels::<T>::iter().count() as u32, i + e);
274
275		#[extrinsic_call]
276		_(frame_system::Origin::<T>::Root, para, i, e);
277
278		// all in all, all of them must be gone by now.
279		assert_eq!(HrmpChannels::<T>::iter().count() as u32, 0);
280		// borrow this function from the tests to make sure state is clear, given that we do a lot
281		// of out-of-ordinary ops here.
282		Hrmp::<T>::assert_storage_consistency_exhaustive();
283	}
284
285	#[benchmark]
286	fn force_process_hrmp_open(
287		// number of channels that need to be processed. Worse case is an N-M relation: unique
288		// sender and recipients for all channels.
289		c: Linear<0, MAX_UNIQUE_CHANNELS>,
290	) {
291		for id in 0..c {
292			let _ = establish_para_connection::<T>(
293				PREFIX_0 + id,
294				PREFIX_1 + id,
295				ParachainSetupStep::Accepted,
296			);
297		}
298		assert_eq!(HrmpOpenChannelRequestsList::<T>::decode_len().unwrap_or_default() as u32, c);
299
300		#[extrinsic_call]
301		_(frame_system::Origin::<T>::Root, c);
302
303		assert_eq!(HrmpOpenChannelRequestsList::<T>::decode_len().unwrap_or_default() as u32, 0);
304	}
305
306	#[benchmark]
307	fn force_process_hrmp_close(
308		// number of channels that need to be processed. Worse case is an N-M relation: unique
309		// sender and recipients for all channels.
310		c: Linear<0, MAX_UNIQUE_CHANNELS>,
311	) {
312		for id in 0..c {
313			let _ = establish_para_connection::<T>(
314				PREFIX_0 + id,
315				PREFIX_1 + id,
316				ParachainSetupStep::CloseRequested,
317			);
318		}
319
320		assert_eq!(HrmpCloseChannelRequestsList::<T>::decode_len().unwrap_or_default() as u32, c);
321
322		#[extrinsic_call]
323		_(frame_system::Origin::<T>::Root, c);
324
325		assert_eq!(HrmpCloseChannelRequestsList::<T>::decode_len().unwrap_or_default() as u32, 0);
326	}
327
328	#[benchmark]
329	fn hrmp_cancel_open_request(
330		// number of items already existing in the `HrmpOpenChannelRequestsList`, other than the
331		// one that we remove.
332		c: Linear<0, MAX_UNIQUE_CHANNELS>,
333	) {
334		for id in 0..c {
335			let _ = establish_para_connection::<T>(
336				PREFIX_0 + id,
337				PREFIX_1 + id,
338				ParachainSetupStep::Requested,
339			);
340		}
341
342		let [(sender, sender_origin), (recipient, _)] =
343			establish_para_connection::<T>(1, 2, ParachainSetupStep::Requested);
344		assert_eq!(
345			HrmpOpenChannelRequestsList::<T>::decode_len().unwrap_or_default() as u32,
346			c + 1
347		);
348		let channel_id = HrmpChannelId { sender, recipient };
349
350		#[extrinsic_call]
351		_(sender_origin, channel_id, c + 1);
352
353		assert_eq!(HrmpOpenChannelRequestsList::<T>::decode_len().unwrap_or_default() as u32, c);
354	}
355
356	// worse case will be `n` parachain channel requests, where in all of them either the sender or
357	// the recipient need to be cleaned. This enforces the deposit of at least one to be processed.
358	// No code path for triggering two deposit process exists.
359	#[benchmark]
360	fn clean_open_channel_requests(c: Linear<0, MAX_UNIQUE_CHANNELS>) {
361		for id in 0..c {
362			let _ = establish_para_connection::<T>(
363				PREFIX_0 + id,
364				PREFIX_1 + id,
365				ParachainSetupStep::Requested,
366			);
367		}
368
369		assert_eq!(HrmpOpenChannelRequestsList::<T>::decode_len().unwrap_or_default() as u32, c);
370		let outgoing = (0..c).map(|id| (id + PREFIX_1).into()).collect::<Vec<ParaId>>();
371		let config = configuration::ActiveConfig::<T>::get();
372
373		#[block]
374		{
375			Hrmp::<T>::clean_open_channel_requests(&config, &outgoing);
376		}
377
378		assert_eq!(HrmpOpenChannelRequestsList::<T>::decode_len().unwrap_or_default() as u32, 0);
379	}
380
381	#[benchmark]
382	fn force_open_hrmp_channel(
383		// Weight parameter only accepts `u32`, `0` and `1` used to represent `false` and `true`,
384		// respectively.
385		c: Linear<0, 1>,
386	) {
387		let sender_id: ParaId = 1u32.into();
388		let sender_origin: crate::Origin = 1u32.into();
389		let recipient_id: ParaId = 2u32.into();
390
391		// Make sure para is registered. The sender does actually need the normal deposit because it
392		// is first going the "init" route.
393		let ed = T::Currency::minimum_balance();
394		let sender_deposit: BalanceOf<T> = configuration::ActiveConfig::<T>::get()
395			.hrmp_sender_deposit
396			.unique_saturated_into();
397		register_parachain_with_balance::<T>(sender_id, sender_deposit + ed);
398		register_parachain_with_balance::<T>(recipient_id, Zero::zero());
399
400		let capacity = configuration::ActiveConfig::<T>::get().hrmp_channel_max_capacity;
401		let message_size = configuration::ActiveConfig::<T>::get().hrmp_channel_max_message_size;
402
403		let channel_id = HrmpChannelId { sender: sender_id, recipient: recipient_id };
404		if c == 1 {
405			// this will consume more weight if a channel _request_ already exists, because it
406			// will need to clear the request.
407			assert_ok!(Hrmp::<T>::hrmp_init_open_channel(
408				sender_origin.clone().into(),
409				recipient_id,
410				capacity,
411				message_size
412			));
413			assert!(HrmpOpenChannelRequests::<T>::get(&channel_id).is_some());
414		} else {
415			if HrmpOpenChannelRequests::<T>::get(&channel_id).is_some() {
416				assert_ok!(Hrmp::<T>::hrmp_cancel_open_request(
417					sender_origin.clone().into(),
418					channel_id.clone(),
419					MAX_UNIQUE_CHANNELS,
420				));
421			}
422			assert!(HrmpOpenChannelRequests::<T>::get(&channel_id).is_none());
423		}
424
425		// but the _channel_ should not exist
426		assert!(HrmpChannels::<T>::get(&channel_id).is_none());
427
428		#[extrinsic_call]
429		_(frame_system::Origin::<T>::Root, sender_id, recipient_id, capacity, message_size);
430
431		assert_last_event::<T>(
432			Event::<T>::HrmpChannelForceOpened {
433				sender: sender_id,
434				recipient: recipient_id,
435				proposed_max_capacity: capacity,
436				proposed_max_message_size: message_size,
437			}
438			.into(),
439		);
440	}
441
442	#[benchmark]
443	fn establish_system_channel() {
444		let sender_id: ParaId = 1u32.into();
445		let recipient_id: ParaId = 2u32.into();
446
447		let caller: T::AccountId = whitelisted_caller();
448		let config = configuration::ActiveConfig::<T>::get();
449
450		// make sure para is registered, and has zero balance.
451		register_parachain_with_balance::<T>(sender_id, Zero::zero());
452		register_parachain_with_balance::<T>(recipient_id, Zero::zero());
453
454		let capacity = config.hrmp_channel_max_capacity;
455		let message_size = config.hrmp_channel_max_message_size;
456
457		#[extrinsic_call]
458		_(frame_system::RawOrigin::Signed(caller), sender_id, recipient_id);
459
460		assert_last_event::<T>(
461			Event::<T>::HrmpSystemChannelOpened {
462				sender: sender_id,
463				recipient: recipient_id,
464				proposed_max_capacity: capacity,
465				proposed_max_message_size: message_size,
466			}
467			.into(),
468		);
469	}
470
471	#[benchmark]
472	fn poke_channel_deposits() {
473		let sender_id: ParaId = 1u32.into();
474		let recipient_id: ParaId = 2u32.into();
475		let channel_id = HrmpChannelId { sender: sender_id, recipient: recipient_id };
476
477		let caller: T::AccountId = whitelisted_caller();
478		let config = configuration::ActiveConfig::<T>::get();
479
480		// make sure para is registered, and has balance to reserve.
481		let ed = T::Currency::minimum_balance();
482		let sender_deposit: BalanceOf<T> = config.hrmp_sender_deposit.unique_saturated_into();
483		let recipient_deposit: BalanceOf<T> = config.hrmp_recipient_deposit.unique_saturated_into();
484		register_parachain_with_balance::<T>(sender_id, ed + sender_deposit);
485		register_parachain_with_balance::<T>(recipient_id, ed + recipient_deposit);
486
487		// Our normal establishment won't actually reserve deposits, so just insert them directly.
488		HrmpChannels::<T>::insert(
489			&channel_id,
490			HrmpChannel {
491				sender_deposit: config.hrmp_sender_deposit,
492				recipient_deposit: config.hrmp_recipient_deposit,
493				max_capacity: config.hrmp_channel_max_capacity,
494				max_total_size: config.hrmp_channel_max_total_size,
495				max_message_size: config.hrmp_channel_max_message_size,
496				msg_count: 0,
497				total_size: 0,
498				mqc_head: None,
499			},
500		);
501		// Actually reserve the deposits.
502		let _ = T::Currency::reserve(&sender_id.into_account_truncating(), sender_deposit);
503		let _ = T::Currency::reserve(&recipient_id.into_account_truncating(), recipient_deposit);
504
505		#[extrinsic_call]
506		_(frame_system::RawOrigin::Signed(caller), sender_id, recipient_id);
507
508		assert_last_event::<T>(
509			Event::<T>::OpenChannelDepositsUpdated { sender: sender_id, recipient: recipient_id }
510				.into(),
511		);
512		let channel = HrmpChannels::<T>::get(&channel_id).unwrap();
513		// Check that the deposit was updated in the channel state.
514		assert_eq!(channel.sender_deposit, 0);
515		assert_eq!(channel.recipient_deposit, 0);
516		// And that the funds were unreserved.
517		assert_eq!(
518			T::Currency::reserved_balance(&sender_id.into_account_truncating()),
519			0u128.unique_saturated_into()
520		);
521		assert_eq!(
522			T::Currency::reserved_balance(&recipient_id.into_account_truncating()),
523			0u128.unique_saturated_into()
524		);
525	}
526
527	#[benchmark]
528	fn establish_channel_with_system() {
529		let sender_id = 1u32;
530		let recipient_id: ParaId = 2u32.into();
531
532		let sender_origin: crate::Origin = sender_id.into();
533
534		// make sure para is registered, and has zero balance.
535		register_parachain_with_balance::<T>(sender_id.into(), Zero::zero());
536		register_parachain_with_balance::<T>(recipient_id, Zero::zero());
537
538		#[extrinsic_call]
539		_(sender_origin, recipient_id);
540
541		let (max_message_size, max_capacity) = T::DefaultChannelSizeAndCapacityWithSystem::get();
542
543		assert_has_event::<T>(
544			Event::<T>::HrmpSystemChannelOpened {
545				sender: sender_id.into(),
546				recipient: recipient_id,
547				proposed_max_capacity: max_capacity,
548				proposed_max_message_size: max_message_size,
549			}
550			.into(),
551		);
552
553		assert_has_event::<T>(
554			Event::<T>::HrmpSystemChannelOpened {
555				sender: recipient_id,
556				recipient: sender_id.into(),
557				proposed_max_capacity: max_capacity,
558				proposed_max_message_size: max_message_size,
559			}
560			.into(),
561		);
562	}
563
564	impl_benchmark_test_suite!(
565		Hrmp,
566		crate::mock::new_test_ext(crate::hrmp::tests::GenesisConfigBuilder::default().build()),
567		crate::mock::Test
568	);
569}