mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-23 02:41:08 +00:00
First draft of offchain phragmen weights (#6032)
* Fist draft of offchain weights * Round of review feedback * Update frame/staking/src/lib.rs * Fix fuzzer * Remove some redundant comment * Weight refund for submit solution -- potentially revert. * First version with custom trimming of the result. * Update frame/staking/src/benchmarking.rs Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * Update frame/staking/src/benchmarking.rs Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * Apply suggestions from code review Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> Co-authored-by: thiolliere <gui.thiolliere@gmail.com> * Update frame/staking/src/benchmarking.rs Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * Update frame/staking/src/benchmarking.rs Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * Some improvements * Benchmark submit solution without phragmen (PR for First draft of offchain phragmen weights) (#6073) * implementation of new benchmark * address comments * replace test * Update frame/staking/src/lib.rs Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * update weight * Fix refund * Clean and rady for final bench * Fix line-wdith * Fix gitlab build * Fix line-wdith * Fix test macro * Update frame/staking/src/lib.rs Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * Update frame/staking/src/benchmarking.rs Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * Better length check * Update frame/staking/src/lib.rs Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> * Update final weight coefficients * Update frame/staking/src/lib.rs * Apply suggestions from code review * Update frame/staking/src/testing_utils.rs * Try and fix the line-width * Revert "Try and fix the line-width" This reverts commit b4e284727220085b9b3daf7682c4bbf29621da09. * Try and fix the line-width the correct way * Revert "Try and fix the line-width the correct way" This reverts commit 04fce128e851c9584f9f0d708a5a73cae799d8c8. Co-authored-by: Alexander Popiak <alexander.popiak@parity.io> Co-authored-by: thiolliere <gui.thiolliere@gmail.com> Co-authored-by: Gavin Wood <gavin@parity.io>
This commit is contained in:
@@ -272,7 +272,7 @@
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
#[cfg(feature = "testing-utils")]
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
pub mod testing_utils;
|
||||
#[cfg(any(feature = "runtime-benchmarks", test))]
|
||||
pub mod benchmarking;
|
||||
@@ -293,7 +293,7 @@ use frame_support::{
|
||||
decl_module, decl_event, decl_storage, ensure, decl_error, debug,
|
||||
weights::{Weight, constants::{WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}},
|
||||
storage::IterableStorageMap,
|
||||
dispatch::{IsSubType, DispatchResult, DispatchResultWithPostInfo},
|
||||
dispatch::{IsSubType, DispatchResult, DispatchResultWithPostInfo, WithPostDispatchInfo},
|
||||
traits::{
|
||||
Currency, LockIdentifier, LockableCurrency, WithdrawReasons, OnUnbalanced, Imbalance, Get,
|
||||
UnixTime, EstimateNextNewSession, EnsureOrigin,
|
||||
@@ -680,6 +680,22 @@ pub enum ElectionStatus<BlockNumber> {
|
||||
Open(BlockNumber),
|
||||
}
|
||||
|
||||
/// Some indications about the size of the election. This must be submitted with the solution.
|
||||
///
|
||||
/// Note that these values must reflect the __total__ number, not only those that are present in the
|
||||
/// solution. In short, these should be the same size as the size of the values dumped in
|
||||
/// `SnapshotValidators` and `SnapshotNominators`.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default)]
|
||||
pub struct ElectionSize {
|
||||
/// Number of validators in the snapshot of the current election round.
|
||||
#[codec(compact)]
|
||||
pub validators: ValidatorIndex,
|
||||
/// Number of nominators in the snapshot of the current election round.
|
||||
#[codec(compact)]
|
||||
pub nominators: NominatorIndex,
|
||||
}
|
||||
|
||||
|
||||
impl<BlockNumber: PartialEq> ElectionStatus<BlockNumber> {
|
||||
fn is_open_at(&self, n: BlockNumber) -> bool {
|
||||
*self == Self::Open(n)
|
||||
@@ -743,6 +759,72 @@ impl<T: Trait> SessionInterface<<T as frame_system::Trait>::AccountId> for T whe
|
||||
}
|
||||
}
|
||||
|
||||
pub mod weight {
|
||||
use super::*;
|
||||
|
||||
/// All weight notes are pertaining to the case of a better solution, in which we execute
|
||||
/// the longest code path.
|
||||
/// Weight: 0 + (0.63 μs * v) + (0.36 μs * n) + (96.53 μs * a ) + (8 μs * w ) with:
|
||||
/// * v validators in snapshot validators,
|
||||
/// * n nominators in snapshot nominators,
|
||||
/// * a assignment in the submitted solution
|
||||
/// * w winners in the submitted solution
|
||||
///
|
||||
/// State reads:
|
||||
/// - Initial checks:
|
||||
/// - ElectionState, CurrentEra, QueuedScore
|
||||
/// - SnapshotValidators.len() + SnapShotNominators.len()
|
||||
/// - ValidatorCount
|
||||
/// - SnapshotValidators
|
||||
/// - SnapshotNominators
|
||||
/// - Iterate over nominators:
|
||||
/// - compact.len() * Nominators(who)
|
||||
/// - (non_self_vote_edges) * SlashingSpans
|
||||
/// - For `assignment_ratio_to_staked`: Basically read the staked value of each stash.
|
||||
/// - (winners.len() + compact.len()) * (Ledger + Bonded)
|
||||
/// - TotalIssuance (read a gzillion times potentially, but well it is cached.)
|
||||
/// - State writes:
|
||||
/// - QueuedElected, QueuedScore
|
||||
pub fn weight_for_submit_solution<T: Trait>(
|
||||
winners: &Vec<ValidatorIndex>,
|
||||
compact: &CompactAssignments,
|
||||
size: &ElectionSize,
|
||||
) -> Weight {
|
||||
(630 * WEIGHT_PER_NANOS).saturating_mul(size.validators as Weight)
|
||||
.saturating_add((360 * WEIGHT_PER_NANOS).saturating_mul(size.nominators as Weight))
|
||||
.saturating_add((96 * WEIGHT_PER_MICROS).saturating_mul(compact.len() as Weight))
|
||||
.saturating_add((8 * WEIGHT_PER_MICROS).saturating_mul(winners.len() as Weight))
|
||||
// Initial checks
|
||||
.saturating_add(T::DbWeight::get().reads(8))
|
||||
// Nominators
|
||||
.saturating_add(T::DbWeight::get().reads(compact.len() as Weight))
|
||||
// SlashingSpans (upper bound for invalid solution)
|
||||
.saturating_add(T::DbWeight::get().reads(compact.edge_count() as Weight))
|
||||
// `assignment_ratio_to_staked`
|
||||
.saturating_add(T::DbWeight::get().reads(2 * ((winners.len() + compact.len()) as Weight)))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
// write queued score and elected
|
||||
.saturating_add(T::DbWeight::get().writes(2))
|
||||
}
|
||||
|
||||
/// Weight of `submit_solution` in case of a correct submission.
|
||||
///
|
||||
/// refund: we charged compact.len() * read(1) for SlashingSpans. A valid solution only reads
|
||||
/// winners.len().
|
||||
pub fn weight_for_correct_submit_solution<T: Trait>(
|
||||
winners: &Vec<ValidatorIndex>,
|
||||
compact: &CompactAssignments,
|
||||
size: &ElectionSize,
|
||||
) -> Weight {
|
||||
// NOTE: for consistency, we re-compute the original weight to maintain their relation and
|
||||
// prevent any foot-guns.
|
||||
let original_weight = weight_for_submit_solution::<T>(winners, compact, size);
|
||||
original_weight
|
||||
.saturating_sub(T::DbWeight::get().reads(compact.edge_count() as Weight))
|
||||
.saturating_add(T::DbWeight::get().reads(winners.len() as Weight))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Trait: frame_system::Trait + SendTransactionTypes<Call<Self>> {
|
||||
/// The staking balance.
|
||||
type Currency: LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>;
|
||||
@@ -1163,6 +1245,8 @@ decl_error! {
|
||||
PhragmenBogusEdge,
|
||||
/// The claimed score does not match with the one computed from the data.
|
||||
PhragmenBogusScore,
|
||||
/// The election size is invalid.
|
||||
PhragmenBogusElectionSize,
|
||||
/// The call is not allowed at the given time due to restrictions of election period.
|
||||
CallNotAllowed,
|
||||
/// Incorrect previous history depth input provided.
|
||||
@@ -2063,51 +2147,26 @@ decl_module! {
|
||||
/// minimized (to ensure less variance)
|
||||
///
|
||||
/// # <weight>
|
||||
/// E: number of edges. m: size of winner committee. n: number of nominators. d: edge degree
|
||||
/// (16 for now) v: number of on-chain validator candidates.
|
||||
///
|
||||
/// NOTE: given a solution which is reduced, we can enable a new check the ensure `|E| < n +
|
||||
/// m`. We don't do this _yet_, but our offchain worker code executes it nonetheless.
|
||||
///
|
||||
/// major steps (all done in `check_and_replace_solution`):
|
||||
///
|
||||
/// - Storage: O(1) read `ElectionStatus`.
|
||||
/// - Storage: O(1) read `PhragmenScore`.
|
||||
/// - Storage: O(1) read `ValidatorCount`.
|
||||
/// - Storage: O(1) length read from `SnapshotValidators`.
|
||||
///
|
||||
/// - Storage: O(v) reads of `AccountId` to fetch `snapshot_validators`.
|
||||
/// - Memory: O(m) iterations to map winner index to validator id.
|
||||
/// - Storage: O(n) reads `AccountId` to fetch `snapshot_nominators`.
|
||||
/// - Memory: O(n + m) reads to map index to `AccountId` for un-compact.
|
||||
///
|
||||
/// - Storage: O(e) accountid reads from `Nomination` to read correct nominations.
|
||||
/// - Storage: O(e) calls into `slashable_balance_of_vote_weight` to convert ratio to staked.
|
||||
///
|
||||
/// - Memory: build_support_map. O(e).
|
||||
/// - Memory: evaluate_support: O(E).
|
||||
///
|
||||
/// - Storage: O(e) writes to `QueuedElected`.
|
||||
/// - Storage: O(1) write to `QueuedScore`
|
||||
///
|
||||
/// The weight of this call is 1/10th of the blocks total weight.
|
||||
/// See `crate::weight` module.
|
||||
/// # </weight>
|
||||
#[weight = 100_000_000_000]
|
||||
#[weight = weight::weight_for_submit_solution::<T>(winners, compact, size)]
|
||||
pub fn submit_election_solution(
|
||||
origin,
|
||||
winners: Vec<ValidatorIndex>,
|
||||
compact_assignments: CompactAssignments,
|
||||
compact: CompactAssignments,
|
||||
score: PhragmenScore,
|
||||
era: EraIndex,
|
||||
) {
|
||||
size: ElectionSize,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let _who = ensure_signed(origin)?;
|
||||
Self::check_and_replace_solution(
|
||||
winners,
|
||||
compact_assignments,
|
||||
compact,
|
||||
ElectionCompute::Signed,
|
||||
score,
|
||||
era,
|
||||
)?
|
||||
size,
|
||||
)
|
||||
}
|
||||
|
||||
/// Unsigned version of `submit_election_solution`.
|
||||
@@ -2115,22 +2174,28 @@ decl_module! {
|
||||
/// Note that this must pass the [`ValidateUnsigned`] check which only allows transactions
|
||||
/// from the local node to be included. In other words, only the block author can include a
|
||||
/// transaction in the block.
|
||||
#[weight = 100_000_000_000]
|
||||
///
|
||||
/// # <weight>
|
||||
/// See `crate::weight` module.
|
||||
/// # </weight>
|
||||
#[weight = weight::weight_for_submit_solution::<T>(winners, compact, size)]
|
||||
pub fn submit_election_solution_unsigned(
|
||||
origin,
|
||||
winners: Vec<ValidatorIndex>,
|
||||
compact_assignments: CompactAssignments,
|
||||
compact: CompactAssignments,
|
||||
score: PhragmenScore,
|
||||
era: EraIndex,
|
||||
) {
|
||||
size: ElectionSize,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_none(origin)?;
|
||||
Self::check_and_replace_solution(
|
||||
winners,
|
||||
compact_assignments,
|
||||
compact,
|
||||
ElectionCompute::Unsigned,
|
||||
score,
|
||||
era,
|
||||
)?
|
||||
size,
|
||||
)
|
||||
// TODO: instead of returning an error, panic. This makes the entire produced block
|
||||
// invalid.
|
||||
// This ensures that block authors will not ever try and submit a solution which is not
|
||||
@@ -2142,6 +2207,7 @@ decl_module! {
|
||||
impl<T: Trait> Module<T> {
|
||||
/// The total balance that can be slashed from a stash account as of right now.
|
||||
pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
|
||||
// Weight note: consider making the stake accessible through stash.
|
||||
Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default()
|
||||
}
|
||||
|
||||
@@ -2156,7 +2222,7 @@ impl<T: Trait> Module<T> {
|
||||
///
|
||||
/// This data is used to efficiently evaluate election results. returns `true` if the operation
|
||||
/// is successful.
|
||||
fn create_stakers_snapshot() -> (bool, Weight) {
|
||||
pub fn create_stakers_snapshot() -> (bool, Weight) {
|
||||
let mut consumed_weight = 0;
|
||||
let mut add_db_reads_writes = |reads, writes| {
|
||||
consumed_weight += T::DbWeight::get().reads_writes(reads, writes);
|
||||
@@ -2518,19 +2584,24 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
|
||||
/// Basic and cheap checks that we perform in validate unsigned, and in the execution.
|
||||
pub fn pre_dispatch_checks(score: PhragmenScore, era: EraIndex) -> Result<(), Error<T>> {
|
||||
///
|
||||
/// State reads: ElectionState, CurrentEr, QueuedScore.
|
||||
///
|
||||
/// This function does weight refund in case of errors, which is based upon the fact that it is
|
||||
/// called at the very beginning of the call site's function.
|
||||
pub fn pre_dispatch_checks(score: PhragmenScore, era: EraIndex) -> DispatchResultWithPostInfo {
|
||||
// discard solutions that are not in-time
|
||||
// check window open
|
||||
ensure!(
|
||||
Self::era_election_status().is_open(),
|
||||
Error::<T>::PhragmenEarlySubmission,
|
||||
Error::<T>::PhragmenEarlySubmission.with_weight(T::DbWeight::get().reads(1)),
|
||||
);
|
||||
|
||||
// check current era.
|
||||
if let Some(current_era) = Self::current_era() {
|
||||
ensure!(
|
||||
current_era == era,
|
||||
Error::<T>::PhragmenEarlySubmission,
|
||||
Error::<T>::PhragmenEarlySubmission.with_weight(T::DbWeight::get().reads(2)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2538,11 +2609,11 @@ impl<T: Trait> Module<T> {
|
||||
if let Some(queued_score) = Self::queued_score() {
|
||||
ensure!(
|
||||
is_score_better(queued_score, score),
|
||||
Error::<T>::PhragmenWeakSubmission,
|
||||
Error::<T>::PhragmenWeakSubmission.with_weight(T::DbWeight::get().reads(3)),
|
||||
)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(None.into())
|
||||
}
|
||||
|
||||
/// Checks a given solution and if correct and improved, writes it on chain as the queued result
|
||||
@@ -2553,21 +2624,46 @@ impl<T: Trait> Module<T> {
|
||||
compute: ElectionCompute,
|
||||
claimed_score: PhragmenScore,
|
||||
era: EraIndex,
|
||||
) -> Result<(), Error<T>> {
|
||||
election_size: ElectionSize,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
// Do the basic checks. era, claimed score and window open.
|
||||
Self::pre_dispatch_checks(claimed_score, era)?;
|
||||
// the weight that we will refund in case of a correct submission. We compute this now
|
||||
// because the data needed for it will be consumed further down.
|
||||
let adjusted_weight = weight::weight_for_correct_submit_solution::<T>(
|
||||
&winners,
|
||||
&compact_assignments,
|
||||
&election_size,
|
||||
);
|
||||
|
||||
// Check that the number of presented winners is sane. Most often we have more candidates
|
||||
// that we need. Then it should be Self::validator_count(). Else it should be all the
|
||||
// than we need. Then it should be `Self::validator_count()`. Else it should be all the
|
||||
// candidates.
|
||||
let snapshot_length = <SnapshotValidators<T>>::decode_len()
|
||||
let snapshot_validators_length = <SnapshotValidators<T>>::decode_len()
|
||||
.map(|l| l as u32)
|
||||
.ok_or_else(|| Error::<T>::SnapshotUnavailable)?;
|
||||
|
||||
// size of the solution must be correct.
|
||||
ensure!(
|
||||
snapshot_validators_length == u32::from(election_size.validators),
|
||||
Error::<T>::PhragmenBogusElectionSize,
|
||||
);
|
||||
|
||||
// check the winner length only here and when we know the length of the snapshot validators
|
||||
// length.
|
||||
let desired_winners = Self::validator_count().min(snapshot_length as u32);
|
||||
let desired_winners = Self::validator_count().min(snapshot_validators_length);
|
||||
ensure!(winners.len() as u32 == desired_winners, Error::<T>::PhragmenBogusWinnerCount);
|
||||
|
||||
let snapshot_nominators_len = <SnapshotNominators<T>>::decode_len()
|
||||
.map(|l| l as u32)
|
||||
.ok_or_else(|| Error::<T>::SnapshotUnavailable)?;
|
||||
|
||||
// rest of the size of the solution must be correct.
|
||||
ensure!(
|
||||
snapshot_nominators_len == election_size.nominators,
|
||||
Error::<T>::PhragmenBogusElectionSize,
|
||||
);
|
||||
|
||||
// decode snapshot validators.
|
||||
let snapshot_validators = Self::snapshot_validators()
|
||||
.ok_or(Error::<T>::SnapshotUnavailable)?;
|
||||
@@ -2581,7 +2677,7 @@ impl<T: Trait> Module<T> {
|
||||
}).collect::<Result<Vec<T::AccountId>, Error<T>>>()?;
|
||||
|
||||
// decode the rest of the snapshot.
|
||||
let snapshot_nominators = <Module<T>>::snapshot_nominators()
|
||||
let snapshot_nominators = Self::snapshot_nominators()
|
||||
.ok_or(Error::<T>::SnapshotUnavailable)?;
|
||||
|
||||
// helpers
|
||||
@@ -2615,7 +2711,7 @@ impl<T: Trait> Module<T> {
|
||||
// have bigger problems.
|
||||
log!(error, "💸 detected an error in the staking locking and snapshot.");
|
||||
// abort.
|
||||
return Err(Error::<T>::PhragmenBogusNominator);
|
||||
return Err(Error::<T>::PhragmenBogusNominator.into());
|
||||
}
|
||||
|
||||
if !is_validator {
|
||||
@@ -2632,14 +2728,14 @@ impl<T: Trait> Module<T> {
|
||||
// each target in the provided distribution must be actually nominated by the
|
||||
// nominator after the last non-zero slash.
|
||||
if nomination.targets.iter().find(|&tt| tt == t).is_none() {
|
||||
return Err(Error::<T>::PhragmenBogusNomination);
|
||||
return Err(Error::<T>::PhragmenBogusNomination.into());
|
||||
}
|
||||
|
||||
if <Self as Store>::SlashingSpans::get(&t).map_or(
|
||||
false,
|
||||
|spans| nomination.submitted_in < spans.last_nonzero_slash(),
|
||||
) {
|
||||
return Err(Error::<T>::PhragmenSlashedNomination);
|
||||
return Err(Error::<T>::PhragmenSlashedNomination.into());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -2679,8 +2775,9 @@ impl<T: Trait> Module<T> {
|
||||
let exposures = Self::collect_exposure(supports);
|
||||
log!(
|
||||
info,
|
||||
"💸 A better solution (with compute {:?}) has been validated and stored on chain.",
|
||||
"💸 A better solution (with compute {:?} and score {:?}) has been validated and stored on chain.",
|
||||
compute,
|
||||
submitted_score,
|
||||
);
|
||||
|
||||
// write new results.
|
||||
@@ -2691,8 +2788,7 @@ impl<T: Trait> Module<T> {
|
||||
});
|
||||
QueuedScore::put(submitted_score);
|
||||
|
||||
Ok(())
|
||||
|
||||
Ok(Some(adjusted_weight).into())
|
||||
}
|
||||
|
||||
/// Start a session potentially starting an era.
|
||||
@@ -2996,7 +3092,7 @@ impl<T: Trait> Module<T> {
|
||||
|
||||
supports.into_iter().map(|(validator, support)| {
|
||||
// build `struct exposure` from `support`
|
||||
let mut others = Vec::new();
|
||||
let mut others = Vec::with_capacity(support.voters.len());
|
||||
let mut own: BalanceOf<T> = Zero::zero();
|
||||
let mut total: BalanceOf<T> = Zero::zero();
|
||||
support.voters
|
||||
@@ -3381,12 +3477,6 @@ impl<T, Reporter, Offender, R, O> ReportOffence<Reporter, Offender, O>
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Trait> From<Error<T>> for InvalidTransaction {
|
||||
fn from(e: Error<T>) -> Self {
|
||||
InvalidTransaction::Custom(e.as_u8())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
|
||||
type Call = Call<T>;
|
||||
@@ -3396,8 +3486,10 @@ impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
|
||||
_,
|
||||
score,
|
||||
era,
|
||||
_,
|
||||
) = call {
|
||||
use offchain_election::DEFAULT_LONGEVITY;
|
||||
use sp_runtime::DispatchError;
|
||||
|
||||
// discard solution not coming from the local OCW.
|
||||
match source {
|
||||
@@ -3408,9 +3500,18 @@ impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = Self::pre_dispatch_checks(*score, *era) {
|
||||
log!(debug, "validate unsigned pre dispatch checks failed due to {:?}.", e);
|
||||
return InvalidTransaction::from(e).into();
|
||||
if let Err(error_with_post_info) = Self::pre_dispatch_checks(*score, *era) {
|
||||
let error = error_with_post_info.error;
|
||||
let error_number = match error {
|
||||
DispatchError::Module { error, ..} => error,
|
||||
_ => 0,
|
||||
};
|
||||
log!(
|
||||
debug,
|
||||
"validate unsigned pre dispatch checks failed due to module error #{:?}.",
|
||||
error,
|
||||
);
|
||||
return InvalidTransaction::Custom(error_number).into();
|
||||
}
|
||||
|
||||
log!(debug, "validateUnsigned succeeded for a solution at era {}.", era);
|
||||
|
||||
Reference in New Issue
Block a user