Skip to main content

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