Allow fee calculation to happen off-chain (#6076)

* Emit a PaymentParameters event once per block

This contains per-block paramaters need to calculate
fees off-chain.

* Add WeightToFee trait

* Add documentation to polynomial types

* Ignore pseudo code snippet for doc tests

* Use `Mul` implementation of Perbill

* Add tests for WeightToFeePolynomial

* Revert "Emit a PaymentParameters event once per block"

This reverts commit 6c4763baff3d8179676a3c1660fe7063fd56a8ca.

Co-authored-by: Gavin Wood <gavin@parity.io>
This commit is contained in:
Alexander Theißen
2020-05-21 12:16:04 +02:00
committed by GitHub
parent e04f237152
commit 9dd21b1eed
14 changed files with 241 additions and 79 deletions
@@ -21,14 +21,14 @@
use sp_runtime::{
Perbill,
traits::{ConvertInto, IdentityLookup},
traits::IdentityLookup,
testing::Header,
};
use sp_core::H256;
use sp_io;
use frame_support::{impl_outer_origin, parameter_types};
use frame_support::traits::Get;
use frame_support::weights::{Weight, DispatchInfo};
use frame_support::weights::{Weight, DispatchInfo, IdentityFee};
use std::cell::RefCell;
use crate::{GenesisConfig, Module, Trait, decl_tests, tests::CallWithDispatchInfo};
@@ -87,7 +87,7 @@ impl pallet_transaction_payment::Trait for Test {
type Currency = Module<Test>;
type OnTransactionPayment = ();
type TransactionByteFee = TransactionByteFee;
type WeightToFee = ConvertInto;
type WeightToFee = IdentityFee<u64>;
type FeeMultiplierUpdate = ();
}
impl Trait for Test {
+3 -3
View File
@@ -21,14 +21,14 @@
use sp_runtime::{
Perbill,
traits::{ConvertInto, IdentityLookup},
traits::IdentityLookup,
testing::Header,
};
use sp_core::H256;
use sp_io;
use frame_support::{impl_outer_origin, parameter_types};
use frame_support::traits::{Get, StorageMapShim};
use frame_support::weights::{Weight, DispatchInfo};
use frame_support::weights::{Weight, DispatchInfo, IdentityFee};
use std::cell::RefCell;
use crate::{GenesisConfig, Module, Trait, decl_tests, tests::CallWithDispatchInfo};
@@ -87,7 +87,7 @@ impl pallet_transaction_payment::Trait for Test {
type Currency = Module<Test>;
type OnTransactionPayment = ();
type TransactionByteFee = TransactionByteFee;
type WeightToFee = ConvertInto;
type WeightToFee = IdentityFee<u64>;
type FeeMultiplierUpdate = ();
}
impl Trait for Test {
+2 -2
View File
@@ -37,7 +37,7 @@ use frame_support::{
assert_ok, assert_err, assert_err_ignore_postinfo, impl_outer_dispatch, impl_outer_event,
impl_outer_origin, parameter_types,
storage::child, StorageMap, StorageValue, traits::{Currency, Get},
weights::{DispatchInfo, DispatchClass, Weight, PostDispatchInfo, Pays},
weights::{DispatchInfo, DispatchClass, Weight, PostDispatchInfo, Pays, IdentityFee},
};
use std::{cell::RefCell, sync::atomic::{AtomicUsize, Ordering}};
use sp_core::storage::well_known_keys;
@@ -152,7 +152,7 @@ impl pallet_transaction_payment::Trait for Test {
type Currency = Balances;
type OnTransactionPayment = ();
type TransactionByteFee = TransactionByteFee;
type WeightToFee = Test;
type WeightToFee = IdentityFee<BalanceOf<Self>>;
type FeeMultiplierUpdate = ();
}
+7 -5
View File
@@ -454,12 +454,12 @@ mod tests {
use sp_core::H256;
use sp_runtime::{
generic::Era, Perbill, DispatchError, testing::{Digest, Header, Block},
traits::{Header as HeaderT, BlakeTwo256, IdentityLookup, Convert, ConvertInto},
traits::{Header as HeaderT, BlakeTwo256, IdentityLookup},
transaction_validity::{InvalidTransaction, UnknownTransaction, TransactionValidityError},
};
use frame_support::{
impl_outer_event, impl_outer_origin, parameter_types, impl_outer_dispatch,
weights::{Weight, RuntimeDbWeight},
weights::{Weight, RuntimeDbWeight, IdentityFee, WeightToFeePolynomial},
traits::{Currency, LockIdentifier, LockableCurrency, WithdrawReasons, WithdrawReason},
};
use frame_system::{self as system, Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo};
@@ -589,7 +589,7 @@ mod tests {
type Currency = Balances;
type OnTransactionPayment = ();
type TransactionByteFee = TransactionByteFee;
type WeightToFee = ConvertInto;
type WeightToFee = IdentityFee<Balance>;
type FeeMultiplierUpdate = ();
}
impl custom::Trait for Runtime {}
@@ -675,7 +675,8 @@ mod tests {
}.assimilate_storage(&mut t).unwrap();
let xt = TestXt::new(Call::Balances(BalancesCall::transfer(2, 69)), sign_extra(1, 0, 0));
let weight = xt.get_dispatch_info().weight + <Runtime as frame_system::Trait>::ExtrinsicBaseWeight::get();
let fee: Balance = <Runtime as pallet_transaction_payment::Trait>::WeightToFee::convert(weight);
let fee: Balance
= <Runtime as pallet_transaction_payment::Trait>::WeightToFee::calc(&weight);
let mut t = sp_io::TestExternalities::new(t);
t.execute_with(|| {
Executive::initialize_block(&Header::new(
@@ -871,7 +872,8 @@ mod tests {
);
let weight = xt.get_dispatch_info().weight
+ <Runtime as frame_system::Trait>::ExtrinsicBaseWeight::get();
let fee: Balance = <Runtime as pallet_transaction_payment::Trait>::WeightToFee::convert(weight);
let fee: Balance =
<Runtime as pallet_transaction_payment::Trait>::WeightToFee::calc(&weight);
Executive::initialize_block(&Header::new(
1,
H256::default(),
+1
View File
@@ -29,6 +29,7 @@ once_cell = { version = "1", default-features = false, optional = true }
sp-state-machine = { version = "0.8.0-dev", optional = true, path = "../../primitives/state-machine" }
bitmask = { version = "0.5.0", default-features = false }
impl-trait-for-tuples = "0.1.3"
smallvec = "1.4.0"
[dev-dependencies]
pretty_assertions = "0.6.1"
+147
View File
@@ -135,6 +135,9 @@ use sp_runtime::{
generic::{CheckedExtrinsic, UncheckedExtrinsic},
};
use crate::dispatch::{DispatchErrorWithPostInfo, DispatchResultWithPostInfo, DispatchError};
use sp_runtime::traits::SaturatedConversion;
use sp_arithmetic::{Perbill, traits::{BaseArithmetic, Saturating}};
use smallvec::{smallvec, SmallVec};
/// Re-export priority as type
pub use sp_runtime::transaction_validity::TransactionPriority;
@@ -530,6 +533,90 @@ impl RuntimeDbWeight {
}
}
/// One coefficient and its position in the `WeightToFeePolynomial`.
///
/// One term of polynomial is calculated as:
///
/// ```ignore
/// coeff_integer * x^(degree) + coeff_frac * x^(degree)
/// ```
///
/// The `negative` value encodes whether the term is added or substracted from the
/// overall polynomial result.
#[derive(Clone, Encode, Decode)]
pub struct WeightToFeeCoefficient<Balance> {
/// The integral part of the coefficient.
pub coeff_integer: Balance,
/// The fractional part of the coefficient.
pub coeff_frac: Perbill,
/// True iff the coefficient should be interpreted as negative.
pub negative: bool,
/// Degree/exponent of the term.
pub degree: u8,
}
/// A list of coefficients that represent one polynomial.
pub type WeightToFeeCoefficients<T> = SmallVec<[WeightToFeeCoefficient<T>; 4]>;
/// A trait that describes the weight to fee calculation as polynomial.
///
/// An implementor should only implement the `polynomial` function.
pub trait WeightToFeePolynomial {
/// The type that is returned as result from polynomial evaluation.
type Balance: BaseArithmetic + From<u32> + Copy;
/// Returns a polynomial that describes the weight to fee conversion.
///
/// This is the only function that should be manually implemented. Please note
/// that all calculation is done in the probably unsigned `Balance` type. This means
/// that the order of coefficients is important as putting the negative coefficients
/// first will most likely saturate the result to zero mid evaluation.
fn polynomial() -> WeightToFeeCoefficients<Self::Balance>;
/// Calculates the fee from the passed `weight` according to the `polynomial`.
///
/// This should not be overriden in most circumstances. Calculation is done in the
/// `Balance` type and never overflows. All evaluation is saturating.
fn calc(weight: &Weight) -> Self::Balance {
Self::polynomial().iter().fold(Self::Balance::saturated_from(0u32), |mut acc, args| {
let w = Self::Balance::saturated_from(*weight).saturating_pow(args.degree.into());
// The sum could get negative. Therefore we only sum with the accumulator.
// The Perbill Mul implementation is non overflowing.
let frac = args.coeff_frac * w;
let integer = args.coeff_integer.saturating_mul(w);
if args.negative {
acc = acc.saturating_sub(frac);
acc = acc.saturating_sub(integer);
} else {
acc = acc.saturating_add(frac);
acc = acc.saturating_add(integer);
}
acc
})
}
}
/// Implementor of `WeightToFeePolynomial` that maps one unit of weight to one unit of fee.
pub struct IdentityFee<T>(sp_std::marker::PhantomData<T>);
impl<T> WeightToFeePolynomial for IdentityFee<T> where
T: BaseArithmetic + From<u32> + Copy
{
type Balance = T;
fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
smallvec!(WeightToFeeCoefficient {
coeff_integer: 1u32.into(),
coeff_frac: Perbill::zero(),
negative: false,
degree: 1,
})
}
}
#[cfg(test)]
#[allow(dead_code)]
mod tests {
@@ -651,4 +738,64 @@ mod tests {
1000
);
}
type Balance = u64;
// 0.5x^3 + 2.333x2 + 7x - 10_000
struct Poly;
impl WeightToFeePolynomial for Poly {
type Balance = Balance;
fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
smallvec![
WeightToFeeCoefficient {
coeff_integer: 0,
coeff_frac: Perbill::from_fraction(0.5),
negative: false,
degree: 3
},
WeightToFeeCoefficient {
coeff_integer: 2,
coeff_frac: Perbill::from_rational_approximation(1u32, 3u32),
negative: false,
degree: 2
},
WeightToFeeCoefficient {
coeff_integer: 7,
coeff_frac: Perbill::zero(),
negative: false,
degree: 1
},
WeightToFeeCoefficient {
coeff_integer: 10_000,
coeff_frac: Perbill::zero(),
negative: true,
degree: 0
},
]
}
}
#[test]
fn polynomial_works() {
assert_eq!(Poly::calc(&100), 514033);
assert_eq!(Poly::calc(&10_123), 518917034928);
}
#[test]
fn polynomial_does_not_underflow() {
assert_eq!(Poly::calc(&0), 0);
}
#[test]
fn polynomial_does_not_overflow() {
assert_eq!(Poly::calc(&Weight::max_value()), Balance::max_value() - 10_000);
}
#[test]
fn identity_fee_works() {
assert_eq!(IdentityFee::<Balance>::calc(&0), 0);
assert_eq!(IdentityFee::<Balance>::calc(&50), 50);
assert_eq!(IdentityFee::<Balance>::calc(&Weight::max_value()), Balance::max_value());
}
}
@@ -18,6 +18,7 @@ sp-runtime = { version = "2.0.0-dev", default-features = false, path = "../../pr
frame-support = { version = "2.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "2.0.0-dev", default-features = false, path = "../system" }
pallet-transaction-payment-rpc-runtime-api = { version = "2.0.0-dev", default-features = false, path = "./rpc/runtime-api" }
smallvec = "1.4.0"
[dev-dependencies]
sp-io = { version = "2.0.0-dev", path = "../../primitives/io" }
+27 -9
View File
@@ -37,7 +37,10 @@ use codec::{Encode, Decode};
use frame_support::{
decl_storage, decl_module,
traits::{Currency, Get, OnUnbalanced, ExistenceRequirement, WithdrawReason, Imbalance},
weights::{Weight, DispatchInfo, PostDispatchInfo, GetDispatchInfo, Pays},
weights::{
Weight, DispatchInfo, PostDispatchInfo, GetDispatchInfo, Pays, WeightToFeePolynomial,
WeightToFeeCoefficient,
},
dispatch::DispatchResult,
};
use sp_runtime::{
@@ -72,7 +75,7 @@ pub trait Trait: frame_system::Trait {
type TransactionByteFee: Get<BalanceOf<Self>>;
/// Convert a weight value into a deductible fee based on the currency type.
type WeightToFee: Convert<Weight, BalanceOf<Self>>;
type WeightToFee: WeightToFeePolynomial<Balance=BalanceOf<Self>>;
/// Update the multiplier of the next block, based on the previous block's weight.
type FeeMultiplierUpdate: Convert<Multiplier, Multiplier>;
@@ -89,9 +92,13 @@ decl_module! {
/// The fee to be paid for making a transaction; the per-byte portion.
const TransactionByteFee: BalanceOf<T> = T::TransactionByteFee::get();
/// The polynomial that is applied in order to derive fee from weight.
const WeightToFee: Vec<WeightToFeeCoefficient<BalanceOf<T>>> =
T::WeightToFee::polynomial().to_vec();
fn on_finalize() {
NextFeeMultiplier::mutate(|fm| {
*fm = T::FeeMultiplierUpdate::convert(*fm)
*fm = T::FeeMultiplierUpdate::convert(*fm);
});
}
}
@@ -183,7 +190,7 @@ impl<T: Trait> Module<T> {
// cap the weight to the maximum defined in runtime, otherwise it will be the
// `Bounded` maximum of its data type, which is not desired.
let capped_weight = weight.min(<T as frame_system::Trait>::MaximumBlockWeight::get());
T::WeightToFee::convert(capped_weight)
T::WeightToFee::calc(&capped_weight)
}
}
@@ -318,7 +325,10 @@ mod tests {
use codec::Encode;
use frame_support::{
impl_outer_dispatch, impl_outer_origin, parameter_types,
weights::{DispatchClass, DispatchInfo, PostDispatchInfo, GetDispatchInfo, Weight},
weights::{
DispatchClass, DispatchInfo, PostDispatchInfo, GetDispatchInfo, Weight,
WeightToFeePolynomial, WeightToFeeCoefficients, WeightToFeeCoefficient,
},
};
use pallet_balances::Call as BalancesCall;
use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
@@ -329,6 +339,7 @@ mod tests {
Perbill,
};
use std::cell::RefCell;
use smallvec::smallvec;
const CALL: &<Runtime as frame_system::Trait>::Call =
&Call::Balances(BalancesCall::transfer(2, 69));
@@ -411,10 +422,17 @@ mod tests {
fn get() -> u64 { TRANSACTION_BYTE_FEE.with(|v| *v.borrow()) }
}
pub struct WeightToFee(u64);
impl Convert<Weight, u64> for WeightToFee {
fn convert(t: Weight) -> u64 {
WEIGHT_TO_FEE.with(|v| *v.borrow() * (t as u64))
pub struct WeightToFee;
impl WeightToFeePolynomial for WeightToFee {
type Balance = u64;
fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
smallvec![WeightToFeeCoefficient {
degree: 1,
coeff_frac: Perbill::zero(),
coeff_integer: WEIGHT_TO_FEE.with(|v| *v.borrow()),
negative: false,
}]
}
}