From 206a7af338f57126a9ae9388408d7210bec833f9 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 12 Oct 2022 11:13:04 +0100 Subject: [PATCH 001/110] The message queue --- Cargo.lock | 33 ++- Cargo.toml | 1 + frame/message-queue/Cargo.toml | 39 +++ frame/message-queue/src/lib.rs | 428 +++++++++++++++++++++++++++++ frame/message-queue/src/tests.rs | 115 ++++++++ frame/message-queue/src/weights.rs | 65 +++++ 6 files changed, 673 insertions(+), 8 deletions(-) create mode 100644 frame/message-queue/Cargo.toml create mode 100644 frame/message-queue/src/lib.rs create mode 100644 frame/message-queue/src/tests.rs create mode 100644 frame/message-queue/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 04b90dfffba1e..3fe395b19da48 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5824,6 +5824,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-message-queue" +version = "0.9.29" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-mmr" version = "4.0.0-dev" @@ -9099,9 +9116,9 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8980cafbe98a7ee7a9cc16b32ebce542c77883f512d83fbf2ddc8f6a85ea74c9" +checksum = "333af15b02563b8182cd863f925bd31ef8fa86a0e095d30c091956057d436153" dependencies = [ "bitvec", "cfg-if 1.0.0", @@ -9113,9 +9130,9 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4260c630e8a8a33429d1688eff2f163f24c65a4e1b1578ef6b565061336e4b6f" +checksum = "53f56acbd0743d29ffa08f911ab5397def774ad01bab3786804cf6ee057fb5e1" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -9271,9 +9288,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.136" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" dependencies = [ "serde_derive", ] @@ -9299,9 +9316,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.136" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +checksum = "81fa1584d3d1bcacd84c277a0dfe21f5b0f6accf4a23d04d4c6d61f1af522b4c" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index e203cbbee7e0d..4f9c086778013 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,7 @@ members = [ "frame/offences", "frame/preimage", "frame/proxy", + "frame/message-queue", "frame/nomination-pools", "frame/nomination-pools/benchmarking", "frame/nomination-pools/test-staking", diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml new file mode 100644 index 0000000000000..5cbc8ff068e3d --- /dev/null +++ b/frame/message-queue/Cargo.toml @@ -0,0 +1,39 @@ +[package] +authors = ["Parity Technologies "] +edition = "2021" +name = "pallet-message-queue" +version = "0.9.29" + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +serde = { version = "1.0.137", optional = true, features = ["derive"] } +log = { version = "0.4.17", default-features = false } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "frame-benchmarking?/std", + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs new file mode 100644 index 0000000000000..fd4ff17be1957 --- /dev/null +++ b/frame/message-queue/src/lib.rs @@ -0,0 +1,428 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Pallet to handle XCM message queuing. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(test)] +mod tests; +mod weights; + +use codec::{Decode, Encode, Codec, MaxEncodedLen}; +use frame_support::{parameter_types, BoundedSlice}; +use scale_info::TypeInfo; +use sp_std::{prelude::*, vec}; +use sp_runtime::Saturating; +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; +pub use pallet::*; +pub use weights::WeightInfo; + +/// Type for identifying an overweight message. +type OverweightIndex = u64; +/// Type for identifying a page. +type PageIndex = u32; +/// Type for representing the size of an offset into a page heap. +type Offset = u16; +/// Type for representing the size of an individual encoded `MessageItem`. +type Size = u16; +/// Maximum size of a page's heap. +const HEAP_SIZE: u32 = 1u32 << 16; + +parameter_types! { + pub const HeapSize: u32 = HEAP_SIZE; +} + +/// Data encoded and prefixed to the encoded `MessageItem`. +#[derive(Encode, Decode, MaxEncodedLen)] +pub struct ItemHeader { + /// The length of this item, not including the size of this header. The next item of the page + /// follows immediately after the payload of this item. + payload_len: Size, + /// Whether this item has been processed. + is_processed: bool, +} + +/// A page of messages. Pages always contain at least one item. +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Default)] +pub struct Page { + /// Messages remaining to be processed; this includes overweight messages which have been + /// skipped. + remaining: Size, + /// The heap-offset of the header of the first message item in this page which is ready for + /// processing. + first: Offset, + /// The heap-offset of the header of the last message item in this page. + last: Offset, + /// The heap. If `self.offset == self.heap.len()` then the page is empty and should be deleted. + heap: BoundedVec, +} + +impl Page { + fn from_message(message: &[u8], origin: &[u8]) -> Self { + let len = ItemHeader::max_encoded_len() + origin.len() + message.len(); + let mut heap = Vec::with_capacity(len); + let payload_len = (origin.len() + message.len()) as Size;// TODO: bounded inputs for safety + let h = ItemHeader { payload_len, is_processed: false }; + h.using_encoded(|d| heap.extend_from_slice(d)); + heap.extend_from_slice(origin); + heap.extend_from_slice(message); + Page { + remaining: 1, + first: 0, + last: 0, + heap: BoundedVec::truncate_from(heap), + } + } + + fn try_append_message(&mut self, message: &[u8], origin: &[u8]) -> Result<(), ()> { + let pos = self.heap.len(); + let len = ItemHeader::max_encoded_len() + origin.len() + message.len(); + let payload_len = (origin.len() + message.len()) as Size;// TODO: bounded inputs for safety + let h = ItemHeader { payload_len, is_processed: false }; + if (HEAP_SIZE as usize).saturating_sub(self.heap.len()) < len { + // Can't fit. + return Err(()) + } + + let mut heap = sp_std::mem::replace(&mut self.heap, Default::default()).into_inner(); + h.using_encoded(|d| heap.extend_from_slice(d)); + heap.extend_from_slice(origin); + heap.extend_from_slice(message); + debug_assert!((heap.len() as u32) < HEAP_SIZE, "already checked size; qed"); + self.heap = BoundedVec::truncate_from(heap); + self.last = pos as Offset; + self.remaining.saturating_inc(); + Ok(()) + } + + fn peek_first(&self) -> Option> { + if self.first >= self.last { + return None + } + let mut item_slice = &self.heap[self.first as usize..]; + if let Ok(h) = ItemHeader::decode(&mut item_slice) { + let payload_len = h.payload_len as usize; + if payload_len <= item_slice.len() { + // impossible to truncate since is sliced up from `self.heap: BoundedVec` + return Some(BoundedSlice::truncate_from(&item_slice[..payload_len])) + } + } + debug_assert!(false, "message-queue: heap corruption"); + None + } + + /// Point `first` at the next message, marking the first as processed if `is_processed` is true. + fn skip_first(&mut self, is_processed: bool) { + let f = self.first as usize; + if let Ok(mut h) = ItemHeader::decode(&mut &self.heap[f..]) { + if is_processed && !h.is_processed { + h.is_processed = true; + h.using_encoded(|d| + self.heap[f..f + d.len()].copy_from_slice(d) + ); + self.remaining.saturating_dec(); + } + self.first.saturating_accrue(ItemHeader::max_encoded_len() as Size + h.payload_len); + } + } + + fn is_complete(&self) -> bool { + self.remaining == 0 + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, Debug)] +pub enum ProcessMessageError { + /// The message data format is unknown (e.g. unrecognised header) + BadFormat, + /// The message data is bad (e.g. decoding returns an error). + Corrupt, + /// The message format is unsupported (e.g. old XCM version). + Unsupported, + /// Message processing was not attempted because it was not certain that the weight limit + /// would be respected. The parameter gives the maximum weight which the message could take + /// to process. + Overweight(Weight), +} + +pub trait ProcessMessage { + /// The transport from where a message originates. + type Origin: Codec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + sp_std::fmt::Debug; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + fn process_message(message: &[u8], origin: Self::Origin, weight_limit: Weight) -> Result<(bool, Weight), ProcessMessageError>; +} + +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Default)] +pub struct BookState { + /// The first page with some items to be processed in it. If this is `>= end`, then there are + /// no pages with items to be processing in them. + begin: PageIndex, + /// One more than the last page with some items to be processed in it. + end: PageIndex, + /// The number of pages stored at present. + count: PageIndex, + /// The earliest page still in storage. If this is `>= end`, then there are + /// no pages in storage. Pages up to `head` are reapable if they have a `remaining` + /// field of zero or if `head - page_number` is sufficiently large compared to + /// `count - (end - begin)`. + historical_head: PageIndex, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + /// The module configuration trait. + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// Processor for a message. + type MessageProcessor: ProcessMessage; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Message discarded due to an inability to decode the item. Usually caused by state + /// corruption. + Discarded { hash: T::Hash }, + /// Message discarded due to an error in the `MessageProcessor` (usually a format error). + ProcessingFailed { hash: T::Hash, origin: MessageOriginOf, error: ProcessMessageError }, + /// Message is processed. + Processed { hash: T::Hash, origin: MessageOriginOf, weight_used: Weight, success: bool }, + /// Message placed in overweight queue. + Overweight { hash: T::Hash, origin: MessageOriginOf, index: OverweightIndex }, + } + + #[pallet::error] + pub enum Error { + /// Dummy. + Dummy, + } + + /// The index of the first and last (non-empty) pages. + #[pallet::storage] + pub(super) type BookStateOf = StorageValue<_, BookState, ValueQuery>; + + /// The lowest known unused page index. + #[pallet::storage] + pub(super) type NextPage = StorageValue<_, PageIndex, ValueQuery>; + + /// The map of page indices to pages. + #[pallet::storage] + pub(super) type Pages = StorageMap<_, Blake2_256, PageIndex, Page, OptionQuery>; + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + let weight_used = Weight::zero(); + weight_used + } + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn send( + origin: OriginFor, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + Ok(()) + } + } +} + +impl Pallet { + fn new_page() -> PageIndex { + let page = NextPage::::get(); + NextPage::::put(page + 1); + page + } + + fn do_enqueue_message( + message: BoundedSlice>, + origin: BoundedSlice>, + ) { + let mut book_state = BookStateOf::::get(); + if book_state.end > book_state.begin { + // Already have a page in progress - attempt to append. + let last = book_state.end - 1; + let mut page = match Pages::::get(last) { + Some(p) => p, + None => { + debug_assert!(false, "Corruption: referenced page doesn't exist."); + return + } + }; + if let Ok(_) = page.try_append_message(&message[..], &origin[..]) { + Pages::::insert(last, &page); + return + } + } + // No room on the page or no page - link in a new page. + book_state.end.saturating_inc(); + Pages::::insert(book_state.end - 1, Page::from_message(&message[..], &origin[..])); + BookStateOf::::put(book_state); + } +/* + /// Service the message queue. + /// + /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. + /// + /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. + fn service_queue(weight_limit: Weight) -> Weight { + let mut processed = 0; + let max_weight = weight_limit.saturating_mul(3).saturating_div(4); + let mut weight_left = weight_limit; + let mut maybe_book_state = BookStateOf::::get(); + while let Some(ref mut book_state) = maybe_book_state { + let page_index = book_state.head; + // TODO: Check `weight_left` and bail before doing this storage read. + let mut page = Pages::::get(page_index); + + let bail = loop { + let mut message = page.peek_first(); + + let was_processed = match MessageOriginOf::::decode(&mut message) { + Ok(origin) => { + let hash = T::Hashing::hash_of(message); + use ProcessMessageError::Overweight; + match T::MessageProcessor::process_message(message, origin.clone(), weight_left) { + Err(Overweight(w)) if processed == 0 || w.any_gte(max_weight) => { + // Permanently overweight - place into overweight queue. + // TODO: record the weight used. + let index = 0;//Self::insert_overweight(origin, message); + Self::deposit_event(Event::::Overweight { hash, origin, index }); + false + }, + Err(Overweight(_)) => { + // Temporarily overweight - save progress and stop processing this queue. + Pages::::insert(page_index, page); + break true + } + Err(error) => { + // Permanent error - drop + Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); + true + }, + Ok((success, weight_used)) => { + // Success + weight_left.saturating_reduce(weight_used); + let event = Event::::Processed { hash, origin, weight_used, success }; + Self::deposit_event(event); + true + }, + } + }, + Err(_) => { + let hash = T::Hashing::hash_of(message); + Self::deposit_event(Event::::Discarded { hash }); + } + }; + + page = match page.without_first(was_processed) { + Ok(p) => p, + Err(maybe_next_page) => { + Pages::::remove(page_index); + if let Some(next_page) = maybe_next_page { + book_state.head = next_page; + break false + } else { + BookStateOf::::kill(); + break true + } + }, + }; + }; + if bail { + break + } + } + BookStateOf::::set(maybe_book_state); + weight_limit.saturating_sub(weight_left) + } + */ +} + +pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); +impl Get for MaxEncodedLenOf { + fn get() -> u32 { + T::max_encoded_len() as u32 + } +} + +pub struct MaxMessageLen(sp_std::marker::PhantomData<(Origin, HeapSize)>); +impl> Get for MaxMessageLen { + fn get() -> u32 { + HeapSize::get() + .saturating_sub(Origin::max_encoded_len() as u32) + .saturating_sub(ItemHeader::max_encoded_len() as u32) + } +} + +pub type MaxMessageLenOf = MaxMessageLen, HeapSize>; +pub type MaxOriginLenOf = MaxEncodedLenOf>; +pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; + +pub trait EnqueueMessage { + type MaxMessageLen: Get; + + /// Enqueue a single `message` from a specific `origin`. + /// + /// Infallible. + fn enqueue_message(message: BoundedSlice, origin: Origin); + + /// Enqueue multiple `messages` from a specific `origin`. + /// + /// If no `message.len()` is greater than `HEAP_SIZE - Origin::max_encoded_len()`, then this + /// is guaranteed to succeed. In the case of `Err`, no messages are queued. + fn enqueue_messages(messages: &[BoundedSlice], origin: Origin); + + // TODO: consider: `fn enqueue_mqc_page(page: &[u8], origin: Origin);` +} + +impl EnqueueMessage> for Pallet { + type MaxMessageLen = MaxMessageLen<::Origin, HeapSize>; + + fn enqueue_message(message: BoundedSlice, origin: ::Origin) { + // the `truncate_from` is just for safety - it will never fail since the bound is the + // maximum encoded length of the type. + origin.using_encoded(|data| Self::do_enqueue_message(message, BoundedSlice::truncate_from(data))) + } + + fn enqueue_messages(messages: &[BoundedSlice], origin: ::Origin) { + origin.using_encoded(|data| { + // the `truncate_from` is just for safety - it will never fail since the bound is the + // maximum encoded length of the type. + let origin_data = BoundedSlice::truncate_from(data); + for &message in messages.iter() { + Self::do_enqueue_message(message, origin_data); + } + }) + } +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs new file mode 100644 index 0000000000000..2138cc664952d --- /dev/null +++ b/frame/message-queue/src/tests.rs @@ -0,0 +1,115 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +// Tests for Multisig Pallet + +#![cfg(test)] + +use super::*; + +use crate as pallet_message_queue; +use frame_support::{ + assert_noop, assert_ok, parameter_types, + traits::{ConstU16, ConstU32, ConstU64, Contains}, +}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type MessageProcessor = TestMessageProcessor; +} + +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] +pub enum MessageOrigin { + Parent, + Peer(u8), +} + +pub struct TestMessageProcessor; +impl ProcessMessage for TestMessageProcessor { + /// The transport from where a message originates. + type Origin = MessageOrigin; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + fn process_message(message: &[u8], origin: Self::Origin, weight_limit: Weight) -> Result<(bool, Weight), ProcessMessageError> { + Ok((true, Weight::zero())) + } +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +#[test] +fn enqueue_works() { + new_test_ext().execute_with(|| { + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"hello"[..]), MessageOrigin::Parent); + }); +} diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs new file mode 100644 index 0000000000000..7e12563c44b11 --- /dev/null +++ b/frame/message-queue/src/weights.rs @@ -0,0 +1,65 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Autogenerated weights for pallet_assets +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_assets +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template=./.maintain/frame-weight-template.hbs +// --output=./frame/assets/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_assets. +pub trait WeightInfo { + fn dummy() -> Weight; +} + +/// Weights for pallet_assets using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Assets Asset (r:1 w:1) + fn dummy() -> Weight { + Weight::from_ref_time(27_167_000 as u64) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Assets Asset (r:1 w:1) + fn dummy() -> Weight { + Weight::from_ref_time(27_167_000 as u64) + } +} From f0bdf13b0ce131021b298ba6ebb3e0b090271599 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 12 Oct 2022 16:05:55 +0100 Subject: [PATCH 002/110] Make fully generic --- Cargo.lock | 1 + frame/message-queue/Cargo.toml | 15 +- frame/message-queue/src/lib.rs | 267 +++++++++++++++++++------------ frame/message-queue/src/tests.rs | 73 ++++++++- 4 files changed, 238 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3fe395b19da48..c284ce08ad4f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5835,6 +5835,7 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml index 5cbc8ff068e3d..aeb5c02a34328 100644 --- a/frame/message-queue/Cargo.toml +++ b/frame/message-queue/Cargo.toml @@ -10,26 +10,29 @@ scale-info = { version = "2.1.2", default-features = false, features = ["derive" serde = { version = "1.0.137", optional = true, features = ["derive"] } log = { version = "0.4.17", default-features = false } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-core = { version = "6.0.0", default-features = false, path = "../../primitives/core" } sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } [features] default = ["std"] std = [ - "frame-benchmarking?/std", "codec/std", - "frame-support/std", - "frame-system/std", "scale-info/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-arithmetic/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index fd4ff17be1957..9e31a228406bd 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -22,34 +22,27 @@ mod tests; mod weights; -use codec::{Decode, Encode, Codec, MaxEncodedLen}; -use frame_support::{parameter_types, BoundedSlice}; -use scale_info::TypeInfo; -use sp_std::{prelude::*, vec}; -use sp_runtime::Saturating; -use frame_support::pallet_prelude::*; +use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use frame_support::{pallet_prelude::*, parameter_types, BoundedSlice}; use frame_system::pallet_prelude::*; pub use pallet::*; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; +use sp_runtime::{ + traits::{Hash, One, Zero}, + SaturatedConversion, Saturating, +}; +use sp_std::{prelude::*, vec}; pub use weights::WeightInfo; /// Type for identifying an overweight message. type OverweightIndex = u64; /// Type for identifying a page. type PageIndex = u32; -/// Type for representing the size of an offset into a page heap. -type Offset = u16; -/// Type for representing the size of an individual encoded `MessageItem`. -type Size = u16; -/// Maximum size of a page's heap. -const HEAP_SIZE: u32 = 1u32 << 16; - -parameter_types! { - pub const HeapSize: u32 = HEAP_SIZE; -} /// Data encoded and prefixed to the encoded `MessageItem`. #[derive(Encode, Decode, MaxEncodedLen)] -pub struct ItemHeader { +pub struct ItemHeader { /// The length of this item, not including the size of this header. The next item of the page /// follows immediately after the payload of this item. payload_len: Size, @@ -58,43 +51,50 @@ pub struct ItemHeader { } /// A page of messages. Pages always contain at least one item. -#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Default)] -pub struct Page { +#[derive(Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(HeapSize))] +#[codec(mel_bound(Size: MaxEncodedLen))] +pub struct Page, HeapSize: Get> { /// Messages remaining to be processed; this includes overweight messages which have been /// skipped. remaining: Size, /// The heap-offset of the header of the first message item in this page which is ready for /// processing. - first: Offset, + first: Size, /// The heap-offset of the header of the last message item in this page. - last: Offset, + last: Size, /// The heap. If `self.offset == self.heap.len()` then the page is empty and should be deleted. - heap: BoundedVec, + heap: BoundedVec>, } -impl Page { +impl< + Size: BaseArithmetic + Unsigned + Copy + Into + Codec + MaxEncodedLen, + HeapSize: Get, + > Page +{ fn from_message(message: &[u8], origin: &[u8]) -> Self { - let len = ItemHeader::max_encoded_len() + origin.len() + message.len(); + let len = ItemHeader::::max_encoded_len() + origin.len() + message.len(); let mut heap = Vec::with_capacity(len); - let payload_len = (origin.len() + message.len()) as Size;// TODO: bounded inputs for safety + let payload_len: Size = (origin.len() + message.len()).saturated_into(); // TODO: bounded inputs for safety let h = ItemHeader { payload_len, is_processed: false }; h.using_encoded(|d| heap.extend_from_slice(d)); heap.extend_from_slice(origin); heap.extend_from_slice(message); Page { - remaining: 1, - first: 0, - last: 0, + remaining: One::one(), + first: Zero::zero(), + last: Zero::zero(), heap: BoundedVec::truncate_from(heap), } } fn try_append_message(&mut self, message: &[u8], origin: &[u8]) -> Result<(), ()> { let pos = self.heap.len(); - let len = ItemHeader::max_encoded_len() + origin.len() + message.len(); - let payload_len = (origin.len() + message.len()) as Size;// TODO: bounded inputs for safety + let len = (ItemHeader::::max_encoded_len() + origin.len() + message.len()) as u32; + let payload_len: Size = (origin.len() + message.len()).saturated_into(); // TODO: bounded inputs for safety let h = ItemHeader { payload_len, is_processed: false }; - if (HEAP_SIZE as usize).saturating_sub(self.heap.len()) < len { + let heap_size: u32 = HeapSize::get().into(); + if heap_size.saturating_sub(self.heap.len() as u32) < len { // Can't fit. return Err(()) } @@ -103,23 +103,24 @@ impl Page { h.using_encoded(|d| heap.extend_from_slice(d)); heap.extend_from_slice(origin); heap.extend_from_slice(message); - debug_assert!((heap.len() as u32) < HEAP_SIZE, "already checked size; qed"); + debug_assert!(heap.len() as u32 <= HeapSize::get().into(), "already checked size; qed"); self.heap = BoundedVec::truncate_from(heap); - self.last = pos as Offset; + self.last = pos.saturated_into(); self.remaining.saturating_inc(); Ok(()) } - fn peek_first(&self) -> Option> { - if self.first >= self.last { + fn peek_first(&self) -> Option>> { + if self.first > self.last { return None } - let mut item_slice = &self.heap[self.first as usize..]; - if let Ok(h) = ItemHeader::decode(&mut item_slice) { - let payload_len = h.payload_len as usize; - if payload_len <= item_slice.len() { - // impossible to truncate since is sliced up from `self.heap: BoundedVec` - return Some(BoundedSlice::truncate_from(&item_slice[..payload_len])) + let mut item_slice = &self.heap[(self.first.into() as usize)..]; + if let Ok(h) = ItemHeader::::decode(&mut item_slice) { + let payload_len = h.payload_len.into(); + if payload_len <= item_slice.len() as u32 { + // impossible to truncate since is sliced up from `self.heap: BoundedVec` + return Some(BoundedSlice::truncate_from(&item_slice[..(payload_len as usize)])) } } debug_assert!(false, "message-queue: heap corruption"); @@ -128,21 +129,21 @@ impl Page { /// Point `first` at the next message, marking the first as processed if `is_processed` is true. fn skip_first(&mut self, is_processed: bool) { - let f = self.first as usize; + let f = self.first.into() as usize; if let Ok(mut h) = ItemHeader::decode(&mut &self.heap[f..]) { if is_processed && !h.is_processed { h.is_processed = true; - h.using_encoded(|d| - self.heap[f..f + d.len()].copy_from_slice(d) - ); + h.using_encoded(|d| self.heap[f..f + d.len()].copy_from_slice(d)); self.remaining.saturating_dec(); } - self.first.saturating_accrue(ItemHeader::max_encoded_len() as Size + h.payload_len); + self.first + .saturating_accrue(ItemHeader::::max_encoded_len().saturated_into()); + self.first.saturating_accrue(h.payload_len); } } fn is_complete(&self) -> bool { - self.remaining == 0 + self.remaining.is_zero() } } @@ -165,7 +166,11 @@ pub trait ProcessMessage { type Origin: Codec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + sp_std::fmt::Debug; /// Process the given message, using no more than `weight_limit` in weight to do so. - fn process_message(message: &[u8], origin: Self::Origin, weight_limit: Weight) -> Result<(bool, Weight), ProcessMessageError>; + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError>; } #[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Default)] @@ -203,6 +208,21 @@ pub mod pallet { /// Processor for a message. type MessageProcessor: ProcessMessage; + + /// Page/heap size type. + type Size: BaseArithmetic + + Unsigned + + Copy + + Into + + Member + + Encode + + Decode + + MaxEncodedLen + + TypeInfo; + + /// The size of the page; this implies the maximum message size which can be sent. + #[pallet::constant] + type HeapSize: Get; } #[pallet::event] @@ -235,7 +255,8 @@ pub mod pallet { /// The map of page indices to pages. #[pallet::storage] - pub(super) type Pages = StorageMap<_, Blake2_256, PageIndex, Page, OptionQuery>; + pub(super) type Pages = + StorageMap<_, Blake2_256, PageIndex, Page, OptionQuery>; #[pallet::hooks] impl Hooks> for Pallet { @@ -248,9 +269,7 @@ pub mod pallet { #[pallet::call] impl Pallet { #[pallet::weight(0)] - pub fn send( - origin: OriginFor, - ) -> DispatchResult { + pub fn send(origin: OriginFor) -> DispatchResult { let _ = ensure_signed(origin)?; Ok(()) } @@ -258,12 +277,6 @@ pub mod pallet { } impl Pallet { - fn new_page() -> PageIndex { - let page = NextPage::::get(); - NextPage::::put(page + 1); - page - } - fn do_enqueue_message( message: BoundedSlice>, origin: BoundedSlice>, @@ -277,7 +290,7 @@ impl Pallet { None => { debug_assert!(false, "Corruption: referenced page doesn't exist."); return - } + }, }; if let Ok(_) = page.try_append_message(&message[..], &origin[..]) { Pages::::insert(last, &page); @@ -289,7 +302,7 @@ impl Pallet { Pages::::insert(book_state.end - 1, Page::from_message(&message[..], &origin[..])); BookStateOf::::put(book_state); } -/* + /// Service the message queue. /// /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. @@ -299,74 +312,90 @@ impl Pallet { let mut processed = 0; let max_weight = weight_limit.saturating_mul(3).saturating_div(4); let mut weight_left = weight_limit; - let mut maybe_book_state = BookStateOf::::get(); - while let Some(ref mut book_state) = maybe_book_state { - let page_index = book_state.head; + let mut book_state = BookStateOf::::get(); + while book_state.end > book_state.begin { + let page_index = book_state.begin; // TODO: Check `weight_left` and bail before doing this storage read. - let mut page = Pages::::get(page_index); + let mut page = match Pages::::get(page_index) { + Some(p) => p, + None => { + debug_assert!(false, "message-queue: referenced page not found"); + break + }, + }; let bail = loop { - let mut message = page.peek_first(); + let mut message = &match page.peek_first() { + Some(m) => m, + None => break false, + }[..]; - let was_processed = match MessageOriginOf::::decode(&mut message) { + let is_processed = match MessageOriginOf::::decode(&mut message) { Ok(origin) => { - let hash = T::Hashing::hash_of(message); + let hash = T::Hashing::hash(message); use ProcessMessageError::Overweight; - match T::MessageProcessor::process_message(message, origin.clone(), weight_left) { - Err(Overweight(w)) if processed == 0 || w.any_gte(max_weight) => { - // Permanently overweight - place into overweight queue. - // TODO: record the weight used. - let index = 0;//Self::insert_overweight(origin, message); - Self::deposit_event(Event::::Overweight { hash, origin, index }); + match T::MessageProcessor::process_message( + message, + origin.clone(), + weight_left, + ) { + Err(Overweight(w)) if processed == 0 || w.any_gt(max_weight) => { + // Permanently overweight. + Self::deposit_event(Event::::Overweight { + hash, + origin, + index: 0, + }); // TODO page + index false }, Err(Overweight(_)) => { - // Temporarily overweight - save progress and stop processing this queue. - Pages::::insert(page_index, page); + // Temporarily overweight - save progress and stop processing this + // queue. break true - } + }, Err(error) => { // Permanent error - drop - Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); + Self::deposit_event(Event::::ProcessingFailed { + hash, + origin, + error, + }); true }, Ok((success, weight_used)) => { // Success - weight_left.saturating_reduce(weight_used); - let event = Event::::Processed { hash, origin, weight_used, success }; + processed.saturating_inc(); + weight_left = weight_left.saturating_sub(weight_used); + let event = + Event::::Processed { hash, origin, weight_used, success }; Self::deposit_event(event); true }, } }, Err(_) => { - let hash = T::Hashing::hash_of(message); + let hash = T::Hashing::hash(message); Self::deposit_event(Event::::Discarded { hash }); - } - }; - - page = match page.without_first(was_processed) { - Ok(p) => p, - Err(maybe_next_page) => { - Pages::::remove(page_index); - if let Some(next_page) = maybe_next_page { - book_state.head = next_page; - break false - } else { - BookStateOf::::kill(); - break true - } + false }, }; + page.skip_first(is_processed); }; + + if page.is_complete() { + Pages::::remove(page_index); + } else { + Pages::::insert(page_index, page); + } + if bail { break } + book_state.begin.saturating_inc(); } - BookStateOf::::set(maybe_book_state); + BookStateOf::::put(book_state); weight_limit.saturating_sub(weight_left) } - */ } pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); @@ -376,19 +405,38 @@ impl Get for MaxEncodedLenOf { } } -pub struct MaxMessageLen(sp_std::marker::PhantomData<(Origin, HeapSize)>); -impl> Get for MaxMessageLen { +pub struct MaxMessageLen( + sp_std::marker::PhantomData<(Origin, Size, HeapSize)>, +); +impl, HeapSize: Get> Get + for MaxMessageLen +{ fn get() -> u32 { - HeapSize::get() + (HeapSize::get().into()) .saturating_sub(Origin::max_encoded_len() as u32) - .saturating_sub(ItemHeader::max_encoded_len() as u32) + .saturating_sub(ItemHeader::::max_encoded_len() as u32) } } -pub type MaxMessageLenOf = MaxMessageLen, HeapSize>; +pub type MaxMessageLenOf = + MaxMessageLen, ::Size, ::HeapSize>; pub type MaxOriginLenOf = MaxEncodedLenOf>; pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; +pub struct HeapSizeU32Of(sp_std::marker::PhantomData); +impl Get for HeapSizeU32Of { + fn get() -> u32 { + T::HeapSize::get().into() + } +} + +pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); +impl, O: Into> Get for IntoU32 { + fn get() -> u32 { + T::get().into() + } +} + pub trait EnqueueMessage { type MaxMessageLen: Get; @@ -407,15 +455,24 @@ pub trait EnqueueMessage { } impl EnqueueMessage> for Pallet { - type MaxMessageLen = MaxMessageLen<::Origin, HeapSize>; + type MaxMessageLen = + MaxMessageLen<::Origin, T::Size, T::HeapSize>; - fn enqueue_message(message: BoundedSlice, origin: ::Origin) { + fn enqueue_message( + message: BoundedSlice, + origin: ::Origin, + ) { // the `truncate_from` is just for safety - it will never fail since the bound is the // maximum encoded length of the type. - origin.using_encoded(|data| Self::do_enqueue_message(message, BoundedSlice::truncate_from(data))) + origin.using_encoded(|data| { + Self::do_enqueue_message(message, BoundedSlice::truncate_from(data)) + }) } - fn enqueue_messages(messages: &[BoundedSlice], origin: ::Origin) { + fn enqueue_messages( + messages: &[BoundedSlice], + origin: ::Origin, + ) { origin.using_encoded(|data| { // the `truncate_from` is just for safety - it will never fail since the bound is the // maximum encoded length of the type. diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 2138cc664952d..dbeb0e46046ba 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -23,8 +23,8 @@ use super::*; use crate as pallet_message_queue; use frame_support::{ - assert_noop, assert_ok, parameter_types, - traits::{ConstU16, ConstU32, ConstU64, Contains}, + parameter_types, + traits::{ConstU32, ConstU64}, }; use sp_core::H256; use sp_runtime::{ @@ -77,39 +77,98 @@ impl frame_system::Config for Test { type MaxConsumers = ConstU32<16>; } +parameter_types! { + pub const HeapSize: u32 = 131072; +} + impl Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); type MessageProcessor = TestMessageProcessor; + type Size = u32; + type HeapSize = HeapSize; } #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] pub enum MessageOrigin { + Here, Parent, Peer(u8), } +parameter_types! { + pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; +} + pub struct TestMessageProcessor; impl ProcessMessage for TestMessageProcessor { /// The transport from where a message originates. type Origin = MessageOrigin; /// Process the given message, using no more than `weight_limit` in weight to do so. - fn process_message(message: &[u8], origin: Self::Origin, weight_limit: Weight) -> Result<(bool, Weight), ProcessMessageError> { - Ok((true, Weight::zero())) + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + let weight = Weight::from_components(1, 1); + if weight.all_lte(weight_limit) { + let mut m = MessagesProcessed::get(); + m.push((message.to_vec(), origin)); + MessagesProcessed::set(m); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } } } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext } #[test] -fn enqueue_works() { +fn enqueue_within_one_page_works() { new_test_ext().execute_with(|| { - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"hello"[..]), MessageOrigin::Parent); + use MessageOrigin::*; + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"hello"[..]), Parent); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"world"[..]), Peer(0)); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"gav"[..]), Here); + MessageQueue::service_queue(Weight::from_components(2, 2)); + assert_eq!( + MessagesProcessed::get(), + vec![(b"hello".to_vec(), Parent), (b"world".to_vec(), Peer(0)),] + ); + + MessagesProcessed::set(vec![]); + MessageQueue::service_queue(Weight::from_components(2, 2)); + assert_eq!(MessagesProcessed::get(), vec![(b"gav".to_vec(), Here),]); + + MessagesProcessed::set(vec![]); + MessageQueue::service_queue(Weight::from_components(2, 2)); + assert_eq!(MessagesProcessed::get(), vec![]); + + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"boo"[..]), Peer(0)); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"yah"[..]), Peer(1)); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"kah"[..]), Peer(0)); + + MessagesProcessed::set(vec![]); + MessageQueue::service_queue(Weight::from_components(2, 2)); + assert_eq!( + MessagesProcessed::get(), + vec![(b"boo".to_vec(), Peer(0)), (b"yah".to_vec(), Peer(1)),] + ); + + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"sha"[..]), Peer(1)); + + MessagesProcessed::set(vec![]); + MessageQueue::service_queue(Weight::from_components(2, 2)); + assert_eq!( + MessagesProcessed::get(), + vec![(b"kah".to_vec(), Peer(0)), (b"sha".to_vec(), Peer(1)),] + ); }); } From 236804a5e48463654794d9c37ead86e4d8715b43 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 12 Oct 2022 16:07:30 +0100 Subject: [PATCH 003/110] Refactor --- frame/message-queue/src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 9e31a228406bd..155cedfd866d2 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -422,13 +422,7 @@ pub type MaxMessageLenOf = MaxMessageLen, ::Size, ::HeapSize>; pub type MaxOriginLenOf = MaxEncodedLenOf>; pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; - -pub struct HeapSizeU32Of(sp_std::marker::PhantomData); -impl Get for HeapSizeU32Of { - fn get() -> u32 { - T::HeapSize::get().into() - } -} +pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); impl, O: Into> Get for IntoU32 { From 2d8ff13e3f73f4f63206b887ba10f5d71692685e Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 12 Oct 2022 16:16:25 +0100 Subject: [PATCH 004/110] Docs --- frame/message-queue/src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 155cedfd866d2..2216f2f95b737 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -184,9 +184,13 @@ pub struct BookState { count: PageIndex, /// The earliest page still in storage. If this is `>= end`, then there are /// no pages in storage. Pages up to `head` are reapable if they have a `remaining` - /// field of zero or if `head - page_number` is sufficiently large compared to + /// field of zero or if `begin - page_number` is sufficiently large compared to /// `count - (end - begin)`. - historical_head: PageIndex, + /// NOTE: This currently doesn't really work and will become "holey" when pages are removed + /// out of order. This can be maintained "automatically" with a doubly-linked-list or + /// "manually" by having a transaction to bump it. However, it probably doesn't actually matter + /// anyway since reaping can happen perfectly well without it. + earliest: PageIndex, } #[frame_support::pallet] @@ -383,7 +387,11 @@ impl Pallet { }; if page.is_complete() { + debug_assert!(!bail, "we never bail if a page became complete"); Pages::::remove(page_index); + if book_state.earliest == page_index { + book_state.earliest.saturating_inc(); + } } else { Pages::::insert(page_index, page); } From afcf58774de7bd2275e6957c97d1a3965dff5f0c Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 12 Oct 2022 16:18:53 +0100 Subject: [PATCH 005/110] Refactor --- frame/message-queue/src/lib.rs | 74 ++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 34 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 2216f2f95b737..4cca91eed5994 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -23,7 +23,7 @@ mod tests; mod weights; use codec::{Codec, Decode, Encode, MaxEncodedLen}; -use frame_support::{pallet_prelude::*, parameter_types, BoundedSlice}; +use frame_support::{pallet_prelude::*, BoundedSlice}; use frame_system::pallet_prelude::*; pub use pallet::*; use scale_info::TypeInfo; @@ -306,12 +306,51 @@ impl Pallet { Pages::::insert(book_state.end - 1, Page::from_message(&message[..], &origin[..])); BookStateOf::::put(book_state); } +} +pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); +impl Get for MaxEncodedLenOf { + fn get() -> u32 { + T::max_encoded_len() as u32 + } +} + +pub struct MaxMessageLen( + sp_std::marker::PhantomData<(Origin, Size, HeapSize)>, +); +impl, HeapSize: Get> Get + for MaxMessageLen +{ + fn get() -> u32 { + (HeapSize::get().into()) + .saturating_sub(Origin::max_encoded_len() as u32) + .saturating_sub(ItemHeader::::max_encoded_len() as u32) + } +} + +pub type MaxMessageLenOf = + MaxMessageLen, ::Size, ::HeapSize>; +pub type MaxOriginLenOf = MaxEncodedLenOf>; +pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; +pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; + +pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); +impl, O: Into> Get for IntoU32 { + fn get() -> u32 { + T::get().into() + } +} + +pub trait ServiceQueue { /// Service the message queue. /// /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. /// /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. + fn service_queue(weight_limit: Weight) -> Weight; +} + +impl ServiceQueue for Pallet { fn service_queue(weight_limit: Weight) -> Weight { let mut processed = 0; let max_weight = weight_limit.saturating_mul(3).saturating_div(4); @@ -406,39 +445,6 @@ impl Pallet { } } -pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); -impl Get for MaxEncodedLenOf { - fn get() -> u32 { - T::max_encoded_len() as u32 - } -} - -pub struct MaxMessageLen( - sp_std::marker::PhantomData<(Origin, Size, HeapSize)>, -); -impl, HeapSize: Get> Get - for MaxMessageLen -{ - fn get() -> u32 { - (HeapSize::get().into()) - .saturating_sub(Origin::max_encoded_len() as u32) - .saturating_sub(ItemHeader::::max_encoded_len() as u32) - } -} - -pub type MaxMessageLenOf = - MaxMessageLen, ::Size, ::HeapSize>; -pub type MaxOriginLenOf = MaxEncodedLenOf>; -pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; -pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; - -pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); -impl, O: Into> Get for IntoU32 { - fn get() -> u32 { - T::get().into() - } -} - pub trait EnqueueMessage { type MaxMessageLen: Get; From c8c82e00d2444e99c260b882dc98c8529979fd98 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 12 Oct 2022 16:23:58 +0100 Subject: [PATCH 006/110] Use iter not slice --- frame/message-queue/src/lib.rs | 11 +++++++---- frame/message-queue/src/tests.rs | 14 ++++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 4cca91eed5994..299a2d5d644c9 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -457,7 +457,10 @@ pub trait EnqueueMessage { /// /// If no `message.len()` is greater than `HEAP_SIZE - Origin::max_encoded_len()`, then this /// is guaranteed to succeed. In the case of `Err`, no messages are queued. - fn enqueue_messages(messages: &[BoundedSlice], origin: Origin); + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: Origin, + ); // TODO: consider: `fn enqueue_mqc_page(page: &[u8], origin: Origin);` } @@ -477,15 +480,15 @@ impl EnqueueMessage> for Pallet { }) } - fn enqueue_messages( - messages: &[BoundedSlice], + fn enqueue_messages<'a>( + messages: impl Iterator>, origin: ::Origin, ) { origin.using_encoded(|data| { // the `truncate_from` is just for safety - it will never fail since the bound is the // maximum encoded length of the type. let origin_data = BoundedSlice::truncate_from(data); - for &message in messages.iter() { + for message in messages { Self::do_enqueue_message(message, origin_data); } }) diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index dbeb0e46046ba..c5f5542ca1a27 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -151,15 +151,21 @@ fn enqueue_within_one_page_works() { MessageQueue::service_queue(Weight::from_components(2, 2)); assert_eq!(MessagesProcessed::get(), vec![]); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"boo"[..]), Peer(0)); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"yah"[..]), Peer(1)); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"kah"[..]), Peer(0)); + MessageQueue::enqueue_messages( + [ + BoundedSlice::truncate_from(&b"boo"[..]), + BoundedSlice::truncate_from(&b"yah"[..]), + BoundedSlice::truncate_from(&b"kah"[..]), + ] + .into_iter(), + Peer(0), + ); MessagesProcessed::set(vec![]); MessageQueue::service_queue(Weight::from_components(2, 2)); assert_eq!( MessagesProcessed::get(), - vec![(b"boo".to_vec(), Peer(0)), (b"yah".to_vec(), Peer(1)),] + vec![(b"boo".to_vec(), Peer(0)), (b"yah".to_vec(), Peer(0)),] ); MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"sha"[..]), Peer(1)); From 5e48ae36a0814f16f3a183a73d60198a5b278a81 Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 12 Oct 2022 17:26:11 +0100 Subject: [PATCH 007/110] Per-origin queues --- frame/message-queue/src/lib.rs | 65 ++++++++++++++++++-------------- frame/message-queue/src/tests.rs | 38 +++++++++++++------ 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 299a2d5d644c9..594dacb174940 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -22,7 +22,7 @@ mod tests; mod weights; -use codec::{Codec, Decode, Encode, MaxEncodedLen}; +use codec::{Codec, Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{pallet_prelude::*, BoundedSlice}; use frame_system::pallet_prelude::*; pub use pallet::*; @@ -163,7 +163,7 @@ pub enum ProcessMessageError { pub trait ProcessMessage { /// The transport from where a message originates. - type Origin: Codec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + sp_std::fmt::Debug; + type Origin: FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + sp_std::fmt::Debug; /// Process the given message, using no more than `weight_limit` in weight to do so. fn process_message( @@ -251,16 +251,20 @@ pub mod pallet { /// The index of the first and last (non-empty) pages. #[pallet::storage] - pub(super) type BookStateOf = StorageValue<_, BookState, ValueQuery>; - - /// The lowest known unused page index. - #[pallet::storage] - pub(super) type NextPage = StorageValue<_, PageIndex, ValueQuery>; + pub(super) type BookStateOf = + StorageMap<_, Twox64Concat, MessageOriginOf, BookState, ValueQuery>; /// The map of page indices to pages. #[pallet::storage] - pub(super) type Pages = - StorageMap<_, Blake2_256, PageIndex, Page, OptionQuery>; + pub(super) type Pages = StorageDoubleMap< + _, + Twox64Concat, + MessageOriginOf, + Twox64Concat, + PageIndex, + Page, + OptionQuery, + >; #[pallet::hooks] impl Hooks> for Pallet { @@ -282,29 +286,34 @@ pub mod pallet { impl Pallet { fn do_enqueue_message( + origin: &MessageOriginOf, message: BoundedSlice>, - origin: BoundedSlice>, + origin_data: BoundedSlice>, ) { - let mut book_state = BookStateOf::::get(); + let mut book_state = BookStateOf::::get(origin); if book_state.end > book_state.begin { // Already have a page in progress - attempt to append. let last = book_state.end - 1; - let mut page = match Pages::::get(last) { + let mut page = match Pages::::get(origin, last) { Some(p) => p, None => { debug_assert!(false, "Corruption: referenced page doesn't exist."); return }, }; - if let Ok(_) = page.try_append_message(&message[..], &origin[..]) { - Pages::::insert(last, &page); + if let Ok(_) = page.try_append_message(&message[..], &origin_data[..]) { + Pages::::insert(origin, last, &page); return } } // No room on the page or no page - link in a new page. book_state.end.saturating_inc(); - Pages::::insert(book_state.end - 1, Page::from_message(&message[..], &origin[..])); - BookStateOf::::put(book_state); + Pages::::insert( + origin, + book_state.end - 1, + Page::from_message(&message[..], &origin_data[..]), + ); + BookStateOf::::insert(origin, book_state); } } @@ -341,25 +350,25 @@ impl, O: Into> Get for IntoU32 { } } -pub trait ServiceQueue { - /// Service the message queue. +pub trait ServiceQueue { + /// Service the given message queue. /// /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. /// /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. - fn service_queue(weight_limit: Weight) -> Weight; + fn service_queue(origin: Origin, weight_limit: Weight) -> Weight; } -impl ServiceQueue for Pallet { - fn service_queue(weight_limit: Weight) -> Weight { +impl ServiceQueue> for Pallet { + fn service_queue(origin: MessageOriginOf, weight_limit: Weight) -> Weight { let mut processed = 0; let max_weight = weight_limit.saturating_mul(3).saturating_div(4); let mut weight_left = weight_limit; - let mut book_state = BookStateOf::::get(); + let mut book_state = BookStateOf::::get(&origin); while book_state.end > book_state.begin { let page_index = book_state.begin; // TODO: Check `weight_left` and bail before doing this storage read. - let mut page = match Pages::::get(page_index) { + let mut page = match Pages::::get(&origin, page_index) { Some(p) => p, None => { debug_assert!(false, "message-queue: referenced page not found"); @@ -427,12 +436,12 @@ impl ServiceQueue for Pallet { if page.is_complete() { debug_assert!(!bail, "we never bail if a page became complete"); - Pages::::remove(page_index); + Pages::::remove(&origin, page_index); if book_state.earliest == page_index { book_state.earliest.saturating_inc(); } } else { - Pages::::insert(page_index, page); + Pages::::insert(&origin, page_index, page); } if bail { @@ -440,7 +449,7 @@ impl ServiceQueue for Pallet { } book_state.begin.saturating_inc(); } - BookStateOf::::put(book_state); + BookStateOf::::insert(&origin, book_state); weight_limit.saturating_sub(weight_left) } } @@ -476,7 +485,7 @@ impl EnqueueMessage> for Pallet { // the `truncate_from` is just for safety - it will never fail since the bound is the // maximum encoded length of the type. origin.using_encoded(|data| { - Self::do_enqueue_message(message, BoundedSlice::truncate_from(data)) + Self::do_enqueue_message(&origin, message, BoundedSlice::truncate_from(data)) }) } @@ -489,7 +498,7 @@ impl EnqueueMessage> for Pallet { // maximum encoded length of the type. let origin_data = BoundedSlice::truncate_from(data); for message in messages { - Self::do_enqueue_message(message, origin_data); + Self::do_enqueue_message(&origin, message, origin_data); } }) } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index c5f5542ca1a27..17f8232e63834 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -130,25 +130,37 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } +pub trait IntoWeight { + fn into_weight(self) -> Weight; +} +impl IntoWeight for u64 { + fn into_weight(self) -> Weight { + Weight::from_components(self, self) + } +} + #[test] fn enqueue_within_one_page_works() { new_test_ext().execute_with(|| { use MessageOrigin::*; - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"hello"[..]), Parent); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"world"[..]), Peer(0)); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"hello"[..]), Here); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"world"[..]), Here); MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"gav"[..]), Here); - MessageQueue::service_queue(Weight::from_components(2, 2)); + assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 2.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![(b"hello".to_vec(), Parent), (b"world".to_vec(), Peer(0)),] + vec![(b"hello".to_vec(), Here), (b"world".to_vec(), Here)] ); MessagesProcessed::set(vec![]); - MessageQueue::service_queue(Weight::from_components(2, 2)); - assert_eq!(MessagesProcessed::get(), vec![(b"gav".to_vec(), Here),]); + assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::get(), vec![(b"gav".to_vec(), Here)]); MessagesProcessed::set(vec![]); - MessageQueue::service_queue(Weight::from_components(2, 2)); + assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 0.into_weight()); + assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 0.into_weight()); + assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 0.into_weight()); + assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 0.into_weight()); assert_eq!(MessagesProcessed::get(), vec![]); MessageQueue::enqueue_messages( @@ -158,23 +170,25 @@ fn enqueue_within_one_page_works() { BoundedSlice::truncate_from(&b"kah"[..]), ] .into_iter(), - Peer(0), + Parent, ); MessagesProcessed::set(vec![]); - MessageQueue::service_queue(Weight::from_components(2, 2)); + assert_eq!(MessageQueue::service_queue(Peer(1), 2.into_weight()), 0.into_weight()); + assert_eq!(MessageQueue::service_queue(Parent, 2.into_weight()), 2.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![(b"boo".to_vec(), Peer(0)), (b"yah".to_vec(), Peer(0)),] + vec![(b"boo".to_vec(), Parent), (b"yah".to_vec(), Parent),] ); MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"sha"[..]), Peer(1)); MessagesProcessed::set(vec![]); - MessageQueue::service_queue(Weight::from_components(2, 2)); + assert_eq!(MessageQueue::service_queue(Peer(1), 2.into_weight()), 1.into_weight()); + assert_eq!(MessageQueue::service_queue(Parent, 2.into_weight()), 1.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![(b"kah".to_vec(), Peer(0)), (b"sha".to_vec(), Peer(1)),] + vec![(b"sha".to_vec(), Peer(1)), (b"kah".to_vec(), Parent),] ); }); } From 2e5bff83a68859be43122fb6961c506519cd02aa Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 12 Oct 2022 18:38:25 +0100 Subject: [PATCH 008/110] Multi-queue processing --- frame/message-queue/src/lib.rs | 109 ++++++++++++++++++++++++++++++- frame/message-queue/src/tests.rs | 24 +++---- 2 files changed, 119 insertions(+), 14 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 594dacb174940..65f1bd1b1f283 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -163,7 +163,14 @@ pub enum ProcessMessageError { pub trait ProcessMessage { /// The transport from where a message originates. - type Origin: FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + sp_std::fmt::Debug; + type Origin: FullCodec + + MaxEncodedLen + + Clone + + Eq + + PartialEq + + TypeInfo + + sp_std::fmt::Debug + + Arity; /// Process the given message, using no more than `weight_limit` in weight to do so. fn process_message( @@ -193,6 +200,54 @@ pub struct BookState { earliest: PageIndex, } +/// Type which has a finite and small total possible number of values. +pub trait Arity { + /// Get the total number of distinct values `Self` can take. + fn arity() -> usize; +} +impl Arity for sp_std::convert::Infallible { + fn arity() -> usize { + 0 + } +} +impl Arity for () { + fn arity() -> usize { + 1 + } +} +impl Arity for bool { + fn arity() -> usize { + 2 + } +} +impl Arity for u8 { + fn arity() -> usize { + 256 + } +} +impl Arity for u16 { + fn arity() -> usize { + 65536 + } +} +impl Arity for i8 { + fn arity() -> usize { + 256 + } +} +impl Arity for i16 { + fn arity() -> usize { + 65536 + } +} + +pub struct GetArity(sp_std::marker::PhantomData); +impl Get for GetArity { + fn get() -> u32 { + T::arity() as u32 + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -254,6 +309,10 @@ pub mod pallet { pub(super) type BookStateOf = StorageMap<_, Twox64Concat, MessageOriginOf, BookState, ValueQuery>; + #[pallet::storage] + pub(super) type ReadyBooks = + StorageValue<_, BoundedVec, GetArity>>, ValueQuery>; + /// The map of page indices to pages. #[pallet::storage] pub(super) type Pages = StorageDoubleMap< @@ -291,7 +350,7 @@ impl Pallet { origin_data: BoundedSlice>, ) { let mut book_state = BookStateOf::::get(origin); - if book_state.end > book_state.begin { + let was_empty = if book_state.end > book_state.begin { // Already have a page in progress - attempt to append. let last = book_state.end - 1; let mut page = match Pages::::get(origin, last) { @@ -305,7 +364,10 @@ impl Pallet { Pages::::insert(origin, last, &page); return } - } + false + } else { + true + }; // No room on the page or no page - link in a new page. book_state.end.saturating_inc(); Pages::::insert( @@ -314,6 +376,15 @@ impl Pallet { Page::from_message(&message[..], &origin_data[..]), ); BookStateOf::::insert(origin, book_state); + if was_empty { + ReadyBooks::::mutate(|b| { + let is_ok = b.try_push(origin.clone()).is_ok(); + debug_assert!( + is_ok, + "ready books can never overflow because duplicates cannot be pushed" + ); + }); + } } } @@ -359,6 +430,15 @@ pub trait ServiceQueue { fn service_queue(origin: Origin, weight_limit: Weight) -> Weight; } +pub trait ServiceQueues { + /// Service all message queues in some fair manner. + /// + /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. + /// + /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. + fn service_queues(weight_limit: Weight) -> Weight; +} + impl ServiceQueue> for Pallet { fn service_queue(origin: MessageOriginOf, weight_limit: Weight) -> Weight { let mut processed = 0; @@ -449,11 +529,34 @@ impl ServiceQueue> for Pallet { } book_state.begin.saturating_inc(); } + if book_state.begin >= book_state.end && processed > 0 { + // empty now having processed at least one. + ReadyBooks::::mutate(|b| { + b.retain(|b| b != &origin); + }); + } BookStateOf::::insert(&origin, book_state); weight_limit.saturating_sub(weight_left) } } +impl ServiceQueues for Pallet { + fn service_queues(weight_limit: Weight) -> Weight { + let queues = ReadyBooks::::get(); + // TODO: take a random number and shuffle the queues rather than the next stuff. + let mut weight_used = Weight::zero(); + for origin in queues.into_iter() { + let left = weight_limit - weight_used; + if left.any_lte(Weight::zero()) { + break + } + let used = Self::service_queue(origin, left); + weight_used.saturating_accrue(used); + } + weight_used + } +} + pub trait EnqueueMessage { type MaxMessageLen: Get; diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 17f8232e63834..7d12531940300 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -95,6 +95,12 @@ pub enum MessageOrigin { Parent, Peer(u8), } +// NOTE: Would be nice to have a derive for this. +impl Arity for MessageOrigin { + fn arity() -> usize { + u8::arity() + 2 + } +} parameter_types! { pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; @@ -146,21 +152,18 @@ fn enqueue_within_one_page_works() { MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"hello"[..]), Here); MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"world"[..]), Here); MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"gav"[..]), Here); - assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 2.into_weight()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!( MessagesProcessed::get(), vec![(b"hello".to_vec(), Here), (b"world".to_vec(), Here)] ); MessagesProcessed::set(vec![]); - assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 1.into_weight()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 1.into_weight()); assert_eq!(MessagesProcessed::get(), vec![(b"gav".to_vec(), Here)]); MessagesProcessed::set(vec![]); - assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 0.into_weight()); - assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 0.into_weight()); - assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 0.into_weight()); - assert_eq!(MessageQueue::service_queue(Here, 2.into_weight()), 0.into_weight()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); assert_eq!(MessagesProcessed::get(), vec![]); MessageQueue::enqueue_messages( @@ -174,8 +177,7 @@ fn enqueue_within_one_page_works() { ); MessagesProcessed::set(vec![]); - assert_eq!(MessageQueue::service_queue(Peer(1), 2.into_weight()), 0.into_weight()); - assert_eq!(MessageQueue::service_queue(Parent, 2.into_weight()), 2.into_weight()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!( MessagesProcessed::get(), vec![(b"boo".to_vec(), Parent), (b"yah".to_vec(), Parent),] @@ -184,11 +186,11 @@ fn enqueue_within_one_page_works() { MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"sha"[..]), Peer(1)); MessagesProcessed::set(vec![]); - assert_eq!(MessageQueue::service_queue(Peer(1), 2.into_weight()), 1.into_weight()); - assert_eq!(MessageQueue::service_queue(Parent, 2.into_weight()), 1.into_weight()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![(b"sha".to_vec(), Peer(1)), (b"kah".to_vec(), Parent),] + vec![(b"kah".to_vec(), Parent), (b"sha".to_vec(), Peer(1)),] ); }); } From 38390459dd3cfe46d2500cecb7b221b2d72e05b5 Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 25 Oct 2022 19:07:36 +0200 Subject: [PATCH 009/110] Introduce MaxReady --- frame/message-queue/src/lib.rs | 127 ++++++++++++++----------------- frame/message-queue/src/tests.rs | 33 +++++--- 2 files changed, 77 insertions(+), 83 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 65f1bd1b1f283..a347fd970e5f7 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -163,14 +163,7 @@ pub enum ProcessMessageError { pub trait ProcessMessage { /// The transport from where a message originates. - type Origin: FullCodec - + MaxEncodedLen - + Clone - + Eq - + PartialEq - + TypeInfo - + sp_std::fmt::Debug - + Arity; + type Origin: FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + sp_std::fmt::Debug; /// Process the given message, using no more than `weight_limit` in weight to do so. fn process_message( @@ -200,54 +193,6 @@ pub struct BookState { earliest: PageIndex, } -/// Type which has a finite and small total possible number of values. -pub trait Arity { - /// Get the total number of distinct values `Self` can take. - fn arity() -> usize; -} -impl Arity for sp_std::convert::Infallible { - fn arity() -> usize { - 0 - } -} -impl Arity for () { - fn arity() -> usize { - 1 - } -} -impl Arity for bool { - fn arity() -> usize { - 2 - } -} -impl Arity for u8 { - fn arity() -> usize { - 256 - } -} -impl Arity for u16 { - fn arity() -> usize { - 65536 - } -} -impl Arity for i8 { - fn arity() -> usize { - 256 - } -} -impl Arity for i16 { - fn arity() -> usize { - 65536 - } -} - -pub struct GetArity(sp_std::marker::PhantomData); -impl Get for GetArity { - fn get() -> u32 { - T::arity() as u32 - } -} - #[frame_support::pallet] pub mod pallet { use super::*; @@ -282,6 +227,11 @@ pub mod pallet { /// The size of the page; this implies the maximum message size which can be sent. #[pallet::constant] type HeapSize: Get; + + /// The maximum number of queues whose ready status we track. If this is too low then some + /// message origins may need to be serviced manually. + #[pallet::constant] + type MaxReady: Get; } #[pallet::event] @@ -296,6 +246,10 @@ pub mod pallet { Processed { hash: T::Hash, origin: MessageOriginOf, weight_used: Weight, success: bool }, /// Message placed in overweight queue. Overweight { hash: T::Hash, origin: MessageOriginOf, index: OverweightIndex }, + /// A queue has become non-empty. + Readied { origin: MessageOriginOf, will_service: bool }, + /// Queue is processed. + QueueProcessed { origin: MessageOriginOf, ready_slot_free: bool }, } #[pallet::error] @@ -311,7 +265,7 @@ pub mod pallet { #[pallet::storage] pub(super) type ReadyBooks = - StorageValue<_, BoundedVec, GetArity>>, ValueQuery>; + StorageValue<_, BoundedVec, T::MaxReady>, ValueQuery>; /// The map of page indices to pages. #[pallet::storage] @@ -340,6 +294,29 @@ pub mod pallet { let _ = ensure_signed(origin)?; Ok(()) } + + /// Execute an overweight message. + /// + /// - `origin`: Must be `Signed`. + /// - `message_origin`: The origin from which the message to be executed arrived. + /// - `page`: The page in the queue in which the message to be executed is sitting. + /// - `index`: The index into the queue of the message to be executed. + /// - `weight_limit`: The maximum amount of weight allowed to be consumed in the execution + /// of the message. + /// + /// Benchmark complexity considerations: O(index + weight_limit). + #[pallet::weight(0)] + pub fn execute_overweight( + origin: OriginFor, + message_origin: MessageOriginOf, + page: PageIndex, + index: T::Size, + weight_limit: Weight, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + + Ok(()) + } } } @@ -350,7 +327,7 @@ impl Pallet { origin_data: BoundedSlice>, ) { let mut book_state = BookStateOf::::get(origin); - let was_empty = if book_state.end > book_state.begin { + if book_state.end > book_state.begin { // Already have a page in progress - attempt to append. let last = book_state.end - 1; let mut page = match Pages::::get(origin, last) { @@ -364,10 +341,7 @@ impl Pallet { Pages::::insert(origin, last, &page); return } - false - } else { - true - }; + } // No room on the page or no page - link in a new page. book_state.end.saturating_inc(); Pages::::insert( @@ -375,14 +349,16 @@ impl Pallet { book_state.end - 1, Page::from_message(&message[..], &origin_data[..]), ); - BookStateOf::::insert(origin, book_state); - if was_empty { - ReadyBooks::::mutate(|b| { - let is_ok = b.try_push(origin.clone()).is_ok(); - debug_assert!( - is_ok, - "ready books can never overflow because duplicates cannot be pushed" - ); + BookStateOf::::insert(&origin, book_state); + let mut ready_books = ReadyBooks::::get(); + if !ready_books.iter().any(|x| x == origin) { + let is_ok = ready_books.try_push(origin.clone()).is_ok(); + if is_ok { + ReadyBooks::::put(ready_books); + } + Self::deposit_event(Event::::Readied { + origin: origin.clone(), + will_service: is_ok, }); } } @@ -531,8 +507,17 @@ impl ServiceQueue> for Pallet { } if book_state.begin >= book_state.end && processed > 0 { // empty now having processed at least one. + let mut removed = false; ReadyBooks::::mutate(|b| { - b.retain(|b| b != &origin); + b.retain(|b| { + let keep = b != &origin; + removed = removed || !keep; + keep + }); + }); + Self::deposit_event(Event::::QueueProcessed { + origin: origin.clone(), + ready_slot_free: removed, }); } BookStateOf::::insert(&origin, book_state); diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 7d12531940300..e4656549802ee 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -79,6 +79,7 @@ impl frame_system::Config for Test { parameter_types! { pub const HeapSize: u32 = 131072; + pub const MaxReady: u32 = 3; } impl Config for Test { @@ -87,19 +88,14 @@ impl Config for Test { type MessageProcessor = TestMessageProcessor; type Size = u32; type HeapSize = HeapSize; + type MaxReady = MaxReady; } #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] pub enum MessageOrigin { Here, - Parent, - Peer(u8), -} -// NOTE: Would be nice to have a derive for this. -impl Arity for MessageOrigin { - fn arity() -> usize { - u8::arity() + 2 - } + There, + Everywhere(u8), } parameter_types! { @@ -173,24 +169,37 @@ fn enqueue_within_one_page_works() { BoundedSlice::truncate_from(&b"kah"[..]), ] .into_iter(), - Parent, + There, ); MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![(b"boo".to_vec(), Parent), (b"yah".to_vec(), Parent),] + vec![(b"boo".to_vec(), There), (b"yah".to_vec(), There),] ); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"sha"[..]), Peer(1)); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"sha"[..]), Everywhere(1)); MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![(b"kah".to_vec(), Parent), (b"sha".to_vec(), Peer(1)),] + vec![(b"kah".to_vec(), There), (b"sha".to_vec(), Everywhere(1)),] ); }); } + +#[test] +fn overflowing_ready_origins_works() { + new_test_ext().execute_with(|| { + use MessageOrigin::*; + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"hello"[..]), Here); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"world"[..]), There); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"foo"[..]), Everywhere(1)); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"bar"[..]), Everywhere(2)); + println!("{:?}", frame_system::Pallet::::events()); + assert!(false); + }); +} From 2bfe22923ce79463c518f7dbd1b59c0fc64cdb69 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 27 Oct 2022 08:59:50 +0200 Subject: [PATCH 010/110] Remove MaxReady in favour of ready ring --- frame/message-queue/src/lib.rs | 180 ++++++++++++++++++++++--------- frame/message-queue/src/tests.rs | 1 - 2 files changed, 128 insertions(+), 53 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index a347fd970e5f7..a8791a88b5817 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -173,8 +173,14 @@ pub trait ProcessMessage { ) -> Result<(bool, Weight), ProcessMessageError>; } -#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, Default)] -pub struct BookState { +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct Neighbours { + prev: MessageOrigin, + next: MessageOrigin, +} + +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] +pub struct BookState { /// The first page with some items to be processed in it. If this is `>= end`, then there are /// no pages with items to be processing in them. begin: PageIndex, @@ -191,6 +197,15 @@ pub struct BookState { /// "manually" by having a transaction to bump it. However, it probably doesn't actually matter /// anyway since reaping can happen perfectly well without it. earliest: PageIndex, + /// If this book has any ready pages, then this will be `Some` with the previous and next + /// neighbours. This wraps around. + ready_neighbours: Option>, +} + +impl Default for BookState { + fn default() -> Self { + Self { begin: 0, end: 0, count: 0, earliest: 0, ready_neighbours: None } + } } #[frame_support::pallet] @@ -246,10 +261,6 @@ pub mod pallet { Processed { hash: T::Hash, origin: MessageOriginOf, weight_used: Weight, success: bool }, /// Message placed in overweight queue. Overweight { hash: T::Hash, origin: MessageOriginOf, index: OverweightIndex }, - /// A queue has become non-empty. - Readied { origin: MessageOriginOf, will_service: bool }, - /// Queue is processed. - QueueProcessed { origin: MessageOriginOf, ready_slot_free: bool }, } #[pallet::error] @@ -261,11 +272,11 @@ pub mod pallet { /// The index of the first and last (non-empty) pages. #[pallet::storage] pub(super) type BookStateOf = - StorageMap<_, Twox64Concat, MessageOriginOf, BookState, ValueQuery>; + StorageMap<_, Twox64Concat, MessageOriginOf, BookState>, ValueQuery>; + /// The origin at which we should begin servicing. #[pallet::storage] - pub(super) type ReadyBooks = - StorageValue<_, BoundedVec, T::MaxReady>, ValueQuery>; + pub(super) type ServiceHead = StorageValue<_, MessageOriginOf, OptionQuery>; /// The map of page indices to pages. #[pallet::storage] @@ -321,13 +332,80 @@ pub mod pallet { } impl Pallet { + /// Knit `origin` into the ready ring right at the end. + /// + /// Return the two ready ring neighbours of `origin`. + fn ready_ring_knit(origin: &MessageOriginOf) -> Result>, ()> { + if let Some(head) = ServiceHead::::get() { + let mut head_book_state = BookStateOf::::get(&head); + let mut head_neighbours = head_book_state.ready_neighbours.take().ok_or(())?; + let tail = head_neighbours.prev; + let mut tail_book_state = BookStateOf::::get(&tail); + let mut tail_neighbours = tail_book_state.ready_neighbours.take().ok_or(())?; + + head_neighbours.prev = origin.clone(); + head_book_state.ready_neighbours = Some(head_neighbours); + + tail_neighbours.next = origin.clone(); + tail_book_state.ready_neighbours = Some(tail_neighbours); + + BookStateOf::::insert(&head, head_book_state); + BookStateOf::::insert(&tail, tail_book_state); + + Ok(Neighbours { next: head, prev: tail }) + } else { + ServiceHead::::put(origin); + Ok(Neighbours { next: origin.clone(), prev: origin.clone() }) + } + } + + fn ready_ring_unknit(origin: &MessageOriginOf, neighbours: Neighbours>) { + if neighbours.next == neighbours.prev { + // Service queue empty. + ServiceHead::::kill(); + } else { + BookStateOf::::mutate(&neighbours.next, |book_state| { + if let Some(ref mut n) = book_state.ready_neighbours { + n.prev = neighbours.prev.clone() + } + }); + BookStateOf::::mutate(&neighbours.prev, |book_state| { + if let Some(ref mut n) = book_state.ready_neighbours { + n.next = neighbours.next.clone() + } + }); + if let Some(mut head) = ServiceHead::::get() { + if &head == origin { + ServiceHead::::put(neighbours.next); + } + } else { + debug_assert!(false, "`ServiceHead` must be some if there was a ready queue"); + } + } + } + + fn bump_service_head() -> Option> { + if let Some(mut head) = ServiceHead::::get() { + let mut head_book_state = BookStateOf::::get(&head); + if let Some(head_neighbours) = head_book_state.ready_neighbours.take() { + ServiceHead::::put(&head_neighbours.next); + Some(head) + } else { + None + } + } else { + None + } + } + fn do_enqueue_message( origin: &MessageOriginOf, message: BoundedSlice>, origin_data: BoundedSlice>, ) { let mut book_state = BookStateOf::::get(origin); - if book_state.end > book_state.begin { + let was_ready = if book_state.end > book_state.begin { + debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); // Already have a page in progress - attempt to append. let last = book_state.end - 1; let mut page = match Pages::::get(origin, last) { @@ -341,7 +419,19 @@ impl Pallet { Pages::::insert(origin, last, &page); return } - } + true + } else { + debug_assert!( + book_state.ready_neighbours.is_none(), + "Must not be in ready ring if not ready" + ); + // insert into ready queue. + match Self::ready_ring_knit(origin) { + Ok(neighbours) => book_state.ready_neighbours = Some(neighbours), + Err(()) => debug_assert!(false, "Ring state invalid when knitting"), + } + false + }; // No room on the page or no page - link in a new page. book_state.end.saturating_inc(); Pages::::insert( @@ -350,17 +440,6 @@ impl Pallet { Page::from_message(&message[..], &origin_data[..]), ); BookStateOf::::insert(&origin, book_state); - let mut ready_books = ReadyBooks::::get(); - if !ready_books.iter().any(|x| x == origin) { - let is_ok = ready_books.try_push(origin.clone()).is_ok(); - if is_ok { - ReadyBooks::::put(ready_books); - } - Self::deposit_event(Event::::Readied { - origin: origin.clone(), - will_service: is_ok, - }); - } } } @@ -397,15 +476,6 @@ impl, O: Into> Get for IntoU32 { } } -pub trait ServiceQueue { - /// Service the given message queue. - /// - /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. - /// - /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. - fn service_queue(origin: Origin, weight_limit: Weight) -> Weight; -} - pub trait ServiceQueues { /// Service all message queues in some fair manner. /// @@ -415,8 +485,11 @@ pub trait ServiceQueues { fn service_queues(weight_limit: Weight) -> Weight; } -impl ServiceQueue> for Pallet { - fn service_queue(origin: MessageOriginOf, weight_limit: Weight) -> Weight { +impl Pallet { + fn service_queue( + origin: MessageOriginOf, + weight_limit: Weight, + ) -> (Weight, Option>) { let mut processed = 0; let max_weight = weight_limit.saturating_mul(3).saturating_div(4); let mut weight_left = weight_limit; @@ -505,38 +578,41 @@ impl ServiceQueue> for Pallet { } book_state.begin.saturating_inc(); } + let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone()); if book_state.begin >= book_state.end && processed > 0 { - // empty now having processed at least one. - let mut removed = false; - ReadyBooks::::mutate(|b| { - b.retain(|b| { - let keep = b != &origin; - removed = removed || !keep; - keep - }); - }); - Self::deposit_event(Event::::QueueProcessed { - origin: origin.clone(), - ready_slot_free: removed, - }); + // No longer ready - unknit. + if let Some(neighbours) = book_state.ready_neighbours.take() { + Self::ready_ring_unknit(&origin, neighbours); + } else { + debug_assert!(false, "Freshly processed queue must have been ready"); + } } - BookStateOf::::insert(&origin, book_state); - weight_limit.saturating_sub(weight_left) + BookStateOf::::insert(&origin, &book_state); + (weight_limit.saturating_sub(weight_left), next_ready) } } impl ServiceQueues for Pallet { fn service_queues(weight_limit: Weight) -> Weight { - let queues = ReadyBooks::::get(); - // TODO: take a random number and shuffle the queues rather than the next stuff. let mut weight_used = Weight::zero(); - for origin in queues.into_iter() { + // TODO: impl bump_service_head + let mut next = match Self::bump_service_head() { + Some(h) => h, + None => return weight_used, + }; + + loop { let left = weight_limit - weight_used; if left.any_lte(Weight::zero()) { break } - let used = Self::service_queue(origin, left); + // Before servicing, bump `ServiceHead` onto the next item. + let (used, n) = Self::service_queue(next, left); weight_used.saturating_accrue(used); + next = match n { + Some(n) => n, + None => break, + } } weight_used } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index e4656549802ee..4cd8c97d19d27 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -200,6 +200,5 @@ fn overflowing_ready_origins_works() { MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"foo"[..]), Everywhere(1)); MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"bar"[..]), Everywhere(2)); println!("{:?}", frame_system::Pallet::::events()); - assert!(false); }); } From 283366b7fe060e3a250880b1af0e7a5cede557f9 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 27 Oct 2022 09:01:21 +0200 Subject: [PATCH 011/110] Cleanups --- frame/message-queue/src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index a8791a88b5817..1ed7ede15d144 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -374,7 +374,7 @@ impl Pallet { n.next = neighbours.next.clone() } }); - if let Some(mut head) = ServiceHead::::get() { + if let Some(head) = ServiceHead::::get() { if &head == origin { ServiceHead::::put(neighbours.next); } @@ -385,7 +385,7 @@ impl Pallet { } fn bump_service_head() -> Option> { - if let Some(mut head) = ServiceHead::::get() { + if let Some(head) = ServiceHead::::get() { let mut head_book_state = BookStateOf::::get(&head); if let Some(head_neighbours) = head_book_state.ready_neighbours.take() { ServiceHead::::put(&head_neighbours.next); @@ -404,7 +404,7 @@ impl Pallet { origin_data: BoundedSlice>, ) { let mut book_state = BookStateOf::::get(origin); - let was_ready = if book_state.end > book_state.begin { + if book_state.end > book_state.begin { debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); // Already have a page in progress - attempt to append. let last = book_state.end - 1; @@ -419,7 +419,6 @@ impl Pallet { Pages::::insert(origin, last, &page); return } - true } else { debug_assert!( book_state.ready_neighbours.is_none(), @@ -430,8 +429,7 @@ impl Pallet { Ok(neighbours) => book_state.ready_neighbours = Some(neighbours), Err(()) => debug_assert!(false, "Ring state invalid when knitting"), } - false - }; + } // No room on the page or no page - link in a new page. book_state.end.saturating_inc(); Pages::::insert( From 76a5c81ecb2cb4a925f3728d04fa557fcc204b76 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 27 Oct 2022 12:30:00 +0200 Subject: [PATCH 012/110] ReadyRing and tests --- frame/message-queue/src/lib.rs | 48 ++++++++++++++---- frame/message-queue/src/tests.rs | 84 +++++++++++++++++++++++++++++--- 2 files changed, 115 insertions(+), 17 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 1ed7ede15d144..742d7021ad085 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -173,13 +173,13 @@ pub trait ProcessMessage { ) -> Result<(bool, Weight), ProcessMessageError>; } -#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] pub struct Neighbours { prev: MessageOrigin, next: MessageOrigin, } -#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] pub struct BookState { /// The first page with some items to be processed in it. If this is `>= end`, then there are /// no pages with items to be processing in them. @@ -331,6 +331,31 @@ pub mod pallet { } } +pub struct ReadyRing { + first: Option>, + next: Option>, +} +impl ReadyRing { + fn new() -> Self { + Self { first: ServiceHead::::get(), next: ServiceHead::::get() } + } +} +impl Iterator for ReadyRing { + type Item = MessageOriginOf; + fn next(&mut self) -> Option { + match self.next.take() { + None => None, + Some(last) => { + self.next = BookStateOf::::get(&last) + .ready_neighbours + .map(|n| n.next) + .filter(|n| Some(n) != self.first.as_ref()); + Some(last) + }, + } + } +} + impl Pallet { /// Knit `origin` into the ready ring right at the end. /// @@ -340,16 +365,14 @@ impl Pallet { let mut head_book_state = BookStateOf::::get(&head); let mut head_neighbours = head_book_state.ready_neighbours.take().ok_or(())?; let tail = head_neighbours.prev; - let mut tail_book_state = BookStateOf::::get(&tail); - let mut tail_neighbours = tail_book_state.ready_neighbours.take().ok_or(())?; - head_neighbours.prev = origin.clone(); head_book_state.ready_neighbours = Some(head_neighbours); + BookStateOf::::insert(&head, head_book_state); + let mut tail_book_state = BookStateOf::::get(&tail); + let mut tail_neighbours = tail_book_state.ready_neighbours.take().ok_or(())?; tail_neighbours.next = origin.clone(); tail_book_state.ready_neighbours = Some(tail_neighbours); - - BookStateOf::::insert(&head, head_book_state); BookStateOf::::insert(&tail, tail_book_state); Ok(Neighbours { next: head, prev: tail }) @@ -360,7 +383,11 @@ impl Pallet { } fn ready_ring_unknit(origin: &MessageOriginOf, neighbours: Neighbours>) { - if neighbours.next == neighbours.prev { + if origin == &neighbours.next { + debug_assert!( + origin == &neighbours.prev, + "unknitting from single item ring; outgoing must be only item" + ); // Service queue empty. ServiceHead::::kill(); } else { @@ -487,9 +514,9 @@ impl Pallet { fn service_queue( origin: MessageOriginOf, weight_limit: Weight, + max_weight: Weight, ) -> (Weight, Option>) { let mut processed = 0; - let max_weight = weight_limit.saturating_mul(3).saturating_div(4); let mut weight_left = weight_limit; let mut book_state = BookStateOf::::get(&origin); while book_state.end > book_state.begin { @@ -592,6 +619,7 @@ impl Pallet { impl ServiceQueues for Pallet { fn service_queues(weight_limit: Weight) -> Weight { + let max_weight = weight_limit.saturating_mul(3).saturating_div(4); let mut weight_used = Weight::zero(); // TODO: impl bump_service_head let mut next = match Self::bump_service_head() { @@ -605,7 +633,7 @@ impl ServiceQueues for Pallet { break } // Before servicing, bump `ServiceHead` onto the next item. - let (used, n) = Self::service_queue(next, left); + let (used, n) = Self::service_queue(next, left, max_weight); weight_used.saturating_accrue(used); next = match n { Some(n) => n, diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 4cd8c97d19d27..6985249bca951 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -186,19 +186,89 @@ fn enqueue_within_one_page_works() { assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![(b"kah".to_vec(), There), (b"sha".to_vec(), Everywhere(1)),] + vec![(b"kah".to_vec(), There), (b"sha".to_vec(), Everywhere(1))] ); }); } +fn msg>(x: &'static str) -> BoundedSlice { + BoundedSlice::truncate_from(x.as_bytes()) +} + +fn vmsg(x: &'static str) -> Vec { + x.as_bytes().to_vec() +} + #[test] -fn overflowing_ready_origins_works() { +fn queue_priority_retains() { new_test_ext().execute_with(|| { use MessageOrigin::*; - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"hello"[..]), Here); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"world"[..]), There); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"foo"[..]), Everywhere(1)); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"bar"[..]), Everywhere(2)); - println!("{:?}", frame_system::Pallet::::events()); + assert_eq!(ReadyRing::::new().collect::>(), vec![]); + MessageQueue::enqueue_message(msg(&"a"), Everywhere(1)); + assert_eq!(ReadyRing::::new().collect::>(), vec![Everywhere(1)]); + MessageQueue::enqueue_message(msg(&"b"), Everywhere(2)); + assert_eq!( + ReadyRing::::new().collect::>(), + vec![Everywhere(1), Everywhere(2)] + ); + MessageQueue::enqueue_message(msg(&"c"), Everywhere(3)); + assert_eq!( + ReadyRing::::new().collect::>(), + vec![Everywhere(1), Everywhere(2), Everywhere(3)] + ); + MessageQueue::enqueue_message(msg(&"d"), Everywhere(2)); + assert_eq!( + ReadyRing::::new().collect::>(), + vec![Everywhere(1), Everywhere(2), Everywhere(3)] + ); + // service head is 1, it will process a, leaving service head at 2. it also processes b but + // doees not empty queue 2, so service head will end at 2. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::get(), + vec![(vmsg(&"a"), Everywhere(1)), (vmsg(&"b"), Everywhere(2)),] + ); + assert_eq!( + ReadyRing::::new().collect::>(), + vec![Everywhere(2), Everywhere(3)] + ); + // service head is 2, so will process d first, then c. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!( + MessagesProcessed::get(), + vec![ + (vmsg(&"a"), Everywhere(1)), + (vmsg(&"b"), Everywhere(2)), + (vmsg(&"d"), Everywhere(2)), + (vmsg(&"c"), Everywhere(3)), + ] + ); + assert_eq!(ReadyRing::::new().collect::>(), vec![]); + }); +} + +#[test] +fn queue_priority_reset_once_serviced() { + new_test_ext().execute_with(|| { + use MessageOrigin::*; + MessageQueue::enqueue_message(msg(&"a"), Everywhere(1)); + MessageQueue::enqueue_message(msg(&"b"), Everywhere(2)); + MessageQueue::enqueue_message(msg(&"c"), Everywhere(3)); + // service head is 1, it will process a, leaving service head at 2. it also processes b and + // empties queue 2, so service head will end at 3. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + MessageQueue::enqueue_message(msg(&"d"), Everywhere(2)); + // service head is 3, so will process c first, then d. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + + assert_eq!( + MessagesProcessed::get(), + vec![ + (vmsg(&"a"), Everywhere(1)), + (vmsg(&"b"), Everywhere(2)), + (vmsg(&"c"), Everywhere(3)), + (vmsg(&"d"), Everywhere(2)), + ] + ); }); } From ef3faafea64033b8f3b0151498772d5c2652da82 Mon Sep 17 00:00:00 2001 From: Gav Date: Sat, 29 Oct 2022 17:04:16 +0200 Subject: [PATCH 013/110] Stale page reaping --- frame/message-queue/src/lib.rs | 165 +++++++++++++++++++------------ frame/message-queue/src/tests.rs | 109 +++++++++++++++----- 2 files changed, 187 insertions(+), 87 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 742d7021ad085..4bb7c4fd6220b 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -32,7 +32,7 @@ use sp_runtime::{ traits::{Hash, One, Zero}, SaturatedConversion, Saturating, }; -use sp_std::{prelude::*, vec}; +use sp_std::{fmt::Debug, prelude::*, vec}; pub use weights::WeightInfo; /// Type for identifying an overweight message. @@ -51,10 +51,10 @@ pub struct ItemHeader { } /// A page of messages. Pages always contain at least one item. -#[derive(Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo, MaxEncodedLen)] +#[derive(Clone, Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(HeapSize))] #[codec(mel_bound(Size: MaxEncodedLen))] -pub struct Page, HeapSize: Get> { +pub struct Page + Debug, HeapSize: Get> { /// Messages remaining to be processed; this includes overweight messages which have been /// skipped. remaining: Size, @@ -68,7 +68,7 @@ pub struct Page, HeapSize: Get> { } impl< - Size: BaseArithmetic + Unsigned + Copy + Into + Codec + MaxEncodedLen, + Size: BaseArithmetic + Unsigned + Copy + Into + Codec + MaxEncodedLen + Debug, HeapSize: Get, > Page { @@ -188,15 +188,6 @@ pub struct BookState { end: PageIndex, /// The number of pages stored at present. count: PageIndex, - /// The earliest page still in storage. If this is `>= end`, then there are - /// no pages in storage. Pages up to `head` are reapable if they have a `remaining` - /// field of zero or if `begin - page_number` is sufficiently large compared to - /// `count - (end - begin)`. - /// NOTE: This currently doesn't really work and will become "holey" when pages are removed - /// out of order. This can be maintained "automatically" with a doubly-linked-list or - /// "manually" by having a transaction to bump it. However, it probably doesn't actually matter - /// anyway since reaping can happen perfectly well without it. - earliest: PageIndex, /// If this book has any ready pages, then this will be `Some` with the previous and next /// neighbours. This wraps around. ready_neighbours: Option>, @@ -204,7 +195,7 @@ pub struct BookState { impl Default for BookState { fn default() -> Self { - Self { begin: 0, end: 0, count: 0, earliest: 0, ready_neighbours: None } + Self { begin: 0, end: 0, count: 0, ready_neighbours: None } } } @@ -243,10 +234,11 @@ pub mod pallet { #[pallet::constant] type HeapSize: Get; - /// The maximum number of queues whose ready status we track. If this is too low then some - /// message origins may need to be serviced manually. + /// The maximum number of stale pages (i.e. of overweight messages) allowed before culling + /// can happen. Once there are more stale pages than this, then historical pages may be + /// dropped, even if they contain unprocessed overweight messages. #[pallet::constant] - type MaxReady: Get; + type MaxStale: Get; } #[pallet::event] @@ -265,8 +257,11 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// Dummy. - Dummy, + /// Page is not reapable because it has items remaining to be processed and is not old + /// enough. + NotReapable, + /// Page to be reaped does not exist. + NoPage, } /// The index of the first and last (non-empty) pages. @@ -459,6 +454,7 @@ impl Pallet { } // No room on the page or no page - link in a new page. book_state.end.saturating_inc(); + book_state.count.saturating_inc(); Pages::::insert( origin, book_state.end - 1, @@ -466,55 +462,50 @@ impl Pallet { ); BookStateOf::::insert(&origin, book_state); } -} -pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); -impl Get for MaxEncodedLenOf { - fn get() -> u32 { - T::max_encoded_len() as u32 - } -} + /// Remove a page which has no more messages remaining to be processed. + fn do_reap_page(origin: &MessageOriginOf, page_index: PageIndex) -> DispatchResult { + let mut book_state = BookStateOf::::get(origin); + // definitely not reapable if the page's index is no less than the `begin`ning of ready + // pages. + ensure!(page_index < book_state.begin, Error::::NotReapable); + + let page = Pages::::get(origin, page_index).ok_or(Error::::NoPage)?; + + // definitely reapable if the page has no messages in it. + let reapable = page.remaining.is_zero(); + + // also reapable if the page index has dropped below our watermark. + let cullable = || { + let total_pages = book_state.count; + let ready_pages = book_state.end.saturating_sub(book_state.begin).min(total_pages); + let stale_pages = total_pages - ready_pages; + let max_stale = T::MaxStale::get(); + let overflow = match stale_pages.checked_sub(max_stale + 1) { + Some(x) => x + 1, + None => return false, + }; + let backlog = (max_stale * max_stale / overflow).max(max_stale); + let watermark = book_state.begin.saturating_sub(backlog); + page_index < watermark + }; + ensure!(reapable || cullable(), Error::::NotReapable); -pub struct MaxMessageLen( - sp_std::marker::PhantomData<(Origin, Size, HeapSize)>, -); -impl, HeapSize: Get> Get - for MaxMessageLen -{ - fn get() -> u32 { - (HeapSize::get().into()) - .saturating_sub(Origin::max_encoded_len() as u32) - .saturating_sub(ItemHeader::::max_encoded_len() as u32) - } -} + Pages::::remove(origin, page_index); + debug_assert!(book_state.count > 0, "reaping a page implies there are pages"); + book_state.count.saturating_dec(); + BookStateOf::::insert(origin, book_state); -pub type MaxMessageLenOf = - MaxMessageLen, ::Size, ::HeapSize>; -pub type MaxOriginLenOf = MaxEncodedLenOf>; -pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; -pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; - -pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); -impl, O: Into> Get for IntoU32 { - fn get() -> u32 { - T::get().into() + Ok(()) } -} -pub trait ServiceQueues { - /// Service all message queues in some fair manner. - /// - /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. - /// - /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. - fn service_queues(weight_limit: Weight) -> Weight; -} - -impl Pallet { + /// Execute any messages remaining to be processed in the queue of `origin`, using up to + /// `weight_limit` to do so. Any messages which would take more than `overweight_limit` to + /// execute are deemed overweight and ignored. fn service_queue( origin: MessageOriginOf, weight_limit: Weight, - max_weight: Weight, + overweight_limit: Weight, ) -> (Weight, Option>) { let mut processed = 0; let mut weight_left = weight_limit; @@ -536,6 +527,10 @@ impl Pallet { None => break false, }[..]; + if weight_left.any_lte(Weight::zero()) { + break true + } + let is_processed = match MessageOriginOf::::decode(&mut message) { Ok(origin) => { let hash = T::Hashing::hash(message); @@ -545,7 +540,7 @@ impl Pallet { origin.clone(), weight_left, ) { - Err(Overweight(w)) if processed == 0 || w.any_gt(max_weight) => { + Err(Overweight(w)) if processed == 0 || w.any_gt(overweight_limit) => { // Permanently overweight. Self::deposit_event(Event::::Overweight { hash, @@ -591,9 +586,8 @@ impl Pallet { if page.is_complete() { debug_assert!(!bail, "we never bail if a page became complete"); Pages::::remove(&origin, page_index); - if book_state.earliest == page_index { - book_state.earliest.saturating_inc(); - } + debug_assert!(book_state.count > 0, "completing a page implies there are pages"); + book_state.count.saturating_dec(); } else { Pages::::insert(&origin, page_index, page); } @@ -617,11 +611,52 @@ impl Pallet { } } +pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); +impl Get for MaxEncodedLenOf { + fn get() -> u32 { + T::max_encoded_len() as u32 + } +} + +pub struct MaxMessageLen( + sp_std::marker::PhantomData<(Origin, Size, HeapSize)>, +); +impl, HeapSize: Get> Get + for MaxMessageLen +{ + fn get() -> u32 { + (HeapSize::get().into()) + .saturating_sub(Origin::max_encoded_len() as u32) + .saturating_sub(ItemHeader::::max_encoded_len() as u32) + } +} + +pub type MaxMessageLenOf = + MaxMessageLen, ::Size, ::HeapSize>; +pub type MaxOriginLenOf = MaxEncodedLenOf>; +pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; +pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; + +pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); +impl, O: Into> Get for IntoU32 { + fn get() -> u32 { + T::get().into() + } +} + +pub trait ServiceQueues { + /// Service all message queues in some fair manner. + /// + /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. + /// + /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. + fn service_queues(weight_limit: Weight) -> Weight; +} + impl ServiceQueues for Pallet { fn service_queues(weight_limit: Weight) -> Weight { let max_weight = weight_limit.saturating_mul(3).saturating_div(4); let mut weight_used = Weight::zero(); - // TODO: impl bump_service_head let mut next = match Self::bump_service_head() { Some(h) => h, None => return weight_used, diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 6985249bca951..78d61b93aa185 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -23,7 +23,7 @@ use super::*; use crate as pallet_message_queue; use frame_support::{ - parameter_types, + assert_noop, assert_ok, parameter_types, traits::{ConstU32, ConstU64}, }; use sp_core::H256; @@ -78,8 +78,8 @@ impl frame_system::Config for Test { } parameter_types! { - pub const HeapSize: u32 = 131072; - pub const MaxReady: u32 = 3; + pub const HeapSize: u32 = 24; + pub const MaxStale: u32 = 2; } impl Config for Test { @@ -88,7 +88,7 @@ impl Config for Test { type MessageProcessor = TestMessageProcessor; type Size = u32; type HeapSize = HeapSize; - type MaxReady = MaxReady; + type MaxStale = MaxStale; } #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] @@ -113,7 +113,20 @@ impl ProcessMessage for TestMessageProcessor { origin: Self::Origin, weight_limit: Weight, ) -> Result<(bool, Weight), ProcessMessageError> { - let weight = Weight::from_components(1, 1); + let weight = if message.starts_with(&b"weight="[..]) { + let mut w: u64 = 0; + for &c in &message[7..] { + if c >= b'0' && c <= b'9' { + w = w * 10 + (c - b'0') as u64; + } else { + break + } + } + w + } else { + 1 + }; + let weight = Weight::from_components(weight, weight); if weight.all_lte(weight_limit) { let mut m = MessagesProcessed::get(); m.push((message.to_vec(), origin)); @@ -145,18 +158,15 @@ impl IntoWeight for u64 { fn enqueue_within_one_page_works() { new_test_ext().execute_with(|| { use MessageOrigin::*; - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"hello"[..]), Here); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"world"[..]), Here); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"gav"[..]), Here); + MessageQueue::enqueue_message(msg(&"a"), Here); + MessageQueue::enqueue_message(msg(&"b"), Here); + MessageQueue::enqueue_message(msg(&"c"), Here); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); - assert_eq!( - MessagesProcessed::get(), - vec![(b"hello".to_vec(), Here), (b"world".to_vec(), Here)] - ); + assert_eq!(MessagesProcessed::get(), vec![(b"a".to_vec(), Here), (b"b".to_vec(), Here)]); MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 1.into_weight()); - assert_eq!(MessagesProcessed::get(), vec![(b"gav".to_vec(), Here)]); + assert_eq!(MessagesProcessed::get(), vec![(b"c".to_vec(), Here)]); MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); @@ -164,9 +174,9 @@ fn enqueue_within_one_page_works() { MessageQueue::enqueue_messages( [ - BoundedSlice::truncate_from(&b"boo"[..]), - BoundedSlice::truncate_from(&b"yah"[..]), - BoundedSlice::truncate_from(&b"kah"[..]), + BoundedSlice::truncate_from(&b"a"[..]), + BoundedSlice::truncate_from(&b"b"[..]), + BoundedSlice::truncate_from(&b"c"[..]), ] .into_iter(), There, @@ -174,19 +184,16 @@ fn enqueue_within_one_page_works() { MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); - assert_eq!( - MessagesProcessed::get(), - vec![(b"boo".to_vec(), There), (b"yah".to_vec(), There),] - ); + assert_eq!(MessagesProcessed::get(), vec![(b"a".to_vec(), There), (b"b".to_vec(), There),]); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"sha"[..]), Everywhere(1)); + MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"d"[..]), Everywhere(1)); MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![(b"kah".to_vec(), There), (b"sha".to_vec(), Everywhere(1))] + vec![(b"c".to_vec(), There), (b"d".to_vec(), Everywhere(1))] ); }); } @@ -272,3 +279,61 @@ fn queue_priority_reset_once_serviced() { ); }); } + +#[test] +fn reaping_overweight_fails_properly() { + new_test_ext().execute_with(|| { + use MessageOrigin::*; + // page 0 + MessageQueue::enqueue_message(msg(&"weight=4"), Here); + MessageQueue::enqueue_message(msg(&"a"), Here); + // page 1 + MessageQueue::enqueue_message(msg(&"weight=4"), Here); + MessageQueue::enqueue_message(msg(&"b"), Here); + // page 2 + MessageQueue::enqueue_message(msg(&"weight=4"), Here); + MessageQueue::enqueue_message(msg(&"c"), Here); + // page 3 + MessageQueue::enqueue_message(msg(&"bigbig 1"), Here); + // page 4 + MessageQueue::enqueue_message(msg(&"bigbig 2"), Here); + // page 5 + MessageQueue::enqueue_message(msg(&"bigbig 3"), Here); + + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"a"), Here), (vmsg(&"b"), Here)]); + // 2 stale now. + + // Not reapable yet, because we haven't hit the stale limit. + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NotReapable); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"c"), Here)]); + // 3 stale now: can take something 4 pages in history. + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"bigbig 1"), Here)]); + + // Not reapable yet, because we haven't hit the stale limit. + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NotReapable); + assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NotReapable); + assert_noop!(MessageQueue::do_reap_page(&Here, 2), Error::::NotReapable); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"bigbig 2"), Here)]); + + // First is now reapable as it is too far behind the first ready page (5). + assert_ok!(MessageQueue::do_reap_page(&Here, 0)); + // Others not reapable yet, because we haven't hit the stale limit. + assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NotReapable); + assert_noop!(MessageQueue::do_reap_page(&Here, 2), Error::::NotReapable); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"bigbig 3"), Here)]); + + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); + // Still not reapable, since the number of stale pages is only 2. + assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NotReapable); + assert_noop!(MessageQueue::do_reap_page(&Here, 2), Error::::NotReapable); + }); +} From 975c903958ba9ec74f879808429b99f80b0d9f2e Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 29 Oct 2022 17:39:42 +0200 Subject: [PATCH 014/110] from_components -> from_parts Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 78d61b93aa185..9e902939f8730 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -126,7 +126,7 @@ impl ProcessMessage for TestMessageProcessor { } else { 1 }; - let weight = Weight::from_components(weight, weight); + let weight = Weight::from_parts(weight, weight); if weight.all_lte(weight_limit) { let mut m = MessagesProcessed::get(); m.push((message.to_vec(), origin)); @@ -150,7 +150,7 @@ pub trait IntoWeight { } impl IntoWeight for u64 { fn into_weight(self) -> Weight { - Weight::from_components(self, self) + Weight::from_parts(self, self) } } From fd19a635629d628bfeb73a17b68c90be58350cad Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 29 Oct 2022 22:27:37 +0200 Subject: [PATCH 015/110] Move WeightCounter to sp_weights Signed-off-by: Oliver Tale-Yazdi --- frame/scheduler/src/lib.rs | 20 +--------------- primitives/weights/src/lib.rs | 43 +++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 19 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index b5ea0deeba9a3..3a60204b37567 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -81,6 +81,7 @@ use sp_runtime::{ BoundedVec, RuntimeDebug, }; use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*}; +use sp_weights::WeightCounter; pub use weights::WeightInfo; /// Just a simple index for naming period tasks. @@ -143,25 +144,6 @@ pub type ScheduledOf = Scheduled< ::AccountId, >; -struct WeightCounter { - used: Weight, - limit: Weight, -} -impl WeightCounter { - fn check_accrue(&mut self, w: Weight) -> bool { - let test = self.used.saturating_add(w); - if test.any_gt(self.limit) { - false - } else { - self.used = test; - true - } - } - fn can_accrue(&mut self, w: Weight) -> bool { - self.used.saturating_add(w).all_lte(self.limit) - } -} - pub(crate) trait MarginalWeightInfo: WeightInfo { fn service_task(maybe_lookup_len: Option, named: bool, periodic: bool) -> Weight { let base = Self::service_task_base(); diff --git a/primitives/weights/src/lib.rs b/primitives/weights/src/lib.rs index e1ac7fcd4e892..6ac3576a15497 100644 --- a/primitives/weights/src/lib.rs +++ b/primitives/weights/src/lib.rs @@ -220,6 +220,49 @@ where } } +/// Tracks the already consumed weight and a hard limit for the maximal consumable weight. +#[derive(Debug, Clone)] +pub struct WeightCounter { + /// The already consumed weight. + pub consumed: Weight, + /// The maximal consumable weight. + pub limit: Weight, +} + +impl WeightCounter { + /// Creates [`Self`] with a maximum limit for the consumable weight. + pub fn from_limit(limit: Weight) -> Self { + Self { consumed: Weight::zero(), limit } + } + + /// The remaining weight that can be consumed. + pub fn remaining(&self) -> Weight { + self.limit.saturating_sub(self.consumed) + } + + /// Consume some weight and defensively fail if it is over the limit. Saturate in any case. + pub fn defensive_saturating_accrue(&mut self, w: Weight) { + self.consumed.saturating_accrue(w); + debug_assert!(self.consumed.all_lte(self.limit), "Weight counter overflow"); + } + + /// Check if the given weight can be consumed. Do nothing if not. + pub fn check_accrue(&mut self, w: Weight) -> bool { + let test = self.consumed.saturating_add(w); + if test.any_gt(self.limit) { + false + } else { + self.consumed = test; + true + } + } + + /// Check if the given weight can be consumed. + pub fn can_accrue(&self, w: Weight) -> bool { + self.consumed.saturating_add(w).all_lte(self.limit) + } +} + #[cfg(test)] #[allow(dead_code)] mod tests { From 58c6c02c3956cf2b6f654c932adf6472f05aeaac Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 29 Oct 2022 22:28:25 +0200 Subject: [PATCH 016/110] Add MockedWeightInfo Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/tests.rs | 73 +++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 9e902939f8730..16b3164040452 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -31,6 +31,7 @@ use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; +use std::collections::BTreeMap; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -84,13 +85,59 @@ parameter_types! { impl Config for Test { type RuntimeEvent = RuntimeEvent; - type WeightInfo = (); + type WeightInfo = MockedWeightInfo; type MessageProcessor = TestMessageProcessor; type Size = u32; type HeapSize = HeapSize; type MaxStale = MaxStale; } +/// Mocked `WeightInfo` impl with allows to set the weight per call. +pub struct MockedWeightInfo; + +parameter_types! { + /// Storage for `MockedWeightInfo`, do not use directly. + static WeightForCall: BTreeMap = Default::default(); +} + +/// Set the return value for a function from the `WeightInfo` trait. +impl MockedWeightInfo { + fn set_weight(call_name: &str, weight: Weight) { + assert!( + super::weights::WeightMetaInfo::<::WeightInfo>::visit_weight_functions( + |f, _| f == call_name + ) + .into_iter() + .any(|i| i), + "Weigh function name invalid: {call_name}" + ); + let mut calls = WeightForCall::get(); + calls.insert(call_name.into(), weight); + WeightForCall::set(calls); + } +} + +impl crate::weights::WeightInfo for MockedWeightInfo { + fn service_page_base() -> Weight { + WeightForCall::get().get("service_page_base").copied().unwrap_or_default() + } + fn service_queue_base() -> Weight { + WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() + } + fn service_page_process_message() -> Weight { + WeightForCall::get() + .get("service_page_process_message") + .copied() + .unwrap_or_default() + } + fn bump_service_head() -> Weight { + WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() + } + fn service_page_next_msg() -> Weight { + WeightForCall::get().get("service_page_next_msg").copied().unwrap_or_default() + } +} + #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] pub enum MessageOrigin { Here, @@ -139,6 +186,7 @@ impl ProcessMessage for TestMessageProcessor { } pub fn new_test_ext() -> sp_io::TestExternalities { + WeightForCall::set(Default::default()); let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); @@ -154,6 +202,29 @@ impl IntoWeight for u64 { } } +#[test] +fn mocked_weight_works() { + new_test_ext().execute_with(|| { + assert!(::WeightInfo::service_page_base().is_zero()); + }); + new_test_ext().execute_with(|| { + MockedWeightInfo::set_weight("service_page_base", Weight::MAX); + assert_eq!(::WeightInfo::service_page_base(), Weight::MAX); + }); + // The externalities reset it. + new_test_ext().execute_with(|| { + assert!(::WeightInfo::service_page_base().is_zero()); + }); +} + +#[test] +#[should_panic] +fn mocked_weight_panics_on_invalid_name() { + new_test_ext().execute_with(|| { + MockedWeightInfo::set_weight("invalid_name", Weight::MAX); + }); +} + #[test] fn enqueue_within_one_page_works() { new_test_ext().execute_with(|| { From 560f994c5f698a0b43eef6ab44c68c5de14e13d6 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 29 Oct 2022 22:30:20 +0200 Subject: [PATCH 017/110] Deploy to kitchensink Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 3 + bin/node/runtime/Cargo.toml | 4 + bin/node/runtime/src/lib.rs | 31 +++++++ frame/message-queue/src/benchmarking.rs | 42 +++++++++ frame/message-queue/src/weights.rs | 115 ++++++++++++++++-------- 5 files changed, 157 insertions(+), 38 deletions(-) create mode 100644 frame/message-queue/src/benchmarking.rs diff --git a/Cargo.lock b/Cargo.lock index 918c247e77789..e3e5798940c9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3345,6 +3345,7 @@ dependencies = [ "pallet-indices", "pallet-lottery", "pallet-membership", + "pallet-message-queue", "pallet-mmr", "pallet-multisig", "pallet-nomination-pools", @@ -5662,6 +5663,8 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-tracing", + "sp-weights", ] [[package]] diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 39364961d57e2..44ff1725db010 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -75,6 +75,7 @@ pallet-indices = { version = "4.0.0-dev", default-features = false, path = "../. pallet-identity = { version = "4.0.0-dev", default-features = false, path = "../../../frame/identity" } pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/lottery" } pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } +pallet-message-queue = { version = "0.9.29", default-features = false, path = "../../../frame/message-queue" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} @@ -150,6 +151,7 @@ std = [ "sp-inherents/std", "pallet-lottery/std", "pallet-membership/std", + "pallet-message-queue/std", "pallet-mmr/std", "pallet-multisig/std", "pallet-nomination-pools/std", @@ -228,6 +230,7 @@ runtime-benchmarks = [ "pallet-indices/runtime-benchmarks", "pallet-lottery/runtime-benchmarks", "pallet-membership/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools-benchmarking/runtime-benchmarks", @@ -281,6 +284,7 @@ try-runtime = [ "pallet-identity/try-runtime", "pallet-lottery/try-runtime", "pallet-membership/try-runtime", + "pallet-message-queue/try-runtime", "pallet-mmr/try-runtime", "pallet-multisig/try-runtime", "pallet-nomination-pools/try-runtime", diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index d49312bd6fe3f..26d707c7c24db 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1120,6 +1120,35 @@ impl pallet_bounties::Config for Runtime { type ChildBountyManager = ChildBounties; } +parameter_types! { + pub const HeapSize: u32 = 24; + pub const MaxStale: u32 = 2; +} + +/// Processes any message while consuming no weight. +pub struct NoOpMessageProcessor; + +impl pallet_message_queue::ProcessMessage for NoOpMessageProcessor { + type Origin = (); + + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), pallet_message_queue::ProcessMessageError> { + Ok((true, Weight::zero())) + } +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type MessageProcessor = NoOpMessageProcessor; + type Size = u32; + type HeapSize = HeapSize; + type MaxReady = MaxReady; +} + parameter_types! { pub const ChildBountyValueMinimum: Balance = 1 * DOLLARS; } @@ -1670,6 +1699,7 @@ construct_runtime!( RankedPolls: pallet_referenda::, RankedCollective: pallet_ranked_collective, FastUnstake: pallet_fast_unstake, + MessageQueue: pallet_message_queue, } ); @@ -1764,6 +1794,7 @@ mod benches { [pallet_indices, Indices] [pallet_lottery, Lottery] [pallet_membership, TechnicalMembership] + [pallet_message_queue, MessageQueue] [pallet_mmr, Mmr] [pallet_multisig, Multisig] [pallet_nomination_pools, NominationPoolsBench::] diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs new file mode 100644 index 0000000000000..0d34b02fdb55f --- /dev/null +++ b/frame/message-queue/src/benchmarking.rs @@ -0,0 +1,42 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Benchmarking for the message queue pallet. + +#![cfg(feature = "runtime-benchmarks")] + +use super::{Pallet as MessageQueue, *}; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::traits::Get; +use frame_system::RawOrigin; +use sp_std::prelude::*; + +benchmarks! { + // Just calling `service_page` without any iterations happening. + service_page_base { }: { } + + // Just calling `service_queue` without any iterations happening. + service_queue_base { }: { } + + // Contains the effort for fetching and (storing or removing) a page. + service_page_process_message { }: { } + + // Worst case for calling `bump_service_head`. + bump_service_head { }: {} + + service_page_next_msg { }: { } +} diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index 7e12563c44b11..6bf633e5e80c0 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -1,39 +1,27 @@ -// This file is part of Substrate. -// Copyright (C) 2022 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. - -//! Autogenerated weights for pallet_assets +//! Autogenerated weights for pallet_message_queue //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-05-23, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! DATE: 2022-10-28, STEPS: `2`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `oty-parity`, CPU: `11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// ./target/release/substrate // benchmark // pallet -// --chain=dev -// --steps=50 -// --repeat=20 -// --pallet=pallet_assets -// --extrinsic=* -// --execution=wasm -// --wasm-execution=compiled -// --template=./.maintain/frame-weight-template.hbs -// --output=./frame/assets/src/weights.rs +// --dev +// --pallet +// pallet-message-queue +// --extrinsic= +// --output +// frame/message-queue/src/weights.rs +// --template +// .maintain/frame-weight-template.hbs +// --steps +// 2 +// --repeat +// 1 #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -42,24 +30,75 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -/// Weight functions needed for pallet_assets. +/// Weight functions needed for pallet_message_queue. pub trait WeightInfo { - fn dummy() -> Weight; + fn service_page_base() -> Weight; + fn service_queue_base() -> Weight; + fn service_page_process_message() -> Weight; + fn bump_service_head() -> Weight; + fn service_page_next_msg() -> Weight; +} + +// TODO auto-generate this by the benchmarking +pub struct WeightMetaInfo(PhantomData); +impl WeightMetaInfo { + /// Executes a callback for each weight function by passing in its name and value. + pub fn visit_weight_functions R, R>(f: F) -> Vec { + vec![ + f("service_page_base", W::service_page_base()), + f("service_queue_base", W::service_queue_base()), + f("service_page_process_message", W::service_page_process_message()), + f("bump_service_head", W::bump_service_head()), + f("service_page_next_msg", W::service_page_next_msg()), + ] + } } -/// Weights for pallet_assets using the Substrate node and recommended hardware. +/// Weights for pallet_message_queue using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Assets Asset (r:1 w:1) - fn dummy() -> Weight { - Weight::from_ref_time(27_167_000 as u64) + fn service_page_base() -> Weight { + // Minimum execution time: 86 nanoseconds. + Weight::from_ref_time(86_000 as u64) + } + fn service_queue_base() -> Weight { + // Minimum execution time: 112 nanoseconds. + Weight::from_ref_time(112_000 as u64) + } + + fn service_page_process_message() -> Weight { + Weight::from_ref_time(112_000 as u64) + } + + fn bump_service_head() -> Weight { + Weight::from_ref_time(112_000 as u64) + } + + fn service_page_next_msg() -> Weight { + Weight::from_ref_time(112_000 as u64) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Assets Asset (r:1 w:1) - fn dummy() -> Weight { - Weight::from_ref_time(27_167_000 as u64) + fn service_page_base() -> Weight { + // Minimum execution time: 86 nanoseconds. + Weight::from_ref_time(86_000 as u64) + } + fn service_queue_base() -> Weight { + // Minimum execution time: 112 nanoseconds. + Weight::from_ref_time(112_000 as u64) + } + + fn service_page_process_message() -> Weight { + Weight::from_ref_time(112_000 as u64) + } + + fn bump_service_head() -> Weight { + Weight::from_ref_time(112_000 as u64) + } + + fn service_page_next_msg() -> Weight { + Weight::from_ref_time(112_000 as u64) } } From 56d0003c32264a188797864c39432696a443b355 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 29 Oct 2022 22:32:34 +0200 Subject: [PATCH 018/110] Use WeightCounter Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/Cargo.toml | 2 ++ frame/message-queue/src/lib.rs | 42 +++++++++++++++++++--------------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml index aeb5c02a34328..697bfb38a31bf 100644 --- a/frame/message-queue/Cargo.toml +++ b/frame/message-queue/Cargo.toml @@ -15,6 +15,7 @@ sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/ sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } sp-arithmetic = { version = "5.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } @@ -30,6 +31,7 @@ std = [ "sp-runtime/std", "sp-std/std", "sp-arithmetic/std", + "sp-weights/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 4bb7c4fd6220b..2186c762f5325 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -33,6 +33,7 @@ use sp_runtime::{ SaturatedConversion, Saturating, }; use sp_std::{fmt::Debug, prelude::*, vec}; +use sp_weights::WeightCounter; pub use weights::WeightInfo; /// Type for identifying an overweight message. @@ -406,7 +407,12 @@ impl Pallet { } } - fn bump_service_head() -> Option> { + fn bump_service_head(weight: &mut WeightCounter) -> Option> { + if !weight.check_accrue(T::WeightInfo::bump_service_head()) { + log::debug!(target: LOG_TARGET, "bump_service_head: weight limit reached"); + return None + } + if let Some(head) = ServiceHead::::get() { let mut head_book_state = BookStateOf::::get(&head); if let Some(head_neighbours) = head_book_state.ready_neighbours.take() { @@ -504,11 +510,10 @@ impl Pallet { /// execute are deemed overweight and ignored. fn service_queue( origin: MessageOriginOf, - weight_limit: Weight, + weight: &mut WeightCounter, overweight_limit: Weight, - ) -> (Weight, Option>) { + ) -> Option> { let mut processed = 0; - let mut weight_left = weight_limit; let mut book_state = BookStateOf::::get(&origin); while book_state.end > book_state.begin { let page_index = book_state.begin; @@ -527,7 +532,7 @@ impl Pallet { None => break false, }[..]; - if weight_left.any_lte(Weight::zero()) { + if weight.remaining().any_lte(Weight::zero()) { break true } @@ -538,7 +543,7 @@ impl Pallet { match T::MessageProcessor::process_message( message, origin.clone(), - weight_left, + weight.remaining(), ) { Err(Overweight(w)) if processed == 0 || w.any_gt(overweight_limit) => { // Permanently overweight. @@ -566,7 +571,7 @@ impl Pallet { Ok((success, weight_used)) => { // Success processed.saturating_inc(); - weight_left = weight_left.saturating_sub(weight_used); + weight.defensive_saturating_accrue(weight_used); let event = Event::::Processed { hash, origin, weight_used, success }; Self::deposit_event(event); @@ -607,7 +612,7 @@ impl Pallet { } } BookStateOf::::insert(&origin, &book_state); - (weight_limit.saturating_sub(weight_left), next_ready) + next_ready } } @@ -655,27 +660,28 @@ pub trait ServiceQueues { impl ServiceQueues for Pallet { fn service_queues(weight_limit: Weight) -> Weight { - let max_weight = weight_limit.saturating_mul(3).saturating_div(4); - let mut weight_used = Weight::zero(); - let mut next = match Self::bump_service_head() { + /// The maximum weight that processing a single message may take. + let max_per_msg = weight_limit.saturating_mul(3).saturating_div(4); + let mut weight = WeightCounter::from_limit(weight_limit); + let mut next = match Self::bump_service_head(&mut weight) { Some(h) => h, - None => return weight_used, + None => return weight.consumed, }; loop { - let left = weight_limit - weight_used; - if left.any_lte(Weight::zero()) { + // TODO should not be needed. Instead add a no-progress check and stop rotating + // the ring in that case. This is only needed since tests declares the base weights + // as zero, so it produces an infinite loop. + if weight.remaining().any_lte(Zero::zero()) { break } - // Before servicing, bump `ServiceHead` onto the next item. - let (used, n) = Self::service_queue(next, left, max_weight); - weight_used.saturating_accrue(used); + let n = Self::service_queue(next, &mut weight, max_per_msg); next = match n { Some(n) => n, None => break, } } - weight_used + weight.consumed } } From 4371fa246f9fc2c4c338ce7bb6b35cb93fe96d94 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sat, 29 Oct 2022 22:33:06 +0200 Subject: [PATCH 019/110] Small fixes and logging Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/Cargo.toml | 4 +++ frame/message-queue/src/lib.rs | 55 +++++++++++++++++++++++++------- frame/message-queue/src/tests.rs | 1 + 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml index 697bfb38a31bf..b77c7aeb31bad 100644 --- a/frame/message-queue/Cargo.toml +++ b/frame/message-queue/Cargo.toml @@ -21,6 +21,9 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +[dev-dependencies] +sp-tracing = { version = "5.0.0", default-features = false, path = "../../primitives/tracing" } + [features] default = ["std"] std = [ @@ -32,6 +35,7 @@ std = [ "sp-std/std", "sp-arithmetic/std", "sp-weights/std", + "sp-tracing/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 2186c762f5325..508e203497022 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -18,12 +18,15 @@ #![cfg_attr(not(feature = "std"), no_std)] +mod benchmarking; #[cfg(test)] mod tests; -mod weights; +pub mod weights; use codec::{Codec, Decode, Encode, FullCodec, MaxEncodedLen}; -use frame_support::{pallet_prelude::*, BoundedSlice}; +use frame_support::{ + defensive, pallet_prelude::*, traits::DefensiveTruncateFrom, BoundedSlice, CloneNoBound, +}; use frame_system::pallet_prelude::*; pub use pallet::*; use scale_info::TypeInfo; @@ -41,6 +44,8 @@ type OverweightIndex = u64; /// Type for identifying a page. type PageIndex = u32; +const LOG_TARGET: &'static str = "runtime::message-queue"; + /// Data encoded and prefixed to the encoded `MessageItem`. #[derive(Encode, Decode, MaxEncodedLen)] pub struct ItemHeader { @@ -52,10 +57,10 @@ pub struct ItemHeader { } /// A page of messages. Pages always contain at least one item. -#[derive(Clone, Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen)] +#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(HeapSize))] #[codec(mel_bound(Size: MaxEncodedLen))] -pub struct Page + Debug, HeapSize: Get> { +pub struct Page + Debug + Clone, HeapSize: Get> { /// Messages remaining to be processed; this includes overweight messages which have been /// skipped. remaining: Size, @@ -111,26 +116,30 @@ impl< Ok(()) } + /// Returns the first message in the page without removing it. + /// + /// SAFETY: Does not panic even on corrupted storage. fn peek_first(&self) -> Option>> { if self.first > self.last { return None } - let mut item_slice = &self.heap[(self.first.into() as usize)..]; + let f = (self.first.into() as usize).min(self.heap.len()); + let mut item_slice = &self.heap[f..]; if let Ok(h) = ItemHeader::::decode(&mut item_slice) { - let payload_len = h.payload_len.into(); - if payload_len <= item_slice.len() as u32 { + let payload_len = h.payload_len.into() as usize; + if payload_len <= item_slice.len() { // impossible to truncate since is sliced up from `self.heap: BoundedVec` - return Some(BoundedSlice::truncate_from(&item_slice[..(payload_len as usize)])) + return Some(BoundedSlice::defensive_truncate_from(&item_slice[..payload_len])) } } - debug_assert!(false, "message-queue: heap corruption"); + defensive!("message-queue: heap corruption"); None } /// Point `first` at the next message, marking the first as processed if `is_processed` is true. fn skip_first(&mut self, is_processed: bool) { - let f = self.first.into() as usize; + let f = (self.first.into() as usize).min(self.heap.len()); if let Ok(mut h) = ItemHeader::decode(&mut &self.heap[f..]) { if is_processed && !h.is_processed { h.is_processed = true; @@ -292,6 +301,15 @@ pub mod pallet { let weight_used = Weight::zero(); weight_used } + + fn integrity_test() { + // TODO currently disabled since it fails. + // None of the weight functions can be zero. + // Otherwise we risk getting stuck in a no-progress situation (= infinite loop). + /*weights::WeightMetaInfo::<::WeightInfo>::visit_weight_functions( + |name, w| assert!(!w.is_zero(), "Weight function is zero: {}", name), + );*/ + } } #[pallet::call] @@ -402,7 +420,7 @@ impl Pallet { ServiceHead::::put(neighbours.next); } } else { - debug_assert!(false, "`ServiceHead` must be some if there was a ready queue"); + defensive!("`ServiceHead` must be some if there was a ready queue"); } } } @@ -417,6 +435,11 @@ impl Pallet { let mut head_book_state = BookStateOf::::get(&head); if let Some(head_neighbours) = head_book_state.ready_neighbours.take() { ServiceHead::::put(&head_neighbours.next); + log::debug!( + target: LOG_TARGET, + "bump_service_head: next head `{:?}`", + head_neighbours.next + ); Some(head) } else { None @@ -439,7 +462,7 @@ impl Pallet { let mut page = match Pages::::get(origin, last) { Some(p) => p, None => { - debug_assert!(false, "Corruption: referenced page doesn't exist."); + defensive!("Corruption: referenced page doesn't exist."); return }, }; @@ -641,6 +664,7 @@ pub type MaxMessageLenOf = pub type MaxOriginLenOf = MaxEncodedLenOf>; pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; +pub type PageOf = Page<::Size, ::HeapSize>; pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); impl, O: Into> Get for IntoU32 { @@ -663,6 +687,13 @@ impl ServiceQueues for Pallet { /// The maximum weight that processing a single message may take. let max_per_msg = weight_limit.saturating_mul(3).saturating_div(4); let mut weight = WeightCounter::from_limit(weight_limit); + log::debug!( + target: LOG_TARGET, + "ServiceQueues::service_queues with weight {} and max_per_msg {}", + weight_limit, + max_per_msg + ); + let mut next = match Self::bump_service_head(&mut weight) { Some(h) => h, None => return weight.consumed, diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 16b3164040452..a1d151800f6bd 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -186,6 +186,7 @@ impl ProcessMessage for TestMessageProcessor { } pub fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); WeightForCall::set(Default::default()); let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut ext = sp_io::TestExternalities::new(t); From 7db583f2bca8ad38554226ca73f5825b2c4dbbd0 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 30 Oct 2022 13:07:30 +0100 Subject: [PATCH 020/110] Add service_page Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 184 +++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 79 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 508e203497022..858a329a0119b 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -528,6 +528,96 @@ impl Pallet { Ok(()) } + /// Service as many messages of a page as possible. + fn service_page( + origin: &MessageOriginOf, + page_index: u32, + processed: &mut u32, + book_state: &mut BookState>, + weight: &mut WeightCounter, + max_per_msg: Weight, + ) -> bool /* stop */ { + let mut page = match Pages::::get(&origin, page_index) { + Some(p) => p, + None => { + debug_assert!(false, "message-queue: referenced page not found"); + return true + }, + }; + + let bail = loop { + if !weight.check_accrue(T::WeightInfo::service_page_next_msg()) { + break true + } + let mut message = &match page.peek_first() { + Some(m) => m, + None => break false, + }[..]; + + // TODO should not be needed. + if weight.remaining().any_lte(Weight::zero()) { + break true + } + + let is_processed = match MessageOriginOf::::decode(&mut message) { + Ok(origin) => { + let hash = T::Hashing::hash(message); + use ProcessMessageError::Overweight; + match T::MessageProcessor::process_message( + message, + origin.clone(), + weight.remaining(), + ) { + Err(Overweight(w)) if *processed == 0 || w.any_gt(max_per_msg) => { + // Permanently overweight. + Self::deposit_event(Event::::Overweight { hash, origin, index: 0 }); // TODO page + index + false + }, + Err(Overweight(_)) => { + // Temporarily overweight - save progress and stop processing this + // queue. + break true + }, + Err(error) => { + // Permanent error - drop + Self::deposit_event(Event::::ProcessingFailed { + hash, + origin, + error, + }); + true + }, + Ok((success, weight_used)) => { + // Success + processed.saturating_inc(); + weight.defensive_saturating_accrue(weight_used); + let event = + Event::::Processed { hash, origin, weight_used, success }; + Self::deposit_event(event); + true + }, + } + }, + Err(_) => { + let hash = T::Hashing::hash(message); + Self::deposit_event(Event::::Discarded { hash }); + false + }, + }; + page.skip_first(is_processed); + }; + + if page.is_complete() { + debug_assert!(!bail, "we never bail if a page became complete"); + Pages::::remove(&origin, page_index); + debug_assert!(book_state.count > 0, "completing a page implies there are pages"); + book_state.count.saturating_dec(); + } else { + Pages::::insert(&origin, page_index, page); + } + bail + } + /// Execute any messages remaining to be processed in the queue of `origin`, using up to /// `weight_limit` to do so. Any messages which would take more than `overweight_limit` to /// execute are deemed overweight and ignored. @@ -540,87 +630,23 @@ impl Pallet { let mut book_state = BookStateOf::::get(&origin); while book_state.end > book_state.begin { let page_index = book_state.begin; - // TODO: Check `weight_left` and bail before doing this storage read. - let mut page = match Pages::::get(&origin, page_index) { - Some(p) => p, - None => { - debug_assert!(false, "message-queue: referenced page not found"); - break - }, - }; - - let bail = loop { - let mut message = &match page.peek_first() { - Some(m) => m, - None => break false, - }[..]; - - if weight.remaining().any_lte(Weight::zero()) { - break true - } - - let is_processed = match MessageOriginOf::::decode(&mut message) { - Ok(origin) => { - let hash = T::Hashing::hash(message); - use ProcessMessageError::Overweight; - match T::MessageProcessor::process_message( - message, - origin.clone(), - weight.remaining(), - ) { - Err(Overweight(w)) if processed == 0 || w.any_gt(overweight_limit) => { - // Permanently overweight. - Self::deposit_event(Event::::Overweight { - hash, - origin, - index: 0, - }); // TODO page + index - false - }, - Err(Overweight(_)) => { - // Temporarily overweight - save progress and stop processing this - // queue. - break true - }, - Err(error) => { - // Permanent error - drop - Self::deposit_event(Event::::ProcessingFailed { - hash, - origin, - error, - }); - true - }, - Ok((success, weight_used)) => { - // Success - processed.saturating_inc(); - weight.defensive_saturating_accrue(weight_used); - let event = - Event::::Processed { hash, origin, weight_used, success }; - Self::deposit_event(event); - true - }, - } - }, - Err(_) => { - let hash = T::Hashing::hash(message); - Self::deposit_event(Event::::Discarded { hash }); - false - }, - }; - page.skip_first(is_processed); - }; - - if page.is_complete() { - debug_assert!(!bail, "we never bail if a page became complete"); - Pages::::remove(&origin, page_index); - debug_assert!(book_state.count > 0, "completing a page implies there are pages"); - book_state.count.saturating_dec(); - } else { - Pages::::insert(&origin, page_index, page); + if !weight.check_accrue(T::WeightInfo::service_queue_base()) { + return None } + if !weight.check_accrue(T::WeightInfo::service_page_process_message()) { + return None + } + + let stop = Self::service_page( + &origin, + page_index, + &mut processed, + &mut book_state, + weight, + overweight_limit, + ); - if bail { + if stop { break } book_state.begin.saturating_inc(); From 84ce78e9aa04d08f01a645ec0b9e003f75a1af28 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 30 Oct 2022 13:07:49 +0100 Subject: [PATCH 021/110] Typo Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 858a329a0119b..8bd1a57e33d3b 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -710,7 +710,7 @@ pub trait ServiceQueues { impl ServiceQueues for Pallet { fn service_queues(weight_limit: Weight) -> Weight { - /// The maximum weight that processing a single message may take. + // The maximum weight that processing a single message may take. let max_per_msg = weight_limit.saturating_mul(3).saturating_div(4); let mut weight = WeightCounter::from_limit(weight_limit); log::debug!( @@ -727,7 +727,7 @@ impl ServiceQueues for Pallet { loop { // TODO should not be needed. Instead add a no-progress check and stop rotating - // the ring in that case. This is only needed since tests declares the base weights + // the ring in that case. This is only needed since tests declares the weights // as zero, so it produces an infinite loop. if weight.remaining().any_lte(Zero::zero()) { break From 5bded121896fc2195af324a1ac31168677ddaeea Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 30 Oct 2022 13:17:35 +0100 Subject: [PATCH 022/110] Move service_page below service_queue Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 93 +++++++++++++++++----------------- 1 file changed, 46 insertions(+), 47 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 8bd1a57e33d3b..665a7d11f0fb1 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -528,7 +528,52 @@ impl Pallet { Ok(()) } + /// Execute any messages remaining to be processed in the queue of `origin`, using up to + /// `weight_limit` to do so. Any messages which would take more than `overweight_limit` to + /// execute are deemed overweight and ignored. + fn service_queue( + origin: MessageOriginOf, + weight: &mut WeightCounter, + overweight_limit: Weight, + ) -> Option> { + let mut processed = 0; + let mut book_state = BookStateOf::::get(&origin); + while book_state.end > book_state.begin { + let page_index = book_state.begin; + if !weight.check_accrue(T::WeightInfo::service_queue_base()) { + return None + } + + let stop = Self::service_page( + &origin, + page_index, + &mut processed, + &mut book_state, + weight, + overweight_limit, + ); + + if stop { + break + } + book_state.begin.saturating_inc(); + } + let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone()); + if book_state.begin >= book_state.end && processed > 0 { + // No longer ready - unknit. + if let Some(neighbours) = book_state.ready_neighbours.take() { + Self::ready_ring_unknit(&origin, neighbours); + } else { + debug_assert!(false, "Freshly processed queue must have been ready"); + } + } + BookStateOf::::insert(&origin, &book_state); + next_ready + } + /// Service as many messages of a page as possible. + /// + /// Returns whether the execution bailed. fn service_page( origin: &MessageOriginOf, page_index: u32, @@ -536,7 +581,7 @@ impl Pallet { book_state: &mut BookState>, weight: &mut WeightCounter, max_per_msg: Weight, - ) -> bool /* stop */ { + ) -> bool { let mut page = match Pages::::get(&origin, page_index) { Some(p) => p, None => { @@ -617,52 +662,6 @@ impl Pallet { } bail } - - /// Execute any messages remaining to be processed in the queue of `origin`, using up to - /// `weight_limit` to do so. Any messages which would take more than `overweight_limit` to - /// execute are deemed overweight and ignored. - fn service_queue( - origin: MessageOriginOf, - weight: &mut WeightCounter, - overweight_limit: Weight, - ) -> Option> { - let mut processed = 0; - let mut book_state = BookStateOf::::get(&origin); - while book_state.end > book_state.begin { - let page_index = book_state.begin; - if !weight.check_accrue(T::WeightInfo::service_queue_base()) { - return None - } - if !weight.check_accrue(T::WeightInfo::service_page_process_message()) { - return None - } - - let stop = Self::service_page( - &origin, - page_index, - &mut processed, - &mut book_state, - weight, - overweight_limit, - ); - - if stop { - break - } - book_state.begin.saturating_inc(); - } - let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone()); - if book_state.begin >= book_state.end && processed > 0 { - // No longer ready - unknit. - if let Some(neighbours) = book_state.ready_neighbours.take() { - Self::ready_ring_unknit(&origin, neighbours); - } else { - debug_assert!(false, "Freshly processed queue must have been ready"); - } - } - BookStateOf::::insert(&origin, &book_state); - next_ready - } } pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); From 77f375fefd7391fc38a6dc747bc89b7ad48fc9db Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 30 Oct 2022 15:18:45 +0100 Subject: [PATCH 023/110] Add service_message Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 8 +- frame/message-queue/src/lib.rs | 160 ++++++++++++++---------- frame/message-queue/src/tests.rs | 4 +- frame/message-queue/src/weights.rs | 8 +- 4 files changed, 105 insertions(+), 75 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 0d34b02fdb55f..3dba6e524995e 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -26,17 +26,17 @@ use frame_system::RawOrigin; use sp_std::prelude::*; benchmarks! { + // Just calling `service_queue` without any iterations happening. + service_queue_base { }: { } + // Just calling `service_page` without any iterations happening. service_page_base { }: { } - // Just calling `service_queue` without any iterations happening. - service_queue_base { }: { } + service_message { }: { } // Contains the effort for fetching and (storing or removing) a page. service_page_process_message { }: { } // Worst case for calling `bump_service_head`. bump_service_head { }: {} - - service_page_next_msg { }: { } } diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 665a7d11f0fb1..dad2d749e1101 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -370,6 +370,21 @@ impl Iterator for ReadyRing { } } +/// The status of a page after trying to execute its next message. +#[derive(PartialEq)] +enum PageExecutionStatus { + /// The execution bailed because there was not enough weight was remaining. + Bailed, + /// No more messages could be loaded. This does _not_ imply `page.is_complete()`. + /// + /// The reasons for this status are: + /// - The end of the page is reached but there could still be skipped messages. + /// - The storage is corrupted. + NoMore, + /// The execution progressed and executed some messages. + Partial, +} + impl Pallet { /// Knit `origin` into the ready ring right at the end. /// @@ -544,7 +559,7 @@ impl Pallet { return None } - let stop = Self::service_page( + let status = Self::service_page( &origin, page_index, &mut processed, @@ -553,7 +568,7 @@ impl Pallet { overweight_limit, ); - if stop { + if status == PageExecutionStatus::Bailed { break } book_state.begin.saturating_inc(); @@ -581,86 +596,101 @@ impl Pallet { book_state: &mut BookState>, weight: &mut WeightCounter, max_per_msg: Weight, - ) -> bool { + ) -> PageExecutionStatus { let mut page = match Pages::::get(&origin, page_index) { Some(p) => p, None => { debug_assert!(false, "message-queue: referenced page not found"); - return true + return PageExecutionStatus::Bailed }, }; - let bail = loop { - if !weight.check_accrue(T::WeightInfo::service_page_next_msg()) { - break true + // Execute as many messages as possible. + let status = loop { + match Self::service_message(origin, &mut page, processed, weight, max_per_msg) { + s @ PageExecutionStatus::Bailed | s @ PageExecutionStatus::NoMore => break s, + // Keep going as long as we make progress... + PageExecutionStatus::Partial => continue, } - let mut message = &match page.peek_first() { - Some(m) => m, - None => break false, - }[..]; - - // TODO should not be needed. - if weight.remaining().any_lte(Weight::zero()) { - break true - } - - let is_processed = match MessageOriginOf::::decode(&mut message) { - Ok(origin) => { - let hash = T::Hashing::hash(message); - use ProcessMessageError::Overweight; - match T::MessageProcessor::process_message( - message, - origin.clone(), - weight.remaining(), - ) { - Err(Overweight(w)) if *processed == 0 || w.any_gt(max_per_msg) => { - // Permanently overweight. - Self::deposit_event(Event::::Overweight { hash, origin, index: 0 }); // TODO page + index - false - }, - Err(Overweight(_)) => { - // Temporarily overweight - save progress and stop processing this - // queue. - break true - }, - Err(error) => { - // Permanent error - drop - Self::deposit_event(Event::::ProcessingFailed { - hash, - origin, - error, - }); - true - }, - Ok((success, weight_used)) => { - // Success - processed.saturating_inc(); - weight.defensive_saturating_accrue(weight_used); - let event = - Event::::Processed { hash, origin, weight_used, success }; - Self::deposit_event(event); - true - }, - } - }, - Err(_) => { - let hash = T::Hashing::hash(message); - Self::deposit_event(Event::::Discarded { hash }); - false - }, - }; - page.skip_first(is_processed); }; if page.is_complete() { - debug_assert!(!bail, "we never bail if a page became complete"); + debug_assert!( + status != PageExecutionStatus::Bailed, + "we never bail if a page became complete" + ); Pages::::remove(&origin, page_index); debug_assert!(book_state.count > 0, "completing a page implies there are pages"); book_state.count.saturating_dec(); } else { Pages::::insert(&origin, page_index, page); } - bail + status + } + + /// Execute the next message of a page. + fn service_message( + origin: &MessageOriginOf, + page: &mut PageOf, + processed: &mut u32, + weight: &mut WeightCounter, + max_per_msg: Weight, + ) -> PageExecutionStatus { + if !weight.check_accrue(T::WeightInfo::service_message()) { + return PageExecutionStatus::Bailed + } + let mut message = &match page.peek_first() { + Some(m) => m, + None => return PageExecutionStatus::NoMore, + }[..]; + + // TODO should not be needed. + if weight.remaining().any_lte(Weight::zero()) { + return PageExecutionStatus::Bailed + } + + let is_processed = match MessageOriginOf::::decode(&mut message) { + Ok(origin) => { + let hash = T::Hashing::hash(message); + use ProcessMessageError::Overweight; + match T::MessageProcessor::process_message( + message, + origin.clone(), + weight.remaining(), + ) { + Err(Overweight(w)) if *processed == 0 || w.any_gt(max_per_msg) => { + // Permanently overweight. + Self::deposit_event(Event::::Overweight { hash, origin, index: 0 }); // TODO page + index + false + }, + Err(Overweight(_)) => { + // Temporarily overweight - save progress and stop processing this + // queue. + return PageExecutionStatus::Bailed + }, + Err(error) => { + // Permanent error - drop + Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); + true + }, + Ok((success, weight_used)) => { + // Success + processed.saturating_inc(); + weight.defensive_saturating_accrue(weight_used); + let event = Event::::Processed { hash, origin, weight_used, success }; + Self::deposit_event(event); + true + }, + } + }, + Err(_) => { + let hash = T::Hashing::hash(message); + Self::deposit_event(Event::::Discarded { hash }); + false + }, + }; + page.skip_first(is_processed); + PageExecutionStatus::Partial } } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index a1d151800f6bd..1f2bb26ef1186 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -133,8 +133,8 @@ impl crate::weights::WeightInfo for MockedWeightInfo { fn bump_service_head() -> Weight { WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() } - fn service_page_next_msg() -> Weight { - WeightForCall::get().get("service_page_next_msg").copied().unwrap_or_default() + fn service_message() -> Weight { + WeightForCall::get().get("service_message").copied().unwrap_or_default() } } diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index 6bf633e5e80c0..e98d2ed9ac667 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -36,7 +36,7 @@ pub trait WeightInfo { fn service_queue_base() -> Weight; fn service_page_process_message() -> Weight; fn bump_service_head() -> Weight; - fn service_page_next_msg() -> Weight; + fn service_message() -> Weight; } // TODO auto-generate this by the benchmarking @@ -49,7 +49,7 @@ impl WeightMetaInfo { f("service_queue_base", W::service_queue_base()), f("service_page_process_message", W::service_page_process_message()), f("bump_service_head", W::bump_service_head()), - f("service_page_next_msg", W::service_page_next_msg()), + f("service_message", W::service_message()), ] } } @@ -74,7 +74,7 @@ impl WeightInfo for SubstrateWeight { Weight::from_ref_time(112_000 as u64) } - fn service_page_next_msg() -> Weight { + fn service_message() -> Weight { Weight::from_ref_time(112_000 as u64) } } @@ -98,7 +98,7 @@ impl WeightInfo for () { Weight::from_ref_time(112_000 as u64) } - fn service_page_next_msg() -> Weight { + fn service_message() -> Weight { Weight::from_ref_time(112_000 as u64) } } From 04d16989af02e0e995485ac8d1fac94c2a032e8f Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 30 Oct 2022 23:55:51 +0100 Subject: [PATCH 024/110] Use correct weight function Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 35 ++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index dad2d749e1101..dfa982c77d146 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -373,7 +373,7 @@ impl Iterator for ReadyRing { /// The status of a page after trying to execute its next message. #[derive(PartialEq)] enum PageExecutionStatus { - /// The execution bailed because there was not enough weight was remaining. + /// The execution bailed because there was not enough weight remaining. Bailed, /// No more messages could be loaded. This does _not_ imply `page.is_complete()`. /// @@ -551,25 +551,28 @@ impl Pallet { weight: &mut WeightCounter, overweight_limit: Weight, ) -> Option> { + if !weight.check_accrue(T::WeightInfo::service_queue_base()) { + return None + } + let mut processed = 0; let mut book_state = BookStateOf::::get(&origin); while book_state.end > book_state.begin { - let page_index = book_state.begin; - if !weight.check_accrue(T::WeightInfo::service_queue_base()) { - return None - } - - let status = Self::service_page( + match Self::service_page( &origin, - page_index, &mut processed, &mut book_state, weight, overweight_limit, - ); - - if status == PageExecutionStatus::Bailed { - break + ) { + // Store the page progress and do not go to the next one. + PageExecutionStatus::Bailed => break, + // Go to the next page if this one is at the end. + PageExecutionStatus::NoMore => (), + // TODO @ggwpez think of a better enum here. + PageExecutionStatus::Partial => { + defensive!("should progress till the end or bail"); + }, } book_state.begin.saturating_inc(); } @@ -591,17 +594,21 @@ impl Pallet { /// Returns whether the execution bailed. fn service_page( origin: &MessageOriginOf, - page_index: u32, processed: &mut u32, book_state: &mut BookState>, weight: &mut WeightCounter, max_per_msg: Weight, ) -> PageExecutionStatus { + if !weight.check_accrue(T::WeightInfo::service_page_base()) { + return PageExecutionStatus::Bailed + } + + let page_index = book_state.begin; let mut page = match Pages::::get(&origin, page_index) { Some(p) => p, None => { debug_assert!(false, "message-queue: referenced page not found"); - return PageExecutionStatus::Bailed + return PageExecutionStatus::NoMore }, }; From 16b4a8fb1ab20d4e93a2d0c491f0bf2842c98e6c Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 1 Nov 2022 10:30:23 +0100 Subject: [PATCH 025/110] Overweight execution --- frame/message-queue/src/lib.rs | 192 +++++++++++++++++++++++---------- 1 file changed, 137 insertions(+), 55 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 4bb7c4fd6220b..b31c3c51d0232 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -142,6 +142,42 @@ impl< } } + /// Return the unprocessed encoded (origin, message) pair of `index` into the page's + /// messages if and only if it was skipped (i.e. of location prior to `first`) and is + /// unprocessed. + fn peek_index(&self, index: usize) -> Option<(usize, bool, &[u8])> { + let mut pos = 0; + let mut item_slice = &self.heap[..]; + let header_len: usize = ItemHeader::::max_encoded_len().saturated_into(); + for _ in 0..index { + let h = ItemHeader::::decode(&mut item_slice).ok()?; + let item_len: usize = header_len + h.payload_len.into() as usize; + if item_slice.len() < item_len { + return None + } + item_slice = &item_slice[item_len..]; + pos.saturating_accrue(item_len); + } + let h = ItemHeader::::decode(&mut item_slice).ok()?; + if item_slice.len() < header_len + h.payload_len.into() as usize { + return None + } + item_slice = &item_slice[header_len + h.payload_len.into() as usize..]; + Some((pos, h.is_processed, item_slice)) + } + + /// Set the `is_processed` flag for the item at `pos` to be `true` if not already and decrement + /// the `remaining` counter of the page. + fn note_processed_at_pos(&mut self, pos: usize) { + if let Ok(mut h) = ItemHeader::::decode(&mut &self.heap[pos..]) { + if !h.is_processed { + h.is_processed = true; + h.using_encoded(|d| self.heap[pos..pos + d.len()].copy_from_slice(d)); + self.remaining.saturating_dec(); + } + } + } + fn is_complete(&self) -> bool { self.remaining.is_zero() } @@ -262,6 +298,10 @@ pub mod pallet { NotReapable, /// Page to be reaped does not exist. NoPage, + NoMessage, + Unexpected, + AlreadyProcessed, + Queued, } /// The index of the first and last (non-empty) pages. @@ -295,10 +335,15 @@ pub mod pallet { #[pallet::call] impl Pallet { - #[pallet::weight(0)] - pub fn send(origin: OriginFor) -> DispatchResult { + /// Remove a page which has no more messages remaining to be processed. + #[pallet::weight(150_000_000_000)] + pub fn reap_page( + origin: OriginFor, + message_origin: MessageOriginOf, + page_index: PageIndex, + ) -> DispatchResult { let _ = ensure_signed(origin)?; - Ok(()) + Self::do_reap_page(&message_origin, page_index) } /// Execute an overweight message. @@ -311,7 +356,7 @@ pub mod pallet { /// of the message. /// /// Benchmark complexity considerations: O(index + weight_limit). - #[pallet::weight(0)] + #[pallet::weight(150_000_000_000)] pub fn execute_overweight( origin: OriginFor, message_origin: MessageOriginOf, @@ -320,8 +365,7 @@ pub mod pallet { weight_limit: Weight, ) -> DispatchResult { let _ = ensure_signed(origin)?; - - Ok(()) + Self::do_execute_overweight(message_origin, page, index, weight_limit) } } } @@ -463,6 +507,39 @@ impl Pallet { BookStateOf::::insert(&origin, book_state); } + pub fn do_execute_overweight( + origin: MessageOriginOf, + page_index: PageIndex, + index: T::Size, + mut weight_limit: Weight, + ) -> DispatchResult { + let mut book_state = BookStateOf::::get(&origin); + let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; + let (pos, is_processed, blob) = + page.peek_index(index.into() as usize).ok_or(Error::::NoMessage)?; + ensure!( + page_index < book_state.begin || + (page_index == book_state.begin && pos < page.first.into() as usize), + Error::::Queued + ); + ensure!(is_processed, Error::::AlreadyProcessed); + let ok = Self::process_message(blob, weight_limit, &mut 0, &mut weight_limit) + .map_err(|_| Error::::Unexpected)?; + if ok { + page.note_processed_at_pos(pos); + } + if page.remaining.is_zero() { + Pages::::remove(&origin, page_index); + debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); + book_state.count.saturating_dec(); + // no need toconsider .first or ready ring since processing an overweight page would + // not alter that state. + } else { + Pages::::insert(&origin, page_index, page); + } + Ok(()) + } + /// Remove a page which has no more messages remaining to be processed. fn do_reap_page(origin: &MessageOriginOf, page_index: PageIndex) -> DispatchResult { let mut book_state = BookStateOf::::get(origin); @@ -499,6 +576,50 @@ impl Pallet { Ok(()) } + fn process_message( + mut message: &[u8], + overweight_limit: Weight, + processed: &mut u32, + weight_left: &mut Weight, + ) -> Result { + match MessageOriginOf::::decode(&mut message) { + Ok(origin) => { + let hash = T::Hashing::hash(message); + use ProcessMessageError::Overweight; + match T::MessageProcessor::process_message(message, origin.clone(), *weight_left) { + Err(Overweight(w)) if *processed == 0 || w.any_gt(overweight_limit) => { + // Permanently overweight. + Self::deposit_event(Event::::Overweight { hash, origin, index: 0 }); // TODO page + index + Ok(false) + }, + Err(Overweight(_)) => { + // Temporarily overweight - save progress and stop processing this + // queue. + Err(true) + }, + Err(error) => { + // Permanent error - drop + Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); + Ok(true) + }, + Ok((success, weight_used)) => { + // Success + processed.saturating_inc(); + *weight_left = weight_left.saturating_sub(weight_used); + let event = Event::::Processed { hash, origin, weight_used, success }; + Self::deposit_event(event); + Ok(true) + }, + } + }, + Err(_) => { + let hash = T::Hashing::hash(message); + Self::deposit_event(Event::::Discarded { hash }); + Ok(true) + }, + } + } + /// Execute any messages remaining to be processed in the queue of `origin`, using up to /// `weight_limit` to do so. Any messages which would take more than `overweight_limit` to /// execute are deemed overweight and ignored. @@ -522,7 +643,7 @@ impl Pallet { }; let bail = loop { - let mut message = &match page.peek_first() { + let message = &match page.peek_first() { Some(m) => m, None => break false, }[..]; @@ -531,56 +652,17 @@ impl Pallet { break true } - let is_processed = match MessageOriginOf::::decode(&mut message) { - Ok(origin) => { - let hash = T::Hashing::hash(message); - use ProcessMessageError::Overweight; - match T::MessageProcessor::process_message( - message, - origin.clone(), - weight_left, - ) { - Err(Overweight(w)) if processed == 0 || w.any_gt(overweight_limit) => { - // Permanently overweight. - Self::deposit_event(Event::::Overweight { - hash, - origin, - index: 0, - }); // TODO page + index - false - }, - Err(Overweight(_)) => { - // Temporarily overweight - save progress and stop processing this - // queue. - break true - }, - Err(error) => { - // Permanent error - drop - Self::deposit_event(Event::::ProcessingFailed { - hash, - origin, - error, - }); - true - }, - Ok((success, weight_used)) => { - // Success - processed.saturating_inc(); - weight_left = weight_left.saturating_sub(weight_used); - let event = - Event::::Processed { hash, origin, weight_used, success }; - Self::deposit_event(event); - true - }, - } - }, - Err(_) => { - let hash = T::Hashing::hash(message); - Self::deposit_event(Event::::Discarded { hash }); - false + match Self::process_message( + message, + overweight_limit, + &mut processed, + &mut weight_left, + ) { + Ok(is_processed) => { + page.skip_first(is_processed); }, + Err(x) => break x, }; - page.skip_first(is_processed); }; if page.is_complete() { From 522fd6582ca05ea5b53a17c9f876f81e6c00d3bf Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 2 Nov 2022 11:28:46 +0100 Subject: [PATCH 026/110] Refactor --- Cargo.lock | 1 + frame/message-queue/src/lib.rs | 74 +++++----------------------------- frame/scheduler/Cargo.toml | 2 + frame/support/src/traits.rs | 3 ++ 4 files changed, 16 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3e5798940c9e..79a91b56874c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6025,6 +6025,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-weights", "substrate-test-utils", ] diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 0b8add400ce66..a655029e36d1c 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -25,7 +25,12 @@ pub mod weights; use codec::{Codec, Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{ - defensive, pallet_prelude::*, traits::DefensiveTruncateFrom, BoundedSlice, CloneNoBound, + defensive, + pallet_prelude::*, + traits::{ + DefensiveTruncateFrom, EnqueueMessage, ProcessMessage, ProcessMessageError, ServiceQueues, + }, + BoundedSlice, CloneNoBound, }; use frame_system::pallet_prelude::*; pub use pallet::*; @@ -193,32 +198,6 @@ impl< } } -#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, Debug)] -pub enum ProcessMessageError { - /// The message data format is unknown (e.g. unrecognised header) - BadFormat, - /// The message data is bad (e.g. decoding returns an error). - Corrupt, - /// The message format is unsupported (e.g. old XCM version). - Unsupported, - /// Message processing was not attempted because it was not certain that the weight limit - /// would be respected. The parameter gives the maximum weight which the message could take - /// to process. - Overweight(Weight), -} - -pub trait ProcessMessage { - /// The transport from where a message originates. - type Origin: FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + sp_std::fmt::Debug; - - /// Process the given message, using no more than `weight_limit` in weight to do so. - fn process_message( - message: &[u8], - origin: Self::Origin, - weight_limit: Weight, - ) -> Result<(bool, Weight), ProcessMessageError>; -} - #[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] pub struct Neighbours { prev: MessageOrigin, @@ -594,8 +573,8 @@ impl Pallet { Pages::::remove(&origin, page_index); debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); book_state.count.saturating_dec(); - // no need toconsider .first or ready ring since processing an overweight page would - // not alter that state. + // no need toconsider .first or ready ring since processing an overweight page would + // not alter that state. } else { Pages::::insert(&origin, page_index, page); } @@ -655,12 +634,8 @@ impl Pallet { let mut book_state = BookStateOf::::get(&origin); let mut total_processed = 0; while book_state.end > book_state.begin { - let (processed, status) = Self::service_page( - &origin, - &mut book_state, - weight, - overweight_limit, - ); + let (processed, status) = + Self::service_page(&origin, &mut book_state, weight, overweight_limit); total_processed.saturating_accrue(processed); match status { // Store the page progress and do not go to the next one. @@ -849,15 +824,6 @@ impl, O: Into> Get for IntoU32 { } } -pub trait ServiceQueues { - /// Service all message queues in some fair manner. - /// - /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. - /// - /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. - fn service_queues(weight_limit: Weight) -> Weight; -} - impl ServiceQueues for Pallet { fn service_queues(weight_limit: Weight) -> Weight { // The maximum weight that processing a single message may take. @@ -892,26 +858,6 @@ impl ServiceQueues for Pallet { } } -pub trait EnqueueMessage { - type MaxMessageLen: Get; - - /// Enqueue a single `message` from a specific `origin`. - /// - /// Infallible. - fn enqueue_message(message: BoundedSlice, origin: Origin); - - /// Enqueue multiple `messages` from a specific `origin`. - /// - /// If no `message.len()` is greater than `HEAP_SIZE - Origin::max_encoded_len()`, then this - /// is guaranteed to succeed. In the case of `Err`, no messages are queued. - fn enqueue_messages<'a>( - messages: impl Iterator>, - origin: Origin, - ); - - // TODO: consider: `fn enqueue_mqc_page(page: &[u8], origin: Origin);` -} - impl EnqueueMessage> for Pallet { type MaxMessageLen = MaxMessageLen<::Origin, T::Size, T::HeapSize>; diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index e78d8cd5061c1..984112649d14b 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -19,6 +19,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } [dev-dependencies] pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } @@ -42,5 +43,6 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-weights/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 2b6c5efee10bb..90b1d46db4e10 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -112,6 +112,9 @@ pub use voting::{ mod preimages; pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, StorePreimage}; +mod messages; +pub use messages::{EnqueueMessage, ProcessMessage, ProcessMessageError, ServiceQueues}; + #[cfg(feature = "try-runtime")] mod try_runtime; #[cfg(feature = "try-runtime")] From 250a6789dba4bdb79b0cce619e2b574b8f5a241d Mon Sep 17 00:00:00 2001 From: Gav Date: Wed, 2 Nov 2022 11:29:18 +0100 Subject: [PATCH 027/110] Missing file --- frame/support/src/traits/messages.rs | 78 ++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 frame/support/src/traits/messages.rs diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs new file mode 100644 index 0000000000000..70950e14f67d4 --- /dev/null +++ b/frame/support/src/traits/messages.rs @@ -0,0 +1,78 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 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. + +//! Traits for managing message queuing and handling. + +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::Get; +use sp_runtime::{BoundedSlice, RuntimeDebug}; +use sp_std::{fmt::Debug, prelude::*}; +use sp_weights::Weight; + +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, RuntimeDebug)] +pub enum ProcessMessageError { + /// The message data format is unknown (e.g. unrecognised header) + BadFormat, + /// The message data is bad (e.g. decoding returns an error). + Corrupt, + /// The message format is unsupported (e.g. old XCM version). + Unsupported, + /// Message processing was not attempted because it was not certain that the weight limit + /// would be respected. The parameter gives the maximum weight which the message could take + /// to process. + Overweight(Weight), +} + +pub trait ProcessMessage { + /// The transport from where a message originates. + type Origin: FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + Debug; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError>; +} + +pub trait ServiceQueues { + /// Service all message queues in some fair manner. + /// + /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. + /// + /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. + fn service_queues(weight_limit: Weight) -> Weight; +} + +pub trait EnqueueMessage { + type MaxMessageLen: Get; + + /// Enqueue a single `message` from a specific `origin`. + /// + /// Infallible. + fn enqueue_message(message: BoundedSlice, origin: Origin); + + /// Enqueue multiple `messages` from a specific `origin`. + /// + /// If no `message.len()` is greater than `HEAP_SIZE - Origin::max_encoded_len()`, then this + /// is guaranteed to succeed. In the case of `Err`, no messages are queued. + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: Origin, + ); +} From 126a3ad36b966e815e5d89ae5372d42a01d837cd Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 2 Nov 2022 11:49:16 +0100 Subject: [PATCH 028/110] Fix WeightCounter usage in scheduler Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 1 + frame/scheduler/Cargo.toml | 2 ++ frame/scheduler/src/benchmarking.rs | 14 +++++++------- frame/scheduler/src/lib.rs | 5 ++--- primitives/weights/src/lib.rs | 4 ++++ 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3e5798940c9e..79a91b56874c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6025,6 +6025,7 @@ dependencies = [ "sp-io", "sp-runtime", "sp-std", + "sp-weights", "substrate-test-utils", ] diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index e78d8cd5061c1..984112649d14b 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -19,6 +19,7 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys sp-io = { version = "6.0.0", default-features = false, path = "../../primitives/io" } sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } +sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } [dev-dependencies] pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } @@ -42,5 +43,6 @@ std = [ "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-weights/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/scheduler/src/benchmarking.rs b/frame/scheduler/src/benchmarking.rs index aaa30fd88ffda..b0727cf06bae3 100644 --- a/frame/scheduler/src/benchmarking.rs +++ b/frame/scheduler/src/benchmarking.rs @@ -123,7 +123,7 @@ fn make_origin(signed: bool) -> ::PalletsOrigin { } fn dummy_counter() -> WeightCounter { - WeightCounter { used: Weight::zero(), limit: Weight::MAX } + WeightCounter::from_limit(Weight::MAX) } benchmarks! { @@ -155,7 +155,7 @@ benchmarks! { let now = BLOCK_NUMBER.into(); let task = make_task::(false, false, false, None, 0); // prevent any tasks from actually being executed as we only want the surrounding weight. - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + let mut counter = WeightCounter::from_limit(Zero::zero()); }: { let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); } verify { @@ -169,7 +169,7 @@ benchmarks! { let now = BLOCK_NUMBER.into(); let task = make_task::(false, false, false, Some(s), 0); // prevent any tasks from actually being executed as we only want the surrounding weight. - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + let mut counter = WeightCounter::from_limit(Zero::zero()); }: { let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); } verify { @@ -181,7 +181,7 @@ benchmarks! { let now = BLOCK_NUMBER.into(); let task = make_task::(false, true, false, None, 0); // prevent any tasks from actually being executed as we only want the surrounding weight. - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + let mut counter = WeightCounter::from_limit(Zero::zero()); }: { let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); } verify { @@ -193,7 +193,7 @@ benchmarks! { let now = BLOCK_NUMBER.into(); let task = make_task::(true, false, false, None, 0); // prevent any tasks from actually being executed as we only want the surrounding weight. - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::zero() }; + let mut counter = WeightCounter::from_limit(Zero::zero()); }: { let result = Scheduler::::service_task(&mut counter, now, now, 0, true, task); } verify { @@ -201,7 +201,7 @@ benchmarks! { // `execute_dispatch` when the origin is `Signed`, not counting the dispatable's weight. execute_dispatch_signed { - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX }; + let mut counter = WeightCounter::from_limit(Weight::MAX); let origin = make_origin::(true); let call = T::Preimages::realize(&make_call::(None)).unwrap().0; }: { @@ -212,7 +212,7 @@ benchmarks! { // `execute_dispatch` when the origin is not `Signed`, not counting the dispatable's weight. execute_dispatch_unsigned { - let mut counter = WeightCounter { used: Weight::zero(), limit: Weight::MAX }; + let mut counter = WeightCounter::from_limit(Weight::MAX); let origin = make_origin::(false); let call = T::Preimages::realize(&make_call::(None)).unwrap().0; }: { diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index 3a60204b37567..3785420370669 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -288,10 +288,9 @@ pub mod pallet { impl Hooks> for Pallet { /// Execute the scheduled calls fn on_initialize(now: T::BlockNumber) -> Weight { - let mut weight_counter = - WeightCounter { used: Weight::zero(), limit: T::MaximumWeight::get() }; + let mut weight_counter = WeightCounter::from_limit(T::MaximumWeight::get()); Self::service_agendas(&mut weight_counter, now, u32::max_value()); - weight_counter.used + weight_counter.consumed } } diff --git a/primitives/weights/src/lib.rs b/primitives/weights/src/lib.rs index 6ac3576a15497..27e08b03fee08 100644 --- a/primitives/weights/src/lib.rs +++ b/primitives/weights/src/lib.rs @@ -235,6 +235,10 @@ impl WeightCounter { Self { consumed: Weight::zero(), limit } } + pub fn unlimited() -> Self { + Self { consumed: Weight::zero(), limit: Weight::MAX } + } + /// The remaining weight that can be consumed. pub fn remaining(&self) -> Weight { self.limit.saturating_sub(self.consumed) From 4a03e253a55d022e459d4b38db9d97e4b4da1040 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 2 Nov 2022 11:51:20 +0100 Subject: [PATCH 029/110] Fix peek_index Take into account that decoding from a mutable slice modifies it. Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 11 +++++------ frame/message-queue/src/tests.rs | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 0b8add400ce66..572005c019a39 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -153,26 +153,25 @@ impl< } /// Return the unprocessed encoded (origin, message) pair of `index` into the page's - /// messages if and only if it was skipped (i.e. of location prior to `first`) and is - /// unprocessed. + /// messages if and only if it was skipped (i.e. of location prior to `first`). fn peek_index(&self, index: usize) -> Option<(usize, bool, &[u8])> { let mut pos = 0; let mut item_slice = &self.heap[..]; let header_len: usize = ItemHeader::::max_encoded_len().saturated_into(); for _ in 0..index { let h = ItemHeader::::decode(&mut item_slice).ok()?; - let item_len: usize = header_len + h.payload_len.into() as usize; + let item_len = h.payload_len.into() as usize; if item_slice.len() < item_len { return None } item_slice = &item_slice[item_len..]; - pos.saturating_accrue(item_len); + pos.saturating_accrue(header_len.saturating_add(item_len)); } let h = ItemHeader::::decode(&mut item_slice).ok()?; - if item_slice.len() < header_len + h.payload_len.into() as usize { + if item_slice.len() < h.payload_len.into() as usize { return None } - item_slice = &item_slice[header_len + h.payload_len.into() as usize..]; + item_slice = &item_slice[..h.payload_len.into() as usize]; Some((pos, h.is_processed, item_slice)) } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index dca2b92b723e1..eebced11f4530 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -409,3 +409,19 @@ fn reaping_overweight_fails_properly() { assert_noop!(MessageQueue::do_reap_page(&Here, 2), Error::::NotReapable); }); } + +#[test] +fn peek_index_works() { + new_test_ext().execute_with(|| { + let (mut page, msgs) = full_page::(); + assert!(msgs > 1, "precondition unmet"); + + for i in 0..msgs { + page.skip_first(i % 2 == 0); + let (pos, processed, payload) = page.peek_index(i).unwrap(); + assert_eq!(pos, 10 * i); + assert_eq!(processed, i % 2 == 0); + assert_eq!(payload, MessageOrigin::Everywhere(i as u32).encode()); + } + }); +} From 91b694d649dc6df4e61a2717846eb2fc9699841a Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 2 Nov 2022 11:52:16 +0100 Subject: [PATCH 030/110] Add tests and bench service_page_item Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 14 +-- frame/message-queue/src/benchmarking.rs | 43 ++++++++- frame/message-queue/src/lib.rs | 43 ++++----- frame/message-queue/src/mock.rs | 69 +++++++++++++++ frame/message-queue/src/tests.rs | 113 +++++++++++++++++------- frame/message-queue/src/weights.rs | 3 +- 6 files changed, 223 insertions(+), 62 deletions(-) create mode 100644 frame/message-queue/src/mock.rs diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 26d707c7c24db..27fb07a34e5f5 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1121,20 +1121,20 @@ impl pallet_bounties::Config for Runtime { } parameter_types! { - pub const HeapSize: u32 = 24; - pub const MaxStale: u32 = 2; + pub const HeapSize: u32 = 64 * 1024; // 64 KiB + pub const MaxStale: u32 = 128; } /// Processes any message while consuming no weight. pub struct NoOpMessageProcessor; impl pallet_message_queue::ProcessMessage for NoOpMessageProcessor { - type Origin = (); + type Origin = u32; fn process_message( - message: &[u8], - origin: Self::Origin, - weight_limit: Weight, + _message: &[u8], + _origin: Self::Origin, + _weight_limit: Weight, ) -> Result<(bool, Weight), pallet_message_queue::ProcessMessageError> { Ok((true, Weight::zero())) } @@ -1146,7 +1146,7 @@ impl pallet_message_queue::Config for Runtime { type MessageProcessor = NoOpMessageProcessor; type Size = u32; type HeapSize = HeapSize; - type MaxReady = MaxReady; + type MaxStale = MaxStale; } parameter_types! { diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 81bc175e60edc..fec4b2c9f300c 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -18,25 +18,62 @@ #![cfg(feature = "runtime-benchmarks")] -use super::{Pallet as MessageQueue, *}; +use super::{mock::*, Pallet as MessageQueue, *}; use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_support::traits::Get; -use frame_system::RawOrigin; +use frame_system::{Pallet as System, RawOrigin}; use sp_std::prelude::*; +static LOG_TARGET: &'static str = "runtime::message-queue::bench"; + benchmarks! { + where_clause { + // NOTE: We need to generate multiple origins; therefore Origin must be `From`. + where <::MessageProcessor as ProcessMessage>::Origin: From + } + // Just calling `service_queue` without any iterations happening. service_queue_base { }: { } // Just calling `service_page` without any iterations happening. service_page_base { }: { } - service_page_item { }: { } + // Processing a single message from a page. + // + // The benchmarks uses a full page and skips all message except the last one, + // although this should not make a difference. + service_page_item { + let (mut page, msgs) = full_page::(); + // Skip all messages except the last one. + for i in 1..msgs { + page.skip_first(true); + } + assert!(page.peek_first().is_some(), "There is one message left"); + let mut weight = WeightCounter::unlimited(); + log::debug!(target: LOG_TARGET, "{} messages per page", msgs); + }: { + let status = MessageQueue::::service_page_item(&0u32.into(), &mut page, &mut weight, Weight::MAX); + assert_eq!(status, PageExecutionStatus::Partial); + } verify { + // Check fot the `Processed` event + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&[]), origin: ((msgs - 1) as u32).into(), + weight_used: 1.into_weight(), success: true + }.into()); + } // Contains the effort for fetching and (storing or removing) a page. service_page_process_message { }: { } // Worst case for calling `bump_service_head`. bump_service_head { }: {} + + // Create a test for each benchmark. + impl_benchmark_test_suite!(MessageQueue, crate::tests::new_test_ext(), crate::tests::Test); +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + assert!(!System::::block_number().is_zero(), "The genesis block has n o events"); + System::::assert_last_event(generic_event.into()); } diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 572005c019a39..0e7d8a99cdecb 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -19,6 +19,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +mod mock; #[cfg(test)] mod tests; pub mod weights; @@ -26,6 +27,7 @@ pub mod weights; use codec::{Codec, Decode, Encode, FullCodec, MaxEncodedLen}; use frame_support::{ defensive, pallet_prelude::*, traits::DefensiveTruncateFrom, BoundedSlice, CloneNoBound, + DefaultNoBound, }; use frame_system::pallet_prelude::*; pub use pallet::*; @@ -47,7 +49,7 @@ type PageIndex = u32; const LOG_TARGET: &'static str = "runtime::message-queue"; /// Data encoded and prefixed to the encoded `MessageItem`. -#[derive(Encode, Decode, MaxEncodedLen)] +#[derive(Encode, Decode, PartialEq, MaxEncodedLen, Debug)] pub struct ItemHeader { /// The length of this item, not including the size of this header. The next item of the page /// follows immediately after the payload of this item. @@ -57,10 +59,12 @@ pub struct ItemHeader { } /// A page of messages. Pages always contain at least one item. -#[derive(CloneNoBound, Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo, MaxEncodedLen)] +#[derive( + CloneNoBound, Encode, Decode, RuntimeDebugNoBound, DefaultNoBound, TypeInfo, MaxEncodedLen, +)] #[scale_info(skip_type_params(HeapSize))] #[codec(mel_bound(Size: MaxEncodedLen))] -pub struct Page + Debug + Clone, HeapSize: Get> { +pub struct Page + Debug + Clone + Default, HeapSize: Get> { /// Messages remaining to be processed; this includes overweight messages which have been /// skipped. remaining: Size, @@ -74,7 +78,7 @@ pub struct Page + Debug + Clone, HeapSize: Get> { } impl< - Size: BaseArithmetic + Unsigned + Copy + Into + Codec + MaxEncodedLen + Debug, + Size: BaseArithmetic + Unsigned + Copy + Into + Codec + MaxEncodedLen + Debug + Default, HeapSize: Get, > Page { @@ -105,7 +109,7 @@ impl< return Err(()) } - let mut heap = sp_std::mem::replace(&mut self.heap, Default::default()).into_inner(); + let mut heap = sp_std::mem::take(&mut self.heap).into_inner(); h.using_encoded(|d| heap.extend_from_slice(d)); heap.extend_from_slice(origin); heap.extend_from_slice(message); @@ -273,7 +277,8 @@ pub mod pallet { + Encode + Decode + MaxEncodedLen - + TypeInfo; + + TypeInfo + + Default; /// The size of the page; this implies the maximum message size which can be sent. #[pallet::constant] @@ -415,7 +420,7 @@ impl Iterator for ReadyRing { } /// The status of a page after trying to execute its next message. -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] enum PageExecutionStatus { /// The execution bailed because there was not enough weight remaining. Bailed, @@ -593,8 +598,8 @@ impl Pallet { Pages::::remove(&origin, page_index); debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); book_state.count.saturating_dec(); - // no need toconsider .first or ready ring since processing an overweight page would - // not alter that state. + // no need toconsider .first or ready ring since processing an overweight page would + // not alter that state. } else { Pages::::insert(&origin, page_index, page); } @@ -654,12 +659,8 @@ impl Pallet { let mut book_state = BookStateOf::::get(&origin); let mut total_processed = 0; while book_state.end > book_state.begin { - let (processed, status) = Self::service_page( - &origin, - &mut book_state, - weight, - overweight_limit, - ); + let (processed, status) = + Self::service_page(&origin, &mut book_state, weight, overweight_limit); total_processed.saturating_accrue(processed); match status { // Store the page progress and do not go to the next one. @@ -738,12 +739,17 @@ impl Pallet { } /// Execute the next message of a page. - fn service_page_item( + pub(crate) fn service_page_item( _origin: &MessageOriginOf, page: &mut PageOf, weight: &mut WeightCounter, overweight_limit: Weight, ) -> PageExecutionStatus { + // This ugly pre-checking is needed for the invariant + // "we never bail if a page became complete". + if page.is_complete() { + return PageExecutionStatus::NoMore + } if !weight.check_accrue(T::WeightInfo::service_page_item()) { return PageExecutionStatus::Bailed } @@ -752,11 +758,6 @@ impl Pallet { None => return PageExecutionStatus::NoMore, }[..]; - // TODO should not be needed. - if weight.remaining().any_lte(Weight::zero()) { - return PageExecutionStatus::Bailed - } - use MessageExecutionStatus::*; let is_processed = match Self::process_message(message, weight, overweight_limit) { InsufficientWeight => return PageExecutionStatus::Bailed, diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs new file mode 100644 index 0000000000000..168c80d7eedd7 --- /dev/null +++ b/frame/message-queue/src/mock.rs @@ -0,0 +1,69 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +#![cfg(any(test, feature = "runtime-benchmarks"))] + +use super::*; + +use frame_support::{assert_noop, assert_ok, parameter_types}; +use sp_std::collections::btree_map::BTreeMap; + +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] +pub enum MessageOrigin { + Here, + There, + Everywhere(u32), +} + +impl From for MessageOrigin { + fn from(i: u32) -> Self { + Self::Everywhere(i) + } +} + +/// Converts `Self` into a `Weight` by using `Self` for all components. +pub trait IntoWeight { + fn into_weight(self) -> Weight; +} + +impl IntoWeight for u64 { + fn into_weight(self) -> Weight { + Weight::from_parts(self, self) + } +} + +pub fn msg>(x: &'static str) -> BoundedSlice { + BoundedSlice::truncate_from(x.as_bytes()) +} + +pub fn vmsg(x: &'static str) -> Vec { + x.as_bytes().to_vec() +} + +/// Returns a full page and its number of empty messages. +pub fn full_page() -> (PageOf, usize) { + let mut msgs = 0; + let mut page = PageOf::::default(); + for i in 0..u32::MAX { + if page.try_append_message(&[], &MessageOrigin::Everywhere(i).encode()).is_err() { + break + } else { + msgs += 1; + } + } + assert!(msgs > 0, "page must hold at least one message"); + (page, msgs) +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index eebced11f4530..925b33da63e64 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -19,7 +19,7 @@ #![cfg(test)] -use super::*; +use crate::{mock::*, *}; use crate as pallet_message_queue; use frame_support::{ @@ -31,7 +31,7 @@ use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; -use std::collections::BTreeMap; +use sp_std::collections::btree_map::BTreeMap; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -79,7 +79,7 @@ impl frame_system::Config for Test { } parameter_types! { - pub const HeapSize: u32 = 24; + pub const HeapSize: u32 = 24; // 64 KiB pub const MaxStale: u32 = 2; } @@ -97,14 +97,15 @@ pub struct MockedWeightInfo; parameter_types! { /// Storage for `MockedWeightInfo`, do not use directly. - static WeightForCall: BTreeMap = Default::default(); + pub static WeightForCall: BTreeMap = Default::default(); } /// Set the return value for a function from the `WeightInfo` trait. impl MockedWeightInfo { - fn set_weight(call_name: &str, weight: Weight) { + /// Set the weight of a specific weight function. + pub fn set_weight(call_name: &str, weight: Weight) { assert!( - super::weights::WeightMetaInfo::<::WeightInfo>::visit_weight_functions( + super::weights::WeightMetaInfo::::visit_weight_functions( |f, _| f == call_name ) .into_iter() @@ -138,13 +139,6 @@ impl crate::weights::WeightInfo for MockedWeightInfo { } } -#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] -pub enum MessageOrigin { - Here, - There, - Everywhere(u8), -} - parameter_types! { pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; } @@ -155,6 +149,9 @@ impl ProcessMessage for TestMessageProcessor { type Origin = MessageOrigin; /// Process the given message, using no more than `weight_limit` in weight to do so. + /// + /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. + /// Errors if given the `weight_limit` is insufficient to process the message. fn process_message( message: &[u8], origin: Self::Origin, @@ -194,13 +191,9 @@ pub fn new_test_ext() -> sp_io::TestExternalities { ext } -pub trait IntoWeight { - fn into_weight(self) -> Weight; -} -impl IntoWeight for u64 { - fn into_weight(self) -> Weight { - Weight::from_parts(self, self) - } +/// Set the weight of a specific weight function. +pub fn set_weight(name: &str, w: Weight) { + MockedWeightInfo::set_weight::(name, w); } #[test] @@ -209,7 +202,7 @@ fn mocked_weight_works() { assert!(::WeightInfo::service_page_base().is_zero()); }); new_test_ext().execute_with(|| { - MockedWeightInfo::set_weight("service_page_base", Weight::MAX); + set_weight("service_page_base", Weight::MAX); assert_eq!(::WeightInfo::service_page_base(), Weight::MAX); }); // The externalities reset it. @@ -222,7 +215,7 @@ fn mocked_weight_works() { #[should_panic] fn mocked_weight_panics_on_invalid_name() { new_test_ext().execute_with(|| { - MockedWeightInfo::set_weight("invalid_name", Weight::MAX); + set_weight("invalid_name", Weight::MAX); }); } @@ -270,14 +263,6 @@ fn enqueue_within_one_page_works() { }); } -fn msg>(x: &'static str) -> BoundedSlice { - BoundedSlice::truncate_from(x.as_bytes()) -} - -fn vmsg(x: &'static str) -> Vec { - x.as_bytes().to_vec() -} - #[test] fn queue_priority_retains() { new_test_ext().execute_with(|| { @@ -410,6 +395,74 @@ fn reaping_overweight_fails_properly() { }); } +#[test] +fn service_page_item_bails() { + new_test_ext().execute_with(|| { + let (mut page, _) = full_page::(); + let mut weight = WeightCounter::from_limit(10.into_weight()); + let overweight_limit = 10.into_weight(); + set_weight("service_page_item", 11.into_weight()); + + assert_eq!( + MessageQueue::service_page_item( + &MessageOrigin::Here, + &mut page, + &mut weight, + overweight_limit + ), + PageExecutionStatus::Bailed + ); + }); +} + +#[test] +fn service_page_consumes_correct_weight() { + new_test_ext().execute_with(|| { + let mut page = PageOf::::from_message(b"weight=3", &MessageOrigin::Here.encode()); + let mut weight = WeightCounter::from_limit(10.into_weight()); + let overweight_limit = 0.into_weight(); + set_weight("service_page_item", 2.into_weight()); + + assert_eq!( + MessageQueue::service_page_item( + &MessageOrigin::Here, + &mut page, + &mut weight, + overweight_limit + ), + PageExecutionStatus::Partial + ); + assert_eq!(weight.consumed, 5.into_weight()); + }); +} + +/// Skips a permanent `Overweight` message and marks it as "unprocessed". +#[test] +fn service_page_skips_perm_overweight_message() { + new_test_ext().execute_with(|| { + let mut page = PageOf::::from_message(b"weight=6", &MessageOrigin::Here.encode()); + let mut weight = WeightCounter::from_limit(7.into_weight()); + let overweight_limit = 5.into_weight(); + set_weight("service_page_item", 2.into_weight()); + + assert_eq!( + MessageQueue::service_page_item( + &MessageOrigin::Here, + &mut page, + &mut weight, + overweight_limit + ), + PageExecutionStatus::Partial + ); + assert_eq!(weight.consumed, 2.into_weight()); + // Check that the message was skipped. + let (pos, processed, payload) = page.peek_index(0).unwrap(); + assert_eq!(pos, 0); + assert_eq!(processed, false); + assert_eq!(payload, (MessageOrigin::Here, b"weight=6").encode()); + }); +} + #[test] fn peek_index_works() { new_test_ext().execute_with(|| { diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index cc93e0e609628..4ec67a9294b2d 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -29,6 +29,7 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; +use sp_std::vec::Vec; /// Weight functions needed for pallet_message_queue. pub trait WeightInfo { @@ -44,7 +45,7 @@ pub struct WeightMetaInfo(PhantomData); impl WeightMetaInfo { /// Executes a callback for each weight function by passing in its name and value. pub fn visit_weight_functions R, R>(f: F) -> Vec { - vec![ + sp_std::vec![ f("service_page_base", W::service_page_base()), f("service_queue_base", W::service_queue_base()), f("service_page_process_message", W::service_page_process_message()), From 702fc995e8b038beeadb867bc1dcad4b97efdb16 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 4 Nov 2022 10:50:36 +0100 Subject: [PATCH 031/110] Add debug_info Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 43 +++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index da9189ff976b2..491aaf39b37cf 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -41,7 +41,7 @@ use sp_runtime::{ traits::{Hash, One, Zero}, SaturatedConversion, Saturating, }; -use sp_std::{fmt::Debug, prelude::*, vec}; +use sp_std::{fmt::Debug, ops::Deref, prelude::*, vec}; use sp_weights::WeightCounter; pub use weights::WeightInfo; @@ -746,6 +746,47 @@ impl Pallet { PageExecutionStatus::Partial } + /// Print which messages are in each queue. + /// + /// # Example output + /// + /// ```text + /// queue Here: + /// page 0: [] + /// > page 1: [] + /// page 2: ["\0weight=4", "\0c", ] + /// page 3: ["\0bigbig 1", ] + /// page 4: ["\0bigbig 2", ] + /// page 5: ["\0bigbig 3", ] + /// ``` + #[cfg(feature = "std")] + fn debug_info() -> String { + let mut info = String::new(); + for (origin, book_state) in BookStateOf::::iter() { + let mut queue = format!("queue {:?}:\n", &origin); + let mut pages = Pages::::iter_prefix(&origin).collect::>(); + pages.sort_by(|(a,_ ), (b, _)| a.cmp(b)); + for (page_index, page) in pages.into_iter() { + let mut page = page; + let mut page_info = format!("page {}: [", page_index); + if book_state.begin == page_index { + page_info = format!("> {}", page_info); + } else { + page_info = format!(" {}", page_info); + } + while let Some(message) = page.peek_first() { + let msg = String::from_utf8_lossy(message.deref()); + page_info.push_str(&format!("{:?}, ", msg)); + page.skip_first(true); + } + page_info.push_str("]\n"); + queue.push_str(&page_info); + } + info.push_str(&queue); + } + info + } + fn process_message( mut message: &[u8], weight: &mut WeightCounter, From 06d4916e0295986fc4a683868cf57547c92f7c73 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 4 Nov 2022 15:26:31 +0100 Subject: [PATCH 032/110] Add no-progress check to service_queues Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 491aaf39b37cf..375869a3bb76d 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -884,17 +884,21 @@ impl ServiceQueues for Pallet { Some(h) => h, None => return weight.consumed, }; + // The last queue that did not make any progress. + // The loop aborts as soon as it arrives at this queue again without making any progress + // on other queues in between. + let mut last_no_progress = None; loop { - // TODO should not be needed. Instead add a no-progress check and stop rotating - // the ring in that case. This is only needed since tests declares the weights - // as zero, so it produces an infinite loop. - if weight.remaining().any_lte(Zero::zero()) { - break - } - let n = Self::service_queue(next, &mut weight, overweight_limit); + let (progressed, n) = Self::service_queue(next.clone(), &mut weight, overweight_limit); next = match n { - Some(n) => n, + Some(n) => + if !progressed && last_no_progress == Some(n.clone()) { + break + } else { + last_no_progress = Some(next); + n + }, None => break, } } From 33644914c6864caee1a85aa6ba86a5d2e15e6448 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 4 Nov 2022 19:26:52 +0100 Subject: [PATCH 033/110] Add more benches Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 54 +++++++++++++++++++------ frame/message-queue/src/weights.rs | 23 ++++------- 2 files changed, 50 insertions(+), 27 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index fec4b2c9f300c..1b3dba14cfa62 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -33,16 +33,38 @@ benchmarks! { where <::MessageProcessor as ProcessMessage>::Origin: From } - // Just calling `service_queue` without any iterations happening. - service_queue_base { }: { } + // `service_queue` without any page processing or unknitting. + service_queue_base { + let mut meter = WeightCounter::unlimited(); + }: { + MessageQueue::::service_queue(0u32.into(), &mut meter, Weight::MAX) + } - // Just calling `service_page` without any iterations happening. - service_page_base { }: { } + // `service_page` without any message processing but with page completion. + service_page_base { + let origin: MessageOriginOf = 0.into(); + let (page, msgs) = full_page::(); + Pages::::insert(&origin, 0, &page); + let mut book_state = single_page_book::(); + let mut meter = WeightCounter::unlimited(); + let limit = Weight::MAX; + }: { + MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) + } + + // Worst case path of `ready_ring_unknit`. + ready_ring_unknit { + let origin: MessageOriginOf = 0.into(); + let neighbours = Neighbours::> { + prev: 0.into(), + next: 1.into() + }; + ServiceHead::::put(&origin); + }: { + MessageQueue::::ready_ring_unknit(&origin, neighbours); + } // Processing a single message from a page. - // - // The benchmarks uses a full page and skips all message except the last one, - // although this should not make a difference. service_page_item { let (mut page, msgs) = full_page::(); // Skip all messages except the last one. @@ -69,11 +91,19 @@ benchmarks! { // Worst case for calling `bump_service_head`. bump_service_head { }: {} + reap_page { + assert!(T::MaxStale::get() >= 2, "pre-condition: MaxStale needs to be at least two"); + + for i in 0..(T::MaxStale::get() + 2) { + MessageQueue::::enqueue_message(msg(&"weight=2"), 0.into()); + } + MessageQueue::::service_queues(1.into_weight()); + + }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0) + verify { + assert_last_event::(Event::PageReaped{ origin: 0.into(), index: 0 }.into()); + } + // Create a test for each benchmark. impl_benchmark_test_suite!(MessageQueue, crate::tests::new_test_ext(), crate::tests::Test); } - -fn assert_last_event(generic_event: ::RuntimeEvent) { - assert!(!System::::block_number().is_zero(), "The genesis block has n o events"); - System::::assert_last_event(generic_event.into()); -} diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index 4ec67a9294b2d..7f19132c8b584 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -38,21 +38,7 @@ pub trait WeightInfo { fn service_page_process_message() -> Weight; fn bump_service_head() -> Weight; fn service_page_item() -> Weight; -} - -// TODO auto-generate this by the benchmarking -pub struct WeightMetaInfo(PhantomData); -impl WeightMetaInfo { - /// Executes a callback for each weight function by passing in its name and value. - pub fn visit_weight_functions R, R>(f: F) -> Vec { - sp_std::vec![ - f("service_page_base", W::service_page_base()), - f("service_queue_base", W::service_queue_base()), - f("service_page_process_message", W::service_page_process_message()), - f("bump_service_head", W::bump_service_head()), - f("service_page_item", W::service_page_item()), - ] - } + fn ready_ring_unknit() -> Weight; } /// Weights for pallet_message_queue using the Substrate node and recommended hardware. @@ -78,6 +64,9 @@ impl WeightInfo for SubstrateWeight { fn service_page_item() -> Weight { Weight::from_ref_time(112_000 as u64) } + fn ready_ring_unknit() -> Weight { + Weight::from_ref_time(112_000 as u64) + } } // For backwards compatibility and tests @@ -102,4 +91,8 @@ impl WeightInfo for () { fn service_page_item() -> Weight { Weight::from_ref_time(112_000 as u64) } + + fn ready_ring_unknit() -> Weight { + Weight::from_ref_time(112_000 as u64) + } } From c991ff5b69db1f253a02dbb2d90059d8e4f3b535 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 4 Nov 2022 19:27:36 +0100 Subject: [PATCH 034/110] Bound from_message and try_append_message Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 51 +++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 375869a3bb76d..27c5c6dda7f07 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -86,39 +86,52 @@ impl< HeapSize: Get, > Page { - fn from_message(message: &[u8], origin: &[u8]) -> Self { - let len = ItemHeader::::max_encoded_len() + origin.len() + message.len(); - let mut heap = Vec::with_capacity(len); - let payload_len: Size = (origin.len() + message.len()).saturated_into(); // TODO: bounded inputs for safety - let h = ItemHeader { payload_len, is_processed: false }; - h.using_encoded(|d| heap.extend_from_slice(d)); - heap.extend_from_slice(origin); - heap.extend_from_slice(message); + /// Create a [`Page`] from one unprocessed message and its origin. + fn from_message( + message: BoundedSlice>, + origin: BoundedSlice>, + ) -> Self { + let payload_len = origin.len().saturating_add(message.len()); + let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); + let header = + ItemHeader:: { payload_len: payload_len.saturated_into(), is_processed: false }; + + let mut heap = Vec::with_capacity(data_len); + header.using_encoded(|h| heap.extend_from_slice(h)); + heap.extend_from_slice(origin.deref()); + heap.extend_from_slice(message.deref()); + Page { remaining: One::one(), first: Zero::zero(), last: Zero::zero(), - heap: BoundedVec::truncate_from(heap), + heap: BoundedVec::defensive_truncate_from(heap), } } - fn try_append_message(&mut self, message: &[u8], origin: &[u8]) -> Result<(), ()> { + /// Try to append one message from an origin. + fn try_append_message( + &mut self, + message: BoundedSlice>, + origin: BoundedSlice>, + ) -> Result<(), ()> { let pos = self.heap.len(); - let len = (ItemHeader::::max_encoded_len() + origin.len() + message.len()) as u32; - let payload_len: Size = (origin.len() + message.len()).saturated_into(); // TODO: bounded inputs for safety - let h = ItemHeader { payload_len, is_processed: false }; + let payload_len = origin.len().saturating_add(message.len()); + let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); + + let header = + ItemHeader:: { payload_len: payload_len.saturated_into(), is_processed: false }; let heap_size: u32 = HeapSize::get().into(); - if heap_size.saturating_sub(self.heap.len() as u32) < len { + if (heap_size as usize).saturating_sub(self.heap.len()) < data_len { // Can't fit. return Err(()) } let mut heap = sp_std::mem::take(&mut self.heap).into_inner(); - h.using_encoded(|d| heap.extend_from_slice(d)); - heap.extend_from_slice(origin); - heap.extend_from_slice(message); - debug_assert!(heap.len() as u32 <= HeapSize::get().into(), "already checked size; qed"); - self.heap = BoundedVec::truncate_from(heap); + header.using_encoded(|h| heap.extend_from_slice(h)); + heap.extend_from_slice(origin.deref()); + heap.extend_from_slice(message.deref()); + self.heap = BoundedVec::defensive_truncate_from(heap); self.last = pos.saturated_into(); self.remaining.saturating_inc(); Ok(()) From 496188d1f1f5d0df87c21bcc8cc9aa7d5735cd70 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 4 Nov 2022 19:28:04 +0100 Subject: [PATCH 035/110] Add PageReaped event Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 27c5c6dda7f07..71aded12a3e57 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -287,13 +287,32 @@ pub mod pallet { pub enum Event { /// Message discarded due to an inability to decode the item. Usually caused by state /// corruption. - Discarded { hash: T::Hash }, + Discarded { + hash: T::Hash, + }, /// Message discarded due to an error in the `MessageProcessor` (usually a format error). - ProcessingFailed { hash: T::Hash, origin: MessageOriginOf, error: ProcessMessageError }, + ProcessingFailed { + hash: T::Hash, + origin: MessageOriginOf, + error: ProcessMessageError, + }, /// Message is processed. - Processed { hash: T::Hash, origin: MessageOriginOf, weight_used: Weight, success: bool }, + Processed { + hash: T::Hash, + origin: MessageOriginOf, + weight_used: Weight, + success: bool, + }, /// Message placed in overweight queue. - Overweight { hash: T::Hash, origin: MessageOriginOf, index: OverweightIndex }, + Overweight { + hash: T::Hash, + origin: MessageOriginOf, + index: OverweightIndex, + }, + PageReaped { + origin: MessageOriginOf, + index: PageIndex, + }, } #[pallet::error] @@ -537,7 +556,7 @@ impl Pallet { return }, }; - if let Ok(_) = page.try_append_message(&message[..], &origin_data[..]) { + if let Ok(_) = page.try_append_message::(message, origin_data) { Pages::::insert(origin, last, &page); return } @@ -558,7 +577,7 @@ impl Pallet { Pages::::insert( origin, book_state.end - 1, - Page::from_message(&message[..], &origin_data[..]), + Page::from_message::(message, origin_data), ); BookStateOf::::insert(&origin, book_state); } @@ -631,6 +650,7 @@ impl Pallet { debug_assert!(book_state.count > 0, "reaping a page implies there are pages"); book_state.count.saturating_dec(); BookStateOf::::insert(origin, book_state); + Self::deposit_event(Event::PageReaped { origin: origin.clone(), index: page_index }); Ok(()) } From ae428570f44f2f9a4e2d5f35462f5e9325f02564 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 4 Nov 2022 19:30:32 +0100 Subject: [PATCH 036/110] Rename BookStateOf and BookStateFor Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 37 +++++++++++++++++----------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 71aded12a3e57..b3f810d7cbde4 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -331,7 +331,7 @@ pub mod pallet { /// The index of the first and last (non-empty) pages. #[pallet::storage] - pub(super) type BookStateOf = + pub(super) type BookStateFor = StorageMap<_, Twox64Concat, MessageOriginOf, BookState>, ValueQuery>; /// The origin at which we should begin servicing. @@ -419,7 +419,7 @@ impl Iterator for ReadyRing { match self.next.take() { None => None, Some(last) => { - self.next = BookStateOf::::get(&last) + self.next = BookStateFor::::get(&last) .ready_neighbours .map(|n| n.next) .filter(|n| Some(n) != self.first.as_ref()); @@ -466,18 +466,18 @@ impl Pallet { /// Return the two ready ring neighbours of `origin`. fn ready_ring_knit(origin: &MessageOriginOf) -> Result>, ()> { if let Some(head) = ServiceHead::::get() { - let mut head_book_state = BookStateOf::::get(&head); + let mut head_book_state = BookStateFor::::get(&head); let mut head_neighbours = head_book_state.ready_neighbours.take().ok_or(())?; let tail = head_neighbours.prev; head_neighbours.prev = origin.clone(); head_book_state.ready_neighbours = Some(head_neighbours); - BookStateOf::::insert(&head, head_book_state); + BookStateFor::::insert(&head, head_book_state); - let mut tail_book_state = BookStateOf::::get(&tail); + let mut tail_book_state = BookStateFor::::get(&tail); let mut tail_neighbours = tail_book_state.ready_neighbours.take().ok_or(())?; tail_neighbours.next = origin.clone(); tail_book_state.ready_neighbours = Some(tail_neighbours); - BookStateOf::::insert(&tail, tail_book_state); + BookStateFor::::insert(&tail, tail_book_state); Ok(Neighbours { next: head, prev: tail }) } else { @@ -495,12 +495,12 @@ impl Pallet { // Service queue empty. ServiceHead::::kill(); } else { - BookStateOf::::mutate(&neighbours.next, |book_state| { + BookStateFor::::mutate(&neighbours.next, |book_state| { if let Some(ref mut n) = book_state.ready_neighbours { n.prev = neighbours.prev.clone() } }); - BookStateOf::::mutate(&neighbours.prev, |book_state| { + BookStateFor::::mutate(&neighbours.prev, |book_state| { if let Some(ref mut n) = book_state.ready_neighbours { n.next = neighbours.next.clone() } @@ -522,7 +522,7 @@ impl Pallet { } if let Some(head) = ServiceHead::::get() { - let mut head_book_state = BookStateOf::::get(&head); + let mut head_book_state = BookStateFor::::get(&head); if let Some(head_neighbours) = head_book_state.ready_neighbours.take() { ServiceHead::::put(&head_neighbours.next); log::debug!( @@ -544,7 +544,7 @@ impl Pallet { message: BoundedSlice>, origin_data: BoundedSlice>, ) { - let mut book_state = BookStateOf::::get(origin); + let mut book_state = BookStateFor::::get(origin); if book_state.end > book_state.begin { debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); // Already have a page in progress - attempt to append. @@ -579,7 +579,7 @@ impl Pallet { book_state.end - 1, Page::from_message::(message, origin_data), ); - BookStateOf::::insert(&origin, book_state); + BookStateFor::::insert(origin, book_state); } pub fn do_execute_overweight( @@ -588,7 +588,7 @@ impl Pallet { index: T::Size, weight_limit: Weight, ) -> DispatchResult { - let mut book_state = BookStateOf::::get(&origin); + let mut book_state = BookStateFor::::get(&origin); let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; let (pos, is_processed, blob) = page.peek_index(index.into() as usize).ok_or(Error::::NoMessage)?; @@ -620,7 +620,7 @@ impl Pallet { /// Remove a page which has no more messages remaining to be processed. fn do_reap_page(origin: &MessageOriginOf, page_index: PageIndex) -> DispatchResult { - let mut book_state = BookStateOf::::get(origin); + let mut book_state = BookStateFor::::get(origin); // definitely not reapable if the page's index is no less than the `begin`ning of ready // pages. ensure!(page_index < book_state.begin, Error::::NotReapable); @@ -649,7 +649,7 @@ impl Pallet { Pages::::remove(origin, page_index); debug_assert!(book_state.count > 0, "reaping a page implies there are pages"); book_state.count.saturating_dec(); - BookStateOf::::insert(origin, book_state); + BookStateFor::::insert(origin, book_state); Self::deposit_event(Event::PageReaped { origin: origin.clone(), index: page_index }); Ok(()) @@ -667,7 +667,7 @@ impl Pallet { return None } - let mut book_state = BookStateOf::::get(&origin); + let mut book_state = BookStateFor::::get(&origin); let mut total_processed = 0; while book_state.end > book_state.begin { let (processed, status) = @@ -694,8 +694,8 @@ impl Pallet { debug_assert!(false, "Freshly processed queue must have been ready"); } } - BookStateOf::::insert(&origin, &book_state); - next_ready + BookStateFor::::insert(&origin, &book_state); + (total_processed > 0, next_ready) } /// Service as many messages of a page as possible. @@ -795,7 +795,7 @@ impl Pallet { #[cfg(feature = "std")] fn debug_info() -> String { let mut info = String::new(); - for (origin, book_state) in BookStateOf::::iter() { + for (origin, book_state) in BookStateFor::::iter() { let mut queue = format!("queue {:?}:\n", &origin); let mut pages = Pages::::iter_prefix(&origin).collect::>(); pages.sort_by(|(a,_ ), (b, _)| a.cmp(b)); @@ -893,6 +893,7 @@ pub type MaxOriginLenOf = MaxEncodedLenOf>; pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; pub type PageOf = Page<::Size, ::HeapSize>; +pub type BookStateOf = BookState>; pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); impl, O: Into> Get for IntoU32 { From 6950ba17f02d5206ddfeea9026648967803bbe6c Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 4 Nov 2022 19:31:03 +0100 Subject: [PATCH 037/110] Update tests and remove logging Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 63 +++++++++------- frame/message-queue/src/mock.rs | 31 +++++++- frame/message-queue/src/tests.rs | 124 +++++++++++++++++++++++++------ 3 files changed, 166 insertions(+), 52 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index b3f810d7cbde4..5af4b78287b47 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -357,6 +357,7 @@ pub mod pallet { weight_used } + /// Check all pre-conditions about the [`crate::Config`] types. fn integrity_test() { // TODO currently disabled since it fails. // None of the weight functions can be zero. @@ -369,7 +370,7 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Remove a page which has no more messages remaining to be processed. + /// Remove a page which has no more messages remaining to be processed or is stale. #[pallet::weight(150_000_000_000)] pub fn reap_page( origin: OriginFor, @@ -452,7 +453,7 @@ enum MessageExecutionStatus { BadFormat, /// There is not enough weight remaining at present. InsufficientWeight, - /// There will never be enough enough weight. + /// There will never be enough weight. Overweight, /// The message was processed successfully. Processed, @@ -517,7 +518,6 @@ impl Pallet { fn bump_service_head(weight: &mut WeightCounter) -> Option> { if !weight.check_accrue(T::WeightInfo::bump_service_head()) { - log::debug!(target: LOG_TARGET, "bump_service_head: weight limit reached"); return None } @@ -525,11 +525,6 @@ impl Pallet { let mut head_book_state = BookStateFor::::get(&head); if let Some(head_neighbours) = head_book_state.ready_neighbours.take() { ServiceHead::::put(&head_neighbours.next); - log::debug!( - target: LOG_TARGET, - "bump_service_head: next head `{:?}`", - head_neighbours.next - ); Some(head) } else { None @@ -608,8 +603,8 @@ impl Pallet { Pages::::remove(&origin, page_index); debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); book_state.count.saturating_dec(); - // no need toconsider .first or ready ring since processing an overweight page would - // not alter that state. + // no need to consider .first or ready ring since processing an overweight page + // would not alter that state. } else { Pages::::insert(&origin, page_index, page); } @@ -662,13 +657,16 @@ impl Pallet { origin: MessageOriginOf, weight: &mut WeightCounter, overweight_limit: Weight, - ) -> Option> { - if !weight.check_accrue(T::WeightInfo::service_queue_base()) { - return None + ) -> (bool, Option>) { + if !weight.check_accrue( + T::WeightInfo::service_queue_base().saturating_add(T::WeightInfo::ready_ring_unknit()), + ) { + return (false, None) } let mut book_state = BookStateFor::::get(&origin); let mut total_processed = 0; + while book_state.end > book_state.begin { let (processed, status) = Self::service_page(&origin, &mut book_state, weight, overweight_limit); @@ -744,6 +742,7 @@ impl Pallet { debug_assert!(book_state.count > 0, "completing a page implies there are pages"); book_state.count.saturating_dec(); } else { + // TODO we only benchmark the `true` case; assuming that it is more expensive. Pages::::insert(&origin, page_index, page); } (total_processed, status) @@ -768,6 +767,7 @@ impl Pallet { Some(m) => m, None => return PageExecutionStatus::NoMore, }[..]; + log::debug!(target: LOG_TARGET, "\n{}", Self::debug_info()); use MessageExecutionStatus::*; let is_processed = match Self::process_message(message, weight, overweight_limit) { @@ -779,12 +779,14 @@ impl Pallet { PageExecutionStatus::Partial } - /// Print which messages are in each queue. + /// Print the pages in each queue, the message in each page. + /// + /// Processed messages are prefixed with a `*` and the current `begin`ning page by `>`. /// /// # Example output /// /// ```text - /// queue Here: + /// queue Here: /// page 0: [] /// > page 1: [] /// page 2: ["\0weight=4", "\0c", ] @@ -796,21 +798,32 @@ impl Pallet { fn debug_info() -> String { let mut info = String::new(); for (origin, book_state) in BookStateFor::::iter() { - let mut queue = format!("queue {:?}:\n", &origin); + let mut queue = format!("queue {:?}:\n", &origin); let mut pages = Pages::::iter_prefix(&origin).collect::>(); - pages.sort_by(|(a,_ ), (b, _)| a.cmp(b)); + pages.sort_by(|(a, _), (b, _)| a.cmp(b)); for (page_index, page) in pages.into_iter() { let mut page = page; - let mut page_info = format!("page {}: [", page_index); + let mut page_info = format!("page {} ({:?} msgs): [", page_index, page.remaining); if book_state.begin == page_index { page_info = format!("> {}", page_info); } else { page_info = format!(" {}", page_info); } - while let Some(message) = page.peek_first() { - let msg = String::from_utf8_lossy(message.deref()); - page_info.push_str(&format!("{:?}, ", msg)); - page.skip_first(true); + for i in 0..u32::MAX { + if let Some((_, processed, message)) = + page.peek_index(i.try_into().expect("std-only code")) + { + let msg = String::from_utf8_lossy(message.deref()); + if processed { + page_info.push_str("*"); + } else { + page_info.push_str(" "); + } + page_info.push_str(&format!("{:?}, ", msg)); + page.skip_first(true); + } else { + break + } } page_info.push_str("]\n"); queue.push_str(&page_info); @@ -907,12 +920,6 @@ impl ServiceQueues for Pallet { // The maximum weight that processing a single message may take. let overweight_limit = weight_limit; let mut weight = WeightCounter::from_limit(weight_limit); - log::debug!( - target: LOG_TARGET, - "ServiceQueues::service_queues with weight {} and overweight_limit {}", - weight_limit, - overweight_limit - ); let mut next = match Self::bump_service_head(&mut weight) { Some(h) => h, diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 168c80d7eedd7..d0473ccd70a05 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -46,19 +46,36 @@ impl IntoWeight for u64 { } pub fn msg>(x: &'static str) -> BoundedSlice { - BoundedSlice::truncate_from(x.as_bytes()) + BoundedSlice::defensive_truncate_from(x.as_bytes()) } pub fn vmsg(x: &'static str) -> Vec { x.as_bytes().to_vec() } -/// Returns a full page and its number of empty messages. +pub fn page(msg: &[u8], origin: MessageOrigin) -> PageOf { + PageOf::::from_message::( + msg.try_into().unwrap(), + BoundedSlice::try_from(&origin.encode()[..]).unwrap(), + ) +} + +pub fn single_page_book() -> BookStateOf { + BookStateOf:: { begin: 0, end: 1, count: 1, ready_neighbours: None } +} + +/// Returns a page filled with empty messages and the number of messages. pub fn full_page() -> (PageOf, usize) { let mut msgs = 0; let mut page = PageOf::::default(); for i in 0..u32::MAX { - if page.try_append_message(&[], &MessageOrigin::Everywhere(i).encode()).is_err() { + if page + .try_append_message::( + [][..].try_into().unwrap(), + MessageOrigin::Everywhere(i).encode()[..].try_into().unwrap(), + ) + .is_err() + { break } else { msgs += 1; @@ -67,3 +84,11 @@ pub fn full_page() -> (PageOf, usize) { assert!(msgs > 0, "page must hold at least one message"); (page, msgs) } + +pub fn assert_last_event(generic_event: ::RuntimeEvent) { + assert!( + !frame_system::Pallet::::block_number().is_zero(), + "The genesis block has n o events" + ); + frame_system::Pallet::::assert_last_event(generic_event.into()); +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 925b33da63e64..0ad857d75de10 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -23,7 +23,7 @@ use crate::{mock::*, *}; use crate as pallet_message_queue; use frame_support::{ - assert_noop, assert_ok, parameter_types, + assert_noop, assert_ok, assert_storage_noop, parameter_types, traits::{ConstU32, ConstU64}, }; use sp_core::H256; @@ -79,7 +79,7 @@ impl frame_system::Config for Test { } parameter_types! { - pub const HeapSize: u32 = 24; // 64 KiB + pub const HeapSize: u32 = 24; pub const MaxStale: u32 = 2; } @@ -104,14 +104,6 @@ parameter_types! { impl MockedWeightInfo { /// Set the weight of a specific weight function. pub fn set_weight(call_name: &str, weight: Weight) { - assert!( - super::weights::WeightMetaInfo::::visit_weight_functions( - |f, _| f == call_name - ) - .into_iter() - .any(|i| i), - "Weigh function name invalid: {call_name}" - ); let mut calls = WeightForCall::get(); calls.insert(call_name.into(), weight); WeightForCall::set(calls); @@ -137,6 +129,9 @@ impl crate::weights::WeightInfo for MockedWeightInfo { fn service_page_item() -> Weight { WeightForCall::get().get("service_page_item").copied().unwrap_or_default() } + fn ready_ring_unknit() -> Weight { + WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() + } } parameter_types! { @@ -211,14 +206,6 @@ fn mocked_weight_works() { }); } -#[test] -#[should_panic] -fn mocked_weight_panics_on_invalid_name() { - new_test_ext().execute_with(|| { - set_weight("invalid_name", Weight::MAX); - }); -} - #[test] fn enqueue_within_one_page_works() { new_test_ext().execute_with(|| { @@ -337,6 +324,29 @@ fn queue_priority_reset_once_serviced() { }); } +#[test] +fn reap_page_permanent_overweight_works() { + assert!(MaxStale::get() >= 2, "pre-condition unmet"); + new_test_ext().execute_with(|| { + use MessageOrigin::*; + // Create pages with messages with a weight of two. + // TODO why do we need `+ 2` here? + for _ in 0..(MaxStale::get() + 2) { + MessageQueue::enqueue_message(msg(&"weight=2"), Here); + } + + // … but only allow the processing to take at most weight 1. + MessageQueue::service_queues(1.into_weight()); + + // We can now reap the first one since they are permanently overweight and over the MaxStale + // limit. + assert_ok!(MessageQueue::do_reap_page(&Here, 0)); + // Cannot reap again. + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NotReapable); + }); +} + #[test] fn reaping_overweight_fails_properly() { new_test_ext().execute_with(|| { @@ -395,6 +405,53 @@ fn reaping_overweight_fails_properly() { }); } +#[test] +fn service_queue_bails() { + // Not enough weight for `service_queue_base`. + new_test_ext().execute_with(|| { + set_weight("service_queue_base", 2.into_weight()); + let mut meter = WeightCounter::from_limit(1.into_weight()); + + assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `ready_ring_unknit`. + new_test_ext().execute_with(|| { + set_weight("ready_ring_unknit", 2.into_weight()); + let mut meter = WeightCounter::from_limit(1.into_weight()); + + assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `service_queue_base` and `ready_ring_unknit`. + new_test_ext().execute_with(|| { + set_weight("service_queue_base", 2.into_weight()); + set_weight("ready_ring_unknit", 2.into_weight()); + + let mut meter = WeightCounter::from_limit(3.into_weight()); + assert_storage_noop!(MessageQueue::service_queue(0.into(), &mut meter, Weight::MAX)); + assert!(meter.consumed.is_zero()); + }); +} + +#[test] +fn service_page_bails() { + // Not enough weight for `service_page_base`. + new_test_ext().execute_with(|| { + set_weight("service_page_base", 2.into_weight()); + let mut meter = WeightCounter::from_limit(1.into_weight()); + let mut book = single_page_book::(); + + assert_storage_noop!(MessageQueue::service_page( + &0.into(), + &mut book, + &mut meter, + Weight::MAX + )); + assert!(meter.consumed.is_zero()); + }); +} + #[test] fn service_page_item_bails() { new_test_ext().execute_with(|| { @@ -418,7 +475,7 @@ fn service_page_item_bails() { #[test] fn service_page_consumes_correct_weight() { new_test_ext().execute_with(|| { - let mut page = PageOf::::from_message(b"weight=3", &MessageOrigin::Here.encode()); + let mut page = page::(b"weight=3", MessageOrigin::Here); let mut weight = WeightCounter::from_limit(10.into_weight()); let overweight_limit = 0.into_weight(); set_weight("service_page_item", 2.into_weight()); @@ -436,11 +493,11 @@ fn service_page_consumes_correct_weight() { }); } -/// Skips a permanent `Overweight` message and marks it as "unprocessed". +/// `service_page_item` skips a permanently `Overweight` message and marks it as `unprocessed`. #[test] fn service_page_skips_perm_overweight_message() { new_test_ext().execute_with(|| { - let mut page = PageOf::::from_message(b"weight=6", &MessageOrigin::Here.encode()); + let mut page = page::(b"weight=6", MessageOrigin::Here); let mut weight = WeightCounter::from_limit(7.into_weight()); let overweight_limit = 5.into_weight(); set_weight("service_page_item", 2.into_weight()); @@ -478,3 +535,28 @@ fn peek_index_works() { } }); } + +#[test] +fn page_from_message_basic_works() { + assert!(MaxOriginLenOf::::get() >= 3, "pre-condition unmet"); + assert!(MaxMessageLenOf::::get() >= 3, "pre-condition unmet"); + + let page = PageOf::::from_message::( + BoundedSlice::defensive_truncate_from(b"MSG"), + BoundedSlice::defensive_truncate_from(b"ORI"), + ); +} + +// `Page::from_message` does not panic when called with the maximum message and origin lengths. +#[test] +fn page_from_message_max_len_works() { + let max_msg_len: usize = MaxMessageLenOf::::get() as usize; + let max_origin_len: usize = MaxOriginLenOf::::get() as usize; + + let page = PageOf::::from_message::( + vec![1; max_msg_len][..].try_into().unwrap(), + vec![2; max_origin_len][..].try_into().unwrap(), + ); + + assert_eq!(page.remaining, 1); +} From 44286af45e0a9eebea92d7647c178ba20cacca2f Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 7 Nov 2022 15:55:07 +0000 Subject: [PATCH 038/110] Remove redundant per-message origins; add footprint() and sweep_queue() --- frame/message-queue/src/lib.rs | 174 +++++++++++++---------- frame/message-queue/src/mock.rs | 205 +++++++++++++++++++++++++-- frame/message-queue/src/tests.rs | 188 ++---------------------- frame/support/src/traits.rs | 2 +- frame/support/src/traits/messages.rs | 106 ++++++++++++-- 5 files changed, 400 insertions(+), 275 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 5af4b78287b47..6e6ff6b1ae45f 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -24,12 +24,13 @@ mod mock; mod tests; pub mod weights; -use codec::{Codec, Decode, Encode, FullCodec, MaxEncodedLen}; +use codec::{Codec, Decode, Encode, MaxEncodedLen}; use frame_support::{ defensive, pallet_prelude::*, traits::{ DefensiveTruncateFrom, EnqueueMessage, ProcessMessage, ProcessMessageError, ServiceQueues, + Footprint, }, BoundedSlice, CloneNoBound, DefaultNoBound, }; @@ -72,6 +73,8 @@ pub struct Page + Debug + Clone + Default, HeapSize: Get> /// Messages remaining to be processed; this includes overweight messages which have been /// skipped. remaining: Size, + /// The size of all remaining messages to be processed. + remaining_size: Size, /// The heap-offset of the header of the first message item in this page which is ready for /// processing. first: Size, @@ -86,23 +89,20 @@ impl< HeapSize: Get, > Page { - /// Create a [`Page`] from one unprocessed message and its origin. - fn from_message( - message: BoundedSlice>, - origin: BoundedSlice>, - ) -> Self { - let payload_len = origin.len().saturating_add(message.len()); + /// Create a [`Page`] from one unprocessed message. + fn from_message(message: BoundedSlice>) -> Self { + let payload_len = message.len(); let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); - let header = - ItemHeader:: { payload_len: payload_len.saturated_into(), is_processed: false }; + let payload_len = payload_len.saturated_into(); + let header = ItemHeader:: { payload_len, is_processed: false }; let mut heap = Vec::with_capacity(data_len); header.using_encoded(|h| heap.extend_from_slice(h)); - heap.extend_from_slice(origin.deref()); heap.extend_from_slice(message.deref()); Page { remaining: One::one(), + remaining_size: payload_len, first: Zero::zero(), last: Zero::zero(), heap: BoundedVec::defensive_truncate_from(heap), @@ -113,14 +113,12 @@ impl< fn try_append_message( &mut self, message: BoundedSlice>, - origin: BoundedSlice>, ) -> Result<(), ()> { let pos = self.heap.len(); - let payload_len = origin.len().saturating_add(message.len()); + let payload_len = message.len(); let data_len = ItemHeader::::max_encoded_len().saturating_add(payload_len); - - let header = - ItemHeader:: { payload_len: payload_len.saturated_into(), is_processed: false }; + let payload_len = payload_len.saturated_into(); + let header = ItemHeader:: { payload_len, is_processed: false }; let heap_size: u32 = HeapSize::get().into(); if (heap_size as usize).saturating_sub(self.heap.len()) < data_len { // Can't fit. @@ -129,11 +127,11 @@ impl< let mut heap = sp_std::mem::take(&mut self.heap).into_inner(); header.using_encoded(|h| heap.extend_from_slice(h)); - heap.extend_from_slice(origin.deref()); heap.extend_from_slice(message.deref()); self.heap = BoundedVec::defensive_truncate_from(heap); self.last = pos.saturated_into(); self.remaining.saturating_inc(); + self.remaining_size.saturating_accrue(payload_len); Ok(()) } @@ -166,6 +164,7 @@ impl< h.is_processed = true; h.using_encoded(|d| self.heap[f..f + d.len()].copy_from_slice(d)); self.remaining.saturating_dec(); + self.remaining_size.saturating_reduce(h.payload_len); } self.first .saturating_accrue(ItemHeader::::max_encoded_len().saturated_into()); @@ -204,6 +203,7 @@ impl< h.is_processed = true; h.using_encoded(|d| self.heap[pos..pos + d.len()].copy_from_slice(d)); self.remaining.saturating_dec(); + self.remaining_size.saturating_reduce(h.payload_len); } } } @@ -231,11 +231,15 @@ pub struct BookState { /// If this book has any ready pages, then this will be `Some` with the previous and next /// neighbours. This wraps around. ready_neighbours: Option>, + /// The number of unprocessed messages stored at present. + message_count: u32, + /// The total size of all unprocessed messages stored at present. + size: u32, } impl Default for BookState { fn default() -> Self { - Self { begin: 0, end: 0, count: 0, ready_neighbours: None } + Self { begin: 0, end: 0, count: 0, ready_neighbours: None, message_count: 0, size: 0 } } } @@ -449,8 +453,6 @@ enum PageExecutionStatus { /// The status of an attempt to process a message. #[derive(PartialEq)] enum MessageExecutionStatus { - /// The message could not be executed due to a format error. - BadFormat, /// There is not enough weight remaining at present. InsufficientWeight, /// There will never be enough weight. @@ -537,9 +539,11 @@ impl Pallet { fn do_enqueue_message( origin: &MessageOriginOf, message: BoundedSlice>, - origin_data: BoundedSlice>, + _origin_data: BoundedSlice>, ) { let mut book_state = BookStateFor::::get(origin); + book_state.message_count.saturating_inc(); + book_state.size.saturating_accrue((message.len() + origin.encode().len()) as u32); if book_state.end > book_state.begin { debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); // Already have a page in progress - attempt to append. @@ -551,8 +555,9 @@ impl Pallet { return }, }; - if let Ok(_) = page.try_append_message::(message, origin_data) { + if let Ok(_) = page.try_append_message::(message) { Pages::::insert(origin, last, &page); + BookStateFor::::insert(origin, book_state); return } } else { @@ -569,11 +574,8 @@ impl Pallet { // No room on the page or no page - link in a new page. book_state.end.saturating_inc(); book_state.count.saturating_inc(); - Pages::::insert( - origin, - book_state.end - 1, - Page::from_message::(message, origin_data), - ); + let page = Page::from_message::(message); + Pages::::insert(origin, book_state.end - 1, &page); BookStateFor::::insert(origin, book_state); } @@ -585,8 +587,9 @@ impl Pallet { ) -> DispatchResult { let mut book_state = BookStateFor::::get(&origin); let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; - let (pos, is_processed, blob) = + let (pos, is_processed, payload) = page.peek_index(index.into() as usize).ok_or(Error::::NoMessage)?; + let payload_len = payload.len() as u32; ensure!( page_index < book_state.begin || (page_index == book_state.begin && pos < page.first.into() as usize), @@ -595,16 +598,19 @@ impl Pallet { ensure!(is_processed, Error::::AlreadyProcessed); use MessageExecutionStatus::*; let mut weight_counter = WeightCounter::from_limit(weight_limit); - match Self::process_message(blob, &mut weight_counter, weight_limit) { + match Self::process_message_payload(origin.clone(), payload, &mut weight_counter, weight_limit) { Overweight | InsufficientWeight => Err(Error::::InsufficientWeight.into()), - BadFormat | Unprocessable | Processed => { + Unprocessable | Processed => { page.note_processed_at_pos(pos); + book_state.message_count.saturating_dec(); + book_state.size.saturating_reduce(payload_len); if page.remaining.is_zero() { + debug_assert!(page.remaining_size.is_zero(), "no messages remaining; no space taken; qed"); Pages::::remove(&origin, page_index); debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); book_state.count.saturating_dec(); - // no need to consider .first or ready ring since processing an overweight page - // would not alter that state. + // no need to consider .first or ready ring since processing an overweight page + // would not alter that state. } else { Pages::::insert(&origin, page_index, page); } @@ -644,6 +650,8 @@ impl Pallet { Pages::::remove(origin, page_index); debug_assert!(book_state.count > 0, "reaping a page implies there are pages"); book_state.count.saturating_dec(); + book_state.message_count.saturating_reduce(page.remaining.into()); + book_state.size.saturating_reduce(page.remaining_size.into()); BookStateFor::::insert(origin, book_state); Self::deposit_event(Event::PageReaped { origin: origin.clone(), index: page_index }); @@ -701,7 +709,7 @@ impl Pallet { /// Returns whether the execution bailed. fn service_page( origin: &MessageOriginOf, - book_state: &mut BookState>, + book_state: &mut BookStateOf, weight: &mut WeightCounter, overweight_limit: Weight, ) -> (u32, PageExecutionStatus) { @@ -723,7 +731,7 @@ impl Pallet { // Execute as many messages as possible. let status = loop { - match Self::service_page_item(origin, &mut page, weight, overweight_limit) { + match Self::service_page_item(origin, book_state, &mut page, weight, overweight_limit) { s @ Bailed | s @ NoMore => break s, // Keep going as long as we make progress... Partial => { @@ -750,7 +758,8 @@ impl Pallet { /// Execute the next message of a page. pub(crate) fn service_page_item( - _origin: &MessageOriginOf, + origin: &MessageOriginOf, + book_state: &mut BookStateOf, page: &mut PageOf, weight: &mut WeightCounter, overweight_limit: Weight, @@ -763,18 +772,22 @@ impl Pallet { if !weight.check_accrue(T::WeightInfo::service_page_item()) { return PageExecutionStatus::Bailed } - let message = &match page.peek_first() { + let payload = &match page.peek_first() { Some(m) => m, None => return PageExecutionStatus::NoMore, }[..]; log::debug!(target: LOG_TARGET, "\n{}", Self::debug_info()); use MessageExecutionStatus::*; - let is_processed = match Self::process_message(message, weight, overweight_limit) { + let is_processed = match Self::process_message_payload(origin.clone(), payload.deref(), weight, overweight_limit) { InsufficientWeight => return PageExecutionStatus::Bailed, - Processed | Unprocessable | BadFormat => true, + Processed | Unprocessable => true, Overweight => false, }; + if is_processed { + book_state.message_count.saturating_dec(); + book_state.size.saturating_reduce(payload.len() as u32); + } page.skip_first(is_processed); PageExecutionStatus::Partial } @@ -833,48 +846,40 @@ impl Pallet { info } - fn process_message( - mut message: &[u8], + fn process_message_payload( + origin: MessageOriginOf, + message: &[u8], weight: &mut WeightCounter, overweight_limit: Weight, ) -> MessageExecutionStatus { - match MessageOriginOf::::decode(&mut message) { - Ok(origin) => { - let hash = T::Hashing::hash(message); - use ProcessMessageError::Overweight; - match T::MessageProcessor::process_message( - message, - origin.clone(), - weight.remaining(), - ) { - Err(Overweight(w)) if w.any_gt(overweight_limit) => { - // Permanently overweight. - Self::deposit_event(Event::::Overweight { hash, origin, index: 0 }); // TODO page + index - MessageExecutionStatus::Overweight - }, - Err(Overweight(_)) => { - // Temporarily overweight - save progress and stop processing this - // queue. - MessageExecutionStatus::InsufficientWeight - }, - Err(error) => { - // Permanent error - drop - Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); - MessageExecutionStatus::Unprocessable - }, - Ok((success, weight_used)) => { - // Success - weight.defensive_saturating_accrue(weight_used); - let event = Event::::Processed { hash, origin, weight_used, success }; - Self::deposit_event(event); - MessageExecutionStatus::Processed - }, - } + let hash = T::Hashing::hash(message); + use ProcessMessageError::Overweight; + match T::MessageProcessor::process_message( + message, + origin.clone(), + weight.remaining(), + ) { + Err(Overweight(w)) if w.any_gt(overweight_limit) => { + // Permanently overweight. + Self::deposit_event(Event::::Overweight { hash, origin, index: 0 }); // TODO page + index + MessageExecutionStatus::Overweight + }, + Err(Overweight(_)) => { + // Temporarily overweight - save progress and stop processing this + // queue. + MessageExecutionStatus::InsufficientWeight + }, + Err(error) => { + // Permanent error - drop + Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); + MessageExecutionStatus::Unprocessable }, - Err(_) => { - let hash = T::Hashing::hash(message); - Self::deposit_event(Event::::Discarded { hash }); - MessageExecutionStatus::BadFormat + Ok((success, weight_used)) => { + // Success + weight.defensive_saturating_accrue(weight_used); + let event = Event::::Processed { hash, origin, weight_used, success }; + Self::deposit_event(event); + MessageExecutionStatus::Processed }, } } @@ -975,4 +980,23 @@ impl EnqueueMessage> for Pallet { } }) } + + // TODO: test. + fn sweep_queue(origin: MessageOriginOf) { + let mut book_state = BookStateFor::::get(&origin); + book_state.begin = book_state.end; + if let Some(neighbours) = book_state.ready_neighbours.take() { + Self::ready_ring_unknit(&origin, neighbours); + } + BookStateFor::::insert(&origin, &book_state); + } + + // TODO: test. + fn footprint(origin: MessageOriginOf) -> Footprint { + let book_state = BookStateFor::::get(&origin); + Footprint { + count: book_state.message_count, + size: book_state.size, + } + } } diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index d0473ccd70a05..9d880faa59f97 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -18,8 +18,173 @@ use super::*; -use frame_support::{assert_noop, assert_ok, parameter_types}; +use frame_support::{parameter_types}; +use frame_support::traits::{ConstU32, ConstU64}; use sp_std::collections::btree_map::BTreeMap; +use crate as pallet_message_queue; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub const HeapSize: u32 = 24; + pub const MaxStale: u32 = 2; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = MockedWeightInfo; + type MessageProcessor = TestMessageProcessor; + type Size = u32; + type HeapSize = HeapSize; + type MaxStale = MaxStale; +} + +/// Mocked `WeightInfo` impl with allows to set the weight per call. +pub struct MockedWeightInfo; + +parameter_types! { + /// Storage for `MockedWeightInfo`, do not use directly. + pub static WeightForCall: BTreeMap = Default::default(); +} + +/// Set the return value for a function from the `WeightInfo` trait. +impl MockedWeightInfo { + /// Set the weight of a specific weight function. + pub fn set_weight(call_name: &str, weight: Weight) { + let mut calls = WeightForCall::get(); + calls.insert(call_name.into(), weight); + WeightForCall::set(calls); + } +} + +impl crate::weights::WeightInfo for MockedWeightInfo { + fn service_page_base() -> Weight { + WeightForCall::get().get("service_page_base").copied().unwrap_or_default() + } + fn service_queue_base() -> Weight { + WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() + } + fn service_page_process_message() -> Weight { + WeightForCall::get() + .get("service_page_process_message") + .copied() + .unwrap_or_default() + } + fn bump_service_head() -> Weight { + WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() + } + fn service_page_item() -> Weight { + WeightForCall::get().get("service_page_item").copied().unwrap_or_default() + } + fn ready_ring_unknit() -> Weight { + WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() + } +} + +parameter_types! { + pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; +} + +pub struct TestMessageProcessor; +impl ProcessMessage for TestMessageProcessor { + /// The transport from where a message originates. + type Origin = MessageOrigin; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + /// + /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. + /// Errors if given the `weight_limit` is insufficient to process the message. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + let weight = if message.starts_with(&b"weight="[..]) { + let mut w: u64 = 0; + for &c in &message[7..] { + if c >= b'0' && c <= b'9' { + w = w * 10 + (c - b'0') as u64; + } else { + break + } + } + w + } else { + 1 + }; + let weight = Weight::from_parts(weight, weight); + if weight.all_lte(weight_limit) { + let mut m = MessagesProcessed::get(); + m.push((message.to_vec(), origin)); + MessagesProcessed::set(m); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + WeightForCall::set(Default::default()); + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +/// Set the weight of a specific weight function. +pub fn set_weight(name: &str, w: Weight) { + MockedWeightInfo::set_weight::(name, w); +} #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] pub enum MessageOrigin { @@ -53,15 +218,19 @@ pub fn vmsg(x: &'static str) -> Vec { x.as_bytes().to_vec() } -pub fn page(msg: &[u8], origin: MessageOrigin) -> PageOf { - PageOf::::from_message::( - msg.try_into().unwrap(), - BoundedSlice::try_from(&origin.encode()[..]).unwrap(), - ) +pub fn page(msg: &[u8]) -> PageOf { + PageOf::::from_message::(msg.try_into().unwrap()) } pub fn single_page_book() -> BookStateOf { - BookStateOf:: { begin: 0, end: 1, count: 1, ready_neighbours: None } + BookState { + begin: 0, + end: 1, + count: 1, + ready_neighbours: None, + message_count: 0, + size: 0, + } } /// Returns a page filled with empty messages and the number of messages. @@ -69,13 +238,8 @@ pub fn full_page() -> (PageOf, usize) { let mut msgs = 0; let mut page = PageOf::::default(); for i in 0..u32::MAX { - if page - .try_append_message::( - [][..].try_into().unwrap(), - MessageOrigin::Everywhere(i).encode()[..].try_into().unwrap(), - ) - .is_err() - { + let r = i.using_encoded(|d| page.try_append_message::(d.try_into().unwrap())); + if r.is_err() { break } else { msgs += 1; @@ -85,6 +249,19 @@ pub fn full_page() -> (PageOf, usize) { (page, msgs) } +/// Returns a page filled with empty messages and the number of messages. +pub fn book_for(page: &PageOf) -> BookStateOf { + BookState { + count: 1, + begin: 0, + end: 1, + ready_neighbours: None, + message_count: page.remaining.into(), + size: page.remaining_size.into() + } +} + +#[allow(dead_code)] pub fn assert_last_event(generic_event: ::RuntimeEvent) { assert!( !frame_system::Pallet::::block_number().is_zero(), diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 0ad857d75de10..b0302e2b58600 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -21,175 +21,9 @@ use crate::{mock::*, *}; -use crate as pallet_message_queue; use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, parameter_types, - traits::{ConstU32, ConstU64}, + assert_noop, assert_ok, assert_storage_noop, }; -use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, -}; -use sp_std::collections::btree_map::BTreeMap; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, - } -); - -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); -} -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; - type Hash = H256; - type RuntimeCall = RuntimeCall; - type Hashing = BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; -} - -parameter_types! { - pub const HeapSize: u32 = 24; - pub const MaxStale: u32 = 2; -} - -impl Config for Test { - type RuntimeEvent = RuntimeEvent; - type WeightInfo = MockedWeightInfo; - type MessageProcessor = TestMessageProcessor; - type Size = u32; - type HeapSize = HeapSize; - type MaxStale = MaxStale; -} - -/// Mocked `WeightInfo` impl with allows to set the weight per call. -pub struct MockedWeightInfo; - -parameter_types! { - /// Storage for `MockedWeightInfo`, do not use directly. - pub static WeightForCall: BTreeMap = Default::default(); -} - -/// Set the return value for a function from the `WeightInfo` trait. -impl MockedWeightInfo { - /// Set the weight of a specific weight function. - pub fn set_weight(call_name: &str, weight: Weight) { - let mut calls = WeightForCall::get(); - calls.insert(call_name.into(), weight); - WeightForCall::set(calls); - } -} - -impl crate::weights::WeightInfo for MockedWeightInfo { - fn service_page_base() -> Weight { - WeightForCall::get().get("service_page_base").copied().unwrap_or_default() - } - fn service_queue_base() -> Weight { - WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() - } - fn service_page_process_message() -> Weight { - WeightForCall::get() - .get("service_page_process_message") - .copied() - .unwrap_or_default() - } - fn bump_service_head() -> Weight { - WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() - } - fn service_page_item() -> Weight { - WeightForCall::get().get("service_page_item").copied().unwrap_or_default() - } - fn ready_ring_unknit() -> Weight { - WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() - } -} - -parameter_types! { - pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; -} - -pub struct TestMessageProcessor; -impl ProcessMessage for TestMessageProcessor { - /// The transport from where a message originates. - type Origin = MessageOrigin; - - /// Process the given message, using no more than `weight_limit` in weight to do so. - /// - /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. - /// Errors if given the `weight_limit` is insufficient to process the message. - fn process_message( - message: &[u8], - origin: Self::Origin, - weight_limit: Weight, - ) -> Result<(bool, Weight), ProcessMessageError> { - let weight = if message.starts_with(&b"weight="[..]) { - let mut w: u64 = 0; - for &c in &message[7..] { - if c >= b'0' && c <= b'9' { - w = w * 10 + (c - b'0') as u64; - } else { - break - } - } - w - } else { - 1 - }; - let weight = Weight::from_parts(weight, weight); - if weight.all_lte(weight_limit) { - let mut m = MessagesProcessed::get(); - m.push((message.to_vec(), origin)); - MessagesProcessed::set(m); - Ok((true, weight)) - } else { - Err(ProcessMessageError::Overweight(weight)) - } - } -} - -pub fn new_test_ext() -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - WeightForCall::set(Default::default()); - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} - -/// Set the weight of a specific weight function. -pub fn set_weight(name: &str, w: Weight) { - MockedWeightInfo::set_weight::(name, w); -} #[test] fn mocked_weight_works() { @@ -463,9 +297,10 @@ fn service_page_item_bails() { assert_eq!( MessageQueue::service_page_item( &MessageOrigin::Here, + &mut book_for::(&page), &mut page, &mut weight, - overweight_limit + overweight_limit, ), PageExecutionStatus::Bailed ); @@ -475,7 +310,7 @@ fn service_page_item_bails() { #[test] fn service_page_consumes_correct_weight() { new_test_ext().execute_with(|| { - let mut page = page::(b"weight=3", MessageOrigin::Here); + let mut page = page::(b"weight=3"); let mut weight = WeightCounter::from_limit(10.into_weight()); let overweight_limit = 0.into_weight(); set_weight("service_page_item", 2.into_weight()); @@ -483,6 +318,7 @@ fn service_page_consumes_correct_weight() { assert_eq!( MessageQueue::service_page_item( &MessageOrigin::Here, + &mut book_for::(&page), &mut page, &mut weight, overweight_limit @@ -497,7 +333,7 @@ fn service_page_consumes_correct_weight() { #[test] fn service_page_skips_perm_overweight_message() { new_test_ext().execute_with(|| { - let mut page = page::(b"weight=6", MessageOrigin::Here); + let mut page = page::(b"weight=6"); let mut weight = WeightCounter::from_limit(7.into_weight()); let overweight_limit = 5.into_weight(); set_weight("service_page_item", 2.into_weight()); @@ -505,6 +341,7 @@ fn service_page_skips_perm_overweight_message() { assert_eq!( MessageQueue::service_page_item( &MessageOrigin::Here, + &mut book_for::(&page), &mut page, &mut weight, overweight_limit @@ -516,7 +353,7 @@ fn service_page_skips_perm_overweight_message() { let (pos, processed, payload) = page.peek_index(0).unwrap(); assert_eq!(pos, 0); assert_eq!(processed, false); - assert_eq!(payload, (MessageOrigin::Here, b"weight=6").encode()); + assert_eq!(payload, b"weight=6".encode()); }); } @@ -529,9 +366,9 @@ fn peek_index_works() { for i in 0..msgs { page.skip_first(i % 2 == 0); let (pos, processed, payload) = page.peek_index(i).unwrap(); - assert_eq!(pos, 10 * i); + assert_eq!(pos, 9 * i); assert_eq!(processed, i % 2 == 0); - assert_eq!(payload, MessageOrigin::Everywhere(i as u32).encode()); + assert_eq!(payload, (i as u32).encode()); } }); } @@ -541,9 +378,8 @@ fn page_from_message_basic_works() { assert!(MaxOriginLenOf::::get() >= 3, "pre-condition unmet"); assert!(MaxMessageLenOf::::get() >= 3, "pre-condition unmet"); - let page = PageOf::::from_message::( + let _page = PageOf::::from_message::( BoundedSlice::defensive_truncate_from(b"MSG"), - BoundedSlice::defensive_truncate_from(b"ORI"), ); } @@ -551,11 +387,9 @@ fn page_from_message_basic_works() { #[test] fn page_from_message_max_len_works() { let max_msg_len: usize = MaxMessageLenOf::::get() as usize; - let max_origin_len: usize = MaxOriginLenOf::::get() as usize; let page = PageOf::::from_message::( vec![1; max_msg_len][..].try_into().unwrap(), - vec![2; max_origin_len][..].try_into().unwrap(), ); assert_eq!(page.remaining, 1); diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 90b1d46db4e10..152711a367c15 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -113,7 +113,7 @@ mod preimages; pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, StorePreimage}; mod messages; -pub use messages::{EnqueueMessage, ProcessMessage, ProcessMessageError, ServiceQueues}; +pub use messages::{EnqueueMessage, ProcessMessage, ProcessMessageError, ServiceQueues, Footprint}; #[cfg(feature = "try-runtime")] mod try_runtime; diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs index 70950e14f67d4..e27150db63fa4 100644 --- a/frame/support/src/traits/messages.rs +++ b/frame/support/src/traits/messages.rs @@ -19,9 +19,9 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_core::Get; -use sp_runtime::{BoundedSlice, RuntimeDebug}; -use sp_std::{fmt::Debug, prelude::*}; +use sp_core::{Get, TypedGet, ConstU32}; +use sp_runtime::{BoundedSlice, RuntimeDebug, traits::Convert}; +use sp_std::{fmt::Debug, prelude::*, marker::PhantomData}; use sp_weights::Weight; #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, RuntimeDebug)] @@ -59,20 +59,110 @@ pub trait ServiceQueues { fn service_queues(weight_limit: Weight) -> Weight; } +#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] +pub struct Footprint { + pub count: u32, + pub size: u32, +} + pub trait EnqueueMessage { type MaxMessageLen: Get; /// Enqueue a single `message` from a specific `origin`. - /// - /// Infallible. fn enqueue_message(message: BoundedSlice, origin: Origin); /// Enqueue multiple `messages` from a specific `origin`. - /// - /// If no `message.len()` is greater than `HEAP_SIZE - Origin::max_encoded_len()`, then this - /// is guaranteed to succeed. In the case of `Err`, no messages are queued. fn enqueue_messages<'a>( messages: impl Iterator>, origin: Origin, ); + + /// Any remaining unprocessed messages should happen only lazily, not proactively. + fn sweep_queue(origin: Origin); + + /// Return the state footprint of the given queue. + fn footprint(origin: Origin) -> Footprint; +} + +impl EnqueueMessage for () { + type MaxMessageLen = ConstU32<0>; + fn enqueue_message(_: BoundedSlice, _: Origin) {} + fn enqueue_messages<'a>( + _: impl Iterator>, + _: Origin, + ) {} + fn sweep_queue(_: Origin) {} + fn footprint(_: Origin) -> Footprint { Footprint::default() } +} + +pub struct TransformOrigin(PhantomData<(E, O, N, C)>); +impl< + E: EnqueueMessage, + O: MaxEncodedLen, + N: MaxEncodedLen, + C: Convert +> EnqueueMessage for TransformOrigin { + type MaxMessageLen = E::MaxMessageLen; + + fn enqueue_message(message: BoundedSlice, origin: N) { + E::enqueue_message(message, C::convert(origin)); + } + + fn enqueue_messages<'a>( + messages: impl Iterator>, + origin: N, + ) { + E::enqueue_messages(messages, C::convert(origin)); + } + + fn sweep_queue(origin: N) { + E::sweep_queue(C::convert(origin)); + } + + fn footprint(origin: N) -> Footprint { + E::footprint(C::convert(origin)) + } +} + +pub trait HandleMessage { + type MaxMessageLen: Get; + + /// Enqueue a single `message` with an implied origin. + fn handle_message(message: BoundedSlice); + + /// Enqueue multiple `messages` from an implied origin. + fn handle_messages<'a>( + messages: impl Iterator>, + ); + + /// Any remaining unprocessed messages should happen only lazily, not proactively. + fn sweep_queue(); + + /// Return the state footprint of the queue. + fn footprint() -> Footprint; } + +pub struct EnqueueWithOrigin(PhantomData<(E, O)>); +impl, O: TypedGet> HandleMessage for EnqueueWithOrigin where + O::Type: MaxEncodedLen +{ + type MaxMessageLen = E::MaxMessageLen; + + fn handle_message(message: BoundedSlice) { + E::enqueue_message(message, O::get()); + } + + fn handle_messages<'a>( + messages: impl Iterator>, + ) { + E::enqueue_messages(messages, O::get()); + } + + fn sweep_queue() { + E::sweep_queue(O::get()); + } + + fn footprint() -> Footprint { + E::footprint(O::get()) + } +} \ No newline at end of file From 3b69fa4f065c941939531af0646d799e65b34230 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 7 Nov 2022 20:43:39 +0100 Subject: [PATCH 039/110] Move testing stuff to mock.rs Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 2 +- frame/message-queue/src/mock.rs | 127 ++++++++++++++++++++++++ frame/message-queue/src/tests.rs | 126 +++-------------------- 3 files changed, 144 insertions(+), 111 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 1b3dba14cfa62..372b19894e79f 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -105,5 +105,5 @@ benchmarks! { } // Create a test for each benchmark. - impl_benchmark_test_suite!(MessageQueue, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::tests::Test); } diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index d0473ccd70a05..9d4b611f4cffd 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -19,6 +19,7 @@ use super::*; use frame_support::{assert_noop, assert_ok, parameter_types}; +use frame_system::Pallet as System; use sp_std::collections::btree_map::BTreeMap; #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] @@ -92,3 +93,129 @@ pub fn assert_last_event(generic_event: ::RuntimeEvent) ); frame_system::Pallet::::assert_last_event(generic_event.into()); } + +/// Mocked `WeightInfo` impl with allows to set the weight per call. +pub struct MockedWeightInfo; + +parameter_types! { + /// Storage for `MockedWeightInfo`, do not use directly. + pub static WeightForCall: BTreeMap = Default::default(); +} + +/// Set the return value for a function from the `WeightInfo` trait. +impl MockedWeightInfo { + /// Set the weight of a specific weight function. + pub fn set_weight(call_name: &str, weight: Weight) { + let mut calls = WeightForCall::get(); + calls.insert(call_name.into(), weight); + WeightForCall::set(calls); + } +} + +impl crate::weights::WeightInfo for MockedWeightInfo { + fn service_page_base() -> Weight { + WeightForCall::get().get("service_page_base").copied().unwrap_or_default() + } + fn service_queue_base() -> Weight { + WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() + } + fn service_page_process_message() -> Weight { + WeightForCall::get() + .get("service_page_process_message") + .copied() + .unwrap_or_default() + } + fn bump_service_head() -> Weight { + WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() + } + fn service_page_item() -> Weight { + WeightForCall::get().get("service_page_item").copied().unwrap_or_default() + } + fn ready_ring_unknit() -> Weight { + WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() + } +} + +parameter_types! { + pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; +} + +pub struct TestMessageProcessor; +impl ProcessMessage for TestMessageProcessor { + /// The transport from where a message originates. + type Origin = MessageOrigin; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + /// + /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. + /// Errors if given the `weight_limit` is insufficient to process the message. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + let weight = if message.starts_with(&b"weight="[..]) { + let mut w: u64 = 0; + for &c in &message[7..] { + if c >= b'0' && c <= b'9' { + w = w * 10 + (c - b'0') as u64; + } else { + break + } + } + w + } else { + 1 + }; + let weight = Weight::from_parts(weight, weight); + if weight.all_lte(weight_limit) { + let mut m = MessagesProcessed::get(); + m.push((message.to_vec(), origin)); + MessagesProcessed::set(m); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +parameter_types! { + pub static NumMessagesProcessed: usize = 0; +} + +pub struct SimpleTestMessageProcessor; +impl ProcessMessage for SimpleTestMessageProcessor { + /// The transport from where a message originates. + type Origin = MessageOrigin; + + /// Process the given message, using no more than `weight_limit` in weight to do so. + /// + /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. + /// Errors if given the `weight_limit` is insufficient to process the message. + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + let weight = Weight::from_parts(1, 1); + + if weight.all_lte(weight_limit) { + NumMessagesProcessed::set(NumMessagesProcessed::get() + 1); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +pub fn new_test_ext() -> sp_io::TestExternalities +where + ::BlockNumber: From, +{ + sp_tracing::try_init_simple(); + WeightForCall::set(Default::default()); + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::::set_block_number(1.into())); + ext +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 0ad857d75de10..93a8036b31c63 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -92,100 +92,6 @@ impl Config for Test { type MaxStale = MaxStale; } -/// Mocked `WeightInfo` impl with allows to set the weight per call. -pub struct MockedWeightInfo; - -parameter_types! { - /// Storage for `MockedWeightInfo`, do not use directly. - pub static WeightForCall: BTreeMap = Default::default(); -} - -/// Set the return value for a function from the `WeightInfo` trait. -impl MockedWeightInfo { - /// Set the weight of a specific weight function. - pub fn set_weight(call_name: &str, weight: Weight) { - let mut calls = WeightForCall::get(); - calls.insert(call_name.into(), weight); - WeightForCall::set(calls); - } -} - -impl crate::weights::WeightInfo for MockedWeightInfo { - fn service_page_base() -> Weight { - WeightForCall::get().get("service_page_base").copied().unwrap_or_default() - } - fn service_queue_base() -> Weight { - WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() - } - fn service_page_process_message() -> Weight { - WeightForCall::get() - .get("service_page_process_message") - .copied() - .unwrap_or_default() - } - fn bump_service_head() -> Weight { - WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() - } - fn service_page_item() -> Weight { - WeightForCall::get().get("service_page_item").copied().unwrap_or_default() - } - fn ready_ring_unknit() -> Weight { - WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() - } -} - -parameter_types! { - pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; -} - -pub struct TestMessageProcessor; -impl ProcessMessage for TestMessageProcessor { - /// The transport from where a message originates. - type Origin = MessageOrigin; - - /// Process the given message, using no more than `weight_limit` in weight to do so. - /// - /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. - /// Errors if given the `weight_limit` is insufficient to process the message. - fn process_message( - message: &[u8], - origin: Self::Origin, - weight_limit: Weight, - ) -> Result<(bool, Weight), ProcessMessageError> { - let weight = if message.starts_with(&b"weight="[..]) { - let mut w: u64 = 0; - for &c in &message[7..] { - if c >= b'0' && c <= b'9' { - w = w * 10 + (c - b'0') as u64; - } else { - break - } - } - w - } else { - 1 - }; - let weight = Weight::from_parts(weight, weight); - if weight.all_lte(weight_limit) { - let mut m = MessagesProcessed::get(); - m.push((message.to_vec(), origin)); - MessagesProcessed::set(m); - Ok((true, weight)) - } else { - Err(ProcessMessageError::Overweight(weight)) - } - } -} - -pub fn new_test_ext() -> sp_io::TestExternalities { - sp_tracing::try_init_simple(); - WeightForCall::set(Default::default()); - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} - /// Set the weight of a specific weight function. pub fn set_weight(name: &str, w: Weight) { MockedWeightInfo::set_weight::(name, w); @@ -193,22 +99,22 @@ pub fn set_weight(name: &str, w: Weight) { #[test] fn mocked_weight_works() { - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { assert!(::WeightInfo::service_page_base().is_zero()); }); - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { set_weight("service_page_base", Weight::MAX); assert_eq!(::WeightInfo::service_page_base(), Weight::MAX); }); // The externalities reset it. - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { assert!(::WeightInfo::service_page_base().is_zero()); }); } #[test] fn enqueue_within_one_page_works() { - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { use MessageOrigin::*; MessageQueue::enqueue_message(msg(&"a"), Here); MessageQueue::enqueue_message(msg(&"b"), Here); @@ -252,7 +158,7 @@ fn enqueue_within_one_page_works() { #[test] fn queue_priority_retains() { - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { use MessageOrigin::*; assert_eq!(ReadyRing::::new().collect::>(), vec![]); MessageQueue::enqueue_message(msg(&"a"), Everywhere(1)); @@ -300,7 +206,7 @@ fn queue_priority_retains() { #[test] fn queue_priority_reset_once_serviced() { - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { use MessageOrigin::*; MessageQueue::enqueue_message(msg(&"a"), Everywhere(1)); MessageQueue::enqueue_message(msg(&"b"), Everywhere(2)); @@ -327,7 +233,7 @@ fn queue_priority_reset_once_serviced() { #[test] fn reap_page_permanent_overweight_works() { assert!(MaxStale::get() >= 2, "pre-condition unmet"); - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { use MessageOrigin::*; // Create pages with messages with a weight of two. // TODO why do we need `+ 2` here? @@ -349,7 +255,7 @@ fn reap_page_permanent_overweight_works() { #[test] fn reaping_overweight_fails_properly() { - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { use MessageOrigin::*; // page 0 MessageQueue::enqueue_message(msg(&"weight=4"), Here); @@ -408,7 +314,7 @@ fn reaping_overweight_fails_properly() { #[test] fn service_queue_bails() { // Not enough weight for `service_queue_base`. - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { set_weight("service_queue_base", 2.into_weight()); let mut meter = WeightCounter::from_limit(1.into_weight()); @@ -416,7 +322,7 @@ fn service_queue_bails() { assert!(meter.consumed.is_zero()); }); // Not enough weight for `ready_ring_unknit`. - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { set_weight("ready_ring_unknit", 2.into_weight()); let mut meter = WeightCounter::from_limit(1.into_weight()); @@ -424,7 +330,7 @@ fn service_queue_bails() { assert!(meter.consumed.is_zero()); }); // Not enough weight for `service_queue_base` and `ready_ring_unknit`. - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { set_weight("service_queue_base", 2.into_weight()); set_weight("ready_ring_unknit", 2.into_weight()); @@ -437,7 +343,7 @@ fn service_queue_bails() { #[test] fn service_page_bails() { // Not enough weight for `service_page_base`. - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { set_weight("service_page_base", 2.into_weight()); let mut meter = WeightCounter::from_limit(1.into_weight()); let mut book = single_page_book::(); @@ -454,7 +360,7 @@ fn service_page_bails() { #[test] fn service_page_item_bails() { - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { let (mut page, _) = full_page::(); let mut weight = WeightCounter::from_limit(10.into_weight()); let overweight_limit = 10.into_weight(); @@ -474,7 +380,7 @@ fn service_page_item_bails() { #[test] fn service_page_consumes_correct_weight() { - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { let mut page = page::(b"weight=3", MessageOrigin::Here); let mut weight = WeightCounter::from_limit(10.into_weight()); let overweight_limit = 0.into_weight(); @@ -496,7 +402,7 @@ fn service_page_consumes_correct_weight() { /// `service_page_item` skips a permanently `Overweight` message and marks it as `unprocessed`. #[test] fn service_page_skips_perm_overweight_message() { - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { let mut page = page::(b"weight=6", MessageOrigin::Here); let mut weight = WeightCounter::from_limit(7.into_weight()); let overweight_limit = 5.into_weight(); @@ -522,7 +428,7 @@ fn service_page_skips_perm_overweight_message() { #[test] fn peek_index_works() { - new_test_ext().execute_with(|| { + new_test_ext::().execute_with(|| { let (mut page, msgs) = full_page::(); assert!(msgs > 1, "precondition unmet"); From 9669c06fb953563fffd9764d1d431e25a3e4a34b Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 7 Nov 2022 20:44:16 +0100 Subject: [PATCH 040/110] Add integration test Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 2 + frame/message-queue/Cargo.toml | 2 + frame/message-queue/src/integration_test.rs | 160 ++++++++++++++++++++ frame/message-queue/src/lib.rs | 1 + 4 files changed, 165 insertions(+) create mode 100644 frame/message-queue/src/integration_test.rs diff --git a/Cargo.lock b/Cargo.lock index 79a91b56874c3..2f38256497329 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5656,6 +5656,8 @@ dependencies = [ "frame-system", "log", "parity-scale-codec", + "rand 0.8.5", + "rand_distr", "scale-info", "serde", "sp-arithmetic", diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml index b77c7aeb31bad..361989ff12596 100644 --- a/frame/message-queue/Cargo.toml +++ b/frame/message-queue/Cargo.toml @@ -23,6 +23,8 @@ frame-system = { version = "4.0.0-dev", default-features = false, path = "../sys [dev-dependencies] sp-tracing = { version = "5.0.0", default-features = false, path = "../../primitives/tracing" } +rand = "0.8.5" +rand_distr = "0.4.3" [features] default = ["std"] diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs new file mode 100644 index 0000000000000..ff7f8aa9782a1 --- /dev/null +++ b/frame/message-queue/src/integration_test.rs @@ -0,0 +1,160 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Stress tests pallet-message-queue. +//! Defines its own runtime config to use larger constants for `HeapSize` and `MaxStale`. + +#![cfg(test)] + +use crate::{mock::*, *}; + +use crate as pallet_message_queue; +use frame_support::{ + assert_noop, assert_ok, assert_storage_noop, parameter_types, + traits::{ConstU32, ConstU64}, +}; +use rand::{prelude::*, Rng, SeedableRng}; +use rand_distr::Pareto; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; +use sp_std::collections::btree_map::BTreeMap; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub const HeapSize: u32 = 64 * 1024; + pub const MaxStale: u32 = 256; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = MockedWeightInfo; + type MessageProcessor = SimpleTestMessageProcessor; + type Size = u32; + type HeapSize = HeapSize; + type MaxStale = MaxStale; +} + +/// Simulates heavy usage by enqueueing and processing large amounts of messages. +#[test] +fn stress_test_enqueue_and_service() { + let blocks = 100; + let max_queues = 10_000; + let max_messages_per_queue = 100_000; + let max_msg_len = MaxMessageLenOf::::get(); + let mut rng = rand::rngs::StdRng::seed_from_u64(0); + + new_test_ext::().execute_with(|| { + for _ in 0..blocks { + let num_queues = rng.gen_range(1..max_queues); + let mut num_messages = 0; + let mut total_msg_len = 0; + + for origin in 0..num_queues { + let num_messages_per_queue = + (rng.sample(Pareto::new(1.0, 1.1).unwrap()) as u32).min(max_messages_per_queue); + + for m in 0..num_messages_per_queue { + let mut message = format!("{}:{}", &origin, &m).into_bytes(); + let msg_len = (rng.sample(Pareto::new(1.0, 1.0).unwrap()) as u32) + .clamp(message.len() as u32, max_msg_len); + message.resize(msg_len as usize, 0); + MessageQueue::enqueue_message( + BoundedSlice::defensive_truncate_from(&message), + origin.into(), + ); + total_msg_len += msg_len; + } + num_messages += num_messages_per_queue; + } + log::info!( + "Enqueued {} messages across {} queues. Total payload {:.2} KiB", + num_messages, + num_queues, + total_msg_len as f64 / 1024.0 + ); + + let mut msgs_remaining = num_messages as u64; + while !msgs_remaining.is_zero() { + // We have to use at least 1 here since otherwise messages will be as permanently + // marked overweight. + let weight = rng.gen_range(1..=msgs_remaining).into_weight(); + + log::info!("Processing {} messages...", weight.ref_time()); + let consumed = MessageQueue::service_queues(weight); + if consumed != weight { + panic!( + "consumed != weight: {} != {}\n{}", + consumed, + weight, + "MessageQueue::debug_info()" + ); + } + let processed = NumMessagesProcessed::take(); + assert_eq!(processed, weight.ref_time() as usize); + System::reset_events(); + msgs_remaining = msgs_remaining.saturating_sub(weight.ref_time()); + } + assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero(), "Nothing left"); + } + }); +} diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 5af4b78287b47..1bd2332b46c5a 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -19,6 +19,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +mod integration_test; mod mock; #[cfg(test)] mod tests; From e8e2726b818970a6ba14ae427058b81c498ccdff Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 7 Nov 2022 20:44:52 +0100 Subject: [PATCH 041/110] Fix no-progress check Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 1bd2332b46c5a..b75db42059748 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -935,10 +935,16 @@ impl ServiceQueues for Pallet { let (progressed, n) = Self::service_queue(next.clone(), &mut weight, overweight_limit); next = match n { Some(n) => - if !progressed && last_no_progress == Some(n.clone()) { - break + if !progressed { + if last_no_progress == Some(n.clone()) { + break + } + if last_no_progress.is_none() { + last_no_progress = Some(next.clone()) + } + n } else { - last_no_progress = Some(next); + last_no_progress = None; n }, None => break, From 6f1af35da7a9d26735d9c8caf3f600b166dad5c4 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 7 Nov 2022 20:45:16 +0100 Subject: [PATCH 042/110] Fix debug_info Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index b75db42059748..2ea391289c86e 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -768,7 +768,6 @@ impl Pallet { Some(m) => m, None => return PageExecutionStatus::NoMore, }[..]; - log::debug!(target: LOG_TARGET, "\n{}", Self::debug_info()); use MessageExecutionStatus::*; let is_processed = match Self::process_message(message, weight, overweight_limit) { @@ -780,14 +779,14 @@ impl Pallet { PageExecutionStatus::Partial } - /// Print the pages in each queue, the message in each page. + /// Print the pages in each queue and the messages in each page. /// - /// Processed messages are prefixed with a `*` and the current `begin`ning page by `>`. + /// Processed messages are prefixed with a `*` and the current `begin`ning page with a `>`. /// /// # Example output /// /// ```text - /// queue Here: + /// queue Here: /// page 0: [] /// > page 1: [] /// page 2: ["\0weight=4", "\0c", ] @@ -804,7 +803,10 @@ impl Pallet { pages.sort_by(|(a, _), (b, _)| a.cmp(b)); for (page_index, page) in pages.into_iter() { let mut page = page; - let mut page_info = format!("page {} ({:?} msgs): [", page_index, page.remaining); + let mut page_info = format!( + "page {} ({:?} first, {:?} last, {:?} remain): [", + page_index, page.first, page.last, page.remaining + ); if book_state.begin == page_index { page_info = format!("> {}", page_info); } else { From b52cd259feb1207be4626b1c422a6f37935bc60a Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 7 Nov 2022 21:01:05 +0100 Subject: [PATCH 043/110] Fixup merge and tests Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 8 +- frame/message-queue/src/integration_test.rs | 16 +- frame/message-queue/src/lib.rs | 42 +++-- frame/message-queue/src/mock.rs | 188 +++++--------------- frame/message-queue/src/tests.rs | 12 +- frame/support/src/traits.rs | 2 +- frame/support/src/traits/messages.rs | 29 +-- 7 files changed, 103 insertions(+), 194 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 372b19894e79f..bb760234c9d7c 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -75,12 +75,12 @@ benchmarks! { let mut weight = WeightCounter::unlimited(); log::debug!(target: LOG_TARGET, "{} messages per page", msgs); }: { - let status = MessageQueue::::service_page_item(&0u32.into(), &mut page, &mut weight, Weight::MAX); + let status = MessageQueue::::service_page_item(&0u32.into(), &mut book_for::(&page), &mut page, &mut weight, Weight::MAX); assert_eq!(status, PageExecutionStatus::Partial); } verify { - // Check fot the `Processed` event + // Check for the `Processed` event. assert_last_event::(Event::Processed { - hash: T::Hashing::hash(&[]), origin: ((msgs - 1) as u32).into(), + hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), weight_used: 1.into_weight(), success: true }.into()); } @@ -105,5 +105,5 @@ benchmarks! { } // Create a test for each benchmark. - impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::tests::Test); + impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::mock::Test); } diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index fbe7b6f590166..59afe96faac23 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -19,7 +19,13 @@ #![cfg(test)] -use crate::{mock::*, *}; +use crate::{ + mock::{ + new_test_ext, IntoWeight, MockedWeightInfo, NumMessagesProcessed, + SimpleTestMessageProcessor, + }, + *, +}; use crate as pallet_message_queue; use frame_support::{ @@ -97,11 +103,11 @@ impl Config for Test { /// Simulates heavy usage by enqueueing and processing large amounts of messages. #[test] fn stress_test_enqueue_and_service() { - let blocks = 100; + let blocks = 10; let max_queues = 10_000; let max_messages_per_queue = 100_000; let max_msg_len = MaxMessageLenOf::::get(); - let mut rng = rand::rngs::StdRng::seed_from_u64(0); + let mut rng = rand::rngs::StdRng::seed_from_u64(42); new_test_ext::().execute_with(|| { for _ in 0..blocks { @@ -144,9 +150,7 @@ fn stress_test_enqueue_and_service() { if consumed != weight { panic!( "consumed != weight: {} != {}\n{}", - consumed, - weight, - "MessageQueue::debug_info()" + consumed, weight, "MessageQueue::debug_info()" ); } let processed = NumMessagesProcessed::take(); diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 5a8bdfdf4af0a..df0c754d9c53c 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -30,8 +30,8 @@ use frame_support::{ defensive, pallet_prelude::*, traits::{ - DefensiveTruncateFrom, EnqueueMessage, ProcessMessage, ProcessMessageError, ServiceQueues, - Footprint, + DefensiveTruncateFrom, EnqueueMessage, Footprint, ProcessMessage, ProcessMessageError, + ServiceQueues, }, BoundedSlice, CloneNoBound, DefaultNoBound, }; @@ -544,7 +544,9 @@ impl Pallet { ) { let mut book_state = BookStateFor::::get(origin); book_state.message_count.saturating_inc(); - book_state.size.saturating_accrue((message.len() + origin.encode().len()) as u32); + book_state + .size + .saturating_accrue((message.len() + origin.encode().len()) as u32); if book_state.end > book_state.begin { debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); // Already have a page in progress - attempt to append. @@ -599,19 +601,27 @@ impl Pallet { ensure!(is_processed, Error::::AlreadyProcessed); use MessageExecutionStatus::*; let mut weight_counter = WeightCounter::from_limit(weight_limit); - match Self::process_message_payload(origin.clone(), payload, &mut weight_counter, weight_limit) { + match Self::process_message_payload( + origin.clone(), + payload, + &mut weight_counter, + weight_limit, + ) { Overweight | InsufficientWeight => Err(Error::::InsufficientWeight.into()), Unprocessable | Processed => { page.note_processed_at_pos(pos); book_state.message_count.saturating_dec(); book_state.size.saturating_reduce(payload_len); if page.remaining.is_zero() { - debug_assert!(page.remaining_size.is_zero(), "no messages remaining; no space taken; qed"); + debug_assert!( + page.remaining_size.is_zero(), + "no messages remaining; no space taken; qed" + ); Pages::::remove(&origin, page_index); debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); book_state.count.saturating_dec(); - // no need to consider .first or ready ring since processing an overweight page - // would not alter that state. + // no need to consider .first or ready ring since processing an overweight page + // would not alter that state. } else { Pages::::insert(&origin, page_index, page); } @@ -779,7 +789,12 @@ impl Pallet { }[..]; use MessageExecutionStatus::*; - let is_processed = match Self::process_message_payload(origin.clone(), payload.deref(), weight, overweight_limit) { + let is_processed = match Self::process_message_payload( + origin.clone(), + payload.deref(), + weight, + overweight_limit, + ) { InsufficientWeight => return PageExecutionStatus::Bailed, Processed | Unprocessable => true, Overweight => false, @@ -857,11 +872,7 @@ impl Pallet { ) -> MessageExecutionStatus { let hash = T::Hashing::hash(message); use ProcessMessageError::Overweight; - match T::MessageProcessor::process_message( - message, - origin.clone(), - weight.remaining(), - ) { + match T::MessageProcessor::process_message(message, origin.clone(), weight.remaining()) { Err(Overweight(w)) if w.any_gt(overweight_limit) => { // Permanently overweight. Self::deposit_event(Event::::Overweight { hash, origin, index: 0 }); // TODO page + index @@ -1003,9 +1014,6 @@ impl EnqueueMessage> for Pallet { // TODO: test. fn footprint(origin: MessageOriginOf) -> Footprint { let book_state = BookStateFor::::get(&origin); - Footprint { - count: book_state.message_count, - size: book_state.size, - } + Footprint { count: book_state.message_count, size: book_state.size } } } diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index b955454bfcb0c..9521c6e82f7fb 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -18,17 +18,17 @@ use super::*; -use frame_support::{assert_noop, assert_ok, parameter_types}; -use frame_system::Pallet as System; -use frame_support::{parameter_types}; -use frame_support::traits::{ConstU32, ConstU64}; -use sp_std::collections::btree_map::BTreeMap; use crate as pallet_message_queue; +use frame_support::{ + assert_noop, assert_ok, parameter_types, + traits::{ConstU32, ConstU64}, +}; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; +use sp_std::collections::btree_map::BTreeMap; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -174,12 +174,47 @@ impl ProcessMessage for TestMessageProcessor { } } -pub fn new_test_ext() -> sp_io::TestExternalities { +parameter_types! { + pub static NumMessagesProcessed: usize = 0; +} + +/// Similar to [`TestMessageProcessor`] but only counts the number of messages processed and does +/// always consume one weight per message. +/// +/// The [`TestMessageProcessor`] is a bit too slow for the integration tests. +pub struct SimpleTestMessageProcessor; +impl ProcessMessage for SimpleTestMessageProcessor { + type Origin = MessageOrigin; + + fn process_message( + message: &[u8], + origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + let weight = Weight::from_parts(1, 1); + + if weight.all_lte(weight_limit) { + NumMessagesProcessed::set(NumMessagesProcessed::get() + 1); + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +/// Create new test externalities. +/// +/// Is generic since it is used by the unit test, integration tests and benchmarks. +pub fn new_test_ext() -> sp_io::TestExternalities +where + ::BlockNumber: From, +{ + #[cfg(test)] // Only log in tests, not in in benchmarks. sp_tracing::try_init_simple(); WeightForCall::set(Default::default()); - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| frame_system::Pallet::::set_block_number(1.into())); ext } @@ -225,14 +260,7 @@ pub fn page(msg: &[u8]) -> PageOf { } pub fn single_page_book() -> BookStateOf { - BookState { - begin: 0, - end: 1, - count: 1, - ready_neighbours: None, - message_count: 0, - size: 0, - } + BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } } /// Returns a page filled with empty messages and the number of messages. @@ -259,7 +287,7 @@ pub fn book_for(page: &PageOf) -> BookStateOf { end: 1, ready_neighbours: None, message_count: page.remaining.into(), - size: page.remaining_size.into() + size: page.remaining_size.into(), } } @@ -271,129 +299,3 @@ pub fn assert_last_event(generic_event: ::RuntimeEvent) ); frame_system::Pallet::::assert_last_event(generic_event.into()); } - -/// Mocked `WeightInfo` impl with allows to set the weight per call. -pub struct MockedWeightInfo; - -parameter_types! { - /// Storage for `MockedWeightInfo`, do not use directly. - pub static WeightForCall: BTreeMap = Default::default(); -} - -/// Set the return value for a function from the `WeightInfo` trait. -impl MockedWeightInfo { - /// Set the weight of a specific weight function. - pub fn set_weight(call_name: &str, weight: Weight) { - let mut calls = WeightForCall::get(); - calls.insert(call_name.into(), weight); - WeightForCall::set(calls); - } -} - -impl crate::weights::WeightInfo for MockedWeightInfo { - fn service_page_base() -> Weight { - WeightForCall::get().get("service_page_base").copied().unwrap_or_default() - } - fn service_queue_base() -> Weight { - WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() - } - fn service_page_process_message() -> Weight { - WeightForCall::get() - .get("service_page_process_message") - .copied() - .unwrap_or_default() - } - fn bump_service_head() -> Weight { - WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() - } - fn service_page_item() -> Weight { - WeightForCall::get().get("service_page_item").copied().unwrap_or_default() - } - fn ready_ring_unknit() -> Weight { - WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() - } -} - -parameter_types! { - pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; -} - -pub struct TestMessageProcessor; -impl ProcessMessage for TestMessageProcessor { - /// The transport from where a message originates. - type Origin = MessageOrigin; - - /// Process the given message, using no more than `weight_limit` in weight to do so. - /// - /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. - /// Errors if given the `weight_limit` is insufficient to process the message. - fn process_message( - message: &[u8], - origin: Self::Origin, - weight_limit: Weight, - ) -> Result<(bool, Weight), ProcessMessageError> { - let weight = if message.starts_with(&b"weight="[..]) { - let mut w: u64 = 0; - for &c in &message[7..] { - if c >= b'0' && c <= b'9' { - w = w * 10 + (c - b'0') as u64; - } else { - break - } - } - w - } else { - 1 - }; - let weight = Weight::from_parts(weight, weight); - if weight.all_lte(weight_limit) { - let mut m = MessagesProcessed::get(); - m.push((message.to_vec(), origin)); - MessagesProcessed::set(m); - Ok((true, weight)) - } else { - Err(ProcessMessageError::Overweight(weight)) - } - } -} - -parameter_types! { - pub static NumMessagesProcessed: usize = 0; -} - -pub struct SimpleTestMessageProcessor; -impl ProcessMessage for SimpleTestMessageProcessor { - /// The transport from where a message originates. - type Origin = MessageOrigin; - - /// Process the given message, using no more than `weight_limit` in weight to do so. - /// - /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. - /// Errors if given the `weight_limit` is insufficient to process the message. - fn process_message( - message: &[u8], - origin: Self::Origin, - weight_limit: Weight, - ) -> Result<(bool, Weight), ProcessMessageError> { - let weight = Weight::from_parts(1, 1); - - if weight.all_lte(weight_limit) { - NumMessagesProcessed::set(NumMessagesProcessed::get() + 1); - Ok((true, weight)) - } else { - Err(ProcessMessageError::Overweight(weight)) - } - } -} - -pub fn new_test_ext() -> sp_io::TestExternalities -where - ::BlockNumber: From, -{ - sp_tracing::try_init_simple(); - WeightForCall::set(Default::default()); - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::::set_block_number(1.into())); - ext -} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 325e7955bf90f..86122b131fcd8 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -21,9 +21,7 @@ use crate::{mock::*, *}; -use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, -}; +use frame_support::{assert_noop, assert_ok, assert_storage_noop}; #[test] fn mocked_weight_works() { @@ -378,9 +376,7 @@ fn page_from_message_basic_works() { assert!(MaxOriginLenOf::::get() >= 3, "pre-condition unmet"); assert!(MaxMessageLenOf::::get() >= 3, "pre-condition unmet"); - let _page = PageOf::::from_message::( - BoundedSlice::defensive_truncate_from(b"MSG"), - ); + let _page = PageOf::::from_message::(BoundedSlice::defensive_truncate_from(b"MSG")); } // `Page::from_message` does not panic when called with the maximum message and origin lengths. @@ -388,9 +384,7 @@ fn page_from_message_basic_works() { fn page_from_message_max_len_works() { let max_msg_len: usize = MaxMessageLenOf::::get() as usize; - let page = PageOf::::from_message::( - vec![1; max_msg_len][..].try_into().unwrap(), - ); + let page = PageOf::::from_message::(vec![1; max_msg_len][..].try_into().unwrap()); assert_eq!(page.remaining, 1); } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 152711a367c15..8869022dfead9 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -113,7 +113,7 @@ mod preimages; pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, StorePreimage}; mod messages; -pub use messages::{EnqueueMessage, ProcessMessage, ProcessMessageError, ServiceQueues, Footprint}; +pub use messages::{EnqueueMessage, Footprint, ProcessMessage, ProcessMessageError, ServiceQueues}; #[cfg(feature = "try-runtime")] mod try_runtime; diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs index e27150db63fa4..b06753ebb0141 100644 --- a/frame/support/src/traits/messages.rs +++ b/frame/support/src/traits/messages.rs @@ -19,9 +19,9 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; -use sp_core::{Get, TypedGet, ConstU32}; -use sp_runtime::{BoundedSlice, RuntimeDebug, traits::Convert}; -use sp_std::{fmt::Debug, prelude::*, marker::PhantomData}; +use sp_core::{ConstU32, Get, TypedGet}; +use sp_runtime::{traits::Convert, BoundedSlice, RuntimeDebug}; +use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use sp_weights::Weight; #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, RuntimeDebug)] @@ -90,18 +90,18 @@ impl EnqueueMessage for () { fn enqueue_messages<'a>( _: impl Iterator>, _: Origin, - ) {} + ) { + } fn sweep_queue(_: Origin) {} - fn footprint(_: Origin) -> Footprint { Footprint::default() } + fn footprint(_: Origin) -> Footprint { + Footprint::default() + } } pub struct TransformOrigin(PhantomData<(E, O, N, C)>); -impl< - E: EnqueueMessage, - O: MaxEncodedLen, - N: MaxEncodedLen, - C: Convert -> EnqueueMessage for TransformOrigin { +impl, O: MaxEncodedLen, N: MaxEncodedLen, C: Convert> EnqueueMessage + for TransformOrigin +{ type MaxMessageLen = E::MaxMessageLen; fn enqueue_message(message: BoundedSlice, origin: N) { @@ -143,8 +143,9 @@ pub trait HandleMessage { } pub struct EnqueueWithOrigin(PhantomData<(E, O)>); -impl, O: TypedGet> HandleMessage for EnqueueWithOrigin where - O::Type: MaxEncodedLen +impl, O: TypedGet> HandleMessage for EnqueueWithOrigin +where + O::Type: MaxEncodedLen, { type MaxMessageLen = E::MaxMessageLen; @@ -165,4 +166,4 @@ impl, O: TypedGet> HandleMessage for EnqueueWithOrigi fn footprint() -> Footprint { E::footprint(O::get()) } -} \ No newline at end of file +} From cf39c9a393f617f0d44f0478ddf18df16182d9e6 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 10 Nov 2022 17:49:13 +0000 Subject: [PATCH 044/110] Fix footprint tracking --- frame/message-queue/src/lib.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index df0c754d9c53c..880146d62da66 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -540,13 +540,13 @@ impl Pallet { fn do_enqueue_message( origin: &MessageOriginOf, message: BoundedSlice>, - _origin_data: BoundedSlice>, ) { let mut book_state = BookStateFor::::get(origin); book_state.message_count.saturating_inc(); book_state .size - .saturating_accrue((message.len() + origin.encode().len()) as u32); + // This should be payload size, but here the payload *is* the message. + .saturating_accrue(message.len() as u32); if book_state.end > book_state.begin { debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); // Already have a page in progress - attempt to append. @@ -970,6 +970,10 @@ impl ServiceQueues for Pallet { } weight.consumed } + // TODO +/* fn execute_overweight(weight_limit: Weight) -> Weight { + + }*/ } impl EnqueueMessage> for Pallet { @@ -980,25 +984,16 @@ impl EnqueueMessage> for Pallet { message: BoundedSlice, origin: ::Origin, ) { - // the `truncate_from` is just for safety - it will never fail since the bound is the - // maximum encoded length of the type. - origin.using_encoded(|data| { - Self::do_enqueue_message(&origin, message, BoundedSlice::truncate_from(data)) - }) + Self::do_enqueue_message(&origin, message) } fn enqueue_messages<'a>( messages: impl Iterator>, origin: ::Origin, ) { - origin.using_encoded(|data| { - // the `truncate_from` is just for safety - it will never fail since the bound is the - // maximum encoded length of the type. - let origin_data = BoundedSlice::truncate_from(data); - for message in messages { - Self::do_enqueue_message(&origin, message, origin_data); - } - }) + for message in messages { + Self::do_enqueue_message(&origin, message); + } } // TODO: test. From 2f463185608fd76d4ae253c5c300c22d45e79ff8 Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 11 Nov 2022 11:13:45 +0000 Subject: [PATCH 045/110] Introduce --- frame/message-queue/src/lib.rs | 24 +++++++++++++++++------- frame/support/src/traits.rs | 2 +- frame/support/src/traits/messages.rs | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 880146d62da66..2b63c978d75ac 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -31,7 +31,7 @@ use frame_support::{ pallet_prelude::*, traits::{ DefensiveTruncateFrom, EnqueueMessage, Footprint, ProcessMessage, ProcessMessageError, - ServiceQueues, + ServiceQueues, ExecuteOverweightError, }, BoundedSlice, CloneNoBound, DefaultNoBound, }; @@ -405,7 +405,8 @@ pub mod pallet { weight_limit: Weight, ) -> DispatchResult { let _ = ensure_signed(origin)?; - Self::do_execute_overweight(message_origin, page, index, weight_limit) + Self::do_execute_overweight(message_origin, page, index, weight_limit)?; + Ok(()) } } } @@ -587,7 +588,7 @@ impl Pallet { page_index: PageIndex, index: T::Size, weight_limit: Weight, - ) -> DispatchResult { + ) -> Result> { let mut book_state = BookStateFor::::get(&origin); let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; let (pos, is_processed, payload) = @@ -625,7 +626,7 @@ impl Pallet { } else { Pages::::insert(&origin, page_index, page); } - Ok(()) + Ok(weight_counter.consumed) }, } } @@ -935,6 +936,8 @@ impl, O: Into> Get for IntoU32 { } impl ServiceQueues for Pallet { + type OverweightMessageAddress = (MessageOriginOf, PageIndex, T::Size); + fn service_queues(weight_limit: Weight) -> Weight { // The maximum weight that processing a single message may take. let overweight_limit = weight_limit; @@ -970,10 +973,17 @@ impl ServiceQueues for Pallet { } weight.consumed } - // TODO -/* fn execute_overweight(weight_limit: Weight) -> Weight { - }*/ + fn execute_overweight( + weight_limit: Weight, + (message_origin, page, index): Self::OverweightMessageAddress, + ) -> Result { + Pallet::::do_execute_overweight(message_origin, page, index, weight_limit) + .map_err(|e| match e { + Error::::InsufficientWeight => ExecuteOverweightError::InsufficientWeight, + _ => ExecuteOverweightError::NotFound, + }) + } } impl EnqueueMessage> for Pallet { diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 8869022dfead9..7265dbe14bdd1 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -113,7 +113,7 @@ mod preimages; pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, StorePreimage}; mod messages; -pub use messages::{EnqueueMessage, Footprint, ProcessMessage, ProcessMessageError, ServiceQueues}; +pub use messages::{EnqueueMessage, Footprint, ProcessMessage, ProcessMessageError, ServiceQueues, ExecuteOverweightError}; #[cfg(feature = "try-runtime")] mod try_runtime; diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs index b06753ebb0141..9db4a5d0581a7 100644 --- a/frame/support/src/traits/messages.rs +++ b/frame/support/src/traits/messages.rs @@ -50,13 +50,27 @@ pub trait ProcessMessage { ) -> Result<(bool, Weight), ProcessMessageError>; } +pub enum ExecuteOverweightError { + NotFound, + InsufficientWeight, +} + pub trait ServiceQueues { + type OverweightMessageAddress; + /// Service all message queues in some fair manner. /// /// - `weight_limit`: The maximum amount of dynamic weight that this call can use. /// /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. fn service_queues(weight_limit: Weight) -> Weight; + + fn execute_overweight( + _weight_limit: Weight, + _address: Self::OverweightMessageAddress, + ) -> Result { + Err(ExecuteOverweightError::NotFound) + } } #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] From 0fabd9c47d48d09f762305be7d3ffd7a5cd7927c Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 11 Nov 2022 11:13:52 +0000 Subject: [PATCH 046/110] Formatting --- frame/message-queue/src/lib.rs | 13 +++++++------ frame/support/src/traits.rs | 5 ++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 2b63c978d75ac..4eb2ab4e68fbe 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -30,8 +30,8 @@ use frame_support::{ defensive, pallet_prelude::*, traits::{ - DefensiveTruncateFrom, EnqueueMessage, Footprint, ProcessMessage, ProcessMessageError, - ServiceQueues, ExecuteOverweightError, + DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, + ProcessMessageError, ServiceQueues, }, BoundedSlice, CloneNoBound, DefaultNoBound, }; @@ -588,7 +588,7 @@ impl Pallet { page_index: PageIndex, index: T::Size, weight_limit: Weight, - ) -> Result> { + ) -> Result> { let mut book_state = BookStateFor::::get(&origin); let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; let (pos, is_processed, payload) = @@ -978,11 +978,12 @@ impl ServiceQueues for Pallet { weight_limit: Weight, (message_origin, page, index): Self::OverweightMessageAddress, ) -> Result { - Pallet::::do_execute_overweight(message_origin, page, index, weight_limit) - .map_err(|e| match e { + Pallet::::do_execute_overweight(message_origin, page, index, weight_limit).map_err(|e| { + match e { Error::::InsufficientWeight => ExecuteOverweightError::InsufficientWeight, _ => ExecuteOverweightError::NotFound, - }) + } + }) } } diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index 7265dbe14bdd1..fbe1501011432 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -113,7 +113,10 @@ mod preimages; pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, StorePreimage}; mod messages; -pub use messages::{EnqueueMessage, Footprint, ProcessMessage, ProcessMessageError, ServiceQueues, ExecuteOverweightError}; +pub use messages::{ + EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, ProcessMessageError, + ServiceQueues, +}; #[cfg(feature = "try-runtime")] mod try_runtime; From 74d5fb3391ea29f5886399a34dcbb217ba65e426 Mon Sep 17 00:00:00 2001 From: Gav Date: Sat, 12 Nov 2022 17:11:10 +0100 Subject: [PATCH 047/110] OverweightEnqueued event, auto-servicing config item --- frame/message-queue/src/lib.rs | 72 ++++++++++++++++++---------- frame/support/src/traits/messages.rs | 1 + 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 4eb2ab4e68fbe..5a8f6af0d6263 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -47,13 +47,9 @@ use sp_std::{fmt::Debug, ops::Deref, prelude::*, vec}; use sp_weights::WeightCounter; pub use weights::WeightInfo; -/// Type for identifying an overweight message. -type OverweightIndex = u64; /// Type for identifying a page. type PageIndex = u32; -const LOG_TARGET: &'static str = "runtime::message-queue"; - /// Data encoded and prefixed to the encoded `MessageItem`. #[derive(Encode, Decode, PartialEq, MaxEncodedLen, Debug)] pub struct ItemHeader { @@ -76,6 +72,8 @@ pub struct Page + Debug + Clone + Default, HeapSize: Get> remaining: Size, /// The size of all remaining messages to be processed. remaining_size: Size, + /// The number of items before the `first` item in this page. + first_index: Size, /// The heap-offset of the header of the first message item in this page which is ready for /// processing. first: Size, @@ -104,6 +102,7 @@ impl< Page { remaining: One::one(), remaining_size: payload_len, + first_index: Zero::zero(), first: Zero::zero(), last: Zero::zero(), heap: BoundedVec::defensive_truncate_from(heap), @@ -170,6 +169,7 @@ impl< self.first .saturating_accrue(ItemHeader::::max_encoded_len().saturated_into()); self.first.saturating_accrue(h.payload_len); + self.first_index.saturating_inc(); } } @@ -285,6 +285,14 @@ pub mod pallet { /// dropped, even if they contain unprocessed overweight messages. #[pallet::constant] type MaxStale: Get; + + /// The amount of weight (if any) which should be provided to the message queue for + /// servicing enqueued items. + /// + /// This may be legitimately `None` in the case that you will call + /// `ServiceQueues::service_queues` manually. + #[pallet::constant] + type ServiceWeight: Get>; } #[pallet::event] @@ -309,10 +317,11 @@ pub mod pallet { success: bool, }, /// Message placed in overweight queue. - Overweight { + OverweightEnqueued { hash: T::Hash, origin: MessageOriginOf, - index: OverweightIndex, + page_index: PageIndex, + message_index: T::Size, }, PageReaped { origin: MessageOriginOf, @@ -358,18 +367,11 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(_n: BlockNumberFor) -> Weight { - let weight_used = Weight::zero(); - weight_used - } - - /// Check all pre-conditions about the [`crate::Config`] types. - fn integrity_test() { - // TODO currently disabled since it fails. - // None of the weight functions can be zero. - // Otherwise we risk getting stuck in a no-progress situation (= infinite loop). - /*weights::WeightMetaInfo::<::WeightInfo>::visit_weight_functions( - |name, w| assert!(!w.is_zero(), "Weight function is zero: {}", name), - );*/ + if let Some(weight_limit) = T::ServiceWeight::get() { + Self::service_queues(weight_limit) + } else { + Weight::zero() + } } } @@ -416,7 +418,7 @@ pub struct ReadyRing { next: Option>, } impl ReadyRing { - fn new() -> Self { + pub fn new() -> Self { Self { first: ServiceHead::::get(), next: ServiceHead::::get() } } } @@ -599,14 +601,18 @@ impl Pallet { (page_index == book_state.begin && pos < page.first.into() as usize), Error::::Queued ); - ensure!(is_processed, Error::::AlreadyProcessed); + ensure!(!is_processed, Error::::AlreadyProcessed); use MessageExecutionStatus::*; let mut weight_counter = WeightCounter::from_limit(weight_limit); match Self::process_message_payload( origin.clone(), + page_index, + index, payload, &mut weight_counter, - weight_limit, + Weight::MAX, + // ^^^ We never recognise it as permanently overweight, since that would result in an + // additional overweight event being deposited. ) { Overweight | InsufficientWeight => Err(Error::::InsufficientWeight.into()), Unprocessable | Processed => { @@ -743,7 +749,14 @@ impl Pallet { // Execute as many messages as possible. let status = loop { - match Self::service_page_item(origin, book_state, &mut page, weight, overweight_limit) { + match Self::service_page_item( + origin, + page_index, + book_state, + &mut page, + weight, + overweight_limit, + ) { s @ Bailed | s @ NoMore => break s, // Keep going as long as we make progress... Partial => { @@ -771,6 +784,7 @@ impl Pallet { /// Execute the next message of a page. pub(crate) fn service_page_item( origin: &MessageOriginOf, + page_index: PageIndex, book_state: &mut BookStateOf, page: &mut PageOf, weight: &mut WeightCounter, @@ -784,6 +798,7 @@ impl Pallet { if !weight.check_accrue(T::WeightInfo::service_page_item()) { return PageExecutionStatus::Bailed } + let payload = &match page.peek_first() { Some(m) => m, None => return PageExecutionStatus::NoMore, @@ -792,6 +807,8 @@ impl Pallet { use MessageExecutionStatus::*; let is_processed = match Self::process_message_payload( origin.clone(), + page_index, + page.first_index, payload.deref(), weight, overweight_limit, @@ -824,7 +841,7 @@ impl Pallet { /// page 5: ["\0bigbig 3", ] /// ``` #[cfg(feature = "std")] - fn debug_info() -> String { + pub fn debug_info() -> String { let mut info = String::new(); for (origin, book_state) in BookStateFor::::iter() { let mut queue = format!("queue {:?}:\n", &origin); @@ -867,6 +884,8 @@ impl Pallet { fn process_message_payload( origin: MessageOriginOf, + page_index: PageIndex, + message_index: T::Size, message: &[u8], weight: &mut WeightCounter, overweight_limit: Weight, @@ -876,7 +895,12 @@ impl Pallet { match T::MessageProcessor::process_message(message, origin.clone(), weight.remaining()) { Err(Overweight(w)) if w.any_gt(overweight_limit) => { // Permanently overweight. - Self::deposit_event(Event::::Overweight { hash, origin, index: 0 }); // TODO page + index + Self::deposit_event(Event::::OverweightEnqueued { + hash, + origin, + page_index, + message_index, + }); MessageExecutionStatus::Overweight }, Err(Overweight(_)) => { diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs index 9db4a5d0581a7..a618b74f780f5 100644 --- a/frame/support/src/traits/messages.rs +++ b/frame/support/src/traits/messages.rs @@ -50,6 +50,7 @@ pub trait ProcessMessage { ) -> Result<(bool, Weight), ProcessMessageError>; } +#[derive(Eq, PartialEq, RuntimeDebug)] pub enum ExecuteOverweightError { NotFound, InsufficientWeight, From 39593644a98ffe004e23a040e31d6cda2ff2b582 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 13 Nov 2022 17:51:22 +0100 Subject: [PATCH 048/110] Update tests and benchmarks Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 20 +-- frame/message-queue/src/benchmarking.rs | 66 +++++++-- frame/message-queue/src/integration_test.rs | 42 ++++-- frame/message-queue/src/lib.rs | 26 +++- frame/message-queue/src/mock.rs | 106 +++----------- frame/message-queue/src/mock_helpers.rs | 120 ++++++++++++++++ frame/message-queue/src/tests.rs | 46 +++++- frame/message-queue/src/weights.rs | 149 ++++++++++++++------ 8 files changed, 400 insertions(+), 175 deletions(-) create mode 100644 frame/message-queue/src/mock_helpers.rs diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 27fb07a34e5f5..4ac0b3a5a0ea4 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1123,30 +1123,18 @@ impl pallet_bounties::Config for Runtime { parameter_types! { pub const HeapSize: u32 = 64 * 1024; // 64 KiB pub const MaxStale: u32 = 128; -} - -/// Processes any message while consuming no weight. -pub struct NoOpMessageProcessor; - -impl pallet_message_queue::ProcessMessage for NoOpMessageProcessor { - type Origin = u32; - - fn process_message( - _message: &[u8], - _origin: Self::Origin, - _weight_limit: Weight, - ) -> Result<(bool, Weight), pallet_message_queue::ProcessMessageError> { - Ok((true, Weight::zero())) - } + pub const ServiceWeight: Option = None; } impl pallet_message_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); - type MessageProcessor = NoOpMessageProcessor; + /// NOTE: Always set this to `NoopMessageProcessor` for benchmarking. + type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor; type Size = u32; type HeapSize = HeapSize; type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; } parameter_types! { diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index bb760234c9d7c..2a85b3786afae 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -18,19 +18,19 @@ #![cfg(feature = "runtime-benchmarks")] -use super::{mock::*, Pallet as MessageQueue, *}; +use super::{mock_helpers::*, Pallet as MessageQueue, *}; use frame_benchmarking::{benchmarks, whitelisted_caller}; use frame_support::traits::Get; use frame_system::{Pallet as System, RawOrigin}; use sp_std::prelude::*; -static LOG_TARGET: &'static str = "runtime::message-queue::bench"; - benchmarks! { where_clause { - // NOTE: We need to generate multiple origins; therefore Origin must be `From`. - where <::MessageProcessor as ProcessMessage>::Origin: From + where + // NOTE: We need to generate multiple origins, therefore Origin is `From`. + <::MessageProcessor as ProcessMessage>::Origin: From, + ::Size: From, } // `service_queue` without any page processing or unknitting. @@ -67,15 +67,14 @@ benchmarks! { // Processing a single message from a page. service_page_item { let (mut page, msgs) = full_page::(); - // Skip all messages except the last one. + // Skip all messages besides the last one. for i in 1..msgs { page.skip_first(true); } assert!(page.peek_first().is_some(), "There is one message left"); let mut weight = WeightCounter::unlimited(); - log::debug!(target: LOG_TARGET, "{} messages per page", msgs); }: { - let status = MessageQueue::::service_page_item(&0u32.into(), &mut book_for::(&page), &mut page, &mut weight, Weight::MAX); + let status = MessageQueue::::service_page_item(&0u32.into(), 0, &mut book_for::(&page), &mut page, &mut weight, Weight::MAX); assert_eq!(status, PageExecutionStatus::Partial); } verify { // Check for the `Processed` event. @@ -92,18 +91,57 @@ benchmarks! { bump_service_head { }: {} reap_page { - assert!(T::MaxStale::get() >= 2, "pre-condition: MaxStale needs to be at least two"); + // Mock the storage to get a cullable page that is not empty. + let origin: MessageOriginOf = 0.into(); + let mut book = single_page_book::(); + let (page, msgs) = full_page::(); - for i in 0..(T::MaxStale::get() + 2) { - MessageQueue::::enqueue_message(msg(&"weight=2"), 0.into()); + for p in 0 .. T::MaxStale::get() * T::MaxStale::get() { + if p == 0 { + Pages::::insert(&origin, p, &page); + } + book.end += 1; + book.count += 1; + book.message_count += msgs as u32; + book.size += page.remaining_size.into(); } - MessageQueue::::service_queues(1.into_weight()); + book.begin = book.end - T::MaxStale::get(); + BookStateFor::::insert(&origin, &book); + System::::reset_events(); }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0) verify { assert_last_event::(Event::PageReaped{ origin: 0.into(), index: 0 }.into()); } - // Create a test for each benchmark. - impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::mock::Test); + execute_overweight { + // Mock the storage + let origin: MessageOriginOf = 0.into(); + let (mut page, msgs) = full_page::(); + page.skip_first(false); // One skipped un-processed overweight message. + let book = book_for::(&page); + Pages::::insert(&origin, 0, &page); + BookStateFor::::insert(&origin, &book); + + }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0u32, 0u32.into(), Weight::MAX) + verify { + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&0u32.encode()), origin: 0.into(), + weight_used: Weight::from_parts(1, 1), success: true + }.into()); + } + + // We need this mainly for the `T::Hashing::hash` effort. + process_message_payload { + let m in 0 .. MaxMessageLenOf::::get(); + let origin: MessageOriginOf = 0.into(); + let msg = vec![1u8; m as usize]; + let mut meter = WeightCounter::unlimited(); + }: { + MessageQueue::::process_message_payload(origin, 0, 0u32.into(), &msg[..], &mut meter, Weight::MAX) + } verify { + assert_eq!(meter.consumed, Weight::from_parts(1, 1)); + } + + impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::integration_test::Test); } diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index 59afe96faac23..692a9aede0003 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -21,8 +21,8 @@ use crate::{ mock::{ - new_test_ext, IntoWeight, MockedWeightInfo, NumMessagesProcessed, - SimpleTestMessageProcessor, + new_test_ext, CountingMessageProcessor, IntoWeight, MockedWeightInfo, + NumMessagesProcessed, }, *, }; @@ -87,27 +87,49 @@ impl frame_system::Config for Test { } parameter_types! { - pub const HeapSize: u32 = 64 * 1024; - pub const MaxStale: u32 = 256; + pub const HeapSize: u32 = 32 * 1024; + pub const MaxStale: u32 = 32; + pub const ServiceWeight: Option = Some(Weight::from_parts(10, 10)); } impl Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = MockedWeightInfo; - type MessageProcessor = SimpleTestMessageProcessor; + type MessageProcessor = CountingMessageProcessor; type Size = u32; type HeapSize = HeapSize; type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; } /// Simulates heavy usage by enqueueing and processing large amounts of messages. +/// +/// Best to run with `--release`. +/// +/// # Example output +/// +/// Each block produces a print in this form: +/// +/// ```pre +/// Enqueued 35298 messages across 5266 queues. Total payload 621.85 KiB +/// Processing 20758 messages... +/// Processing 2968 messages... +/// Processing 7686 messages... +/// Processing 3287 messages... +/// Processing 461 messages... +/// Processing 14 messages... +/// Processing 112 messages... +/// Processing 5 messages... +/// Processing 6 messages... +/// Processing 1 messages... +/// ``` #[test] fn stress_test_enqueue_and_service() { let blocks = 10; let max_queues = 10_000; let max_messages_per_queue = 100_000; let max_msg_len = MaxMessageLenOf::::get(); - let mut rng = rand::rngs::StdRng::seed_from_u64(42); + let mut rng = rand::rngs::StdRng::seed_from_u64(2); new_test_ext::().execute_with(|| { for _ in 0..blocks { @@ -141,8 +163,8 @@ fn stress_test_enqueue_and_service() { let mut msgs_remaining = num_messages as u64; while !msgs_remaining.is_zero() { - // We have to use at least 1 here since otherwise messages will be as permanently - // marked overweight. + // We have to use at least 1 here since otherwise messages will marked as + // permanently overweight. let weight = rng.gen_range(1..=msgs_remaining).into_weight(); log::info!("Processing {} messages...", weight.ref_time()); @@ -150,7 +172,9 @@ fn stress_test_enqueue_and_service() { if consumed != weight { panic!( "consumed != weight: {} != {}\n{}", - consumed, weight, "MessageQueue::debug_info()" + consumed, + weight, + MessageQueue::debug_info() ); } let processed = NumMessagesProcessed::take(); diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 5a8f6af0d6263..46896409af8dc 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -21,7 +21,7 @@ mod benchmarking; mod integration_test; mod mock; -#[cfg(test)] +pub mod mock_helpers; mod tests; pub mod weights; @@ -252,8 +252,8 @@ pub mod pallet { #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); - #[pallet::config] /// The module configuration trait. + #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -262,6 +262,11 @@ pub mod pallet { type WeightInfo: WeightInfo; /// Processor for a message. + /// + /// Must be set to [`mock_helpers::NoopMessageProcessor`] for benchmarking. + /// Other message processors that consumes exactly (1, 1) weight for any give message will + /// work as well. Otherwise the benchmarking will also measure the weight of the message + /// processor, which is not desired. type MessageProcessor: ProcessMessage; /// Page/heap size type. @@ -373,12 +378,19 @@ pub mod pallet { Weight::zero() } } + + /// Check all assumptions about [`crate::Config`]. + fn integrity_test() { + assert!(!MaxMessageLenOf::::get().is_zero(), "HeapSize too low"); + // This value is squared and should not overflow. + assert!(T::MaxStale::get() <= u16::MAX as u32, "MaxStale too large"); + } } #[pallet::call] impl Pallet { /// Remove a page which has no more messages remaining to be processed or is stale. - #[pallet::weight(150_000_000_000)] + #[pallet::weight(T::WeightInfo::reap_page())] pub fn reap_page( origin: OriginFor, message_origin: MessageOriginOf, @@ -398,7 +410,7 @@ pub mod pallet { /// of the message. /// /// Benchmark complexity considerations: O(index + weight_limit). - #[pallet::weight(150_000_000_000)] + #[pallet::weight(T::WeightInfo::execute_overweight())] pub fn execute_overweight( origin: OriginFor, message_origin: MessageOriginOf, @@ -1031,7 +1043,9 @@ impl EnqueueMessage> for Pallet { } } - // TODO: test. + /// Force removes a queue from the ready ring. + /// + /// Does not remove its pages from the storage. fn sweep_queue(origin: MessageOriginOf) { let mut book_state = BookStateFor::::get(&origin); book_state.begin = book_state.end; @@ -1041,7 +1055,7 @@ impl EnqueueMessage> for Pallet { BookStateFor::::insert(&origin, &book_state); } - // TODO: test. + /// Returns the [`Footprint`] of a queue. fn footprint(origin: MessageOriginOf) -> Footprint { let book_state = BookStateFor::::get(&origin); Footprint { count: book_state.message_count, size: book_state.size } diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 9521c6e82f7fb..587f9003dfcb4 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -#![cfg(any(test, feature = "runtime-benchmarks"))] +#![cfg(any(test, feature = "std"))] use super::*; +pub use super::mock_helpers::*; use crate as pallet_message_queue; use frame_support::{ @@ -24,10 +25,8 @@ use frame_support::{ traits::{ConstU32, ConstU64}, }; use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, -}; +use sp_runtime::testing::Header; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use sp_std::collections::btree_map::BTreeMap; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -43,7 +42,6 @@ frame_support::construct_runtime!( MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, } ); - parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); @@ -74,12 +72,11 @@ impl frame_system::Config for Test { type OnSetCode = (); type MaxConsumers = ConstU32<16>; } - parameter_types! { pub const HeapSize: u32 = 24; pub const MaxStale: u32 = 2; + pub const ServiceWeight: Option = Some(Weight::from_parts(10, 10)); } - impl Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = MockedWeightInfo; @@ -87,6 +84,7 @@ impl Config for Test { type Size = u32; type HeapSize = HeapSize; type MaxStale = MaxStale; + type ServiceWeight = ServiceWeight; } /// Mocked `WeightInfo` impl with allows to set the weight per call. @@ -108,6 +106,12 @@ impl MockedWeightInfo { } impl crate::weights::WeightInfo for MockedWeightInfo { + fn reap_page() -> Weight { + WeightForCall::get().get("reap_page").copied().unwrap_or_default() + } + fn execute_overweight() -> Weight { + WeightForCall::get().get("execute_overweight").copied().unwrap_or_default() + } fn service_page_base() -> Weight { WeightForCall::get().get("service_page_base").copied().unwrap_or_default() } @@ -129,6 +133,9 @@ impl crate::weights::WeightInfo for MockedWeightInfo { fn ready_ring_unknit() -> Weight { WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() } + fn process_message_payload(_: u32) -> Weight { + WeightForCall::get().get("process_message_payload").copied().unwrap_or_default() + } } parameter_types! { @@ -182,8 +189,8 @@ parameter_types! { /// always consume one weight per message. /// /// The [`TestMessageProcessor`] is a bit too slow for the integration tests. -pub struct SimpleTestMessageProcessor; -impl ProcessMessage for SimpleTestMessageProcessor { +pub struct CountingMessageProcessor; +impl ProcessMessage for CountingMessageProcessor { type Origin = MessageOrigin; fn process_message( @@ -205,11 +212,11 @@ impl ProcessMessage for SimpleTestMessageProcessor { /// Create new test externalities. /// /// Is generic since it is used by the unit test, integration tests and benchmarks. +#[cfg(test)] pub fn new_test_ext() -> sp_io::TestExternalities where ::BlockNumber: From, { - #[cfg(test)] // Only log in tests, not in in benchmarks. sp_tracing::try_init_simple(); WeightForCall::set(Default::default()); let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); @@ -222,80 +229,3 @@ where pub fn set_weight(name: &str, w: Weight) { MockedWeightInfo::set_weight::(name, w); } - -#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] -pub enum MessageOrigin { - Here, - There, - Everywhere(u32), -} - -impl From for MessageOrigin { - fn from(i: u32) -> Self { - Self::Everywhere(i) - } -} - -/// Converts `Self` into a `Weight` by using `Self` for all components. -pub trait IntoWeight { - fn into_weight(self) -> Weight; -} - -impl IntoWeight for u64 { - fn into_weight(self) -> Weight { - Weight::from_parts(self, self) - } -} - -pub fn msg>(x: &'static str) -> BoundedSlice { - BoundedSlice::defensive_truncate_from(x.as_bytes()) -} - -pub fn vmsg(x: &'static str) -> Vec { - x.as_bytes().to_vec() -} - -pub fn page(msg: &[u8]) -> PageOf { - PageOf::::from_message::(msg.try_into().unwrap()) -} - -pub fn single_page_book() -> BookStateOf { - BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } -} - -/// Returns a page filled with empty messages and the number of messages. -pub fn full_page() -> (PageOf, usize) { - let mut msgs = 0; - let mut page = PageOf::::default(); - for i in 0..u32::MAX { - let r = i.using_encoded(|d| page.try_append_message::(d.try_into().unwrap())); - if r.is_err() { - break - } else { - msgs += 1; - } - } - assert!(msgs > 0, "page must hold at least one message"); - (page, msgs) -} - -/// Returns a page filled with empty messages and the number of messages. -pub fn book_for(page: &PageOf) -> BookStateOf { - BookState { - count: 1, - begin: 0, - end: 1, - ready_neighbours: None, - message_count: page.remaining.into(), - size: page.remaining_size.into(), - } -} - -#[allow(dead_code)] -pub fn assert_last_event(generic_event: ::RuntimeEvent) { - assert!( - !frame_system::Pallet::::block_number().is_zero(), - "The genesis block has n o events" - ); - frame_system::Pallet::::assert_last_event(generic_event.into()); -} diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs new file mode 100644 index 0000000000000..5cb8e0dfd3e27 --- /dev/null +++ b/frame/message-queue/src/mock_helpers.rs @@ -0,0 +1,120 @@ +// Copyright 2022 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! Setup helpers for testing and benchmarking. +//! +//! Cannot be put into mock.rs since benchmarks require no-std and mock.rs is std. + +use crate::*; + +/// Converts `Self` into a `Weight` by using `Self` for all components. +pub trait IntoWeight { + fn into_weight(self) -> Weight; +} + +impl IntoWeight for u64 { + fn into_weight(self) -> Weight { + Weight::from_parts(self, self) + } +} + +/// Mocked message origin. +#[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] +pub enum MessageOrigin { + Here, + There, + Everywhere(u32), +} + +impl From for MessageOrigin { + fn from(i: u32) -> Self { + Self::Everywhere(i) + } +} + +/// Processes any message and consumes (1, 1) weight per message. +pub struct NoopMessageProcessor; +impl ProcessMessage for NoopMessageProcessor { + type Origin = MessageOrigin; + + fn process_message( + _message: &[u8], + _origin: Self::Origin, + weight_limit: Weight, + ) -> Result<(bool, Weight), ProcessMessageError> { + let weight = Weight::from_parts(1, 1); + + if weight.all_lte(weight_limit) { + Ok((true, weight)) + } else { + Err(ProcessMessageError::Overweight(weight)) + } + } +} + +/// Create a message from the given data. +pub fn msg>(x: &'static str) -> BoundedSlice { + BoundedSlice::defensive_truncate_from(x.as_bytes()) +} + +pub fn vmsg(x: &'static str) -> Vec { + x.as_bytes().to_vec() +} + +pub fn page(msg: &[u8]) -> PageOf { + PageOf::::from_message::(msg.try_into().unwrap()) +} + +pub fn single_page_book() -> BookStateOf { + BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } +} + +/// Returns a page filled with empty messages and the number of messages. +pub fn full_page() -> (PageOf, usize) { + let mut msgs = 0; + let mut page = PageOf::::default(); + for i in 0..u32::MAX { + let r = i.using_encoded(|d| page.try_append_message::(d.try_into().unwrap())); + if r.is_err() { + break + } else { + msgs += 1; + } + } + assert!(msgs > 0, "page must hold at least one message"); + (page, msgs) +} + +/// Returns a page filled with empty messages and the number of messages. +pub fn book_for(page: &PageOf) -> BookStateOf { + BookState { + count: 1, + begin: 0, + end: 1, + ready_neighbours: None, + message_count: page.remaining.into(), + size: page.remaining_size.into(), + } +} + +/// Assert the last event that was emitted. +pub fn assert_last_event(generic_event: ::RuntimeEvent) { + assert!( + !frame_system::Pallet::::block_number().is_zero(), + "The genesis block has n o events" + ); + frame_system::Pallet::::assert_last_event(generic_event.into()); +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 86122b131fcd8..ef1ca08b713d8 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -163,7 +163,7 @@ fn reap_page_permanent_overweight_works() { use MessageOrigin::*; // Create pages with messages with a weight of two. // TODO why do we need `+ 2` here? - for _ in 0..(MaxStale::get() + 2) { + for _ in 0..(MaxStale::get() * MaxStale::get()) { MessageQueue::enqueue_message(msg(&"weight=2"), Here); } @@ -175,7 +175,6 @@ fn reap_page_permanent_overweight_works() { assert_ok!(MessageQueue::do_reap_page(&Here, 0)); // Cannot reap again. assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); - assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NotReapable); }); } @@ -295,6 +294,7 @@ fn service_page_item_bails() { assert_eq!( MessageQueue::service_page_item( &MessageOrigin::Here, + 0, &mut book_for::(&page), &mut page, &mut weight, @@ -316,6 +316,7 @@ fn service_page_consumes_correct_weight() { assert_eq!( MessageQueue::service_page_item( &MessageOrigin::Here, + 0, &mut book_for::(&page), &mut page, &mut weight, @@ -339,6 +340,7 @@ fn service_page_skips_perm_overweight_message() { assert_eq!( MessageQueue::service_page_item( &MessageOrigin::Here, + 0, &mut book_for::(&page), &mut page, &mut weight, @@ -388,3 +390,43 @@ fn page_from_message_max_len_works() { assert_eq!(page.remaining, 1); } + +#[test] +fn sweep_queue_works() { + new_test_ext::().execute_with(|| { + let origin = MessageOrigin::Here; + let (page, _) = full_page::(); + let mut book = book_for::(&page); + assert!(book.begin != book.end, "pre-condition: the book is not empty"); + Pages::::insert(&origin, &0, &page); + BookStateFor::::insert(&origin, &book); + + MessageQueue::sweep_queue(origin); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(&origin); + assert_eq!(book.begin, book.end, "Begin and end are now the same"); + assert!(Pages::::contains_key(&origin, &0), "Page was not swept"); + }) +} + +#[test] +fn footprint_works() { + new_test_ext::().execute_with(|| { + let origin = MessageOrigin::Here; + let (page, msgs) = full_page::(); + let mut book = book_for::(&page); + BookStateFor::::insert(&origin, &book); + + let info = MessageQueue::footprint(origin); + assert_eq!(info.count as usize, msgs); + assert_eq!(info.size, page.remaining_size); + }) +} + +#[test] +fn footprint_default_works() { + new_test_ext::().execute_with(|| { + let origin = MessageOrigin::Here; + assert_eq!(MessageQueue::footprint(origin), Default::default()); + }) +} diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index 7f19132c8b584..7dfb0e2410847 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_message_queue //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-10-28, STEPS: `2`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-11-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `oty-parity`, CPU: `11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 @@ -14,14 +14,14 @@ // --pallet // pallet-message-queue // --extrinsic= -// --output -// frame/message-queue/src/weights.rs -// --template -// .maintain/frame-weight-template.hbs // --steps -// 2 +// 50 // --repeat -// 1 +// 20 +// --template +// .maintain/frame-weight-template.hbs +// --output +// frame/message-queue/src/weights.rs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -29,70 +29,139 @@ use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use sp_std::marker::PhantomData; -use sp_std::vec::Vec; /// Weight functions needed for pallet_message_queue. pub trait WeightInfo { - fn service_page_base() -> Weight; fn service_queue_base() -> Weight; + fn service_page_base() -> Weight; + fn ready_ring_unknit() -> Weight; + fn service_page_item() -> Weight; fn service_page_process_message() -> Weight; fn bump_service_head() -> Weight; - fn service_page_item() -> Weight; - fn ready_ring_unknit() -> Weight; + fn reap_page() -> Weight; + fn execute_overweight() -> Weight; + fn process_message_payload(m: u32, ) -> Weight; } /// Weights for pallet_message_queue using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // Storage: MessageQueue BookStateFor (r:1 w:1) + fn service_queue_base() -> Weight { + // Minimum execution time: 3_617 nanoseconds. + Weight::from_ref_time(3_745_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base() -> Weight { - // Minimum execution time: 86 nanoseconds. - Weight::from_ref_time(86_000 as u64) + // Minimum execution time: 10_203_821 nanoseconds. + Weight::from_ref_time(10_467_456_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } - fn service_queue_base() -> Weight { - // Minimum execution time: 112 nanoseconds. - Weight::from_ref_time(112_000 as u64) + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + fn ready_ring_unknit() -> Weight { + // Minimum execution time: 5_953 nanoseconds. + Weight::from_ref_time(6_130_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + } + fn service_page_item() -> Weight { + // Minimum execution time: 9_057 nanoseconds. + Weight::from_ref_time(9_822_000 as u64) } - fn service_page_process_message() -> Weight { - Weight::from_ref_time(112_000 as u64) + // Minimum execution time: 33 nanoseconds. + Weight::from_ref_time(44_000 as u64) } - fn bump_service_head() -> Weight { - Weight::from_ref_time(112_000 as u64) + // Minimum execution time: 34 nanoseconds. + Weight::from_ref_time(40_000 as u64) } - - fn service_page_item() -> Weight { - Weight::from_ref_time(112_000 as u64) + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn reap_page() -> Weight { + // Minimum execution time: 27_224 nanoseconds. + Weight::from_ref_time(37_874_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } - fn ready_ring_unknit() -> Weight { - Weight::from_ref_time(112_000 as u64) + // Storage: MessageQueue BookStateFor (r:1 w:0) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight() -> Weight { + // Minimum execution time: 30_305 nanoseconds. + Weight::from_ref_time(38_079_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + /// The range of component `m` is `[0, 65526]`. + fn process_message_payload(m: u32, ) -> Weight { + // Minimum execution time: 9_164 nanoseconds. + Weight::from_ref_time(15_061_680 as u64) + // Standard Error: 12 + .saturating_add(Weight::from_ref_time(912 as u64).saturating_mul(m as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { + // Storage: MessageQueue BookStateFor (r:1 w:1) + fn service_queue_base() -> Weight { + // Minimum execution time: 3_617 nanoseconds. + Weight::from_ref_time(3_745_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base() -> Weight { - // Minimum execution time: 86 nanoseconds. - Weight::from_ref_time(86_000 as u64) + // Minimum execution time: 10_203_821 nanoseconds. + Weight::from_ref_time(10_467_456_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - fn service_queue_base() -> Weight { - // Minimum execution time: 112 nanoseconds. - Weight::from_ref_time(112_000 as u64) + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + fn ready_ring_unknit() -> Weight { + // Minimum execution time: 5_953 nanoseconds. + Weight::from_ref_time(6_130_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + } + fn service_page_item() -> Weight { + // Minimum execution time: 9_057 nanoseconds. + Weight::from_ref_time(9_822_000 as u64) } - fn service_page_process_message() -> Weight { - Weight::from_ref_time(112_000 as u64) + // Minimum execution time: 33 nanoseconds. + Weight::from_ref_time(44_000 as u64) } - fn bump_service_head() -> Weight { - Weight::from_ref_time(112_000 as u64) + // Minimum execution time: 34 nanoseconds. + Weight::from_ref_time(40_000 as u64) } - - fn service_page_item() -> Weight { - Weight::from_ref_time(112_000 as u64) + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn reap_page() -> Weight { + // Minimum execution time: 27_224 nanoseconds. + Weight::from_ref_time(37_874_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - - fn ready_ring_unknit() -> Weight { - Weight::from_ref_time(112_000 as u64) + // Storage: MessageQueue BookStateFor (r:1 w:0) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight() -> Weight { + // Minimum execution time: 30_305 nanoseconds. + Weight::from_ref_time(38_079_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + /// The range of component `m` is `[0, 65526]`. + fn process_message_payload(m: u32, ) -> Weight { + // Minimum execution time: 9_164 nanoseconds. + Weight::from_ref_time(15_061_680 as u64) + // Standard Error: 12 + .saturating_add(Weight::from_ref_time(912 as u64).saturating_mul(m as u64)) } } From 674563dd80258358cb418c956f8a1462fa7f5763 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 13 Nov 2022 18:02:03 +0100 Subject: [PATCH 049/110] Clippy Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/integration_test.rs | 8 +++---- frame/message-queue/src/mock.rs | 26 +++++++++++---------- frame/message-queue/src/tests.rs | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index 692a9aede0003..0ae8c731a3b07 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -21,25 +21,23 @@ use crate::{ mock::{ - new_test_ext, CountingMessageProcessor, IntoWeight, MockedWeightInfo, - NumMessagesProcessed, + new_test_ext, CountingMessageProcessor, IntoWeight, MockedWeightInfo, NumMessagesProcessed, }, *, }; use crate as pallet_message_queue; use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, parameter_types, + parameter_types, traits::{ConstU32, ConstU64}, }; -use rand::{prelude::*, Rng, SeedableRng}; +use rand::{Rng, SeedableRng}; use rand_distr::Pareto; use sp_core::H256; use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, }; -use sp_std::collections::btree_map::BTreeMap; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 587f9003dfcb4..c6f09bad14a07 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -16,17 +16,19 @@ #![cfg(any(test, feature = "std"))] -use super::*; pub use super::mock_helpers::*; +use super::*; use crate as pallet_message_queue; use frame_support::{ - assert_noop, assert_ok, parameter_types, + parameter_types, traits::{ConstU32, ConstU64}, }; use sp_core::H256; -use sp_runtime::testing::Header; -use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; use sp_std::collections::btree_map::BTreeMap; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; @@ -80,7 +82,7 @@ parameter_types! { impl Config for Test { type RuntimeEvent = RuntimeEvent; type WeightInfo = MockedWeightInfo; - type MessageProcessor = TestMessageProcessor; + type MessageProcessor = RecordingMessageProcessor; type Size = u32; type HeapSize = HeapSize; type MaxStale = MaxStale; @@ -142,8 +144,8 @@ parameter_types! { pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; } -pub struct TestMessageProcessor; -impl ProcessMessage for TestMessageProcessor { +pub struct RecordingMessageProcessor; +impl ProcessMessage for RecordingMessageProcessor { /// The transport from where a message originates. type Origin = MessageOrigin; @@ -185,17 +187,17 @@ parameter_types! { pub static NumMessagesProcessed: usize = 0; } -/// Similar to [`TestMessageProcessor`] but only counts the number of messages processed and does -/// always consume one weight per message. +/// Similar to [`RecordingMessageProcessor`] but only counts the number of messages processed and +/// does always consume one weight per message. /// -/// The [`TestMessageProcessor`] is a bit too slow for the integration tests. +/// The [`RecordingMessageProcessor`] is a bit too slow for the integration tests. pub struct CountingMessageProcessor; impl ProcessMessage for CountingMessageProcessor { type Origin = MessageOrigin; fn process_message( - message: &[u8], - origin: Self::Origin, + _message: &[u8], + _origin: Self::Origin, weight_limit: Weight, ) -> Result<(bool, Weight), ProcessMessageError> { let weight = Weight::from_parts(1, 1); diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index ef1ca08b713d8..9f940762bd0db 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -396,7 +396,7 @@ fn sweep_queue_works() { new_test_ext::().execute_with(|| { let origin = MessageOrigin::Here; let (page, _) = full_page::(); - let mut book = book_for::(&page); + let book = book_for::(&page); assert!(book.begin != book.end, "pre-condition: the book is not empty"); Pages::::insert(&origin, &0, &page); BookStateFor::::insert(&origin, &book); @@ -414,7 +414,7 @@ fn footprint_works() { new_test_ext::().execute_with(|| { let origin = MessageOrigin::Here; let (page, msgs) = full_page::(); - let mut book = book_for::(&page); + let book = book_for::(&page); BookStateFor::::insert(&origin, &book); let info = MessageQueue::footprint(origin); From c5c3589c32ee67c6b5b2381dd5a1e427c1110fbe Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Sun, 13 Nov 2022 19:29:37 +0100 Subject: [PATCH 050/110] Add tests Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 22 ++--- frame/message-queue/src/mock.rs | 1 + frame/message-queue/src/mock_helpers.rs | 3 +- frame/message-queue/src/tests.rs | 102 ++++++++++++++++++++++-- 4 files changed, 104 insertions(+), 24 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 46896409af8dc..510d27afb3958 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -173,8 +173,7 @@ impl< } } - /// Return the unprocessed encoded (origin, message) pair of `index` into the page's - /// messages if and only if it was skipped (i.e. of location prior to `first`). + /// Return the message with index `index` in the form of `(position, processed, message)`. fn peek_index(&self, index: usize) -> Option<(usize, bool, &[u8])> { let mut pos = 0; let mut item_slice = &self.heap[..]; @@ -382,8 +381,8 @@ pub mod pallet { /// Check all assumptions about [`crate::Config`]. fn integrity_test() { assert!(!MaxMessageLenOf::::get().is_zero(), "HeapSize too low"); - // This value is squared and should not overflow. - assert!(T::MaxStale::get() <= u16::MAX as u32, "MaxStale too large"); + // This value gets squared and should not overflow. + assert!(T::MaxStale::get().checked_pow(2).is_some(), "MaxStale too large"); } } @@ -859,17 +858,12 @@ impl Pallet { let mut queue = format!("queue {:?}:\n", &origin); let mut pages = Pages::::iter_prefix(&origin).collect::>(); pages.sort_by(|(a, _), (b, _)| a.cmp(b)); - for (page_index, page) in pages.into_iter() { - let mut page = page; + for (page_index, mut page) in pages.into_iter() { + let page_info = if book_state.begin == page_index { ">" } else { " " }; let mut page_info = format!( - "page {} ({:?} first, {:?} last, {:?} remain): [", - page_index, page.first, page.last, page.remaining + "{} page {} ({:?} first, {:?} last, {:?} remain): [ ", + page_info, page_index, page.first, page.last, page.remaining ); - if book_state.begin == page_index { - page_info = format!("> {}", page_info); - } else { - page_info = format!(" {}", page_info); - } for i in 0..u32::MAX { if let Some((_, processed, message)) = page.peek_index(i.try_into().expect("std-only code")) @@ -877,8 +871,6 @@ impl Pallet { let msg = String::from_utf8_lossy(message.deref()); if processed { page_info.push_str("*"); - } else { - page_info.push_str(" "); } page_info.push_str(&format!("{:?}, ", msg)); page.skip_first(true); diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index c6f09bad14a07..04bb10ffde997 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -228,6 +228,7 @@ where } /// Set the weight of a specific weight function. +#[allow(dead_code)] pub fn set_weight(name: &str, w: Weight) { MockedWeightInfo::set_weight::(name, w); } diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 5cb8e0dfd3e27..9231a94e5209c 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -74,6 +74,7 @@ pub fn vmsg(x: &'static str) -> Vec { x.as_bytes().to_vec() } +/// Create a page from a single message. pub fn page(msg: &[u8]) -> PageOf { PageOf::::from_message::(msg.try_into().unwrap()) } @@ -82,7 +83,7 @@ pub fn single_page_book() -> BookStateOf { BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } } -/// Returns a page filled with empty messages and the number of messages. +/// Returns a full page of messages with their index as payload and the number of messages. pub fn full_page() -> (PageOf, usize) { let mut msgs = 0; let mut page = PageOf::::default(); diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 9f940762bd0db..2027dc930e12c 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -162,7 +162,6 @@ fn reap_page_permanent_overweight_works() { new_test_ext::().execute_with(|| { use MessageOrigin::*; // Create pages with messages with a weight of two. - // TODO why do we need `+ 2` here? for _ in 0..(MaxStale::get() * MaxStale::get()) { MessageQueue::enqueue_message(msg(&"weight=2"), Here); } @@ -332,13 +331,13 @@ fn service_page_consumes_correct_weight() { #[test] fn service_page_skips_perm_overweight_message() { new_test_ext::().execute_with(|| { - let mut page = page::(b"weight=6"); - let mut weight = WeightCounter::from_limit(7.into_weight()); - let overweight_limit = 5.into_weight(); + let mut page = page::(b"TooMuch"); + let mut weight = WeightCounter::from_limit(2.into_weight()); + let overweight_limit = 0.into_weight(); set_weight("service_page_item", 2.into_weight()); assert_eq!( - MessageQueue::service_page_item( + crate::Pallet::::service_page_item( &MessageOrigin::Here, 0, &mut book_for::(&page), @@ -349,30 +348,117 @@ fn service_page_skips_perm_overweight_message() { PageExecutionStatus::Partial ); assert_eq!(weight.consumed, 2.into_weight()); + assert_last_event::( + Event::OverweightEnqueued { + hash: ::Hashing::hash(b"TooMuch"), + origin: MessageOrigin::Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + // Check that the message was skipped. let (pos, processed, payload) = page.peek_index(0).unwrap(); assert_eq!(pos, 0); assert_eq!(processed, false); - assert_eq!(payload, b"weight=6".encode()); + assert_eq!(payload, b"TooMuch".encode()); }); } #[test] fn peek_index_works() { + use super::integration_test::Test; // Run with larger page size. new_test_ext::().execute_with(|| { + // Fill a page with messages. let (mut page, msgs) = full_page::(); - assert!(msgs > 1, "precondition unmet"); + let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; for i in 0..msgs { + // Skip all even messages. page.skip_first(i % 2 == 0); + // Peek each message and check that it is correct. let (pos, processed, payload) = page.peek_index(i).unwrap(); - assert_eq!(pos, 9 * i); + assert_eq!(pos, msg_enc_len * i); assert_eq!(processed, i % 2 == 0); + // `full_page` uses the index as payload. assert_eq!(payload, (i as u32).encode()); } }); } +#[test] +fn peek_first_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + // Fill a page with messages. + let (mut page, msgs) = full_page::(); + + for i in 0..msgs { + let msg = page.peek_first().unwrap(); + // `full_page` uses the index as payload. + assert_eq!(msg.deref(), (i as u32).encode()); + page.skip_first(i % 2 == 0); // True of False should not matter here. + } + assert!(page.peek_first().is_none(), "Page must be at the end"); + }); +} + +#[test] +fn note_processed_at_pos_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + let (mut page, msgs) = full_page::(); + + for i in 0..msgs { + let (pos, processed, _) = page.peek_index(i).unwrap(); + assert_eq!(processed, false); + assert_eq!(page.remaining as usize, msgs - i); + + page.note_processed_at_pos(pos); + + let (_, processed, _) = page.peek_index(i).unwrap(); + assert_eq!(processed, true); + assert_eq!(page.remaining as usize, msgs - i - 1); + } + }); +} + +#[test] +fn is_complete_works() { + use super::integration_test::Test; // Run with larger page size. + new_test_ext::().execute_with(|| { + let (mut page, msgs) = full_page::(); + assert!(msgs > 3, "Boring"); + let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; + + assert!(!page.is_complete()); + for i in 0..msgs { + if i % 2 == 0 { + page.skip_first(false); + } else { + page.note_processed_at_pos(msg_enc_len * i); + } + } + // Not complete since `skip_first` was called with `false`. + assert!(!page.is_complete()); + for i in 0..msgs { + if i % 2 == 0 { + assert!(!page.is_complete()); + let (pos, _, _) = page.peek_index(i).unwrap(); + page.note_processed_at_pos(pos); + } + } + assert!(page.is_complete()); + assert_eq!(page.remaining_size, 0); + // Each message is marked as processed. + for i in 0..msgs { + let (_, processed, _) = page.peek_index(i).unwrap(); + assert_eq!(processed, true); + } + }); +} + #[test] fn page_from_message_basic_works() { assert!(MaxOriginLenOf::::get() >= 3, "pre-condition unmet"); From e5543628ccd1da7d91d6fcd609067f28dd55303e Mon Sep 17 00:00:00 2001 From: Gav Date: Mon, 14 Nov 2022 18:34:35 +0100 Subject: [PATCH 051/110] Provide change handler --- frame/message-queue/src/integration_test.rs | 1 + frame/message-queue/src/lib.rs | 24 ++++++++++++++++++++- frame/message-queue/src/mock.rs | 1 + 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index 0ae8c731a3b07..3aa50f32f778b 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -95,6 +95,7 @@ impl Config for Test { type WeightInfo = MockedWeightInfo; type MessageProcessor = CountingMessageProcessor; type Size = u32; + type QueueChangeHandler = (); type HeapSize = HeapSize; type MaxStale = MaxStale; type ServiceWeight = ServiceWeight; diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 510d27afb3958..4941738e7ef47 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -243,6 +243,14 @@ impl Default for BookState { } } +pub trait OnQueueChanged { + fn on_queue_changed(id: Id, items_count: u32, items_size: u32); +} + +impl OnQueueChanged for () { + fn on_queue_changed(_: Id, _: u32, _: u32) {} +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -280,6 +288,10 @@ pub mod pallet { + TypeInfo + Default; + /// Code to be called when a message queue changes - either with items introduced or + /// removed. + type QueueChangeHandler: OnQueueChanged<::Origin>; + /// The size of the page; this implies the maximum message size which can be sent. #[pallet::constant] type HeapSize: Get; @@ -643,6 +655,11 @@ impl Pallet { } else { Pages::::insert(&origin, page_index, page); } + T::QueueChangeHandler::on_queue_changed( + origin, + book_state.message_count, + book_state.size, + ); Ok(weight_counter.consumed) }, } @@ -730,6 +747,7 @@ impl Pallet { } } BookStateFor::::insert(&origin, &book_state); + T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); (total_processed > 0, next_ready) } @@ -1023,7 +1041,9 @@ impl EnqueueMessage> for Pallet { message: BoundedSlice, origin: ::Origin, ) { - Self::do_enqueue_message(&origin, message) + Self::do_enqueue_message(&origin, message); + let book_state = BookStateFor::::get(&origin); + T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); } fn enqueue_messages<'a>( @@ -1033,6 +1053,8 @@ impl EnqueueMessage> for Pallet { for message in messages { Self::do_enqueue_message(&origin, message); } + let book_state = BookStateFor::::get(&origin); + T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); } /// Force removes a queue from the ready ring. diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 04bb10ffde997..a4b01f99392aa 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -84,6 +84,7 @@ impl Config for Test { type WeightInfo = MockedWeightInfo; type MessageProcessor = RecordingMessageProcessor; type Size = u32; + type QueueChangeHandler = (); type HeapSize = HeapSize; type MaxStale = MaxStale; type ServiceWeight = ServiceWeight; From 25d2c06a55f9379e238d832c4738d19a574bab81 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Nov 2022 17:21:39 +0100 Subject: [PATCH 052/110] Add missing BookStateFor::insert and call QueueChangeHandler Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 1 + frame/message-queue/src/lib.rs | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b830b81e6b277..bfcfc97e74a7e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1141,6 +1141,7 @@ impl pallet_message_queue::Config for Runtime { /// NOTE: Always set this to `NoopMessageProcessor` for benchmarking. type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor; type Size = u32; + type QueueChangeHandler = (); type HeapSize = HeapSize; type MaxStale = MaxStale; type ServiceWeight = ServiceWeight; diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 4941738e7ef47..494baa50f4728 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -605,6 +605,7 @@ impl Pallet { book_state.count.saturating_inc(); let page = Page::from_message::(message); Pages::::insert(origin, book_state.end - 1, &page); + // NOTE: `T::QueueChangeHandler` is called by the caller. BookStateFor::::insert(origin, book_state); } @@ -655,6 +656,7 @@ impl Pallet { } else { Pages::::insert(&origin, page_index, page); } + BookStateFor::::insert(&origin, &book_state); T::QueueChangeHandler::on_queue_changed( origin, book_state.message_count, @@ -699,6 +701,11 @@ impl Pallet { book_state.message_count.saturating_reduce(page.remaining.into()); book_state.size.saturating_reduce(page.remaining_size.into()); BookStateFor::::insert(origin, book_state); + T::QueueChangeHandler::on_queue_changed( + origin.clone(), + book_state.message_count, + book_state.size, + ); Self::deposit_event(Event::PageReaped { origin: origin.clone(), index: page_index }); Ok(()) @@ -747,7 +754,13 @@ impl Pallet { } } BookStateFor::::insert(&origin, &book_state); - T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); + if total_processed > 0 { + T::QueueChangeHandler::on_queue_changed( + origin, + book_state.message_count, + book_state.size, + ); + } (total_processed > 0, next_ready) } From 22fe8999e8aaaed13dd5c153f1f847e775b03600 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Nov 2022 17:22:32 +0100 Subject: [PATCH 053/110] Docs Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 54 +++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 494baa50f4728..20f4e5204d8dc 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -213,12 +213,18 @@ impl< } } +/// The neighbours of a queue in the a double-linked list #[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] pub struct Neighbours { prev: MessageOrigin, next: MessageOrigin, } +/// The state of a queue as represented by a book of its pages. +/// +/// Each queue has exactly one book which holds all of its pages. All pages of a book combined +/// contain all of the messages of its queue; hence the name *Book*. +/// Books can be chained together in a double-linked fashion through their `ready_neighbours` field. #[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] pub struct BookState { /// The first page with some items to be processed in it. If this is `>= end`, then there are @@ -243,7 +249,9 @@ impl Default for BookState { } } +/// Notifies the implementor of changes to a queue. pub trait OnQueueChanged { + /// The queue `id` changed and now has these properties. fn on_queue_changed(id: Id, items_count: u32, items_size: u32); } @@ -316,22 +324,11 @@ pub mod pallet { pub enum Event { /// Message discarded due to an inability to decode the item. Usually caused by state /// corruption. - Discarded { - hash: T::Hash, - }, + Discarded { hash: T::Hash }, /// Message discarded due to an error in the `MessageProcessor` (usually a format error). - ProcessingFailed { - hash: T::Hash, - origin: MessageOriginOf, - error: ProcessMessageError, - }, + ProcessingFailed { hash: T::Hash, origin: MessageOriginOf, error: ProcessMessageError }, /// Message is processed. - Processed { - hash: T::Hash, - origin: MessageOriginOf, - weight_used: Weight, - success: bool, - }, + Processed { hash: T::Hash, origin: MessageOriginOf, weight_used: Weight, success: bool }, /// Message placed in overweight queue. OverweightEnqueued { hash: T::Hash, @@ -339,10 +336,8 @@ pub mod pallet { page_index: PageIndex, message_index: T::Size, }, - PageReaped { - origin: MessageOriginOf, - index: PageIndex, - }, + /// This page was reaped. + PageReaped { origin: MessageOriginOf, index: PageIndex }, } #[pallet::error] @@ -352,10 +347,13 @@ pub mod pallet { NotReapable, /// Page to be reaped does not exist. NoPage, + /// The referenced message could not be found. NoMessage, - Unexpected, + /// The message was already processed and cannot be processed again. AlreadyProcessed, + /// The message is queued for future execution. Queued, + /// There is temporarily not enough weight to continue servicing messages. InsufficientWeight, } @@ -537,6 +535,7 @@ impl Pallet { }); if let Some(head) = ServiceHead::::get() { if &head == origin { + // NOTE: This case is benchmarked by `ready_ring_unknit`. ServiceHead::::put(neighbours.next); } } else { @@ -545,6 +544,9 @@ impl Pallet { } } + /// Tries to bump the current `ServiceHead` to the next ready queue. + /// + /// Returns the current head if it got be bumped and `None` otherwise. fn bump_service_head(weight: &mut WeightCounter) -> Option> { if !weight.check_accrue(T::WeightInfo::bump_service_head()) { return None @@ -573,6 +575,7 @@ impl Pallet { .size // This should be payload size, but here the payload *is* the message. .saturating_accrue(message.len() as u32); + if book_state.end > book_state.begin { debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); // Already have a page in progress - attempt to append. @@ -609,6 +612,10 @@ impl Pallet { BookStateFor::::insert(origin, book_state); } + /// Try to execute a single message that was marked as overweight. + /// + /// The `weight_limit` is the weight that can be consumed to execute the message. The base + /// weight of the function it self must be measured by the caller. pub fn do_execute_overweight( origin: MessageOriginOf, page_index: PageIndex, @@ -774,7 +781,10 @@ impl Pallet { overweight_limit: Weight, ) -> (u32, PageExecutionStatus) { use PageExecutionStatus::*; - if !weight.check_accrue(T::WeightInfo::service_page_base()) { + if !weight.check_accrue( + T::WeightInfo::service_page_base_completion() + .max(T::WeightInfo::service_page_base_no_completion()), + ) { return (0, Bailed) } @@ -917,6 +927,7 @@ impl Pallet { info } + /// Process a single message. fn process_message_payload( origin: MessageOriginOf, page_index: PageIndex, @@ -1033,6 +1044,9 @@ impl ServiceQueues for Pallet { weight.consumed } + /// Execute a single overweight message. + /// + /// The weight limit must be enough for `execute_overweight` and the message execution itself. fn execute_overweight( weight_limit: Weight, (message_origin, page, index): Self::OverweightMessageAddress, From e650ae8b87f5bf1ef6481488068ba1e75f485024 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Nov 2022 17:41:57 +0100 Subject: [PATCH 054/110] Update benchmarks and weights Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 67 ++++++++------- frame/message-queue/src/mock.rs | 15 +++- frame/message-queue/src/tests.rs | 8 +- frame/message-queue/src/weights.rs | 107 +++++++++++++----------- 4 files changed, 112 insertions(+), 85 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 2a85b3786afae..3a2f2048df329 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -41,9 +41,24 @@ benchmarks! { } // `service_page` without any message processing but with page completion. - service_page_base { + service_page_base_completion { let origin: MessageOriginOf = 0.into(); - let (page, msgs) = full_page::(); + let page = PageOf::::default(); + Pages::::insert(&origin, 0, &page); + let mut book_state = single_page_book::(); + let mut meter = WeightCounter::unlimited(); + let limit = Weight::MAX; + }: { + MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) + } + + // `service_page` without any message processing and without page completion. + service_page_base_no_completion { + let origin: MessageOriginOf = 0.into(); + let mut page = PageOf::::default(); + // Mock the storage such that `is_complete` returns `false` but `peek_first` returns `None`. + page.first = 1.into(); + page.remaining = 1.into(); Pages::::insert(&origin, 0, &page); let mut book_state = single_page_book::(); let mut meter = WeightCounter::unlimited(); @@ -66,32 +81,37 @@ benchmarks! { // Processing a single message from a page. service_page_item { - let (mut page, msgs) = full_page::(); - // Skip all messages besides the last one. - for i in 1..msgs { - page.skip_first(true); - } - assert!(page.peek_first().is_some(), "There is one message left"); + let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; + let mut page = page::(&msg.clone()); + let mut book = book_for::(&page); + assert!(page.peek_first().is_some(), "There is one message"); let mut weight = WeightCounter::unlimited(); }: { - let status = MessageQueue::::service_page_item(&0u32.into(), 0, &mut book_for::(&page), &mut page, &mut weight, Weight::MAX); + let status = MessageQueue::::service_page_item(&0u32.into(), 0, &mut book, &mut page, &mut weight, Weight::MAX); assert_eq!(status, PageExecutionStatus::Partial); } verify { - // Check for the `Processed` event. + // Check that it was processed. assert_last_event::(Event::Processed { - hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), + hash: T::Hashing::hash(&msg), 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); } - // Contains the effort for fetching and (storing or removing) a page. - service_page_process_message { }: { } - // Worst case for calling `bump_service_head`. - bump_service_head { }: {} + bump_service_head { + setup_bump_service_head::(0.into(), 10.into()); + let mut weight = WeightCounter::unlimited(); + }: { + MessageQueue::::bump_service_head(&mut weight); + } verify { + assert_eq!(ServiceHead::::get().unwrap(), 10u32.into()); + } reap_page { - // Mock the storage to get a cullable page that is not empty. + // Mock the storage to get a *cullable* but not *reapable* page. let origin: MessageOriginOf = 0.into(); let mut book = single_page_book::(); let (page, msgs) = full_page::(); @@ -107,11 +127,12 @@ benchmarks! { } book.begin = book.end - T::MaxStale::get(); BookStateFor::::insert(&origin, &book); + assert!(Pages::::contains_key(&origin, 0)); - System::::reset_events(); }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0) verify { assert_last_event::(Event::PageReaped{ origin: 0.into(), index: 0 }.into()); + assert!(!Pages::::contains_key(&origin, 0)); } execute_overweight { @@ -131,17 +152,5 @@ benchmarks! { }.into()); } - // We need this mainly for the `T::Hashing::hash` effort. - process_message_payload { - let m in 0 .. MaxMessageLenOf::::get(); - let origin: MessageOriginOf = 0.into(); - let msg = vec![1u8; m as usize]; - let mut meter = WeightCounter::unlimited(); - }: { - MessageQueue::::process_message_payload(origin, 0, 0u32.into(), &msg[..], &mut meter, Weight::MAX) - } verify { - assert_eq!(meter.consumed, Weight::from_parts(1, 1)); - } - impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::integration_test::Test); } diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index a4b01f99392aa..c9ef9f824bd41 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -115,8 +115,17 @@ impl crate::weights::WeightInfo for MockedWeightInfo { fn execute_overweight() -> Weight { WeightForCall::get().get("execute_overweight").copied().unwrap_or_default() } - fn service_page_base() -> Weight { - WeightForCall::get().get("service_page_base").copied().unwrap_or_default() + fn service_page_base_completion() -> Weight { + WeightForCall::get() + .get("service_page_base_completion") + .copied() + .unwrap_or_default() + } + fn service_page_base_no_completion() -> Weight { + WeightForCall::get() + .get("service_page_base_no_completion") + .copied() + .unwrap_or_default() } fn service_queue_base() -> Weight { WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() @@ -136,7 +145,7 @@ impl crate::weights::WeightInfo for MockedWeightInfo { fn ready_ring_unknit() -> Weight { WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() } - fn process_message_payload(_: u32) -> Weight { + fn process_message_payload() -> Weight { WeightForCall::get().get("process_message_payload").copied().unwrap_or_default() } } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 2027dc930e12c..ff72b032fdbcc 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -26,15 +26,15 @@ use frame_support::{assert_noop, assert_ok, assert_storage_noop}; #[test] fn mocked_weight_works() { new_test_ext::().execute_with(|| { - assert!(::WeightInfo::service_page_base().is_zero()); + assert!(::WeightInfo::service_queue_base().is_zero()); }); new_test_ext::().execute_with(|| { - set_weight("service_page_base", Weight::MAX); - assert_eq!(::WeightInfo::service_page_base(), Weight::MAX); + set_weight("service_queue_base", Weight::MAX); + assert_eq!(::WeightInfo::service_queue_base(), Weight::MAX); }); // The externalities reset it. new_test_ext::().execute_with(|| { - assert!(::WeightInfo::service_page_base().is_zero()); + assert!(::WeightInfo::service_queue_base().is_zero()); }); } diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index 7dfb0e2410847..885de8dd9ad59 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_message_queue //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-11-15, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `oty-parity`, CPU: `11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 @@ -33,14 +33,15 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_message_queue. pub trait WeightInfo { fn service_queue_base() -> Weight; - fn service_page_base() -> Weight; + fn service_page_base_completion() -> Weight; + fn service_page_base_no_completion() -> Weight; fn ready_ring_unknit() -> Weight; fn service_page_item() -> Weight; fn service_page_process_message() -> Weight; fn bump_service_head() -> Weight; fn reap_page() -> Weight; fn execute_overweight() -> Weight; - fn process_message_payload(m: u32, ) -> Weight; + fn process_message_payload() -> Weight; } /// Weights for pallet_message_queue using the Substrate node and recommended hardware. @@ -48,60 +49,64 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_617 nanoseconds. - Weight::from_ref_time(3_745_000 as u64) + // Minimum execution time: 3_489 nanoseconds. + Weight::from_ref_time(3_611_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: MessageQueue Pages (r:1 w:1) - fn service_page_base() -> Weight { - // Minimum execution time: 10_203_821 nanoseconds. - Weight::from_ref_time(10_467_456_000 as u64) + fn service_page_base_completion() -> Weight { + // Minimum execution time: 5_513 nanoseconds. + Weight::from_ref_time(5_680_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_no_completion() -> Weight { + // Minimum execution time: 5_665 nanoseconds. + Weight::from_ref_time(5_758_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: MessageQueue BookStateFor (r:2 w:2) // Storage: MessageQueue ServiceHead (r:1 w:1) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 5_953 nanoseconds. - Weight::from_ref_time(6_130_000 as u64) + // Minimum execution time: 5_913 nanoseconds. + Weight::from_ref_time(6_056_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } fn service_page_item() -> Weight { - // Minimum execution time: 9_057 nanoseconds. - Weight::from_ref_time(9_822_000 as u64) + // Minimum execution time: 8_354 nanoseconds. + Weight::from_ref_time(9_730_000 as u64) } fn service_page_process_message() -> Weight { - // Minimum execution time: 33 nanoseconds. - Weight::from_ref_time(44_000 as u64) + // Minimum execution time: 28 nanoseconds. + Weight::from_ref_time(31_000 as u64) } fn bump_service_head() -> Weight { - // Minimum execution time: 34 nanoseconds. - Weight::from_ref_time(40_000 as u64) + // Minimum execution time: 28 nanoseconds. + Weight::from_ref_time(33_000 as u64) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 27_224 nanoseconds. - Weight::from_ref_time(37_874_000 as u64) + // Minimum execution time: 26_075 nanoseconds. + Weight::from_ref_time(36_499_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } // Storage: MessageQueue BookStateFor (r:1 w:0) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight() -> Weight { - // Minimum execution time: 30_305 nanoseconds. - Weight::from_ref_time(38_079_000 as u64) + // Minimum execution time: 29_557 nanoseconds. + Weight::from_ref_time(41_779_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } - /// The range of component `m` is `[0, 65526]`. - fn process_message_payload(m: u32, ) -> Weight { - // Minimum execution time: 9_164 nanoseconds. - Weight::from_ref_time(15_061_680 as u64) - // Standard Error: 12 - .saturating_add(Weight::from_ref_time(912 as u64).saturating_mul(m as u64)) + fn process_message_payload() -> Weight { + // Minimum execution time: 75_445 nanoseconds. + Weight::from_ref_time(75_978_000 as u64) } } @@ -109,59 +114,63 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_617 nanoseconds. - Weight::from_ref_time(3_745_000 as u64) + // Minimum execution time: 3_489 nanoseconds. + Weight::from_ref_time(3_611_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: MessageQueue Pages (r:1 w:1) + fn service_page_base_completion() -> Weight { + // Minimum execution time: 5_513 nanoseconds. + Weight::from_ref_time(5_680_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: MessageQueue Pages (r:1 w:1) - fn service_page_base() -> Weight { - // Minimum execution time: 10_203_821 nanoseconds. - Weight::from_ref_time(10_467_456_000 as u64) + fn service_page_base_no_completion() -> Weight { + // Minimum execution time: 5_665 nanoseconds. + Weight::from_ref_time(5_758_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: MessageQueue BookStateFor (r:2 w:2) // Storage: MessageQueue ServiceHead (r:1 w:1) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 5_953 nanoseconds. - Weight::from_ref_time(6_130_000 as u64) + // Minimum execution time: 5_913 nanoseconds. + Weight::from_ref_time(6_056_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } fn service_page_item() -> Weight { - // Minimum execution time: 9_057 nanoseconds. - Weight::from_ref_time(9_822_000 as u64) + // Minimum execution time: 8_354 nanoseconds. + Weight::from_ref_time(9_730_000 as u64) } fn service_page_process_message() -> Weight { - // Minimum execution time: 33 nanoseconds. - Weight::from_ref_time(44_000 as u64) + // Minimum execution time: 28 nanoseconds. + Weight::from_ref_time(31_000 as u64) } fn bump_service_head() -> Weight { - // Minimum execution time: 34 nanoseconds. - Weight::from_ref_time(40_000 as u64) + // Minimum execution time: 28 nanoseconds. + Weight::from_ref_time(33_000 as u64) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 27_224 nanoseconds. - Weight::from_ref_time(37_874_000 as u64) + // Minimum execution time: 26_075 nanoseconds. + Weight::from_ref_time(36_499_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } // Storage: MessageQueue BookStateFor (r:1 w:0) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight() -> Weight { - // Minimum execution time: 30_305 nanoseconds. - Weight::from_ref_time(38_079_000 as u64) + // Minimum execution time: 29_557 nanoseconds. + Weight::from_ref_time(41_779_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } - /// The range of component `m` is `[0, 65526]`. - fn process_message_payload(m: u32, ) -> Weight { - // Minimum execution time: 9_164 nanoseconds. - Weight::from_ref_time(15_061_680 as u64) - // Standard Error: 12 - .saturating_add(Weight::from_ref_time(912 as u64).saturating_mul(m as u64)) + fn process_message_payload() -> Weight { + // Minimum execution time: 75_445 nanoseconds. + Weight::from_ref_time(75_978_000 as u64) } } From a1917e25ad21b1e23e1d695b351840694554493c Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Nov 2022 17:43:36 +0100 Subject: [PATCH 055/110] More tests... Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/mock_helpers.rs | 11 ++ frame/message-queue/src/tests.rs | 149 ++++++++++++++++++++++-- 2 files changed, 153 insertions(+), 7 deletions(-) diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 9231a94e5209c..0aa5da4081e97 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -119,3 +119,14 @@ pub fn assert_last_event(generic_event: ::RuntimeEvent) ); frame_system::Pallet::::assert_last_event(generic_event.into()); } + +/// Provide a setup for `bump_service_head`. +pub fn setup_bump_service_head( + current: <::MessageProcessor as ProcessMessage>::Origin, + next: <::MessageProcessor as ProcessMessage>::Origin, +) { + let mut book = single_page_book::(); + book.ready_neighbours = Some(Neighbours::> { prev: next.clone(), next }); + ServiceHead::::put(¤t); + BookStateFor::::insert(¤t, &book); +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index ff72b032fdbcc..88ecec175465e 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -21,7 +21,8 @@ use crate::{mock::*, *}; -use frame_support::{assert_noop, assert_ok, assert_storage_noop}; +use frame_support::{assert_noop, assert_ok, assert_storage_noop, StorageNoopGuard}; +use rand::{Rng, SeedableRng}; #[test] fn mocked_weight_works() { @@ -264,16 +265,77 @@ fn service_queue_bails() { }); } +#[test] +fn service_page_works() { + use super::integration_test::Test; + use MessageOrigin::*; + use PageExecutionStatus::*; // Run with larger page size. + new_test_ext::().execute_with(|| { + set_weight("service_page_base_completion", 2.into_weight()); + set_weight("service_page_item", 3.into_weight()); + set_weight("process_message_payload", 4.into_weight()); + + let (page, mut msgs) = full_page::(); + assert!(msgs >= 10, "pre-condition: need at least 10 msgs per page"); + let mut book = book_for::(&page); + Pages::::insert(&Here, 0, page); + + // Call it a few times each with a random weight limit. + let mut rng = rand::rngs::StdRng::seed_from_u64(42); + while msgs > 0 { + let process = rng.gen_range(0..=msgs); + msgs -= process; + + // Enough weight to process `process` messages. + let mut meter = + WeightCounter::from_limit(((2 + (3 + 4 + 1) * process) as u64).into_weight()); + System::reset_events(); + let (processed, status) = + crate::Pallet::::service_page(&Here, &mut book, &mut meter, Weight::MAX); + assert_eq!(processed as usize, process); + assert_eq!(NumMessagesProcessed::take(), process); + assert_eq!(System::events().len(), process); + if msgs == 0 { + assert_eq!(status, NoMore); + } else { + assert_eq!(status, Bailed); + } + } + assert!(!Pages::::contains_key(&Here, 0), "The page got removed"); + }); +} + +// `service_page` does nothing when called with an insufficient weight limit. #[test] fn service_page_bails() { - // Not enough weight for `service_page_base`. + // Not enough weight for `service_page_base_completion`. + new_test_ext::().execute_with(|| { + set_weight("service_page_base_completion", 2.into_weight()); + let mut meter = WeightCounter::from_limit(1.into_weight()); + + let (page, _) = full_page::(); + let mut book = book_for::(&page); + Pages::::insert(MessageOrigin::Here, 0, page); + + assert_storage_noop!(MessageQueue::service_page( + &MessageOrigin::Here, + &mut book, + &mut meter, + Weight::MAX + )); + assert!(meter.consumed.is_zero()); + }); + // Not enough weight for `service_page_base_no_completion`. new_test_ext::().execute_with(|| { - set_weight("service_page_base", 2.into_weight()); + set_weight("service_page_base_no_completion", 2.into_weight()); let mut meter = WeightCounter::from_limit(1.into_weight()); - let mut book = single_page_book::(); + + let (page, _) = full_page::(); + let mut book = book_for::(&page); + Pages::::insert(MessageOrigin::Here, 0, page); assert_storage_noop!(MessageQueue::service_page( - &0.into(), + &MessageOrigin::Here, &mut book, &mut meter, Weight::MAX @@ -285,6 +347,7 @@ fn service_page_bails() { #[test] fn service_page_item_bails() { new_test_ext::().execute_with(|| { + let _guard = StorageNoopGuard::default(); let (mut page, _) = full_page::(); let mut weight = WeightCounter::from_limit(10.into_weight()); let overweight_limit = 10.into_weight(); @@ -304,8 +367,42 @@ fn service_page_item_bails() { }); } +/// `bump_service_head` does nothing when called with an insufficient weight limit. +#[test] +fn bump_service_head_bails() { + new_test_ext::().execute_with(|| { + set_weight("bump_service_head", 2.into_weight()); + setup_bump_service_head::(0.into(), 10.into()); + + let _guard = StorageNoopGuard::default(); + let mut meter = WeightCounter::from_limit(1.into_weight()); + assert!(MessageQueue::bump_service_head(&mut meter).is_none()); + assert_eq!(meter.consumed, 0.into_weight()); + }); +} + +#[test] +fn bump_service_head_works() { + new_test_ext::().execute_with(|| { + set_weight("bump_service_head", 2.into_weight()); + let mut meter = WeightCounter::unlimited(); + + assert_eq!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); + assert_eq!(meter.consumed, 2.into_weight()); + + setup_bump_service_head::(0.into(), 1.into()); + + assert_eq!(MessageQueue::bump_service_head(&mut meter), Some(0.into())); + assert_eq!(ServiceHead::::get().unwrap(), 1.into(), "Bumped the head"); + assert_eq!(meter.consumed, 4.into_weight()); + + assert_eq!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); + assert_eq!(meter.consumed, 6.into_weight()); + }); +} + #[test] -fn service_page_consumes_correct_weight() { +fn service_page_item_consumes_correct_weight() { new_test_ext::().execute_with(|| { let mut page = page::(b"weight=3"); let mut weight = WeightCounter::from_limit(10.into_weight()); @@ -329,7 +426,7 @@ fn service_page_consumes_correct_weight() { /// `service_page_item` skips a permanently `Overweight` message and marks it as `unprocessed`. #[test] -fn service_page_skips_perm_overweight_message() { +fn service_page_item_skips_perm_overweight_message() { new_test_ext::().execute_with(|| { let mut page = page::(b"TooMuch"); let mut weight = WeightCounter::from_limit(2.into_weight()); @@ -516,3 +613,41 @@ fn footprint_default_works() { assert_eq!(MessageQueue::footprint(origin), Default::default()); }) } + +#[test] +fn execute_overweight_works() { + new_test_ext::().execute_with(|| { + set_weight("bump_service_head", 1.into_weight()); + set_weight("service_queue_base", 1.into_weight()); + set_weight("service_page_base_completion", 1.into_weight()); + set_weight("process_message_payload", 1.into_weight()); + + // Enqueue a message + let origin = MessageOrigin::Here; + MessageQueue::enqueue_message(msg(&"weight=6"), origin); + // Load the current book + let book = BookStateFor::::get(&origin); + assert_eq!(book.message_count, 1); + + // Mark the message as permanently overweight. + assert_eq!(MessageQueue::service_queues(5.into_weight()), 5.into_weight()); + assert_last_event::( + Event::OverweightEnqueued { + hash: ::Hashing::hash(b"weight=6"), + origin: MessageOrigin::Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + // Now try to execute it. + let consumed = MessageQueue::do_execute_overweight(origin, 0, 0, 7.into_weight()).unwrap(); + // There is no message left in the book. + let book = BookStateFor::::get(&origin); + assert_eq!(book.message_count, 0); + // Doing it again will error. + let consumed = + MessageQueue::do_execute_overweight(origin, 0, 0, 60.into_weight()).unwrap_err(); // TODO not 60 + // here + }); +} From c99f26d9aff33a827bba933834e833899a5f28c3 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Nov 2022 17:44:44 +0100 Subject: [PATCH 056/110] Use weight metering functions Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 20f4e5204d8dc..ea6186d25b0b6 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -827,7 +827,6 @@ impl Pallet { debug_assert!(book_state.count > 0, "completing a page implies there are pages"); book_state.count.saturating_dec(); } else { - // TODO we only benchmark the `true` case; assuming that it is more expensive. Pages::::insert(&origin, page_index, page); } (total_processed, status) @@ -936,6 +935,10 @@ impl Pallet { weight: &mut WeightCounter, overweight_limit: Weight, ) -> MessageExecutionStatus { + if !weight.check_accrue(T::WeightInfo::process_message_payload()) { + return MessageExecutionStatus::InsufficientWeight + } + let hash = T::Hashing::hash(message); use ProcessMessageError::Overweight; match T::MessageProcessor::process_message(message, origin.clone(), weight.remaining()) { @@ -1051,12 +1054,17 @@ impl ServiceQueues for Pallet { weight_limit: Weight, (message_origin, page, index): Self::OverweightMessageAddress, ) -> Result { - Pallet::::do_execute_overweight(message_origin, page, index, weight_limit).map_err(|e| { - match e { + let mut weight = WeightCounter::from_limit(weight_limit); + if !weight.check_accrue(T::WeightInfo::execute_overweight()) { + return Err(ExecuteOverweightError::InsufficientWeight) + } + + Pallet::::do_execute_overweight(message_origin, page, index, weight.remaining()).map_err( + |e| match e { Error::::InsufficientWeight => ExecuteOverweightError::InsufficientWeight, _ => ExecuteOverweightError::NotFound, - } - }) + }, + ) } } From 7bbb8630ac8e6f3fcd59d16aceab0045580e0453 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Nov 2022 17:57:58 +0100 Subject: [PATCH 057/110] weightInfo::process_message_payload is gone Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 5 +- frame/message-queue/src/lib.rs | 10 +-- frame/message-queue/src/mock.rs | 9 --- frame/message-queue/src/tests.rs | 10 +-- frame/message-queue/src/weights.rs | 98 +++++++++++-------------- 5 files changed, 56 insertions(+), 76 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 3a2f2048df329..215d88a1a1210 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -138,7 +138,8 @@ benchmarks! { execute_overweight { // Mock the storage let origin: MessageOriginOf = 0.into(); - let (mut page, msgs) = full_page::(); + let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; + let mut page = page::(&msg); page.skip_first(false); // One skipped un-processed overweight message. let book = book_for::(&page); Pages::::insert(&origin, 0, &page); @@ -147,7 +148,7 @@ benchmarks! { }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0u32, 0u32.into(), Weight::MAX) verify { assert_last_event::(Event::Processed { - hash: T::Hashing::hash(&0u32.encode()), origin: 0.into(), + hash: T::Hashing::hash(&msg), origin: 0.into(), weight_used: Weight::from_parts(1, 1), success: true }.into()); } diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index ea6186d25b0b6..809747b6bf403 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -707,7 +707,7 @@ impl Pallet { book_state.count.saturating_dec(); book_state.message_count.saturating_reduce(page.remaining.into()); book_state.size.saturating_reduce(page.remaining_size.into()); - BookStateFor::::insert(origin, book_state); + BookStateFor::::insert(&origin, &book_state); T::QueueChangeHandler::on_queue_changed( origin.clone(), book_state.message_count, @@ -927,6 +927,10 @@ impl Pallet { } /// Process a single message. + /// + /// The base weight of this function needs to be accounted for by the caller. `weight` is the + /// remaining weight to process the message. `overweight_limit` is the maximum weight that a + /// message can ever consume. Messages above this limit are marked as permanently overweight. fn process_message_payload( origin: MessageOriginOf, page_index: PageIndex, @@ -935,10 +939,6 @@ impl Pallet { weight: &mut WeightCounter, overweight_limit: Weight, ) -> MessageExecutionStatus { - if !weight.check_accrue(T::WeightInfo::process_message_payload()) { - return MessageExecutionStatus::InsufficientWeight - } - let hash = T::Hashing::hash(message); use ProcessMessageError::Overweight; match T::MessageProcessor::process_message(message, origin.clone(), weight.remaining()) { diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index c9ef9f824bd41..2e9b6c4a4fc24 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -130,12 +130,6 @@ impl crate::weights::WeightInfo for MockedWeightInfo { fn service_queue_base() -> Weight { WeightForCall::get().get("service_queue_base").copied().unwrap_or_default() } - fn service_page_process_message() -> Weight { - WeightForCall::get() - .get("service_page_process_message") - .copied() - .unwrap_or_default() - } fn bump_service_head() -> Weight { WeightForCall::get().get("bump_service_head").copied().unwrap_or_default() } @@ -145,9 +139,6 @@ impl crate::weights::WeightInfo for MockedWeightInfo { fn ready_ring_unknit() -> Weight { WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() } - fn process_message_payload() -> Weight { - WeightForCall::get().get("process_message_payload").copied().unwrap_or_default() - } } parameter_types! { diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 88ecec175465e..f806cfe83a4b6 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -273,7 +273,6 @@ fn service_page_works() { new_test_ext::().execute_with(|| { set_weight("service_page_base_completion", 2.into_weight()); set_weight("service_page_item", 3.into_weight()); - set_weight("process_message_payload", 4.into_weight()); let (page, mut msgs) = full_page::(); assert!(msgs >= 10, "pre-condition: need at least 10 msgs per page"); @@ -288,7 +287,7 @@ fn service_page_works() { // Enough weight to process `process` messages. let mut meter = - WeightCounter::from_limit(((2 + (3 + 4 + 1) * process) as u64).into_weight()); + WeightCounter::from_limit(((2 + (3 + 1) * process) as u64).into_weight()); System::reset_events(); let (processed, status) = crate::Pallet::::service_page(&Here, &mut book, &mut meter, Weight::MAX); @@ -620,7 +619,6 @@ fn execute_overweight_works() { set_weight("bump_service_head", 1.into_weight()); set_weight("service_queue_base", 1.into_weight()); set_weight("service_page_base_completion", 1.into_weight()); - set_weight("process_message_payload", 1.into_weight()); // Enqueue a message let origin = MessageOrigin::Here; @@ -630,7 +628,7 @@ fn execute_overweight_works() { assert_eq!(book.message_count, 1); // Mark the message as permanently overweight. - assert_eq!(MessageQueue::service_queues(5.into_weight()), 5.into_weight()); + assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); assert_last_event::( Event::OverweightEnqueued { hash: ::Hashing::hash(b"weight=6"), @@ -642,12 +640,12 @@ fn execute_overweight_works() { ); // Now try to execute it. let consumed = MessageQueue::do_execute_overweight(origin, 0, 0, 7.into_weight()).unwrap(); + assert_eq!(consumed, 6.into_weight()); // There is no message left in the book. let book = BookStateFor::::get(&origin); assert_eq!(book.message_count, 0); // Doing it again will error. let consumed = - MessageQueue::do_execute_overweight(origin, 0, 0, 60.into_weight()).unwrap_err(); // TODO not 60 - // here + MessageQueue::do_execute_overweight(origin, 0, 0, 60.into_weight()).unwrap_err(); }); } diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index 885de8dd9ad59..92ac1d432f6bc 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -37,11 +37,9 @@ pub trait WeightInfo { fn service_page_base_no_completion() -> Weight; fn ready_ring_unknit() -> Weight; fn service_page_item() -> Weight; - fn service_page_process_message() -> Weight; fn bump_service_head() -> Weight; fn reap_page() -> Weight; fn execute_overweight() -> Weight; - fn process_message_payload() -> Weight; } /// Weights for pallet_message_queue using the Substrate node and recommended hardware. @@ -49,64 +47,60 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_489 nanoseconds. - Weight::from_ref_time(3_611_000 as u64) + // Minimum execution time: 3_677 nanoseconds. + Weight::from_ref_time(3_838_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_completion() -> Weight { - // Minimum execution time: 5_513 nanoseconds. - Weight::from_ref_time(5_680_000 as u64) + // Minimum execution time: 5_431 nanoseconds. + Weight::from_ref_time(5_685_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 5_665 nanoseconds. - Weight::from_ref_time(5_758_000 as u64) + // Minimum execution time: 5_598 nanoseconds. + Weight::from_ref_time(5_816_000 as u64) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: MessageQueue BookStateFor (r:2 w:2) // Storage: MessageQueue ServiceHead (r:1 w:1) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 5_913 nanoseconds. - Weight::from_ref_time(6_056_000 as u64) + // Minimum execution time: 5_926 nanoseconds. + Weight::from_ref_time(6_208_000 as u64) .saturating_add(T::DbWeight::get().reads(3 as u64)) .saturating_add(T::DbWeight::get().writes(3 as u64)) } fn service_page_item() -> Weight { - // Minimum execution time: 8_354 nanoseconds. - Weight::from_ref_time(9_730_000 as u64) - } - fn service_page_process_message() -> Weight { - // Minimum execution time: 28 nanoseconds. - Weight::from_ref_time(31_000 as u64) + // Minimum execution time: 74_873 nanoseconds. + Weight::from_ref_time(100_821_000 as u64) } + // Storage: MessageQueue ServiceHead (r:1 w:1) + // Storage: MessageQueue BookStateFor (r:1 w:0) fn bump_service_head() -> Weight { - // Minimum execution time: 28 nanoseconds. - Weight::from_ref_time(33_000 as u64) + // Minimum execution time: 8_222 nanoseconds. + Weight::from_ref_time(9_503_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 26_075 nanoseconds. - Weight::from_ref_time(36_499_000 as u64) + // Minimum execution time: 27_052 nanoseconds. + Weight::from_ref_time(36_827_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) .saturating_add(T::DbWeight::get().writes(2 as u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:0) + // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight() -> Weight { - // Minimum execution time: 29_557 nanoseconds. - Weight::from_ref_time(41_779_000 as u64) + // Minimum execution time: 92_801 nanoseconds. + Weight::from_ref_time(93_715_000 as u64) .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - fn process_message_payload() -> Weight { - // Minimum execution time: 75_445 nanoseconds. - Weight::from_ref_time(75_978_000 as u64) + .saturating_add(T::DbWeight::get().writes(2 as u64)) } } @@ -114,63 +108,59 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_489 nanoseconds. - Weight::from_ref_time(3_611_000 as u64) + // Minimum execution time: 3_677 nanoseconds. + Weight::from_ref_time(3_838_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_completion() -> Weight { - // Minimum execution time: 5_513 nanoseconds. - Weight::from_ref_time(5_680_000 as u64) + // Minimum execution time: 5_431 nanoseconds. + Weight::from_ref_time(5_685_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 5_665 nanoseconds. - Weight::from_ref_time(5_758_000 as u64) + // Minimum execution time: 5_598 nanoseconds. + Weight::from_ref_time(5_816_000 as u64) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: MessageQueue BookStateFor (r:2 w:2) // Storage: MessageQueue ServiceHead (r:1 w:1) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 5_913 nanoseconds. - Weight::from_ref_time(6_056_000 as u64) + // Minimum execution time: 5_926 nanoseconds. + Weight::from_ref_time(6_208_000 as u64) .saturating_add(RocksDbWeight::get().reads(3 as u64)) .saturating_add(RocksDbWeight::get().writes(3 as u64)) } fn service_page_item() -> Weight { - // Minimum execution time: 8_354 nanoseconds. - Weight::from_ref_time(9_730_000 as u64) - } - fn service_page_process_message() -> Weight { - // Minimum execution time: 28 nanoseconds. - Weight::from_ref_time(31_000 as u64) + // Minimum execution time: 74_873 nanoseconds. + Weight::from_ref_time(100_821_000 as u64) } + // Storage: MessageQueue ServiceHead (r:1 w:1) + // Storage: MessageQueue BookStateFor (r:1 w:0) fn bump_service_head() -> Weight { - // Minimum execution time: 28 nanoseconds. - Weight::from_ref_time(33_000 as u64) + // Minimum execution time: 8_222 nanoseconds. + Weight::from_ref_time(9_503_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 26_075 nanoseconds. - Weight::from_ref_time(36_499_000 as u64) + // Minimum execution time: 27_052 nanoseconds. + Weight::from_ref_time(36_827_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) .saturating_add(RocksDbWeight::get().writes(2 as u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:0) + // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight() -> Weight { - // Minimum execution time: 29_557 nanoseconds. - Weight::from_ref_time(41_779_000 as u64) + // Minimum execution time: 92_801 nanoseconds. + Weight::from_ref_time(93_715_000 as u64) .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - fn process_message_payload() -> Weight { - // Minimum execution time: 75_445 nanoseconds. - Weight::from_ref_time(75_978_000 as u64) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) } } From 1b114dc001a697c44c94ec5b1f12ba43a5163185 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Nov 2022 18:16:31 +0100 Subject: [PATCH 058/110] Add defensive_saturating_accrue Signed-off-by: Oliver Tale-Yazdi --- primitives/weights/src/weight_meter.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/primitives/weights/src/weight_meter.rs b/primitives/weights/src/weight_meter.rs index d03e72968bb09..17c5da1502e9e 100644 --- a/primitives/weights/src/weight_meter.rs +++ b/primitives/weights/src/weight_meter.rs @@ -71,6 +71,12 @@ impl WeightMeter { time.max(pov) } + /// Consume some weight and defensively fail if it is over the limit. Saturate in any case. + pub fn defensive_saturating_accrue(&mut self, w: Weight) { + self.consumed.saturating_accrue(w); + debug_assert!(self.consumed.all_lte(self.limit), "Weight counter overflow"); + } + /// Consume the given weight after checking that it can be consumed. Otherwise do nothing. pub fn check_accrue(&mut self, w: Weight) -> bool { self.consumed.checked_add(&w).map_or(false, |test| { From efdbc3ad063a45b418ac354fcc788808e15253da Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Nov 2022 18:16:52 +0100 Subject: [PATCH 059/110] Rename WeightCounter to WeightMeter Ctr+Shift+H should do the trick. Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 10 +++--- frame/message-queue/src/lib.rs | 18 +++++----- frame/message-queue/src/tests.rs | 22 ++++++------ frame/scheduler/src/lib.rs | 2 +- primitives/weights/src/lib.rs | 47 ------------------------- 5 files changed, 26 insertions(+), 73 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 215d88a1a1210..4cbf462b4dec7 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -35,7 +35,7 @@ benchmarks! { // `service_queue` without any page processing or unknitting. service_queue_base { - let mut meter = WeightCounter::unlimited(); + let mut meter = WeightMeter::max_limit(); }: { MessageQueue::::service_queue(0u32.into(), &mut meter, Weight::MAX) } @@ -46,7 +46,7 @@ benchmarks! { let page = PageOf::::default(); Pages::::insert(&origin, 0, &page); let mut book_state = single_page_book::(); - let mut meter = WeightCounter::unlimited(); + let mut meter = WeightMeter::max_limit(); let limit = Weight::MAX; }: { MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) @@ -61,7 +61,7 @@ benchmarks! { page.remaining = 1.into(); Pages::::insert(&origin, 0, &page); let mut book_state = single_page_book::(); - let mut meter = WeightCounter::unlimited(); + let mut meter = WeightMeter::max_limit(); let limit = Weight::MAX; }: { MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) @@ -85,7 +85,7 @@ benchmarks! { let mut page = page::(&msg.clone()); let mut book = book_for::(&page); assert!(page.peek_first().is_some(), "There is one message"); - let mut weight = WeightCounter::unlimited(); + let mut weight = WeightMeter::max_limit(); }: { let status = MessageQueue::::service_page_item(&0u32.into(), 0, &mut book, &mut page, &mut weight, Weight::MAX); assert_eq!(status, PageExecutionStatus::Partial); @@ -103,7 +103,7 @@ benchmarks! { // Worst case for calling `bump_service_head`. bump_service_head { setup_bump_service_head::(0.into(), 10.into()); - let mut weight = WeightCounter::unlimited(); + let mut weight = WeightMeter::max_limit(); }: { MessageQueue::::bump_service_head(&mut weight); } verify { diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 809747b6bf403..598681875a68c 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -44,7 +44,7 @@ use sp_runtime::{ SaturatedConversion, Saturating, }; use sp_std::{fmt::Debug, ops::Deref, prelude::*, vec}; -use sp_weights::WeightCounter; +use sp_weights::WeightMeter; pub use weights::WeightInfo; /// Type for identifying a page. @@ -547,7 +547,7 @@ impl Pallet { /// Tries to bump the current `ServiceHead` to the next ready queue. /// /// Returns the current head if it got be bumped and `None` otherwise. - fn bump_service_head(weight: &mut WeightCounter) -> Option> { + fn bump_service_head(weight: &mut WeightMeter) -> Option> { if !weight.check_accrue(T::WeightInfo::bump_service_head()) { return None } @@ -634,7 +634,7 @@ impl Pallet { ); ensure!(!is_processed, Error::::AlreadyProcessed); use MessageExecutionStatus::*; - let mut weight_counter = WeightCounter::from_limit(weight_limit); + let mut weight_counter = WeightMeter::from_limit(weight_limit); match Self::process_message_payload( origin.clone(), page_index, @@ -723,7 +723,7 @@ impl Pallet { /// execute are deemed overweight and ignored. fn service_queue( origin: MessageOriginOf, - weight: &mut WeightCounter, + weight: &mut WeightMeter, overweight_limit: Weight, ) -> (bool, Option>) { if !weight.check_accrue( @@ -777,7 +777,7 @@ impl Pallet { fn service_page( origin: &MessageOriginOf, book_state: &mut BookStateOf, - weight: &mut WeightCounter, + weight: &mut WeightMeter, overweight_limit: Weight, ) -> (u32, PageExecutionStatus) { use PageExecutionStatus::*; @@ -838,7 +838,7 @@ impl Pallet { page_index: PageIndex, book_state: &mut BookStateOf, page: &mut PageOf, - weight: &mut WeightCounter, + weight: &mut WeightMeter, overweight_limit: Weight, ) -> PageExecutionStatus { // This ugly pre-checking is needed for the invariant @@ -936,7 +936,7 @@ impl Pallet { page_index: PageIndex, message_index: T::Size, message: &[u8], - weight: &mut WeightCounter, + weight: &mut WeightMeter, overweight_limit: Weight, ) -> MessageExecutionStatus { let hash = T::Hashing::hash(message); @@ -1014,7 +1014,7 @@ impl ServiceQueues for Pallet { fn service_queues(weight_limit: Weight) -> Weight { // The maximum weight that processing a single message may take. let overweight_limit = weight_limit; - let mut weight = WeightCounter::from_limit(weight_limit); + let mut weight = WeightMeter::from_limit(weight_limit); let mut next = match Self::bump_service_head(&mut weight) { Some(h) => h, @@ -1054,7 +1054,7 @@ impl ServiceQueues for Pallet { weight_limit: Weight, (message_origin, page, index): Self::OverweightMessageAddress, ) -> Result { - let mut weight = WeightCounter::from_limit(weight_limit); + let mut weight = WeightMeter::from_limit(weight_limit); if !weight.check_accrue(T::WeightInfo::execute_overweight()) { return Err(ExecuteOverweightError::InsufficientWeight) } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index f806cfe83a4b6..f227de11eee35 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -241,7 +241,7 @@ fn service_queue_bails() { // Not enough weight for `service_queue_base`. new_test_ext::().execute_with(|| { set_weight("service_queue_base", 2.into_weight()); - let mut meter = WeightCounter::from_limit(1.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); assert!(meter.consumed.is_zero()); @@ -249,7 +249,7 @@ fn service_queue_bails() { // Not enough weight for `ready_ring_unknit`. new_test_ext::().execute_with(|| { set_weight("ready_ring_unknit", 2.into_weight()); - let mut meter = WeightCounter::from_limit(1.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); assert!(meter.consumed.is_zero()); @@ -259,7 +259,7 @@ fn service_queue_bails() { set_weight("service_queue_base", 2.into_weight()); set_weight("ready_ring_unknit", 2.into_weight()); - let mut meter = WeightCounter::from_limit(3.into_weight()); + let mut meter = WeightMeter::from_limit(3.into_weight()); assert_storage_noop!(MessageQueue::service_queue(0.into(), &mut meter, Weight::MAX)); assert!(meter.consumed.is_zero()); }); @@ -287,7 +287,7 @@ fn service_page_works() { // Enough weight to process `process` messages. let mut meter = - WeightCounter::from_limit(((2 + (3 + 1) * process) as u64).into_weight()); + WeightMeter::from_limit(((2 + (3 + 1) * process) as u64).into_weight()); System::reset_events(); let (processed, status) = crate::Pallet::::service_page(&Here, &mut book, &mut meter, Weight::MAX); @@ -310,7 +310,7 @@ fn service_page_bails() { // Not enough weight for `service_page_base_completion`. new_test_ext::().execute_with(|| { set_weight("service_page_base_completion", 2.into_weight()); - let mut meter = WeightCounter::from_limit(1.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); let (page, _) = full_page::(); let mut book = book_for::(&page); @@ -327,7 +327,7 @@ fn service_page_bails() { // Not enough weight for `service_page_base_no_completion`. new_test_ext::().execute_with(|| { set_weight("service_page_base_no_completion", 2.into_weight()); - let mut meter = WeightCounter::from_limit(1.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); let (page, _) = full_page::(); let mut book = book_for::(&page); @@ -348,7 +348,7 @@ fn service_page_item_bails() { new_test_ext::().execute_with(|| { let _guard = StorageNoopGuard::default(); let (mut page, _) = full_page::(); - let mut weight = WeightCounter::from_limit(10.into_weight()); + let mut weight = WeightMeter::from_limit(10.into_weight()); let overweight_limit = 10.into_weight(); set_weight("service_page_item", 11.into_weight()); @@ -374,7 +374,7 @@ fn bump_service_head_bails() { setup_bump_service_head::(0.into(), 10.into()); let _guard = StorageNoopGuard::default(); - let mut meter = WeightCounter::from_limit(1.into_weight()); + let mut meter = WeightMeter::from_limit(1.into_weight()); assert!(MessageQueue::bump_service_head(&mut meter).is_none()); assert_eq!(meter.consumed, 0.into_weight()); }); @@ -384,7 +384,7 @@ fn bump_service_head_bails() { fn bump_service_head_works() { new_test_ext::().execute_with(|| { set_weight("bump_service_head", 2.into_weight()); - let mut meter = WeightCounter::unlimited(); + let mut meter = WeightMeter::max_limit(); assert_eq!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); assert_eq!(meter.consumed, 2.into_weight()); @@ -404,7 +404,7 @@ fn bump_service_head_works() { fn service_page_item_consumes_correct_weight() { new_test_ext::().execute_with(|| { let mut page = page::(b"weight=3"); - let mut weight = WeightCounter::from_limit(10.into_weight()); + let mut weight = WeightMeter::from_limit(10.into_weight()); let overweight_limit = 0.into_weight(); set_weight("service_page_item", 2.into_weight()); @@ -428,7 +428,7 @@ fn service_page_item_consumes_correct_weight() { fn service_page_item_skips_perm_overweight_message() { new_test_ext::().execute_with(|| { let mut page = page::(b"TooMuch"); - let mut weight = WeightCounter::from_limit(2.into_weight()); + let mut weight = WeightMeter::from_limit(2.into_weight()); let overweight_limit = 0.into_weight(); set_weight("service_page_item", 2.into_weight()); diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index e5b895d977356..ac5e43a6d3d6b 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -81,7 +81,7 @@ use sp_runtime::{ BoundedVec, RuntimeDebug, }; use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*}; -use sp_weights::WeightCounter; +use sp_weights::WeightMeter; pub use weights::WeightInfo; /// Just a simple index for naming period tasks. diff --git a/primitives/weights/src/lib.rs b/primitives/weights/src/lib.rs index c77dd16989f8c..af9e730fbfefd 100644 --- a/primitives/weights/src/lib.rs +++ b/primitives/weights/src/lib.rs @@ -224,53 +224,6 @@ where } } -/// Tracks the already consumed weight and a hard limit for the maximal consumable weight. -#[derive(Debug, Clone)] -pub struct WeightCounter { - /// The already consumed weight. - pub consumed: Weight, - /// The maximal consumable weight. - pub limit: Weight, -} - -impl WeightCounter { - /// Creates [`Self`] with a maximum limit for the consumable weight. - pub fn from_limit(limit: Weight) -> Self { - Self { consumed: Weight::zero(), limit } - } - - pub fn unlimited() -> Self { - Self { consumed: Weight::zero(), limit: Weight::MAX } - } - - /// The remaining weight that can be consumed. - pub fn remaining(&self) -> Weight { - self.limit.saturating_sub(self.consumed) - } - - /// Consume some weight and defensively fail if it is over the limit. Saturate in any case. - pub fn defensive_saturating_accrue(&mut self, w: Weight) { - self.consumed.saturating_accrue(w); - debug_assert!(self.consumed.all_lte(self.limit), "Weight counter overflow"); - } - - /// Check if the given weight can be consumed. Do nothing if not. - pub fn check_accrue(&mut self, w: Weight) -> bool { - let test = self.consumed.saturating_add(w); - if test.any_gt(self.limit) { - false - } else { - self.consumed = test; - true - } - } - - /// Check if the given weight can be consumed. - pub fn can_accrue(&self, w: Weight) -> bool { - self.consumed.saturating_add(w).all_lte(self.limit) - } -} - #[cfg(test)] #[allow(dead_code)] mod tests { From 5059afa904d3e0f5f8dd531dd2466290c5d588fc Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 15 Nov 2022 18:28:42 +0100 Subject: [PATCH 060/110] Test on_initialize Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/integration_test.rs | 7 ++++--- frame/message-queue/src/tests.rs | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index 3aa50f32f778b..0587ef985e62a 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -87,7 +87,7 @@ impl frame_system::Config for Test { parameter_types! { pub const HeapSize: u32 = 32 * 1024; pub const MaxStale: u32 = 32; - pub const ServiceWeight: Option = Some(Weight::from_parts(10, 10)); + pub static ServiceWeight: Option = Some(Weight::from_parts(100, 100)); } impl Config for Test { @@ -131,7 +131,7 @@ fn stress_test_enqueue_and_service() { let mut rng = rand::rngs::StdRng::seed_from_u64(2); new_test_ext::().execute_with(|| { - for _ in 0..blocks { + for block in 0..blocks { let num_queues = rng.gen_range(1..max_queues); let mut num_messages = 0; let mut total_msg_len = 0; @@ -165,9 +165,10 @@ fn stress_test_enqueue_and_service() { // We have to use at least 1 here since otherwise messages will marked as // permanently overweight. let weight = rng.gen_range(1..=msgs_remaining).into_weight(); + ServiceWeight::set(Some(weight)); log::info!("Processing {} messages...", weight.ref_time()); - let consumed = MessageQueue::service_queues(weight); + let consumed = MessageQueue::on_initialize(block); if consumed != weight { panic!( "consumed != weight: {} != {}\n{}", diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index f227de11eee35..8db0a3975df4c 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -286,8 +286,7 @@ fn service_page_works() { msgs -= process; // Enough weight to process `process` messages. - let mut meter = - WeightMeter::from_limit(((2 + (3 + 1) * process) as u64).into_weight()); + let mut meter = WeightMeter::from_limit(((2 + (3 + 1) * process) as u64).into_weight()); System::reset_events(); let (processed, status) = crate::Pallet::::service_page(&Here, &mut book, &mut meter, Weight::MAX); From 4e55aa18877a1329479188fbdf8a58469142d3fb Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 21 Nov 2022 15:04:20 +0100 Subject: [PATCH 061/110] Add module docs Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 174 +++++++++++++++++++++++- frame/message-queue/src/mock_helpers.rs | 2 +- 2 files changed, 173 insertions(+), 3 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 598681875a68c..9390e6886c65f 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -14,7 +14,166 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Pallet to handle XCM message queuing. +//! # Generalized Message Queue Pallet +//! +//! Provides generalized message queuing and processing capabilities on a per-queue basis for +//! arbitrary use-cases. +//! +//! # Design Goals +//! +//! - Minimal assumptions about `Message`s and `MessageOrigin`s. Both should be MEL bounded blobs. +//! This ensures the generality and reusability of the pallet. +//! - Well known and tightly limited pre-dispatch PoV weights, especially for message execution. +//! This is paramount for the success of the pallet since message execution is done in +//! `on_initialize` which must _never_ under-estimate its PoV weight. It also needs a frugal PoV +//! footprint since PoV is scare and this is (possibly) done in every block. This must also hold in +//! the presence of unpredictable message size distributions. +//! - Usable as XCMP, DMP and UMP message/dispatch queue - possibly through adapter types. +//! +//! # Design +//! +//! The pallet has means to enqueue, store and process messages. This is implemented by having +//! *queues* which store enqueued messages and can be *served* to process said messages. A queue is +//! identified by its origin in the `BookStateFor`. Each message has an origin which defines into +//! which queue it will be stored. Messages are stored by being appended to the last [`Page`] of a +//! book. Each book keeps track of its pages by indexing `Pages`. The `ReadyRing` contains all +//! queues which hold at least one unprocessed message and are thereby *ready* to be serviced. The +//! `ServiceHead` indicates which *ready* queue is the next to be serviced. +//! The pallet implements [`frame_support::traits::EnqueueMessage`], +//! [`frame_support::traits::ServiceQueues`] and has [`frame_support::traits::ProcessMessage`] and +//! [`OnQueueChanged`] hooks to communicate with the outside world. +//! +//! NOTE: The storage items are not linked since they are not public. +//! +//! **Message Execution** +//! +//! Executing a message is offloaded the [`Config::MessageProcessor`] which contains the actual +//! logic of how to handle the message since they are blobs. A message can be temporarily or +//! permanently overweight. The pallet will perpetually try to execute a temporarily overweight +//! message. A permanently overweight message is skipped and must be executed manually. +//! +//! **Pagination** +//! +//! Queues are stored in a *paged* manner by splitting their messages into [`Page`]s. This results +//! in a lot of complexity when implementing the pallet but is completely necessary to archive the +//! second #[Design Goal](design-goals). The problem comes from the fact a message can *possibly* be +//! quite large, lets say 64KiB. This then results in a *MEL* of at least 64KiB which results in a +//! PoV of at least 64KiB. Now we have the assumption that most messages are much shorter than their +//! maximum allowed length. This would result in most messages having a pre-dispatch PoV size which +//! is much larger than their post-dispatch PoV size, possibly by a factor of thousand. Disregarding +//! this observation would cripple the processing power of the pallet since it cannot straighten out +//! this discrepancy at runtime. The implemented solution tightly packs multiple messages into a +//! page, which allows for a post-dispatch PoV size which is much closer to the worst case +//! pre-dispatch PoV size. To be more formal; the ratio between *Actual Encode Length* and *Max +//! Encoded Length* per message: `AEL / MEL` is much closer to one than without the optimization. +//! +//! NOTE: The enqueuing and storing of messages are only a means to implement the processing and are +//! not goals per se. +//! +//! **Weight Metering** +//! +//! The pallet utilizes the [`sp_weights::WeightMeter`] to manually track its consumption to always +//! stay within the required limit. This implies that the message processor hook can calculate the +//! weight of a message without executing it. This restricts the possible use-cases but is necessary +//! since the pallet runs in `on_initialize` which has a hard weight limit. The weight meter is used +//! in a way that `can_accrue` and `check_accrue` are always used to check the remaining weight of +//! an operation before committing to it. The process of exiting due to insufficient weight is +//! termed "bailing". +//! +//! # Scenario: Message enqueuing +//! +//! A message `m` is enqueued for origin `o` into queue `Q[o]` through +//! [`frame_support::traits::EnqueueMessage::enqueue_message`]`(m, o)`. +//! +//! First the queue is either loaded if it exists or otherwise created with empty default values. +//! The message is then inserted to the queue by appended it into its last `Page` or by creating a +//! new `Page` just for `m` if it does not fit in there. The number of messages in the `Book` is +//! incremented. +//! +//! `Q[o]` is now *ready* which will eventually result in `m` being processed. +//! +//! # Scenario: Message processing +//! +//! The pallet runs each block in `on_initialize` or when being manually called through +//! [`frame_support::traits::ServiceQueues::service_queues`]. +//! +//! First it tries to "rotate" the `ReadyRing` by one through advancing the `ServiceHead` to the +//! next *ready* queue. It then starts to service this queue by servicing as many pages of it as +//! possible. Servicing a page means to execute as many message of it as possible. Each executed +//! message is marked as *processed* if the [`Config::MessageProcessor`] return Ok. An event +//! [`Event::Processed`] is emitted afterwards. It is possible that the weight limit of the pallet +//! will never allow a specific message to be executed. In this case it remains as unprocessed and +//! is skipped. This process stops if either there are no more messages in the queue of the +//! remaining weight became insufficient to service this queue. If there is enough weight it tries +//! to advance to the next *ready* queue and service it. This continues until there are no more +//! queues on which it can make progress or not enough weight to check that. +//! +//! # Scenario: Overweight execution +//! +//! A permanently over-weight message which was skipped by the message processing will never be +//! executed automatically through `on_initialize` nor by calling +//! [`frame_support::traits::ServiceQueues::service_queues`]. +//! +//! Manual intervention in the form of +//! [`frame_support::traits::ServiceQueues::execute_overweight`] is necessary. Overweight messages +//! emit an [`Event::OverweightEnqueued`] event which can be used to extract the arguments for +//! manual execution. This only works on permanently overweight messages. +//! +//! # Terminology +//! +//! - `Message`: A blob of data into which the pallet has no introspection, defined as +//! [`BoundedSlice>`]. The message length is limited by [`MaxMessageLenOf`] +//! which is calculated implicitly from [`Config::HeapSize`], the MEL of the `MessageOrigin` and +//! the MEL of an [`ItemHeader`]. +//! - `MessageOrigin`: A generic *origin* of a message, defined as [`MessageOriginOf`]. The +//! requirements for it are kept minimal to remain as generic as possible. The type is defined in +//! [`frame_support::traits::ProcessMessage::Origin`]. +//! - `Page`: An array of `Message`s, see [`Page`]. Can never be empty. +//! - `Book`: A list of `Page`s, see [`BookState`]. Can be empty. +//! - `Queue`: A `Book` together with an `MessageOrigin` which can be part of the `ReadyRing`. Can +//! be empty. +//! - `ReadyRing`: A double-linked list which contains all *ready* `Queue`s. It chains together the +//! queues via their `ready_neighbours` fields. A `Queue` is *ready* if it contains at least one +//! `Message` which can be processed. Can be empty. +//! - `ServiceHead`: A pointer into the `ReadyRing` to the next `Queue` to be serviced. +//! - (`un`)`processed`: A message is marked as *processed* after it was executed by the pallet. A +//! message which was either: not yet executed or could not be executed remains as `unprocessed` +//! which is the default state for a message after being enqueued. +//! - `knitting`/`unknitting`: The means of adding or removing a `Queue` from the `ReadyRing`. +//! - `MEL`: The Max Encoded Length of a type, see [`codec::MaxEncodedLen`]. +//! +//! # Properties +//! +//! **Liveness - Enqueueing** +//! +//! It is always possible to enqueue any message for any `MessageOrigin`. TODO is this true?! +//! +//! **Liveness - Processing** +//! +//! `on_initialize` always respects its finite weight-limit. +//! +//! **Progress - Enqueueing** +//! +//! An enqueued message immediately becomes *unprocessed* and thereby eligible for execution. +//! +//! **Progress - Processing** +//! +//! The pallet will execute at least one unprocessed message per block, if there is any. Ensuring +//! this property needs careful consideration of the concrete weights, since it is possible that the +//! weight limit of `on_initialize` never allows for the execution of even one message; trivially if +//! the limit is set to zero. `integrity_test` can be used to ensure that this property holds. +//! +//! **Fairness - Enqueuing** +//! +//! Enqueueing a message for a specific `MessageOrigin` does not influence the ability to enqueue a +//! message for the same of any other `MessageOrigin`; guaranteed by **Liveness - Enqueueing**. +//! +//! **Fairness - Processing** +//! +//! The average amount of weight available for message processing is the same for each queue if the +//! number of queues is constant. Creating a new queue must therefore be, possibly economically, +//! expensive. Currently this is archived by having one queue per para-chain/thread, which keeps the +//! number of queues within `O(n)` and should be "good enough". #![cfg_attr(not(feature = "std"), no_std)] @@ -249,7 +408,7 @@ impl Default for BookState { } } -/// Notifies the implementor of changes to a queue. +/// Notifies the implementor of changes to a queue. Mainly when messages got added or removed. pub trait OnQueueChanged { /// The queue `id` changed and now has these properties. fn on_queue_changed(id: Id, items_count: u32, items_size: u32); @@ -434,6 +593,7 @@ pub mod pallet { } } +/// One link of the double-linked ready ring which contains all *ready* queues. pub struct ReadyRing { first: Option>, next: Option>, @@ -973,6 +1133,7 @@ impl Pallet { } } +/// Provides a [`sp_core::Get`] to access the `MEL` of a [`codec::MaxEncodedLen`] type. pub struct MaxEncodedLenOf(sp_std::marker::PhantomData); impl Get for MaxEncodedLenOf { fn get() -> u32 { @@ -980,6 +1141,7 @@ impl Get for MaxEncodedLenOf { } } +/// Calculates the maximum message length and exposed it through the [`codec::MaxEncodedLen`] trait. pub struct MaxMessageLen( sp_std::marker::PhantomData<(Origin, Size, HeapSize)>, ); @@ -993,14 +1155,22 @@ impl, HeapSize: Get } } +/// The maximal message length of this pallet. pub type MaxMessageLenOf = MaxMessageLen, ::Size, ::HeapSize>; +/// The maximal encoded origin length of this pallet. pub type MaxOriginLenOf = MaxEncodedLenOf>; +/// The `MessageOrigin` or this pallet. pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; +/// The maximal heap size of this pallet. pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; +/// The [`Page`] of this pallet. pub type PageOf = Page<::Size, ::HeapSize>; +/// The [`BookState`] of this pallet. pub type BookStateOf = BookState>; +/// Converts a [`sp_core::Get`] with returns a type that can be cast into an `u32` into a `Get` +/// which returns an `u32`. pub struct IntoU32(sp_std::marker::PhantomData<(T, O)>); impl, O: Into> Get for IntoU32 { fn get() -> u32 { diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 0aa5da4081e97..15a819ce10db2 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -//! Setup helpers for testing and benchmarking. +//! Std setup helpers for testing and benchmarking. //! //! Cannot be put into mock.rs since benchmarks require no-std and mock.rs is std. From a27593e3b37b082f5d597a3a7253125f9d39586a Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 21 Nov 2022 22:38:34 +0100 Subject: [PATCH 062/110] Remove origin from MaxMessageLen The message origin is not encoded into the heap and does therefore not influence the max message length anymore. Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 4 +--- frame/message-queue/src/tests.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 9390e6886c65f..107a81aca023b 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -123,8 +123,7 @@ //! //! - `Message`: A blob of data into which the pallet has no introspection, defined as //! [`BoundedSlice>`]. The message length is limited by [`MaxMessageLenOf`] -//! which is calculated implicitly from [`Config::HeapSize`], the MEL of the `MessageOrigin` and -//! the MEL of an [`ItemHeader`]. +//! which is calculated from [`Config::HeapSize`] and [`ItemHeader::max_encoded_len()`]. //! - `MessageOrigin`: A generic *origin* of a message, defined as [`MessageOriginOf`]. The //! requirements for it are kept minimal to remain as generic as possible. The type is defined in //! [`frame_support::traits::ProcessMessage::Origin`]. @@ -1150,7 +1149,6 @@ impl, HeapSize: Get { fn get() -> u32 { (HeapSize::get().into()) - .saturating_sub(Origin::max_encoded_len() as u32) .saturating_sub(ItemHeader::::max_encoded_len() as u32) } } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 8db0a3975df4c..0d28531370421 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -559,6 +559,23 @@ fn page_from_message_basic_works() { assert!(MaxOriginLenOf::::get() >= 3, "pre-condition unmet"); assert!(MaxMessageLenOf::::get() >= 3, "pre-condition unmet"); +#[test] +fn page_try_append_message_max_msg_len_works_works() { + use super::integration_test::Test; // Run with larger page size. + + // We start off with an empty page. + let mut page = PageOf::::default(); + // … and append a message with maximum possible length. + let mut msg = vec![123u8; MaxMessageLenOf::::get() as usize]; + // … which works. + page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) + .unwrap(); + // Now we cannot append *anything* since the heap is full. + page.try_append_message::(BoundedSlice::defensive_truncate_from(&[])) + .unwrap_err(); + assert_eq!(page.heap.len(), ::HeapSize::get() as usize); +} + let _page = PageOf::::from_message::(BoundedSlice::defensive_truncate_from(b"MSG")); } From ce8f656f532be9b791a34ade172bb93c493bce52 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 21 Nov 2022 23:18:08 +0100 Subject: [PATCH 063/110] Add BoundedVec::as_slice Signed-off-by: Oliver Tale-Yazdi --- primitives/core/src/bounded/bounded_vec.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/primitives/core/src/bounded/bounded_vec.rs b/primitives/core/src/bounded/bounded_vec.rs index 2f39f3340ce50..11121cb6a4980 100644 --- a/primitives/core/src/bounded/bounded_vec.rs +++ b/primitives/core/src/bounded/bounded_vec.rs @@ -675,6 +675,13 @@ impl> BoundedVec { } } +impl BoundedVec { + /// Return a [`BoundedSlice`] with the content and bound of [`Self`]. + pub fn as_slice(&self) -> BoundedSlice { + BoundedSlice(&self.0[..], PhantomData::default()) + } +} + impl Default for BoundedVec { fn default() -> Self { // the bound cannot be below 0, which is satisfied by an empty vector From 650643059889fabb87472536d9f28c2e5d5e9bba Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 21 Nov 2022 23:19:07 +0100 Subject: [PATCH 064/110] Test Page::{from_message, try_append_message} Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/tests.rs | 100 ++++++++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 7 deletions(-) diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 0d28531370421..3ff9ec8db1d51 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -21,8 +21,8 @@ use crate::{mock::*, *}; -use frame_support::{assert_noop, assert_ok, assert_storage_noop, StorageNoopGuard}; -use rand::{Rng, SeedableRng}; +use frame_support::{assert_noop, assert_ok, assert_storage_noop, bounded_vec, StorageNoopGuard}; +use rand::{rngs::StdRng, Rng, SeedableRng}; #[test] fn mocked_weight_works() { @@ -267,9 +267,9 @@ fn service_queue_bails() { #[test] fn service_page_works() { - use super::integration_test::Test; + use super::integration_test::Test; // Run with larger page size. use MessageOrigin::*; - use PageExecutionStatus::*; // Run with larger page size. + use PageExecutionStatus::*; new_test_ext::().execute_with(|| { set_weight("service_page_base_completion", 2.into_weight()); set_weight("service_page_item", 3.into_weight()); @@ -556,8 +556,54 @@ fn is_complete_works() { #[test] fn page_from_message_basic_works() { - assert!(MaxOriginLenOf::::get() >= 3, "pre-condition unmet"); - assert!(MaxMessageLenOf::::get() >= 3, "pre-condition unmet"); + assert!(MaxMessageLenOf::::get() > 0, "pre-condition unmet"); + let mut msg: BoundedVec> = Default::default(); + msg.bounded_resize(MaxMessageLenOf::::get() as usize, 123); + + let page = PageOf::::from_message::(msg.as_slice()); + assert_eq!(page.remaining, 1); + assert_eq!(page.remaining_size as usize, msg.len()); + assert!(page.first_index == 0 && page.first == 0 && page.last == 0); + + // Verify the content of the heap. + let mut heap = Vec::::new(); + let header = + ItemHeader::<::Size> { payload_len: msg.len() as u32, is_processed: false }; + heap.extend(header.encode()); + heap.extend(msg.deref()); + assert_eq!(page.heap, heap); +} + +#[test] +fn page_try_append_message_basic_works() { + use super::integration_test::Test; // Run with larger page size. + + let mut page = PageOf::::default(); + let mut msgs = 0; + // Append as many 4-byte message as possible. + for i in 0..u32::MAX { + let r = i.using_encoded(|i| page.try_append_message::(i.try_into().unwrap())); + if r.is_err() { + break + } else { + msgs += 1; + } + } + let expected_msgs = (::HeapSize::get()) / + (ItemHeader::<::Size>::max_encoded_len() as u32 + 4); + assert_eq!(expected_msgs, msgs, "Wrong number of messages"); + assert_eq!(page.remaining, msgs); + assert_eq!(page.remaining_size, msgs * 4); + + // Verify that the heap content is correct. + let mut heap = Vec::::new(); + for i in 0..msgs { + let header = ItemHeader::<::Size> { payload_len: 4, is_processed: false }; + heap.extend(header.encode()); + heap.extend(i.encode()); + } + assert_eq!(page.heap, heap); +} #[test] fn page_try_append_message_max_msg_len_works_works() { @@ -576,7 +622,47 @@ fn page_try_append_message_max_msg_len_works_works() { assert_eq!(page.heap.len(), ::HeapSize::get() as usize); } - let _page = PageOf::::from_message::(BoundedSlice::defensive_truncate_from(b"MSG")); +#[test] +fn page_try_append_message_with_remaining_size_works_works() { + use super::integration_test::Test; // Run with larger page size. + let header_size = ItemHeader::<::Size>::max_encoded_len(); + + // We start off with an empty page. + let mut page = PageOf::::default(); + let mut remaining = ::HeapSize::get() as usize; + let mut msgs = Vec::new(); + let mut rng = StdRng::seed_from_u64(42); + // Now we keep appending messages with different lengths. + while remaining >= header_size { + let take = rng.gen_range(0..=(remaining - header_size)) as usize; + let msg = vec![123u8; take]; + page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) + .unwrap(); + remaining -= take + header_size; + msgs.push(msg); + } + // Cannot even fit a single header in there now. + assert!(remaining < header_size); + assert_eq!(::HeapSize::get() as usize - page.heap.len(), remaining); + assert_eq!(page.remaining as usize, msgs.len()); + assert_eq!( + page.remaining_size as usize, + msgs.iter().fold(0, |mut a, m| { + a += m.len(); + a + }) + ); + // Verify the heap content. + let mut heap = Vec::new(); + for msg in msgs.into_iter() { + let header = ItemHeader::<::Size> { + payload_len: msg.len() as u32, + is_processed: false, + }; + heap.extend(header.encode()); + heap.extend(msg); + } + assert_eq!(page.heap, heap); } // `Page::from_message` does not panic when called with the maximum message and origin lengths. From 0edd21dcea49f2229d54c1a6e1f723bc7f56fc25 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 22 Nov 2022 01:04:39 +0100 Subject: [PATCH 065/110] Fixup docs Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 15 +++++++++++++-- frame/message-queue/src/tests.rs | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 107a81aca023b..25ca5a11897bf 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -70,6 +70,14 @@ //! NOTE: The enqueuing and storing of messages are only a means to implement the processing and are //! not goals per se. //! +//! **Page Data Layout** +//! +//! A Page contains a heap which holds all its messages. The heap is built by concatenating +//! `(ItemHeader, Message)` pairs. The [`ItemHeader`] contains the length of the message which is +//! needed for retrieving it. This layout allows for constant access time of the next message and +//! linear access time for any message in the page. The header must remain minimal to reduce its PoV +//! impact. +//! //! **Weight Metering** //! //! The pallet utilizes the [`sp_weights::WeightMeter`] to manually track its consumption to always @@ -267,7 +275,7 @@ impl< } } - /// Try to append one message from an origin. + /// Try to append one message to a page. fn try_append_message( &mut self, message: BoundedSlice>, @@ -355,6 +363,8 @@ impl< /// Set the `is_processed` flag for the item at `pos` to be `true` if not already and decrement /// the `remaining` counter of the page. + /// + /// Does nothing if no [`ItemHeader`] could be decoded at the given position. fn note_processed_at_pos(&mut self, pos: usize) { if let Ok(mut h) = ItemHeader::::decode(&mut &self.heap[pos..]) { if !h.is_processed { @@ -366,6 +376,7 @@ impl< } } + /// Returns whether the page is *complete* which means that no messages remain. fn is_complete(&self) -> bool { self.remaining.is_zero() } @@ -592,7 +603,7 @@ pub mod pallet { } } -/// One link of the double-linked ready ring which contains all *ready* queues. +/// Current and next *ready* queues. pub struct ReadyRing { first: Option>, next: Option>, diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 3ff9ec8db1d51..bfc080ace5f23 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Tests for Multisig Pallet +//! Tests for Message Queue Pallet. #![cfg(test)] From 0ba2bb880c1d131d83ab3fe2484e254396b2faed Mon Sep 17 00:00:00 2001 From: Gav Date: Tue, 22 Nov 2022 13:57:25 +0900 Subject: [PATCH 066/110] Docs --- frame/message-queue/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 4941738e7ef47..665fbcc174d67 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -243,7 +243,9 @@ impl Default for BookState { } } +/// Handler code for when the items in a queue change. pub trait OnQueueChanged { + /// Note that the queue `id` now has `item_count` items in it, taking up `items_size` bytes. fn on_queue_changed(id: Id, items_count: u32, items_size: u32); } From 73c7e49211b4a2300fc7f7b43d2f9ef4c7adc353 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 22 Nov 2022 16:49:58 +0100 Subject: [PATCH 067/110] Do nothing in sweep_queue if the queue does not exist ... otherwise it inserts default values into the storage. Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 5 ++++- frame/message-queue/src/tests.rs | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 25ca5a11897bf..7f84ec24cb64e 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -1273,8 +1273,11 @@ impl EnqueueMessage> for Pallet { /// Force removes a queue from the ready ring. /// - /// Does not remove its pages from the storage. + /// Does not remove its pages from the storage. Does nothing if the queue does not exist. fn sweep_queue(origin: MessageOriginOf) { + if !BookStateFor::::contains_key(&origin) { + return + } let mut book_state = BookStateFor::::get(&origin); book_state.begin = book_state.end; if let Some(neighbours) = book_state.ready_neighbours.take() { diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index bfc080ace5f23..8c3aedf8b7204 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -693,6 +693,14 @@ fn sweep_queue_works() { }) } +#[test] +fn sweep_queue_invalid_noops() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + assert_storage_noop!(MessageQueue::sweep_queue(Here)); + }); +} + #[test] fn footprint_works() { new_test_ext::().execute_with(|| { From 4717fc39c7b41525134419acbd3c247d9d9c2071 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 22 Nov 2022 16:52:33 +0100 Subject: [PATCH 068/110] Test ring (un)knitting Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/tests.rs | 96 ++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 8c3aedf8b7204..ce6d3fb5fc663 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -757,5 +757,101 @@ fn execute_overweight_works() { // Doing it again will error. let consumed = MessageQueue::do_execute_overweight(origin, 0, 0, 60.into_weight()).unwrap_err(); + +/// Checks that (un)knitting the ready ring works with just one queue. +/// +/// This case is interesting since it wraps and a lot of `mutate` now operate on the same object. +#[test] +fn ready_ring_knit_basic_works() { + use MessageOrigin::*; + + new_test_ext::().execute_with(|| { + BookStateFor::::insert(Here, &empty_book::()); + + for i in 0..10 { + if i % 2 == 0 { + knit(&Here); + assert_ring(&[Here]); + } else { + unknit(&Here); + assert_ring(&[]); + } + } + assert_ring(&[]); + }); +} + +#[test] +fn ready_ring_knit_and_unknit_works() { + use MessageOrigin::*; + + new_test_ext::().execute_with(|| { + // Place three queues into the storage. + BookStateFor::::insert(Here, &empty_book::()); + BookStateFor::::insert(There, &empty_book::()); + BookStateFor::::insert(Everywhere(0), &empty_book::()); + + // Knit them into the ready ring. + assert_ring(&[]); + knit(&Here); + assert_ring(&[Here]); + knit(&There); + assert_ring(&[Here, There]); + knit(&Everywhere(0)); + assert_ring(&[Here, There, Everywhere(0)]); + + // Now unknit… + unknit(&Here); + assert_ring(&[There, Everywhere(0)]); + unknit(&There); + assert_ring(&[Everywhere(0)]); + unknit(&Everywhere(0)); + assert_ring(&[]); }); } + +/// Knit a queue into the ready-ring and write it back to storage. +fn knit(o: &MessageOrigin) { + let mut b = BookStateFor::::get(o); + b.ready_neighbours = MessageQueue::ready_ring_knit(o).ok().defensive(); + BookStateFor::::insert(o, b); +} + +/// Unknit a queue into the ready-ring and write it back to storage. +fn unknit(o: &MessageOrigin) { + let mut b = BookStateFor::::get(o); + MessageQueue::ready_ring_unknit(o, b.ready_neighbours.unwrap()); + b.ready_neighbours = None; + BookStateFor::::insert(o, b); +} + +/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. +pub fn build_triple_ring() { + use MessageOrigin::*; + BookStateFor::::insert(Here, &empty_book::()); + BookStateFor::::insert(There, &empty_book::()); + BookStateFor::::insert(Everywhere(0), &empty_book::()); + + // Knit them into the ready ring. + knit(&Here); + knit(&There); + knit(&Everywhere(0)); + assert_ring(&[Here, There, Everywhere(0)]); +} + +/// Check that the Ready Ring consists of `neighbours` in that exact order. +/// +/// Also check that the first element is the service head. +fn assert_ring(neighbours: &[MessageOrigin]) { + for (i, origin) in neighbours.iter().enumerate() { + let book = BookStateFor::::get(&origin); + assert_eq!( + book.ready_neighbours, + Some(Neighbours { + prev: neighbours[(i + neighbours.len() - 1) % neighbours.len()], + next: neighbours[(i + 1) % neighbours.len()], + }) + ); + } + assert_eq!(ServiceHead::::get(), neighbours.first().cloned()); +} From a6ead94e6443f453d44bf8855eb88faf15003548 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 22 Nov 2022 16:52:49 +0100 Subject: [PATCH 069/110] Upgrade stress-test Change the test to not assume that all queued messages will be processed in the next block but split it over multiple. Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/integration_test.rs | 174 ++++++++++++-------- 1 file changed, 105 insertions(+), 69 deletions(-) diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index 0587ef985e62a..117ca366c711d 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -31,7 +31,7 @@ use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, }; -use rand::{Rng, SeedableRng}; +use rand::{rngs::StdRng, Rng, SeedableRng}; use rand_distr::Pareto; use sp_core::H256; use sp_runtime::{ @@ -103,86 +103,122 @@ impl Config for Test { /// Simulates heavy usage by enqueueing and processing large amounts of messages. /// -/// Best to run with `--release`. +/// Best to run with `-r`, `RUST_LOG=info` and `RUSTFLAGS='-Cdebug-assertions=y'`. /// /// # Example output /// -/// Each block produces a print in this form: -/// /// ```pre -/// Enqueued 35298 messages across 5266 queues. Total payload 621.85 KiB -/// Processing 20758 messages... -/// Processing 2968 messages... -/// Processing 7686 messages... -/// Processing 3287 messages... -/// Processing 461 messages... -/// Processing 14 messages... -/// Processing 112 messages... -/// Processing 5 messages... -/// Processing 6 messages... -/// Processing 1 messages... +/// Enqueued 1189 messages across 176 queues. Payload 46.97 KiB +/// Processing 772 of 1189 messages +/// Enqueued 9270 messages across 1559 queues. Payload 131.85 KiB +/// Processing 6262 of 9687 messages +/// Enqueued 5025 messages across 1225 queues. Payload 100.23 KiB +/// Processing 1739 of 8450 messages +/// Enqueued 42061 messages across 6357 queues. Payload 536.29 KiB +/// Processing 11675 of 48772 messages +/// Enqueued 20253 messages across 2420 queues. Payload 288.34 KiB +/// Processing 28711 of 57350 messages +/// Processing all remaining 28639 messages /// ``` #[test] +#[ignore] // Only run in the CI. fn stress_test_enqueue_and_service() { - let blocks = 10; + let blocks = 20; let max_queues = 10_000; - let max_messages_per_queue = 100_000; + let max_messages_per_queue = 10_000; let max_msg_len = MaxMessageLenOf::::get(); - let mut rng = rand::rngs::StdRng::seed_from_u64(2); + let mut rng = StdRng::seed_from_u64(42); new_test_ext::().execute_with(|| { + let mut msgs_remaining = 0; for block in 0..blocks { - let num_queues = rng.gen_range(1..max_queues); - let mut num_messages = 0; - let mut total_msg_len = 0; - - for origin in 0..num_queues { - let num_messages_per_queue = - (rng.sample(Pareto::new(1.0, 1.1).unwrap()) as u32).min(max_messages_per_queue); - - for m in 0..num_messages_per_queue { - let mut message = format!("{}:{}", &origin, &m).into_bytes(); - let msg_len = (rng.sample(Pareto::new(1.0, 1.0).unwrap()) as u32) - .clamp(message.len() as u32, max_msg_len); - message.resize(msg_len as usize, 0); - MessageQueue::enqueue_message( - BoundedSlice::defensive_truncate_from(&message), - origin.into(), - ); - total_msg_len += msg_len; - } - num_messages += num_messages_per_queue; - } - log::info!( - "Enqueued {} messages across {} queues. Total payload {:.2} KiB", - num_messages, - num_queues, - total_msg_len as f64 / 1024.0 - ); - - let mut msgs_remaining = num_messages as u64; - while !msgs_remaining.is_zero() { - // We have to use at least 1 here since otherwise messages will marked as - // permanently overweight. - let weight = rng.gen_range(1..=msgs_remaining).into_weight(); - ServiceWeight::set(Some(weight)); - - log::info!("Processing {} messages...", weight.ref_time()); - let consumed = MessageQueue::on_initialize(block); - if consumed != weight { - panic!( - "consumed != weight: {} != {}\n{}", - consumed, - weight, - MessageQueue::debug_info() - ); - } - let processed = NumMessagesProcessed::take(); - assert_eq!(processed, weight.ref_time() as usize); - System::reset_events(); - msgs_remaining = msgs_remaining.saturating_sub(weight.ref_time()); - } - assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero(), "Nothing left"); + // Start by enqueuing a large number of messages. + let (enqueued, total_msg_len) = + enqueue_messages(max_queues, max_messages_per_queue, max_msg_len, &mut rng); + msgs_remaining += enqueued; + + // Pick a fraction of all messages currently in queue and process them. + let processed = rng.gen_range(1..=msgs_remaining); + log::info!("Processing {} of all messages {}", processed, msgs_remaining); + process_messages(processed); // This also advances the block. + msgs_remaining -= processed; } + log::info!("Processing all remaining {} messages", msgs_remaining); + process_messages(msgs_remaining); + post_conditions(); }); } + +/// Enqueue a random number of random messages into a random number of queues. +fn enqueue_messages( + max_queues: u32, + max_per_queue: u32, + max_msg_len: u32, + rng: &mut StdRng, +) -> (u32, usize) { + let num_queues = rng.gen_range(1..max_queues); + let mut num_messages = 0; + let mut total_msg_len = 0; + for origin in 0..num_queues { + let num_messages_per_queue = + (rng.sample(Pareto::new(1.0, 1.1).unwrap()) as u32).min(max_per_queue); + + for m in 0..num_messages_per_queue { + let mut message = format!("{}:{}", &origin, &m).into_bytes(); + let msg_len = (rng.sample(Pareto::new(1.0, 1.0).unwrap()) as u32) + .clamp(message.len() as u32, max_msg_len); + message.resize(msg_len as usize, 0); + MessageQueue::enqueue_message( + BoundedSlice::defensive_truncate_from(&message), + origin.into(), + ); + total_msg_len += msg_len; + } + num_messages += num_messages_per_queue; + } + log::info!( + "Enqueued {} messages across {} queues. Payload {:.2} KiB", + num_messages, + num_queues, + total_msg_len as f64 / 1024.0 + ); + (num_messages, total_msg_len as usize) +} + +/// Process the number of messages. +fn process_messages(num_msgs: u32) { + let weight = (num_msgs as u64).into_weight(); + ServiceWeight::set(Some(weight)); + let consumed = next_block(); + + assert_eq!(consumed, weight, "\n{}", MessageQueue::debug_info()); + assert_eq!(NumMessagesProcessed::take(), num_msgs as usize); +} + +/// Returns the weight consumed by `MessageQueue::on_initialize()`. +fn next_block() -> Weight { + MessageQueue::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + MessageQueue::on_initialize(System::block_number()) +} + +/// Assert that the pallet is in the expected post state. +fn post_conditions() { + // All queues are empty. + for (_, book) in BookStateFor::::iter() { + assert!(book.end >= book.begin); + assert_eq!(book.count, 0); + assert_eq!(book.size, 0); + assert_eq!(book.message_count, 0); + assert!(book.ready_neighbours.is_none()); + } + // No pages remain. + assert_eq!(Pages::::iter().count(), 0); + // Service head is gone. + assert!(ServiceHead::::get().is_none()); + // This still works fine. + assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero(), "Nothing left"); + next_block(); +} From bcee3f7918f037cddc040b70f8e3417a0458f7ba Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 22 Nov 2022 16:55:55 +0100 Subject: [PATCH 070/110] More tests... Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/mock_helpers.rs | 4 + frame/message-queue/src/tests.rs | 140 +++++++++++++++++++++--- 2 files changed, 129 insertions(+), 15 deletions(-) diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 15a819ce10db2..480fa09f17b42 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -83,6 +83,10 @@ pub fn single_page_book() -> BookStateOf { BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } } +pub fn empty_book() -> BookStateOf { + BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } +} + /// Returns a full page of messages with their index as payload and the number of messages. pub fn full_page() -> (PageOf, usize) { let mut msgs = 0; diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index ce6d3fb5fc663..69106c1975156 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -365,6 +365,29 @@ fn service_page_item_bails() { }); } +#[test] +fn bump_service_head_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create a ready ring with three queues. + BookStateFor::::insert(Here, &empty_book::()); + knit(&Here); + BookStateFor::::insert(There, &empty_book::()); + knit(&There); + BookStateFor::::insert(Everywhere(0), &empty_book::()); + knit(&Everywhere(0)); + + // Bump 99 times. + for i in 0..99 { + let current = MessageQueue::bump_service_head(&mut WeightMeter::max_limit()).unwrap(); + assert_eq!(current, [Here, There, Everywhere(0)][i % 3]); + } + + // The ready ring is intact and the service head is still `Here`. + assert_ring(&[Here, There, Everywhere(0)]); + }); +} + /// `bump_service_head` does nothing when called with an insufficient weight limit. #[test] fn bump_service_head_bails() { @@ -380,7 +403,7 @@ fn bump_service_head_bails() { } #[test] -fn bump_service_head_works() { +fn bump_service_head_trivial_works() { new_test_ext::().execute_with(|| { set_weight("bump_service_head", 2.into_weight()); let mut meter = WeightMeter::max_limit(); @@ -399,6 +422,26 @@ fn bump_service_head_works() { }); } +#[test] +fn bump_service_head_no_head_noops() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + // Create a ready ring with three queues. + BookStateFor::::insert(Here, &empty_book::()); + knit(&Here); + BookStateFor::::insert(There, &empty_book::()); + knit(&There); + BookStateFor::::insert(Everywhere(0), &empty_book::()); + knit(&Everywhere(0)); + + // But remove the service head. + ServiceHead::::kill(); + + // Nothing happens. + assert_storage_noop!(MessageQueue::bump_service_head(&mut WeightMeter::max_limit())); + }); +} + #[test] fn service_page_item_consumes_correct_weight() { new_test_ext::().execute_with(|| { @@ -519,6 +562,16 @@ fn note_processed_at_pos_works() { }); } +#[test] +fn note_processed_at_pos_idempotent() { + let (mut page, msgs) = full_page::(); + page.note_processed_at_pos(0); + + let original = page.clone(); + page.note_processed_at_pos(0); + assert_eq!(page.heap, original.heap); +} + #[test] fn is_complete_works() { use super::integration_test::Test; // Run with larger page size. @@ -677,22 +730,50 @@ fn page_from_message_max_len_works() { #[test] fn sweep_queue_works() { + use MessageOrigin::*; new_test_ext::().execute_with(|| { - let origin = MessageOrigin::Here; - let (page, _) = full_page::(); - let book = book_for::(&page); - assert!(book.begin != book.end, "pre-condition: the book is not empty"); - Pages::::insert(&origin, &0, &page); - BookStateFor::::insert(&origin, &book); + build_triple_ring(); - MessageQueue::sweep_queue(origin); + let book = BookStateFor::::get(&Here); + assert!(book.begin != book.end); + // Removing the service head works + assert_eq!(ServiceHead::::get(), Some(Here)); + MessageQueue::sweep_queue(Here); + assert_ring(&[There, Everywhere(0)]); // The book still exits, but has updated begin and end. - let book = BookStateFor::::get(&origin); - assert_eq!(book.begin, book.end, "Begin and end are now the same"); - assert!(Pages::::contains_key(&origin, &0), "Page was not swept"); + let book = BookStateFor::::get(&Here); + assert_eq!(book.begin, book.end); + + // Removing something that is not the service head works. + assert!(ServiceHead::::get() != Some(Everywhere(0))); + MessageQueue::sweep_queue(Everywhere(0)); + assert_ring(&[There]); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(&Everywhere(0)); + assert_eq!(book.begin, book.end); + + MessageQueue::sweep_queue(There); + // The book still exits, but has updated begin and end. + let book = BookStateFor::::get(&There); + assert_eq!(book.begin, book.end); + assert_ring(&[]); }) } +/// Test that `sweep_queue` also works if the ReadyRing wraps around. +#[test] +fn sweep_queue_wraps_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + BookStateFor::::insert(Here, &empty_book::()); + knit(&Here); + + MessageQueue::sweep_queue(Here); + let book = BookStateFor::::get(Here); + assert!(book.ready_neighbours.is_none()); + }); +} + #[test] fn sweep_queue_invalid_noops() { use MessageOrigin::*; @@ -723,6 +804,24 @@ fn footprint_default_works() { }) } +/// The footprint of a swept queue is still correct. +#[test] +fn footprint_on_swept_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + let mut book = empty_book::(); + book.message_count = 3; + book.size = 10; + BookStateFor::::insert(Here, &book); + knit(&Here); + + MessageQueue::sweep_queue(Here); + let fp = MessageQueue::footprint(Here); + assert_eq!(fp.count, 3); + assert_eq!(fp.size, 10); + }) +} + #[test] fn execute_overweight_works() { new_test_ext::().execute_with(|| { @@ -748,15 +847,26 @@ fn execute_overweight_works() { } .into(), ); - // Now try to execute it. - let consumed = MessageQueue::do_execute_overweight(origin, 0, 0, 7.into_weight()).unwrap(); + // Now try to execute it with too few weight. + let consumed = + ::execute_overweight(5.into_weight(), (origin, 0, 0)); + assert_eq!(consumed, Err(ExecuteOverweightError::InsufficientWeight)); + + // Execute it with enough weight. + let consumed = + ::execute_overweight(7.into_weight(), (origin, 0, 0)) + .unwrap(); assert_eq!(consumed, 6.into_weight()); // There is no message left in the book. let book = BookStateFor::::get(&origin); assert_eq!(book.message_count, 0); - // Doing it again will error. + + // Doing it again with enough weight will error. let consumed = - MessageQueue::do_execute_overweight(origin, 0, 0, 60.into_weight()).unwrap_err(); + ::execute_overweight(70.into_weight(), (origin, 0, 0)); + assert_eq!(consumed, Err(ExecuteOverweightError::NotFound)); + }); +} /// Checks that (un)knitting the ready ring works with just one queue. /// From 8277caf5e15ced58fef5f86dd6deec85e0cc642c Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 22 Nov 2022 16:56:02 +0100 Subject: [PATCH 071/110] Beauty fixes Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 7f84ec24cb64e..cb286c40f422b 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -196,8 +196,8 @@ use frame_support::{ defensive, pallet_prelude::*, traits::{ - DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, - ProcessMessageError, ServiceQueues, + Defensive, DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, + ProcessMessage, ProcessMessageError, ServiceQueues, }, BoundedSlice, CloneNoBound, DefaultNoBound, }; @@ -384,6 +384,7 @@ impl< /// The neighbours of a queue in the a double-linked list #[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(PartialEq))] pub struct Neighbours { prev: MessageOrigin, next: MessageOrigin, @@ -1159,8 +1160,7 @@ impl, HeapSize: Get for MaxMessageLen { fn get() -> u32 { - (HeapSize::get().into()) - .saturating_sub(ItemHeader::::max_encoded_len() as u32) + (HeapSize::get().into()).saturating_sub(ItemHeader::::max_encoded_len() as u32) } } From 2961ccf0fe6e1d16ce6473d9e1fde09ad8976523 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 22 Nov 2022 17:00:26 +0100 Subject: [PATCH 072/110] clippy Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/integration_test.rs | 4 ++-- frame/message-queue/src/lib.rs | 4 ++-- frame/message-queue/src/tests.rs | 8 +++++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index 117ca366c711d..e56cce60b487e 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -131,9 +131,9 @@ fn stress_test_enqueue_and_service() { new_test_ext::().execute_with(|| { let mut msgs_remaining = 0; - for block in 0..blocks { + for _ in 0..blocks { // Start by enqueuing a large number of messages. - let (enqueued, total_msg_len) = + let (enqueued, _) = enqueue_messages(max_queues, max_messages_per_queue, max_msg_len, &mut rng); msgs_remaining += enqueued; diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index cb286c40f422b..d1be45789211a 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -196,8 +196,8 @@ use frame_support::{ defensive, pallet_prelude::*, traits::{ - Defensive, DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, - ProcessMessage, ProcessMessageError, ServiceQueues, + DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, + ProcessMessageError, ServiceQueues, }, BoundedSlice, CloneNoBound, DefaultNoBound, }; diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 69106c1975156..0fe43ae77431d 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -21,7 +21,9 @@ use crate::{mock::*, *}; -use frame_support::{assert_noop, assert_ok, assert_storage_noop, bounded_vec, StorageNoopGuard}; +use frame_support::{ + assert_noop, assert_ok, assert_storage_noop, traits::Defensive, StorageNoopGuard, +}; use rand::{rngs::StdRng, Rng, SeedableRng}; #[test] @@ -564,7 +566,7 @@ fn note_processed_at_pos_works() { #[test] fn note_processed_at_pos_idempotent() { - let (mut page, msgs) = full_page::(); + let (mut page, _) = full_page::(); page.note_processed_at_pos(0); let original = page.clone(); @@ -665,7 +667,7 @@ fn page_try_append_message_max_msg_len_works_works() { // We start off with an empty page. let mut page = PageOf::::default(); // … and append a message with maximum possible length. - let mut msg = vec![123u8; MaxMessageLenOf::::get() as usize]; + let msg = vec![123u8; MaxMessageLenOf::::get() as usize]; // … which works. page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) .unwrap(); From b593d7bde8e4bf1e8a531c9625edd462a95c31c6 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 22 Nov 2022 20:48:55 +0100 Subject: [PATCH 073/110] Rename BoundedVec::as_slice to as_bounded_slice Conflicts with deref().as_slice() otherwise. Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/tests.rs | 2 +- primitives/core/src/bounded/bounded_vec.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 0fe43ae77431d..66641da891893 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -615,7 +615,7 @@ fn page_from_message_basic_works() { let mut msg: BoundedVec> = Default::default(); msg.bounded_resize(MaxMessageLenOf::::get() as usize, 123); - let page = PageOf::::from_message::(msg.as_slice()); + let page = PageOf::::from_message::(msg.as_bounded_slice()); assert_eq!(page.remaining, 1); assert_eq!(page.remaining_size as usize, msg.len()); assert!(page.first_index == 0 && page.first == 0 && page.last == 0); diff --git a/primitives/core/src/bounded/bounded_vec.rs b/primitives/core/src/bounded/bounded_vec.rs index 11121cb6a4980..6e1e1c7cfda64 100644 --- a/primitives/core/src/bounded/bounded_vec.rs +++ b/primitives/core/src/bounded/bounded_vec.rs @@ -677,7 +677,7 @@ impl> BoundedVec { impl BoundedVec { /// Return a [`BoundedSlice`] with the content and bound of [`Self`]. - pub fn as_slice(&self) -> BoundedSlice { + pub fn as_bounded_slice(&self) -> BoundedSlice { BoundedSlice(&self.0[..], PhantomData::default()) } } From 3524b687ea6f73e6b6db1b54c87e2f05e811be3e Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Tue, 22 Nov 2022 20:50:45 +0100 Subject: [PATCH 074/110] Fix imports Signed-off-by: Oliver Tale-Yazdi --- frame/scheduler/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index ac5e43a6d3d6b..2e0d0c6be1db5 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -73,7 +73,6 @@ use frame_support::{ weights::{Weight, WeightMeter}, }; use frame_system::{self as system}; -pub use pallet::*; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::{ @@ -81,7 +80,8 @@ use sp_runtime::{ BoundedVec, RuntimeDebug, }; use sp_std::{borrow::Borrow, cmp::Ordering, marker::PhantomData, prelude::*}; -use sp_weights::WeightMeter; + +pub use pallet::*; pub use weights::WeightInfo; /// Just a simple index for naming period tasks. From 4e81171a5d92f39214feac3ef22e1f2323f165af Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 23 Nov 2022 18:31:45 +0100 Subject: [PATCH 075/110] Remove ReadyRing struct Was used for testing only. Instead use 'fn assert_ring' which also check the service head and backlinks. Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 26 -------------------------- frame/message-queue/src/mock.rs | 17 +++++++++++++++++ frame/message-queue/src/tests.rs | 26 +++++++------------------- 3 files changed, 24 insertions(+), 45 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index d1be45789211a..058f7b4a1c718 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -604,32 +604,6 @@ pub mod pallet { } } -/// Current and next *ready* queues. -pub struct ReadyRing { - first: Option>, - next: Option>, -} -impl ReadyRing { - pub fn new() -> Self { - Self { first: ServiceHead::::get(), next: ServiceHead::::get() } - } -} -impl Iterator for ReadyRing { - type Item = MessageOriginOf; - fn next(&mut self) -> Option { - match self.next.take() { - None => None, - Some(last) => { - self.next = BookStateFor::::get(&last) - .ready_neighbours - .map(|n| n.next) - .filter(|n| Some(n) != self.first.as_ref()); - Some(last) - }, - } - } -} - /// The status of a page after trying to execute its next message. #[derive(PartialEq, Debug)] enum PageExecutionStatus { diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 2e9b6c4a4fc24..169e8f20f3dcd 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -233,3 +233,20 @@ where pub fn set_weight(name: &str, w: Weight) { MockedWeightInfo::set_weight::(name, w); } + +/// Check that the Ready Ring consists of `neighbours` in that exact order. +/// +/// Also check that all backlinks are valid and that the first element is the service head. +fn assert_ring(neighbours: &[MessageOrigin]) { + for (i, origin) in neighbours.iter().enumerate() { + let book = BookStateFor::::get(&origin); + assert_eq!( + book.ready_neighbours, + Some(Neighbours { + prev: neighbours[(i + neighbours.len() - 1) % neighbours.len()], + next: neighbours[(i + 1) % neighbours.len()], + }) + ); + } + assert_eq!(ServiceHead::::get(), neighbours.first().cloned()); +} diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 66641da891893..1f57403047cf9 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -89,24 +89,15 @@ fn enqueue_within_one_page_works() { fn queue_priority_retains() { new_test_ext::().execute_with(|| { use MessageOrigin::*; - assert_eq!(ReadyRing::::new().collect::>(), vec![]); + assert_ring(&[]); MessageQueue::enqueue_message(msg(&"a"), Everywhere(1)); - assert_eq!(ReadyRing::::new().collect::>(), vec![Everywhere(1)]); + assert_ring(&[Everywhere(1)]); MessageQueue::enqueue_message(msg(&"b"), Everywhere(2)); - assert_eq!( - ReadyRing::::new().collect::>(), - vec![Everywhere(1), Everywhere(2)] - ); + assert_ring(&[Everywhere(1), Everywhere(2)]); MessageQueue::enqueue_message(msg(&"c"), Everywhere(3)); - assert_eq!( - ReadyRing::::new().collect::>(), - vec![Everywhere(1), Everywhere(2), Everywhere(3)] - ); + assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); MessageQueue::enqueue_message(msg(&"d"), Everywhere(2)); - assert_eq!( - ReadyRing::::new().collect::>(), - vec![Everywhere(1), Everywhere(2), Everywhere(3)] - ); + assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); // service head is 1, it will process a, leaving service head at 2. it also processes b but // doees not empty queue 2, so service head will end at 2. assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); @@ -114,10 +105,7 @@ fn queue_priority_retains() { MessagesProcessed::get(), vec![(vmsg(&"a"), Everywhere(1)), (vmsg(&"b"), Everywhere(2)),] ); - assert_eq!( - ReadyRing::::new().collect::>(), - vec![Everywhere(2), Everywhere(3)] - ); + assert_ring(&[Everywhere(2), Everywhere(3)]); // service head is 2, so will process d first, then c. assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!( @@ -129,7 +117,7 @@ fn queue_priority_retains() { (vmsg(&"c"), Everywhere(3)), ] ); - assert_eq!(ReadyRing::::new().collect::>(), vec![]); + assert_ring(&[]); }); } From 1653fbae047b7f5316ee27c6dd92f39592570bcd Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 23 Nov 2022 18:32:49 +0100 Subject: [PATCH 076/110] Beauty fixes Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 25 +++++++++++++------------ frame/message-queue/src/mock_helpers.rs | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 058f7b4a1c718..6d2b17dd9983c 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -125,7 +125,9 @@ //! Manual intervention in the form of //! [`frame_support::traits::ServiceQueues::execute_overweight`] is necessary. Overweight messages //! emit an [`Event::OverweightEnqueued`] event which can be used to extract the arguments for -//! manual execution. This only works on permanently overweight messages. +//! manual execution. This only works on permanently overweight messages. There is no guarantee that +//! this will work since the message could be part of a stale page and be reaped before execution +//! commences. //! //! # Terminology //! @@ -382,11 +384,13 @@ impl< } } -/// The neighbours of a queue in the a double-linked list +/// A single link in the double-linked Ready Ring list. #[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] #[cfg_attr(feature = "std", derive(PartialEq))] pub struct Neighbours { + /// The previous queue. prev: MessageOrigin, + /// The next queue. next: MessageOrigin, } @@ -745,7 +749,9 @@ impl Pallet { // insert into ready queue. match Self::ready_ring_knit(origin) { Ok(neighbours) => book_state.ready_neighbours = Some(neighbours), - Err(()) => debug_assert!(false, "Ring state invalid when knitting"), + Err(()) => { + defensive!("Ring state invalid when knitting"); + }, } } // No room on the page or no page - link in a new page. @@ -819,7 +825,7 @@ impl Pallet { } } - /// Remove a page which has no more messages remaining to be processed. + /// Remove a stale page or one which has no more messages remaining to be processed. fn do_reap_page(origin: &MessageOriginOf, page_index: PageIndex) -> DispatchResult { let mut book_state = BookStateFor::::get(origin); // definitely not reapable if the page's index is no less than the `begin`ning of ready @@ -889,7 +895,6 @@ impl Pallet { PageExecutionStatus::Bailed => break, // Go to the next page if this one is at the end. PageExecutionStatus::NoMore => (), - // TODO @ggwpez think of a better enum here. PageExecutionStatus::Partial => { defensive!("should progress till the end or bail"); }, @@ -902,7 +907,7 @@ impl Pallet { if let Some(neighbours) = book_state.ready_neighbours.take() { Self::ready_ring_unknit(&origin, neighbours); } else { - debug_assert!(false, "Freshly processed queue must have been ready"); + defensive!("Freshly processed queue must have been ready"); } } BookStateFor::::insert(&origin, &book_state); @@ -918,7 +923,7 @@ impl Pallet { /// Service as many messages of a page as possible. /// - /// Returns whether the execution bailed. + /// Returns how many messages were processed and the page's status. fn service_page( origin: &MessageOriginOf, book_state: &mut BookStateOf, @@ -937,7 +942,7 @@ impl Pallet { let mut page = match Pages::::get(&origin, page_index) { Some(p) => p, None => { - debug_assert!(false, "message-queue: referenced page not found"); + defensive!("message-queue: referenced page not found"); return (0, NoMore) }, }; @@ -1245,9 +1250,6 @@ impl EnqueueMessage> for Pallet { T::QueueChangeHandler::on_queue_changed(origin, book_state.message_count, book_state.size); } - /// Force removes a queue from the ready ring. - /// - /// Does not remove its pages from the storage. Does nothing if the queue does not exist. fn sweep_queue(origin: MessageOriginOf) { if !BookStateFor::::contains_key(&origin) { return @@ -1260,7 +1262,6 @@ impl EnqueueMessage> for Pallet { BookStateFor::::insert(&origin, &book_state); } - /// Returns the [`Footprint`] of a queue. fn footprint(origin: MessageOriginOf) -> Footprint { let book_state = BookStateFor::::get(&origin); Footprint { count: book_state.message_count, size: book_state.size } diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 480fa09f17b42..fc6768254f81b 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -31,7 +31,7 @@ impl IntoWeight for u64 { } } -/// Mocked message origin. +/// Mocked message origin for testing. #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug)] pub enum MessageOrigin { Here, From b7b9f264f6fdc505679677b6181db9d636ffd8fe Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 23 Nov 2022 18:37:45 +0100 Subject: [PATCH 077/110] Fix stale page watermark Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 2 +- frame/message-queue/src/mock.rs | 7 +++ frame/message-queue/src/tests.rs | 100 +++++++++++++++++++++---------- 3 files changed, 77 insertions(+), 32 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 6d2b17dd9983c..49f89b9c5ca83 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -847,7 +847,7 @@ impl Pallet { Some(x) => x + 1, None => return false, }; - let backlog = (max_stale * max_stale / overflow).max(max_stale); + let backlog = (max_stale / overflow).max(max_stale); let watermark = book_state.begin.saturating_sub(backlog); page_index < watermark }; diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 169e8f20f3dcd..66ec6f8c929aa 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -234,6 +234,13 @@ pub fn set_weight(name: &str, w: Weight) { MockedWeightInfo::set_weight::(name, w); } +/// Assert that exactly these pages are present. Assumes `Here` origin. +pub fn assert_pages(indices: &[u32]) { + assert_eq!(Pages::::iter().count(), indices.len()); + for i in indices { + assert!(Pages::::contains_key(&MessageOrigin::Here, i)); + } +} /// Check that the Ready Ring consists of `neighbours` in that exact order. /// /// Also check that all backlinks are valid and that the first element is the service head. diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 1f57403047cf9..4eb287c18a12c 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -149,29 +149,37 @@ fn queue_priority_reset_once_serviced() { #[test] fn reap_page_permanent_overweight_works() { - assert!(MaxStale::get() >= 2, "pre-condition unmet"); + use MessageOrigin::*; new_test_ext::().execute_with(|| { - use MessageOrigin::*; - // Create pages with messages with a weight of two. - for _ in 0..(MaxStale::get() * MaxStale::get()) { + // Create 10 pages more than the stale limit. + for i in 0..(MaxStale::get() + 10) { MessageQueue::enqueue_message(msg(&"weight=2"), Here); } - - // … but only allow the processing to take at most weight 1. + assert_eq!(Pages::::iter().count(), MaxStale::get() as usize + 10); + // Mark all pages as stale since their message is permanently overweight. MessageQueue::service_queues(1.into_weight()); - // We can now reap the first one since they are permanently overweight and over the MaxStale - // limit. - assert_ok!(MessageQueue::do_reap_page(&Here, 0)); - // Cannot reap again. - assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); + // Cannot reap any page within the stale limit. + for i in 10..(MaxStale::get() + 10) { + assert_noop!(MessageQueue::do_reap_page(&Here, 10), Error::::NotReapable); + } + // Can reap the stale ones below the watermark. + for i in 0..10 { + assert_ok!(MessageQueue::do_reap_page(&Here, i)); + } + // Cannot reap any more pages. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } }); } #[test] fn reaping_overweight_fails_properly() { + use MessageOrigin::*; + assert_eq!(MaxStale::get(), 2, "The stale limit is two"); + new_test_ext::().execute_with(|| { - use MessageOrigin::*; // page 0 MessageQueue::enqueue_message(msg(&"weight=4"), Here); MessageQueue::enqueue_message(msg(&"a"), Here); @@ -187,42 +195,72 @@ fn reaping_overweight_fails_properly() { MessageQueue::enqueue_message(msg(&"bigbig 2"), Here); // page 5 MessageQueue::enqueue_message(msg(&"bigbig 3"), Here); + // Double-check that exactly these pages exist. + assert_pages(&[0, 1, 2, 3, 4, 5]); + // Start by servicing with 2 weight; this is enough for 0:"a" and 1:"b". assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"a"), Here), (vmsg(&"b"), Here)]); - // 2 stale now. + assert_pages(&[0, 1, 2, 3, 4, 5]); - // Not reapable yet, because we haven't hit the stale limit. - assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NotReapable); + // 0 and 1 are now since they both contain a permanently overweight message. + // Nothing is reapable yet, because we haven't hit the stale limit of 2. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + // Service with 1 weight; this is enough for 2:"c". assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"c"), Here)]); - // 3 stale now: can take something 4 pages in history. + // 0, 1 and 2 are stale now because of a permanently overweight message. + // 0 should be reapable since it is over the stale limit of 2. + assert_ok!(MessageQueue::do_reap_page(&Here, 0)); + assert_pages(&[1, 2, 3, 4, 5]); + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); + // ... but no other pages. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + + // Service page 3. assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"bigbig 1"), Here)]); + assert_pages(&[1, 2, 4, 5]); - // Not reapable yet, because we haven't hit the stale limit. - assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NotReapable); - assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NotReapable); - assert_noop!(MessageQueue::do_reap_page(&Here, 2), Error::::NotReapable); + // Nothing reapable. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } + // Service page 4. assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"bigbig 2"), Here)]); + assert_pages(&[1, 2, 5]); + assert_noop!(MessageQueue::do_reap_page(&Here, 3), Error::::NoPage); - // First is now reapable as it is too far behind the first ready page (5). - assert_ok!(MessageQueue::do_reap_page(&Here, 0)); - // Others not reapable yet, because we haven't hit the stale limit. - assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NotReapable); - assert_noop!(MessageQueue::do_reap_page(&Here, 2), Error::::NotReapable); + // Nothing reapable. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + } - assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); - assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"bigbig 3"), Here)]); + // Mark 5 as stale by being permanently overweight. + assert_eq!(MessageQueue::service_queues(0.into_weight()), 0.into_weight()); + assert!(MessagesProcessed::take().is_empty()); - assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); - // Still not reapable, since the number of stale pages is only 2. - assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NotReapable); - assert_noop!(MessageQueue::do_reap_page(&Here, 2), Error::::NotReapable); + // 1, 2 and 5 stale now: 1 should be reapable. + assert_ok!(MessageQueue::do_reap_page(&Here, 1)); + assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NoPage); + + // 2 and 5 are present but not reapable. + assert_pages(&[2, 5]); + for i in [2, 5] { + assert_noop!(MessageQueue::do_reap_page(&Here, i), Error::::NotReapable); + } + // 0, 1, 3 and 4 are gone. + for i in [0, 1, 3, 4] { + assert_noop!(MessageQueue::do_reap_page(&Here, i), Error::::NoPage); + } }); } From 5a377f6daf0a406a9d0a44279b1046754b8d63e8 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 23 Nov 2022 18:38:55 +0100 Subject: [PATCH 078/110] Cleanup Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/mock.rs | 34 ++++++++++++++++-- frame/message-queue/src/tests.rs | 60 +++++++------------------------- 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 66ec6f8c929aa..24b432d5e92d5 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -22,7 +22,7 @@ use super::*; use crate as pallet_message_queue; use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64}, + traits::{ConstU32, ConstU64, Defensive}, }; use sp_core::H256; use sp_runtime::{ @@ -241,10 +241,40 @@ pub fn assert_pages(indices: &[u32]) { assert!(Pages::::contains_key(&MessageOrigin::Here, i)); } } + +/// Knit a queue into the ready-ring and write it back to storage. +pub fn knit(o: &MessageOrigin) { + let mut b = BookStateFor::::get(o); + b.ready_neighbours = MessageQueue::ready_ring_knit(o).ok().defensive(); + BookStateFor::::insert(o, b); +} + +/// Unknit a queue into the ready-ring and write it back to storage. +pub fn unknit(o: &MessageOrigin) { + let mut b = BookStateFor::::get(o); + MessageQueue::ready_ring_unknit(o, b.ready_neighbours.unwrap()); + b.ready_neighbours = None; + BookStateFor::::insert(o, b); +} + +/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. +pub fn build_triple_ring() { + use MessageOrigin::*; + BookStateFor::::insert(Here, &empty_book::()); + BookStateFor::::insert(There, &empty_book::()); + BookStateFor::::insert(Everywhere(0), &empty_book::()); + + // Knit them into the ready ring. + knit(&Here); + knit(&There); + knit(&Everywhere(0)); + assert_ring(&[Here, There, Everywhere(0)]); +} + /// Check that the Ready Ring consists of `neighbours` in that exact order. /// /// Also check that all backlinks are valid and that the first element is the service head. -fn assert_ring(neighbours: &[MessageOrigin]) { +pub fn assert_ring(neighbours: &[MessageOrigin]) { for (i, origin) in neighbours.iter().enumerate() { let book = BookStateFor::::get(&origin); assert_eq!( diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 4eb287c18a12c..b3f0be73178e3 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -554,7 +554,7 @@ fn peek_index_works() { } #[test] -fn peek_first_works() { +fn peek_first_and_skip_first_works() { use super::integration_test::Test; // Run with larger page size. new_test_ext::().execute_with(|| { // Fill a page with messages. @@ -567,6 +567,12 @@ fn peek_first_works() { page.skip_first(i % 2 == 0); // True of False should not matter here. } assert!(page.peek_first().is_none(), "Page must be at the end"); + + // Check that all messages were correctly marked as (un)processed. + for i in 0..msgs { + let (_, processed, _) = page.peek_index(i).unwrap(); + assert_eq!(processed, i % 2 == 0); + } }); } @@ -587,6 +593,12 @@ fn note_processed_at_pos_works() { assert_eq!(processed, true); assert_eq!(page.remaining as usize, msgs - i - 1); } + // `skip_first` still works fine. + for i in 0..msgs { + page.peek_first().unwrap(); + page.skip_first(false); + } + assert!(page.peek_first().is_none()); }); } @@ -947,49 +959,3 @@ fn ready_ring_knit_and_unknit_works() { assert_ring(&[]); }); } - -/// Knit a queue into the ready-ring and write it back to storage. -fn knit(o: &MessageOrigin) { - let mut b = BookStateFor::::get(o); - b.ready_neighbours = MessageQueue::ready_ring_knit(o).ok().defensive(); - BookStateFor::::insert(o, b); -} - -/// Unknit a queue into the ready-ring and write it back to storage. -fn unknit(o: &MessageOrigin) { - let mut b = BookStateFor::::get(o); - MessageQueue::ready_ring_unknit(o, b.ready_neighbours.unwrap()); - b.ready_neighbours = None; - BookStateFor::::insert(o, b); -} - -/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. -pub fn build_triple_ring() { - use MessageOrigin::*; - BookStateFor::::insert(Here, &empty_book::()); - BookStateFor::::insert(There, &empty_book::()); - BookStateFor::::insert(Everywhere(0), &empty_book::()); - - // Knit them into the ready ring. - knit(&Here); - knit(&There); - knit(&Everywhere(0)); - assert_ring(&[Here, There, Everywhere(0)]); -} - -/// Check that the Ready Ring consists of `neighbours` in that exact order. -/// -/// Also check that the first element is the service head. -fn assert_ring(neighbours: &[MessageOrigin]) { - for (i, origin) in neighbours.iter().enumerate() { - let book = BookStateFor::::get(&origin); - assert_eq!( - book.ready_neighbours, - Some(Neighbours { - prev: neighbours[(i + neighbours.len() - 1) % neighbours.len()], - next: neighbours[(i + 1) % neighbours.len()], - }) - ); - } - assert_eq!(ServiceHead::::get(), neighbours.first().cloned()); -} From bdf63cc118637ddb88f67da4f72eebb99f7c9dac Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 23 Nov 2022 18:48:37 +0100 Subject: [PATCH 079/110] Fix test feature and clippy Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 16 ++-- frame/message-queue/src/mock.rs | 16 ++-- frame/message-queue/src/tests.rs | 128 +++++++++++++++---------------- 3 files changed, 79 insertions(+), 81 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 49f89b9c5ca83..39b6cd7ff3ec7 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -736,7 +736,7 @@ impl Pallet { return }, }; - if let Ok(_) = page.try_append_message::(message) { + if page.try_append_message::(message).is_ok() { Pages::::insert(origin, last, &page); BookStateFor::::insert(origin, book_state); return @@ -758,7 +758,7 @@ impl Pallet { book_state.end.saturating_inc(); book_state.count.saturating_inc(); let page = Page::from_message::(message); - Pages::::insert(origin, book_state.end - 1, &page); + Pages::::insert(origin, book_state.end - 1, page); // NOTE: `T::QueueChangeHandler` is called by the caller. BookStateFor::::insert(origin, book_state); } @@ -796,7 +796,7 @@ impl Pallet { // ^^^ We never recognise it as permanently overweight, since that would result in an // additional overweight event being deposited. ) { - Overweight | InsufficientWeight => Err(Error::::InsufficientWeight.into()), + Overweight | InsufficientWeight => Err(Error::::InsufficientWeight), Unprocessable | Processed => { page.note_processed_at_pos(pos); book_state.message_count.saturating_dec(); @@ -858,7 +858,7 @@ impl Pallet { book_state.count.saturating_dec(); book_state.message_count.saturating_reduce(page.remaining.into()); book_state.size.saturating_reduce(page.remaining_size.into()); - BookStateFor::::insert(&origin, &book_state); + BookStateFor::::insert(origin, &book_state); T::QueueChangeHandler::on_queue_changed( origin.clone(), book_state.message_count, @@ -939,7 +939,7 @@ impl Pallet { } let page_index = book_state.begin; - let mut page = match Pages::::get(&origin, page_index) { + let mut page = match Pages::::get(origin, page_index) { Some(p) => p, None => { defensive!("message-queue: referenced page not found"); @@ -973,11 +973,11 @@ impl Pallet { status != PageExecutionStatus::Bailed, "we never bail if a page became complete" ); - Pages::::remove(&origin, page_index); + Pages::::remove(origin, page_index); debug_assert!(book_state.count > 0, "completing a page implies there are pages"); book_state.count.saturating_dec(); } else { - Pages::::insert(&origin, page_index, page); + Pages::::insert(origin, page_index, page); } (total_processed, status) } @@ -1060,7 +1060,7 @@ impl Pallet { { let msg = String::from_utf8_lossy(message.deref()); if processed { - page_info.push_str("*"); + page_info.push('*'); } page_info.push_str(&format!("{:?}, ", msg)); page.skip_first(true); diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 24b432d5e92d5..a81728cb8fc99 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Polkadot. If not, see . -#![cfg(any(test, feature = "std"))] +#![cfg(test)] pub use super::mock_helpers::*; use super::*; @@ -162,7 +162,7 @@ impl ProcessMessage for RecordingMessageProcessor { let weight = if message.starts_with(&b"weight="[..]) { let mut w: u64 = 0; for &c in &message[7..] { - if c >= b'0' && c <= b'9' { + if (b'0'..=b'9').contains(&c) { w = w * 10 + (c - b'0') as u64; } else { break @@ -215,7 +215,6 @@ impl ProcessMessage for CountingMessageProcessor { /// Create new test externalities. /// /// Is generic since it is used by the unit test, integration tests and benchmarks. -#[cfg(test)] pub fn new_test_ext() -> sp_io::TestExternalities where ::BlockNumber: From, @@ -229,7 +228,6 @@ where } /// Set the weight of a specific weight function. -#[allow(dead_code)] pub fn set_weight(name: &str, w: Weight) { MockedWeightInfo::set_weight::(name, w); } @@ -238,7 +236,7 @@ pub fn set_weight(name: &str, w: Weight) { pub fn assert_pages(indices: &[u32]) { assert_eq!(Pages::::iter().count(), indices.len()); for i in indices { - assert!(Pages::::contains_key(&MessageOrigin::Here, i)); + assert!(Pages::::contains_key(MessageOrigin::Here, i)); } } @@ -260,9 +258,9 @@ pub fn unknit(o: &MessageOrigin) { /// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. pub fn build_triple_ring() { use MessageOrigin::*; - BookStateFor::::insert(Here, &empty_book::()); - BookStateFor::::insert(There, &empty_book::()); - BookStateFor::::insert(Everywhere(0), &empty_book::()); + BookStateFor::::insert(Here, empty_book::()); + BookStateFor::::insert(There, empty_book::()); + BookStateFor::::insert(Everywhere(0), empty_book::()); // Knit them into the ready ring. knit(&Here); @@ -276,7 +274,7 @@ pub fn build_triple_ring() { /// Also check that all backlinks are valid and that the first element is the service head. pub fn assert_ring(neighbours: &[MessageOrigin]) { for (i, origin) in neighbours.iter().enumerate() { - let book = BookStateFor::::get(&origin); + let book = BookStateFor::::get(origin); assert_eq!( book.ready_neighbours, Some(Neighbours { diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index b3f0be73178e3..e3e7b9c9d8ec7 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -22,7 +22,7 @@ use crate::{mock::*, *}; use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, traits::Defensive, StorageNoopGuard, + assert_noop, assert_ok, assert_storage_noop, StorageNoopGuard, }; use rand::{rngs::StdRng, Rng, SeedableRng}; @@ -45,9 +45,9 @@ fn mocked_weight_works() { fn enqueue_within_one_page_works() { new_test_ext::().execute_with(|| { use MessageOrigin::*; - MessageQueue::enqueue_message(msg(&"a"), Here); - MessageQueue::enqueue_message(msg(&"b"), Here); - MessageQueue::enqueue_message(msg(&"c"), Here); + MessageQueue::enqueue_message(msg("a"), Here); + MessageQueue::enqueue_message(msg("b"), Here); + MessageQueue::enqueue_message(msg("c"), Here); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!(MessagesProcessed::get(), vec![(b"a".to_vec(), Here), (b"b".to_vec(), Here)]); @@ -90,20 +90,20 @@ fn queue_priority_retains() { new_test_ext::().execute_with(|| { use MessageOrigin::*; assert_ring(&[]); - MessageQueue::enqueue_message(msg(&"a"), Everywhere(1)); + MessageQueue::enqueue_message(msg("a"), Everywhere(1)); assert_ring(&[Everywhere(1)]); - MessageQueue::enqueue_message(msg(&"b"), Everywhere(2)); + MessageQueue::enqueue_message(msg("b"), Everywhere(2)); assert_ring(&[Everywhere(1), Everywhere(2)]); - MessageQueue::enqueue_message(msg(&"c"), Everywhere(3)); + MessageQueue::enqueue_message(msg("c"), Everywhere(3)); assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); - MessageQueue::enqueue_message(msg(&"d"), Everywhere(2)); + MessageQueue::enqueue_message(msg("d"), Everywhere(2)); assert_ring(&[Everywhere(1), Everywhere(2), Everywhere(3)]); // service head is 1, it will process a, leaving service head at 2. it also processes b but // doees not empty queue 2, so service head will end at 2. assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![(vmsg(&"a"), Everywhere(1)), (vmsg(&"b"), Everywhere(2)),] + vec![(vmsg("a"), Everywhere(1)), (vmsg("b"), Everywhere(2)),] ); assert_ring(&[Everywhere(2), Everywhere(3)]); // service head is 2, so will process d first, then c. @@ -111,10 +111,10 @@ fn queue_priority_retains() { assert_eq!( MessagesProcessed::get(), vec![ - (vmsg(&"a"), Everywhere(1)), - (vmsg(&"b"), Everywhere(2)), - (vmsg(&"d"), Everywhere(2)), - (vmsg(&"c"), Everywhere(3)), + (vmsg("a"), Everywhere(1)), + (vmsg("b"), Everywhere(2)), + (vmsg("d"), Everywhere(2)), + (vmsg("c"), Everywhere(3)), ] ); assert_ring(&[]); @@ -125,23 +125,23 @@ fn queue_priority_retains() { fn queue_priority_reset_once_serviced() { new_test_ext::().execute_with(|| { use MessageOrigin::*; - MessageQueue::enqueue_message(msg(&"a"), Everywhere(1)); - MessageQueue::enqueue_message(msg(&"b"), Everywhere(2)); - MessageQueue::enqueue_message(msg(&"c"), Everywhere(3)); + MessageQueue::enqueue_message(msg("a"), Everywhere(1)); + MessageQueue::enqueue_message(msg("b"), Everywhere(2)); + MessageQueue::enqueue_message(msg("c"), Everywhere(3)); // service head is 1, it will process a, leaving service head at 2. it also processes b and // empties queue 2, so service head will end at 3. assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); - MessageQueue::enqueue_message(msg(&"d"), Everywhere(2)); + MessageQueue::enqueue_message(msg("d"), Everywhere(2)); // service head is 3, so will process c first, then d. assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!( MessagesProcessed::get(), vec![ - (vmsg(&"a"), Everywhere(1)), - (vmsg(&"b"), Everywhere(2)), - (vmsg(&"c"), Everywhere(3)), - (vmsg(&"d"), Everywhere(2)), + (vmsg("a"), Everywhere(1)), + (vmsg("b"), Everywhere(2)), + (vmsg("c"), Everywhere(3)), + (vmsg("d"), Everywhere(2)), ] ); }); @@ -152,15 +152,15 @@ fn reap_page_permanent_overweight_works() { use MessageOrigin::*; new_test_ext::().execute_with(|| { // Create 10 pages more than the stale limit. - for i in 0..(MaxStale::get() + 10) { - MessageQueue::enqueue_message(msg(&"weight=2"), Here); + for _ in 0..(MaxStale::get() + 10) { + MessageQueue::enqueue_message(msg("weight=2"), Here); } assert_eq!(Pages::::iter().count(), MaxStale::get() as usize + 10); // Mark all pages as stale since their message is permanently overweight. MessageQueue::service_queues(1.into_weight()); // Cannot reap any page within the stale limit. - for i in 10..(MaxStale::get() + 10) { + for _ in 10..(MaxStale::get() + 10) { assert_noop!(MessageQueue::do_reap_page(&Here, 10), Error::::NotReapable); } // Can reap the stale ones below the watermark. @@ -181,26 +181,26 @@ fn reaping_overweight_fails_properly() { new_test_ext::().execute_with(|| { // page 0 - MessageQueue::enqueue_message(msg(&"weight=4"), Here); - MessageQueue::enqueue_message(msg(&"a"), Here); + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("a"), Here); // page 1 - MessageQueue::enqueue_message(msg(&"weight=4"), Here); - MessageQueue::enqueue_message(msg(&"b"), Here); + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("b"), Here); // page 2 - MessageQueue::enqueue_message(msg(&"weight=4"), Here); - MessageQueue::enqueue_message(msg(&"c"), Here); + MessageQueue::enqueue_message(msg("weight=4"), Here); + MessageQueue::enqueue_message(msg("c"), Here); // page 3 - MessageQueue::enqueue_message(msg(&"bigbig 1"), Here); + MessageQueue::enqueue_message(msg("bigbig 1"), Here); // page 4 - MessageQueue::enqueue_message(msg(&"bigbig 2"), Here); + MessageQueue::enqueue_message(msg("bigbig 2"), Here); // page 5 - MessageQueue::enqueue_message(msg(&"bigbig 3"), Here); + MessageQueue::enqueue_message(msg("bigbig 3"), Here); // Double-check that exactly these pages exist. assert_pages(&[0, 1, 2, 3, 4, 5]); // Start by servicing with 2 weight; this is enough for 0:"a" and 1:"b". assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); - assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"a"), Here), (vmsg(&"b"), Here)]); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here), (vmsg("b"), Here)]); assert_pages(&[0, 1, 2, 3, 4, 5]); // 0 and 1 are now since they both contain a permanently overweight message. @@ -211,7 +211,7 @@ fn reaping_overweight_fails_properly() { // Service with 1 weight; this is enough for 2:"c". assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); - assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"c"), Here)]); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("c"), Here)]); // 0, 1 and 2 are stale now because of a permanently overweight message. // 0 should be reapable since it is over the stale limit of 2. @@ -225,7 +225,7 @@ fn reaping_overweight_fails_properly() { // Service page 3. assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); - assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"bigbig 1"), Here)]); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 1"), Here)]); assert_pages(&[1, 2, 4, 5]); // Nothing reapable. @@ -235,7 +235,7 @@ fn reaping_overweight_fails_properly() { // Service page 4. assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); - assert_eq!(MessagesProcessed::take(), vec![(vmsg(&"bigbig 2"), Here)]); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 2"), Here)]); assert_pages(&[1, 2, 5]); assert_noop!(MessageQueue::do_reap_page(&Here, 3), Error::::NoPage); @@ -305,7 +305,7 @@ fn service_page_works() { let (page, mut msgs) = full_page::(); assert!(msgs >= 10, "pre-condition: need at least 10 msgs per page"); let mut book = book_for::(&page); - Pages::::insert(&Here, 0, page); + Pages::::insert(Here, 0, page); // Call it a few times each with a random weight limit. let mut rng = rand::rngs::StdRng::seed_from_u64(42); @@ -327,7 +327,7 @@ fn service_page_works() { assert_eq!(status, Bailed); } } - assert!(!Pages::::contains_key(&Here, 0), "The page got removed"); + assert!(!Pages::::contains_key(Here, 0), "The page got removed"); }); } @@ -398,11 +398,11 @@ fn bump_service_head_works() { use MessageOrigin::*; new_test_ext::().execute_with(|| { // Create a ready ring with three queues. - BookStateFor::::insert(Here, &empty_book::()); + BookStateFor::::insert(Here, empty_book::()); knit(&Here); - BookStateFor::::insert(There, &empty_book::()); + BookStateFor::::insert(There, empty_book::()); knit(&There); - BookStateFor::::insert(Everywhere(0), &empty_book::()); + BookStateFor::::insert(Everywhere(0), empty_book::()); knit(&Everywhere(0)); // Bump 99 times. @@ -455,11 +455,11 @@ fn bump_service_head_no_head_noops() { use MessageOrigin::*; new_test_ext::().execute_with(|| { // Create a ready ring with three queues. - BookStateFor::::insert(Here, &empty_book::()); + BookStateFor::::insert(Here, empty_book::()); knit(&Here); - BookStateFor::::insert(There, &empty_book::()); + BookStateFor::::insert(There, empty_book::()); knit(&There); - BookStateFor::::insert(Everywhere(0), &empty_book::()); + BookStateFor::::insert(Everywhere(0), empty_book::()); knit(&Everywhere(0)); // But remove the service head. @@ -527,7 +527,7 @@ fn service_page_item_skips_perm_overweight_message() { // Check that the message was skipped. let (pos, processed, payload) = page.peek_index(0).unwrap(); assert_eq!(pos, 0); - assert_eq!(processed, false); + assert!(!processed); assert_eq!(payload, b"TooMuch".encode()); }); } @@ -584,17 +584,17 @@ fn note_processed_at_pos_works() { for i in 0..msgs { let (pos, processed, _) = page.peek_index(i).unwrap(); - assert_eq!(processed, false); + assert!(!processed); assert_eq!(page.remaining as usize, msgs - i); page.note_processed_at_pos(pos); let (_, processed, _) = page.peek_index(i).unwrap(); - assert_eq!(processed, true); + assert!(processed); assert_eq!(page.remaining as usize, msgs - i - 1); } // `skip_first` still works fine. - for i in 0..msgs { + for _ in 0..msgs { page.peek_first().unwrap(); page.skip_first(false); } @@ -642,7 +642,7 @@ fn is_complete_works() { // Each message is marked as processed. for i in 0..msgs { let (_, processed, _) = page.peek_index(i).unwrap(); - assert_eq!(processed, true); + assert!(processed); } }); } @@ -727,7 +727,7 @@ fn page_try_append_message_with_remaining_size_works_works() { let mut rng = StdRng::seed_from_u64(42); // Now we keep appending messages with different lengths. while remaining >= header_size { - let take = rng.gen_range(0..=(remaining - header_size)) as usize; + let take = rng.gen_range(0..=(remaining - header_size)); let msg = vec![123u8; take]; page.try_append_message::(BoundedSlice::defensive_truncate_from(&msg)) .unwrap(); @@ -774,14 +774,14 @@ fn sweep_queue_works() { new_test_ext::().execute_with(|| { build_triple_ring(); - let book = BookStateFor::::get(&Here); + let book = BookStateFor::::get(Here); assert!(book.begin != book.end); // Removing the service head works assert_eq!(ServiceHead::::get(), Some(Here)); MessageQueue::sweep_queue(Here); assert_ring(&[There, Everywhere(0)]); // The book still exits, but has updated begin and end. - let book = BookStateFor::::get(&Here); + let book = BookStateFor::::get(Here); assert_eq!(book.begin, book.end); // Removing something that is not the service head works. @@ -789,12 +789,12 @@ fn sweep_queue_works() { MessageQueue::sweep_queue(Everywhere(0)); assert_ring(&[There]); // The book still exits, but has updated begin and end. - let book = BookStateFor::::get(&Everywhere(0)); + let book = BookStateFor::::get(Everywhere(0)); assert_eq!(book.begin, book.end); MessageQueue::sweep_queue(There); // The book still exits, but has updated begin and end. - let book = BookStateFor::::get(&There); + let book = BookStateFor::::get(There); assert_eq!(book.begin, book.end); assert_ring(&[]); }) @@ -805,7 +805,7 @@ fn sweep_queue_works() { fn sweep_queue_wraps_works() { use MessageOrigin::*; new_test_ext::().execute_with(|| { - BookStateFor::::insert(Here, &empty_book::()); + BookStateFor::::insert(Here, empty_book::()); knit(&Here); MessageQueue::sweep_queue(Here); @@ -828,7 +828,7 @@ fn footprint_works() { let origin = MessageOrigin::Here; let (page, msgs) = full_page::(); let book = book_for::(&page); - BookStateFor::::insert(&origin, &book); + BookStateFor::::insert(origin, book); let info = MessageQueue::footprint(origin); assert_eq!(info.count as usize, msgs); @@ -871,9 +871,9 @@ fn execute_overweight_works() { // Enqueue a message let origin = MessageOrigin::Here; - MessageQueue::enqueue_message(msg(&"weight=6"), origin); + MessageQueue::enqueue_message(msg("weight=6"), origin); // Load the current book - let book = BookStateFor::::get(&origin); + let book = BookStateFor::::get(origin); assert_eq!(book.message_count, 1); // Mark the message as permanently overweight. @@ -898,7 +898,7 @@ fn execute_overweight_works() { .unwrap(); assert_eq!(consumed, 6.into_weight()); // There is no message left in the book. - let book = BookStateFor::::get(&origin); + let book = BookStateFor::::get(origin); assert_eq!(book.message_count, 0); // Doing it again with enough weight will error. @@ -916,7 +916,7 @@ fn ready_ring_knit_basic_works() { use MessageOrigin::*; new_test_ext::().execute_with(|| { - BookStateFor::::insert(Here, &empty_book::()); + BookStateFor::::insert(Here, empty_book::()); for i in 0..10 { if i % 2 == 0 { @@ -937,9 +937,9 @@ fn ready_ring_knit_and_unknit_works() { new_test_ext::().execute_with(|| { // Place three queues into the storage. - BookStateFor::::insert(Here, &empty_book::()); - BookStateFor::::insert(There, &empty_book::()); - BookStateFor::::insert(Everywhere(0), &empty_book::()); + BookStateFor::::insert(Here, empty_book::()); + BookStateFor::::insert(There, empty_book::()); + BookStateFor::::insert(Everywhere(0), empty_book::()); // Knit them into the ready ring. assert_ring(&[]); From 09dc712f7a8dae6443f88be021be958b5c56f235 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Nov 2022 12:59:14 +0100 Subject: [PATCH 080/110] QueueChanged handler is called correctly Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 44 +++++++++++++++++++------------- frame/message-queue/src/mock.rs | 16 +++++++++++- frame/message-queue/src/tests.rs | 27 +++++++++++++++----- 3 files changed, 62 insertions(+), 25 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 39b6cd7ff3ec7..3cadf47b750f4 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -619,9 +619,19 @@ enum PageExecutionStatus { /// - The end of the page is reached but there could still be skipped messages. /// - The storage is corrupted. NoMore, - /// The execution progressed and executed some messages. The inner is the number of messages - /// removed from the queue. - Partial, +} + +/// The status after trying to execute the next item of a [`Page`]. +#[derive(PartialEq, Debug)] +enum ItemExecutionStatus { + /// The execution bailed because there was not enough weight remaining. + Bailed, + /// The item was not found. + NoItem, + /// Whether the execution of an item resulted in it being processed. + /// + /// One reason for `false` would be permanently overweight. + Executed(bool), } /// The status of an attempt to process a message. @@ -895,9 +905,6 @@ impl Pallet { PageExecutionStatus::Bailed => break, // Go to the next page if this one is at the end. PageExecutionStatus::NoMore => (), - PageExecutionStatus::Partial => { - defensive!("should progress till the end or bail"); - }, }; book_state.begin.saturating_inc(); } @@ -951,6 +958,7 @@ impl Pallet { // Execute as many messages as possible. let status = loop { + use ItemExecutionStatus::*; match Self::service_page_item( origin, page_index, @@ -959,18 +967,17 @@ impl Pallet { weight, overweight_limit, ) { - s @ Bailed | s @ NoMore => break s, + Bailed => break PageExecutionStatus::Bailed, + NoItem => break PageExecutionStatus::NoMore, // Keep going as long as we make progress... - Partial => { - total_processed.saturating_inc(); - continue - }, + Executed(true) => total_processed.saturating_inc(), + Executed(false) => (), } }; if page.is_complete() { debug_assert!( - status != PageExecutionStatus::Bailed, + status != Bailed, "we never bail if a page became complete" ); Pages::::remove(origin, page_index); @@ -990,19 +997,19 @@ impl Pallet { page: &mut PageOf, weight: &mut WeightMeter, overweight_limit: Weight, - ) -> PageExecutionStatus { + ) -> ItemExecutionStatus { // This ugly pre-checking is needed for the invariant // "we never bail if a page became complete". if page.is_complete() { - return PageExecutionStatus::NoMore + return ItemExecutionStatus::NoItem } if !weight.check_accrue(T::WeightInfo::service_page_item()) { - return PageExecutionStatus::Bailed + return ItemExecutionStatus::Bailed } let payload = &match page.peek_first() { Some(m) => m, - None => return PageExecutionStatus::NoMore, + None => return ItemExecutionStatus::NoItem, }[..]; use MessageExecutionStatus::*; @@ -1014,16 +1021,17 @@ impl Pallet { weight, overweight_limit, ) { - InsufficientWeight => return PageExecutionStatus::Bailed, + InsufficientWeight => return ItemExecutionStatus::Bailed, Processed | Unprocessable => true, Overweight => false, }; + if is_processed { book_state.message_count.saturating_dec(); book_state.size.saturating_reduce(payload.len() as u32); } page.skip_first(is_processed); - PageExecutionStatus::Partial + ItemExecutionStatus::Executed(is_processed) } /// Print the pages in each queue and the messages in each page. diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index a81728cb8fc99..b31623283c229 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -84,7 +84,7 @@ impl Config for Test { type WeightInfo = MockedWeightInfo; type MessageProcessor = RecordingMessageProcessor; type Size = u32; - type QueueChangeHandler = (); + type QueueChangeHandler = RecordingQueueChangeHandler; type HeapSize = HeapSize; type MaxStale = MaxStale; type ServiceWeight = ServiceWeight; @@ -212,6 +212,19 @@ impl ProcessMessage for CountingMessageProcessor { } } +parameter_types! { + /// Storage for `RecordingQueueChangeHandler`, do not use directly. + pub static QueueChanges: Vec<(MessageOrigin, u32, u32)> = vec![]; +} + +/// Records all queue changes into [`QueueChanges`]. +pub struct RecordingQueueChangeHandler; +impl OnQueueChanged for RecordingQueueChangeHandler { + fn on_queue_changed(id: MessageOrigin, items_count: u32, items_size: u32) { + QueueChanges::mutate(|cs| cs.push((id, items_count, items_size))); + } +} + /// Create new test externalities. /// /// Is generic since it is used by the unit test, integration tests and benchmarks. @@ -221,6 +234,7 @@ where { sp_tracing::try_init_simple(); WeightForCall::set(Default::default()); + QueueChanges::set(Default::default()); let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| frame_system::Pallet::::set_block_number(1.into())); diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index e3e7b9c9d8ec7..248a31eb0cdc5 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -156,6 +156,7 @@ fn reap_page_permanent_overweight_works() { MessageQueue::enqueue_message(msg("weight=2"), Here); } assert_eq!(Pages::::iter().count(), MaxStale::get() as usize + 10); + assert_eq!(QueueChanges::take().len(), 12); // Mark all pages as stale since their message is permanently overweight. MessageQueue::service_queues(1.into_weight()); @@ -164,12 +165,15 @@ fn reap_page_permanent_overweight_works() { assert_noop!(MessageQueue::do_reap_page(&Here, 10), Error::::NotReapable); } // Can reap the stale ones below the watermark. + let b = BookStateFor::::get(Here); for i in 0..10 { assert_ok!(MessageQueue::do_reap_page(&Here, i)); + assert_eq!(QueueChanges::take(), vec![(Here, b.message_count - (i + 1), b.size - (i + 1) * 8)]); } // Cannot reap any more pages. for (o, i, _) in Pages::::iter() { assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); + assert!(QueueChanges::take().is_empty()); } }); } @@ -388,7 +392,7 @@ fn service_page_item_bails() { &mut weight, overweight_limit, ), - PageExecutionStatus::Bailed + ItemExecutionStatus::Bailed ); }); } @@ -487,7 +491,7 @@ fn service_page_item_consumes_correct_weight() { &mut weight, overweight_limit ), - PageExecutionStatus::Partial + ItemExecutionStatus::Executed(true) ); assert_eq!(weight.consumed, 5.into_weight()); }); @@ -511,7 +515,7 @@ fn service_page_item_skips_perm_overweight_message() { &mut weight, overweight_limit ), - PageExecutionStatus::Partial + ItemExecutionStatus::Executed(false) ); assert_eq!(weight.consumed, 2.into_weight()); assert_last_event::( @@ -797,6 +801,9 @@ fn sweep_queue_works() { let book = BookStateFor::::get(There); assert_eq!(book.begin, book.end); assert_ring(&[]); + + // Sweeping a queue never calls OnQueueChanged. + assert!(QueueChanges::take().is_empty()); }) } @@ -833,11 +840,15 @@ fn footprint_works() { let info = MessageQueue::footprint(origin); assert_eq!(info.count as usize, msgs); assert_eq!(info.size, page.remaining_size); + + // Sweeping a queue never calls OnQueueChanged. + assert!(QueueChanges::take().is_empty()); }) } +/// The footprint of an invalid queue is the default footprint. #[test] -fn footprint_default_works() { +fn footprint_invalid_works() { new_test_ext::().execute_with(|| { let origin = MessageOrigin::Here; assert_eq!(MessageQueue::footprint(origin), Default::default()); @@ -876,8 +887,9 @@ fn execute_overweight_works() { let book = BookStateFor::::get(origin); assert_eq!(book.message_count, 1); - // Mark the message as permanently overweight. + // Mark the message as peermanently overweight. assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); + assert_eq!(QueueChanges::take(), vec![(origin, 1, 8)]); assert_last_event::( Event::OverweightEnqueued { hash: ::Hashing::hash(b"weight=6"), @@ -887,16 +899,18 @@ fn execute_overweight_works() { } .into(), ); + // Now try to execute it with too few weight. let consumed = ::execute_overweight(5.into_weight(), (origin, 0, 0)); assert_eq!(consumed, Err(ExecuteOverweightError::InsufficientWeight)); // Execute it with enough weight. + assert!(QueueChanges::take().is_empty()); let consumed = ::execute_overweight(7.into_weight(), (origin, 0, 0)) .unwrap(); - assert_eq!(consumed, 6.into_weight()); + assert_eq!(QueueChanges::take(), vec![(origin, 0, 0)]); // There is no message left in the book. let book = BookStateFor::::get(origin); assert_eq!(book.message_count, 0); @@ -905,6 +919,7 @@ fn execute_overweight_works() { let consumed = ::execute_overweight(70.into_weight(), (origin, 0, 0)); assert_eq!(consumed, Err(ExecuteOverweightError::NotFound)); + assert!(QueueChanges::take().is_empty()); }); } From a6063ed480fd46f8554879cff7fb7d1bc6eedb80 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Nov 2022 13:00:24 +0100 Subject: [PATCH 081/110] Update benches Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 75 +++++++---- frame/message-queue/src/mock.rs | 3 + frame/message-queue/src/weights.rs | 159 +++++++++++++----------- 3 files changed, 140 insertions(+), 97 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 4cbf462b4dec7..e16e28682831a 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -17,27 +17,56 @@ //! 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::{benchmarks, whitelisted_caller}; use frame_support::traits::Get; -use frame_system::{Pallet as System, RawOrigin}; +use frame_system::RawOrigin; use sp_std::prelude::*; benchmarks! { where_clause { where - // NOTE: We need to generate multiple origins, therefore Origin is `From`. - <::MessageProcessor as ProcessMessage>::Origin: From, + // NOTE: We need to generate multiple origins, therefore Origin is `From`. The + // `PartialEq` is for asserting the outcome of the ring (un)knitting and *could* be + // removed if really necessary. + <::MessageProcessor as ProcessMessage>::Origin: From + PartialEq, ::Size: From, } - // `service_queue` without any page processing or unknitting. + // Worst case path of `ready_ring_knit`. The benchmark is unused and for verification only. + ready_ring_knit { + let mid: MessageOriginOf:: = 1.into(); + build_ring::(&[0.into(), mid.clone(), 2.into()]); + unknit::(&mid); + assert_ring::(&[0.into(), 2.into()]); + let mut neighbours = None; + }: { + neighbours = MessageQueue::::ready_ring_knit(&mid).ok(); + } verify { + // The neighbours needs to be modified manually. + BookStateFor::::mutate(&mid, |b| { b.ready_neighbours = neighbours }); + assert_ring::(&[0.into(), 2.into(), mid]); + } + + // Worst case path of `ready_ring_unknit`. The benchmark is unused and for verification only. + ready_ring_unknit { + build_ring::(&[0.into(), 1.into(), 2.into()]); + assert_ring::(&[0.into(), 1.into(), 2.into()]); + let o: MessageOriginOf:: = 0.into(); + let neighbours = BookStateFor::::get(&o).ready_neighbours.unwrap(); + }: { + MessageQueue::::ready_ring_unknit(&o, neighbours); + } verify { + assert_ring::(&[1.into(), 2.into()]); + } + + // `service_queues` without any queue processing. service_queue_base { - let mut meter = WeightMeter::max_limit(); }: { - MessageQueue::::service_queue(0u32.into(), &mut meter, Weight::MAX) + MessageQueue::::service_queue(0.into(), &mut WeightMeter::max_limit(), Weight::MAX) } // `service_page` without any message processing but with page completion. @@ -67,18 +96,7 @@ benchmarks! { MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) } - // Worst case path of `ready_ring_unknit`. - ready_ring_unknit { - let origin: MessageOriginOf = 0.into(); - let neighbours = Neighbours::> { - prev: 0.into(), - next: 1.into() - }; - ServiceHead::::put(&origin); - }: { - MessageQueue::::ready_ring_unknit(&origin, neighbours); - } - + // Processing a single message from a page. service_page_item { let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; @@ -88,7 +106,7 @@ benchmarks! { let mut weight = WeightMeter::max_limit(); }: { let status = MessageQueue::::service_page_item(&0u32.into(), 0, &mut book, &mut page, &mut weight, Weight::MAX); - assert_eq!(status, PageExecutionStatus::Partial); + assert_eq!(status, ItemExecutionStatus::Executed(true)); } verify { // Check that it was processed. assert_last_event::(Event::Processed { @@ -108,6 +126,7 @@ benchmarks! { MessageQueue::::bump_service_head(&mut weight); } verify { assert_eq!(ServiceHead::::get().unwrap(), 10u32.into()); + assert_eq!(weight.consumed, T::WeightInfo::bump_service_head()); } reap_page { @@ -135,20 +154,24 @@ benchmarks! { assert!(!Pages::::contains_key(&origin, 0)); } + // Worst case for `execute_overweight`. + // + // 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. + // TODO one for page remove and one for insert path execute_overweight { - // Mock the storage let origin: MessageOriginOf = 0.into(); - let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; - let mut page = page::(&msg); - page.skip_first(false); // One skipped un-processed overweight message. + let (mut page, msgs) = full_page::(); + // Skip all messages. + for _ in 0..msgs { + page.skip_first(false); + } let book = book_for::(&page); Pages::::insert(&origin, 0, &page); BookStateFor::::insert(&origin, &book); - - }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0u32, 0u32.into(), Weight::MAX) + }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX) verify { assert_last_event::(Event::Processed { - hash: T::Hashing::hash(&msg), origin: 0.into(), + hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), weight_used: Weight::from_parts(1, 1), success: true }.into()); } diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index b31623283c229..0b6c6923f9b48 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -136,6 +136,9 @@ impl crate::weights::WeightInfo for MockedWeightInfo { fn service_page_item() -> Weight { WeightForCall::get().get("service_page_item").copied().unwrap_or_default() } + fn ready_ring_knit() -> Weight { + WeightForCall::get().get("ready_ring_knit").copied().unwrap_or_default() + } fn ready_ring_unknit() -> Weight { WeightForCall::get().get("ready_ring_unknit").copied().unwrap_or_default() } diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index 92ac1d432f6bc..f5fe0a7aab8bb 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_message_queue //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-15, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-11-24, STEPS: `50`, REPEAT: 200, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `oty-parity`, CPU: `11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 @@ -17,7 +17,7 @@ // --steps // 50 // --repeat -// 20 +// 200 // --template // .maintain/frame-weight-template.hbs // --output @@ -32,10 +32,11 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_message_queue. pub trait WeightInfo { + fn ready_ring_knit() -> Weight; + fn ready_ring_unknit() -> Weight; fn service_queue_base() -> Weight; fn service_page_base_completion() -> Weight; fn service_page_base_no_completion() -> Weight; - fn ready_ring_unknit() -> Weight; fn service_page_item() -> Weight; fn bump_service_head() -> Weight; fn reap_page() -> Weight; @@ -45,122 +46,138 @@ pub trait WeightInfo { /// Weights for pallet_message_queue using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + // Storage: MessageQueue ServiceHead (r:1 w:0) + // Storage: MessageQueue BookStateFor (r:2 w:2) + fn ready_ring_knit() -> Weight { + // Minimum execution time: 8_321 nanoseconds. + Weight::from_ref_time(8_742_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + fn ready_ring_unknit() -> Weight { + // Minimum execution time: 8_474 nanoseconds. + Weight::from_ref_time(12_972_000) + .saturating_add(T::DbWeight::get().reads(3)) + .saturating_add(T::DbWeight::get().writes(3)) + } // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_677 nanoseconds. - Weight::from_ref_time(3_838_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 3_532 nanoseconds. + Weight::from_ref_time(3_786_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_completion() -> Weight { - // Minimum execution time: 5_431 nanoseconds. - Weight::from_ref_time(5_685_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_268 nanoseconds. + Weight::from_ref_time(7_434_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 5_598 nanoseconds. - Weight::from_ref_time(5_816_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: MessageQueue BookStateFor (r:2 w:2) - // Storage: MessageQueue ServiceHead (r:1 w:1) - fn ready_ring_unknit() -> Weight { - // Minimum execution time: 5_926 nanoseconds. - Weight::from_ref_time(6_208_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Minimum execution time: 5_313 nanoseconds. + Weight::from_ref_time(5_546_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } fn service_page_item() -> Weight { - // Minimum execution time: 74_873 nanoseconds. - Weight::from_ref_time(100_821_000 as u64) + // Minimum execution time: 74_606 nanoseconds. + Weight::from_ref_time(111_057_000) } // Storage: MessageQueue ServiceHead (r:1 w:1) // Storage: MessageQueue BookStateFor (r:1 w:0) fn bump_service_head() -> Weight { - // Minimum execution time: 8_222 nanoseconds. - Weight::from_ref_time(9_503_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_524 nanoseconds. + Weight::from_ref_time(6_068_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(1)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 27_052 nanoseconds. - Weight::from_ref_time(36_827_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 25_297 nanoseconds. + Weight::from_ref_time(25_854_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight() -> Weight { - // Minimum execution time: 92_801 nanoseconds. - Weight::from_ref_time(93_715_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Minimum execution time: 73_758 nanoseconds. + Weight::from_ref_time(77_262_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) } } // For backwards compatibility and tests impl WeightInfo for () { + // Storage: MessageQueue ServiceHead (r:1 w:0) + // Storage: MessageQueue BookStateFor (r:2 w:2) + fn ready_ring_knit() -> Weight { + // Minimum execution time: 8_321 nanoseconds. + Weight::from_ref_time(8_742_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:2 w:2) + // Storage: MessageQueue ServiceHead (r:1 w:1) + fn ready_ring_unknit() -> Weight { + // Minimum execution time: 8_474 nanoseconds. + Weight::from_ref_time(12_972_000) + .saturating_add(RocksDbWeight::get().reads(3)) + .saturating_add(RocksDbWeight::get().writes(3)) + } // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_677 nanoseconds. - Weight::from_ref_time(3_838_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 3_532 nanoseconds. + Weight::from_ref_time(3_786_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_completion() -> Weight { - // Minimum execution time: 5_431 nanoseconds. - Weight::from_ref_time(5_685_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_268 nanoseconds. + Weight::from_ref_time(7_434_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 5_598 nanoseconds. - Weight::from_ref_time(5_816_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: MessageQueue BookStateFor (r:2 w:2) - // Storage: MessageQueue ServiceHead (r:1 w:1) - fn ready_ring_unknit() -> Weight { - // Minimum execution time: 5_926 nanoseconds. - Weight::from_ref_time(6_208_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Minimum execution time: 5_313 nanoseconds. + Weight::from_ref_time(5_546_000) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(1)) } fn service_page_item() -> Weight { - // Minimum execution time: 74_873 nanoseconds. - Weight::from_ref_time(100_821_000 as u64) + // Minimum execution time: 74_606 nanoseconds. + Weight::from_ref_time(111_057_000) } // Storage: MessageQueue ServiceHead (r:1 w:1) // Storage: MessageQueue BookStateFor (r:1 w:0) fn bump_service_head() -> Weight { - // Minimum execution time: 8_222 nanoseconds. - Weight::from_ref_time(9_503_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Minimum execution time: 5_524 nanoseconds. + Weight::from_ref_time(6_068_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 27_052 nanoseconds. - Weight::from_ref_time(36_827_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 25_297 nanoseconds. + Weight::from_ref_time(25_854_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight() -> Weight { - // Minimum execution time: 92_801 nanoseconds. - Weight::from_ref_time(93_715_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Minimum execution time: 73_758 nanoseconds. + Weight::from_ref_time(77_262_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) } } From 1f102a95f350711a97100201e6cf91a9a3aa7543 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Nov 2022 13:01:15 +0100 Subject: [PATCH 082/110] Abstract testing functions Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/mock.rs | 49 ++++++------------------- frame/message-queue/src/mock_helpers.rs | 44 ++++++++++++++++++++++ 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 0b6c6923f9b48..710feef7a6b34 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -257,48 +257,21 @@ pub fn assert_pages(indices: &[u32]) { } } -/// Knit a queue into the ready-ring and write it back to storage. -pub fn knit(o: &MessageOrigin) { - let mut b = BookStateFor::::get(o); - b.ready_neighbours = MessageQueue::ready_ring_knit(o).ok().defensive(); - BookStateFor::::insert(o, b); -} - -/// Unknit a queue into the ready-ring and write it back to storage. -pub fn unknit(o: &MessageOrigin) { - let mut b = BookStateFor::::get(o); - MessageQueue::ready_ring_unknit(o, b.ready_neighbours.unwrap()); - b.ready_neighbours = None; - BookStateFor::::insert(o, b); -} - /// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. pub fn build_triple_ring() { use MessageOrigin::*; - BookStateFor::::insert(Here, empty_book::()); - BookStateFor::::insert(There, empty_book::()); - BookStateFor::::insert(Everywhere(0), empty_book::()); + build_ring::(&[Here, There, Everywhere(0)]) +} - // Knit them into the ready ring. - knit(&Here); - knit(&There); - knit(&Everywhere(0)); - assert_ring(&[Here, There, Everywhere(0)]); +/// Shim to get rid of the annoying `::` everywhere. +pub fn assert_ring(queues: &[MessageOrigin]) { + super::mock_helpers::assert_ring::(queues); } -/// Check that the Ready Ring consists of `neighbours` in that exact order. -/// -/// Also check that all backlinks are valid and that the first element is the service head. -pub fn assert_ring(neighbours: &[MessageOrigin]) { - for (i, origin) in neighbours.iter().enumerate() { - let book = BookStateFor::::get(origin); - assert_eq!( - book.ready_neighbours, - Some(Neighbours { - prev: neighbours[(i + neighbours.len() - 1) % neighbours.len()], - next: neighbours[(i + 1) % neighbours.len()], - }) - ); - } - assert_eq!(ServiceHead::::get(), neighbours.first().cloned()); +pub fn knit(queue: &MessageOrigin) { + super::mock_helpers::knit::(queue); +} + +pub fn unknit(queue: &MessageOrigin) { + super::mock_helpers::unknit::(queue); } diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index fc6768254f81b..278a60b9ecf0f 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -19,6 +19,7 @@ //! Cannot be put into mock.rs since benchmarks require no-std and mock.rs is std. use crate::*; +use frame_support::traits::Defensive; /// Converts `Self` into a `Weight` by using `Self` for all components. pub trait IntoWeight { @@ -134,3 +135,46 @@ pub fn setup_bump_service_head( ServiceHead::::put(¤t); BookStateFor::::insert(¤t, &book); } + +/// Knit a queue into the ready-ring and write it back to storage. +pub fn knit(o: &<::MessageProcessor as ProcessMessage>::Origin) { + let mut b = BookStateFor::::get(o); + b.ready_neighbours = crate::Pallet::::ready_ring_knit(o).ok().defensive(); + BookStateFor::::insert(o, b); +} + +/// Unknit a queue into the ready-ring and write it back to storage. +pub fn unknit(o: &<::MessageProcessor as ProcessMessage>::Origin) { + let mut b = BookStateFor::::get(o); + crate::Pallet::::ready_ring_unknit(o, b.ready_neighbours.unwrap()); + b.ready_neighbours = None; + BookStateFor::::insert(o, b); +} + +/// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. +pub fn build_ring(queues: &[<::MessageProcessor as ProcessMessage>::Origin]) { + for queue in queues { + BookStateFor::::insert(queue, empty_book::()); + } + for queue in queues { + knit::(queue); + } + assert_ring::(queues); +} + +/// Check that the Ready Ring consists of `queues` in that exact order. +/// +/// Also check that all backlinks are valid and that the first element is the service head. +pub fn assert_ring(queues: &[<::MessageProcessor as ProcessMessage>::Origin]) { + for (i, origin) in queues.iter().enumerate() { + let book = BookStateFor::::get(origin); + assert_eq!( + book.ready_neighbours, + Some(Neighbours { + prev: queues[(i + queues.len() - 1) % queues.len()].clone(), + next: queues[(i + 1) % queues.len()].clone(), + }) + ); + } + assert_eq!(ServiceHead::::get(), queues.first().cloned()); +} From f10a3020360afa713570f6e62b1bb9c87f035ddb Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Nov 2022 13:11:05 +0100 Subject: [PATCH 083/110] More tests Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/tests.rs | 92 ++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 248a31eb0cdc5..4a39678458440 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -147,6 +147,38 @@ fn queue_priority_reset_once_serviced() { }); } +#[test] +fn service_queues_basic_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + MessageQueue::enqueue_messages(vec![msg("a"), msg("ab"), msg("abc")].into_iter(), Here); + MessageQueue::enqueue_messages(vec![msg("x"), msg("xy"), msg("xyz")].into_iter(), There); + assert_eq!(QueueChanges::take(), vec![(Here, 3, 6), (There, 3, 6)]); + + // Service one message from `Here`. + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 2, 5)]); + + // Service one message from `There`. + ServiceHead::::set(There.into()); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("x"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 2, 5)]); + + // Service the remaining from `Here`. + ServiceHead::::set(Here.into()); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("ab"), Here), (vmsg("abc"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 0, 0)]); + + // Service all remaining messages. + assert_eq!(MessageQueue::service_queues(Weight::MAX), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("xy"), There), (vmsg("xyz"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 0, 0)]); + }); +} + #[test] fn reap_page_permanent_overweight_works() { use MessageOrigin::*; @@ -974,3 +1006,63 @@ fn ready_ring_knit_and_unknit_works() { assert_ring(&[]); }); } + +#[test] +fn enqueue_message_works() { + use MessageOrigin::*; + let max_msg_per_page = ::HeapSize::get() / (ItemHeader::<::Size>::max_encoded_len() as u32 + 1); + + new_test_ext::().execute_with(|| { + // Enqueue messages which should fill three pages. + let n = (max_msg_per_page * 3); + for i in 1..=n { + MessageQueue::enqueue_message(msg("a"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, i, i)], "OnQueueChanged not called"); + } + assert_eq!(Pages::::iter().count(), 3); + + // Enqueue one more onto page 4. + MessageQueue::enqueue_message(msg("abc"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, n+1, n+3)]); + assert_eq!(Pages::::iter().count(), 4); + + // Check the state. + assert_eq!(BookStateFor::::iter().count(), 1); + let book = BookStateFor::::get(Here); + assert_eq!(book.message_count, n+1); + assert_eq!(book.size, n+3); + assert_eq!((book.begin, book.end), (0, 4)); + assert_eq!(book.count as usize, Pages::::iter().count()); + }); +} + +#[test] +fn enqueue_messages_works() { + use MessageOrigin::*; + let max_msg_per_page = ::HeapSize::get() / (ItemHeader::<::Size>::max_encoded_len() as u32 + 1); + + new_test_ext::().execute_with(|| { + // Enqueue messages which should fill three pages. + let n = (max_msg_per_page * 3); + let msgs = vec![msg("a"); n as usize]; + + // Now queue all messages at once. + MessageQueue::enqueue_messages(msgs.into_iter(), Here); + // The changed handler should only be called once. + assert_eq!(QueueChanges::take(), vec![(Here, n, n)], "OnQueueChanged not called"); + assert_eq!(Pages::::iter().count(), 3); + + // Enqueue one more onto page 4. + MessageQueue::enqueue_message(msg("abc"), Here); + assert_eq!(QueueChanges::take(), vec![(Here, n+1, n+3)]); + assert_eq!(Pages::::iter().count(), 4); + + // Check the state. + assert_eq!(BookStateFor::::iter().count(), 1); + let book = BookStateFor::::get(Here); + assert_eq!(book.message_count, n+1); + assert_eq!(book.size, n+3); + assert_eq!((book.begin, book.end), (0, 4)); + assert_eq!(book.count as usize, Pages::::iter().count()); + }); +} From b4c31ebe0f94962c2b95f6e4a285ab12b3173dee Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Nov 2022 13:11:22 +0100 Subject: [PATCH 084/110] Cleanup Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 3cadf47b750f4..0715a995b6a8a 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -386,7 +386,7 @@ impl< /// A single link in the double-linked Ready Ring list. #[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(PartialEq))] +#[cfg_attr(any(feature = "std", feature = "runtime-benchmarks"), derive(PartialEq))] pub struct Neighbours { /// The previous queue. prev: MessageOrigin, @@ -565,8 +565,6 @@ pub mod pallet { /// Check all assumptions about [`crate::Config`]. fn integrity_test() { assert!(!MaxMessageLenOf::::get().is_zero(), "HeapSize too low"); - // This value gets squared and should not overflow. - assert!(T::MaxStale::get().checked_pow(2).is_some(), "MaxStale too large"); } } @@ -694,7 +692,6 @@ impl Pallet { }); if let Some(head) = ServiceHead::::get() { if &head == origin { - // NOTE: This case is benchmarked by `ready_ring_unknit`. ServiceHead::::put(neighbours.next); } } else { @@ -1151,14 +1148,14 @@ impl, HeapSize: Get } } -/// The maximal message length of this pallet. +/// The maximal message length. pub type MaxMessageLenOf = MaxMessageLen, ::Size, ::HeapSize>; -/// The maximal encoded origin length of this pallet. +/// The maximal encoded origin length. pub type MaxOriginLenOf = MaxEncodedLenOf>; -/// The `MessageOrigin` or this pallet. +/// The `MessageOrigin` of this pallet. pub type MessageOriginOf = <::MessageProcessor as ProcessMessage>::Origin; -/// The maximal heap size of this pallet. +/// The maximal heap size of a page. pub type HeapSizeU32Of = IntoU32<::HeapSize, ::Size>; /// The [`Page`] of this pallet. pub type PageOf = Page<::Size, ::HeapSize>; From 7cb2e51cf3f1266e2695e7f1fe0e96b11dd8e2a6 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Nov 2022 13:22:38 +0100 Subject: [PATCH 085/110] Clippy Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/mock.rs | 2 +- frame/message-queue/src/tests.rs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 710feef7a6b34..b4494fb7c1e1b 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -22,7 +22,7 @@ use super::*; use crate as pallet_message_queue; use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64, Defensive}, + traits::{ConstU32, ConstU64}, }; use sp_core::H256; use sp_runtime::{ diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 4a39678458440..5431c402ed488 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -942,6 +942,7 @@ fn execute_overweight_works() { let consumed = ::execute_overweight(7.into_weight(), (origin, 0, 0)) .unwrap(); + assert_eq!(consumed, 6.into_weight()); assert_eq!(QueueChanges::take(), vec![(origin, 0, 0)]); // There is no message left in the book. let book = BookStateFor::::get(origin); @@ -1014,7 +1015,7 @@ fn enqueue_message_works() { new_test_ext::().execute_with(|| { // Enqueue messages which should fill three pages. - let n = (max_msg_per_page * 3); + let n = max_msg_per_page * 3; for i in 1..=n { MessageQueue::enqueue_message(msg("a"), Here); assert_eq!(QueueChanges::take(), vec![(Here, i, i)], "OnQueueChanged not called"); @@ -1043,7 +1044,7 @@ fn enqueue_messages_works() { new_test_ext::().execute_with(|| { // Enqueue messages which should fill three pages. - let n = (max_msg_per_page * 3); + let n = max_msg_per_page * 3; let msgs = vec![msg("a"); n as usize]; // Now queue all messages at once. From b1e139beff31d4db09fc1a7ba5f5c21306050f69 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Nov 2022 13:22:51 +0100 Subject: [PATCH 086/110] fmt Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 2 +- frame/message-queue/src/lib.rs | 7 ++----- frame/message-queue/src/mock_helpers.rs | 8 ++++++-- frame/message-queue/src/tests.rs | 27 ++++++++++++++----------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index e16e28682831a..5185965ce22a3 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -96,7 +96,7 @@ benchmarks! { MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) } - + // Processing a single message from a page. service_page_item { let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 0715a995b6a8a..cf904206a9418 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -973,10 +973,7 @@ impl Pallet { }; if page.is_complete() { - debug_assert!( - status != Bailed, - "we never bail if a page became complete" - ); + debug_assert!(status != Bailed, "we never bail if a page became complete"); Pages::::remove(origin, page_index); debug_assert!(book_state.count > 0, "completing a page implies there are pages"); book_state.count.saturating_dec(); @@ -1022,7 +1019,7 @@ impl Pallet { Processed | Unprocessable => true, Overweight => false, }; - + if is_processed { book_state.message_count.saturating_dec(); book_state.size.saturating_reduce(payload.len() as u32); diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 278a60b9ecf0f..9675410bd8d5d 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -152,7 +152,9 @@ pub fn unknit(o: &<::MessageProcessor as ProcessMessage> } /// Build a ring with three queues: `Here`, `There` and `Everywhere(0)`. -pub fn build_ring(queues: &[<::MessageProcessor as ProcessMessage>::Origin]) { +pub fn build_ring( + queues: &[<::MessageProcessor as ProcessMessage>::Origin], +) { for queue in queues { BookStateFor::::insert(queue, empty_book::()); } @@ -165,7 +167,9 @@ pub fn build_ring(queues: &[<::MessageProcessor as Proce /// Check that the Ready Ring consists of `queues` in that exact order. /// /// Also check that all backlinks are valid and that the first element is the service head. -pub fn assert_ring(queues: &[<::MessageProcessor as ProcessMessage>::Origin]) { +pub fn assert_ring( + queues: &[<::MessageProcessor as ProcessMessage>::Origin], +) { for (i, origin) in queues.iter().enumerate() { let book = BookStateFor::::get(origin); assert_eq!( diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 5431c402ed488..5cfc884bef25b 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -21,9 +21,7 @@ use crate::{mock::*, *}; -use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, StorageNoopGuard, -}; +use frame_support::{assert_noop, assert_ok, assert_storage_noop, StorageNoopGuard}; use rand::{rngs::StdRng, Rng, SeedableRng}; #[test] @@ -200,7 +198,10 @@ fn reap_page_permanent_overweight_works() { let b = BookStateFor::::get(Here); for i in 0..10 { assert_ok!(MessageQueue::do_reap_page(&Here, i)); - assert_eq!(QueueChanges::take(), vec![(Here, b.message_count - (i + 1), b.size - (i + 1) * 8)]); + assert_eq!( + QueueChanges::take(), + vec![(Here, b.message_count - (i + 1), b.size - (i + 1) * 8)] + ); } // Cannot reap any more pages. for (o, i, _) in Pages::::iter() { @@ -1011,7 +1012,8 @@ fn ready_ring_knit_and_unknit_works() { #[test] fn enqueue_message_works() { use MessageOrigin::*; - let max_msg_per_page = ::HeapSize::get() / (ItemHeader::<::Size>::max_encoded_len() as u32 + 1); + let max_msg_per_page = ::HeapSize::get() / + (ItemHeader::<::Size>::max_encoded_len() as u32 + 1); new_test_ext::().execute_with(|| { // Enqueue messages which should fill three pages. @@ -1024,14 +1026,14 @@ fn enqueue_message_works() { // Enqueue one more onto page 4. MessageQueue::enqueue_message(msg("abc"), Here); - assert_eq!(QueueChanges::take(), vec![(Here, n+1, n+3)]); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); assert_eq!(Pages::::iter().count(), 4); // Check the state. assert_eq!(BookStateFor::::iter().count(), 1); let book = BookStateFor::::get(Here); - assert_eq!(book.message_count, n+1); - assert_eq!(book.size, n+3); + assert_eq!(book.message_count, n + 1); + assert_eq!(book.size, n + 3); assert_eq!((book.begin, book.end), (0, 4)); assert_eq!(book.count as usize, Pages::::iter().count()); }); @@ -1040,7 +1042,8 @@ fn enqueue_message_works() { #[test] fn enqueue_messages_works() { use MessageOrigin::*; - let max_msg_per_page = ::HeapSize::get() / (ItemHeader::<::Size>::max_encoded_len() as u32 + 1); + let max_msg_per_page = ::HeapSize::get() / + (ItemHeader::<::Size>::max_encoded_len() as u32 + 1); new_test_ext::().execute_with(|| { // Enqueue messages which should fill three pages. @@ -1055,14 +1058,14 @@ fn enqueue_messages_works() { // Enqueue one more onto page 4. MessageQueue::enqueue_message(msg("abc"), Here); - assert_eq!(QueueChanges::take(), vec![(Here, n+1, n+3)]); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); assert_eq!(Pages::::iter().count(), 4); // Check the state. assert_eq!(BookStateFor::::iter().count(), 1); let book = BookStateFor::::get(Here); - assert_eq!(book.message_count, n+1); - assert_eq!(book.size, n+3); + assert_eq!(book.message_count, n + 1); + assert_eq!(book.size, n + 3); assert_eq!((book.begin, book.end), (0, 4)); assert_eq!(book.count as usize, Pages::::iter().count()); }); From eb209e2871adeac1a0a1b652ca8bf54feada1c5b Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Nov 2022 14:10:08 +0100 Subject: [PATCH 087/110] Simplify tests Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/tests.rs | 46 +++++++++++++------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 5cfc884bef25b..6a1ce0d61d83d 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -47,37 +47,28 @@ fn enqueue_within_one_page_works() { MessageQueue::enqueue_message(msg("b"), Here); MessageQueue::enqueue_message(msg("c"), Here); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); - assert_eq!(MessagesProcessed::get(), vec![(b"a".to_vec(), Here), (b"b".to_vec(), Here)]); + assert_eq!(MessagesProcessed::take(), vec![(b"a".to_vec(), Here), (b"b".to_vec(), Here)]); - MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 1.into_weight()); - assert_eq!(MessagesProcessed::get(), vec![(b"c".to_vec(), Here)]); + assert_eq!(MessagesProcessed::take(), vec![(b"c".to_vec(), Here)]); - MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); - assert_eq!(MessagesProcessed::get(), vec![]); + assert!(MessagesProcessed::get().is_empty()); - MessageQueue::enqueue_messages( - [ - BoundedSlice::truncate_from(&b"a"[..]), - BoundedSlice::truncate_from(&b"b"[..]), - BoundedSlice::truncate_from(&b"c"[..]), - ] - .into_iter(), - There, - ); + MessageQueue::enqueue_messages([msg("a"), msg("b"), msg("c")].into_iter(), There); - MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); - assert_eq!(MessagesProcessed::get(), vec![(b"a".to_vec(), There), (b"b".to_vec(), There),]); + assert_eq!( + MessagesProcessed::take(), + vec![(b"a".to_vec(), There), (b"b".to_vec(), There),] + ); - MessageQueue::enqueue_message(BoundedSlice::truncate_from(&b"d"[..]), Everywhere(1)); + MessageQueue::enqueue_message(msg("d"), Everywhere(1)); - MessagesProcessed::set(vec![]); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!(MessageQueue::service_queues(2.into_weight()), 0.into_weight()); assert_eq!( - MessagesProcessed::get(), + MessagesProcessed::take(), vec![(b"c".to_vec(), There), (b"d".to_vec(), Everywhere(1))] ); }); @@ -100,7 +91,7 @@ fn queue_priority_retains() { // doees not empty queue 2, so service head will end at 2. assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!( - MessagesProcessed::get(), + MessagesProcessed::take(), vec![(vmsg("a"), Everywhere(1)), (vmsg("b"), Everywhere(2)),] ); assert_ring(&[Everywhere(2), Everywhere(3)]); @@ -108,12 +99,7 @@ fn queue_priority_retains() { assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!( MessagesProcessed::get(), - vec![ - (vmsg("a"), Everywhere(1)), - (vmsg("b"), Everywhere(2)), - (vmsg("d"), Everywhere(2)), - (vmsg("c"), Everywhere(3)), - ] + vec![(vmsg("d"), Everywhere(2)), (vmsg("c"), Everywhere(3)),] ); assert_ring(&[]); }); @@ -240,7 +226,7 @@ fn reaping_overweight_fails_properly() { assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here), (vmsg("b"), Here)]); assert_pages(&[0, 1, 2, 3, 4, 5]); - // 0 and 1 are now since they both contain a permanently overweight message. + // 0 and 1 are now stale since they both contain a permanently overweight message. // Nothing is reapable yet, because we haven't hit the stale limit of 2. for (o, i, _) in Pages::::iter() { assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); @@ -906,6 +892,7 @@ fn footprint_on_swept_works() { }) } +// TODO test page removal #[test] fn execute_overweight_works() { new_test_ext::().execute_with(|| { @@ -920,7 +907,7 @@ fn execute_overweight_works() { let book = BookStateFor::::get(origin); assert_eq!(book.message_count, 1); - // Mark the message as peermanently overweight. + // Mark the message as permanently overweight. assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); assert_eq!(QueueChanges::take(), vec![(origin, 1, 8)]); assert_last_event::( @@ -939,6 +926,7 @@ fn execute_overweight_works() { assert_eq!(consumed, Err(ExecuteOverweightError::InsufficientWeight)); // Execute it with enough weight. + assert_eq!(Pages::::iter().count(), 1); assert!(QueueChanges::take().is_empty()); let consumed = ::execute_overweight(7.into_weight(), (origin, 0, 0)) @@ -948,6 +936,8 @@ fn execute_overweight_works() { // There is no message left in the book. let book = BookStateFor::::get(origin); assert_eq!(book.message_count, 0); + // And no more pages. + assert_eq!(Pages::::iter().count(), 0); // Doing it again with enough weight will error. let consumed = From d319aadbdfa40f41244494b95e4ac0553418f2c5 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 24 Nov 2022 14:10:35 +0100 Subject: [PATCH 088/110] Make stuff compile Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 3 +-- frame/message-queue/src/mock_helpers.rs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index cf904206a9418..e4c4872aec3a7 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -385,8 +385,7 @@ impl< } /// A single link in the double-linked Ready Ring list. -#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug)] -#[cfg_attr(any(feature = "std", feature = "runtime-benchmarks"), derive(PartialEq))] +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebug, PartialEq)] pub struct Neighbours { /// The previous queue. prev: MessageOrigin, diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 9675410bd8d5d..e8ea7be1bc841 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -117,6 +117,7 @@ pub fn book_for(page: &PageOf) -> BookStateOf { } /// Assert the last event that was emitted. +#[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] pub fn assert_last_event(generic_event: ::RuntimeEvent) { assert!( !frame_system::Pallet::::block_number().is_zero(), From 136a5210aaf717cae183d19f93893a2679f12a29 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 28 Nov 2022 15:34:59 +0100 Subject: [PATCH 089/110] Extend overweight execution benchmark Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 33 ++++++++- frame/message-queue/src/lib.rs | 9 ++- frame/message-queue/src/mock.rs | 13 +++- frame/message-queue/src/weights.rs | 97 +++++++++++++++---------- 4 files changed, 104 insertions(+), 48 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 5185965ce22a3..630047f079000 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -154,11 +154,33 @@ benchmarks! { assert!(!Pages::::contains_key(&origin, 0)); } - // Worst case for `execute_overweight`. + // 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. - // TODO one for page remove and one for insert path - execute_overweight { + execute_overweight_page_removed { + let origin: MessageOriginOf = 0.into(); + let (mut page, msgs) = full_page::(); + // Skip all messages. + for _ in 1..msgs { + page.skip_first(true); + } + page.skip_first(false); + let book = book_for::(&page); + Pages::::insert(&origin, 0, &page); + BookStateFor::::insert(&origin, &book); + }: { + MessageQueue::::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap() + } + verify { + assert_last_event::(Event::Processed { + hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), + weight_used: Weight::from_parts(1, 1), success: true + }.into()); + assert!(!Pages::::contains_key(&origin, 0), "Page must be removed"); + } + + // Worst case for `execute_overweight` where the page is updated. + execute_overweight_page_updated { let origin: MessageOriginOf = 0.into(); let (mut page, msgs) = full_page::(); // Skip all messages. @@ -168,12 +190,15 @@ benchmarks! { let book = book_for::(&page); Pages::::insert(&origin, 0, &page); BookStateFor::::insert(&origin, &book); - }: _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX) + }: { + MessageQueue::::execute_overweight(RawOrigin::Signed(whitelisted_caller()).into(), 0u32.into(), 0u32, ((msgs - 1) as u32).into(), Weight::MAX).unwrap() + } verify { assert_last_event::(Event::Processed { hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), origin: 0.into(), weight_used: Weight::from_parts(1, 1), success: true }.into()); + assert!(Pages::::contains_key(&origin, 0), "Page must be updated"); } impl_benchmark_test_suite!(MessageQueue, crate::mock::new_test_ext::(), crate::integration_test::Test); diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index e4c4872aec3a7..3882ad9b2c4c3 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -590,7 +590,9 @@ pub mod pallet { /// of the message. /// /// Benchmark complexity considerations: O(index + weight_limit). - #[pallet::weight(T::WeightInfo::execute_overweight())] + #[pallet::weight( + T::WeightInfo::execute_overweight_page_updated().max( + T::WeightInfo::execute_overweight_page_removed()))] pub fn execute_overweight( origin: OriginFor, message_origin: MessageOriginOf, @@ -1214,7 +1216,10 @@ impl ServiceQueues for Pallet { (message_origin, page, index): Self::OverweightMessageAddress, ) -> Result { let mut weight = WeightMeter::from_limit(weight_limit); - if !weight.check_accrue(T::WeightInfo::execute_overweight()) { + if !weight.check_accrue( + T::WeightInfo::execute_overweight_page_removed() + .max(T::WeightInfo::execute_overweight_page_updated()), + ) { return Err(ExecuteOverweightError::InsufficientWeight) } diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index b4494fb7c1e1b..258e0bf117dac 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -112,8 +112,17 @@ impl crate::weights::WeightInfo for MockedWeightInfo { fn reap_page() -> Weight { WeightForCall::get().get("reap_page").copied().unwrap_or_default() } - fn execute_overweight() -> Weight { - WeightForCall::get().get("execute_overweight").copied().unwrap_or_default() + fn execute_overweight_page_updated() -> Weight { + WeightForCall::get() + .get("execute_overweight_page_updated") + .copied() + .unwrap_or_default() + } + fn execute_overweight_page_removed() -> Weight { + WeightForCall::get() + .get("execute_overweight_page_removed") + .copied() + .unwrap_or_default() } fn service_page_base_completion() -> Weight { WeightForCall::get() diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index f5fe0a7aab8bb..00830c65fe044 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -2,7 +2,7 @@ //! Autogenerated weights for pallet_message_queue //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-24, STEPS: `50`, REPEAT: 200, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2022-11-28, STEPS: `50`, REPEAT: 200, LOW RANGE: `[]`, HIGH RANGE: `[]` //! HOSTNAME: `oty-parity`, CPU: `11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz` //! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 @@ -40,7 +40,8 @@ pub trait WeightInfo { fn service_page_item() -> Weight; fn bump_service_head() -> Weight; fn reap_page() -> Weight; - fn execute_overweight() -> Weight; + fn execute_overweight_page_removed() -> Weight; + fn execute_overweight_page_updated() -> Weight; } /// Weights for pallet_message_queue using the Substrate node and recommended hardware. @@ -49,65 +50,73 @@ impl WeightInfo for SubstrateWeight { // Storage: MessageQueue ServiceHead (r:1 w:0) // Storage: MessageQueue BookStateFor (r:2 w:2) fn ready_ring_knit() -> Weight { - // Minimum execution time: 8_321 nanoseconds. - Weight::from_ref_time(8_742_000) + // Minimum execution time: 7_624 nanoseconds. + Weight::from_ref_time(8_085_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:2 w:2) // Storage: MessageQueue ServiceHead (r:1 w:1) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 8_474 nanoseconds. - Weight::from_ref_time(12_972_000) + // Minimum execution time: 7_495 nanoseconds. + Weight::from_ref_time(8_204_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_532 nanoseconds. - Weight::from_ref_time(3_786_000) + // Minimum execution time: 3_141 nanoseconds. + Weight::from_ref_time(3_351_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_completion() -> Weight { - // Minimum execution time: 5_268 nanoseconds. - Weight::from_ref_time(7_434_000) + // Minimum execution time: 4_645 nanoseconds. + Weight::from_ref_time(4_983_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 5_313 nanoseconds. - Weight::from_ref_time(5_546_000) + // Minimum execution time: 4_853 nanoseconds. + Weight::from_ref_time(5_484_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } fn service_page_item() -> Weight { - // Minimum execution time: 74_606 nanoseconds. - Weight::from_ref_time(111_057_000) + // Minimum execution time: 66_343 nanoseconds. + Weight::from_ref_time(74_817_000) } // Storage: MessageQueue ServiceHead (r:1 w:1) // Storage: MessageQueue BookStateFor (r:1 w:0) fn bump_service_head() -> Weight { - // Minimum execution time: 5_524 nanoseconds. - Weight::from_ref_time(6_068_000) + // Minimum execution time: 5_343 nanoseconds. + Weight::from_ref_time(5_666_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 25_297 nanoseconds. - Weight::from_ref_time(25_854_000) + // Minimum execution time: 23_052 nanoseconds. + Weight::from_ref_time(24_801_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) - fn execute_overweight() -> Weight { - // Minimum execution time: 73_758 nanoseconds. - Weight::from_ref_time(77_262_000) + fn execute_overweight_page_removed() -> Weight { + // Minimum execution time: 68_156 nanoseconds. + Weight::from_ref_time(70_916_000) + .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_updated() -> Weight { + // Minimum execution time: 68_642 nanoseconds. + Weight::from_ref_time(73_010_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -118,65 +127,73 @@ impl WeightInfo for () { // Storage: MessageQueue ServiceHead (r:1 w:0) // Storage: MessageQueue BookStateFor (r:2 w:2) fn ready_ring_knit() -> Weight { - // Minimum execution time: 8_321 nanoseconds. - Weight::from_ref_time(8_742_000) + // Minimum execution time: 7_624 nanoseconds. + Weight::from_ref_time(8_085_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:2 w:2) // Storage: MessageQueue ServiceHead (r:1 w:1) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 8_474 nanoseconds. - Weight::from_ref_time(12_972_000) + // Minimum execution time: 7_495 nanoseconds. + Weight::from_ref_time(8_204_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_532 nanoseconds. - Weight::from_ref_time(3_786_000) + // Minimum execution time: 3_141 nanoseconds. + Weight::from_ref_time(3_351_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_completion() -> Weight { - // Minimum execution time: 5_268 nanoseconds. - Weight::from_ref_time(7_434_000) + // Minimum execution time: 4_645 nanoseconds. + Weight::from_ref_time(4_983_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 5_313 nanoseconds. - Weight::from_ref_time(5_546_000) + // Minimum execution time: 4_853 nanoseconds. + Weight::from_ref_time(5_484_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } fn service_page_item() -> Weight { - // Minimum execution time: 74_606 nanoseconds. - Weight::from_ref_time(111_057_000) + // Minimum execution time: 66_343 nanoseconds. + Weight::from_ref_time(74_817_000) } // Storage: MessageQueue ServiceHead (r:1 w:1) // Storage: MessageQueue BookStateFor (r:1 w:0) fn bump_service_head() -> Weight { - // Minimum execution time: 5_524 nanoseconds. - Weight::from_ref_time(6_068_000) + // Minimum execution time: 5_343 nanoseconds. + Weight::from_ref_time(5_666_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 25_297 nanoseconds. - Weight::from_ref_time(25_854_000) + // Minimum execution time: 23_052 nanoseconds. + Weight::from_ref_time(24_801_000) + .saturating_add(RocksDbWeight::get().reads(2)) + .saturating_add(RocksDbWeight::get().writes(2)) + } + // Storage: MessageQueue BookStateFor (r:1 w:1) + // Storage: MessageQueue Pages (r:1 w:1) + fn execute_overweight_page_removed() -> Weight { + // Minimum execution time: 68_156 nanoseconds. + Weight::from_ref_time(70_916_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) - fn execute_overweight() -> Weight { - // Minimum execution time: 73_758 nanoseconds. - Weight::from_ref_time(77_262_000) + fn execute_overweight_page_updated() -> Weight { + // Minimum execution time: 68_642 nanoseconds. + Weight::from_ref_time(73_010_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(2)) } From 8cafb2c923141777fd72aa29cb9b8460b304bd78 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 28 Nov 2022 15:39:21 +0100 Subject: [PATCH 090/110] Remove TODOs Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 2 +- frame/message-queue/src/tests.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 3882ad9b2c4c3..34a6ed7e27109 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -155,7 +155,7 @@ //! //! **Liveness - Enqueueing** //! -//! It is always possible to enqueue any message for any `MessageOrigin`. TODO is this true?! +//! It is always possible to enqueue any message for any `MessageOrigin`. //! //! **Liveness - Processing** //! diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 6a1ce0d61d83d..5001f3569e4c1 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -892,7 +892,6 @@ fn footprint_on_swept_works() { }) } -// TODO test page removal #[test] fn execute_overweight_works() { new_test_ext::().execute_with(|| { @@ -906,6 +905,7 @@ fn execute_overweight_works() { // Load the current book let book = BookStateFor::::get(origin); assert_eq!(book.message_count, 1); + assert!(Pages::::contains_key(&origin, 0)); // Mark the message as permanently overweight. assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); @@ -944,6 +944,7 @@ fn execute_overweight_works() { ::execute_overweight(70.into_weight(), (origin, 0, 0)); assert_eq!(consumed, Err(ExecuteOverweightError::NotFound)); assert!(QueueChanges::take().is_empty()); + assert!(!Pages::::contains_key(&origin, 0), "Page is gone"); }); } From 02e2a7cdb7fc97a48f78afbb6c35b64e258cdd90 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 28 Nov 2022 15:40:17 +0100 Subject: [PATCH 091/110] Test service queue with faulty MessageProcessor Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/mock.rs | 33 ++++++++++++++++++++--- frame/message-queue/src/tests.rs | 45 +++++++++++++++++++++++++++++++- 2 files changed, 73 insertions(+), 5 deletions(-) diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 258e0bf117dac..d70f4ce905a43 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -157,6 +157,7 @@ parameter_types! { pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; } +/// A message processor which records all processed messages into [`MessagesProcessed`]. pub struct RecordingMessageProcessor; impl ProcessMessage for RecordingMessageProcessor { /// The transport from where a message originates. @@ -165,12 +166,15 @@ impl ProcessMessage for RecordingMessageProcessor { /// Process the given message, using no more than `weight_limit` in weight to do so. /// /// Consumes exactly `n` weight of all components if it starts `weight=n` and `1` otherwise. - /// Errors if given the `weight_limit` is insufficient to process the message. + /// Errors if given the `weight_limit` is insufficient to process the message or if the message + /// is `badformat`, `corrupt` or `unsupported` with the respective error. fn process_message( message: &[u8], origin: Self::Origin, weight_limit: Weight, ) -> Result<(bool, Weight), ProcessMessageError> { + processing_message(message)?; + let weight = if message.starts_with(&b"weight="[..]) { let mut w: u64 = 0; for &c in &message[7..] { @@ -185,6 +189,7 @@ impl ProcessMessage for RecordingMessageProcessor { 1 }; let weight = Weight::from_parts(weight, weight); + if weight.all_lte(weight_limit) { let mut m = MessagesProcessed::get(); m.push((message.to_vec(), origin)); @@ -196,8 +201,23 @@ impl ProcessMessage for RecordingMessageProcessor { } } +/// Processed a mocked message. Messages that end with `badformat`, `corrupt` or `unsupported` will fail with the respective error. +fn processing_message(msg: &[u8]) -> Result<(), ProcessMessageError> { + let msg = String::from_utf8_lossy(msg); + if msg.ends_with("badformat") { + Err(ProcessMessageError::BadFormat) + } else if msg.ends_with("corrupt") { + Err(ProcessMessageError::Corrupt) + } else if msg.ends_with("unsupported") { + Err(ProcessMessageError::Unsupported) + } else { + Ok(()) + } +} + parameter_types! { pub static NumMessagesProcessed: usize = 0; + pub static NumMessagesErrored: usize = 0; } /// Similar to [`RecordingMessageProcessor`] but only counts the number of messages processed and @@ -209,10 +229,14 @@ impl ProcessMessage for CountingMessageProcessor { type Origin = MessageOrigin; fn process_message( - _message: &[u8], + message: &[u8], _origin: Self::Origin, weight_limit: Weight, ) -> Result<(bool, Weight), ProcessMessageError> { + if let Err(e) = processing_message(message) { + NumMessagesErrored::set(NumMessagesErrored::get() + 1); + return Err(e) + } let weight = Weight::from_parts(1, 1); if weight.all_lte(weight_limit) { @@ -245,8 +269,9 @@ where ::BlockNumber: From, { sp_tracing::try_init_simple(); - WeightForCall::set(Default::default()); - QueueChanges::set(Default::default()); + WeightForCall::take(); + QueueChanges::take(); + NumMessagesErrored::take(); let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| frame_system::Pallet::::set_block_number(1.into())); diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 5001f3569e4c1..a529b61c4ed51 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -163,6 +163,49 @@ fn service_queues_basic_works() { }); } +#[test] +fn service_queues_failing_messages_works() { + use MessageOrigin::*; + new_test_ext::().execute_with(|| { + set_weight("service_page_item", 1.into_weight()); + MessageQueue::enqueue_message(msg("badformat"), Here); + MessageQueue::enqueue_message(msg("corrupt"), Here); + MessageQueue::enqueue_message(msg("unsupported"), Here); + // Starts with three pages. + assert_pages(&[0, 1, 2]); + + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"badformat"), + origin: MessageOrigin::Here, + error: ProcessMessageError::BadFormat, + } + .into(), + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"corrupt"), + origin: MessageOrigin::Here, + error: ProcessMessageError::Corrupt, + } + .into(), + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_last_event::( + Event::ProcessingFailed { + hash: ::Hashing::hash(b"unsupported"), + origin: MessageOrigin::Here, + error: ProcessMessageError::Unsupported, + } + .into(), + ); + // All pages removed. + assert_pages(&[]); + }); +} + #[test] fn reap_page_permanent_overweight_works() { use MessageOrigin::*; @@ -172,7 +215,7 @@ fn reap_page_permanent_overweight_works() { MessageQueue::enqueue_message(msg("weight=2"), Here); } assert_eq!(Pages::::iter().count(), MaxStale::get() as usize + 10); - assert_eq!(QueueChanges::take().len(), 12); + assert_eq!(QueueChanges::take().len(), MaxStale::get() as usize + 10); // Mark all pages as stale since their message is permanently overweight. MessageQueue::service_queues(1.into_weight()); From 42636e59b319bc6d25abae0f85f94b222bff92d3 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 28 Nov 2022 16:08:01 +0100 Subject: [PATCH 092/110] fmt Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/mock.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index d70f4ce905a43..4657194c8dd1a 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -201,7 +201,8 @@ impl ProcessMessage for RecordingMessageProcessor { } } -/// Processed a mocked message. Messages that end with `badformat`, `corrupt` or `unsupported` will fail with the respective error. +/// Processed a mocked message. Messages that end with `badformat`, `corrupt` or `unsupported` will +/// fail with the respective error. fn processing_message(msg: &[u8]) -> Result<(), ProcessMessageError> { let msg = String::from_utf8_lossy(msg); if msg.ends_with("badformat") { From 71152a896e665a3465954fee4db1401be7473376 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 28 Nov 2022 17:35:25 +0000 Subject: [PATCH 093/110] Update pallet ui tests to 1.65 Signed-off-by: Oliver Tale-Yazdi --- .../storage_ensure_span_are_ok_on_wrong_gen.stderr | 6 +++--- .../storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr index 42ef5a34e4c30..999d8585c221a 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` diff --git a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr index 461d63ebb0d9c..e2870ffb9e86f 100644 --- a/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr +++ b/frame/support/test/tests/pallet_ui/storage_ensure_span_are_ok_on_wrong_gen_unnamed.stderr @@ -28,7 +28,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `PartialStorageInfoTrait` @@ -69,7 +69,7 @@ error[E0277]: the trait bound `Bar: TypeInfo` is not satisfied (A, B, C, D) (A, B, C, D, E) (A, B, C, D, E, F) - and 161 others + and 162 others = note: required for `Bar` to implement `StaticTypeInfo` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` @@ -103,7 +103,7 @@ error[E0277]: the trait bound `Bar: EncodeLike` is not satisfied <&[(T,)] as EncodeLike>> <&[(T,)] as EncodeLike>> <&[T] as EncodeLike>> - and 278 others + and 279 others = note: required for `Bar` to implement `FullEncode` = note: required for `Bar` to implement `FullCodec` = note: required for `frame_support::pallet_prelude::StorageValue<_GeneratedPrefixForStorageFoo, Bar>` to implement `StorageEntryMetadataBuilder` From 567ccb272f1c4e723ab7476ebd597fb2475ef5df Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Mon, 28 Nov 2022 20:03:51 +0000 Subject: [PATCH 094/110] More docs Signed-off-by: Oliver Tale-Yazdi --- frame/support/src/traits/messages.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs index a618b74f780f5..998f67459bd3b 100644 --- a/frame/support/src/traits/messages.rs +++ b/frame/support/src/traits/messages.rs @@ -24,6 +24,8 @@ use sp_runtime::{traits::Convert, BoundedSlice, RuntimeDebug}; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use sp_weights::Weight; +/// Errors that can happen when attempting to process a message with +/// [`ProcessMessage::process_message()`]. #[derive(Copy, Clone, Eq, PartialEq, Encode, Decode, TypeInfo, RuntimeDebug)] pub enum ProcessMessageError { /// The message data format is unknown (e.g. unrecognised header) @@ -38,6 +40,7 @@ pub enum ProcessMessageError { Overweight(Weight), } +/// Can process messages from a specific origin. pub trait ProcessMessage { /// The transport from where a message originates. type Origin: FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + Debug; @@ -50,13 +53,19 @@ pub trait ProcessMessage { ) -> Result<(bool, Weight), ProcessMessageError>; } +/// Errors that can happen when attempting to execute an overweight message with +/// [`ServiceQueues::execute_overweight()`]. #[derive(Eq, PartialEq, RuntimeDebug)] pub enum ExecuteOverweightError { + /// The referenced message was not found. NotFound, + /// The available weight was insufficient to execute the message. InsufficientWeight, } +/// Can service queues and execute overweight messages. pub trait ServiceQueues { + /// Addresses a specific overweight message. type OverweightMessageAddress; /// Service all message queues in some fair manner. @@ -66,6 +75,8 @@ pub trait ServiceQueues { /// Returns the dynamic weight used by this call; is never greater than `weight_limit`. fn service_queues(weight_limit: Weight) -> Weight; + /// Executes a message that could not be executed by [`Self::service_queues()`] because it was + /// temporarily overweight. fn execute_overweight( _weight_limit: Weight, _address: Self::OverweightMessageAddress, @@ -74,13 +85,16 @@ pub trait ServiceQueues { } } +/// The resource footprint of a queue. #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] pub struct Footprint { pub count: u32, pub size: u32, } +/// Can enqueue messages for multiple origins. pub trait EnqueueMessage { + /// The maximal length any enqueued message may have. type MaxMessageLen: Get; /// Enqueue a single `message` from a specific `origin`. @@ -113,6 +127,7 @@ impl EnqueueMessage for () { } } +/// Transform the origin of an [`EnqueueMessage`] via `C::convert`. pub struct TransformOrigin(PhantomData<(E, O, N, C)>); impl, O: MaxEncodedLen, N: MaxEncodedLen, C: Convert> EnqueueMessage for TransformOrigin @@ -139,7 +154,9 @@ impl, O: MaxEncodedLen, N: MaxEncodedLen, C: Convert> } } +/// Handles incoming messages for a single origin. pub trait HandleMessage { + /// The maximal length any enqueued message may have. type MaxMessageLen: Get; /// Enqueue a single `message` with an implied origin. @@ -157,6 +174,7 @@ pub trait HandleMessage { fn footprint() -> Footprint; } +/// Adapter type to transform an [`EnqueueMessage`] with an origin into a [`HandleMessage`] impl. pub struct EnqueueWithOrigin(PhantomData<(E, O)>); impl, O: TypedGet> HandleMessage for EnqueueWithOrigin where From 48c338c50e99406b83ddff8eb15a7b151a7307d6 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Wed, 30 Nov 2022 11:51:32 +0000 Subject: [PATCH 095/110] Review doc fixes Co-authored-by: Robert Klotzner Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 34a6ed7e27109..454ef0e68de27 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -21,14 +21,14 @@ //! //! # Design Goals //! -//! - Minimal assumptions about `Message`s and `MessageOrigin`s. Both should be MEL bounded blobs. +//! 1. Minimal assumptions about `Message`s and `MessageOrigin`s. Both should be MEL bounded blobs. //! This ensures the generality and reusability of the pallet. -//! - Well known and tightly limited pre-dispatch PoV weights, especially for message execution. +//! 2. Well known and tightly limited pre-dispatch PoV weights, especially for message execution. //! This is paramount for the success of the pallet since message execution is done in //! `on_initialize` which must _never_ under-estimate its PoV weight. It also needs a frugal PoV -//! footprint since PoV is scare and this is (possibly) done in every block. This must also hold in -//! the presence of unpredictable message size distributions. -//! - Usable as XCMP, DMP and UMP message/dispatch queue - possibly through adapter types. +//! footprint since PoV is scarce and this is (possibly) done in every block. This must also hold +//! in the presence of unpredictable message size distributions. +//! 3. Usable as XCMP, DMP and UMP message/dispatch queue - possibly through adapter types. //! //! # Design //! @@ -47,7 +47,7 @@ //! //! **Message Execution** //! -//! Executing a message is offloaded the [`Config::MessageProcessor`] which contains the actual +//! Executing a message is offloaded to the [`Config::MessageProcessor`] which contains the actual //! logic of how to handle the message since they are blobs. A message can be temporarily or //! permanently overweight. The pallet will perpetually try to execute a temporarily overweight //! message. A permanently overweight message is skipped and must be executed manually. @@ -62,10 +62,8 @@ //! maximum allowed length. This would result in most messages having a pre-dispatch PoV size which //! is much larger than their post-dispatch PoV size, possibly by a factor of thousand. Disregarding //! this observation would cripple the processing power of the pallet since it cannot straighten out -//! this discrepancy at runtime. The implemented solution tightly packs multiple messages into a -//! page, which allows for a post-dispatch PoV size which is much closer to the worst case -//! pre-dispatch PoV size. To be more formal; the ratio between *Actual Encode Length* and *Max -//! Encoded Length* per message: `AEL / MEL` is much closer to one than without the optimization. +//! this discrepancy at runtime. Conceptually, the implementation is packing as many messages into a +//! single bounded vec, as actually fit into the bounds. This reduces the wasted PoV. //! //! NOTE: The enqueuing and storing of messages are only a means to implement the processing and are //! not goals per se. @@ -111,7 +109,7 @@ //! message is marked as *processed* if the [`Config::MessageProcessor`] return Ok. An event //! [`Event::Processed`] is emitted afterwards. It is possible that the weight limit of the pallet //! will never allow a specific message to be executed. In this case it remains as unprocessed and -//! is skipped. This process stops if either there are no more messages in the queue of the +//! is skipped. This process stops if either there are no more messages in the queue or the //! remaining weight became insufficient to service this queue. If there is enough weight it tries //! to advance to the next *ready* queue and service it. This continues until there are no more //! queues on which it can make progress or not enough weight to check that. From 617b0c9c74498fced824961e2a7ead28d36184e9 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Fri, 2 Dec 2022 19:40:39 +0900 Subject: [PATCH 096/110] Add weight_limit to extrinsic weight of execute_overweight --- frame/message-queue/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 454ef0e68de27..5481d4a423426 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -590,17 +590,19 @@ pub mod pallet { /// Benchmark complexity considerations: O(index + weight_limit). #[pallet::weight( T::WeightInfo::execute_overweight_page_updated().max( - T::WeightInfo::execute_overweight_page_removed()))] + T::WeightInfo::execute_overweight_page_removed()).saturating_add(*weight_limit) + )] pub fn execute_overweight( origin: OriginFor, message_origin: MessageOriginOf, page: PageIndex, index: T::Size, weight_limit: Weight, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let _ = ensure_signed(origin)?; - Self::do_execute_overweight(message_origin, page, index, weight_limit)?; - Ok(()) + let actual_weight = + Self::do_execute_overweight(message_origin, page, index, weight_limit)?; + Ok(Some(actual_weight).into()) } } } From 15ad9e80fb9c61ceccc0a33c338d01ca86ac5373 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 7 Dec 2022 00:19:42 +0900 Subject: [PATCH 097/110] Correctly return unused weight --- frame/message-queue/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 5481d4a423426..12868a012a7fb 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -602,7 +602,7 @@ pub mod pallet { let _ = ensure_signed(origin)?; let actual_weight = Self::do_execute_overweight(message_origin, page, index, weight_limit)?; - Ok(Some(actual_weight).into()) + Ok(Some(weight_limit.saturating_sub(actual_weight)).into()) } } } From ba0999e2bbed8c8f1307c884708a672f59a9df89 Mon Sep 17 00:00:00 2001 From: Keith Yeung Date: Wed, 7 Dec 2022 00:39:42 +0900 Subject: [PATCH 098/110] Return actual weight consumed in do_execute_overweight --- frame/message-queue/src/lib.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 12868a012a7fb..bc1f6181b8a80 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -602,7 +602,7 @@ pub mod pallet { let _ = ensure_signed(origin)?; let actual_weight = Self::do_execute_overweight(message_origin, page, index, weight_limit)?; - Ok(Some(weight_limit.saturating_sub(actual_weight)).into()) + Ok(Some(actual_weight).into()) } } } @@ -809,7 +809,7 @@ impl Pallet { page.note_processed_at_pos(pos); book_state.message_count.saturating_dec(); book_state.size.saturating_reduce(payload_len); - if page.remaining.is_zero() { + let page_weight = if page.remaining.is_zero() { debug_assert!( page.remaining_size.is_zero(), "no messages remaining; no space taken; qed" @@ -817,18 +817,20 @@ impl Pallet { Pages::::remove(&origin, page_index); debug_assert!(book_state.count >= 1, "page exists, so book must have pages"); book_state.count.saturating_dec(); + T::WeightInfo::execute_overweight_page_removed() // no need to consider .first or ready ring since processing an overweight page // would not alter that state. } else { Pages::::insert(&origin, page_index, page); - } + T::WeightInfo::execute_overweight_page_updated() + }; BookStateFor::::insert(&origin, &book_state); T::QueueChangeHandler::on_queue_changed( origin, book_state.message_count, book_state.size, ); - Ok(weight_counter.consumed) + Ok(weight_counter.consumed.saturating_add(page_weight)) }, } } From 3cb51c1e933c3fbb879f077908d075db4a3e0e13 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Dec 2022 10:43:00 +0100 Subject: [PATCH 099/110] Review fixes Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 13 +++++++------ frame/message-queue/src/lib.rs | 22 +++++++++++++--------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index ca03bdd61783c..513a0f6ecf968 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1131,9 +1131,10 @@ impl pallet_bounties::Config for Runtime { } parameter_types! { - pub const HeapSize: u32 = 64 * 1024; // 64 KiB - pub const MaxStale: u32 = 128; - pub const ServiceWeight: Option = None; + /// Allocate at most 20% of each block for message processing. + /// + /// Is set to 20% since the scheduler can already consume a maximum of 80%. + pub const MessageQueueServiceWeight: Option = Perbill::from_percent(20) * RuntimeBlockWeights::get().max_block; } impl pallet_message_queue::Config for Runtime { @@ -1143,9 +1144,9 @@ impl pallet_message_queue::Config for Runtime { type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor; type Size = u32; type QueueChangeHandler = (); - type HeapSize = HeapSize; - type MaxStale = MaxStale; - type ServiceWeight = ServiceWeight; + type HeapSize = ConstU32<{ 64 * 1024 }>; + type MaxStale = ConstU32<128>; + type ServiceWeight = MessageQueueServiceWeight; } parameter_types! { diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index bc1f6181b8a80..b0cb87a8aa2d5 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -65,9 +65,6 @@ //! this discrepancy at runtime. Conceptually, the implementation is packing as many messages into a //! single bounded vec, as actually fit into the bounds. This reduces the wasted PoV. //! -//! NOTE: The enqueuing and storing of messages are only a means to implement the processing and are -//! not goals per se. -//! //! **Page Data Layout** //! //! A Page contains a heap which holds all its messages. The heap is built by concatenating @@ -237,6 +234,8 @@ pub struct Page + Debug + Clone + Default, HeapSize: Get> /// skipped. remaining: Size, /// The size of all remaining messages to be processed. + /// + /// Includes overweight messages outside of the `first` to `last` window. remaining_size: Size, /// The number of items before the `first` item in this page. first_index: Size, @@ -404,6 +403,9 @@ pub struct BookState { /// One more than the last page with some items to be processed in it. end: PageIndex, /// The number of pages stored at present. + /// + /// This might be larger than `end-begin`, because we keep pages with unprocessed overweight + /// messages outside of the end/begin window. count: PageIndex, /// If this book has any ready pages, then this will be `Some` with the previous and next /// neighbours. This wraps around. @@ -472,6 +474,10 @@ pub mod pallet { type QueueChangeHandler: OnQueueChanged<::Origin>; /// The size of the page; this implies the maximum message size which can be sent. + /// + /// A good value depends on the expected message sizes, their weights, the weight that is + /// available for processing them and the maximal needed message size. The maximal message + /// size is slightly lower than this as defined by [`MaxMessageLenOf`]. #[pallet::constant] type HeapSize: Get; @@ -853,12 +859,10 @@ impl Pallet { let ready_pages = book_state.end.saturating_sub(book_state.begin).min(total_pages); let stale_pages = total_pages - ready_pages; let max_stale = T::MaxStale::get(); - let overflow = match stale_pages.checked_sub(max_stale + 1) { - Some(x) => x + 1, - None => return false, - }; - let backlog = (max_stale / overflow).max(max_stale); - let watermark = book_state.begin.saturating_sub(backlog); + if stale_pages <= max_stale { + return false + } + let watermark = book_state.begin.saturating_sub(max_stale); page_index < watermark }; ensure!(reapable || cullable(), Error::::NotReapable); From ad8087512fc19dc184523ea6b07b60492a673352 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Dec 2022 11:04:08 +0100 Subject: [PATCH 100/110] Set version 7.0.0-dev Signed-off-by: Oliver Tale-Yazdi --- Cargo.lock | 2 +- bin/node/runtime/Cargo.toml | 2 +- frame/message-queue/Cargo.toml | 7 ++++++- frame/message-queue/src/integration_test.rs | 2 +- frame/message-queue/src/lib.rs | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8a49cd82ab833..41c641cf05963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5325,7 +5325,7 @@ dependencies = [ [[package]] name = "pallet-message-queue" -version = "0.9.29" +version = "7.0.0-dev" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 26602de197a5f..477545c9ac332 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -75,7 +75,7 @@ pallet-indices = { version = "4.0.0-dev", default-features = false, path = "../. pallet-identity = { version = "4.0.0-dev", default-features = false, path = "../../../frame/identity" } pallet-lottery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/lottery" } pallet-membership = { version = "4.0.0-dev", default-features = false, path = "../../../frame/membership" } -pallet-message-queue = { version = "0.9.29", default-features = false, path = "../../../frame/message-queue" } +pallet-message-queue = { version = "7.0.0-dev", default-features = false, path = "../../../frame/message-queue" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml index 846198d8702fc..bcc4eedb94268 100644 --- a/frame/message-queue/Cargo.toml +++ b/frame/message-queue/Cargo.toml @@ -2,7 +2,12 @@ authors = ["Parity Technologies "] edition = "2021" name = "pallet-message-queue" -version = "0.9.29" +version = "7.0.0-dev" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet to queue and process messages" +readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index e56cce60b487e..a9b6ee9bd2214 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright 2022 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index b0cb87a8aa2d5..0f0b3e220f1ee 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// Copyright 2022 Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify From 25e3ae6672bf89c906c8f884e1e412f3e9c10b58 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Dec 2022 11:04:27 +0100 Subject: [PATCH 101/110] Make it compile Signed-off-by: Oliver Tale-Yazdi --- bin/node/runtime/src/lib.rs | 2 +- frame/message-queue/src/benchmarking.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b84a494909ad3..99a01d3766856 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1136,7 +1136,7 @@ parameter_types! { /// Allocate at most 20% of each block for message processing. /// /// Is set to 20% since the scheduler can already consume a maximum of 80%. - pub const MessageQueueServiceWeight: Option = Perbill::from_percent(20) * RuntimeBlockWeights::get().max_block; + pub MessageQueueServiceWeight: Option = Some(Perbill::from_percent(20) * RuntimeBlockWeights::get().max_block); } impl pallet_message_queue::Config for Runtime { diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 630047f079000..990075c9b7b95 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -36,7 +36,7 @@ benchmarks! { ::Size: From, } - // Worst case path of `ready_ring_knit`. The benchmark is unused and for verification only. + // Worst case path of `ready_ring_knit`. ready_ring_knit { let mid: MessageOriginOf:: = 1.into(); build_ring::(&[0.into(), mid.clone(), 2.into()]); @@ -51,7 +51,7 @@ benchmarks! { assert_ring::(&[0.into(), 2.into(), mid]); } - // Worst case path of `ready_ring_unknit`. The benchmark is unused and for verification only. + // Worst case path of `ready_ring_unknit`. ready_ring_unknit { build_ring::(&[0.into(), 1.into(), 2.into()]); assert_ring::(&[0.into(), 1.into(), 2.into()]); @@ -96,7 +96,6 @@ benchmarks! { MessageQueue::::service_page(&origin, &mut book_state, &mut meter, limit) } - // Processing a single message from a page. service_page_item { let msg = vec![1u8; MaxMessageLenOf::::get() as usize]; From eb1c0dbaba402e04458eca857a6518dade6ea4c7 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Dec 2022 11:31:46 +0100 Subject: [PATCH 102/110] Switch message_size to u64 Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 14 ++++++------- frame/message-queue/src/mock.rs | 4 ++-- frame/message-queue/src/mock_helpers.rs | 2 +- frame/message-queue/src/tests.rs | 28 ++++++++++++++----------- frame/support/src/traits/messages.rs | 2 +- 5 files changed, 27 insertions(+), 23 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 0f0b3e220f1ee..2def674a3d408 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -413,7 +413,7 @@ pub struct BookState { /// The number of unprocessed messages stored at present. message_count: u32, /// The total size of all unprocessed messages stored at present. - size: u32, + size: u64, } impl Default for BookState { @@ -425,11 +425,11 @@ impl Default for BookState { /// Notifies the implementor of changes to a queue. Mainly when messages got added or removed. pub trait OnQueueChanged { /// The queue `id` changed and now has these properties. - fn on_queue_changed(id: Id, items_count: u32, items_size: u32); + fn on_queue_changed(id: Id, items_count: u32, items_size: u64); } impl OnQueueChanged for () { - fn on_queue_changed(_: Id, _: u32, _: u32) {} + fn on_queue_changed(_: Id, _: u32, _: u64) {} } #[frame_support::pallet] @@ -737,7 +737,7 @@ impl Pallet { book_state .size // This should be payload size, but here the payload *is* the message. - .saturating_accrue(message.len() as u32); + .saturating_accrue(message.len() as u64); if book_state.end > book_state.begin { debug_assert!(book_state.ready_neighbours.is_some(), "Must be in ready ring if ready"); @@ -791,7 +791,7 @@ impl Pallet { let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; let (pos, is_processed, payload) = page.peek_index(index.into() as usize).ok_or(Error::::NoMessage)?; - let payload_len = payload.len() as u32; + let payload_len = payload.len() as u64; ensure!( page_index < book_state.begin || (page_index == book_state.begin && pos < page.first.into() as usize), @@ -871,7 +871,7 @@ impl Pallet { debug_assert!(book_state.count > 0, "reaping a page implies there are pages"); book_state.count.saturating_dec(); book_state.message_count.saturating_reduce(page.remaining.into()); - book_state.size.saturating_reduce(page.remaining_size.into()); + book_state.size.saturating_reduce(page.remaining_size.into() as u64); BookStateFor::::insert(origin, &book_state); T::QueueChangeHandler::on_queue_changed( origin.clone(), @@ -1029,7 +1029,7 @@ impl Pallet { if is_processed { book_state.message_count.saturating_dec(); - book_state.size.saturating_reduce(payload.len() as u32); + book_state.size.saturating_reduce(payload.len() as u64); } page.skip_first(is_processed); ItemExecutionStatus::Executed(is_processed) diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 4657194c8dd1a..88a79afd21dd9 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -251,13 +251,13 @@ impl ProcessMessage for CountingMessageProcessor { parameter_types! { /// Storage for `RecordingQueueChangeHandler`, do not use directly. - pub static QueueChanges: Vec<(MessageOrigin, u32, u32)> = vec![]; + pub static QueueChanges: Vec<(MessageOrigin, u32, u64)> = vec![]; } /// Records all queue changes into [`QueueChanges`]. pub struct RecordingQueueChangeHandler; impl OnQueueChanged for RecordingQueueChangeHandler { - fn on_queue_changed(id: MessageOrigin, items_count: u32, items_size: u32) { + fn on_queue_changed(id: MessageOrigin, items_count: u32, items_size: u64) { QueueChanges::mutate(|cs| cs.push((id, items_count, items_size))); } } diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index e8ea7be1bc841..4148377d9f2de 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -112,7 +112,7 @@ pub fn book_for(page: &PageOf) -> BookStateOf { end: 1, ready_neighbours: None, message_count: page.remaining.into(), - size: page.remaining_size.into(), + size: page.remaining_size.into() as u64, } } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index a529b61c4ed51..b21c35a1f7f95 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -229,7 +229,7 @@ fn reap_page_permanent_overweight_works() { assert_ok!(MessageQueue::do_reap_page(&Here, i)); assert_eq!( QueueChanges::take(), - vec![(Here, b.message_count - (i + 1), b.size - (i + 1) * 8)] + vec![(Here, b.message_count - (i + 1), b.size - (i as u64 + 1) * 8)] ); } // Cannot reap any more pages. @@ -901,7 +901,7 @@ fn footprint_works() { let info = MessageQueue::footprint(origin); assert_eq!(info.count as usize, msgs); - assert_eq!(info.size, page.remaining_size); + assert_eq!(info.size, page.remaining_size as u64); // Sweeping a queue never calls OnQueueChanged. assert!(QueueChanges::take().is_empty()); @@ -1046,27 +1046,31 @@ fn ready_ring_knit_and_unknit_works() { #[test] fn enqueue_message_works() { use MessageOrigin::*; - let max_msg_per_page = ::HeapSize::get() / - (ItemHeader::<::Size>::max_encoded_len() as u32 + 1); + let max_msg_per_page = ::HeapSize::get() as u64 / + (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); new_test_ext::().execute_with(|| { // Enqueue messages which should fill three pages. let n = max_msg_per_page * 3; for i in 1..=n { MessageQueue::enqueue_message(msg("a"), Here); - assert_eq!(QueueChanges::take(), vec![(Here, i, i)], "OnQueueChanged not called"); + assert_eq!( + QueueChanges::take(), + vec![(Here, i as u32, i)], + "OnQueueChanged not called" + ); } assert_eq!(Pages::::iter().count(), 3); // Enqueue one more onto page 4. MessageQueue::enqueue_message(msg("abc"), Here); - assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); + assert_eq!(QueueChanges::take(), vec![(Here, (n + 1) as u32, n + 3)]); assert_eq!(Pages::::iter().count(), 4); // Check the state. assert_eq!(BookStateFor::::iter().count(), 1); let book = BookStateFor::::get(Here); - assert_eq!(book.message_count, n + 1); + assert_eq!(book.message_count as u64, n + 1); assert_eq!(book.size, n + 3); assert_eq!((book.begin, book.end), (0, 4)); assert_eq!(book.count as usize, Pages::::iter().count()); @@ -1076,8 +1080,8 @@ fn enqueue_message_works() { #[test] fn enqueue_messages_works() { use MessageOrigin::*; - let max_msg_per_page = ::HeapSize::get() / - (ItemHeader::<::Size>::max_encoded_len() as u32 + 1); + let max_msg_per_page = ::HeapSize::get() as u64 / + (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); new_test_ext::().execute_with(|| { // Enqueue messages which should fill three pages. @@ -1087,18 +1091,18 @@ fn enqueue_messages_works() { // Now queue all messages at once. MessageQueue::enqueue_messages(msgs.into_iter(), Here); // The changed handler should only be called once. - assert_eq!(QueueChanges::take(), vec![(Here, n, n)], "OnQueueChanged not called"); + assert_eq!(QueueChanges::take(), vec![(Here, n as u32, n)], "OnQueueChanged not called"); assert_eq!(Pages::::iter().count(), 3); // Enqueue one more onto page 4. MessageQueue::enqueue_message(msg("abc"), Here); - assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); + assert_eq!(QueueChanges::take(), vec![(Here, (n + 1) as u32, n + 3)]); assert_eq!(Pages::::iter().count(), 4); // Check the state. assert_eq!(BookStateFor::::iter().count(), 1); let book = BookStateFor::::get(Here); - assert_eq!(book.message_count, n + 1); + assert_eq!(book.message_count as u64, n + 1); assert_eq!(book.size, n + 3); assert_eq!((book.begin, book.end), (0, 4)); assert_eq!(book.count as usize, Pages::::iter().count()); diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs index 998f67459bd3b..23ac4c5852a80 100644 --- a/frame/support/src/traits/messages.rs +++ b/frame/support/src/traits/messages.rs @@ -89,7 +89,7 @@ pub trait ServiceQueues { #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] pub struct Footprint { pub count: u32, - pub size: u32, + pub size: u64, } /// Can enqueue messages for multiple origins. From 3b0bf6a246cb4129ebe2c3b1c85769a01ce18793 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Dec 2022 11:37:15 +0100 Subject: [PATCH 103/110] Switch message_count to u64 Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/lib.rs | 8 ++++---- frame/message-queue/src/mock.rs | 4 ++-- frame/message-queue/src/mock_helpers.rs | 2 +- frame/message-queue/src/tests.rs | 14 +++++--------- frame/support/src/traits/messages.rs | 2 +- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 2def674a3d408..6e0f1863c276c 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -411,7 +411,7 @@ pub struct BookState { /// neighbours. This wraps around. ready_neighbours: Option>, /// The number of unprocessed messages stored at present. - message_count: u32, + message_count: u64, /// The total size of all unprocessed messages stored at present. size: u64, } @@ -425,11 +425,11 @@ impl Default for BookState { /// Notifies the implementor of changes to a queue. Mainly when messages got added or removed. pub trait OnQueueChanged { /// The queue `id` changed and now has these properties. - fn on_queue_changed(id: Id, items_count: u32, items_size: u64); + fn on_queue_changed(id: Id, items_count: u64, items_size: u64); } impl OnQueueChanged for () { - fn on_queue_changed(_: Id, _: u32, _: u64) {} + fn on_queue_changed(_: Id, _: u64, _: u64) {} } #[frame_support::pallet] @@ -870,7 +870,7 @@ impl Pallet { Pages::::remove(origin, page_index); debug_assert!(book_state.count > 0, "reaping a page implies there are pages"); book_state.count.saturating_dec(); - book_state.message_count.saturating_reduce(page.remaining.into()); + book_state.message_count.saturating_reduce(page.remaining.into() as u64); book_state.size.saturating_reduce(page.remaining_size.into() as u64); BookStateFor::::insert(origin, &book_state); T::QueueChangeHandler::on_queue_changed( diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 88a79afd21dd9..bb9942443e226 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -251,13 +251,13 @@ impl ProcessMessage for CountingMessageProcessor { parameter_types! { /// Storage for `RecordingQueueChangeHandler`, do not use directly. - pub static QueueChanges: Vec<(MessageOrigin, u32, u64)> = vec![]; + pub static QueueChanges: Vec<(MessageOrigin, u64, u64)> = vec![]; } /// Records all queue changes into [`QueueChanges`]. pub struct RecordingQueueChangeHandler; impl OnQueueChanged for RecordingQueueChangeHandler { - fn on_queue_changed(id: MessageOrigin, items_count: u32, items_size: u64) { + fn on_queue_changed(id: MessageOrigin, items_count: u64, items_size: u64) { QueueChanges::mutate(|cs| cs.push((id, items_count, items_size))); } } diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index 4148377d9f2de..39d961d8fc558 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -111,7 +111,7 @@ pub fn book_for(page: &PageOf) -> BookStateOf { begin: 0, end: 1, ready_neighbours: None, - message_count: page.remaining.into(), + message_count: page.remaining.into() as u64, size: page.remaining_size.into() as u64, } } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index b21c35a1f7f95..644b19c3b7c4e 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -229,7 +229,7 @@ fn reap_page_permanent_overweight_works() { assert_ok!(MessageQueue::do_reap_page(&Here, i)); assert_eq!( QueueChanges::take(), - vec![(Here, b.message_count - (i + 1), b.size - (i as u64 + 1) * 8)] + vec![(Here, b.message_count - (i as u64 + 1), b.size - (i as u64 + 1) * 8)] ); } // Cannot reap any more pages. @@ -1054,17 +1054,13 @@ fn enqueue_message_works() { let n = max_msg_per_page * 3; for i in 1..=n { MessageQueue::enqueue_message(msg("a"), Here); - assert_eq!( - QueueChanges::take(), - vec![(Here, i as u32, i)], - "OnQueueChanged not called" - ); + assert_eq!(QueueChanges::take(), vec![(Here, i, i)], "OnQueueChanged not called"); } assert_eq!(Pages::::iter().count(), 3); // Enqueue one more onto page 4. MessageQueue::enqueue_message(msg("abc"), Here); - assert_eq!(QueueChanges::take(), vec![(Here, (n + 1) as u32, n + 3)]); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); assert_eq!(Pages::::iter().count(), 4); // Check the state. @@ -1091,12 +1087,12 @@ fn enqueue_messages_works() { // Now queue all messages at once. MessageQueue::enqueue_messages(msgs.into_iter(), Here); // The changed handler should only be called once. - assert_eq!(QueueChanges::take(), vec![(Here, n as u32, n)], "OnQueueChanged not called"); + assert_eq!(QueueChanges::take(), vec![(Here, n, n)], "OnQueueChanged not called"); assert_eq!(Pages::::iter().count(), 3); // Enqueue one more onto page 4. MessageQueue::enqueue_message(msg("abc"), Here); - assert_eq!(QueueChanges::take(), vec![(Here, (n + 1) as u32, n + 3)]); + assert_eq!(QueueChanges::take(), vec![(Here, n + 1, n + 3)]); assert_eq!(Pages::::iter().count(), 4); // Check the state. diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs index 23ac4c5852a80..9b86c421ad9e0 100644 --- a/frame/support/src/traits/messages.rs +++ b/frame/support/src/traits/messages.rs @@ -88,7 +88,7 @@ pub trait ServiceQueues { /// The resource footprint of a queue. #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] pub struct Footprint { - pub count: u32, + pub count: u64, pub size: u64, } From 5013e863b4c81214889d61b6b689aff9450edb65 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Dec 2022 11:58:50 +0100 Subject: [PATCH 104/110] Fix benchmarks Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/benchmarking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 990075c9b7b95..c0ff20431d00e 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -140,8 +140,8 @@ benchmarks! { } book.end += 1; book.count += 1; - book.message_count += msgs as u32; - book.size += page.remaining_size.into(); + book.message_count += msgs as u64; + book.size += page.remaining_size.into() as u64; } book.begin = book.end - T::MaxStale::get(); BookStateFor::::insert(&origin, &book); From 865f4930636abfa2714dae919478753883f0177e Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Dec 2022 12:51:39 +0100 Subject: [PATCH 105/110] Make CI green Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml index bcc4eedb94268..1da411e2fad31 100644 --- a/frame/message-queue/Cargo.toml +++ b/frame/message-queue/Cargo.toml @@ -27,7 +27,7 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../su frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } [dev-dependencies] -sp-tracing = { version = "6.0.0", default-features = false, path = "../../primitives/tracing" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } rand = "0.8.5" rand_distr = "0.4.3" @@ -42,7 +42,6 @@ std = [ "sp-std/std", "sp-arithmetic/std", "sp-weights/std", - "sp-tracing/std", "frame-benchmarking?/std", "frame-support/std", "frame-system/std", From b291e5d0c9217cef9a92c4cf61242ae8f454b304 Mon Sep 17 00:00:00 2001 From: Gav Date: Thu, 8 Dec 2022 19:44:06 +0000 Subject: [PATCH 106/110] Docs --- frame/message-queue/src/lib.rs | 35 ++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index f1353db8e8db9..9b976c48245c9 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -857,12 +857,39 @@ impl Pallet { let cullable = || { let total_pages = book_state.count; let ready_pages = book_state.end.saturating_sub(book_state.begin).min(total_pages); + + // The number of stale pages - i.e. pages which contain unprocessed overweight messages. + // We would prefer to keep these around but will restrict how far into history they can + // extend if we notice that there's too many of them. + // + // We don't know *where* in history these pages are so we use a dynamic formula which + // reduces the historical time horizon as the stale pages pile up and increases it as + // they reduce. let stale_pages = total_pages - ready_pages; + + // The maximum number of stale pages (i.e. of overweight messages) allowed before + // culling can happen at all. Once there are more stale pages than this, then historical + // pages may be dropped, even if they contain unprocessed overweight messages. let max_stale = T::MaxStale::get(); - if stale_pages <= max_stale { - return false - } - let watermark = book_state.begin.saturating_sub(max_stale); + + // The amount beyond the maximum which are being used. If it's not beyond the maximum + // then we exit now since no culling is needed. + let overflow = match stale_pages.checked_sub(max_stale + 1) { + Some(x) => x + 1, + None => return false, + }; + + // The special formula which tells us how deep into index-history we will pages. As + // the overflow is greater (and thus the need to drop items from storage is more urgent) + // this is reduced, allowing a greater range of pages to be culled. + // With a minimum `overflow` (`1`), this returns `max_stale ** 2`, indicating we only + // cull beyond that number of indices deep into history. + // At this overflow increases, our depth reduces down to a limit of `max_stale`. We + // never want to reduce below this since this will certainly allow enough pages to be + // culled in order to bring `overflow` back to zero. + let backlog = (max_stale * max_stale / overflow).max(max_stale); + + let watermark = book_state.begin.saturating_sub(backlog); page_index < watermark }; ensure!(reapable || cullable(), Error::::NotReapable); From 41fdf07e91c75eff2587520bb07c1448323a0cf8 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Thu, 8 Dec 2022 22:57:12 +0100 Subject: [PATCH 107/110] Update tests Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/src/tests.rs | 96 ++++++++++++++------------------ 1 file changed, 41 insertions(+), 55 deletions(-) diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 644b19c3b7c4e..103fb690ddba7 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -211,27 +211,31 @@ fn reap_page_permanent_overweight_works() { use MessageOrigin::*; new_test_ext::().execute_with(|| { // Create 10 pages more than the stale limit. - for _ in 0..(MaxStale::get() + 10) { + let n = (MaxStale::get() + 10) as usize; + for _ in 0..n { MessageQueue::enqueue_message(msg("weight=2"), Here); } - assert_eq!(Pages::::iter().count(), MaxStale::get() as usize + 10); - assert_eq!(QueueChanges::take().len(), MaxStale::get() as usize + 10); + assert_eq!(Pages::::iter().count(), n); + assert_eq!(QueueChanges::take().len(), n); // Mark all pages as stale since their message is permanently overweight. MessageQueue::service_queues(1.into_weight()); - // Cannot reap any page within the stale limit. - for _ in 10..(MaxStale::get() + 10) { - assert_noop!(MessageQueue::do_reap_page(&Here, 10), Error::::NotReapable); - } - // Can reap the stale ones below the watermark. - let b = BookStateFor::::get(Here); - for i in 0..10 { + // Check that we can reap everything below the watermark. + let max_stale = MaxStale::get(); + for i in 0..n as u32 { + let b = BookStateFor::::get(Here); + let stale_pages = n as u32 - i; + let overflow = stale_pages.saturating_sub(max_stale + 1) + 1; + let backlog = (max_stale * max_stale / overflow).max(max_stale); + let watermark = b.begin.saturating_sub(backlog); + + if i >= watermark { + break + } assert_ok!(MessageQueue::do_reap_page(&Here, i)); - assert_eq!( - QueueChanges::take(), - vec![(Here, b.message_count - (i as u64 + 1), b.size - (i as u64 + 1) * 8)] - ); + assert_eq!(QueueChanges::take(), vec![(Here, b.message_count - 1, b.size - 8)]); } + // Cannot reap any more pages. for (o, i, _) in Pages::::iter() { assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); @@ -264,68 +268,50 @@ fn reaping_overweight_fails_properly() { // Double-check that exactly these pages exist. assert_pages(&[0, 1, 2, 3, 4, 5]); - // Start by servicing with 2 weight; this is enough for 0:"a" and 1:"b". assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here), (vmsg("b"), Here)]); - assert_pages(&[0, 1, 2, 3, 4, 5]); + // 2 stale now. - // 0 and 1 are now stale since they both contain a permanently overweight message. - // Nothing is reapable yet, because we haven't hit the stale limit of 2. + // Nothing reapable yet, because we haven't hit the stale limit. for (o, i, _) in Pages::::iter() { assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); } + assert_pages(&[0, 1, 2, 3, 4, 5]); - // Service with 1 weight; this is enough for 2:"c". assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg("c"), Here)]); + // 3 stale now: can take something 4 pages in history. - // 0, 1 and 2 are stale now because of a permanently overweight message. - // 0 should be reapable since it is over the stale limit of 2. - assert_ok!(MessageQueue::do_reap_page(&Here, 0)); - assert_pages(&[1, 2, 3, 4, 5]); - assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); - // ... but no other pages. - for (o, i, _) in Pages::::iter() { - assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); - } - - // Service page 3. assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 1"), Here)]); - assert_pages(&[1, 2, 4, 5]); - // Nothing reapable. + // Nothing reapable yet, because we haven't hit the stale limit. for (o, i, _) in Pages::::iter() { assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); } + assert_pages(&[0, 1, 2, 4, 5]); - // Service page 4. assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 2"), Here)]); - assert_pages(&[1, 2, 5]); - assert_noop!(MessageQueue::do_reap_page(&Here, 3), Error::::NoPage); + assert_pages(&[0, 1, 2, 5]); - // Nothing reapable. + // First is now reapable as it is too far behind the first ready page (5). + assert_ok!(MessageQueue::do_reap_page(&Here, 0)); + // Others not reapable yet, because we haven't hit the stale limit. for (o, i, _) in Pages::::iter() { assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); } + assert_pages(&[1, 2, 5]); - // Mark 5 as stale by being permanently overweight. - assert_eq!(MessageQueue::service_queues(0.into_weight()), 0.into_weight()); - assert!(MessagesProcessed::take().is_empty()); - - // 1, 2 and 5 stale now: 1 should be reapable. - assert_ok!(MessageQueue::do_reap_page(&Here, 1)); - assert_noop!(MessageQueue::do_reap_page(&Here, 1), Error::::NoPage); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("bigbig 3"), Here)]); - // 2 and 5 are present but not reapable. - assert_pages(&[2, 5]); - for i in [2, 5] { - assert_noop!(MessageQueue::do_reap_page(&Here, i), Error::::NotReapable); - } - // 0, 1, 3 and 4 are gone. - for i in [0, 1, 3, 4] { - assert_noop!(MessageQueue::do_reap_page(&Here, i), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 0), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 3), Error::::NoPage); + assert_noop!(MessageQueue::do_reap_page(&Here, 4), Error::::NoPage); + // Still not reapable, since the number of stale pages is only 2. + for (o, i, _) in Pages::::iter() { + assert_noop!(MessageQueue::do_reap_page(&o, i), Error::::NotReapable); } }); } @@ -948,7 +934,7 @@ fn execute_overweight_works() { // Load the current book let book = BookStateFor::::get(origin); assert_eq!(book.message_count, 1); - assert!(Pages::::contains_key(&origin, 0)); + assert!(Pages::::contains_key(origin, 0)); // Mark the message as permanently overweight. assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); @@ -987,7 +973,7 @@ fn execute_overweight_works() { ::execute_overweight(70.into_weight(), (origin, 0, 0)); assert_eq!(consumed, Err(ExecuteOverweightError::NotFound)); assert!(QueueChanges::take().is_empty()); - assert!(!Pages::::contains_key(&origin, 0), "Page is gone"); + assert!(!Pages::::contains_key(origin, 0), "Page is gone"); }); } @@ -1066,7 +1052,7 @@ fn enqueue_message_works() { // Check the state. assert_eq!(BookStateFor::::iter().count(), 1); let book = BookStateFor::::get(Here); - assert_eq!(book.message_count as u64, n + 1); + assert_eq!(book.message_count, n + 1); assert_eq!(book.size, n + 3); assert_eq!((book.begin, book.end), (0, 4)); assert_eq!(book.count as usize, Pages::::iter().count()); @@ -1098,7 +1084,7 @@ fn enqueue_messages_works() { // Check the state. assert_eq!(BookStateFor::::iter().count(), 1); let book = BookStateFor::::get(Here); - assert_eq!(book.message_count as u64, n + 1); + assert_eq!(book.message_count, n + 1); assert_eq!(book.size, n + 3); assert_eq!((book.begin, book.end), (0, 4)); assert_eq!(book.count as usize, Pages::::iter().count()); From be6d5c93197d6fb05b7c00b3da50486e4e8daa27 Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Thu, 8 Dec 2022 23:29:09 +0000 Subject: [PATCH 108/110] ".git/.scripts/bench-bot.sh" pallet dev pallet_message_queue --- frame/message-queue/src/weights.rs | 128 ++++++++++++++++------------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index 00830c65fe044..cd9268ffde224 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -1,27 +1,43 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. //! Autogenerated weights for pallet_message_queue //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-28, STEPS: `50`, REPEAT: 200, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `oty-parity`, CPU: `11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz` -//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 +//! DATE: 2022-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/release/substrate +// /home/benchbot/cargo_target_dir/production/substrate // benchmark // pallet -// --dev -// --pallet -// pallet-message-queue -// --extrinsic= -// --steps -// 50 -// --repeat -// 200 -// --template -// .maintain/frame-weight-template.hbs -// --output -// frame/message-queue/src/weights.rs +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_message_queue +// --chain=dev +// --header=./HEADER-APACHE2 +// --output=./frame/message-queue/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] @@ -50,73 +66,73 @@ impl WeightInfo for SubstrateWeight { // Storage: MessageQueue ServiceHead (r:1 w:0) // Storage: MessageQueue BookStateFor (r:2 w:2) fn ready_ring_knit() -> Weight { - // Minimum execution time: 7_624 nanoseconds. - Weight::from_ref_time(8_085_000) + // Minimum execution time: 12_330 nanoseconds. + Weight::from_ref_time(12_711_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:2 w:2) // Storage: MessageQueue ServiceHead (r:1 w:1) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 7_495 nanoseconds. - Weight::from_ref_time(8_204_000) + // Minimum execution time: 12_322 nanoseconds. + Weight::from_ref_time(12_560_000) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) } // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_141 nanoseconds. - Weight::from_ref_time(3_351_000) + // Minimum execution time: 4_652 nanoseconds. + Weight::from_ref_time(4_848_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_completion() -> Weight { - // Minimum execution time: 4_645 nanoseconds. - Weight::from_ref_time(4_983_000) + // Minimum execution time: 7_115 nanoseconds. + Weight::from_ref_time(7_407_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 4_853 nanoseconds. - Weight::from_ref_time(5_484_000) + // Minimum execution time: 6_974 nanoseconds. + Weight::from_ref_time(7_200_000) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } fn service_page_item() -> Weight { - // Minimum execution time: 66_343 nanoseconds. - Weight::from_ref_time(74_817_000) + // Minimum execution time: 79_657 nanoseconds. + Weight::from_ref_time(80_050_000) } // Storage: MessageQueue ServiceHead (r:1 w:1) // Storage: MessageQueue BookStateFor (r:1 w:0) fn bump_service_head() -> Weight { - // Minimum execution time: 5_343 nanoseconds. - Weight::from_ref_time(5_666_000) + // Minimum execution time: 7_598 nanoseconds. + Weight::from_ref_time(8_118_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 23_052 nanoseconds. - Weight::from_ref_time(24_801_000) + // Minimum execution time: 60_562 nanoseconds. + Weight::from_ref_time(61_430_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight_page_removed() -> Weight { - // Minimum execution time: 68_156 nanoseconds. - Weight::from_ref_time(70_916_000) + // Minimum execution time: 74_582 nanoseconds. + Weight::from_ref_time(75_445_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight_page_updated() -> Weight { - // Minimum execution time: 68_642 nanoseconds. - Weight::from_ref_time(73_010_000) + // Minimum execution time: 87_526 nanoseconds. + Weight::from_ref_time(88_055_000) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) } @@ -127,73 +143,73 @@ impl WeightInfo for () { // Storage: MessageQueue ServiceHead (r:1 w:0) // Storage: MessageQueue BookStateFor (r:2 w:2) fn ready_ring_knit() -> Weight { - // Minimum execution time: 7_624 nanoseconds. - Weight::from_ref_time(8_085_000) + // Minimum execution time: 12_330 nanoseconds. + Weight::from_ref_time(12_711_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:2 w:2) // Storage: MessageQueue ServiceHead (r:1 w:1) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 7_495 nanoseconds. - Weight::from_ref_time(8_204_000) + // Minimum execution time: 12_322 nanoseconds. + Weight::from_ref_time(12_560_000) .saturating_add(RocksDbWeight::get().reads(3)) .saturating_add(RocksDbWeight::get().writes(3)) } // Storage: MessageQueue BookStateFor (r:1 w:1) fn service_queue_base() -> Weight { - // Minimum execution time: 3_141 nanoseconds. - Weight::from_ref_time(3_351_000) + // Minimum execution time: 4_652 nanoseconds. + Weight::from_ref_time(4_848_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_completion() -> Weight { - // Minimum execution time: 4_645 nanoseconds. - Weight::from_ref_time(4_983_000) + // Minimum execution time: 7_115 nanoseconds. + Weight::from_ref_time(7_407_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: MessageQueue Pages (r:1 w:1) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 4_853 nanoseconds. - Weight::from_ref_time(5_484_000) + // Minimum execution time: 6_974 nanoseconds. + Weight::from_ref_time(7_200_000) .saturating_add(RocksDbWeight::get().reads(1)) .saturating_add(RocksDbWeight::get().writes(1)) } fn service_page_item() -> Weight { - // Minimum execution time: 66_343 nanoseconds. - Weight::from_ref_time(74_817_000) + // Minimum execution time: 79_657 nanoseconds. + Weight::from_ref_time(80_050_000) } // Storage: MessageQueue ServiceHead (r:1 w:1) // Storage: MessageQueue BookStateFor (r:1 w:0) fn bump_service_head() -> Weight { - // Minimum execution time: 5_343 nanoseconds. - Weight::from_ref_time(5_666_000) + // Minimum execution time: 7_598 nanoseconds. + Weight::from_ref_time(8_118_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(1)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn reap_page() -> Weight { - // Minimum execution time: 23_052 nanoseconds. - Weight::from_ref_time(24_801_000) + // Minimum execution time: 60_562 nanoseconds. + Weight::from_ref_time(61_430_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight_page_removed() -> Weight { - // Minimum execution time: 68_156 nanoseconds. - Weight::from_ref_time(70_916_000) + // Minimum execution time: 74_582 nanoseconds. + Weight::from_ref_time(75_445_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(2)) } // Storage: MessageQueue BookStateFor (r:1 w:1) // Storage: MessageQueue Pages (r:1 w:1) fn execute_overweight_page_updated() -> Weight { - // Minimum execution time: 68_642 nanoseconds. - Weight::from_ref_time(73_010_000) + // Minimum execution time: 87_526 nanoseconds. + Weight::from_ref_time(88_055_000) .saturating_add(RocksDbWeight::get().reads(2)) .saturating_add(RocksDbWeight::get().writes(2)) } From f08e055206558b763d191718ca50e78f49f4bc83 Mon Sep 17 00:00:00 2001 From: Oliver Tale-Yazdi Date: Fri, 9 Dec 2022 10:36:06 +0100 Subject: [PATCH 109/110] Dont mention README.md in the Cargo.toml Signed-off-by: Oliver Tale-Yazdi --- frame/message-queue/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml index 1da411e2fad31..47d114902f52c 100644 --- a/frame/message-queue/Cargo.toml +++ b/frame/message-queue/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet to queue and process messages" -readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } From b4feb0521c246c477fcc87f36bf65cccd613b21c Mon Sep 17 00:00:00 2001 From: Gav Date: Fri, 9 Dec 2022 10:03:30 +0000 Subject: [PATCH 110/110] Remove reference to readme --- frame/message-queue/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml index 1da411e2fad31..47d114902f52c 100644 --- a/frame/message-queue/Cargo.toml +++ b/frame/message-queue/Cargo.toml @@ -7,7 +7,6 @@ license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet to queue and process messages" -readme = "README.md" [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }