From df7f0fe9c8479038003ed9c7b7a944a5ae5e9b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 13 Nov 2025 14:52:30 +0100 Subject: [PATCH 01/30] Introduce `MaxParachainBlockWeight` and related functionality This pull request introduces `MaxParachainBlockWeight` to calculate the max weight per parachain block. This is a preparation for [Block Bundling](https://github.com/paritytech/polkadot-sdk/issues/6495) which requires that the maximum block weight is dynamic. Block bundling requires a dynamic maximum block weight because it bundles multiple blocks into one `PoV`. Each `PoV` gets `2s` of execution time and `10MiB` of proof size. These resources need to be split up between all the blocks of one `PoV`. This doesn't require the weight to be dynamic. However, it gets complicated when a transaction should be applied that requires more resources than what one of these blocks can provide, e.g. for doing a runtime upgrade. In this case `MaxParachainBlockWeight` supports to increase the block weight of one block to take up the weight of the full `PoV`. The feature will not only be useful for things like runtime upgrade, but also could enable users to pay for running some huge contracts or whatever. For more information, please refer to the docs provided in the code of this pull request. For `MaxParachainBlockWeight` to work correctly, it provides a pre-inherent hook and a transaction extension. Both are required to track the weight correctly. --- Cargo.lock | 4 + cumulus/pallets/parachain-system/Cargo.toml | 9 +- .../parachain-system/src/benchmarking.rs | 210 ++++- .../parachain-system/src/block_weight/mock.rs | 372 +++++++++ .../parachain-system/src/block_weight/mod.rs | 206 +++++ .../src/block_weight/pre_inherents_hook.rs | 77 ++ .../src/block_weight/tests.rs | 788 ++++++++++++++++++ .../src/block_weight/transaction_extension.rs | 489 +++++++++++ cumulus/pallets/parachain-system/src/lib.rs | 114 ++- cumulus/pallets/parachain-system/src/tests.rs | 80 +- .../src/validate_block/implementation.rs | 3 + .../src/validate_block/tests.rs | 16 +- .../pallets/parachain-system/src/weights.rs | 28 + .../cumulus_pallet_parachain_system.rs | 11 + .../cumulus_pallet_parachain_system.rs | 11 + .../cumulus_pallet_parachain_system.rs | 11 + .../cumulus_pallet_parachain_system.rs | 11 + .../cumulus_pallet_parachain_system.rs | 11 + .../cumulus_pallet_parachain_system.rs | 11 + .../cumulus_pallet_parachain_system.rs | 11 + .../cumulus_pallet_parachain_system.rs | 11 + .../cumulus_pallet_parachain_system.rs | 11 + .../cumulus_pallet_parachain_system.rs | 11 + cumulus/primitives/core/src/lib.rs | 86 +- polkadot/primitives/src/v9/mod.rs | 12 + .../cumulus_pallet_parachain_system.rs | 11 + .../frame/support/src/traits/messages.rs | 10 + .../system/src/extensions/check_weight.rs | 8 +- substrate/frame/system/src/lib.rs | 2 +- substrate/primitives/runtime/src/testing.rs | 30 +- 30 files changed, 2608 insertions(+), 57 deletions(-) create mode 100644 cumulus/pallets/parachain-system/src/block_weight/mock.rs create mode 100644 cumulus/pallets/parachain-system/src/block_weight/mod.rs create mode 100644 cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs create mode 100644 cumulus/pallets/parachain-system/src/block_weight/tests.rs create mode 100644 cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs diff --git a/Cargo.lock b/Cargo.lock index 8b44504bc8868..de5dd04fe50ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4712,8 +4712,11 @@ dependencies = [ "cumulus-primitives-proof-size-hostfunction", "cumulus-test-client", "cumulus-test-relay-sproof-builder", + "derive-where", + "docify", "environmental", "frame-benchmarking", + "frame-executive", "frame-support", "frame-system", "futures", @@ -4724,6 +4727,7 @@ dependencies = [ "pallet-message-queue", "parity-scale-codec", "polkadot-parachain-primitives", + "polkadot-primitives", "polkadot-runtime-parachains", "rand 0.8.5", "rstest", diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 7c111579f0672..50c262ad8c733 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -15,6 +15,7 @@ workspace = true array-bytes = { workspace = true } bytes = { workspace = true } codec = { features = ["derive"], workspace = true } +derive-where = { workspace = true } environmental = { workspace = true } hashbrown = { workspace = true } impl-trait-for-tuples = { workspace = true } @@ -40,6 +41,7 @@ sp-version = { workspace = true } # Polkadot polkadot-parachain-primitives = { features = ["wasm-api"], workspace = true } +polkadot-primitives = { workspace = true } polkadot-runtime-parachains = { workspace = true } xcm = { workspace = true } xcm-builder = { workspace = true } @@ -50,6 +52,9 @@ cumulus-primitives-core = { workspace = true } cumulus-primitives-parachain-inherent = { workspace = true } cumulus-primitives-proof-size-hostfunction = { workspace = true } +# For building docs +docify = { workspace = true } + [dev-dependencies] assert_matches = { workspace = true } futures = { workspace = true } @@ -58,8 +63,8 @@ rand = { workspace = true, default-features = true } rstest = { workspace = true } trie-standardmap = { workspace = true } - # Substrate +frame-executive = { workspace = true } sc-consensus = { workspace = true } sp-api = { workspace = true, default-features = true } sp-consensus-slots = { workspace = true, default-features = true } @@ -67,6 +72,7 @@ sp-crypto-hashing = { workspace = true, default-features = true } sp-keyring = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } + # Cumulus cumulus-test-client = { workspace = true } cumulus-test-relay-sproof-builder = { workspace = true, default-features = true } @@ -87,6 +93,7 @@ std = [ "log/std", "pallet-message-queue/std", "polkadot-parachain-primitives/std", + "polkadot-primitives/std", "polkadot-runtime-parachains/std", "scale-info/std", "sp-consensus-babe/std", diff --git a/cumulus/pallets/parachain-system/src/benchmarking.rs b/cumulus/pallets/parachain-system/src/benchmarking.rs index c3d59e82255a3..fd05cac40d256 100644 --- a/cumulus/pallets/parachain-system/src/benchmarking.rs +++ b/cumulus/pallets/parachain-system/src/benchmarking.rs @@ -20,12 +20,31 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use crate::parachain_inherent::InboundDownwardMessages; -use cumulus_primitives_core::{relay_chain::Hash as RelayHash, InboundDownwardMessage}; +use crate::{ + block_weight::{BlockWeightMode, DynamicMaxBlockWeight, MaxParachainBlockWeight}, + parachain_inherent::InboundDownwardMessages, +}; +use cumulus_primitives_core::{ + relay_chain::Hash as RelayHash, BundleInfo, CoreInfo, InboundDownwardMessage, +}; use frame_benchmarking::v2::*; -use sp_runtime::traits::BlakeTwo256; +use frame_support::{ + dispatch::{DispatchInfo, PostDispatchInfo}, + weights::constants::WEIGHT_REF_TIME_PER_SECOND, +}; +use frame_system::RawOrigin; +use sp_core::ConstU32; +use sp_runtime::traits::{BlakeTwo256, DispatchTransaction, Dispatchable}; -#[benchmarks] +fn has_use_full_core_digest() -> bool { + let digest = frame_system::Pallet::::digest(); + CumulusDigestItem::contains_use_full_core(&digest) +} + +#[benchmarks(where + T: Send + Sync, + T::RuntimeCall: Dispatchable, +)] mod benchmarks { use super::*; @@ -64,6 +83,189 @@ mod benchmarks { head } + /// The worst-case scenario for the block weight transaction extension. + /// + /// Before executing an extrinsic `FractionOfCore` is set, changed to `PotentialFullCore` and + /// post dispatch switches to `FullCore`. + #[benchmark] + fn block_weight_tx_extension_max_weight() -> Result<(), BenchmarkError> { + let caller = account("caller", 0, 0); + + frame_system::Pallet::::note_inherents_applied(); + + frame_system::Pallet::::set_extrinsic_index(1); + + frame_system::Pallet::::deposit_log( + BundleInfo { index: 0, maybe_last: false }.to_digest_item(), + ); + frame_system::Pallet::::deposit_log( + CoreInfo { + selector: 0.into(), + claim_queue_offset: 0.into(), + number_of_cores: 1.into(), + } + .to_digest_item(), + ); + let target_weight = MaxParachainBlockWeight::>::target_block_weight(); + + let info = DispatchInfo { + // The weight needs to be more than the target weight. + call_weight: target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 0)), + extension_weight: Weight::zero(), + class: DispatchClass::Normal, + ..Default::default() + }; + let call: T::RuntimeCall = frame_system::Call::remark { remark: vec![] }.into(); + let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; + let len = 0_usize; + + crate::BlockWeightMode::::put(BlockWeightMode::FractionOfCore { + first_transaction_index: None, + }); + + let ext = DynamicMaxBlockWeight::>::new(()); + + #[block] + { + ext.test_run(RawOrigin::Signed(caller).into(), &call, &info, len, 0, |_| { + // Normally this is done by `CheckWeight` + frame_system::Pallet::::register_extra_weight_unchecked( + info.call_weight, + DispatchClass::Normal, + ); + Ok(post_info) + }) + .unwrap() + .unwrap(); + } + + assert_eq!(crate::BlockWeightMode::::get().unwrap(), BlockWeightMode::FullCore); + assert!(has_use_full_core_digest::()); + assert_eq!( + MaxParachainBlockWeight::>::get(), + MaxParachainBlockWeight::>::FULL_CORE_WEIGHT + ); + + Ok(()) + } + + /// A benchmark that assumes that an extrinsic was executed with `FractionOfCore` set. + #[benchmark] + fn block_weight_tx_extension_stays_fraction_of_core() -> Result<(), BenchmarkError> { + let caller = account("caller", 0, 0); + + frame_system::Pallet::::note_inherents_applied(); + + frame_system::Pallet::::set_extrinsic_index(1); + + frame_system::Pallet::::deposit_log( + BundleInfo { index: 0, maybe_last: false }.to_digest_item(), + ); + frame_system::Pallet::::deposit_log( + CoreInfo { + selector: 0.into(), + claim_queue_offset: 0.into(), + number_of_cores: 1.into(), + } + .to_digest_item(), + ); + let target_weight = MaxParachainBlockWeight::>::target_block_weight(); + + let info = DispatchInfo { + call_weight: Weight::from_parts(1024, 1024), + extension_weight: Weight::zero(), + class: DispatchClass::Normal, + ..Default::default() + }; + let call: T::RuntimeCall = frame_system::Call::remark { remark: vec![] }.into(); + let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; + let len = 0_usize; + + crate::BlockWeightMode::::put(BlockWeightMode::FractionOfCore { + first_transaction_index: None, + }); + + let ext = DynamicMaxBlockWeight::>::new(()); + + #[block] + { + ext.test_run(RawOrigin::Signed(caller).into(), &call, &info, len, 0, |_| { + // Normally this is done by `CheckWeight` + frame_system::Pallet::::register_extra_weight_unchecked( + info.call_weight, + DispatchClass::Normal, + ); + Ok(post_info) + }) + .unwrap() + .unwrap(); + } + + assert_eq!( + crate::BlockWeightMode::::get().unwrap(), + BlockWeightMode::FractionOfCore { first_transaction_index: Some(1) } + ); + assert!(!has_use_full_core_digest::()); + assert_eq!(MaxParachainBlockWeight::>::get(), target_weight); + + Ok(()) + } + + /// A benchmark that assumes that `FullCore` was set already before executing an extrinsic. + #[benchmark] + fn block_weight_tx_extension_full_core() -> Result<(), BenchmarkError> { + let caller = account("caller", 0, 0); + + frame_system::Pallet::::note_inherents_applied(); + + frame_system::Pallet::::set_extrinsic_index(1); + + frame_system::Pallet::::deposit_log( + BundleInfo { index: 0, maybe_last: false }.to_digest_item(), + ); + frame_system::Pallet::::deposit_log( + CoreInfo { + selector: 0.into(), + claim_queue_offset: 0.into(), + number_of_cores: 1.into(), + } + .to_digest_item(), + ); + + let info = DispatchInfo { + call_weight: Weight::from_parts(1024, 1024), + extension_weight: Weight::zero(), + class: DispatchClass::Normal, + ..Default::default() + }; + let call: T::RuntimeCall = frame_system::Call::remark { remark: vec![] }.into(); + let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; + let len = 0_usize; + + crate::BlockWeightMode::::put(BlockWeightMode::FullCore); + + let ext = DynamicMaxBlockWeight::>::new(()); + + #[block] + { + ext.test_run(RawOrigin::Signed(caller).into(), &call, &info, len, 0, |_| { + // Normally this is done by `CheckWeight` + frame_system::Pallet::::register_extra_weight_unchecked( + info.call_weight, + DispatchClass::Normal, + ); + Ok(post_info) + }) + .unwrap() + .unwrap(); + } + + assert_eq!(crate::BlockWeightMode::::get().unwrap(), BlockWeightMode::FullCore); + + Ok(()) + } + impl_benchmark_test_suite! { Pallet, crate::mock::new_test_ext(), diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs new file mode 100644 index 0000000000000..0eba745c88d9a --- /dev/null +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -0,0 +1,372 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. +// 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. + +use super::{transaction_extension::DynamicMaxBlockWeight, *}; +use crate::{self as parachain_system, PreviousCoreCount}; +use codec::Compact; +use cumulus_primitives_core::{ + BundleInfo, ClaimQueueOffset, CoreInfo, CoreSelector, CumulusDigestItem, +}; +use frame_support::{ + construct_runtime, derive_impl, + dispatch::DispatchClass, + parameter_types, + traits::PreInherents, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight}, + Weight, + }, +}; +use frame_system::limits::BlockWeights; +use sp_core::ConstU32; +use sp_io; +use sp_runtime::{ + generic::{self, UncheckedExtrinsic}, + testing::UintAuthorityId, + BuildStorage, Perbill, +}; + +const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(1); + +/// A simple call, which one doesn't matter. +pub const CALL: &RuntimeCall = + &RuntimeCall::System(frame_system::Call::set_heap_pages { pages: 0u64 }); + +pub type ExtrinsicOnlyOperational = UncheckedExtrinsic< + UintAuthorityId, + only_operational_runtime::RuntimeCall, + UintAuthorityId, + DynamicMaxBlockWeight, 10, false>, +>; + +pub type Extrinsic = UncheckedExtrinsic< + UintAuthorityId, + RuntimeCall, + UintAuthorityId, + DynamicMaxBlockWeight>, +>; + +pub type Block = + generic::Block::Hashing>, Extrinsic>; + +pub type BlockOnlyOperational = generic::Block< + generic::Header::Hashing>, + ExtrinsicOnlyOperational, +>; + +pub const TARGET_BLOCK_RATE: u32 = 12; + +#[docify::export(tx_extension_setup)] +pub type TxExtension = DynamicMaxBlockWeight< + Runtime, + // Here you need to set the other extensions that are required by your runtime... + ( + frame_system::AuthorizeCall, + frame_system::CheckNonZeroSender, + frame_system::CheckSpecVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, + ), + ConstU32, +>; + +#[allow(dead_code)] +type NotDeadCode = TxExtension; + +#[docify::export_content(max_block_weight_setup)] +mod max_block_weight_setup { + use super::*; + + type MaximumBlockWeight = MaxParachainBlockWeight>; + + parameter_types! { + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(MaximumBlockWeight::get()); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MaximumBlockWeight::get()); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + } +} + +#[frame_support::pallet(dev_mode)] +pub mod test_pallet { + use frame_support::{ + dispatch::DispatchClass, pallet_prelude::*, weights::constants::WEIGHT_REF_TIME_PER_SECOND, + }; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + crate::Config {} + + #[pallet::call] + impl Pallet { + /// A heavy call with Normal dispatch class that consumes significant weight. + #[pallet::weight((Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024), DispatchClass::Normal))] + pub fn heavy_call_normal(_: OriginFor) -> DispatchResult { + Ok(()) + } + + /// A heavy call with Operational dispatch class that consumes significant weight. + #[pallet::weight((Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024), DispatchClass::Operational))] + pub fn heavy_call_operational(_: OriginFor) -> DispatchResult { + Ok(()) + } + + /// A heavy call with Operational dispatch class that consumes significant weight. + #[pallet::weight((Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024), DispatchClass::Mandatory))] + pub fn heavy_call_mandatory(_: OriginFor) -> DispatchResult { + Ok(()) + } + } + + #[pallet::inherent] + impl ProvideInherent for Pallet { + type Call = Call; + type Error = sp_inherents::MakeFatalError<()>; + const INHERENT_IDENTIFIER: InherentIdentifier = *b"testtest"; + + fn create_inherent(_data: &InherentData) -> Option { + None + } + + fn is_inherent(call: &Self::Call) -> bool { + matches!(call, Call::heavy_call_mandatory {}) + } + } +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +#[docify::export(pre_inherents_setup)] +impl frame_system::Config for Runtime { + // Setup the block weight. + type BlockWeights = max_block_weight_setup::RuntimeBlockWeights; + // Set the `PreInherents` hook. + type PreInherents = DynamicMaxBlockWeightHooks>; + + // Just required to make it compile, but not that important for this example here. + type Block = Block; + type OnSetCode = crate::ParachainSetCode; + type AccountId = u64; + type Lookup = UintAuthorityId; + // Rest of the types are omitted here. +} + +impl crate::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = (); + type OutboundXcmpMessageSource = (); + type DmpQueue = (); + type ReservedDmpWeight = (); + type XcmpMessageHandler = (); + type ReservedXcmpWeight = (); + type CheckAssociatedRelayNumber = crate::RelayNumberStrictlyIncreases; + type WeightInfo = (); + type ConsensusHook = crate::ExpectParentIncluded; + type RelayParentOffset = (); +} + +impl test_pallet::Config for Runtime {} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + ParachainSystem: parachain_system, + TestPallet: test_pallet, + } +); + +pub mod only_operational_runtime { + use frame_support::{construct_runtime, derive_impl}; + use sp_core::ConstU32; + use sp_runtime::testing::UintAuthorityId; + + use crate::block_weight::{mock::BlockOnlyOperational, DynamicMaxBlockWeightHooks}; + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for RuntimeOnlyOperational { + // Setup the block weight. + type BlockWeights = super::max_block_weight_setup::RuntimeBlockWeights; + // Set the `PreInherents` hook. + type PreInherents = + DynamicMaxBlockWeightHooks>; + + // Just required to make it compile, but not that important for this example here. + type Block = BlockOnlyOperational; + type OnSetCode = crate::ParachainSetCode; + type AccountId = u64; + type Lookup = UintAuthorityId; + // Rest of the types are omitted here. + } + + impl crate::Config for RuntimeOnlyOperational { + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = (); + type OutboundXcmpMessageSource = (); + type DmpQueue = (); + type ReservedDmpWeight = (); + type XcmpMessageHandler = (); + type ReservedXcmpWeight = (); + type CheckAssociatedRelayNumber = crate::RelayNumberStrictlyIncreases; + type WeightInfo = (); + type ConsensusHook = crate::ExpectParentIncluded; + type RelayParentOffset = (); + } + + impl super::test_pallet::Config for RuntimeOnlyOperational {} + + construct_runtime!( + pub enum RuntimeOnlyOperational { + System: frame_system, + ParachainSystem: super::parachain_system, + TestPallet: super::test_pallet, + } + ); +} + +pub use only_operational_runtime::{ + RuntimeCall as RuntimeCallOnlyOperational, RuntimeOnlyOperational, +}; + +/// Executive: handles dispatch to the various modules. +pub type Executive = + frame_executive::Executive, Runtime, ()>; + +/// Executive configured to only accept operational transaction to go over the limit. +pub type ExecutiveOnlyOperational = frame_executive::Executive< + RuntimeOnlyOperational, + BlockOnlyOperational, + frame_system::ChainContext, + RuntimeOnlyOperational, + (), +>; + +/// Builder for test externalities +pub struct TestExtBuilder { + num_cores: Option, + bundle_index: Option, + bundle_maybe_last: bool, + previous_core_count: Option, +} + +impl Default for TestExtBuilder { + fn default() -> Self { + sp_tracing::try_init_simple(); + + Self { + num_cores: None, + bundle_index: None, + bundle_maybe_last: false, + previous_core_count: None, + } + } +} + +impl TestExtBuilder { + /// Create a new builder + pub fn new() -> Self { + Self::default() + } + + /// Set the number of cores + pub fn number_of_cores(mut self, num_cores: u16) -> Self { + self.num_cores = Some(num_cores); + self + } + + /// Set the `PreviousCoreCount` storage value. + pub fn previous_core_count(mut self, previous_core_count: u16) -> Self { + self.previous_core_count = Some(previous_core_count); + self + } + + /// Set this as the first block in the core (bundle index = 0) + pub fn first_block_in_core(mut self, is_first: bool) -> Self { + if is_first { + self.bundle_index = Some(0); + } else if self.bundle_index.is_none() { + // If not first and no bundle index set, default to index 1 + self.bundle_index = Some(1); + } + self + } + + /// Build the test externalities + pub fn build(self) -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::from(storage); + + ext.execute_with(|| { + // Add core info if specified + if let Some(num_cores) = self.num_cores { + let core_info = CoreInfo { + selector: CoreSelector(0), + claim_queue_offset: ClaimQueueOffset(0), + number_of_cores: Compact(num_cores), + }; + let digest = CumulusDigestItem::CoreInfo(core_info).to_digest_item(); + frame_system::Pallet::::deposit_log(digest); + } + + // Add bundle info if specified + if let Some(bundle_index) = self.bundle_index { + let bundle_info = + BundleInfo { index: bundle_index, maybe_last: self.bundle_maybe_last }; + let digest = CumulusDigestItem::BundleInfo(bundle_info).to_digest_item(); + frame_system::Pallet::::deposit_log(digest); + } + + if let Some(previous_core_count) = self.previous_core_count { + PreviousCoreCount::::put(Compact(previous_core_count)); + } + }); + + ext + } +} + +/// Helper to check if UseFullCore digest was deposited +pub fn has_use_full_core_digest() -> bool { + let digest = frame_system::Pallet::::digest(); + CumulusDigestItem::contains_use_full_core(&digest) +} + +/// Helper to register weight as consumed (simulating on_initialize) +pub fn register_weight(weight: Weight, class: DispatchClass) { + frame_system::Pallet::::register_extra_weight_unchecked(weight, class); +} + +/// Emulates what happes after `initialize_block` finished. +pub fn initialize_block_finished() { + System::set_block_consumed_resources(Weight::zero(), 0); + System::note_finished_initialize(); + ::PreInherents::pre_inherents(); + System::note_inherents_applied(); +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs new file mode 100644 index 0000000000000..4a703ea21f4d4 --- /dev/null +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -0,0 +1,206 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. +// 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. + +//! Provides functionality to dynamically calculate the block weight for a parachain. +//! +//! With block bundling, parachains are relative free to choose whatever block interval they want. +//! The block interval is the time between individual blocks. The available resources per block (max +//! block weight) depend on the number of cores allocated to the parachain on the relay chain. Each +//! relay chain cores provides an execution time of `2s` and a storage size of `10MiB`. Depending on +//! the desired number of blocks to produce, the resources need to be divided between the individual +//! blocks. With small blocks that do not have that many resources available, a problem may arises +//! for bigger transactions not fitting into blocks anymore, e.g. a runtime upgrade. For these cases +//! the weight of a block can be increased to use the weight of a full core. Only the first block of +//! a core is allowed to increase its weight to use the full core weight. In the case of the first +//! block using the full core weight, there will be no further block build on the same core. This is +//! signaled to the node by setting the [`CumulusDigestItem::UseFullCore`] digest item.` +//! +//! The [`MaxParachainBlockWeight`] provides a [`Get`] implementation that will return the max block +//! weight as determined by the [`DynamicMaxBlockWeight`] transaction extension. +//! +//! [`DynamicMaxBlockWeightHooks`] needs to be registered as a pre-inherent hook. It is used to +//! handle the weight consumption of `on_initialize` and change the block weight mode based on the +//! consumed weight. +//! +//! # Setup +//! +//! Setup the transaction extension: +#![doc = docify::embed!("src/block_weight/mock.rs", tx_extension_setup)] +//! +//! Setting up `MaximumBlockWeight`: +#![doc = docify::embed!("src/block_weight/mock.rs", max_block_weight_setup)] +//! +//! Registering of the `PreInherents` hook: +#![doc = docify::embed!("src/block_weight/mock.rs", pre_inherents_setup)] + +use crate::{Config, PreviousCoreCount}; +use codec::{Decode, Encode}; +use core::marker::PhantomData; +use cumulus_primitives_core::CumulusDigestItem; +use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; +use polkadot_primitives::MAX_POV_SIZE; +use scale_info::TypeInfo; +use sp_core::Get; +use sp_runtime::Digest; + +#[cfg(test)] +mod mock; +pub mod pre_inherents_hook; +#[cfg(test)] +mod tests; +pub mod transaction_extension; + +pub use pre_inherents_hook::DynamicMaxBlockWeightHooks; +pub use transaction_extension::DynamicMaxBlockWeight; + +const LOG_TARGET: &str = "runtime::parachain-system::block-weight"; + +/// The current block weight mode. +/// +/// Based on this mode [`MaxParachainBlockWeight`] determines the current allowed block weight. +#[derive(Debug, Encode, Decode, Clone, Copy, TypeInfo, PartialEq)] +pub enum BlockWeightMode { + /// The block is allowed to use the weight of a full core. + FullCore, + /// The current active transaction is allowed to use the weight of a full core. + PotentialFullCore { + /// The index of the first transaction. + first_transaction_index: Option, + /// The target weight that was used to determine that the extrinsic is above this limit. + target_weight: Weight, + }, + /// The block is only allowed to consume its fraction of the core. + /// + /// How much each block is allowed to consume, depends on the target number of blocks and the + /// available cores on the relay chain. + FractionOfCore { + /// The index of the first transaction. + first_transaction_index: Option, + }, +} + +/// Calculates the maximum block weight for a parachain. +/// +/// Based on the available cores and the number of desired blocks a block weight is calculated. +/// +/// The max block weight is partly dynamic and controlled via the [`DynamicMaxBlockWeight`] +/// transaction extension. The transaction extension is communicating the desired max block weight +/// using the [`BlockWeightMode`]. +pub struct MaxParachainBlockWeight(PhantomData<(Config, TargetBlockRate)>); + +impl> + MaxParachainBlockWeight +{ + // Maximum ref time per core + const MAX_REF_TIME_PER_CORE_NS: u64 = 2 * WEIGHT_REF_TIME_PER_SECOND; + pub(crate) const FULL_CORE_WEIGHT: Weight = + Weight::from_parts(Self::MAX_REF_TIME_PER_CORE_NS, MAX_POV_SIZE as u64); + + /// Returns the target block weight for one block. + pub(crate) fn target_block_weight() -> Weight { + let digest = frame_system::Pallet::::digest(); + Self::target_block_weight_with_digest(&digest) + } + + /// Same as [`Self::target_block_weight`], but takes the `digests` directly. + fn target_block_weight_with_digest(digest: &Digest) -> Weight { + let number_of_cores = CumulusDigestItem::find_core_info(&digest).map_or_else( + || PreviousCoreCount::::get().map_or(1, |pc| pc.0), + |ci| ci.number_of_cores.0, + ) as u32; + + let target_blocks = TargetBlockRate::get(); + + // Ensure we have at least one core and valid target blocks + if number_of_cores == 0 || target_blocks == 0 { + return Self::FULL_CORE_WEIGHT; + } + + // At maximum we want to allow `6s` of ref time, because we don't want to overload nodes + // that are running with standard hardware. These nodes need to be able to import all the + // blocks in 6s. + let total_ref_time = (number_of_cores as u64) + .saturating_mul(Self::MAX_REF_TIME_PER_CORE_NS) + .min(WEIGHT_REF_TIME_PER_SECOND * 6); + let ref_time_per_block = total_ref_time + .saturating_div(target_blocks as u64) + .min(Self::MAX_REF_TIME_PER_CORE_NS); + + let total_pov_size = (number_of_cores as u64).saturating_mul(MAX_POV_SIZE as u64); + // Each block at max gets one core. + let proof_size_per_block = + total_pov_size.saturating_div(target_blocks as u64).min(MAX_POV_SIZE as u64); + + Weight::from_parts(ref_time_per_block, proof_size_per_block) + } +} + +impl> Get + for MaxParachainBlockWeight +{ + fn get() -> Weight { + let digest = frame_system::Pallet::::digest(); + let target_block_weight = Self::target_block_weight_with_digest(&digest); + + let maybe_full_core_weight = if is_first_block_in_core_with_digest(&digest).unwrap_or(false) + { + Self::FULL_CORE_WEIGHT + } else { + target_block_weight + }; + + // If we are in `on_initialize` or at applying the inherents, we allow the maximum block + // weight as allowed by the current context. + if !frame_system::Pallet::::inherents_applied() { + return maybe_full_core_weight + } + + match crate::BlockWeightMode::::get() { + // We allow the full core. + Some(BlockWeightMode::FullCore | BlockWeightMode::PotentialFullCore { .. }) => + Self::FULL_CORE_WEIGHT, + // Let's calculate below how much weight we can use. + Some(BlockWeightMode::FractionOfCore { .. }) => target_block_weight, + // Either the runtime is not using the `DynamicMaxBlockWeight` extension or there is a + // bug. The value should be set before applying the first extrinsic. + None => maybe_full_core_weight, + } + } +} + +/// Is this the first block in a core? +fn is_first_block_in_core() -> Option { + let digest = frame_system::Pallet::::digest(); + is_first_block_in_core_with_digest(&digest) +} + +/// Is this the first block in a core? (takes digest as parameter) +/// +/// Returns `None` if the [`CumulusDigestItem::BundleInfo`] digest is not set. +fn is_first_block_in_core_with_digest(digest: &Digest) -> Option { + CumulusDigestItem::find_bundle_info(digest).map(|bi| bi.index == 0) +} + +/// Is the `BlockWeight` already above the target block weight? +/// +/// Returns `None` if the [`CumulusDigestItem::BundleInfo`] digest is not set. +fn block_weight_over_target_block_weight>() -> bool { + let target_block_weight = MaxParachainBlockWeight::::target_block_weight(); + + frame_system::Pallet::::remaining_block_weight() + .consumed() + .any_gt(target_block_weight) +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs new file mode 100644 index 0000000000000..0c51997c36303 --- /dev/null +++ b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs @@ -0,0 +1,77 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. +// 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. + +use super::{ + block_weight_over_target_block_weight, is_first_block_in_core, BlockWeightMode, LOG_TARGET, +}; +use crate::block_weight::MaxParachainBlockWeight; +use cumulus_primitives_core::CumulusDigestItem; +use frame_support::traits::PreInherents; +use sp_core::Get; + +/// A pre-inherent hook that may increases max block weight after `on_initialize`. +/// +/// The hook is called before applying the first inherent. It checks the used block weight of +/// `on_initialize`. If the used block weight is above the target block weight, the hook will set +/// the [`CumulusDigestItem::UseFullCore`] digest. Regardless on if this is the first block in a +/// core or not. This is done to inform the node that this is the last block for the current core. +pub struct DynamicMaxBlockWeightHooks( + pub core::marker::PhantomData<(Config, TargetBlockRate)>, +); + +impl PreInherents for DynamicMaxBlockWeightHooks +where + Config: crate::Config, + TargetBlockRate: Get, +{ + fn pre_inherents() { + if !block_weight_over_target_block_weight::() { + // We still initialize the `BlockWeightMode`. + crate::BlockWeightMode::::put(BlockWeightMode::FractionOfCore { + first_transaction_index: None, + }); + return + } + + let is_first_block_in_core = is_first_block_in_core::().unwrap_or(false); + + if !is_first_block_in_core { + log::error!( + target: LOG_TARGET, + "Inherent block logic took longer than the target block weight, THIS IS A BUG!!!", + ); + + // We are already above the allowed maximum and do not want to accept any more + // extrinsics. + frame_system::Pallet::::register_extra_weight_unchecked( + MaxParachainBlockWeight::::FULL_CORE_WEIGHT, + frame_support::dispatch::DispatchClass::Mandatory, + ); + } else { + log::debug!( + target: LOG_TARGET, + "Inherent block logic took longer than the target block weight, going to use the full core", + ); + } + + crate::BlockWeightMode::::put(BlockWeightMode::FullCore); + + // Inform the node that this block uses the full core. + frame_system::Pallet::::deposit_log( + CumulusDigestItem::UseFullCore.to_digest_item(), + ); + } +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs new file mode 100644 index 0000000000000..f45e8604d9d31 --- /dev/null +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -0,0 +1,788 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. +// 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. + +use super::{mock::*, transaction_extension::DynamicMaxBlockWeight, *}; +use assert_matches::assert_matches; +use codec::Compact; +use cumulus_primitives_core::{ + BundleInfo, ClaimQueueOffset, CoreInfo, CoreSelector, CumulusDigestItem, +}; +use frame_support::{ + assert_ok, + dispatch::{DispatchClass, DispatchInfo, PostDispatchInfo}, + pallet_prelude::{InvalidTransaction, TransactionSource}, + traits::PreInherents, + weights::constants::WEIGHT_REF_TIME_PER_SECOND, +}; +use frame_system::{CheckWeight, RawOrigin as SystemOrigin}; +use polkadot_primitives::MAX_POV_SIZE; +use sp_core::ConstU32; +use sp_runtime::{ + traits::{DispatchTransaction, Header, TransactionExtension}, + Digest, +}; + +type TxExtension = DynamicMaxBlockWeight, ConstU32<4>>; +type TxExtensionOnlyOperational = + DynamicMaxBlockWeight, ConstU32<4>, 10, false>; +type MaximumBlockWeight = MaxParachainBlockWeight>; + +#[test] +fn test_single_core_single_block() { + TestExtBuilder::new().number_of_cores(1).build().execute_with(|| { + let weight = MaxParachainBlockWeight::>::get(); + + assert_eq!(weight.ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); + assert_eq!(weight.proof_size(), MAX_POV_SIZE as u64); + }); +} + +#[test] +fn test_single_core_multiple_blocks() { + TestExtBuilder::new().number_of_cores(1).build().execute_with(|| { + let weight = MaxParachainBlockWeight::>::get(); + + // With 1 core and 4 target blocks, should get 0.5s ref time and 1/4 PoV size per block + assert_eq!(weight.ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND / 4); + assert_eq!(weight.proof_size(), (1 * MAX_POV_SIZE as u64) / 4); + }); +} + +#[test] +fn test_multiple_cores_single_block() { + TestExtBuilder::new().number_of_cores(3).build().execute_with(|| { + let weight = MaxParachainBlockWeight::>::get(); + + // With 3 cores and 1 target blocks, should get 2s ref time and 1 PoV size + assert_eq!(weight.ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); + assert_eq!(weight.proof_size(), MAX_POV_SIZE as u64); + }); +} + +#[test] +fn test_multiple_cores_multiple_blocks() { + TestExtBuilder::new().number_of_cores(2).build().execute_with(|| { + let weight = MaxParachainBlockWeight::>::get(); + + // With 2 cores and 4 target blocks, should get 1s ref time and 2x PoV size / 4 per + // block + assert_eq!(weight.ref_time(), 2 * 2 * WEIGHT_REF_TIME_PER_SECOND / 4); + assert_eq!(weight.proof_size(), (2 * MAX_POV_SIZE as u64) / 4); + }); +} + +#[test] +fn test_no_core_info() { + TestExtBuilder::new().build().execute_with(|| { + let weight = MaxParachainBlockWeight::>::get(); + + // Without core info, it takes the `PreviousCoreCount` into account. + assert_eq!(weight.ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND / 4); + assert_eq!(weight.proof_size(), MAX_POV_SIZE as u64 / 4); + }); +} + +#[test] +fn test_zero_cores() { + TestExtBuilder::new().number_of_cores(0).build().execute_with(|| { + let weight = MaxParachainBlockWeight::>::get(); + + // With 0 cores, should return conservative default + assert_eq!(weight.ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); + assert_eq!(weight.proof_size(), MAX_POV_SIZE as u64); + }); +} + +#[test] +fn test_zero_target_blocks() { + TestExtBuilder::new().number_of_cores(2).build().execute_with(|| { + let weight = MaxParachainBlockWeight::>::get(); + assert_eq!(weight.ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); + assert_eq!(weight.proof_size(), MAX_POV_SIZE as u64); + }); +} + +#[test] +fn test_target_block_weight_calculation() { + TestExtBuilder::new().number_of_cores(4).build().execute_with(|| { + // Test target_block_weight function directly + // Both calls return the same since ConstU32<4> is fixed at compile time + let weight = MaxParachainBlockWeight::>::target_block_weight(); + + assert_eq!(weight.ref_time(), 3 * 2 * WEIGHT_REF_TIME_PER_SECOND / 4); + assert_eq!(weight.proof_size(), MAX_POV_SIZE as u64); + }); +} + +#[test] +fn test_max_ref_time_per_core_cap() { + TestExtBuilder::new().number_of_cores(8).build().execute_with(|| { + // With 8 cores and 4 target blocks, ref time per block should be capped at 2s per core + let weight = MaxParachainBlockWeight::>::get(); + + // 8 cores * 2s = 16s total, divided by 4 blocks = 4s, but capped at 6s for all blocks in + // total + assert_eq!(weight.ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND * 3 / 4); + assert_eq!(weight.proof_size(), MAX_POV_SIZE as u64); + }); +} + +#[test] +fn test_target_block_weight_with_digest_edge_cases() { + TestExtBuilder::new().build().execute_with(|| { + // Test with empty digest + let empty_digest = Digest::default(); + let weight = + MaxParachainBlockWeight::>::target_block_weight_with_digest( + &empty_digest, + ); + assert_eq!(weight, MaxParachainBlockWeight::>::FULL_CORE_WEIGHT / 4); + + // Test with digest containing core info + let core_info = CoreInfo { + selector: CoreSelector(0), + claim_queue_offset: ClaimQueueOffset(0), + number_of_cores: Compact(2u16), + }; + + let digest = Digest { logs: vec![CumulusDigestItem::CoreInfo(core_info).to_digest_item()] }; + + // With 2 cores and 4 target blocks: (2 cores * 2s) / 4 blocks = 1s + let weight = + MaxParachainBlockWeight::>::target_block_weight_with_digest( + &digest, + ); + assert_eq!(weight.ref_time(), 2 * 2 * WEIGHT_REF_TIME_PER_SECOND / 4); + assert_eq!(weight.proof_size(), (2 * MAX_POV_SIZE as u64) / 4); + }); +} + +#[test] +fn test_is_first_block_in_core_functions() { + TestExtBuilder::new().number_of_cores(1).build().execute_with(|| { + let empty_digest = Digest::default(); + assert!(super::is_first_block_in_core_with_digest(&empty_digest).is_none()); + + // Test with bundle info index = 0 - should return true + let bundle_info_first = BundleInfo { index: 0, maybe_last: false }; + let digest_item_first = CumulusDigestItem::BundleInfo(bundle_info_first).to_digest_item(); + let mut digest_first = Digest::default(); + digest_first.push(digest_item_first); + assert!(super::is_first_block_in_core_with_digest(&digest_first).unwrap()); + + // Test with bundle info index > 0 - should return false + let bundle_info_not_first = BundleInfo { index: 5, maybe_last: true }; + let digest_item_not_first = + CumulusDigestItem::BundleInfo(bundle_info_not_first).to_digest_item(); + let mut digest_not_first = Digest::default(); + digest_not_first.push(digest_item_not_first); + assert!(!super::is_first_block_in_core_with_digest(&digest_not_first).unwrap()); + }); +} + +#[test] +fn tx_extension_sets_fraction_of_core_mode() { + use frame_support::dispatch::{DispatchClass, DispatchInfo}; + + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + initialize_block_finished(); + + // Create a small transaction + let small_weight = Weight::from_parts(100_000, 1024); + let info = DispatchInfo { + call_weight: small_weight, + class: DispatchClass::Normal, + pays_fee: frame_support::dispatch::Pays::Yes, + ..Default::default() + }; + + assert_ok!(TxExtension::validate_and_prepare( + TxExtension::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + )); + + assert_eq!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FractionOfCore { first_transaction_index: Some(0) }) + ); + }); +} + +#[test] +fn tx_extension_large_tx_enables_full_core_usage() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + initialize_block_finished(); + + // Create a transaction larger than target weight + let target_weight = MaximumBlockWeight::target_block_weight(); + let large_weight = target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024)); + + let info = DispatchInfo { + call_weight: large_weight, + class: DispatchClass::Normal, + ..Default::default() + }; + + assert_ok!(TxExtension::validate_and_prepare( + TxExtension::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + )); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::PotentialFullCore { first_transaction_index: Some(0), .. }) + ); + + let mut post_info = + PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; + + assert_ok!(TxExtension::post_dispatch((), &info, &mut post_info, 0, &Ok(()))); + + assert_eq!(crate::BlockWeightMode::::get(), Some(BlockWeightMode::FullCore)); + assert!(has_use_full_core_digest()); + assert_eq!(MaximumBlockWeight::get().ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); + }); +} + +#[test] +fn tx_extension_only_allows_large_operational_tx_to_enable_full_core_usage() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + initialize_block_finished(); + + // Create a transaction larger than target weight + let target_weight = MaximumBlockWeight::target_block_weight(); + let large_weight = target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024)); + + let mut info = DispatchInfo { + call_weight: large_weight, + class: DispatchClass::Normal, + ..Default::default() + }; + + // As `Normal` transaction this should be rejected. + assert_eq!( + TxExtensionOnlyOperational::validate_and_prepare( + TxExtensionOnlyOperational::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + ) + .unwrap_err(), + InvalidTransaction::ExhaustsResources.into() + ); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FractionOfCore { first_transaction_index: None }) + ); + + info.class = DispatchClass::Operational; + + // As `Operational` transaction this is accepted. + assert_ok!(TxExtensionOnlyOperational::validate_and_prepare( + TxExtensionOnlyOperational::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + )); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::PotentialFullCore { first_transaction_index: Some(0), .. }) + ); + + let mut post_info = + PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; + + assert_ok!(TxExtension::post_dispatch((), &info, &mut post_info, 0, &Ok(()))); + + assert_eq!(crate::BlockWeightMode::::get(), Some(BlockWeightMode::FullCore)); + assert!(has_use_full_core_digest()); + assert_eq!(MaximumBlockWeight::get().ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); + }); +} + +#[test] +fn tx_extension_large_tx_with_refund_goes_back_to_fractional() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + initialize_block_finished(); + + // Create a transaction larger than target weight + let target_weight = MaximumBlockWeight::target_block_weight(); + let large_weight = target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024)); + + let info = DispatchInfo { + call_weight: large_weight, + class: DispatchClass::Normal, + ..Default::default() + }; + + assert_ok!(TxExtension::validate_and_prepare( + TxExtension::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + )); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::PotentialFullCore { first_transaction_index: Some(0), .. }) + ); + + let mut post_info = PostDispatchInfo { + actual_weight: Some(Weight::from_parts(5000, 5000)), + pays_fee: Default::default(), + }; + + assert_ok!(TxExtension::post_dispatch((), &info, &mut post_info, 0, &Ok(()))); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FractionOfCore { .. }) + ); + assert!(!has_use_full_core_digest()); + assert_eq!(MaximumBlockWeight::get(), target_weight); + }); +} + +#[test] +fn tx_extension_large_tx_is_rejected_on_non_first_block() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(false) + .build() + .execute_with(|| { + initialize_block_finished(); + + // Create a transaction larger than target weight + let target_weight = MaximumBlockWeight::target_block_weight(); + let large_weight = target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024)); + + let info = DispatchInfo { + call_weight: large_weight, + class: DispatchClass::Normal, + ..Default::default() + }; + + assert_eq!( + TxExtension::validate_and_prepare( + TxExtension::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + ) + .unwrap_err(), + InvalidTransaction::ExhaustsResources.into() + ); + + // Should stay in FractionOfCore mode (not PotentialFullCore) since not first block + assert_eq!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FractionOfCore { first_transaction_index: None }) + ); + assert!(!has_use_full_core_digest()); + assert_eq!(MaximumBlockWeight::get(), target_weight); + }); +} + +#[test] +fn tx_extension_post_dispatch_to_full_core_because_of_manual_weight() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(false) + .build() + .execute_with(|| { + initialize_block_finished(); + + let target_weight = + MaxParachainBlockWeight::>::target_block_weight(); + + // Transaction announces small weight + let small_weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND / 10, 1024); + let info = DispatchInfo { call_weight: small_weight, ..Default::default() }; + + assert_ok!(TxExtension::validate_and_prepare( + TxExtension::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + )); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FractionOfCore { first_transaction_index: Some(0) }) + ); + + // But actually uses much more weight (bug in weight annotation) + let large_weight = target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024)); + register_weight(large_weight, DispatchClass::Normal); + + let mut post_info = + PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; + assert_ok!(TxExtension::post_dispatch((), &info, &mut post_info, 0, &Ok(()))); + + // Should transition to FullCore due to exceeding limit + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FullCore) + ); + + assert!(has_use_full_core_digest()); + }); +} + +#[test] +fn tx_extension_large_tx_after_limit_is_rejected() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + initialize_block_finished(); + + // Set some index above the limit. + System::set_extrinsic_index(20); + + // Create a transaction larger than target weight + let target_weight = MaximumBlockWeight::target_block_weight(); + let large_weight = target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024)); + + let info = DispatchInfo { call_weight: large_weight, ..Default::default() }; + + assert_eq!( + TxExtension::validate_and_prepare( + TxExtension::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + ) + .unwrap_err(), + InvalidTransaction::ExhaustsResources.into() + ); + + assert_eq!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FractionOfCore { first_transaction_index: None }) + ); + assert!(!has_use_full_core_digest()); + assert_eq!(MaximumBlockWeight::get(), target_weight); + }); +} + +#[test] +fn tx_extension_large_weight_before_first_tx() { + for first_block_in_core in [true, false] { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(first_block_in_core) + .build() + .execute_with(|| { + initialize_block_finished(); + + let target_weight = MaximumBlockWeight::target_block_weight(); + let large_weight = target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024)); + + register_weight(large_weight, DispatchClass::Normal); + + let small_weight = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND / 10, 1024); + let info = DispatchInfo { call_weight: small_weight, ..Default::default() }; + + let res = TxExtension::validate_and_prepare( + TxExtension::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + ); + + if first_block_in_core { + assert!(res.is_ok()) + } else { + assert_eq!(res.unwrap_err(), InvalidTransaction::ExhaustsResources.into()); + } + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FullCore) + ); + + assert!(has_use_full_core_digest()); + assert_eq!(MaximumBlockWeight::get().ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); + + if !first_block_in_core { + // Should have registered FULL_CORE_WEIGHT to prevent more transactions + let final_remaining = frame_system::Pallet::::remaining_block_weight(); + assert!(final_remaining + .consumed() + .all_gte(MaximumBlockWeight::FULL_CORE_WEIGHT)); + } + }); + } +} + +#[test] +fn pre_inherents_hook_first_block_over_limit() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + // Simulate on_initialize consuming more than target weight + let target_weight = MaximumBlockWeight::target_block_weight(); + let excessive_weight = target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024)); + + register_weight(excessive_weight, DispatchClass::Mandatory); + + // Call pre_inherents hook + DynamicMaxBlockWeightHooks::>::pre_inherents(); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FullCore) + ); + + // Should have UseFullCore digest + assert!(has_use_full_core_digest()); + }); +} + +#[test] +fn pre_inherents_hook_non_first_block_over_limit() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(false) + .build() + .execute_with(|| { + // Simulate on_initialize consuming more than target weight + let target_weight = MaximumBlockWeight::target_block_weight(); + let excessive_weight = target_weight + .saturating_add(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, 1024 * 1024)); + + register_weight(excessive_weight, DispatchClass::Mandatory); + + // Call pre_inherents hook + DynamicMaxBlockWeightHooks::>::pre_inherents(); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FullCore) + ); + + assert!(has_use_full_core_digest()); + + // Should have registered FULL_CORE_WEIGHT to prevent more transactions + let final_remaining = frame_system::Pallet::::remaining_block_weight(); + assert!(final_remaining.consumed().all_gte(MaximumBlockWeight::FULL_CORE_WEIGHT)); + }); +} + +#[test] +fn pre_inherents_hook_under_limit_no_change() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + // Simulate on_initialize consuming less than target weight + let target_weight = MaximumBlockWeight::target_block_weight(); + let small_weight = + Weight::from_parts(target_weight.ref_time() / 2, target_weight.proof_size() / 2); + + register_weight(small_weight, DispatchClass::Mandatory); + + // Call pre_inherents hook + DynamicMaxBlockWeightHooks::>::pre_inherents(); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::FractionOfCore { first_transaction_index: None }) + ); + + // Should NOT have UseFullCore digest + assert!(!has_use_full_core_digest()); + }); +} + +#[test] +fn max_weight_without_bundle_info() { + TestExtBuilder::new().number_of_cores(2).build().execute_with(|| { + // Without bundle info, cannot determine if first block + // Should still work but max weight determination will be conservative + + frame_system::Pallet::::note_finished_initialize(); + + let max_weight = MaximumBlockWeight::get(); + + // With 2 cores and 12 target blocks + let expected_weight = Weight::from_parts( + 2 * 2 * WEIGHT_REF_TIME_PER_SECOND / TARGET_BLOCK_RATE as u64, + 2 * MAX_POV_SIZE as u64 / TARGET_BLOCK_RATE as u64, + ); + + assert_eq!(max_weight, expected_weight); + }); +} + +#[test] +fn ref_time_and_pov_size_cap() { + TestExtBuilder::new().number_of_cores(10).build().execute_with(|| { + frame_system::Pallet::::note_finished_initialize(); + + let max_weight = MaxParachainBlockWeight::>::get(); + + // At most one core will always only be able to use the resources of one core. + assert_eq!(max_weight.ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); + assert_eq!(max_weight.proof_size(), MAX_POV_SIZE as u64); + + let max_weight = MaxParachainBlockWeight::>::get(); + + // Each blocks get its own core (can use the max pov size), but ref time of all blocks + // together is in max `6s` + assert_eq!(max_weight.ref_time(), 6 * WEIGHT_REF_TIME_PER_SECOND / 4); + assert_eq!(max_weight.proof_size(), MAX_POV_SIZE as u64); + }); +} + +#[test] +fn executive_validate_block_handles_normal_transactions() { + TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { + let call = RuntimeCall::TestPallet(test_pallet::Call::heavy_call_normal {}); + + let xt = Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), ().into()); + + assert!(Executive::validate_transaction( + TransactionSource::External, + xt.clone(), + Default::default() + ) + .is_ok()); + }); + + TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { + let call = RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_normal {}); + + let xt = ExtrinsicOnlyOperational::new_signed(call, 1u64.into(), 1u64.into(), ().into()); + + assert_eq!( + ExecutiveOnlyOperational::validate_transaction( + TransactionSource::External, + xt, + Default::default() + ) + .unwrap_err(), + InvalidTransaction::ExhaustsResources.into() + ); + }); +} + +#[test] +fn executive_validate_block_handles_operational_transactions() { + TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { + let call = RuntimeCall::TestPallet(test_pallet::Call::heavy_call_operational {}); + + let xt = Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), ().into()); + + assert!(Executive::validate_transaction( + TransactionSource::External, + xt.clone(), + Default::default() + ) + .is_ok()); + }); + + TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { + let call = + RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_operational {}); + + let xt = ExtrinsicOnlyOperational::new_signed(call, 1u64.into(), 1u64.into(), ().into()); + + assert!(ExecutiveOnlyOperational::validate_transaction( + TransactionSource::External, + xt, + Default::default() + ) + .is_ok()); + }); +} + +#[test] +fn executive_with_operational_only_applies_big_inherent() { + TestExtBuilder::new() + .number_of_cores(1) + .first_block_in_core(true) + .build() + .execute_with(|| { + Executive::initialize_block(&Header::new( + 1, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + )); + + let call = + RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_mandatory {}); + + let xt = ExtrinsicOnlyOperational::new_bare(call); + + ExecutiveOnlyOperational::apply_extrinsic(xt).unwrap().unwrap(); + }); +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs new file mode 100644 index 0000000000000..e54fc2985b78c --- /dev/null +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -0,0 +1,489 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// This file is part of Cumulus. +// 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. + +use super::{ + block_weight_over_target_block_weight, is_first_block_in_core_with_digest, BlockWeightMode, + MaxParachainBlockWeight, LOG_TARGET, +}; +use crate::WeightInfo; +use alloc::vec::Vec; +use codec::{Decode, DecodeWithMemTracking, Encode}; +use cumulus_primitives_core::CumulusDigestItem; +use frame_support::{ + dispatch::{DispatchClass, DispatchInfo, PostDispatchInfo}, + pallet_prelude::{ + InvalidTransaction, TransactionSource, TransactionValidityError, ValidTransaction, + }, + weights::Weight, +}; +use scale_info::TypeInfo; +use sp_core::Get; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, Implication, PostDispatchInfoOf, TransactionExtension}, + DispatchResult, +}; + +/// Transaction extension that dynamically changes the max block weight. +/// +/// With block bundling, parachains are running with block weights that may not allow certain +/// transactions to be applied, e.g. a runtime upgrade. To ensure that these transactions can still +/// be applied, this transaction extension can change the max block weight as required. There are +/// multiple requirements for it to change the block weight: +/// +/// 1. Only the first block of a core is allowed to change its block weight. +/// +/// 2. Any `inherent` or any transaction up to `MAX_TRANSACTION_TO_CONSIDER` requires more block +/// weight than the target block weight. Target block weight is the max weight for the respective +/// extrinsic class. +/// +/// Because the node is tracking the wall clock time while building a block to abort block +/// production if it takes too long, we do not allow any block to change the block weight. The node +/// knows that the first block of a core may runs longer. So, the node allows this block to take up +/// to `2s` of wall clock time. `2s` is the time each `PoV` gets on the relay chain for its +/// validation or in other words the maximum core execution time. The extension sets the +/// [`CumulusDigestItem::UseFullCore`] digest when the block should occupy the entire core. +/// +/// Before dispatching an extrinsic the extension will check the requirements and set the +/// appropriate [`BlockWeightMode`]. After the extrinsic has finished, the checks from before +/// dispatching the extrinsic are repeated with the post dispatch weights. The [`BlockWeightMode`] +/// may is changed properly. +/// +/// # Note +/// +/// The extension requires that any of the inner extensions sets the +/// [`BlockWeight`](frame_system::BlockWeight). Otherwise the weight tracking is not working +/// properly. Normally this is done by [`CheckWeight`](frame_system::CheckWeight). +/// +/// # Generic parameters +/// +/// - `Config`: The [`Config`](crate::Config) trait of this pallet. +/// +/// - `Inner`: The inner transaction extensions aka the other transaction extensions to be used by +/// the runtime. +/// +/// - `TargetBlockRate`: The target block rate the parachain should be running with. Or in other +/// words, the number of blocks the parachain should produce in `6s`(relay chain slot duration). +/// +/// - `MAX_TRANSACTION`: The maximum number of transactions to consider before giving up to change +/// the max block weight. +/// +/// - `ALLOW_NORMAL`: Should transactions with a dispatch class `Normal` be allowed to change the +/// max block weight? +#[derive(Encode, Decode, DecodeWithMemTracking, TypeInfo)] +#[derive_where::derive_where(Clone, Eq, PartialEq, Default; Inner)] +#[scale_info(skip_type_params(Config, TargetBlockRate))] +pub struct DynamicMaxBlockWeight< + Config, + Inner, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32 = 10, + const ALLOW_NORMAL: bool = true, +>(pub Inner, core::marker::PhantomData<(Config, TargetBlockRate)>); + +impl + DynamicMaxBlockWeight +{ + /// Create a new [`DynamicMaxBlockWeight`] instance. + pub fn new(s: S) -> Self { + Self(s, Default::default()) + } +} + +impl< + Config, + Inner, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, + > DynamicMaxBlockWeight +where + Config: crate::Config, + TargetBlockRate: Get, +{ + /// Should be executed before `validate` is called for any inner extension. + fn pre_validate_extrinsic( + info: &DispatchInfo, + len: usize, + ) -> Result<(), TransactionValidityError> { + let is_not_inherent = frame_system::Pallet::::inherents_applied(); + let extrinsic_index = frame_system::Pallet::::extrinsic_index().unwrap_or_default(); + let transaction_index = is_not_inherent.then(|| extrinsic_index); + + crate::BlockWeightMode::::mutate(|mode| { + let current_mode = *mode.get_or_insert_with(|| BlockWeightMode::FractionOfCore { + first_transaction_index: transaction_index, + }); + + log::trace!( + target: LOG_TARGET, + "About to pre-validate an extrinsic. current_mode={current_mode:?}, transaction_index={transaction_index:?}" + ); + + match current_mode { + // We are already allowing the full core, not that much more to do here. + BlockWeightMode::FullCore => {}, + BlockWeightMode::PotentialFullCore { first_transaction_index, .. } | + BlockWeightMode::FractionOfCore { first_transaction_index } => { + let is_potential = + matches!(current_mode, BlockWeightMode::PotentialFullCore { .. }); + debug_assert!( + !is_potential, + "`PotentialFullCore` should resolve to `FullCore` or `FractionOfCore` after applying a transaction.", + ); + + let digest = frame_system::Pallet::::digest(); + let block_weight_over_limit = extrinsic_index == 0 + && block_weight_over_target_block_weight::(); + + let block_weights = Config::BlockWeights::get(); + let target_weight = block_weights.get(info.class).max_total.unwrap_or_else( + || MaxParachainBlockWeight::::target_block_weight_with_digest(&digest).saturating_sub(block_weights.base_block) + ); + + // Protection against a misconfiguration as this should be detected by the pre-inherent hook. + if block_weight_over_limit { + *mode = Some(BlockWeightMode::FullCore); + + // Inform the node that this block uses the full core. + frame_system::Pallet::::deposit_log( + CumulusDigestItem::UseFullCore.to_digest_item(), + ); + + if !is_first_block_in_core_with_digest(&digest).unwrap_or(false) { + // We are already above the allowed maximum and do not want to accept any more + // extrinsics. + frame_system::Pallet::::register_extra_weight_unchecked( + MaxParachainBlockWeight::::FULL_CORE_WEIGHT, + DispatchClass::Mandatory, + ); + } + + log::error!( + target: LOG_TARGET, + "Inherent block logic took longer than the target block weight, \ + `DynamicMaxBlockWeightHooks` not registered as `PreInherents` hook!", + ); + } else if info + .total_weight() + // The extrinsic lengths counts towards the POV size + .saturating_add(Weight::from_parts(0, len as u64)) + .any_gt(target_weight) + { + // When `ALLOW_NORMAL` is `true`, we want to allow all classes of transactions. Inherents are always allowed. + let class_allowed = if ALLOW_NORMAL { true } else { info.class == DispatchClass::Operational } + || info.class == DispatchClass::Mandatory; + + // If the `BundleInfo` digest is not set (function returns `None`), it means we are in some offchain + // call like `validate_block`. In this case we assume this is the first block, otherwise these big + // transactions will never be able to enter the tx pool. + let is_first_block = is_first_block_in_core_with_digest(&digest).unwrap_or(true); + + if transaction_index.unwrap_or_default().saturating_sub(first_transaction_index.unwrap_or_default()) < MAX_TRANSACTION_TO_CONSIDER + && is_first_block && class_allowed { + log::trace!( + target: LOG_TARGET, + "Enabling `PotentialFullCore` mode for extrinsic", + ); + + *mode = Some(BlockWeightMode::PotentialFullCore { + target_weight, + // While applying inherents `extrinsic_index` and `first_transaction_index` will be `None`. + // When the first transaction is applied, we want to store the index. + first_transaction_index: first_transaction_index.or(transaction_index), + }); + } else { + log::trace!( + target: LOG_TARGET, + "Transaction is over the block limit, but is either outside of the allowed window or the dispatch class is not allowed.", + ); + + return Err(InvalidTransaction::ExhaustsResources) + } + } else if is_potential { + log::trace!( + target: LOG_TARGET, + "Resetting back to `FractionOfCore`" + ); + *mode = + Some(BlockWeightMode::FractionOfCore { first_transaction_index: first_transaction_index.or(transaction_index) }); + } else { + log::trace!( + target: LOG_TARGET, + "Not changing block weight mode" + ); + + *mode = + Some(BlockWeightMode::FractionOfCore { first_transaction_index: first_transaction_index.or(transaction_index) }); + } + }, + }; + + Ok(()) + }).map_err(Into::into) + } + + /// Should be called after all inner extensions have finished executing their post dispatch + /// handling. + /// + /// Returns the weight to refund. Aka the weight that wasn't used by this extension. + fn post_dispatch_extrinsic(info: &DispatchInfo) -> Weight { + crate::BlockWeightMode::::mutate(|weight_mode| { + let Some(mode) = *weight_mode else { return Weight::zero() }; + + match mode { + // If the previous mode was already `FullCore`, we are fine. + BlockWeightMode::FullCore => + Config::WeightInfo::block_weight_tx_extension_max_weight() + .saturating_sub(Config::WeightInfo::block_weight_tx_extension_full_core()), + BlockWeightMode::FractionOfCore { .. } => { + let digest = frame_system::Pallet::::digest(); + let target_block_weight = + MaxParachainBlockWeight::::target_block_weight_with_digest(&digest); + + let is_above_limit = frame_system::Pallet::::remaining_block_weight() + .consumed() + .any_gt(target_block_weight); + + // If we are above the limit, it means the transaction used more weight than + // what it had announced, which should not happen. + if is_above_limit { + log::error!( + target: LOG_TARGET, + "Extrinsic ({}) used more weight than what it had announced and pushed the \ + block above the allowed weight limit!", + frame_system::Pallet::::extrinsic_index().unwrap_or_default() + ); + + // If this isn't the first block in a core, we register the full core weight + // to ensure that we don't include any other transactions. Because we don't + // know how many weight of the core was already used by the blocks before. + if !is_first_block_in_core_with_digest(&digest).unwrap_or(false) { + log::error!( + target: LOG_TARGET, + "Registering `FULL_CORE_WEIGHT` to ensure no other transaction is included \ + in this block, because this isn't the first block in the core!", + ); + + frame_system::Pallet::::register_extra_weight_unchecked( + MaxParachainBlockWeight::::FULL_CORE_WEIGHT, + DispatchClass::Mandatory, + ); + } + + *weight_mode = Some(BlockWeightMode::FullCore); + + // Inform the node that this block uses the full core. + frame_system::Pallet::::deposit_log( + CumulusDigestItem::UseFullCore.to_digest_item(), + ); + } + + Config::WeightInfo::block_weight_tx_extension_max_weight().saturating_sub( + Config::WeightInfo::block_weight_tx_extension_stays_fraction_of_core(), + ) + }, + // Now we need to check if the transaction required more weight than a fraction of a + // core block. + BlockWeightMode::PotentialFullCore { first_transaction_index, target_weight } => { + let block_weight = frame_system::BlockWeight::::get(); + let extrinsic_class_weight = block_weight.get(info.class); + + if extrinsic_class_weight.any_gt(target_weight) { + log::trace!( + target: LOG_TARGET, + "Extrinsic class weight {extrinsic_class_weight:?} above target weight {target_weight:?}, enabling `FullCore` mode." + ); + + *weight_mode = Some(BlockWeightMode::FullCore); + + // Inform the node that this block uses the full core. + frame_system::Pallet::::deposit_log( + CumulusDigestItem::UseFullCore.to_digest_item(), + ); + } else { + log::trace!( + target: LOG_TARGET, + "Extrinsic class weight {extrinsic_class_weight:?} not above target \ + weight {target_weight:?}, going back to `FractionOfCore` mode." + ); + + *weight_mode = + Some(BlockWeightMode::FractionOfCore { first_transaction_index }); + } + + // We run into the worst case, so no refund :) + Weight::zero() + }, + } + }) + } +} + +impl< + Config, + Inner, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, + > From + for DynamicMaxBlockWeight< + Config, + Inner, + TargetBlockRate, + MAX_TRANSACTION_TO_CONSIDER, + ALLOW_NORMAL, + > +{ + fn from(s: Inner) -> Self { + Self::new(s) + } +} + +impl< + Config, + Inner: core::fmt::Debug, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, + > core::fmt::Debug + for DynamicMaxBlockWeight< + Config, + Inner, + TargetBlockRate, + MAX_TRANSACTION_TO_CONSIDER, + ALLOW_NORMAL, + > +{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> { + write!(f, "DynamicMaxBlockWeight<{:?}>", self.0) + } +} + +impl< + Config: crate::Config + Send + Sync, + Inner: TransactionExtension, + TargetBlockRate: Get + Send + Sync + 'static, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, + > TransactionExtension + for DynamicMaxBlockWeight< + Config, + Inner, + TargetBlockRate, + MAX_TRANSACTION_TO_CONSIDER, + ALLOW_NORMAL, + > +where + Config::RuntimeCall: Dispatchable, +{ + const IDENTIFIER: &'static str = "DynamicMaxBlockWeight"; + + type Implicit = Inner::Implicit; + + type Val = Inner::Val; + + type Pre = Inner::Pre; + + fn implicit(&self) -> Result { + self.0.implicit() + } + + fn metadata() -> Vec { + let mut inner = Inner::metadata(); + inner.push(sp_runtime::traits::TransactionExtensionMetadata { + identifier: "DynamicMaxBlockWeight", + ty: scale_info::meta_type::<()>(), + implicit: scale_info::meta_type::<()>(), + }); + inner + } + + fn weight(&self, _: &Config::RuntimeCall) -> Weight { + Config::WeightInfo::block_weight_tx_extension_max_weight() + } + + fn validate( + &self, + origin: Config::RuntimeOrigin, + call: &Config::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + self_implicit: Self::Implicit, + inherited_implication: &impl Implication, + source: TransactionSource, + ) -> Result<(ValidTransaction, Self::Val, Config::RuntimeOrigin), TransactionValidityError> { + Self::pre_validate_extrinsic(info, len)?; + + self.0 + .validate(origin, call, info, len, self_implicit, inherited_implication, source) + } + + fn prepare( + self, + val: Self::Val, + origin: &Config::RuntimeOrigin, + call: &Config::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.0.prepare(val, origin, call, info, len) + } + + fn post_dispatch_details( + pre: Self::Pre, + info: &DispatchInfoOf, + post_info: &PostDispatchInfo, + len: usize, + result: &DispatchResult, + ) -> Result { + let weight_refund = Inner::post_dispatch_details(pre, info, post_info, len, result)?; + + let extra_refund = Self::post_dispatch_extrinsic(info); + + Ok(weight_refund.saturating_add(extra_refund)) + } + + fn bare_validate( + call: &Config::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> frame_support::pallet_prelude::TransactionValidity { + Inner::bare_validate(call, info, len) + } + + fn bare_validate_and_prepare( + call: &Config::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> Result<(), TransactionValidityError> { + Self::pre_validate_extrinsic(info, len)?; + + Inner::bare_validate_and_prepare(call, info, len) + } + + fn bare_post_dispatch( + info: &DispatchInfoOf, + post_info: &mut PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + Inner::bare_post_dispatch(info, post_info, len, result)?; + + Self::post_dispatch_extrinsic(info); + + Ok(()) + } +} diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 489a7452480c0..f86f59df59e74 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -33,9 +33,10 @@ use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec}; use codec::{Decode, DecodeLimit, Encode}; use core::cmp; use cumulus_primitives_core::{ - relay_chain, AbridgedHostConfiguration, ChannelInfo, ChannelStatus, CollationInfo, - CumulusDigestItem, GetChannelInfo, ListChannelInfos, MessageSendError, OutboundHrmpMessage, - ParaId, PersistedValidationData, UpwardMessage, UpwardMessageSender, XcmpMessageHandler, + relay_chain::{self, UMPSignal, UMP_SEPARATOR}, + AbridgedHostConfiguration, ChannelInfo, ChannelStatus, CollationInfo, CumulusDigestItem, + GetChannelInfo, ListChannelInfos, MessageSendError, OutboundHrmpMessage, ParaId, + PersistedValidationData, UpwardMessage, UpwardMessageSender, XcmpMessageHandler, XcmpMessageSource, }; use cumulus_primitives_parachain_inherent::{v0, MessageQueueChain, ParachainInherentData}; @@ -62,18 +63,15 @@ use xcm::{latest::XcmHash, VersionedLocation, VersionedXcm, MAX_XCM_DECODE_DEPTH use xcm_builder::InspectMessageQueues; mod benchmarking; +pub mod block_weight; +pub mod consensus_hook; pub mod migration; mod mock; +pub mod relay_state_snapshot; #[cfg(test)] mod tests; -pub mod weights; - -pub use weights::WeightInfo; - mod unincluded_segment; - -pub mod consensus_hook; -pub mod relay_state_snapshot; +pub mod weights; #[macro_use] pub mod validate_block; mod descendant_validation; @@ -107,10 +105,11 @@ pub use consensus_hook::{ConsensusHook, ExpectParentIncluded}; pub use cumulus_pallet_parachain_system_proc_macro::register_validate_block; pub use relay_state_snapshot::{MessagingStateSnapshot, RelayChainStateProof}; pub use unincluded_segment::{Ancestor, UsedBandwidth}; +pub use weights::WeightInfo; pub use pallet::*; -const LOG_TARGET: &str = "parachain-system"; +const LOG_TARGET: &str = "runtime::parachain-system"; /// Something that can check the associated relay block number. /// @@ -187,8 +186,9 @@ pub mod ump_constants { #[frame_support::pallet] pub mod pallet { use super::*; + use codec::Compact; use cumulus_primitives_core::CoreInfoExistsAtMaxOnce; - use frame_support::pallet_prelude::*; + use frame_support::pallet_prelude::{ValueQuery, *}; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -361,8 +361,24 @@ pub mod pallet { UpwardMessages::::put(&up[..num as usize]); *up = up.split_off(num as usize); - // Send the core selector UMP signal. - Self::send_ump_signal(); + if let Some(core_info) = + CumulusDigestItem::find_core_info(&frame_system::Pallet::::digest()) + { + PendingUpwardSignals::::mutate(|signals| { + signals.push( + UMPSignal::SelectCore(core_info.selector, core_info.claim_queue_offset) + .encode(), + ); + }); + + PreviousCoreCount::::put(core_info.number_of_cores); + } else { + // Without the digest, we assume that it is `1`. + PreviousCoreCount::::put(Compact(1u16)); + } + + // Send the pending UMP signals. + Self::send_ump_signals(); // If the total size of the pending messages is less than the threshold, // we decrease the fee factor, since the queue is less congested. @@ -472,6 +488,8 @@ pub mod pallet { weight += T::DbWeight::get().reads_writes(3, 2); } + BlockWeightMode::::kill(); + // Remove the validation from the old block. ValidationData::::kill(); // NOTE: Killing here is required to at least include the trie nodes down to the keys @@ -585,7 +603,7 @@ pub mod pallet { validation_data: vfp, relay_chain_state, relay_parent_descendants, - collator_peer_id: _, + collator_peer_id, } = data; // Check that the associated relay chain block number is as expected. @@ -693,6 +711,12 @@ pub mod pallet { ::on_validation_data(&vfp); + if let Some(collator_peer_id) = collator_peer_id { + PendingUpwardSignals::::mutate(|signals| { + signals.push(UMPSignal::ApprovedPeer(collator_peer_id).encode()); + }); + } + total_weight.saturating_accrue(Self::enqueue_inbound_downward_messages( relevant_messaging_state.dmq_mqc_head, inbound_messages_data.downward_messages, @@ -760,6 +784,22 @@ pub mod pallet { NotScheduled, } + /// The current block weight mode. + /// + /// This is used to determine what is the maximum allowed block weight, for more information see + /// [`block_weight`]. + #[pallet::storage] + #[pallet::whitelist_storage] + pub type BlockWeightMode = + StorageValue<_, block_weight::BlockWeightMode, OptionQuery>; + + /// The core count available to the parachain in the previous block. + /// + /// This is mainly used for offchain functionality to calculate the correct target block weight. + #[pallet::storage] + #[pallet::whitelist_storage] + pub type PreviousCoreCount = StorageValue<_, Compact, OptionQuery>; + /// Latest included block descendants the runtime accepted. In other words, these are /// ancestors of the currently executing block which have not been included in the observed /// relay-chain state. @@ -905,14 +945,20 @@ pub mod pallet { /// Upward messages that were sent in a block. /// - /// This will be cleared in `on_initialize` of each new block. + /// This will be cleared in `on_initialize` for each new block. #[pallet::storage] pub type UpwardMessages = StorageValue<_, Vec, ValueQuery>; - /// Upward messages that are still pending and not yet send to the relay chain. + /// Upward messages that are still pending and not yet sent to the relay chain. #[pallet::storage] pub type PendingUpwardMessages = StorageValue<_, Vec, ValueQuery>; + /// Upward signals that are still pending and not yet sent to the relay chain. + /// + /// This will be cleared in `on_finalize` for each block. + #[pallet::storage] + pub type PendingUpwardSignals = StorageValue<_, Vec, ValueQuery>; + /// The factor to multiply the base delivery fee by for UMP. #[pallet::storage] pub type UpwardDeliveryFeeFactor = @@ -1384,7 +1430,11 @@ impl Pallet { // // If this fails, the parachain needs to wait for ancestors to be included before // a new block is allowed. - assert!(new_len < capacity.get(), "no space left for the block in the unincluded segment"); + assert!( + new_len < capacity.get(), + "No space left for the block in the unincluded segment: new_len({new_len}) < capacity({})", + capacity.get() + ); weight_used } @@ -1448,7 +1498,7 @@ impl Pallet { // Ensure that `ValidationData` exists. We do not care about the validation data per se, // but we do care about the [`UpgradeRestrictionSignal`] which arrives with the same // inherent. - ensure!(>::exists(), Error::::ValidationDataNotAvailable,); + ensure!(>::exists(), Error::::ValidationDataNotAvailable); ensure!(>::get().is_none(), Error::::ProhibitedByPolkadot); ensure!(!>::exists(), Error::::OverlappingUpgrades); @@ -1507,23 +1557,15 @@ impl Pallet { CustomValidationHeadData::::put(head_data); } - /// Send the ump signals - fn send_ump_signal() { - use cumulus_primitives_core::relay_chain::{UMPSignal, UMP_SEPARATOR}; - - UpwardMessages::::mutate(|up| { - if let Some(core_info) = - CumulusDigestItem::find_core_info(&frame_system::Pallet::::digest()) - { - up.push(UMP_SEPARATOR); - - // Send the core selector signal. - up.push( - UMPSignal::SelectCore(core_info.selector, core_info.claim_queue_offset) - .encode(), - ); - } - }); + /// Send the pending ump signals + fn send_ump_signals() { + let mut ump_signals = PendingUpwardSignals::::take(); + if !ump_signals.is_empty() { + UpwardMessages::::append(UMP_SEPARATOR); + UpwardMessages::::mutate(|up| { + up.append(&mut ump_signals); + }); + } } /// Open HRMP channel for using it in benchmarks or tests. diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs index ca2767e1c87af..dc9fab04f706f 100755 --- a/cumulus/pallets/parachain-system/src/tests.rs +++ b/cumulus/pallets/parachain-system/src/tests.rs @@ -19,8 +19,12 @@ use super::*; use crate::mock::*; +use alloc::collections::BTreeMap; use core::num::NonZeroU32; -use cumulus_primitives_core::{AbridgedHrmpChannel, InboundDownwardMessage, InboundHrmpMessage}; +use cumulus_primitives_core::{ + relay_chain::ApprovedPeerId, AbridgedHrmpChannel, ClaimQueueOffset, CoreInfo, CoreSelector, + InboundDownwardMessage, InboundHrmpMessage, CUMULUS_CONSENSUS_ID, +}; use cumulus_primitives_parachain_inherent::{ v0, INHERENT_IDENTIFIER, PARACHAIN_INHERENT_IDENTIFIER_V0, }; @@ -31,6 +35,7 @@ use rand::Rng; use relay_chain::HrmpChannelId; use sp_core::H256; use sp_inherents::InherentDataProvider; +use sp_runtime::DigestItem; use sp_trie::StorageProof; #[test] @@ -180,7 +185,7 @@ fn unincluded_segment_works() { } #[test] -#[should_panic = "no space left for the block in the unincluded segment"] +#[should_panic = "No space left for the block in the unincluded segment: new_len(1) < capacity(1)"] fn unincluded_segment_is_limited() { CONSENSUS_HOOK.with(|c| { *c.borrow_mut() = Box::new(|_| (Weight::zero(), NonZeroU32::new(1).unwrap().into())) @@ -1655,3 +1660,74 @@ fn ump_fee_factor_increases_and_decreases() { }, ); } + +#[test] +fn ump_signals_are_sent_correctly() { + let core_info = CoreInfo { + selector: CoreSelector(1), + claim_queue_offset: ClaimQueueOffset(1), + number_of_cores: codec::Compact(1), + }; + + // Test cases list with the following format: + // `((expect_approved_peer, expect_select_core), expected_upward_messages)` + let test_cases = BTreeMap::from([ + ((false, false), vec![b"Test".to_vec()]), + ( + (true, false), + vec![ + b"Test".to_vec(), + UMP_SEPARATOR, + UMPSignal::ApprovedPeer(ApprovedPeerId::try_from(b"12345".to_vec()).unwrap()) + .encode(), + ], + ), + ( + (false, true), + vec![ + b"Test".to_vec(), + UMP_SEPARATOR, + UMPSignal::SelectCore(core_info.selector, core_info.claim_queue_offset).encode(), + ], + ), + ( + (true, true), + vec![ + b"Test".to_vec(), + UMP_SEPARATOR, + UMPSignal::ApprovedPeer(ApprovedPeerId::try_from(b"12345".to_vec()).unwrap()) + .encode(), + UMPSignal::SelectCore(core_info.selector, core_info.claim_queue_offset).encode(), + ], + ), + ]); + + for ((expect_approved_peer, expect_select_core), expected_upward_messages) in test_cases { + let core_info_digest = CumulusDigestItem::CoreInfo(core_info.clone()).encode(); + + BlockTests::new() + .with_inherent_data(move |_, _, data| { + if expect_approved_peer { + data.collator_peer_id = + Some(ApprovedPeerId::try_from(b"12345".to_vec()).unwrap()); + } + }) + .add_with_post_test( + 1, + move || { + ParachainSystem::send_upward_message(b"Test".to_vec()).unwrap(); + + if expect_select_core { + System::deposit_log(DigestItem::PreRuntime( + CUMULUS_CONSENSUS_ID, + core_info_digest.clone(), + )); + } + }, + move || { + assert_eq!(PendingUpwardSignals::::get(), Vec::>::new()); + assert_eq!(UpwardMessages::::get(), expected_upward_messages); + }, + ); + } +} diff --git a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs index f77827ff68078..8b9d05e69fd62 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs @@ -83,6 +83,8 @@ where B::Extrinsic: ExtrinsicCall, ::Call: IsSubType>, { + // sp_runtime::runtime_logger::RuntimeLogger::init(); + let _guard = ( // Replace storage calls with our own implementations sp_io::storage::host_read.replace_implementation(host_storage_read), @@ -335,6 +337,7 @@ where upward_messages .try_push(UMP_SEPARATOR) .expect("UMPSignals does not fit in UMPMessages"); + upward_messages .try_extend(upward_message_signals.into_iter()) .expect("UMPSignals does not fit in UMPMessages"); diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index ad0a8cd63e859..38535eb6c2c70 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -30,14 +30,14 @@ use cumulus_test_client::{ use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use polkadot_parachain_primitives::primitives::ValidationResult; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; -use sp_api::{ApiExt, Core, ProofRecorder, ProvideRuntimeApi}; -use sp_consensus_slots::SlotDuration; +use sp_api::{ApiExt, Core, ProofRecorder, ProvideRuntimeApi, StorageProof}; +use sp_consensus_babe::SlotDuration; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, Block as BlockT, Header as HeaderT}, DigestItem, }; -use sp_trie::{proof_size_extension::ProofSizeExt, recorder::IgnoredNodes, StorageProof}; +use sp_trie::{proof_size_extension::ProofSizeExt, recorder::IgnoredNodes}; use std::{env, process::Command}; fn call_validate_block_validation_result( @@ -147,6 +147,7 @@ fn build_block_with_witness( let cumulus_test_client::BlockBuilderAndSupportData { mut block_builder, persisted_validation_data, + .. } = client.init_block_builder_with_pre_digests(Some(validation_data), sproof_builder, pre_digests); extra_extrinsics.into_iter().for_each(|e| block_builder.push(e).unwrap()); @@ -241,11 +242,10 @@ fn build_multiple_blocks_with_witness( }) .unwrap(); - ignored_nodes.extend(IgnoredNodes::from_storage_proof::( - &built_block.proof.clone().unwrap(), - )); + let proof_new = built_block.proof.unwrap(); + ignored_nodes.extend(IgnoredNodes::from_storage_proof::(&proof_new)); ignored_nodes.extend(IgnoredNodes::from_memory_db(built_block.storage_changes.transaction)); - proof = StorageProof::merge([proof, built_block.proof.unwrap()]); + proof = StorageProof::merge([proof, proof_new]); parent_head = built_block.block.header.clone(); @@ -518,7 +518,6 @@ fn state_changes_in_multiple_blocks_are_applied_in_exact_order() { sp_tracing::try_init_simple(); let blocks_per_pov = 12; - // disable the core selection logic let (client, genesis_head) = create_elastic_scaling_test_client(); // 1. Build the initial block that stores values in the map. @@ -583,7 +582,6 @@ fn validate_block_handles_ump_signal() { relay_chain::{UMPSignal, UMP_SEPARATOR}, ClaimQueueOffset, CoreInfo, CoreSelector, }; - sp_tracing::try_init_simple(); let (client, parent_head) = create_elastic_scaling_test_client(); diff --git a/cumulus/pallets/parachain-system/src/weights.rs b/cumulus/pallets/parachain-system/src/weights.rs index ba7d8b1e87f6b..086a6b993b695 100644 --- a/cumulus/pallets/parachain-system/src/weights.rs +++ b/cumulus/pallets/parachain-system/src/weights.rs @@ -55,6 +55,9 @@ use core::marker::PhantomData; /// Weight functions needed for cumulus_pallet_parachain_system. pub trait WeightInfo { fn enqueue_inbound_downward_messages(n: u32, ) -> Weight; + fn block_weight_tx_extension_max_weight() -> Weight; + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight; + fn block_weight_tx_extension_full_core() -> Weight; } /// Weights for cumulus_pallet_parachain_system using the Substrate node and recommended hardware. @@ -84,6 +87,18 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } + + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } // For backwards compatibility and tests @@ -112,4 +127,17 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } + + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } + } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs index 23dd800922aea..b7ba28ac2fbcc 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs @@ -74,4 +74,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs index 28f8aca5f5e7e..2573210408d95 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -74,4 +74,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs index 145a6e3e3cf1b..ebb497e21decd 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/cumulus_pallet_parachain_system.rs @@ -74,4 +74,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_parachain_system.rs index e60c9cfde30e5..5507c5ffd7f2a 100644 --- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -74,4 +74,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_parachain_system.rs index 9ebfbd2fbd0a3..671e4715b8219 100644 --- a/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/collectives/collectives-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -74,4 +74,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_parachain_system.rs index 73c4b2ba241d2..bc702a215a66a 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/weights/cumulus_pallet_parachain_system.rs @@ -74,4 +74,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs index 8f5714bbe0cd7..ece41c94ab847 100644 --- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -74,4 +74,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/cumulus_pallet_parachain_system.rs index a753f6fc78f87..4c8cc2b5fc6f8 100644 --- a/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/glutton/glutton-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -75,4 +75,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_parachain_system.rs index 58aef8cd5ab87..6e7e6acc40e3b 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/cumulus_pallet_parachain_system.rs @@ -74,4 +74,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_parachain_system.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_parachain_system.rs index 05c07f998e8e2..085b4b0fa85e7 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_parachain_system.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/cumulus_pallet_parachain_system.rs @@ -74,4 +74,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/cumulus/primitives/core/src/lib.rs b/cumulus/primitives/core/src/lib.rs index e06a92dcef8bc..03f337b279671 100644 --- a/cumulus/primitives/core/src/lib.rs +++ b/cumulus/primitives/core/src/lib.rs @@ -231,6 +231,43 @@ pub struct CoreInfo { pub number_of_cores: Compact, } +impl core::hash::Hash for CoreInfo { + fn hash(&self, state: &mut H) { + state.write_u8(self.selector.0); + state.write_u8(self.claim_queue_offset.0); + state.write_u16(self.number_of_cores.0); + } +} + +impl CoreInfo { + /// Puts this into a [`CumulusDigestItem::CoreInfo`] and then encodes it as a Substrate + /// [`DigestItem`]. + pub fn to_digest_item(&self) -> DigestItem { + CumulusDigestItem::CoreInfo(self.clone()).to_digest_item() + } +} + +/// Information about a block that is part of a PoV bundle. +#[derive(Clone, Debug, Decode, Encode, PartialEq)] +pub struct BundleInfo { + /// The index of the block in the bundle. + pub index: u8, + /// Is this the last block in the bundle from the point of view of the node? + /// + /// It is possible that at `index` zero the runtime outputs the + /// [`CumulusDigestItem::UseFullCore`] that informs the node to use an entire for one block + /// only. + pub maybe_last: bool, +} + +impl BundleInfo { + /// Puts this into a [`CumulusDigestItem::BundleInfo`] and then encodes it as a Substrate + /// [`DigestItem`]. + pub fn to_digest_item(&self) -> DigestItem { + CumulusDigestItem::BundleInfo(self.clone()).to_digest_item() + } +} + /// Return value of [`CumulusDigestItem::core_info_exists_at_max_once`] #[derive(Debug, Clone, PartialEq, Eq)] pub enum CoreInfoExistsAtMaxOnce { @@ -261,14 +298,25 @@ pub enum CumulusDigestItem { /// block. #[codec(index = 1)] CoreInfo(CoreInfo), + /// A digest item providing information about the position of the block in the bundle. + #[codec(index = 2)] + BundleInfo(BundleInfo), + /// A digest item informing the node that this block should be put alone onto a core. + /// + /// In other words, the core should not be shared with other blocks. + #[codec(index = 3)] + UseFullCore, } impl CumulusDigestItem { /// Encode this as a Substrate [`DigestItem`]. pub fn to_digest_item(&self) -> DigestItem { + let encoded = self.encode(); + match self { - Self::RelayParent(_) => DigestItem::Consensus(CUMULUS_CONSENSUS_ID, self.encode()), - Self::CoreInfo(_) => DigestItem::PreRuntime(CUMULUS_CONSENSUS_ID, self.encode()), + Self::RelayParent(_) | Self::UseFullCore => + DigestItem::Consensus(CUMULUS_CONSENSUS_ID, encoded), + _ => DigestItem::PreRuntime(CUMULUS_CONSENSUS_ID, encoded), } } @@ -347,6 +395,40 @@ impl CumulusDigestItem { _ => None, }) } + + /// Returns the [`BundleInfo`] from the given `digest`. + pub fn find_bundle_info(digest: &Digest) -> Option { + digest.convert_first(|d| match d { + DigestItem::PreRuntime(id, val) if id == &CUMULUS_CONSENSUS_ID => { + let Ok(CumulusDigestItem::BundleInfo(bundle_info)) = + CumulusDigestItem::decode_all(&mut &val[..]) + else { + return None + }; + + Some(bundle_info) + }, + _ => None, + }) + } + + /// Returns `true` if the given `digest` contains the [`Self::UseFullCore`] item. + pub fn contains_use_full_core(digest: &Digest) -> bool { + digest + .convert_first(|d| match d { + DigestItem::Consensus(id, val) if id == &CUMULUS_CONSENSUS_ID => { + let Ok(CumulusDigestItem::UseFullCore) = + CumulusDigestItem::decode_all(&mut &val[..]) + else { + return None + }; + + Some(true) + }, + _ => None, + }) + .unwrap_or_default() + } } /// diff --git a/polkadot/primitives/src/v9/mod.rs b/polkadot/primitives/src/v9/mod.rs index 360da8ff9b956..c55edd47c3c19 100644 --- a/polkadot/primitives/src/v9/mod.rs +++ b/polkadot/primitives/src/v9/mod.rs @@ -2238,10 +2238,22 @@ impl Ord for CommittedCandidateReceiptV2 { #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug, Copy)] pub struct CoreSelector(pub u8); +impl From for CoreSelector { + fn from(value: u8) -> Self { + Self(value) + } +} + /// An offset in the relay chain claim queue. #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug, Copy)] pub struct ClaimQueueOffset(pub u8); +impl From for ClaimQueueOffset { + fn from(value: u8) -> Self { + Self(value) + } +} + /// Signals that a parachain can send to the relay chain via the UMP queue. #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug)] pub enum UMPSignal { diff --git a/substrate/frame/staking-async/runtimes/parachain/src/weights/cumulus_pallet_parachain_system.rs b/substrate/frame/staking-async/runtimes/parachain/src/weights/cumulus_pallet_parachain_system.rs index b91921ce85eb1..55bd6a43a6a0f 100644 --- a/substrate/frame/staking-async/runtimes/parachain/src/weights/cumulus_pallet_parachain_system.rs +++ b/substrate/frame/staking-async/runtimes/parachain/src/weights/cumulus_pallet_parachain_system.rs @@ -79,4 +79,15 @@ impl cumulus_pallet_parachain_system::WeightInfo for We .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(4)) } + fn block_weight_tx_extension_max_weight() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_stays_fraction_of_core() -> Weight { + Weight::zero() + } + + fn block_weight_tx_extension_full_core() -> Weight { + Weight::zero() + } } diff --git a/substrate/frame/support/src/traits/messages.rs b/substrate/frame/support/src/traits/messages.rs index 0a5c70f8f0fa5..eefe47ff53a61 100644 --- a/substrate/frame/support/src/traits/messages.rs +++ b/substrate/frame/support/src/traits/messages.rs @@ -356,6 +356,16 @@ pub trait HandleMessage { fn sweep_queue(); } +impl HandleMessage for () { + type MaxMessageLen = ConstU32<0>; + + fn handle_message(_: BoundedSlice) {} + + fn handle_messages<'a>(_: impl Iterator>) {} + + fn sweep_queue() {} +} + /// 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 diff --git a/substrate/frame/system/src/extensions/check_weight.rs b/substrate/frame/system/src/extensions/check_weight.rs index 16522611ca474..b64f68cb71b82 100644 --- a/substrate/frame/system/src/extensions/check_weight.rs +++ b/substrate/frame/system/src/extensions/check_weight.rs @@ -38,10 +38,16 @@ use sp_weights::Weight; /// /// This extension does not influence any fields of `TransactionValidity` in case the /// transaction is valid. -#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, Default, TypeInfo)] +#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct CheckWeight(core::marker::PhantomData); +impl Default for CheckWeight { + fn default() -> Self { + Self(Default::default()) + } +} + impl CheckWeight where T::RuntimeCall: Dispatchable, diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index c0729d47a6efe..8cb28f16561af 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -2147,7 +2147,7 @@ impl Pallet { } /// Sets the index of extrinsic that is currently executing. - #[cfg(any(feature = "std", test))] + #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] pub fn set_extrinsic_index(extrinsic_index: u32) { storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &extrinsic_index) } diff --git a/substrate/primitives/runtime/src/testing.rs b/substrate/primitives/runtime/src/testing.rs index 647f5eb78d5e1..59f71d4ce4a9a 100644 --- a/substrate/primitives/runtime/src/testing.rs +++ b/substrate/primitives/runtime/src/testing.rs @@ -21,7 +21,7 @@ use crate::{ codec::{Codec, Decode, DecodeWithMemTracking, Encode, EncodeLike, MaxEncodedLen}, generic::{self, LazyBlock, UncheckedExtrinsic}, scale_info::TypeInfo, - traits::{self, BlakeTwo256, Dispatchable, LazyExtrinsic, OpaqueKeys}, + traits::{self, BlakeTwo256, Dispatchable, LazyExtrinsic, Lookup, OpaqueKeys, StaticLookup}, DispatchResultWithInfo, KeyTypeId, OpaqueExtrinsic, }; use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize}; @@ -54,6 +54,12 @@ use std::{cell::RefCell, fmt::Debug}; )] pub struct UintAuthorityId(pub u64); +impl core::fmt::Display for UintAuthorityId { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(&self.0, f) + } +} + impl From for UintAuthorityId { fn from(id: u64) -> Self { UintAuthorityId(id) @@ -162,6 +168,28 @@ impl traits::IdentifyAccount for UintAuthorityId { } } +impl StaticLookup for UintAuthorityId { + type Source = Self; + type Target = u64; + + fn lookup(s: Self::Source) -> Result { + Ok(s.0) + } + + fn unlookup(t: Self::Target) -> Self::Source { + Self(t) + } +} + +impl Lookup for UintAuthorityId { + type Source = Self; + type Target = u64; + + fn lookup(&self, s: Self::Source) -> Result { + Ok(s.0) + } +} + impl traits::Verify for UintAuthorityId { type Signer = Self; From 2712aefa885546317c7a0f065198995ad2037920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 14 Nov 2025 08:55:43 +0100 Subject: [PATCH 02/30] Update cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs Co-authored-by: Guillaume Thiolliere --- .../parachain-system/src/block_weight/transaction_extension.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index e54fc2985b78c..cbfeddb3eb153 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -59,7 +59,7 @@ use sp_runtime::{ /// Before dispatching an extrinsic the extension will check the requirements and set the /// appropriate [`BlockWeightMode`]. After the extrinsic has finished, the checks from before /// dispatching the extrinsic are repeated with the post dispatch weights. The [`BlockWeightMode`] -/// may is changed properly. +/// is changed properly. /// /// # Note /// From 2917609a27a96cf480accb37c4dbc923fe1280e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 14 Nov 2025 08:55:52 +0100 Subject: [PATCH 03/30] Update cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs Co-authored-by: Guillaume Thiolliere --- .../src/block_weight/transaction_extension.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index cbfeddb3eb153..08301f2f30310 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -411,8 +411,8 @@ where inner } - fn weight(&self, _: &Config::RuntimeCall) -> Weight { - Config::WeightInfo::block_weight_tx_extension_max_weight() + fn weight(&self, call: &Config::RuntimeCall) -> Weight { + Config::WeightInfo::block_weight_tx_extension_max_weight().saturating_add(self.0.weight(call)) } fn validate( From 86ad20f689a60a7c671192c67af6a3d0d0ee02d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 14 Nov 2025 13:23:43 +0100 Subject: [PATCH 04/30] New test and some fixes --- .../parachain-system/src/block_weight/mock.rs | 12 +++- .../parachain-system/src/block_weight/mod.rs | 10 +-- .../src/block_weight/tests.rs | 70 +++++++++++++++++-- .../src/block_weight/transaction_extension.rs | 3 +- cumulus/pallets/parachain-system/src/lib.rs | 6 -- substrate/frame/support/src/traits/hooks.rs | 5 ++ 6 files changed, 84 insertions(+), 22 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs index 0eba745c88d9a..8439fc8cad9c2 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mock.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -30,7 +30,7 @@ use frame_support::{ Weight, }, }; -use frame_system::limits::BlockWeights; +use frame_system::{limits::BlockWeights, CheckWeight}; use sp_core::ConstU32; use sp_io; use sp_runtime::{ @@ -49,14 +49,20 @@ pub type ExtrinsicOnlyOperational = UncheckedExtrinsic< UintAuthorityId, only_operational_runtime::RuntimeCall, UintAuthorityId, - DynamicMaxBlockWeight, 10, false>, + DynamicMaxBlockWeight< + RuntimeOnlyOperational, + CheckWeight, + ConstU32, + 10, + false, + >, >; pub type Extrinsic = UncheckedExtrinsic< UintAuthorityId, RuntimeCall, UintAuthorityId, - DynamicMaxBlockWeight>, + DynamicMaxBlockWeight, ConstU32>, >; pub type Block = diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index 4a703ea21f4d4..db1f223845c99 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -162,20 +162,14 @@ impl> Get target_block_weight }; - // If we are in `on_initialize` or at applying the inherents, we allow the maximum block - // weight as allowed by the current context. - if !frame_system::Pallet::::inherents_applied() { - return maybe_full_core_weight - } - match crate::BlockWeightMode::::get() { // We allow the full core. Some(BlockWeightMode::FullCore | BlockWeightMode::PotentialFullCore { .. }) => Self::FULL_CORE_WEIGHT, // Let's calculate below how much weight we can use. Some(BlockWeightMode::FractionOfCore { .. }) => target_block_weight, - // Either the runtime is not using the `DynamicMaxBlockWeight` extension or there is a - // bug. The value should be set before applying the first extrinsic. + // If we are in `on_initialize` or at applying the inherents, we allow the maximum block + // weight as allowed by the current context. None => maybe_full_core_weight, } } diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index f45e8604d9d31..5904c265173e7 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -706,7 +706,7 @@ fn executive_validate_block_handles_normal_transactions() { TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { let call = RuntimeCall::TestPallet(test_pallet::Call::heavy_call_normal {}); - let xt = Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), ().into()); + let xt = Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), Default::default()); assert!(Executive::validate_transaction( TransactionSource::External, @@ -719,7 +719,7 @@ fn executive_validate_block_handles_normal_transactions() { TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { let call = RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_normal {}); - let xt = ExtrinsicOnlyOperational::new_signed(call, 1u64.into(), 1u64.into(), ().into()); + let xt = ExtrinsicOnlyOperational::new_signed(call, 1u64.into(), 1u64.into(), Default::default()); assert_eq!( ExecutiveOnlyOperational::validate_transaction( @@ -738,7 +738,7 @@ fn executive_validate_block_handles_operational_transactions() { TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { let call = RuntimeCall::TestPallet(test_pallet::Call::heavy_call_operational {}); - let xt = Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), ().into()); + let xt = Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), Default::default()); assert!(Executive::validate_transaction( TransactionSource::External, @@ -752,7 +752,12 @@ fn executive_validate_block_handles_operational_transactions() { let call = RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_operational {}); - let xt = ExtrinsicOnlyOperational::new_signed(call, 1u64.into(), 1u64.into(), ().into()); + let xt = ExtrinsicOnlyOperational::new_signed( + call, + 1u64.into(), + 1u64.into(), + Default::default(), + ); assert!(ExecutiveOnlyOperational::validate_transaction( TransactionSource::External, @@ -786,3 +791,60 @@ fn executive_with_operational_only_applies_big_inherent() { ExecutiveOnlyOperational::apply_extrinsic(xt).unwrap().unwrap(); }); } + +#[test] +fn block_weight_mode_from_previous_block_is_ignored_in_validate_block() { + TestExtBuilder::new() + .number_of_cores(4) + .first_block_in_core(true) + .build() + .execute_with(|| { + let call = RuntimeCallOnlyOperational::TestPallet( + test_pallet::Call::heavy_call_operational {}, + ); + + let xt = ExtrinsicOnlyOperational::new_signed( + call, + 1u64.into(), + 1u64.into(), + Default::default(), + ); + + Executive::initialize_block(&Header::new( + 1, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + )); + + assert!(ExecutiveOnlyOperational::apply_extrinsic(xt,).is_ok()); + + Executive::finalize_block(); + + assert_eq!( + crate::BlockWeightMode::::get().unwrap(), + BlockWeightMode::FullCore + ); + + let call = + RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_normal {}); + + let xt = ExtrinsicOnlyOperational::new_signed( + call, + 1u64.into(), + 1u64.into(), + Default::default(), + ); + + assert_eq!( + ExecutiveOnlyOperational::validate_transaction( + TransactionSource::External, + xt, + Default::default() + ) + .unwrap_err(), + InvalidTransaction::ExhaustsResources.into() + ); + }); +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 08301f2f30310..d83bf20fc62cb 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -412,7 +412,8 @@ where } fn weight(&self, call: &Config::RuntimeCall) -> Weight { - Config::WeightInfo::block_weight_tx_extension_max_weight().saturating_add(self.0.weight(call)) + Config::WeightInfo::block_weight_tx_extension_max_weight() + .saturating_add(self.0.weight(call)) } fn validate( diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index dee06a139c35c..b7ac3a26eefdc 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -715,12 +715,6 @@ pub mod pallet { ); } - if let Some(collator_peer_id) = collator_peer_id { - PendingUpwardSignals::::mutate(|signals| { - signals.push(UMPSignal::ApprovedPeer(collator_peer_id).encode()); - }); - } - total_weight.saturating_accrue(Self::enqueue_inbound_downward_messages( relevant_messaging_state.dmq_mqc_head, inbound_messages_data.downward_messages, diff --git a/substrate/frame/support/src/traits/hooks.rs b/substrate/frame/support/src/traits/hooks.rs index f7404ca7dbe85..b4027547c47e5 100644 --- a/substrate/frame/support/src/traits/hooks.rs +++ b/substrate/frame/support/src/traits/hooks.rs @@ -130,6 +130,11 @@ impl_for_tuples_attr! { &[for_tuples!( #( Tuple::on_idle ),* )]; let mut weight = Weight::zero(); let len = on_idle_functions.len(); + + if len == 0 { + return Weight::zero() + } + let start_index = n % (len as u32).into(); let start_index = start_index.try_into().ok().expect( "`start_index % len` always fits into `usize`, because `len` can be in maximum `usize::MAX`; qed" From 5b5190b08b6c54db6b86783af5775e3f340cd146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 14 Nov 2025 15:00:50 +0100 Subject: [PATCH 05/30] Ensure we ignore the block weight mode from the previous block --- .../parachain-system/src/block_weight/mod.rs | 71 +++++++++++++++++-- .../src/block_weight/pre_inherents_hook.rs | 8 +-- .../src/block_weight/tests.rs | 39 ++++++---- .../src/block_weight/transaction_extension.rs | 59 ++++++++------- cumulus/pallets/parachain-system/src/lib.rs | 2 +- 5 files changed, 129 insertions(+), 50 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index db1f223845c99..d8d7114852efc 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -50,7 +50,11 @@ use crate::{Config, PreviousCoreCount}; use codec::{Decode, Encode}; use core::marker::PhantomData; use cumulus_primitives_core::CumulusDigestItem; -use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}; +use frame_support::{ + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, + CloneNoBound, DebugNoBound, +}; +use frame_system::pallet_prelude::BlockNumberFor; use polkadot_primitives::MAX_POV_SIZE; use scale_info::TypeInfo; use sp_core::Get; @@ -71,12 +75,20 @@ const LOG_TARGET: &str = "runtime::parachain-system::block-weight"; /// The current block weight mode. /// /// Based on this mode [`MaxParachainBlockWeight`] determines the current allowed block weight. -#[derive(Debug, Encode, Decode, Clone, Copy, TypeInfo, PartialEq)] -pub enum BlockWeightMode { +#[derive(DebugNoBound, Encode, Decode, CloneNoBound, TypeInfo, PartialEq)] +#[scale_info(skip_type_params(T))] +pub enum BlockWeightMode { /// The block is allowed to use the weight of a full core. - FullCore, + FullCore { + /// The block in which this mode was set. Is used to determine if this is maybe stale mode + /// setting, e.g. when running `validate_block`. + context: BlockNumberFor, + }, /// The current active transaction is allowed to use the weight of a full core. PotentialFullCore { + /// The block in which this mode was set. Is used to determine if this is maybe stale mode + /// setting, e.g. when running `validate_block`. + context: BlockNumberFor, /// The index of the first transaction. first_transaction_index: Option, /// The target weight that was used to determine that the extrinsic is above this limit. @@ -87,11 +99,54 @@ pub enum BlockWeightMode { /// How much each block is allowed to consume, depends on the target number of blocks and the /// available cores on the relay chain. FractionOfCore { + /// The block in which this mode was set. Is used to determine if this is maybe stale mode + /// setting, e.g. when running `validate_block`. + context: BlockNumberFor, /// The index of the first transaction. first_transaction_index: Option, }, } +impl BlockWeightMode { + /// Check if this mode is stale, aka was set in a previous block. + fn is_stale(&self) -> bool { + let context = self.context(); + + context < frame_system::Pallet::::block_number() + } + + /// Returns the context (block) in which this mode was set. + fn context(&self) -> BlockNumberFor { + match self { + Self::FullCore { context } | + Self::PotentialFullCore { context, .. } | + Self::FractionOfCore { context, .. } => *context, + } + } + + /// Create a new instance of `Self::FullCore`. + fn full_core() -> Self { + Self::FullCore { context: frame_system::Pallet::::block_number() } + } + + /// Create new instance of `Self::FractionOfCore`. + fn fraction_of_core(first_transaction_index: Option) -> Self { + Self::FractionOfCore { + context: frame_system::Pallet::::block_number(), + first_transaction_index, + } + } + + /// Create new instance of `Self::PotentialFullCore`. + fn potential_full_core(first_transaction_index: Option, target_weight: Weight) -> Self { + Self::PotentialFullCore { + context: frame_system::Pallet::::block_number(), + first_transaction_index, + target_weight, + } + } +} + /// Calculates the maximum block weight for a parachain. /// /// Based on the available cores and the number of desired blocks a block weight is calculated. @@ -164,10 +219,12 @@ impl> Get match crate::BlockWeightMode::::get() { // We allow the full core. - Some(BlockWeightMode::FullCore | BlockWeightMode::PotentialFullCore { .. }) => - Self::FULL_CORE_WEIGHT, + Some( + BlockWeightMode::::FullCore { .. } | + BlockWeightMode::::PotentialFullCore { .. }, + ) => Self::FULL_CORE_WEIGHT, // Let's calculate below how much weight we can use. - Some(BlockWeightMode::FractionOfCore { .. }) => target_block_weight, + Some(BlockWeightMode::::FractionOfCore { .. }) => target_block_weight, // If we are in `on_initialize` or at applying the inherents, we allow the maximum block // weight as allowed by the current context. None => maybe_full_core_weight, diff --git a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs index 0c51997c36303..faf766ebd2145 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs @@ -40,9 +40,9 @@ where fn pre_inherents() { if !block_weight_over_target_block_weight::() { // We still initialize the `BlockWeightMode`. - crate::BlockWeightMode::::put(BlockWeightMode::FractionOfCore { - first_transaction_index: None, - }); + crate::BlockWeightMode::::put(BlockWeightMode::::fraction_of_core( + None, + )); return } @@ -67,7 +67,7 @@ where ); } - crate::BlockWeightMode::::put(BlockWeightMode::FullCore); + crate::BlockWeightMode::::put(BlockWeightMode::::full_core()); // Inform the node that this block uses the full core. frame_system::Pallet::::deposit_log( diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index 5904c265173e7..7a6c58b379f84 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -224,7 +224,7 @@ fn tx_extension_sets_fraction_of_core_mode() { assert_eq!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FractionOfCore { first_transaction_index: Some(0) }) + Some(BlockWeightMode::fraction_of_core(Some(0))) ); }); } @@ -268,7 +268,10 @@ fn tx_extension_large_tx_enables_full_core_usage() { assert_ok!(TxExtension::post_dispatch((), &info, &mut post_info, 0, &Ok(()))); - assert_eq!(crate::BlockWeightMode::::get(), Some(BlockWeightMode::FullCore)); + assert_eq!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::full_core()) + ); assert!(has_use_full_core_digest()); assert_eq!(MaximumBlockWeight::get().ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); }); @@ -310,7 +313,7 @@ fn tx_extension_only_allows_large_operational_tx_to_enable_full_core_usage() { assert_matches!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FractionOfCore { first_transaction_index: None }) + Some(BlockWeightMode::FractionOfCore { first_transaction_index: None, .. }) ); info.class = DispatchClass::Operational; @@ -335,7 +338,10 @@ fn tx_extension_only_allows_large_operational_tx_to_enable_full_core_usage() { assert_ok!(TxExtension::post_dispatch((), &info, &mut post_info, 0, &Ok(()))); - assert_eq!(crate::BlockWeightMode::::get(), Some(BlockWeightMode::FullCore)); + assert_eq!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::full_core()) + ); assert!(has_use_full_core_digest()); assert_eq!(MaximumBlockWeight::get().ref_time(), 2 * WEIGHT_REF_TIME_PER_SECOND); }); @@ -427,7 +433,7 @@ fn tx_extension_large_tx_is_rejected_on_non_first_block() { // Should stay in FractionOfCore mode (not PotentialFullCore) since not first block assert_eq!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FractionOfCore { first_transaction_index: None }) + Some(BlockWeightMode::fraction_of_core(None)) ); assert!(!has_use_full_core_digest()); assert_eq!(MaximumBlockWeight::get(), target_weight); @@ -461,7 +467,7 @@ fn tx_extension_post_dispatch_to_full_core_because_of_manual_weight() { assert_matches!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FractionOfCore { first_transaction_index: Some(0) }) + Some(BlockWeightMode::FractionOfCore { first_transaction_index: Some(0), .. }) ); // But actually uses much more weight (bug in weight annotation) @@ -476,7 +482,7 @@ fn tx_extension_post_dispatch_to_full_core_because_of_manual_weight() { // Should transition to FullCore due to exceeding limit assert_matches!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FullCore) + Some(BlockWeightMode::FullCore { .. }) ); assert!(has_use_full_core_digest()); @@ -517,7 +523,7 @@ fn tx_extension_large_tx_after_limit_is_rejected() { assert_eq!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FractionOfCore { first_transaction_index: None }) + Some(BlockWeightMode::fraction_of_core(None)) ); assert!(!has_use_full_core_digest()); assert_eq!(MaximumBlockWeight::get(), target_weight); @@ -560,7 +566,7 @@ fn tx_extension_large_weight_before_first_tx() { assert_matches!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FullCore) + Some(BlockWeightMode::FullCore { .. }) ); assert!(has_use_full_core_digest()); @@ -596,7 +602,7 @@ fn pre_inherents_hook_first_block_over_limit() { assert_matches!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FullCore) + Some(BlockWeightMode::FullCore { .. }) ); // Should have UseFullCore digest @@ -623,7 +629,7 @@ fn pre_inherents_hook_non_first_block_over_limit() { assert_matches!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FullCore) + Some(BlockWeightMode::FullCore { .. }) ); assert!(has_use_full_core_digest()); @@ -653,7 +659,7 @@ fn pre_inherents_hook_under_limit_no_change() { assert_matches!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::FractionOfCore { first_transaction_index: None }) + Some(BlockWeightMode::FractionOfCore { first_transaction_index: None, .. }) ); // Should NOT have UseFullCore digest @@ -719,7 +725,12 @@ fn executive_validate_block_handles_normal_transactions() { TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { let call = RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_normal {}); - let xt = ExtrinsicOnlyOperational::new_signed(call, 1u64.into(), 1u64.into(), Default::default()); + let xt = ExtrinsicOnlyOperational::new_signed( + call, + 1u64.into(), + 1u64.into(), + Default::default(), + ); assert_eq!( ExecutiveOnlyOperational::validate_transaction( @@ -824,7 +835,7 @@ fn block_weight_mode_from_previous_block_is_ignored_in_validate_block() { assert_eq!( crate::BlockWeightMode::::get().unwrap(), - BlockWeightMode::FullCore + BlockWeightMode::full_core() ); let call = diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index d83bf20fc62cb..6056f92de486b 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -123,22 +123,28 @@ where let transaction_index = is_not_inherent.then(|| extrinsic_index); crate::BlockWeightMode::::mutate(|mode| { - let current_mode = *mode.get_or_insert_with(|| BlockWeightMode::FractionOfCore { - first_transaction_index: transaction_index, - }); + let current_mode = mode.get_or_insert_with(|| BlockWeightMode::::fraction_of_core(transaction_index)); + + // If the mode is stale (from previous block), we reset it. + // + // This happens for example when running in an offchain context. + if current_mode.is_stale() { + *current_mode = BlockWeightMode::fraction_of_core(transaction_index); + } log::trace!( target: LOG_TARGET, "About to pre-validate an extrinsic. current_mode={current_mode:?}, transaction_index={transaction_index:?}" ); + let is_potential = + matches!(current_mode, &mut BlockWeightMode::PotentialFullCore { .. }); + match current_mode { // We are already allowing the full core, not that much more to do here. - BlockWeightMode::FullCore => {}, - BlockWeightMode::PotentialFullCore { first_transaction_index, .. } | - BlockWeightMode::FractionOfCore { first_transaction_index } => { - let is_potential = - matches!(current_mode, BlockWeightMode::PotentialFullCore { .. }); + BlockWeightMode::::FullCore { ..} => {}, + BlockWeightMode::::PotentialFullCore { first_transaction_index, .. } | + BlockWeightMode::::FractionOfCore { first_transaction_index, .. } => { debug_assert!( !is_potential, "`PotentialFullCore` should resolve to `FullCore` or `FractionOfCore` after applying a transaction.", @@ -155,7 +161,7 @@ where // Protection against a misconfiguration as this should be detected by the pre-inherent hook. if block_weight_over_limit { - *mode = Some(BlockWeightMode::FullCore); + *mode = Some(BlockWeightMode::::full_core()); // Inform the node that this block uses the full core. frame_system::Pallet::::deposit_log( @@ -198,12 +204,12 @@ where "Enabling `PotentialFullCore` mode for extrinsic", ); - *mode = Some(BlockWeightMode::PotentialFullCore { - target_weight, + *mode = Some(BlockWeightMode::::potential_full_core ( // While applying inherents `extrinsic_index` and `first_transaction_index` will be `None`. // When the first transaction is applied, we want to store the index. - first_transaction_index: first_transaction_index.or(transaction_index), - }); + first_transaction_index.or(transaction_index), + target_weight, + )); } else { log::trace!( target: LOG_TARGET, @@ -218,7 +224,7 @@ where "Resetting back to `FractionOfCore`" ); *mode = - Some(BlockWeightMode::FractionOfCore { first_transaction_index: first_transaction_index.or(transaction_index) }); + Some(BlockWeightMode::::fraction_of_core(first_transaction_index.or(transaction_index))); } else { log::trace!( target: LOG_TARGET, @@ -226,7 +232,7 @@ where ); *mode = - Some(BlockWeightMode::FractionOfCore { first_transaction_index: first_transaction_index.or(transaction_index) }); + Some(BlockWeightMode::::fraction_of_core(first_transaction_index.or(transaction_index))); } }, }; @@ -241,14 +247,14 @@ where /// Returns the weight to refund. Aka the weight that wasn't used by this extension. fn post_dispatch_extrinsic(info: &DispatchInfo) -> Weight { crate::BlockWeightMode::::mutate(|weight_mode| { - let Some(mode) = *weight_mode else { return Weight::zero() }; + let Some(mode) = weight_mode else { return Weight::zero() }; match mode { // If the previous mode was already `FullCore`, we are fine. - BlockWeightMode::FullCore => + BlockWeightMode::::FullCore { .. } => Config::WeightInfo::block_weight_tx_extension_max_weight() .saturating_sub(Config::WeightInfo::block_weight_tx_extension_full_core()), - BlockWeightMode::FractionOfCore { .. } => { + BlockWeightMode::::FractionOfCore { .. } => { let digest = frame_system::Pallet::::digest(); let target_block_weight = MaxParachainBlockWeight::::target_block_weight_with_digest(&digest); @@ -283,7 +289,7 @@ where ); } - *weight_mode = Some(BlockWeightMode::FullCore); + *weight_mode = Some(BlockWeightMode::::full_core()); // Inform the node that this block uses the full core. frame_system::Pallet::::deposit_log( @@ -297,17 +303,21 @@ where }, // Now we need to check if the transaction required more weight than a fraction of a // core block. - BlockWeightMode::PotentialFullCore { first_transaction_index, target_weight } => { + BlockWeightMode::::PotentialFullCore { + first_transaction_index, + target_weight, + .. + } => { let block_weight = frame_system::BlockWeight::::get(); let extrinsic_class_weight = block_weight.get(info.class); - if extrinsic_class_weight.any_gt(target_weight) { + if extrinsic_class_weight.any_gt(*target_weight) { log::trace!( target: LOG_TARGET, "Extrinsic class weight {extrinsic_class_weight:?} above target weight {target_weight:?}, enabling `FullCore` mode." ); - *weight_mode = Some(BlockWeightMode::FullCore); + *weight_mode = Some(BlockWeightMode::::full_core()); // Inform the node that this block uses the full core. frame_system::Pallet::::deposit_log( @@ -320,8 +330,9 @@ where weight {target_weight:?}, going back to `FractionOfCore` mode." ); - *weight_mode = - Some(BlockWeightMode::FractionOfCore { first_transaction_index }); + *weight_mode = Some(BlockWeightMode::::fraction_of_core( + *first_transaction_index, + )); } // We run into the worst case, so no refund :) diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index b7ac3a26eefdc..3ed529a3ce728 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -789,7 +789,7 @@ pub mod pallet { #[pallet::storage] #[pallet::whitelist_storage] pub type BlockWeightMode = - StorageValue<_, block_weight::BlockWeightMode, OptionQuery>; + StorageValue<_, block_weight::BlockWeightMode, OptionQuery>; /// The core count available to the parachain in the previous block. /// From a8641d6c6bf411c2ef2e64b1aa71c958e5ebbc09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 14 Nov 2025 15:01:51 +0100 Subject: [PATCH 06/30] Update cumulus/pallets/parachain-system/src/validate_block/implementation.rs Co-authored-by: Oliver Tale-Yazdi --- .../parachain-system/src/validate_block/implementation.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs index bcad111b7bef1..22f1154517d96 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs @@ -83,8 +83,6 @@ where B::Extrinsic: ExtrinsicCall, ::Call: IsSubType>, { - // sp_runtime::runtime_logger::RuntimeLogger::init(); - let _guard = ( // Replace storage calls with our own implementations sp_io::storage::host_read.replace_implementation(host_storage_read), From eba0d1474487c106243e09bca067b4a2297d7c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 14 Nov 2025 16:02:34 +0100 Subject: [PATCH 07/30] Allow MBMs to take the full core --- .../parachain-system/src/block_weight/mock.rs | 27 ++++++++---- .../parachain-system/src/block_weight/mod.rs | 20 ++++----- .../src/block_weight/pre_inherents_hook.rs | 28 +++++++++---- .../src/block_weight/tests.rs | 42 +++++++++++++++---- .../src/block_weight/transaction_extension.rs | 6 +-- 5 files changed, 88 insertions(+), 35 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs index 8439fc8cad9c2..13dc037b4652f 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mock.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -23,6 +23,7 @@ use cumulus_primitives_core::{ use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, + migrations::MultiStepMigrator, parameter_types, traits::PreInherents, weights::{ @@ -208,27 +209,39 @@ construct_runtime!( } ); +parameter_types! { + pub static MbmOngoing: bool = false; +} + +pub struct Migrator; + +impl MultiStepMigrator for Migrator { + fn ongoing() -> bool { + MbmOngoing::get() + } + + fn step() -> Weight { + Weight::zero() + } +} + pub mod only_operational_runtime { + use super::{BlockOnlyOperational, Migrator}; + use crate::block_weight::DynamicMaxBlockWeightHooks; use frame_support::{construct_runtime, derive_impl}; use sp_core::ConstU32; use sp_runtime::testing::UintAuthorityId; - use crate::block_weight::{mock::BlockOnlyOperational, DynamicMaxBlockWeightHooks}; - #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for RuntimeOnlyOperational { - // Setup the block weight. type BlockWeights = super::max_block_weight_setup::RuntimeBlockWeights; - // Set the `PreInherents` hook. type PreInherents = DynamicMaxBlockWeightHooks>; - - // Just required to make it compile, but not that important for this example here. type Block = BlockOnlyOperational; type OnSetCode = crate::ParachainSetCode; type AccountId = u64; type Lookup = UintAuthorityId; - // Rest of the types are omitted here. + type MultiBlockMigrator = Migrator; } impl crate::Config for RuntimeOnlyOperational { diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index d8d7114852efc..15270b21098b4 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -71,6 +71,11 @@ pub use pre_inherents_hook::DynamicMaxBlockWeightHooks; pub use transaction_extension::DynamicMaxBlockWeight; const LOG_TARGET: &str = "runtime::parachain-system::block-weight"; +/// Maximum ref time per core +const MAX_REF_TIME_PER_CORE_NS: u64 = 2 * WEIGHT_REF_TIME_PER_SECOND; +/// The available weight per core on the relay chain. +pub(crate) const FULL_CORE_WEIGHT: Weight = + Weight::from_parts(MAX_REF_TIME_PER_CORE_NS, MAX_POV_SIZE as u64); /// The current block weight mode. /// @@ -159,11 +164,6 @@ pub struct MaxParachainBlockWeight(PhantomData<(Config, impl> MaxParachainBlockWeight { - // Maximum ref time per core - const MAX_REF_TIME_PER_CORE_NS: u64 = 2 * WEIGHT_REF_TIME_PER_SECOND; - pub(crate) const FULL_CORE_WEIGHT: Weight = - Weight::from_parts(Self::MAX_REF_TIME_PER_CORE_NS, MAX_POV_SIZE as u64); - /// Returns the target block weight for one block. pub(crate) fn target_block_weight() -> Weight { let digest = frame_system::Pallet::::digest(); @@ -181,18 +181,18 @@ impl> // Ensure we have at least one core and valid target blocks if number_of_cores == 0 || target_blocks == 0 { - return Self::FULL_CORE_WEIGHT; + return FULL_CORE_WEIGHT; } // At maximum we want to allow `6s` of ref time, because we don't want to overload nodes // that are running with standard hardware. These nodes need to be able to import all the // blocks in 6s. let total_ref_time = (number_of_cores as u64) - .saturating_mul(Self::MAX_REF_TIME_PER_CORE_NS) + .saturating_mul(MAX_REF_TIME_PER_CORE_NS) .min(WEIGHT_REF_TIME_PER_SECOND * 6); let ref_time_per_block = total_ref_time .saturating_div(target_blocks as u64) - .min(Self::MAX_REF_TIME_PER_CORE_NS); + .min(MAX_REF_TIME_PER_CORE_NS); let total_pov_size = (number_of_cores as u64).saturating_mul(MAX_POV_SIZE as u64); // Each block at max gets one core. @@ -212,7 +212,7 @@ impl> Get let maybe_full_core_weight = if is_first_block_in_core_with_digest(&digest).unwrap_or(false) { - Self::FULL_CORE_WEIGHT + FULL_CORE_WEIGHT } else { target_block_weight }; @@ -222,7 +222,7 @@ impl> Get Some( BlockWeightMode::::FullCore { .. } | BlockWeightMode::::PotentialFullCore { .. }, - ) => Self::FULL_CORE_WEIGHT, + ) => FULL_CORE_WEIGHT, // Let's calculate below how much weight we can use. Some(BlockWeightMode::::FractionOfCore { .. }) => target_block_weight, // If we are in `on_initialize` or at applying the inherents, we allow the maximum block diff --git a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs index faf766ebd2145..4f9074a30eb32 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs @@ -17,9 +17,9 @@ use super::{ block_weight_over_target_block_weight, is_first_block_in_core, BlockWeightMode, LOG_TARGET, }; -use crate::block_weight::MaxParachainBlockWeight; +use crate::block_weight::FULL_CORE_WEIGHT; use cumulus_primitives_core::CumulusDigestItem; -use frame_support::traits::PreInherents; +use frame_support::{migrations::MultiStepMigrator, traits::PreInherents}; use sp_core::Get; /// A pre-inherent hook that may increases max block weight after `on_initialize`. @@ -39,10 +39,24 @@ where { fn pre_inherents() { if !block_weight_over_target_block_weight::() { - // We still initialize the `BlockWeightMode`. - crate::BlockWeightMode::::put(BlockWeightMode::::fraction_of_core( - None, - )); + let new_mode = if Config::MultiBlockMigrator::ongoing() { + log::debug!( + target: LOG_TARGET, + "Multi block migrations are still ongoing, allowing the full core.", + ); + + // Inform the node that this block uses the full core. + frame_system::Pallet::::deposit_log( + CumulusDigestItem::UseFullCore.to_digest_item(), + ); + + BlockWeightMode::::full_core() + } else { + BlockWeightMode::::fraction_of_core(None) + }; + + crate::BlockWeightMode::::put(new_mode); + return } @@ -57,7 +71,7 @@ where // We are already above the allowed maximum and do not want to accept any more // extrinsics. frame_system::Pallet::::register_extra_weight_unchecked( - MaxParachainBlockWeight::::FULL_CORE_WEIGHT, + FULL_CORE_WEIGHT, frame_support::dispatch::DispatchClass::Mandatory, ); } else { diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index 7a6c58b379f84..3fdcb58476ae4 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -149,7 +149,7 @@ fn test_target_block_weight_with_digest_edge_cases() { MaxParachainBlockWeight::>::target_block_weight_with_digest( &empty_digest, ); - assert_eq!(weight, MaxParachainBlockWeight::>::FULL_CORE_WEIGHT / 4); + assert_eq!(weight, FULL_CORE_WEIGHT / 4); // Test with digest containing core info let core_info = CoreInfo { @@ -575,9 +575,7 @@ fn tx_extension_large_weight_before_first_tx() { if !first_block_in_core { // Should have registered FULL_CORE_WEIGHT to prevent more transactions let final_remaining = frame_system::Pallet::::remaining_block_weight(); - assert!(final_remaining - .consumed() - .all_gte(MaximumBlockWeight::FULL_CORE_WEIGHT)); + assert!(final_remaining.consumed().all_gte(FULL_CORE_WEIGHT)); } }); } @@ -636,7 +634,7 @@ fn pre_inherents_hook_non_first_block_over_limit() { // Should have registered FULL_CORE_WEIGHT to prevent more transactions let final_remaining = frame_system::Pallet::::remaining_block_weight(); - assert!(final_remaining.consumed().all_gte(MaximumBlockWeight::FULL_CORE_WEIGHT)); + assert!(final_remaining.consumed().all_gte(FULL_CORE_WEIGHT)); }); } @@ -821,7 +819,7 @@ fn block_weight_mode_from_previous_block_is_ignored_in_validate_block() { Default::default(), ); - Executive::initialize_block(&Header::new( + ExecutiveOnlyOperational::initialize_block(&Header::new( 1, Default::default(), Default::default(), @@ -829,9 +827,9 @@ fn block_weight_mode_from_previous_block_is_ignored_in_validate_block() { Default::default(), )); - assert!(ExecutiveOnlyOperational::apply_extrinsic(xt,).is_ok()); + assert!(ExecutiveOnlyOperational::apply_extrinsic(xt).is_ok()); - Executive::finalize_block(); + ExecutiveOnlyOperational::finalize_block(); assert_eq!( crate::BlockWeightMode::::get().unwrap(), @@ -859,3 +857,31 @@ fn block_weight_mode_from_previous_block_is_ignored_in_validate_block() { ); }); } + +#[test] +fn ongoin_mbm_requests_full_core() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + MbmOngoing::set(true); + ExecutiveOnlyOperational::initialize_block(&Header::new( + 1, + Default::default(), + Default::default(), + Default::default(), + Default::default(), + )); + + assert_eq!( + FULL_CORE_WEIGHT, + ::BlockWeights::get().max_block + ); + + ExecutiveOnlyOperational::finalize_block(); + + assert!(has_use_full_core_digest()); + MbmOngoing::set(false); + }); +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 6056f92de486b..8e59a080a4e4d 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -16,7 +16,7 @@ use super::{ block_weight_over_target_block_weight, is_first_block_in_core_with_digest, BlockWeightMode, - MaxParachainBlockWeight, LOG_TARGET, + MaxParachainBlockWeight, LOG_TARGET, FULL_CORE_WEIGHT }; use crate::WeightInfo; use alloc::vec::Vec; @@ -172,7 +172,7 @@ where // We are already above the allowed maximum and do not want to accept any more // extrinsics. frame_system::Pallet::::register_extra_weight_unchecked( - MaxParachainBlockWeight::::FULL_CORE_WEIGHT, + FULL_CORE_WEIGHT, DispatchClass::Mandatory, ); } @@ -284,7 +284,7 @@ where ); frame_system::Pallet::::register_extra_weight_unchecked( - MaxParachainBlockWeight::::FULL_CORE_WEIGHT, + FULL_CORE_WEIGHT, DispatchClass::Mandatory, ); } From 9de019ec63ef2e686087332e897b7f41870b92c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 14 Nov 2025 16:15:10 +0100 Subject: [PATCH 08/30] Fixes --- .../parachain-system/src/benchmarking.rs | 25 ++++++++----------- .../parachain-system/src/block_weight/mod.rs | 6 ++--- cumulus/pallets/parachain-system/src/lib.rs | 2 ++ 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/benchmarking.rs b/cumulus/pallets/parachain-system/src/benchmarking.rs index fd05cac40d256..d417029ab1120 100644 --- a/cumulus/pallets/parachain-system/src/benchmarking.rs +++ b/cumulus/pallets/parachain-system/src/benchmarking.rs @@ -21,7 +21,9 @@ use super::*; use crate::{ - block_weight::{BlockWeightMode, DynamicMaxBlockWeight, MaxParachainBlockWeight}, + block_weight::{ + BlockWeightMode, DynamicMaxBlockWeight, MaxParachainBlockWeight, FULL_CORE_WEIGHT, + }, parachain_inherent::InboundDownwardMessages, }; use cumulus_primitives_core::{ @@ -120,9 +122,7 @@ mod benchmarks { let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; let len = 0_usize; - crate::BlockWeightMode::::put(BlockWeightMode::FractionOfCore { - first_transaction_index: None, - }); + crate::BlockWeightMode::::put(BlockWeightMode::fraction_of_core(None)); let ext = DynamicMaxBlockWeight::>::new(()); @@ -140,12 +140,9 @@ mod benchmarks { .unwrap(); } - assert_eq!(crate::BlockWeightMode::::get().unwrap(), BlockWeightMode::FullCore); + assert_eq!(crate::BlockWeightMode::::get().unwrap(), BlockWeightMode::full_core()); assert!(has_use_full_core_digest::()); - assert_eq!( - MaxParachainBlockWeight::>::get(), - MaxParachainBlockWeight::>::FULL_CORE_WEIGHT - ); + assert_eq!(MaxParachainBlockWeight::>::get(), FULL_CORE_WEIGHT); Ok(()) } @@ -182,9 +179,7 @@ mod benchmarks { let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; let len = 0_usize; - crate::BlockWeightMode::::put(BlockWeightMode::FractionOfCore { - first_transaction_index: None, - }); + crate::BlockWeightMode::::put(BlockWeightMode::fraction_of_core(None)); let ext = DynamicMaxBlockWeight::>::new(()); @@ -204,7 +199,7 @@ mod benchmarks { assert_eq!( crate::BlockWeightMode::::get().unwrap(), - BlockWeightMode::FractionOfCore { first_transaction_index: Some(1) } + BlockWeightMode::fraction_of_core(Some(1)) ); assert!(!has_use_full_core_digest::()); assert_eq!(MaxParachainBlockWeight::>::get(), target_weight); @@ -243,7 +238,7 @@ mod benchmarks { let post_info = PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }; let len = 0_usize; - crate::BlockWeightMode::::put(BlockWeightMode::FullCore); + crate::BlockWeightMode::::put(BlockWeightMode::full_core()); let ext = DynamicMaxBlockWeight::>::new(()); @@ -261,7 +256,7 @@ mod benchmarks { .unwrap(); } - assert_eq!(crate::BlockWeightMode::::get().unwrap(), BlockWeightMode::FullCore); + assert_eq!(crate::BlockWeightMode::::get().unwrap(), BlockWeightMode::full_core()); Ok(()) } diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index 15270b21098b4..346bd0150b6e7 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -130,12 +130,12 @@ impl BlockWeightMode { } /// Create a new instance of `Self::FullCore`. - fn full_core() -> Self { + pub(crate) fn full_core() -> Self { Self::FullCore { context: frame_system::Pallet::::block_number() } } /// Create new instance of `Self::FractionOfCore`. - fn fraction_of_core(first_transaction_index: Option) -> Self { + pub(crate) fn fraction_of_core(first_transaction_index: Option) -> Self { Self::FractionOfCore { context: frame_system::Pallet::::block_number(), first_transaction_index, @@ -143,7 +143,7 @@ impl BlockWeightMode { } /// Create new instance of `Self::PotentialFullCore`. - fn potential_full_core(first_transaction_index: Option, target_weight: Weight) -> Self { + pub(crate) fn potential_full_core(first_transaction_index: Option, target_weight: Weight) -> Self { Self::PotentialFullCore { context: frame_system::Pallet::::block_number(), first_transaction_index, diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 3ed529a3ce728..e2f23f8dfb61a 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -786,6 +786,8 @@ pub mod pallet { /// /// This is used to determine what is the maximum allowed block weight, for more information see /// [`block_weight`]. + /// + /// Killed in [`Self::on_initialize`] and set by the [`block_weight`] logic. #[pallet::storage] #[pallet::whitelist_storage] pub type BlockWeightMode = From 8901237670f58f3f908355133b630dbb68c7ecfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 14 Nov 2025 16:43:26 +0100 Subject: [PATCH 09/30] Fix benchmark --- cumulus/pallets/parachain-system/src/benchmarking.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cumulus/pallets/parachain-system/src/benchmarking.rs b/cumulus/pallets/parachain-system/src/benchmarking.rs index d417029ab1120..ebe3c66785f52 100644 --- a/cumulus/pallets/parachain-system/src/benchmarking.rs +++ b/cumulus/pallets/parachain-system/src/benchmarking.rs @@ -212,6 +212,8 @@ mod benchmarks { fn block_weight_tx_extension_full_core() -> Result<(), BenchmarkError> { let caller = account("caller", 0, 0); + frame_system::Pallet::::set_block_number(1u32.into()); + frame_system::Pallet::::note_inherents_applied(); frame_system::Pallet::::set_extrinsic_index(1); From ef278586bbd610bf2408de3e0180bb6fa684cb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 14 Nov 2025 16:44:45 +0100 Subject: [PATCH 10/30] FMT --- cumulus/pallets/parachain-system/src/block_weight/mod.rs | 5 ++++- .../src/block_weight/transaction_extension.rs | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index 346bd0150b6e7..3afc89d7487ad 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -143,7 +143,10 @@ impl BlockWeightMode { } /// Create new instance of `Self::PotentialFullCore`. - pub(crate) fn potential_full_core(first_transaction_index: Option, target_weight: Weight) -> Self { + pub(crate) fn potential_full_core( + first_transaction_index: Option, + target_weight: Weight, + ) -> Self { Self::PotentialFullCore { context: frame_system::Pallet::::block_number(), first_transaction_index, diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 8e59a080a4e4d..9acf46f37a975 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -16,7 +16,7 @@ use super::{ block_weight_over_target_block_weight, is_first_block_in_core_with_digest, BlockWeightMode, - MaxParachainBlockWeight, LOG_TARGET, FULL_CORE_WEIGHT + MaxParachainBlockWeight, FULL_CORE_WEIGHT, LOG_TARGET, }; use crate::WeightInfo; use alloc::vec::Vec; From 066cd7c3d21742c97a9451b10052ed33ac9c6398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 18 Nov 2025 21:05:38 +0100 Subject: [PATCH 11/30] Support full core weight for pre transactions --- .../parachain-system/src/block_weight/mod.rs | 8 ++- .../src/block_weight/tests.rs | 56 ++++++++++++++++--- .../src/block_weight/transaction_extension.rs | 7 +-- 3 files changed, 57 insertions(+), 14 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index 3afc89d7487ad..bf1ef97b78763 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -220,17 +220,19 @@ impl> Get target_block_weight }; - match crate::BlockWeightMode::::get() { + match crate::BlockWeightMode::::get().filter(|m| !m.is_stale()) { // We allow the full core. Some( BlockWeightMode::::FullCore { .. } | BlockWeightMode::::PotentialFullCore { .. }, ) => FULL_CORE_WEIGHT, // Let's calculate below how much weight we can use. - Some(BlockWeightMode::::FractionOfCore { .. }) => target_block_weight, + Some(BlockWeightMode::::FractionOfCore { first_transaction_index, .. }) + if first_transaction_index.is_some() => + target_block_weight, // If we are in `on_initialize` or at applying the inherents, we allow the maximum block // weight as allowed by the current context. - None => maybe_full_core_weight, + Some(BlockWeightMode::::FractionOfCore { .. }) | None => maybe_full_core_weight, } } } diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index 3fdcb58476ae4..9a0cb06529db9 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -249,6 +249,8 @@ fn tx_extension_large_tx_enables_full_core_usage() { ..Default::default() }; + System::set_extrinsic_index(1); + assert_ok!(TxExtension::validate_and_prepare( TxExtension::new(Default::default()), SystemOrigin::Signed(0).into(), @@ -260,7 +262,7 @@ fn tx_extension_large_tx_enables_full_core_usage() { assert_matches!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::PotentialFullCore { first_transaction_index: Some(0), .. }) + Some(BlockWeightMode::PotentialFullCore { first_transaction_index: Some(1), .. }) ); let mut post_info = @@ -356,6 +358,8 @@ fn tx_extension_large_tx_with_refund_goes_back_to_fractional() { .execute_with(|| { initialize_block_finished(); + System::set_extrinsic_index(1); + // Create a transaction larger than target weight let target_weight = MaximumBlockWeight::target_block_weight(); let large_weight = target_weight @@ -378,7 +382,7 @@ fn tx_extension_large_tx_with_refund_goes_back_to_fractional() { assert_matches!( crate::BlockWeightMode::::get(), - Some(BlockWeightMode::PotentialFullCore { first_transaction_index: Some(0), .. }) + Some(BlockWeightMode::PotentialFullCore { first_transaction_index: Some(1), .. }) ); let mut post_info = PostDispatchInfo { @@ -526,7 +530,6 @@ fn tx_extension_large_tx_after_limit_is_rejected() { Some(BlockWeightMode::fraction_of_core(None)) ); assert!(!has_use_full_core_digest()); - assert_eq!(MaximumBlockWeight::get(), target_weight); }); } @@ -789,7 +792,7 @@ fn executive_with_operational_only_applies_big_inherent() { Default::default(), Default::default(), Default::default(), - Default::default(), + System::digest(), )); let call = @@ -824,10 +827,10 @@ fn block_weight_mode_from_previous_block_is_ignored_in_validate_block() { Default::default(), Default::default(), Default::default(), - Default::default(), + System::digest(), )); - assert!(ExecutiveOnlyOperational::apply_extrinsic(xt).is_ok()); + assert_ok!(ExecutiveOnlyOperational::apply_extrinsic(xt)); ExecutiveOnlyOperational::finalize_block(); @@ -871,7 +874,7 @@ fn ongoin_mbm_requests_full_core() { Default::default(), Default::default(), Default::default(), - Default::default(), + System::digest(), )); assert_eq!( @@ -885,3 +888,42 @@ fn ongoin_mbm_requests_full_core() { MbmOngoing::set(false); }); } + +#[test] +fn ignores_previous_block_weight_in_on_initialize() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + crate::BlockWeightMode::::put( + BlockWeightMode::fraction_of_core(None), + ); + + // Start a new block + System::set_block_number(1); + + assert_eq!(MaximumBlockWeight::get(), FULL_CORE_WEIGHT); + }); +} + +#[test] +fn full_core_weight_in_inherent_context() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + Executive::initialize_block(&Header::new( + 1, + Default::default(), + Default::default(), + Default::default(), + System::digest(), + )); + + assert!(!frame_system::Pallet::::inherents_applied()); + + assert_eq!(MaximumBlockWeight::get(), FULL_CORE_WEIGHT); + }); +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 9acf46f37a975..34e0861236550 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -142,7 +142,7 @@ where match current_mode { // We are already allowing the full core, not that much more to do here. - BlockWeightMode::::FullCore { ..} => {}, + BlockWeightMode::::FullCore { .. } => {}, BlockWeightMode::::PotentialFullCore { first_transaction_index, .. } | BlockWeightMode::::FractionOfCore { first_transaction_index, .. } => { debug_assert!( @@ -155,9 +155,8 @@ where && block_weight_over_target_block_weight::(); let block_weights = Config::BlockWeights::get(); - let target_weight = block_weights.get(info.class).max_total.unwrap_or_else( - || MaxParachainBlockWeight::::target_block_weight_with_digest(&digest).saturating_sub(block_weights.base_block) - ); + let target_weight = MaxParachainBlockWeight::::target_block_weight_with_digest(&digest) + .saturating_sub(block_weights.base_block); // Protection against a misconfiguration as this should be detected by the pre-inherent hook. if block_weight_over_limit { From 0b98e3f39b0c27c57fae28f2fdc7d8aa841d07ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 19 Nov 2025 13:53:53 +0100 Subject: [PATCH 12/30] Fix unsigned transactions --- .../parachain-system/src/block_weight/mock.rs | 14 ++ .../src/block_weight/tests.rs | 132 ++++++++++-------- .../src/block_weight/transaction_extension.rs | 2 + 3 files changed, 92 insertions(+), 56 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs index 13dc037b4652f..c485af0935e5b 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mock.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -166,6 +166,20 @@ pub mod test_pallet { matches!(call, Call::heavy_call_mandatory {}) } } + + #[pallet::validate_unsigned] + impl sp_runtime::traits::ValidateUnsigned for Pallet { + type Call = Call; + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + Ok(ValidTransaction { + priority: u64::max_value(), + requires: Vec::new(), + provides: vec![call.encode()], + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + } + } } #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index 9a0cb06529db9..68fccca3249a0 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -710,74 +710,94 @@ fn ref_time_and_pov_size_cap() { #[test] fn executive_validate_block_handles_normal_transactions() { - TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { - let call = RuntimeCall::TestPallet(test_pallet::Call::heavy_call_normal {}); - - let xt = Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), Default::default()); + for signed in [true, false] { + TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { + let call = RuntimeCall::TestPallet(test_pallet::Call::heavy_call_normal {}); + + let xt = if signed { + Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), Default::default()) + } else { + Extrinsic::new_bare(call) + }; - assert!(Executive::validate_transaction( - TransactionSource::External, - xt.clone(), - Default::default() - ) - .is_ok()); - }); + assert_ok!(Executive::validate_transaction( + TransactionSource::External, + xt.clone(), + Default::default() + )); + }); - TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { - let call = RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_normal {}); + TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { + let call = + RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_normal {}); - let xt = ExtrinsicOnlyOperational::new_signed( - call, - 1u64.into(), - 1u64.into(), - Default::default(), - ); + let xt = if signed { + ExtrinsicOnlyOperational::new_signed( + call, + 1u64.into(), + 1u64.into(), + Default::default(), + ) + } else { + ExtrinsicOnlyOperational::new_bare(call) + }; - assert_eq!( - ExecutiveOnlyOperational::validate_transaction( - TransactionSource::External, - xt, - Default::default() - ) - .unwrap_err(), - InvalidTransaction::ExhaustsResources.into() - ); - }); + assert_eq!( + ExecutiveOnlyOperational::validate_transaction( + TransactionSource::External, + xt, + Default::default() + ) + .unwrap_err(), + InvalidTransaction::ExhaustsResources.into() + ); + }); + } } #[test] fn executive_validate_block_handles_operational_transactions() { - TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { - let call = RuntimeCall::TestPallet(test_pallet::Call::heavy_call_operational {}); - - let xt = Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), Default::default()); + for signed in [true, false] { + TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { + let call = RuntimeCall::TestPallet(test_pallet::Call::heavy_call_operational {}); + + let xt = if signed { + Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), Default::default()) + } else { + Extrinsic::new_bare(call) + }; - assert!(Executive::validate_transaction( - TransactionSource::External, - xt.clone(), - Default::default() - ) - .is_ok()); - }); + assert_ok!(Executive::validate_transaction( + TransactionSource::External, + xt.clone(), + Default::default() + )); + }); - TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { - let call = - RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::heavy_call_operational {}); + TestExtBuilder::new().previous_core_count(3).build().execute_with(|| { + let call = RuntimeCallOnlyOperational::TestPallet( + test_pallet::Call::heavy_call_operational {}, + ); - let xt = ExtrinsicOnlyOperational::new_signed( - call, - 1u64.into(), - 1u64.into(), - Default::default(), - ); + let xt = if signed { + ExtrinsicOnlyOperational::new_signed( + call, + 1u64.into(), + 1u64.into(), + Default::default(), + ) + } else { + ExtrinsicOnlyOperational::new_bare(call) + }; - assert!(ExecutiveOnlyOperational::validate_transaction( - TransactionSource::External, - xt, - Default::default() - ) - .is_ok()); - }); + assert!(ExecutiveOnlyOperational::validate_transaction( + TransactionSource::External, + xt, + Default::default() + ) + .is_ok()); + }); + } } #[test] diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 34e0861236550..fa218dbb353e0 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -472,6 +472,8 @@ where info: &DispatchInfoOf, len: usize, ) -> frame_support::pallet_prelude::TransactionValidity { + Self::pre_validate_extrinsic(info, len)?; + Inner::bare_validate(call, info, len) } From 3499274de9c33b5955045336bfd3e9ec5648a618 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 19 Nov 2025 16:15:58 +0100 Subject: [PATCH 13/30] Respect the dispatch class max weight --- .../parachain-system/src/block_weight/mock.rs | 8 ++- .../parachain-system/src/block_weight/mod.rs | 23 +++++-- .../src/block_weight/tests.rs | 61 +++++++++++++++++++ .../src/block_weight/transaction_extension.rs | 21 +++++-- 4 files changed, 102 insertions(+), 11 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs index c485af0935e5b..0bb3c5f643d35 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mock.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -75,6 +75,7 @@ pub type BlockOnlyOperational = generic::Block< >; pub const TARGET_BLOCK_RATE: u32 = 12; +pub const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); #[docify::export(tx_extension_setup)] pub type TxExtension = DynamicMaxBlockWeight< @@ -108,7 +109,7 @@ mod max_block_weight_setup { weights.base_extrinsic = ExtrinsicBaseWeight::get(); }) .for_class(DispatchClass::Normal, |weights| { - weights.max_total = Some(MaximumBlockWeight::get()); + weights.max_total = Some(MaximumBlockWeight::get() * NORMAL_DISPATCH_RATIO); }) .for_class(DispatchClass::Operational, |weights| { weights.max_total = Some(MaximumBlockWeight::get()); @@ -150,6 +151,11 @@ pub mod test_pallet { pub fn heavy_call_mandatory(_: OriginFor) -> DispatchResult { Ok(()) } + + #[pallet::weight((_weight.clone(), DispatchClass::Normal))] + pub fn use_weight(_: OriginFor, _weight: Weight) -> DispatchResult { + Ok(()) + } } #[pallet::inherent] diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index bf1ef97b78763..8008b63eb5fa3 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -56,6 +56,7 @@ use frame_support::{ }; use frame_system::pallet_prelude::BlockNumberFor; use polkadot_primitives::MAX_POV_SIZE; +use polkadot_runtime_parachains::inclusion::migration::v0::PendingAvailabilityCommitments_Storage_Instance; use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::Digest; @@ -77,6 +78,9 @@ const MAX_REF_TIME_PER_CORE_NS: u64 = 2 * WEIGHT_REF_TIME_PER_SECOND; pub(crate) const FULL_CORE_WEIGHT: Weight = Weight::from_parts(MAX_REF_TIME_PER_CORE_NS, MAX_POV_SIZE as u64); +// Is set to `true` when we are currently inside of `pre_validate_extrinsic`. +environmental::environmental!(inside_pre_validate: bool); + /// The current block weight mode. /// /// Based on this mode [`MaxParachainBlockWeight`] determines the current allowed block weight. @@ -189,7 +193,7 @@ impl> // At maximum we want to allow `6s` of ref time, because we don't want to overload nodes // that are running with standard hardware. These nodes need to be able to import all the - // blocks in 6s. + // blocks in `6s`. let total_ref_time = (number_of_cores as u64) .saturating_mul(MAX_REF_TIME_PER_CORE_NS) .min(WEIGHT_REF_TIME_PER_SECOND * 6); @@ -220,6 +224,13 @@ impl> Get target_block_weight }; + // Check if we are inside `pre_validate_extrinsic` of the transaction extension. + // + // When `pre_validate_extrinsic` calls this code, it is interested to know the + // `target_block_weight` which is then used to calculate the weight for each dispatch class. + // If `FullCore` mode is already enabled, the target weight is not important anymore. + let in_pre_validate = inside_pre_validate::with(|v| *v).unwrap_or(false); + match crate::BlockWeightMode::::get().filter(|m| !m.is_stale()) { // We allow the full core. Some( @@ -228,11 +239,13 @@ impl> Get ) => FULL_CORE_WEIGHT, // Let's calculate below how much weight we can use. Some(BlockWeightMode::::FractionOfCore { first_transaction_index, .. }) - if first_transaction_index.is_some() => + if first_transaction_index.is_some() && !in_pre_validate => target_block_weight, - // If we are in `on_initialize` or at applying the inherents, we allow the maximum block - // weight as allowed by the current context. - Some(BlockWeightMode::::FractionOfCore { .. }) | None => maybe_full_core_weight, + // If we are in `on_initialize`, at applying the inherents or before applying the first + // transaction, we allow the maximum block weight as allowed by the current context. + Some(BlockWeightMode::::FractionOfCore { .. }) | None if !in_pre_validate => + maybe_full_core_weight, + _ => target_block_weight, } } } diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index 68fccca3249a0..1ab5c14b2910d 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -947,3 +947,64 @@ fn full_core_weight_in_inherent_context() { assert_eq!(MaximumBlockWeight::get(), FULL_CORE_WEIGHT); }); } + +#[test] +fn executive_validate_transaction_respects_dispatch_class_max_block_size() { + // Create some weight which is slightly above the allowed dispatch class max size. + let call_weight = TestExtBuilder::new().previous_core_count(4).build().execute_with(|| { + MaximumBlockWeight::target_block_weight() * NORMAL_DISPATCH_RATIO + Weight::from_parts(1, 1) + }); + + for signed in [true, false] { + TestExtBuilder::new().previous_core_count(4).build().execute_with(|| { + assert!(::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap() + .all_lt(call_weight)); + assert!(MaximumBlockWeight::target_block_weight().all_gt(call_weight)); + + let call = + RuntimeCall::TestPallet(test_pallet::Call::use_weight { weight: call_weight }); + + let xt = if signed { + Extrinsic::new_signed(call, 1u64.into(), 1u64.into(), Default::default()) + } else { + Extrinsic::new_bare(call) + }; + + assert_ok!(Executive::validate_transaction( + TransactionSource::External, + xt.clone(), + Default::default() + )); + }); + + TestExtBuilder::new().previous_core_count(4).build().execute_with(|| { + let call = RuntimeCallOnlyOperational::TestPallet(test_pallet::Call::use_weight { + weight: call_weight, + }); + + let xt = if signed { + ExtrinsicOnlyOperational::new_signed( + call, + 1u64.into(), + 1u64.into(), + Default::default(), + ) + } else { + ExtrinsicOnlyOperational::new_bare(call) + }; + + assert_eq!( + ExecutiveOnlyOperational::validate_transaction( + TransactionSource::External, + xt, + Default::default() + ) + .unwrap_err(), + InvalidTransaction::ExhaustsResources.into() + ); + }); + } +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index fa218dbb353e0..017468130caf5 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -15,8 +15,8 @@ // limitations under the License. use super::{ - block_weight_over_target_block_weight, is_first_block_in_core_with_digest, BlockWeightMode, - MaxParachainBlockWeight, FULL_CORE_WEIGHT, LOG_TARGET, + block_weight_over_target_block_weight, inside_pre_validate, is_first_block_in_core_with_digest, + BlockWeightMode, MaxParachainBlockWeight, FULL_CORE_WEIGHT, LOG_TARGET, }; use crate::WeightInfo; use alloc::vec::Vec; @@ -154,9 +154,20 @@ where let block_weight_over_limit = extrinsic_index == 0 && block_weight_over_target_block_weight::(); - let block_weights = Config::BlockWeights::get(); - let target_weight = MaxParachainBlockWeight::::target_block_weight_with_digest(&digest) - .saturating_sub(block_weights.base_block); + // If `BlockWeights` is configured correctly, it will internally call `MaxParachainBlockWeight::get()` + // and by setting this variable to `true`, we tell it the context. This is important as we want to get + // the `target_block_weight` and not the full core weight. Otherwise, we will here get a too huge weight + // and do not set the `PotentialFullCore` weight, leading to `CheckWeight` rejecting the extrinsic. + // + // All of this is only important for extrinsics that will enable the `PotentialFullCore` mode. + let block_weights = inside_pre_validate::using(&mut true, || Config::BlockWeights::get()); + let target_weight = block_weights + .get(info.class) + .max_total + .unwrap_or_else(|| + MaxParachainBlockWeight::::target_block_weight_with_digest(&digest) + .saturating_sub(block_weights.base_block) + ); // Protection against a misconfiguration as this should be detected by the pre-inherent hook. if block_weight_over_limit { From 1cea26874127f20f43ddadf558ea3844846601ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 19 Nov 2025 21:44:08 +0100 Subject: [PATCH 14/30] Fix `on_idle` --- .../parachain-system/src/block_weight/mock.rs | 53 +++++++++++++++++-- .../parachain-system/src/block_weight/mod.rs | 34 ++++++++---- .../src/block_weight/tests.rs | 29 +++++++++- substrate/frame/system/src/lib.rs | 5 ++ 4 files changed, 106 insertions(+), 15 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs index 0bb3c5f643d35..aee3cda9bfebc 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mock.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -15,7 +15,7 @@ // limitations under the License. use super::{transaction_extension::DynamicMaxBlockWeight, *}; -use crate::{self as parachain_system, PreviousCoreCount}; +use crate::{self as parachain_system, MessagingStateSnapshot, PreviousCoreCount}; use codec::Compact; use cumulus_primitives_core::{ BundleInfo, ClaimQueueOffset, CoreInfo, CoreSelector, CumulusDigestItem, @@ -32,6 +32,7 @@ use frame_support::{ }, }; use frame_system::{limits::BlockWeights, CheckWeight}; +use polkadot_primitives::PersistedValidationData; use sp_core::ConstU32; use sp_io; use sp_runtime::{ @@ -121,6 +122,7 @@ mod max_block_weight_setup { #[frame_support::pallet(dev_mode)] pub mod test_pallet { + use super::*; use frame_support::{ dispatch::DispatchClass, pallet_prelude::*, weights::constants::WEIGHT_REF_TIME_PER_SECOND, }; @@ -173,6 +175,17 @@ pub mod test_pallet { } } + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_idle(_block: BlockNumberFor, limit: Weight) -> Weight { + if let Some(max) = OnIdleMaxLeftWeight::get() { + assert!(limit.all_lte(max)); + } + + Weight::zero() + } + } + #[pallet::validate_unsigned] impl sp_runtime::traits::ValidateUnsigned for Pallet { type Call = Call; @@ -231,6 +244,7 @@ construct_runtime!( parameter_types! { pub static MbmOngoing: bool = false; + pub static OnIdleMaxLeftWeight: Option = None; } pub struct Migrator; @@ -295,8 +309,13 @@ pub use only_operational_runtime::{ }; /// Executive: handles dispatch to the various modules. -pub type Executive = - frame_executive::Executive, Runtime, ()>; +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, +>; /// Executive configured to only accept operational transaction to go over the limit. pub type ExecutiveOnlyOperational = frame_executive::Executive< @@ -304,7 +323,7 @@ pub type ExecutiveOnlyOperational = frame_executive::Executive< BlockOnlyOperational, frame_system::ChainContext, RuntimeOnlyOperational, - (), + only_operational_runtime::AllPalletsWithSystem, >; /// Builder for test externalities @@ -409,3 +428,29 @@ pub fn initialize_block_finished() { ::PreInherents::pre_inherents(); System::note_inherents_applied(); } + +/// Fakes the call to `set_validation_data`. +pub fn fake_set_validation_data() { + crate::ValidationData::::put(PersistedValidationData::default()); + crate::HostConfiguration::::put(cumulus_primitives_core::AbridgedHostConfiguration { + max_code_size: 2 * 1024 * 1024, + max_head_data_size: 1024 * 1024, + max_upward_queue_count: 8, + max_upward_queue_size: 1024, + max_upward_message_size: 256, + max_upward_message_num_per_candidate: 5, + hrmp_max_message_num_per_candidate: 5, + validation_upgrade_cooldown: 6, + validation_upgrade_delay: 6, + async_backing_params: cumulus_primitives_core::relay_chain::AsyncBackingParams { + allowed_ancestry_len: 0, + max_candidate_depth: 0, + }, + }); + crate::RelevantMessagingState::::put(MessagingStateSnapshot { + dmq_mqc_head: Default::default(), + relay_dispatch_queue_remaining_capacity: Default::default(), + ingress_channels: Vec::new(), + egress_channels: Vec::new(), + }); +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index 8008b63eb5fa3..ab12ed46c12e4 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -56,7 +56,6 @@ use frame_support::{ }; use frame_system::pallet_prelude::BlockNumberFor; use polkadot_primitives::MAX_POV_SIZE; -use polkadot_runtime_parachains::inclusion::migration::v0::PendingAvailabilityCommitments_Storage_Instance; use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::Digest; @@ -99,6 +98,8 @@ pub enum BlockWeightMode { /// setting, e.g. when running `validate_block`. context: BlockNumberFor, /// The index of the first transaction. + /// + /// Stays `None` for all inherents until there is the first transaction. first_transaction_index: Option, /// The target weight that was used to determine that the extrinsic is above this limit. target_weight: Weight, @@ -112,6 +113,8 @@ pub enum BlockWeightMode { /// setting, e.g. when running `validate_block`. context: BlockNumberFor, /// The index of the first transaction. + /// + /// Stays `None` for all inherents until there is the first transaction. first_transaction_index: Option, }, } @@ -237,15 +240,26 @@ impl> Get BlockWeightMode::::FullCore { .. } | BlockWeightMode::::PotentialFullCore { .. }, ) => FULL_CORE_WEIGHT, - // Let's calculate below how much weight we can use. - Some(BlockWeightMode::::FractionOfCore { first_transaction_index, .. }) - if first_transaction_index.is_some() && !in_pre_validate => - target_block_weight, - // If we are in `on_initialize`, at applying the inherents or before applying the first - // transaction, we allow the maximum block weight as allowed by the current context. - Some(BlockWeightMode::::FractionOfCore { .. }) | None if !in_pre_validate => - maybe_full_core_weight, - _ => target_block_weight, + // We are in `pre_validate`. + _ if in_pre_validate => target_block_weight, + // Only use the fraction of a core. + Some(BlockWeightMode::::FractionOfCore { first_transaction_index, .. }) => { + let is_phase_finalization = frame_system::Pallet::::execution_phase() + .map_or(false, |p| matches!(p, frame_system::Phase::Finalization)); + + if first_transaction_index.is_none() && !is_phase_finalization { + // We are running in the context of inherents or `on_poll`, here we allow the + // full core weight. + maybe_full_core_weight + } else { + // If we are finalizing the block (e.g. `on_idle` is running and + // `finalize_block`) or nothing required more than the target block weight, we + // only allow the target block weight. + target_block_weight + } + }, + // We are in `on_initialize` or in an offchain context. + None => maybe_full_core_weight, } } } diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index 1ab5c14b2910d..2ff20fd973739 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -852,6 +852,8 @@ fn block_weight_mode_from_previous_block_is_ignored_in_validate_block() { assert_ok!(ExecutiveOnlyOperational::apply_extrinsic(xt)); + fake_set_validation_data(); + ExecutiveOnlyOperational::finalize_block(); assert_eq!( @@ -882,7 +884,7 @@ fn block_weight_mode_from_previous_block_is_ignored_in_validate_block() { } #[test] -fn ongoin_mbm_requests_full_core() { +fn ongoing_mbm_requests_full_core() { TestExtBuilder::new() .number_of_cores(2) .first_block_in_core(true) @@ -902,6 +904,8 @@ fn ongoin_mbm_requests_full_core() { ::BlockWeights::get().max_block ); + fake_set_validation_data(); + ExecutiveOnlyOperational::finalize_block(); assert!(has_use_full_core_digest()); @@ -1008,3 +1012,26 @@ fn executive_validate_transaction_respects_dispatch_class_max_block_size() { }); } } + +#[test] +fn on_idle_uses_correct_weight() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + Executive::initialize_block(&Header::new( + 1, + Default::default(), + Default::default(), + Default::default(), + System::digest(), + )); + + fake_set_validation_data(); + + OnIdleMaxLeftWeight::set(Some(MaximumBlockWeight::target_block_weight())); + + Executive::finalize_block(); + }); +} diff --git a/substrate/frame/system/src/lib.rs b/substrate/frame/system/src/lib.rs index c9eb684a1c3b1..a1924219a8aaa 100644 --- a/substrate/frame/system/src/lib.rs +++ b/substrate/frame/system/src/lib.rs @@ -1884,6 +1884,11 @@ impl Pallet { AllExtrinsicsLen::::get().unwrap_or_default() } + /// Returns the current active execution phase. + pub fn execution_phase() -> Option { + ExecutionPhase::::get() + } + /// Inform the system pallet of some additional weight that should be accounted for, in the /// current block. /// From 5ed9e2908edef61917ec47dd09ebc77d4b9560ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 20 Nov 2025 12:18:40 +0100 Subject: [PATCH 15/30] Test `on_poll` --- .../parachain-system/src/block_weight/mock.rs | 11 ++++++++- .../parachain-system/src/block_weight/mod.rs | 10 ++++---- .../src/block_weight/tests.rs | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+), 5 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs index aee3cda9bfebc..ac1a9bcdd4845 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mock.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -124,7 +124,9 @@ mod max_block_weight_setup { pub mod test_pallet { use super::*; use frame_support::{ - dispatch::DispatchClass, pallet_prelude::*, weights::constants::WEIGHT_REF_TIME_PER_SECOND, + dispatch::DispatchClass, + pallet_prelude::*, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, WeightMeter}, }; use frame_system::pallet_prelude::*; @@ -184,6 +186,12 @@ pub mod test_pallet { Weight::zero() } + + fn on_poll(_n: BlockNumberFor, weight: &mut WeightMeter) { + if let Some(max) = OnPollMaxLeftWeight::get() { + assert!(weight.remaining().all_lte(max)); + } + } } #[pallet::validate_unsigned] @@ -245,6 +253,7 @@ construct_runtime!( parameter_types! { pub static MbmOngoing: bool = false; pub static OnIdleMaxLeftWeight: Option = None; + pub static OnPollMaxLeftWeight: Option = None; } pub struct Migrator; diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index ab12ed46c12e4..9fe8502e59e78 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -246,15 +246,17 @@ impl> Get Some(BlockWeightMode::::FractionOfCore { first_transaction_index, .. }) => { let is_phase_finalization = frame_system::Pallet::::execution_phase() .map_or(false, |p| matches!(p, frame_system::Phase::Finalization)); + let inherents_applied = frame_system::Pallet::::inherents_applied(); - if first_transaction_index.is_none() && !is_phase_finalization { - // We are running in the context of inherents or `on_poll`, here we allow the + if first_transaction_index.is_none() && !is_phase_finalization && !inherents_applied + { + // We are running in the context of inherents, here we allow the // full core weight. maybe_full_core_weight } else { // If we are finalizing the block (e.g. `on_idle` is running and - // `finalize_block`) or nothing required more than the target block weight, we - // only allow the target block weight. + // `finalize_block`), running `on_poll` or nothing required more than the target + // block weight, we only allow the target block weight. target_block_weight } }, diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index 2ff20fd973739..353dcdf53a338 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -1035,3 +1035,26 @@ fn on_idle_uses_correct_weight() { Executive::finalize_block(); }); } + +#[test] +fn on_poll_uses_correct_weight() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + Executive::initialize_block(&Header::new( + 1, + Default::default(), + Default::default(), + Default::default(), + System::digest(), + )); + + fake_set_validation_data(); + + OnPollMaxLeftWeight::set(Some(MaximumBlockWeight::target_block_weight())); + + Executive::finalize_block(); + }); +} From 3404f67ccebe536bca738ec52f565271800da66a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 20 Nov 2025 12:29:37 +0100 Subject: [PATCH 16/30] More docs --- .../parachain-system/src/block_weight/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index 9fe8502e59e78..39c97cf9975a3 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -45,6 +45,24 @@ //! //! Registering of the `PreInherents` hook: #![doc = docify::embed!("src/block_weight/mock.rs", pre_inherents_setup)] +//! # Weight per context +//! +//! Depending on the context, [`MaxParachainBlockWeight`] may returns a different max weight. The +//! max weight is only allowed to change in the first block of a core. Otherwise, all blocks need to +//! follow the target block weight determined based on the number of cores and the target block +//! rate. In the case of a first block, the following contexts may allow to access the full core +//! weight: +//! +//! - `on_initialize`: All logic that runs in this context up to the execution of `inherents` will +//! get access to the full core weight. +//! - `inherents`: Inherents also have access to the full core weight. +//! - `on_poll`: Only gets access to the target block weight. +//! - `transactions`: May get access to the full core weight, depends if they enable the access to +//! the full core weight based on the logic of [`DynamicMaxBlockWeight`]. +//! - `on_finalize`/`on_idle`: Only gets access to the target block weight. +//! +//! If any context that allows to use the full core weight, pushes the used block weight above the +//! target block weight, all other contexts will get access to the full core weight. use crate::{Config, PreviousCoreCount}; use codec::{Decode, Encode}; From 836b0915d6a862fd6f468e53d104adbde705e936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 3 Dec 2025 15:13:17 +0100 Subject: [PATCH 17/30] Fix test --- .../src/block_weight/transaction_extension.rs | 30 ++++++++++++------- .../src/validate_block/tests.rs | 6 +--- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 017468130caf5..088e77ed45cae 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -46,8 +46,13 @@ use sp_runtime::{ /// 1. Only the first block of a core is allowed to change its block weight. /// /// 2. Any `inherent` or any transaction up to `MAX_TRANSACTION_TO_CONSIDER` requires more block -/// weight than the target block weight. Target block weight is the max weight for the respective -/// extrinsic class. +/// weight than the target extrinsic weight. Target extrinsic weight is the max weight for the +/// respective extrinsic class. The priority to determine the target e weight is the following, we +/// start checking if +/// [`WeightsPerClass::max_extrinsic`](frame_system::limits::WeightsPerClass::max_extrinsic) is +/// set, after this +/// [`WeightsPerClass::max_total`](frame_system::limits::WeightsPerClass::max_total) and if both +/// of these are `None` we fall back to the actual target block weight. /// /// Because the node is tracking the wall clock time while building a block to abort block /// production if it takes too long, we do not allow any block to change the block weight. The node @@ -161,13 +166,18 @@ where // // All of this is only important for extrinsics that will enable the `PotentialFullCore` mode. let block_weights = inside_pre_validate::using(&mut true, || Config::BlockWeights::get()); - let target_weight = block_weights - .get(info.class) - .max_total - .unwrap_or_else(|| - MaxParachainBlockWeight::::target_block_weight_with_digest(&digest) - .saturating_sub(block_weights.base_block) - ); + let class_weights = block_weights.get(info.class); + let target_block_weight = + MaxParachainBlockWeight::::target_block_weight_with_digest(&digest) + .saturating_sub(block_weights.base_block); + + // `max_extrinsic` determines the maximum weight allowed for one transaction. + // If that isn't set, we fall back to `max_total` which represents the total allowed weight for + // this dispatch class. If all previous weights are `None`, we fall back to the target block weight. + let target_weight = class_weights + .max_extrinsic + .or(class_weights.max_total) + .unwrap_or(target_block_weight); // Protection against a misconfiguration as this should be detected by the pre-inherent hook. if block_weight_over_limit { @@ -214,7 +224,7 @@ where "Enabling `PotentialFullCore` mode for extrinsic", ); - *mode = Some(BlockWeightMode::::potential_full_core ( + *mode = Some(BlockWeightMode::::potential_full_core( // While applying inherents `extrinsic_index` and `first_transaction_index` will be `None`. // When the first transaction is applied, we want to store the index. first_transaction_index.or(transaction_index), diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index 2c68fb63fa4df..aea995ce7394a 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -16,7 +16,7 @@ use crate::{validate_block::MemoryOptimizedValidationParams, *}; use codec::{Decode, DecodeAll, Encode}; -use cumulus_primitives_core::{relay_chain, ParachainBlockData, PersistedValidationData}; +use cumulus_primitives_core::{relay_chain, ParachainBlockData, PersistedValidationData, relay_chain::{UMPSignal, UMP_SEPARATOR}, ClaimQueueOffset, CoreInfo, CoreSelector, }; use cumulus_test_client::{ generate_extrinsic, generate_extrinsic_with_pair, runtime::{ @@ -615,10 +615,6 @@ fn state_changes_in_multiple_blocks_are_applied_in_exact_order() { #[test] fn validate_block_handles_ump_signal() { - use cumulus_primitives_core::{ - relay_chain::{UMPSignal, UMP_SEPARATOR}, - ClaimQueueOffset, CoreInfo, CoreSelector, - }; sp_tracing::try_init_simple(); let (client, parent_head) = create_elastic_scaling_test_client(); From 915f6ae80ca370e80310fbc52d6b90b2accf9379 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 4 Dec 2025 12:49:41 +0000 Subject: [PATCH 18/30] Update from github-actions[bot] running command 'fmt' --- .../src/block_weight/transaction_extension.rs | 4 ++-- .../pallets/parachain-system/src/validate_block/tests.rs | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 088e77ed45cae..757e323d5a4fa 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -47,8 +47,8 @@ use sp_runtime::{ /// /// 2. Any `inherent` or any transaction up to `MAX_TRANSACTION_TO_CONSIDER` requires more block /// weight than the target extrinsic weight. Target extrinsic weight is the max weight for the -/// respective extrinsic class. The priority to determine the target e weight is the following, we -/// start checking if +/// respective extrinsic class. The priority to determine the target e weight is the following, +/// we start checking if /// [`WeightsPerClass::max_extrinsic`](frame_system::limits::WeightsPerClass::max_extrinsic) is /// set, after this /// [`WeightsPerClass::max_total`](frame_system::limits::WeightsPerClass::max_total) and if both diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index aea995ce7394a..78e8f3926d173 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -16,7 +16,11 @@ use crate::{validate_block::MemoryOptimizedValidationParams, *}; use codec::{Decode, DecodeAll, Encode}; -use cumulus_primitives_core::{relay_chain, ParachainBlockData, PersistedValidationData, relay_chain::{UMPSignal, UMP_SEPARATOR}, ClaimQueueOffset, CoreInfo, CoreSelector, }; +use cumulus_primitives_core::{ + relay_chain, + relay_chain::{UMPSignal, UMP_SEPARATOR}, + ClaimQueueOffset, CoreInfo, CoreSelector, ParachainBlockData, PersistedValidationData, +}; use cumulus_test_client::{ generate_extrinsic, generate_extrinsic_with_pair, runtime::{ From ee19d58f9d50edcdc055700c91009a10f55ec57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 10 Dec 2025 17:32:29 +0100 Subject: [PATCH 19/30] Ensure we support uneven number of blocks --- .../parachain-system/src/block_weight/mod.rs | 30 +++++++++++-------- .../src/block_weight/tests.rs | 11 +++++++ 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index 39c97cf9975a3..43d7e051822a5 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -203,29 +203,33 @@ impl> let number_of_cores = CumulusDigestItem::find_core_info(&digest).map_or_else( || PreviousCoreCount::::get().map_or(1, |pc| pc.0), |ci| ci.number_of_cores.0, - ) as u32; + ) as u64; - let target_blocks = TargetBlockRate::get(); + let target_blocks = TargetBlockRate::get() as u64; // Ensure we have at least one core and valid target blocks if number_of_cores == 0 || target_blocks == 0 { return FULL_CORE_WEIGHT; } + let blocks_per_core = target_blocks.div_ceil(number_of_cores); + + let ref_time_per_block = MAX_REF_TIME_PER_CORE_NS / blocks_per_core; + // At maximum we want to allow `6s` of ref time, because we don't want to overload nodes // that are running with standard hardware. These nodes need to be able to import all the // blocks in `6s`. - let total_ref_time = (number_of_cores as u64) - .saturating_mul(MAX_REF_TIME_PER_CORE_NS) - .min(WEIGHT_REF_TIME_PER_SECOND * 6); - let ref_time_per_block = total_ref_time - .saturating_div(target_blocks as u64) - .min(MAX_REF_TIME_PER_CORE_NS); - - let total_pov_size = (number_of_cores as u64).saturating_mul(MAX_POV_SIZE as u64); - // Each block at max gets one core. - let proof_size_per_block = - total_pov_size.saturating_div(target_blocks as u64).min(MAX_POV_SIZE as u64); + let total_ref_time = ref_time_per_block * target_blocks; + let ref_time_per_block = if total_ref_time > 6 * WEIGHT_REF_TIME_PER_SECOND { + ref_time_per_block - + (total_ref_time - 6 * WEIGHT_REF_TIME_PER_SECOND).div_ceil(target_blocks) + } else { + ref_time_per_block + }; + + // PoV size we can use as much as we can get from the cores, but at maximum it is one block + // per core. Or in other words, one block can not span across multiple cores. + let proof_size_per_block = MAX_POV_SIZE as u64 / blocks_per_core; Weight::from_parts(ref_time_per_block, proof_size_per_block) } diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index 353dcdf53a338..aa3c8ba6b9ba1 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -106,6 +106,17 @@ fn test_zero_cores() { }); } +#[test] +fn test_uneven_number_of_blocks_on_even_number_of_cores() { + TestExtBuilder::new().number_of_cores(2).build().execute_with(|| { + let weight = MaxParachainBlockWeight::>::get(); + + // Each block should get half of a core. + assert_eq!(weight.ref_time(), WEIGHT_REF_TIME_PER_SECOND); + assert_eq!(weight.proof_size(), MAX_POV_SIZE as u64 / 2); + }); +} + #[test] fn test_zero_target_blocks() { TestExtBuilder::new().number_of_cores(2).build().execute_with(|| { From 001c7de0e00ad8f2828cdff391353feb8198c41c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 11 Dec 2025 08:54:48 +0100 Subject: [PATCH 20/30] Make clippy happy --- cumulus/pallets/parachain-system/src/block_weight/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs index ac1a9bcdd4845..7dba292f55de3 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mock.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -156,7 +156,7 @@ pub mod test_pallet { Ok(()) } - #[pallet::weight((_weight.clone(), DispatchClass::Normal))] + #[pallet::weight((*_weight, DispatchClass::Normal))] pub fn use_weight(_: OriginFor, _weight: Weight) -> DispatchResult { Ok(()) } From ea5c5d989a70949fe67332a0bc3ab6f46535e708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 15 Dec 2025 14:13:40 +0100 Subject: [PATCH 21/30] Ensure we check the block weight after executing a transaction --- .../src/block_weight/tests.rs | 60 ++++++++++++++++++- .../src/block_weight/transaction_extension.rs | 9 ++- 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index aa3c8ba6b9ba1..be27cb89049c9 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -35,9 +35,10 @@ use sp_runtime::{ Digest, }; -type TxExtension = DynamicMaxBlockWeight, ConstU32<4>>; +type TxExtension = + DynamicMaxBlockWeight, ConstU32>; type TxExtensionOnlyOperational = - DynamicMaxBlockWeight, ConstU32<4>, 10, false>; + DynamicMaxBlockWeight, ConstU32, 10, false>; type MaximumBlockWeight = MaxParachainBlockWeight>; #[test] @@ -1069,3 +1070,58 @@ fn on_poll_uses_correct_weight() { Executive::finalize_block(); }); } + +// This test ensures when a transaction enables `PotentialFullCore` in `pre-dispatch`, but in post +// dispatch the transaction has a lower weight we don't go back to `FractionalCore` if the total +// block weight is above the target block weight. +#[test] +fn post_dispatch_is_taking_block_weight_into_account() { + TestExtBuilder::new() + .number_of_cores(2) + .first_block_in_core(true) + .build() + .execute_with(|| { + initialize_block_finished(); + System::set_extrinsic_index(1); + + let target_weight = MaximumBlockWeight::target_block_weight(); + + let sixty_percent = Weight::from_parts( + target_weight.ref_time() * 60 / 100, + target_weight.proof_size() * 60 / 100, + ); + + // Simulate on_initialize using 60% of target weight + register_weight(sixty_percent, DispatchClass::Mandatory); + + let info = DispatchInfo { + call_weight: target_weight, + class: DispatchClass::Normal, + ..Default::default() + }; + + assert_ok!(TxExtension::validate_and_prepare( + TxExtension::new(Default::default()), + SystemOrigin::Signed(0).into(), + &CALL, + &info, + 100, + 0, + )); + + assert_matches!( + crate::BlockWeightMode::::get(), + Some(BlockWeightMode::PotentialFullCore { .. }) + ); + + // Post-dispatch with actual_weight = 60% of target + let mut post_info = PostDispatchInfo { + actual_weight: Some(sixty_percent), + pays_fee: Default::default(), + }; + + assert_ok!(TxExtension::post_dispatch((), &info, &mut post_info, 0, &Ok(()))); + + assert!(has_use_full_core_digest()); + }); +} diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 757e323d5a4fa..bb850aeec30b9 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -321,8 +321,7 @@ where Config::WeightInfo::block_weight_tx_extension_stays_fraction_of_core(), ) }, - // Now we need to check if the transaction required more weight than a fraction of a - // core block. + // Now we check if the transaction required more weight than the target weight. BlockWeightMode::::PotentialFullCore { first_transaction_index, target_weight, @@ -331,7 +330,11 @@ where let block_weight = frame_system::BlockWeight::::get(); let extrinsic_class_weight = block_weight.get(info.class); - if extrinsic_class_weight.any_gt(*target_weight) { + // The transaction weight after execution is may not above the target weight, + // but the full block weight is maybe now above the target weight. + if extrinsic_class_weight.any_gt(*target_weight) || + block_weight_over_target_block_weight::() + { log::trace!( target: LOG_TARGET, "Extrinsic class weight {extrinsic_class_weight:?} above target weight {target_weight:?}, enabling `FullCore` mode." From a92ca4305f2aa384141fdbe37c70b789b8077135 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Fri, 6 Feb 2026 17:43:37 +0100 Subject: [PATCH 22/30] Review feedback --- .../parachain-system/src/block_weight/mod.rs | 33 +++++++++-------- .../src/block_weight/pre_inherents_hook.rs | 4 +- .../src/block_weight/transaction_extension.rs | 37 ++++++++----------- cumulus/primitives/core/src/lib.rs | 8 +++- 4 files changed, 42 insertions(+), 40 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index 43d7e051822a5..52c124495d8e5 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -16,7 +16,7 @@ //! Provides functionality to dynamically calculate the block weight for a parachain. //! -//! With block bundling, parachains are relative free to choose whatever block interval they want. +//! With block bundling, parachains are relatively free to choose whatever block interval they want. //! The block interval is the time between individual blocks. The available resources per block (max //! block weight) depend on the number of cores allocated to the parachain on the relay chain. Each //! relay chain cores provides an execution time of `2s` and a storage size of `10MiB`. Depending on @@ -47,7 +47,7 @@ #![doc = docify::embed!("src/block_weight/mock.rs", pre_inherents_setup)] //! # Weight per context //! -//! Depending on the context, [`MaxParachainBlockWeight`] may returns a different max weight. The +//! Depending on the context, [`MaxParachainBlockWeight`] may return a different max weight. The //! max weight is only allowed to change in the first block of a core. Otherwise, all blocks need to //! follow the target block weight determined based on the number of cores and the target block //! rate. In the case of a first block, the following contexts may allow to access the full core @@ -73,7 +73,7 @@ use frame_support::{ CloneNoBound, DebugNoBound, }; use frame_system::pallet_prelude::BlockNumberFor; -use polkadot_primitives::MAX_POV_SIZE; +use polkadot_primitives::{executor_params::DEFAULT_BACKING_EXECUTION_TIMEOUT, MAX_POV_SIZE}; use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::Digest; @@ -90,12 +90,16 @@ pub use transaction_extension::DynamicMaxBlockWeight; const LOG_TARGET: &str = "runtime::parachain-system::block-weight"; /// Maximum ref time per core -const MAX_REF_TIME_PER_CORE_NS: u64 = 2 * WEIGHT_REF_TIME_PER_SECOND; +const MAX_REF_TIME_PER_CORE_NS: u64 = + DEFAULT_BACKING_EXECUTION_TIMEOUT.as_secs() * WEIGHT_REF_TIME_PER_SECOND; /// The available weight per core on the relay chain. pub(crate) const FULL_CORE_WEIGHT: Weight = Weight::from_parts(MAX_REF_TIME_PER_CORE_NS, MAX_POV_SIZE as u64); // Is set to `true` when we are currently inside of `pre_validate_extrinsic`. +// +// Forces `MaxParachainBlockWeight::get()` to return fractional weight, enabling detection of +// transactions that exceed the fractional target limit. environmental::environmental!(inside_pre_validate: bool); /// The current block weight mode. @@ -214,18 +218,13 @@ impl> let blocks_per_core = target_blocks.div_ceil(number_of_cores); - let ref_time_per_block = MAX_REF_TIME_PER_CORE_NS / blocks_per_core; - // At maximum we want to allow `6s` of ref time, because we don't want to overload nodes // that are running with standard hardware. These nodes need to be able to import all the // blocks in `6s`. - let total_ref_time = ref_time_per_block * target_blocks; - let ref_time_per_block = if total_ref_time > 6 * WEIGHT_REF_TIME_PER_SECOND { - ref_time_per_block - - (total_ref_time - 6 * WEIGHT_REF_TIME_PER_SECOND).div_ceil(target_blocks) - } else { - ref_time_per_block - }; + let ref_time_per_block = core::cmp::min( + MAX_REF_TIME_PER_CORE_NS / blocks_per_core, // Core allocation limit + (6 * WEIGHT_REF_TIME_PER_SECOND) / target_blocks, // Full node import limit + ); // PoV size we can use as much as we can get from the cores, but at maximum it is one block // per core. Or in other words, one block can not span across multiple cores. @@ -252,8 +251,12 @@ impl> Get // Check if we are inside `pre_validate_extrinsic` of the transaction extension. // // When `pre_validate_extrinsic` calls this code, it is interested to know the - // `target_block_weight` which is then used to calculate the weight for each dispatch class. - // If `FullCore` mode is already enabled, the target weight is not important anymore. + // fractional `target_block_weight` which is then used to calculate the weight for each + // dispatch class. Fractional weight is returned to detect transactions exceeding the + // fractional target, enabling proper transition to `PotentialFullCore` mode. + // + // If `FullCore` mode is already enabled, the fractional target weight is not important + // anymore. let in_pre_validate = inside_pre_validate::with(|v| *v).unwrap_or(false); match crate::BlockWeightMode::::get().filter(|m| !m.is_stale()) { diff --git a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs index 4f9074a30eb32..6103bb03c6cc8 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs @@ -65,7 +65,7 @@ where if !is_first_block_in_core { log::error!( target: LOG_TARGET, - "Inherent block logic took longer than the target block weight, THIS IS A BUG!!!", + "Block initialization logic took longer than the target block weight, THIS IS A BUG!!!", ); // We are already above the allowed maximum and do not want to accept any more @@ -77,7 +77,7 @@ where } else { log::debug!( target: LOG_TARGET, - "Inherent block logic took longer than the target block weight, going to use the full core", + "Block initialization logic took longer than the target block weight, going to use the full core", ); } diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index bb850aeec30b9..11fe9411c28af 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -161,7 +161,7 @@ where // If `BlockWeights` is configured correctly, it will internally call `MaxParachainBlockWeight::get()` // and by setting this variable to `true`, we tell it the context. This is important as we want to get - // the `target_block_weight` and not the full core weight. Otherwise, we will here get a too huge weight + // the fractional `target_block_weight` and not the full core weight. Otherwise, we will here get a too huge weight // and do not set the `PotentialFullCore` weight, leading to `CheckWeight` rejecting the extrinsic. // // All of this is only important for extrinsics that will enable the `PotentialFullCore` mode. @@ -209,11 +209,10 @@ where .any_gt(target_weight) { // When `ALLOW_NORMAL` is `true`, we want to allow all classes of transactions. Inherents are always allowed. - let class_allowed = if ALLOW_NORMAL { true } else { info.class == DispatchClass::Operational } - || info.class == DispatchClass::Mandatory; + let class_allowed = ALLOW_NORMAL || matches!(info.class, DispatchClass::Operational | DispatchClass::Mandatory); // If the `BundleInfo` digest is not set (function returns `None`), it means we are in some offchain - // call like `validate_block`. In this case we assume this is the first block, otherwise these big + // call like `validate_transaction`. In this case we assume this is the first block, otherwise these big // transactions will never be able to enter the tx pool. let is_first_block = is_first_block_in_core_with_digest(&digest).unwrap_or(true); @@ -238,18 +237,18 @@ where return Err(InvalidTransaction::ExhaustsResources) } - } else if is_potential { - log::trace!( - target: LOG_TARGET, - "Resetting back to `FractionOfCore`" - ); - *mode = - Some(BlockWeightMode::::fraction_of_core(first_transaction_index.or(transaction_index))); } else { - log::trace!( - target: LOG_TARGET, - "Not changing block weight mode" - ); + if is_potential { + log::trace!( + target: LOG_TARGET, + "Resetting back to `FractionOfCore`" + ); + } else { + log::trace!( + target: LOG_TARGET, + "Not changing block weight mode" + ); + } *mode = Some(BlockWeightMode::::fraction_of_core(first_transaction_index.or(transaction_index))); @@ -276,12 +275,8 @@ where .saturating_sub(Config::WeightInfo::block_weight_tx_extension_full_core()), BlockWeightMode::::FractionOfCore { .. } => { let digest = frame_system::Pallet::::digest(); - let target_block_weight = - MaxParachainBlockWeight::::target_block_weight_with_digest(&digest); - - let is_above_limit = frame_system::Pallet::::remaining_block_weight() - .consumed() - .any_gt(target_block_weight); + let is_above_limit = + block_weight_over_target_block_weight::(); // If we are above the limit, it means the transaction used more weight than // what it had announced, which should not happen. diff --git a/cumulus/primitives/core/src/lib.rs b/cumulus/primitives/core/src/lib.rs index 13ca4ba200903..602ab1f2dcaa8 100644 --- a/cumulus/primitives/core/src/lib.rs +++ b/cumulus/primitives/core/src/lib.rs @@ -253,8 +253,8 @@ pub struct BundleInfo { pub index: u8, /// Is this the last block in the bundle from the point of view of the node? /// - /// It is possible that at `index` zero the runtime outputs the - /// [`CumulusDigestItem::UseFullCore`] that informs the node to use an entire for one block + /// It is possible that the runtime outputs the + /// [`CumulusDigestItem::UseFullCore`] to inform the node to use an entire for one block /// only. pub maybe_last: bool, } @@ -303,6 +303,10 @@ pub enum CumulusDigestItem { /// A digest item informing the node that this block should be put alone onto a core. /// /// In other words, the core should not be shared with other blocks. + /// + /// Under certain conditions (mainly runtime misconfigurations) the digest is still set when + /// there are muliple blocks per core. This is done to communicate to the collator that block + /// production for this core should be stopped. #[codec(index = 3)] UseFullCore, } From 535054f4735935d69013e6bf23916774b547a3b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Mon, 9 Feb 2026 22:07:41 +0100 Subject: [PATCH 23/30] Ensure UMP + HRMP messages fit into the limits --- .../parachain-system/src/block_weight/mod.rs | 2 - .../src/block_weight/pre_inherents_hook.rs | 2 +- .../src/block_weight/transaction_extension.rs | 5 +- cumulus/pallets/parachain-system/src/lib.rs | 60 +++++++++- .../src/validate_block/tests.rs | 107 +++++++++++++++++- cumulus/primitives/core/src/lib.rs | 9 +- cumulus/test/client/src/block_builder.rs | 4 +- cumulus/test/runtime/src/lib.rs | 2 +- cumulus/test/runtime/src/test_pallet.rs | 36 ++++++ 9 files changed, 213 insertions(+), 14 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index 52c124495d8e5..d594b0aa2a7cc 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -39,10 +39,8 @@ //! //! Setup the transaction extension: #![doc = docify::embed!("src/block_weight/mock.rs", tx_extension_setup)] -//! //! Setting up `MaximumBlockWeight`: #![doc = docify::embed!("src/block_weight/mock.rs", max_block_weight_setup)] -//! //! Registering of the `PreInherents` hook: #![doc = docify::embed!("src/block_weight/mock.rs", pre_inherents_setup)] //! # Weight per context diff --git a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs index 6103bb03c6cc8..4cb4af9b32530 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs @@ -57,7 +57,7 @@ where crate::BlockWeightMode::::put(new_mode); - return + return; } let is_first_block_in_core = is_first_block_in_core::().unwrap_or(false); diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 11fe9411c28af..f896b041f9697 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -270,9 +270,10 @@ where match mode { // If the previous mode was already `FullCore`, we are fine. - BlockWeightMode::::FullCore { .. } => + BlockWeightMode::::FullCore { .. } => { Config::WeightInfo::block_weight_tx_extension_max_weight() - .saturating_sub(Config::WeightInfo::block_weight_tx_extension_full_core()), + .saturating_sub(Config::WeightInfo::block_weight_tx_extension_full_core()) + }, BlockWeightMode::::FractionOfCore { .. } => { let digest = frame_system::Pallet::::digest(); let is_above_limit = diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 230be658436b5..1efc1ee7f96ac 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -112,6 +112,21 @@ pub use pallet::*; const LOG_TARGET: &str = "runtime::parachain-system"; +/// Tracks cumulative UMP and HRMP messages sent across blocks within a single PoV. +#[derive(Encode, Decode, Clone, Debug, TypeInfo, Default)] +pub struct PoVMessages { + /// Relay parent storage root of the current PoV. + pub relay_storage_root_or_hash: relay_chain::Hash, + /// The core selector of the current Pov. + pub core_selector: u8, + /// The bundle index of the current PoV. `None` when `BundleInfo` digest is absent. + pub bundle_index: u8, + /// Cumulative count of UMP messages sent in this PoV. + pub ump_msg_count: u32, + /// Cumulative count of HRMP outbound messages sent in this PoV. + pub hrmp_outbound_count: u32, +} + /// Something that can check the associated relay block number. /// /// Each Parachain block is built in the context of a relay chain block, this trait allows us @@ -315,6 +330,29 @@ pub mod pallet { // unincluded segment. Self::adjust_egress_bandwidth_limits(); + let current_core_selector = + CumulusDigestItem::find_core_info(&frame_system::Pallet::::digest()) + .map_or(0, |ci| ci.selector.0); + + let current_bundle_index = + CumulusDigestItem::find_bundle_info(&frame_system::Pallet::::digest()) + .map_or(0, |bi| bi.index); + + let mut pov_tracker = PoVMessagesTracker::::get() + .filter(|tracker| { + // If the relay parent changes, this is for sure a different `PoV`. + tracker.relay_storage_root_or_hash == vfp.relay_parent_storage_root && + // A different core selector also means we are on a different `PoV`. + tracker.core_selector == current_core_selector && + // The bundle index needs to increase, or we are in a different `PoV`. + current_bundle_index > tracker.bundle_index + }) + .unwrap_or_default(); + + pov_tracker.bundle_index = current_bundle_index; + pov_tracker.core_selector = current_core_selector; + pov_tracker.relay_storage_root_or_hash = vfp.relay_parent_storage_root; + let (ump_msg_count, ump_total_bytes) = >::mutate(|up| { let (available_capacity, available_size) = match RelevantMessagingState::::get() { @@ -332,8 +370,12 @@ pub mod pallet { }, }; - let available_capacity = - cmp::min(available_capacity, host_config.max_upward_message_num_per_candidate); + let available_capacity = cmp::min( + available_capacity, + host_config + .max_upward_message_num_per_candidate + .saturating_sub(pov_tracker.ump_msg_count), + ); // Count the number of messages we can possibly fit in the given constraints, i.e. // available_capacity and available_size. @@ -362,6 +404,8 @@ pub mod pallet { UpwardMessages::::put(&up[..num as usize]); *up = up.split_off(num as usize); + pov_tracker.ump_msg_count = pov_tracker.ump_msg_count.saturating_add(num); + if let Some(core_info) = CumulusDigestItem::find_core_info(&frame_system::Pallet::::digest()) { @@ -407,6 +451,9 @@ pub mod pallet { .min(>::take()) as usize; + let maximum_channels = + maximum_channels.saturating_sub(pov_tracker.hrmp_outbound_count as usize); + // Note: this internally calls the `GetChannelInfo` implementation for this // pallet, which draws on the `RelevantMessagingState`. That in turn has // been adjusted above to reflect the correct limits in all channels. @@ -416,6 +463,10 @@ pub mod pallet { .map(|(recipient, data)| OutboundHrmpMessage { recipient, data }) .collect::>(); + pov_tracker.hrmp_outbound_count = + pov_tracker.hrmp_outbound_count.saturating_add(outbound_messages.len() as u32); + PoVMessagesTracker::::put(pov_tracker); + // Update the unincluded segment length; capacity checks were done previously in // `set_validation_data`, so this can be done unconditionally. { @@ -454,6 +505,7 @@ pub mod pallet { // Check in `on_initialize` guarantees there's space for this block. UnincludedSegment::::append(ancestor); } + HrmpOutboundMessages::::put(outbound_messages); } @@ -986,6 +1038,10 @@ pub mod pallet { #[pallet::storage] pub type CustomValidationHeadData = StorageValue<_, Vec, OptionQuery>; + /// Tracks cumulative `UMP` and `HRMP` messages sent across blocks in the current `PoV`. + #[pallet::storage] + pub type PoVMessagesTracker = StorageValue<_, PoVMessages, OptionQuery>; + #[pallet::inherent] impl ProvideInherent for Pallet { type Call = Call; diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index 3d79a130a4622..ddffd2f1ef664 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -19,7 +19,8 @@ use codec::{Decode, DecodeAll, Encode}; use cumulus_primitives_core::{ relay_chain, relay_chain::{UMPSignal, UMP_SEPARATOR}, - ClaimQueueOffset, CoreInfo, CoreSelector, ParachainBlockData, PersistedValidationData, + BundleInfo, ClaimQueueOffset, CoreInfo, CoreSelector, CumulusDigestItem, ParaId, + ParachainBlockData, PersistedValidationData, }; use cumulus_test_client::{ generate_extrinsic, generate_extrinsic_with_pair, @@ -213,6 +214,11 @@ fn build_multiple_blocks_with_witness( sproof_builder.clone(), timestamp, ignored_nodes.clone(), + Some(vec![CumulusDigestItem::BundleInfo(BundleInfo { + index: i as u8, + maybe_last: i as u32 + 1 == num_blocks, + }) + .to_digest_item()]), ); persisted_validation_data = Some(p_v_data); @@ -811,3 +817,102 @@ fn validate_block_rejects_huge_header_single_block() { ); } } + +#[test] +fn validate_block_with_max_ump_messages_and_4_blocks_per_pov() { + sp_tracing::try_init_simple(); + + let blocks_per_pov = 4; + let max_per_candidate = 100; + let (client, parent_head) = create_elastic_scaling_test_client(); + + let mut sproof_builder = + RelayStateSproofBuilder { current_slot: 1.into(), ..Default::default() }; + sproof_builder.host_config.max_upward_message_num_per_candidate = max_per_candidate; + sproof_builder.host_config.max_upward_message_size = 256; + sproof_builder.host_config.max_upward_queue_count = blocks_per_pov * max_per_candidate; + sproof_builder.host_config.max_upward_queue_size = blocks_per_pov * max_per_candidate; + sproof_builder.relay_dispatch_queue_remaining_capacity = + Some((blocks_per_pov * max_per_candidate, blocks_per_pov * max_per_candidate)); + + let TestBlockData { block, validation_data } = build_multiple_blocks_with_witness( + &client, + parent_head.clone(), + sproof_builder, + blocks_per_pov, + |i| { + vec![generate_extrinsic_with_pair( + &client, + Charlie.into(), + TestPalletCall::send_n_upward_messages { n: max_per_candidate }, + Some(i), + )] + }, + ); + + let header = block.blocks().last().unwrap().header().clone(); + let result = call_validate_block_validation_result( + test_runtime::elastic_scaling_500ms::WASM_BINARY + .expect("You need to build the WASM binaries to run the tests!"), + parent_head, + block, + validation_data.relay_parent_storage_root, + ) + .expect("Calls `validate_block`"); + + let res_header = Header::decode(&mut &result.head_data.0[..]).expect("Decodes `Header`."); + assert_eq!(header, res_header); + + let ump_count = result.upward_messages.iter().take_while(|m| **m != UMP_SEPARATOR).count(); + assert_eq!(ump_count, max_per_candidate as usize); +} + +#[test] +fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { + sp_tracing::try_init_simple(); + + let blocks_per_pov = 4; + let max_per_candidate = 100; + let recipient = ParaId::from(300); + let (client, parent_head) = create_elastic_scaling_test_client(); + + let mut sproof_builder = + RelayStateSproofBuilder { current_slot: 1.into(), ..Default::default() }; + sproof_builder.host_config.hrmp_max_message_num_per_candidate = max_per_candidate; + sproof_builder.para_id = ParaId::from(100); + + let channel = sproof_builder.upsert_outbound_channel(recipient); + channel.max_capacity = blocks_per_pov; + channel.max_total_size = blocks_per_pov * max_per_candidate * 256; + channel.max_message_size = 256; + + let TestBlockData { block, validation_data } = build_multiple_blocks_with_witness( + &client, + parent_head.clone(), + sproof_builder, + blocks_per_pov, + |i| { + vec![generate_extrinsic_with_pair( + &client, + Charlie.into(), + TestPalletCall::queue_hrmp_messages { n: max_per_candidate, recipient }, + Some(i), + )] + }, + ); + + let header = block.blocks().last().unwrap().header().clone(); + let result = call_validate_block_validation_result( + test_runtime::elastic_scaling_500ms::WASM_BINARY + .expect("You need to build the WASM binaries to run the tests!"), + parent_head, + block, + validation_data.relay_parent_storage_root, + ) + .expect("Calls `validate_block`"); + + let res_header = Header::decode(&mut &result.head_data.0[..]).expect("Decodes `Header`."); + assert_eq!(header, res_header); + + assert_eq!(result.horizontal_messages.len(), max_per_candidate as usize); +} diff --git a/cumulus/primitives/core/src/lib.rs b/cumulus/primitives/core/src/lib.rs index fdce4cf6bdde1..d24d0c5cec293 100644 --- a/cumulus/primitives/core/src/lib.rs +++ b/cumulus/primitives/core/src/lib.rs @@ -317,8 +317,9 @@ impl CumulusDigestItem { let encoded = self.encode(); match self { - Self::RelayParent(_) | Self::UseFullCore => - DigestItem::Consensus(CUMULUS_CONSENSUS_ID, encoded), + Self::RelayParent(_) | Self::UseFullCore => { + DigestItem::Consensus(CUMULUS_CONSENSUS_ID, encoded) + }, _ => DigestItem::PreRuntime(CUMULUS_CONSENSUS_ID, encoded), } } @@ -406,7 +407,7 @@ impl CumulusDigestItem { let Ok(CumulusDigestItem::BundleInfo(bundle_info)) = CumulusDigestItem::decode_all(&mut &val[..]) else { - return None + return None; }; Some(bundle_info) @@ -423,7 +424,7 @@ impl CumulusDigestItem { let Ok(CumulusDigestItem::UseFullCore) = CumulusDigestItem::decode_all(&mut &val[..]) else { - return None + return None; }; Some(true) diff --git a/cumulus/test/client/src/block_builder.rs b/cumulus/test/client/src/block_builder.rs index 6672a19e35257..f7e9e44ef49a2 100644 --- a/cumulus/test/client/src/block_builder.rs +++ b/cumulus/test/client/src/block_builder.rs @@ -84,6 +84,7 @@ pub trait InitBlockBuilder { relay_sproof_builder: RelayStateSproofBuilder, timestamp: u64, ignored_nodes: ProofRecorderIgnoredNodes, + extra_pre_digests: Option>, ) -> BlockBuilderAndSupportData<'_>; /// Init a specific block builder that works for the test runtime. @@ -241,6 +242,7 @@ impl InitBlockBuilder for Client { relay_sproof_builder: RelayStateSproofBuilder, timestamp: u64, ignored_nodes: ProofRecorderIgnoredNodes, + extra_pre_digests: Option>, ) -> BlockBuilderAndSupportData<'_> { init_block_builder( self, @@ -248,7 +250,7 @@ impl InitBlockBuilder for Client { validation_data, relay_sproof_builder, Some(timestamp), - None, + extra_pre_digests, Some(ignored_nodes), ) } diff --git a/cumulus/test/runtime/src/lib.rs b/cumulus/test/runtime/src/lib.rs index 011fbc1613c0e..cb371557e8725 100644 --- a/cumulus/test/runtime/src/lib.rs +++ b/cumulus/test/runtime/src/lib.rs @@ -375,7 +375,7 @@ impl cumulus_pallet_parachain_system::Config for Runtime { type SelfParaId = parachain_info::Pallet; type RuntimeEvent = RuntimeEvent; type OnSystemEvent = (); - type OutboundXcmpMessageSource = (); + type OutboundXcmpMessageSource = TestPallet; // Ignore all DMP messages by enqueueing them into `()`: type DmpQueue = frame_support::traits::EnqueueWithOrigin<(), sp_core::ConstU8<0>>; type ReservedDmpWeight = (); diff --git a/cumulus/test/runtime/src/test_pallet.rs b/cumulus/test/runtime/src/test_pallet.rs index a972198c300d9..40b8d489f40b2 100644 --- a/cumulus/test/runtime/src/test_pallet.rs +++ b/cumulus/test/runtime/src/test_pallet.rs @@ -25,6 +25,7 @@ pub const TEST_RUNTIME_UPGRADE_KEY: &[u8] = b"+test_runtime_upgrade_key+"; pub mod pallet { use crate::test_pallet::TEST_RUNTIME_UPGRADE_KEY; use alloc::vec; + use cumulus_primitives_core::{ParaId, XcmpMessageSource}; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; @@ -38,6 +39,22 @@ pub mod pallet { #[pallet::storage] pub type TestMap = StorageMap<_, Twox64Concat, u32, (), ValueQuery>; + /// Pending outbound HRMP messages queued by test extrinsics. + #[pallet::storage] + pub type PendingOutboundHrmpMessages = + StorageValue<_, alloc::vec::Vec<(ParaId, alloc::vec::Vec)>, ValueQuery>; + + impl XcmpMessageSource for Pallet { + fn take_outbound_messages( + maximum_channels: usize, + ) -> alloc::vec::Vec<(ParaId, alloc::vec::Vec)> { + PendingOutboundHrmpMessages::::mutate(|messages| { + let to_take = messages.len().min(maximum_channels); + messages.drain(..to_take).collect() + }) + } + } + #[pallet::hooks] impl Hooks> for Pallet {} @@ -105,6 +122,25 @@ pub mod pallet { TestMap::::remove(key); Ok(()) } + + /// Directly sets `n` small UMP messages in `PendingUpwardMessages`. + #[pallet::weight(0)] + pub fn send_n_upward_messages(_: OriginFor, n: u32) -> DispatchResult { + let messages: alloc::vec::Vec<_> = (0..n).map(|i| vec![(i % 256) as u8]).collect(); + cumulus_pallet_parachain_system::PendingUpwardMessages::::put(messages); + Ok(()) + } + + /// Queues `n` small HRMP messages to `recipient`. + #[pallet::weight(0)] + pub fn queue_hrmp_messages(_: OriginFor, n: u32, recipient: ParaId) -> DispatchResult { + PendingOutboundHrmpMessages::::mutate(|messages| { + for i in 0..n { + messages.push((recipient, vec![(i % 256) as u8])); + } + }); + Ok(()) + } } #[derive(frame_support::DefaultNoBound)] From 44911d456271875b98467e578e3a2640dffe53fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 10 Mar 2026 22:36:37 +0100 Subject: [PATCH 24/30] Also take the size into account for UMP messages --- .../parachain-system/src/benchmarking.rs | 4 +- .../parachain-system/src/block_weight/mock.rs | 8 +- .../parachain-system/src/block_weight/mod.rs | 4 +- .../src/block_weight/pre_inherents_hook.rs | 2 +- .../src/block_weight/tests.rs | 28 ++-- .../src/block_weight/transaction_extension.rs | 52 ++++---- .../src/descendant_validation.rs | 60 +++++---- cumulus/pallets/parachain-system/src/lib.rs | 12 +- cumulus/pallets/parachain-system/src/mock.rs | 8 +- .../src/parachain_inherent.rs | 4 +- .../src/relay_state_snapshot.rs | 4 +- cumulus/pallets/parachain-system/src/tests.rs | 25 ++-- .../src/unincluded_segment.rs | 4 +- .../src/validate_block/implementation.rs | 35 ++--- .../src/validate_block/tests.rs | 122 +++++++++++++----- .../src/validate_block/trie_cache.rs | 4 +- .../src/validate_block/trie_recorder.rs | 2 +- cumulus/test/runtime/src/test_pallet.rs | 9 ++ 18 files changed, 236 insertions(+), 151 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/benchmarking.rs b/cumulus/pallets/parachain-system/src/benchmarking.rs index ebe3c66785f52..5640720064dd0 100644 --- a/cumulus/pallets/parachain-system/src/benchmarking.rs +++ b/cumulus/pallets/parachain-system/src/benchmarking.rs @@ -22,12 +22,12 @@ use super::*; use crate::{ block_weight::{ - BlockWeightMode, DynamicMaxBlockWeight, MaxParachainBlockWeight, FULL_CORE_WEIGHT, + BlockWeightMode, DynamicMaxBlockWeight, FULL_CORE_WEIGHT, MaxParachainBlockWeight, }, parachain_inherent::InboundDownwardMessages, }; use cumulus_primitives_core::{ - relay_chain::Hash as RelayHash, BundleInfo, CoreInfo, InboundDownwardMessage, + BundleInfo, CoreInfo, InboundDownwardMessage, relay_chain::Hash as RelayHash, }; use frame_benchmarking::v2::*; use frame_support::{ diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs index 7dba292f55de3..0801cd9aaecf6 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mock.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -27,18 +27,18 @@ use frame_support::{ parameter_types, traits::PreInherents, weights::{ - constants::{BlockExecutionWeight, ExtrinsicBaseWeight}, Weight, + constants::{BlockExecutionWeight, ExtrinsicBaseWeight}, }, }; -use frame_system::{limits::BlockWeights, CheckWeight}; +use frame_system::{CheckWeight, limits::BlockWeights}; use polkadot_primitives::PersistedValidationData; use sp_core::ConstU32; use sp_io; use sp_runtime::{ + BuildStorage, Perbill, generic::{self, UncheckedExtrinsic}, testing::UintAuthorityId, - BuildStorage, Perbill, }; const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(1); @@ -126,7 +126,7 @@ pub mod test_pallet { use frame_support::{ dispatch::DispatchClass, pallet_prelude::*, - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, WeightMeter}, + weights::{WeightMeter, constants::WEIGHT_REF_TIME_PER_SECOND}, }; use frame_system::pallet_prelude::*; diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index d594b0aa2a7cc..a4e01e5cbd704 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -67,11 +67,11 @@ use codec::{Decode, Encode}; use core::marker::PhantomData; use cumulus_primitives_core::CumulusDigestItem; use frame_support::{ - weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, CloneNoBound, DebugNoBound, + weights::{Weight, constants::WEIGHT_REF_TIME_PER_SECOND}, }; use frame_system::pallet_prelude::BlockNumberFor; -use polkadot_primitives::{executor_params::DEFAULT_BACKING_EXECUTION_TIMEOUT, MAX_POV_SIZE}; +use polkadot_primitives::{MAX_POV_SIZE, executor_params::DEFAULT_BACKING_EXECUTION_TIMEOUT}; use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::Digest; diff --git a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs index 4cb4af9b32530..28b9382ab2914 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs @@ -15,7 +15,7 @@ // limitations under the License. use super::{ - block_weight_over_target_block_weight, is_first_block_in_core, BlockWeightMode, LOG_TARGET, + BlockWeightMode, LOG_TARGET, block_weight_over_target_block_weight, is_first_block_in_core, }; use crate::block_weight::FULL_CORE_WEIGHT; use cumulus_primitives_core::CumulusDigestItem; diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index be27cb89049c9..71652e127d19a 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -31,8 +31,8 @@ use frame_system::{CheckWeight, RawOrigin as SystemOrigin}; use polkadot_primitives::MAX_POV_SIZE; use sp_core::ConstU32; use sp_runtime::{ - traits::{DispatchTransaction, Header, TransactionExtension}, Digest, + traits::{DispatchTransaction, Header, TransactionExtension}, }; type TxExtension = @@ -802,12 +802,14 @@ fn executive_validate_block_handles_operational_transactions() { ExtrinsicOnlyOperational::new_bare(call) }; - assert!(ExecutiveOnlyOperational::validate_transaction( - TransactionSource::External, - xt, - Default::default() - ) - .is_ok()); + assert!( + ExecutiveOnlyOperational::validate_transaction( + TransactionSource::External, + xt, + Default::default() + ) + .is_ok() + ); }); } } @@ -973,11 +975,13 @@ fn executive_validate_transaction_respects_dispatch_class_max_block_size() { for signed in [true, false] { TestExtBuilder::new().previous_core_count(4).build().execute_with(|| { - assert!(::BlockWeights::get() - .get(DispatchClass::Normal) - .max_total - .unwrap() - .all_lt(call_weight)); + assert!( + ::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap() + .all_lt(call_weight) + ); assert!(MaximumBlockWeight::target_block_weight().all_gt(call_weight)); let call = diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index f896b041f9697..417eb8773833f 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -15,8 +15,8 @@ // limitations under the License. use super::{ + BlockWeightMode, FULL_CORE_WEIGHT, LOG_TARGET, MaxParachainBlockWeight, block_weight_over_target_block_weight, inside_pre_validate, is_first_block_in_core_with_digest, - BlockWeightMode, MaxParachainBlockWeight, FULL_CORE_WEIGHT, LOG_TARGET, }; use crate::WeightInfo; use alloc::vec::Vec; @@ -32,8 +32,8 @@ use frame_support::{ use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::{ - traits::{DispatchInfoOf, Dispatchable, Implication, PostDispatchInfoOf, TransactionExtension}, DispatchResult, + traits::{DispatchInfoOf, Dispatchable, Implication, PostDispatchInfoOf, TransactionExtension}, }; /// Transaction extension that dynamically changes the max block weight. @@ -108,12 +108,12 @@ impl DynamicMaxBlockWeight + Config, + Inner, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, +> DynamicMaxBlockWeight where Config: crate::Config, TargetBlockRate: Get, @@ -363,12 +363,12 @@ where } impl< - Config, - Inner, - TargetBlockRate, - const MAX_TRANSACTION_TO_CONSIDER: u32, - const ALLOW_NORMAL: bool, - > From + Config, + Inner, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, +> From for DynamicMaxBlockWeight< Config, Inner, @@ -383,12 +383,12 @@ impl< } impl< - Config, - Inner: core::fmt::Debug, - TargetBlockRate, - const MAX_TRANSACTION_TO_CONSIDER: u32, - const ALLOW_NORMAL: bool, - > core::fmt::Debug + Config, + Inner: core::fmt::Debug, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, +> core::fmt::Debug for DynamicMaxBlockWeight< Config, Inner, @@ -403,12 +403,12 @@ impl< } impl< - Config: crate::Config + Send + Sync, - Inner: TransactionExtension, - TargetBlockRate: Get + Send + Sync + 'static, - const MAX_TRANSACTION_TO_CONSIDER: u32, - const ALLOW_NORMAL: bool, - > TransactionExtension + Config: crate::Config + Send + Sync, + Inner: TransactionExtension, + TargetBlockRate: Get + Send + Sync + 'static, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, +> TransactionExtension for DynamicMaxBlockWeight< Config, Inner, diff --git a/cumulus/pallets/parachain-system/src/descendant_validation.rs b/cumulus/pallets/parachain-system/src/descendant_validation.rs index b856d3298b01b..cf51374fd67c0 100644 --- a/cumulus/pallets/parachain-system/src/descendant_validation.rs +++ b/cumulus/pallets/parachain-system/src/descendant_validation.rs @@ -14,15 +14,15 @@ // limitations under the License. use crate::{ - descendant_validation::RelayParentVerificationError::InvalidNumberOfDescendants, RelayChainStateProof, + descendant_validation::RelayParentVerificationError::InvalidNumberOfDescendants, }; use alloc::vec::Vec; use sp_consensus_babe::{ - digests::{CompatibleDigestItem, NextEpochDescriptor}, AuthorityIndex, + digests::{CompatibleDigestItem, NextEpochDescriptor}, }; -use sp_runtime::{traits::Header, RuntimeAppPublic}; +use sp_runtime::{RuntimeAppPublic, traits::Header}; /// Verifies that the provided relay parent descendants form a valid chain /// and are signed by relay chain authorities. If relay chain descendants shall be checked, @@ -217,15 +217,15 @@ mod tests { use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use rstest::rstest; use sp_consensus_babe::{ + AuthorityId, AuthorityPair, BABE_ENGINE_ID, BabeAuthorityWeight, ConsensusLog, digests::{CompatibleDigestItem, NextEpochDescriptor, PreDigest, PrimaryPreDigest}, - AuthorityId, AuthorityPair, BabeAuthorityWeight, ConsensusLog, BABE_ENGINE_ID, }; use sp_core::{ + H256, Pair, sr25519::vrf::{VrfPreOutput, VrfProof, VrfSignature}, - Pair, H256, }; use sp_keyring::Sr25519Keyring; - use sp_runtime::{testing::Header as TestHeader, DigestItem}; + use sp_runtime::{DigestItem, testing::Header as TestHeader}; const PARA_ID: u32 = 2000; /// Verify a header chain with different lengths and different number of authorities included in @@ -244,13 +244,15 @@ mod tests { // Expected number of parents passed to the function does not include actual relay parent let expected_number_of_descendants = (relay_parent_descendants.len() - 1) as u32; - assert!(verify_relay_parent_descendants( - &relay_state_proof, - relay_parent_descendants, - relay_parent_state_root, - expected_number_of_descendants, - ) - .is_ok()); + assert!( + verify_relay_parent_descendants( + &relay_state_proof, + relay_parent_descendants, + relay_parent_state_root, + expected_number_of_descendants, + ) + .is_ok() + ); } #[rstest] @@ -444,13 +446,15 @@ mod tests { // Expected number of parents passed to the function does not include actual relay parent let expected_number_of_descendants = (relay_parent_descendants.len() - 1) as u32; - assert!(verify_relay_parent_descendants( - &relay_state_proof, - relay_parent_descendants, - relay_parent_state_root, - expected_number_of_descendants, - ) - .is_ok()); + assert!( + verify_relay_parent_descendants( + &relay_state_proof, + relay_parent_descendants, + relay_parent_state_root, + expected_number_of_descendants, + ) + .is_ok() + ); } /// Test some interesting epoch change positions, like epoch change on RP directly, and last @@ -470,13 +474,15 @@ mod tests { // Expected number of parents passed to the function does not include actual relay parent let expected_number_of_descendants = (relay_parent_descendants.len() - 1) as u32; - assert!(verify_relay_parent_descendants( - &relay_state_proof, - relay_parent_descendants, - relay_parent_state_root, - expected_number_of_descendants, - ) - .is_ok()); + assert!( + verify_relay_parent_descendants( + &relay_state_proof, + relay_parent_descendants, + relay_parent_state_root, + expected_number_of_descendants, + ) + .is_ok() + ); } /// Helper function to create a mock `RelayChainStateProof`. diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index ebccabfbdf41a..1b9184481b642 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -112,7 +112,7 @@ pub use pallet::*; const LOG_TARGET: &str = "runtime::parachain-system"; -/// Tracks cumulative UMP and HRMP messages sent across blocks within a single PoV. +/// Tracks cumulative UMP and HRMP messages and their sizes sent across blocks within a single PoV. #[derive(Encode, Decode, Clone, Debug, TypeInfo, Default)] pub struct PoVMessages { /// Relay parent storage root of the current PoV. @@ -123,6 +123,8 @@ pub struct PoVMessages { pub bundle_index: u8, /// Cumulative count of UMP messages sent in this PoV. pub ump_msg_count: u32, + /// Cumulative size of UMP messages sent in this PoV. + pub ump_msg_size: u32, /// Cumulative count of HRMP outbound messages sent in this PoV. pub hrmp_outbound_count: u32, } @@ -184,7 +186,9 @@ impl CheckAssociatedRelayNumber for RelayNumberMonotonicallyIncreases { previous: RelayChainBlockNumber, ) { if current < previous { - panic!("Relay chain block number needs to monotonically increase between Parachain blocks!") + panic!( + "Relay chain block number needs to monotonically increase between Parachain blocks!" + ) } } } @@ -377,6 +381,9 @@ pub mod pallet { .saturating_sub(pov_tracker.ump_msg_count), ); + // Also reduce available_size by what we've already sent in this PoV + let available_size = available_size.saturating_sub(pov_tracker.ump_msg_size); + // Count the number of messages we can possibly fit in the given constraints, i.e. // available_capacity and available_size. let (num, total_size) = up @@ -405,6 +412,7 @@ pub mod pallet { *up = up.split_off(num as usize); pov_tracker.ump_msg_count = pov_tracker.ump_msg_count.saturating_add(num); + pov_tracker.ump_msg_size = pov_tracker.ump_msg_size.saturating_add(total_size); if let Some(core_info) = CumulusDigestItem::find_core_info(&frame_system::Pallet::::digest()) diff --git a/cumulus/pallets/parachain-system/src/mock.rs b/cumulus/pallets/parachain-system/src/mock.rs index 15acd522d8ff0..e9682ab172ca6 100644 --- a/cumulus/pallets/parachain-system/src/mock.rs +++ b/cumulus/pallets/parachain-system/src/mock.rs @@ -24,8 +24,8 @@ use alloc::collections::vec_deque::VecDeque; use codec::Encode; use core::num::NonZeroU32; use cumulus_primitives_core::{ - relay_chain::BlockNumber as RelayBlockNumber, AggregateMessageOrigin, InboundDownwardMessage, - InboundHrmpMessage, PersistedValidationData, + AggregateMessageOrigin, InboundDownwardMessage, InboundHrmpMessage, PersistedValidationData, + relay_chain::BlockNumber as RelayBlockNumber, }; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use frame_support::{ @@ -37,9 +37,9 @@ use frame_support::{ }, weights::{Weight, WeightMeter}, }; -use frame_system::{limits::BlockWeights, pallet_prelude::BlockNumberFor, RawOrigin}; +use frame_system::{RawOrigin, limits::BlockWeights, pallet_prelude::BlockNumberFor}; use sp_core::ConstU32; -use sp_runtime::{traits::BlakeTwo256, BuildStorage}; +use sp_runtime::{BuildStorage, traits::BlakeTwo256}; use sp_version::RuntimeVersion; use std::cell::RefCell; diff --git a/cumulus/pallets/parachain-system/src/parachain_inherent.rs b/cumulus/pallets/parachain-system/src/parachain_inherent.rs index 348f1e3a1ff5e..15a472162526c 100644 --- a/cumulus/pallets/parachain-system/src/parachain_inherent.rs +++ b/cumulus/pallets/parachain-system/src/parachain_inherent.rs @@ -19,10 +19,10 @@ use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec}; use core::fmt::Debug; use cumulus_primitives_core::{ + InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, relay_chain::{ ApprovedPeerId, BlockNumber as RelayChainBlockNumber, BlockNumber, Header as RelayHeader, }, - InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, }; use cumulus_primitives_parachain_inherent::{HashedMessage, ParachainInherentData}; use frame_support::{ @@ -30,7 +30,7 @@ use frame_support::{ pallet_prelude::{Decode, DecodeWithMemTracking, Encode}, }; use scale_info::TypeInfo; -use sp_core::{bounded::BoundedSlice, Get}; +use sp_core::{Get, bounded::BoundedSlice}; /// A structure that helps identify a message inside a collection of messages sorted by `sent_at`. /// diff --git a/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs b/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs index 75687d0c3e481..941b279f38b4e 100644 --- a/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs +++ b/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs @@ -19,12 +19,12 @@ use alloc::vec::Vec; use codec::{Decode, Encode}; use cumulus_primitives_core::{ - relay_chain, AbridgedHostConfiguration, AbridgedHrmpChannel, ParaId, + AbridgedHostConfiguration, AbridgedHrmpChannel, ParaId, relay_chain, }; use scale_info::TypeInfo; use sp_runtime::traits::HashingFor; use sp_state_machine::{Backend, TrieBackend, TrieBackendBuilder}; -use sp_trie::{HashDBT, MemoryDB, StorageProof, EMPTY_PREFIX}; +use sp_trie::{EMPTY_PREFIX, HashDBT, MemoryDB, StorageProof}; /// The capacity of the upward message queue of a parachain on the relay chain. // The field order should stay the same as the data can be found in the proof to ensure both are diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs index eb295f5d78fef..199a4b564b024 100755 --- a/cumulus/pallets/parachain-system/src/tests.rs +++ b/cumulus/pallets/parachain-system/src/tests.rs @@ -22,11 +22,11 @@ use crate::mock::*; use alloc::collections::BTreeMap; use core::num::NonZeroU32; use cumulus_primitives_core::{ - relay_chain::ApprovedPeerId, AbridgedHrmpChannel, ClaimQueueOffset, CoreInfo, CoreSelector, - InboundDownwardMessage, InboundHrmpMessage, CUMULUS_CONSENSUS_ID, + AbridgedHrmpChannel, CUMULUS_CONSENSUS_ID, ClaimQueueOffset, CoreInfo, CoreSelector, + InboundDownwardMessage, InboundHrmpMessage, relay_chain::ApprovedPeerId, }; use cumulus_primitives_parachain_inherent::{ - v0, INHERENT_IDENTIFIER, PARACHAIN_INHERENT_IDENTIFIER_V0, + INHERENT_IDENTIFIER, PARACHAIN_INHERENT_IDENTIFIER_V0, v0, }; use frame_support::{assert_ok, parameter_types, weights::Weight}; use frame_system::RawOrigin; @@ -905,10 +905,12 @@ fn runtime_upgrade_events() { }) ); - assert!(System::digest() - .logs() - .iter() - .any(|d| *d == sp_runtime::generic::DigestItem::RuntimeEnvironmentUpdated)); + assert!( + System::digest() + .logs() + .iter() + .any(|d| *d == sp_runtime::generic::DigestItem::RuntimeEnvironmentUpdated) + ); }, ); } @@ -1698,10 +1700,10 @@ fn deposits_relay_parent_storage_root() { || {}, || { let digest = System::digest(); - assert!(cumulus_primitives_core::rpsr_digest::extract_relay_parent_storage_root( - &digest - ) - .is_some()); + assert!( + cumulus_primitives_core::rpsr_digest::extract_relay_parent_storage_root(&digest) + .is_some() + ); }, ); } @@ -1826,3 +1828,4 @@ fn ump_signals_are_sent_correctly() { ); } } + diff --git a/cumulus/pallets/parachain-system/src/unincluded_segment.rs b/cumulus/pallets/parachain-system/src/unincluded_segment.rs index 493d8afd97acc..df3587cf79808 100644 --- a/cumulus/pallets/parachain-system/src/unincluded_segment.rs +++ b/cumulus/pallets/parachain-system/src/unincluded_segment.rs @@ -21,12 +21,12 @@ //! sent to relay chain. use super::relay_state_snapshot::{MessagingStateSnapshot, RelayDispatchQueueRemainingCapacity}; +use Debug; use alloc::collections::btree_map::BTreeMap; use codec::{Decode, Encode}; use core::marker::PhantomData; -use cumulus_primitives_core::{relay_chain, ParaId}; +use cumulus_primitives_core::{ParaId, relay_chain}; use scale_info::TypeInfo; -use Debug; /// Constraints on outbound HRMP channel. #[derive(Clone, Debug)] diff --git a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs index 5aed36b05f192..5b2ea74205e81 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs @@ -16,28 +16,28 @@ //! The actual implementation of the validate block functionality. -use super::{trie_cache, trie_recorder, MemoryOptimizedValidationParams}; +use super::{MemoryOptimizedValidationParams, trie_cache, trie_recorder}; use alloc::vec::Vec; use codec::{Decode, Encode}; use cumulus_primitives_core::{ + ClaimQueueOffset, CoreSelector, ParachainBlockData, PersistedValidationData, relay_chain::{ - BlockNumber as RNumber, Hash as RHash, UMPSignal, MAX_HEAD_DATA_SIZE, UMP_SEPARATOR, + BlockNumber as RNumber, Hash as RHash, MAX_HEAD_DATA_SIZE, UMP_SEPARATOR, UMPSignal, }, - ClaimQueueOffset, CoreSelector, ParachainBlockData, PersistedValidationData, }; use frame_support::{ - traits::{ExecuteBlock, Get, IsSubType}, BoundedVec, + traits::{ExecuteBlock, Get, IsSubType}, }; use polkadot_parachain_primitives::primitives::{HeadData, ValidationResult}; -use sp_core::storage::{well_known_keys, ChildInfo, StateVersion}; -use sp_externalities::{set_and_run_with_externalities, Externalities}; -use sp_io::{hashing::blake2_128, KillStorageResult}; +use sp_core::storage::{ChildInfo, StateVersion, well_known_keys}; +use sp_externalities::{Externalities, set_and_run_with_externalities}; +use sp_io::{KillStorageResult, hashing::blake2_128}; use sp_runtime::traits::{ Block as BlockT, ExtrinsicCall, Hash as HashT, HashingFor, Header as HeaderT, LazyBlock, }; use sp_state_machine::OverlayedChanges; -use sp_trie::{HashDBT, ProofSizeProvider, EMPTY_PREFIX}; +use sp_trie::{EMPTY_PREFIX, HashDBT, ProofSizeProvider}; use trie_recorder::{SeenNodes, SizeOnlyRecorderProvider}; type Ext<'a, Block, Backend> = sp_state_machine::Ext<'a, HashingFor, Backend>; @@ -247,7 +247,9 @@ where overlay.storage(well_known_keys::CODE).is_some() }; if code_upgrade_detected && num_blocks > 1 { - panic!("When applying a runtime upgrade, only one block per PoV is allowed. Received {num_blocks}.") + panic!( + "When applying a runtime upgrade, only one block per PoV is allowed. Received {num_blocks}." + ) } run_with_externalities_and_recorder::( &backend, @@ -287,16 +289,17 @@ where } }) .for_each(|m| { - upward_messages.try_push(m) - .expect( - "Number of upward messages should not be greater than `MAX_UPWARD_MESSAGE_NUM`", - ) + upward_messages.try_push(m).expect( + "Number of upward messages should not be greater than `MAX_UPWARD_MESSAGE_NUM`", + ) }); processed_downward_messages += crate::ProcessedDownwardMessages::::get(); - horizontal_messages.try_extend(crate::HrmpOutboundMessages::::get().into_iter()).expect( - "Number of horizontal messages should not be greater than `MAX_HORIZONTAL_MESSAGE_NUM`", - ); + horizontal_messages + .try_extend(crate::HrmpOutboundMessages::::get().into_iter()) + .expect( + "Number of horizontal messages should not be greater than `MAX_HORIZONTAL_MESSAGE_NUM`", + ); hrmp_watermark = crate::HrmpWatermark::::get(); if block_index + 1 == num_blocks { diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index ddffd2f1ef664..b496ac3e0508f 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -17,31 +17,31 @@ use crate::{validate_block::MemoryOptimizedValidationParams, *}; use codec::{Decode, DecodeAll, Encode}; use cumulus_primitives_core::{ - relay_chain, - relay_chain::{UMPSignal, UMP_SEPARATOR}, BundleInfo, ClaimQueueOffset, CoreInfo, CoreSelector, CumulusDigestItem, ParaId, - ParachainBlockData, PersistedValidationData, + ParachainBlockData, PersistedValidationData, relay_chain, + relay_chain::{UMP_SEPARATOR, UMPSignal}, }; use cumulus_test_client::{ - generate_extrinsic, generate_extrinsic_with_pair, + BlockData, BlockOrigin, BuildParachainBlockData, Client, DefaultTestClientBuilderExt, HeadData, + InitBlockBuilder, + Sr25519Keyring::{Alice, Bob, Charlie}, + TestClientBuilder, TestClientBuilderExt, ValidationParams, generate_extrinsic, + generate_extrinsic_with_pair, runtime::{ self as test_runtime, Block, Hash, Header, SudoCall, SystemCall, TestPalletCall, UncheckedExtrinsic, WASM_BINARY, }, - seal_block, transfer, BlockData, BlockOrigin, BuildParachainBlockData, Client, - DefaultTestClientBuilderExt, HeadData, InitBlockBuilder, - Sr25519Keyring::{Alice, Bob, Charlie}, - TestClientBuilder, TestClientBuilderExt, ValidationParams, + seal_block, transfer, }; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use polkadot_parachain_primitives::primitives::ValidationResult; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; use sp_api::{ApiExt, Core, ProofRecorder, ProvideRuntimeApi, StorageProof}; use sp_consensus_babe::SlotDuration; -use sp_core::{Hasher, H256}; +use sp_core::{H256, Hasher}; use sp_runtime::{ - traits::{BlakeTwo256, Block as BlockT, Header as HeaderT}, DigestItem, + traits::{BlakeTwo256, Block as BlockT, Header as HeaderT}, }; use sp_trie::{proof_size_extension::ProofSizeExt, recorder::IgnoredNodes}; use std::{env, process::Command}; @@ -214,11 +214,13 @@ fn build_multiple_blocks_with_witness( sproof_builder.clone(), timestamp, ignored_nodes.clone(), - Some(vec![CumulusDigestItem::BundleInfo(BundleInfo { - index: i as u8, - maybe_last: i as u32 + 1 == num_blocks, - }) - .to_digest_item()]), + Some(vec![ + CumulusDigestItem::BundleInfo(BundleInfo { + index: i as u8, + maybe_last: i as u32 + 1 == num_blocks, + }) + .to_digest_item(), + ]), ); persisted_validation_data = Some(p_v_data); @@ -452,8 +454,10 @@ fn validate_block_invalid_parent_hash() { .expect("Runs the test"); assert!(output.status.success()); - assert!(dbg!(String::from_utf8(output.stderr).unwrap()) - .contains("Parachain head needs to be the parent of the first block")); + assert!( + dbg!(String::from_utf8(output.stderr).unwrap()) + .contains("Parachain head needs to be the parent of the first block") + ); } } @@ -480,8 +484,10 @@ fn validate_block_fails_on_invalid_validation_data() { .expect("Runs the test"); assert!(output.status.success()); - assert!(dbg!(String::from_utf8(output.stderr).unwrap()) - .contains("Relay parent storage root doesn't match")); + assert!( + dbg!(String::from_utf8(output.stderr).unwrap()) + .contains("Relay parent storage root doesn't match") + ); } } @@ -636,12 +642,14 @@ fn validate_block_handles_ump_signal() { extra_extrinsics, parent_head.clone(), Default::default(), - vec![CumulusDigestItem::CoreInfo(CoreInfo { - selector: CoreSelector(0), - claim_queue_offset: ClaimQueueOffset(0), - number_of_cores: 1.into(), - }) - .to_digest_item()], + vec![ + CumulusDigestItem::CoreInfo(CoreInfo { + selector: CoreSelector(0), + claim_queue_offset: ClaimQueueOffset(0), + number_of_cores: 1.into(), + }) + .to_digest_item(), + ], ); let upward_messages = call_validate_block_validation_result( @@ -691,8 +699,10 @@ fn ensure_we_only_like_blockchains() { .expect("Runs the test"); assert!(output.status.success()); - assert!(dbg!(String::from_utf8(output.stderr).unwrap()) - .contains("Not a valid chain of blocks :(")); + assert!( + dbg!(String::from_utf8(output.stderr).unwrap()) + .contains("Not a valid chain of blocks :(") + ); } } @@ -777,8 +787,10 @@ fn rejects_multiple_blocks_per_pov_when_applying_runtime_upgrade() { assert!(output.status.success()); - assert!(dbg!(String::from_utf8(output.stderr).unwrap()) - .contains("only one block per PoV is allowed")); + assert!( + dbg!(String::from_utf8(output.stderr).unwrap()) + .contains("only one block per PoV is allowed") + ); } } @@ -886,18 +898,48 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { channel.max_total_size = blocks_per_pov * max_per_candidate * 256; channel.max_message_size = 256; + // Configure UMP limits for size enforcement testing + sproof_builder.host_config.max_upward_message_num_per_candidate = 10; + sproof_builder.host_config.max_upward_message_size = 1000; + sproof_builder.relay_dispatch_queue_remaining_capacity = Some((10, 2000)); + let TestBlockData { block, validation_data } = build_multiple_blocks_with_witness( &client, parent_head.clone(), sproof_builder, blocks_per_pov, |i| { - vec![generate_extrinsic_with_pair( - &client, - Charlie.into(), - TestPalletCall::queue_hrmp_messages { n: max_per_candidate, recipient }, - Some(i), - )] + let mut extrinsics = vec![ + // Send HRMP messages (original test) + generate_extrinsic_with_pair( + &client, + Charlie.into(), + TestPalletCall::queue_hrmp_messages { n: max_per_candidate, recipient }, + Some(i), + ), + ]; + + // Add UMP message of 500 bytes in each block + if i < 3 { + // Send 500 bytes in blocks 0, 1, 2 (total 1500 bytes) + extrinsics.push(generate_extrinsic_with_pair( + &client, + Charlie.into(), + TestPalletCall::send_upward_message_of_size { size: 500 }, + Some(i), + )); + } + // Block 3: try to send 600 bytes, should be deferred due to size limit (2000 - 1500 = 500 remaining) + else { + extrinsics.push(generate_extrinsic_with_pair( + &client, + Charlie.into(), + TestPalletCall::send_upward_message_of_size { size: 600 }, + Some(i), + )); + } + + extrinsics }, ); @@ -914,5 +956,15 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { let res_header = Header::decode(&mut &result.head_data.0[..]).expect("Decodes `Header`."); assert_eq!(header, res_header); + // Verify HRMP messages (original assertion) assert_eq!(result.horizontal_messages.len(), max_per_candidate as usize); + + // Verify UMP size enforcement: only 3 messages should be sent (blocks 0, 1, 2) + // Block 3's 600 byte message should be deferred because only 500 bytes remain + assert_eq!(result.upward_messages.len(), 3, "Expected 3 UMP messages to be sent (block 3's message deferred due to size limit)"); + + // Verify sizes of sent messages + for msg in result.upward_messages.iter() { + assert_eq!(msg.len(), 500, "Each sent message should be 500 bytes"); + } } diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs index cf6eb6fb94f33..e0a50d11165b8 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs @@ -18,10 +18,10 @@ use alloc::boxed::Box; use core::cell::{RefCell, RefMut}; -use hashbrown::{hash_map::Entry, HashMap}; +use hashbrown::{HashMap, hash_map::Entry}; use sp_state_machine::TrieCacheProvider; use sp_trie::{NodeCodec, RandomState}; -use trie_db::{node::NodeOwned, Hasher}; +use trie_db::{Hasher, node::NodeOwned}; /// Special purpose trie cache implementation that is able to cache an unlimited number /// of values. To be used in `validate_block` to serve values and nodes that diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs index 3e4b854d301ee..b491e30be86b4 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs @@ -148,8 +148,8 @@ unsafe impl Sync for SizeOnlyRecorderProvider {} mod tests { use rand::Rng; use sp_trie::{ - cache::{CacheSize, SharedTrieCache}, MemoryDB, ProofSizeProvider, TrieRecorderProvider, + cache::{CacheSize, SharedTrieCache}, }; use trie_db::{Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut, TrieRecorder}; use trie_standardmap::{Alphabet, StandardMap, ValueMode}; diff --git a/cumulus/test/runtime/src/test_pallet.rs b/cumulus/test/runtime/src/test_pallet.rs index f1f8a070c78be..15a5a29ab2ad3 100644 --- a/cumulus/test/runtime/src/test_pallet.rs +++ b/cumulus/test/runtime/src/test_pallet.rs @@ -146,6 +146,15 @@ pub mod pallet { Ok(()) } + /// Sends a UMP message of specific size (in bytes). + #[pallet::weight(0)] + pub fn send_upward_message_of_size(_: OriginFor, size: u32) -> DispatchResult { + let message = alloc::vec![0u8; size as usize]; + cumulus_pallet_parachain_system::Pallet::::send_upward_message(message) + .map_err(|_| "Failed to send upward message")?; + Ok(()) + } + /// Queues `n` small HRMP messages to `recipient`. #[pallet::weight(0)] pub fn queue_hrmp_messages(_: OriginFor, n: u32, recipient: ParaId) -> DispatchResult { From 3f04aafa583460e9f94e5fb0866decadbf0e3afa Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:45:54 +0000 Subject: [PATCH 25/30] Update from github-actions[bot] running command 'fmt' --- .../parachain-system/src/benchmarking.rs | 4 +- .../parachain-system/src/block_weight/mock.rs | 8 +- .../parachain-system/src/block_weight/mod.rs | 4 +- .../src/block_weight/pre_inherents_hook.rs | 2 +- .../src/block_weight/tests.rs | 28 +++--- .../src/block_weight/transaction_extension.rs | 52 +++++------ .../src/descendant_validation.rs | 60 ++++++------- cumulus/pallets/parachain-system/src/mock.rs | 8 +- .../src/parachain_inherent.rs | 4 +- .../src/relay_state_snapshot.rs | 4 +- cumulus/pallets/parachain-system/src/tests.rs | 25 +++--- .../src/unincluded_segment.rs | 4 +- .../src/validate_block/implementation.rs | 16 ++-- .../src/validate_block/tests.rs | 87 +++++++++---------- .../src/validate_block/trie_cache.rs | 4 +- .../src/validate_block/trie_recorder.rs | 2 +- 16 files changed, 146 insertions(+), 166 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/benchmarking.rs b/cumulus/pallets/parachain-system/src/benchmarking.rs index 5640720064dd0..ebe3c66785f52 100644 --- a/cumulus/pallets/parachain-system/src/benchmarking.rs +++ b/cumulus/pallets/parachain-system/src/benchmarking.rs @@ -22,12 +22,12 @@ use super::*; use crate::{ block_weight::{ - BlockWeightMode, DynamicMaxBlockWeight, FULL_CORE_WEIGHT, MaxParachainBlockWeight, + BlockWeightMode, DynamicMaxBlockWeight, MaxParachainBlockWeight, FULL_CORE_WEIGHT, }, parachain_inherent::InboundDownwardMessages, }; use cumulus_primitives_core::{ - BundleInfo, CoreInfo, InboundDownwardMessage, relay_chain::Hash as RelayHash, + relay_chain::Hash as RelayHash, BundleInfo, CoreInfo, InboundDownwardMessage, }; use frame_benchmarking::v2::*; use frame_support::{ diff --git a/cumulus/pallets/parachain-system/src/block_weight/mock.rs b/cumulus/pallets/parachain-system/src/block_weight/mock.rs index 0801cd9aaecf6..7dba292f55de3 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mock.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mock.rs @@ -27,18 +27,18 @@ use frame_support::{ parameter_types, traits::PreInherents, weights::{ - Weight, constants::{BlockExecutionWeight, ExtrinsicBaseWeight}, + Weight, }, }; -use frame_system::{CheckWeight, limits::BlockWeights}; +use frame_system::{limits::BlockWeights, CheckWeight}; use polkadot_primitives::PersistedValidationData; use sp_core::ConstU32; use sp_io; use sp_runtime::{ - BuildStorage, Perbill, generic::{self, UncheckedExtrinsic}, testing::UintAuthorityId, + BuildStorage, Perbill, }; const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(1); @@ -126,7 +126,7 @@ pub mod test_pallet { use frame_support::{ dispatch::DispatchClass, pallet_prelude::*, - weights::{WeightMeter, constants::WEIGHT_REF_TIME_PER_SECOND}, + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, WeightMeter}, }; use frame_system::pallet_prelude::*; diff --git a/cumulus/pallets/parachain-system/src/block_weight/mod.rs b/cumulus/pallets/parachain-system/src/block_weight/mod.rs index a4e01e5cbd704..d594b0aa2a7cc 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/mod.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/mod.rs @@ -67,11 +67,11 @@ use codec::{Decode, Encode}; use core::marker::PhantomData; use cumulus_primitives_core::CumulusDigestItem; use frame_support::{ + weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, CloneNoBound, DebugNoBound, - weights::{Weight, constants::WEIGHT_REF_TIME_PER_SECOND}, }; use frame_system::pallet_prelude::BlockNumberFor; -use polkadot_primitives::{MAX_POV_SIZE, executor_params::DEFAULT_BACKING_EXECUTION_TIMEOUT}; +use polkadot_primitives::{executor_params::DEFAULT_BACKING_EXECUTION_TIMEOUT, MAX_POV_SIZE}; use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::Digest; diff --git a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs index 28b9382ab2914..4cb4af9b32530 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/pre_inherents_hook.rs @@ -15,7 +15,7 @@ // limitations under the License. use super::{ - BlockWeightMode, LOG_TARGET, block_weight_over_target_block_weight, is_first_block_in_core, + block_weight_over_target_block_weight, is_first_block_in_core, BlockWeightMode, LOG_TARGET, }; use crate::block_weight::FULL_CORE_WEIGHT; use cumulus_primitives_core::CumulusDigestItem; diff --git a/cumulus/pallets/parachain-system/src/block_weight/tests.rs b/cumulus/pallets/parachain-system/src/block_weight/tests.rs index 71652e127d19a..be27cb89049c9 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/tests.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/tests.rs @@ -31,8 +31,8 @@ use frame_system::{CheckWeight, RawOrigin as SystemOrigin}; use polkadot_primitives::MAX_POV_SIZE; use sp_core::ConstU32; use sp_runtime::{ - Digest, traits::{DispatchTransaction, Header, TransactionExtension}, + Digest, }; type TxExtension = @@ -802,14 +802,12 @@ fn executive_validate_block_handles_operational_transactions() { ExtrinsicOnlyOperational::new_bare(call) }; - assert!( - ExecutiveOnlyOperational::validate_transaction( - TransactionSource::External, - xt, - Default::default() - ) - .is_ok() - ); + assert!(ExecutiveOnlyOperational::validate_transaction( + TransactionSource::External, + xt, + Default::default() + ) + .is_ok()); }); } } @@ -975,13 +973,11 @@ fn executive_validate_transaction_respects_dispatch_class_max_block_size() { for signed in [true, false] { TestExtBuilder::new().previous_core_count(4).build().execute_with(|| { - assert!( - ::BlockWeights::get() - .get(DispatchClass::Normal) - .max_total - .unwrap() - .all_lt(call_weight) - ); + assert!(::BlockWeights::get() + .get(DispatchClass::Normal) + .max_total + .unwrap() + .all_lt(call_weight)); assert!(MaximumBlockWeight::target_block_weight().all_gt(call_weight)); let call = diff --git a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs index 417eb8773833f..f896b041f9697 100644 --- a/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs +++ b/cumulus/pallets/parachain-system/src/block_weight/transaction_extension.rs @@ -15,8 +15,8 @@ // limitations under the License. use super::{ - BlockWeightMode, FULL_CORE_WEIGHT, LOG_TARGET, MaxParachainBlockWeight, block_weight_over_target_block_weight, inside_pre_validate, is_first_block_in_core_with_digest, + BlockWeightMode, MaxParachainBlockWeight, FULL_CORE_WEIGHT, LOG_TARGET, }; use crate::WeightInfo; use alloc::vec::Vec; @@ -32,8 +32,8 @@ use frame_support::{ use scale_info::TypeInfo; use sp_core::Get; use sp_runtime::{ - DispatchResult, traits::{DispatchInfoOf, Dispatchable, Implication, PostDispatchInfoOf, TransactionExtension}, + DispatchResult, }; /// Transaction extension that dynamically changes the max block weight. @@ -108,12 +108,12 @@ impl DynamicMaxBlockWeight + Config, + Inner, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, + > DynamicMaxBlockWeight where Config: crate::Config, TargetBlockRate: Get, @@ -363,12 +363,12 @@ where } impl< - Config, - Inner, - TargetBlockRate, - const MAX_TRANSACTION_TO_CONSIDER: u32, - const ALLOW_NORMAL: bool, -> From + Config, + Inner, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, + > From for DynamicMaxBlockWeight< Config, Inner, @@ -383,12 +383,12 @@ impl< } impl< - Config, - Inner: core::fmt::Debug, - TargetBlockRate, - const MAX_TRANSACTION_TO_CONSIDER: u32, - const ALLOW_NORMAL: bool, -> core::fmt::Debug + Config, + Inner: core::fmt::Debug, + TargetBlockRate, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, + > core::fmt::Debug for DynamicMaxBlockWeight< Config, Inner, @@ -403,12 +403,12 @@ impl< } impl< - Config: crate::Config + Send + Sync, - Inner: TransactionExtension, - TargetBlockRate: Get + Send + Sync + 'static, - const MAX_TRANSACTION_TO_CONSIDER: u32, - const ALLOW_NORMAL: bool, -> TransactionExtension + Config: crate::Config + Send + Sync, + Inner: TransactionExtension, + TargetBlockRate: Get + Send + Sync + 'static, + const MAX_TRANSACTION_TO_CONSIDER: u32, + const ALLOW_NORMAL: bool, + > TransactionExtension for DynamicMaxBlockWeight< Config, Inner, diff --git a/cumulus/pallets/parachain-system/src/descendant_validation.rs b/cumulus/pallets/parachain-system/src/descendant_validation.rs index cf51374fd67c0..b856d3298b01b 100644 --- a/cumulus/pallets/parachain-system/src/descendant_validation.rs +++ b/cumulus/pallets/parachain-system/src/descendant_validation.rs @@ -14,15 +14,15 @@ // limitations under the License. use crate::{ - RelayChainStateProof, descendant_validation::RelayParentVerificationError::InvalidNumberOfDescendants, + RelayChainStateProof, }; use alloc::vec::Vec; use sp_consensus_babe::{ - AuthorityIndex, digests::{CompatibleDigestItem, NextEpochDescriptor}, + AuthorityIndex, }; -use sp_runtime::{RuntimeAppPublic, traits::Header}; +use sp_runtime::{traits::Header, RuntimeAppPublic}; /// Verifies that the provided relay parent descendants form a valid chain /// and are signed by relay chain authorities. If relay chain descendants shall be checked, @@ -217,15 +217,15 @@ mod tests { use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use rstest::rstest; use sp_consensus_babe::{ - AuthorityId, AuthorityPair, BABE_ENGINE_ID, BabeAuthorityWeight, ConsensusLog, digests::{CompatibleDigestItem, NextEpochDescriptor, PreDigest, PrimaryPreDigest}, + AuthorityId, AuthorityPair, BabeAuthorityWeight, ConsensusLog, BABE_ENGINE_ID, }; use sp_core::{ - H256, Pair, sr25519::vrf::{VrfPreOutput, VrfProof, VrfSignature}, + Pair, H256, }; use sp_keyring::Sr25519Keyring; - use sp_runtime::{DigestItem, testing::Header as TestHeader}; + use sp_runtime::{testing::Header as TestHeader, DigestItem}; const PARA_ID: u32 = 2000; /// Verify a header chain with different lengths and different number of authorities included in @@ -244,15 +244,13 @@ mod tests { // Expected number of parents passed to the function does not include actual relay parent let expected_number_of_descendants = (relay_parent_descendants.len() - 1) as u32; - assert!( - verify_relay_parent_descendants( - &relay_state_proof, - relay_parent_descendants, - relay_parent_state_root, - expected_number_of_descendants, - ) - .is_ok() - ); + assert!(verify_relay_parent_descendants( + &relay_state_proof, + relay_parent_descendants, + relay_parent_state_root, + expected_number_of_descendants, + ) + .is_ok()); } #[rstest] @@ -446,15 +444,13 @@ mod tests { // Expected number of parents passed to the function does not include actual relay parent let expected_number_of_descendants = (relay_parent_descendants.len() - 1) as u32; - assert!( - verify_relay_parent_descendants( - &relay_state_proof, - relay_parent_descendants, - relay_parent_state_root, - expected_number_of_descendants, - ) - .is_ok() - ); + assert!(verify_relay_parent_descendants( + &relay_state_proof, + relay_parent_descendants, + relay_parent_state_root, + expected_number_of_descendants, + ) + .is_ok()); } /// Test some interesting epoch change positions, like epoch change on RP directly, and last @@ -474,15 +470,13 @@ mod tests { // Expected number of parents passed to the function does not include actual relay parent let expected_number_of_descendants = (relay_parent_descendants.len() - 1) as u32; - assert!( - verify_relay_parent_descendants( - &relay_state_proof, - relay_parent_descendants, - relay_parent_state_root, - expected_number_of_descendants, - ) - .is_ok() - ); + assert!(verify_relay_parent_descendants( + &relay_state_proof, + relay_parent_descendants, + relay_parent_state_root, + expected_number_of_descendants, + ) + .is_ok()); } /// Helper function to create a mock `RelayChainStateProof`. diff --git a/cumulus/pallets/parachain-system/src/mock.rs b/cumulus/pallets/parachain-system/src/mock.rs index e9682ab172ca6..15acd522d8ff0 100644 --- a/cumulus/pallets/parachain-system/src/mock.rs +++ b/cumulus/pallets/parachain-system/src/mock.rs @@ -24,8 +24,8 @@ use alloc::collections::vec_deque::VecDeque; use codec::Encode; use core::num::NonZeroU32; use cumulus_primitives_core::{ - AggregateMessageOrigin, InboundDownwardMessage, InboundHrmpMessage, PersistedValidationData, - relay_chain::BlockNumber as RelayBlockNumber, + relay_chain::BlockNumber as RelayBlockNumber, AggregateMessageOrigin, InboundDownwardMessage, + InboundHrmpMessage, PersistedValidationData, }; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use frame_support::{ @@ -37,9 +37,9 @@ use frame_support::{ }, weights::{Weight, WeightMeter}, }; -use frame_system::{RawOrigin, limits::BlockWeights, pallet_prelude::BlockNumberFor}; +use frame_system::{limits::BlockWeights, pallet_prelude::BlockNumberFor, RawOrigin}; use sp_core::ConstU32; -use sp_runtime::{BuildStorage, traits::BlakeTwo256}; +use sp_runtime::{traits::BlakeTwo256, BuildStorage}; use sp_version::RuntimeVersion; use std::cell::RefCell; diff --git a/cumulus/pallets/parachain-system/src/parachain_inherent.rs b/cumulus/pallets/parachain-system/src/parachain_inherent.rs index 15a472162526c..348f1e3a1ff5e 100644 --- a/cumulus/pallets/parachain-system/src/parachain_inherent.rs +++ b/cumulus/pallets/parachain-system/src/parachain_inherent.rs @@ -19,10 +19,10 @@ use alloc::{collections::btree_map::BTreeMap, vec, vec::Vec}; use core::fmt::Debug; use cumulus_primitives_core::{ - InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, relay_chain::{ ApprovedPeerId, BlockNumber as RelayChainBlockNumber, BlockNumber, Header as RelayHeader, }, + InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData, }; use cumulus_primitives_parachain_inherent::{HashedMessage, ParachainInherentData}; use frame_support::{ @@ -30,7 +30,7 @@ use frame_support::{ pallet_prelude::{Decode, DecodeWithMemTracking, Encode}, }; use scale_info::TypeInfo; -use sp_core::{Get, bounded::BoundedSlice}; +use sp_core::{bounded::BoundedSlice, Get}; /// A structure that helps identify a message inside a collection of messages sorted by `sent_at`. /// diff --git a/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs b/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs index 941b279f38b4e..75687d0c3e481 100644 --- a/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs +++ b/cumulus/pallets/parachain-system/src/relay_state_snapshot.rs @@ -19,12 +19,12 @@ use alloc::vec::Vec; use codec::{Decode, Encode}; use cumulus_primitives_core::{ - AbridgedHostConfiguration, AbridgedHrmpChannel, ParaId, relay_chain, + relay_chain, AbridgedHostConfiguration, AbridgedHrmpChannel, ParaId, }; use scale_info::TypeInfo; use sp_runtime::traits::HashingFor; use sp_state_machine::{Backend, TrieBackend, TrieBackendBuilder}; -use sp_trie::{EMPTY_PREFIX, HashDBT, MemoryDB, StorageProof}; +use sp_trie::{HashDBT, MemoryDB, StorageProof, EMPTY_PREFIX}; /// The capacity of the upward message queue of a parachain on the relay chain. // The field order should stay the same as the data can be found in the proof to ensure both are diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs index 199a4b564b024..eb295f5d78fef 100755 --- a/cumulus/pallets/parachain-system/src/tests.rs +++ b/cumulus/pallets/parachain-system/src/tests.rs @@ -22,11 +22,11 @@ use crate::mock::*; use alloc::collections::BTreeMap; use core::num::NonZeroU32; use cumulus_primitives_core::{ - AbridgedHrmpChannel, CUMULUS_CONSENSUS_ID, ClaimQueueOffset, CoreInfo, CoreSelector, - InboundDownwardMessage, InboundHrmpMessage, relay_chain::ApprovedPeerId, + relay_chain::ApprovedPeerId, AbridgedHrmpChannel, ClaimQueueOffset, CoreInfo, CoreSelector, + InboundDownwardMessage, InboundHrmpMessage, CUMULUS_CONSENSUS_ID, }; use cumulus_primitives_parachain_inherent::{ - INHERENT_IDENTIFIER, PARACHAIN_INHERENT_IDENTIFIER_V0, v0, + v0, INHERENT_IDENTIFIER, PARACHAIN_INHERENT_IDENTIFIER_V0, }; use frame_support::{assert_ok, parameter_types, weights::Weight}; use frame_system::RawOrigin; @@ -905,12 +905,10 @@ fn runtime_upgrade_events() { }) ); - assert!( - System::digest() - .logs() - .iter() - .any(|d| *d == sp_runtime::generic::DigestItem::RuntimeEnvironmentUpdated) - ); + assert!(System::digest() + .logs() + .iter() + .any(|d| *d == sp_runtime::generic::DigestItem::RuntimeEnvironmentUpdated)); }, ); } @@ -1700,10 +1698,10 @@ fn deposits_relay_parent_storage_root() { || {}, || { let digest = System::digest(); - assert!( - cumulus_primitives_core::rpsr_digest::extract_relay_parent_storage_root(&digest) - .is_some() - ); + assert!(cumulus_primitives_core::rpsr_digest::extract_relay_parent_storage_root( + &digest + ) + .is_some()); }, ); } @@ -1828,4 +1826,3 @@ fn ump_signals_are_sent_correctly() { ); } } - diff --git a/cumulus/pallets/parachain-system/src/unincluded_segment.rs b/cumulus/pallets/parachain-system/src/unincluded_segment.rs index df3587cf79808..493d8afd97acc 100644 --- a/cumulus/pallets/parachain-system/src/unincluded_segment.rs +++ b/cumulus/pallets/parachain-system/src/unincluded_segment.rs @@ -21,12 +21,12 @@ //! sent to relay chain. use super::relay_state_snapshot::{MessagingStateSnapshot, RelayDispatchQueueRemainingCapacity}; -use Debug; use alloc::collections::btree_map::BTreeMap; use codec::{Decode, Encode}; use core::marker::PhantomData; -use cumulus_primitives_core::{ParaId, relay_chain}; +use cumulus_primitives_core::{relay_chain, ParaId}; use scale_info::TypeInfo; +use Debug; /// Constraints on outbound HRMP channel. #[derive(Clone, Debug)] diff --git a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs index 5b2ea74205e81..a4de351c50cba 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/implementation.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/implementation.rs @@ -16,28 +16,28 @@ //! The actual implementation of the validate block functionality. -use super::{MemoryOptimizedValidationParams, trie_cache, trie_recorder}; +use super::{trie_cache, trie_recorder, MemoryOptimizedValidationParams}; use alloc::vec::Vec; use codec::{Decode, Encode}; use cumulus_primitives_core::{ - ClaimQueueOffset, CoreSelector, ParachainBlockData, PersistedValidationData, relay_chain::{ - BlockNumber as RNumber, Hash as RHash, MAX_HEAD_DATA_SIZE, UMP_SEPARATOR, UMPSignal, + BlockNumber as RNumber, Hash as RHash, UMPSignal, MAX_HEAD_DATA_SIZE, UMP_SEPARATOR, }, + ClaimQueueOffset, CoreSelector, ParachainBlockData, PersistedValidationData, }; use frame_support::{ - BoundedVec, traits::{ExecuteBlock, Get, IsSubType}, + BoundedVec, }; use polkadot_parachain_primitives::primitives::{HeadData, ValidationResult}; -use sp_core::storage::{ChildInfo, StateVersion, well_known_keys}; -use sp_externalities::{Externalities, set_and_run_with_externalities}; -use sp_io::{KillStorageResult, hashing::blake2_128}; +use sp_core::storage::{well_known_keys, ChildInfo, StateVersion}; +use sp_externalities::{set_and_run_with_externalities, Externalities}; +use sp_io::{hashing::blake2_128, KillStorageResult}; use sp_runtime::traits::{ Block as BlockT, ExtrinsicCall, Hash as HashT, HashingFor, Header as HeaderT, LazyBlock, }; use sp_state_machine::OverlayedChanges; -use sp_trie::{EMPTY_PREFIX, HashDBT, ProofSizeProvider}; +use sp_trie::{HashDBT, ProofSizeProvider, EMPTY_PREFIX}; use trie_recorder::{SeenNodes, SizeOnlyRecorderProvider}; type Ext<'a, Block, Backend> = sp_state_machine::Ext<'a, HashingFor, Backend>; diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index b496ac3e0508f..f86090ad3d623 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -17,31 +17,31 @@ use crate::{validate_block::MemoryOptimizedValidationParams, *}; use codec::{Decode, DecodeAll, Encode}; use cumulus_primitives_core::{ + relay_chain, + relay_chain::{UMPSignal, UMP_SEPARATOR}, BundleInfo, ClaimQueueOffset, CoreInfo, CoreSelector, CumulusDigestItem, ParaId, - ParachainBlockData, PersistedValidationData, relay_chain, - relay_chain::{UMP_SEPARATOR, UMPSignal}, + ParachainBlockData, PersistedValidationData, }; use cumulus_test_client::{ - BlockData, BlockOrigin, BuildParachainBlockData, Client, DefaultTestClientBuilderExt, HeadData, - InitBlockBuilder, - Sr25519Keyring::{Alice, Bob, Charlie}, - TestClientBuilder, TestClientBuilderExt, ValidationParams, generate_extrinsic, - generate_extrinsic_with_pair, + generate_extrinsic, generate_extrinsic_with_pair, runtime::{ self as test_runtime, Block, Hash, Header, SudoCall, SystemCall, TestPalletCall, UncheckedExtrinsic, WASM_BINARY, }, - seal_block, transfer, + seal_block, transfer, BlockData, BlockOrigin, BuildParachainBlockData, Client, + DefaultTestClientBuilderExt, HeadData, InitBlockBuilder, + Sr25519Keyring::{Alice, Bob, Charlie}, + TestClientBuilder, TestClientBuilderExt, ValidationParams, }; use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use polkadot_parachain_primitives::primitives::ValidationResult; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; use sp_api::{ApiExt, Core, ProofRecorder, ProvideRuntimeApi, StorageProof}; use sp_consensus_babe::SlotDuration; -use sp_core::{H256, Hasher}; +use sp_core::{Hasher, H256}; use sp_runtime::{ - DigestItem, traits::{BlakeTwo256, Block as BlockT, Header as HeaderT}, + DigestItem, }; use sp_trie::{proof_size_extension::ProofSizeExt, recorder::IgnoredNodes}; use std::{env, process::Command}; @@ -214,13 +214,11 @@ fn build_multiple_blocks_with_witness( sproof_builder.clone(), timestamp, ignored_nodes.clone(), - Some(vec![ - CumulusDigestItem::BundleInfo(BundleInfo { - index: i as u8, - maybe_last: i as u32 + 1 == num_blocks, - }) - .to_digest_item(), - ]), + Some(vec![CumulusDigestItem::BundleInfo(BundleInfo { + index: i as u8, + maybe_last: i as u32 + 1 == num_blocks, + }) + .to_digest_item()]), ); persisted_validation_data = Some(p_v_data); @@ -454,10 +452,8 @@ fn validate_block_invalid_parent_hash() { .expect("Runs the test"); assert!(output.status.success()); - assert!( - dbg!(String::from_utf8(output.stderr).unwrap()) - .contains("Parachain head needs to be the parent of the first block") - ); + assert!(dbg!(String::from_utf8(output.stderr).unwrap()) + .contains("Parachain head needs to be the parent of the first block")); } } @@ -484,10 +480,8 @@ fn validate_block_fails_on_invalid_validation_data() { .expect("Runs the test"); assert!(output.status.success()); - assert!( - dbg!(String::from_utf8(output.stderr).unwrap()) - .contains("Relay parent storage root doesn't match") - ); + assert!(dbg!(String::from_utf8(output.stderr).unwrap()) + .contains("Relay parent storage root doesn't match")); } } @@ -642,14 +636,12 @@ fn validate_block_handles_ump_signal() { extra_extrinsics, parent_head.clone(), Default::default(), - vec![ - CumulusDigestItem::CoreInfo(CoreInfo { - selector: CoreSelector(0), - claim_queue_offset: ClaimQueueOffset(0), - number_of_cores: 1.into(), - }) - .to_digest_item(), - ], + vec![CumulusDigestItem::CoreInfo(CoreInfo { + selector: CoreSelector(0), + claim_queue_offset: ClaimQueueOffset(0), + number_of_cores: 1.into(), + }) + .to_digest_item()], ); let upward_messages = call_validate_block_validation_result( @@ -699,10 +691,8 @@ fn ensure_we_only_like_blockchains() { .expect("Runs the test"); assert!(output.status.success()); - assert!( - dbg!(String::from_utf8(output.stderr).unwrap()) - .contains("Not a valid chain of blocks :(") - ); + assert!(dbg!(String::from_utf8(output.stderr).unwrap()) + .contains("Not a valid chain of blocks :(")); } } @@ -787,10 +777,8 @@ fn rejects_multiple_blocks_per_pov_when_applying_runtime_upgrade() { assert!(output.status.success()); - assert!( - dbg!(String::from_utf8(output.stderr).unwrap()) - .contains("only one block per PoV is allowed") - ); + assert!(dbg!(String::from_utf8(output.stderr).unwrap()) + .contains("only one block per PoV is allowed")); } } @@ -918,7 +906,7 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { Some(i), ), ]; - + // Add UMP message of 500 bytes in each block if i < 3 { // Send 500 bytes in blocks 0, 1, 2 (total 1500 bytes) @@ -929,7 +917,8 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { Some(i), )); } - // Block 3: try to send 600 bytes, should be deferred due to size limit (2000 - 1500 = 500 remaining) + // Block 3: try to send 600 bytes, should be deferred due to size limit (2000 - 1500 = + // 500 remaining) else { extrinsics.push(generate_extrinsic_with_pair( &client, @@ -938,7 +927,7 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { Some(i), )); } - + extrinsics }, ); @@ -958,11 +947,15 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { // Verify HRMP messages (original assertion) assert_eq!(result.horizontal_messages.len(), max_per_candidate as usize); - + // Verify UMP size enforcement: only 3 messages should be sent (blocks 0, 1, 2) // Block 3's 600 byte message should be deferred because only 500 bytes remain - assert_eq!(result.upward_messages.len(), 3, "Expected 3 UMP messages to be sent (block 3's message deferred due to size limit)"); - + assert_eq!( + result.upward_messages.len(), + 3, + "Expected 3 UMP messages to be sent (block 3's message deferred due to size limit)" + ); + // Verify sizes of sent messages for msg in result.upward_messages.iter() { assert_eq!(msg.len(), 500, "Each sent message should be 500 bytes"); diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs index e0a50d11165b8..cf6eb6fb94f33 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_cache.rs @@ -18,10 +18,10 @@ use alloc::boxed::Box; use core::cell::{RefCell, RefMut}; -use hashbrown::{HashMap, hash_map::Entry}; +use hashbrown::{hash_map::Entry, HashMap}; use sp_state_machine::TrieCacheProvider; use sp_trie::{NodeCodec, RandomState}; -use trie_db::{Hasher, node::NodeOwned}; +use trie_db::{node::NodeOwned, Hasher}; /// Special purpose trie cache implementation that is able to cache an unlimited number /// of values. To be used in `validate_block` to serve values and nodes that diff --git a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs index b491e30be86b4..3e4b854d301ee 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/trie_recorder.rs @@ -148,8 +148,8 @@ unsafe impl Sync for SizeOnlyRecorderProvider {} mod tests { use rand::Rng; use sp_trie::{ - MemoryDB, ProofSizeProvider, TrieRecorderProvider, cache::{CacheSize, SharedTrieCache}, + MemoryDB, ProofSizeProvider, TrieRecorderProvider, }; use trie_db::{Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut, TrieRecorder}; use trie_standardmap::{Alphabet, StandardMap, ValueMode}; From 249b84647f876f7a556159ddb1a793f472691192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 10 Mar 2026 23:28:30 +0100 Subject: [PATCH 26/30] Prdoc + fixes --- cumulus/pallets/parachain-system/Cargo.toml | 2 ++ prdoc/pr_10315.prdoc | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 prdoc/pr_10315.prdoc diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 5fdf4e2d68352..32fb1c2587161 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -125,6 +125,7 @@ runtime-benchmarks = [ "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm/runtime-benchmarks", ] @@ -134,6 +135,7 @@ try-runtime = [ "frame-system/try-runtime", "pallet-message-queue/try-runtime", "polkadot-runtime-parachains/try-runtime", + "frame-executive/try-runtime", "sp-runtime/try-runtime", ] diff --git a/prdoc/pr_10315.prdoc b/prdoc/pr_10315.prdoc new file mode 100644 index 0000000000000..dbd680e6f1e3b --- /dev/null +++ b/prdoc/pr_10315.prdoc @@ -0,0 +1,16 @@ +title: "Introduce `MaxParachainBlockWeight` and related functionality" +doc: +- audience: Runtime Dev + description: | + This PR introduces `MaxParachainBlockWeight` to calculate the max weight per parachain block dynamically. + This is a preparation for Block Bundling which requires that the maximum block weight is dynamic. + Block bundling requires a dynamic maximum block weight because it bundles multiple blocks into one PoV. + Each PoV gets 2s of execution time and 10MiB of proof size. These resources need to be split up between + all the blocks of one PoV. + + Additionally, this PR adds UMP message size tracking and enforcement across PoV to ensure that message + size limits are respected when multiple blocks are bundled in the same PoV. + +crates: +- name: cumulus-pallet-parachain-system + bump: minor From 39f6ffc1ea33e444b89b97033b8cf93bccefc670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Tue, 10 Mar 2026 23:43:36 +0100 Subject: [PATCH 27/30] More fixes --- cumulus/pallets/parachain-system/Cargo.toml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 32fb1c2587161..846314a6c3b94 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -90,6 +90,7 @@ std = [ "cumulus-primitives-proof-size-hostfunction/std", "environmental/std", "frame-benchmarking/std", + "frame-executive/std", "frame-support/std", "frame-system/std", "log/std", @@ -113,6 +114,7 @@ std = [ "trie-db/std", "xcm-builder/std", "xcm/std", + "frame-executive/std" ] runtime-benchmarks = [ @@ -123,19 +125,19 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-primitives/runtime-benchmarks", "polkadot-runtime-parachains/runtime-benchmarks", "sp-runtime/runtime-benchmarks", - "polkadot-primitives/runtime-benchmarks", "xcm-builder/runtime-benchmarks", "xcm/runtime-benchmarks", ] try-runtime = [ + "frame-executive/try-runtime", "frame-support/try-runtime", "frame-system/try-runtime", "pallet-message-queue/try-runtime", "polkadot-runtime-parachains/try-runtime", - "frame-executive/try-runtime", "sp-runtime/try-runtime", ] From eacf91b2d2b65a4e4142899865ea764050e8fe7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 11 Mar 2026 15:36:49 +0100 Subject: [PATCH 28/30] Fix errors --- cumulus/pallets/parachain-system/Cargo.toml | 1 - .../src/validate_block/tests.rs | 6 ++-- prdoc/pr_10315.prdoc | 28 ++++++++++++++++++- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/cumulus/pallets/parachain-system/Cargo.toml b/cumulus/pallets/parachain-system/Cargo.toml index 846314a6c3b94..69b77bca478de 100644 --- a/cumulus/pallets/parachain-system/Cargo.toml +++ b/cumulus/pallets/parachain-system/Cargo.toml @@ -114,7 +114,6 @@ std = [ "trie-db/std", "xcm-builder/std", "xcm/std", - "frame-executive/std" ] runtime-benchmarks = [ diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index f86090ad3d623..2121cc9a50926 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -903,7 +903,7 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { &client, Charlie.into(), TestPalletCall::queue_hrmp_messages { n: max_per_candidate, recipient }, - Some(i), + Some(i * 2), ), ]; @@ -914,7 +914,7 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { &client, Charlie.into(), TestPalletCall::send_upward_message_of_size { size: 500 }, - Some(i), + Some(i * 2 + 1), )); } // Block 3: try to send 600 bytes, should be deferred due to size limit (2000 - 1500 = @@ -924,7 +924,7 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { &client, Charlie.into(), TestPalletCall::send_upward_message_of_size { size: 600 }, - Some(i), + Some(i * 2 + 1), )); } diff --git a/prdoc/pr_10315.prdoc b/prdoc/pr_10315.prdoc index dbd680e6f1e3b..f68c3bbb2a849 100644 --- a/prdoc/pr_10315.prdoc +++ b/prdoc/pr_10315.prdoc @@ -13,4 +13,30 @@ doc: crates: - name: cumulus-pallet-parachain-system - bump: minor + bump: major +- name: frame-support + bump: patch +- name: sp-runtime + bump: patch +- name: frame-system + bump: patch +- name: cumulus-primitives-core + bump: patch +- name: polkadot-primitives + bump: patch +- name: asset-hub-rococo-runtime + bump: patch +- name: asset-hub-westend-runtime + bump: patch +- name: bridge-hub-rococo-runtime + bump: patch +- name: bridge-hub-westend-runtime + bump: patch +- name: collectives-westend-runtime + bump: patch +- name: coretime-westend-runtime + bump: patch +- name: people-westend-runtime + bump: patch +- name: glutton-westend-runtime + bump: patch From d3e8abba5969b030231d4c301593578b88c02a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 12 Mar 2026 11:56:40 +0100 Subject: [PATCH 29/30] Better tracking --- cumulus/pallets/parachain-system/src/lib.rs | 23 +-- .../src/validate_block/tests.rs | 150 ++++++++++++------ 2 files changed, 108 insertions(+), 65 deletions(-) diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 1b9184481b642..4bb2eb78eddc7 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -112,7 +112,7 @@ pub use pallet::*; const LOG_TARGET: &str = "runtime::parachain-system"; -/// Tracks cumulative UMP and HRMP messages and their sizes sent across blocks within a single PoV. +/// Tracks cumulative UMP and HRMP message counts sent across blocks within a single PoV. #[derive(Encode, Decode, Clone, Debug, TypeInfo, Default)] pub struct PoVMessages { /// Relay parent storage root of the current PoV. @@ -123,8 +123,6 @@ pub struct PoVMessages { pub bundle_index: u8, /// Cumulative count of UMP messages sent in this PoV. pub ump_msg_count: u32, - /// Cumulative size of UMP messages sent in this PoV. - pub ump_msg_size: u32, /// Cumulative count of HRMP outbound messages sent in this PoV. pub hrmp_outbound_count: u32, } @@ -381,9 +379,6 @@ pub mod pallet { .saturating_sub(pov_tracker.ump_msg_count), ); - // Also reduce available_size by what we've already sent in this PoV - let available_size = available_size.saturating_sub(pov_tracker.ump_msg_size); - // Count the number of messages we can possibly fit in the given constraints, i.e. // available_capacity and available_size. let (num, total_size) = up @@ -412,7 +407,6 @@ pub mod pallet { *up = up.split_off(num as usize); pov_tracker.ump_msg_count = pov_tracker.ump_msg_count.saturating_add(num); - pov_tracker.ump_msg_size = pov_tracker.ump_msg_size.saturating_add(total_size); if let Some(core_info) = CumulusDigestItem::find_core_info(&frame_system::Pallet::::digest()) @@ -1557,24 +1551,17 @@ impl Pallet { // Reads: 2 // Writes: 1 fn adjust_egress_bandwidth_limits() { - let unincluded_segment = match AggregatedUnincludedSegment::::get() { - None => return, - Some(s) => s, - }; + let Some(unincluded_segment) = AggregatedUnincludedSegment::::get() else { return }; >::mutate(|messaging_state| { - let messaging_state = match messaging_state { - None => return, - Some(s) => s, - }; + let Some(messaging_state) = messaging_state else { return }; let used_bandwidth = unincluded_segment.used_bandwidth(); let channels = &mut messaging_state.egress_channels; for (para_id, used) in used_bandwidth.hrmp_outgoing.iter() { - let i = match channels.binary_search_by_key(para_id, |item| item.0) { - Ok(i) => i, - Err(_) => continue, // indicates channel closed. + let Ok(i) = channels.binary_search_by_key(para_id, |item| item.0) else { + continue; // indicates channel closed. }; let c = &mut channels[i].1; diff --git a/cumulus/pallets/parachain-system/src/validate_block/tests.rs b/cumulus/pallets/parachain-system/src/validate_block/tests.rs index 2121cc9a50926..382dd8e798540 100644 --- a/cumulus/pallets/parachain-system/src/validate_block/tests.rs +++ b/cumulus/pallets/parachain-system/src/validate_block/tests.rs @@ -886,10 +886,53 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { channel.max_total_size = blocks_per_pov * max_per_candidate * 256; channel.max_message_size = 256; - // Configure UMP limits for size enforcement testing - sproof_builder.host_config.max_upward_message_num_per_candidate = 10; + let TestBlockData { block, validation_data } = build_multiple_blocks_with_witness( + &client, + parent_head.clone(), + sproof_builder, + blocks_per_pov, + |i| { + vec![generate_extrinsic_with_pair( + &client, + Charlie.into(), + TestPalletCall::queue_hrmp_messages { n: max_per_candidate, recipient }, + Some(i), + )] + }, + ); + + let header = block.blocks().last().unwrap().header().clone(); + let result = call_validate_block_validation_result( + test_runtime::elastic_scaling_500ms::WASM_BINARY + .expect("You need to build the WASM binaries to run the tests!"), + parent_head, + block, + validation_data.relay_parent_storage_root, + ) + .expect("Calls `validate_block`"); + + let res_header = Header::decode(&mut &result.head_data.0[..]).expect("Decodes `Header`."); + assert_eq!(header, res_header); + + assert_eq!(result.horizontal_messages.len(), max_per_candidate as usize); +} + +#[test] +fn validate_block_with_ump_size_constraint_and_4_blocks_per_pov() { + sp_tracing::try_init_simple(); + + let blocks_per_pov = 4; + let msg_size = 500; + let (client, parent_head) = create_elastic_scaling_test_client(); + + let mut sproof_builder = + RelayStateSproofBuilder { current_slot: 1.into(), ..Default::default() }; + sproof_builder.host_config.max_upward_message_num_per_candidate = 100; sproof_builder.host_config.max_upward_message_size = 1000; - sproof_builder.relay_dispatch_queue_remaining_capacity = Some((10, 2000)); + sproof_builder.host_config.max_upward_queue_count = 100; + sproof_builder.host_config.max_upward_queue_size = 100_000; + // Only 1500 bytes of remaining size: enough for 3 x 500-byte messages but not 4. + sproof_builder.relay_dispatch_queue_remaining_capacity = Some((100, 1500)); let TestBlockData { block, validation_data } = build_multiple_blocks_with_witness( &client, @@ -897,38 +940,12 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { sproof_builder, blocks_per_pov, |i| { - let mut extrinsics = vec![ - // Send HRMP messages (original test) - generate_extrinsic_with_pair( - &client, - Charlie.into(), - TestPalletCall::queue_hrmp_messages { n: max_per_candidate, recipient }, - Some(i * 2), - ), - ]; - - // Add UMP message of 500 bytes in each block - if i < 3 { - // Send 500 bytes in blocks 0, 1, 2 (total 1500 bytes) - extrinsics.push(generate_extrinsic_with_pair( - &client, - Charlie.into(), - TestPalletCall::send_upward_message_of_size { size: 500 }, - Some(i * 2 + 1), - )); - } - // Block 3: try to send 600 bytes, should be deferred due to size limit (2000 - 1500 = - // 500 remaining) - else { - extrinsics.push(generate_extrinsic_with_pair( - &client, - Charlie.into(), - TestPalletCall::send_upward_message_of_size { size: 600 }, - Some(i * 2 + 1), - )); - } - - extrinsics + vec![generate_extrinsic_with_pair( + &client, + Charlie.into(), + TestPalletCall::send_upward_message_of_size { size: msg_size }, + Some(i), + )] }, ); @@ -945,19 +962,58 @@ fn validate_block_with_max_hrmp_messages_and_4_blocks_per_pov() { let res_header = Header::decode(&mut &result.head_data.0[..]).expect("Decodes `Header`."); assert_eq!(header, res_header); - // Verify HRMP messages (original assertion) - assert_eq!(result.horizontal_messages.len(), max_per_candidate as usize); + // Only 3 of 4 messages should be sent. The 4th is deferred because + // 3 x 500 = 1500 bytes exhausts the remaining size budget. + let ump_count = result.upward_messages.iter().take_while(|m| **m != UMP_SEPARATOR).count(); + assert_eq!(ump_count, 3); +} - // Verify UMP size enforcement: only 3 messages should be sent (blocks 0, 1, 2) - // Block 3's 600 byte message should be deferred because only 500 bytes remain - assert_eq!( - result.upward_messages.len(), - 3, - "Expected 3 UMP messages to be sent (block 3's message deferred due to size limit)" +#[test] +fn validate_block_with_ump_capacity_constraint_and_4_blocks_per_pov() { + sp_tracing::try_init_simple(); + + let blocks_per_pov = 4; + let (client, parent_head) = create_elastic_scaling_test_client(); + + let mut sproof_builder = + RelayStateSproofBuilder { current_slot: 1.into(), ..Default::default() }; + sproof_builder.host_config.max_upward_message_num_per_candidate = 100; + sproof_builder.host_config.max_upward_message_size = 1000; + sproof_builder.host_config.max_upward_queue_count = 100; + sproof_builder.host_config.max_upward_queue_size = 100_000; + // Only 3 messages remaining in the relay dispatch queue. + sproof_builder.relay_dispatch_queue_remaining_capacity = Some((3, 100_000)); + + let TestBlockData { block, validation_data } = build_multiple_blocks_with_witness( + &client, + parent_head.clone(), + sproof_builder, + blocks_per_pov, + |i| { + vec![generate_extrinsic_with_pair( + &client, + Charlie.into(), + TestPalletCall::send_upward_message_of_size { size: 100 }, + Some(i), + )] + }, ); - // Verify sizes of sent messages - for msg in result.upward_messages.iter() { - assert_eq!(msg.len(), 500, "Each sent message should be 500 bytes"); - } + let header = block.blocks().last().unwrap().header().clone(); + let result = call_validate_block_validation_result( + test_runtime::elastic_scaling_500ms::WASM_BINARY + .expect("You need to build the WASM binaries to run the tests!"), + parent_head, + block, + validation_data.relay_parent_storage_root, + ) + .expect("Calls `validate_block`"); + + let res_header = Header::decode(&mut &result.head_data.0[..]).expect("Decodes `Header`."); + assert_eq!(header, res_header); + + // Only 3 of 4 messages should be sent. The 4th is deferred because + // the relay dispatch queue remaining count (3) is exhausted. + let ump_count = result.upward_messages.iter().take_while(|m| **m != UMP_SEPARATOR).count(); + assert_eq!(ump_count, 3); } From 9766f5db7a09f954ac59a1d8d720820bba0eef7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 12 Mar 2026 11:58:11 +0100 Subject: [PATCH 30/30] Docs --- cumulus/pallets/parachain-system/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs index 4bb2eb78eddc7..41d6e43cd7c18 100644 --- a/cumulus/pallets/parachain-system/src/lib.rs +++ b/cumulus/pallets/parachain-system/src/lib.rs @@ -1045,6 +1045,8 @@ pub mod pallet { pub type CustomValidationHeadData = StorageValue<_, Vec, OptionQuery>; /// Tracks cumulative `UMP` and `HRMP` messages sent across blocks in the current `PoV`. + /// + /// Across different candidates/PoVs the budgets are tracked by [`AggregatedUnincludedSegment`]. #[pallet::storage] pub type PoVMessagesTracker = StorageValue<_, PoVMessages, OptionQuery>;