Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FRAME: Meta Transaction (extension based version) #3712

Draft
wants to merge 5 commits into
base: george/restore-gav-tx-ext
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions substrate/frame/support/src/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,8 @@ where
}

/// Implementation for unchecked extrinsic.
impl<Address, Call, Signature, Extension> GetDispatchInfo
for UncheckedExtrinsic<Address, Call, Signature, Extension>
impl<Address, Call, Signature, Extension, Context> GetDispatchInfo
for UncheckedExtrinsic<Address, Call, Signature, Extension, Context>
where
Call: GetDispatchInfo + Dispatchable,
{
Expand All @@ -380,7 +380,8 @@ where
}

/// Implementation for checked extrinsic.
impl<AccountId, Call, Extension> GetDispatchInfo for CheckedExtrinsic<AccountId, Call, Extension>
impl<AccountId, Call, Extension, Context> GetDispatchInfo
for CheckedExtrinsic<AccountId, Call, Extension, Context>
where
Call: GetDispatchInfo,
{
Expand Down
4 changes: 2 additions & 2 deletions substrate/frame/support/src/traits/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -919,8 +919,8 @@ pub trait ExtrinsicCall: sp_runtime::traits::Extrinsic {
fn call(&self) -> &Self::Call;
}

impl<Address, Call, Signature, Extra> ExtrinsicCall
for sp_runtime::generic::UncheckedExtrinsic<Address, Call, Signature, Extra>
impl<Address, Call, Signature, Extra, Context> ExtrinsicCall
for sp_runtime::generic::UncheckedExtrinsic<Address, Call, Signature, Extra, Context>
where
Address: TypeInfo,
Call: TypeInfo,
Expand Down
89 changes: 89 additions & 0 deletions substrate/frame/support/src/transaction_extensions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,92 @@ where
Ok((ValidTransaction::default(), (), origin))
}
}

/// Transaction extension that sets the signed origin of the account ID from a given signature if
/// that signature was derived from the `inherited_implication`, which contains the call and all
/// subsequent extensions. If signature is not provided, this extension is no-op.
#[derive(
CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo,
)]
#[codec(encode_bound())]
#[codec(decode_bound())]
pub struct VerifyAccountSignature<V: Verify>
where
V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
<V::Signer as IdentifyAccount>::AccountId:
Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
{
signature: Option<(V, <V::Signer as IdentifyAccount>::AccountId)>,
}

impl<V: Verify> Default for VerifyAccountSignature<V>
where
V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
<V::Signer as IdentifyAccount>::AccountId:
Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
{
fn default() -> Self {
Self { signature: None }
}
}

impl<V: Verify> VerifyAccountSignature<V>
where
V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
<V::Signer as IdentifyAccount>::AccountId:
Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
{
pub fn new_with_sign(signature: V, account: <V::Signer as IdentifyAccount>::AccountId) -> Self {
Self { signature: Some((signature, account)) }
}
}

impl<V: Verify> TransactionExtensionBase for VerifyAccountSignature<V>
where
V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
<V::Signer as IdentifyAccount>::AccountId:
Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
{
const IDENTIFIER: &'static str = "VerifyAccountSignature";
type Implicit = ();
}

impl<V: Verify, Call: Dispatchable + Encode, Context> TransactionExtension<Call, Context>
for VerifyAccountSignature<V>
where
V: Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
<V::Signer as IdentifyAccount>::AccountId:
Codec + Debug + Sync + Send + Clone + Eq + PartialEq + StaticTypeInfo,
<Call as Dispatchable>::RuntimeOrigin: From<Option<<V::Signer as IdentifyAccount>::AccountId>>,
{
type Val = ();
type Pre = ();
impl_tx_ext_default!(Call; Context; prepare);

fn validate(
&self,
origin: <Call as Dispatchable>::RuntimeOrigin,
_call: &Call,
_info: &DispatchInfoOf<Call>,
_len: usize,
_: &mut Context,
_: (),
inherited_implication: &impl Encode,
) -> Result<
(ValidTransaction, Self::Val, <Call as Dispatchable>::RuntimeOrigin),
TransactionValidityError,
> {
let (signature, account_id) = match &self.signature {
Some((s, a)) => (s, a.clone()),
None => return Ok((ValidTransaction::default(), (), origin)),
};

let msg = inherited_implication.using_encoded(blake2_256);

if !signature.verify(&msg[..], &account_id) {
Err(InvalidTransaction::BadProof)?
}
let origin = Some(account_id).into();
Ok((ValidTransaction::default(), (), origin))
}
}
1 change: 1 addition & 0 deletions substrate/frame/transaction-payment/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ sp-std = { path = "../../primitives/std", default-features = false }
[dev-dependencies]
serde_json = { workspace = true, default-features = true }
pallet-balances = { path = "../balances" }
keyring = { package = "sp-keyring", path = "../../primitives/keyring" }

[features]
default = ["std"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ use frame_support::{
},
DefaultNoBound,
};
use pallet_transaction_payment::{ChargeTransactionPayment, OnChargeTransaction};
use pallet_transaction_payment::{OnChargeTransaction, Priority};
use scale_info::TypeInfo;
use sp_runtime::{
traits::{
Expand Down Expand Up @@ -317,7 +317,7 @@ where
let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
// Non-mutating call of `compute_fee` to calculate the fee used in the transaction priority.
let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, self.tip);
let priority = ChargeTransactionPayment::<T>::get_priority(info, len, self.tip, fee);
let priority = Priority::<T>::get_priority(info, len, self.tip, fee);
let validity = ValidTransaction { priority, ..Default::default() };
let val = (self.tip, who.clone(), fee);
Ok((validity, val, origin))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,11 @@ where
(ValidTransaction, Self::Val, <T::RuntimeCall as Dispatchable>::RuntimeOrigin),
TransactionValidityError,
> {
use pallet_transaction_payment::ChargeTransactionPayment;
use pallet_transaction_payment::Priority;
let who = origin.as_system_origin_signer().ok_or(InvalidTransaction::BadSigner)?;
// Non-mutating call of `compute_fee` to calculate the fee used in the transaction priority.
let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, self.tip);
let priority = ChargeTransactionPayment::<T>::get_priority(info, len, self.tip, fee);
let priority = Priority::<T>::get_priority(info, len, self.tip, fee);
let val = (self.tip, who.clone(), fee);
let validity = ValidTransaction { priority, ..Default::default() };
Ok((validity, val, origin))
Expand Down
121 changes: 52 additions & 69 deletions substrate/frame/transaction-payment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#![cfg_attr(not(feature = "std"), no_std)]

use codec::{Decode, Encode, MaxEncodedLen};
use core::marker::PhantomData;
use scale_info::TypeInfo;

use frame_support::{
Expand All @@ -73,14 +74,16 @@ use sp_std::prelude::*;
pub use types::{FeeDetails, InclusionFee, RuntimeDispatchInfo};
pub use weights::WeightInfo;

#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
// TODO: failing now, needs a type annotation for Context.
// #[cfg(test)]
// mod mock;
// #[cfg(test)]
// mod tests;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

pub mod meta_tx;
mod payment;
mod types;
pub mod weights;
Expand Down Expand Up @@ -689,72 +692,13 @@ where
}
}

/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
/// in the queue.
///
/// # Transaction Validity
///
/// This extension sets the `priority` field of `TransactionValidity` depending on the amount
/// of tip being paid per weight unit.
///
/// Operational transactions will receive an additional priority bump, so that they are normally
/// considered before regular transactions.
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);

impl<T: Config> ChargeTransactionPayment<T>
/// Priority for a transaction with the given `DispatchInfo`, encoded length and user-included tip.
pub struct Priority<T: Config>(PhantomData<T>);
impl<T: Config> Priority<T>
where
T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
BalanceOf<T>: Send + Sync,
{
/// utility constructor. Used only in client/factory code.
pub fn from(fee: BalanceOf<T>) -> Self {
Self(fee)
}

/// Returns the tip as being chosen by the transaction sender.
pub fn tip(&self) -> BalanceOf<T> {
self.0
}

fn withdraw_fee(
&self,
who: &T::AccountId,
call: &T::RuntimeCall,
info: &DispatchInfoOf<T::RuntimeCall>,
fee: BalanceOf<T>,
) -> Result<
(
BalanceOf<T>,
<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::LiquidityInfo,
),
TransactionValidityError,
> {
let tip = self.0;

<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::withdraw_fee(
who, call, info, fee, tip,
)
.map(|i| (fee, i))
}

fn can_withdraw_fee(
&self,
who: &T::AccountId,
call: &T::RuntimeCall,
info: &DispatchInfoOf<T::RuntimeCall>,
len: usize,
) -> Result<BalanceOf<T>, TransactionValidityError> {
let tip = self.0;
let fee = Pallet::<T>::compute_fee(len as u32, info, tip);

<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::can_withdraw_fee(
who, call, info, fee, tip,
)?;
Ok(fee)
}

/// Get an appropriate priority for a transaction with the given `DispatchInfo`, encoded length
/// and user-included tip.
///
Expand Down Expand Up @@ -830,6 +774,32 @@ where
}
}

/// Require the transactor pay for themselves and maybe include a tip to gain additional priority
/// in the queue.
///
/// # Transaction Validity
///
/// This extension sets the `priority` field of `TransactionValidity` depending on the amount
/// of tip being paid per weight unit.
///
/// Operational transactions will receive an additional priority bump, so that they are normally
/// considered before regular transactions.
#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct ChargeTransactionPayment<T: Config>(#[codec(compact)] BalanceOf<T>);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no changes to the API of this type. get_priority method decoupled to the separate Priority type to be reused. the withdraw_fee and can_withdraw_fee just dropped.


impl<T: Config> ChargeTransactionPayment<T> {
/// utility constructor. Used only in client/factory code.
pub fn from(fee: BalanceOf<T>) -> Self {
Self(fee)
}

/// Returns the tip as being chosen by the transaction sender.
pub fn tip(&self) -> BalanceOf<T> {
self.0
}
}

impl<T: Config> sp_std::fmt::Debug for ChargeTransactionPayment<T> {
#[cfg(feature = "std")]
fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
Expand Down Expand Up @@ -888,11 +858,20 @@ where
> {
let who = frame_system::ensure_signed(origin.clone())
.map_err(|_| InvalidTransaction::BadSigner)?;
let final_fee = self.can_withdraw_fee(&who, call, info, len)?;

let tip = self.0;

let final_fee = {
let fee = Pallet::<T>::compute_fee(len as u32, info, tip);
<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::can_withdraw_fee(
&who, call, info, fee, tip,
)?;
fee
};

Ok((
ValidTransaction {
priority: Self::get_priority(info, len, tip, final_fee),
priority: Priority::<T>::get_priority(info, len, tip, final_fee),
..Default::default()
},
(self.0, who, final_fee),
Expand All @@ -911,7 +890,11 @@ where
) -> Result<Self::Pre, TransactionValidityError> {
let (tip, who, fee) = val;
// Mutating call to `withdraw_fee` to actually charge for the transaction.
let (_final_fee, imbalance) = self.withdraw_fee(&who, call, info, fee)?;
let imbalance =
<<T as Config>::OnChargeTransaction as OnChargeTransaction<T>>::withdraw_fee(
&who, call, info, fee, tip,
)?;

Ok((tip, who, imbalance))
}

Expand Down
Loading
Loading