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 2 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
153 changes: 151 additions & 2 deletions polkadot/runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,10 @@ where
.saturating_sub(1);
let tip = 0;
let tx_ext: TxExtension = (
pallet_transaction_payment::SetFeeAgent::<sp_runtime::MultiSignature>::default(),
frame_support::transaction_extensions::SignedOriginSignature::<
sp_runtime::MultiSignature,
>::default(),
frame_system::CheckNonZeroSender::<Runtime>::new(),
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
Expand Down Expand Up @@ -1553,6 +1557,8 @@ pub type SignedBlock = generic::SignedBlock<Block>;
pub type BlockId = generic::BlockId<Block>;
/// The extension to the basic transaction logic.
pub type TxExtension = (
pallet_transaction_payment::SetFeeAgent<sp_runtime::MultiSignature>,
frame_support::transaction_extensions::SignedOriginSignature<sp_runtime::MultiSignature>,
frame_system::CheckNonZeroSender<Runtime>,
frame_system::CheckSpecVersion<Runtime>,
frame_system::CheckTxVersion<Runtime>,
Expand All @@ -1563,6 +1569,144 @@ pub type TxExtension = (
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
);

#[test]
fn free_transaction_extension_test() {
sp_io::TestExternalities::default().execute_with(|| {
use frame_support::{
dispatch::{DispatchInfo, RawOrigin},
traits::BuildGenesisConfig,
};
use keyring::AccountKeyring;
use sp_io::hashing::blake2_256;
use sp_runtime::{
traits::{DispatchTransaction, TransactionExtensionBase},
MultiSignature,
};

// The part of `TxExtension` that has to be provided and signed by user who wants
// the transaciton fee to be sponsored by someone else.
type BaseTxExtension = (
Copy link
Contributor

Choose a reason for hiding this comment

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

frame_system::CheckNonZeroSender<Runtime>,
frame_system::CheckSpecVersion<Runtime>,
frame_system::CheckTxVersion<Runtime>,
frame_system::CheckGenesis<Runtime>,
frame_system::CheckMortality<Runtime>,
frame_system::CheckNonce<Runtime>,
frame_system::CheckWeight<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
);

// The part of `TxExtension` that has to be provided and signed by the fee agent,
// the user who sponsors the transaction fee.
type SignedTxExtension = (
frame_support::transaction_extensions::SignedOriginSignature<MultiSignature>,
BaseTxExtension,
);

frame_system::GenesisConfig::<Runtime>::default().build();
System::set_block_number(1);

// Alice wants the transaction fee to be sponsored by Bob.
let alice_keyring = AccountKeyring::Alice;
let bob_keyring = AccountKeyring::Bob;

let alice_account = AccountId::from(alice_keyring.public());
let bob_account = AccountId::from(bob_keyring.public());

// Setup the initial balances.
let alice_balance = 10 * ExistentialDeposit::get();
let bob_balance = 10 * ExistentialDeposit::get();

Balances::force_set_balance(
RuntimeOrigin::root(),
alice_account.clone().into(),
alice_balance,
)
.unwrap();

Balances::force_set_balance(RuntimeOrigin::root(), bob_account.clone().into(), bob_balance)
.unwrap();

// The call that Alice wants to be executed.
let call = RuntimeCall::System(frame_system::Call::remark_with_event { remark: vec![1] });

// Alice builds the transaction extension for the sponsored transaction.
let stmt_ext: BaseTxExtension = (
frame_system::CheckNonZeroSender::<Runtime>::new(),
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckMortality::<Runtime>::from(sp_runtime::generic::Era::immortal()),
frame_system::CheckNonce::<Runtime>::from(
frame_system::Pallet::<Runtime>::account(&alice_account).nonce,
),
frame_system::CheckWeight::<Runtime>::new(),
// In case if any agent can sponsor the transaction fee.
// pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::with_any_agent(),
// Only Bob can sponsor the transaction fee.
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::with_agent(
bob_account.clone(),
),
)
.into();

// Alice signs the transaction extension and shares it.

let stmt_sign = MultiSignature::Sr25519(
(call.clone(), stmt_ext.clone(), stmt_ext.implicit().unwrap())
.using_encoded(|e| alice_keyring.sign(&blake2_256(e))),
);

let statement = (call.clone(), stmt_ext, stmt_sign).encode();

type Statement = (RuntimeCall, BaseTxExtension, MultiSignature);
let (stmt_call, stmt_ext, stmt_sign) = Statement::decode(&mut &statement[..]).unwrap();

// Bob constructs the transaction based on Alice statemnt.

let signed_tx_ext = frame_support::transaction_extensions::SignedOriginSignature::<
MultiSignature,
>::new_with_sign(stmt_sign, alice_account.clone());

let mut signed_tx_ext = signed_tx_ext.encode();
signed_tx_ext.append(&mut stmt_ext.encode());
let signed_tx_ext: SignedTxExtension =
SignedTxExtension::decode(&mut &signed_tx_ext[..]).unwrap();

// Bob signs the transaction with Alice's part to poof he is willing to sponser the fee.
let signed_tx_sign = MultiSignature::Sr25519(
(stmt_call, signed_tx_ext.clone(), signed_tx_ext.implicit().unwrap())
.using_encoded(|e| bob_keyring.sign(&blake2_256(e))),
);

let tx_ext = pallet_transaction_payment::SetFeeAgent::<MultiSignature>::new_with_agent(
signed_tx_sign,
bob_account.clone(),
);

let mut tx_ext_encoded = tx_ext.encode();
tx_ext_encoded.append(&mut signed_tx_ext.encode());

// The final valid for submission transaction extension.
let tx_ext: TxExtension = TxExtension::decode(&mut &tx_ext_encoded[..]).unwrap();

let _ = SignedPayload::new(call.clone(), tx_ext.clone()).unwrap();

let _ = TxExtension::dispatch_transaction(
tx_ext,
RawOrigin::None.into(),
call,
&DispatchInfo::default(),
0,
)
.unwrap();

// Alice balance is unchanged, Bob paid the transaction fee.
assert_eq!(alice_balance, Balances::free_balance(alice_account));
assert!(bob_balance > Balances::free_balance(bob_account));
});
}

pub struct NominationPoolsMigrationV4OldPallet;
impl Get<Perbill> for NominationPoolsMigrationV4OldPallet {
fn get() -> Perbill {
Expand Down Expand Up @@ -1715,8 +1859,13 @@ pub mod migrations {
}

/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>;
pub type UncheckedExtrinsic = generic::UncheckedExtrinsic<
Address,
RuntimeCall,
Signature,
TxExtension,
pallet_transaction_payment::Context<AccountId>,
>;
/// Executive: handles dispatch to the various modules.
pub type Executive = frame_executive::Executive<
Runtime,
Expand Down
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
93 changes: 93 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,96 @@ where
Ok((ValidTransaction::default(), (), origin))
}
}

/// Transaction extension that sets the origin to the given account ID if the provided signature by
/// that account is valid for all subsequent extensions. If signature is not provided, this
/// extension is no-op.
// TODO better doc.
#[derive(
CloneNoBound, EqNoBound, PartialEqNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo,
)]
#[codec(encode_bound())]
#[codec(decode_bound())]
pub struct SignedOriginSignature<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 SignedOriginSignature<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> SignedOriginSignature<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_id: <V::Signer as IdentifyAccount>::AccountId,
) -> Self {
Self { signature: Some((signature, account_id)) }
}
}

impl<V: Verify> TransactionExtensionBase for SignedOriginSignature<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 = "SignedOriginSignature";
type Implicit = ();
}

impl<V: Verify, Call: Dispatchable + Encode, Context> TransactionExtension<Call, Context>
for SignedOriginSignature<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()), // TODO check if origin None
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))
}
}
Loading
Loading