referrerpolicy=no-referrer-when-downgrade

frame_system/extensions/
check_mortality.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::{pallet_prelude::BlockNumberFor, BlockHash, Config, Pallet};
19use codec::{Decode, DecodeWithMemTracking, Encode};
20use frame_support::pallet_prelude::TransactionSource;
21use scale_info::TypeInfo;
22use sp_runtime::{
23	generic::Era,
24	impl_tx_ext_default,
25	traits::{DispatchInfoOf, SaturatedConversion, TransactionExtension, ValidateResult},
26	transaction_validity::{InvalidTransaction, TransactionValidityError, ValidTransaction},
27};
28
29/// Check for transaction mortality.
30///
31/// The extension adds [`Era`] to every signed extrinsic. It also contributes to the signed data, by
32/// including the hash of the block at [`Era::birth`].
33///
34/// # Transaction Validity
35///
36/// The extension affects `longevity` of the transaction according to the [`Era`] definition.
37#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
38#[scale_info(skip_type_params(T))]
39pub struct CheckMortality<T: Config + Send + Sync>(pub Era, core::marker::PhantomData<T>);
40
41impl<T: Config + Send + Sync> CheckMortality<T> {
42	/// utility constructor. Used only in client/factory code.
43	pub fn from(era: Era) -> Self {
44		Self(era, core::marker::PhantomData)
45	}
46}
47
48impl<T: Config + Send + Sync> core::fmt::Debug for CheckMortality<T> {
49	#[cfg(feature = "std")]
50	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
51		write!(f, "CheckMortality({:?})", self.0)
52	}
53
54	#[cfg(not(feature = "std"))]
55	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
56		Ok(())
57	}
58}
59
60impl<T: Config + Send + Sync> TransactionExtension<T::RuntimeCall> for CheckMortality<T> {
61	const IDENTIFIER: &'static str = "CheckMortality";
62	type Implicit = T::Hash;
63
64	fn implicit(&self) -> Result<Self::Implicit, TransactionValidityError> {
65		let current_u64 = <Pallet<T>>::block_number().saturated_into::<u64>();
66		let n = self.0.birth(current_u64).saturated_into::<BlockNumberFor<T>>();
67		if !<BlockHash<T>>::contains_key(n) {
68			Err(InvalidTransaction::AncientBirthBlock.into())
69		} else {
70			Ok(<Pallet<T>>::block_hash(n))
71		}
72	}
73	type Pre = ();
74	type Val = ();
75
76	fn weight(&self, _: &T::RuntimeCall) -> sp_weights::Weight {
77		if self.0.is_immortal() {
78			// All immortal transactions will always read the hash of the genesis block, so to avoid
79			// charging this multiple times in a block we manually set the proof size to 0.
80			<T::ExtensionsWeightInfo as super::WeightInfo>::check_mortality_immortal_transaction()
81				.set_proof_size(0)
82		} else {
83			<T::ExtensionsWeightInfo as super::WeightInfo>::check_mortality_mortal_transaction()
84		}
85	}
86
87	fn validate(
88		&self,
89		origin: <T as Config>::RuntimeOrigin,
90		_call: &T::RuntimeCall,
91		_info: &DispatchInfoOf<T::RuntimeCall>,
92		_len: usize,
93		_self_implicit: Self::Implicit,
94		_inherited_implication: &impl Encode,
95		_source: TransactionSource,
96	) -> ValidateResult<Self::Val, T::RuntimeCall> {
97		let current_u64 = <Pallet<T>>::block_number().saturated_into::<u64>();
98		let valid_till = self.0.death(current_u64);
99		Ok((
100			ValidTransaction {
101				longevity: valid_till.saturating_sub(current_u64),
102				..Default::default()
103			},
104			(),
105			origin,
106		))
107	}
108	impl_tx_ext_default!(T::RuntimeCall; prepare);
109}
110
111#[cfg(test)]
112mod tests {
113	use super::*;
114	use crate::mock::{new_test_ext, System, Test, CALL};
115	use frame_support::{
116		dispatch::{DispatchClass, DispatchInfo, Pays},
117		weights::Weight,
118	};
119	use sp_core::H256;
120	use sp_runtime::{
121		traits::DispatchTransaction, transaction_validity::TransactionSource::External,
122	};
123
124	#[test]
125	fn signed_ext_check_era_should_work() {
126		new_test_ext().execute_with(|| {
127			// future
128			assert_eq!(
129				CheckMortality::<Test>::from(Era::mortal(4, 2)).implicit().err().unwrap(),
130				InvalidTransaction::AncientBirthBlock.into(),
131			);
132
133			// correct
134			System::set_block_number(13);
135			<BlockHash<Test>>::insert(12, H256::repeat_byte(1));
136			assert!(CheckMortality::<Test>::from(Era::mortal(4, 12)).implicit().is_ok());
137		})
138	}
139
140	#[test]
141	fn signed_ext_check_era_should_change_longevity() {
142		new_test_ext().execute_with(|| {
143			let normal = DispatchInfo {
144				call_weight: Weight::from_parts(100, 0),
145				extension_weight: Weight::zero(),
146				class: DispatchClass::Normal,
147				pays_fee: Pays::Yes,
148			};
149			let len = 0_usize;
150			let ext = (
151				crate::CheckWeight::<Test>::new(),
152				CheckMortality::<Test>::from(Era::mortal(16, 256)),
153			);
154			System::set_block_number(17);
155			<BlockHash<Test>>::insert(16, H256::repeat_byte(1));
156
157			assert_eq!(
158				ext.validate_only(Some(1).into(), CALL, &normal, len, External, 0)
159					.unwrap()
160					.0
161					.longevity,
162				15
163			);
164		})
165	}
166}