From 5771803b56c8b2c4c1a5eb06d2a9f25b1fd98284 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Sat, 18 Jun 2022 10:59:16 +0200 Subject: [PATCH 01/19] submit emergency solution --- .../election-provider-multi-phase/src/lib.rs | 53 ++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 2f1f6463df719..b6da2f2fe6ccb 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -172,7 +172,7 @@ //! //! ## Error types //! -//! This pallet provides a verbose error system to ease future debugging and debugging. The overall +//! 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 @@ -957,9 +957,58 @@ pub mod pallet { 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::DbWeight::get().reads_writes(1, 1))] + pub fn submit_emergency_solution( + origin: OriginFor, + raw_solution: Box>>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); + + let size = Self::snapshot_metadata().ok_or(Error::::MissingSnapshotMetadata)?; + + ensure!( + Self::solution_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(), + Error::::SignedTooMuchWeight + ); + + let deposit = Self::deposit_for(&raw_solution, size); + + T::Currency::reserve(&who, deposit).map_err(|_| Error::::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.clone(), 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(()) + } + /// 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. From d3c0705f3b0c36902a77ff5c19c617cbe77832e7 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Tue, 21 Jun 2022 11:34:05 +0200 Subject: [PATCH 02/19] tests & configurable punishment --- .../election-provider-multi-phase/src/lib.rs | 57 ++++++++++++++++++- .../election-provider-multi-phase/src/mock.rs | 2 + .../src/signed.rs | 9 +++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index b6da2f2fe6ccb..19ab7a64c4270 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -705,6 +705,11 @@ pub mod pallet { /// The weight of the pallet. type WeightInfo: WeightInfo; + + /// The multiple of the signed submission punishment for bad submission + /// during the emergency phase. + #[pallet::constant] + type EmergencyPunishmentMultiple: Get; } #[pallet::hooks] @@ -980,7 +985,7 @@ pub mod pallet { Error::::SignedTooMuchWeight ); - let deposit = Self::deposit_for(&raw_solution, size); + let deposit = Self::deposit_for_emergency(&raw_solution, size); T::Currency::reserve(&who, deposit).map_err(|_| Error::::SignedCannotPayDeposit)?; @@ -989,7 +994,7 @@ pub mod pallet { T::EstimateCallFee::estimate_call_fee(&call, None.into()) }; - match Self::feasibility_check(*raw_solution.clone(), ElectionCompute::Signed) { + match Self::feasibility_check(*raw_solution, ElectionCompute::Signed) { Ok(ready_solution) => { Self::finalize_signed_phase_accept_solution( ready_solution, @@ -2066,6 +2071,54 @@ 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 queed solution shouldn't be none now because the submitted + // solution is correct. + assert!(MultiPhase::queued_solution().is_some()); + }); + } + + #[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))); + + // The queed solution should be none now because the submitted + // solution is incorrect. + assert!(MultiPhase::queued_solution().is_none()); + }); + } + #[test] fn fallback_strategy_works() { ExtBuilder::default().onchain_fallback(true).build_and_execute(|| { diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 7eff70b47eba5..938a98a28fe52 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -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 EmergencyPunishmentMultiple: u32 = 2; pub static EpochLength: u64 = 30; pub static OnChainFallback: bool = true; @@ -387,6 +388,7 @@ impl crate::Config for Runtime { type MaxElectableTargets = MaxElectableTargets; type MinerConfig = Self; type Solver = SequentialPhragmen, Balancing>; + type EmergencyPunishmentMultiple = EmergencyPunishmentMultiple; } impl frame_system::offchain::SendTransactionTypes for Runtime diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index eca75139f925a..7b89a7c3cfff0 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -521,6 +521,15 @@ impl Pallet { .saturating_add(len_deposit) .saturating_add(weight_deposit) } + + /// Same as `deposit_for`, but returns a multiple of the result. + pub fn deposit_for_emergency( + raw_solution: &RawSolution>, + size: SolutionOrSnapshotSize, + ) -> BalanceOf { + BalanceOf::::from(T::EmergencyPunishmentMultiple::get()) * + Self::deposit_for(raw_solution, size) + } } #[cfg(test)] From 54746ca3074f2e298b7878410644d85a80d4a346 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Tue, 21 Jun 2022 12:53:59 +0200 Subject: [PATCH 03/19] fix --- bin/node/runtime/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index addcd0b35a9d9..b6ed24da5404c 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -714,6 +714,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; + type EmergencyPunishmentMultiple = ConstU32<2>; } parameter_types! { From 72f6d4dc50d43034dae7fddeb799b220da1f89d3 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Wed, 22 Jun 2022 08:20:06 +0200 Subject: [PATCH 04/19] better naming --- bin/node/runtime/src/lib.rs | 2 +- frame/election-provider-multi-phase/src/lib.rs | 6 +++--- frame/election-provider-multi-phase/src/mock.rs | 4 ++-- frame/election-provider-multi-phase/src/signed.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index b6ed24da5404c..1a83cc5ef980d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -714,7 +714,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; - type EmergencyPunishmentMultiple = ConstU32<2>; + type EmergencyDepositMultiple = ConstU32<2>; } parameter_types! { diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 19ab7a64c4270..793fdbd1ee47f 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -706,10 +706,10 @@ pub mod pallet { /// The weight of the pallet. type WeightInfo: WeightInfo; - /// The multiple of the signed submission punishment for bad submission - /// during the emergency phase. + /// The multiple of regular deposit needed for signed phase during + /// emergency phase. #[pallet::constant] - type EmergencyPunishmentMultiple: Get; + type EmergencyDepositMultiple: Get; } #[pallet::hooks] diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 938a98a28fe52..c56d5403b0f86 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -281,7 +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 EmergencyPunishmentMultiple: u32 = 2; + pub static EmergencyDepositMultiple: u32 = 2; pub static EpochLength: u64 = 30; pub static OnChainFallback: bool = true; @@ -388,7 +388,7 @@ impl crate::Config for Runtime { type MaxElectableTargets = MaxElectableTargets; type MinerConfig = Self; type Solver = SequentialPhragmen, Balancing>; - type EmergencyPunishmentMultiple = EmergencyPunishmentMultiple; + type EmergencyDepositMultiple = EmergencyDepositMultiple; } impl frame_system::offchain::SendTransactionTypes for Runtime diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 7b89a7c3cfff0..ab024b7357ae3 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -527,7 +527,7 @@ impl Pallet { raw_solution: &RawSolution>, size: SolutionOrSnapshotSize, ) -> BalanceOf { - BalanceOf::::from(T::EmergencyPunishmentMultiple::get()) * + BalanceOf::::from(T::EmergencyDepositMultiple::get()) * Self::deposit_for(raw_solution, size) } } From 345e1a4cb97a5da5f1e125d925b7ae82d23c78c5 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Wed, 22 Jun 2022 09:31:28 +0200 Subject: [PATCH 05/19] documentation --- .../election-provider-multi-phase/src/lib.rs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 793fdbd1ee47f..5bb2fa316202f 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -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. @@ -151,6 +151,14 @@ //! //! 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 From 9b2ef3bd90645b10e650780c1c5a6caa37c3d927 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Thu, 30 Jun 2022 10:06:00 +0200 Subject: [PATCH 06/19] Update frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Niklas Adolfsson --- frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 5bb2fa316202f..85efc296ec25b 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -2096,7 +2096,7 @@ mod tests { assert_ok!(MultiPhase::submit_emergency_solution(origin, Box::new(solution))); - // The queed solution shouldn't be none now because the submitted + // The queued solution shouldn't be none now because the submitted // solution is correct. assert!(MultiPhase::queued_solution().is_some()); }); From 9946a3fc2a7f1670837710d8b9cf0ee8cc438f5d Mon Sep 17 00:00:00 2001 From: Szegoo Date: Thu, 30 Jun 2022 10:16:03 +0200 Subject: [PATCH 07/19] renaming --- bin/node/runtime/src/lib.rs | 2 +- frame/election-provider-multi-phase/src/lib.rs | 4 ++-- frame/election-provider-multi-phase/src/mock.rs | 4 ++-- frame/election-provider-multi-phase/src/signed.rs | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 8f03798fe5263..255c9cc32cb7e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -713,7 +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; - type EmergencyDepositMultiple = ConstU32<2>; + type EmergencyDepositMultiplier = ConstU32<2>; } parameter_types! { diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 85efc296ec25b..82cac89e060e3 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -717,7 +717,7 @@ pub mod pallet { /// The multiple of regular deposit needed for signed phase during /// emergency phase. #[pallet::constant] - type EmergencyDepositMultiple: Get; + type EmergencyDepositMultiplier: Get; } #[pallet::hooks] @@ -2121,7 +2121,7 @@ mod tests { assert_ok!(MultiPhase::submit_emergency_solution(origin, Box::new(solution))); - // The queed solution should be none now because the submitted + // The queued solution should be none now because the submitted // solution is incorrect. assert!(MultiPhase::queued_solution().is_none()); }); diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index c56d5403b0f86..562c6607eb88d 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -281,7 +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 EmergencyDepositMultiple: u32 = 2; + pub static EmergencyDepositMultiplier: u32 = 2; pub static EpochLength: u64 = 30; pub static OnChainFallback: bool = true; @@ -388,7 +388,7 @@ impl crate::Config for Runtime { type MaxElectableTargets = MaxElectableTargets; type MinerConfig = Self; type Solver = SequentialPhragmen, Balancing>; - type EmergencyDepositMultiple = EmergencyDepositMultiple; + type EmergencyDepositMultiplier = EmergencyDepositMultiplier; } impl frame_system::offchain::SendTransactionTypes for Runtime diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index ab024b7357ae3..76895e1233b51 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -522,12 +522,12 @@ impl Pallet { .saturating_add(weight_deposit) } - /// Same as `deposit_for`, but returns a multiple of the result. + /// Similar as `deposit_for`, but returns the result for a emergency solution. pub fn deposit_for_emergency( raw_solution: &RawSolution>, size: SolutionOrSnapshotSize, ) -> BalanceOf { - BalanceOf::::from(T::EmergencyDepositMultiple::get()) * + BalanceOf::::from(T::EmergencyDepositMultiplier::get()) * Self::deposit_for(raw_solution, size) } } From 3ce6710a76a7dd9a66a08b4ef773af0a996e8c4c Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 30 Jun 2022 10:48:49 +0100 Subject: [PATCH 08/19] Update frame/election-provider-multi-phase/src/lib.rs --- frame/election-provider-multi-phase/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 82cac89e060e3..096e7030feb4f 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -983,9 +983,7 @@ pub mod pallet { raw_solution: Box>>, ) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); - let size = Self::snapshot_metadata().ok_or(Error::::MissingSnapshotMetadata)?; ensure!( From c51a51d9587a73afc7c387698f03971a2489114c Mon Sep 17 00:00:00 2001 From: Kian Paimani <5588131+kianenigma@users.noreply.github.com> Date: Thu, 30 Jun 2022 10:49:50 +0100 Subject: [PATCH 09/19] Update frame/election-provider-multi-phase/src/lib.rs --- frame/election-provider-multi-phase/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 096e7030feb4f..a48f5d79f1500 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -992,7 +992,6 @@ pub mod pallet { ); let deposit = Self::deposit_for_emergency(&raw_solution, size); - T::Currency::reserve(&who, deposit).map_err(|_| Error::::SignedCannotPayDeposit)?; let call_fee = { From 28a93a67e8102f7dd835772c7786b345e9b06430 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Thu, 30 Jun 2022 12:01:04 +0200 Subject: [PATCH 10/19] fix --- .../election-provider-multi-phase/src/lib.rs | 92 +++++++++---------- .../src/signed.rs | 3 +- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index a48f5d79f1500..aab53a302bb51 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -970,52 +970,6 @@ pub mod pallet { 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::DbWeight::get().reads_writes(1, 1))] - pub fn submit_emergency_solution( - origin: OriginFor, - raw_solution: Box>>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); - let size = Self::snapshot_metadata().ok_or(Error::::MissingSnapshotMetadata)?; - - ensure!( - Self::solution_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(), - Error::::SignedTooMuchWeight - ); - - let deposit = Self::deposit_for_emergency(&raw_solution, size); - T::Currency::reserve(&who, deposit).map_err(|_| Error::::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(()) - } - /// Submit a solution for the signed phase. /// /// The dispatch origin for this call must be __signed__. @@ -1129,6 +1083,52 @@ pub mod pallet { >::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::DbWeight::get().reads_writes(1, 1))] + pub fn submit_emergency_solution( + origin: OriginFor, + raw_solution: Box>>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + ensure!(Self::current_phase().is_emergency(), >::CallNotAllowed); + let size = Self::snapshot_metadata().ok_or(Error::::MissingSnapshotMetadata)?; + + ensure!( + Self::solution_weight_of(&raw_solution, size) < T::SignedMaxWeight::get(), + Error::::SignedTooMuchWeight + ); + + let deposit = Self::deposit_for_emergency(&raw_solution, size); + T::Currency::reserve(&who, deposit).map_err(|_| Error::::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] diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 76895e1233b51..33688bca6318e 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -527,8 +527,9 @@ impl Pallet { raw_solution: &RawSolution>, size: SolutionOrSnapshotSize, ) -> BalanceOf { - BalanceOf::::from(T::EmergencyDepositMultiplier::get()) * + BalanceOf::::from(T::EmergencyDepositMultiplier::get()).saturating_mul( Self::deposit_for(raw_solution, size) + ) } } From 340c65be074d83dd6d23f67d8416eab841cea88e Mon Sep 17 00:00:00 2001 From: Szegoo Date: Thu, 30 Jun 2022 12:07:48 +0200 Subject: [PATCH 11/19] fmt --- frame/election-provider-multi-phase/src/signed.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 33688bca6318e..21e74781fa678 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -527,9 +527,8 @@ impl Pallet { raw_solution: &RawSolution>, size: SolutionOrSnapshotSize, ) -> BalanceOf { - BalanceOf::::from(T::EmergencyDepositMultiplier::get()).saturating_mul( - Self::deposit_for(raw_solution, size) - ) + BalanceOf::::from(T::EmergencyDepositMultiplier::get()) + .saturating_mul(Self::deposit_for(raw_solution, size)) } } From 3f125fd426419e639025ae1575411b18b1580098 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Thu, 30 Jun 2022 13:20:23 +0200 Subject: [PATCH 12/19] test for events --- .../election-provider-multi-phase/src/lib.rs | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index aab53a302bb51..5f00fdd29d333 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -2093,9 +2093,21 @@ mod tests { assert_ok!(MultiPhase::submit_emergency_solution(origin, Box::new(solution))); - // The queued solution shouldn't be none now because the submitted + // The queued solution should be some now because the submitted // solution is correct. assert!(MultiPhase::queued_solution().is_some()); + + 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 } + ] + ); }); } @@ -2116,11 +2128,24 @@ mod tests { solution.round += 1; let origin = crate::mock::Origin::signed(99); - assert_ok!(MultiPhase::submit_emergency_solution(origin, Box::new(solution))); + 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()); + + 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 } + ] + ); }); } From a176c1c139edcbad6a745edb32da5991d6d86469 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Thu, 30 Jun 2022 14:12:32 +0200 Subject: [PATCH 13/19] line wrapping fix --- .../election-provider-multi-phase/src/lib.rs | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 5f00fdd29d333..b945f1997560c 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -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: @@ -129,9 +129,9 @@ //! 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`. +//! 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. @@ -151,13 +151,12 @@ //! //! 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. +//! 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) //! @@ -180,8 +179,8 @@ //! //! ## Error types //! -//! This pallet provides a verbose error system to ease future 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 @@ -714,8 +713,7 @@ pub mod pallet { /// The weight of the pallet. type WeightInfo: WeightInfo; - /// The multiple of regular deposit needed for signed phase during - /// emergency phase. + /// The multiple of regular deposit needed for signed phase during emergency phase. #[pallet::constant] type EmergencyDepositMultiplier: Get; } @@ -1084,13 +1082,12 @@ pub mod pallet { Ok(()) } - /// Submit a signed solution during the emergency phase. - /// It will go through the `feasibility_check` right away. + /// 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. + /// The deposit that is reserved might be rewarded or slashed based on the outcome. #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] pub fn submit_emergency_solution( origin: OriginFor, @@ -2093,8 +2090,7 @@ mod tests { assert_ok!(MultiPhase::submit_emergency_solution(origin, Box::new(solution))); - // The queued solution should be some now because the submitted - // solution is correct. + // The queued solution should be some now because the submitted solution is correct. assert!(MultiPhase::queued_solution().is_some()); let reward = crate::mock::SignedRewardBase::get(); @@ -2130,8 +2126,7 @@ mod tests { assert_ok!(MultiPhase::submit_emergency_solution(origin, Box::new(solution.clone()))); - // The queued solution should be none now because the submitted - // solution is incorrect. + // The queued solution should be none now because the submitted solution is incorrect. assert!(MultiPhase::queued_solution().is_none()); let size = MultiPhase::snapshot_metadata().unwrap(); From 1c38c68a914e8248729acc32f416b20cb2578c04 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Wed, 20 Jul 2022 09:06:00 +0300 Subject: [PATCH 14/19] fix inaccurate weight --- frame/election-provider-multi-phase/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index fb4d778b2f7df..5fbe72f0d94b2 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1088,7 +1088,14 @@ pub mod pallet { /// 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::DbWeight::get().reads_writes(1, 1))] + #[pallet::weight( + T::WeightInfo::feasibility_check( + T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS, + T::BenchmarkingConfig::MAXIMUM_TARGETS, + T::BenchmarkingConfig::ACTIVE_VOTERS[1], + T::BenchmarkingConfig::DESIRED_TARGETS[1] + ) + + T::DbWeight::get().reads_writes(1, 1))] pub fn submit_emergency_solution( origin: OriginFor, raw_solution: Box>>, From 0c1932fef2bc698caadc025a6161cfe2d00d8885 Mon Sep 17 00:00:00 2001 From: Szegoo Date: Wed, 20 Jul 2022 09:11:06 +0300 Subject: [PATCH 15/19] fix inaccurate weight --- frame/election-provider-multi-phase/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 5fbe72f0d94b2..38c1c8cd85720 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1090,12 +1090,12 @@ pub mod pallet { /// The deposit that is reserved might be rewarded or slashed based on the outcome. #[pallet::weight( T::WeightInfo::feasibility_check( - T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS, - T::BenchmarkingConfig::MAXIMUM_TARGETS, - T::BenchmarkingConfig::ACTIVE_VOTERS[1], - T::BenchmarkingConfig::DESIRED_TARGETS[1] - ) - + T::DbWeight::get().reads_writes(1, 1))] + T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS, + T::BenchmarkingConfig::MAXIMUM_TARGETS, + T::BenchmarkingConfig::ACTIVE_VOTERS[1], + T::BenchmarkingConfig::DESIRED_TARGETS[1] + ) + + T::DbWeight::get().reads_writes(1, 1))] pub fn submit_emergency_solution( origin: OriginFor, raw_solution: Box>>, From d801649e692a30fde567c8abeccce366fa30dd17 Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Wed, 20 Jul 2022 09:15:36 +0300 Subject: [PATCH 16/19] fix inaccurate weight --- frame/election-provider-multi-phase/src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index fb4d778b2f7df..38c1c8cd85720 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1088,7 +1088,14 @@ pub mod pallet { /// 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::DbWeight::get().reads_writes(1, 1))] + #[pallet::weight( + T::WeightInfo::feasibility_check( + T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS, + T::BenchmarkingConfig::MAXIMUM_TARGETS, + T::BenchmarkingConfig::ACTIVE_VOTERS[1], + T::BenchmarkingConfig::DESIRED_TARGETS[1] + ) + + T::DbWeight::get().reads_writes(1, 1))] pub fn submit_emergency_solution( origin: OriginFor, raw_solution: Box>>, From 48c72deca621744f46386d28133dd9909e3752fe Mon Sep 17 00:00:00 2001 From: Sergej Sakac <73715684+Szegoo@users.noreply.github.com> Date: Sat, 23 Jul 2022 06:36:49 +0300 Subject: [PATCH 17/19] Update frame/election-provider-multi-phase/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/election-provider-multi-phase/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 38c1c8cd85720..d4a7c2e4bcb65 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1095,7 +1095,7 @@ pub mod pallet { T::BenchmarkingConfig::ACTIVE_VOTERS[1], T::BenchmarkingConfig::DESIRED_TARGETS[1] ) - + T::DbWeight::get().reads_writes(1, 1))] + .saturating_add(T::DbWeight::get().reads_writes(1, 1)))] pub fn submit_emergency_solution( origin: OriginFor, raw_solution: Box>>, From 8dbc1ad5f2bdc6fe26f3b8a6bfd8239cad6138dc Mon Sep 17 00:00:00 2001 From: Szegoo Date: Sat, 23 Jul 2022 07:01:26 +0300 Subject: [PATCH 18/19] use solution_weight --- frame/election-provider-multi-phase/src/lib.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 38c1c8cd85720..d1109e03a4703 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1089,13 +1089,9 @@ pub mod pallet { /// /// The deposit that is reserved might be rewarded or slashed based on the outcome. #[pallet::weight( - T::WeightInfo::feasibility_check( - T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS, - T::BenchmarkingConfig::MAXIMUM_TARGETS, - T::BenchmarkingConfig::ACTIVE_VOTERS[1], - T::BenchmarkingConfig::DESIRED_TARGETS[1] - ) - + T::DbWeight::get().reads_writes(1, 1))] + T::MinerConfig::solution_weight() + .saturating_add(T::DbWeight::get().reads_writes(1, 1)) + )] pub fn submit_emergency_solution( origin: OriginFor, raw_solution: Box>>, From 6a362f5dde32c22d29a83f83c256aca6ca0839aa Mon Sep 17 00:00:00 2001 From: Szegoo Date: Sat, 23 Jul 2022 08:06:35 +0300 Subject: [PATCH 19/19] fix --- frame/election-provider-multi-phase/src/lib.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index d1109e03a4703..19c4d15b08f86 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1089,7 +1089,12 @@ pub mod pallet { /// /// The deposit that is reserved might be rewarded or slashed based on the outcome. #[pallet::weight( - T::MinerConfig::solution_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(