frame_system/extensions/
check_nonce.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
18extern crate alloc;
19
20use alloc::{vec, vec::Vec};
21
22use crate::Config;
23use codec::{Decode, DecodeWithMemTracking, Encode};
24use frame_support::{dispatch::DispatchInfo, pallet_prelude::TransactionSource, DebugNoBound};
25use scale_info::TypeInfo;
26use sp_runtime::{
27	traits::{
28		AsSystemOriginSigner, CheckedAdd, DispatchInfoOf, Dispatchable, One, PostDispatchInfoOf,
29		TransactionExtension, ValidateResult, Zero,
30	},
31	transaction_validity::{
32		InvalidTransaction, TransactionLongevity, TransactionValidityError, ValidTransaction,
33	},
34	DispatchResult, Saturating,
35};
36use sp_weights::Weight;
37
38/// Nonce check and increment to give replay protection for transactions.
39///
40/// # Transaction Validity
41///
42/// This extension affects `requires` and `provides` tags of validity, but DOES NOT
43/// set the `priority` field. Make sure that AT LEAST one of the transaction extension sets
44/// some kind of priority upon validating transactions.
45///
46/// The preparation step assumes that the nonce information has not changed since the validation
47/// step. This means that other extensions ahead of `CheckNonce` in the pipeline must not alter the
48/// nonce during their own preparation step, or else the transaction may be rejected during dispatch
49/// or lead to an inconsistent account state.
50#[derive(Encode, Decode, DecodeWithMemTracking, Clone, Eq, PartialEq, TypeInfo)]
51#[scale_info(skip_type_params(T))]
52pub struct CheckNonce<T: Config>(#[codec(compact)] pub T::Nonce);
53
54/// For a valid transaction the provides and requires information related to the nonce.
55pub struct ValidNonceInfo {
56	/// The encoded `provides` used for this transaction.
57	pub provides: Vec<Vec<u8>>,
58	/// The encoded `requires` used for this transaction.
59	pub requires: Vec<Vec<u8>>,
60}
61
62impl<T: Config> CheckNonce<T> {
63	/// utility constructor. Used only in client/factory code.
64	pub fn from(nonce: T::Nonce) -> Self {
65		Self(nonce)
66	}
67
68	/// In transaction extension, validate nonce for account, on success returns provides and
69	/// requires.
70	pub fn validate_nonce_for_account(
71		who: &T::AccountId,
72		nonce: T::Nonce,
73	) -> Result<ValidNonceInfo, TransactionValidityError> {
74		let account = crate::Account::<T>::get(who);
75		if account.providers.is_zero() && account.sufficients.is_zero() {
76			// Nonce storage not paid for
77			return Err(InvalidTransaction::Payment.into())
78		}
79		if nonce < account.nonce {
80			return Err(InvalidTransaction::Stale.into())
81		}
82
83		let provides = vec![Encode::encode(&(who.clone(), nonce))];
84		let requires = if account.nonce < nonce {
85			vec![Encode::encode(&(who.clone(), nonce.saturating_sub(One::one())))]
86		} else {
87			vec![]
88		};
89
90		Ok(ValidNonceInfo { provides, requires })
91	}
92
93	/// In transaction extension, prepare nonce for account.
94	pub fn prepare_nonce_for_account(
95		who: &T::AccountId,
96		mut nonce: T::Nonce,
97	) -> Result<(), TransactionValidityError> {
98		let account = crate::Account::<T>::get(who);
99		if nonce > account.nonce {
100			return Err(InvalidTransaction::Future.into())
101		}
102		nonce = nonce.checked_add(&T::Nonce::one()).unwrap_or(T::Nonce::zero());
103		crate::Account::<T>::mutate(who, |account| account.nonce = nonce);
104		Ok(())
105	}
106}
107
108impl<T: Config> core::fmt::Debug for CheckNonce<T> {
109	#[cfg(feature = "std")]
110	fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
111		write!(f, "CheckNonce({})", self.0)
112	}
113
114	#[cfg(not(feature = "std"))]
115	fn fmt(&self, _: &mut core::fmt::Formatter) -> core::fmt::Result {
116		Ok(())
117	}
118}
119
120/// Operation to perform from `validate` to `prepare` in [`CheckNonce`] transaction extension.
121#[derive(DebugNoBound)]
122pub enum Val<T: Config> {
123	/// Account and its nonce to check for.
124	CheckNonce(T::AccountId),
125	/// Weight to refund.
126	Refund(Weight),
127}
128
129/// Operation to perform from `prepare` to `post_dispatch_details` in [`CheckNonce`] transaction
130/// extension.
131#[derive(DebugNoBound)]
132pub enum Pre {
133	/// The transaction extension weight should not be refunded.
134	NonceChecked,
135	/// The transaction extension weight should be refunded.
136	Refund(Weight),
137}
138
139impl<T: Config> TransactionExtension<T::RuntimeCall> for CheckNonce<T>
140where
141	T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
142	<T::RuntimeCall as Dispatchable>::RuntimeOrigin: AsSystemOriginSigner<T::AccountId> + Clone,
143{
144	const IDENTIFIER: &'static str = "CheckNonce";
145	type Implicit = ();
146	type Val = Val<T>;
147	type Pre = Pre;
148
149	fn weight(&self, _: &T::RuntimeCall) -> sp_weights::Weight {
150		<T::ExtensionsWeightInfo as super::WeightInfo>::check_nonce()
151	}
152
153	fn validate(
154		&self,
155		origin: <T as Config>::RuntimeOrigin,
156		call: &T::RuntimeCall,
157		_info: &DispatchInfoOf<T::RuntimeCall>,
158		_len: usize,
159		_self_implicit: Self::Implicit,
160		_inherited_implication: &impl Encode,
161		_source: TransactionSource,
162	) -> ValidateResult<Self::Val, T::RuntimeCall> {
163		let Some(who) = origin.as_system_origin_signer() else {
164			return Ok((Default::default(), Val::Refund(self.weight(call)), origin))
165		};
166		let ValidNonceInfo { provides, requires } = Self::validate_nonce_for_account(who, self.0)?;
167
168		let validity = ValidTransaction {
169			priority: 0,
170			requires,
171			provides,
172			longevity: TransactionLongevity::max_value(),
173			propagate: true,
174		};
175
176		Ok((validity, Val::CheckNonce(who.clone()), origin))
177	}
178
179	fn prepare(
180		self,
181		val: Self::Val,
182		_origin: &T::RuntimeOrigin,
183		_call: &T::RuntimeCall,
184		_info: &DispatchInfoOf<T::RuntimeCall>,
185		_len: usize,
186	) -> Result<Self::Pre, TransactionValidityError> {
187		let (who, nonce) = match val {
188			Val::CheckNonce(who) => (who, self.0),
189			Val::Refund(weight) => return Ok(Pre::Refund(weight)),
190		};
191		Self::prepare_nonce_for_account(&who, nonce).map(|_| Pre::NonceChecked)
192	}
193
194	fn post_dispatch_details(
195		pre: Self::Pre,
196		_info: &DispatchInfo,
197		_post_info: &PostDispatchInfoOf<T::RuntimeCall>,
198		_len: usize,
199		_result: &DispatchResult,
200	) -> Result<Weight, TransactionValidityError> {
201		match pre {
202			Pre::NonceChecked => Ok(Weight::zero()),
203			Pre::Refund(weight) => Ok(weight),
204		}
205	}
206}
207
208#[cfg(test)]
209mod tests {
210	use super::*;
211	use crate::mock::{new_test_ext, RuntimeCall, Test, CALL};
212	use frame_support::{
213		assert_ok, assert_storage_noop, dispatch::GetDispatchInfo, traits::OriginTrait,
214	};
215	use sp_runtime::{
216		traits::{AsTransactionAuthorizedOrigin, DispatchTransaction, TxBaseImplication},
217		transaction_validity::TransactionSource::External,
218	};
219
220	#[test]
221	fn signed_ext_check_nonce_works() {
222		new_test_ext().execute_with(|| {
223			crate::Account::<Test>::insert(
224				1,
225				crate::AccountInfo {
226					nonce: 1u64.into(),
227					consumers: 0,
228					providers: 1,
229					sufficients: 0,
230					data: 0,
231				},
232			);
233			let info = DispatchInfo::default();
234			let len = 0_usize;
235			// stale
236			assert_storage_noop!({
237				assert_eq!(
238					CheckNonce::<Test>(0u64.into())
239						.validate_only(Some(1).into(), CALL, &info, len, External, 0)
240						.unwrap_err(),
241					TransactionValidityError::Invalid(InvalidTransaction::Stale)
242				);
243				assert_eq!(
244					CheckNonce::<Test>(0u64.into())
245						.validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
246						.unwrap_err(),
247					TransactionValidityError::Invalid(InvalidTransaction::Stale)
248				);
249			});
250			// correct
251			assert_ok!(CheckNonce::<Test>(1u64.into()).validate_only(
252				Some(1).into(),
253				CALL,
254				&info,
255				len,
256				External,
257				0,
258			));
259			assert_ok!(CheckNonce::<Test>(1u64.into()).validate_and_prepare(
260				Some(1).into(),
261				CALL,
262				&info,
263				len,
264				0,
265			));
266			// future
267			assert_ok!(CheckNonce::<Test>(5u64.into()).validate_only(
268				Some(1).into(),
269				CALL,
270				&info,
271				len,
272				External,
273				0,
274			));
275			assert_eq!(
276				CheckNonce::<Test>(5u64.into())
277					.validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
278					.unwrap_err(),
279				TransactionValidityError::Invalid(InvalidTransaction::Future)
280			);
281		})
282	}
283
284	#[test]
285	fn signed_ext_check_nonce_requires_provider() {
286		new_test_ext().execute_with(|| {
287			crate::Account::<Test>::insert(
288				2,
289				crate::AccountInfo {
290					nonce: 1u64.into(),
291					consumers: 0,
292					providers: 1,
293					sufficients: 0,
294					data: 0,
295				},
296			);
297			crate::Account::<Test>::insert(
298				3,
299				crate::AccountInfo {
300					nonce: 1u64.into(),
301					consumers: 0,
302					providers: 0,
303					sufficients: 1,
304					data: 0,
305				},
306			);
307			let info = DispatchInfo::default();
308			let len = 0_usize;
309			// Both providers and sufficients zero
310			assert_storage_noop!({
311				assert_eq!(
312					CheckNonce::<Test>(1u64.into())
313						.validate_only(Some(1).into(), CALL, &info, len, External, 0)
314						.unwrap_err(),
315					TransactionValidityError::Invalid(InvalidTransaction::Payment)
316				);
317				assert_eq!(
318					CheckNonce::<Test>(1u64.into())
319						.validate_and_prepare(Some(1).into(), CALL, &info, len, 0)
320						.unwrap_err(),
321					TransactionValidityError::Invalid(InvalidTransaction::Payment)
322				);
323			});
324			// Non-zero providers
325			assert_ok!(CheckNonce::<Test>(1u64.into()).validate_only(
326				Some(2).into(),
327				CALL,
328				&info,
329				len,
330				External,
331				0,
332			));
333			assert_ok!(CheckNonce::<Test>(1u64.into()).validate_and_prepare(
334				Some(2).into(),
335				CALL,
336				&info,
337				len,
338				0,
339			));
340			// Non-zero sufficients
341			assert_ok!(CheckNonce::<Test>(1u64.into()).validate_only(
342				Some(3).into(),
343				CALL,
344				&info,
345				len,
346				External,
347				0,
348			));
349			assert_ok!(CheckNonce::<Test>(1u64.into()).validate_and_prepare(
350				Some(3).into(),
351				CALL,
352				&info,
353				len,
354				0,
355			));
356		})
357	}
358
359	#[test]
360	fn unsigned_check_nonce_works() {
361		new_test_ext().execute_with(|| {
362			let info = DispatchInfo::default();
363			let len = 0_usize;
364			let (_, val, origin) = CheckNonce::<Test>(1u64.into())
365				.validate(None.into(), CALL, &info, len, (), &TxBaseImplication(CALL), External)
366				.unwrap();
367			assert!(!origin.is_transaction_authorized());
368			assert_ok!(CheckNonce::<Test>(1u64.into()).prepare(val, &origin, CALL, &info, len));
369		})
370	}
371
372	#[test]
373	fn check_nonce_preserves_account_data() {
374		new_test_ext().execute_with(|| {
375			crate::Account::<Test>::insert(
376				1,
377				crate::AccountInfo {
378					nonce: 1u64.into(),
379					consumers: 0,
380					providers: 1,
381					sufficients: 0,
382					data: 0,
383				},
384			);
385			let info = DispatchInfo::default();
386			let len = 0_usize;
387			// run the validation step
388			let (_, val, origin) = CheckNonce::<Test>(1u64.into())
389				.validate(Some(1).into(), CALL, &info, len, (), &TxBaseImplication(CALL), External)
390				.unwrap();
391			// mutate `AccountData` for the caller
392			crate::Account::<Test>::mutate(1, |info| {
393				info.data = 42;
394			});
395			// run the preparation step
396			assert_ok!(CheckNonce::<Test>(1u64.into()).prepare(val, &origin, CALL, &info, len));
397			// only the nonce should be altered by the preparation step
398			let expected_info = crate::AccountInfo {
399				nonce: 2u64.into(),
400				consumers: 0,
401				providers: 1,
402				sufficients: 0,
403				data: 42,
404			};
405			assert_eq!(crate::Account::<Test>::get(1), expected_info);
406		})
407	}
408
409	#[test]
410	fn check_nonce_skipped_and_refund_for_other_origins() {
411		new_test_ext().execute_with(|| {
412			let ext = CheckNonce::<Test>(1u64.into());
413
414			let mut info = CALL.get_dispatch_info();
415			info.extension_weight = ext.weight(CALL);
416
417			// Ensure we test the refund.
418			assert!(info.extension_weight != Weight::zero());
419
420			let len = CALL.encoded_size();
421
422			let origin = crate::RawOrigin::Root.into();
423			let (pre, origin) = ext.validate_and_prepare(origin, CALL, &info, len, 0).unwrap();
424
425			assert!(origin.as_system_ref().unwrap().is_root());
426
427			let pd_res = Ok(());
428			let mut post_info = frame_support::dispatch::PostDispatchInfo {
429				actual_weight: Some(info.total_weight()),
430				pays_fee: Default::default(),
431			};
432
433			<CheckNonce<Test> as TransactionExtension<RuntimeCall>>::post_dispatch(
434				pre,
435				&info,
436				&mut post_info,
437				len,
438				&pd_res,
439			)
440			.unwrap();
441
442			assert_eq!(post_info.actual_weight, Some(info.call_weight));
443		})
444	}
445}