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}