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