Fix the broken weight multiplier update function (#6334)

* Initial draft, has some todos left

* remove ununsed import

* Apply suggestions from code review

* Some refactors with migration

* Fix more test and cleanup

* Fix for companion

* Apply suggestions from code review

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Update bin/node/runtime/src/impls.rs

* Fix weight

* Add integrity test

* length is not affected.

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
This commit is contained in:
Kian Paimani
2020-06-17 15:20:17 +02:00
committed by GitHub
parent 67c9ac9393
commit 82bdf1a891
10 changed files with 458 additions and 253 deletions
+148 -150
View File
@@ -18,11 +18,9 @@
//! Some configurable implementations as associated type for the substrate runtime.
use node_primitives::Balance;
use sp_runtime::traits::{Convert, Saturating};
use sp_runtime::{FixedPointNumber, Perquintill};
use frame_support::traits::{OnUnbalanced, Currency, Get};
use pallet_transaction_payment::Multiplier;
use crate::{Balances, System, Authorship, MaximumBlockWeight, NegativeImbalance};
use sp_runtime::traits::Convert;
use frame_support::traits::{OnUnbalanced, Currency};
use crate::{Balances, Authorship, NegativeImbalance};
pub struct Author;
impl OnUnbalanced<NegativeImbalance> for Author {
@@ -47,89 +45,63 @@ impl Convert<u128, Balance> for CurrencyToVoteHandler {
fn convert(x: u128) -> Balance { x * Self::factor() }
}
/// Update the given multiplier based on the following formula
///
/// diff = (previous_block_weight - target_weight)/max_weight
/// v = 0.00004
/// next_weight = weight * (1 + (v * diff) + (v * diff)^2 / 2)
///
/// Where `target_weight` must be given as the `Get` implementation of the `T` generic type.
/// https://research.web3.foundation/en/latest/polkadot/Token%20Economics/#relay-chain-transaction-fees
pub struct TargetedFeeAdjustment<T>(sp_std::marker::PhantomData<T>);
impl<T: Get<Perquintill>> Convert<Multiplier, Multiplier> for TargetedFeeAdjustment<T> {
fn convert(multiplier: Multiplier) -> Multiplier {
let max_weight = MaximumBlockWeight::get();
let block_weight = System::block_weight().total().min(max_weight);
let target_weight = (T::get() * max_weight) as u128;
let block_weight = block_weight as u128;
// determines if the first_term is positive
let positive = block_weight >= target_weight;
let diff_abs = block_weight.max(target_weight) - block_weight.min(target_weight);
// safe, diff_abs cannot exceed u64.
let diff = Multiplier::saturating_from_rational(diff_abs, max_weight.max(1));
let diff_squared = diff.saturating_mul(diff);
// 0.00004 = 4/100_000 = 40_000/10^9
let v = Multiplier::saturating_from_rational(4, 100_000);
// 0.00004^2 = 16/10^10 Taking the future /2 into account... 8/10^10
let v_squared_2 = Multiplier::saturating_from_rational(8, 10_000_000_000u64);
let first_term = v.saturating_mul(diff);
let second_term = v_squared_2.saturating_mul(diff_squared);
if positive {
// 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(excess)
} else {
// Defensive-only: first_term > second_term. Safe subtraction.
let negative = first_term.saturating_sub(second_term);
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(Multiplier::saturating_from_integer(-1))
}
}
}
#[cfg(test)]
mod tests {
mod multiplier_tests {
use super::*;
use sp_runtime::assert_eq_error_rate;
use crate::{MaximumBlockWeight, AvailableBlockRatio, Runtime};
use crate::{constants::currency::*, TransactionPayment, TargetBlockFullness};
use sp_runtime::{assert_eq_error_rate, FixedPointNumber};
use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment};
use crate::{
constants::{currency::*, time::*},
TransactionPayment, MaximumBlockWeight, AvailableBlockRatio, Runtime, TargetBlockFullness,
AdjustmentVariable, System, MinimumMultiplier,
};
use frame_support::weights::{Weight, WeightToFeePolynomial};
fn max() -> Weight {
MaximumBlockWeight::get()
AvailableBlockRatio::get() * MaximumBlockWeight::get()
}
fn min_multiplier() -> Multiplier {
MinimumMultiplier::get()
}
fn target() -> Weight {
TargetBlockFullness::get() * max()
}
// poc reference implementation.
fn fee_multiplier_update(block_weight: Weight, previous: Multiplier) -> Multiplier {
// update based on runtime impl.
fn runtime_multiplier_update(fm: Multiplier) -> Multiplier {
TargetedFeeAdjustment::<
Runtime,
TargetBlockFullness,
AdjustmentVariable,
MinimumMultiplier,
>::convert(fm)
}
// update based on reference impl.
fn truth_value_update(block_weight: Weight, previous: Multiplier) -> Multiplier {
let accuracy = Multiplier::accuracy() as f64;
let previous_float = previous.into_inner() as f64 / accuracy;
// bump if it is zero.
let previous_float = previous_float.max(min_multiplier().into_inner() as f64 / accuracy);
// maximum tx weight
let m = max() as f64;
// block weight always truncated to max weight
let block_weight = (block_weight as f64).min(m);
let v: f64 = 0.00004;
let v: f64 = AdjustmentVariable::get().to_fraction();
// Ideal saturation in terms of weight
let ss = target() as f64;
// Current saturation in terms of weight
let s = block_weight;
let fm = v * (s/m - ss/m) + v.powi(2) * (s/m - ss/m).powi(2) / 2.0;
let addition_fm = Multiplier::from_inner((fm * Multiplier::accuracy() as f64).round() as i128);
previous.saturating_add(addition_fm)
let t1 = v * (s/m - ss/m);
let t2 = v.powi(2) * (s/m - ss/m).powi(2) / 2.0;
let next_float = previous_float * (1.0 + t1 + t2);
Multiplier::from_fraction(next_float)
}
fn run_with_system_weight<F>(w: Weight, assertions: F) where F: Fn() -> () {
@@ -142,11 +114,12 @@ mod tests {
}
#[test]
fn fee_multiplier_update_poc_works() {
let fm = Multiplier::saturating_from_rational(0, 1);
fn truth_value_update_poc_works() {
let fm = Multiplier::saturating_from_rational(1, 2);
let test_set = vec![
(0, fm.clone()),
(100, fm.clone()),
(1000, fm.clone()),
(target(), fm.clone()),
(max() / 2, fm.clone()),
(max(), fm.clone()),
@@ -154,37 +127,71 @@ mod tests {
test_set.into_iter().for_each(|(w, fm)| {
run_with_system_weight(w, || {
assert_eq_error_rate!(
fee_multiplier_update(w, fm),
TargetedFeeAdjustment::<TargetBlockFullness>::convert(fm),
// Error is only 1 in 10^18
Multiplier::from_inner(1),
truth_value_update(w, fm),
runtime_multiplier_update(fm),
// Error is only 1 in 100^18
Multiplier::from_inner(100),
);
})
})
}
#[test]
fn empty_chain_simulation() {
// just a few txs per_block.
let block_weight = 0;
run_with_system_weight(block_weight, || {
let mut fm = Multiplier::default();
fn multiplier_can_grow_from_zero() {
// if the min is too small, then this will not change, and we are doomed forever.
// the weight is 1/100th bigger than target.
run_with_system_weight(target() * 101 / 100, || {
let next = runtime_multiplier_update(min_multiplier());
assert!(next > min_multiplier(), "{:?} !>= {:?}", next, min_multiplier());
})
}
#[test]
fn multiplier_cannot_go_below_limit() {
// will not go any further below even if block is empty.
run_with_system_weight(0, || {
let next = runtime_multiplier_update(min_multiplier());
assert_eq!(next, min_multiplier());
})
}
#[test]
fn time_to_reach_zero() {
// blocks per 24h in substrate-node: 28,800 (k)
// s* = 0.1875
// The bound from the research in an empty chain is:
// v <~ (p / k(0 - s*))
// p > v * k * -0.1875
// to get p == -1 we'd need
// -1 > 0.00001 * k * -0.1875
// 1 < 0.00001 * k * 0.1875
// 10^9 / 1875 < k
// k > 533_333 ~ 18,5 days.
run_with_system_weight(0, || {
// start from 1, the default.
let mut fm = Multiplier::one();
let mut iterations: u64 = 0;
loop {
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(fm);
let next = runtime_multiplier_update(fm);
fm = next;
if fm == Multiplier::saturating_from_integer(-1) { break; }
if fm == min_multiplier() { break; }
iterations += 1;
}
println!("iteration {}, new fm = {:?}. Weight fee is now zero", iterations, fm);
assert!(iterations > 50_000, "This assertion is just a warning; Don't panic. \
Current substrate/polkadot node are configured with a _slow adjusting fee_ \
mechanism. Hence, it is really unlikely that fees collapse to zero even on an \
empty chain in less than at least of couple of thousands of empty blocks. But this \
simulation indicates that fees collapsed to zero after {} almost-empty blocks. \
Check it",
iterations,
);
assert!(iterations > 533_333);
})
}
#[test]
fn min_change_per_day() {
run_with_system_weight(max(), || {
let mut fm = Multiplier::one();
// See the example in the doc of `TargetedFeeAdjustment`. are at least 0.234, hence
// `fm > 1.234`.
for _ in 0..DAYS {
let next = runtime_multiplier_update(fm);
fm = next;
}
assert!(fm > Multiplier::saturating_from_rational(1234, 1000));
})
}
@@ -196,17 +203,17 @@ mod tests {
// almost full. The entire quota of normal transactions is taken.
let block_weight = AvailableBlockRatio::get() * max() - 100;
// Default substrate minimum.
let tx_weight = 10_000;
// Default substrate weight.
let tx_weight = frame_support::weights::constants::ExtrinsicBaseWeight::get();
run_with_system_weight(block_weight, || {
// initial value configured on module
let mut fm = Multiplier::default();
let mut fm = Multiplier::one();
assert_eq!(fm, TransactionPayment::next_fee_multiplier());
let mut iterations: u64 = 0;
loop {
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(fm);
let next = runtime_multiplier_update(fm);
// if no change, panic. This should never happen in this case.
if fm == next { panic!("The fee should ever increase"); }
fm = next;
@@ -230,95 +237,86 @@ mod tests {
#[test]
fn stateless_weight_mul() {
// This test will show that heavy blocks have a weight multiplier greater than 0
// and light blocks will have a weight multiplier less than 0.
let fm = Multiplier::saturating_from_rational(1, 2);
run_with_system_weight(target() / 4, || {
// `fee_multiplier_update` is enough as it is the absolute truth value.
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
assert_eq!(
let next = runtime_multiplier_update(fm);
assert_eq_error_rate!(
next,
fee_multiplier_update(target() / 4 ,Multiplier::default())
truth_value_update(target() / 4 , fm),
Multiplier::from_inner(100),
);
// Light block. Fee is reduced a little.
assert!(next < Multiplier::zero())
// Light block. Multiplier is reduced a little.
assert!(next < fm);
});
run_with_system_weight(target() / 2, || {
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
assert_eq!(
next,
fee_multiplier_update(target() / 2 ,Multiplier::default())
);
// Light block. Fee is reduced a little.
assert!(next < Multiplier::zero())
run_with_system_weight(target() / 2, || {
let next = runtime_multiplier_update(fm);
assert_eq_error_rate!(
next,
truth_value_update(target() / 2 , fm),
Multiplier::from_inner(100),
);
// Light block. Multiplier is reduced a little.
assert!(next < fm);
});
run_with_system_weight(target(), || {
// ideal. Original fee. No changes.
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
assert_eq!(next, Multiplier::zero())
let next = runtime_multiplier_update(fm);
assert_eq_error_rate!(
next,
truth_value_update(target(), fm),
Multiplier::from_inner(100),
);
// ideal. No changes.
assert_eq!(next, fm)
});
run_with_system_weight(target() * 2, || {
// More than ideal. Fee is increased.
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
assert_eq!(
let next = runtime_multiplier_update(fm);
assert_eq_error_rate!(
next,
fee_multiplier_update(target() * 2 ,Multiplier::default())
truth_value_update(target() * 2 , fm),
Multiplier::from_inner(100),
);
// Heavy block. Fee is increased a little.
assert!(next > Multiplier::zero())
assert!(next > fm);
});
}
#[test]
fn stateful_weight_mul_grow_to_infinity() {
fn weight_mul_grow_on_big_block() {
run_with_system_weight(target() * 2, || {
let mut original = Multiplier::default();
let mut original = Multiplier::zero();
let mut next = Multiplier::default();
(0..1_000).for_each(|_| {
next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(original);
assert_eq!(
next = runtime_multiplier_update(original);
assert_eq_error_rate!(
next,
fee_multiplier_update(target() * 2, original),
truth_value_update(target() * 2, original),
Multiplier::from_inner(100),
);
// must always increase
assert!(next > original);
assert!(next > original, "{:?} !>= {:?}", next, original);
original = next;
});
});
}
#[test]
fn stateful_weight_mil_collapse_to_minus_one() {
run_with_system_weight(0, || {
let mut original = Multiplier::default(); // 0
fn weight_mul_decrease_on_small_block() {
run_with_system_weight(target() / 2, || {
let mut original = Multiplier::saturating_from_rational(1, 2);
let mut next;
// decreases
next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(original);
assert_eq!(
next,
fee_multiplier_update(0, original),
);
assert!(next < original);
original = next;
// keeps decreasing
next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(original);
assert_eq!(
next,
fee_multiplier_update(0, original),
);
assert!(next < original);
// ... stops going down at -1
assert_eq!(
TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::saturating_from_integer(-1)),
Multiplier::saturating_from_integer(-1)
);
for _ in 0..100 {
// decreases
next = runtime_multiplier_update(original);
assert!(next < original, "{:?} !<= {:?}", next, original);
original = next;
}
})
}
@@ -347,8 +345,8 @@ mod tests {
Weight::max_value(),
].into_iter().for_each(|i| {
run_with_system_weight(i, || {
let next = TargetedFeeAdjustment::<TargetBlockFullness>::convert(Multiplier::default());
let truth = fee_multiplier_update(i, Multiplier::default());
let next = runtime_multiplier_update(Multiplier::one());
let truth = truth_value_update(i, Multiplier::one());
assert_eq_error_rate!(truth, next, Multiplier::from_inner(50_000_000));
});
});
@@ -359,7 +357,7 @@ mod tests {
.into_iter()
.for_each(|i| {
run_with_system_weight(i, || {
let fm = TargetedFeeAdjustment::<TargetBlockFullness>::convert(max_fm);
let fm = runtime_multiplier_update(max_fm);
// won't grow. The convert saturates everything.
assert_eq!(fm, max_fm);
})
+8 -13
View File
@@ -44,8 +44,8 @@ pub use node_primitives::{AccountId, Signature};
use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment};
use sp_api::impl_runtime_apis;
use sp_runtime::{
Permill, Perbill, Perquintill, Percent, PerThing, ApplyExtrinsicResult,
impl_opaque_keys, generic, create_runtime_str, ModuleId,
Permill, Perbill, Perquintill, Percent, ApplyExtrinsicResult,
impl_opaque_keys, generic, create_runtime_str, ModuleId, FixedPointNumber,
};
use sp_runtime::curve::PiecewiseLinear;
use sp_runtime::transaction_validity::{TransactionValidity, TransactionSource, TransactionPriority};
@@ -61,6 +61,7 @@ use pallet_grandpa::fg_primitives;
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo;
pub use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment};
use pallet_contracts_rpc_runtime_api::ContractExecResult;
use pallet_session::{historical as pallet_session_historical};
use sp_inherents::{InherentData, CheckInherentsResult};
@@ -77,7 +78,7 @@ pub use pallet_staking::StakerStatus;
/// Implementations of some helper traits passed into runtime modules as associated types.
pub mod impls;
use impls::{CurrencyToVoteHandler, Author, TargetedFeeAdjustment};
use impls::{CurrencyToVoteHandler, Author};
/// Constant values used within the runtime.
pub mod constants;
@@ -295,23 +296,17 @@ impl pallet_balances::Trait for Runtime {
parameter_types! {
pub const TransactionByteFee: Balance = 10 * MILLICENTS;
pub const TargetBlockFullness: Perquintill = Perquintill::from_percent(25);
pub AdjustmentVariable: Multiplier = Multiplier::saturating_from_rational(1, 100_000);
pub MinimumMultiplier: Multiplier = Multiplier::saturating_from_rational(1, 1_000_000_000u128);
}
// for a sane configuration, this should always be less than `AvailableBlockRatio`.
const_assert!(
TargetBlockFullness::get().deconstruct() <
(AvailableBlockRatio::get().deconstruct() as <Perquintill as PerThing>::Inner)
* (<Perquintill as PerThing>::ACCURACY / <Perbill as PerThing>::ACCURACY as <Perquintill as PerThing>::Inner)
);
impl pallet_transaction_payment::Trait for Runtime {
type Currency = Balances;
type OnTransactionPayment = DealWithFees;
type TransactionByteFee = TransactionByteFee;
// In the Substrate node, a weight of 10_000_000 (smallest non-zero weight)
// is mapped to 10_000_000 units of fees, hence:
type WeightToFee = IdentityFee<Balance>;
type FeeMultiplierUpdate = TargetedFeeAdjustment<TargetBlockFullness>;
type FeeMultiplierUpdate =
TargetedFeeAdjustment<Self, TargetBlockFullness, AdjustmentVariable, MinimumMultiplier>;
}
parameter_types! {