mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-19 21:41:02 +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:
@@ -702,6 +702,7 @@ impl pallet_election_provider_multi_phase::MinerConfig for Runtime {
|
|||||||
type Solution = NposSolution16;
|
type Solution = NposSolution16;
|
||||||
type MaxVotesPerVoter =
|
type MaxVotesPerVoter =
|
||||||
<<Self as pallet_election_provider_multi_phase::Config>::DataProvider as ElectionDataProvider>::MaxVotesPerVoter;
|
<<Self as pallet_election_provider_multi_phase::Config>::DataProvider as ElectionDataProvider>::MaxVotesPerVoter;
|
||||||
|
type MaxWinners = MaxActiveValidators;
|
||||||
|
|
||||||
// The unsigned submissions have to respect the weight of the submit_unsigned call, thus their
|
// 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.
|
// weight estimate function is wired to this call's weight.
|
||||||
|
|||||||
@@ -247,10 +247,7 @@ use sp_arithmetic::{
|
|||||||
traits::{CheckedAdd, Zero},
|
traits::{CheckedAdd, Zero},
|
||||||
UpperOf,
|
UpperOf,
|
||||||
};
|
};
|
||||||
use sp_npos_elections::{
|
use sp_npos_elections::{BoundedSupports, ElectionScore, IdentifierT, Supports, VoteWeight};
|
||||||
assignment_ratio_to_staked_normalized, BoundedSupports, ElectionScore, EvaluateSupport,
|
|
||||||
Supports, VoteWeight,
|
|
||||||
};
|
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
transaction_validity::{
|
transaction_validity::{
|
||||||
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
|
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
|
||||||
@@ -430,13 +427,17 @@ impl<C: Default> Default for RawSolution<C> {
|
|||||||
DefaultNoBound,
|
DefaultNoBound,
|
||||||
scale_info::TypeInfo,
|
scale_info::TypeInfo,
|
||||||
)]
|
)]
|
||||||
#[scale_info(skip_type_params(T))]
|
#[scale_info(skip_type_params(AccountId, MaxWinners))]
|
||||||
pub struct ReadySolution<T: Config> {
|
pub struct ReadySolution<AccountId, MaxWinners>
|
||||||
|
where
|
||||||
|
AccountId: IdentifierT,
|
||||||
|
MaxWinners: Get<u32>,
|
||||||
|
{
|
||||||
/// The final supports of the solution.
|
/// The final supports of the solution.
|
||||||
///
|
///
|
||||||
/// This is target-major vector, storing each winners, total backing, and each individual
|
/// This is target-major vector, storing each winners, total backing, and each individual
|
||||||
/// backer.
|
/// backer.
|
||||||
pub supports: BoundedSupports<T::AccountId, T::MaxWinners>,
|
pub supports: BoundedSupports<AccountId, MaxWinners>,
|
||||||
/// The score of the solution.
|
/// The score of the solution.
|
||||||
///
|
///
|
||||||
/// This is needed to potentially challenge the solution.
|
/// This is needed to potentially challenge the solution.
|
||||||
@@ -451,11 +452,11 @@ pub struct ReadySolution<T: Config> {
|
|||||||
/// These are stored together because they are often accessed together.
|
/// These are stored together because they are often accessed together.
|
||||||
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
|
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)]
|
||||||
#[scale_info(skip_type_params(T))]
|
#[scale_info(skip_type_params(T))]
|
||||||
pub struct RoundSnapshot<T: Config> {
|
pub struct RoundSnapshot<AccountId, DataProvider> {
|
||||||
/// All of the voters.
|
/// All of the voters.
|
||||||
pub voters: Vec<VoterOf<T>>,
|
pub voters: Vec<DataProvider>,
|
||||||
/// All of the targets.
|
/// All of the targets.
|
||||||
pub targets: Vec<T::AccountId>,
|
pub targets: Vec<AccountId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encodes the length of a solution or a snapshot.
|
/// Encodes the length of a solution or a snapshot.
|
||||||
@@ -614,6 +615,7 @@ pub mod pallet {
|
|||||||
type MinerConfig: crate::unsigned::MinerConfig<
|
type MinerConfig: crate::unsigned::MinerConfig<
|
||||||
AccountId = Self::AccountId,
|
AccountId = Self::AccountId,
|
||||||
MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
|
MaxVotesPerVoter = <Self::DataProvider as ElectionDataProvider>::MaxVotesPerVoter,
|
||||||
|
MaxWinners = Self::MaxWinners,
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/// Maximum number of signed submissions that can be queued.
|
/// Maximum number of signed submissions that can be queued.
|
||||||
@@ -733,6 +735,11 @@ pub mod pallet {
|
|||||||
fn max_votes_per_voter() -> u32 {
|
fn max_votes_per_voter() -> u32 {
|
||||||
<T::MinerConfig as MinerConfig>::MaxVotesPerVoter::get()
|
<T::MinerConfig as MinerConfig>::MaxVotesPerVoter::get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pallet::constant_name(MinerMaxWinners)]
|
||||||
|
fn max_winners() -> u32 {
|
||||||
|
<T::MinerConfig as MinerConfig>::MaxWinners::get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::hooks]
|
#[pallet::hooks]
|
||||||
@@ -1247,14 +1254,15 @@ pub mod pallet {
|
|||||||
/// Current best solution, signed or unsigned, queued to be returned upon `elect`.
|
/// Current best solution, signed or unsigned, queued to be returned upon `elect`.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn queued_solution)]
|
#[pallet::getter(fn queued_solution)]
|
||||||
pub type QueuedSolution<T: Config> = StorageValue<_, ReadySolution<T>>;
|
pub type QueuedSolution<T: Config> =
|
||||||
|
StorageValue<_, ReadySolution<T::AccountId, T::MaxWinners>>;
|
||||||
|
|
||||||
/// Snapshot data of the round.
|
/// Snapshot data of the round.
|
||||||
///
|
///
|
||||||
/// This is created at the beginning of the signed phase and cleared upon calling `elect`.
|
/// This is created at the beginning of the signed phase and cleared upon calling `elect`.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
#[pallet::getter(fn snapshot)]
|
#[pallet::getter(fn snapshot)]
|
||||||
pub type Snapshot<T: Config> = StorageValue<_, RoundSnapshot<T>>;
|
pub type Snapshot<T: Config> = StorageValue<_, RoundSnapshot<T::AccountId, VoterOf<T>>>;
|
||||||
|
|
||||||
/// Desired number of targets to elect for this round.
|
/// Desired number of targets to elect for this round.
|
||||||
///
|
///
|
||||||
@@ -1385,7 +1393,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
// instead of using storage APIs, we do a manual encoding into a fixed-size buffer.
|
// instead of using storage APIs, we do a manual encoding into a fixed-size buffer.
|
||||||
// `encoded_size` encodes it without storing it anywhere, this should not cause any
|
// `encoded_size` encodes it without storing it anywhere, this should not cause any
|
||||||
// allocation.
|
// allocation.
|
||||||
let snapshot = RoundSnapshot::<T> { voters, targets };
|
let snapshot = RoundSnapshot::<T::AccountId, VoterOf<T>> { voters, targets };
|
||||||
let size = snapshot.encoded_size();
|
let size = snapshot.encoded_size();
|
||||||
log!(debug, "snapshot pre-calculated size {:?}", size);
|
log!(debug, "snapshot pre-calculated size {:?}", size);
|
||||||
let mut buffer = Vec::with_capacity(size);
|
let mut buffer = Vec::with_capacity(size);
|
||||||
@@ -1479,89 +1487,22 @@ impl<T: Config> Pallet<T> {
|
|||||||
pub fn feasibility_check(
|
pub fn feasibility_check(
|
||||||
raw_solution: RawSolution<SolutionOf<T::MinerConfig>>,
|
raw_solution: RawSolution<SolutionOf<T::MinerConfig>>,
|
||||||
compute: ElectionCompute,
|
compute: ElectionCompute,
|
||||||
) -> Result<ReadySolution<T>, FeasibilityError> {
|
) -> Result<ReadySolution<T::AccountId, T::MaxWinners>, FeasibilityError> {
|
||||||
let RawSolution { solution, score, round } = raw_solution;
|
|
||||||
|
|
||||||
// First, check round.
|
|
||||||
ensure!(Self::round() == round, FeasibilityError::InvalidRound);
|
|
||||||
|
|
||||||
// Winners are not directly encoded in the solution.
|
|
||||||
let winners = solution.unique_targets();
|
|
||||||
|
|
||||||
let desired_targets =
|
let desired_targets =
|
||||||
Self::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
|
Self::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?;
|
||||||
|
|
||||||
ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount);
|
let snapshot = Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?;
|
||||||
// Fail early if targets requested by data provider exceed maximum winners supported.
|
let round = Self::round();
|
||||||
ensure!(
|
let minimum_untrusted_score = Self::minimum_untrusted_score();
|
||||||
desired_targets <= <T as pallet::Config>::MaxWinners::get(),
|
|
||||||
FeasibilityError::TooManyDesiredTargets
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure that the solution's score can pass absolute min-score.
|
Miner::<T::MinerConfig>::feasibility_check(
|
||||||
let submitted_score = raw_solution.score;
|
raw_solution,
|
||||||
ensure!(
|
compute,
|
||||||
Self::minimum_untrusted_score().map_or(true, |min_score| {
|
desired_targets,
|
||||||
submitted_score.strict_threshold_better(min_score, Perbill::zero())
|
snapshot,
|
||||||
}),
|
round,
|
||||||
FeasibilityError::UntrustedScoreTooLow
|
minimum_untrusted_score,
|
||||||
);
|
)
|
||||||
|
|
||||||
// Read the entire snapshot.
|
|
||||||
let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } =
|
|
||||||
Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?;
|
|
||||||
|
|
||||||
// ----- Start building. First, we need some closures.
|
|
||||||
let cache = helpers::generate_voter_cache::<T::MinerConfig>(&snapshot_voters);
|
|
||||||
let voter_at = helpers::voter_at_fn::<T::MinerConfig>(&snapshot_voters);
|
|
||||||
let target_at = helpers::target_at_fn::<T::MinerConfig>(&snapshot_targets);
|
|
||||||
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,
|
|
||||||
// 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::MinerConfig>(&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 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform the tasks to be done after a new `elect` has been triggered:
|
/// Perform the tasks to be done after a new `elect` has been triggered:
|
||||||
|
|||||||
@@ -297,6 +297,8 @@ parameter_types! {
|
|||||||
pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real;
|
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();
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub static MaxWinners: u32 = 200;
|
pub static MaxWinners: u32 = 200;
|
||||||
|
|
||||||
pub static EpochLength: u64 = 30;
|
pub static EpochLength: u64 = 30;
|
||||||
@@ -359,6 +361,7 @@ impl MinerConfig for Runtime {
|
|||||||
type MaxLength = MinerMaxLength;
|
type MaxLength = MinerMaxLength;
|
||||||
type MaxWeight = MinerMaxWeight;
|
type MaxWeight = MinerMaxWeight;
|
||||||
type MaxVotesPerVoter = <StakingMock as ElectionDataProvider>::MaxVotesPerVoter;
|
type MaxVotesPerVoter = <StakingMock as ElectionDataProvider>::MaxVotesPerVoter;
|
||||||
|
type MaxWinners = MaxWinners;
|
||||||
type Solution = TestNposSolution;
|
type Solution = TestNposSolution;
|
||||||
|
|
||||||
fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight {
|
fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight {
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
///
|
///
|
||||||
/// Infallible
|
/// Infallible
|
||||||
pub fn finalize_signed_phase_accept_solution(
|
pub fn finalize_signed_phase_accept_solution(
|
||||||
ready_solution: ReadySolution<T>,
|
ready_solution: ReadySolution<T::AccountId, T::MaxWinners>,
|
||||||
who: &T::AccountId,
|
who: &T::AccountId,
|
||||||
deposit: BalanceOf<T>,
|
deposit: BalanceOf<T>,
|
||||||
call_fee: BalanceOf<T>,
|
call_fee: BalanceOf<T>,
|
||||||
|
|||||||
@@ -23,12 +23,17 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use codec::Encode;
|
use codec::Encode;
|
||||||
use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight};
|
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 frame_system::offchain::SubmitTransaction;
|
||||||
use scale_info::TypeInfo;
|
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,
|
ElectionScore, EvaluateSupport,
|
||||||
};
|
};
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
offchain::storage::{MutateStorageError, StorageValueRef},
|
offchain::storage::{MutateStorageError, StorageValueRef},
|
||||||
@@ -351,7 +356,7 @@ impl<T: Config> Pallet<T> {
|
|||||||
|
|
||||||
// ensure score is being improved. Panic henceforth.
|
// ensure score is being improved. Panic henceforth.
|
||||||
ensure!(
|
ensure!(
|
||||||
Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution
|
Self::queued_solution().map_or(true, |q: ReadySolution<_, _>| raw_solution
|
||||||
.score
|
.score
|
||||||
.strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())),
|
.strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())),
|
||||||
Error::<T>::PreDispatchWeakSubmission,
|
Error::<T>::PreDispatchWeakSubmission,
|
||||||
@@ -387,6 +392,8 @@ pub trait MinerConfig {
|
|||||||
///
|
///
|
||||||
/// The weight is computed using `solution_weight`.
|
/// The weight is computed using `solution_weight`.
|
||||||
type MaxWeight: Get<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.
|
/// Something that can compute the weight of a solution.
|
||||||
///
|
///
|
||||||
/// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`].
|
/// 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
|
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)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user