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