referrerpolicy=no-referrer-when-downgrade

frame_system/
limits.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Block resource limits configuration structures.
19//!
20//! FRAME defines two resources that are limited within a block:
21//! - Weight (execution cost/time)
22//! - Length (block size)
23//!
24//! `frame_system` tracks consumption of each of these resources separately for each
25//! `DispatchClass`. This module contains configuration object for both resources,
26//! which should be passed to `frame_system` configuration when runtime is being set up.
27
28use alloc::{format, string::String, vec::Vec};
29use frame_support::{
30	dispatch::{DispatchClass, OneOrMany, PerDispatchClass},
31	weights::{constants, Weight},
32};
33use scale_info::TypeInfo;
34use sp_runtime::{traits::Bounded, Perbill};
35
36/// Block length limit configuration.
37#[derive(Debug, Clone, codec::Encode, codec::Decode, TypeInfo)]
38pub struct BlockLength {
39	/// Maximal total length in bytes for each extrinsic class.
40	///
41	/// In the worst case, the total block length is going to be:
42	/// `MAX(max)`
43	pub max: PerDispatchClass<u32>,
44	/// Optional maximum header size in bytes.
45	///
46	/// It is still possible that a header goes above this limit, if the runtime deposits too many
47	/// digests in the header. However, it is assumed that the runtime restricts the access for
48	/// depositing digests in the header.
49	///
50	/// If None, defaults to 20% of max block length.
51	pub max_header_size: Option<u32>,
52}
53
54impl Default for BlockLength {
55	fn default() -> Self {
56		let mut max = PerDispatchClass::new(|_| 5 * 1024 * 1024);
57		*max.get_mut(DispatchClass::Normal) =
58			DEFAULT_NORMAL_RATIO * *max.get_mut(DispatchClass::Normal);
59
60		Self { max, max_header_size: None }
61	}
62}
63
64impl BlockLength {
65	/// Create new `BlockLength` with `max` for every class.
66	#[deprecated(
67		note = "Use `BlockLength::builder().max(value).build()` instead. Will be removed after July 2026."
68	)]
69	pub fn max(max: u32) -> Self {
70		Self { max: PerDispatchClass::new(|_| max), max_header_size: None }
71	}
72
73	/// Create new `BlockLength` with `max` for `Operational` & `Mandatory`
74	/// and `normal * max` for `Normal`.
75	#[deprecated(
76		note = "Use `BlockLength::builder().normal_ratio(value, ratio).build()` instead. Will be removed after July 2026."
77	)]
78	pub fn max_with_normal_ratio(max: u32, normal: Perbill) -> Self {
79		Self {
80			max: PerDispatchClass::new(|class| {
81				if class == DispatchClass::Normal {
82					normal * max
83				} else {
84					max
85				}
86			}),
87			max_header_size: None,
88		}
89	}
90
91	/// Returns a builder to build a [`BlockLength`].
92	pub fn builder() -> BlockLengthBuilder {
93		BlockLengthBuilder { length: Default::default() }
94	}
95
96	/// Returns the maximum header size.
97	pub fn max_header_size(&self) -> u32 {
98		self.max_header_size.unwrap_or_else(|| {
99			let max_block_len = *self
100				.max
101				.get(DispatchClass::Normal)
102				.max(self.max.get(DispatchClass::Operational))
103				.max(self.max.get(DispatchClass::Mandatory));
104			max_block_len / 5
105		})
106	}
107}
108
109/// Builder for [`BlockLength`].
110pub struct BlockLengthBuilder {
111	length: BlockLength,
112}
113
114impl BlockLengthBuilder {
115	/// Set max block length for all classes.
116	pub fn max_length(mut self, max: u32) -> Self {
117		self.length.max = PerDispatchClass::new(|_| max);
118		self
119	}
120
121	/// Modify max block length for the given [`DispatchClass`].
122	pub fn modify_max_length_for_class(
123		mut self,
124		class: DispatchClass,
125		modify: impl Fn(&mut u32),
126	) -> Self {
127		modify(self.length.max.get_mut(class));
128		self
129	}
130
131	/// Set the max header size.
132	pub fn max_header_size(mut self, size: u32) -> Self {
133		self.length.max_header_size = Some(size);
134		self
135	}
136
137	/// Build the final [`BlockLength`].
138	pub fn build(self) -> BlockLength {
139		self.length
140	}
141}
142
143#[derive(Default, Debug)]
144pub struct ValidationErrors {
145	pub has_errors: bool,
146	pub errors: Vec<String>,
147}
148
149macro_rules! error_assert {
150	($cond : expr, $err : expr, $format : expr $(, $params: expr )*$(,)*) => {
151		if !$cond {
152			$err.has_errors = true;
153			{ $err.errors.push(format!($format $(, &$params )*)); }
154		}
155	}
156}
157
158/// A result of validating `BlockWeights` correctness.
159pub type ValidationResult = Result<BlockWeights, ValidationErrors>;
160
161/// A ratio of `Normal` dispatch class within block, used as default value for
162/// `BlockWeight` and `BlockLength`. The `Default` impls are provided mostly for convenience
163/// to use in tests.
164const DEFAULT_NORMAL_RATIO: Perbill = Perbill::from_percent(75);
165
166/// `DispatchClass`-specific weight configuration.
167#[derive(Debug, Clone, codec::Encode, codec::Decode, TypeInfo)]
168pub struct WeightsPerClass {
169	/// Base weight of single extrinsic of given class.
170	pub base_extrinsic: Weight,
171	/// Maximal weight of single extrinsic. Should NOT include `base_extrinsic` cost.
172	///
173	/// `None` indicates that this class of extrinsics doesn't have a limit.
174	pub max_extrinsic: Option<Weight>,
175	/// Block maximal total weight for all extrinsics of given class.
176	///
177	/// `None` indicates that weight sum of this class of extrinsics is not
178	/// restricted. Use this value carefully, since it might produce heavily oversized
179	/// blocks.
180	///
181	/// In the worst case, the total weight consumed by the class is going to be:
182	/// `MAX(max_total) + MAX(reserved)`.
183	pub max_total: Option<Weight>,
184	/// Block reserved allowance for all extrinsics of a particular class.
185	///
186	/// Setting to `None` indicates that extrinsics of that class are allowed
187	/// to go over total block weight (but at most `max_total` for that class).
188	/// Setting to `Some(x)` guarantees that at least `x` weight of particular class
189	/// is processed in every block.
190	pub reserved: Option<Weight>,
191}
192
193/// Block weight limits & base values configuration.
194///
195/// This object is responsible for defining weight limits and base weight values tracked
196/// during extrinsic execution.
197///
198/// Each block starts with `base_block` weight being consumed right away. Next up the
199/// `on_initialize` pallet callbacks are invoked and their cost is added before any extrinsic
200/// is executed. This cost is tracked as `Mandatory` dispatch class.
201///
202/// ```text,ignore
203/// |   | `max_block`    |   |
204/// |   |                |   |
205/// |   |                |   |
206/// |   |                |   |
207/// |   |                |  #| `on_initialize`
208/// |  #| `base_block`   |  #|
209/// |NOM|                |NOM|
210///  ||\_ Mandatory
211///  |\__ Operational
212///  \___ Normal
213/// ```
214///
215/// The remaining capacity can be used to dispatch extrinsics. Note that each dispatch class
216/// is being tracked separately, but the sum can't exceed `max_block` (except for `reserved`).
217/// Below you can see a picture representing full block with 3 extrinsics (two `Operational` and
218/// one `Normal`). Each class has it's own limit `max_total`, but also the sum cannot exceed
219/// `max_block` value.
220///
221/// ```text,ignore
222///                          -- `Mandatory` limit (unlimited)
223/// | # |                 |   |
224/// | # | `Ext3`          | - - `Operational` limit
225/// |#  | `Ext2`          |-  - `Normal` limit
226/// | # | `Ext1`          | # |
227/// |  #| `on_initialize` | ##|
228/// |  #| `base_block`    |###|
229/// |NOM|                 |NOM|
230/// ```
231///
232/// It should be obvious now that it's possible for one class to reach it's limit (say `Normal`),
233/// while the block has still capacity to process more transactions (`max_block` not reached,
234/// `Operational` transactions can still go in). Setting `max_total` to `None` disables the
235/// per-class limit. This is generally highly recommended for `Mandatory` dispatch class, while it
236/// can be dangerous for `Normal` class and should only be done with extra care and consideration.
237///
238/// Often it's desirable for some class of transactions to be added to the block despite it being
239/// full. For instance one might want to prevent high-priority `Normal` transactions from pushing
240/// out lower-priority `Operational` transactions. In such cases you might add a `reserved` capacity
241/// for given class.
242///
243/// ```test,ignore
244///              _
245///   #           \
246///   #   `Ext8`   - `reserved`
247///   #          _/
248/// | # | `Ext7                 | - - `Operational` limit
249/// |#  | `Ext6`                |   |
250/// |#  | `Ext5`                |-# - `Normal` limit
251/// |#  | `Ext4`                |## |
252/// |  #| `on_initialize`       |###|
253/// |  #| `base_block`          |###|
254/// |NOM|                       |NOM|
255/// ```
256///
257/// In the above example, `Ext4-6` fill up the block almost up to `max_block`. `Ext7` would not fit
258/// if there wasn't the extra `reserved` space for `Operational` transactions. Note that `max_total`
259/// limit applies to `reserved` space as well (i.e. the sum of weights of `Ext7` & `Ext8` mustn't
260/// exceed it). Setting `reserved` to `None` allows the extrinsics to always get into the block up
261/// to their `max_total` limit. If `max_total` is set to `None` as well, all extrinsics witch
262/// dispatchables of given class will always end up in the block (recommended for `Mandatory`
263/// dispatch class).
264///
265/// As a consequence of `reserved` space, total consumed block weight might exceed `max_block`
266/// value, so this parameter should rather be thought of as "target block weight" than a hard limit.
267#[derive(Debug, Clone, codec::Encode, codec::Decode, TypeInfo)]
268pub struct BlockWeights {
269	/// Base weight of block execution.
270	pub base_block: Weight,
271	/// Maximal total weight consumed by all kinds of extrinsics (without `reserved` space).
272	pub max_block: Weight,
273	/// Weight limits for extrinsics of given dispatch class.
274	pub per_class: PerDispatchClass<WeightsPerClass>,
275}
276
277impl Default for BlockWeights {
278	fn default() -> Self {
279		Self::with_sensible_defaults(
280			Weight::from_parts(constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX),
281			DEFAULT_NORMAL_RATIO,
282		)
283	}
284}
285
286impl BlockWeights {
287	/// Get per-class weight settings.
288	pub fn get(&self, class: DispatchClass) -> &WeightsPerClass {
289		self.per_class.get(class)
290	}
291
292	/// Verifies correctness of this `BlockWeights` object.
293	pub fn validate(self) -> ValidationResult {
294		fn or_max(w: Option<Weight>) -> Weight {
295			w.unwrap_or_else(Weight::max_value)
296		}
297		let mut error = ValidationErrors::default();
298
299		for class in DispatchClass::all() {
300			let weights = self.per_class.get(*class);
301			let max_for_class = or_max(weights.max_total);
302			let base_for_class = weights.base_extrinsic;
303			let reserved = or_max(weights.reserved);
304			// Make sure that if total is set it's greater than base_block &&
305			// base_for_class
306			error_assert!(
307				(max_for_class.all_gt(self.base_block) && max_for_class.all_gt(base_for_class))
308				|| max_for_class == Weight::zero(),
309				&mut error,
310				"[{:?}] {:?} (total) has to be greater than {:?} (base block) & {:?} (base extrinsic)",
311				class, max_for_class, self.base_block, base_for_class,
312			);
313			// Max extrinsic can't be greater than max_for_class.
314			error_assert!(
315				weights
316					.max_extrinsic
317					.unwrap_or(Weight::zero())
318					.all_lte(max_for_class.saturating_sub(base_for_class)),
319				&mut error,
320				"[{:?}] {:?} (max_extrinsic) can't be greater than {:?} (max for class)",
321				class,
322				weights.max_extrinsic,
323				max_for_class.saturating_sub(base_for_class),
324			);
325			// Max extrinsic should not be 0
326			error_assert!(
327				weights.max_extrinsic.unwrap_or_else(Weight::max_value).all_gt(Weight::zero()),
328				&mut error,
329				"[{:?}] {:?} (max_extrinsic) must not be 0. Check base cost and average initialization cost.",
330				class, weights.max_extrinsic,
331			);
332			// Make sure that if reserved is set it's greater than base_for_class.
333			error_assert!(
334				reserved.all_gt(base_for_class) || reserved == Weight::zero(),
335				&mut error,
336				"[{:?}] {:?} (reserved) has to be greater than {:?} (base extrinsic) if set",
337				class,
338				reserved,
339				base_for_class,
340			);
341			// Make sure max block is greater than max_total if it's set.
342			error_assert!(
343				self.max_block.all_gte(weights.max_total.unwrap_or(Weight::zero())),
344				&mut error,
345				"[{:?}] {:?} (max block) has to be greater than {:?} (max for class)",
346				class,
347				self.max_block,
348				weights.max_total,
349			);
350			// Make sure we can fit at least one extrinsic.
351			error_assert!(
352				self.max_block.all_gt(base_for_class + self.base_block),
353				&mut error,
354				"[{:?}] {:?} (max block) must fit at least one extrinsic {:?} (base weight)",
355				class,
356				self.max_block,
357				base_for_class + self.base_block,
358			);
359		}
360
361		if error.has_errors {
362			Err(error)
363		} else {
364			Ok(self)
365		}
366	}
367
368	/// Create new weights definition, with both `Normal` and `Operational`
369	/// classes limited to given weight.
370	///
371	/// Note there is no reservation for `Operational` class, so this constructor
372	/// is not suitable for production deployments.
373	pub fn simple_max(block_weight: Weight) -> Self {
374		Self::builder()
375			.base_block(Weight::zero())
376			.for_class(DispatchClass::all(), |weights| {
377				weights.base_extrinsic = Weight::zero();
378			})
379			.for_class(DispatchClass::non_mandatory(), |weights| {
380				weights.max_total = block_weight.into();
381			})
382			.build()
383			.expect("We only specify max_total and leave base values as defaults; qed")
384	}
385
386	/// Create a sensible default weights system given only expected maximal block weight and the
387	/// ratio that `Normal` extrinsics should occupy.
388	///
389	/// Assumptions:
390	///  - Average block initialization is assumed to be `10%`.
391	///  - `Operational` transactions have reserved allowance (`1.0 - normal_ratio`)
392	pub fn with_sensible_defaults(expected_block_weight: Weight, normal_ratio: Perbill) -> Self {
393		let normal_weight = normal_ratio * expected_block_weight;
394		Self::builder()
395			.for_class(DispatchClass::Normal, |weights| {
396				weights.max_total = normal_weight.into();
397			})
398			.for_class(DispatchClass::Operational, |weights| {
399				weights.max_total = expected_block_weight.into();
400				weights.reserved = (expected_block_weight - normal_weight).into();
401			})
402			.avg_block_initialization(Perbill::from_percent(10))
403			.build()
404			.expect("Sensible defaults are tested to be valid; qed")
405	}
406
407	/// Start constructing new `BlockWeights` object.
408	///
409	/// By default all kinds except of `Mandatory` extrinsics are disallowed.
410	pub fn builder() -> BlockWeightsBuilder {
411		BlockWeightsBuilder {
412			weights: BlockWeights {
413				base_block: constants::BlockExecutionWeight::get(),
414				max_block: Weight::zero(),
415				per_class: PerDispatchClass::new(|class| {
416					let initial =
417						if class == DispatchClass::Mandatory { None } else { Some(Weight::zero()) };
418					WeightsPerClass {
419						base_extrinsic: constants::ExtrinsicBaseWeight::get(),
420						max_extrinsic: None,
421						max_total: initial,
422						reserved: initial,
423					}
424				}),
425			},
426			init_cost: None,
427		}
428	}
429}
430
431/// An opinionated builder for `Weights` object.
432pub struct BlockWeightsBuilder {
433	weights: BlockWeights,
434	init_cost: Option<Perbill>,
435}
436
437impl BlockWeightsBuilder {
438	/// Set base block weight.
439	pub fn base_block(mut self, base_block: Weight) -> Self {
440		self.weights.base_block = base_block;
441		self
442	}
443
444	/// Average block initialization weight cost.
445	///
446	/// This value is used to derive maximal allowed extrinsic weight for each
447	/// class, based on the allowance.
448	///
449	/// This is to make sure that extrinsics don't stay forever in the pool,
450	/// because they could seemingly fit the block (since they are below `max_block`),
451	/// but the cost of calling `on_initialize` always prevents them from being included.
452	pub fn avg_block_initialization(mut self, init_cost: Perbill) -> Self {
453		self.init_cost = Some(init_cost);
454		self
455	}
456
457	/// Set parameters for particular class.
458	///
459	/// Note: `None` values of `max_extrinsic` will be overwritten in `build` in case
460	/// `avg_block_initialization` rate is set to a non-zero value.
461	pub fn for_class(
462		mut self,
463		class: impl OneOrMany<DispatchClass>,
464		action: impl Fn(&mut WeightsPerClass),
465	) -> Self {
466		for class in class.into_iter() {
467			action(self.weights.per_class.get_mut(class));
468		}
469		self
470	}
471
472	/// Construct the `BlockWeights` object.
473	pub fn build(self) -> ValidationResult {
474		// compute max extrinsic size
475		let Self { mut weights, init_cost } = self;
476
477		// compute max block size.
478		for class in DispatchClass::all() {
479			weights.max_block = match weights.per_class.get(*class).max_total {
480				Some(max) => max.max(weights.max_block),
481				_ => weights.max_block,
482			};
483		}
484		// compute max size of single extrinsic
485		if let Some(init_weight) = init_cost.map(|rate| rate * weights.max_block) {
486			for class in DispatchClass::all() {
487				let per_class = weights.per_class.get_mut(*class);
488				if per_class.max_extrinsic.is_none() && init_cost.is_some() {
489					per_class.max_extrinsic = per_class
490						.max_total
491						.map(|x| x.saturating_sub(init_weight))
492						.map(|x| x.saturating_sub(per_class.base_extrinsic));
493				}
494			}
495		}
496
497		// Validate the result
498		weights.validate()
499	}
500
501	/// Construct the `BlockWeights` object or panic if it's invalid.
502	///
503	/// This is a convenience method to be called whenever you construct a runtime.
504	pub fn build_or_panic(self) -> BlockWeights {
505		self.build().expect(
506			"Builder finished with `build_or_panic`; The panic is expected if runtime weights are not correct"
507		)
508	}
509}
510
511#[cfg(test)]
512mod tests {
513	use super::*;
514
515	#[test]
516	fn default_weights_are_valid() {
517		BlockWeights::default().validate().unwrap();
518	}
519}