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