mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-03 19:17:26 +00:00
frame epm: expose feasibility_check in MinerConfig (#13555)
* frame epm: expose feasibity_check in miner The goal with this commit is to expose the `feasibity_check` such that anyone that implements the `MinerConfig trait` can utilize it * cleanup * fix tests
This commit is contained in:
@@ -23,12 +23,17 @@ use crate::{
|
||||
};
|
||||
use codec::Encode;
|
||||
use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight};
|
||||
use frame_support::{dispatch::DispatchResult, ensure, traits::Get, BoundedVec};
|
||||
use frame_support::{
|
||||
dispatch::DispatchResult,
|
||||
ensure,
|
||||
traits::{DefensiveResult, Get},
|
||||
BoundedVec,
|
||||
};
|
||||
use frame_system::offchain::SubmitTransaction;
|
||||
use scale_info::TypeInfo;
|
||||
use sp_npos_elections::{
|
||||
assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult,
|
||||
ElectionScore,
|
||||
ElectionScore, EvaluateSupport,
|
||||
};
|
||||
use sp_runtime::{
|
||||
offchain::storage::{MutateStorageError, StorageValueRef},
|
||||
@@ -351,7 +356,7 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
// ensure score is being improved. Panic henceforth.
|
||||
ensure!(
|
||||
Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution
|
||||
Self::queued_solution().map_or(true, |q: ReadySolution<_, _>| raw_solution
|
||||
.score
|
||||
.strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())),
|
||||
Error::<T>::PreDispatchWeakSubmission,
|
||||
@@ -387,6 +392,8 @@ pub trait MinerConfig {
|
||||
///
|
||||
/// The weight is computed using `solution_weight`.
|
||||
type MaxWeight: Get<Weight>;
|
||||
/// The maximum number of winners that can be elected.
|
||||
type MaxWinners: Get<u32>;
|
||||
/// Something that can compute the weight of a solution.
|
||||
///
|
||||
/// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`].
|
||||
@@ -689,6 +696,91 @@ impl<T: MinerConfig> Miner<T> {
|
||||
);
|
||||
final_decision
|
||||
}
|
||||
|
||||
/// Checks the feasibility of a solution.
|
||||
pub fn feasibility_check(
|
||||
raw_solution: RawSolution<SolutionOf<T>>,
|
||||
compute: ElectionCompute,
|
||||
desired_targets: u32,
|
||||
snapshot: RoundSnapshot<T::AccountId, MinerVoterOf<T>>,
|
||||
current_round: u32,
|
||||
minimum_untrusted_score: Option<ElectionScore>,
|
||||
) -> Result<ReadySolution<T::AccountId, T::MaxWinners>, FeasibilityError> {
|
||||
let RawSolution { solution, score, round } = raw_solution;
|
||||
let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = snapshot;
|
||||
|
||||
// First, check round.
|
||||
ensure!(current_round == round, FeasibilityError::InvalidRound);
|
||||
|
||||
// Winners are not directly encoded in the solution.
|
||||
let winners = solution.unique_targets();
|
||||
|
||||
ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount);
|
||||
// Fail early if targets requested by data provider exceed maximum winners supported.
|
||||
ensure!(desired_targets <= T::MaxWinners::get(), FeasibilityError::TooManyDesiredTargets);
|
||||
|
||||
// Ensure that the solution's score can pass absolute min-score.
|
||||
let submitted_score = raw_solution.score;
|
||||
ensure!(
|
||||
minimum_untrusted_score.map_or(true, |min_score| {
|
||||
submitted_score.strict_threshold_better(min_score, sp_runtime::Perbill::zero())
|
||||
}),
|
||||
FeasibilityError::UntrustedScoreTooLow
|
||||
);
|
||||
|
||||
// ----- Start building. First, we need some closures.
|
||||
let cache = helpers::generate_voter_cache::<T>(&snapshot_voters);
|
||||
let voter_at = helpers::voter_at_fn::<T>(&snapshot_voters);
|
||||
let target_at = helpers::target_at_fn::<T>(&snapshot_targets);
|
||||
let voter_index = helpers::voter_index_fn_usize::<T>(&cache);
|
||||
|
||||
// Then convert solution -> assignment. This will fail if any of the indices are gibberish,
|
||||
// namely any of the voters or targets.
|
||||
let assignments = solution
|
||||
.into_assignment(voter_at, target_at)
|
||||
.map_err::<FeasibilityError, _>(Into::into)?;
|
||||
|
||||
// Ensure that assignments is correct.
|
||||
let _ = assignments.iter().try_for_each(|assignment| {
|
||||
// Check that assignment.who is actually a voter (defensive-only).
|
||||
// NOTE: while using the index map from `voter_index` is better than a blind linear
|
||||
// search, this *still* has room for optimization. Note that we had the index when
|
||||
// we did `solution -> assignment` and we lost it. Ideal is to keep the index
|
||||
// around.
|
||||
|
||||
// Defensive-only: must exist in the snapshot.
|
||||
let snapshot_index =
|
||||
voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?;
|
||||
// Defensive-only: index comes from the snapshot, must exist.
|
||||
let (_voter, _stake, targets) =
|
||||
snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?;
|
||||
|
||||
// Check that all of the targets are valid based on the snapshot.
|
||||
if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) {
|
||||
return Err(FeasibilityError::InvalidVote)
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
|
||||
// ----- Start building support. First, we need one more closure.
|
||||
let stake_of = helpers::stake_of_fn::<T>(&snapshot_voters, &cache);
|
||||
|
||||
// This might fail if the normalization fails. Very unlikely. See `integrity_test`.
|
||||
let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of)
|
||||
.map_err::<FeasibilityError, _>(Into::into)?;
|
||||
let supports = sp_npos_elections::to_supports(&staked_assignments);
|
||||
|
||||
// Finally, check that the claimed score was indeed correct.
|
||||
let known_score = supports.evaluate();
|
||||
ensure!(known_score == score, FeasibilityError::InvalidScore);
|
||||
|
||||
// Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`.
|
||||
let supports = supports
|
||||
.try_into()
|
||||
.defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?;
|
||||
|
||||
Ok(ReadySolution { supports, compute, score })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user