Skip to content

Commit

Permalink
Batched Capacity Transactions (#1346)
Browse files Browse the repository at this point in the history
# Goal
Add the ability to pay for a batch of Capacity transactions.

The `pay_with_capacity_batch_all` extrinsic was added to the
`frequency-tx-payment` pallet.

Closes #1276

---------
  • Loading branch information
John Mela authored Apr 12, 2023
1 parent 3ef1978 commit 44a40c5
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 30 deletions.
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.

10 changes: 8 additions & 2 deletions common/primitives/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ pub use sp_runtime::{
traits::{BlakeTwo256, IdentifyAccount, Verify},
MultiAddress, MultiSignature, OpaqueExtrinsic,
};
use sp_std::boxed::Box;
use sp_std::{boxed::Box, vec::Vec};

use frame_support::dispatch::DispatchError;
use frame_support::dispatch::{DispatchError, DispatchResultWithPostInfo};

/// Some way of identifying an account on the chain. We intentionally make it equivalent
/// to the public key of our transaction signing scheme.
Expand Down Expand Up @@ -59,3 +59,9 @@ pub trait ProposalProvider<AccountId, Proposal> {
#[cfg(any(feature = "runtime-benchmarks", feature = "test"))]
fn proposal_count() -> u32;
}

/// The provider for interfacing into the Utility pallet.
pub trait UtilityProvider<Origin, RuntimeCall> {
/// Passthrough into the Utility::batch_all call
fn batch_all(origin: Origin, calls: Vec<RuntimeCall>) -> DispatchResultWithPostInfo;
}
1 change: 1 addition & 0 deletions pallets/frequency-tx-payment/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ common-primitives = {default-features = false, path = "../../common/primitives"}

[dev-dependencies]
pallet-balances = {git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36"}
pallet-utility = {git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36"}

# Frequency related dev dependencies
pallet-msa = {path = '../msa'}
Expand Down
13 changes: 13 additions & 0 deletions pallets/frequency-tx-payment/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ benchmarks! {
let call: Box<<T as Config>::RuntimeCall> = Box::new(SystemCall::remark { remark: vec![] }.into());
}: _ (RawOrigin::Signed(sender), call)

pay_with_capacity_batch_all {
let n in 0 .. (T::MaximumCapacityBatchLength::get() as u32);

let sender: T::AccountId = whitelisted_caller();

let mut batched_calls: Vec<<T as Config>::RuntimeCall> = vec![];

for i in 0 .. n {
let call: <T as Config>::RuntimeCall = SystemCall::remark { remark: vec![] }.into();
batched_calls.push(call);
}
}: _ (RawOrigin::Signed(sender), batched_calls)

impl_benchmark_test_suite!(
Pallet,
crate::mock::ExtBuilder::default().build(),
Expand Down
96 changes: 73 additions & 23 deletions pallets/frequency-tx-payment/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ use sp_runtime::{
};
use sp_std::prelude::*;

use common_primitives::capacity::{Nontransferable, Replenishable};
use common_primitives::{
capacity::{Nontransferable, Replenishable},
node::UtilityProvider,
};
pub use pallet::*;
pub use weights::*;

Expand Down Expand Up @@ -168,32 +171,35 @@ pub mod pallet {

/// Charge Capacity for transaction payments.
type OnChargeCapacityTransaction: OnChargeCapacityTransaction<Self>;

/// The maxmimum number of capacity calls that can be batched together.
#[pallet::constant]
type MaximumCapacityBatchLength: Get<u8>;

type BatchProvider: UtilityProvider<OriginFor<Self>, <Self as Config>::RuntimeCall>;
}

#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {}

#[pallet::error]
pub enum Error<T> {
/// The maximum amount of requested batched calls was exceeded
BatchedCallAmountExceedsMaximum,
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Dispatch the given call as a sub_type of pay_with_capacity. Calls dispatched in this
/// fashion, if allowed, will pay with Capacity
/// fashion, if allowed, will pay with Capacity.
// The weight calculation is a temporary adjustment because overhead benchmarks do not account
// for capacity calls. We count reads and writes for a pay_with_capacity call,
// then subtract one of each for regular transactions since overhead benchmarks account for these.
// Storage: Msa PublicKeyToMsaId (r:1)
// Storage: Capacity CapacityLedger(r:1, w:2)
// Storage: Capacity CurrentEpoch(r:1) ? maybe cached in on_initialize
// Storage: System Account(r:1)
// Total (r: 4-1=3, w: 2-1=1)
#[pallet::call_index(0)]
#[pallet::weight({
use frame_support::weights::constants::RocksDbWeight;
let dispatch_info = call.get_dispatch_info();
let capacity_overhead = RocksDbWeight::get().reads(2)
.saturating_add(
RocksDbWeight::get().writes(1)
);
let capacity_overhead = Pallet::<T>::get_capacity_overhead_weight();
let total = capacity_overhead.saturating_add(dispatch_info.weight);
(< T as Config >::WeightInfo::pay_with_capacity().saturating_add(total), dispatch_info.class)
})]
Expand All @@ -205,10 +211,48 @@ pub mod pallet {

call.dispatch(origin)
}

/// Dispatch the given call as a sub_type of pay_with_capacity_batch_all. Calls dispatched in this
/// fashion, if allowed, will pay with Capacity.
#[pallet::call_index(1)]
#[pallet::weight({
let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::<Vec<_>>();
let dispatch_weight = dispatch_infos.iter()
.map(|di| di.weight)
.fold(Weight::zero(), |total: Weight, weight: Weight| total.saturating_add(weight));
let capacity_overhead = Pallet::<T>::get_capacity_overhead_weight();
let total = capacity_overhead.saturating_add(dispatch_weight);
(< T as Config >::WeightInfo::pay_with_capacity_batch_all(calls.len() as u32).saturating_add(total), DispatchClass::Normal)
})]
pub fn pay_with_capacity_batch_all(
origin: OriginFor<T>,
calls: Vec<<T as Config>::RuntimeCall>,
) -> DispatchResultWithPostInfo {
ensure_signed(origin.clone())?;
ensure!(
calls.len() <= T::MaximumCapacityBatchLength::get().into(),
Error::<T>::BatchedCallAmountExceedsMaximum
);

T::BatchProvider::batch_all(origin, calls)
}
}
}

impl<T: Config> Pallet<T> {
// The weight calculation is a temporary adjustment because overhead benchmarks do not account
// for capacity calls. We count reads and writes for a pay_with_capacity call,
// then subtract one of each for regular transactions since overhead benchmarks account for these.
// Storage: Msa PublicKeyToMsaId (r:1)
// Storage: Capacity CapacityLedger(r:1, w:2)
// Storage: Capacity CurrentEpoch(r:1) ? maybe cached in on_initialize
// Storage: System Account(r:1)
// Total (r: 4-1=3, w: 2-1=1)
pub fn get_capacity_overhead_weight() -> Weight {
T::DbWeight::get().reads(2).saturating_add(T::DbWeight::get().writes(1))
}

pub fn compute_capacity_fee(
len: u32,
class: DispatchClass,
Expand Down Expand Up @@ -284,7 +328,8 @@ where
/// Return the tip as being chosen by the transaction sender.
pub fn tip(&self, call: &<T as frame_system::Config>::RuntimeCall) -> BalanceOf<T> {
match call.is_sub_type() {
Some(Call::pay_with_capacity { .. }) => Zero::zero(),
Some(Call::pay_with_capacity { .. }) |
Some(Call::pay_with_capacity_batch_all { .. }) => Zero::zero(),
_ => self.0,
}
}
Expand All @@ -298,26 +343,31 @@ where
len: usize,
) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
match call.is_sub_type() {
Some(Call::pay_with_capacity { call }) => self.withdraw_capacity_fee(who, call, len),
Some(Call::pay_with_capacity { call }) =>
self.withdraw_capacity_fee(who, &vec![*call.clone()], len),
Some(Call::pay_with_capacity_batch_all { calls }) =>
self.withdraw_capacity_fee(who, calls, len),
_ => self.withdraw_token_fee(who, call, info, len, self.tip(call)),
}
}

/// Withdraws transaction fee paid Capacity using a key associated to an MSA.
/// Withdraws the transaction fee paid in Capacity using a key associated to an MSA.
fn withdraw_capacity_fee(
&self,
key: &T::AccountId,
call: &<T as Config>::RuntimeCall,
calls: &Vec<<T as Config>::RuntimeCall>,
len: usize,
) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
let extrinsic_weight = T::CapacityCalls::get_stable_weight(call)
.ok_or(ChargeFrqTransactionPaymentError::CallIsNotCapacityEligible.into())?;
let mut calls_weight_sum = Weight::zero();

let fee = Pallet::<T>::compute_capacity_fee(
len as u32,
call.get_dispatch_info().class,
extrinsic_weight,
);
for call in calls {
let call_weight = T::CapacityCalls::get_stable_weight(call)
.ok_or(ChargeFrqTransactionPaymentError::CallIsNotCapacityEligible.into())?;
calls_weight_sum = calls_weight_sum.saturating_add(call_weight);
}

let fee =
Pallet::<T>::compute_capacity_fee(len as u32, DispatchClass::Normal, calls_weight_sum);

let fee = T::OnChargeCapacityTransaction::withdraw_fee(key, fee.into())?;

Expand Down
19 changes: 19 additions & 0 deletions pallets/frequency-tx-payment/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ frame_support::construct_runtime!(
Capacity: pallet_capacity::{Pallet, Call, Storage, Event<T>},
TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event<T>},
FrequencyTxPayment: pallet_frequency_tx_payment::{Pallet, Call, Event<T>},
Utility: pallet_utility::{Pallet, Call, Storage, Event},
}
);

Expand Down Expand Up @@ -98,6 +99,7 @@ impl pallet_balances::Config for Test {

parameter_types! {
pub const MaxSchemaGrantsPerDelegation: u32 = 30;
pub const MaximumCapacityBatchLength: u8 = 2;
}
impl Clone for MaxSchemaGrantsPerDelegation {
fn clone(&self) -> Self {
Expand Down Expand Up @@ -237,13 +239,30 @@ impl GetStableWeight<RuntimeCall, Weight> for TestCapacityCalls {
}
}

pub struct CapacityBatchProvider;

impl UtilityProvider<RuntimeOrigin, RuntimeCall> for CapacityBatchProvider {
fn batch_all(origin: RuntimeOrigin, calls: Vec<RuntimeCall>) -> DispatchResultWithPostInfo {
Utility::batch_all(origin, calls)
}
}

impl pallet_utility::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PalletsOrigin = OriginCaller;
type WeightInfo = ();
}

impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type Capacity = Capacity;
type WeightInfo = ();
type CapacityCalls = TestCapacityCalls;
type OnChargeCapacityTransaction = payment::CapacityAdapter<Balances, Msa>;
type MaximumCapacityBatchLength = MaximumCapacityBatchLength;
type BatchProvider = CapacityBatchProvider;
}

pub struct ExtBuilder {
Expand Down
Loading

0 comments on commit 44a40c5

Please sign in to comment.