Skip to main content

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