feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,92 @@
[package]
name = "pezpallet-election-provider-multi-phase"
version = "27.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "PALLET two phase election providers"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
log = { workspace = true }
scale-info = { features = ["derive"], workspace = true }
pezframe-support = { workspace = true }
pezframe-system = { workspace = true }
pezframe-election-provider-support = { workspace = true }
pezsp-arithmetic = { workspace = true }
pezsp-core = { workspace = true }
pezsp-io = { workspace = true }
pezsp-npos-elections = { workspace = true }
pezsp-runtime = { workspace = true }
# Optional imports for benchmarking
pezframe-benchmarking = { optional = true, workspace = true }
rand = { features = ["alloc", "small_rng"], optional = true, workspace = true }
strum = { features = ["derive"], optional = true, workspace = true }
# optional for remote testing
hex = { workspace = true, default-features = true, optional = true }
remote-externalities = { workspace = true, default-features = true, optional = true }
tokio = { features = [
"macros",
], workspace = true, default-features = true, optional = true }
[dev-dependencies]
pezframe-benchmarking = { workspace = true, default-features = true }
pezpallet-balances = { workspace = true, default-features = true }
parking_lot = { workspace = true, default-features = true }
rand = { workspace = true, default-features = true }
pezsp-tracing = { workspace = true, default-features = true }
[features]
remote-mining = ["hex", "remote-externalities", "tokio"]
default = ["std"]
std = [
"codec/std",
"pezframe-benchmarking?/std",
"pezframe-election-provider-support/std",
"pezframe-support/std",
"pezframe-system/std",
"log/std",
"pezpallet-balances/std",
"rand/std",
"scale-info/std",
"pezsp-arithmetic/std",
"pezsp-core/std",
"pezsp-io/std",
"pezsp-npos-elections/std",
"pezsp-runtime/std",
"pezsp-tracing/std",
"strum/std",
]
runtime-benchmarks = [
"pezframe-benchmarking/runtime-benchmarks",
"pezframe-election-provider-support/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"rand",
"remote-externalities?/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-npos-elections/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"strum",
]
try-runtime = [
"pezframe-election-provider-support/try-runtime",
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezpallet-balances/try-runtime",
"pezsp-runtime/try-runtime",
]
@@ -0,0 +1,634 @@
// This file is part of Bizinikiwi.
// 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.
//! Two phase election pallet benchmarking.
use core::cmp::Reverse;
use pezframe_benchmarking::{v2::*, BenchmarkError};
use pezframe_election_provider_support::{bounds::DataProviderBounds, IndexAssignment};
use pezframe_support::{
assert_ok,
traits::{Hooks, TryCollect},
BoundedVec,
};
use pezframe_system::RawOrigin;
use rand::{prelude::SliceRandom, rngs::SmallRng, SeedableRng};
use pezsp_arithmetic::{per_things::Percent, traits::One};
use pezsp_runtime::InnerOf;
use crate::{unsigned::IndexAssignmentOf, *};
const SEED: u32 = 999;
/// Creates a **valid** solution with exactly the given size.
///
/// The snapshot is also created internally.
fn solution_with_size<T: Config>(
size: SolutionOrSnapshotSize,
active_voters_count: u32,
desired_targets: u32,
) -> Result<RawSolution<SolutionOf<T::MinerConfig>>, &'static str> {
ensure!(size.targets >= desired_targets, "must have enough targets");
ensure!(
size.targets >= (<SolutionOf<T::MinerConfig>>::LIMIT * 2) as u32,
"must have enough targets for unique votes."
);
ensure!(size.voters >= active_voters_count, "must have enough voters");
ensure!(
(<SolutionOf<T::MinerConfig>>::LIMIT as u32) < desired_targets,
"must have enough winners to give them votes."
);
let ed: VoteWeight = T::Currency::minimum_balance().saturated_into::<u64>();
let stake: VoteWeight = ed.max(One::one()).saturating_mul(100);
// first generates random targets.
let targets: Vec<T::AccountId> = (0..size.targets)
.map(|i| pezframe_benchmarking::account("Targets", i, SEED))
.collect();
let mut rng = SmallRng::seed_from_u64(SEED.into());
// decide who are the winners.
let winners = targets
.as_slice()
.choose_multiple(&mut rng, desired_targets as usize)
.cloned()
.collect::<Vec<_>>();
// first generate active voters who must vote for a subset of winners.
let active_voters = (0..active_voters_count)
.map(|i| {
// chose a random subset of winners.
let winner_votes: BoundedVec<_, _> = winners
.as_slice()
.choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT)
.cloned()
.try_collect()
.expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed.");
let voter = pezframe_benchmarking::account::<T::AccountId>("Voter", i, SEED);
(voter, stake, winner_votes)
})
.collect::<Vec<_>>();
// rest of the voters. They can only vote for non-winners.
let non_winners = targets
.iter()
.filter(|t| !winners.contains(t))
.cloned()
.collect::<Vec<T::AccountId>>();
let rest_voters = (active_voters_count..size.voters)
.map(|i| {
let votes: BoundedVec<_, _> = (&non_winners)
.choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT)
.cloned()
.try_collect()
.expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed.");
let voter = pezframe_benchmarking::account::<T::AccountId>("Voter", i, SEED);
(voter, stake, votes)
})
.collect::<Vec<_>>();
let mut all_voters = active_voters.clone();
all_voters.extend(rest_voters);
all_voters.shuffle(&mut rng);
assert_eq!(active_voters.len() as u32, active_voters_count);
assert_eq!(all_voters.len() as u32, size.voters);
assert_eq!(winners.len() as u32, desired_targets);
SnapshotMetadata::<T>::put(SolutionOrSnapshotSize {
voters: all_voters.len() as u32,
targets: targets.len() as u32,
});
DesiredTargets::<T>::put(desired_targets);
Snapshot::<T>::put(RoundSnapshot { voters: all_voters.clone(), targets: targets.clone() });
// write the snapshot to staking or whoever is the data provider, in case it is needed further
// down the road.
T::DataProvider::put_snapshot(all_voters.clone(), targets.clone(), Some(stake));
let cache = helpers::generate_voter_cache::<T::MinerConfig>(&all_voters);
let stake_of = helpers::stake_of_fn::<T::MinerConfig>(&all_voters, &cache);
let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache);
let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets);
let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&all_voters);
let target_at = helpers::target_at_fn::<T::MinerConfig>(&targets);
let assignments = active_voters
.iter()
.map(|(voter, _stake, votes)| {
let percent_per_edge: InnerOf<SolutionAccuracyOf<T>> =
(100 / votes.len()).try_into().unwrap_or_else(|_| panic!("failed to convert"));
unsigned::Assignment::<T> {
who: voter.clone(),
distribution: votes
.iter()
.map(|t| (t.clone(), SolutionAccuracyOf::<T>::from_percent(percent_per_edge)))
.collect::<Vec<_>>(),
}
})
.collect::<Vec<_>>();
let solution =
<SolutionOf<T::MinerConfig>>::from_assignment(&assignments, &voter_index, &target_index)
.unwrap();
let score = solution.clone().score(stake_of, voter_at, target_at).unwrap();
let round = Round::<T>::get();
assert!(
score.minimal_stake > 0,
"score is zero, this probably means that the stakes are not set."
);
Ok(RawSolution { solution, score, round })
}
fn set_up_data_provider<T: Config>(v: u32, t: u32) {
T::DataProvider::clear();
log!(
info,
"setting up with voters = {} [degree = {}], targets = {}",
v,
<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
t
);
// fill targets.
let mut targets = (0..t)
.map(|i| {
let target = pezframe_benchmarking::account::<T::AccountId>("Target", i, SEED);
T::DataProvider::add_target(target.clone());
target
})
.collect::<Vec<_>>();
// we should always have enough voters to fill.
assert!(
targets.len() > <T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() as usize
);
targets.truncate(<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get() as usize);
// fill voters.
(0..v).for_each(|i| {
let voter = pezframe_benchmarking::account::<T::AccountId>("Voter", i, SEED);
let weight = T::Currency::minimum_balance().saturated_into::<u64>() * 1000;
T::DataProvider::add_voter(voter, weight, targets.clone().try_into().unwrap());
});
}
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn on_initialize_nothing() {
assert!(CurrentPhase::<T>::get().is_off());
#[block]
{
Pallet::<T>::on_initialize(1_u32.into());
}
assert!(CurrentPhase::<T>::get().is_off());
}
#[benchmark]
fn on_initialize_open_signed() {
assert!(Snapshot::<T>::get().is_none());
assert!(CurrentPhase::<T>::get().is_off());
#[block]
{
Pallet::<T>::phase_transition(Phase::Signed);
}
assert!(Snapshot::<T>::get().is_none());
assert!(CurrentPhase::<T>::get().is_signed());
}
#[benchmark]
fn on_initialize_open_unsigned() {
assert!(Snapshot::<T>::get().is_none());
assert!(CurrentPhase::<T>::get().is_off());
#[block]
{
let now = pezframe_system::Pallet::<T>::block_number();
Pallet::<T>::phase_transition(Phase::Unsigned((true, now)));
}
assert!(Snapshot::<T>::get().is_none());
assert!(CurrentPhase::<T>::get().is_unsigned());
}
#[benchmark]
fn finalize_signed_phase_accept_solution() {
let receiver = account("receiver", 0, SEED);
let initial_balance = T::Currency::minimum_balance() + 10_u32.into();
T::Currency::make_free_balance_be(&receiver, initial_balance);
let ready = Default::default();
let deposit: BalanceOf<T> = 10_u32.into();
let reward: BalanceOf<T> = T::SignedRewardBase::get();
let call_fee: BalanceOf<T> = 30_u32.into();
assert_ok!(T::Currency::reserve(&receiver, deposit));
assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
#[block]
{
Pallet::<T>::finalize_signed_phase_accept_solution(ready, &receiver, deposit, call_fee);
}
assert_eq!(T::Currency::free_balance(&receiver), initial_balance + reward + call_fee);
assert_eq!(T::Currency::reserved_balance(&receiver), 0_u32.into());
}
#[benchmark]
fn finalize_signed_phase_reject_solution() {
let receiver = account("receiver", 0, SEED);
let initial_balance = T::Currency::minimum_balance() + 10_u32.into();
let deposit: BalanceOf<T> = 10_u32.into();
T::Currency::make_free_balance_be(&receiver, initial_balance);
assert_ok!(T::Currency::reserve(&receiver, deposit));
assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
assert_eq!(T::Currency::reserved_balance(&receiver), 10_u32.into());
#[block]
{
Pallet::<T>::finalize_signed_phase_reject_solution(&receiver, deposit)
}
assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance());
assert_eq!(T::Currency::reserved_balance(&receiver), 0_u32.into());
}
#[benchmark]
fn create_snapshot_internal(
// Number of votes in snapshot.
v: Linear<{ T::BenchmarkingConfig::VOTERS[0] }, { T::BenchmarkingConfig::VOTERS[1] }>,
// Number of targets in snapshot.
t: Linear<{ T::BenchmarkingConfig::TARGETS[0] }, { T::BenchmarkingConfig::TARGETS[1] }>,
) -> Result<(), BenchmarkError> {
// We don't directly need the data-provider to be populated, but it is just easy to use it.
set_up_data_provider::<T>(v, t);
// default bounds are unbounded.
let targets =
T::DataProvider::electable_targets(DataProviderBounds::default(), Zero::zero())?;
let voters = T::DataProvider::electing_voters(DataProviderBounds::default(), Zero::zero())?;
let desired_targets = T::DataProvider::desired_targets()?;
assert!(Snapshot::<T>::get().is_none());
#[block]
{
Pallet::<T>::create_snapshot_internal(targets, voters, desired_targets)
}
assert!(Snapshot::<T>::get().is_some());
assert_eq!(SnapshotMetadata::<T>::get().ok_or("metadata missing")?.voters, v);
assert_eq!(SnapshotMetadata::<T>::get().ok_or("metadata missing")?.targets, t);
Ok(())
}
// A call to `<Pallet as ElectionProvider>::elect` where we only return the queued solution.
#[benchmark]
fn elect_queued(
// Number of assignments, i.e. `solution.len()`.
// This means the active nominators, thus must be a subset of `v`.
a: Linear<
{ T::BenchmarkingConfig::ACTIVE_VOTERS[0] },
{ T::BenchmarkingConfig::ACTIVE_VOTERS[1] },
>,
// Number of desired targets. Must be a subset of `t`.
d: Linear<
{ T::BenchmarkingConfig::DESIRED_TARGETS[0] },
{ T::BenchmarkingConfig::DESIRED_TARGETS[1] },
>,
) -> Result<(), BenchmarkError> {
// Number of votes in snapshot. Not dominant.
let v = T::BenchmarkingConfig::VOTERS[1];
// Number of targets in snapshot. Not dominant.
let t = T::BenchmarkingConfig::TARGETS[1];
let witness = SolutionOrSnapshotSize { voters: v, targets: t };
let raw_solution = solution_with_size::<T>(witness, a, d)?;
let ready_solution = Pallet::<T>::feasibility_check(raw_solution, ElectionCompute::Signed)
.map_err(<&str>::from)?;
CurrentPhase::<T>::put(Phase::Signed);
// Assume a queued solution is stored, regardless of where it comes from.
QueuedSolution::<T>::put(ready_solution);
// These are set by the `solution_with_size` function.
assert!(DesiredTargets::<T>::get().is_some());
assert!(Snapshot::<T>::get().is_some());
assert!(SnapshotMetadata::<T>::get().is_some());
let result;
#[block]
{
result = <Pallet<T> as ElectionProvider>::elect(Zero::zero());
}
assert!(result.is_ok());
assert!(QueuedSolution::<T>::get().is_none());
assert!(DesiredTargets::<T>::get().is_none());
assert!(Snapshot::<T>::get().is_none());
assert!(SnapshotMetadata::<T>::get().is_none());
assert_eq!(
CurrentPhase::<T>::get(),
<Phase<pezframe_system::pezpallet_prelude::BlockNumberFor::<T>>>::Off
);
Ok(())
}
#[benchmark]
fn submit() -> Result<(), BenchmarkError> {
// The queue is full and the solution is only better than the worse.
Pallet::<T>::create_snapshot().map_err(<&str>::from)?;
Pallet::<T>::phase_transition(Phase::Signed);
Round::<T>::put(1);
let mut signed_submissions = SignedSubmissions::<T>::get();
// Insert `max` submissions
for i in 0..(T::SignedMaxSubmissions::get() - 1) {
let raw_solution = RawSolution {
score: ElectionScore {
minimal_stake: 10_000_000u128 + (i as u128),
..Default::default()
},
..Default::default()
};
let signed_submission = SignedSubmission {
raw_solution,
who: account("submitters", i, SEED),
deposit: Default::default(),
call_fee: Default::default(),
};
signed_submissions.insert(signed_submission);
}
signed_submissions.put();
// This score will eject the weakest one.
let solution = RawSolution {
score: ElectionScore { minimal_stake: 10_000_000u128 + 1, ..Default::default() },
..Default::default()
};
let caller = pezframe_benchmarking::whitelisted_caller();
let deposit =
Pallet::<T>::deposit_for(&solution, SnapshotMetadata::<T>::get().unwrap_or_default());
T::Currency::make_free_balance_be(
&caller,
T::Currency::minimum_balance() * 1000u32.into() + deposit,
);
#[extrinsic_call]
_(RawOrigin::Signed(caller), Box::new(solution));
assert!(Pallet::<T>::signed_submissions().len() as u32 == T::SignedMaxSubmissions::get());
Ok(())
}
#[benchmark]
fn submit_unsigned(
// Number of votes in snapshot.
v: Linear<{ T::BenchmarkingConfig::VOTERS[0] }, { T::BenchmarkingConfig::VOTERS[1] }>,
// Number of targets in snapshot.
t: Linear<{ T::BenchmarkingConfig::TARGETS[0] }, { T::BenchmarkingConfig::TARGETS[1] }>,
// Number of assignments, i.e. `solution.len()`.
// This means the active nominators, thus must be a subset of `v` component.
a: Linear<
{ T::BenchmarkingConfig::ACTIVE_VOTERS[0] },
{ T::BenchmarkingConfig::ACTIVE_VOTERS[1] },
>,
// Number of desired targets. Must be a subset of `t` component.
d: Linear<
{ T::BenchmarkingConfig::DESIRED_TARGETS[0] },
{ T::BenchmarkingConfig::DESIRED_TARGETS[1] },
>,
) -> Result<(), BenchmarkError> {
let witness = SolutionOrSnapshotSize { voters: v, targets: t };
let raw_solution = solution_with_size::<T>(witness, a, d)?;
assert!(QueuedSolution::<T>::get().is_none());
CurrentPhase::<T>::put(Phase::Unsigned((true, 1_u32.into())));
#[extrinsic_call]
_(RawOrigin::None, Box::new(raw_solution), witness);
assert!(QueuedSolution::<T>::get().is_some());
Ok(())
}
// This is checking a valid solution. The worse case is indeed a valid solution.
#[benchmark]
fn feasibility_check(
// Number of votes in snapshot.
v: Linear<{ T::BenchmarkingConfig::VOTERS[0] }, { T::BenchmarkingConfig::VOTERS[1] }>,
// Number of targets in snapshot.
t: Linear<{ T::BenchmarkingConfig::TARGETS[0] }, { T::BenchmarkingConfig::TARGETS[1] }>,
// Number of assignments, i.e. `solution.len()`.
// This means the active nominators, thus must be a subset of `v` component.
a: Linear<
{ T::BenchmarkingConfig::ACTIVE_VOTERS[0] },
{ T::BenchmarkingConfig::ACTIVE_VOTERS[1] },
>,
// Number of desired targets. Must be a subset of `t` component.
d: Linear<
{ T::BenchmarkingConfig::DESIRED_TARGETS[0] },
{ T::BenchmarkingConfig::DESIRED_TARGETS[1] },
>,
) -> Result<(), BenchmarkError> {
let size = SolutionOrSnapshotSize { voters: v, targets: t };
let raw_solution = solution_with_size::<T>(size, a, d)?;
assert_eq!(raw_solution.solution.voter_count() as u32, a);
assert_eq!(raw_solution.solution.unique_targets().len() as u32, d);
let result;
#[block]
{
result = Pallet::<T>::feasibility_check(raw_solution, ElectionCompute::Unsigned);
}
assert!(result.is_ok());
Ok(())
}
// NOTE: this weight is not used anywhere, but the fact that it should succeed when execution in
// isolation is vital to ensure memory-safety. For the same reason, we don't care about the
// components iterating, we merely check that this operation will work with the "maximum"
// numbers.
//
// ONLY run this benchmark in isolation, and pass the `--extra` flag to enable it.
//
// NOTE: If this benchmark does not run out of memory with a given heap pages, it means that the
// OCW process can SURELY succeed with the given configuration, but the opposite is not true.
// This benchmark is doing more work than a raw call to `OffchainWorker_offchain_worker` runtime
// api call, since it is also setting up some mock data, which will itself exhaust the heap to
// some extent.
#[benchmark(extra)]
fn mine_solution_offchain_memory() {
// Number of votes in snapshot. Fixed to maximum.
let v = T::BenchmarkingConfig::MINER_MAXIMUM_VOTERS;
// Number of targets in snapshot. Fixed to maximum.
let t = T::BenchmarkingConfig::MAXIMUM_TARGETS;
set_up_data_provider::<T>(v, t);
let now = pezframe_system::Pallet::<T>::block_number();
CurrentPhase::<T>::put(Phase::Unsigned((true, now)));
Pallet::<T>::create_snapshot().unwrap();
#[block]
{
// we can't really verify this as it won't write anything to state, check logs.
Pallet::<T>::offchain_worker(now)
}
}
// NOTE: this weight is not used anywhere, but the fact that it should succeed when execution in
// isolation is vital to ensure memory-safety. For the same reason, we don't care about the
// components iterating, we merely check that this operation will work with the "maximum"
// numbers.
//
// ONLY run this benchmark in isolation, and pass the `--extra` flag to enable it.
#[benchmark(extra)]
fn create_snapshot_memory() -> Result<(), BenchmarkError> {
// Number of votes in snapshot. Fixed to maximum.
let v = T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS;
// Number of targets in snapshot. Fixed to maximum.
let t = T::BenchmarkingConfig::MAXIMUM_TARGETS;
set_up_data_provider::<T>(v, t);
assert!(Snapshot::<T>::get().is_none());
#[block]
{
Pallet::<T>::create_snapshot().map_err(|_| "could not create snapshot")?;
}
assert!(Snapshot::<T>::get().is_some());
assert_eq!(SnapshotMetadata::<T>::get().ok_or("snapshot missing")?.voters, v);
assert_eq!(SnapshotMetadata::<T>::get().ok_or("snapshot missing")?.targets, t);
Ok(())
}
#[benchmark(extra)]
fn trim_assignments_length(
// Number of votes in snapshot.
v: Linear<{ T::BenchmarkingConfig::VOTERS[0] }, { T::BenchmarkingConfig::VOTERS[1] }>,
// Number of targets in snapshot.
t: Linear<{ T::BenchmarkingConfig::TARGETS[0] }, { T::BenchmarkingConfig::TARGETS[1] }>,
// Number of assignments, i.e. `solution.len()`.
// This means the active nominators, thus must be a subset of `v` component.
a: Linear<
{ T::BenchmarkingConfig::ACTIVE_VOTERS[0] },
{ T::BenchmarkingConfig::ACTIVE_VOTERS[1] },
>,
// Number of desired targets. Must be a subset of `t` component.
d: Linear<
{ T::BenchmarkingConfig::DESIRED_TARGETS[0] },
{ T::BenchmarkingConfig::DESIRED_TARGETS[1] },
>,
// Subtract this percentage from the actual encoded size.
f: Linear<0, 95>,
) -> Result<(), BenchmarkError> {
// Compute a random solution, then work backwards to get the lists of voters, targets, and
// assignments
let witness = SolutionOrSnapshotSize { voters: v, targets: t };
let RawSolution { solution, .. } = solution_with_size::<T>(witness, a, d)?;
let RoundSnapshot { voters, targets } = Snapshot::<T>::get().ok_or("snapshot missing")?;
let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&voters);
let target_at = helpers::target_at_fn::<T::MinerConfig>(&targets);
let mut assignments = solution
.into_assignment(voter_at, target_at)
.expect("solution generated by `solution_with_size` must be valid.");
// make a voter cache and some helper functions for access
let cache = helpers::generate_voter_cache::<T::MinerConfig>(&voters);
let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache);
let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets);
// sort assignments by decreasing voter stake
assignments.sort_by_key(|unsigned::Assignment::<T> { who, .. }| {
let stake = cache
.get(who)
.map(|idx| {
let (_, stake, _) = voters[*idx];
stake
})
.unwrap_or_default();
Reverse(stake)
});
let mut index_assignments = assignments
.into_iter()
.map(|assignment| IndexAssignment::new(&assignment, &voter_index, &target_index))
.collect::<Result<Vec<_>, _>>()
.unwrap();
let encoded_size_of = |assignments: &[IndexAssignmentOf<T::MinerConfig>]| {
SolutionOf::<T::MinerConfig>::try_from(assignments)
.map(|solution| solution.encoded_size())
};
let desired_size = Percent::from_percent(100 - f.saturated_into::<u8>())
.mul_ceil(encoded_size_of(index_assignments.as_slice()).unwrap());
log!(trace, "desired_size = {}", desired_size);
#[block]
{
Miner::<T::MinerConfig>::trim_assignments_length(
desired_size.saturated_into(),
&mut index_assignments,
&encoded_size_of,
)
.unwrap();
}
let solution =
SolutionOf::<T::MinerConfig>::try_from(index_assignments.as_slice()).unwrap();
let encoding = solution.encode();
log!(
trace,
"encoded size prediction = {}",
encoded_size_of(index_assignments.as_slice()).unwrap(),
);
log!(trace, "actual encoded size = {}", encoding.len());
assert!(encoding.len() <= desired_size);
Ok(())
}
impl_benchmark_test_suite! {
Pallet,
mock::ExtBuilder::default().build_offchainify(10).0,
mock::Runtime,
}
}
@@ -0,0 +1,219 @@
// This file is part of Bizinikiwi.
// 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 helper functions/macros for this crate.
use crate::{
unsigned::{MinerConfig, MinerVoterOf},
SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight,
};
use alloc::{collections::btree_map::BTreeMap, vec::Vec};
#[macro_export]
macro_rules! log {
($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => {
log::$level!(
target: $crate::LOG_TARGET,
concat!("[#{:?}] 🗳 ", $pattern), pezframe_system::Pallet::<T>::block_number() $(, $values)*
)
};
}
// This is only useful for a context where a `<T: Config>` is not in scope.
#[macro_export]
macro_rules! log_no_system {
($level:tt, $pattern:expr $(, $values:expr)* $(,)?) => {
log::$level!(
target: $crate::LOG_TARGET,
concat!("🗳 ", $pattern) $(, $values)*
)
};
}
/// Generate a btree-map cache of the voters and their indices.
///
/// This can be used to efficiently build index getter closures.
pub fn generate_voter_cache<T: MinerConfig>(
snapshot: &Vec<MinerVoterOf<T>>,
) -> BTreeMap<T::AccountId, usize> {
let mut cache: BTreeMap<T::AccountId, usize> = BTreeMap::new();
snapshot.iter().enumerate().for_each(|(i, (x, _, _))| {
let _existed = cache.insert(x.clone(), i);
// if a duplicate exists, we only consider the last one. Defensive only, should never
// happen.
debug_assert!(_existed.is_none());
});
cache
}
/// Create a function that returns the index of a voter in the snapshot.
///
/// The returning index type is the same as the one defined in `T::Solution::Voter`.
///
/// ## Warning
///
/// Note that this will represent the snapshot data from which the `cache` is generated.
pub fn voter_index_fn<T: MinerConfig>(
cache: &BTreeMap<T::AccountId, usize>,
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
move |who| {
cache
.get(who)
.and_then(|i| <usize as TryInto<SolutionVoterIndexOf<T>>>::try_into(*i).ok())
}
}
/// Create a function that returns the index of a voter in the snapshot.
///
/// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is
/// borrowed.
pub fn voter_index_fn_owned<T: MinerConfig>(
cache: BTreeMap<T::AccountId, usize>,
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> {
move |who| {
cache
.get(who)
.and_then(|i| <usize as TryInto<SolutionVoterIndexOf<T>>>::try_into(*i).ok())
}
}
/// Same as [`voter_index_fn`], but the returning index is converted into usize, if possible.
///
/// ## Warning
///
/// Note that this will represent the snapshot data from which the `cache` is generated.
pub fn voter_index_fn_usize<T: MinerConfig>(
cache: &BTreeMap<T::AccountId, usize>,
) -> impl Fn(&T::AccountId) -> Option<usize> + '_ {
move |who| cache.get(who).cloned()
}
/// A non-optimized, linear version of [`voter_index_fn`] that does not need a cache and does a
/// linear search.
///
/// ## Warning
///
/// Not meant to be used in production.
#[cfg(test)]
pub fn voter_index_fn_linear<T: MinerConfig>(
snapshot: &Vec<MinerVoterOf<T>>,
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
move |who| {
snapshot
.iter()
.position(|(x, _, _)| x == who)
.and_then(|i| <usize as TryInto<SolutionVoterIndexOf<T>>>::try_into(i).ok())
}
}
/// Create a function that returns the index of a target in the snapshot.
///
/// The returned index type is the same as the one defined in `T::Solution::Target`.
///
/// Note: to the extent possible, the returned function should be cached and reused. Producing that
/// function requires a `O(n log n)` data transform. Each invocation of that function completes
/// in `O(log n)`.
pub fn target_index_fn<T: MinerConfig>(
snapshot: &Vec<T::AccountId>,
) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
let cache: BTreeMap<_, _> =
snapshot.iter().enumerate().map(|(idx, account_id)| (account_id, idx)).collect();
move |who| {
cache
.get(who)
.and_then(|i| <usize as TryInto<SolutionTargetIndexOf<T>>>::try_into(*i).ok())
}
}
/// Create a function the returns the index to a target in the snapshot.
///
/// The returned index type is the same as the one defined in `T::Solution::Target`.
///
/// ## Warning
///
/// Not meant to be used in production.
#[cfg(test)]
pub fn target_index_fn_linear<T: MinerConfig>(
snapshot: &Vec<T::AccountId>,
) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
move |who| {
snapshot
.iter()
.position(|x| x == who)
.and_then(|i| <usize as TryInto<SolutionTargetIndexOf<T>>>::try_into(i).ok())
}
}
/// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter
/// account using a linearly indexable snapshot.
pub fn voter_at_fn<T: MinerConfig>(
snapshot: &Vec<MinerVoterOf<T>>,
) -> impl Fn(SolutionVoterIndexOf<T>) -> Option<T::AccountId> + '_ {
move |i| {
<SolutionVoterIndexOf<T> as TryInto<usize>>::try_into(i)
.ok()
.and_then(|i| snapshot.get(i).map(|(x, _, _)| x).cloned())
}
}
/// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target
/// account using a linearly indexable snapshot.
pub fn target_at_fn<T: MinerConfig>(
snapshot: &Vec<T::AccountId>,
) -> impl Fn(SolutionTargetIndexOf<T>) -> Option<T::AccountId> + '_ {
move |i| {
<SolutionTargetIndexOf<T> as TryInto<usize>>::try_into(i)
.ok()
.and_then(|i| snapshot.get(i).cloned())
}
}
/// Create a function to get the stake of a voter.
///
/// This is not optimized and uses a linear search.
#[cfg(test)]
pub fn stake_of_fn_linear<T: MinerConfig>(
snapshot: &Vec<MinerVoterOf<T>>,
) -> impl Fn(&T::AccountId) -> VoteWeight + '_ {
move |who| {
snapshot
.iter()
.find(|(x, _, _)| x == who)
.map(|(_, x, _)| *x)
.unwrap_or_default()
}
}
/// Create a function to get the stake of a voter.
///
/// ## Warning
///
/// The cache need must be derived from the same snapshot. Zero is returned if a voter is
/// non-existent.
pub fn stake_of_fn<'a, T: MinerConfig>(
snapshot: &'a Vec<MinerVoterOf<T>>,
cache: &'a BTreeMap<T::AccountId, usize>,
) -> impl Fn(&T::AccountId) -> VoteWeight + 'a {
move |who| {
if let Some(index) = cache.get(who) {
snapshot.get(*index).map(|(_, x, _)| x).cloned().unwrap_or_default()
} else {
0
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,78 @@
// This file is part of Bizinikiwi.
// 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.
pub mod v1 {
use alloc::collections::btree_map::BTreeMap;
use pezframe_support::{
storage::unhashed,
traits::{Defensive, GetStorageVersion, OnRuntimeUpgrade},
BoundedVec,
};
use crate::*;
pub struct MigrateToV1<T>(core::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
fn on_runtime_upgrade() -> Weight {
let current = Pallet::<T>::in_code_storage_version();
let onchain = Pallet::<T>::on_chain_storage_version();
log!(
info,
"Running migration with in-code storage version {:?} / onchain {:?}",
current,
onchain
);
if current == 1 && onchain == 0 {
if SignedSubmissionIndices::<T>::exists() {
// This needs to be tested at a both a block height where this value exists, and
// when it doesn't.
let now = pezframe_system::Pallet::<T>::block_number();
let map = unhashed::get::<BTreeMap<ElectionScore, u32>>(
&SignedSubmissionIndices::<T>::hashed_key(),
)
.defensive_unwrap_or_default();
let vector = map
.into_iter()
.map(|(score, index)| (score, now, index))
.collect::<Vec<_>>();
log!(
debug,
"{:?} SignedSubmissionIndices read from storage (max: {:?})",
vector.len(),
T::SignedMaxSubmissions::get()
);
// defensive-only, assuming a constant `SignedMaxSubmissions`.
let bounded = BoundedVec::<_, _>::truncate_from(vector);
SignedSubmissionIndices::<T>::put(bounded);
log!(info, "SignedSubmissionIndices existed and got migrated");
} else {
log!(info, "SignedSubmissionIndices did NOT exist.");
}
current.put::<Pallet<T>>();
T::DbWeight::get().reads_writes(2, 1)
} else {
log!(info, "Migration did not execute. This probably should be removed");
T::DbWeight::get().reads(1)
}
}
}
}
@@ -0,0 +1,691 @@
// This file is part of Bizinikiwi.
// 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.
use super::*;
use crate::{self as multi_phase, signed::GeometricDepositBase, unsigned::MinerConfig};
use pezframe_election_provider_support::{
bounds::{DataProviderBounds, ElectionBounds, ElectionBoundsBuilder},
data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen,
};
pub use pezframe_support::derive_impl;
use pezframe_support::{
parameter_types,
traits::{ConstU32, Hooks},
weights::{constants, Weight},
BoundedVec,
};
use multi_phase::unsigned::{IndexAssignmentOf, VoterOf};
use parking_lot::RwLock;
use pezsp_core::{
offchain::{
testing::{PoolState, TestOffchainExt, TestTransactionPoolExt},
OffchainDbExt, OffchainWorkerExt, TransactionPoolExt,
},
ConstBool, H256,
};
use pezsp_npos_elections::{
assignment_ratio_to_staked_normalized, seq_phragmen, to_supports, BalancingConfig,
ElectionResult, EvaluateSupport,
};
use pezsp_runtime::{
bounded_vec,
testing::Header,
traits::{BlakeTwo256, Convert, IdentityLookup},
BuildStorage, PerU16, Percent,
};
use std::sync::Arc;
pub type Block = pezsp_runtime::generic::Block<Header, UncheckedExtrinsic>;
pub type UncheckedExtrinsic =
pezsp_runtime::generic::UncheckedExtrinsic<AccountId, RuntimeCall, (), ()>;
pezframe_support::construct_runtime!(
pub enum Runtime {
System: pezframe_system,
Balances: pezpallet_balances,
MultiPhase: multi_phase,
}
);
pub(crate) type Balance = u64;
pub(crate) type AccountId = u64;
pub(crate) type BlockNumber = u64;
pub(crate) type VoterIndex = u32;
pub(crate) type TargetIndex = u16;
pezframe_election_provider_support::generate_solution_type!(
#[compact]
pub struct TestNposSolution::<
VoterIndex = VoterIndex,
TargetIndex = TargetIndex,
Accuracy = PerU16,
MaxVoters = ConstU32::<2_000>
>(16)
);
/// All events of this pallet.
pub(crate) fn multi_phase_events() -> Vec<super::Event<Runtime>> {
System::read_events_for_pallet::<super::Event<Runtime>>()
}
/// To from `now` to block `n`.
pub fn roll_to(n: BlockNumber) {
let now = System::block_number();
for i in now + 1..=n {
System::set_block_number(i);
MultiPhase::on_initialize(i);
}
}
pub fn roll_to_unsigned() {
while !matches!(CurrentPhase::<Runtime>::get(), Phase::Unsigned(_)) {
roll_to(System::block_number() + 1);
}
}
pub fn roll_to_signed() {
while !matches!(CurrentPhase::<Runtime>::get(), Phase::Signed) {
roll_to(System::block_number() + 1);
}
}
pub fn roll_to_with_ocw(n: BlockNumber) {
let now = System::block_number();
for i in now + 1..=n {
System::set_block_number(i);
MultiPhase::on_initialize(i);
MultiPhase::offchain_worker(i);
}
}
pub fn roll_to_round(n: u32) {
assert!(Round::<Runtime>::get() <= n);
while Round::<Runtime>::get() != n {
roll_to_signed();
pezframe_support::assert_ok!(MultiPhase::elect(Zero::zero()));
}
}
pub struct TrimHelpers {
pub voters: Vec<VoterOf<Runtime>>,
pub assignments: Vec<IndexAssignmentOf<Runtime>>,
pub encoded_size_of:
Box<dyn Fn(&[IndexAssignmentOf<Runtime>]) -> Result<usize, pezsp_npos_elections::Error>>,
pub voter_index: Box<
dyn Fn(
&<Runtime as pezframe_system::Config>::AccountId,
) -> Option<SolutionVoterIndexOf<Runtime>>,
>,
}
/// Helpers for setting up trimming tests.
///
/// Assignments are pre-sorted in reverse order of stake.
pub fn trim_helpers() -> TrimHelpers {
let RoundSnapshot { voters, targets } = Snapshot::<Runtime>::get().unwrap();
let stakes: std::collections::HashMap<_, _> =
voters.iter().map(|(id, stake, _)| (*id, *stake)).collect();
// Compute the size of a solution comprised of the selected arguments.
//
// This function completes in `O(edges)`; it's expensive, but linear.
let encoded_size_of = Box::new(|assignments: &[IndexAssignmentOf<Runtime>]| {
SolutionOf::<Runtime>::try_from(assignments).map(|s| s.encoded_size())
});
let cache = helpers::generate_voter_cache::<Runtime>(&voters);
let voter_index = helpers::voter_index_fn_owned::<Runtime>(cache);
let target_index = helpers::target_index_fn::<Runtime>(&targets);
let desired_targets = crate::DesiredTargets::<Runtime>::get().unwrap();
let ElectionResult::<_, SolutionAccuracyOf<Runtime>> { mut assignments, .. } =
seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap();
// sort by decreasing order of stake
assignments.sort_by_key(|assignment| {
std::cmp::Reverse(stakes.get(&assignment.who).cloned().unwrap_or_default())
});
// convert to IndexAssignment
let assignments = assignments
.iter()
.map(|assignment| {
IndexAssignmentOf::<Runtime>::new(assignment, &voter_index, &target_index)
})
.collect::<Result<Vec<_>, _>>()
.expect("test assignments don't contain any voters with too many votes");
TrimHelpers { voters, assignments, encoded_size_of, voter_index: Box::new(voter_index) }
}
/// Spit out a verifiable raw solution.
///
/// This is a good example of what an offchain miner would do.
pub fn raw_solution() -> RawSolution<SolutionOf<Runtime>> {
let RoundSnapshot { voters, targets } = Snapshot::<Runtime>::get().unwrap();
let desired_targets = crate::DesiredTargets::<Runtime>::get().unwrap();
let ElectionResult::<_, SolutionAccuracyOf<Runtime>> { winners: _, assignments } =
seq_phragmen(desired_targets as usize, targets.clone(), voters.clone(), None).unwrap();
// closures
let cache = helpers::generate_voter_cache::<Runtime>(&voters);
let voter_index = helpers::voter_index_fn_linear::<Runtime>(&voters);
let target_index = helpers::target_index_fn_linear::<Runtime>(&targets);
let stake_of = helpers::stake_of_fn::<Runtime>(&voters, &cache);
let score = {
let staked = assignment_ratio_to_staked_normalized(assignments.clone(), &stake_of).unwrap();
to_supports(&staked).evaluate()
};
let solution =
SolutionOf::<Runtime>::from_assignment(&assignments, &voter_index, &target_index).unwrap();
let round = Round::<Runtime>::get();
RawSolution { solution, score, round }
}
pub fn witness() -> SolutionOrSnapshotSize {
Snapshot::<Runtime>::get()
.map(|snap| SolutionOrSnapshotSize {
voters: snap.voters.len() as u32,
targets: snap.targets.len() as u32,
})
.unwrap_or_default()
}
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Runtime {
type SS58Prefix = ();
type BaseCallFilter = pezframe_support::traits::Everything;
type RuntimeOrigin = RuntimeOrigin;
type Nonce = u64;
type RuntimeCall = RuntimeCall;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ();
type DbWeight = ();
type BlockLength = ();
type BlockWeights = BlockWeights;
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pezpallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type OnSetCode = ();
type MaxConsumers = ConstU32<16>;
}
const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
parameter_types! {
pub BlockWeights: pezframe_system::limits::BlockWeights = pezframe_system::limits::BlockWeights
::with_sensible_defaults(
Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX),
NORMAL_DISPATCH_RATIO,
);
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Runtime {
type AccountStore = System;
}
#[derive(Default, Eq, PartialEq, Debug, Clone, Copy)]
pub enum MockedWeightInfo {
#[default]
Basic,
Complex,
Real,
}
parameter_types! {
pub static Targets: Vec<AccountId> = vec![10, 20, 30, 40];
pub static Voters: Vec<VoterOf<Runtime>> = vec![
(1, 10, bounded_vec![10, 20]),
(2, 10, bounded_vec![30, 40]),
(3, 10, bounded_vec![40]),
(4, 10, bounded_vec![10, 20, 30, 40]),
// self votes.
(10, 10, bounded_vec![10]),
(20, 20, bounded_vec![20]),
(30, 30, bounded_vec![30]),
(40, 40, bounded_vec![40]),
];
pub static DesiredTargets: u32 = 2;
pub static SignedPhase: BlockNumber = 10;
pub static UnsignedPhase: BlockNumber = 5;
pub static SignedMaxSubmissions: u32 = 5;
pub static SignedMaxRefunds: u32 = 1;
// for tests only. if `EnableVariableDepositBase` is true, the deposit base will be calculated
// by `Multiphase::DepositBase`. Otherwise the deposit base is `SignedFixedDeposit`.
pub static EnableVariableDepositBase: bool = false;
pub static SignedFixedDeposit: Balance = 5;
pub static SignedDepositIncreaseFactor: Percent = Percent::from_percent(10);
pub static SignedDepositByte: Balance = 0;
pub static SignedDepositWeight: Balance = 0;
pub static SignedRewardBase: Balance = 7;
pub static SignedMaxWeight: Weight = BlockWeights::get().max_block;
pub static MinerTxPriority: u64 = 100;
pub static BetterSignedThreshold: Perbill = Perbill::zero();
pub static OffchainRepeat: BlockNumber = 5;
pub static MinerMaxWeight: Weight = BlockWeights::get().max_block;
pub static MinerMaxLength: u32 = 256;
pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real;
pub static MaxElectingVoters: VoterIndex = u32::max_value();
pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value();
#[derive(Debug)]
pub static MaxWinners: u32 = 200;
#[derive(Debug)]
pub static MaxBackersPerWinner: u32 = 200;
// `ElectionBounds` and `OnChainElectionsBounds` are defined separately to set them independently in the tests.
pub static ElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
pub static OnChainElectionsBounds: ElectionBounds = ElectionBoundsBuilder::default().build();
pub static EpochLength: u64 = 30;
pub static OnChainFallback: bool = true;
}
pub struct OnChainSeqPhragmen;
impl onchain::Config for OnChainSeqPhragmen {
type System = Runtime;
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
type DataProvider = StakingMock;
type WeightInfo = ();
type MaxWinnersPerPage = MaxWinners;
type MaxBackersPerWinner = MaxBackersPerWinner;
type Sort = ConstBool<true>;
type Bounds = OnChainElectionsBounds;
}
pub struct MockFallback;
impl ElectionProvider for MockFallback {
type AccountId = AccountId;
type BlockNumber = BlockNumber;
type Error = &'static str;
type MaxWinnersPerPage = MaxWinners;
type MaxBackersPerWinner = MaxBackersPerWinner;
type MaxBackersPerWinnerFinal = MaxBackersPerWinner;
type Pages = ConstU32<1>;
type DataProvider = StakingMock;
fn elect(_remaining: PageIndex) -> Result<BoundedSupportsOf<Self>, Self::Error> {
unimplemented!()
}
fn duration() -> Self::BlockNumber {
0
}
fn start() -> Result<(), Self::Error> {
Ok(())
}
fn status() -> Result<bool, ()> {
Ok(true)
}
}
impl InstantElectionProvider for MockFallback {
fn instant_elect(
voters: Vec<VoterOf<Runtime>>,
targets: Vec<AccountId>,
desired_targets: u32,
) -> Result<BoundedSupportsOf<Self>, Self::Error> {
if OnChainFallback::get() {
onchain::OnChainExecution::<OnChainSeqPhragmen>::instant_elect(
voters,
targets,
desired_targets,
)
.map_err(|_| "onchain::OnChainExecution failed.")
} else {
Err("NoFallback.")
}
}
fn bother() -> bool {
OnChainFallback::get()
}
}
parameter_types! {
pub static Balancing: Option<BalancingConfig> = Some( BalancingConfig { iterations: 0, tolerance: 0 } );
}
pub struct TestBenchmarkingConfig;
impl BenchmarkingConfig for TestBenchmarkingConfig {
const VOTERS: [u32; 2] = [400, 600];
const ACTIVE_VOTERS: [u32; 2] = [100, 300];
const TARGETS: [u32; 2] = [200, 400];
const DESIRED_TARGETS: [u32; 2] = [100, 180];
const SNAPSHOT_MAXIMUM_VOTERS: u32 = 1000;
const MINER_MAXIMUM_VOTERS: u32 = 1000;
const MAXIMUM_TARGETS: u32 = 200;
}
impl MinerConfig for Runtime {
type AccountId = AccountId;
type MaxLength = MinerMaxLength;
type MaxWeight = MinerMaxWeight;
type MaxVotesPerVoter = <StakingMock as ElectionDataProvider>::MaxVotesPerVoter;
type MaxWinners = MaxWinners;
type MaxBackersPerWinner = MaxBackersPerWinner;
type Solution = TestNposSolution;
fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight {
match MockWeightInfo::get() {
MockedWeightInfo::Basic => Weight::from_parts(
(10 as u64).saturating_add((5 as u64).saturating_mul(a as u64)),
0,
),
MockedWeightInfo::Complex =>
Weight::from_parts((0 * v + 0 * t + 1000 * a + 0 * d) as u64, 0),
MockedWeightInfo::Real =>
<() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d),
}
}
}
impl crate::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type EstimateCallFee = pezframe_support::traits::ConstU64<8>;
type SignedPhase = SignedPhase;
type UnsignedPhase = UnsignedPhase;
type BetterSignedThreshold = BetterSignedThreshold;
type OffchainRepeat = OffchainRepeat;
type MinerTxPriority = MinerTxPriority;
type SignedRewardBase = SignedRewardBase;
type SignedDepositBase = Self;
type SignedDepositByte = ();
type SignedDepositWeight = ();
type SignedMaxWeight = SignedMaxWeight;
type SignedMaxSubmissions = SignedMaxSubmissions;
type SignedMaxRefunds = SignedMaxRefunds;
type SlashHandler = ();
type RewardHandler = ();
type DataProvider = StakingMock;
type WeightInfo = ();
type BenchmarkingConfig = TestBenchmarkingConfig;
type Fallback = MockFallback;
type GovernanceFallback =
pezframe_election_provider_support::onchain::OnChainExecution<OnChainSeqPhragmen>;
type ForceOrigin = pezframe_system::EnsureRoot<AccountId>;
type MaxWinners = MaxWinners;
type MaxBackersPerWinner = MaxBackersPerWinner;
type MinerConfig = Self;
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
type ElectionBounds = ElectionsBounds;
}
impl Convert<usize, BalanceOf<Runtime>> for Runtime {
/// returns the geometric increase deposit fee if `EnableVariableDepositBase` is set, otherwise
/// the fee is `SignedFixedDeposit`.
fn convert(queue_len: usize) -> Balance {
if !EnableVariableDepositBase::get() {
SignedFixedDeposit::get()
} else {
GeometricDepositBase::<Balance, SignedFixedDeposit, SignedDepositIncreaseFactor>::convert(queue_len)
}
}
}
impl<LocalCall> pezframe_system::offchain::CreateTransactionBase<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
type RuntimeCall = RuntimeCall;
type Extrinsic = Extrinsic;
}
impl<LocalCall> pezframe_system::offchain::CreateBare<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
Extrinsic::new_bare(call)
}
}
pub type Extrinsic = pezsp_runtime::testing::TestXt<RuntimeCall, ()>;
parameter_types! {
pub MaxNominations: u32 = <TestNposSolution as NposSolution>::LIMIT as u32;
// only used in testing to manipulate mock behaviour
pub static DataProviderAllowBadData: bool = false;
}
#[derive(Default)]
pub struct ExtBuilder {}
pub struct StakingMock;
impl ElectionDataProvider for StakingMock {
type BlockNumber = BlockNumber;
type AccountId = AccountId;
type MaxVotesPerVoter = MaxNominations;
fn electable_targets(
bounds: DataProviderBounds,
remaining_pages: PageIndex,
) -> data_provider::Result<Vec<AccountId>> {
assert!(remaining_pages.is_zero());
let targets = Targets::get();
if !DataProviderAllowBadData::get() &&
bounds.count.map_or(false, |max_len| targets.len() > max_len.0 as usize)
{
return Err("Targets too big");
}
Ok(targets)
}
fn electing_voters(
bounds: DataProviderBounds,
remaining_pages: PageIndex,
) -> data_provider::Result<Vec<VoterOf<Runtime>>> {
assert!(remaining_pages.is_zero());
let mut voters = Voters::get();
if !DataProviderAllowBadData::get() {
if let Some(max_len) = bounds.count {
voters.truncate(max_len.0 as usize)
}
}
Ok(voters)
}
fn desired_targets() -> data_provider::Result<u32> {
Ok(DesiredTargets::get())
}
fn next_election_prediction(now: u64) -> u64 {
now + EpochLength::get() - now % EpochLength::get()
}
#[cfg(feature = "runtime-benchmarks")]
fn put_snapshot(
voters: Vec<VoterOf<Runtime>>,
targets: Vec<AccountId>,
_target_stake: Option<VoteWeight>,
) {
Targets::set(targets);
Voters::set(voters);
}
#[cfg(feature = "runtime-benchmarks")]
fn clear() {
Targets::set(vec![]);
Voters::set(vec![]);
}
#[cfg(feature = "runtime-benchmarks")]
fn add_voter(
voter: AccountId,
weight: VoteWeight,
targets: pezframe_support::BoundedVec<AccountId, Self::MaxVotesPerVoter>,
) {
let mut current = Voters::get();
current.push((voter, weight, targets));
Voters::set(current);
}
#[cfg(feature = "runtime-benchmarks")]
fn add_target(target: AccountId) {
let mut current = Targets::get();
current.push(target);
Targets::set(current);
}
}
impl ExtBuilder {
pub fn miner_tx_priority(self, p: u64) -> Self {
<MinerTxPriority>::set(p);
self
}
pub fn better_signed_threshold(self, p: Perbill) -> Self {
<BetterSignedThreshold>::set(p);
self
}
pub fn phases(self, signed: BlockNumber, unsigned: BlockNumber) -> Self {
<SignedPhase>::set(signed);
<UnsignedPhase>::set(unsigned);
self
}
pub fn onchain_fallback(self, onchain: bool) -> Self {
<OnChainFallback>::set(onchain);
self
}
pub fn miner_weight(self, weight: Weight) -> Self {
<MinerMaxWeight>::set(weight);
self
}
pub fn mock_weight_info(self, mock: MockedWeightInfo) -> Self {
<MockWeightInfo>::set(mock);
self
}
pub fn desired_targets(self, t: u32) -> Self {
<DesiredTargets>::set(t);
self
}
pub fn add_voter(
self,
who: AccountId,
stake: Balance,
targets: BoundedVec<AccountId, MaxNominations>,
) -> Self {
VOTERS.with(|v| v.borrow_mut().push((who, stake, targets)));
self
}
pub fn signed_max_submission(self, count: u32) -> Self {
<SignedMaxSubmissions>::set(count);
self
}
pub fn signed_base_deposit(self, base: u64, variable: bool, increase: Percent) -> Self {
<EnableVariableDepositBase>::set(variable);
<SignedFixedDeposit>::set(base);
<SignedDepositIncreaseFactor>::set(increase);
self
}
pub fn signed_deposit(self, base: u64, byte: u64, weight: u64) -> Self {
<SignedFixedDeposit>::set(base);
<SignedDepositByte>::set(byte);
<SignedDepositWeight>::set(weight);
self
}
pub fn signed_weight(self, weight: Weight) -> Self {
<SignedMaxWeight>::set(weight);
self
}
pub fn max_backers_per_winner(self, max: u32) -> Self {
MaxBackersPerWinner::set(max);
self
}
pub fn build(self) -> pezsp_io::TestExternalities {
pezsp_tracing::try_init_simple();
let mut storage =
pezframe_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
let _ = pezpallet_balances::GenesisConfig::<Runtime> {
balances: vec![
// bunch of account for submitting stuff only.
(99, 100),
(100, 100),
(101, 100),
(102, 100),
(103, 100),
(104, 100),
(105, 100),
(999, 100),
(9999, 100),
],
..Default::default()
}
.assimilate_storage(&mut storage);
pezsp_io::TestExternalities::from(storage)
}
pub fn build_offchainify(
self,
iters: u32,
) -> (pezsp_io::TestExternalities, Arc<RwLock<PoolState>>) {
let mut ext = self.build();
let (offchain, offchain_state) = TestOffchainExt::new();
let (pool, pool_state) = TestTransactionPoolExt::new();
let mut seed = [0_u8; 32];
seed[0..4].copy_from_slice(&iters.to_le_bytes());
offchain_state.write().seed = seed;
ext.register_extension(OffchainDbExt::new(offchain.clone()));
ext.register_extension(OffchainWorkerExt::new(offchain));
ext.register_extension(TransactionPoolExt::new(pool));
(ext, pool_state)
}
pub fn build_and_execute(self, test: impl FnOnce() -> ()) {
pezsp_tracing::try_init_simple();
let mut ext = self.build();
ext.execute_with(test);
#[cfg(feature = "try-runtime")]
ext.execute_with(|| {
pezframe_support::assert_ok!(
<MultiPhase as pezframe_support::traits::Hooks<u64>>::try_state(System::block_number())
);
});
}
}
pub(crate) fn balances(who: &AccountId) -> (Balance, Balance) {
(Balances::free_balance(who), Balances::reserved_balance(who))
}
@@ -0,0 +1,265 @@
// This file is part of Bizinikiwi.
// 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.
//! Remote mining tests for Kusama and Pezkuwi.
//!
//! Run like this:
//!
//! ```ignore
//! RUST_LOG=remote-ext=info,runtime::election-provider=debug cargo test --release --features remote-mining -p pezpallet-election-provider-multi-phase mine_for_ -- --test-threads 1
//! ```
//!
//! See the comments below on how to feed specific hash.
use crate::{ElectionCompute, Miner, MinerConfig, RawSolution, RoundSnapshot};
use codec::Decode;
use core::marker::PhantomData;
use pezframe_election_provider_support::generate_solution_type;
use pezframe_support::{
traits::Get,
weights::constants::{WEIGHT_PROOF_SIZE_PER_MB, WEIGHT_REF_TIME_PER_SECOND},
};
use remote_externalities::{Builder, Mode, OnlineConfig, Transport};
use pezsp_core::{ConstU32, H256};
use pezsp_npos_elections::BalancingConfig;
use pezsp_runtime::{Perbill, Weight};
pub mod pezkuwi {
use super::*;
pub struct MinerConfig;
pub struct MaxWeight;
impl Get<Weight> for MaxWeight {
fn get() -> Weight {
Weight::from_parts(
WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
WEIGHT_PROOF_SIZE_PER_MB.saturating_mul(5),
)
}
}
generate_solution_type!(
#[compact]
pub struct PezkuwiSolution::<
VoterIndex = u32,
TargetIndex = u16,
Accuracy = pezsp_runtime::PerU16,
MaxVoters = ConstU32<22_500>,
>(16)
);
/// Some configs are a bit inconsistent, but we don't care about them for now.
impl crate::MinerConfig for MinerConfig {
type AccountId = pezsp_runtime::AccountId32;
type MaxBackersPerWinner = ConstU32<1024>;
type MaxLength = ConstU32<{ 4 * 1024 * 1024 }>;
type MaxVotesPerVoter = ConstU32<16>;
type MaxWeight = MaxWeight;
type MaxWinners = ConstU32<1000>;
type Solution = PezkuwiSolution;
fn solution_weight(
_voters: u32,
_targets: u32,
_active_voters: u32,
_degree: u32,
) -> Weight {
Default::default()
}
}
}
pub mod kusama {
use super::*;
pub struct MinerConfig;
pub struct MaxWeight;
impl Get<Weight> for MaxWeight {
fn get() -> Weight {
Weight::from_parts(
WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2),
WEIGHT_PROOF_SIZE_PER_MB.saturating_mul(5),
)
}
}
generate_solution_type!(
#[compact]
pub struct PezkuwiSolution::<
VoterIndex = u32,
TargetIndex = u16,
Accuracy = pezsp_runtime::PerU16,
MaxVoters = ConstU32<12_500>,
>(24)
);
/// Some configs are a bit inconsistent, but we don't care about them for now.
impl crate::MinerConfig for MinerConfig {
type AccountId = pezsp_runtime::AccountId32;
type MaxBackersPerWinner = ConstU32<1024>;
type MaxLength = ConstU32<{ 4 * 1024 * 1024 }>;
type MaxVotesPerVoter = ConstU32<24>;
type MaxWeight = MaxWeight;
type MaxWinners = ConstU32<1000>;
type Solution = PezkuwiSolution;
fn solution_weight(
_voters: u32,
_targets: u32,
_active_voters: u32,
_degree: u32,
) -> Weight {
Default::default()
}
}
}
pub struct HackyGetSnapshot<T: MinerConfig>(PhantomData<T>);
type UntypedSnapshotOf<T> = RoundSnapshot<
<T as MinerConfig>::AccountId,
pezframe_election_provider_support::Voter<
<T as MinerConfig>::AccountId,
<T as MinerConfig>::MaxVotesPerVoter,
>,
>;
impl<T: MinerConfig> HackyGetSnapshot<T> {
fn snapshot() -> UntypedSnapshotOf<T>
where
UntypedSnapshotOf<T>: Decode,
{
let key = [
pezsp_core::hashing::twox_128(b"ElectionProviderMultiPhase"),
pezsp_core::hashing::twox_128(b"Snapshot"),
]
.concat();
pezframe_support::storage::unhashed::get::<UntypedSnapshotOf<T>>(&key).unwrap()
}
fn desired_targets() -> u32 {
let key = [
pezsp_core::hashing::twox_128(b"ElectionProviderMultiPhase"),
pezsp_core::hashing::twox_128(b"DesiredTargets"),
]
.concat();
pezframe_support::storage::unhashed::get::<u32>(&key).unwrap()
}
}
pub type FakeBlock = pezsp_runtime::testing::Block<pezsp_runtime::testing::TestXt<(), ()>>;
pub struct Balancing;
impl Get<Option<BalancingConfig>> for Balancing {
fn get() -> Option<BalancingConfig> {
Some(BalancingConfig { iterations: 10, tolerance: 0 })
}
}
pub type SolverOf<T> = pezframe_election_provider_support::SequentialPhragmen<
<T as MinerConfig>::AccountId,
Perbill,
Balancing,
>;
fn test_for_network<T: MinerConfig>()
where
UntypedSnapshotOf<T>: Decode,
{
let snapshot = HackyGetSnapshot::<T>::snapshot();
let desired_targets = HackyGetSnapshot::<T>::desired_targets();
let (solution, score, _size, _trimming) =
Miner::<T>::mine_solution_with_snapshot::<SolverOf<T>>(
snapshot.voters.clone(),
snapshot.targets.clone(),
desired_targets,
)
.unwrap();
let raw_solution = RawSolution { round: 0, solution, score };
let _ready_solution = Miner::<T>::feasibility_check(
raw_solution,
ElectionCompute::Signed,
desired_targets,
snapshot,
0,
Default::default(),
)
.unwrap();
}
#[tokio::test]
async fn mine_for_pezkuwi() {
pezsp_tracing::try_init_simple();
// good way to find good block hashes: https://polkadot.subscan.io/event?page=1&time_dimension=date&module=electionprovidermultiphase&event_id=solutionstored
// we are just looking for blocks with snapshot present, that's all.
let block_hash_str = std::option_env!("BLOCK_HASH")
// known good pezkuwi hash
.unwrap_or("047f1f5b1081fdaa72c9224d0ea302553738556758dc53269b1bfe6a069986bb")
.to_string();
let block_hash = H256::from_slice(hex::decode(block_hash_str).unwrap().as_ref());
let online = OnlineConfig {
at: Some(block_hash),
pallets: vec!["ElectionProviderMultiPhase".to_string()],
transport: Transport::from(
std::option_env!("WS").unwrap_or("wss://rpc.pezkuwichain.io").to_string(),
),
..Default::default()
};
let _ = Builder::<FakeBlock>::default()
.mode(Mode::Online(online))
.build()
.await
.unwrap()
.execute_with(|| {
test_for_network::<pezkuwi::MinerConfig>();
});
}
#[tokio::test]
async fn mine_for_kusama() {
pezsp_tracing::try_init_simple();
// good way to find good block hashes: https://kusama.subscan.io/event?page=1&time_dimension=date&module=electionprovidermultiphase&event_id=solutionstored
// we are just looking for blocks with snapshot present, that's all.
let block_hash_str = std::option_env!("BLOCK_HASH")
// known good kusama hash
.unwrap_or("d5d9f5e098fcb41915c85e6695eddc18c0bc4aa4976ad0d9bf5f4713039bca26")
.to_string();
let block_hash = H256::from_slice(hex::decode(block_hash_str).unwrap().as_ref());
let online = OnlineConfig {
at: Some(block_hash),
pallets: vec!["ElectionProviderMultiPhase".to_string()],
transport: Transport::from(
std::option_env!("WS").unwrap_or("wss://rpc.zagros.pezkuwichain.io").to_string(),
),
..Default::default()
};
let _ = Builder::<FakeBlock>::default()
.mode(Mode::Online(online))
.build()
.await
.unwrap()
.execute_with(|| {
test_for_network::<kusama::MinerConfig>();
});
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,480 @@
// This file is part of Bizinikiwi.
// 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.
// This file is part of Bizinikiwi.
// 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.
//! Autogenerated weights for `pezpallet_election_provider_multi_phase`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
// Executed Command:
// frame-omni-bencher
// v1
// benchmark
// pallet
// --extrinsic=*
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
// --pallet=pezpallet_election_provider_multi_phase
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/election-provider-multi-phase/src/weights.rs
// --wasm-execution=compiled
// --steps=50
// --repeat=20
// --heap-pages=4096
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
// --no-storage-info
// --no-min-squares
// --no-median-slopes
// --genesis-builder-policy=none
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
#![allow(dead_code)]
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pezpallet_election_provider_multi_phase`.
pub trait WeightInfo {
fn on_initialize_nothing() -> Weight;
fn on_initialize_open_signed() -> Weight;
fn on_initialize_open_unsigned() -> Weight;
fn finalize_signed_phase_accept_solution() -> Weight;
fn finalize_signed_phase_reject_solution() -> Weight;
fn create_snapshot_internal(v: u32, t: u32, ) -> Weight;
fn elect_queued(a: u32, d: u32, ) -> Weight;
fn submit() -> Weight;
fn submit_unsigned(v: u32, t: u32, a: u32, d: u32, ) -> Weight;
fn feasibility_check(v: u32, t: u32, a: u32, d: u32, ) -> Weight;
}
/// Weights for `pezpallet_election_provider_multi_phase` using the Bizinikiwi node and recommended hardware.
pub struct BizinikiwiWeight<T>(PhantomData<T>);
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn on_initialize_nothing() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `1485`
// Minimum execution time: 1_355_000 picoseconds.
Weight::from_parts(1_403_000, 1485)
.saturating_add(T::DbWeight::get().reads(1_u64))
}
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn on_initialize_open_signed() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `1485`
// Minimum execution time: 5_350_000 picoseconds.
Weight::from_parts(5_507_000, 1485)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn on_initialize_open_unsigned() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `1485`
// Minimum execution time: 6_110_000 picoseconds.
Weight::from_parts(6_312_000, 1485)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `ElectionProviderMultiPhase::QueuedSolution` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::QueuedSolution` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn finalize_signed_phase_accept_solution() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 24_085_000 picoseconds.
Weight::from_parts(24_495_000, 3593)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn finalize_signed_phase_reject_solution() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 16_509_000 picoseconds.
Weight::from_parts(17_064_000, 3593)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `ElectionProviderMultiPhase::SnapshotMetadata` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::SnapshotMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::DesiredTargets` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::DesiredTargets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Snapshot` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::Snapshot` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `v` is `[1000, 2000]`.
/// The range of component `t` is `[500, 1000]`.
fn create_snapshot_internal(v: u32, t: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 502_941_000 picoseconds.
Weight::from_parts(3_670_497, 0)
// Standard Error: 2_331
.saturating_add(Weight::from_parts(417_190, 0).saturating_mul(v.into()))
// Standard Error: 4_660
.saturating_add(Weight::from_parts(123_320, 0).saturating_mul(t.into()))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionIndices` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionIndices` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionNextIndex` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionNextIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SnapshotMetadata` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SnapshotMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionsMap` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionsMap` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::QueuedSolution` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::QueuedSolution` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::DesiredTargets` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::DesiredTargets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Snapshot` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::Snapshot` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `a` is `[500, 800]`.
/// The range of component `d` is `[200, 400]`.
fn elect_queued(a: u32, d: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `204 + a * (768 ±0) + d * (48 ±0)`
// Estimated: `3756 + a * (768 ±0) + d * (49 ±0)`
// Minimum execution time: 351_780_000 picoseconds.
Weight::from_parts(377_006_000, 3756)
// Standard Error: 8_676
.saturating_add(Weight::from_parts(497_086, 0).saturating_mul(a.into()))
.saturating_add(T::DbWeight::get().reads(7_u64))
.saturating_add(T::DbWeight::get().writes(8_u64))
.saturating_add(Weight::from_parts(0, 768).saturating_mul(a.into()))
.saturating_add(Weight::from_parts(0, 49).saturating_mul(d.into()))
}
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SnapshotMetadata` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::SnapshotMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionIndices` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionIndices` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionNextIndex` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionNextIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionsMap` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionsMap` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn submit() -> Weight {
// Proof Size summary in bytes:
// Measured: `683`
// Estimated: `2168`
// Minimum execution time: 40_602_000 picoseconds.
Weight::from_parts(41_937_000, 2168)
.saturating_add(T::DbWeight::get().reads(5_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::DesiredTargets` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::DesiredTargets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::QueuedSolution` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::QueuedSolution` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SnapshotMetadata` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::SnapshotMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Snapshot` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Snapshot` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::MinimumUntrustedScore` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::MinimumUntrustedScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `v` is `[1000, 2000]`.
/// The range of component `t` is `[500, 1000]`.
/// The range of component `a` is `[500, 800]`.
/// The range of component `d` is `[200, 400]`.
fn submit_unsigned(v: u32, t: u32, a: u32, _d: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `89 + t * (32 ±0) + v * (553 ±0)`
// Estimated: `1574 + t * (32 ±0) + v * (553 ±0)`
// Minimum execution time: 5_664_045_000 picoseconds.
Weight::from_parts(5_771_637_000, 1574)
// Standard Error: 18_838
.saturating_add(Weight::from_parts(202_471, 0).saturating_mul(v.into()))
// Standard Error: 55_824
.saturating_add(Weight::from_parts(4_436_597, 0).saturating_mul(a.into()))
.saturating_add(T::DbWeight::get().reads(7_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into()))
.saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into()))
}
/// Storage: `ElectionProviderMultiPhase::DesiredTargets` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::DesiredTargets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Snapshot` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Snapshot` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::MinimumUntrustedScore` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::MinimumUntrustedScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `v` is `[1000, 2000]`.
/// The range of component `t` is `[500, 1000]`.
/// The range of component `a` is `[500, 800]`.
/// The range of component `d` is `[200, 400]`.
fn feasibility_check(v: u32, t: u32, a: u32, _d: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `64 + t * (32 ±0) + v * (553 ±0)`
// Estimated: `1549 + t * (32 ±0) + v * (553 ±0)`
// Minimum execution time: 4_805_475_000 picoseconds.
Weight::from_parts(4_914_697_000, 1549)
// Standard Error: 18_827
.saturating_add(Weight::from_parts(384_743, 0).saturating_mul(v.into()))
// Standard Error: 55_792
.saturating_add(Weight::from_parts(2_733_267, 0).saturating_mul(a.into()))
.saturating_add(T::DbWeight::get().reads(4_u64))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into()))
.saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into()))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn on_initialize_nothing() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `1485`
// Minimum execution time: 1_355_000 picoseconds.
Weight::from_parts(1_403_000, 1485)
.saturating_add(RocksDbWeight::get().reads(1_u64))
}
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn on_initialize_open_signed() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `1485`
// Minimum execution time: 5_350_000 picoseconds.
Weight::from_parts(5_507_000, 1485)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn on_initialize_open_unsigned() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `1485`
// Minimum execution time: 6_110_000 picoseconds.
Weight::from_parts(6_312_000, 1485)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
/// Storage: `ElectionProviderMultiPhase::QueuedSolution` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::QueuedSolution` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
fn finalize_signed_phase_accept_solution() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 24_085_000 picoseconds.
Weight::from_parts(24_495_000, 3593)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: `System::Account` (r:1 w:1)
/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
fn finalize_signed_phase_reject_solution() -> Weight {
// Proof Size summary in bytes:
// Measured: `52`
// Estimated: `3593`
// Minimum execution time: 16_509_000 picoseconds.
Weight::from_parts(17_064_000, 3593)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `ElectionProviderMultiPhase::SnapshotMetadata` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::SnapshotMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::DesiredTargets` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::DesiredTargets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Snapshot` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::Snapshot` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `v` is `[1000, 2000]`.
/// The range of component `t` is `[500, 1000]`.
fn create_snapshot_internal(v: u32, t: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 502_941_000 picoseconds.
Weight::from_parts(3_670_497, 0)
// Standard Error: 2_331
.saturating_add(Weight::from_parts(417_190, 0).saturating_mul(v.into()))
// Standard Error: 4_660
.saturating_add(Weight::from_parts(123_320, 0).saturating_mul(t.into()))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionIndices` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionIndices` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionNextIndex` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionNextIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SnapshotMetadata` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SnapshotMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionsMap` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionsMap` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::QueuedSolution` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::QueuedSolution` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::DesiredTargets` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::DesiredTargets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Snapshot` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::Snapshot` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `a` is `[500, 800]`.
/// The range of component `d` is `[200, 400]`.
fn elect_queued(a: u32, d: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `204 + a * (768 ±0) + d * (48 ±0)`
// Estimated: `3756 + a * (768 ±0) + d * (49 ±0)`
// Minimum execution time: 351_780_000 picoseconds.
Weight::from_parts(377_006_000, 3756)
// Standard Error: 8_676
.saturating_add(Weight::from_parts(497_086, 0).saturating_mul(a.into()))
.saturating_add(RocksDbWeight::get().reads(7_u64))
.saturating_add(RocksDbWeight::get().writes(8_u64))
.saturating_add(Weight::from_parts(0, 768).saturating_mul(a.into()))
.saturating_add(Weight::from_parts(0, 49).saturating_mul(d.into()))
}
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SnapshotMetadata` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::SnapshotMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionIndices` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionIndices` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionNextIndex` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionNextIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SignedSubmissionsMap` (r:0 w:1)
/// Proof: `ElectionProviderMultiPhase::SignedSubmissionsMap` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn submit() -> Weight {
// Proof Size summary in bytes:
// Measured: `683`
// Estimated: `2168`
// Minimum execution time: 40_602_000 picoseconds.
Weight::from_parts(41_937_000, 2168)
.saturating_add(RocksDbWeight::get().reads(5_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
/// Storage: `ElectionProviderMultiPhase::CurrentPhase` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::CurrentPhase` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::DesiredTargets` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::DesiredTargets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::QueuedSolution` (r:1 w:1)
/// Proof: `ElectionProviderMultiPhase::QueuedSolution` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::SnapshotMetadata` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::SnapshotMetadata` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Snapshot` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Snapshot` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::MinimumUntrustedScore` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::MinimumUntrustedScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `v` is `[1000, 2000]`.
/// The range of component `t` is `[500, 1000]`.
/// The range of component `a` is `[500, 800]`.
/// The range of component `d` is `[200, 400]`.
fn submit_unsigned(v: u32, t: u32, a: u32, _d: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `89 + t * (32 ±0) + v * (553 ±0)`
// Estimated: `1574 + t * (32 ±0) + v * (553 ±0)`
// Minimum execution time: 5_664_045_000 picoseconds.
Weight::from_parts(5_771_637_000, 1574)
// Standard Error: 18_838
.saturating_add(Weight::from_parts(202_471, 0).saturating_mul(v.into()))
// Standard Error: 55_824
.saturating_add(Weight::from_parts(4_436_597, 0).saturating_mul(a.into()))
.saturating_add(RocksDbWeight::get().reads(7_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into()))
.saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into()))
}
/// Storage: `ElectionProviderMultiPhase::DesiredTargets` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::DesiredTargets` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Snapshot` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Snapshot` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::Round` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::Round` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `ElectionProviderMultiPhase::MinimumUntrustedScore` (r:1 w:0)
/// Proof: `ElectionProviderMultiPhase::MinimumUntrustedScore` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// The range of component `v` is `[1000, 2000]`.
/// The range of component `t` is `[500, 1000]`.
/// The range of component `a` is `[500, 800]`.
/// The range of component `d` is `[200, 400]`.
fn feasibility_check(v: u32, t: u32, a: u32, _d: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `64 + t * (32 ±0) + v * (553 ±0)`
// Estimated: `1549 + t * (32 ±0) + v * (553 ±0)`
// Minimum execution time: 4_805_475_000 picoseconds.
Weight::from_parts(4_914_697_000, 1549)
// Standard Error: 18_827
.saturating_add(Weight::from_parts(384_743, 0).saturating_mul(v.into()))
// Standard Error: 55_792
.saturating_add(Weight::from_parts(2_733_267, 0).saturating_mul(a.into()))
.saturating_add(RocksDbWeight::get().reads(4_u64))
.saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into()))
.saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into()))
}
}
@@ -0,0 +1,77 @@
[package]
name = "pezpallet-election-provider-e2e-test"
version = "1.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "FRAME election provider multi phase pallet tests with staking pallet, bags-list and session pallets"
publish = false
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dev-dependencies]
codec = { features = ["derive"], workspace = true, default-features = true }
log = { workspace = true }
parking_lot = { workspace = true, default-features = true }
scale-info = { features = [
"derive",
], workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-io = { workspace = true, default-features = true }
pezsp-npos-elections = { workspace = true }
pezsp-runtime = { workspace = true, default-features = true }
pezsp-staking = { workspace = true, default-features = true }
pezsp-tracing = { workspace = true, default-features = true }
pezframe-election-provider-support = { workspace = true, default-features = true }
pezframe-support = { workspace = true, default-features = true }
pezframe-system = { workspace = true, default-features = true }
pezpallet-bags-list = { workspace = true, default-features = true }
pezpallet-balances = { workspace = true, default-features = true }
pezpallet-delegated-staking = { workspace = true, default-features = true }
pezpallet-election-provider-multi-phase = { workspace = true, default-features = true }
pezpallet-nomination-pools = { workspace = true, default-features = true }
pezpallet-session = { workspace = true, default-features = true }
pezpallet-staking = { workspace = true, default-features = true }
pezpallet-timestamp = { workspace = true, default-features = true }
[features]
try-runtime = [
"pezframe-election-provider-support/try-runtime",
"pezframe-support/try-runtime",
"pezframe-system/try-runtime",
"pezpallet-bags-list/try-runtime",
"pezpallet-balances/try-runtime",
"pezpallet-delegated-staking/try-runtime",
"pezpallet-election-provider-multi-phase/try-runtime",
"pezpallet-nomination-pools/try-runtime",
"pezpallet-session/try-runtime",
"pezpallet-staking/try-runtime",
"pezpallet-timestamp/try-runtime",
"pezsp-runtime/try-runtime",
]
runtime-benchmarks = [
"pezframe-election-provider-support/runtime-benchmarks",
"pezframe-support/runtime-benchmarks",
"pezframe-system/runtime-benchmarks",
"pezpallet-bags-list/runtime-benchmarks",
"pezpallet-balances/runtime-benchmarks",
"pezpallet-delegated-staking/runtime-benchmarks",
"pezpallet-election-provider-multi-phase/runtime-benchmarks",
"pezpallet-nomination-pools/runtime-benchmarks",
"pezpallet-session/runtime-benchmarks",
"pezpallet-staking/runtime-benchmarks",
"pezpallet-timestamp/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-npos-elections/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-staking/runtime-benchmarks",
]
@@ -0,0 +1,431 @@
// This file is part of Bizinikiwi.
// 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.
#![cfg(test)]
// We do not declare all features used by `construct_runtime`
#[allow(unexpected_cfgs)]
mod mock;
pub(crate) const LOG_TARGET: &str = "tests::e2e-epm";
use pezframe_support::{assert_err, assert_ok};
use mock::*;
use pezpallet_timestamp::Now;
use pezsp_core::Get;
use pezsp_runtime::Perbill;
use crate::mock::RuntimeOrigin;
use pezpallet_election_provider_multi_phase::CurrentPhase;
// syntactic sugar for logging.
#[macro_export]
macro_rules! log {
($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
log::$level!(
target: crate::LOG_TARGET,
concat!("🛠️ ", $patter) $(, $values)*
)
};
}
fn log_current_time() {
log!(
trace,
"block: {:?}, session: {:?}, era: {:?}, EPM phase: {:?} ts: {:?}",
System::block_number(),
Session::current_index(),
pezpallet_staking::CurrentEra::<Runtime>::get(),
CurrentPhase::<Runtime>::get(),
Now::<Runtime>::get()
);
}
#[test]
fn block_progression_works() {
let (ext, pool_state, _) = ExtBuilder::default().build_offchainify();
execute_with(ext, || {
assert_eq!(active_era(), 0);
assert_eq!(Session::current_index(), 0);
assert!(CurrentPhase::<Runtime>::get().is_off());
assert!(start_next_active_era(pool_state.clone()).is_ok());
assert_eq!(active_era(), 1);
assert_eq!(Session::current_index(), <SessionsPerEra as Get<u32>>::get());
assert!(CurrentPhase::<Runtime>::get().is_off());
roll_to_epm_signed();
assert!(CurrentPhase::<Runtime>::get().is_signed());
});
let (ext, pool_state, _) = ExtBuilder::default().build_offchainify();
execute_with(ext, || {
assert_eq!(active_era(), 0);
assert_eq!(Session::current_index(), 0);
assert!(CurrentPhase::<Runtime>::get().is_off());
assert!(start_next_active_era_delayed_solution(pool_state).is_ok());
// if the solution is delayed, EPM will end up in emergency mode..
assert!(CurrentPhase::<Runtime>::get().is_emergency());
// .. era won't progress..
assert_eq!(active_era(), 0);
// .. but session does.
assert_eq!(Session::current_index(), 2);
})
}
#[test]
fn offchainify_works() {
use pezpallet_election_provider_multi_phase::QueuedSolution;
let staking_builder = StakingExtBuilder::default();
let epm_builder = EpmExtBuilder::default();
let (ext, pool_state, _) = ExtBuilder::default()
.epm(epm_builder)
.staking(staking_builder)
.build_offchainify();
execute_with(ext, || {
// test ocw progression and solution queue if submission when unsigned phase submission is
// not delayed.
for _ in 0..100 {
roll_one(pool_state.clone(), false);
let current_phase = CurrentPhase::<Runtime>::get();
assert!(
match QueuedSolution::<Runtime>::get() {
Some(_) => current_phase.is_unsigned(),
None => !current_phase.is_unsigned(),
},
"solution must be queued *only* in unsigned phase"
);
}
// test ocw solution queue if submission in unsigned phase is delayed.
for _ in 0..100 {
roll_one(pool_state.clone(), true);
assert_eq!(
QueuedSolution::<Runtime>::get(),
None,
"solution must never be submitted and stored since it is delayed"
);
}
})
}
#[test]
/// Inspired by the Kusama incident of 8th Dec 2022 and its resolution through the governance
/// fallback.
///
/// Mass slash of validators shouldn't disable more than 1/3 of them (the byzantine threshold). Also
/// no new era should be forced which could lead to EPM entering emergency mode.
fn mass_slash_doesnt_enter_emergency_phase() {
let epm_builder = EpmExtBuilder::default().disable_emergency_throttling();
let staking_builder = StakingExtBuilder::default().validator_count(7);
let (mut ext, _, _) = ExtBuilder::default()
.epm(epm_builder)
.staking(staking_builder)
.build_offchainify();
ext.execute_with(|| {
assert_eq!(pezpallet_staking::ForceEra::<Runtime>::get(), pezpallet_staking::Forcing::NotForcing);
let active_set_size_before_slash = Session::validators().len();
// assuming half is above the disabling limit (default 1/3), otherwise test will break
let slashed = slash_half_the_active_set();
let active_set_size_after_slash = Session::validators().len();
// active set should stay the same before and after the slash
assert_eq!(active_set_size_before_slash, active_set_size_after_slash);
// Find the indices of the disabled validators
let active_set = Session::validators();
let potentially_disabled = slashed
.into_iter()
.map(|d| active_set.iter().position(|a| *a == d).unwrap() as u32)
.collect::<Vec<_>>();
// Ensure that every actually disabled validator is also in the potentially disabled set
// (not necessarily the other way around)
let disabled = Session::disabled_validators();
for d in disabled.iter() {
assert!(potentially_disabled.contains(d));
}
// Ensure no more than disabling limit of validators (default 1/3) is disabled
let disabling_limit = pezpallet_session::disabling::UpToLimitWithReEnablingDisablingStrategy::<
SLASHING_DISABLING_FACTOR,
>::disable_limit(active_set_size_before_slash);
assert!(disabled.len() == disabling_limit);
assert_eq!(pezpallet_staking::ForceEra::<Runtime>::get(), pezpallet_staking::Forcing::NotForcing);
});
}
#[test]
/// Continuously slash 10% of the active validators per era.
///
/// Since the `OffendingValidatorsThreshold` is only checked per era staking does not force a new
/// era even as the number of active validators is decreasing across eras. When processing a new
/// slash, staking calculates the offending threshold based on the length of the current list of
/// active validators. Thus, slashing a percentage of the current validators that is lower than
/// `OffendingValidatorsThreshold` will never force a new era. However, as the slashes progress, if
/// the subsequent elections do not meet the minimum election untrusted score, the election will
/// fail and enter in emergency mode.
fn continuous_slashes_below_offending_threshold() {
let staking_builder = StakingExtBuilder::default().validator_count(10);
let epm_builder = EpmExtBuilder::default().disable_emergency_throttling();
let (ext, pool_state, _) = ExtBuilder::default()
.epm(epm_builder)
.staking(staking_builder)
.build_offchainify();
execute_with(ext, || {
assert_eq!(Session::validators().len(), 10);
let mut active_validator_set = Session::validators();
roll_to_epm_signed();
// set a minimum election score.
assert!(set_minimum_election_score(500, 1000, 500).is_ok());
// slash 10% of the active validators and progress era until the minimum trusted score
// is reached.
while active_validator_set.len() > 0 {
let slashed = slash_percentage(Perbill::from_percent(10));
assert_eq!(slashed.len(), 1);
// break loop when era does not progress; EPM is in emergency phase as election
// failed due to election minimum score.
if start_next_active_era(pool_state.clone()).is_err() {
assert!(CurrentPhase::<Runtime>::get().is_emergency());
break;
}
active_validator_set = Session::validators();
log!(
trace,
"slashed 10% of active validators ({:?}). After slash: {:?}",
slashed,
active_validator_set
);
}
});
}
#[test]
/// Active ledger balance may fall below ED if account chills before unbounding.
///
/// Unbonding call fails if the remaining ledger's stash balance falls below the existential
/// deposit. However, if the stash is chilled before unbonding, the ledger's active balance may
/// be below ED. In that case, only the stash (or root) can kill the ledger entry by calling
/// `withdraw_unbonded` after the bonding period has passed.
///
/// Related to <https://github.com/pezkuwichain/kurdistan-sdk/issues/16>.
fn ledger_consistency_active_balance_below_ed() {
use pezpallet_staking::{Error, Event};
let (ext, pool_state, _) =
ExtBuilder::default().staking(StakingExtBuilder::default()).build_offchainify();
execute_with(ext, || {
assert_eq!(Staking::ledger(11.into()).unwrap().active, 1000);
// unbonding total of active stake passes because chill occurs implicitly when unbonding
// full amount.
assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000));
// the active balance of the ledger entry is 0, while total balance is 1000 until
// `withdraw_unbonded` is called.
assert_eq!(Staking::ledger(11.into()).unwrap().active, 0);
assert_eq!(Staking::ledger(11.into()).unwrap().total, 1000);
// trying to withdraw the unbonded balance won't work yet because not enough bonding
// eras have passed.
assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0));
assert_eq!(Staking::ledger(11.into()).unwrap().total, 1000);
// tries to reap stash after chilling, which fails since the stash total balance is
// above ED.
assert_err!(
Staking::reap_stash(RuntimeOrigin::signed(11), 21, 0),
Error::<Runtime>::FundedTarget,
);
// check the events so far: 1x Chilled and 1x Unbounded
assert_eq!(
staking_events(),
[Event::Chilled { stash: 11 }, Event::Unbonded { stash: 11, amount: 1000 }]
);
// after advancing `BondingDuration` eras, the `withdraw_unbonded` will unlock the
// chunks and the ledger entry will be cleared, since the ledger active balance is 0.
advance_eras(
<Runtime as pezpallet_staking::Config>::BondingDuration::get() as usize,
pool_state,
);
assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0));
assert!(Staking::ledger(11.into()).is_err());
});
}
#[test]
/// Automatic withdrawal of unlocking funds in staking propagates to the nomination pools and its
/// state correctly.
///
/// The staking pallet may withdraw unlocking funds from a pool's bonded account without a pool
/// member or operator calling explicitly `Call::withdraw*`. This test verifies that the member's
/// are eventually paid and the `TotalValueLocked` is kept in sync in those cases.
fn automatic_unbonding_pools() {
use pezpallet_nomination_pools::TotalValueLocked;
// closure to fetch the staking unlocking chunks of an account.
let unlocking_chunks_of = |account: AccountId| -> usize {
Staking::ledger(pezsp_staking::StakingAccount::Controller(account))
.unwrap()
.unlocking
.len()
};
let (ext, pool_state, _) = ExtBuilder::default()
.pools(PoolsExtBuilder::default().max_unbonding(1))
.staking(StakingExtBuilder::default().max_unlocking(1).bonding_duration(2))
.build_offchainify();
execute_with(ext, || {
assert_eq!(<Runtime as pezpallet_staking::Config>::MaxUnlockingChunks::get(), 1);
assert_eq!(<Runtime as pezpallet_staking::Config>::BondingDuration::get(), 2);
assert_eq!(<Runtime as pezpallet_nomination_pools::Config>::MaxUnbonding::get(), 1);
// init state of pool members.
let init_free_balance_2 = Balances::free_balance(2);
let init_free_balance_3 = Balances::free_balance(3);
let pool_bonded_account = Pools::generate_bonded_account(1);
// creates a pool with 5 bonded, owned by 1.
assert_ok!(Pools::create(RuntimeOrigin::signed(1), 5, 1, 1, 1));
assert_eq!(staked_amount_for(pool_bonded_account), 5);
let init_tvl = TotalValueLocked::<Runtime>::get();
// 2 joins the pool.
assert_ok!(Pools::join(RuntimeOrigin::signed(2), 10, 1));
assert_eq!(staked_amount_for(pool_bonded_account), 15);
// 3 joins the pool.
assert_ok!(Pools::join(RuntimeOrigin::signed(3), 10, 1));
assert_eq!(staked_amount_for(pool_bonded_account), 25);
assert_eq!(TotalValueLocked::<Runtime>::get(), 25);
// currently unlocking 0 chunks in the bonded pools ledger.
assert_eq!(unlocking_chunks_of(pool_bonded_account), 0);
// unbond 2 from pool.
assert_ok!(Pools::unbond(RuntimeOrigin::signed(2), 2, 10));
// amount is still locked in the pool, needs to wait for unbonding period.
assert_eq!(staked_amount_for(pool_bonded_account), 25);
// max chunks in the ledger are now filled up (`MaxUnlockingChunks == 1`).
assert_eq!(unlocking_chunks_of(pool_bonded_account), 1);
// tries to unbond 3 from pool. it will fail since there are no unlocking chunks left
// available and the current in the queue haven't been there for more than bonding
// duration.
assert_err!(
Pools::unbond(RuntimeOrigin::signed(3), 3, 10),
pezpallet_staking::Error::<Runtime>::NoMoreChunks
);
assert_eq!(current_era(), 0);
// progress over bonding duration.
for _ in 0..=<Runtime as pezpallet_staking::Config>::BondingDuration::get() {
start_next_active_era(pool_state.clone()).unwrap();
}
assert_eq!(current_era(), 3);
System::reset_events();
let staked_before_withdraw_pool = staked_amount_for(pool_bonded_account);
assert_eq!(delegated_balance_for(pool_bonded_account), 5 + 10 + 10);
// now unbonding 3 will work, although the pool's ledger still has the unlocking chunks
// filled up.
assert_ok!(Pools::unbond(RuntimeOrigin::signed(3), 3, 10));
assert_eq!(unlocking_chunks_of(pool_bonded_account), 1);
assert_eq!(
staking_events(),
[
// auto-withdraw happened as expected to release 2's unbonding funds, but the funds
// were not transferred to 2 and stay in the pool's transferrable balance instead.
pezpallet_staking::Event::Withdrawn { stash: pool_bonded_account, amount: 10 },
pezpallet_staking::Event::Unbonded { stash: pool_bonded_account, amount: 10 }
]
);
// balance of the pool remains the same, it hasn't withdraw explicitly from the pool yet.
assert_eq!(delegated_balance_for(pool_bonded_account), 25);
// but the locked amount in the pool's account decreases due to the auto-withdraw:
assert_eq!(staked_before_withdraw_pool - 10, staked_amount_for(pool_bonded_account));
// TVL correctly updated.
assert_eq!(TotalValueLocked::<Runtime>::get(), 25 - 10);
// however, note that the withdrawing from the pool still works for 2, the funds are taken
// from the pool's non staked balance.
assert_eq!(delegated_balance_for(pool_bonded_account), 25);
assert_eq!(staked_amount_for(pool_bonded_account), 15);
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(2), 2, 10));
assert_eq!(delegated_balance_for(pool_bonded_account), 15);
assert_eq!(Balances::free_balance(2), 20);
assert_eq!(TotalValueLocked::<Runtime>::get(), 15);
// 3 cannot withdraw yet.
assert_err!(
Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10),
pezpallet_nomination_pools::Error::<Runtime>::CannotWithdrawAny
);
// progress over bonding duration.
for _ in 0..=<Runtime as pezpallet_staking::Config>::BondingDuration::get() {
start_next_active_era(pool_state.clone()).unwrap();
}
assert_eq!(current_era(), 6);
System::reset_events();
assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(3), 3, 10));
// final conditions are the expected.
assert_eq!(delegated_balance_for(pool_bonded_account), 5); // 5 init bonded
assert_eq!(Balances::free_balance(2), init_free_balance_2);
assert_eq!(Balances::free_balance(3), init_free_balance_3);
assert_eq!(TotalValueLocked::<Runtime>::get(), init_tvl);
});
}
@@ -0,0 +1,992 @@
// This file is part of Bizinikiwi.
// 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.
#![allow(dead_code)]
use pezframe_support::{
assert_ok, parameter_types, traits,
traits::{Hooks, UnfilteredDispatchable, VariantCountOf},
weights::constants,
PalletId,
};
use pezframe_system::EnsureRoot;
use pezsp_core::{ConstBool, ConstU32, Get};
use pezsp_npos_elections::{ElectionScore, VoteWeight};
use pezsp_runtime::{
offchain::{
testing::{OffchainState, PoolState, TestOffchainExt, TestTransactionPoolExt},
OffchainDbExt, OffchainWorkerExt, TransactionPoolExt,
},
testing,
traits::Zero,
transaction_validity, BuildStorage, PerU16, Perbill, Percent,
};
use pezsp_staking::{
offence::{OffenceDetails, OnOffenceHandler},
Agent, DelegationInterface, EraIndex, SessionIndex, StakingInterface,
};
use std::collections::BTreeMap;
use codec::Decode;
use pezframe_election_provider_support::{
bounds::ElectionBoundsBuilder, onchain, ElectionDataProvider, ExtendedBalance,
SequentialPhragmen, Weight,
};
use pezpallet_election_provider_multi_phase::{
unsigned::MinerConfig, Call, CurrentPhase, ElectionCompute, GeometricDepositBase,
QueuedSolution, SolutionAccuracyOf,
};
use pezpallet_staking::{ActiveEra, CurrentEra, ErasStartSessionIndex, StakerStatus};
use parking_lot::RwLock;
use std::sync::Arc;
use crate::{log, log_current_time};
use pezframe_support::{derive_impl, traits::Nothing};
pub const INIT_TIMESTAMP: BlockNumber = 30_000;
pub const BLOCK_TIME: BlockNumber = 1000;
type Block = pezframe_system::mocking::MockBlockU32<Runtime>;
type Extrinsic = pezsp_runtime::testing::TestXt<RuntimeCall, ()>;
pezframe_support::construct_runtime!(
pub enum Runtime {
System: pezframe_system,
ElectionProviderMultiPhase: pezpallet_election_provider_multi_phase,
Staking: pezpallet_staking,
DelegatedStaking: pezpallet_delegated_staking,
Pools: pezpallet_nomination_pools,
Balances: pezpallet_balances,
BagsList: pezpallet_bags_list,
Session: pezpallet_session,
Historical: pezpallet_session::historical,
Timestamp: pezpallet_timestamp,
}
);
pub(crate) type AccountId = u128;
pub(crate) type AccountIndex = u32;
pub(crate) type BlockNumber = u32;
pub(crate) type Balance = u64;
pub(crate) type VoterIndex = u16;
pub(crate) type TargetIndex = u16;
pub(crate) type Moment = u32;
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Runtime {
type AccountId = AccountId;
type Block = Block;
type AccountData = pezpallet_balances::AccountData<Balance>;
type Lookup = pezsp_runtime::traits::IdentityLookup<Self::AccountId>;
}
const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75);
parameter_types! {
pub static ExistentialDeposit: Balance = 1;
pub BlockWeights: pezframe_system::limits::BlockWeights = pezframe_system::limits::BlockWeights
::with_sensible_defaults(
Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX),
NORMAL_DISPATCH_RATIO,
);
}
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Runtime {
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type MaxFreezes = VariantCountOf<RuntimeFreezeReason>;
type RuntimeHoldReason = RuntimeHoldReason;
type RuntimeFreezeReason = RuntimeFreezeReason;
type FreezeIdentifier = RuntimeFreezeReason;
}
impl pezpallet_timestamp::Config for Runtime {
type Moment = Moment;
type OnTimestampSet = ();
type MinimumPeriod = traits::ConstU32<5>;
type WeightInfo = ();
}
parameter_types! {
pub static Period: u32 = 30;
pub static Offset: u32 = 0;
}
pezsp_runtime::impl_opaque_keys! {
pub struct SessionKeys {
pub other: OtherSessionHandler,
}
}
impl pezpallet_session::Config for Runtime {
type SessionManager = pezpallet_session::historical::NoteHistoricalRoot<Runtime, Staking>;
type Keys = SessionKeys;
type ShouldEndSession = pezpallet_session::PeriodicSessions<Period, Offset>;
type NextSessionRotation = pezpallet_session::PeriodicSessions<Period, Offset>;
type SessionHandler = (OtherSessionHandler,);
type RuntimeEvent = RuntimeEvent;
type ValidatorId = AccountId;
type ValidatorIdOf = pezsp_runtime::traits::ConvertInto;
type DisablingStrategy = pezpallet_session::disabling::UpToLimitWithReEnablingDisablingStrategy<
SLASHING_DISABLING_FACTOR,
>;
type WeightInfo = ();
type Currency = Balances;
type KeyDeposit = ();
}
impl pezpallet_session::historical::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type FullIdentification = ();
type FullIdentificationOf = pezpallet_staking::UnitIdentificationOf<Self>;
}
pezframe_election_provider_support::generate_solution_type!(
#[compact]
pub struct MockNposSolution::<
VoterIndex = VoterIndex,
TargetIndex = TargetIndex,
Accuracy = PerU16,
MaxVoters = ConstU32::<2_000>
>(16)
);
parameter_types! {
pub static SignedPhase: BlockNumber = 10;
pub static UnsignedPhase: BlockNumber = 10;
// we expect a minimum of 3 blocks in signed phase and unsigned phases before trying
// entering in emergency phase after the election failed.
pub static MinBlocksBeforeEmergency: BlockNumber = 3;
pub static MaxActiveValidators: u32 = 1000;
pub static OffchainRepeat: u32 = 5;
pub static MinerMaxLength: u32 = 256;
pub static MinerMaxWeight: Weight = BlockWeights::get().max_block;
pub static TransactionPriority: transaction_validity::TransactionPriority = 1;
#[derive(Debug)]
pub static MaxWinners: u32 = 100;
#[derive(Debug)]
pub static MaxBackersPerWinner: u32 = 100;
pub static MaxVotesPerVoter: u32 = 16;
pub static SignedFixedDeposit: Balance = 1;
pub static SignedDepositIncreaseFactor: Percent = Percent::from_percent(10);
pub static ElectionBounds: pezframe_election_provider_support::bounds::ElectionBounds = ElectionBoundsBuilder::default()
.voters_count(1_000.into()).targets_count(1_000.into()).build();
}
impl pezpallet_election_provider_multi_phase::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type EstimateCallFee = pezframe_support::traits::ConstU64<8>;
type SignedPhase = SignedPhase;
type UnsignedPhase = UnsignedPhase;
type BetterSignedThreshold = ();
type OffchainRepeat = OffchainRepeat;
type MinerTxPriority = TransactionPriority;
type MinerConfig = Self;
type SignedMaxSubmissions = ConstU32<10>;
type SignedRewardBase = ();
type SignedDepositBase =
GeometricDepositBase<Balance, SignedFixedDeposit, SignedDepositIncreaseFactor>;
type SignedDepositByte = ();
type SignedMaxRefunds = ConstU32<3>;
type SignedDepositWeight = ();
type SignedMaxWeight = ();
type SlashHandler = ();
type RewardHandler = ();
type DataProvider = Staking;
type Fallback = pezframe_election_provider_support::NoElection<(
AccountId,
BlockNumber,
Staking,
MaxWinners,
MaxBackersPerWinner,
)>;
type GovernanceFallback = onchain::OnChainExecution<OnChainSeqPhragmen>;
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, ()>;
type ForceOrigin = EnsureRoot<AccountId>;
type MaxWinners = MaxWinners;
type MaxBackersPerWinner = MaxBackersPerWinner;
type ElectionBounds = ElectionBounds;
type BenchmarkingConfig = NoopElectionProviderBenchmarkConfig;
type WeightInfo = ();
}
impl MinerConfig for Runtime {
type AccountId = AccountId;
type Solution = MockNposSolution;
type MaxVotesPerVoter =
<<Self as pezpallet_election_provider_multi_phase::Config>::DataProvider as ElectionDataProvider>::MaxVotesPerVoter;
type MaxLength = MinerMaxLength;
type MaxWeight = MinerMaxWeight;
type MaxWinners = MaxWinners;
type MaxBackersPerWinner = MaxBackersPerWinner;
fn solution_weight(_v: u32, _t: u32, _a: u32, _d: u32) -> Weight {
Weight::zero()
}
}
const THRESHOLDS: [VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
parameter_types! {
pub static BagThresholds: &'static [pezsp_npos_elections::VoteWeight] = &THRESHOLDS;
pub const SessionsPerEra: pezsp_staking::SessionIndex = 2;
pub static BondingDuration: pezsp_staking::EraIndex = 28;
pub const SlashDeferDuration: pezsp_staking::EraIndex = 7; // 1/4 the bonding duration.
}
impl pezpallet_bags_list::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type ScoreProvider = Staking;
type BagThresholds = BagThresholds;
type Score = VoteWeight;
type MaxAutoRebagPerBlock = ();
}
pub struct BalanceToU256;
impl pezsp_runtime::traits::Convert<Balance, pezsp_core::U256> for BalanceToU256 {
fn convert(n: Balance) -> pezsp_core::U256 {
n.into()
}
}
pub struct U256ToBalance;
impl pezsp_runtime::traits::Convert<pezsp_core::U256, Balance> for U256ToBalance {
fn convert(n: pezsp_core::U256) -> Balance {
n.try_into().unwrap()
}
}
parameter_types! {
pub const PoolsPalletId: pezframe_support::PalletId = pezframe_support::PalletId(*b"py/nopls");
pub static MaxUnbonding: u32 = 8;
}
impl pezpallet_nomination_pools::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type Currency = Balances;
type RuntimeFreezeReason = RuntimeFreezeReason;
type RewardCounter = pezsp_runtime::FixedU128;
type BalanceToU256 = BalanceToU256;
type U256ToBalance = U256ToBalance;
type StakeAdapter =
pezpallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>;
type PostUnbondingPoolsWindow = ConstU32<2>;
type PalletId = PoolsPalletId;
type MaxMetadataLen = ConstU32<256>;
type MaxUnbonding = MaxUnbonding;
type MaxPointsToBalance = pezframe_support::traits::ConstU8<10>;
type AdminOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
type BlockNumberProvider = System;
type Filter = Nothing;
}
parameter_types! {
pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
}
impl pezpallet_delegated_staking::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type PalletId = DelegatedStakingPalletId;
type Currency = Balances;
type OnSlash = ();
type SlashRewardFraction = SlashRewardFraction;
type RuntimeHoldReason = RuntimeHoldReason;
type CoreStaking = Staking;
}
parameter_types! {
pub static MaxUnlockingChunks: u32 = 32;
}
/// Upper limit on the number of NPOS nominations.
const MAX_QUOTA_NOMINATIONS: u32 = 16;
/// Disabling factor set explicitly to byzantine threshold
pub(crate) const SLASHING_DISABLING_FACTOR: usize = 3;
#[derive_impl(pezpallet_staking::config_preludes::TestDefaultConfig)]
impl pezpallet_staking::Config for Runtime {
type OldCurrency = Balances;
type Currency = Balances;
type CurrencyBalance = Balance;
type UnixTime = Timestamp;
type SessionsPerEra = SessionsPerEra;
type BondingDuration = BondingDuration;
type SlashDeferDuration = SlashDeferDuration;
type AdminOrigin = EnsureRoot<AccountId>; // root can cancel slashes
type SessionInterface = Self;
type EraPayout = ();
type NextNewSession = Session;
type MaxExposurePageSize = ConstU32<256>;
type ElectionProvider = ElectionProviderMultiPhase;
type GenesisElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
type VoterList = BagsList;
type NominationsQuota = pezpallet_staking::FixedNominationsQuota<MAX_QUOTA_NOMINATIONS>;
type TargetList = pezpallet_staking::UseValidatorsMap<Self>;
type MaxUnlockingChunks = MaxUnlockingChunks;
type EventListeners = (Pools, DelegatedStaking);
type WeightInfo = pezpallet_staking::weights::BizinikiwiWeight<Runtime>;
type BenchmarkingConfig = pezpallet_staking::TestBenchmarkingConfig;
}
impl<LocalCall> pezframe_system::offchain::CreateTransactionBase<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
type RuntimeCall = RuntimeCall;
type Extrinsic = Extrinsic;
}
impl<LocalCall> pezframe_system::offchain::CreateBare<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
Extrinsic::new_bare(call)
}
}
pub struct OnChainSeqPhragmen;
parameter_types! {
pub static VotersBound: u32 = 600;
pub static TargetsBound: u32 = 400;
}
impl onchain::Config for OnChainSeqPhragmen {
type MaxWinnersPerPage = MaxWinners;
type MaxBackersPerWinner = MaxBackersPerWinner;
type Sort = ConstBool<true>;
type System = Runtime;
type Solver = SequentialPhragmen<
AccountId,
pezpallet_election_provider_multi_phase::SolutionAccuracyOf<Runtime>,
>;
type DataProvider = Staking;
type WeightInfo = ();
type Bounds = ElectionBounds;
}
pub struct NoopElectionProviderBenchmarkConfig;
impl pezpallet_election_provider_multi_phase::BenchmarkingConfig
for NoopElectionProviderBenchmarkConfig
{
const VOTERS: [u32; 2] = [0, 0];
const TARGETS: [u32; 2] = [0, 0];
const ACTIVE_VOTERS: [u32; 2] = [0, 0];
const DESIRED_TARGETS: [u32; 2] = [0, 0];
const SNAPSHOT_MAXIMUM_VOTERS: u32 = 0;
const MINER_MAXIMUM_VOTERS: u32 = 0;
const MAXIMUM_TARGETS: u32 = 0;
}
pub struct OtherSessionHandler;
impl traits::OneSessionHandler<AccountId> for OtherSessionHandler {
type Key = testing::UintAuthorityId;
fn on_genesis_session<'a, I: 'a>(_: I)
where
I: Iterator<Item = (&'a AccountId, Self::Key)>,
AccountId: 'a,
{
}
fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I)
where
I: Iterator<Item = (&'a AccountId, Self::Key)>,
AccountId: 'a,
{
}
fn on_disabled(_validator_index: u32) {}
}
impl pezsp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler {
type Public = testing::UintAuthorityId;
}
pub struct StakingExtBuilder {
validator_count: u32,
minimum_validator_count: u32,
min_nominator_bond: Balance,
min_validator_bond: Balance,
status: BTreeMap<AccountId, StakerStatus<AccountId>>,
stakes: BTreeMap<AccountId, Balance>,
stakers: Vec<(AccountId, AccountId, Balance, StakerStatus<AccountId>)>,
}
impl Default for StakingExtBuilder {
fn default() -> Self {
let stakers = vec![
// (stash, ctrl, stake, status)
// these two will be elected in the default test where we elect 2.
(11, 11, 1000, StakerStatus::<AccountId>::Validator),
(21, 21, 1000, StakerStatus::<AccountId>::Validator),
// loser validators if validator_count() is default.
(31, 31, 500, StakerStatus::<AccountId>::Validator),
(41, 41, 1500, StakerStatus::<AccountId>::Validator),
(51, 51, 1500, StakerStatus::<AccountId>::Validator),
(61, 61, 1500, StakerStatus::<AccountId>::Validator),
(71, 71, 1500, StakerStatus::<AccountId>::Validator),
(81, 81, 1500, StakerStatus::<AccountId>::Validator),
(91, 91, 1500, StakerStatus::<AccountId>::Validator),
(101, 101, 500, StakerStatus::<AccountId>::Validator),
// an idle validator
(201, 201, 1000, StakerStatus::<AccountId>::Idle),
];
Self {
validator_count: 2,
minimum_validator_count: 0,
min_nominator_bond: ExistentialDeposit::get(),
min_validator_bond: ExistentialDeposit::get(),
status: Default::default(),
stakes: Default::default(),
stakers,
}
}
}
impl StakingExtBuilder {
pub fn validator_count(mut self, n: u32) -> Self {
self.validator_count = n;
self
}
pub fn max_unlocking(self, max: u32) -> Self {
<MaxUnlockingChunks>::set(max);
self
}
pub fn bonding_duration(self, eras: EraIndex) -> Self {
<BondingDuration>::set(eras);
self
}
}
pub struct EpmExtBuilder {}
impl Default for EpmExtBuilder {
fn default() -> Self {
EpmExtBuilder {}
}
}
impl EpmExtBuilder {
pub fn disable_emergency_throttling(self) -> Self {
<MinBlocksBeforeEmergency>::set(0);
self
}
pub fn phases(self, signed: BlockNumber, unsigned: BlockNumber) -> Self {
<SignedPhase>::set(signed);
<UnsignedPhase>::set(unsigned);
self
}
}
pub struct PoolsExtBuilder {}
impl Default for PoolsExtBuilder {
fn default() -> Self {
PoolsExtBuilder {}
}
}
impl PoolsExtBuilder {
pub fn max_unbonding(self, max: u32) -> Self {
<MaxUnbonding>::set(max);
self
}
}
pub struct BalancesExtBuilder {
balances: Vec<(AccountId, Balance)>,
}
impl Default for BalancesExtBuilder {
fn default() -> Self {
let balances = vec![
// (account_id, balance)
(1, 10),
(2, 20),
(3, 300),
(4, 400),
// controllers (still used in some tests. Soon to be deprecated).
(10, 100),
(20, 100),
(30, 100),
(40, 100),
(50, 100),
(60, 100),
(70, 100),
(80, 100),
(90, 100),
(100, 100),
(200, 100),
// stashes
(11, 1100),
(21, 2000),
(31, 3000),
(41, 4000),
(51, 5000),
(61, 6000),
(71, 7000),
(81, 8000),
(91, 9000),
(101, 10000),
(201, 20000),
// This allows us to have a total_payout different from 0.
(999, 1_000_000_000_000),
];
Self { balances }
}
}
pub struct ExtBuilder {
staking_builder: StakingExtBuilder,
epm_builder: EpmExtBuilder,
balances_builder: BalancesExtBuilder,
pools_builder: PoolsExtBuilder,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
staking_builder: StakingExtBuilder::default(),
epm_builder: EpmExtBuilder::default(),
balances_builder: BalancesExtBuilder::default(),
pools_builder: PoolsExtBuilder::default(),
}
}
}
impl ExtBuilder {
pub fn build(&self) -> pezsp_io::TestExternalities {
pezsp_tracing::try_init_simple();
let mut storage =
pezframe_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
let _ = pezpallet_balances::GenesisConfig::<Runtime> {
balances: self.balances_builder.balances.clone(),
..Default::default()
}
.assimilate_storage(&mut storage);
let mut stakers = self.staking_builder.stakers.clone();
self.staking_builder.status.clone().into_iter().for_each(|(stash, status)| {
let (_, _, _, ref mut prev_status) = stakers
.iter_mut()
.find(|s| s.0 == stash)
.expect("set_status staker should exist; qed");
*prev_status = status;
});
// replaced any of the stakes if needed.
self.staking_builder.stakes.clone().into_iter().for_each(|(stash, stake)| {
let (_, _, ref mut prev_stake, _) = stakers
.iter_mut()
.find(|s| s.0 == stash)
.expect("set_stake staker should exits; qed.");
*prev_stake = stake;
});
let _ = pezpallet_staking::GenesisConfig::<Runtime> {
stakers: stakers.clone(),
validator_count: self.staking_builder.validator_count,
minimum_validator_count: self.staking_builder.minimum_validator_count,
slash_reward_fraction: Perbill::from_percent(10),
min_nominator_bond: self.staking_builder.min_nominator_bond,
min_validator_bond: self.staking_builder.min_validator_bond,
..Default::default()
}
.assimilate_storage(&mut storage);
let _ = pezpallet_session::GenesisConfig::<Runtime> {
// set the keys for the first session.
keys: stakers
.into_iter()
.map(|(id, ..)| (id, id, SessionKeys { other: (id as AccountId as u64).into() }))
.collect(),
..Default::default()
}
.assimilate_storage(&mut storage);
let mut ext = pezsp_io::TestExternalities::from(storage);
// We consider all test to start after timestamp is initialized This must be ensured by
// having `timestamp::on_initialize` called before `staking::on_initialize`.
ext.execute_with(|| {
System::set_block_number(1);
Session::on_initialize(1);
<Staking as Hooks<u32>>::on_initialize(1);
Timestamp::set_timestamp(INIT_TIMESTAMP);
});
ext
}
pub fn staking(mut self, builder: StakingExtBuilder) -> Self {
self.staking_builder = builder;
self
}
pub fn epm(mut self, builder: EpmExtBuilder) -> Self {
self.epm_builder = builder;
self
}
pub fn pools(mut self, builder: PoolsExtBuilder) -> Self {
self.pools_builder = builder;
self
}
pub fn balances(mut self, builder: BalancesExtBuilder) -> Self {
self.balances_builder = builder;
self
}
pub fn build_offchainify(
self,
) -> (pezsp_io::TestExternalities, Arc<RwLock<PoolState>>, Arc<RwLock<OffchainState>>) {
// add offchain and pool externality extensions.
let mut ext = self.build();
let (offchain, offchain_state) = TestOffchainExt::new();
let (pool, pool_state) = TestTransactionPoolExt::new();
ext.register_extension(OffchainDbExt::new(offchain.clone()));
ext.register_extension(OffchainWorkerExt::new(offchain));
ext.register_extension(TransactionPoolExt::new(pool));
(ext, pool_state, offchain_state)
}
}
pub(crate) fn execute_with(mut ext: pezsp_io::TestExternalities, test: impl FnOnce() -> ()) {
ext.execute_with(test);
#[cfg(feature = "try-runtime")]
ext.execute_with(|| {
let bn = System::block_number();
assert_ok!(<ElectionProviderMultiPhase as Hooks<BlockNumber>>::try_state(bn));
assert_ok!(<Staking as Hooks<BlockNumber>>::try_state(bn));
assert_ok!(<Pools as Hooks<BlockNumber>>::try_state(bn));
assert_ok!(<Session as Hooks<BlockNumber>>::try_state(bn));
});
}
// Progress to given block, triggering session and era changes as we progress and ensuring that
// there is a solution queued when expected.
pub fn roll_to(n: BlockNumber, delay_solution: bool) {
for b in (System::block_number()) + 1..=n {
System::set_block_number(b);
Session::on_initialize(b);
Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP);
// TODO(gpestana): implement a realistic OCW worker instead of simulating it
// https://github.com/pezkuwichain/kurdistan-sdk/issues/10
// if there's no solution queued and the solution should not be delayed, try mining and
// queue a solution.
if CurrentPhase::<Runtime>::get().is_signed() && !delay_solution {
let _ = try_queue_solution(ElectionCompute::Signed).map_err(|e| {
log!(info, "failed to mine/queue solution: {:?}", e);
});
}
ElectionProviderMultiPhase::on_initialize(b);
Staking::on_initialize(b);
if b != n {
Staking::on_finalize(System::block_number());
}
Pools::on_initialize(b);
log_current_time();
}
}
// Progress to given block, triggering session and era changes as we progress and ensuring that
// there is a solution queued when expected.
pub fn roll_to_with_ocw(n: BlockNumber, pool: Arc<RwLock<PoolState>>, delay_solution: bool) {
for b in (System::block_number()) + 1..=n {
System::set_block_number(b);
Session::on_initialize(b);
Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP);
ElectionProviderMultiPhase::on_initialize(b);
ElectionProviderMultiPhase::offchain_worker(b);
if !delay_solution && pool.read().transactions.len() > 0 {
// decode submit_unsigned callable that may be queued in the pool by ocw. skip all
// other extrinsics in the pool.
for encoded in &pool.read().transactions {
let extrinsic = Extrinsic::decode(&mut &encoded[..]).unwrap();
let _ = match extrinsic.function {
RuntimeCall::ElectionProviderMultiPhase(
call @ Call::submit_unsigned { .. },
) => {
// call submit_unsigned callable in OCW pool.
crate::assert_ok!(call.dispatch_bypass_filter(RuntimeOrigin::none()));
},
_ => (),
};
}
pool.try_write().unwrap().transactions.clear();
}
Staking::on_initialize(b);
if b != n {
Staking::on_finalize(System::block_number());
}
log_current_time();
}
}
// helper to progress one block ahead.
pub fn roll_one(pool: Arc<RwLock<PoolState>>, delay_solution: bool) {
let bn = System::block_number().saturating_add(1);
roll_to_with_ocw(bn, pool, delay_solution);
}
/// Progresses from the current block number (whatever that may be) to the block where the session
/// `session_index` starts.
pub(crate) fn start_session(
session_index: SessionIndex,
pool: Arc<RwLock<PoolState>>,
delay_solution: bool,
) {
let end = if Offset::get().is_zero() {
Period::get() * session_index
} else {
Offset::get() * session_index + Period::get() * session_index
};
assert!(end >= System::block_number());
roll_to_with_ocw(end, pool, delay_solution);
// session must have progressed properly.
assert_eq!(
Session::current_index(),
session_index,
"current session index = {}, expected = {}",
Session::current_index(),
session_index,
);
}
/// Go one session forward.
pub(crate) fn advance_session(pool: Arc<RwLock<PoolState>>) {
let current_index = Session::current_index();
start_session(current_index + 1, pool, false);
}
pub(crate) fn advance_session_delayed_solution(pool: Arc<RwLock<PoolState>>) {
let current_index = Session::current_index();
start_session(current_index + 1, pool, true);
}
pub(crate) fn start_next_active_era(pool: Arc<RwLock<PoolState>>) -> Result<(), ()> {
start_active_era(active_era() + 1, pool, false)
}
pub(crate) fn start_next_active_era_delayed_solution(
pool: Arc<RwLock<PoolState>>,
) -> Result<(), ()> {
start_active_era(active_era() + 1, pool, true)
}
pub(crate) fn advance_eras(n: usize, pool: Arc<RwLock<PoolState>>) {
for _ in 0..n {
assert_ok!(start_next_active_era(pool.clone()));
}
}
/// Progress until the given era.
pub(crate) fn start_active_era(
era_index: EraIndex,
pool: Arc<RwLock<PoolState>>,
delay_solution: bool,
) -> Result<(), ()> {
let era_before = current_era();
start_session((era_index * <SessionsPerEra as Get<u32>>::get()).into(), pool, delay_solution);
log!(
info,
"start_active_era - era_before: {}, current era: {} -> progress to: {} -> after era: {}",
era_before,
active_era(),
era_index,
current_era(),
);
// if the solution was not delayed, era should have progressed.
if !delay_solution && (active_era() != era_index || current_era() != active_era()) {
Err(())
} else {
Ok(())
}
}
pub(crate) fn active_era() -> EraIndex {
ActiveEra::<Runtime>::get().unwrap().index
}
pub(crate) fn current_era() -> EraIndex {
CurrentEra::<Runtime>::get().unwrap()
}
// Fast forward until EPM signed phase.
pub fn roll_to_epm_signed() {
while !matches!(
CurrentPhase::<Runtime>::get(),
pezpallet_election_provider_multi_phase::Phase::Signed
) {
roll_to(System::block_number() + 1, false);
}
}
// Fast forward until EPM unsigned phase.
pub fn roll_to_epm_unsigned() {
while !matches!(
CurrentPhase::<Runtime>::get(),
pezpallet_election_provider_multi_phase::Phase::Unsigned(_)
) {
roll_to(System::block_number() + 1, false);
}
}
// Fast forward until EPM off.
pub fn roll_to_epm_off() {
while !matches!(
CurrentPhase::<Runtime>::get(),
pezpallet_election_provider_multi_phase::Phase::Off
) {
roll_to(System::block_number() + 1, false);
}
}
// Queue a solution based on the current snapshot.
pub(crate) fn try_queue_solution(when: ElectionCompute) -> Result<(), String> {
let raw_solution = ElectionProviderMultiPhase::mine_solution()
.map_err(|e| format!("error mining solution: {:?}", e))?;
ElectionProviderMultiPhase::feasibility_check(raw_solution.0, when)
.map(|ready| {
QueuedSolution::<Runtime>::put(ready);
})
.map_err(|e| format!("error in solution feasibility: {:?}", e))
}
pub(crate) fn on_offence_now(
offenders: &[OffenceDetails<
AccountId,
pezpallet_session::historical::IdentificationTuple<Runtime>,
>],
slash_fraction: &[Perbill],
) {
let now = ActiveEra::<Runtime>::get().unwrap().index;
let _ = <Staking as OnOffenceHandler<_, _, _>>::on_offence(
offenders,
slash_fraction,
ErasStartSessionIndex::<Runtime>::get(now).unwrap(),
);
}
// Add offence to validator, slash it.
pub(crate) fn add_slash(who: &AccountId) {
on_offence_now(
&[OffenceDetails { offender: (*who, ()), reporters: vec![] }],
&[Perbill::from_percent(10)],
);
}
// Slashes 1/2 of the active set. Returns the `AccountId`s of the slashed validators.
pub(crate) fn slash_half_the_active_set() -> Vec<AccountId> {
let mut slashed = Session::validators();
slashed.truncate(slashed.len() / 2);
for v in slashed.iter() {
add_slash(v);
}
slashed
}
// Slashes a percentage of the active nominators that haven't been slashed yet, with
// a minimum of 1 validator slash.
pub(crate) fn slash_percentage(percentage: Perbill) -> Vec<AccountId> {
let validators = Session::validators();
let mut remaining_slashes = (percentage * validators.len() as u32).max(1);
let mut slashed = vec![];
for v in validators.into_iter() {
if remaining_slashes != 0 {
add_slash(&v);
slashed.push(v);
remaining_slashes -= 1;
}
}
slashed
}
pub(crate) fn set_minimum_election_score(
minimal_stake: ExtendedBalance,
sum_stake: ExtendedBalance,
sum_stake_squared: ExtendedBalance,
) -> Result<(), ()> {
let election_score = ElectionScore { minimal_stake, sum_stake, sum_stake_squared };
ElectionProviderMultiPhase::set_minimum_untrusted_score(
RuntimeOrigin::root(),
Some(election_score),
)
.map(|_| ())
.map_err(|_| ())
}
pub(crate) fn staked_amount_for(account_id: AccountId) -> Balance {
Staking::total_stake(&account_id).expect("account must be staker")
}
pub(crate) fn delegated_balance_for(account_id: AccountId) -> Balance {
DelegatedStaking::agent_balance(Agent::from(account_id)).unwrap_or_default()
}
pub(crate) fn staking_events() -> Vec<pezpallet_staking::Event<Runtime>> {
System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None })
.collect::<Vec<_>>()
}
pub(crate) fn epm_events() -> Vec<pezpallet_election_provider_multi_phase::Event<Runtime>> {
System::events()
.into_iter()
.map(|r| r.event)
.filter_map(|e| {
if let RuntimeEvent::ElectionProviderMultiPhase(inner) = e {
Some(inner)
} else {
None
}
})
.collect::<Vec<_>>()
}