mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 02:48:03 +00:00
a71f018c7a
Part of #3326 This one is easier as all the storage items are public. @ggwpez @kianenigma @shawntabrizi --------- Signed-off-by: Matteo Muraca <mmuraca247@gmail.com> Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: command-bot <> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
491 lines
14 KiB
Rust
491 lines
14 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) Parity Technologies (UK) Ltd.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
//! Some configurable implementations as associated type for the substrate runtime.
|
|
|
|
use frame_support::{
|
|
pallet_prelude::*,
|
|
traits::{
|
|
fungibles::{Balanced, Credit},
|
|
Currency, OnUnbalanced,
|
|
},
|
|
};
|
|
use pallet_alliance::{IdentityVerifier, ProposalIndex, ProposalProvider};
|
|
use pallet_asset_tx_payment::HandleCredit;
|
|
use pallet_identity::legacy::IdentityField;
|
|
use sp_std::prelude::*;
|
|
|
|
use crate::{
|
|
AccountId, AllianceCollective, AllianceMotion, Assets, Authorship, Balances, Hash,
|
|
NegativeImbalance, Runtime, RuntimeCall,
|
|
};
|
|
|
|
pub struct Author;
|
|
impl OnUnbalanced<NegativeImbalance> for Author {
|
|
fn on_nonzero_unbalanced(amount: NegativeImbalance) {
|
|
if let Some(author) = Authorship::author() {
|
|
Balances::resolve_creating(&author, amount);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A `HandleCredit` implementation that naively transfers the fees to the block author.
|
|
/// Will drop and burn the assets in case the transfer fails.
|
|
pub struct CreditToBlockAuthor;
|
|
impl HandleCredit<AccountId, Assets> for CreditToBlockAuthor {
|
|
fn handle_credit(credit: Credit<AccountId, Assets>) {
|
|
if let Some(author) = pallet_authorship::Pallet::<Runtime>::author() {
|
|
// Drop the result which will trigger the `OnDrop` of the imbalance in case of error.
|
|
let _ = Assets::resolve(&author, credit);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct AllianceIdentityVerifier;
|
|
impl IdentityVerifier<AccountId> for AllianceIdentityVerifier {
|
|
fn has_required_identities(who: &AccountId) -> bool {
|
|
crate::Identity::has_identity(who, (IdentityField::Display | IdentityField::Web).bits())
|
|
}
|
|
|
|
fn has_good_judgement(who: &AccountId) -> bool {
|
|
use pallet_identity::Judgement;
|
|
crate::Identity::identity(who)
|
|
.map(|(registration, _)| registration.judgements)
|
|
.map_or(false, |judgements| {
|
|
judgements
|
|
.iter()
|
|
.any(|(_, j)| matches!(j, Judgement::KnownGood | Judgement::Reasonable))
|
|
})
|
|
}
|
|
|
|
fn super_account_id(who: &AccountId) -> Option<AccountId> {
|
|
crate::Identity::super_of(who).map(|parent| parent.0)
|
|
}
|
|
}
|
|
|
|
pub struct AllianceProposalProvider;
|
|
impl ProposalProvider<AccountId, Hash, RuntimeCall> for AllianceProposalProvider {
|
|
fn propose_proposal(
|
|
who: AccountId,
|
|
threshold: u32,
|
|
proposal: Box<RuntimeCall>,
|
|
length_bound: u32,
|
|
) -> Result<(u32, u32), DispatchError> {
|
|
AllianceMotion::do_propose_proposed(who, threshold, proposal, length_bound)
|
|
}
|
|
|
|
fn vote_proposal(
|
|
who: AccountId,
|
|
proposal: Hash,
|
|
index: ProposalIndex,
|
|
approve: bool,
|
|
) -> Result<bool, DispatchError> {
|
|
AllianceMotion::do_vote(who, proposal, index, approve)
|
|
}
|
|
|
|
fn close_proposal(
|
|
proposal_hash: Hash,
|
|
proposal_index: ProposalIndex,
|
|
proposal_weight_bound: Weight,
|
|
length_bound: u32,
|
|
) -> DispatchResultWithPostInfo {
|
|
AllianceMotion::do_close(proposal_hash, proposal_index, proposal_weight_bound, length_bound)
|
|
}
|
|
|
|
fn proposal_of(proposal_hash: Hash) -> Option<RuntimeCall> {
|
|
pallet_collective::ProposalOf::<Runtime, AllianceCollective>::get(proposal_hash)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod multiplier_tests {
|
|
use frame_support::{
|
|
dispatch::DispatchClass,
|
|
weights::{Weight, WeightToFee},
|
|
};
|
|
use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment};
|
|
use sp_runtime::{
|
|
assert_eq_error_rate,
|
|
traits::{Convert, One, Zero},
|
|
BuildStorage, FixedPointNumber,
|
|
};
|
|
|
|
use crate::{
|
|
constants::{currency::*, time::*},
|
|
AdjustmentVariable, MaximumMultiplier, MinimumMultiplier, Runtime,
|
|
RuntimeBlockWeights as BlockWeights, System, TargetBlockFullness, TransactionPayment,
|
|
};
|
|
|
|
fn max_normal() -> Weight {
|
|
BlockWeights::get()
|
|
.get(DispatchClass::Normal)
|
|
.max_total
|
|
.unwrap_or_else(|| BlockWeights::get().max_block)
|
|
}
|
|
|
|
fn min_multiplier() -> Multiplier {
|
|
MinimumMultiplier::get()
|
|
}
|
|
|
|
fn target() -> Weight {
|
|
TargetBlockFullness::get() * max_normal()
|
|
}
|
|
|
|
// update based on runtime impl.
|
|
fn runtime_multiplier_update(fm: Multiplier) -> Multiplier {
|
|
TargetedFeeAdjustment::<
|
|
Runtime,
|
|
TargetBlockFullness,
|
|
AdjustmentVariable,
|
|
MinimumMultiplier,
|
|
MaximumMultiplier,
|
|
>::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);
|
|
|
|
let max_normal = max_normal();
|
|
let target_weight = target();
|
|
let normalized_weight_dimensions = (
|
|
block_weight.ref_time() as f64 / max_normal.ref_time() as f64,
|
|
block_weight.proof_size() as f64 / max_normal.proof_size() as f64,
|
|
);
|
|
|
|
let (normal, max, target) =
|
|
if normalized_weight_dimensions.0 < normalized_weight_dimensions.1 {
|
|
(block_weight.proof_size(), max_normal.proof_size(), target_weight.proof_size())
|
|
} else {
|
|
(block_weight.ref_time(), max_normal.ref_time(), target_weight.ref_time())
|
|
};
|
|
|
|
// maximum tx weight
|
|
let m = max as f64;
|
|
// block weight always truncated to max weight
|
|
let block_weight = (normal as f64).min(m);
|
|
let v: f64 = AdjustmentVariable::get().to_float();
|
|
|
|
// Ideal saturation in terms of weight
|
|
let ss = target as f64;
|
|
// Current saturation in terms of weight
|
|
let s = block_weight;
|
|
|
|
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_float(next_float)
|
|
}
|
|
|
|
fn run_with_system_weight<F>(w: Weight, assertions: F)
|
|
where
|
|
F: Fn() -> (),
|
|
{
|
|
let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::<Runtime>::default()
|
|
.build_storage()
|
|
.unwrap()
|
|
.into();
|
|
t.execute_with(|| {
|
|
System::set_block_consumed_resources(w, 0);
|
|
assertions()
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn truth_value_update_poc_works() {
|
|
let fm = Multiplier::saturating_from_rational(1, 2);
|
|
let test_set = vec![
|
|
(Weight::zero(), fm),
|
|
(Weight::from_parts(100, 0), fm),
|
|
(Weight::from_parts(1000, 0), fm),
|
|
(target(), fm),
|
|
(max_normal() / 2, fm),
|
|
(max_normal(), fm),
|
|
];
|
|
test_set.into_iter().for_each(|(w, fm)| {
|
|
run_with_system_weight(w, || {
|
|
assert_eq_error_rate!(
|
|
truth_value_update(w, fm),
|
|
runtime_multiplier_update(fm),
|
|
// Error is only 1 in 100^18
|
|
Multiplier::from_inner(100),
|
|
);
|
|
})
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn multiplier_can_grow_from_zero() {
|
|
// if the min is too small, then this will not change, and we are doomed forever.
|
|
// the block ref time is 1/100th bigger than target.
|
|
run_with_system_weight(target().set_ref_time(target().ref_time() * 101 / 100), || {
|
|
let next = runtime_multiplier_update(min_multiplier());
|
|
assert!(next > min_multiplier(), "{:?} !> {:?}", next, min_multiplier());
|
|
});
|
|
|
|
// the block proof size is 1/100th bigger than target.
|
|
run_with_system_weight(target().set_proof_size((target().proof_size() / 100) * 101), || {
|
|
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(Weight::zero(), || {
|
|
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(Weight::zero(), || {
|
|
// start from 1, the default.
|
|
let mut fm = Multiplier::one();
|
|
let mut iterations: u64 = 0;
|
|
loop {
|
|
let next = runtime_multiplier_update(fm);
|
|
fm = next;
|
|
if fm == min_multiplier() {
|
|
break;
|
|
}
|
|
iterations += 1;
|
|
}
|
|
assert!(iterations > 533_333);
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn min_change_per_day() {
|
|
run_with_system_weight(max_normal(), || {
|
|
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));
|
|
})
|
|
}
|
|
|
|
#[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 = BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap() -
|
|
Weight::from_parts(100, 0);
|
|
|
|
// 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::one();
|
|
assert_eq!(fm, TransactionPayment::next_fee_multiplier());
|
|
|
|
let mut iterations: u64 = 0;
|
|
loop {
|
|
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;
|
|
iterations += 1;
|
|
let fee =
|
|
<Runtime as pallet_transaction_payment::Config>::WeightToFee::weight_to_fee(
|
|
&tx_weight,
|
|
);
|
|
let adjusted_fee = fm.saturating_mul_acc_int(fee);
|
|
println!(
|
|
"iteration {}, new fm = {:?}. Fee at this point is: {} units / {} millicents, \
|
|
{} cents, {} dollars",
|
|
iterations,
|
|
fm,
|
|
adjusted_fee,
|
|
adjusted_fee / MILLICENTS,
|
|
adjusted_fee / CENTS,
|
|
adjusted_fee / DOLLARS,
|
|
);
|
|
}
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn stateless_weight_mul() {
|
|
let fm = Multiplier::saturating_from_rational(1, 2);
|
|
run_with_system_weight(target() / 4, || {
|
|
let next = runtime_multiplier_update(fm);
|
|
assert_eq_error_rate!(
|
|
next,
|
|
truth_value_update(target() / 4, fm),
|
|
Multiplier::from_inner(100),
|
|
);
|
|
|
|
// Light block. Multiplier is reduced a little.
|
|
assert!(next < fm);
|
|
});
|
|
|
|
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(), || {
|
|
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 = runtime_multiplier_update(fm);
|
|
assert_eq_error_rate!(
|
|
next,
|
|
truth_value_update(target() * 2, fm),
|
|
Multiplier::from_inner(100),
|
|
);
|
|
|
|
// Heavy block. Fee is increased a little.
|
|
assert!(next > fm);
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn weight_mul_grow_on_big_block() {
|
|
run_with_system_weight(target() * 2, || {
|
|
let mut original = Multiplier::zero();
|
|
let mut next = Multiplier::default();
|
|
|
|
(0..1_000).for_each(|_| {
|
|
next = runtime_multiplier_update(original);
|
|
assert_eq_error_rate!(
|
|
next,
|
|
truth_value_update(target() * 2, original),
|
|
Multiplier::from_inner(100),
|
|
);
|
|
// must always increase
|
|
assert!(next > original, "{:?} !>= {:?}", next, original);
|
|
original = next;
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
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;
|
|
|
|
for _ in 0..100 {
|
|
// decreases
|
|
next = runtime_multiplier_update(original);
|
|
assert!(next < original, "{:?} !<= {:?}", next, original);
|
|
original = next;
|
|
}
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn weight_to_fee_should_not_overflow_on_large_weights() {
|
|
let kb_time = Weight::from_parts(1024, 0);
|
|
let kb_size = Weight::from_parts(0, 1024);
|
|
let mb_time = 1024u64 * kb_time;
|
|
let max_fm = Multiplier::saturating_from_integer(i128::MAX);
|
|
|
|
// check that for all values it can compute, correctly.
|
|
vec![
|
|
Weight::zero(),
|
|
// testcases ignoring proof size part of the weight.
|
|
Weight::from_parts(1, 0),
|
|
Weight::from_parts(10, 0),
|
|
Weight::from_parts(1000, 0),
|
|
kb_time,
|
|
10u64 * kb_time,
|
|
100u64 * kb_time,
|
|
mb_time,
|
|
10u64 * mb_time,
|
|
Weight::from_parts(2147483647, 0),
|
|
Weight::from_parts(4294967295, 0),
|
|
// testcases ignoring ref time part of the weight.
|
|
Weight::from_parts(0, 100000000000),
|
|
1000000u64 * kb_size,
|
|
1000000000u64 * kb_size,
|
|
Weight::from_parts(0, 18014398509481983),
|
|
Weight::from_parts(0, 9223372036854775807),
|
|
// test cases with both parts of the weight.
|
|
BlockWeights::get().max_block / 1024,
|
|
BlockWeights::get().max_block / 2,
|
|
BlockWeights::get().max_block,
|
|
Weight::MAX / 2,
|
|
Weight::MAX,
|
|
]
|
|
.into_iter()
|
|
.for_each(|i| {
|
|
run_with_system_weight(i, || {
|
|
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));
|
|
});
|
|
});
|
|
|
|
// Some values that are all above the target and will cause an increase.
|
|
let t = target();
|
|
vec![
|
|
t + Weight::from_parts(100, 0),
|
|
t + Weight::from_parts(0, t.proof_size() * 2),
|
|
t * 2,
|
|
t * 4,
|
|
]
|
|
.into_iter()
|
|
.for_each(|i| {
|
|
run_with_system_weight(i, || {
|
|
let fm = runtime_multiplier_update(max_fm);
|
|
// won't grow. The convert saturates everything.
|
|
assert_eq!(fm, max_fm);
|
|
})
|
|
});
|
|
}
|
|
}
|