Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Companion for EPM duplicate submissions #6115

Merged
merged 15 commits into from
Oct 19, 2022
Merged
1 change: 1 addition & 0 deletions runtime/kusama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1475,6 +1475,7 @@ pub type Executive = frame_executive::Executive<
pallet_multisig::migrations::v1::MigrateToV1<Runtime>,
// "Properly migrate weights to v2" <https://github.com/paritytech/polkadot/pull/6091>
parachains_configuration::migration::v3::MigrateToV3<Runtime>,
pallet_election_provider_multi_phase::migrations::v1::MigrateToV1<Runtime>,
),
>;
/// The payload being signed in the transactions.
Expand Down
1 change: 1 addition & 0 deletions runtime/polkadot/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1568,6 +1568,7 @@ pub type Executive = frame_executive::Executive<
pallet_multisig::migrations::v1::MigrateToV1<Runtime>,
// "Properly migrate weights to v2" <https://github.com/paritytech/polkadot/pull/6091>
parachains_configuration::migration::v3::MigrateToV3<Runtime>,
pallet_election_provider_multi_phase::migrations::v1::MigrateToV1<Runtime>,
),
>;

Expand Down
1 change: 1 addition & 0 deletions runtime/westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,6 +1219,7 @@ pub type Executive = frame_executive::Executive<
pallet_multisig::migrations::v1::MigrateToV1<Runtime>,
// "Properly migrate weights to v2" <https://github.com/paritytech/polkadot/pull/6091>
parachains_configuration::migration::v3::MigrateToV3<Runtime>,
pallet_election_provider_multi_phase::migrations::v1::MigrateToV1<Runtime>,
),
>;
/// The payload being signed in transactions.
Expand Down
1 change: 1 addition & 0 deletions utils/staking-miner/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ enum Error<T: EPM::Config> {
AlreadySubmitted,
VersionMismatch,
StrategyNotSatisfied,
QueueFull,
Other(String),
}

Expand Down
123 changes: 85 additions & 38 deletions utils/staking-miner/src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ where
.map_err::<Error<T>, _>(Into::into)?
.unwrap_or_default();

for (_score, idx) in indices {
for (_score, _bn, idx) in indices {
let key = StorageKey(EPM::SignedSubmissionsMap::<T>::hashed_key_for(idx));

if let Some(submission) = rpc
Expand All @@ -81,20 +81,37 @@ where
Ok(())
}

/// `true` if `our_score` should pass the onchain `best_score` with the given strategy.
pub(crate) fn score_passes_strategy(
our_score: sp_npos_elections::ElectionScore,
best_score: sp_npos_elections::ElectionScore,
strategy: SubmissionStrategy,
) -> bool {
match strategy {
SubmissionStrategy::Always => true,
SubmissionStrategy::IfLeading =>
our_score == best_score ||
our_score.strict_threshold_better(best_score, Perbill::zero()),
SubmissionStrategy::ClaimBetterThan(epsilon) =>
our_score.strict_threshold_better(best_score, epsilon),
SubmissionStrategy::ClaimNoWorseThan(epsilon) => {
!best_score.strict_threshold_better(our_score, epsilon)
},
}
}

/// Reads all current solutions and checks the scores according to the `SubmissionStrategy`.
async fn ensure_no_better_solution<T: EPM::Config, B: BlockT>(
async fn ensure_strategy_met<T: EPM::Config, B: BlockT>(
rpc: &SharedRpcClient,
at: Hash,
score: sp_npos_elections::ElectionScore,
strategy: SubmissionStrategy,
max_submissions: u32,
) -> Result<(), Error<T>> {
let epsilon = match strategy {
// don't care about current scores.
SubmissionStrategy::Always => return Ok(()),
SubmissionStrategy::IfLeading => Perbill::zero(),
SubmissionStrategy::ClaimBetterThan(epsilon) => epsilon,
};
// don't care about current scores.
if matches!(strategy, SubmissionStrategy::Always) {
return Ok(())
}

let indices_key = StorageKey(EPM::SignedSubmissionIndices::<T>::hashed_key().to_vec());

Expand All @@ -104,34 +121,16 @@ async fn ensure_no_better_solution<T: EPM::Config, B: BlockT>(
.map_err::<Error<T>, _>(Into::into)?
.unwrap_or_default();

let mut is_best_score = true;
let mut scores = 0;

log::debug!(target: LOG_TARGET, "submitted solutions on chain: {:?}", indices);

// BTreeMap is ordered, take last to get the max score.
for (curr_max_score, _) in indices.into_iter() {
if !score.strict_threshold_better(curr_max_score, epsilon) {
log::warn!(target: LOG_TARGET, "mined score is not better; skipping to submit");
is_best_score = false;
}

if score == curr_max_score {
log::warn!(
target: LOG_TARGET,
"mined score has the same score as already submitted score"
);
}

// Indices can't be bigger than u32::MAX so can't overflow.
scores += 1;
// we check the queue here as well. Could be checked elsewhere.
if indices.len() as u32 >= max_submissions {
return Err(Error::<T>::QueueFull)
}

if scores == max_submissions {
log::warn!(target: LOG_TARGET, "The submissions queue is full");
}
// default score is all zeros, any score is better than it.
let best_score = indices.last().map(|(score, _, _)| *score).unwrap_or_default();
niklasad1 marked this conversation as resolved.
Show resolved Hide resolved
log::debug!(target: LOG_TARGET, "best onchain score is {:?}", best_score);

if is_best_score {
if score_passes_strategy(score, best_score, strategy) {
Ok(())
} else {
Err(Error::StrategyNotSatisfied)
Expand Down Expand Up @@ -314,9 +313,14 @@ macro_rules! monitor_cmd_for { ($runtime:tt) => { paste::paste! {
}
};

let ensure_no_better_fut = tokio::spawn(async move {
ensure_no_better_solution::<Runtime, Block>(&rpc1, latest_head, score, config.submission_strategy,
SignedMaxSubmissions::get()).await
let ensure_strategy_met_fut = tokio::spawn(async move {
ensure_strategy_met::<Runtime, Block>(
&rpc1,
latest_head,
score,
config.submission_strategy,
SignedMaxSubmissions::get()
).await
});

let ensure_signed_phase_fut = tokio::spawn(async move {
Expand All @@ -325,10 +329,10 @@ macro_rules! monitor_cmd_for { ($runtime:tt) => { paste::paste! {

// Run the calls in parallel and return once all has completed or any failed.
if let Err(err) = tokio::try_join!(
flatten(ensure_no_better_fut),
flatten(ensure_strategy_met_fut),
flatten(ensure_signed_phase_fut),
) {
log::debug!(target: LOG_TARGET, "Skipping to submit at block {}; {}", at.number, err);
log::error!(target: LOG_TARGET, "Skipping to submit at block {}; {}", at.number, err);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make this warn or something, it will end up with a log for each block that isn't a part of the signed phase

kianenigma marked this conversation as resolved.
Show resolved Hide resolved
return;
}

Expand Down Expand Up @@ -420,3 +424,46 @@ macro_rules! monitor_cmd_for { ($runtime:tt) => { paste::paste! {
monitor_cmd_for!(polkadot);
monitor_cmd_for!(kusama);
monitor_cmd_for!(westend);

#[cfg(test)]
pub mod tests {
use super::*;

#[test]
fn score_passes_strategy_works() {
let s = |x| sp_npos_elections::ElectionScore { minimal_stake: x, ..Default::default() };
let two = Perbill::from_percent(2);

// anything passes Always
assert!(score_passes_strategy(s(0), s(0), SubmissionStrategy::Always));
assert!(score_passes_strategy(s(5), s(0), SubmissionStrategy::Always));
assert!(score_passes_strategy(s(5), s(10), SubmissionStrategy::Always));

// if leading
assert!(score_passes_strategy(s(0), s(0), SubmissionStrategy::IfLeading));
assert!(score_passes_strategy(s(1), s(0), SubmissionStrategy::IfLeading));
assert!(score_passes_strategy(s(2), s(0), SubmissionStrategy::IfLeading));
assert!(!score_passes_strategy(s(5), s(10), SubmissionStrategy::IfLeading));
assert!(!score_passes_strategy(s(9), s(10), SubmissionStrategy::IfLeading));
assert!(score_passes_strategy(s(10), s(10), SubmissionStrategy::IfLeading));

// if better by 2%
assert!(!score_passes_strategy(s(50), s(100), SubmissionStrategy::ClaimBetterThan(two)));
assert!(!score_passes_strategy(s(100), s(100), SubmissionStrategy::ClaimBetterThan(two)));
assert!(!score_passes_strategy(s(101), s(100), SubmissionStrategy::ClaimBetterThan(two)));
assert!(!score_passes_strategy(s(102), s(100), SubmissionStrategy::ClaimBetterThan(two)));
assert!(score_passes_strategy(s(103), s(100), SubmissionStrategy::ClaimBetterThan(two)));
assert!(score_passes_strategy(s(150), s(100), SubmissionStrategy::ClaimBetterThan(two)));

// if no less than 2% worse
assert!(!score_passes_strategy(s(50), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
assert!(!score_passes_strategy(s(97), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
assert!(score_passes_strategy(s(98), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
assert!(score_passes_strategy(s(99), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
assert!(score_passes_strategy(s(100), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
assert!(score_passes_strategy(s(101), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
assert!(score_passes_strategy(s(102), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
assert!(score_passes_strategy(s(103), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
assert!(score_passes_strategy(s(150), s(100), SubmissionStrategy::ClaimNoWorseThan(two)));
}
}
16 changes: 11 additions & 5 deletions utils/staking-miner/src/opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,14 @@ pub(crate) struct InfoOpts {
#[derive(Debug, Copy, Clone)]
#[cfg_attr(test, derive(PartialEq))]
pub enum SubmissionStrategy {
// Only submit if at the time, we are the best.
IfLeading,
// Always submit.
/// Always submit.
Always,
// Submit if we are leading, or if the solution that's leading is more that the given `Perbill`
// better than us. This helps detect obviously fake solutions and still combat them.
/// Only submit if at the time, we are the best (or equal to it).
IfLeading,
/// Submit if we are no worse than `Perbill` worse than the best.
ClaimNoWorseThan(Perbill),
/// Submit if we are leading, or if the solution that's leading is more that the given `Perbill`
/// better than us. This helps detect obviously fake solutions and still combat them.
ClaimBetterThan(Perbill),
}

Expand All @@ -185,6 +187,7 @@ pub(crate) enum Solver {
/// * --submission-strategy if-leading: only submit if leading
/// * --submission-strategy always: always submit
/// * --submission-strategy "percent-better <percent>": submit if submission is `n` percent better.
/// * --submission-strategy "no-worse-than <percent>": submit if submission is no more than `n` percent worse.
kianenigma marked this conversation as resolved.
Show resolved Hide resolved
///
impl FromStr for SubmissionStrategy {
type Err = String;
Expand All @@ -196,6 +199,9 @@ impl FromStr for SubmissionStrategy {
Self::IfLeading
} else if s == "always" {
Self::Always
} else if s.starts_with("no-worse-than ") {
kianenigma marked this conversation as resolved.
Show resolved Hide resolved
let percent: u32 = s[14..].parse().map_err(|e| format!("{:?}", e))?;
Self::ClaimNoWorseThan(Perbill::from_percent(percent))
} else if s.starts_with("percent-better ") {
let percent: u32 = s[15..].parse().map_err(|e| format!("{:?}", e))?;
Self::ClaimBetterThan(Perbill::from_percent(percent))
Expand Down