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

submit emergency solution #11703

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5771803
submit emergency solution
Szegoo Jun 18, 2022
d3c0705
tests & configurable punishment
Szegoo Jun 21, 2022
54746ca
fix
Szegoo Jun 21, 2022
72f6d4d
better naming
Szegoo Jun 22, 2022
345e1a4
documentation
Szegoo Jun 22, 2022
704d73b
Merge branch 'paritytech:master' into submit-emergency-solution
Szegoo Jun 25, 2022
e7a2afb
Merge branch 'paritytech:master' into submit-emergency-solution
Szegoo Jun 30, 2022
9b2ef3b
Update frame/election-provider-multi-phase/src/lib.rs
Szegoo Jun 30, 2022
9946a3f
renaming
Szegoo Jun 30, 2022
3ce6710
Update frame/election-provider-multi-phase/src/lib.rs
kianenigma Jun 30, 2022
c51a51d
Update frame/election-provider-multi-phase/src/lib.rs
kianenigma Jun 30, 2022
28a93a6
fix
Szegoo Jun 30, 2022
340c65b
fmt
Szegoo Jun 30, 2022
3f125fd
test for events
Szegoo Jun 30, 2022
a176c1c
line wrapping fix
Szegoo Jun 30, 2022
8a29e94
Merge branch 'paritytech:master' into submit-emergency-solution
Szegoo Jun 30, 2022
32cf700
Merge branch 'paritytech:master' into submit-emergency-solution
Szegoo Jul 5, 2022
405dd34
Merge branch 'paritytech:master' into submit-emergency-solution
Szegoo Jul 15, 2022
1c38c68
fix inaccurate weight
Szegoo Jul 20, 2022
0c1932f
fix inaccurate weight
Szegoo Jul 20, 2022
d801649
fix inaccurate weight
Szegoo Jul 20, 2022
87f55d0
Merge branch 'paritytech:master' into submit-emergency-solution
Szegoo Jul 20, 2022
48c72de
Update frame/election-provider-multi-phase/src/lib.rs
Szegoo Jul 23, 2022
8dbc1ad
use solution_weight
Szegoo Jul 23, 2022
71fd834
use solution_weight
Szegoo Jul 23, 2022
6a362f5
fix
Szegoo Jul 23, 2022
c12cfa9
Merge branch 'paritytech:master' into submit-emergency-solution
Szegoo Jul 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime {
type MaxElectingVoters = MaxElectingVoters;
type BenchmarkingConfig = ElectionProviderBenchmarkConfig;
type WeightInfo = pallet_election_provider_multi_phase::weights::SubstrateWeight<Self>;
type EmergencyDepositMultiplier = ConstU32<2>;
}

parameter_types! {
Expand Down
157 changes: 146 additions & 11 deletions frame/election-provider-multi-phase/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@
//! Upon the end of the signed phase, the solutions are examined from best to worse (i.e. `pop()`ed
//! until drained). Each solution undergoes an expensive `Pallet::feasibility_check`, which ensures
//! the score claimed by this score was correct, and it is valid based on the election data (i.e.
//! votes and targets). At each step, if the current best solution passes the feasibility check,
//! it is considered to be the best one. The sender of the origin is rewarded, and the rest of the
//! votes and targets). At each step, if the current best solution passes the feasibility check, it
//! is considered to be the best one. The sender of the origin is rewarded, and the rest of the
//! queued solutions get their deposit back and are discarded, without being checked.
//!
//! The following example covers all of the cases at the end of the signed phase:
Expand Down Expand Up @@ -126,12 +126,12 @@
//! 2. Any other unforeseen internal error
//!
//! A call to `T::ElectionProvider::elect` is made, and `Ok(_)` cannot be returned, then the pallet
//! proceeds to the [`Phase::Emergency`]. During this phase, any solution can be submitted from
//! [`Config::ForceOrigin`], without any checking, via [`Pallet::set_emergency_election_result`]
//! transaction. Hence, `[`Config::ForceOrigin`]` should only be set to a trusted origin, such as
//! the council or root. Once submitted, the forced solution is kept in [`QueuedSolution`] until the
//! next call to `T::ElectionProvider::elect`, where it is returned and [`Phase`] goes back to
//! `Off`.
//! proceeds to the [`Phase::Emergency`]. During this phase, there are two possibilities. One of
//! them is to use the [`Pallet::set_emergency_election_result`] function that submits a solution
//! without any checking. It only accepts solutions from `[`Config::ForceOrigin`]`, hence it should
//! only be set to a trusted origin, such as the council or root. Once submitted, the forced
//! solution is kept in [`QueuedSolution`] until the next call to `T::ElectionProvider::elect`,
//! where it is returned and [`Phase`] goes back to `Off`.
//!
//! This implies that the user of this pallet (i.e. a staking pallet) should re-try calling
//! `T::ElectionProvider::elect` in case of error, until `OK(_)` is returned.
Expand All @@ -151,6 +151,13 @@
//!
//! See the `staking-miner` documentation in the Polkadot repository for more information.
//!
//! The other option is to use the `Pallet::submit_emergency_solution` function which allows you to
//! submit signed solutions during the emergency phase. The only difference between this function
//! and the [`Pallet::submit`] function is that in [`Pallet::submit_emergency_solution`] the
//! submissions are checked on the fly. Good solutions will get rewarded, but bad submissions will
//! be highly punished. The deposit needed for submitting during the emergency phase is higher
//! compared to the one in the regular submission.
//!
//! ## Feasible Solution (correct solution)
//!
//! All submissions must undergo a feasibility check. Signed solutions are checked one by one at the
Expand All @@ -172,8 +179,8 @@
//!
//! ## Error types
//!
//! This pallet provides a verbose error system to ease future debugging and debugging. The overall
//! hierarchy of errors is as follows:
//! This pallet provides a verbose error system to ease future debugging. The overall hierarchy of
//! errors is as follows:
//!
//! 1. [`pallet::Error`]: These are the errors that can be returned in the dispatchables of the
//! pallet, either signed or unsigned. Since decomposition with nested enums is not possible
Expand Down Expand Up @@ -705,6 +712,10 @@ pub mod pallet {

/// The weight of the pallet.
type WeightInfo: WeightInfo;

/// The multiple of regular deposit needed for signed phase during emergency phase.
#[pallet::constant]
type EmergencyDepositMultiplier: Get<u32>;
}

#[pallet::hooks]
Expand Down Expand Up @@ -959,7 +970,7 @@ pub mod pallet {

/// Submit a solution for the signed phase.
///
/// The dispatch origin fo this call must be __signed__.
/// The dispatch origin for this call must be __signed__.
///
/// The solution is potentially queued, based on the claimed score and processed at the end
/// of the signed phase.
Expand Down Expand Up @@ -1070,6 +1081,59 @@ pub mod pallet {
<QueuedSolution<T>>::put(solution);
Ok(())
}

/// Submit a signed solution during the emergency phase. It will go through the
/// `feasibility_check` right away.
///
/// The dispatch origin for this call must be __signed__.
///
/// The deposit that is reserved might be rewarded or slashed based on the outcome.
#[pallet::weight(
T::MinerConfig::solution_weight(
T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS,
T::BenchmarkingConfig::MAXIMUM_TARGETS,
T::BenchmarkingConfig::ACTIVE_VOTERS[1],
T::BenchmarkingConfig::DESIRED_TARGETS[1]
)
.saturating_add(T::DbWeight::get().reads_writes(1, 1))
)]
pub fn submit_emergency_solution(
origin: OriginFor<T>,
raw_solution: Box<RawSolution<SolutionOf<T::MinerConfig>>>,
) -> DispatchResult {
let who = ensure_signed(origin)?;
ensure!(Self::current_phase().is_emergency(), <Error<T>>::CallNotAllowed);
let size = Self::snapshot_metadata().ok_or(Error::<T>::MissingSnapshotMetadata)?;

ensure!(
Self::solution_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(),
Error::<T>::SignedTooMuchWeight
);

let deposit = Self::deposit_for_emergency(&raw_solution, size);
T::Currency::reserve(&who, deposit).map_err(|_| Error::<T>::SignedCannotPayDeposit)?;

let call_fee = {
let call = Call::submit { raw_solution: raw_solution.clone() };
T::EstimateCallFee::estimate_call_fee(&call, None.into())
};

match Self::feasibility_check(*raw_solution, ElectionCompute::Signed) {
Ok(ready_solution) => {
Self::finalize_signed_phase_accept_solution(
ready_solution,
&who,
deposit,
call_fee,
);
},
Err(_) => {
Self::finalize_signed_phase_reject_solution(&who, deposit);
},
}

Ok(())
}
}

#[pallet::event]
Expand Down Expand Up @@ -2017,6 +2081,77 @@ mod tests {
})
}

#[test]
fn accepts_good_solution_during_emergency() {
ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
roll_to(25);
assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25)));

// No solutions are submitted so the queue should be empty
assert!(MultiPhase::queued_solution().is_none());
assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback."));

assert!(MultiPhase::current_phase().is_emergency());

let solution = crate::mock::raw_solution();
let origin = crate::mock::Origin::signed(99);

assert_ok!(MultiPhase::submit_emergency_solution(origin, Box::new(solution)));

// The queued solution should be some now because the submitted solution is correct.
assert!(MultiPhase::queued_solution().is_some());
Szegoo marked this conversation as resolved.
Show resolved Hide resolved

let reward = crate::mock::SignedRewardBase::get();

assert_eq!(
multi_phase_events(),
vec![
Event::SignedPhaseStarted { round: 1 },
Event::UnsignedPhaseStarted { round: 1 },
Event::ElectionFinalized { election_compute: None },
Event::Rewarded { account: 99, value: reward }
]
);
});
}

#[test]
fn rejects_bad_solution_during_emergency() {
ExtBuilder::default().onchain_fallback(false).build_and_execute(|| {
roll_to(25);
assert_eq!(MultiPhase::current_phase(), Phase::Unsigned((true, 25)));

// No solutions are submitted so the queue should be empty.
assert!(MultiPhase::queued_solution().is_none());
assert_eq!(MultiPhase::elect().unwrap_err(), ElectionError::Fallback("NoFallback."));

assert!(MultiPhase::current_phase().is_emergency());

let mut solution = crate::mock::raw_solution();
// modifying the solution, so that it becomes incorrect.
solution.round += 1;
let origin = crate::mock::Origin::signed(99);

assert_ok!(MultiPhase::submit_emergency_solution(origin, Box::new(solution.clone())));

// The queued solution should be none now because the submitted solution is incorrect.
assert!(MultiPhase::queued_solution().is_none());
Szegoo marked this conversation as resolved.
Show resolved Hide resolved

let size = MultiPhase::snapshot_metadata().unwrap();
let deposit = MultiPhase::deposit_for_emergency(&solution, size);

assert_eq!(
multi_phase_events(),
vec![
Event::SignedPhaseStarted { round: 1 },
Event::UnsignedPhaseStarted { round: 1 },
Event::ElectionFinalized { election_compute: None },
Event::Slashed { account: 99, value: deposit }
]
);
});
}

#[test]
fn fallback_strategy_works() {
ExtBuilder::default().onchain_fallback(true).build_and_execute(|| {
Expand Down
2 changes: 2 additions & 0 deletions frame/election-provider-multi-phase/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ parameter_types! {
pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real;
pub static MaxElectingVoters: VoterIndex = u32::max_value();
pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value();
pub static EmergencyDepositMultiplier: u32 = 2;

pub static EpochLength: u64 = 30;
pub static OnChainFallback: bool = true;
Expand Down Expand Up @@ -387,6 +388,7 @@ impl crate::Config for Runtime {
type MaxElectableTargets = MaxElectableTargets;
type MinerConfig = Self;
type Solver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
type EmergencyDepositMultiplier = EmergencyDepositMultiplier;
}

impl<LocalCall> frame_system::offchain::SendTransactionTypes<LocalCall> for Runtime
Expand Down
9 changes: 9 additions & 0 deletions frame/election-provider-multi-phase/src/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,15 @@ impl<T: Config> Pallet<T> {
.saturating_add(len_deposit)
.saturating_add(weight_deposit)
}

/// Similar as `deposit_for`, but returns the result for a emergency solution.
pub fn deposit_for_emergency(
raw_solution: &RawSolution<SolutionOf<T::MinerConfig>>,
size: SolutionOrSnapshotSize,
) -> BalanceOf<T> {
BalanceOf::<T>::from(T::EmergencyDepositMultiplier::get())
.saturating_mul(Self::deposit_for(raw_solution, size))
}
}

#[cfg(test)]
Expand Down