referrerpolicy=no-referrer-when-downgrade

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, 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			assert_eq!(
812				BlockWeight::<Test>::get().total(),
813				info.total_weight() + prior_block_weight + base_extrinsic
814			);
815
816			// Refund less accurately than the benchmark
817			BlockWeight::<Test>::mutate(|current_weight| {
818				current_weight.reduce(inaccurate_refund, DispatchClass::Normal);
819			});
820			crate::ExtrinsicWeightReclaimed::<Test>::put(inaccurate_refund);
821
822			// Do the post dispatch
823			assert_ok!(CheckWeight::<Test>::post_dispatch_details(
824				pre,
825				&info,
826				&post_info,
827				len,
828				&Ok(())
829			));
830
831			// Ensure the accurate refund from benchmark is used
832			assert_eq!(
833				crate::ExtrinsicWeightReclaimed::<Test>::get(),
834				post_info.calc_unspent(&info)
835			);
836			assert_eq!(
837				BlockWeight::<Test>::get().total(),
838				post_info.actual_weight.unwrap() + prior_block_weight + base_extrinsic
839			);
840		})
841	}
842
843	#[test]
844	fn zero_weight_extrinsic_still_has_base_weight() {
845		new_test_ext().execute_with(|| {
846			let weights = block_weights();
847			let free = DispatchInfo { call_weight: Weight::zero(), ..Default::default() };
848			let len = 0_usize;
849
850			// Initial weight from `weights.base_block`
851			assert_eq!(System::block_weight().total(), weights.base_block);
852			assert_ok!(CheckWeight::<Test>(PhantomData).validate_and_prepare(
853				Some(1).into(),
854				CALL,
855				&free,
856				len,
857				0,
858			));
859			assert_eq!(
860				System::block_weight().total(),
861				weights.get(DispatchClass::Normal).base_extrinsic + weights.base_block
862			);
863		})
864	}
865
866	#[test]
867	fn normal_and_mandatory_tracked_separately() {
868		new_test_ext().execute_with(|| {
869			// Max block is 1024
870			// Max normal is 768 (75%)
871			// Max mandatory is unlimited
872			let max_normal =
873				DispatchInfo { call_weight: Weight::from_parts(753, 0), ..Default::default() };
874			let mandatory = DispatchInfo {
875				call_weight: Weight::from_parts(1019, 0),
876				class: DispatchClass::Mandatory,
877				..Default::default()
878			};
879
880			let len = 0_usize;
881
882			let next_len = CheckWeight::<Test>::check_block_length(&max_normal, len).unwrap();
883			assert_ok!(CheckWeight::<Test>::do_prepare(&max_normal, len, next_len));
884			assert_eq!(System::block_weight().total(), Weight::from_parts(768, 0));
885			let next_len = CheckWeight::<Test>::check_block_length(&mandatory, len).unwrap();
886			assert_ok!(CheckWeight::<Test>::do_prepare(&mandatory, len, next_len));
887			assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX));
888			assert_eq!(System::block_weight().total(), Weight::from_parts(1024 + 768, 0));
889			assert_eq!(CheckWeight::<Test>::check_extrinsic_weight(&mandatory), Ok(()));
890		});
891	}
892
893	#[test]
894	fn no_max_total_should_still_be_limited_by_max_block() {
895		// given
896		let maximum_weight = BlockWeights::builder()
897			.base_block(Weight::zero())
898			.for_class(DispatchClass::non_mandatory(), |w| {
899				w.base_extrinsic = Weight::zero();
900				w.max_total = Some(Weight::from_parts(20, u64::MAX));
901			})
902			.for_class(DispatchClass::Mandatory, |w| {
903				w.base_extrinsic = Weight::zero();
904				w.reserved = Some(Weight::from_parts(5, u64::MAX));
905				w.max_total = None;
906			})
907			.build_or_panic();
908		let all_weight = crate::ConsumedWeight::new(|class| match class {
909			DispatchClass::Normal => Weight::from_parts(10, 0),
910			DispatchClass::Operational => Weight::from_parts(10, 0),
911			DispatchClass::Mandatory => Weight::zero(),
912		});
913		assert_eq!(maximum_weight.max_block, all_weight.total().set_proof_size(u64::MAX));
914
915		// fits into reserved
916		let mandatory1 = DispatchInfo {
917			call_weight: Weight::from_parts(5, 0),
918			class: DispatchClass::Mandatory,
919			..Default::default()
920		};
921		// does not fit into reserved and the block is full.
922		let mandatory2 = DispatchInfo {
923			call_weight: Weight::from_parts(6, 0),
924			class: DispatchClass::Mandatory,
925			..Default::default()
926		};
927
928		// when
929		assert_ok!(calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
930			&maximum_weight,
931			all_weight.clone(),
932			&mandatory1,
933			0
934		));
935		assert_err!(
936			calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
937				&maximum_weight,
938				all_weight,
939				&mandatory2,
940				0
941			),
942			InvalidTransaction::ExhaustsResources
943		);
944	}
945
946	#[test]
947	fn proof_size_includes_length() {
948		let maximum_weight = BlockWeights::builder()
949			.base_block(Weight::zero())
950			.for_class(DispatchClass::non_mandatory(), |w| {
951				w.base_extrinsic = Weight::zero();
952				w.max_total = Some(Weight::from_parts(20, 1000));
953			})
954			.for_class(DispatchClass::Mandatory, |w| {
955				w.base_extrinsic = Weight::zero();
956				w.max_total = Some(Weight::from_parts(20, 1000));
957			})
958			.build_or_panic();
959		let all_weight = crate::ConsumedWeight::new(|class| match class {
960			DispatchClass::Normal => Weight::from_parts(5, 0),
961			DispatchClass::Operational => Weight::from_parts(5, 0),
962			DispatchClass::Mandatory => Weight::from_parts(0, 0),
963		});
964
965		let normal = DispatchInfo {
966			call_weight: Weight::from_parts(5, 0),
967			class: DispatchClass::Normal,
968			..Default::default()
969		};
970
971		let mandatory = DispatchInfo {
972			call_weight: Weight::from_parts(5, 0),
973			class: DispatchClass::Mandatory,
974			..Default::default()
975		};
976
977		// Using 0 length extrinsics.
978		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
979			&maximum_weight,
980			all_weight.clone(),
981			&normal,
982			0,
983		)
984		.unwrap();
985
986		assert_eq!(consumed.total().saturating_sub(all_weight.total()), normal.total_weight());
987
988		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
989			&maximum_weight,
990			all_weight.clone(),
991			&mandatory,
992			0,
993		)
994		.unwrap();
995		assert_eq!(consumed.total().saturating_sub(all_weight.total()), mandatory.total_weight());
996
997		// Using non zero length extrinsics.
998		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
999			&maximum_weight,
1000			all_weight.clone(),
1001			&normal,
1002			100,
1003		)
1004		.unwrap();
1005		// Must account for the len in the proof size
1006		assert_eq!(
1007			consumed.total().saturating_sub(all_weight.total()),
1008			normal.total_weight().add_proof_size(100)
1009		);
1010
1011		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
1012			&maximum_weight,
1013			all_weight.clone(),
1014			&mandatory,
1015			100,
1016		)
1017		.unwrap();
1018		// Must account for the len in the proof size
1019		assert_eq!(
1020			consumed.total().saturating_sub(all_weight.total()),
1021			mandatory.total_weight().add_proof_size(100)
1022		);
1023
1024		// Using oversized zero length extrinsics.
1025		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
1026			&maximum_weight,
1027			all_weight.clone(),
1028			&normal,
1029			2000,
1030		);
1031		// errors out
1032		assert_eq!(consumed, Err(InvalidTransaction::ExhaustsResources.into()));
1033
1034		// Using oversized zero length extrinsics.
1035		let consumed = calculate_consumed_weight::<<Test as Config>::RuntimeCall>(
1036			&maximum_weight,
1037			all_weight.clone(),
1038			&mandatory,
1039			2000,
1040		);
1041		// errors out
1042		assert_eq!(consumed, Err(InvalidTransaction::ExhaustsResources.into()));
1043	}
1044}