referrerpolicy=no-referrer-when-downgrade

frame_system/extensions/
check_weight.rs

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