Skip to main content

topsoil_core/system/extensions/
check_weight.rs

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