mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-24 20:35:41 +00:00
New sub-trait to mock staking miner (#11350)
* new separate config trait for staking miner * fix some docs and stuff * relax trait bounds * some cleanup * Update frame/election-provider-multi-phase/src/unsigned.rs * add comment * self review and fix build * fix docs Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
This commit is contained in:
@@ -23,7 +23,9 @@
|
|||||||
#![recursion_limit = "256"]
|
#![recursion_limit = "256"]
|
||||||
|
|
||||||
use codec::{Decode, Encode, MaxEncodedLen};
|
use codec::{Decode, Encode, MaxEncodedLen};
|
||||||
use frame_election_provider_support::{onchain, ExtendedBalance, SequentialPhragmen, VoteWeight};
|
use frame_election_provider_support::{
|
||||||
|
onchain, ElectionDataProvider, ExtendedBalance, SequentialPhragmen, VoteWeight,
|
||||||
|
};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
construct_runtime,
|
construct_runtime,
|
||||||
pallet_prelude::Get,
|
pallet_prelude::Get,
|
||||||
@@ -660,6 +662,25 @@ impl onchain::BoundedConfig for OnChainSeqPhragmen {
|
|||||||
type TargetsBound = ConstU32<2_000>;
|
type TargetsBound = ConstU32<2_000>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl pallet_election_provider_multi_phase::MinerConfig for Runtime {
|
||||||
|
type AccountId = AccountId;
|
||||||
|
type MaxLength = MinerMaxLength;
|
||||||
|
type MaxWeight = MinerMaxWeight;
|
||||||
|
type Solution = NposSolution16;
|
||||||
|
type MaxVotesPerVoter =
|
||||||
|
<<Self as pallet_election_provider_multi_phase::Config>::DataProvider as ElectionDataProvider>::MaxVotesPerVoter;
|
||||||
|
|
||||||
|
// The unsigned submissions have to respect the weight of the submit_unsigned call, thus their
|
||||||
|
// weight estimate function is wired to this call's weight.
|
||||||
|
fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight {
|
||||||
|
<
|
||||||
|
<Self as pallet_election_provider_multi_phase::Config>::WeightInfo
|
||||||
|
as
|
||||||
|
pallet_election_provider_multi_phase::WeightInfo
|
||||||
|
>::submit_unsigned(v, t, a, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl pallet_election_provider_multi_phase::Config for Runtime {
|
impl pallet_election_provider_multi_phase::Config for Runtime {
|
||||||
type Event = Event;
|
type Event = Event;
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
@@ -669,9 +690,8 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
|
|||||||
type BetterUnsignedThreshold = BetterUnsignedThreshold;
|
type BetterUnsignedThreshold = BetterUnsignedThreshold;
|
||||||
type BetterSignedThreshold = ();
|
type BetterSignedThreshold = ();
|
||||||
type OffchainRepeat = OffchainRepeat;
|
type OffchainRepeat = OffchainRepeat;
|
||||||
type MinerMaxWeight = MinerMaxWeight;
|
|
||||||
type MinerMaxLength = MinerMaxLength;
|
|
||||||
type MinerTxPriority = MultiPhaseUnsignedPriority;
|
type MinerTxPriority = MultiPhaseUnsignedPriority;
|
||||||
|
type MinerConfig = Self;
|
||||||
type SignedMaxSubmissions = ConstU32<10>;
|
type SignedMaxSubmissions = ConstU32<10>;
|
||||||
type SignedRewardBase = SignedRewardBase;
|
type SignedRewardBase = SignedRewardBase;
|
||||||
type SignedDepositBase = SignedDepositBase;
|
type SignedDepositBase = SignedDepositBase;
|
||||||
@@ -682,7 +702,6 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
|
|||||||
type SlashHandler = (); // burn slashes
|
type SlashHandler = (); // burn slashes
|
||||||
type RewardHandler = (); // nothing to do upon rewards
|
type RewardHandler = (); // nothing to do upon rewards
|
||||||
type DataProvider = Staking;
|
type DataProvider = Staking;
|
||||||
type Solution = NposSolution16;
|
|
||||||
type Fallback = onchain::BoundedExecution<OnChainSeqPhragmen>;
|
type Fallback = onchain::BoundedExecution<OnChainSeqPhragmen>;
|
||||||
type GovernanceFallback = onchain::BoundedExecution<OnChainSeqPhragmen>;
|
type GovernanceFallback = onchain::BoundedExecution<OnChainSeqPhragmen>;
|
||||||
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Self>, OffchainRandomBalancing>;
|
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Self>, OffchainRandomBalancing>;
|
||||||
@@ -2017,7 +2036,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn perbill_as_onchain_accuracy() {
|
fn perbill_as_onchain_accuracy() {
|
||||||
type OnChainAccuracy =
|
type OnChainAccuracy =
|
||||||
<<Runtime as pallet_election_provider_multi_phase::Config>::Solution as NposSolution>::Accuracy;
|
<<Runtime as pallet_election_provider_multi_phase::MinerConfig>::Solution as NposSolution>::Accuracy;
|
||||||
let maximum_chain_accuracy: Vec<UpperOf<OnChainAccuracy>> = (0..MaxNominations::get())
|
let maximum_chain_accuracy: Vec<UpperOf<OnChainAccuracy>> = (0..MaxNominations::get())
|
||||||
.map(|_| <UpperOf<OnChainAccuracy>>::from(OnChainAccuracy::one().deconstruct()))
|
.map(|_| <UpperOf<OnChainAccuracy>>::from(OnChainAccuracy::one().deconstruct()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -39,15 +39,15 @@ fn solution_with_size<T: Config>(
|
|||||||
size: SolutionOrSnapshotSize,
|
size: SolutionOrSnapshotSize,
|
||||||
active_voters_count: u32,
|
active_voters_count: u32,
|
||||||
desired_targets: u32,
|
desired_targets: u32,
|
||||||
) -> Result<RawSolution<SolutionOf<T>>, &'static str> {
|
) -> Result<RawSolution<SolutionOf<T::MinerConfig>>, &'static str> {
|
||||||
ensure!(size.targets >= desired_targets, "must have enough targets");
|
ensure!(size.targets >= desired_targets, "must have enough targets");
|
||||||
ensure!(
|
ensure!(
|
||||||
size.targets >= (<SolutionOf<T>>::LIMIT * 2) as u32,
|
size.targets >= (<SolutionOf<T::MinerConfig>>::LIMIT * 2) as u32,
|
||||||
"must have enough targets for unique votes."
|
"must have enough targets for unique votes."
|
||||||
);
|
);
|
||||||
ensure!(size.voters >= active_voters_count, "must have enough voters");
|
ensure!(size.voters >= active_voters_count, "must have enough voters");
|
||||||
ensure!(
|
ensure!(
|
||||||
(<SolutionOf<T>>::LIMIT as u32) < desired_targets,
|
(<SolutionOf<T::MinerConfig>>::LIMIT as u32) < desired_targets,
|
||||||
"must have enough winners to give them votes."
|
"must have enough winners to give them votes."
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -74,10 +74,10 @@ fn solution_with_size<T: Config>(
|
|||||||
// chose a random subset of winners.
|
// chose a random subset of winners.
|
||||||
let winner_votes: BoundedVec<_, _> = winners
|
let winner_votes: BoundedVec<_, _> = winners
|
||||||
.as_slice()
|
.as_slice()
|
||||||
.choose_multiple(&mut rng, <SolutionOf<T>>::LIMIT)
|
.choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT)
|
||||||
.cloned()
|
.cloned()
|
||||||
.try_collect()
|
.try_collect()
|
||||||
.expect("<SolutionOf<T>>::LIMIT is the correct bound; qed.");
|
.expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed.");
|
||||||
let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
|
let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
|
||||||
(voter, stake, winner_votes)
|
(voter, stake, winner_votes)
|
||||||
})
|
})
|
||||||
@@ -92,10 +92,10 @@ fn solution_with_size<T: Config>(
|
|||||||
let rest_voters = (active_voters_count..size.voters)
|
let rest_voters = (active_voters_count..size.voters)
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let votes: BoundedVec<_, _> = (&non_winners)
|
let votes: BoundedVec<_, _> = (&non_winners)
|
||||||
.choose_multiple(&mut rng, <SolutionOf<T>>::LIMIT)
|
.choose_multiple(&mut rng, <SolutionOf<T::MinerConfig>>::LIMIT)
|
||||||
.cloned()
|
.cloned()
|
||||||
.try_collect()
|
.try_collect()
|
||||||
.expect("<SolutionOf<T>>::LIMIT is the correct bound; qed.");
|
.expect("<SolutionOf<T::MinerConfig>>::LIMIT is the correct bound; qed.");
|
||||||
let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
|
let voter = frame_benchmarking::account::<T::AccountId>("Voter", i, SEED);
|
||||||
(voter, stake, votes)
|
(voter, stake, votes)
|
||||||
})
|
})
|
||||||
@@ -120,12 +120,12 @@ fn solution_with_size<T: Config>(
|
|||||||
// down the road.
|
// down the road.
|
||||||
T::DataProvider::put_snapshot(all_voters.clone(), targets.clone(), Some(stake));
|
T::DataProvider::put_snapshot(all_voters.clone(), targets.clone(), Some(stake));
|
||||||
|
|
||||||
let cache = helpers::generate_voter_cache::<T>(&all_voters);
|
let cache = helpers::generate_voter_cache::<T::MinerConfig>(&all_voters);
|
||||||
let stake_of = helpers::stake_of_fn::<T>(&all_voters, &cache);
|
let stake_of = helpers::stake_of_fn::<T::MinerConfig>(&all_voters, &cache);
|
||||||
let voter_index = helpers::voter_index_fn::<T>(&cache);
|
let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache);
|
||||||
let target_index = helpers::target_index_fn::<T>(&targets);
|
let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets);
|
||||||
let voter_at = helpers::voter_at_fn::<T>(&all_voters);
|
let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&all_voters);
|
||||||
let target_at = helpers::target_at_fn::<T>(&targets);
|
let target_at = helpers::target_at_fn::<T::MinerConfig>(&targets);
|
||||||
|
|
||||||
let assignments = active_voters
|
let assignments = active_voters
|
||||||
.iter()
|
.iter()
|
||||||
@@ -143,7 +143,8 @@ fn solution_with_size<T: Config>(
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let solution =
|
let solution =
|
||||||
<SolutionOf<T>>::from_assignment(&assignments, &voter_index, &target_index).unwrap();
|
<SolutionOf<T::MinerConfig>>::from_assignment(&assignments, &voter_index, &target_index)
|
||||||
|
.unwrap();
|
||||||
let score = solution.clone().score(stake_of, voter_at, target_at).unwrap();
|
let score = solution.clone().score(stake_of, voter_at, target_at).unwrap();
|
||||||
let round = <MultiPhase<T>>::round();
|
let round = <MultiPhase<T>>::round();
|
||||||
|
|
||||||
@@ -480,14 +481,14 @@ frame_benchmarking::benchmarks! {
|
|||||||
let witness = SolutionOrSnapshotSize { voters: v, targets: t };
|
let witness = SolutionOrSnapshotSize { voters: v, targets: t };
|
||||||
let RawSolution { solution, .. } = solution_with_size::<T>(witness, a, d)?;
|
let RawSolution { solution, .. } = solution_with_size::<T>(witness, a, d)?;
|
||||||
let RoundSnapshot { voters, targets } = MultiPhase::<T>::snapshot().ok_or("snapshot missing")?;
|
let RoundSnapshot { voters, targets } = MultiPhase::<T>::snapshot().ok_or("snapshot missing")?;
|
||||||
let voter_at = helpers::voter_at_fn::<T>(&voters);
|
let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&voters);
|
||||||
let target_at = helpers::target_at_fn::<T>(&targets);
|
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.");
|
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
|
// make a voter cache and some helper functions for access
|
||||||
let cache = helpers::generate_voter_cache::<T>(&voters);
|
let cache = helpers::generate_voter_cache::<T::MinerConfig>(&voters);
|
||||||
let voter_index = helpers::voter_index_fn::<T>(&cache);
|
let voter_index = helpers::voter_index_fn::<T::MinerConfig>(&cache);
|
||||||
let target_index = helpers::target_index_fn::<T>(&targets);
|
let target_index = helpers::target_index_fn::<T::MinerConfig>(&targets);
|
||||||
|
|
||||||
// sort assignments by decreasing voter stake
|
// sort assignments by decreasing voter stake
|
||||||
assignments.sort_by_key(|crate::unsigned::Assignment::<T> { who, .. }| {
|
assignments.sort_by_key(|crate::unsigned::Assignment::<T> { who, .. }| {
|
||||||
@@ -504,21 +505,21 @@ frame_benchmarking::benchmarks! {
|
|||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let encoded_size_of = |assignments: &[IndexAssignmentOf<T>]| {
|
let encoded_size_of = |assignments: &[IndexAssignmentOf<T::MinerConfig>]| {
|
||||||
SolutionOf::<T>::try_from(assignments).map(|solution| solution.encoded_size())
|
SolutionOf::<T::MinerConfig>::try_from(assignments).map(|solution| solution.encoded_size())
|
||||||
};
|
};
|
||||||
|
|
||||||
let desired_size = Percent::from_percent(100 - f.saturated_into::<u8>())
|
let desired_size = Percent::from_percent(100 - f.saturated_into::<u8>())
|
||||||
.mul_ceil(encoded_size_of(index_assignments.as_slice()).unwrap());
|
.mul_ceil(encoded_size_of(index_assignments.as_slice()).unwrap());
|
||||||
log!(trace, "desired_size = {}", desired_size);
|
log!(trace, "desired_size = {}", desired_size);
|
||||||
}: {
|
}: {
|
||||||
MultiPhase::<T>::trim_assignments_length(
|
crate::Miner::<T::MinerConfig>::trim_assignments_length(
|
||||||
desired_size.saturated_into(),
|
desired_size.saturated_into(),
|
||||||
&mut index_assignments,
|
&mut index_assignments,
|
||||||
&encoded_size_of,
|
&encoded_size_of,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
} verify {
|
} verify {
|
||||||
let solution = SolutionOf::<T>::try_from(index_assignments.as_slice()).unwrap();
|
let solution = SolutionOf::<T::MinerConfig>::try_from(index_assignments.as_slice()).unwrap();
|
||||||
let encoding = solution.encode();
|
let encoding = solution.encode();
|
||||||
log!(
|
log!(
|
||||||
trace,
|
trace,
|
||||||
|
|||||||
@@ -17,7 +17,10 @@
|
|||||||
|
|
||||||
//! Some helper functions/macros for this crate.
|
//! Some helper functions/macros for this crate.
|
||||||
|
|
||||||
use crate::{unsigned::VoterOf, Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight};
|
use crate::{
|
||||||
|
unsigned::{MinerConfig, MinerVoterOf},
|
||||||
|
SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight,
|
||||||
|
};
|
||||||
use sp_std::{collections::btree_map::BTreeMap, prelude::*};
|
use sp_std::{collections::btree_map::BTreeMap, prelude::*};
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@@ -30,11 +33,22 @@ macro_rules! log {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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.
|
/// Generate a btree-map cache of the voters and their indices.
|
||||||
///
|
///
|
||||||
/// This can be used to efficiently build index getter closures.
|
/// This can be used to efficiently build index getter closures.
|
||||||
pub fn generate_voter_cache<T: Config>(
|
pub fn generate_voter_cache<T: MinerConfig>(
|
||||||
snapshot: &Vec<VoterOf<T>>,
|
snapshot: &Vec<MinerVoterOf<T>>,
|
||||||
) -> BTreeMap<T::AccountId, usize> {
|
) -> BTreeMap<T::AccountId, usize> {
|
||||||
let mut cache: BTreeMap<T::AccountId, usize> = BTreeMap::new();
|
let mut cache: BTreeMap<T::AccountId, usize> = BTreeMap::new();
|
||||||
snapshot.iter().enumerate().for_each(|(i, (x, _, _))| {
|
snapshot.iter().enumerate().for_each(|(i, (x, _, _))| {
|
||||||
@@ -54,7 +68,7 @@ pub fn generate_voter_cache<T: Config>(
|
|||||||
/// ## Warning
|
/// ## Warning
|
||||||
///
|
///
|
||||||
/// Note that this will represent the snapshot data from which the `cache` is generated.
|
/// Note that this will represent the snapshot data from which the `cache` is generated.
|
||||||
pub fn voter_index_fn<T: Config>(
|
pub fn voter_index_fn<T: MinerConfig>(
|
||||||
cache: &BTreeMap<T::AccountId, usize>,
|
cache: &BTreeMap<T::AccountId, usize>,
|
||||||
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
|
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
|
||||||
move |who| {
|
move |who| {
|
||||||
@@ -68,7 +82,7 @@ pub fn voter_index_fn<T: Config>(
|
|||||||
///
|
///
|
||||||
/// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is
|
/// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is
|
||||||
/// borrowed.
|
/// borrowed.
|
||||||
pub fn voter_index_fn_owned<T: Config>(
|
pub fn voter_index_fn_owned<T: MinerConfig>(
|
||||||
cache: BTreeMap<T::AccountId, usize>,
|
cache: BTreeMap<T::AccountId, usize>,
|
||||||
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> {
|
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> {
|
||||||
move |who| {
|
move |who| {
|
||||||
@@ -83,7 +97,7 @@ pub fn voter_index_fn_owned<T: Config>(
|
|||||||
/// ## Warning
|
/// ## Warning
|
||||||
///
|
///
|
||||||
/// Note that this will represent the snapshot data from which the `cache` is generated.
|
/// Note that this will represent the snapshot data from which the `cache` is generated.
|
||||||
pub fn voter_index_fn_usize<T: Config>(
|
pub fn voter_index_fn_usize<T: MinerConfig>(
|
||||||
cache: &BTreeMap<T::AccountId, usize>,
|
cache: &BTreeMap<T::AccountId, usize>,
|
||||||
) -> impl Fn(&T::AccountId) -> Option<usize> + '_ {
|
) -> impl Fn(&T::AccountId) -> Option<usize> + '_ {
|
||||||
move |who| cache.get(who).cloned()
|
move |who| cache.get(who).cloned()
|
||||||
@@ -96,8 +110,8 @@ pub fn voter_index_fn_usize<T: Config>(
|
|||||||
///
|
///
|
||||||
/// Not meant to be used in production.
|
/// Not meant to be used in production.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn voter_index_fn_linear<T: Config>(
|
pub fn voter_index_fn_linear<T: MinerConfig>(
|
||||||
snapshot: &Vec<VoterOf<T>>,
|
snapshot: &Vec<MinerVoterOf<T>>,
|
||||||
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
|
) -> impl Fn(&T::AccountId) -> Option<SolutionVoterIndexOf<T>> + '_ {
|
||||||
move |who| {
|
move |who| {
|
||||||
snapshot
|
snapshot
|
||||||
@@ -114,7 +128,7 @@ pub fn voter_index_fn_linear<T: Config>(
|
|||||||
/// Note: to the extent possible, the returned function should be cached and reused. Producing that
|
/// 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
|
/// function requires a `O(n log n)` data transform. Each invocation of that function completes
|
||||||
/// in `O(log n)`.
|
/// in `O(log n)`.
|
||||||
pub fn target_index_fn<T: Config>(
|
pub fn target_index_fn<T: MinerConfig>(
|
||||||
snapshot: &Vec<T::AccountId>,
|
snapshot: &Vec<T::AccountId>,
|
||||||
) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
|
) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
|
||||||
let cache: BTreeMap<_, _> =
|
let cache: BTreeMap<_, _> =
|
||||||
@@ -134,7 +148,7 @@ pub fn target_index_fn<T: Config>(
|
|||||||
///
|
///
|
||||||
/// Not meant to be used in production.
|
/// Not meant to be used in production.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn target_index_fn_linear<T: Config>(
|
pub fn target_index_fn_linear<T: MinerConfig>(
|
||||||
snapshot: &Vec<T::AccountId>,
|
snapshot: &Vec<T::AccountId>,
|
||||||
) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
|
) -> impl Fn(&T::AccountId) -> Option<SolutionTargetIndexOf<T>> + '_ {
|
||||||
move |who| {
|
move |who| {
|
||||||
@@ -147,8 +161,8 @@ pub fn target_index_fn_linear<T: Config>(
|
|||||||
|
|
||||||
/// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter
|
/// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter
|
||||||
/// account using a linearly indexible snapshot.
|
/// account using a linearly indexible snapshot.
|
||||||
pub fn voter_at_fn<T: Config>(
|
pub fn voter_at_fn<T: MinerConfig>(
|
||||||
snapshot: &Vec<VoterOf<T>>,
|
snapshot: &Vec<MinerVoterOf<T>>,
|
||||||
) -> impl Fn(SolutionVoterIndexOf<T>) -> Option<T::AccountId> + '_ {
|
) -> impl Fn(SolutionVoterIndexOf<T>) -> Option<T::AccountId> + '_ {
|
||||||
move |i| {
|
move |i| {
|
||||||
<SolutionVoterIndexOf<T> as TryInto<usize>>::try_into(i)
|
<SolutionVoterIndexOf<T> as TryInto<usize>>::try_into(i)
|
||||||
@@ -159,7 +173,7 @@ pub fn voter_at_fn<T: Config>(
|
|||||||
|
|
||||||
/// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target
|
/// Create a function that can map a target index ([`SolutionTargetIndexOf`]) to the actual target
|
||||||
/// account using a linearly indexible snapshot.
|
/// account using a linearly indexible snapshot.
|
||||||
pub fn target_at_fn<T: Config>(
|
pub fn target_at_fn<T: MinerConfig>(
|
||||||
snapshot: &Vec<T::AccountId>,
|
snapshot: &Vec<T::AccountId>,
|
||||||
) -> impl Fn(SolutionTargetIndexOf<T>) -> Option<T::AccountId> + '_ {
|
) -> impl Fn(SolutionTargetIndexOf<T>) -> Option<T::AccountId> + '_ {
|
||||||
move |i| {
|
move |i| {
|
||||||
@@ -173,8 +187,8 @@ pub fn target_at_fn<T: Config>(
|
|||||||
///
|
///
|
||||||
/// This is not optimized and uses a linear search.
|
/// This is not optimized and uses a linear search.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn stake_of_fn_linear<T: Config>(
|
pub fn stake_of_fn_linear<T: MinerConfig>(
|
||||||
snapshot: &Vec<VoterOf<T>>,
|
snapshot: &Vec<MinerVoterOf<T>>,
|
||||||
) -> impl Fn(&T::AccountId) -> VoteWeight + '_ {
|
) -> impl Fn(&T::AccountId) -> VoteWeight + '_ {
|
||||||
move |who| {
|
move |who| {
|
||||||
snapshot
|
snapshot
|
||||||
@@ -191,8 +205,8 @@ pub fn stake_of_fn_linear<T: Config>(
|
|||||||
///
|
///
|
||||||
/// The cache need must be derived from the same snapshot. Zero is returned if a voter is
|
/// The cache need must be derived from the same snapshot. Zero is returned if a voter is
|
||||||
/// non-existent.
|
/// non-existent.
|
||||||
pub fn stake_of_fn<'a, T: Config>(
|
pub fn stake_of_fn<'a, T: MinerConfig>(
|
||||||
snapshot: &'a Vec<VoterOf<T>>,
|
snapshot: &'a Vec<MinerVoterOf<T>>,
|
||||||
cache: &'a BTreeMap<T::AccountId, usize>,
|
cache: &'a BTreeMap<T::AccountId, usize>,
|
||||||
) -> impl Fn(&T::AccountId) -> VoteWeight + 'a {
|
) -> impl Fn(&T::AccountId) -> VoteWeight + 'a {
|
||||||
move |who| {
|
move |who| {
|
||||||
|
|||||||
@@ -103,7 +103,7 @@
|
|||||||
//!
|
//!
|
||||||
//! Validators will only submit solutions if the one that they have computed is sufficiently better
|
//! Validators will only submit solutions if the one that they have computed is sufficiently better
|
||||||
//! than the best queued one (see [`pallet::Config::BetterUnsignedThreshold`]) and will limit the
|
//! than the best queued one (see [`pallet::Config::BetterUnsignedThreshold`]) and will limit the
|
||||||
//! weight of the solution to [`pallet::Config::MinerMaxWeight`].
|
//! weight of the solution to [`MinerConfig::MaxWeight`].
|
||||||
//!
|
//!
|
||||||
//! The unsigned phase can be made passive depending on how the previous signed phase went, by
|
//! The unsigned phase can be made passive depending on how the previous signed phase went, by
|
||||||
//! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always
|
//! setting the first inner value of [`Phase`] to `false`. For now, the signed phase is always
|
||||||
@@ -276,16 +276,18 @@ pub use signed::{
|
|||||||
BalanceOf, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission, SignedSubmissionOf,
|
BalanceOf, NegativeImbalanceOf, PositiveImbalanceOf, SignedSubmission, SignedSubmissionOf,
|
||||||
SignedSubmissions, SubmissionIndicesOf,
|
SignedSubmissions, SubmissionIndicesOf,
|
||||||
};
|
};
|
||||||
|
pub use unsigned::{Miner, MinerConfig};
|
||||||
|
|
||||||
/// The solution type used by this crate.
|
/// The solution type used by this crate.
|
||||||
pub type SolutionOf<T> = <T as Config>::Solution;
|
pub type SolutionOf<T> = <T as MinerConfig>::Solution;
|
||||||
|
|
||||||
/// The voter index. Derived from [`SolutionOf`].
|
/// The voter index. Derived from [`SolutionOf`].
|
||||||
pub type SolutionVoterIndexOf<T> = <SolutionOf<T> as NposSolution>::VoterIndex;
|
pub type SolutionVoterIndexOf<T> = <SolutionOf<T> as NposSolution>::VoterIndex;
|
||||||
/// The target index. Derived from [`SolutionOf`].
|
/// The target index. Derived from [`SolutionOf`].
|
||||||
pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex;
|
pub type SolutionTargetIndexOf<T> = <SolutionOf<T> as NposSolution>::TargetIndex;
|
||||||
/// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`].
|
/// The accuracy of the election, when submitted from offchain. Derived from [`SolutionOf`].
|
||||||
pub type SolutionAccuracyOf<T> = <SolutionOf<T> as NposSolution>::Accuracy;
|
pub type SolutionAccuracyOf<T> =
|
||||||
|
<SolutionOf<<T as crate::Config>::MinerConfig> as NposSolution>::Accuracy;
|
||||||
/// The fallback election type.
|
/// The fallback election type.
|
||||||
pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProvider>::Error;
|
pub type FallbackErrorOf<T> = <<T as crate::Config>::Fallback as ElectionProvider>::Error;
|
||||||
|
|
||||||
@@ -488,7 +490,7 @@ pub enum ElectionError<T: Config> {
|
|||||||
/// An error happened in the feasibility check sub-system.
|
/// An error happened in the feasibility check sub-system.
|
||||||
Feasibility(FeasibilityError),
|
Feasibility(FeasibilityError),
|
||||||
/// An error in the miner (offchain) sub-system.
|
/// An error in the miner (offchain) sub-system.
|
||||||
Miner(unsigned::MinerError<T>),
|
Miner(unsigned::MinerError),
|
||||||
/// An error happened in the data provider.
|
/// An error happened in the data provider.
|
||||||
DataProvider(&'static str),
|
DataProvider(&'static str),
|
||||||
/// An error nested in the fallback.
|
/// An error nested in the fallback.
|
||||||
@@ -520,8 +522,8 @@ impl<T: Config> From<FeasibilityError> for ElectionError<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> From<unsigned::MinerError<T>> for ElectionError<T> {
|
impl<T: Config> From<unsigned::MinerError> for ElectionError<T> {
|
||||||
fn from(e: unsigned::MinerError<T>) -> Self {
|
fn from(e: unsigned::MinerError) -> Self {
|
||||||
ElectionError::Miner(e)
|
ElectionError::Miner(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -605,12 +607,14 @@ pub mod pallet {
|
|||||||
#[pallet::constant]
|
#[pallet::constant]
|
||||||
type MinerTxPriority: Get<TransactionPriority>;
|
type MinerTxPriority: Get<TransactionPriority>;
|
||||||
|
|
||||||
/// Maximum weight that the miner should consume.
|
/// Configurations of the embedded miner.
|
||||||
///
|
///
|
||||||
/// The miner will ensure that the total weight of the unsigned solution will not exceed
|
/// Any external software implementing this can use the [`unsigned::Miner`] type provided,
|
||||||
/// this value, based on [`WeightInfo::submit_unsigned`].
|
/// which can mine new solutions and trim them accordingly.
|
||||||
#[pallet::constant]
|
type MinerConfig: crate::unsigned::MinerConfig<
|
||||||
type MinerMaxWeight: Get<Weight>;
|
AccountId = Self::AccountId,
|
||||||
|
MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
|
||||||
|
>;
|
||||||
|
|
||||||
/// Maximum number of signed submissions that can be queued.
|
/// Maximum number of signed submissions that can be queued.
|
||||||
///
|
///
|
||||||
@@ -624,7 +628,9 @@ pub mod pallet {
|
|||||||
|
|
||||||
/// Maximum weight of a signed solution.
|
/// Maximum weight of a signed solution.
|
||||||
///
|
///
|
||||||
/// This should probably be similar to [`Config::MinerMaxWeight`].
|
/// If [`Config::MinerConfig`] is being implemented to submit signed solutions (outside of
|
||||||
|
/// this pallet), then [`MinerConfig::solution_weight`] is used to compare against
|
||||||
|
/// this value.
|
||||||
#[pallet::constant]
|
#[pallet::constant]
|
||||||
type SignedMaxWeight: Get<Weight>;
|
type SignedMaxWeight: Get<Weight>;
|
||||||
|
|
||||||
@@ -652,11 +658,11 @@ pub mod pallet {
|
|||||||
/// are only over a single block, but once multi-block elections are introduced they will
|
/// are only over a single block, but once multi-block elections are introduced they will
|
||||||
/// take place over multiple blocks.
|
/// take place over multiple blocks.
|
||||||
#[pallet::constant]
|
#[pallet::constant]
|
||||||
type MaxElectingVoters: Get<SolutionVoterIndexOf<Self>>;
|
type MaxElectingVoters: Get<SolutionVoterIndexOf<Self::MinerConfig>>;
|
||||||
|
|
||||||
/// The maximum number of electable targets to put in the snapshot.
|
/// The maximum number of electable targets to put in the snapshot.
|
||||||
#[pallet::constant]
|
#[pallet::constant]
|
||||||
type MaxElectableTargets: Get<SolutionTargetIndexOf<Self>>;
|
type MaxElectableTargets: Get<SolutionTargetIndexOf<Self::MinerConfig>>;
|
||||||
|
|
||||||
/// Handler for the slashed deposits.
|
/// Handler for the slashed deposits.
|
||||||
type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;
|
type SlashHandler: OnUnbalanced<NegativeImbalanceOf<Self>>;
|
||||||
@@ -664,30 +670,12 @@ pub mod pallet {
|
|||||||
/// Handler for the rewards.
|
/// Handler for the rewards.
|
||||||
type RewardHandler: OnUnbalanced<PositiveImbalanceOf<Self>>;
|
type RewardHandler: OnUnbalanced<PositiveImbalanceOf<Self>>;
|
||||||
|
|
||||||
/// Maximum length (bytes) that the mined solution should consume.
|
|
||||||
///
|
|
||||||
/// The miner will ensure that the total length of the unsigned solution will not exceed
|
|
||||||
/// this value.
|
|
||||||
#[pallet::constant]
|
|
||||||
type MinerMaxLength: Get<u32>;
|
|
||||||
|
|
||||||
/// Something that will provide the election data.
|
/// Something that will provide the election data.
|
||||||
type DataProvider: ElectionDataProvider<
|
type DataProvider: ElectionDataProvider<
|
||||||
AccountId = Self::AccountId,
|
AccountId = Self::AccountId,
|
||||||
BlockNumber = Self::BlockNumber,
|
BlockNumber = Self::BlockNumber,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/// The solution type.
|
|
||||||
type Solution: codec::Codec
|
|
||||||
+ Default
|
|
||||||
+ PartialEq
|
|
||||||
+ Eq
|
|
||||||
+ Clone
|
|
||||||
+ sp_std::fmt::Debug
|
|
||||||
+ Ord
|
|
||||||
+ NposSolution
|
|
||||||
+ TypeInfo;
|
|
||||||
|
|
||||||
/// Configuration for the fallback.
|
/// Configuration for the fallback.
|
||||||
type Fallback: InstantElectionProvider<
|
type Fallback: InstantElectionProvider<
|
||||||
AccountId = Self::AccountId,
|
AccountId = Self::AccountId,
|
||||||
@@ -824,12 +812,12 @@ pub mod pallet {
|
|||||||
use sp_std::mem::size_of;
|
use sp_std::mem::size_of;
|
||||||
// The index type of both voters and targets need to be smaller than that of usize (very
|
// The index type of both voters and targets need to be smaller than that of usize (very
|
||||||
// unlikely to be the case, but anyhow)..
|
// unlikely to be the case, but anyhow)..
|
||||||
assert!(size_of::<SolutionVoterIndexOf<T>>() <= size_of::<usize>());
|
assert!(size_of::<SolutionVoterIndexOf<T::MinerConfig>>() <= size_of::<usize>());
|
||||||
assert!(size_of::<SolutionTargetIndexOf<T>>() <= size_of::<usize>());
|
assert!(size_of::<SolutionTargetIndexOf<T::MinerConfig>>() <= size_of::<usize>());
|
||||||
|
|
||||||
// ----------------------------
|
// ----------------------------
|
||||||
// Based on the requirements of [`sp_npos_elections::Assignment::try_normalize`].
|
// Based on the requirements of [`sp_npos_elections::Assignment::try_normalize`].
|
||||||
let max_vote: usize = <SolutionOf<T> as NposSolution>::LIMIT;
|
let max_vote: usize = <SolutionOf<T::MinerConfig> as NposSolution>::LIMIT;
|
||||||
|
|
||||||
// 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf<OffchainAccuracy>`.
|
// 2. Maximum sum of [SolutionAccuracy; 16] must fit into `UpperOf<OffchainAccuracy>`.
|
||||||
let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T>>> = (0..max_vote)
|
let maximum_chain_accuracy: Vec<UpperOf<SolutionAccuracyOf<T>>> = (0..max_vote)
|
||||||
@@ -850,7 +838,7 @@ pub mod pallet {
|
|||||||
// solution cannot represent any voters more than `LIMIT` anyhow.
|
// solution cannot represent any voters more than `LIMIT` anyhow.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
|
<T::DataProvider as ElectionDataProvider>::MaxVotesPerVoter::get(),
|
||||||
<SolutionOf<T> as NposSolution>::LIMIT as u32,
|
<SolutionOf<T::MinerConfig> as NposSolution>::LIMIT as u32,
|
||||||
);
|
);
|
||||||
|
|
||||||
// While it won't cause any failures, setting `SignedMaxRefunds` gt
|
// While it won't cause any failures, setting `SignedMaxRefunds` gt
|
||||||
@@ -887,7 +875,7 @@ pub mod pallet {
|
|||||||
))]
|
))]
|
||||||
pub fn submit_unsigned(
|
pub fn submit_unsigned(
|
||||||
origin: OriginFor<T>,
|
origin: OriginFor<T>,
|
||||||
raw_solution: Box<RawSolution<SolutionOf<T>>>,
|
raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
|
||||||
witness: SolutionOrSnapshotSize,
|
witness: SolutionOrSnapshotSize,
|
||||||
) -> DispatchResultWithPostInfo {
|
) -> DispatchResultWithPostInfo {
|
||||||
ensure_none(origin)?;
|
ensure_none(origin)?;
|
||||||
@@ -976,7 +964,7 @@ pub mod pallet {
|
|||||||
#[pallet::weight(T::WeightInfo::submit())]
|
#[pallet::weight(T::WeightInfo::submit())]
|
||||||
pub fn submit(
|
pub fn submit(
|
||||||
origin: OriginFor<T>,
|
origin: OriginFor<T>,
|
||||||
raw_solution: Box<RawSolution<SolutionOf<T>>>,
|
raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
|
||||||
) -> DispatchResult {
|
) -> DispatchResult {
|
||||||
let who = ensure_signed(origin)?;
|
let who = ensure_signed(origin)?;
|
||||||
|
|
||||||
@@ -991,7 +979,7 @@ pub mod pallet {
|
|||||||
let size = Self::snapshot_metadata().ok_or(Error::<T>::MissingSnapshotMetadata)?;
|
let size = Self::snapshot_metadata().ok_or(Error::<T>::MissingSnapshotMetadata)?;
|
||||||
|
|
||||||
ensure!(
|
ensure!(
|
||||||
Self::feasibility_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(),
|
Self::solution_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(),
|
||||||
Error::<T>::SignedTooMuchWeight,
|
Error::<T>::SignedTooMuchWeight,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1429,7 +1417,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
|
|
||||||
/// Checks the feasibility of a solution.
|
/// Checks the feasibility of a solution.
|
||||||
pub fn feasibility_check(
|
pub fn feasibility_check(
|
||||||
raw_solution: RawSolution<SolutionOf<T>>,
|
raw_solution: RawSolution<SolutionOf<T::MinerConfig>>,
|
||||||
compute: ElectionCompute,
|
compute: ElectionCompute,
|
||||||
) -> Result<ReadySolution<T::AccountId>, FeasibilityError> {
|
) -> Result<ReadySolution<T::AccountId>, FeasibilityError> {
|
||||||
let RawSolution { solution, score, round } = raw_solution;
|
let RawSolution { solution, score, round } = raw_solution;
|
||||||
@@ -1459,10 +1447,10 @@ impl<T: Config> Pallet<T> {
|
|||||||
Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?;
|
Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?;
|
||||||
|
|
||||||
// ----- Start building. First, we need some closures.
|
// ----- Start building. First, we need some closures.
|
||||||
let cache = helpers::generate_voter_cache::<T>(&snapshot_voters);
|
let cache = helpers::generate_voter_cache::<T::MinerConfig>(&snapshot_voters);
|
||||||
let voter_at = helpers::voter_at_fn::<T>(&snapshot_voters);
|
let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&snapshot_voters);
|
||||||
let target_at = helpers::target_at_fn::<T>(&snapshot_targets);
|
let target_at = helpers::target_at_fn::<T::MinerConfig>(&snapshot_targets);
|
||||||
let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
|
let voter_index = helpers::voter_index_fn_usize::<T::MinerConfig>(&cache);
|
||||||
|
|
||||||
// Then convert solution -> assignment. This will fail if any of the indices are gibberish,
|
// Then convert solution -> assignment. This will fail if any of the indices are gibberish,
|
||||||
// namely any of the voters or targets.
|
// namely any of the voters or targets.
|
||||||
@@ -1493,7 +1481,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// ----- Start building support. First, we need one more closure.
|
// ----- Start building support. First, we need one more closure.
|
||||||
let stake_of = helpers::stake_of_fn::<T>(&snapshot_voters, &cache);
|
let stake_of = helpers::stake_of_fn::<T::MinerConfig>(&snapshot_voters, &cache);
|
||||||
|
|
||||||
// This might fail if the normalization fails. Very unlikely. See `integrity_test`.
|
// This might fail if the normalization fails. Very unlikely. See `integrity_test`.
|
||||||
let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of)
|
let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of)
|
||||||
@@ -1803,8 +1791,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
mock::{
|
mock::{
|
||||||
multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MultiPhase,
|
multi_phase_events, roll_to, AccountId, ExtBuilder, MockWeightInfo, MockedWeightInfo,
|
||||||
Runtime, SignedMaxSubmissions, System, TargetIndex, Targets,
|
MultiPhase, Runtime, SignedMaxSubmissions, System, TargetIndex, Targets,
|
||||||
},
|
},
|
||||||
Phase,
|
Phase,
|
||||||
};
|
};
|
||||||
@@ -2123,7 +2111,7 @@ mod tests {
|
|||||||
// set the solution balancing to get the desired score.
|
// set the solution balancing to get the desired score.
|
||||||
crate::mock::Balancing::set(Some((2, 0)));
|
crate::mock::Balancing::set(Some((2, 0)));
|
||||||
|
|
||||||
let (solution, _) = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
|
let (solution, _) = MultiPhase::mine_solution().unwrap();
|
||||||
// Default solution's score.
|
// Default solution's score.
|
||||||
assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. }));
|
assert!(matches!(solution.score, ElectionScore { minimal_stake: 50, .. }));
|
||||||
|
|
||||||
@@ -2147,7 +2135,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn number_of_voters_allowed_2sec_block() {
|
fn number_of_voters_allowed_2sec_block() {
|
||||||
// Just a rough estimate with the substrate weights.
|
// Just a rough estimate with the substrate weights.
|
||||||
assert!(!MockWeightInfo::get());
|
assert_eq!(MockWeightInfo::get(), MockedWeightInfo::Real);
|
||||||
|
|
||||||
let all_voters: u32 = 10_000;
|
let all_voters: u32 = 10_000;
|
||||||
let all_targets: u32 = 5_000;
|
let all_targets: u32 = 5_000;
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate as multi_phase;
|
use crate::{self as multi_phase, unsigned::MinerConfig};
|
||||||
use frame_election_provider_support::{
|
use frame_election_provider_support::{
|
||||||
data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen,
|
data_provider, onchain, ElectionDataProvider, NposSolution, SequentialPhragmen,
|
||||||
};
|
};
|
||||||
@@ -239,6 +239,13 @@ impl pallet_balances::Config for Runtime {
|
|||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Debug, Clone, Copy)]
|
||||||
|
pub enum MockedWeightInfo {
|
||||||
|
Basic,
|
||||||
|
Complex,
|
||||||
|
Real,
|
||||||
|
}
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub static Targets: Vec<AccountId> = vec![10, 20, 30, 40];
|
pub static Targets: Vec<AccountId> = vec![10, 20, 30, 40];
|
||||||
pub static Voters: Vec<VoterOf<Runtime>> = vec![
|
pub static Voters: Vec<VoterOf<Runtime>> = vec![
|
||||||
@@ -269,7 +276,7 @@ parameter_types! {
|
|||||||
pub static OffchainRepeat: BlockNumber = 5;
|
pub static OffchainRepeat: BlockNumber = 5;
|
||||||
pub static MinerMaxWeight: Weight = BlockWeights::get().max_block;
|
pub static MinerMaxWeight: Weight = BlockWeights::get().max_block;
|
||||||
pub static MinerMaxLength: u32 = 256;
|
pub static MinerMaxLength: u32 = 256;
|
||||||
pub static MockWeightInfo: bool = false;
|
pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real;
|
||||||
pub static MaxElectingVoters: VoterIndex = u32::max_value();
|
pub static MaxElectingVoters: VoterIndex = u32::max_value();
|
||||||
pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value();
|
pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value();
|
||||||
|
|
||||||
@@ -314,85 +321,6 @@ impl InstantElectionProvider for MockFallback {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hopefully this won't be too much of a hassle to maintain.
|
|
||||||
pub struct DualMockWeightInfo;
|
|
||||||
impl multi_phase::weights::WeightInfo for DualMockWeightInfo {
|
|
||||||
fn on_initialize_nothing() -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
Zero::zero()
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::on_initialize_nothing()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn create_snapshot_internal(v: u32, t: u32) -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
Zero::zero()
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::create_snapshot_internal(v, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn on_initialize_open_signed() -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
Zero::zero()
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::on_initialize_open_signed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn on_initialize_open_unsigned() -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
Zero::zero()
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::on_initialize_open_unsigned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn elect_queued(a: u32, d: u32) -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
Zero::zero()
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::elect_queued(a, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn finalize_signed_phase_accept_solution() -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
Zero::zero()
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::finalize_signed_phase_accept_solution()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn finalize_signed_phase_reject_solution() -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
Zero::zero()
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::finalize_signed_phase_reject_solution()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn submit() -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
Zero::zero()
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::submit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
// 10 base
|
|
||||||
// 5 per edge.
|
|
||||||
(10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight))
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::submit_unsigned(v, t, a, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn feasibility_check(v: u32, t: u32, a: u32, d: u32) -> Weight {
|
|
||||||
if MockWeightInfo::get() {
|
|
||||||
// 10 base
|
|
||||||
// 5 per edge.
|
|
||||||
(10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight))
|
|
||||||
} else {
|
|
||||||
<() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub static Balancing: Option<(usize, ExtendedBalance)> = Some((0, 0));
|
pub static Balancing: Option<(usize, ExtendedBalance)> = Some((0, 0));
|
||||||
}
|
}
|
||||||
@@ -410,6 +338,24 @@ impl BenchmarkingConfig for TestBenchmarkingConfig {
|
|||||||
const MAXIMUM_TARGETS: u32 = 200;
|
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 Solution = TestNposSolution;
|
||||||
|
|
||||||
|
fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight {
|
||||||
|
match MockWeightInfo::get() {
|
||||||
|
MockedWeightInfo::Basic =>
|
||||||
|
(10 as Weight).saturating_add((5 as Weight).saturating_mul(a as Weight)),
|
||||||
|
MockedWeightInfo::Complex => (0 * v + 0 * t + 1000 * a + 0 * d) as Weight,
|
||||||
|
MockedWeightInfo::Real =>
|
||||||
|
<() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl crate::Config for Runtime {
|
impl crate::Config for Runtime {
|
||||||
type Event = Event;
|
type Event = Event;
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
@@ -419,8 +365,6 @@ impl crate::Config for Runtime {
|
|||||||
type BetterUnsignedThreshold = BetterUnsignedThreshold;
|
type BetterUnsignedThreshold = BetterUnsignedThreshold;
|
||||||
type BetterSignedThreshold = BetterSignedThreshold;
|
type BetterSignedThreshold = BetterSignedThreshold;
|
||||||
type OffchainRepeat = OffchainRepeat;
|
type OffchainRepeat = OffchainRepeat;
|
||||||
type MinerMaxWeight = MinerMaxWeight;
|
|
||||||
type MinerMaxLength = MinerMaxLength;
|
|
||||||
type MinerTxPriority = MinerTxPriority;
|
type MinerTxPriority = MinerTxPriority;
|
||||||
type SignedRewardBase = SignedRewardBase;
|
type SignedRewardBase = SignedRewardBase;
|
||||||
type SignedDepositBase = SignedDepositBase;
|
type SignedDepositBase = SignedDepositBase;
|
||||||
@@ -432,14 +376,14 @@ impl crate::Config for Runtime {
|
|||||||
type SlashHandler = ();
|
type SlashHandler = ();
|
||||||
type RewardHandler = ();
|
type RewardHandler = ();
|
||||||
type DataProvider = StakingMock;
|
type DataProvider = StakingMock;
|
||||||
type WeightInfo = DualMockWeightInfo;
|
type WeightInfo = ();
|
||||||
type BenchmarkingConfig = TestBenchmarkingConfig;
|
type BenchmarkingConfig = TestBenchmarkingConfig;
|
||||||
type Fallback = MockFallback;
|
type Fallback = MockFallback;
|
||||||
type GovernanceFallback = NoFallback<Self>;
|
type GovernanceFallback = NoFallback<Self>;
|
||||||
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
|
type ForceOrigin = frame_system::EnsureRoot<AccountId>;
|
||||||
type Solution = TestNposSolution;
|
|
||||||
type MaxElectingVoters = MaxElectingVoters;
|
type MaxElectingVoters = MaxElectingVoters;
|
||||||
type MaxElectableTargets = MaxElectableTargets;
|
type MaxElectableTargets = MaxElectableTargets;
|
||||||
|
type MinerConfig = Self;
|
||||||
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
|
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,7 +506,7 @@ impl ExtBuilder {
|
|||||||
<MinerMaxWeight>::set(weight);
|
<MinerMaxWeight>::set(weight);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn mock_weight_info(self, mock: bool) -> Self {
|
pub fn mock_weight_info(self, mock: MockedWeightInfo) -> Self {
|
||||||
<MockWeightInfo>::set(mock);
|
<MockWeightInfo>::set(mock);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
//! The signed phase implementation.
|
//! The signed phase implementation.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Config, ElectionCompute, Pallet, QueuedSolution, RawSolution, ReadySolution,
|
unsigned::MinerConfig, Config, ElectionCompute, Pallet, QueuedSolution, RawSolution,
|
||||||
SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap, SolutionOf,
|
ReadySolution, SignedSubmissionIndices, SignedSubmissionNextIndex, SignedSubmissionsMap,
|
||||||
SolutionOrSnapshotSize, Weight, WeightInfo,
|
SolutionOf, SolutionOrSnapshotSize, Weight, WeightInfo,
|
||||||
};
|
};
|
||||||
use codec::{Decode, Encode, HasCompact};
|
use codec::{Decode, Encode, HasCompact};
|
||||||
use frame_election_provider_support::NposSolution;
|
use frame_election_provider_support::NposSolution;
|
||||||
@@ -93,8 +93,11 @@ pub type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency<
|
|||||||
pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
|
pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
|
||||||
<T as frame_system::Config>::AccountId,
|
<T as frame_system::Config>::AccountId,
|
||||||
>>::NegativeImbalance;
|
>>::NegativeImbalance;
|
||||||
pub type SignedSubmissionOf<T> =
|
pub type SignedSubmissionOf<T> = SignedSubmission<
|
||||||
SignedSubmission<<T as frame_system::Config>::AccountId, BalanceOf<T>, SolutionOf<T>>;
|
<T as frame_system::Config>::AccountId,
|
||||||
|
BalanceOf<T>,
|
||||||
|
<<T as crate::Config>::MinerConfig as MinerConfig>::Solution,
|
||||||
|
>;
|
||||||
|
|
||||||
pub type SubmissionIndicesOf<T> =
|
pub type SubmissionIndicesOf<T> =
|
||||||
BoundedBTreeMap<ElectionScore, u32, <T as Config>::SignedMaxSubmissions>;
|
BoundedBTreeMap<ElectionScore, u32, <T as Config>::SignedMaxSubmissions>;
|
||||||
@@ -482,12 +485,12 @@ impl<T: Config> Pallet<T> {
|
|||||||
T::SlashHandler::on_unbalanced(negative_imbalance);
|
T::SlashHandler::on_unbalanced(negative_imbalance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The feasibility weight of the given raw solution.
|
/// The weight of the given raw solution.
|
||||||
pub fn feasibility_weight_of(
|
pub fn solution_weight_of(
|
||||||
raw_solution: &RawSolution<SolutionOf<T>>,
|
raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
|
||||||
size: SolutionOrSnapshotSize,
|
size: SolutionOrSnapshotSize,
|
||||||
) -> Weight {
|
) -> Weight {
|
||||||
T::WeightInfo::feasibility_check(
|
T::MinerConfig::solution_weight(
|
||||||
size.voters,
|
size.voters,
|
||||||
size.targets,
|
size.targets,
|
||||||
raw_solution.solution.voter_count() as u32,
|
raw_solution.solution.voter_count() as u32,
|
||||||
@@ -503,12 +506,12 @@ impl<T: Config> Pallet<T> {
|
|||||||
/// 2. a per-byte deposit, for renting the state usage.
|
/// 2. a per-byte deposit, for renting the state usage.
|
||||||
/// 3. a per-weight deposit, for the potential weight usage in an upcoming on_initialize
|
/// 3. a per-weight deposit, for the potential weight usage in an upcoming on_initialize
|
||||||
pub fn deposit_for(
|
pub fn deposit_for(
|
||||||
raw_solution: &RawSolution<SolutionOf<T>>,
|
raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
|
||||||
size: SolutionOrSnapshotSize,
|
size: SolutionOrSnapshotSize,
|
||||||
) -> BalanceOf<T> {
|
) -> BalanceOf<T> {
|
||||||
let encoded_len: u32 = raw_solution.encoded_size().saturated_into();
|
let encoded_len: u32 = raw_solution.encoded_size().saturated_into();
|
||||||
let encoded_len: BalanceOf<T> = encoded_len.into();
|
let encoded_len: BalanceOf<T> = encoded_len.into();
|
||||||
let feasibility_weight = Self::feasibility_weight_of(raw_solution, size);
|
let feasibility_weight = Self::solution_weight_of(raw_solution, size);
|
||||||
|
|
||||||
let len_deposit = T::SignedDepositByte::get().saturating_mul(encoded_len);
|
let len_deposit = T::SignedDepositByte::get().saturating_mul(encoded_len);
|
||||||
let weight_deposit =
|
let weight_deposit =
|
||||||
@@ -525,8 +528,8 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
mock::{
|
mock::{
|
||||||
balances, raw_solution, roll_to, Balances, ExtBuilder, MultiPhase, Origin, Runtime,
|
balances, raw_solution, roll_to, Balances, ExtBuilder, MockedWeightInfo, MultiPhase,
|
||||||
SignedMaxRefunds, SignedMaxSubmissions, SignedMaxWeight,
|
Origin, Runtime, SignedMaxRefunds, SignedMaxSubmissions, SignedMaxWeight,
|
||||||
},
|
},
|
||||||
Error, Perbill, Phase,
|
Error, Perbill, Phase,
|
||||||
};
|
};
|
||||||
@@ -955,14 +958,13 @@ mod tests {
|
|||||||
fn cannot_consume_too_much_future_weight() {
|
fn cannot_consume_too_much_future_weight() {
|
||||||
ExtBuilder::default()
|
ExtBuilder::default()
|
||||||
.signed_weight(40)
|
.signed_weight(40)
|
||||||
.mock_weight_info(true)
|
.mock_weight_info(MockedWeightInfo::Basic)
|
||||||
.build_and_execute(|| {
|
.build_and_execute(|| {
|
||||||
roll_to(15);
|
roll_to(15);
|
||||||
assert!(MultiPhase::current_phase().is_signed());
|
assert!(MultiPhase::current_phase().is_signed());
|
||||||
|
|
||||||
let (raw, witness) =
|
let (raw, witness) = MultiPhase::mine_solution().unwrap();
|
||||||
MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
|
let solution_weight = <Runtime as MinerConfig>::solution_weight(
|
||||||
let solution_weight = <Runtime as Config>::WeightInfo::feasibility_check(
|
|
||||||
witness.voters,
|
witness.voters,
|
||||||
witness.targets,
|
witness.targets,
|
||||||
raw.solution.voter_count() as u32,
|
raw.solution.voter_count() as u32,
|
||||||
|
|||||||
@@ -20,14 +20,15 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
helpers, Call, Config, ElectionCompute, Error, FeasibilityError, Pallet, RawSolution,
|
helpers, Call, Config, ElectionCompute, Error, FeasibilityError, Pallet, RawSolution,
|
||||||
ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight,
|
ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight,
|
||||||
WeightInfo,
|
|
||||||
};
|
};
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use frame_election_provider_support::{NposSolution, NposSolver, PerThing128};
|
use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight};
|
||||||
use frame_support::{dispatch::DispatchResult, ensure, traits::Get};
|
use frame_support::{dispatch::DispatchResult, ensure, traits::Get, BoundedVec};
|
||||||
use frame_system::offchain::SubmitTransaction;
|
use frame_system::offchain::SubmitTransaction;
|
||||||
|
use scale_info::TypeInfo;
|
||||||
use sp_npos_elections::{
|
use sp_npos_elections::{
|
||||||
assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult,
|
assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult,
|
||||||
|
ElectionScore,
|
||||||
};
|
};
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
offchain::storage::{MutateStorageError, StorageValueRef},
|
offchain::storage::{MutateStorageError, StorageValueRef},
|
||||||
@@ -47,6 +48,12 @@ pub(crate) const OFFCHAIN_CACHED_CALL: &[u8] = b"parity/multi-phase-unsigned-ele
|
|||||||
/// voted.
|
/// voted.
|
||||||
pub type VoterOf<T> = frame_election_provider_support::VoterOf<<T as Config>::DataProvider>;
|
pub type VoterOf<T> = frame_election_provider_support::VoterOf<<T as Config>::DataProvider>;
|
||||||
|
|
||||||
|
/// Same as [`VoterOf`], but parameterized by the `MinerConfig`.
|
||||||
|
pub type MinerVoterOf<T> = frame_election_provider_support::Voter<
|
||||||
|
<T as MinerConfig>::AccountId,
|
||||||
|
<T as MinerConfig>::MaxVotesPerVoter,
|
||||||
|
>;
|
||||||
|
|
||||||
/// The relative distribution of a voter's stake among the winning targets.
|
/// The relative distribution of a voter's stake among the winning targets.
|
||||||
pub type Assignment<T> =
|
pub type Assignment<T> =
|
||||||
sp_npos_elections::Assignment<<T as frame_system::Config>::AccountId, SolutionAccuracyOf<T>>;
|
sp_npos_elections::Assignment<<T as frame_system::Config>::AccountId, SolutionAccuracyOf<T>>;
|
||||||
@@ -59,7 +66,7 @@ pub type IndexAssignmentOf<T> = frame_election_provider_support::IndexAssignment
|
|||||||
pub type SolverErrorOf<T> = <<T as Config>::Solver as NposSolver>::Error;
|
pub type SolverErrorOf<T> = <<T as Config>::Solver as NposSolver>::Error;
|
||||||
/// Error type for operations related to the OCW npos solution miner.
|
/// Error type for operations related to the OCW npos solution miner.
|
||||||
#[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)]
|
#[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)]
|
||||||
pub enum MinerError<T: Config> {
|
pub enum MinerError {
|
||||||
/// An internal error in the NPoS elections crate.
|
/// An internal error in the NPoS elections crate.
|
||||||
NposElections(sp_npos_elections::Error),
|
NposElections(sp_npos_elections::Error),
|
||||||
/// Snapshot data was unavailable unexpectedly.
|
/// Snapshot data was unavailable unexpectedly.
|
||||||
@@ -81,23 +88,23 @@ pub enum MinerError<T: Config> {
|
|||||||
/// There are no more voters to remove to trim the solution.
|
/// There are no more voters to remove to trim the solution.
|
||||||
NoMoreVoters,
|
NoMoreVoters,
|
||||||
/// An error from the solver.
|
/// An error from the solver.
|
||||||
Solver(SolverErrorOf<T>),
|
Solver,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> From<sp_npos_elections::Error> for MinerError<T> {
|
impl From<sp_npos_elections::Error> for MinerError {
|
||||||
fn from(e: sp_npos_elections::Error) -> Self {
|
fn from(e: sp_npos_elections::Error) -> Self {
|
||||||
MinerError::NposElections(e)
|
MinerError::NposElections(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> From<FeasibilityError> for MinerError<T> {
|
impl From<FeasibilityError> for MinerError {
|
||||||
fn from(e: FeasibilityError) -> Self {
|
fn from(e: FeasibilityError) -> Self {
|
||||||
MinerError::Feasibility(e)
|
MinerError::Feasibility(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save a given call into OCW storage.
|
/// Save a given call into OCW storage.
|
||||||
fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError<T>> {
|
fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError> {
|
||||||
log!(debug, "saving a call to the offchain storage.");
|
log!(debug, "saving a call to the offchain storage.");
|
||||||
let storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL);
|
let storage = StorageValueRef::persistent(OFFCHAIN_CACHED_CALL);
|
||||||
match storage.mutate::<_, (), _>(|_| Ok(call.clone())) {
|
match storage.mutate::<_, (), _>(|_| Ok(call.clone())) {
|
||||||
@@ -115,7 +122,7 @@ fn save_solution<T: Config>(call: &Call<T>) -> Result<(), MinerError<T>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get a saved solution from OCW storage if it exists.
|
/// Get a saved solution from OCW storage if it exists.
|
||||||
fn restore_solution<T: Config>() -> Result<Call<T>, MinerError<T>> {
|
fn restore_solution<T: Config>() -> Result<Call<T>, MinerError> {
|
||||||
StorageValueRef::persistent(OFFCHAIN_CACHED_CALL)
|
StorageValueRef::persistent(OFFCHAIN_CACHED_CALL)
|
||||||
.get()
|
.get()
|
||||||
.ok()
|
.ok()
|
||||||
@@ -146,9 +153,46 @@ fn ocw_solution_exists<T: Config>() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
|
/// Mine a new npos solution.
|
||||||
|
///
|
||||||
|
/// The Npos Solver type, `S`, must have the same AccountId and Error type as the
|
||||||
|
/// [`crate::Config::Solver`] in order to create a unified return type.
|
||||||
|
pub fn mine_solution(
|
||||||
|
) -> Result<(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize), MinerError> {
|
||||||
|
let RoundSnapshot { voters, targets } =
|
||||||
|
Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
|
||||||
|
let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
|
||||||
|
let (solution, score, size) = Miner::<T::MinerConfig>::mine_solution_with_snapshot::<
|
||||||
|
T::Solver,
|
||||||
|
>(voters, targets, desired_targets)?;
|
||||||
|
let round = Self::round();
|
||||||
|
Ok((RawSolution { solution, score, round }, size))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which
|
||||||
|
/// is ready to be submitted to the chain.
|
||||||
|
///
|
||||||
|
/// Will always reduce the solution as well.
|
||||||
|
pub fn prepare_election_result<Accuracy: PerThing128>(
|
||||||
|
election_result: ElectionResult<T::AccountId, Accuracy>,
|
||||||
|
) -> Result<(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize), MinerError> {
|
||||||
|
let RoundSnapshot { voters, targets } =
|
||||||
|
Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
|
||||||
|
let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
|
||||||
|
let (solution, score, size) =
|
||||||
|
Miner::<T::MinerConfig>::prepare_election_result_with_snapshot(
|
||||||
|
election_result,
|
||||||
|
voters,
|
||||||
|
targets,
|
||||||
|
desired_targets,
|
||||||
|
)?;
|
||||||
|
let round = Self::round();
|
||||||
|
Ok((RawSolution { solution, score, round }, size))
|
||||||
|
}
|
||||||
|
|
||||||
/// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit
|
/// Attempt to restore a solution from cache. Otherwise, compute it fresh. Either way, submit
|
||||||
/// if our call's score is greater than that of the cached solution.
|
/// if our call's score is greater than that of the cached solution.
|
||||||
pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError<T>> {
|
pub fn restore_or_compute_then_maybe_submit() -> Result<(), MinerError> {
|
||||||
log!(debug, "miner attempting to restore or compute an unsigned solution.");
|
log!(debug, "miner attempting to restore or compute an unsigned solution.");
|
||||||
|
|
||||||
let call = restore_solution::<T>()
|
let call = restore_solution::<T>()
|
||||||
@@ -162,7 +206,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
Err(MinerError::SolutionCallInvalid)
|
Err(MinerError::SolutionCallInvalid)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.or_else::<MinerError<T>, _>(|error| {
|
.or_else::<MinerError, _>(|error| {
|
||||||
log!(debug, "restoring solution failed due to {:?}", error);
|
log!(debug, "restoring solution failed due to {:?}", error);
|
||||||
match error {
|
match error {
|
||||||
MinerError::NoStoredSolution => {
|
MinerError::NoStoredSolution => {
|
||||||
@@ -193,7 +237,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction.
|
/// Mine a new solution, cache it, and submit it back to the chain as an unsigned transaction.
|
||||||
pub fn mine_check_save_submit() -> Result<(), MinerError<T>> {
|
pub fn mine_check_save_submit() -> Result<(), MinerError> {
|
||||||
log!(debug, "miner attempting to compute an unsigned solution.");
|
log!(debug, "miner attempting to compute an unsigned solution.");
|
||||||
|
|
||||||
let call = Self::mine_checked_call()?;
|
let call = Self::mine_checked_call()?;
|
||||||
@@ -202,7 +246,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Mine a new solution as a call. Performs all checks.
|
/// Mine a new solution as a call. Performs all checks.
|
||||||
pub fn mine_checked_call() -> Result<Call<T>, MinerError<T>> {
|
pub fn mine_checked_call() -> Result<Call<T>, MinerError> {
|
||||||
// get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID.
|
// get the solution, with a load of checks to ensure if submitted, IT IS ABSOLUTELY VALID.
|
||||||
let (raw_solution, witness) = Self::mine_and_check()?;
|
let (raw_solution, witness) = Self::mine_and_check()?;
|
||||||
|
|
||||||
@@ -219,7 +263,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
Ok(call)
|
Ok(call)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit_call(call: Call<T>) -> Result<(), MinerError<T>> {
|
fn submit_call(call: Call<T>) -> Result<(), MinerError> {
|
||||||
log!(debug, "miner submitting a solution as an unsigned transaction");
|
log!(debug, "miner submitting a solution as an unsigned transaction");
|
||||||
|
|
||||||
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
|
||||||
@@ -230,9 +274,9 @@ impl<T: Config> Pallet<T> {
|
|||||||
//
|
//
|
||||||
// Performance: note that it internally clones the provided solution.
|
// Performance: note that it internally clones the provided solution.
|
||||||
pub fn basic_checks(
|
pub fn basic_checks(
|
||||||
raw_solution: &RawSolution<SolutionOf<T>>,
|
raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
|
||||||
solution_type: &str,
|
solution_type: &str,
|
||||||
) -> Result<(), MinerError<T>> {
|
) -> Result<(), MinerError> {
|
||||||
Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| {
|
Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| {
|
||||||
log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err);
|
log!(debug, "pre-dispatch checks failed for {} solution: {:?}", solution_type, err);
|
||||||
MinerError::PreDispatchChecksFailed(err)
|
MinerError::PreDispatchChecksFailed(err)
|
||||||
@@ -255,45 +299,155 @@ impl<T: Config> Pallet<T> {
|
|||||||
/// If you want a checked solution and submit it at the same time, use
|
/// If you want a checked solution and submit it at the same time, use
|
||||||
/// [`Pallet::mine_check_save_submit`].
|
/// [`Pallet::mine_check_save_submit`].
|
||||||
pub fn mine_and_check(
|
pub fn mine_and_check(
|
||||||
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>> {
|
) -> Result<(RawSolution<SolutionOf<T::MinerConfig>>, SolutionOrSnapshotSize), MinerError> {
|
||||||
let (raw_solution, witness) = Self::mine_solution::<T::Solver>()?;
|
let (raw_solution, witness) = Self::mine_solution()?;
|
||||||
Self::basic_checks(&raw_solution, "mined")?;
|
Self::basic_checks(&raw_solution, "mined")?;
|
||||||
Ok((raw_solution, witness))
|
Ok((raw_solution, witness))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mine a new npos solution.
|
/// Checks if an execution of the offchain worker is permitted at the given block number, or
|
||||||
|
/// not.
|
||||||
///
|
///
|
||||||
/// The Npos Solver type, `S`, must have the same AccountId and Error type as the
|
/// This makes sure that
|
||||||
/// [`crate::Config::Solver`] in order to create a unified return type.
|
/// 1. we don't run on previous blocks in case of a re-org
|
||||||
pub fn mine_solution<S>(
|
/// 2. we don't run twice within a window of length `T::OffchainRepeat`.
|
||||||
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>>
|
///
|
||||||
where
|
/// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()`
|
||||||
S: NposSolver<AccountId = T::AccountId, Error = SolverErrorOf<T>>,
|
/// is returned, `now` is written in storage and will be used in further calls as the baseline.
|
||||||
{
|
pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> {
|
||||||
let RoundSnapshot { voters, targets } =
|
let threshold = T::OffchainRepeat::get();
|
||||||
Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
|
let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK);
|
||||||
let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
|
|
||||||
|
|
||||||
S::solve(desired_targets as usize, targets, voters)
|
let mutate_stat = last_block.mutate::<_, &'static str, _>(
|
||||||
.map_err(|e| MinerError::Solver::<T>(e))
|
|maybe_head: Result<Option<T::BlockNumber>, _>| {
|
||||||
.and_then(|e| Self::prepare_election_result::<S::Accuracy>(e))
|
match maybe_head {
|
||||||
|
Ok(Some(head)) if now < head => Err("fork."),
|
||||||
|
Ok(Some(head)) if now >= head && now <= head + threshold =>
|
||||||
|
Err("recently executed."),
|
||||||
|
Ok(Some(head)) if now > head + threshold => {
|
||||||
|
// we can run again now. Write the new head.
|
||||||
|
Ok(now)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
// value doesn't exists. Probably this node just booted up. Write, and run
|
||||||
|
Ok(now)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
match mutate_stat {
|
||||||
|
// all good
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
// failed to write.
|
||||||
|
Err(MutateStorageError::ConcurrentModification(_)) =>
|
||||||
|
Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")),
|
||||||
|
// fork etc.
|
||||||
|
Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert a raw solution from [`sp_npos_elections::ElectionResult`] to [`RawSolution`], which
|
/// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned
|
||||||
/// is ready to be submitted to the chain.
|
/// transaction.
|
||||||
///
|
///
|
||||||
/// Will always reduce the solution as well.
|
/// Can optionally also be called during dispatch, if needed.
|
||||||
pub fn prepare_election_result<Accuracy: PerThing128>(
|
///
|
||||||
|
/// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's
|
||||||
|
/// code, so that we do less and less storage reads here.
|
||||||
|
pub fn unsigned_pre_dispatch_checks(
|
||||||
|
raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
|
||||||
|
) -> DispatchResult {
|
||||||
|
// ensure solution is timely. Don't panic yet. This is a cheap check.
|
||||||
|
ensure!(Self::current_phase().is_unsigned_open(), Error::<T>::PreDispatchEarlySubmission);
|
||||||
|
|
||||||
|
// ensure round is current
|
||||||
|
ensure!(Self::round() == raw_solution.round, Error::<T>::OcwCallWrongEra);
|
||||||
|
|
||||||
|
// ensure correct number of winners.
|
||||||
|
ensure!(
|
||||||
|
Self::desired_targets().unwrap_or_default() ==
|
||||||
|
raw_solution.solution.unique_targets().len() as u32,
|
||||||
|
Error::<T>::PreDispatchWrongWinnerCount,
|
||||||
|
);
|
||||||
|
|
||||||
|
// ensure score is being improved. Panic henceforth.
|
||||||
|
ensure!(
|
||||||
|
Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution
|
||||||
|
.score
|
||||||
|
.strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())),
|
||||||
|
Error::<T>::PreDispatchWeakSubmission,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configurations for a miner that comes with this pallet.
|
||||||
|
pub trait MinerConfig {
|
||||||
|
/// The account id type.
|
||||||
|
type AccountId: Ord + Clone + codec::Codec + sp_std::fmt::Debug;
|
||||||
|
/// The solution that the miner is mining.
|
||||||
|
type Solution: codec::Codec
|
||||||
|
+ Default
|
||||||
|
+ PartialEq
|
||||||
|
+ Eq
|
||||||
|
+ Clone
|
||||||
|
+ sp_std::fmt::Debug
|
||||||
|
+ Ord
|
||||||
|
+ NposSolution
|
||||||
|
+ TypeInfo;
|
||||||
|
/// Maximum number of votes per voter in the snapshots.
|
||||||
|
type MaxVotesPerVoter;
|
||||||
|
/// Maximum length of the solution that the miner is allowed to generate.
|
||||||
|
///
|
||||||
|
/// Solutions are trimmed to respect this.
|
||||||
|
type MaxLength: Get<u32>;
|
||||||
|
/// Maximum weight of the solution that the miner is allowed to generate.
|
||||||
|
///
|
||||||
|
/// Solutions are trimmed to respect this.
|
||||||
|
///
|
||||||
|
/// The weight is computed using `solution_weight`.
|
||||||
|
type MaxWeight: Get<Weight>;
|
||||||
|
/// Something that can compute the weight of a solution.
|
||||||
|
///
|
||||||
|
/// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`].
|
||||||
|
fn solution_weight(voters: u32, targets: u32, active_voters: u32, degree: u32) -> Weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A base miner, suitable to be used for both signed and unsigned submissions.
|
||||||
|
pub struct Miner<T: MinerConfig>(sp_std::marker::PhantomData<T>);
|
||||||
|
impl<T: MinerConfig> Miner<T> {
|
||||||
|
/// Same as [`Pallet::mine_solution`], but the input snapshot data must be given.
|
||||||
|
pub fn mine_solution_with_snapshot<S>(
|
||||||
|
voters: Vec<(T::AccountId, VoteWeight, BoundedVec<T::AccountId, T::MaxVotesPerVoter>)>,
|
||||||
|
targets: Vec<T::AccountId>,
|
||||||
|
desired_targets: u32,
|
||||||
|
) -> Result<(SolutionOf<T>, ElectionScore, SolutionOrSnapshotSize), MinerError>
|
||||||
|
where
|
||||||
|
S: NposSolver<AccountId = T::AccountId>,
|
||||||
|
{
|
||||||
|
S::solve(desired_targets as usize, targets.clone(), voters.clone())
|
||||||
|
.map_err(|e| {
|
||||||
|
log_no_system!(error, "solver error: {:?}", e);
|
||||||
|
MinerError::Solver
|
||||||
|
})
|
||||||
|
.and_then(|e| {
|
||||||
|
Self::prepare_election_result_with_snapshot::<S::Accuracy>(
|
||||||
|
e,
|
||||||
|
voters,
|
||||||
|
targets,
|
||||||
|
desired_targets,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as [`Pallet::prepare_election_result`], but the input snapshot mut be given as inputs.
|
||||||
|
pub fn prepare_election_result_with_snapshot<Accuracy: PerThing128>(
|
||||||
election_result: ElectionResult<T::AccountId, Accuracy>,
|
election_result: ElectionResult<T::AccountId, Accuracy>,
|
||||||
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError<T>> {
|
voters: Vec<(T::AccountId, VoteWeight, BoundedVec<T::AccountId, T::MaxVotesPerVoter>)>,
|
||||||
// NOTE: This code path is generally not optimized as it is run offchain. Could use some at
|
targets: Vec<T::AccountId>,
|
||||||
// some point though.
|
desired_targets: u32,
|
||||||
|
) -> Result<(SolutionOf<T>, ElectionScore, SolutionOrSnapshotSize), MinerError> {
|
||||||
// storage items. Note: we have already read this from storage, they must be in cache.
|
|
||||||
let RoundSnapshot { voters, targets } =
|
|
||||||
Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
|
|
||||||
let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
|
|
||||||
|
|
||||||
// now make some helper closures.
|
// now make some helper closures.
|
||||||
let cache = helpers::generate_voter_cache::<T>(&voters);
|
let cache = helpers::generate_voter_cache::<T>(&voters);
|
||||||
let voter_index = helpers::voter_index_fn::<T>(&cache);
|
let voter_index = helpers::voter_index_fn::<T>(&cache);
|
||||||
@@ -355,11 +509,11 @@ impl<T: Config> Pallet<T> {
|
|||||||
Self::trim_assignments_weight(
|
Self::trim_assignments_weight(
|
||||||
desired_targets,
|
desired_targets,
|
||||||
size,
|
size,
|
||||||
T::MinerMaxWeight::get(),
|
T::MaxWeight::get(),
|
||||||
&mut index_assignments,
|
&mut index_assignments,
|
||||||
);
|
);
|
||||||
Self::trim_assignments_length(
|
Self::trim_assignments_length(
|
||||||
T::MinerMaxLength::get(),
|
T::MaxLength::get(),
|
||||||
&mut index_assignments,
|
&mut index_assignments,
|
||||||
&encoded_size_of,
|
&encoded_size_of,
|
||||||
)?;
|
)?;
|
||||||
@@ -370,43 +524,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
// re-calc score.
|
// re-calc score.
|
||||||
let score = solution.clone().score(stake_of, voter_at, target_at)?;
|
let score = solution.clone().score(stake_of, voter_at, target_at)?;
|
||||||
|
|
||||||
let round = Self::round();
|
Ok((solution, score, size))
|
||||||
Ok((RawSolution { solution, score, round }, size))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Greedily reduce the size of the solution to fit into the block w.r.t. weight.
|
|
||||||
///
|
|
||||||
/// The weight of the solution is foremost a function of the number of voters (i.e.
|
|
||||||
/// `assignments.len()`). Aside from this, the other components of the weight are invariant. The
|
|
||||||
/// number of winners shall not be changed (otherwise the solution is invalid) and the
|
|
||||||
/// `ElectionSize` is merely a representation of the total number of stakers.
|
|
||||||
///
|
|
||||||
/// Thus, we reside to stripping away some voters from the `assignments`.
|
|
||||||
///
|
|
||||||
/// Note that the solution is already computed, and the winners are elected based on the merit
|
|
||||||
/// of the entire stake in the system. Nonetheless, some of the voters will be removed further
|
|
||||||
/// down the line.
|
|
||||||
///
|
|
||||||
/// Indeed, the score must be computed **after** this step. If this step reduces the score too
|
|
||||||
/// much or remove a winner, then the solution must be discarded **after** this step.
|
|
||||||
pub fn trim_assignments_weight(
|
|
||||||
desired_targets: u32,
|
|
||||||
size: SolutionOrSnapshotSize,
|
|
||||||
max_weight: Weight,
|
|
||||||
assignments: &mut Vec<IndexAssignmentOf<T>>,
|
|
||||||
) {
|
|
||||||
let maximum_allowed_voters =
|
|
||||||
Self::maximum_voter_for_weight::<T::WeightInfo>(desired_targets, size, max_weight);
|
|
||||||
let removing: usize =
|
|
||||||
assignments.len().saturating_sub(maximum_allowed_voters.saturated_into());
|
|
||||||
log!(
|
|
||||||
debug,
|
|
||||||
"from {} assignments, truncating to {} for weight, removing {}",
|
|
||||||
assignments.len(),
|
|
||||||
maximum_allowed_voters,
|
|
||||||
removing,
|
|
||||||
);
|
|
||||||
assignments.truncate(maximum_allowed_voters as usize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Greedily reduce the size of the solution to fit into the block w.r.t length.
|
/// Greedily reduce the size of the solution to fit into the block w.r.t length.
|
||||||
@@ -427,7 +545,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
max_allowed_length: u32,
|
max_allowed_length: u32,
|
||||||
assignments: &mut Vec<IndexAssignmentOf<T>>,
|
assignments: &mut Vec<IndexAssignmentOf<T>>,
|
||||||
encoded_size_of: impl Fn(&[IndexAssignmentOf<T>]) -> Result<usize, sp_npos_elections::Error>,
|
encoded_size_of: impl Fn(&[IndexAssignmentOf<T>]) -> Result<usize, sp_npos_elections::Error>,
|
||||||
) -> Result<(), MinerError<T>> {
|
) -> Result<(), MinerError> {
|
||||||
// Perform a binary search for the max subset of which can fit into the allowed
|
// Perform a binary search for the max subset of which can fit into the allowed
|
||||||
// length. Having discovered that, we can truncate efficiently.
|
// length. Having discovered that, we can truncate efficiently.
|
||||||
let max_allowed_length: usize = max_allowed_length.saturated_into();
|
let max_allowed_length: usize = max_allowed_length.saturated_into();
|
||||||
@@ -470,7 +588,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
// after this point, we never error.
|
// after this point, we never error.
|
||||||
// check before edit.
|
// check before edit.
|
||||||
|
|
||||||
log!(
|
log_no_system!(
|
||||||
debug,
|
debug,
|
||||||
"from {} assignments, truncating to {} for length, removing {}",
|
"from {} assignments, truncating to {} for length, removing {}",
|
||||||
assignments.len(),
|
assignments.len(),
|
||||||
@@ -482,10 +600,45 @@ impl<T: Config> Pallet<T> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Greedily reduce the size of the solution to fit into the block w.r.t. weight.
|
||||||
|
///
|
||||||
|
/// The weight of the solution is foremost a function of the number of voters (i.e.
|
||||||
|
/// `assignments.len()`). Aside from this, the other components of the weight are invariant. The
|
||||||
|
/// number of winners shall not be changed (otherwise the solution is invalid) and the
|
||||||
|
/// `ElectionSize` is merely a representation of the total number of stakers.
|
||||||
|
///
|
||||||
|
/// Thus, we reside to stripping away some voters from the `assignments`.
|
||||||
|
///
|
||||||
|
/// Note that the solution is already computed, and the winners are elected based on the merit
|
||||||
|
/// of the entire stake in the system. Nonetheless, some of the voters will be removed further
|
||||||
|
/// down the line.
|
||||||
|
///
|
||||||
|
/// Indeed, the score must be computed **after** this step. If this step reduces the score too
|
||||||
|
/// much or remove a winner, then the solution must be discarded **after** this step.
|
||||||
|
pub fn trim_assignments_weight(
|
||||||
|
desired_targets: u32,
|
||||||
|
size: SolutionOrSnapshotSize,
|
||||||
|
max_weight: Weight,
|
||||||
|
assignments: &mut Vec<IndexAssignmentOf<T>>,
|
||||||
|
) {
|
||||||
|
let maximum_allowed_voters =
|
||||||
|
Self::maximum_voter_for_weight(desired_targets, size, max_weight);
|
||||||
|
let removing: usize =
|
||||||
|
assignments.len().saturating_sub(maximum_allowed_voters.saturated_into());
|
||||||
|
log_no_system!(
|
||||||
|
debug,
|
||||||
|
"from {} assignments, truncating to {} for weight, removing {}",
|
||||||
|
assignments.len(),
|
||||||
|
maximum_allowed_voters,
|
||||||
|
removing,
|
||||||
|
);
|
||||||
|
assignments.truncate(maximum_allowed_voters as usize);
|
||||||
|
}
|
||||||
|
|
||||||
/// Find the maximum `len` that a solution can have in order to fit into the block weight.
|
/// Find the maximum `len` that a solution can have in order to fit into the block weight.
|
||||||
///
|
///
|
||||||
/// This only returns a value between zero and `size.nominators`.
|
/// This only returns a value between zero and `size.nominators`.
|
||||||
pub fn maximum_voter_for_weight<W: WeightInfo>(
|
pub fn maximum_voter_for_weight(
|
||||||
desired_winners: u32,
|
desired_winners: u32,
|
||||||
size: SolutionOrSnapshotSize,
|
size: SolutionOrSnapshotSize,
|
||||||
max_weight: Weight,
|
max_weight: Weight,
|
||||||
@@ -499,7 +652,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
|
|
||||||
// helper closures.
|
// helper closures.
|
||||||
let weight_with = |active_voters: u32| -> Weight {
|
let weight_with = |active_voters: u32| -> Weight {
|
||||||
W::submit_unsigned(size.voters, size.targets, active_voters, desired_winners)
|
T::solution_weight(size.voters, size.targets, active_voters, desired_winners)
|
||||||
};
|
};
|
||||||
|
|
||||||
let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result<u32, ()> {
|
let next_voters = |current_weight: Weight, voters: u32, step: u32| -> Result<u32, ()> {
|
||||||
@@ -553,176 +706,65 @@ impl<T: Config> Pallet<T> {
|
|||||||
);
|
);
|
||||||
final_decision
|
final_decision
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if an execution of the offchain worker is permitted at the given block number, or
|
|
||||||
/// not.
|
|
||||||
///
|
|
||||||
/// This makes sure that
|
|
||||||
/// 1. we don't run on previous blocks in case of a re-org
|
|
||||||
/// 2. we don't run twice within a window of length `T::OffchainRepeat`.
|
|
||||||
///
|
|
||||||
/// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()`
|
|
||||||
/// is returned, `now` is written in storage and will be used in further calls as the baseline.
|
|
||||||
pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError<T>> {
|
|
||||||
let threshold = T::OffchainRepeat::get();
|
|
||||||
let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK);
|
|
||||||
|
|
||||||
let mutate_stat = last_block.mutate::<_, &'static str, _>(
|
|
||||||
|maybe_head: Result<Option<T::BlockNumber>, _>| {
|
|
||||||
match maybe_head {
|
|
||||||
Ok(Some(head)) if now < head => Err("fork."),
|
|
||||||
Ok(Some(head)) if now >= head && now <= head + threshold =>
|
|
||||||
Err("recently executed."),
|
|
||||||
Ok(Some(head)) if now > head + threshold => {
|
|
||||||
// we can run again now. Write the new head.
|
|
||||||
Ok(now)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
// value doesn't exists. Probably this node just booted up. Write, and run
|
|
||||||
Ok(now)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
match mutate_stat {
|
|
||||||
// all good
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
// failed to write.
|
|
||||||
Err(MutateStorageError::ConcurrentModification(_)) =>
|
|
||||||
Err(MinerError::Lock("failed to write to offchain db (concurrent modification).")),
|
|
||||||
// fork etc.
|
|
||||||
Err(MutateStorageError::ValueFunctionFailed(why)) => Err(MinerError::Lock(why)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Do the basics checks that MUST happen during the validation and pre-dispatch of an unsigned
|
|
||||||
/// transaction.
|
|
||||||
///
|
|
||||||
/// Can optionally also be called during dispatch, if needed.
|
|
||||||
///
|
|
||||||
/// NOTE: Ideally, these tests should move more and more outside of this and more to the miner's
|
|
||||||
/// code, so that we do less and less storage reads here.
|
|
||||||
pub fn unsigned_pre_dispatch_checks(
|
|
||||||
raw_solution: &RawSolution<SolutionOf<T>>,
|
|
||||||
) -> DispatchResult {
|
|
||||||
// ensure solution is timely. Don't panic yet. This is a cheap check.
|
|
||||||
ensure!(Self::current_phase().is_unsigned_open(), Error::<T>::PreDispatchEarlySubmission);
|
|
||||||
|
|
||||||
// ensure round is current
|
|
||||||
ensure!(Self::round() == raw_solution.round, Error::<T>::OcwCallWrongEra);
|
|
||||||
|
|
||||||
// ensure correct number of winners.
|
|
||||||
ensure!(
|
|
||||||
Self::desired_targets().unwrap_or_default() ==
|
|
||||||
raw_solution.solution.unique_targets().len() as u32,
|
|
||||||
Error::<T>::PreDispatchWrongWinnerCount,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ensure score is being improved. Panic henceforth.
|
|
||||||
ensure!(
|
|
||||||
Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution
|
|
||||||
.score
|
|
||||||
.strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())),
|
|
||||||
Error::<T>::PreDispatchWeakSubmission,
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod max_weight {
|
mod max_weight {
|
||||||
#![allow(unused_variables)]
|
#![allow(unused_variables)]
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::mock::MultiPhase;
|
use crate::mock::{MockWeightInfo, Runtime};
|
||||||
|
|
||||||
struct TestWeight;
|
|
||||||
impl crate::weights::WeightInfo for TestWeight {
|
|
||||||
fn elect_queued(a: u32, d: u32) -> Weight {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
fn create_snapshot_internal(v: u32, t: u32) -> Weight {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
fn on_initialize_nothing() -> Weight {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
fn on_initialize_open_signed() -> Weight {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
fn on_initialize_open_unsigned() -> Weight {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
fn finalize_signed_phase_accept_solution() -> Weight {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
fn finalize_signed_phase_reject_solution() -> Weight {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
fn submit() -> Weight {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
fn submit_unsigned(v: u32, t: u32, a: u32, d: u32) -> Weight {
|
|
||||||
(0 * v + 0 * t + 1000 * a + 0 * d) as Weight
|
|
||||||
}
|
|
||||||
fn feasibility_check(v: u32, _t: u32, a: u32, d: u32) -> Weight {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn find_max_voter_binary_search_works() {
|
fn find_max_voter_binary_search_works() {
|
||||||
let w = SolutionOrSnapshotSize { voters: 10, targets: 0 };
|
let w = SolutionOrSnapshotSize { voters: 10, targets: 0 };
|
||||||
|
MockWeightInfo::set(crate::mock::MockedWeightInfo::Complex);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 0), 0);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1), 0);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 999), 0);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1000), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1001), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1990), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1990), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1999), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 2);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2000), 2);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 2);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2001), 2);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 2);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2010), 2);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2990), 2);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2990), 2);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2999), 2);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2999), 2);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3000), 3);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3000), 3);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 3);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3333), 3);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 5500), 5);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 5500), 5);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 7777), 7);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 7777), 7);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 9999), 9);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 9999), 9);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 10_000), 10);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 10_000), 10);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 10_999), 10);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 10_999), 10);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 11_000), 10);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 11_000), 10);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 22_000), 10);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 22_000), 10);
|
||||||
|
|
||||||
let w = SolutionOrSnapshotSize { voters: 1, targets: 0 };
|
let w = SolutionOrSnapshotSize { voters: 1, targets: 0 };
|
||||||
|
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 0), 0);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1), 0);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 999), 0);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1000), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1001), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1990), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1990), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1999), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2000), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2001), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2010), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3333), 1);
|
||||||
|
|
||||||
let w = SolutionOrSnapshotSize { voters: 2, targets: 0 };
|
let w = SolutionOrSnapshotSize { voters: 2, targets: 0 };
|
||||||
|
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 0), 0);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 0), 0);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1), 0);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1), 0);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 999), 0);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 999), 0);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1000), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1000), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1001), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1001), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 1999), 1);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 1999), 1);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2000), 2);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2000), 2);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2001), 2);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2001), 2);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 2010), 2);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 2010), 2);
|
||||||
assert_eq!(MultiPhase::maximum_voter_for_weight::<TestWeight>(0, w, 3333), 2);
|
assert_eq!(Miner::<Runtime>::maximum_voter_for_weight(0, w, 3333), 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -988,8 +1030,7 @@ mod tests {
|
|||||||
assert_eq!(MultiPhase::desired_targets().unwrap(), 2);
|
assert_eq!(MultiPhase::desired_targets().unwrap(), 2);
|
||||||
|
|
||||||
// mine seq_phragmen solution with 2 iters.
|
// mine seq_phragmen solution with 2 iters.
|
||||||
let (solution, witness) =
|
let (solution, witness) = MultiPhase::mine_solution().unwrap();
|
||||||
MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
|
|
||||||
|
|
||||||
// ensure this solution is valid.
|
// ensure this solution is valid.
|
||||||
assert!(MultiPhase::queued_solution().is_none());
|
assert!(MultiPhase::queued_solution().is_none());
|
||||||
@@ -1002,14 +1043,13 @@ mod tests {
|
|||||||
fn miner_trims_weight() {
|
fn miner_trims_weight() {
|
||||||
ExtBuilder::default()
|
ExtBuilder::default()
|
||||||
.miner_weight(100)
|
.miner_weight(100)
|
||||||
.mock_weight_info(true)
|
.mock_weight_info(crate::mock::MockedWeightInfo::Basic)
|
||||||
.build_and_execute(|| {
|
.build_and_execute(|| {
|
||||||
roll_to(25);
|
roll_to(25);
|
||||||
assert!(MultiPhase::current_phase().is_unsigned());
|
assert!(MultiPhase::current_phase().is_unsigned());
|
||||||
|
|
||||||
let (raw, witness) =
|
let (raw, witness) = MultiPhase::mine_solution().unwrap();
|
||||||
MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
|
let solution_weight = <Runtime as MinerConfig>::solution_weight(
|
||||||
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
|
||||||
witness.voters,
|
witness.voters,
|
||||||
witness.targets,
|
witness.targets,
|
||||||
raw.solution.voter_count() as u32,
|
raw.solution.voter_count() as u32,
|
||||||
@@ -1022,9 +1062,8 @@ mod tests {
|
|||||||
// now reduce the max weight
|
// now reduce the max weight
|
||||||
<MinerMaxWeight>::set(25);
|
<MinerMaxWeight>::set(25);
|
||||||
|
|
||||||
let (raw, witness) =
|
let (raw, witness) = MultiPhase::mine_solution().unwrap();
|
||||||
MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
|
let solution_weight = <Runtime as MinerConfig>::solution_weight(
|
||||||
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
|
||||||
witness.voters,
|
witness.voters,
|
||||||
witness.targets,
|
witness.targets,
|
||||||
raw.solution.voter_count() as u32,
|
raw.solution.voter_count() as u32,
|
||||||
@@ -1044,8 +1083,7 @@ mod tests {
|
|||||||
assert!(MultiPhase::current_phase().is_unsigned());
|
assert!(MultiPhase::current_phase().is_unsigned());
|
||||||
|
|
||||||
// Force the number of winners to be bigger to fail
|
// Force the number of winners to be bigger to fail
|
||||||
let (mut solution, _) =
|
let (mut solution, _) = MultiPhase::mine_solution().unwrap();
|
||||||
MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
|
|
||||||
solution.solution.votes1[0].1 = 4;
|
solution.solution.votes1[0].1 = 4;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -1460,8 +1498,12 @@ mod tests {
|
|||||||
let solution_clone = solution.clone();
|
let solution_clone = solution.clone();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
MultiPhase::trim_assignments_length(encoded_len, &mut assignments, encoded_size_of)
|
Miner::<Runtime>::trim_assignments_length(
|
||||||
.unwrap();
|
encoded_len,
|
||||||
|
&mut assignments,
|
||||||
|
encoded_size_of,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||||
@@ -1481,7 +1523,7 @@ mod tests {
|
|||||||
let solution_clone = solution.clone();
|
let solution_clone = solution.clone();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
MultiPhase::trim_assignments_length(
|
Miner::<Runtime>::trim_assignments_length(
|
||||||
encoded_len as u32 - 1,
|
encoded_len as u32 - 1,
|
||||||
&mut assignments,
|
&mut assignments,
|
||||||
encoded_size_of,
|
encoded_size_of,
|
||||||
@@ -1514,8 +1556,12 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// when
|
// when
|
||||||
MultiPhase::trim_assignments_length(encoded_len - 1, &mut assignments, encoded_size_of)
|
Miner::<Runtime>::trim_assignments_length(
|
||||||
.unwrap();
|
encoded_len - 1,
|
||||||
|
&mut assignments,
|
||||||
|
encoded_size_of,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// then
|
// then
|
||||||
assert_eq!(assignments.len(), count - 1, "we must have removed exactly one assignment");
|
assert_eq!(assignments.len(), count - 1, "we must have removed exactly one assignment");
|
||||||
@@ -1542,11 +1588,11 @@ mod tests {
|
|||||||
assert_eq!(min_solution_size, SolutionOf::<Runtime>::LIMIT);
|
assert_eq!(min_solution_size, SolutionOf::<Runtime>::LIMIT);
|
||||||
|
|
||||||
// all of this should not panic.
|
// all of this should not panic.
|
||||||
MultiPhase::trim_assignments_length(0, &mut assignments, encoded_size_of.clone())
|
Miner::<Runtime>::trim_assignments_length(0, &mut assignments, encoded_size_of.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
MultiPhase::trim_assignments_length(1, &mut assignments, encoded_size_of.clone())
|
Miner::<Runtime>::trim_assignments_length(1, &mut assignments, encoded_size_of.clone())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
MultiPhase::trim_assignments_length(
|
Miner::<Runtime>::trim_assignments_length(
|
||||||
min_solution_size as u32,
|
min_solution_size as u32,
|
||||||
&mut assignments,
|
&mut assignments,
|
||||||
encoded_size_of,
|
encoded_size_of,
|
||||||
@@ -1563,7 +1609,7 @@ mod tests {
|
|||||||
|
|
||||||
// trim to min solution size.
|
// trim to min solution size.
|
||||||
let min_solution_size = SolutionOf::<Runtime>::LIMIT as u32;
|
let min_solution_size = SolutionOf::<Runtime>::LIMIT as u32;
|
||||||
MultiPhase::trim_assignments_length(
|
Miner::<Runtime>::trim_assignments_length(
|
||||||
min_solution_size,
|
min_solution_size,
|
||||||
&mut assignments,
|
&mut assignments,
|
||||||
encoded_size_of,
|
encoded_size_of,
|
||||||
@@ -1582,15 +1628,15 @@ mod tests {
|
|||||||
roll_to(25);
|
roll_to(25);
|
||||||
|
|
||||||
// how long would the default solution be?
|
// how long would the default solution be?
|
||||||
let solution = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
|
let solution = MultiPhase::mine_solution().unwrap();
|
||||||
let max_length = <Runtime as Config>::MinerMaxLength::get();
|
let max_length = <Runtime as MinerConfig>::MaxLength::get();
|
||||||
let solution_size = solution.0.solution.encoded_size();
|
let solution_size = solution.0.solution.encoded_size();
|
||||||
assert!(solution_size <= max_length as usize);
|
assert!(solution_size <= max_length as usize);
|
||||||
|
|
||||||
// now set the max size to less than the actual size and regenerate
|
// now set the max size to less than the actual size and regenerate
|
||||||
<Runtime as Config>::MinerMaxLength::set(solution_size as u32 - 1);
|
<Runtime as MinerConfig>::MaxLength::set(solution_size as u32 - 1);
|
||||||
let solution = MultiPhase::mine_solution::<<Runtime as Config>::Solver>().unwrap();
|
let solution = MultiPhase::mine_solution().unwrap();
|
||||||
let max_length = <Runtime as Config>::MinerMaxLength::get();
|
let max_length = <Runtime as MinerConfig>::MaxLength::get();
|
||||||
let solution_size = solution.0.solution.encoded_size();
|
let solution_size = solution.0.solution.encoded_size();
|
||||||
assert!(solution_size <= max_length as usize);
|
assert!(solution_size <= max_length as usize);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user