referrerpolicy=no-referrer-when-downgrade

pallet_message_queue/
benchmarking.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Benchmarking for the message queue pallet.
19
20#![cfg(feature = "runtime-benchmarks")]
21#![allow(unused_assignments)] // Needed for `ready_ring_knit`.
22
23use super::{mock_helpers::*, Pallet as MessageQueue, *};
24
25use frame_benchmarking::v2::*;
26use frame_support::traits::Get;
27use frame_system::RawOrigin;
28use sp_io::hashing::blake2_256;
29
30#[benchmarks(
31	where
32		<<T as Config>::MessageProcessor as ProcessMessage>::Origin: From<u32> + PartialEq,
33		<T as Config>::Size: From<u32>,
34		// NOTE: We need to generate multiple origins, therefore Origin is `From<u32>`. The
35		// `PartialEq` is for asserting the outcome of the ring (un)knitting and *could* be
36		// removed if really necessary.
37)]
38mod benchmarks {
39	use super::*;
40
41	// Worst case path of `ready_ring_knit`.
42	#[benchmark]
43	fn ready_ring_knit() {
44		let mid: MessageOriginOf<T> = 1.into();
45		build_ring::<T>(&[0.into(), mid.clone(), 2.into()]);
46		unknit::<T>(&mid);
47		assert_ring::<T>(&[0.into(), 2.into()]);
48		let mut neighbours = None;
49
50		#[block]
51		{
52			neighbours = MessageQueue::<T>::ready_ring_knit(&mid).ok();
53		}
54
55		// The neighbours needs to be modified manually.
56		BookStateFor::<T>::mutate(&mid, |b| b.ready_neighbours = neighbours);
57		assert_ring::<T>(&[0.into(), 2.into(), mid]);
58	}
59
60	// Worst case path of `ready_ring_unknit`.
61	#[benchmark]
62	fn ready_ring_unknit() {
63		build_ring::<T>(&[0.into(), 1.into(), 2.into()]);
64		assert_ring::<T>(&[0.into(), 1.into(), 2.into()]);
65		let o: MessageOriginOf<T> = 0.into();
66		let neighbours = BookStateFor::<T>::get(&o).ready_neighbours.unwrap();
67
68		#[block]
69		{
70			MessageQueue::<T>::ready_ring_unknit(&o, neighbours);
71		}
72
73		assert_ring::<T>(&[1.into(), 2.into()]);
74	}
75
76	// `service_queues` without any queue processing.
77	#[benchmark]
78	fn service_queue_base() {
79		#[block]
80		{
81			MessageQueue::<T>::service_queue(0.into(), &mut WeightMeter::new(), Weight::MAX);
82		}
83	}
84
85	// `service_page` without any message processing but with page completion.
86	#[benchmark]
87	fn service_page_base_completion() {
88		let origin: MessageOriginOf<T> = 0.into();
89		let page = PageOf::<T>::default();
90		Pages::<T>::insert(&origin, 0, &page);
91		let mut book_state = single_page_book::<T>();
92		let mut meter = WeightMeter::new();
93		let limit = Weight::MAX;
94
95		#[block]
96		{
97			MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit);
98		}
99	}
100
101	// `service_page` without any message processing and without page completion.
102	#[benchmark]
103	fn service_page_base_no_completion() {
104		let origin: MessageOriginOf<T> = 0.into();
105		let mut page = PageOf::<T>::default();
106		// Mock the storage such that `is_complete` returns `false` but `peek_first` returns `None`.
107		page.first = 1.into();
108		page.remaining = 1.into();
109		Pages::<T>::insert(&origin, 0, &page);
110		let mut book_state = single_page_book::<T>();
111		let mut meter = WeightMeter::new();
112		let limit = Weight::MAX;
113
114		#[block]
115		{
116			MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit);
117		}
118	}
119
120	// Processing a single message from a page.
121	#[benchmark]
122	fn service_page_item() {
123		let msg = vec![1u8; MaxMessageLenOf::<T>::get() as usize];
124		let mut page = page::<T>(&msg.clone());
125		let mut book = book_for::<T>(&page);
126		assert!(page.peek_first().is_some(), "There is one message");
127		let mut weight = WeightMeter::new();
128
129		#[block]
130		{
131			let status = MessageQueue::<T>::service_page_item(
132				&0u32.into(),
133				0,
134				&mut book,
135				&mut page,
136				&mut weight,
137				Weight::MAX,
138			);
139			assert_eq!(status, ItemExecutionStatus::Executed(true));
140		}
141
142		// Check that it was processed.
143		assert_last_event::<T>(
144			Event::Processed {
145				id: blake2_256(&msg).into(),
146				origin: 0.into(),
147				weight_used: 1.into_weight(),
148				success: true,
149			}
150			.into(),
151		);
152		let (_, processed, _) = page.peek_index(0).unwrap();
153		assert!(processed);
154		assert_eq!(book.message_count, 0);
155	}
156
157	// Worst case for calling `bump_service_head`.
158	#[benchmark]
159	fn bump_service_head() {
160		setup_bump_service_head::<T>(0.into(), 10.into());
161		let mut weight = WeightMeter::new();
162
163		#[block]
164		{
165			MessageQueue::<T>::bump_service_head(&mut weight);
166		}
167
168		assert_eq!(ServiceHead::<T>::get().unwrap(), 10u32.into());
169		assert_eq!(weight.consumed(), T::WeightInfo::bump_service_head());
170	}
171
172	// Worst case for calling `bump_service_head`.
173	#[benchmark]
174	fn set_service_head() {
175		setup_bump_service_head::<T>(0.into(), 1.into());
176		let mut weight = WeightMeter::new();
177		assert_eq!(ServiceHead::<T>::get().unwrap(), 0u32.into());
178
179		#[block]
180		{
181			assert!(MessageQueue::<T>::set_service_head(&mut weight, &1u32.into()).unwrap());
182		}
183
184		assert_eq!(ServiceHead::<T>::get().unwrap(), 1u32.into());
185		assert_eq!(weight.consumed(), T::WeightInfo::set_service_head());
186	}
187
188	#[benchmark]
189	fn reap_page() {
190		// Mock the storage to get a *cullable* but not *reapable* page.
191		let origin: MessageOriginOf<T> = 0.into();
192		let mut book = single_page_book::<T>();
193		let (page, msgs) = full_page::<T>();
194
195		for p in 0..T::MaxStale::get() * T::MaxStale::get() {
196			if p == 0 {
197				Pages::<T>::insert(&origin, p, &page);
198			}
199			book.end += 1;
200			book.count += 1;
201			book.message_count += msgs as u64;
202			book.size += page.remaining_size.into() as u64;
203		}
204		book.begin = book.end - T::MaxStale::get();
205		BookStateFor::<T>::insert(&origin, &book);
206		assert!(Pages::<T>::contains_key(&origin, 0));
207
208		#[extrinsic_call]
209		_(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0);
210
211		assert_last_event::<T>(Event::PageReaped { origin: 0.into(), index: 0 }.into());
212		assert!(!Pages::<T>::contains_key(&origin, 0));
213	}
214
215	// Worst case for `execute_overweight` where the page is removed as completed.
216	//
217	// The worst case occurs when executing the last message in a page of which all are skipped
218	// since it is using `peek_index` which has linear complexities.
219	#[benchmark]
220	fn execute_overweight_page_removed() {
221		let origin: MessageOriginOf<T> = 0.into();
222		let (mut page, msgs) = full_page::<T>();
223		// Skip all messages.
224		for _ in 1..msgs {
225			page.skip_first(true);
226		}
227		page.skip_first(false);
228		let book = book_for::<T>(&page);
229		Pages::<T>::insert(&origin, 0, &page);
230		BookStateFor::<T>::insert(&origin, &book);
231
232		#[block]
233		{
234			MessageQueue::<T>::execute_overweight(
235				RawOrigin::Signed(whitelisted_caller()).into(),
236				0u32.into(),
237				0u32,
238				((msgs - 1) as u32).into(),
239				Weight::MAX,
240			)
241			.unwrap();
242		}
243
244		assert_last_event::<T>(
245			Event::Processed {
246				id: blake2_256(&((msgs - 1) as u32).encode()).into(),
247				origin: 0.into(),
248				weight_used: Weight::from_parts(1, 1),
249				success: true,
250			}
251			.into(),
252		);
253		assert!(!Pages::<T>::contains_key(&origin, 0), "Page must be removed");
254	}
255
256	// Worst case for `execute_overweight` where the page is updated.
257	#[benchmark]
258	fn execute_overweight_page_updated() {
259		let origin: MessageOriginOf<T> = 0.into();
260		let (mut page, msgs) = full_page::<T>();
261		// Skip all messages.
262		for _ in 0..msgs {
263			page.skip_first(false);
264		}
265		let book = book_for::<T>(&page);
266		Pages::<T>::insert(&origin, 0, &page);
267		BookStateFor::<T>::insert(&origin, &book);
268
269		#[block]
270		{
271			MessageQueue::<T>::execute_overweight(
272				RawOrigin::Signed(whitelisted_caller()).into(),
273				0u32.into(),
274				0u32,
275				((msgs - 1) as u32).into(),
276				Weight::MAX,
277			)
278			.unwrap();
279		}
280
281		assert_last_event::<T>(
282			Event::Processed {
283				id: blake2_256(&((msgs - 1) as u32).encode()).into(),
284				origin: 0.into(),
285				weight_used: Weight::from_parts(1, 1),
286				success: true,
287			}
288			.into(),
289		);
290		assert!(Pages::<T>::contains_key(&origin, 0), "Page must be updated");
291	}
292
293	impl_benchmark_test_suite! {
294		MessageQueue,
295		crate::mock::new_test_ext::<crate::integration_test::Test>(),
296		crate::integration_test::Test
297	}
298}