refactor: Transaction-Payment module (#3816)

* Initial draft that compiles

* Extract payment stuff from balances

* Extract multiplier update stuff from system

* Some fixes.

* Update len-fee as well

* some review comments.

* Remove todo

* bump
This commit is contained in:
Kian Paimani
2019-10-17 14:21:32 +02:00
committed by GitHub
parent 1711483fb6
commit 183c188111
59 changed files with 784 additions and 596 deletions
+4 -4
View File
@@ -67,10 +67,10 @@ pub mod time {
pub const DAYS: BlockNumber = HOURS * 24;
}
// CRITICAL NOTE: The system module maintains two constants: a _maximum_ block weight and a
// _ratio_ of it yielding the portion which is accessible to normal transactions (reserving the rest
// for operational ones). `TARGET_BLOCK_FULLNESS` is entirely independent and the system module is
// not aware of if, nor should it care about it. This constant simply denotes on which ratio of the
// CRITICAL NOTE: The system module maintains two constants: a _maximum_ block weight and a _ratio_
// of it yielding the portion which is accessible to normal transactions (reserving the rest for
// operational ones). `TARGET_BLOCK_FULLNESS` is entirely independent and the system module is not
// aware of if, nor should it care about it. This constant simply denotes on which ratio of the
// _maximum_ block weight we tweak the fees. It does NOT care about the type of the dispatch.
//
// For the system to be configured in a sane way, `TARGET_BLOCK_FULLNESS` should always be less than
+103 -64
View File
@@ -17,7 +17,7 @@
//! Some configurable implementations as associated type for the substrate runtime.
use node_primitives::Balance;
use sr_primitives::weights::{Weight, WeightMultiplier};
use sr_primitives::weights::Weight;
use sr_primitives::traits::{Convert, Saturating};
use sr_primitives::Fixed64;
use support::traits::{OnUnbalanced, Currency};
@@ -82,10 +82,10 @@ impl Convert<Weight, Balance> for WeightToFee {
/// next_weight = weight * (1 + (v . diff) + (v . diff)^2 / 2)
///
/// https://research.web3.foundation/en/latest/polkadot/Token%20Economics/#relay-chain-transaction-fees
pub struct WeightMultiplierUpdateHandler;
pub struct FeeMultiplierUpdateHandler;
impl Convert<(Weight, WeightMultiplier), WeightMultiplier> for WeightMultiplierUpdateHandler {
fn convert(previous_state: (Weight, WeightMultiplier)) -> WeightMultiplier {
impl Convert<(Weight, Fixed64), Fixed64> for FeeMultiplierUpdateHandler {
fn convert(previous_state: (Weight, Fixed64)) -> Fixed64 {
let (block_weight, multiplier) = previous_state;
let max_weight = MaximumBlockWeight::get();
let target_weight = (TARGET_BLOCK_FULLNESS * max_weight) as u128;
@@ -113,17 +113,17 @@ impl Convert<(Weight, WeightMultiplier), WeightMultiplier> for WeightMultiplierU
// Note: this is merely bounded by how big the multiplier and the inner value can go,
// not by any economical reasoning.
let excess = first_term.saturating_add(second_term);
multiplier.saturating_add(WeightMultiplier::from_fixed(excess))
multiplier.saturating_add(excess)
} else {
// first_term > second_term
// Proof: first_term > second_term. Safe subtraction.
let negative = first_term - second_term;
multiplier.saturating_sub(WeightMultiplier::from_fixed(negative))
multiplier.saturating_sub(negative)
// despite the fact that apply_to saturates weight (final fee cannot go below 0)
// it is crucially important to stop here and don't further reduce the weight fee
// multiplier. While at -1, it means that the network is so un-congested that all
// transactions have no weight fee. We stop here and only increase if the network
// became more busy.
.max(WeightMultiplier::from_rational(-1, 1))
.max(Fixed64::from_rational(-1, 1))
}
}
}
@@ -132,7 +132,6 @@ impl Convert<(Weight, WeightMultiplier), WeightMultiplier> for WeightMultiplierU
mod tests {
use super::*;
use sr_primitives::weights::Weight;
use sr_primitives::Perbill;
use crate::{MaximumBlockWeight, AvailableBlockRatio, Runtime};
use crate::constants::currency::*;
@@ -145,8 +144,7 @@ mod tests {
}
// poc reference implementation.
#[allow(dead_code)]
fn weight_multiplier_update(block_weight: Weight) -> Perbill {
fn fee_multiplier_update(block_weight: Weight, previous: Fixed64) -> Fixed64 {
let block_weight = block_weight as f32;
let v: f32 = 0.00004;
@@ -157,53 +155,84 @@ mod tests {
// Current saturation in terms of weight
let s = block_weight;
let fm = 1.0 + (v * (s/m - ss/m)) + (v.powi(2) * (s/m - ss/m).powi(2)) / 2.0;
// return a per-bill-like value.
let fm = if fm >= 1.0 { fm - 1.0 } else { 1.0 - fm };
Perbill::from_parts((fm * 1_000_000_000_f32) as u32)
let fm = (v * (s/m - ss/m)) + (v.powi(2) * (s/m - ss/m).powi(2)) / 2.0;
let addition_fm = Fixed64::from_parts((fm * 1_000_000_000_f32) as i64);
previous.saturating_add(addition_fm)
}
fn wm(parts: i64) -> WeightMultiplier {
WeightMultiplier::from_parts(parts)
fn fm(parts: i64) -> Fixed64 {
Fixed64::from_parts(parts)
}
#[test]
fn fee_multiplier_update_poc_works() {
let fm = Fixed64::from_rational(0, 1);
let test_set = vec![
// TODO: this has a rounding error and fails.
// (0, fm.clone()),
(100, fm.clone()),
(target(), fm.clone()),
(max() / 2, fm.clone()),
(max(), fm.clone()),
];
test_set.into_iter().for_each(|(w, fm)| {
assert_eq!(
fee_multiplier_update(w, fm),
FeeMultiplierUpdateHandler::convert((w, fm)),
"failed for weight {} and prev fm {:?}",
w,
fm,
);
})
}
#[test]
fn empty_chain_simulation() {
// just a few txs per_block.
let block_weight = 1000;
let mut wm = WeightMultiplier::default();
let mut fm = Fixed64::default();
let mut iterations: u64 = 0;
loop {
let next = WeightMultiplierUpdateHandler::convert((block_weight, wm));
wm = next;
if wm == WeightMultiplier::from_rational(-1, 1) { break; }
let next = FeeMultiplierUpdateHandler::convert((block_weight, fm));
fm = next;
if fm == Fixed64::from_rational(-1, 1) { break; }
iterations += 1;
}
println!("iteration {}, new wm = {:?}. Weight fee is now zero", iterations, wm);
println!("iteration {}, new fm = {:?}. Weight fee is now zero", iterations, fm);
}
#[test]
#[ignore]
fn congested_chain_simulation() {
// `cargo test congested_chain_simulation -- --nocapture` to get some insight.
// almost full. The entire quota of normal transactions is taken.
let block_weight = AvailableBlockRatio::get() * max();
let tx_weight = 1000;
let mut wm = WeightMultiplier::default();
// default minimum substrate weight
let tx_weight = 10_000u32;
// initial value of system
let mut fm = Fixed64::default();
assert_eq!(fm, Fixed64::from_parts(0));
let mut iterations: u64 = 0;
loop {
let next = WeightMultiplierUpdateHandler::convert((block_weight, wm));
if wm == next { break; }
wm = next;
let next = FeeMultiplierUpdateHandler::convert((block_weight, fm));
if fm == next { break; }
fm = next;
iterations += 1;
let fee = <Runtime as balances::Trait>::WeightToFee::convert(wm.apply_to(tx_weight));
let fee = <Runtime as transaction_payment::Trait>::WeightToFee::convert(tx_weight);
let adjusted_fee = fm.saturated_multiply_accumulate(fee);
println!(
"iteration {}, new wm = {:?}. Fee at this point is: {} millicents, {} cents, {} dollars",
"iteration {}, new fm = {:?}. Fee at this point is: \
{} units, {} millicents, {} cents, {} dollars",
iterations,
wm,
fee / MILLICENTS,
fee / CENTS,
fee / DOLLARS
fm,
adjusted_fee,
adjusted_fee / MILLICENTS,
adjusted_fee / CENTS,
adjusted_fee / DOLLARS
);
}
}
@@ -212,65 +241,65 @@ mod tests {
fn stateless_weight_mul() {
// Light block. Fee is reduced a little.
assert_eq!(
WeightMultiplierUpdateHandler::convert((target() / 4, WeightMultiplier::default())),
wm(-7500)
FeeMultiplierUpdateHandler::convert((target() / 4, Fixed64::default())),
fm(-7500)
);
// a bit more. Fee is decreased less, meaning that the fee increases as the block grows.
assert_eq!(
WeightMultiplierUpdateHandler::convert((target() / 2, WeightMultiplier::default())),
wm(-5000)
FeeMultiplierUpdateHandler::convert((target() / 2, Fixed64::default())),
fm(-5000)
);
// ideal. Original fee. No changes.
assert_eq!(
WeightMultiplierUpdateHandler::convert((target(), WeightMultiplier::default())),
wm(0)
FeeMultiplierUpdateHandler::convert((target(), Fixed64::default())),
fm(0)
);
// // More than ideal. Fee is increased.
assert_eq!(
WeightMultiplierUpdateHandler::convert(((target() * 2), WeightMultiplier::default())),
wm(10000)
FeeMultiplierUpdateHandler::convert(((target() * 2), Fixed64::default())),
fm(10000)
);
}
#[test]
fn stateful_weight_mul_grow_to_infinity() {
assert_eq!(
WeightMultiplierUpdateHandler::convert((target() * 2, WeightMultiplier::default())),
wm(10000)
FeeMultiplierUpdateHandler::convert((target() * 2, Fixed64::default())),
fm(10000)
);
assert_eq!(
WeightMultiplierUpdateHandler::convert((target() * 2, wm(10000))),
wm(20000)
FeeMultiplierUpdateHandler::convert((target() * 2, fm(10000))),
fm(20000)
);
assert_eq!(
WeightMultiplierUpdateHandler::convert((target() * 2, wm(20000))),
wm(30000)
FeeMultiplierUpdateHandler::convert((target() * 2, fm(20000))),
fm(30000)
);
// ...
assert_eq!(
WeightMultiplierUpdateHandler::convert((target() * 2, wm(1_000_000_000))),
wm(1_000_000_000 + 10000)
FeeMultiplierUpdateHandler::convert((target() * 2, fm(1_000_000_000))),
fm(1_000_000_000 + 10000)
);
}
#[test]
fn stateful_weight_mil_collapse_to_minus_one() {
assert_eq!(
WeightMultiplierUpdateHandler::convert((0, WeightMultiplier::default())),
wm(-10000)
FeeMultiplierUpdateHandler::convert((0, Fixed64::default())),
fm(-10000)
);
assert_eq!(
WeightMultiplierUpdateHandler::convert((0, wm(-10000))),
wm(-20000)
FeeMultiplierUpdateHandler::convert((0, fm(-10000))),
fm(-20000)
);
assert_eq!(
WeightMultiplierUpdateHandler::convert((0, wm(-20000))),
wm(-30000)
FeeMultiplierUpdateHandler::convert((0, fm(-20000))),
fm(-30000)
);
// ...
assert_eq!(
WeightMultiplierUpdateHandler::convert((0, wm(1_000_000_000 * -1))),
wm(-1_000_000_000)
FeeMultiplierUpdateHandler::convert((0, fm(1_000_000_000 * -1))),
fm(-1_000_000_000)
);
}
@@ -278,20 +307,30 @@ mod tests {
fn weight_to_fee_should_not_overflow_on_large_weights() {
let kb = 1024 as Weight;
let mb = kb * kb;
let max_fm = WeightMultiplier::from_fixed(Fixed64::from_natural(i64::max_value()));
let max_fm = Fixed64::from_natural(i64::max_value());
vec![0, 1, 10, 1000, kb, 10 * kb, 100 * kb, mb, 10 * mb, Weight::max_value() / 2, Weight::max_value()]
.into_iter()
.for_each(|i| {
WeightMultiplierUpdateHandler::convert((i, WeightMultiplier::default()));
});
vec![
0,
1,
10,
1000,
kb,
10 * kb,
100 * kb,
mb,
10 * mb,
Weight::max_value() / 2,
Weight::max_value()
].into_iter().for_each(|i| {
FeeMultiplierUpdateHandler::convert((i, Fixed64::default()));
});
// Some values that are all above the target and will cause an increase.
let t = target();
vec![t + 100, t * 2, t * 4]
.into_iter()
.for_each(|i| {
let fm = WeightMultiplierUpdateHandler::convert((
let fm = FeeMultiplierUpdateHandler::convert((
i,
max_fm
));
+17 -9
View File
@@ -65,7 +65,7 @@ pub use staking::StakerStatus;
/// Implementations of some helper traits passed into runtime modules as associated types.
pub mod impls;
use impls::{CurrencyToVoteHandler, WeightMultiplierUpdateHandler, Author, WeightToFee};
use impls::{CurrencyToVoteHandler, FeeMultiplierUpdateHandler, Author, WeightToFee};
/// Constant values used within the runtime.
pub mod constants;
@@ -84,8 +84,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
// and set impl_version to equal spec_version. If only runtime
// implementation changes and behavior does not, then leave spec_version as
// is and increment impl_version.
spec_version: 177,
impl_version: 177,
spec_version: 178,
impl_version: 178,
apis: RUNTIME_API_VERSIONS,
};
@@ -125,7 +125,6 @@ impl system::Trait for Runtime {
type AccountId = AccountId;
type Lookup = Indices;
type Header = generic::Header<BlockNumber, BlakeTwo256>;
type WeightMultiplierUpdate = WeightMultiplierUpdateHandler;
type Event = Event;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
@@ -161,8 +160,6 @@ parameter_types! {
pub const ExistentialDeposit: Balance = 1 * DOLLARS;
pub const TransferFee: Balance = 1 * CENTS;
pub const CreationFee: Balance = 1 * CENTS;
pub const TransactionBaseFee: Balance = 1 * CENTS;
pub const TransactionByteFee: Balance = 10 * MILLICENTS;
}
impl balances::Trait for Runtime {
@@ -170,15 +167,25 @@ impl balances::Trait for Runtime {
type OnFreeBalanceZero = ((Staking, Contracts), Session);
type OnNewAccount = Indices;
type Event = Event;
type TransactionPayment = DealWithFees;
type DustRemoval = ();
type TransferPayment = ();
type ExistentialDeposit = ExistentialDeposit;
type TransferFee = TransferFee;
type CreationFee = CreationFee;
}
parameter_types! {
pub const TransactionBaseFee: Balance = 1 * CENTS;
pub const TransactionByteFee: Balance = 10 * MILLICENTS;
}
impl transaction_payment::Trait for Runtime {
type Currency = Balances;
type OnTransactionPayment = DealWithFees;
type TransactionBaseFee = TransactionBaseFee;
type TransactionByteFee = TransactionByteFee;
type WeightToFee = WeightToFee;
type FeeMultiplierUpdate = FeeMultiplierUpdateHandler;
}
parameter_types! {
@@ -480,7 +487,7 @@ impl system::offchain::CreateTransaction<Runtime, UncheckedExtrinsic> for Runtim
system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)),
system::CheckNonce::<Runtime>::from(index),
system::CheckWeight::<Runtime>::new(),
balances::TakeFees::<Runtime>::from(tip),
transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
Default::default(),
);
let raw_payload = SignedPayload::new(call, extra).ok()?;
@@ -504,6 +511,7 @@ construct_runtime!(
Authorship: authorship::{Module, Call, Storage, Inherent},
Indices: indices,
Balances: balances::{default, Error},
TransactionPayment: transaction_payment::{Module, Storage},
Staking: staking::{default, OfflineWorker},
Session: session::{Module, Call, Storage, Event, Config<T>},
Democracy: democracy::{Module, Call, Storage, Config, Event<T>},
@@ -540,7 +548,7 @@ pub type SignedExtra = (
system::CheckEra<Runtime>,
system::CheckNonce<Runtime>,
system::CheckWeight<Runtime>,
balances::TakeFees<Runtime>,
transaction_payment::ChargeTransactionPayment<Runtime>,
contracts::CheckBlockGasLimit<Runtime>,
);
/// Unchecked extrinsic type as expected by this runtime.