Files
pezkuwi-subxt/substrate/frame/message-queue/src/benchmarking.rs
T
Oliver Tale-Yazdi 7e7fe99047 [FRAME] Make MQ pallet re-entrancy safe (#2356)
Closes https://github.com/paritytech/polkadot-sdk/issues/2319

Changes:
- Ensure that only `enqueue_message(s)` is callable from within the
message processor. This prevents messed up storage that can currently
happen when the pallet is called into recursively.
- Use `H256` instead of `[u8; 32]` for clearer API.

## Details

The re-entracy check is done with the `environmental` crate by adding a
`with_service_mutex(f)` function that runs the closure exclusively. This
works since the MQ pallet is not instantiable.

---------

Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io>
Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com>
2023-12-07 17:48:13 +01:00

284 lines
7.8 KiB
Rust

// This file is part of Substrate.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Benchmarking for the message queue pallet.
#![cfg(feature = "runtime-benchmarks")]
#![allow(unused_assignments)] // Needed for `ready_ring_knit`.
use super::{mock_helpers::*, Pallet as MessageQueue, *};
use frame_benchmarking::v2::*;
use frame_support::traits::Get;
use frame_system::RawOrigin;
use sp_io::hashing::blake2_256;
use sp_std::prelude::*;
#[benchmarks(
where
<<T as Config>::MessageProcessor as ProcessMessage>::Origin: From<u32> + PartialEq,
<T as Config>::Size: From<u32>,
// NOTE: We need to generate multiple origins, therefore Origin is `From<u32>`. The
// `PartialEq` is for asserting the outcome of the ring (un)knitting and *could* be
// removed if really necessary.
)]
mod benchmarks {
use super::*;
// Worst case path of `ready_ring_knit`.
#[benchmark]
fn ready_ring_knit() {
let mid: MessageOriginOf<T> = 1.into();
build_ring::<T>(&[0.into(), mid.clone(), 2.into()]);
unknit::<T>(&mid);
assert_ring::<T>(&[0.into(), 2.into()]);
let mut neighbours = None;
#[block]
{
neighbours = MessageQueue::<T>::ready_ring_knit(&mid).ok();
}
// The neighbours needs to be modified manually.
BookStateFor::<T>::mutate(&mid, |b| b.ready_neighbours = neighbours);
assert_ring::<T>(&[0.into(), 2.into(), mid]);
}
// Worst case path of `ready_ring_unknit`.
#[benchmark]
fn ready_ring_unknit() {
build_ring::<T>(&[0.into(), 1.into(), 2.into()]);
assert_ring::<T>(&[0.into(), 1.into(), 2.into()]);
let o: MessageOriginOf<T> = 0.into();
let neighbours = BookStateFor::<T>::get(&o).ready_neighbours.unwrap();
#[block]
{
MessageQueue::<T>::ready_ring_unknit(&o, neighbours);
}
assert_ring::<T>(&[1.into(), 2.into()]);
}
// `service_queues` without any queue processing.
#[benchmark]
fn service_queue_base() {
#[block]
{
MessageQueue::<T>::service_queue(0.into(), &mut WeightMeter::new(), Weight::MAX);
}
}
// `service_page` without any message processing but with page completion.
#[benchmark]
fn service_page_base_completion() {
let origin: MessageOriginOf<T> = 0.into();
let page = PageOf::<T>::default();
Pages::<T>::insert(&origin, 0, &page);
let mut book_state = single_page_book::<T>();
let mut meter = WeightMeter::new();
let limit = Weight::MAX;
#[block]
{
MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit);
}
}
// `service_page` without any message processing and without page completion.
#[benchmark]
fn service_page_base_no_completion() {
let origin: MessageOriginOf<T> = 0.into();
let mut page = PageOf::<T>::default();
// Mock the storage such that `is_complete` returns `false` but `peek_first` returns `None`.
page.first = 1.into();
page.remaining = 1.into();
Pages::<T>::insert(&origin, 0, &page);
let mut book_state = single_page_book::<T>();
let mut meter = WeightMeter::new();
let limit = Weight::MAX;
#[block]
{
MessageQueue::<T>::service_page(&origin, &mut book_state, &mut meter, limit);
}
}
// Processing a single message from a page.
#[benchmark]
fn service_page_item() {
let msg = vec![1u8; MaxMessageLenOf::<T>::get() as usize];
let mut page = page::<T>(&msg.clone());
let mut book = book_for::<T>(&page);
assert!(page.peek_first().is_some(), "There is one message");
let mut weight = WeightMeter::new();
#[block]
{
let status = MessageQueue::<T>::service_page_item(
&0u32.into(),
0,
&mut book,
&mut page,
&mut weight,
Weight::MAX,
);
assert_eq!(status, ItemExecutionStatus::Executed(true));
}
// Check that it was processed.
assert_last_event::<T>(
Event::Processed {
id: blake2_256(&msg).into(),
origin: 0.into(),
weight_used: 1.into_weight(),
success: true,
}
.into(),
);
let (_, processed, _) = page.peek_index(0).unwrap();
assert!(processed);
assert_eq!(book.message_count, 0);
}
// Worst case for calling `bump_service_head`.
#[benchmark]
fn bump_service_head() {
setup_bump_service_head::<T>(0.into(), 10.into());
let mut weight = WeightMeter::new();
#[block]
{
MessageQueue::<T>::bump_service_head(&mut weight);
}
assert_eq!(ServiceHead::<T>::get().unwrap(), 10u32.into());
assert_eq!(weight.consumed(), T::WeightInfo::bump_service_head());
}
#[benchmark]
fn reap_page() {
// Mock the storage to get a *cullable* but not *reapable* page.
let origin: MessageOriginOf<T> = 0.into();
let mut book = single_page_book::<T>();
let (page, msgs) = full_page::<T>();
for p in 0..T::MaxStale::get() * T::MaxStale::get() {
if p == 0 {
Pages::<T>::insert(&origin, p, &page);
}
book.end += 1;
book.count += 1;
book.message_count += msgs as u64;
book.size += page.remaining_size.into() as u64;
}
book.begin = book.end - T::MaxStale::get();
BookStateFor::<T>::insert(&origin, &book);
assert!(Pages::<T>::contains_key(&origin, 0));
#[extrinsic_call]
_(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0);
assert_last_event::<T>(Event::PageReaped { origin: 0.into(), index: 0 }.into());
assert!(!Pages::<T>::contains_key(&origin, 0));
}
// Worst case for `execute_overweight` where the page is removed as completed.
//
// The worst case occurs when executing the last message in a page of which all are skipped
// since it is using `peek_index` which has linear complexities.
#[benchmark]
fn execute_overweight_page_removed() {
let origin: MessageOriginOf<T> = 0.into();
let (mut page, msgs) = full_page::<T>();
// Skip all messages.
for _ in 1..msgs {
page.skip_first(true);
}
page.skip_first(false);
let book = book_for::<T>(&page);
Pages::<T>::insert(&origin, 0, &page);
BookStateFor::<T>::insert(&origin, &book);
#[block]
{
MessageQueue::<T>::execute_overweight(
RawOrigin::Signed(whitelisted_caller()).into(),
0u32.into(),
0u32,
((msgs - 1) as u32).into(),
Weight::MAX,
)
.unwrap();
}
assert_last_event::<T>(
Event::Processed {
id: blake2_256(&((msgs - 1) as u32).encode()).into(),
origin: 0.into(),
weight_used: Weight::from_parts(1, 1),
success: true,
}
.into(),
);
assert!(!Pages::<T>::contains_key(&origin, 0), "Page must be removed");
}
// Worst case for `execute_overweight` where the page is updated.
#[benchmark]
fn execute_overweight_page_updated() {
let origin: MessageOriginOf<T> = 0.into();
let (mut page, msgs) = full_page::<T>();
// Skip all messages.
for _ in 0..msgs {
page.skip_first(false);
}
let book = book_for::<T>(&page);
Pages::<T>::insert(&origin, 0, &page);
BookStateFor::<T>::insert(&origin, &book);
#[block]
{
MessageQueue::<T>::execute_overweight(
RawOrigin::Signed(whitelisted_caller()).into(),
0u32.into(),
0u32,
((msgs - 1) as u32).into(),
Weight::MAX,
)
.unwrap();
}
assert_last_event::<T>(
Event::Processed {
id: blake2_256(&((msgs - 1) as u32).encode()).into(),
origin: 0.into(),
weight_used: Weight::from_parts(1, 1),
success: true,
}
.into(),
);
assert!(Pages::<T>::contains_key(&origin, 0), "Page must be updated");
}
impl_benchmark_test_suite! {
MessageQueue,
crate::mock::new_test_ext::<crate::integration_test::Test>(),
crate::integration_test::Test
}
}