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	use alloc::vec;
41
42	// Worst case path of `ready_ring_knit`.
43	#[benchmark]
44	fn ready_ring_knit() {
45		let mid: MessageOriginOf<T> = 1.into();
46		build_ring::<T>(&[0.into(), mid.clone(), 2.into()]);
47		unknit::<T>(&mid);
48		assert_ring::<T>(&[0.into(), 2.into()]);
49		let mut neighbours = None;
50
51		#[block]
52		{
53			neighbours = MessageQueue::<T>::ready_ring_knit(&mid).ok();
54		}
55
56		// The neighbours needs to be modified manually.
57		BookStateFor::<T>::mutate(&mid, |b| b.ready_neighbours = neighbours);
58		assert_ring::<T>(&[0.into(), 2.into(), mid]);
59	}
60
61	// Worst case path of `ready_ring_unknit`.
62	#[benchmark]
63	fn ready_ring_unknit() {
64		build_ring::<T>(&[0.into(), 1.into(), 2.into()]);
65		assert_ring::<T>(&[0.into(), 1.into(), 2.into()]);
66		let o: MessageOriginOf<T> = 0.into();
67		let neighbours = BookStateFor::<T>::get(&o).ready_neighbours.unwrap();
68
69		#[block]
70		{
71			MessageQueue::<T>::ready_ring_unknit(&o, neighbours);
72		}
73
74		assert_ring::<T>(&[1.into(), 2.into()]);
75	}
76
77	// `service_queues` without any queue processing.
78	#[benchmark]
79	fn service_queue_base() {
80		#[block]
81		{
82			MessageQueue::<T>::service_queue(0.into(), &mut WeightMeter::new(), Weight::MAX);
83		}
84	}
85
86	// `service_page` without any message processing but with page completion.
87	#[benchmark]
88	fn service_page_base_completion() {
89		let origin: MessageOriginOf<T> = 0.into();
90		let page = PageOf::<T>::default();
91		Pages::<T>::insert(&origin, 0, &page);
92		let mut book_state = single_page_book::<T>();
93		let mut meter = WeightMeter::new();
94		let limit = Weight::MAX;
95
96		#[block]
97		{
98			MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit);
99		}
100	}
101
102	// `service_page` without any message processing and without page completion.
103	#[benchmark]
104	fn service_page_base_no_completion() {
105		let origin: MessageOriginOf<T> = 0.into();
106		let mut page = PageOf::<T>::default();
107		// Mock the storage such that `is_complete` returns `false` but `peek_first` returns `None`.
108		page.first = 1.into();
109		page.remaining = 1.into();
110		Pages::<T>::insert(&origin, 0, &page);
111		let mut book_state = single_page_book::<T>();
112		let mut meter = WeightMeter::new();
113		let limit = Weight::MAX;
114
115		#[block]
116		{
117			MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit);
118		}
119	}
120
121	// Processing a single message from a page.
122	#[benchmark]
123	fn service_page_item() {
124		let msg = vec![1u8; MaxMessageLenOf::<T>::get() as usize];
125		let mut page = page::<T>(&msg.clone());
126		let mut book = book_for::<T>(&page);
127		assert!(page.peek_first().is_some(), "There is one message");
128		let mut weight = WeightMeter::new();
129
130		#[block]
131		{
132			let status = MessageQueue::<T>::service_page_item(
133				&0u32.into(),
134				0,
135				&mut book,
136				&mut page,
137				&mut weight,
138				Weight::MAX,
139			);
140			assert_eq!(status, ItemExecutionStatus::Executed(true));
141		}
142
143		// Check that it was processed.
144		assert_last_event::<T>(
145			Event::Processed {
146				id: blake2_256(&msg).into(),
147				origin: 0.into(),
148				weight_used: 1.into_weight(),
149				success: true,
150			}
151			.into(),
152		);
153		let (_, processed, _) = page.peek_index(0).unwrap();
154		assert!(processed);
155		assert_eq!(book.message_count, 0);
156	}
157
158	// Worst case for calling `bump_service_head`.
159	#[benchmark]
160	fn bump_service_head() {
161		setup_bump_service_head::<T>(0.into(), 10.into());
162		let mut weight = WeightMeter::new();
163
164		#[block]
165		{
166			MessageQueue::<T>::bump_service_head(&mut weight);
167		}
168
169		assert_eq!(ServiceHead::<T>::get().unwrap(), 10u32.into());
170		assert_eq!(weight.consumed(), T::WeightInfo::bump_service_head());
171	}
172
173	// Worst case for calling `bump_service_head`.
174	#[benchmark]
175	fn set_service_head() {
176		setup_bump_service_head::<T>(0.into(), 1.into());
177		let mut weight = WeightMeter::new();
178		assert_eq!(ServiceHead::<T>::get().unwrap(), 0u32.into());
179
180		#[block]
181		{
182			assert!(MessageQueue::<T>::set_service_head(&mut weight, &1u32.into()).unwrap());
183		}
184
185		assert_eq!(ServiceHead::<T>::get().unwrap(), 1u32.into());
186		assert_eq!(weight.consumed(), T::WeightInfo::set_service_head());
187	}
188
189	#[benchmark]
190	fn reap_page() {
191		// Mock the storage to get a *cullable* but not *reapable* page.
192		let origin: MessageOriginOf<T> = 0.into();
193		let mut book = single_page_book::<T>();
194		let (page, msgs) = full_page::<T>();
195
196		for p in 0..T::MaxStale::get() * T::MaxStale::get() {
197			if p == 0 {
198				Pages::<T>::insert(&origin, p, &page);
199			}
200			book.end += 1;
201			book.count += 1;
202			book.message_count += msgs as u64;
203			book.size += page.remaining_size.into() as u64;
204		}
205		book.begin = book.end - T::MaxStale::get();
206		BookStateFor::<T>::insert(&origin, &book);
207		assert!(Pages::<T>::contains_key(&origin, 0));
208
209		#[extrinsic_call]
210		_(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0);
211
212		assert_last_event::<T>(Event::PageReaped { origin: 0.into(), index: 0 }.into());
213		assert!(!Pages::<T>::contains_key(&origin, 0));
214	}
215
216	// Worst case for `execute_overweight` where the page is removed as completed.
217	//
218	// The worst case occurs when executing the last message in a page of which all are skipped
219	// since it is using `peek_index` which has linear complexities.
220	#[benchmark]
221	fn execute_overweight_page_removed() {
222		let origin: MessageOriginOf<T> = 0.into();
223		let (mut page, msgs) = full_page::<T>();
224		// Skip all messages.
225		for _ in 1..msgs {
226			page.skip_first(true);
227		}
228		page.skip_first(false);
229		let book = book_for::<T>(&page);
230		Pages::<T>::insert(&origin, 0, &page);
231		BookStateFor::<T>::insert(&origin, &book);
232
233		#[block]
234		{
235			MessageQueue::<T>::execute_overweight(
236				RawOrigin::Signed(whitelisted_caller()).into(),
237				0u32.into(),
238				0u32,
239				((msgs - 1) as u32).into(),
240				Weight::MAX,
241			)
242			.unwrap();
243		}
244
245		assert_last_event::<T>(
246			Event::Processed {
247				id: blake2_256(&((msgs - 1) as u32).encode()).into(),
248				origin: 0.into(),
249				weight_used: Weight::from_parts(1, 1),
250				success: true,
251			}
252			.into(),
253		);
254		assert!(!Pages::<T>::contains_key(&origin, 0), "Page must be removed");
255	}
256
257	// Worst case for `execute_overweight` where the page is updated.
258	#[benchmark]
259	fn execute_overweight_page_updated() {
260		let origin: MessageOriginOf<T> = 0.into();
261		let (mut page, msgs) = full_page::<T>();
262		// Skip all messages.
263		for _ in 0..msgs {
264			page.skip_first(false);
265		}
266		let book = book_for::<T>(&page);
267		Pages::<T>::insert(&origin, 0, &page);
268		BookStateFor::<T>::insert(&origin, &book);
269
270		#[block]
271		{
272			MessageQueue::<T>::execute_overweight(
273				RawOrigin::Signed(whitelisted_caller()).into(),
274				0u32.into(),
275				0u32,
276				((msgs - 1) as u32).into(),
277				Weight::MAX,
278			)
279			.unwrap();
280		}
281
282		assert_last_event::<T>(
283			Event::Processed {
284				id: blake2_256(&((msgs - 1) as u32).encode()).into(),
285				origin: 0.into(),
286				weight_used: Weight::from_parts(1, 1),
287				success: true,
288			}
289			.into(),
290		);
291		assert!(Pages::<T>::contains_key(&origin, 0), "Page must be updated");
292	}
293
294	impl_benchmark_test_suite! {
295		MessageQueue,
296		crate::mock::new_test_ext::<crate::integration_test::Test>(),
297		crate::integration_test::Test
298	}
299}