git.delta.rocks / unique-network / refs/commits / d41364fb01e4

difftreelog

feat(collator-selection) method refactoring + unit tests complete

Fahrrader2022-12-22parent: #313a9f3.patch.diff
in: master

9 files changed

modifiedpallets/collator-selection/src/benchmarking.rsdiffbeforeafterboth
130 where_clause { where T: pallet_authorship::Config + session::Config }130 where_clause { where T: pallet_authorship::Config + session::Config }
131131
132 set_invulnerables {132 set_invulnerables {
133 let b in 1 .. T::MaxInvulnerables::get();133 let b in 1 .. T::MaxCollators::get();
134 let new_invulnerables = register_validators::<T>(b);134 let new_invulnerables = register_validators::<T>(b);
135 let origin = T::UpdateOrigin::successful_origin();135 let origin = T::UpdateOrigin::successful_origin();
136 }: {136 }: {
142 assert_last_event::<T>(Event::NewInvulnerables{invulnerables: new_invulnerables}.into());142 assert_last_event::<T>(Event::NewInvulnerables{invulnerables: new_invulnerables}.into());
143 }143 }
144144
145 set_desired_candidates {145 set_desired_collators {
146 let max: u32 = 999;146 let max: u32 = 999;
147 let origin = T::UpdateOrigin::successful_origin();147 let origin = T::UpdateOrigin::successful_origin();
148 }: {148 }: {
149 assert_ok!(149 assert_ok!(
150 <CollatorSelection<T>>::set_desired_candidates(origin, max.clone())150 <CollatorSelection<T>>::set_desired_collators(origin, max.clone())
151 );151 );
152 }152 }
153 verify {153 verify {
154 assert_last_event::<T>(Event::NewDesiredCandidates{desired_candidates: max}.into());154 assert_last_event::<T>(Event::NewDesiredCollators{desired_collators: max}.into());
155 }155 }
156156
157 set_license_bond {157 set_license_bond {
169 // worse case is when we have all the max-candidate slots filled except one, and we fill that169 // worse case is when we have all the max-candidate slots filled except one, and we fill that
170 // one.170 // one.
171 register_as_candidate {171 register_as_candidate {
172 let c in 1 .. T::MaxCandidates::get();172 let c in 1 .. T::MaxCollators::get();
173173
174 <LicenseBond<T>>::put(T::Currency::minimum_balance());174 <LicenseBond<T>>::put(T::Currency::minimum_balance());
175 <DesiredCandidates<T>>::put(c + 1);175 <DesiredCollators<T>>::put(c + 1);
176176
177 register_validators::<T>(c);177 register_validators::<T>(c);
178 register_candidates::<T>(c);178 register_candidates::<T>(c);
194194
195 // worse case is the last candidate leaving.195 // worse case is the last candidate leaving.
196 leave_intent {196 leave_intent {
197 let c in (T::MinCandidates::get() + 1) .. T::MaxCandidates::get();197 let c in (T::MinCandidates::get() + 1) .. T::MaxCollators::get();
198 <LicenseBond<T>>::put(T::Currency::minimum_balance());198 <LicenseBond<T>>::put(T::Currency::minimum_balance());
199 <DesiredCandidates<T>>::put(c);199 <DesiredCollators<T>>::put(c);
200200
201 register_validators::<T>(c);201 register_validators::<T>(c);
202 register_candidates::<T>(c);202 register_candidates::<T>(c);
230230
231 // worst case for new session.231 // worst case for new session.
232 new_session {232 new_session {
233 let r in 1 .. T::MaxCandidates::get();233 let r in 1 .. T::MaxCollators::get();
234 let c in 1 .. T::MaxCandidates::get();234 let c in 1 .. T::MaxCollators::get();
235235
236 <LicenseBond<T>>::put(T::Currency::minimum_balance());236 <LicenseBond<T>>::put(T::Currency::minimum_balance());
237 <DesiredCandidates<T>>::put(c);237 <DesiredCollators<T>>::put(c);
238 frame_system::Pallet::<T>::set_block_number(0u32.into());238 frame_system::Pallet::<T>::set_block_number(0u32.into());
239239
240 register_validators::<T>(c);240 register_validators::<T>(c);
modifiedpallets/collator-selection/src/lib.rsdiffbeforeafterboth
98 dispatch::{DispatchClass, DispatchResultWithPostInfo},98 dispatch::{DispatchClass, DispatchResultWithPostInfo},
99 inherent::Vec,99 inherent::Vec,
100 pallet_prelude::*,100 pallet_prelude::*,
101 sp_runtime::{101 sp_runtime::traits::{AccountIdConversion, CheckedSub, Saturating, Zero},
102 traits::{AccountIdConversion, CheckedSub, Saturating, Zero},
103 RuntimeDebug,
104 },
105 traits::{102 traits::{
106 Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency,103 Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency,
107 ValidatorRegistration,104 ValidatorRegistration,
146 /// Account Identifier from which the internal Pot is generated.143 /// Account Identifier from which the internal Pot is generated.
147 type PotId: Get<PalletId>;144 type PotId: Get<PalletId>;
148145
149 /// Maximum number of candidates that we should have. This is enforced in code.146 /// Maximum number of candidates and invulnerables that we should have. This is enforced in code.
150 ///
151 /// This does not take into account the invulnerables.
152 type MaxCandidates: Get<u32>;147 type MaxCollators: Get<u32>;
153
154 /// Minimum number of candidates that we should have. This is used for disaster recovery.
155 ///
156 /// This does not take into account the invulnerables.
157 type MinCandidates: Get<u32>;
158
159 /// Maximum number of invulnerables. This is enforced in code.
160 type MaxInvulnerables: Get<u32>;
161148
162 /// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination.149 /// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination.
163 type SlashRatio: Get<Perbill>;150 type SlashRatio: Get<Perbill>;
177 type WeightInfo: WeightInfo;164 type WeightInfo: WeightInfo;
178 }165 }
179
180 /// Basic information about a collation candidate.
181 #[derive(
182 PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
183 )]
184 pub struct LicenseInfo<AccountId, Balance> {
185 /// Account identifier.
186 pub who: AccountId,
187 /// Reserved deposit.
188 pub deposit: Balance,
189 }
190166
191 #[pallet::pallet]167 #[pallet::pallet]
192 #[pallet::generate_store(pub(super) trait Store)]168 #[pallet::generate_store(pub(super) trait Store)]
196 #[pallet::storage]172 #[pallet::storage]
197 #[pallet::getter(fn invulnerables)]173 #[pallet::getter(fn invulnerables)]
198 pub type Invulnerables<T: Config> =174 pub type Invulnerables<T: Config> =
199 StorageValue<_, BoundedVec<T::AccountId, T::MaxInvulnerables>, ValueQuery>;175 StorageValue<_, BoundedVec<T::AccountId, T::MaxCollators>, ValueQuery>;
200176
201 /// The (community) collation license holders.177 /// The (community) collation license holders.
202 #[pallet::storage]178 #[pallet::storage]
209 #[pallet::getter(fn candidates)]185 #[pallet::getter(fn candidates)]
210 pub type Candidates<T: Config> = StorageValue<186 pub type Candidates<T: Config> = StorageValue<
211 _,187 _,
212 BoundedVec<T::AccountId, T::MaxCandidates>, //LicenseInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>, // license ID?188 BoundedVec<T::AccountId, T::MaxCollators>, //LicenseInfo<T::AccountId, BalanceOf<T>>, T::MaxCollators>, // license ID?
213 ValueQuery,189 ValueQuery,
214 >;190 >;
215191
228204
229 /// Desired number of candidates.205 /// Desired number of candidates.
230 ///206 ///
231 /// This should ideally always be less than [`Config::MaxCandidates`] for weights to be correct.207 /// This should ideally always be less than [`Config::MaxCollators`] for weights to be correct.
232 #[pallet::storage]208 #[pallet::storage]
233 #[pallet::getter(fn desired_candidates)]209 #[pallet::getter(fn desired_collators)]
234 pub type DesiredCandidates<T> = StorageValue<_, u32, ValueQuery>;210 pub type DesiredCollators<T> = StorageValue<_, u32, ValueQuery>;
235211
236 /// Fixed amount to deposit to become a collator.212 /// Fixed amount to deposit to become a collator.
237 ///213 ///
245 pub invulnerables: Vec<T::AccountId>,221 pub invulnerables: Vec<T::AccountId>,
246 pub license_bond: BalanceOf<T>,222 pub license_bond: BalanceOf<T>,
247 pub kick_threshold: T::BlockNumber,223 pub kick_threshold: T::BlockNumber,
248 pub desired_candidates: u32,224 pub desired_collators: u32,
249 }225 }
250226
251 #[cfg(feature = "std")]227 #[cfg(feature = "std")]
255 invulnerables: Default::default(),231 invulnerables: Default::default(),
256 license_bond: Default::default(),232 license_bond: Default::default(),
257 kick_threshold: T::BlockNumber::one(),233 kick_threshold: T::BlockNumber::one(),
258 desired_candidates: Default::default(),234 desired_collators: Default::default(),
259 }235 }
260 }236 }
261 }237 }
273 );249 );
274250
275 let bounded_invulnerables =251 let bounded_invulnerables =
276 BoundedVec::<_, T::MaxInvulnerables>::try_from(self.invulnerables.clone())252 BoundedVec::<_, T::MaxCollators>::try_from(self.invulnerables.clone())
277 .expect("genesis invulnerables are more than T::MaxInvulnerables");253 .expect("genesis invulnerables are more than T::MaxCollators");
278 assert!(254 assert!(
279 T::MaxCandidates::get() >= self.desired_candidates,255 T::MaxCollators::get() >= self.desired_collators,
280 "genesis desired_candidates are more than T::MaxCandidates",256 "genesis desired_collators are more than T::MaxCollators",
281 );257 );
282258
283 <DesiredCandidates<T>>::put(&self.desired_candidates);259 <DesiredCollators<T>>::put(self.desired_collators);
284 <LicenseBond<T>>::put(&self.license_bond);260 <LicenseBond<T>>::put(self.license_bond);
285 <KickThreshold<T>>::put(&self.kick_threshold);261 <KickThreshold<T>>::put(self.kick_threshold);
286 <Invulnerables<T>>::put(bounded_invulnerables);262 <Invulnerables<T>>::put(bounded_invulnerables);
287 }263 }
288 }264 }
289265
290 #[pallet::event]266 #[pallet::event]
291 #[pallet::generate_deposit(pub(super) fn deposit_event)]267 #[pallet::generate_deposit(pub(super) fn deposit_event)]
292 pub enum Event<T: Config> {268 pub enum Event<T: Config> {
293 NewDesiredCandidates {269 NewDesiredCollators {
294 desired_candidates: u32,270 desired_collators: u32,
295 },271 },
296 NewLicenseBond {272 NewLicenseBond {
297 bond_amount: BalanceOf<T>,273 bond_amount: BalanceOf<T>,
326 pub enum Error<T> {302 pub enum Error<T> {
327 /// Too many candidates303 /// Too many candidates
328 TooManyCandidates,304 TooManyCandidates,
329 /// Too few candidates
330 TooFewCandidates,
331 /// Unknown error305 /// Unknown error
332 Unknown,306 Unknown,
333 /// Permission issue307 /// Permission issue
334 Permission,308 Permission,
335 /// User already holds license to collate309 /// User already holds license to collate
336 AlreadyLicenseHolder,310 AlreadyHoldingLicense,
337 /// User does not hold a license to collate311 /// User does not hold a license to collate
338 NoLicense,312 NoLicense,
339 /// User is already a candidate313 /// User is already a candidate
360 #[pallet::call]334 #[pallet::call]
361 impl<T: Config> Pallet<T> {335 impl<T: Config> Pallet<T> {
362 /// Add a collator to the list of invulnerable (fixed) collators.336 /// Add a collator to the list of invulnerable (fixed) collators.
363 #[pallet::weight(T::WeightInfo::set_invulnerables(1 as u32))] // todo:collator weight337 #[pallet::weight(T::WeightInfo::set_invulnerables(1u32))] // todo:collator weight
364 pub fn add_invulnerable(338 pub fn add_invulnerable(
365 origin: OriginFor<T>,339 origin: OriginFor<T>,
366 new: T::AccountId,340 new: T::AccountId,
379 return Ok(().into());353 return Ok(().into());
380 }354 }
381
382 // todo:collator check license holders, release moneys, promotion!
383 // force_release_license? Error::<T>::lreadyLicenseHolder?
384355
385 <Invulnerables<T>>::try_append(new.clone())356 <Invulnerables<T>>::try_append(new.clone())
386 .map_err(|_| Error::<T>::TooManyInvulnerables)?;357 .map_err(|_| Error::<T>::TooManyInvulnerables)?;
358
359 // try to offboard the new invulnerable if it was a collator candidate before
360 let _ = Self::try_remove_candidate(&new);
361
387 Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });362 Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });
388 Ok(().into())363 Ok(().into())
417 Ok(().into())392 Ok(().into())
418 }393 }
419394
420 /// Set the ideal number of collators (not including the invulnerables).395 /// Set the ideal number of collators. If lowering this number,
421 /// If lowering this number, then the number of running collators could be higher than this figure.396 /// then the number of running collators could be higher than this figure.
422 /// Aside from that edge case, there should be no other way to have more collators than the desired number.397 /// Aside from that edge case, there should be no other way to have more collators than the desired number.
423 #[pallet::weight(T::WeightInfo::set_desired_candidates())]398 #[pallet::weight(T::WeightInfo::set_desired_collators())]
424 pub fn set_desired_candidates(399 pub fn set_desired_collators(origin: OriginFor<T>, max: u32) -> DispatchResultWithPostInfo {
425 origin: OriginFor<T>,
426 max: u32,
427 ) -> DispatchResultWithPostInfo {
428 T::UpdateOrigin::ensure_origin(origin)?;400 T::UpdateOrigin::ensure_origin(origin)?;
429 // we trust origin calls, this is just a for more accurate benchmarking401 // we trust origin calls, this is just a for more accurate benchmarking
430 if max > T::MaxCandidates::get() {402 if max > T::MaxCollators::get() {
431 log::warn!("max > T::MaxCandidates; you might need to run benchmarks again");403 log::warn!("max > T::MaxCollators; you might need to run benchmarks again");
432 }404 }
433 <DesiredCandidates<T>>::put(&max);405 <DesiredCollators<T>>::put(max);
434 Self::deposit_event(Event::NewDesiredCandidates {406 Self::deposit_event(Event::NewDesiredCollators {
435 desired_candidates: max,407 desired_collators: max,
436 });408 });
437 Ok(().into())409 Ok(().into())
438 }410 }
444 bond: BalanceOf<T>,416 bond: BalanceOf<T>,
445 ) -> DispatchResultWithPostInfo {417 ) -> DispatchResultWithPostInfo {
446 T::UpdateOrigin::ensure_origin(origin)?;418 T::UpdateOrigin::ensure_origin(origin)?;
447 <LicenseBond<T>>::put(&bond);419 <LicenseBond<T>>::put(bond);
448 Self::deposit_event(Event::NewLicenseBond { bond_amount: bond });420 Self::deposit_event(Event::NewLicenseBond { bond_amount: bond });
449 Ok(().into())421 Ok(().into())
450 }422 }
470 /// (a) already have registered session keys and (b) be able to reserve the `LicenseBond`.442 /// (a) already have registered session keys and (b) be able to reserve the `LicenseBond`.
471 ///443 ///
472 /// This call is not available to `Invulnerable` collators.444 /// This call is not available to `Invulnerable` collators.
473 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))] // todo:collator weight445 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCollators::get()))] // todo:collator weight
474 pub fn get_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {446 pub fn get_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
475 // register_as_candidate447 // register_as_candidate
476 let who = ensure_signed(origin)?;448 let who = ensure_signed(origin)?;
477449
478 if Licenses::<T>::contains_key(&who) {450 if Licenses::<T>::contains_key(&who) {
479 return Ok(().into());451 return Err(Error::<T>::AlreadyHoldingLicense.into());
480 }452 }
481453
482 ensure!(454 /*ensure!(
483 !Self::invulnerables().contains(&who),455 !Self::invulnerables().contains(&who),
484 Error::<T>::AlreadyInvulnerable456 Error::<T>::AlreadyInvulnerable
485 );457 );*/
486458
487 let validator_key = T::ValidatorIdOf::convert(who.clone())459 let validator_key = T::ValidatorIdOf::convert(who.clone())
488 .ok_or(Error::<T>::NoAssociatedValidatorId)?;460 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
501 T::Currency::reserve(&who, deposit)?;473 T::Currency::reserve(&who, deposit)?;
502 Licenses::<T>::insert(who.clone(), deposit);474 Licenses::<T>::insert(who.clone(), deposit);
503475
504 /*let current_count =476 /*let current_count =
505 <Licenses<T>>::try_mutate(|licenses| -> Result<usize, DispatchError> {477 <Licenses<T>>::try_mutate(|licenses| -> Result<usize, DispatchError> {
506 if T::OriginPrivilegeCmp::cmp_privilege(&origin, &scheduled.origin) {478 if T::OriginPrivilegeCmp::cmp_privilege(&origin, &scheduled.origin) {
507 return Err(BadOrigin.into());479 return Err(BadOrigin.into());
508 }480 }
509 if candidates.iter().any(|candidate| *candidate == who) {481 if candidates.iter().any(|candidate| *candidate == who) {
510 Err(Error::<T>::AlreadyLicenseHolder)?482 Err(Error::<T>::AlreadyHoldingLicense)?
511 } else {483 } else {
512 T::Currency::reserve(&who, deposit)?;484 T::Currency::reserve(&who, deposit)?;
513 candidates485 candidates
514 .try_push(incoming)486 .try_push(incoming)
515 .map_err(|_| Error::<T>::TooManyCandidates)?;487 .map_err(|_| Error::<T>::TooManyCandidates)?;
516 <LastAuthoredBlock<T>>::insert(488 <LastAuthoredBlock<T>>::insert(
517 who.clone(),489 who.clone(),
518 frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),490 frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),
519 );491 );
520 Ok(candidates.len())492 Ok(candidates.len())
521 }493 }
522 })?;*/494 })?;*/
523495
524 Self::deposit_event(Event::LicenseObtained {496 Self::deposit_event(Event::LicenseObtained {
525 account_id: who,497 account_id: who,
532 /// The account must already hold a license, and cannot offboard immediately during a session.504 /// The account must already hold a license, and cannot offboard immediately during a session.
533 ///505 ///
534 /// This call is not available to `Invulnerable` collators.506 /// This call is not available to `Invulnerable` collators.
535 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCandidates::get()))] // todo:collator weight507 #[pallet::weight(T::WeightInfo::register_as_candidate(T::MaxCollators::get()))] // todo:collator weight
536 pub fn onboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {508 pub fn onboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
537 // register_as_candidate509 // register_as_candidate
538 let who = ensure_signed(origin)?;510 let who = ensure_signed(origin)?;
539511
540 // ensure the user obtained the license.512 // ensure the user obtained the license.
541 ensure!(Licenses::<T>::contains_key(&who), Error::<T>::NoLicense);513 ensure!(Licenses::<T>::contains_key(&who), Error::<T>::NoLicense);
542 // ensure we are below limit.514 // ensure we are below limit.
543 let length = <Candidates<T>>::decode_len().unwrap_or_default();515 let length = <Candidates<T>>::decode_len().unwrap_or_default()
516 + <Invulnerables<T>>::decode_len().unwrap_or_default();
544 ensure!(517 ensure!(
545 (length as u32) < Self::desired_candidates(),518 (length as u32) < Self::desired_collators(),
546 Error::<T>::TooManyCandidates519 Error::<T>::TooManyCandidates
547 );520 );
548 // todo:collator really need it?521 // todo:collator really need it?
551 Error::<T>::AlreadyInvulnerable524 Error::<T>::AlreadyInvulnerable
552 );525 );
553526
554 let deposit = Self::license_bond();
555 // First authored block is current block plus kick threshold to handle session delay
556 /*let incoming = LicenseInfo {527 /*let incoming = LicenseInfo {
557 who: who.clone(),528 who: who.clone(),
558 deposit,529 deposit,
563 if candidates.iter().any(|candidate| *candidate == who) {534 if candidates.iter().any(|candidate| *candidate == who) {
564 Err(Error::<T>::AlreadyCandidate)?535 Err(Error::<T>::AlreadyCandidate)?
565 } else {536 } else {
566 T::Currency::reserve(&who, deposit)?;
567 candidates537 candidates
568 .try_push(who.clone())538 .try_push(who.clone())
569 .map_err(|_| Error::<T>::TooManyCandidates)?;539 .map_err(|_| Error::<T>::TooManyCandidates)?;
540 // First authored block is current block plus kick threshold to handle session delay
570 <LastAuthoredBlock<T>>::insert(541 <LastAuthoredBlock<T>>::insert(
571 who.clone(),542 who.clone(),
572 frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),543 frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),
583 /// session change. The license to `onboard` later at any other time will remain.554 /// session change. The license to `onboard` later at any other time will remain.
584 ///555 ///
585 /// This call will fail if the total number of candidates would drop below `MinCandidates`. todo:collator maybe not556 /// This call will fail if the total number of candidates would drop below `MinCandidates`. todo:collator maybe not
586 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))] // todo:collator weight557 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCollators::get()))] // todo:collator weight
587 pub fn offboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {558 pub fn offboard(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
588 // leave_intent559 // leave_intent
589 let who = ensure_signed(origin)?;560 let who = ensure_signed(origin)?;
590 // todo:collator invulnerables and candidates should count against min candidates together561 /* todo:collator invulnerables and candidates should count against min candidates together
591 ensure!(562 ensure!(
592 Self::candidates().len() as u32 > T::MinCandidates::get(),563 Self::candidates().len() as u32 > T::MinCandidates::get(),
593 Error::<T>::TooFewCandidates564 Error::<T>::TooFewCandidates
594 );565 );*/
595 let current_count = Self::try_remove_candidate(&who)?;566 let current_count = Self::try_remove_candidate(&who)?;
596567
597 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into())568 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
598 }569 }
599570
600 /// Forfeit `origin`'s own license. The `LicenseBond` will be unreserved immediately.571 /// Forfeit `origin`'s own license. The `LicenseBond` will be unreserved immediately.
601 ///572 ///
602 /// This call is not available to `Invulnerable` collators.573 /// This call is not available to `Invulnerable` collators.
603 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))] // todo:collator weight574 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCollators::get()))] // todo:collator weight
604 pub fn release_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {575 pub fn release_license(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
605 // leave_intent576 // leave_intent
606 let who = ensure_signed(origin)?;577 let who = ensure_signed(origin)?;
607 // let current_count = Self::try_remove_candidate(&who, false)?;578
608 Self::try_release_license(&who, false)?;579 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;
609580
610 Ok(().into())581 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
611 }582 }
612583
613 /// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license.584 /// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license.
614 /// Note that the collator can only leave on session change.585 /// Note that the collator can only leave on session change.
615 /// The `LicenseBond` will be unreserved and returned immediately.586 /// The `LicenseBond` will be unreserved and returned immediately.
616 ///587 ///
617 /// This call is not available to `Invulnerable` collators.588 /// This call is not available to `Invulnerable` collators.
618 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))] // todo:collator weight589 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCollators::get()))] // todo:collator weight
619 pub fn force_release_license(590 pub fn force_revoke_license(
620 origin: OriginFor<T>,591 origin: OriginFor<T>,
621 who: T::AccountId,592 who: T::AccountId,
622 ) -> DispatchResultWithPostInfo {593 ) -> DispatchResultWithPostInfo {
623 // leave_intent594 // leave_intent
624 T::UpdateOrigin::ensure_origin(origin)?;595 T::UpdateOrigin::ensure_origin(origin)?;
625596
626 let current_count = Self::try_remove_candidate(&who)?;597 let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?;
627 Self::try_release_license(&who, false)?;
628598
629 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight599 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into()) // todo:collator weight
630 }600 }
636 T::PotId::get().into_account_truncating()606 T::PotId::get().into_account_truncating()
637 }607 }
608
609 fn try_remove_candidate_and_release_license(
610 who: &T::AccountId,
611 should_slash: bool,
612 ignore_if_not_candidate: bool,
613 ) -> Result<usize, DispatchError> {
614 let current_count = Self::try_remove_candidate(who);
615 let current_count = if ignore_if_not_candidate
616 && current_count == Err(Error::<T>::NotCandidate.into())
617 {
618 <Candidates<T>>::decode_len().unwrap_or_default()
619 } else {
620 current_count?
621 };
622 Self::try_release_license(who, should_slash)?;
623 Ok(current_count)
624 }
638625
639 /// Removes a candidate from the collator pool for the next session if they exist.626 /// Removes a candidate from the collator pool for the next session if they exist.
640 fn try_remove_candidate(who: &T::AccountId) -> Result<usize, DispatchError> {627 fn try_remove_candidate(who: &T::AccountId) -> Result<usize, DispatchError> {
657 /// Removes a candidate if they exist and sends them back their deposit, optionally slashed.644 /// Removes a candidate if they exist and sends them back their deposit, optionally slashed.
658 fn try_release_license(who: &T::AccountId, should_slash: bool) -> DispatchResult {645 fn try_release_license(who: &T::AccountId, should_slash: bool) -> DispatchResult {
659 let mut deposit_returned = BalanceOf::<T>::default();646 let mut deposit_returned = BalanceOf::<T>::default();
660 Licenses::<T>::try_mutate_exists(&who, |deposit| -> DispatchResult {647 Licenses::<T>::try_mutate_exists(who, |deposit| -> DispatchResult {
661 if let Some(deposit) = deposit.take() {648 if let Some(deposit) = deposit.take() {
662 if should_slash {649 if should_slash {
663 let slashed = T::SlashRatio::get() * deposit;650 let slashed = T::SlashRatio::get() * deposit;
690 ///677 ///
691 /// This is done on the fly, as frequent as we are told to do so, as the session manager.678 /// This is done on the fly, as frequent as we are told to do so, as the session manager.
692 pub fn assemble_collators(679 pub fn assemble_collators(
693 candidates: BoundedVec<T::AccountId, T::MaxCandidates>,680 candidates: BoundedVec<T::AccountId, T::MaxCollators>,
694 ) -> Vec<T::AccountId> {681 ) -> Vec<T::AccountId> {
695 let mut collators = Self::invulnerables().to_vec();682 let mut collators = Self::invulnerables().to_vec();
696 collators.extend(candidates);683 collators.extend(candidates);
700 /// Kicks out candidates that did not produce a block in the kick threshold687 /// Kicks out candidates that did not produce a block in the kick threshold
701 /// and **confiscates** their deposits to the treasury.688 /// and **confiscates** their deposits to the treasury.
702 pub fn kick_stale_candidates(689 pub fn kick_stale_candidates(
703 candidates: BoundedVec<T::AccountId, T::MaxCandidates>, //LicenseInfo<T::AccountId, BalanceOf<T>>690 candidates: BoundedVec<T::AccountId, T::MaxCollators>, //LicenseInfo<T::AccountId, BalanceOf<T>>
704 ) -> BoundedVec<T::AccountId, T::MaxCandidates> {691 ) -> BoundedVec<T::AccountId, T::MaxCollators> {
705 let now = frame_system::Pallet::<T>::block_number();692 let now = frame_system::Pallet::<T>::block_number();
706 let kick_threshold = Self::kick_threshold();693 let kick_threshold = Self::kick_threshold();
707 candidates694 candidates
708 .into_iter()695 .into_iter()
709 .filter_map(|c| {696 .filter_map(|c| {
710 let last_block = <LastAuthoredBlock<T>>::get(c.clone());697 let last_block = <LastAuthoredBlock<T>>::get(c.clone());
711 let since_last = now.saturating_sub(last_block);698 let since_last = now.saturating_sub(last_block);
712 if since_last < kick_threshold ||699 if since_last < kick_threshold {
713 Self::candidates().len() as u32 <= T::MinCandidates::get()
714 {
715 Some(c)700 Some(c)
716 } else {701 } else {
717 let outcome = Self::try_remove_candidate(&c);
718 if let Err(why) = outcome {
719 log::warn!("Failed to remove candidate {:?}", why);
720 debug_assert!(false, "failed to remove candidate {:?}", why);
721 return None;
722 }
723 let outcome = Self::try_release_license(&c, true);702 let outcome = Self::try_remove_candidate_and_release_license(&c, true, false);
724 if let Err(why) = outcome {703 if let Err(why) = outcome {
725 log::warn!("Failed to release license {:?}", why);704 log::warn!("Failed to kick collator and release license {:?}", why);
726 debug_assert!(false, "failed to release license {:?}", why);705 debug_assert!(false, "failed to kick collator and release license {why:?}");
727 }706 }
728 None707 None
729 }708 }
modifiedpallets/collator-selection/src/mock.rsdiffbeforeafterboth
206206
207parameter_types! {207parameter_types! {
208 pub const PotId: PalletId = PalletId(*b"PotStake");208 pub const PotId: PalletId = PalletId(*b"PotStake");
209 pub const MaxCandidates: u32 = 20;209 pub const MaxCollators: u32 = 20;
210 pub const MaxInvulnerables: u32 = 20;
211 pub const MinCandidates: u32 = 1;
212 pub const MaxAuthorities: u32 = 100_000;210 pub const MaxAuthorities: u32 = 100_000;
213 pub const SlashRatio: Perbill = Perbill::one();211 pub const SlashRatio: Perbill = Perbill::one();
214}212}
230 type Currency = Balances;228 type Currency = Balances;
231 type UpdateOrigin = EnsureSignedBy<RootAccount, u64>;229 type UpdateOrigin = EnsureSignedBy<RootAccount, u64>;
232 type PotId = PotId;230 type PotId = PotId;
233 type MaxCandidates = MaxCandidates;231 type MaxCollators = MaxCollators;
234 type MinCandidates = MinCandidates;
235 type MaxInvulnerables = MaxInvulnerables;
236 // type KickThreshold = Period;232 // type KickThreshold = Period;
237 type SlashRatio = SlashRatio;233 type SlashRatio = SlashRatio;
238 type TreasuryAccountId = ();234 type TreasuryAccountId = ();
263 })259 })
264 .collect::<Vec<_>>();260 .collect::<Vec<_>>();
265 let collator_selection = collator_selection::GenesisConfig::<Test> {261 let collator_selection = collator_selection::GenesisConfig::<Test> {
266 desired_candidates: 2,262 desired_collators: 5,
267 license_bond: 10,263 license_bond: 10,
268 kick_threshold: 1,264 kick_threshold: 10,
269 invulnerables,265 invulnerables,
270 };266 };
271 let session = pallet_session::GenesisConfig::<Test> { keys };267 let session = pallet_session::GenesisConfig::<Test> { keys };
modifiedpallets/collator-selection/src/tests.rsdiffbeforeafterboth
31// limitations under the License.31// limitations under the License.
3232
33use crate as collator_selection;33use crate as collator_selection;
34use crate::{mock::*, LicenseInfo, Error};34use crate::{mock::*, Error};
35use frame_support::{35use frame_support::{
36 assert_noop, assert_ok,36 assert_noop, assert_ok,
37 traits::{Currency, GenesisBuild, OnInitialize},37 traits::{Currency, GenesisBuild, OnInitialize},
38};38};
39use pallet_balances::Error as BalancesError;39use pallet_balances::Error as BalancesError;
40use sp_runtime::traits::BadOrigin;40use sp_runtime::traits::BadOrigin;
4141
42fn get_license_and_onboard(account_id: <Test as frame_system::Config>::AccountId) {
43 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(
44 account_id
45 )));
46 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(
47 account_id
48 )));
49}
50
42#[test]51#[test]
43fn basic_setup_works() {52fn basic_setup_works() {
44 new_test_ext().execute_with(|| {53 new_test_ext().execute_with(|| {
45 assert_eq!(CollatorSelection::desired_candidates(), 2);54 assert_eq!(CollatorSelection::desired_collators(), 5);
46 assert_eq!(CollatorSelection::license_bond(), 10);55 assert_eq!(CollatorSelection::license_bond(), 10);
4756
48 assert!(CollatorSelection::candidates().is_empty());57 assert!(CollatorSelection::candidates().is_empty());
51}60}
5261
53// todo:collator add more tests later62// todo:collator add more tests later
63// invulnerable after onboard + invulnerables can bypass desired_candidates
5464
55#[test]65#[test]
56fn it_should_add_invulnerables() {66fn it_should_add_invulnerables() {
112}122}
113123
114#[test]124#[test]
115fn set_desired_candidates_works() {125fn set_desired_collators_works() {
116 new_test_ext().execute_with(|| {126 new_test_ext().execute_with(|| {
117 // given127 // given
118 assert_eq!(CollatorSelection::desired_candidates(), 2);128 assert_eq!(CollatorSelection::desired_collators(), 5);
119129
120 // can set130 // can set
121 assert_ok!(CollatorSelection::set_desired_candidates(131 assert_ok!(CollatorSelection::set_desired_collators(
122 RuntimeOrigin::signed(RootAccount::get()),132 RuntimeOrigin::signed(RootAccount::get()),
123 7133 7
124 ));134 ));
125 assert_eq!(CollatorSelection::desired_candidates(), 7);135 assert_eq!(CollatorSelection::desired_collators(), 7);
126136
127 // rejects bad origin137 // rejects bad origin
128 assert_noop!(138 assert_noop!(
129 CollatorSelection::set_desired_candidates(RuntimeOrigin::signed(1), 8),139 CollatorSelection::set_desired_collators(RuntimeOrigin::signed(1), 8),
130 BadOrigin140 BadOrigin
131 );141 );
132 });142 });
154}164}
155165
156#[test]166#[test]
157fn cannot_register_candidate_if_too_many() {167fn cannot_onboard_candidate_with_no_license() {
158 new_test_ext().execute_with(|| {168 new_test_ext().execute_with(|| {
159 // reset desired candidates:169 // can't onboard a candidate who did not get a license.
170 assert_noop!(
171 CollatorSelection::onboard(RuntimeOrigin::signed(3)),
160 <crate::DesiredCandidates<Test>>::put(0);172 Error::<Test>::NoLicense,
173 );
161174
175 // but give it a license and welcome aboard.
176 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
177 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(3)));
178 })
179}
180
181#[test]
182fn cannot_onboard_candidate_if_too_many() {
183 new_test_ext().execute_with(|| {
184 // reset desired candidates
185 <crate::DesiredCollators<Test>>::put(0);
186
187 // can still get a license.
188 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));
189
162 // can't accept anyone anymore.190 // can't accept anyone anymore.
163 assert_noop!(191 assert_noop!(
164 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)),192 CollatorSelection::onboard(RuntimeOrigin::signed(4)),
165 Error::<Test>::TooManyCandidates,193 Error::<Test>::TooManyCandidates,
166 );194 );
167195
168 // reset desired candidates:196 // reset desired candidates to invulnerables + 1
169 <crate::DesiredCandidates<Test>>::put(1);197 <crate::DesiredCollators<Test>>::put(3);
170 assert_ok!(CollatorSelection::register_as_candidate(198 assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(4)));
171 RuntimeOrigin::signed(4)
172 ));
173199
174 // but no more200 // but no more.
201 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(5)));
175 assert_noop!(202 assert_noop!(
176 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(5)),203 CollatorSelection::onboard(RuntimeOrigin::signed(5)),
177 Error::<Test>::TooManyCandidates,204 Error::<Test>::TooManyCandidates,
178 );205 );
179 })206 })
180}207}
181208
182#[test]209#[test]
183fn cannot_unregister_candidate_if_too_few() {210fn cannot_obtain_license_if_keys_not_registered() {
184 new_test_ext().execute_with(|| {211 new_test_ext().execute_with(|| {
185 // reset desired candidates:212 // can't 7 because keys not registered.
186 <crate::DesiredCandidates<Test>>::put(1);
187 assert_ok!(CollatorSelection::register_as_candidate(
188 RuntimeOrigin::signed(4)
189 ));
190
191 // can not remove too few
192 assert_noop!(213 assert_noop!(
193 CollatorSelection::leave_intent(RuntimeOrigin::signed(4)),214 CollatorSelection::get_license(RuntimeOrigin::signed(7)),
194 Error::<Test>::TooFewCandidates,215 Error::<Test>::ValidatorNotRegistered
195 );216 );
196 })217 })
197}218}
198219
199#[test]220#[test]
200fn cannot_register_as_candidate_if_invulnerable() {221fn cannot_obtain_license_if_poor() {
201 new_test_ext().execute_with(|| {222 new_test_ext().execute_with(|| {
202 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);223 assert_eq!(Balances::free_balance(&3), 100);
224 assert_eq!(Balances::free_balance(&33), 0);
203225
204 // can't 1 because it is invulnerable.226 // works
205 assert_noop!(227 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
206 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(1)),
207 Error::<Test>::AlreadyInvulnerable,
208 );
209 })
210}
211228
212#[test]
213fn cannot_register_as_candidate_if_keys_not_registered() {
214 new_test_ext().execute_with(|| {229 // poor
215 // can't 7 because keys not registered.
216 assert_noop!(230 assert_noop!(
217 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(7)),231 CollatorSelection::get_license(RuntimeOrigin::signed(33)),
218 Error::<Test>::ValidatorNotRegistered232 BalancesError::<Test>::InsufficientBalance,
219 );233 );
220 })234 });
221}235}
222236
223#[test]237#[test]
224fn cannot_register_dupe_candidate() {238fn cannot_onboard_dupe_candidate() {
225 new_test_ext().execute_with(|| {239 new_test_ext().execute_with(|| {
226 // can add 3 as candidate240 // can add 3 as candidate
227 assert_ok!(CollatorSelection::register_as_candidate(241 get_license_and_onboard(3);
228 RuntimeOrigin::signed(3)
229 ));
230 let addition = LicenseInfo {242 assert_eq!(CollatorSelection::licenses(3), 10);
231 who: 3,
232 deposit: 10,
233 };
234 assert_eq!(CollatorSelection::candidates(), vec![addition]);243 assert_eq!(CollatorSelection::candidates(), vec![3]);
235 assert_eq!(CollatorSelection::last_authored_block(3), 10);244 assert_eq!(CollatorSelection::last_authored_block(3), 10);
236 assert_eq!(Balances::free_balance(3), 90);245 assert_eq!(Balances::free_balance(3), 90);
237246
238 // but no more247 // but no more
239 assert_noop!(248 assert_noop!(
240 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(3)),249 CollatorSelection::get_license(RuntimeOrigin::signed(3)),
250 Error::<Test>::AlreadyHoldingLicense,
251 );
252 assert_noop!(
253 CollatorSelection::onboard(RuntimeOrigin::signed(3)),
241 Error::<Test>::AlreadyCandidate,254 Error::<Test>::AlreadyCandidate,
242 );255 );
243 })256 })
244}257}
245258
246#[test]259#[test]
247fn cannot_register_as_candidate_if_poor() {260fn becoming_candidate_works() {
248 new_test_ext().execute_with(|| {261 new_test_ext().execute_with(|| {
249 assert_eq!(Balances::free_balance(&3), 100);
250 assert_eq!(Balances::free_balance(&33), 0);
251
252 // works
253 assert_ok!(CollatorSelection::register_as_candidate(
254 RuntimeOrigin::signed(3)
255 ));
256
257 // poor
258 assert_noop!(
259 CollatorSelection::register_as_candidate(RuntimeOrigin::signed(33)),
260 BalancesError::<Test>::InsufficientBalance,
261 );
262 });
263}
264
265#[test]
266fn register_as_candidate_works() {
267 new_test_ext().execute_with(|| {
268 // given262 // given
269 assert_eq!(CollatorSelection::desired_candidates(), 2);263 assert_eq!(CollatorSelection::desired_collators(), 5);
270 assert_eq!(CollatorSelection::license_bond(), 10);264 assert_eq!(CollatorSelection::license_bond(), 10);
271 assert_eq!(CollatorSelection::candidates(), Vec::new());265 assert_eq!(CollatorSelection::candidates(), Vec::new());
272 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);266 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
275 assert_eq!(Balances::free_balance(&3), 100);269 assert_eq!(Balances::free_balance(&3), 100);
276 assert_eq!(Balances::free_balance(&4), 100);270 assert_eq!(Balances::free_balance(&4), 100);
277271
278 assert_ok!(CollatorSelection::register_as_candidate(272 get_license_and_onboard(3);
279 RuntimeOrigin::signed(3)
280 ));
281 assert_ok!(CollatorSelection::register_as_candidate(273 get_license_and_onboard(4);
282 RuntimeOrigin::signed(4)
283 ));
284274
285 assert_eq!(Balances::free_balance(&3), 90);275 assert_eq!(Balances::free_balance(&3), 90);
286 assert_eq!(Balances::free_balance(&4), 90);276 assert_eq!(Balances::free_balance(&4), 90);
290}280}
291281
292#[test]282#[test]
293fn leave_intent() {283fn cannot_become_candidate_if_invulnerable() {
294 new_test_ext().execute_with(|| {284 new_test_ext().execute_with(|| {
295 // register a candidate.285 assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]);
286
287 // can obtain a license even if is invulnerable.
296 assert_ok!(CollatorSelection::register_as_candidate(288 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(1)));
297 RuntimeOrigin::signed(3)289 // but cannot onboard
290 assert_noop!(
291 CollatorSelection::onboard(RuntimeOrigin::signed(1)),
292 Error::<Test>::AlreadyInvulnerable,
293 );
294
295 // get a license and then become invulnerable.
296 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
297 assert_ok!(CollatorSelection::add_invulnerable(
298 RuntimeOrigin::signed(RootAccount::get()),
299 3
298 ));300 ));
299 assert_eq!(Balances::free_balance(3), 90);301 assert_noop!(
302 CollatorSelection::onboard(RuntimeOrigin::signed(3)),
303 Error::<Test>::AlreadyInvulnerable,
304 );
305 })
306}
300307
308#[test]
309fn can_become_invulnerable_if_candidate() {
301 // register too so can leave above min candidates310 new_test_ext().execute_with(|| {
311 // become a candidate and then become invulnerable.
312 get_license_and_onboard(3);
313 assert_eq!(CollatorSelection::candidates(), vec![3]);
314
302 assert_ok!(CollatorSelection::register_as_candidate(315 assert_ok!(CollatorSelection::add_invulnerable(
303 RuntimeOrigin::signed(5)316 RuntimeOrigin::signed(RootAccount::get()),
317 3
304 ));318 ));
305 assert_eq!(Balances::free_balance(5), 90);319 // should exclude from candidates, but not revoke the license
320 assert_eq!(CollatorSelection::candidates(), vec![]);
321 assert_eq!(CollatorSelection::licenses(3), 10);
322 assert_eq!(Balances::free_balance(3), 90);
323 });
324}
306325
326#[test]
327fn offboard() {
307 // cannot leave if not candidate.328 new_test_ext().execute_with(|| {
329 // register a candidate.
330 get_license_and_onboard(3);
331 assert_eq!(Balances::free_balance(3), 90);
332
333 // cannot leave if holds license but not yet candidate.
334 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4)));
308 assert_noop!(335 assert_noop!(
309 CollatorSelection::leave_intent(RuntimeOrigin::signed(4)),336 CollatorSelection::offboard(RuntimeOrigin::signed(4)),
310 Error::<Test>::NotCandidate337 Error::<Test>::NotCandidate
311 );338 );
339 // cannot leave if does not hold license.
340 assert_noop!(
341 CollatorSelection::offboard(RuntimeOrigin::signed(5)),
342 Error::<Test>::NotCandidate
343 );
312344
313 // bond is returned345 // bond is returned - only after releasing the license
346 assert_ok!(CollatorSelection::offboard(RuntimeOrigin::signed(3)));
347 assert_eq!(Balances::free_balance(3), 90);
348 assert_eq!(CollatorSelection::last_authored_block(3), 0);
314 assert_ok!(CollatorSelection::leave_intent(RuntimeOrigin::signed(3)));349 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));
315 assert_eq!(Balances::free_balance(3), 100);350 assert_eq!(Balances::free_balance(3), 100);
316 assert_eq!(CollatorSelection::last_authored_block(3), 0);
317 });351 });
318}352}
319353
320#[test]354#[test]
355fn release_license() {
356 new_test_ext().execute_with(|| {
357 // obtain a license to collate and reserve the bond.
358 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
359 assert_eq!(Balances::free_balance(3), 90);
360
361 // release the license and get the bond back.
362 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));
363 assert_eq!(Balances::free_balance(3), 100);
364
365 // register a candidate.
366 get_license_and_onboard(3);
367 assert_eq!(Balances::free_balance(3), 90);
368
369 // can release license even if onboarded.
370 assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3)));
371 assert_eq!(Balances::free_balance(3), 100);
372 assert_eq!(CollatorSelection::candidates(), vec![]);
373 });
374}
375
376#[test]
377fn force_revoke_license() {
378 new_test_ext().execute_with(|| {
379 // obtain a license to collate and reserve the bond.
380 assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3)));
381 assert_eq!(Balances::free_balance(3), 90);
382
383 // cannot execute the operation as non-root
384 assert_noop!(
385 CollatorSelection::force_revoke_license(RuntimeOrigin::signed(3), 3),
386 BadOrigin
387 );
388
389 // release the license and get the bond back.
390 assert_ok!(CollatorSelection::force_revoke_license(
391 RuntimeOrigin::signed(RootAccount::get()),
392 3
393 ));
394 assert_eq!(Balances::free_balance(3), 100);
395
396 // register a candidate.
397 get_license_and_onboard(3);
398 assert_eq!(Balances::free_balance(3), 90);
399
400 // can release license even if onboarded.
401 assert_ok!(CollatorSelection::force_revoke_license(
402 RuntimeOrigin::signed(RootAccount::get()),
403 3
404 ));
405 assert_eq!(Balances::free_balance(3), 100);
406 assert_eq!(CollatorSelection::candidates(), vec![]);
407 });
408}
409
410#[test]
321fn authorship_event_handler() {411fn authorship_event_handler() {
322 new_test_ext().execute_with(|| {412 new_test_ext().execute_with(|| {
323 // put 100 in the pot + 5 for ED413 // put 100 in the pot + 5 for ED
324 Balances::make_free_balance_be(&CollatorSelection::account_id(), 105);414 Balances::make_free_balance_be(&CollatorSelection::account_id(), 105);
325415
326 // 4 is the default author.416 // 4 is the default author.
327 assert_eq!(Balances::free_balance(4), 100);417 assert_eq!(Balances::free_balance(4), 100);
328 assert_ok!(CollatorSelection::register_as_candidate(418 get_license_and_onboard(4);
329 RuntimeOrigin::signed(4)
330 ));
331 // triggers `note_author`419 // triggers `note_author`
332 Authorship::on_initialize(1);420 Authorship::on_initialize(1);
333421
334 let collator = LicenseInfo {422 assert_eq!(CollatorSelection::candidates(), vec![4]);
335 who: 4,
336 deposit: 10,
337 };
338
339 assert_eq!(CollatorSelection::candidates(), vec![collator]);
340 assert_eq!(CollatorSelection::last_authored_block(4), 0);423 assert_eq!(CollatorSelection::last_authored_block(4), 0);
341424
342 // half of the pot goes to the collator who's the author (4 in tests).425 // half of the pot goes to the collator who's the author (4 in tests).
355 Balances::make_free_balance_be(&CollatorSelection::account_id(), 5);438 Balances::make_free_balance_be(&CollatorSelection::account_id(), 5);
356 // 4 is the default author.439 // 4 is the default author.
357 assert_eq!(Balances::free_balance(4), 100);440 assert_eq!(Balances::free_balance(4), 100);
358 assert_ok!(CollatorSelection::register_as_candidate(441 get_license_and_onboard(4);
359 RuntimeOrigin::signed(4)
360 ));
361 // triggers `note_author`442 // triggers `note_author`
362 Authorship::on_initialize(1);443 Authorship::on_initialize(1);
363444
364 let collator = LicenseInfo {445 assert_eq!(CollatorSelection::candidates(), vec![4]);
365 who: 4,
366 deposit: 10,
367 };
368
369 assert_eq!(CollatorSelection::candidates(), vec![collator]);
370 assert_eq!(CollatorSelection::last_authored_block(4), 0);446 assert_eq!(CollatorSelection::last_authored_block(4), 0);
371 // Nothing received447 // Nothing received
372 assert_eq!(Balances::free_balance(4), 90);448 assert_eq!(Balances::free_balance(4), 90);
389 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);465 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
390466
391 // add a new collator467 // add a new collator
392 assert_ok!(CollatorSelection::register_as_candidate(468 get_license_and_onboard(5);
393 RuntimeOrigin::signed(3)
394 ));
395469
396 // session won't see this.470 // session won't see this.
397 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);471 assert_eq!(SessionHandlerCollators::get(), vec![1, 2]);
410 initialize_to_block(20);484 initialize_to_block(20);
411 assert_eq!(SessionChangeBlock::get(), 20);485 assert_eq!(SessionChangeBlock::get(), 20);
412 // changed are now reflected to session handlers.486 // changed are now reflected to session handlers.
413 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3]);487 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5]);
414 });488 });
415}489}
416490
417#[test]491#[test]
418fn kick_mechanism() {492fn kick_mechanism() {
419 new_test_ext().execute_with(|| {493 new_test_ext().execute_with(|| {
420 // add a new collator494 // add a new collator
421 assert_ok!(CollatorSelection::register_as_candidate(495 get_license_and_onboard(3);
422 RuntimeOrigin::signed(3)
423 ));
424 assert_ok!(CollatorSelection::register_as_candidate(496 get_license_and_onboard(4);
425 RuntimeOrigin::signed(4)497
426 ));
427 initialize_to_block(10);498 initialize_to_block(10);
428 assert_eq!(CollatorSelection::candidates().len(), 2);499 assert_eq!(CollatorSelection::candidates().len(), 2);
500
429 initialize_to_block(20);501 initialize_to_block(20);
430 assert_eq!(SessionChangeBlock::get(), 20);502 assert_eq!(SessionChangeBlock::get(), 20);
431 // 4 authored this block, gets to stay 3 was kicked503 // 4 authored this block, gets to stay 3 was kicked
432 assert_eq!(CollatorSelection::candidates().len(), 1);504 assert_eq!(CollatorSelection::candidates().len(), 1);
433 // 3 will be kicked after 1 session delay505 // 3 will be kicked after 1 session delay
434 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);506 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);
435 let collator = LicenseInfo {507
436 who: 4,508 assert_eq!(CollatorSelection::candidates(), vec![4]);
437 deposit: 10,
438 };
439 assert_eq!(CollatorSelection::candidates(), vec![collator]);
440 assert_eq!(CollatorSelection::kick_threshold(), 1);509 assert_eq!(CollatorSelection::kick_threshold(), 10);
441 assert_eq!(CollatorSelection::last_authored_block(4), 20);510 assert_eq!(CollatorSelection::last_authored_block(4), 20);
511
442 initialize_to_block(30);512 initialize_to_block(30);
443 // 3 gets kicked after 1 session delay513 // 3 gets kicked after 1 session delay
444 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]);514 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]);
445 // kicked collator gets funds back515 // kicked collator gets their funds slashed, the deposit going to treasury
446 assert_eq!(Balances::free_balance(3), 100);
447 });
448}
449
450#[test]
451fn should_not_kick_mechanism_too_few() {
452 new_test_ext().execute_with(|| {
453 // add a new collator
454 assert_ok!(CollatorSelection::register_as_candidate(
455 RuntimeOrigin::signed(3)
456 ));
457 assert_ok!(CollatorSelection::register_as_candidate(
458 RuntimeOrigin::signed(5)
459 ));
460 initialize_to_block(10);
461 assert_eq!(CollatorSelection::candidates().len(), 2);
462 initialize_to_block(20);
463 assert_eq!(SessionChangeBlock::get(), 20);
464 // 4 authored this block, 5 gets to stay too few 3 was kicked
465 assert_eq!(CollatorSelection::candidates().len(), 1);
466 // 3 will be kicked after 1 session delay
467 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 5]);
468 let collator = LicenseInfo {
469 who: 5,
470 deposit: 10,
471 };
472 assert_eq!(CollatorSelection::candidates(), vec![collator]);
473 assert_eq!(CollatorSelection::last_authored_block(4), 20);
474 initialize_to_block(30);
475 // 3 gets kicked after 1 session delay
476 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5]);
477 // kicked collator gets funds back
478 assert_eq!(Balances::free_balance(3), 100);516 assert_eq!(Balances::free_balance(3), 90);
479 });517 });
480}518}
481519
489 let invulnerables = vec![1, 1];527 let invulnerables = vec![1, 1];
490528
491 let collator_selection = collator_selection::GenesisConfig::<Test> {529 let collator_selection = collator_selection::GenesisConfig::<Test> {
492 desired_candidates: 2,530 desired_collators: 5,
493 license_bond: 10,531 license_bond: 10,
494 kick_threshold: 1,532 kick_threshold: 10,
495 invulnerables,533 invulnerables,
496 };534 };
497 // collator selection must be initialized before session.535 // collator selection must be initialized before session.
modifiedpallets/collator-selection/src/weights.rsdiffbeforeafterboth
45// The weight info trait for `pallet_collator_selection`.45// The weight info trait for `pallet_collator_selection`.
46pub trait WeightInfo {46pub trait WeightInfo {
47 fn set_invulnerables(_b: u32) -> Weight;47 fn set_invulnerables(_b: u32) -> Weight;
48 fn set_desired_candidates() -> Weight;48 fn set_desired_collators() -> Weight;
49 fn set_license_bond() -> Weight;49 fn set_license_bond() -> Weight;
50 fn register_as_candidate(_c: u32) -> Weight;50 fn register_as_candidate(_c: u32) -> Weight;
51 fn leave_intent(_c: u32) -> Weight;51 fn leave_intent(_c: u32) -> Weight;
62 .saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64))62 .saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64))
63 .saturating_add(T::DbWeight::get().writes(1 as u64))63 .saturating_add(T::DbWeight::get().writes(1 as u64))
64 }64 }
65 fn set_desired_candidates() -> Weight {65 fn set_desired_collators() -> Weight {
66 Weight::from_ref_time(16_363_000 as u64).saturating_add(T::DbWeight::get().writes(1 as u64))66 Weight::from_ref_time(16_363_000 as u64).saturating_add(T::DbWeight::get().writes(1 as u64))
67 }67 }
68 fn set_license_bond() -> Weight {68 fn set_license_bond() -> Weight {
108 .saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64))108 .saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64))
109 .saturating_add(RocksDbWeight::get().writes(1 as u64))109 .saturating_add(RocksDbWeight::get().writes(1 as u64))
110 }110 }
111 fn set_desired_candidates() -> Weight {111 fn set_desired_collators() -> Weight {
112 Weight::from_ref_time(16_363_000 as u64)112 Weight::from_ref_time(16_363_000 as u64)
113 .saturating_add(RocksDbWeight::get().writes(1 as u64))113 .saturating_add(RocksDbWeight::get().writes(1 as u64))
114 }114 }
modifiedprimitives/common/src/constants.rsdiffbeforeafterboth
45/// Minimum balance required to create or keep an account open.45/// Minimum balance required to create or keep an account open.
46pub const EXISTENTIAL_DEPOSIT: u128 = 0;46pub const EXISTENTIAL_DEPOSIT: u128 = 0;
47/// Amount of Balance reserved for candidate registration.47/// Amount of Balance reserved for candidate registration.
48pub const GENESIS_LICENSE_BOND: u128 = EXISTENTIAL_DEPOSIT;48pub const GENESIS_LICENSE_BOND: u128 = 1_000_000_000_000 * UNIQUE;
49/// How long a periodic session lasts in blocks.49/// How long a periodic session lasts in blocks.
50pub const SESSION_LENGTH: BlockNumber = MINUTES;50pub const SESSION_LENGTH: BlockNumber = HOURS;
5151
52// Targeting 0.1 UNQ per transfer52// Targeting 0.1 UNQ per transfer
53pub const WEIGHT_TO_FEE_COEFF: u32 = /*<weight2fee>*/175_199_920/*</weight2fee>*/;53pub const WEIGHT_TO_FEE_COEFF: u32 = /*<weight2fee>*/175_199_920/*</weight2fee>*/;
modifiedruntime/common/config/pallets/collator_selection.rsdiffbeforeafterboth
5555
56parameter_types! {56parameter_types! {
57 pub const PotId: PalletId = PalletId(*b"PotStake");57 pub const PotId: PalletId = PalletId(*b"PotStake");
58 pub const MaxCandidates: u32 = 30; // todo:collator 30 collator slots - 3 planned invulnerables58 pub const MaxCollators: u32 = 10;
59 pub const MinCandidates: u32 = 1;
60 pub const MaxInvulnerables: u32 = 30;
61 pub const SlashRatio: Perbill = Perbill::from_percent(100);59 pub const SlashRatio: Perbill = Perbill::from_percent(100);
62}60}
6361
68 type UpdateOrigin = EnsureRoot<AccountId>;66 type UpdateOrigin = EnsureRoot<AccountId>;
69 type TreasuryAccountId = TreasuryAccountId;67 type TreasuryAccountId = TreasuryAccountId;
70 type PotId = PotId;68 type PotId = PotId;
71 type MaxCandidates = MaxCandidates;69 type MaxCollators = MaxCollators;
72 type MinCandidates = MinCandidates;
73 type MaxInvulnerables = MaxInvulnerables;
74 // todo:collator kick threshold should be in storage and configured only by root -- or rather UpdateOrigin70 // todo:collator kick threshold should be in storage and configured only by root -- or rather UpdateOrigin
75 type SlashRatio = SlashRatio;71 type SlashRatio = SlashRatio;
76 type ValidatorId = <Self as frame_system::Config>::AccountId;72 type ValidatorId = <Self as frame_system::Config>::AccountId;
modifiedruntime/common/mod.rsdiffbeforeafterboth
192 };192 };
193 use pallet_session::SessionManager;193 use pallet_session::SessionManager;
194 use up_common::constants::GENESIS_LICENSE_BOND;194 use up_common::constants::GENESIS_LICENSE_BOND;
195 use crate::config::pallets::collator_selection::MaxInvulnerables;195 use crate::config::pallets::collator_selection::MaxCollators;
196196
197 let mut weight = <Runtime as frame_system::Config>::DbWeight::get().reads(1);197 let mut weight = <Runtime as frame_system::Config>::DbWeight::get().reads(1);
198198
231 })231 })
232 .collect::<Vec<_>>();232 .collect::<Vec<_>>();
233233
234 let bounded_invulnerables = BoundedVec::<_, MaxInvulnerables>::try_from(234 let bounded_invulnerables = BoundedVec::<_, MaxCollators>::try_from(
235 invulnerables235 invulnerables
236 .iter()236 .iter()
237 .cloned()237 .cloned()
238 .map(|(acc, _)| acc)238 .map(|(acc, _)| acc)
239 .collect::<Vec<_>>(),239 .collect::<Vec<_>>(),
240 )240 )
241 .expect("Existing collators/invulnerables are more than MaxInvulnerables");241 .expect("Existing collators/invulnerables are more than MaxCollators");
242242
243 <pallet_collator_selection::Invulnerables<Runtime>>::put(bounded_invulnerables);243 <pallet_collator_selection::Invulnerables<Runtime>>::put(bounded_invulnerables);
244 <pallet_collator_selection::DesiredCandidates<Runtime>>::put(0);244 <pallet_collator_selection::DesiredCollators<Runtime>>::put(MaxCollators::get());
245 <pallet_collator_selection::LicenseBond<Runtime>>::put(GENESIS_LICENSE_BOND);245 <pallet_collator_selection::LicenseBond<Runtime>>::put(GENESIS_LICENSE_BOND);
246246
247 let keys = invulnerables247 let keys = invulnerables
modifiedruntime/common/tests/mod.rsdiffbeforeafterboth
18use sp_core::{Public, Pair};18use sp_core::{Public, Pair};
19use sp_std::vec;19use sp_std::vec;
20use up_common::types::AuraId;20use up_common::types::AuraId;
21use crate::{GenesisConfig, ParachainInfoConfig, AuraConfig};21use crate::{GenesisConfig, ParachainInfoConfig};
2222
23pub mod xcm;23pub mod xcm;
2424
28 .public()28 .public()
29}29}
3030
31#[cfg(feature = "collator-selection")]
32fn new_test_ext(para_id: u32) -> sp_io::TestExternalities {
33 use sp_core::{sr25519};
34 use sp_runtime::traits::{IdentifyAccount, Verify};
35 use crate::{AccountId, Signature, SessionKeys, CollatorSelectionConfig, SessionConfig};
36
37 type AccountPublic = <Signature as Verify>::Signer;
38
39 fn get_account_id_from_seed<TPublic: Public>(seed: &str) -> AccountId
40 where
41 AccountPublic: From<<TPublic::Pair as Pair>::Public>,
42 {
43 AccountPublic::from(get_from_seed::<TPublic>(seed)).into_account()
44 }
45
46 let accounts = vec!["Alice", "Bob"];
47 let keys = accounts
48 .iter()
49 .map(|&acc| {
50 let account_id = get_account_id_from_seed::<sr25519::Public>(acc);
51 (
52 account_id.clone(),
53 account_id,
54 SessionKeys {
55 aura: get_from_seed::<AuraId>(acc),
56 },
57 )
58 })
59 .collect::<Vec<_>>();
60 let invulnerables = accounts
61 .iter()
62 .map(|acc| get_account_id_from_seed::<sr25519::Public>(acc))
63 .collect::<Vec<_>>();
64
65 let cfg = GenesisConfig {
66 collator_selection: CollatorSelectionConfig {
67 desired_collators: 2,
68 license_bond: 10,
69 kick_threshold: 10,
70 invulnerables,
71 },
72 session: SessionConfig { keys },
73 parachain_info: ParachainInfoConfig {
74 parachain_id: para_id.into(),
75 },
76 ..GenesisConfig::default()
77 };
78
79 cfg.build_storage().unwrap().into()
80}
81
82#[cfg(not(feature = "collator-selection"))]
31fn new_test_ext(para_id: u32) -> sp_io::TestExternalities {83fn new_test_ext(para_id: u32) -> sp_io::TestExternalities {
84 use crate::AuraConfig;
85
32 let cfg = GenesisConfig {86 let cfg = GenesisConfig {
33 aura: AuraConfig {87 aura: AuraConfig {