mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 08:11:03 +00:00
Multi-Block Election part 0: preparation and some cleanup. (#9442)
* Partially applied * Everything builds, need to implement compact encoding as well. * Fix some tests, add a ui test as well. * Fix everything and everything. * small nits * a bunch more rename * more reorg * more reorg * last nit of self-review * Seemingly fixed the build now * Fix build * make it work again * Update primitives/npos-elections/solution-type/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Update primitives/npos-elections/solution-type/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * nits * factor out double type * fix try-build Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
This commit is contained in:
@@ -18,8 +18,9 @@
|
||||
//! The unsigned phase, and its miner.
|
||||
|
||||
use crate::{
|
||||
helpers, Call, CompactAccuracyOf, CompactOf, Config, ElectionCompute, Error, FeasibilityError,
|
||||
Pallet, RawSolution, ReadySolution, RoundSnapshot, SolutionOrSnapshotSize, Weight, WeightInfo,
|
||||
helpers, Call, Config, ElectionCompute, Error, FeasibilityError, Pallet, RawSolution,
|
||||
ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight,
|
||||
WeightInfo,
|
||||
};
|
||||
use codec::{Decode, Encode};
|
||||
use frame_support::{dispatch::DispatchResult, ensure, traits::Get};
|
||||
@@ -27,7 +28,7 @@ use frame_system::offchain::SubmitTransaction;
|
||||
use sp_arithmetic::Perbill;
|
||||
use sp_npos_elections::{
|
||||
assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, is_score_better,
|
||||
seq_phragmen, CompactSolution, ElectionResult,
|
||||
seq_phragmen, ElectionResult, NposSolution,
|
||||
};
|
||||
use sp_runtime::{
|
||||
offchain::storage::{MutateStorageError, StorageValueRef},
|
||||
@@ -54,11 +55,11 @@ pub type Voter<T> = (
|
||||
|
||||
/// The relative distribution of a voter's stake among the winning targets.
|
||||
pub type Assignment<T> =
|
||||
sp_npos_elections::Assignment<<T as frame_system::Config>::AccountId, CompactAccuracyOf<T>>;
|
||||
sp_npos_elections::Assignment<<T as frame_system::Config>::AccountId, SolutionAccuracyOf<T>>;
|
||||
|
||||
/// The [`IndexAssignment`][sp_npos_elections::IndexAssignment] type specialized for a particular
|
||||
/// runtime `T`.
|
||||
pub type IndexAssignmentOf<T> = sp_npos_elections::IndexAssignmentOf<CompactOf<T>>;
|
||||
pub type IndexAssignmentOf<T> = sp_npos_elections::IndexAssignmentOf<SolutionOf<T>>;
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub enum MinerError {
|
||||
@@ -231,7 +232,7 @@ impl<T: Config> Pallet<T> {
|
||||
//
|
||||
// Performance: note that it internally clones the provided solution.
|
||||
pub fn basic_checks(
|
||||
raw_solution: &RawSolution<CompactOf<T>>,
|
||||
raw_solution: &RawSolution<SolutionOf<T>>,
|
||||
solution_type: &str,
|
||||
) -> Result<(), MinerError> {
|
||||
Self::unsigned_pre_dispatch_checks(raw_solution).map_err(|err| {
|
||||
@@ -257,7 +258,7 @@ impl<T: Config> Pallet<T> {
|
||||
/// [`Pallet::mine_check_save_submit`].
|
||||
pub fn mine_and_check(
|
||||
iters: usize,
|
||||
) -> Result<(RawSolution<CompactOf<T>>, SolutionOrSnapshotSize), MinerError> {
|
||||
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError> {
|
||||
let (raw_solution, witness) = Self::mine_solution(iters)?;
|
||||
Self::basic_checks(&raw_solution, "mined")?;
|
||||
Ok((raw_solution, witness))
|
||||
@@ -266,12 +267,12 @@ impl<T: Config> Pallet<T> {
|
||||
/// Mine a new npos solution.
|
||||
pub fn mine_solution(
|
||||
iters: usize,
|
||||
) -> Result<(RawSolution<CompactOf<T>>, SolutionOrSnapshotSize), MinerError> {
|
||||
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError> {
|
||||
let RoundSnapshot { voters, targets } =
|
||||
Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?;
|
||||
let desired_targets = Self::desired_targets().ok_or(MinerError::SnapshotUnAvailable)?;
|
||||
|
||||
seq_phragmen::<_, CompactAccuracyOf<T>>(
|
||||
seq_phragmen::<_, SolutionAccuracyOf<T>>(
|
||||
desired_targets as usize,
|
||||
targets,
|
||||
voters,
|
||||
@@ -286,8 +287,8 @@ impl<T: Config> Pallet<T> {
|
||||
///
|
||||
/// Will always reduce the solution as well.
|
||||
pub fn prepare_election_result(
|
||||
election_result: ElectionResult<T::AccountId, CompactAccuracyOf<T>>,
|
||||
) -> Result<(RawSolution<CompactOf<T>>, SolutionOrSnapshotSize), MinerError> {
|
||||
election_result: ElectionResult<T::AccountId, SolutionAccuracyOf<T>>,
|
||||
) -> Result<(RawSolution<SolutionOf<T>>, SolutionOrSnapshotSize), MinerError> {
|
||||
// NOTE: This code path is generally not optimized as it is run offchain. Could use some at
|
||||
// some point though.
|
||||
|
||||
@@ -304,11 +305,11 @@ impl<T: Config> Pallet<T> {
|
||||
let target_at = helpers::target_at_fn::<T>(&targets);
|
||||
let stake_of = helpers::stake_of_fn::<T>(&voters, &cache);
|
||||
|
||||
// Compute the size of a compact solution comprised of the selected arguments.
|
||||
// Compute the size of a solution comprised of the selected arguments.
|
||||
//
|
||||
// This function completes in `O(edges)`; it's expensive, but linear.
|
||||
let encoded_size_of = |assignments: &[IndexAssignmentOf<T>]| {
|
||||
CompactOf::<T>::try_from(assignments).map(|compact| compact.encoded_size())
|
||||
SolutionOf::<T>::try_from(assignments).map(|s| s.encoded_size())
|
||||
};
|
||||
|
||||
let ElectionResult { assignments, winners } = election_result;
|
||||
@@ -345,7 +346,7 @@ impl<T: Config> Pallet<T> {
|
||||
};
|
||||
|
||||
// convert to `IndexAssignment`. This improves the runtime complexity of repeatedly
|
||||
// converting to `Compact`.
|
||||
// converting to `Solution`.
|
||||
let mut index_assignments = sorted_assignments
|
||||
.into_iter()
|
||||
.map(|assignment| IndexAssignmentOf::<T>::new(&assignment, &voter_index, &target_index))
|
||||
@@ -366,15 +367,15 @@ impl<T: Config> Pallet<T> {
|
||||
&encoded_size_of,
|
||||
)?;
|
||||
|
||||
// now make compact.
|
||||
let compact = CompactOf::<T>::try_from(&index_assignments)?;
|
||||
// now make solution.
|
||||
let solution = SolutionOf::<T>::try_from(&index_assignments)?;
|
||||
|
||||
// re-calc score.
|
||||
let winners = sp_npos_elections::to_without_backing(winners);
|
||||
let score = compact.clone().score(&winners, stake_of, voter_at, target_at)?;
|
||||
let score = solution.clone().score(&winners, stake_of, voter_at, target_at)?;
|
||||
|
||||
let round = Self::round();
|
||||
Ok((RawSolution { compact, score, round }, size))
|
||||
Ok((RawSolution { solution, score, round }, size))
|
||||
}
|
||||
|
||||
/// Get a random number of iterations to run the balancing in the OCW.
|
||||
@@ -502,7 +503,7 @@ impl<T: Config> Pallet<T> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Find the maximum `len` that a compact 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`.
|
||||
pub fn maximum_voter_for_weight<W: WeightInfo>(
|
||||
@@ -623,24 +624,26 @@ impl<T: Config> Pallet<T> {
|
||||
///
|
||||
/// 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(solution: &RawSolution<CompactOf<T>>) -> DispatchResult {
|
||||
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() == solution.round, Error::<T>::OcwCallWrongEra);
|
||||
ensure!(Self::round() == raw_solution.round, Error::<T>::OcwCallWrongEra);
|
||||
|
||||
// ensure correct number of winners.
|
||||
ensure!(
|
||||
Self::desired_targets().unwrap_or_default() ==
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
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<_>| is_score_better::<Perbill>(
|
||||
solution.score,
|
||||
raw_solution.score,
|
||||
q.score,
|
||||
T::SolutionImprovementThreshold::get()
|
||||
)),
|
||||
@@ -753,7 +756,7 @@ mod tests {
|
||||
mock::{
|
||||
roll_to, roll_to_with_ocw, trim_helpers, witness, BlockNumber, Call as OuterCall,
|
||||
ExtBuilder, Extrinsic, MinerMaxWeight, MultiPhase, Origin, Runtime, System,
|
||||
TestCompact, TrimHelpers, UnsignedPhase,
|
||||
TestNposSolution, TrimHelpers, UnsignedPhase,
|
||||
},
|
||||
CurrentPhase, InvalidTransaction, Phase, QueuedSolution, TransactionSource,
|
||||
TransactionValidityError,
|
||||
@@ -772,7 +775,8 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_unsigned_retracts_wrong_phase() {
|
||||
ExtBuilder::default().desired_targets(0).build_and_execute(|| {
|
||||
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
||||
let solution =
|
||||
RawSolution::<TestNposSolution> { score: [5, 0, 0], ..Default::default() };
|
||||
let call = Call::submit_unsigned(Box::new(solution.clone()), witness());
|
||||
|
||||
// initial
|
||||
@@ -841,7 +845,8 @@ mod tests {
|
||||
roll_to(25);
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
|
||||
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
||||
let solution =
|
||||
RawSolution::<TestNposSolution> { score: [5, 0, 0], ..Default::default() };
|
||||
let call = Call::submit_unsigned(Box::new(solution.clone()), witness());
|
||||
|
||||
// initial
|
||||
@@ -878,9 +883,9 @@ mod tests {
|
||||
roll_to(25);
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
|
||||
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
||||
let call = Call::submit_unsigned(Box::new(solution.clone()), witness());
|
||||
assert_eq!(solution.compact.unique_targets().len(), 0);
|
||||
let raw = RawSolution::<TestNposSolution> { score: [5, 0, 0], ..Default::default() };
|
||||
let call = Call::submit_unsigned(Box::new(raw.clone()), witness());
|
||||
assert_eq!(raw.solution.unique_targets().len(), 0);
|
||||
|
||||
// won't work anymore.
|
||||
assert!(matches!(
|
||||
@@ -904,7 +909,7 @@ mod tests {
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
|
||||
let solution =
|
||||
RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
||||
RawSolution::<TestNposSolution> { score: [5, 0, 0], ..Default::default() };
|
||||
let call = Call::submit_unsigned(Box::new(solution.clone()), witness());
|
||||
|
||||
assert_eq!(
|
||||
@@ -930,7 +935,8 @@ mod tests {
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
|
||||
// This is in itself an invalid BS solution.
|
||||
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
||||
let solution =
|
||||
RawSolution::<TestNposSolution> { score: [5, 0, 0], ..Default::default() };
|
||||
let call = Call::submit_unsigned(Box::new(solution.clone()), witness());
|
||||
let outer_call: OuterCall = call.into();
|
||||
let _ = outer_call.dispatch(Origin::none());
|
||||
@@ -946,7 +952,8 @@ mod tests {
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
|
||||
// This solution is unfeasible as well, but we won't even get there.
|
||||
let solution = RawSolution::<TestCompact> { score: [5, 0, 0], ..Default::default() };
|
||||
let solution =
|
||||
RawSolution::<TestNposSolution> { score: [5, 0, 0], ..Default::default() };
|
||||
|
||||
let mut correct_witness = witness();
|
||||
correct_witness.voters += 1;
|
||||
@@ -986,30 +993,30 @@ mod tests {
|
||||
roll_to(25);
|
||||
assert!(MultiPhase::current_phase().is_unsigned());
|
||||
|
||||
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let (raw, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
||||
witness.voters,
|
||||
witness.targets,
|
||||
solution.compact.voter_count() as u32,
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
raw.solution.voter_count() as u32,
|
||||
raw.solution.unique_targets().len() as u32,
|
||||
);
|
||||
// default solution will have 5 edges (5 * 5 + 10)
|
||||
assert_eq!(solution_weight, 35);
|
||||
assert_eq!(solution.compact.voter_count(), 5);
|
||||
assert_eq!(raw.solution.voter_count(), 5);
|
||||
|
||||
// now reduce the max weight
|
||||
<MinerMaxWeight>::set(25);
|
||||
|
||||
let (solution, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let (raw, witness) = MultiPhase::mine_solution(2).unwrap();
|
||||
let solution_weight = <Runtime as Config>::WeightInfo::submit_unsigned(
|
||||
witness.voters,
|
||||
witness.targets,
|
||||
solution.compact.voter_count() as u32,
|
||||
solution.compact.unique_targets().len() as u32,
|
||||
raw.solution.voter_count() as u32,
|
||||
raw.solution.unique_targets().len() as u32,
|
||||
);
|
||||
// default solution will have 5 edges (5 * 5 + 10)
|
||||
assert_eq!(solution_weight, 25);
|
||||
assert_eq!(solution.compact.voter_count(), 3);
|
||||
assert_eq!(raw.solution.voter_count(), 3);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1068,7 +1075,7 @@ mod tests {
|
||||
Assignment { who: 10, distribution: vec![(10, PerU16::one())] },
|
||||
Assignment {
|
||||
who: 7,
|
||||
// note: this percent doesn't even matter, in compact it is 100%.
|
||||
// note: this percent doesn't even matter, in solution it is 100%.
|
||||
distribution: vec![(10, PerU16::one())],
|
||||
},
|
||||
],
|
||||
@@ -1090,7 +1097,7 @@ mod tests {
|
||||
Assignment { who: 7, distribution: vec![(10, PerU16::one())] },
|
||||
Assignment {
|
||||
who: 8,
|
||||
// note: this percent doesn't even matter, in compact it is 100%.
|
||||
// note: this percent doesn't even matter, in solution it is 100%.
|
||||
distribution: vec![(10, PerU16::one())],
|
||||
},
|
||||
],
|
||||
@@ -1400,17 +1407,17 @@ mod tests {
|
||||
|
||||
// given
|
||||
let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers();
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
let encoded_len = compact.encoded_size() as u32;
|
||||
let compact_clone = compact.clone();
|
||||
let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
let encoded_len = solution.encoded_size() as u32;
|
||||
let solution_clone = solution.clone();
|
||||
|
||||
// when
|
||||
MultiPhase::trim_assignments_length(encoded_len, &mut assignments, encoded_size_of)
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
assert_eq!(compact, compact_clone);
|
||||
let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
assert_eq!(solution, solution_clone);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1421,9 +1428,9 @@ mod tests {
|
||||
|
||||
// given
|
||||
let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers();
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
let encoded_len = compact.encoded_size();
|
||||
let compact_clone = compact.clone();
|
||||
let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
let encoded_len = solution.encoded_size();
|
||||
let solution_clone = solution.clone();
|
||||
|
||||
// when
|
||||
MultiPhase::trim_assignments_length(
|
||||
@@ -1434,9 +1441,9 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
// then
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
assert_ne!(compact, compact_clone);
|
||||
assert!(compact.encoded_size() < encoded_len);
|
||||
let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
assert_ne!(solution, solution_clone);
|
||||
assert!(solution.encoded_size() < encoded_len);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1448,8 +1455,8 @@ mod tests {
|
||||
// given
|
||||
let TrimHelpers { voters, mut assignments, encoded_size_of, voter_index } =
|
||||
trim_helpers();
|
||||
let compact = CompactOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
let encoded_len = compact.encoded_size() as u32;
|
||||
let solution = SolutionOf::<Runtime>::try_from(assignments.as_slice()).unwrap();
|
||||
let encoded_len = solution.encoded_size() as u32;
|
||||
let count = assignments.len();
|
||||
let min_stake_voter = voters
|
||||
.iter()
|
||||
@@ -1476,15 +1483,15 @@ mod tests {
|
||||
// we shan't panic if assignments are initially empty.
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
let encoded_size_of = Box::new(|assignments: &[IndexAssignmentOf<Runtime>]| {
|
||||
CompactOf::<Runtime>::try_from(assignments).map(|compact| compact.encoded_size())
|
||||
SolutionOf::<Runtime>::try_from(assignments).map(|solution| solution.encoded_size())
|
||||
});
|
||||
|
||||
let mut assignments = vec![];
|
||||
|
||||
// since we have 16 fields, we need to store the length fields of 16 vecs, thus 16 bytes
|
||||
// minimum.
|
||||
let min_compact_size = encoded_size_of(&assignments).unwrap();
|
||||
assert_eq!(min_compact_size, CompactOf::<Runtime>::LIMIT);
|
||||
let min_solution_size = encoded_size_of(&assignments).unwrap();
|
||||
assert_eq!(min_solution_size, SolutionOf::<Runtime>::LIMIT);
|
||||
|
||||
// all of this should not panic.
|
||||
MultiPhase::trim_assignments_length(0, &mut assignments, encoded_size_of.clone())
|
||||
@@ -1492,7 +1499,7 @@ mod tests {
|
||||
MultiPhase::trim_assignments_length(1, &mut assignments, encoded_size_of.clone())
|
||||
.unwrap();
|
||||
MultiPhase::trim_assignments_length(
|
||||
min_compact_size as u32,
|
||||
min_solution_size as u32,
|
||||
&mut assignments,
|
||||
encoded_size_of,
|
||||
)
|
||||
@@ -1506,10 +1513,10 @@ mod tests {
|
||||
let TrimHelpers { mut assignments, encoded_size_of, .. } = trim_helpers();
|
||||
assert!(assignments.len() > 0);
|
||||
|
||||
// trim to min compact size.
|
||||
let min_compact_size = CompactOf::<Runtime>::LIMIT as u32;
|
||||
// trim to min solution size.
|
||||
let min_solution_size = SolutionOf::<Runtime>::LIMIT as u32;
|
||||
MultiPhase::trim_assignments_length(
|
||||
min_compact_size,
|
||||
min_solution_size,
|
||||
&mut assignments,
|
||||
encoded_size_of,
|
||||
)
|
||||
@@ -1529,14 +1536,14 @@ mod tests {
|
||||
// how long would the default solution be?
|
||||
let solution = MultiPhase::mine_solution(0).unwrap();
|
||||
let max_length = <Runtime as Config>::MinerMaxLength::get();
|
||||
let solution_size = solution.0.compact.encoded_size();
|
||||
let solution_size = solution.0.solution.encoded_size();
|
||||
assert!(solution_size <= max_length as usize);
|
||||
|
||||
// now set the max size to less than the actual size and regenerate
|
||||
<Runtime as Config>::MinerMaxLength::set(solution_size as u32 - 1);
|
||||
let solution = MultiPhase::mine_solution(0).unwrap();
|
||||
let max_length = <Runtime as Config>::MinerMaxLength::get();
|
||||
let solution_size = solution.0.compact.encoded_size();
|
||||
let solution_size = solution.0.solution.encoded_size();
|
||||
assert!(solution_size <= max_length as usize);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user