Skip to main content

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