frame_system/extensions/
check_weight.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
18use crate::{limits::BlockWeights, Config, Pallet, LOG_TARGET};
19use codec::{Decode, Encode};
20use frame_support::{
21	dispatch::{DispatchInfo, PostDispatchInfo},
22	traits::Get,
23};
24use scale_info::TypeInfo;
25use sp_runtime::{
26	traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension},
27	transaction_validity::{InvalidTransaction, TransactionValidity, TransactionValidityError},
28	DispatchResult,
29};
30use sp_weights::Weight;
31
32/// Block resource (weight) limit check.
33///
34/// # Transaction Validity
35///
36/// This extension does not influence any fields of `TransactionValidity` in case the
37/// transaction is valid.
38#[derive(Encode, Decode, Clone, Eq, PartialEq, Default, TypeInfo)]
39#[scale_info(skip_type_params(T))]
40pub struct CheckWeight<T: Config + Send + Sync>(core::marker::PhantomData<T>);
41
42impl<T: Config + Send + Sync> CheckWeight<T>
43where
44	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
45{
46	/// Checks if the current extrinsic does not exceed the maximum weight a single extrinsic
47	/// with given `DispatchClass` can have.
48	fn check_extrinsic_weight(
49		info: &DispatchInfoOf<T::RuntimeCall>,
50	) -> Result<(), TransactionValidityError> {
51		let max = T::BlockWeights::get().get(info.class).max_extrinsic;
52		match max {
53			Some(max) if info.weight.any_gt(max) => {
54				log::debug!(
55					target: LOG_TARGET,
56					"Extrinsic {} is greater than the max extrinsic {}",
57					info.weight,
58					max,
59				);
60
61				Err(InvalidTransaction::ExhaustsResources.into())
62			},
63			_ => Ok(()),
64		}
65	}
66
67	/// Checks if the current extrinsic can fit into the block with respect to block length limits.
68	///
69	/// Upon successes, it returns the new block length as a `Result`.
70	fn check_block_length(
71		info: &DispatchInfoOf<T::RuntimeCall>,
72		len: usize,
73	) -> Result<u32, TransactionValidityError> {
74		let length_limit = T::BlockLength::get();
75		let current_len = Pallet::<T>::all_extrinsics_len();
76		let added_len = len as u32;
77		let next_len = current_len.saturating_add(added_len);
78		if next_len > *length_limit.max.get(info.class) {
79			log::debug!(
80				target: LOG_TARGET,
81				"Exceeded block length limit: {} > {}",
82				next_len,
83				length_limit.max.get(info.class),
84			);
85
86			Err(InvalidTransaction::ExhaustsResources.into())
87		} else {
88			Ok(next_len)
89		}
90	}
91
92	/// Creates new `SignedExtension` to check weight of the extrinsic.
93	pub fn new() -> Self {
94		Self(Default::default())
95	}
96
97	/// Do the pre-dispatch checks. This can be applied to both signed and unsigned.
98	///
99	/// It checks and notes the new weight and length.
100	pub fn do_pre_dispatch(
101		info: &DispatchInfoOf<T::RuntimeCall>,
102		len: usize,
103	) -> Result<(), TransactionValidityError> {
104		let next_len = Self::check_block_length(info, len)?;
105
106		let all_weight = Pallet::<T>::block_weight();
107		let maximum_weight = T::BlockWeights::get();
108		let next_weight =
109			calculate_consumed_weight::<T::RuntimeCall>(&maximum_weight, all_weight, info, len)?;
110		Self::check_extrinsic_weight(info)?;
111
112		crate::AllExtrinsicsLen::<T>::put(next_len);
113		crate::BlockWeight::<T>::put(next_weight);
114		Ok(())
115	}
116
117	/// Do the validate checks. This can be applied to both signed and unsigned.
118	///
119	/// It only checks that the block weight and length limit will not exceed.
120	pub fn do_validate(info: &DispatchInfoOf<T::RuntimeCall>, len: usize) -> TransactionValidity {
121		// ignore the next length. If they return `Ok`, then it is below the limit.
122		let _ = Self::check_block_length(info, len)?;
123		// during validation we skip block limit check. Since the `validate_transaction`
124		// call runs on an empty block anyway, by this we prevent `on_initialize` weight
125		// consumption from causing false negatives.
126		Self::check_extrinsic_weight(info)?;
127
128		Ok(Default::default())
129	}
130}
131
132/// Checks if the current extrinsic can fit into the block with respect to block weight limits.
133///
134/// Upon successes, it returns the new block weight as a `Result`.
135pub fn calculate_consumed_weight<Call>(
136	maximum_weight: &BlockWeights,
137	mut all_weight: crate::ConsumedWeight,
138	info: &DispatchInfoOf<Call>,
139	len: usize,
140) -> Result<crate::ConsumedWeight, TransactionValidityError>
141where
142	Call: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
143{
144	// Also Consider extrinsic length as proof weight.
145	let extrinsic_weight = info
146		.weight
147		.saturating_add(maximum_weight.get(info.class).base_extrinsic)
148		.saturating_add(Weight::from_parts(0, len as u64));
149	let limit_per_class = maximum_weight.get(info.class);
150
151	// add the weight. If class is unlimited, use saturating add instead of checked one.
152	if limit_per_class.max_total.is_none() && limit_per_class.reserved.is_none() {
153		all_weight.accrue(extrinsic_weight, info.class)
154	} else {
155		all_weight.checked_accrue(extrinsic_weight, info.class).map_err(|_| {
156			log::debug!(
157				target: LOG_TARGET,
158				"All weight checked add overflow.",
159			);
160
161			InvalidTransaction::ExhaustsResources
162		})?;
163	}
164
165	let per_class = *all_weight.get(info.class);
166
167	// Check if we don't exceed per-class allowance
168	match limit_per_class.max_total {
169		Some(max) if per_class.any_gt(max) => {
170			log::debug!(
171				target: LOG_TARGET,
172				"Exceeded the per-class allowance.",
173			);
174
175			return Err(InvalidTransaction::ExhaustsResources.into());
176		},
177		// There is no `max_total` limit (`None`),
178		// or we are below the limit.
179		_ => {},
180	}
181
182	// In cases total block weight is exceeded, we need to fall back
183	// to `reserved` pool if there is any.
184	if all_weight.total().any_gt(maximum_weight.max_block) {
185		match limit_per_class.reserved {
186			// We are over the limit in reserved pool.
187			Some(reserved) if per_class.any_gt(reserved) => {
188				log::debug!(
189					target: LOG_TARGET,
190					"Total block weight is exceeded.",
191				);
192
193				return Err(InvalidTransaction::ExhaustsResources.into());
194			},
195			// There is either no limit in reserved pool (`None`),
196			// or we are below the limit.
197			_ => {},
198		}
199	}
200
201	Ok(all_weight)
202}
203
204impl<T: Config + Send + Sync> SignedExtension for CheckWeight<T>
205where
206	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
207{
208	type AccountId = T::AccountId;
209	type Call = T::RuntimeCall;
210	type AdditionalSigned = ();
211	type Pre = ();
212	const IDENTIFIER: &'static str = "CheckWeight";
213
214	fn additional_signed(&self) -> core::result::Result<(), TransactionValidityError> {
215		Ok(())
216	}
217
218	fn pre_dispatch(
219		self,
220		_who: &Self::AccountId,
221		_call: &Self::Call,
222		info: &DispatchInfoOf<Self::Call>,
223		len: usize,
224	) -> Result<(), TransactionValidityError> {
225		Self::do_pre_dispatch(info, len)
226	}
227
228	fn validate(
229		&self,
230		_who: &Self::AccountId,
231		_call: &Self::Call,
232		info: &DispatchInfoOf<Self::Call>,
233		len: usize,
234	) -> TransactionValidity {
235		Self::do_validate(info, len)
236	}
237
238	fn pre_dispatch_unsigned(
239		_call: &Self::Call,
240		info: &DispatchInfoOf<Self::Call>,
241		len: usize,
242	) -> Result<(), TransactionValidityError> {
243		Self::do_pre_dispatch(info, len)
244	}
245
246	fn validate_unsigned(
247		_call: &Self::Call,
248		info: &DispatchInfoOf<Self::Call>,
249		len: usize,
250	) -> TransactionValidity {
251		Self::do_validate(info, len)
252	}
253
254	fn post_dispatch(
255		_pre: Option<Self::Pre>,
256		info: &DispatchInfoOf<Self::Call>,
257		post_info: &PostDispatchInfoOf<Self::Call>,
258		_len: usize,
259		_result: &DispatchResult,
260	) -> Result<(), TransactionValidityError> {
261		let unspent = post_info.calc_unspent(info);
262		if unspent.any_gt(Weight::zero()) {
263			crate::BlockWeight::<T>::mutate(|current_weight| {
264				current_weight.reduce(unspent, info.class);
265			})
266		}
267
268		log::trace!(
269			target: LOG_TARGET,
270			"Used block weight: {:?}",
271			crate::BlockWeight::<T>::get(),
272		);
273
274		log::trace!(
275			target: LOG_TARGET,
276			"Used block length: {:?}",
277			Pallet::<T>::all_extrinsics_len(),
278		);
279
280		Ok(())
281	}
282}
283
284impl<T: Config + Send + Sync> core::fmt::Debug for CheckWeight<T> {
285	#[cfg(feature = "std")]
286	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
287		write!(f, "CheckWeight")
288	}
289
290	#[cfg(not(feature = "std"))]
291	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
292		Ok(())
293	}
294}
295
296#[cfg(test)]
297mod tests {
298	use super::*;
299	use crate::{
300		mock::{new_test_ext, System, Test, CALL},
301		AllExtrinsicsLen, BlockWeight, DispatchClass,
302	};
303	use core::marker::PhantomData;
304	use frame_support::{assert_err, assert_ok, dispatch::Pays, weights::Weight};
305
306	fn block_weights() -> crate::limits::BlockWeights {
307		<Test as crate::Config>::BlockWeights::get()
308	}
309
310	fn normal_weight_limit() -> Weight {
311		block_weights()
312			.get(DispatchClass::Normal)
313			.max_total
314			.unwrap_or_else(|| block_weights().max_block)
315	}
316
317	fn block_weight_limit() -> Weight {
318		block_weights().max_block
319	}
320
321	fn normal_length_limit() -> u32 {
322		*<Test as Config>::BlockLength::get().max.get(DispatchClass::Normal)
323	}
324
325	#[test]
326	fn mandatory_extrinsic_doesnt_care_about_limits() {
327		fn check(call: impl FnOnce(&DispatchInfo, usize)) {
328			new_test_ext().execute_with(|| {
329				let max = DispatchInfo {
330					weight: Weight::MAX,
331					class: DispatchClass::Mandatory,
332					..Default::default()
333				};
334				let len = 0_usize;
335
336				call(&max, len);
337			});
338		}
339
340		check(|max, len| {
341			assert_ok!(CheckWeight::<Test>::do_pre_dispatch(max, len));
342			assert_eq!(System::block_weight().total(), Weight::MAX);
343			assert!(System::block_weight().total().ref_time() > block_weight_limit().ref_time());
344		});
345		check(|max, len| {
346			assert_ok!(CheckWeight::<Test>::do_validate(max, len));
347		});
348	}
349
350	#[test]
351	fn normal_extrinsic_limited_by_maximum_extrinsic_weight() {
352		new_test_ext().execute_with(|| {
353			let max = DispatchInfo {
354				weight: block_weights().get(DispatchClass::Normal).max_extrinsic.unwrap() +
355					Weight::from_parts(1, 0),
356				class: DispatchClass::Normal,
357				..Default::default()
358			};
359			let len = 0_usize;
360			assert_err!(
361				CheckWeight::<Test>::do_validate(&max, len),
362				InvalidTransaction::ExhaustsResources
363			);
364		});
365	}
366
367	#[test]
368	fn operational_extrinsic_limited_by_operational_space_limit() {
369		new_test_ext().execute_with(|| {
370			let weights = block_weights();
371			let operational_limit = weights
372				.get(DispatchClass::Operational)
373				.max_total
374				.unwrap_or_else(|| weights.max_block);
375			let base_weight = weights.get(DispatchClass::Operational).base_extrinsic;
376
377			let weight = operational_limit - base_weight;
378			let okay =
379				DispatchInfo { weight, class: DispatchClass::Operational, ..Default::default() };
380			let max = DispatchInfo {
381				weight: weight + Weight::from_parts(1, 0),
382				class: DispatchClass::Operational,
383				..Default::default()
384			};
385			let len = 0_usize;
386
387			assert_eq!(CheckWeight::<Test>::do_validate(&okay, len), Ok(Default::default()));
388			assert_err!(
389				CheckWeight::<Test>::do_validate(&max, len),
390				InvalidTransaction::ExhaustsResources
391			);
392		});
393	}
394
395	#[test]
396	fn register_extra_weight_unchecked_doesnt_care_about_limits() {
397		new_test_ext().execute_with(|| {
398			System::register_extra_weight_unchecked(Weight::MAX, DispatchClass::Normal);
399			assert_eq!(System::block_weight().total(), Weight::MAX);
400			assert!(System::block_weight().total().ref_time() > block_weight_limit().ref_time());
401		});
402	}
403
404	#[test]
405	fn full_block_with_normal_and_operational() {
406		new_test_ext().execute_with(|| {
407			// Max block is 1024
408			// Max normal is 768 (75%)
409			// 10 is taken for block execution weight
410			// So normal extrinsic can be 758 weight (-5 for base extrinsic weight)
411			// And Operational can be 246 to produce a full block (-10 for base)
412			let max_normal =
413				DispatchInfo { weight: Weight::from_parts(753, 0), ..Default::default() };
414			let rest_operational = DispatchInfo {
415				weight: Weight::from_parts(246, 0),
416				class: DispatchClass::Operational,
417				..Default::default()
418			};
419
420			let len = 0_usize;
421
422			assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&max_normal, len));
423			assert_eq!(System::block_weight().total(), Weight::from_parts(768, 0));
424			assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&rest_operational, len));
425			assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX));
426			assert_eq!(System::block_weight().total(), block_weight_limit().set_proof_size(0));
427			// Checking single extrinsic should not take current block weight into account.
428			assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&rest_operational), Ok(()));
429		});
430	}
431
432	#[test]
433	fn dispatch_order_does_not_effect_weight_logic() {
434		new_test_ext().execute_with(|| {
435			// We switch the order of `full_block_with_normal_and_operational`
436			let max_normal =
437				DispatchInfo { weight: Weight::from_parts(753, 0), ..Default::default() };
438			let rest_operational = DispatchInfo {
439				weight: Weight::from_parts(246, 0),
440				class: DispatchClass::Operational,
441				..Default::default()
442			};
443
444			let len = 0_usize;
445
446			assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&rest_operational, len));
447			// Extra 20 here from block execution + base extrinsic weight
448			assert_eq!(System::block_weight().total(), Weight::from_parts(266, 0));
449			assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&max_normal, len));
450			assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX));
451			assert_eq!(System::block_weight().total(), block_weight_limit().set_proof_size(0));
452		});
453	}
454
455	#[test]
456	fn operational_works_on_full_block() {
457		new_test_ext().execute_with(|| {
458			// An on_initialize takes up the whole block! (Every time!)
459			System::register_extra_weight_unchecked(Weight::MAX, DispatchClass::Mandatory);
460			let dispatch_normal = DispatchInfo {
461				weight: Weight::from_parts(251, 0),
462				class: DispatchClass::Normal,
463				..Default::default()
464			};
465			let dispatch_operational = DispatchInfo {
466				weight: Weight::from_parts(246, 0),
467				class: DispatchClass::Operational,
468				..Default::default()
469			};
470			let len = 0_usize;
471
472			assert_err!(
473				CheckWeight::<Test>::do_pre_dispatch(&dispatch_normal, len),
474				InvalidTransaction::ExhaustsResources
475			);
476			// Thank goodness we can still do an operational transaction to possibly save the
477			// blockchain.
478			assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&dispatch_operational, len));
479			// Not too much though
480			assert_err!(
481				CheckWeight::<Test>::do_pre_dispatch(&dispatch_operational, len),
482				InvalidTransaction::ExhaustsResources
483			);
484			// Even with full block, validity of single transaction should be correct.
485			assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&dispatch_operational), Ok(()));
486		});
487	}
488
489	#[test]
490	fn signed_ext_check_weight_works_operational_tx() {
491		new_test_ext().execute_with(|| {
492			let normal = DispatchInfo { weight: Weight::from_parts(100, 0), ..Default::default() };
493			let op = DispatchInfo {
494				weight: Weight::from_parts(100, 0),
495				class: DispatchClass::Operational,
496				pays_fee: Pays::Yes,
497			};
498			let len = 0_usize;
499			let normal_limit = normal_weight_limit();
500
501			// given almost full block
502			BlockWeight::<Test>::mutate(|current_weight| {
503				current_weight.set(normal_limit, DispatchClass::Normal)
504			});
505			// will not fit.
506			assert_err!(
507				CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &normal, len),
508				InvalidTransaction::ExhaustsResources
509			);
510			// will fit.
511			assert_ok!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &op, len));
512
513			// likewise for length limit.
514			let len = 100_usize;
515			AllExtrinsicsLen::<Test>::put(normal_length_limit());
516			assert_err!(
517				CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &normal, len),
518				InvalidTransaction::ExhaustsResources
519			);
520			assert_ok!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &op, len));
521		})
522	}
523
524	#[test]
525	fn signed_ext_check_weight_block_size_works() {
526		new_test_ext().execute_with(|| {
527			let normal = DispatchInfo::default();
528			let normal_limit = normal_weight_limit().ref_time() as usize;
529			let reset_check_weight = |tx, s, f| {
530				AllExtrinsicsLen::<Test>::put(0);
531				let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, tx, s);
532				if f {
533					assert!(r.is_err())
534				} else {
535					assert!(r.is_ok())
536				}
537			};
538
539			reset_check_weight(&normal, normal_limit - 1, false);
540			reset_check_weight(&normal, normal_limit, false);
541			reset_check_weight(&normal, normal_limit + 1, true);
542
543			// Operational ones don't have this limit.
544			let op = DispatchInfo {
545				weight: Weight::zero(),
546				class: DispatchClass::Operational,
547				pays_fee: Pays::Yes,
548			};
549			reset_check_weight(&op, normal_limit, false);
550			reset_check_weight(&op, normal_limit + 100, false);
551			reset_check_weight(&op, 1024, false);
552			reset_check_weight(&op, 1025, true);
553		})
554	}
555
556	#[test]
557	fn signed_ext_check_weight_works_normal_tx() {
558		new_test_ext().execute_with(|| {
559			let normal_limit = normal_weight_limit();
560			let small = DispatchInfo { weight: Weight::from_parts(100, 0), ..Default::default() };
561			let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
562			let medium =
563				DispatchInfo { weight: normal_limit - base_extrinsic, ..Default::default() };
564			let big = DispatchInfo {
565				weight: normal_limit - base_extrinsic + Weight::from_parts(1, 0),
566				..Default::default()
567			};
568			let len = 0_usize;
569
570			let reset_check_weight = |i, f, s| {
571				BlockWeight::<Test>::mutate(|current_weight| {
572					current_weight.set(s, DispatchClass::Normal)
573				});
574				let r = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, i, len);
575				if f {
576					assert!(r.is_err())
577				} else {
578					assert!(r.is_ok())
579				}
580			};
581
582			reset_check_weight(&small, false, Weight::zero());
583			reset_check_weight(&medium, false, Weight::zero());
584			reset_check_weight(&big, true, Weight::from_parts(1, 0));
585		})
586	}
587
588	#[test]
589	fn signed_ext_check_weight_refund_works() {
590		new_test_ext().execute_with(|| {
591			// This is half of the max block weight
592			let info = DispatchInfo { weight: Weight::from_parts(512, 0), ..Default::default() };
593			let post_info = PostDispatchInfo {
594				actual_weight: Some(Weight::from_parts(128, 0)),
595				pays_fee: Default::default(),
596			};
597			let len = 0_usize;
598			let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic;
599
600			// We allow 75% for normal transaction, so we put 25% - extrinsic base weight
601			BlockWeight::<Test>::mutate(|current_weight| {
602				current_weight.set(Weight::zero(), DispatchClass::Mandatory);
603				current_weight
604					.set(Weight::from_parts(256, 0) - base_extrinsic, DispatchClass::Normal);
605			});
606
607			let pre = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap();
608			assert_eq!(
609				BlockWeight::<Test>::get().total(),
610				info.weight + Weight::from_parts(256, 0)
611			);
612
613			assert_ok!(CheckWeight::<Test>::post_dispatch(
614				Some(pre),
615				&info,
616				&post_info,
617				len,
618				&Ok(())
619			));
620			assert_eq!(
621				BlockWeight::<Test>::get().total(),
622				post_info.actual_weight.unwrap() + Weight::from_parts(256, 0)
623			);
624		})
625	}
626
627	#[test]
628	fn signed_ext_check_weight_actual_weight_higher_than_max_is_capped() {
629		new_test_ext().execute_with(|| {
630			let info = DispatchInfo { weight: Weight::from_parts(512, 0), ..Default::default() };
631			let post_info = PostDispatchInfo {
632				actual_weight: Some(Weight::from_parts(700, 0)),
633				pays_fee: Default::default(),
634			};
635			let len = 0_usize;
636
637			BlockWeight::<Test>::mutate(|current_weight| {
638				current_weight.set(Weight::zero(), DispatchClass::Mandatory);
639				current_weight.set(Weight::from_parts(128, 0), DispatchClass::Normal);
640			});
641
642			let pre = CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap();
643			assert_eq!(
644				BlockWeight::<Test>::get().total(),
645				info.weight +
646					Weight::from_parts(128, 0) +
647					block_weights().get(DispatchClass::Normal).base_extrinsic,
648			);
649
650			assert_ok!(CheckWeight::<Test>::post_dispatch(
651				Some(pre),
652				&info,
653				&post_info,
654				len,
655				&Ok(())
656			));
657			assert_eq!(
658				BlockWeight::<Test>::get().total(),
659				info.weight +
660					Weight::from_parts(128, 0) +
661					block_weights().get(DispatchClass::Normal).base_extrinsic,
662			);
663		})
664	}
665
666	#[test]
667	fn zero_weight_extrinsic_still_has_base_weight() {
668		new_test_ext().execute_with(|| {
669			let weights = block_weights();
670			let free = DispatchInfo { weight: Weight::zero(), ..Default::default() };
671			let len = 0_usize;
672
673			// Initial weight from `weights.base_block`
674			assert_eq!(System::block_weight().total(), weights.base_block);
675			assert_ok!(CheckWeight::<Test>(PhantomData).pre_dispatch(&1, CALL, &free, len));
676			assert_eq!(
677				System::block_weight().total(),
678				weights.get(DispatchClass::Normal).base_extrinsic + weights.base_block
679			);
680		})
681	}
682
683	#[test]
684	fn normal_and_mandatory_tracked_separately() {
685		new_test_ext().execute_with(|| {
686			// Max block is 1024
687			// Max normal is 768 (75%)
688			// Max mandatory is unlimited
689			let max_normal =
690				DispatchInfo { weight: Weight::from_parts(753, 0), ..Default::default() };
691			let mandatory = DispatchInfo {
692				weight: Weight::from_parts(1019, 0),
693				class: DispatchClass::Mandatory,
694				..Default::default()
695			};
696
697			let len = 0_usize;
698
699			assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&max_normal, len));
700			assert_eq!(System::block_weight().total(), Weight::from_parts(768, 0));
701			assert_ok!(CheckWeight::<Test>::do_pre_dispatch(&mandatory, len));
702			assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX));
703			assert_eq!(System::block_weight().total(), Weight::from_parts(1024 + 768, 0));
704			assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&mandatory), Ok(()));
705		});
706	}
707
708	#[test]
709	fn no_max_total_should_still_be_limited_by_max_block() {
710		// given
711		let maximum_weight = BlockWeights::builder()
712			.base_block(Weight::zero())
713			.for_class(DispatchClass::non_mandatory(), |w| {
714				w.base_extrinsic = Weight::zero();
715				w.max_total = Some(Weight::from_parts(20, u64::MAX));
716			})
717			.for_class(DispatchClass::Mandatory, |w| {
718				w.base_extrinsic = Weight::zero();
719				w.reserved = Some(Weight::from_parts(5, u64::MAX));
720				w.max_total = None;
721			})
722			.build_or_panic();
723		let all_weight = crate::ConsumedWeight::new(|class| match class {
724			DispatchClass::Normal => Weight::from_parts(10, 0),
725			DispatchClass::Operational => Weight::from_parts(10, 0),
726			DispatchClass::Mandatory => Weight::zero(),
727		});
728		assert_eq!(maximum_weight.max_block, all_weight.total().set_proof_size(u64::MAX));
729
730		// fits into reserved
731		let mandatory1 = DispatchInfo {
732			weight: Weight::from_parts(5, 0),
733			class: DispatchClass::Mandatory,
734			..Default::default()
735		};
736		// does not fit into reserved and the block is full.
737		let mandatory2 = DispatchInfo {
738			weight: Weight::from_parts(6, 0),
739			class: DispatchClass::Mandatory,
740			..Default::default()
741		};
742
743		// when
744		assert_ok!(calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
745			&maximum_weight,
746			all_weight.clone(),
747			&mandatory1,
748			0
749		));
750		assert_err!(
751			calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
752				&maximum_weight,
753				all_weight,
754				&mandatory2,
755				0
756			),
757			InvalidTransaction::ExhaustsResources
758		);
759	}
760
761	#[test]
762	fn proof_size_includes_length() {
763		let maximum_weight = BlockWeights::builder()
764			.base_block(Weight::zero())
765			.for_class(DispatchClass::non_mandatory(), |w| {
766				w.base_extrinsic = Weight::zero();
767				w.max_total = Some(Weight::from_parts(20, 1000));
768			})
769			.for_class(DispatchClass::Mandatory, |w| {
770				w.base_extrinsic = Weight::zero();
771				w.max_total = Some(Weight::from_parts(20, 1000));
772			})
773			.build_or_panic();
774		let all_weight = crate::ConsumedWeight::new(|class| match class {
775			DispatchClass::Normal => Weight::from_parts(5, 0),
776			DispatchClass::Operational => Weight::from_parts(5, 0),
777			DispatchClass::Mandatory => Weight::from_parts(0, 0),
778		});
779
780		let normal = DispatchInfo {
781			weight: Weight::from_parts(5, 0),
782			class: DispatchClass::Normal,
783			..Default::default()
784		};
785
786		let mandatory = DispatchInfo {
787			weight: Weight::from_parts(5, 0),
788			class: DispatchClass::Mandatory,
789			..Default::default()
790		};
791
792		// Using 0 length extrinsics.
793		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
794			&maximum_weight,
795			all_weight.clone(),
796			&normal,
797			0,
798		)
799		.unwrap();
800
801		assert_eq!(consumed.total().saturating_sub(all_weight.total()), normal.weight);
802
803		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
804			&maximum_weight,
805			all_weight.clone(),
806			&mandatory,
807			0,
808		)
809		.unwrap();
810		assert_eq!(consumed.total().saturating_sub(all_weight.total()), mandatory.weight);
811
812		// Using non zero length extrinsics.
813		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
814			&maximum_weight,
815			all_weight.clone(),
816			&normal,
817			100,
818		)
819		.unwrap();
820		// Must account for the len in the proof size
821		assert_eq!(
822			consumed.total().saturating_sub(all_weight.total()),
823			normal.weight.add_proof_size(100)
824		);
825
826		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
827			&maximum_weight,
828			all_weight.clone(),
829			&mandatory,
830			100,
831		)
832		.unwrap();
833		// Must account for the len in the proof size
834		assert_eq!(
835			consumed.total().saturating_sub(all_weight.total()),
836			mandatory.weight.add_proof_size(100)
837		);
838
839		// Using oversized zero length extrinsics.
840		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
841			&maximum_weight,
842			all_weight.clone(),
843			&normal,
844			2000,
845		);
846		// errors out
847		assert_eq!(consumed, Err(InvalidTransaction::ExhaustsResources.into()));
848
849		// Using oversized zero length extrinsics.
850		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
851			&maximum_weight,
852			all_weight.clone(),
853			&mandatory,
854			2000,
855		);
856		// errors out
857		assert_eq!(consumed, Err(InvalidTransaction::ExhaustsResources.into()));
858	}
859}