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