From a25a431f1575d4702fc9a5769a9b934c621c4d09 Mon Sep 17 00:00:00 2001 From: Dmitrii Novikov Date: Tue, 21 May 2024 12:14:57 +0400 Subject: [PATCH] fix(runtime): Fix inability to use vouchers with no balance (#3967) --- gsdk/src/metadata/generated.rs | 9 +-- node/cli/src/benchmarking.rs | 2 +- node/testing/src/keyring.rs | 6 +- runtime/vara/src/lib.rs | 115 +++++++++++++++++++++++++++++++-- 4 files changed, 117 insertions(+), 15 deletions(-) diff --git a/gsdk/src/metadata/generated.rs b/gsdk/src/metadata/generated.rs index 9e876ed15a0..d8fe07e2e23 100644 --- a/gsdk/src/metadata/generated.rs +++ b/gsdk/src/metadata/generated.rs @@ -205,13 +205,6 @@ pub mod runtime_types { )] pub struct CheckNonZeroSender; } - pub mod check_nonce { - use super::runtime_types; - #[derive( - Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode, - )] - pub struct CheckNonce(#[codec(compact)] pub ::core::primitive::u32); - } pub mod check_spec_version { use super::runtime_types; #[derive( @@ -7459,6 +7452,8 @@ pub mod runtime_types { } } #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] + pub struct CustomCheckNonce(#[codec(compact)] pub ::core::primitive::u32); + #[derive(Debug, crate::gp::Decode, crate::gp::DecodeAsType, crate::gp::Encode)] pub struct NposSolution16 { pub votes1: ::std::vec::Vec<( ::subxt::ext::codec::Compact<::core::primitive::u32>, diff --git a/node/cli/src/benchmarking.rs b/node/cli/src/benchmarking.rs index 02ccc894981..af13934f899 100644 --- a/node/cli/src/benchmarking.rs +++ b/node/cli/src/benchmarking.rs @@ -81,7 +81,7 @@ macro_rules! with_signed_payload { frame_system::CheckMortality::::from( sp_runtime::generic::Era::mortal($period, $current_block), ), - frame_system::CheckNonce::::from($nonce), + runtime::CustomCheckNonce::::from($nonce), frame_system::CheckWeight::::new(), pallet_gear_payment::CustomChargeTransactionPayment::::from($tip), ); diff --git a/node/testing/src/keyring.rs b/node/testing/src/keyring.rs index 2b601318c35..bdeca6582c9 100644 --- a/node/testing/src/keyring.rs +++ b/node/testing/src/keyring.rs @@ -23,8 +23,8 @@ use runtime_primitives::{AccountId, Nonce}; use sp_keyring::{AccountKeyring, Ed25519Keyring, Sr25519Keyring}; use sp_runtime::generic::Era; use vara_runtime::{ - CustomChargeTransactionPayment, RuntimeCall, SessionKeys, SignedExtra, StakingBlackList, - UncheckedExtrinsic, + CustomChargeTransactionPayment, CustomCheckNonce, RuntimeCall, SessionKeys, SignedExtra, + StakingBlackList, UncheckedExtrinsic, }; pub type CheckedExtrinsic = @@ -82,7 +82,7 @@ pub fn signed_extra(nonce: Nonce) -> SignedExtra { frame_system::CheckTxVersion::new(), frame_system::CheckGenesis::new(), frame_system::CheckEra::from(Era::mortal(256, 0)), - frame_system::CheckNonce::from(nonce), + CustomCheckNonce::from(nonce), frame_system::CheckWeight::new(), CustomChargeTransactionPayment::from(0), ) diff --git a/runtime/vara/src/lib.rs b/runtime/vara/src/lib.rs index bbbaa4f2e91..3ddb61a831e 100644 --- a/runtime/vara/src/lib.rs +++ b/runtime/vara/src/lib.rs @@ -30,7 +30,6 @@ use frame_election_provider_support::{ bounds::ElectionBoundsBuilder, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen, VoteWeight, }; -use frame_support::weights::ConstantMultiplier; pub use frame_support::{ construct_runtime, dispatch::{DispatchClass, WeighData}, @@ -52,6 +51,13 @@ pub use frame_support::{ }, PalletId, StorageValue, }; +use frame_support::{ + dispatch::DispatchInfo, + pallet_prelude::{ + InvalidTransaction, TransactionLongevity, TransactionValidityError, ValidTransaction, + }, + weights::ConstantMultiplier, +}; use frame_system::{ limits::{BlockLength, BlockWeights}, EnsureRoot, @@ -81,6 +87,7 @@ pub use runtime_common::{ }; pub use runtime_primitives::{AccountId, Signature, VARA_SS58_PREFIX}; use runtime_primitives::{Balance, BlockNumber, Hash, Moment, Nonce}; +use scale_info::TypeInfo; use sp_api::impl_runtime_apis; #[cfg(any(feature = "std", test))] use sp_api::{CallApiAt, CallContext, Extensions, OverlayedChanges, ProofRecorder}; @@ -91,8 +98,8 @@ use sp_runtime::{ codec::{Decode, Encode, MaxEncodedLen}, create_runtime_str, generic, impl_opaque_keys, traits::{ - AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, IdentityLookup, NumberFor, - OpaqueKeys, + AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, DispatchInfoOf, Dispatchable, + IdentityLookup, NumberFor, One, OpaqueKeys, SignedExtension, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, ApplyExtrinsicResult, FixedU128, Perbill, Percent, Permill, Perquintill, RuntimeDebug, @@ -1303,7 +1310,7 @@ pub type SignedExtra = ( frame_system::CheckTxVersion, frame_system::CheckGenesis, frame_system::CheckEra, - frame_system::CheckNonce, + CustomCheckNonce, frame_system::CheckWeight, CustomChargeTransactionPayment, ); @@ -1554,3 +1561,103 @@ where } } } + +/// Nonce check and increment to give replay protection for transactions. +/// +/// # Transaction Validity +/// +/// This extension affects `requires` and `provides` tags of validity, but DOES NOT +/// set the `priority` field. Make sure that AT LEAST one of the signed extension sets +/// some kind of priority upon validating transactions. +/// +/// NOTE: Copy-paste from substrate/frame/system/src/extensions/check_nonce.rs, +/// but without providers and sufficients checks, so contains revert of changes +/// from substrate v1.3.0 https://github.com/paritytech/polkadot-sdk/pull/1578. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct CustomCheckNonce(#[codec(compact)] pub T::Nonce); + +impl CustomCheckNonce { + /// utility constructor. Used only in client/factory code. + pub fn from(nonce: T::Nonce) -> Self { + Self(nonce) + } +} + +impl sp_std::fmt::Debug for CustomCheckNonce { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "CustomCheckNonce({})", self.0) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl SignedExtension for CustomCheckNonce +where + T::RuntimeCall: Dispatchable, +{ + type AccountId = as SignedExtension>::AccountId; + type Call = as SignedExtension>::Call; + type AdditionalSigned = as SignedExtension>::AdditionalSigned; + type Pre = as SignedExtension>::Pre; + const IDENTIFIER: &'static str = as SignedExtension>::IDENTIFIER; + + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + Ok(()) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + _call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> Result<(), TransactionValidityError> { + let mut account = frame_system::Account::::get(who); + + if self.0 != account.nonce { + return Err(if self.0 < account.nonce { + InvalidTransaction::Stale + } else { + InvalidTransaction::Future + } + .into()); + } + account.nonce += T::Nonce::one(); + frame_system::Account::::insert(who, account); + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + _call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + let account = frame_system::Account::::get(who); + + if self.0 < account.nonce { + return InvalidTransaction::Stale.into(); + } + + let provides = vec![Encode::encode(&(who, self.0))]; + let requires = if account.nonce < self.0 { + vec![Encode::encode(&(who, self.0 - One::one()))] + } else { + vec![] + }; + + Ok(ValidTransaction { + priority: 0, + requires, + provides, + longevity: TransactionLongevity::max_value(), + propagate: true, + }) + } +}