git.delta.rocks / unique-network / refs/commits / 1e14fe80093b

difftreelog

feat(collator-selection) add+remove invulnerable methods + tests + miscellaneous changes before more refactoring

Fahrrader2022-12-21parent: #9b89504.patch.diff
in: master

11 files changed

modifiednode/cli/src/chain_spec.rsdiffbeforeafterboth
24use serde_json::map::Map;24use serde_json::map::Map;
2525
26use up_common::types::opaque::*;26use up_common::types::opaque::*;
27use up_common::constants::GENESIS_CANDIDACY_BOND;27use up_common::constants::{GENESIS_CANDIDACY_BOND, SESSION_LENGTH};
2828
29#[cfg(feature = "unique-runtime")]29#[cfg(feature = "unique-runtime")]
30pub use unique_runtime as default_runtime;30pub use unique_runtime as default_runtime;
197 .map(|(acc, _)| acc)197 .map(|(acc, _)| acc)
198 .collect(),198 .collect(),
199 candidacy_bond: GENESIS_CANDIDACY_BOND,199 candidacy_bond: GENESIS_CANDIDACY_BOND,
200 kick_threshold: SESSION_LENGTH,
200 ..Default::default()201 ..Default::default()
201 },202 },
202 session: SessionConfig {203 session: SessionConfig {
modifiedpallets/collator-selection/src/benchmarking.rsdiffbeforeafterboth
200 whitelist!(leaving);205 whitelist!(leaving);
201 }: _(RawOrigin::Signed(leaving.clone()))206 }: _(RawOrigin::Signed(leaving.clone()))
202 verify {207 verify {
208 // todo:collator verify these
203 assert_last_event::<T>(Event::CandidateRemoved{account_id: leaving}.into());209 assert_last_event::<T>(Event::CandidateRemoved{account_id: leaving, deposit_returned: bond / 2u32.into() }.into());
204 }210 }
205211
206 // worse case is paying a non-existing candidate account.212 // worse case is paying a non-existing candidate account.
modifiedpallets/collator-selection/src/lib.rsdiffbeforeafterboth
30// See the License for the specific language governing permissions and30// See the License for the specific language governing permissions and
31// limitations under the License.31// limitations under the License.
3232
33// todo:collator documentation
33//! Collator Selection pallet.34//! Collator Selection pallet.
34//!35//!
35//! A pallet to manage collators in a parachain.36//! A pallet to manage collators in a parachain.
109 };110 };
110 use frame_system::{pallet_prelude::*, Config as SystemConfig};111 use frame_system::{pallet_prelude::*, Config as SystemConfig};
111 use pallet_session::SessionManager;112 use pallet_session::SessionManager;
112 use sp_runtime::traits::Convert;113 use sp_runtime::{
114 Perbill,
115 traits::{One, Convert},
116 };
113 use sp_staking::SessionIndex;117 use sp_staking::SessionIndex;
114118
115 type BalanceOf<T> =119 type BalanceOf<T> =
136 /// Origin that can dictate updating parameters of this pallet.140 /// Origin that can dictate updating parameters of this pallet.
137 type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;141 type UpdateOrigin: EnsureOrigin<Self::RuntimeOrigin>;
142
143 /// Account Identifier that holds the chain's treasury.
144 type TreasuryAccountId: Get<Self::AccountId>;
138145
139 /// Account Identifier from which the internal Pot is generated.146 /// Account Identifier from which the internal Pot is generated.
140 type PotId: Get<PalletId>;147 type PotId: Get<PalletId>;
152 /// Maximum number of invulnerables. This is enforced in code.159 /// Maximum number of invulnerables. This is enforced in code.
153 type MaxInvulnerables: Get<u32>;160 type MaxInvulnerables: Get<u32>;
154161
155 // Will be kicked if block is not produced in threshold.162 /// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination.
156 type KickThreshold: Get<Self::BlockNumber>;163 type SlashRatio: Get<Perbill>;
157164
158 /// A stable ID for a validator.165 /// A stable ID for a validator.
159 type ValidatorId: Member + Parameter;166 type ValidatorId: Member + Parameter;
200 ValueQuery,207 ValueQuery,
201 >;208 >;
209
210 /// Collator will be kicked if it does not produce a block within the threshold (does not apply to invulnerables).
211 ///
212 /// Should be a multiple of session or things will get inconsistent. todo:collator reword?
213 #[pallet::storage]
214 #[pallet::getter(fn kick_threshold)]
215 pub type KickThreshold<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;
202216
203 /// Last block authored by collator.217 /// Last block authored by collator.
204 #[pallet::storage]218 #[pallet::storage]
224 pub struct GenesisConfig<T: Config> {238 pub struct GenesisConfig<T: Config> {
225 pub invulnerables: Vec<T::AccountId>,239 pub invulnerables: Vec<T::AccountId>,
226 pub candidacy_bond: BalanceOf<T>,240 pub candidacy_bond: BalanceOf<T>,
241 pub kick_threshold: T::BlockNumber,
227 pub desired_candidates: u32,242 pub desired_candidates: u32,
228 }243 }
229244
233 Self {248 Self {
234 invulnerables: Default::default(),249 invulnerables: Default::default(),
235 candidacy_bond: Default::default(),250 candidacy_bond: Default::default(),
251 kick_threshold: T::BlockNumber::one(),
236 desired_candidates: Default::default(),252 desired_candidates: Default::default(),
237 }253 }
238 }254 }
258276
259 <DesiredCandidates<T>>::put(&self.desired_candidates);277 <DesiredCandidates<T>>::put(&self.desired_candidates);
260 <CandidacyBond<T>>::put(&self.candidacy_bond);278 <CandidacyBond<T>>::put(&self.candidacy_bond);
279 <KickThreshold<T>>::put(&self.kick_threshold);
261 <Invulnerables<T>>::put(bounded_invulnerables);280 <Invulnerables<T>>::put(bounded_invulnerables);
262 }281 }
263 }282 }
264283
265 #[pallet::event]284 #[pallet::event]
266 #[pallet::generate_deposit(pub(super) fn deposit_event)]285 #[pallet::generate_deposit(pub(super) fn deposit_event)]
267 pub enum Event<T: Config> {286 pub enum Event<T: Config> {
268 NewInvulnerables { invulnerables: Vec<T::AccountId> },
269 NewDesiredCandidates { desired_candidates: u32 },287 NewDesiredCandidates {
288 desired_candidates: u32,
289 },
270 NewCandidacyBond { bond_amount: BalanceOf<T> },290 NewCandidacyBond {
291 bond_amount: BalanceOf<T>,
292 },
293 NewKickThreshold {
294 length_in_blocks: T::BlockNumber,
295 },
296 InvulnerableAdded {
297 invulnerable: T::AccountId,
298 },
299 InvulnerableRemoved {
300 invulnerable: T::AccountId,
301 },
271 CandidateAdded { account_id: T::AccountId, deposit: BalanceOf<T> },302 CandidateAdded {
303 account_id: T::AccountId,
304 deposit: BalanceOf<T>,
305 },
272 CandidateRemoved { account_id: T::AccountId },306 CandidateRemoved {
307 account_id: T::AccountId,
308 deposit_returned: BalanceOf<T>,
309 },
273 }310 }
274311
289 NotCandidate,326 NotCandidate,
290 /// Too many invulnerables327 /// Too many invulnerables
291 TooManyInvulnerables,328 TooManyInvulnerables,
329 /// Too few invulnerables
330 TooFewInvulnerables,
292 /// User is already an Invulnerable331 /// User is already an Invulnerable
293 AlreadyInvulnerable,332 AlreadyInvulnerable,
333 /// User is not an Invulnerable
334 NotInvulnerable,
294 /// Account has no associated validator ID335 /// Account has no associated validator ID
295 NoAssociatedValidatorId,336 NoAssociatedValidatorId,
296 /// Validator ID is not yet registered337 /// Validator ID is not yet registered
302343
303 #[pallet::call]344 #[pallet::call]
304 impl<T: Config> Pallet<T> {345 impl<T: Config> Pallet<T> {
305 /// Set the list of invulnerable (fixed) collators.346 /// Add a collator to the list of invulnerable (fixed) collators.
306 #[pallet::weight(T::WeightInfo::set_invulnerables(new.len() as u32))]347 #[pallet::weight(T::WeightInfo::set_invulnerables(1 as u32))] // todo:collator weight
307 pub fn set_invulnerables(348 pub fn add_invulnerable(
308 origin: OriginFor<T>,349 origin: OriginFor<T>,
309 new: Vec<T::AccountId>,350 new: T::AccountId,
310 ) -> DispatchResultWithPostInfo {351 ) -> DispatchResultWithPostInfo {
311 T::UpdateOrigin::ensure_origin(origin)?;352 T::UpdateOrigin::ensure_origin(origin)?;
312 let bounded_invulnerables = BoundedVec::<_, T::MaxInvulnerables>::try_from(new)
313 .map_err(|_| Error::<T>::TooManyInvulnerables)?;
314353
315 // check if the invulnerables have associated validator keys before they are set354 // check if the new invulnerable has associated validator keys before it is added
316 for account_id in bounded_invulnerables.iter() {
317 let validator_key = T::ValidatorIdOf::convert(account_id.clone())355 let validator_key = T::ValidatorIdOf::convert(new.clone())
318 .ok_or(Error::<T>::NoAssociatedValidatorId)?;356 .ok_or(Error::<T>::NoAssociatedValidatorId)?;
319 ensure!(357 ensure!(
320 T::ValidatorRegistration::is_registered(&validator_key),358 T::ValidatorRegistration::is_registered(&validator_key),
321 Error::<T>::ValidatorNotRegistered359 Error::<T>::ValidatorNotRegistered
322 );360 );
323 }361 // ensure!(!Self::invulnerables().contains(&new), Error::<T>::AlreadyInvulnerable);
362 if Self::invulnerables().contains(&new) {
363 return Ok(().into());
364 }
324365
325 <Invulnerables<T>>::put(&bounded_invulnerables);366 <Invulnerables<T>>::try_append(new.clone())
367 .map_err(|_| Error::<T>::TooManyInvulnerables)?;
326 Self::deposit_event(Event::NewInvulnerables {368 Self::deposit_event(Event::InvulnerableAdded { invulnerable: new });
327 invulnerables: bounded_invulnerables.to_vec(),
328 });
329 Ok(().into())369 Ok(().into())
330 }370 }
371
372 /// Remove a collator from the list of invulnerable (fixed) collators.
373 #[pallet::weight(T::WeightInfo::set_invulnerables(1))] // todo:collator weight
374 pub fn remove_invulnerable(
375 origin: OriginFor<T>,
376 who: T::AccountId,
377 ) -> DispatchResultWithPostInfo {
378 T::UpdateOrigin::ensure_origin(origin)?;
379
380 // let index = Self::invulnerables().into_iter().position(|r| r == who).ok_or(Error::<T>::NotInvulnerable)?;
381 <Invulnerables<T>>::try_mutate(|invulnerables| -> DispatchResult {
382 if invulnerables.len() <= 1 {
383 return Err(Error::<T>::TooFewInvulnerables.into());
384 }
385
386 let index = invulnerables
387 .into_iter()
388 .position(|r| *r == who)
389 .ok_or(Error::<T>::NotInvulnerable)?;
390 invulnerables.remove(index);
391 Ok(())
392 })?;
393 /*let bounded_invulnerables = BoundedVec::<_, T::MaxInvulnerables>::try_from(new)
394 .map_err(|_| Error::<T>::TooManyInvulnerables)?;
395
396 <Invulnerables<T>>::put(&bounded_invulnerables);*/
397 Self::deposit_event(Event::InvulnerableRemoved { invulnerable: who });
398 Ok(().into())
399 }
331400
332 /// Set the ideal number of collators (not including the invulnerables).401 /// Set the ideal number of collators (not including the invulnerables).
333 /// If lowering this number, then the number of running collators could be higher than this figure.402 /// If lowering this number, then the number of running collators could be higher than this figure.
344 }413 }
345 <DesiredCandidates<T>>::put(&max);414 <DesiredCandidates<T>>::put(&max);
346 Self::deposit_event(Event::NewDesiredCandidates { desired_candidates: max });415 Self::deposit_event(Event::NewDesiredCandidates {
416 desired_candidates: max,
417 });
347 Ok(().into())418 Ok(().into())
348 }419 }
359 Ok(().into())430 Ok(().into())
360 }431 }
432
433 /// Set the length of the kick threshold.
434 /// Note that if the length is not a multiple of the session period, it might get inconsistent.
435 #[pallet::weight(T::WeightInfo::set_candidacy_bond())] // todo:collator weight
436 pub fn set_kick_threshold(
437 origin: OriginFor<T>,
438 kick_threshold: T::BlockNumber,
439 ) -> DispatchResultWithPostInfo {
440 T::UpdateOrigin::ensure_origin(origin)?;
441 // todo:collator insert something to guarantee consistency?
442 <KickThreshold<T>>::put(kick_threshold);
443 Self::deposit_event(Event::NewKickThreshold {
444 length_in_blocks: kick_threshold,
445 });
446 Ok(().into())
447 }
361448
362 /// Register this account as a collator candidate. The account must (a) already have449 /// Register this account as a collator candidate. The account must (a) already have
363 /// registered session keys and (b) be able to reserve the `CandidacyBond`.450 /// registered session keys and (b) be able to reserve the `CandidacyBond`.
460 (length as u32) < Self::desired_candidates(),
461 Error::<T>::TooManyCandidates
462 );
463 // todo:collator really need it?
373 ensure!(!Self::invulnerables().contains(&who), Error::<T>::AlreadyInvulnerable);464 ensure!(
465 !Self::invulnerables().contains(&who),
466 Error::<T>::AlreadyInvulnerable
383 // First authored block is current block plus kick threshold to handle session delay477 // First authored block is current block plus kick threshold to handle session delay
384 let incoming = CandidateInfo { who: who.clone(), deposit };478 let incoming = CandidateInfo {
479 who: who.clone(),
480 deposit,
481 };
385482
386 let current_count =483 let current_count =
491 .map_err(|_| Error::<T>::TooManyCandidates)?;
393 <LastAuthoredBlock<T>>::insert(492 <LastAuthoredBlock<T>>::insert(
394 who.clone(),493 who.clone(),
395 frame_system::Pallet::<T>::block_number() + T::KickThreshold::get(),494 frame_system::Pallet::<T>::block_number() + Self::kick_threshold(),
396 );495 );
397 Ok(candidates.len())496 Ok(candidates.len())
398 }497 }
399 })?;498 })?;
400499
401 Self::deposit_event(Event::CandidateAdded { account_id: who, deposit });500 Self::deposit_event(Event::CandidateAdded {
501 account_id: who,
502 deposit,
503 });
402 Ok(Some(T::WeightInfo::register_as_candidate(current_count as u32)).into())504 Ok(Some(T::WeightInfo::register_as_candidate(current_count as u32)).into())
403 }505 }
411 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))]513 #[pallet::weight(T::WeightInfo::leave_intent(T::MaxCandidates::get()))]
412 pub fn leave_intent(origin: OriginFor<T>) -> DispatchResultWithPostInfo {514 pub fn leave_intent(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
413 let who = ensure_signed(origin)?;515 let who = ensure_signed(origin)?;
516 // todo:collator invulnerables and candidates should count against min candidates together
414 ensure!(517 ensure!(
415 Self::candidates().len() as u32 > T::MinCandidates::get(),518 Self::candidates().len() as u32 > T::MinCandidates::get(),
416 Error::<T>::TooFewCandidates519 Error::<T>::TooFewCandidates
417 );520 );
418 let current_count = Self::try_remove_candidate(&who)?;521 let current_count = Self::try_remove_candidate(&who, false)?;
419522
420 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into())523 Ok(Some(T::WeightInfo::leave_intent(current_count as u32)).into())
421 }524 }
427 T::PotId::get().into_account_truncating()530 T::PotId::get().into_account_truncating()
428 }531 }
429532
430 /// Removes a candidate if they exist and sends them back their deposit533 /// Removes a candidate if they exist and sends them back their deposit, optionally slashed.
431 fn try_remove_candidate(who: &T::AccountId) -> Result<usize, DispatchError> {534 fn try_remove_candidate(
535 who: &T::AccountId,
536 should_slash: bool,
537 ) -> Result<usize, DispatchError> {
538 let mut deposit_returned = BalanceOf::<T>::default();
432 let current_count =539 let current_count =
433 <Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {540 <Candidates<T>>::try_mutate(|candidates| -> Result<usize, DispatchError> {
434 let index = candidates541 let index = candidates
435 .iter()542 .iter()
436 .position(|candidate| candidate.who == *who)543 .position(|candidate| candidate.who == *who)
437 .ok_or(Error::<T>::NotCandidate)?;544 .ok_or(Error::<T>::NotCandidate)?;
438 let candidate = candidates.remove(index);545 let candidate = candidates.remove(index);
546 let deposit = candidate.deposit;
547
548 if should_slash {
549 let slashed = T::SlashRatio::get() * deposit;
550 let remaining = deposit - slashed;
551
552 let (imbalance, _) = T::Currency::slash_reserved(who, slashed);
553 //T::Currency::unreserve(who, remaining);
554 deposit_returned = remaining;
555
556 T::Currency::resolve_creating(&T::TreasuryAccountId::get(), imbalance);
557
558 // Self::deposit_event(Event::CandidateSlashed(who.clone()));
559 } else {
560 //T::Currency::unreserve(who, deposit);
561 deposit_returned = deposit;
562 }
563
439 T::Currency::unreserve(who, candidate.deposit);564 T::Currency::unreserve(who, deposit_returned);
565 // candidates.remove(index);
440 <LastAuthoredBlock<T>>::remove(who.clone());566 <LastAuthoredBlock<T>>::remove(who.clone());
441 Ok(candidates.len())567 Ok(candidates.len())
442 })?;568 })?;
443 Self::deposit_event(Event::CandidateRemoved { account_id: who.clone() });569 Self::deposit_event(Event::CandidateRemoved {
570 account_id: who.clone(),
571 deposit_returned,
572 });
444 Ok(current_count)573 Ok(current_count)
445 }574 }
456 }585 }
457586
458 /// Kicks out candidates that did not produce a block in the kick threshold587 /// Kicks out candidates that did not produce a block in the kick threshold
459 /// and refund their deposits.588 /// and **confiscates** their deposits to the treasury.
460 pub fn kick_stale_candidates(589 pub fn kick_stale_candidates(
461 candidates: BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,590 candidates: BoundedVec<CandidateInfo<T::AccountId, BalanceOf<T>>, T::MaxCandidates>,
462 ) -> BoundedVec<T::AccountId, T::MaxCandidates> {591 ) -> BoundedVec<T::AccountId, T::MaxCandidates> {
463 let now = frame_system::Pallet::<T>::block_number();592 let now = frame_system::Pallet::<T>::block_number();
464 let kick_threshold = T::KickThreshold::get();593 let kick_threshold = Self::kick_threshold();
465 candidates594 candidates
466 .into_iter()595 .into_iter()
467 .filter_map(|c| {596 .filter_map(|c| {
472 {601 {
473 Some(c.who)602 Some(c.who)
474 } else {603 } else {
475 let outcome = Self::try_remove_candidate(&c.who);604 let outcome = Self::try_remove_candidate(&c.who, true);
476 if let Err(why) = outcome {605 if let Err(why) = outcome {
477 log::warn!("Failed to remove candidate {:?}", why);606 log::warn!("Failed to remove candidate {:?}", why);
478 debug_assert!(false, "failed to remove candidate {:?}", why);607 debug_assert!(false, "failed to remove candidate {:?}", why);
modifiedpallets/collator-selection/src/mock.rsdiffbeforeafterboth
43use sp_runtime::{43use sp_runtime::{
44 testing::{Header, UintAuthorityId},44 testing::{Header, UintAuthorityId},
45 traits::{BlakeTwo256, IdentityLookup, OpaqueKeys},45 traits::{BlakeTwo256, IdentityLookup, OpaqueKeys},
46 RuntimeAppPublic,46 Perbill, RuntimeAppPublic,
47};47};
4848
49type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;49type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
210 pub const MaxInvulnerables: u32 = 20;210 pub const MaxInvulnerables: u32 = 20;
211 pub const MinCandidates: u32 = 1;211 pub const MinCandidates: u32 = 1;
212 pub const MaxAuthorities: u32 = 100_000;212 pub const MaxAuthorities: u32 = 100_000;
213 pub const SlashRatio: Perbill = Perbill::one();
213}214}
214215
215pub struct IsRegistered;216pub struct IsRegistered;
224}225}
225226
226impl Config for Test {227impl Config for Test {
228 // todo:collator mocks and stocks
227 type RuntimeEvent = RuntimeEvent;229 type RuntimeEvent = RuntimeEvent;
228 type Currency = Balances;230 type Currency = Balances;
229 type UpdateOrigin = EnsureSignedBy<RootAccount, u64>;231 type UpdateOrigin = EnsureSignedBy<RootAccount, u64>;
230 type PotId = PotId;232 type PotId = PotId;
231 type MaxCandidates = MaxCandidates;233 type MaxCandidates = MaxCandidates;
232 type MinCandidates = MinCandidates;234 type MinCandidates = MinCandidates;
233 type MaxInvulnerables = MaxInvulnerables;235 type MaxInvulnerables = MaxInvulnerables;
236 // type KickThreshold = Period;
234 type KickThreshold = Period;237 type SlashRatio = SlashRatio;
238 type TreasuryAccountId = ();
235 type ValidatorId = <Self as frame_system::Config>::AccountId;239 type ValidatorId = <Self as frame_system::Config>::AccountId;
236 type ValidatorIdOf = IdentityCollator;240 type ValidatorIdOf = IdentityCollator;
237 type ValidatorRegistration = IsRegistered;241 type ValidatorRegistration = IsRegistered;
246 let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)];252 let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)];
247 let keys = balances253 let keys = balances
248 .iter()254 .iter()
249 .map(|&(i, _)| (i, i, MockSessionKeys { aura: UintAuthorityId(i) }))255 .map(|&(i, _)| {
256 (
257 i,
258 i,
259 MockSessionKeys {
260 aura: UintAuthorityId(i),
261 },
262 )
263 })
250 .collect::<Vec<_>>();264 .collect::<Vec<_>>();
251 let collator_selection = collator_selection::GenesisConfig::<Test> {265 let collator_selection = collator_selection::GenesisConfig::<Test> {
252 desired_candidates: 2,266 desired_candidates: 2,
253 candidacy_bond: 10,267 candidacy_bond: 10,
268 kick_threshold: 1,
254 invulnerables,269 invulnerables,
255 };270 };
256 let session = pallet_session::GenesisConfig::<Test> { keys };271 let session = pallet_session::GenesisConfig::<Test> { keys };
modifiedpallets/collator-selection/src/tests.rsdiffbeforeafterboth
196 ));
188 let addition = CandidateInfo { who: 3, deposit: 10 };197 let addition = CandidateInfo {
198 who: 3,
199 deposit: 10,
200 };
189 assert_eq!(CollatorSelection::candidates(), vec![addition]);201 assert_eq!(CollatorSelection::candidates(), vec![addition]);
190 assert_eq!(CollatorSelection::last_authored_block(3), 10);202 assert_eq!(CollatorSelection::last_authored_block(3), 10);
276300
277 let collator = CandidateInfo { who: 4, deposit: 10 };301 let collator = CandidateInfo {
302 who: 4,
303 deposit: 10,
304 };
278305
279 assert_eq!(CollatorSelection::candidates(), vec![collator]);306 assert_eq!(CollatorSelection::candidates(), vec![collator]);
301330
302 let collator = CandidateInfo { who: 4, deposit: 10 };331 let collator = CandidateInfo {
332 who: 4,
333 deposit: 10,
334 };
303335
304 assert_eq!(CollatorSelection::candidates(), vec![collator]);336 assert_eq!(CollatorSelection::candidates(), vec![collator]);
363 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);401 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]);
364 let collator = CandidateInfo { who: 4, deposit: 10 };402 let collator = CandidateInfo {
403 who: 4,
404 deposit: 10,
405 };
365 assert_eq!(CollatorSelection::candidates(), vec![collator]);406 assert_eq!(CollatorSelection::candidates(), vec![collator]);
366 assert_eq!(CollatorSelection::last_authored_block(4), 20);407 assert_eq!(CollatorSelection::last_authored_block(4), 20);
388 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 5]);433 assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 5]);
389 let collator = CandidateInfo { who: 5, deposit: 10 };434 let collator = CandidateInfo {
435 who: 5,
436 deposit: 10,
437 };
390 assert_eq!(CollatorSelection::candidates(), vec![collator]);438 assert_eq!(CollatorSelection::candidates(), vec![collator]);
391 assert_eq!(CollatorSelection::last_authored_block(4), 20);439 assert_eq!(CollatorSelection::last_authored_block(4), 20);
modifiedpallets/collator-selection/src/weights.rsdiffbeforeafterboth
41};41};
42use sp_std::marker::PhantomData;42use sp_std::marker::PhantomData;
4343
44// todo:collator re-generate weights
44// The weight info trait for `pallet_collator_selection`.45// The weight info trait for `pallet_collator_selection`.
45pub trait WeightInfo {46pub trait WeightInfo {
46 fn set_invulnerables(_b: u32) -> Weight;47 fn set_invulnerables(_b: u32) -> Weight;
modifiedprimitives/common/src/constants.rsdiffbeforeafterboth
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_CANDIDACY_BOND: u128 = EXISTENTIAL_DEPOSIT;48pub const GENESIS_CANDIDACY_BOND: u128 = EXISTENTIAL_DEPOSIT;
49/// How long a periodic session lasts in blocks.
50pub const SESSION_LENGTH: BlockNumber = MINUTES;
4951
50// Targeting 0.1 UNQ per transfer52// Targeting 0.1 UNQ per transfer
51pub 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
18use frame_system::EnsureRoot;18use frame_system::EnsureRoot;
19use crate::{19use crate::{
20 AccountId, BlockNumber, Runtime, RuntimeEvent, Balances, Aura, Session, SessionKeys,20 AccountId, BlockNumber, Runtime, RuntimeEvent, Balances, Aura, Session, SessionKeys,
21 CollatorSelection,21 CollatorSelection, config::pallets::TreasuryAccountId,
22};22};
23use sp_runtime::Perbill;
23use up_common::constants::*;24use up_common::constants::*;
2425
25parameter_types! {26parameter_types! {
26 pub const SessionPeriod: BlockNumber = HOURS;27 pub const SessionPeriod: BlockNumber = SESSION_LENGTH;
27 pub const SessionOffset: BlockNumber = 0;28 pub const SessionOffset: BlockNumber = 0;
28}29}
2930
5455
55parameter_types! {56parameter_types! {
56 pub const PotId: PalletId = PalletId(*b"PotStake");57 pub const PotId: PalletId = PalletId(*b"PotStake");
57 pub const MaxCandidates: u32 = 1000;58 pub const MaxCandidates: u32 = 30; // todo:collator 30 collator slots - 3 planned invulnerables
58 pub const MinCandidates: u32 = 5;59 pub const MinCandidates: u32 = 1;
59 pub const MaxInvulnerables: u32 = 100;60 pub const MaxInvulnerables: u32 = 30;
61 pub const SlashRatio: Perbill = Perbill::from_percent(100);
60}62}
6163
62impl pallet_collator_selection::Config for Runtime {64impl pallet_collator_selection::Config for Runtime {
63 type RuntimeEvent = RuntimeEvent;65 type RuntimeEvent = RuntimeEvent;
64 type Currency = Balances;66 type Currency = Balances;
65 // We allow root only to execute privileged collator selection operations.67 // We allow root only to execute privileged collator selection operations.
66 type UpdateOrigin = EnsureRoot<AccountId>;68 type UpdateOrigin = EnsureRoot<AccountId>;
69 type TreasuryAccountId = TreasuryAccountId;
67 type PotId = PotId;70 type PotId = PotId;
68 type MaxCandidates = MaxCandidates;71 type MaxCandidates = MaxCandidates;
69 type MinCandidates = MinCandidates;72 type MinCandidates = MinCandidates;
70 type MaxInvulnerables = MaxInvulnerables;73 type MaxInvulnerables = MaxInvulnerables;
71 // todo:collator kick threshold should be in storage and configured only by root -- or rather UpdateOrigin74 // todo:collator kick threshold should be in storage and configured only by root -- or rather UpdateOrigin
72 // Should be a multiple of session or things will get inconsistent.
73 type KickThreshold = SessionPeriod;75 type SlashRatio = SlashRatio;
74 type ValidatorId = <Self as frame_system::Config>::AccountId;76 type ValidatorId = <Self as frame_system::Config>::AccountId;
75 type ValidatorIdOf = pallet_collator_selection::IdentityCollator;77 type ValidatorIdOf = pallet_collator_selection::IdentityCollator;
76 type ValidatorRegistration = Session;78 type ValidatorRegistration = Session;
modifiedruntime/common/runtime_apis.rsdiffbeforeafterboth
703 #[cfg(feature = "rmrk")]703 #[cfg(feature = "rmrk")]
704 list_benchmark!(list, extra, pallet_proxy_rmrk_equip, RmrkEquip);704 list_benchmark!(list, extra, pallet_proxy_rmrk_equip, RmrkEquip);
705
706 // todo:collator check benchmarks
707 #[cfg(feature = "collator-selection")]
708 list_benchmark!(list, extra, pallet_collator_selection, CollatorSelection);
705709
706 #[cfg(feature = "foreign-assets")]710 #[cfg(feature = "foreign-assets")]
707 list_benchmark!(list, extra, pallet_foreign_assets, ForeignAssets);711 list_benchmark!(list, extra, pallet_foreign_assets, ForeignAssets);
766 #[cfg(feature = "rmrk")]770 #[cfg(feature = "rmrk")]
767 add_benchmark!(params, batches, pallet_proxy_rmrk_equip, RmrkEquip);771 add_benchmark!(params, batches, pallet_proxy_rmrk_equip, RmrkEquip);
772
773 // todo:collator check benchmarks
774 #[cfg(feature = "collator-selection")]
775 add_benchmark!(params, batches, pallet_collator_selection, CollatorSelection);
768776
769 #[cfg(feature = "foreign-assets")]777 #[cfg(feature = "foreign-assets")]
770 add_benchmark!(params, batches, pallet_foreign_assets, ForeignAssets);778 add_benchmark!(params, batches, pallet_foreign_assets, ForeignAssets);
modifiedtests/src/collatorSelection.test.tsdiffbeforeafterboth
17import {IKeyringPair} from '@polkadot/types/types';17import {IKeyringPair} from '@polkadot/types/types';
18import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from './util';18import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from './util';
1919
20async function resetInvulnerables() {
21 await usingPlaygrounds(async (helper, privateKey) => {
20// todo Most preferable to launch this test in parallel somehow -- or change the session period (1 hr).22 const superuser = await privateKey('//Alice');
23 const alice = await privateKey('//Alice');
24 const bob = await privateKey('//Bob');
25 const invulnerables = await helper.collatorSelection.getInvulnerables();
26 if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) {
27 console.warn('Alice and Bob are not the invulnerables! Reinstating them back. '
28 + 'Current invulnerables\' size: ' + invulnerables.length);
29
30 let nonce = await helper.chain.getNonce(alice.address);
31 await Promise.all([
32 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}),
33 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}),
34 ]);
35
36 nonce = await helper.chain.getNonce(alice.address);
37 await Promise.all(invulnerables.map((invulnerable: any) => {
38 if (invulnerable == alice.address || invulnerable == bob.address) return new Promise<void>(res => res());
39 return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});
40 }));
41 }
42 });
43}
44
45// todo:collator Most preferable to launch this test in parallel somehow -- or change the session period (1 hr).
46// + 18 tests: 5 (1+4) on session change
21describe('Integration Test: Dynamic shuffling of collators', () => {47describe('Integration Test: Collator Selection', () => {
22 let superuser: IKeyringPair;48 let superuser: IKeyringPair;
2349
24 // These are the default invulnerables, and should return to be invulnerables after this suite.50 // These are the default invulnerables, and should return to be invulnerables after this suite.
25 let aliceAddress: string;51 let alice: IKeyringPair;
26 let bobAddress: string;52 let bob: IKeyringPair;
2753
28 let charlie: IKeyringPair;54 let charlie: IKeyringPair;
29 let dave: IKeyringPair;55 let dave: IKeyringPair;
30 //let eve: IKeyringPair;56 //let eve: IKeyringPair;
3157
32 before(async function() {58 before(async function() {
33 await usingPlaygrounds(async (helper, privateKey) => {59 await usingPlaygrounds(async (helper, privateKey) => {
34 requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]);60 requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]);
3561
62 //todo:collator
36 //const donor = await privateKey({filename: __filename});63 //const donor = await privateKey({filename: __filename});
37 //[charlie, dave] = await helper.arrange.createAccounts([100n, 100n], donor);64 //[charlie, dave] = await helper.arrange.createAccounts([100n, 100n], donor);
65 alice = await privateKey('//Alice');
66 bob = await privateKey('//Bob');
38 charlie = await privateKey('//Charlie');67 charlie = await privateKey('//Charlie');
39 dave = await privateKey('//Dave');68 dave = await privateKey('//Dave');
4069
41 superuser = await privateKey('//Alice');70 superuser = await privateKey('//Alice');
42 aliceAddress = (await privateKey('//Alice')).address;71 });
43 bobAddress = (await privateKey('//Bob')).address;72 });
4473
45 expect((await helper.executeExtrinsic(charlie, 'api.tx.session.setKeys', [74 describe('Dynamic shuffling of collators', () => {
46 '0x' + Buffer.from(charlie.addressRaw).toString('hex'),75 before(async function() {
47 '0x0',76 await usingPlaygrounds(async (helper) => {
48 ])).status.toLowerCase()).to.be.equal('success');77 expect((await helper.collatorSelection.setOwnKeys(charlie))
78 .status.toLowerCase()).to.be.equal('success');
79 expect((await helper.collatorSelection.setOwnKeys(dave))
80 .status.toLowerCase()).to.be.equal('success');
81
82 // todo:collator check necessity + add RPC for invulnerables / just improve in general
83 // validators = await helper.callRpc('api.query.session.validators');
84 const invulnerables = await helper.callRpc('api.query.collatorSelection.invulnerables');
85 if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) {
86 console.warn('Alice and Bob are not the invulnerables! Reinstating them back. '
87 + 'Current invulnerables\' size: ' + invulnerables.length);
88
89 await Promise.all([
90 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: 0}),
91 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: 1}),
92 ]);
93
94 let nonce = 0;
95 await Promise.all(invulnerables.map((invulnerable: any) => {
96 if (invulnerable == alice.address || invulnerable == bob.address) return new Promise((res) => res);
97 return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++});
98 }));
99 }
100 });
101 });
102
103 itSub('Change invulnerables and make sure they start producing blocks', async ({helper}) => {
104 await expect(Promise.all([
105 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [charlie.address], true, {nonce: 0}),
106 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [dave.address], true, {nonce: 1}),
107 ])).to.be.fulfilled;
108
109 await expect(Promise.all([
110 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [alice.address], true, {nonce: 0}),
111 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [bob.address], true, {nonce: 1}),
112 ])).to.be.fulfilled;
113
114 const newInvulnerables = await helper.callRpc('api.query.collatorSelection.invulnerables');
115 expect(newInvulnerables).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);
116
117 const expectedSessionIndex = (await helper.callRpc('api.query.session.currentIndex')).toNumber() + 2;
118 let currentSessionIndex = -1;
119 console.log('Waiting for the session after the next.'
120 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');
121
122 while (currentSessionIndex < expectedSessionIndex) {
123 // eslint-disable-next-line no-async-promise-executor
124 currentSessionIndex = await expect(helper.wait.withTimeout(new Promise(async (resolve) => {
125 //todo:collator
126 console.log('starting wait...');
127 console.time('ein');
128 await helper.wait.newBlocks(1);
129 console.timeLog('ein');
130 const res = (await helper.callRpc('api.query.session.currentIndex')).toNumber();
131 console.timeEnd('ein');
132 resolve(res);
133 }), 24000, 'The chain has stopped producing blocks!')).to.be.fulfilled;
134 }
135
136 const newValidators = await helper.callRpc('api.query.session.validators');
137 expect(newValidators).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);
138
139 const lastBlockNumber = await helper.chain.getLatestBlockNumber();
140 await helper.wait.newBlocks(1);
141 const lastCharlieBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [charlie.address])).toNumber();
142 const lastDaveBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [dave.address])).toNumber();
143 expect(lastCharlieBlock >= lastBlockNumber || lastDaveBlock >= lastBlockNumber).to.be.true;
144 });
145
146 // todo:collator keyless invulnerables? will hang, so, a breaking test, eh
147 // register candidate without sudos and the like
148
149 after(async () => {
150 await usingPlaygrounds(async (helper) => {
151 if (helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) return;
49152
50 expect((await helper.executeExtrinsic(dave, 'api.tx.session.setKeys', [153 await Promise.all([
154 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: 0}),
51 '0x' + Buffer.from(dave.addressRaw).toString('hex'),155 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: 1}),
52 '0x0',156 ]);
53 ])).status.toLowerCase()).to.be.equal('success');157
54158 await Promise.all([
55 const validators = await helper.callRpc('api.query.session.validators');159 await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [charlie.address], true, {nonce: 0}),
56 expect(validators).to.not.contain(charlie.address).and.not.contain(dave.address);160 await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [dave.address], true, {nonce: 1}),
161 ]);
162 });
57 });163 });
58 });164 });
59165
60 itSub('Change invulnerables and make sure they start producing blocks', async ({helper}) => {166 // todo:collator make sure that there is enough session time for a set of tests
167 // 28 non-functioning collators, teehee.
61168
62 const tx = helper.constructApiCall('api.tx.collatorSelection.setInvulnerables', [[169 describe('Addition and removal of invulnerables', () => {
63 charlie.address,170 before(async function() {
64 dave.address,171 await resetInvulnerables();
65 ]]);
66 await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [tx])).to.be.fulfilled;172 });
67173
68 const newInvulnerables = await helper.callRpc('api.query.collatorSelection.invulnerables');174 describe('Positive', () => {
175 itSub('Adds an invulnerable', async ({helper}) => {
69 expect(newInvulnerables).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);176 const [account] = await helper.arrange.createAccounts([10n], superuser);
177 const invulnerables = await helper.collatorSelection.getInvulnerables();
70178
71 const expectedSessionIndex = (await helper.callRpc('api.query.session.currentIndex')).toNumber() + 2;179 await helper.collatorSelection.setOwnKeys(account);
72 let currentSessionIndex = -1;180 await helper.getSudo().collatorSelection.addInvulnerable(superuser, account.address);
73 console.log('Waiting for the session after the next.' 181
74 + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.');182 const newInvulnerables = await helper.collatorSelection.getInvulnerables();
183 expect(invulnerables.concat(account.address)).to.have.all.members(newInvulnerables);
184 });
75185
76 while (currentSessionIndex < expectedSessionIndex) {186 itSub('Removes an invulnerable', async ({helper}) => {
77 // eslint-disable-next-line no-async-promise-executor
78 currentSessionIndex = await expect(helper.wait.withTimeout(new Promise(async (resolve) => {
79 await helper.wait.newBlocks(1);187 const invulnerables = await helper.collatorSelection.getInvulnerables();
80 const res = (await helper.callRpc('api.query.session.currentIndex')).toNumber();
81 resolve(res);188 const lastInvulnerable = invulnerables.pop();
82 }), 24000, 'The chain has stopped producing blocks!')).to.be.fulfilled;
83 }
84189
85 const newValidators = await helper.callRpc('api.query.session.validators');190 await helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable);
191 const newInvulnerables = await helper.collatorSelection.getInvulnerables();
86 expect(newValidators).to.contain(charlie.address).and.contain(dave.address).and.be.length(2);192 // invulnerables had its last element removed, so they should be equal
193 expect(newInvulnerables).to.have.all.members(invulnerables);
194 });
195 });
87196
88 const lastBlockNumber = await helper.chain.getLatestBlockNumber();197 describe('Negative', () => {
89 await helper.wait.newBlocks(1);198 itSub('Does not duplicate an invulnerable', async ({helper}) => {
90 const lastCharlieBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [charlie.address])).toNumber();199 const invulnerables = await helper.collatorSelection.getInvulnerables();
91 const lastDaveBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [dave.address])).toNumber();200 // adding an already invulnerable should not fail, but should not duplicate it either
92 expect(lastCharlieBlock >= lastBlockNumber || lastDaveBlock >= lastBlockNumber).to.be.true;201 await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, invulnerables[0]))
93 });202 .to.be.fulfilled;
203 const newInvulnerables = await helper.collatorSelection.getInvulnerables();
204 expect(newInvulnerables).to.have.all.members(invulnerables);
205 });
206
207 itSub('Cannot allow invulnerables to be empty', async ({helper}) => {
208 const invulnerables = await helper.collatorSelection.getInvulnerables();
209 const lastInvulnerable = invulnerables.pop();
210
211 let nonce = await helper.chain.getNonce(superuser.address);
212 await Promise.all(invulnerables.map((i: any) =>
213 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i], true, {nonce: nonce++})));
214
215 await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable))
216 .to.be.rejected;//todo:collator With(/collatorSelection.TooFewInvulnerables/);
217
218 const newInvulnerables = await helper.collatorSelection.getInvulnerables();
219 expect(newInvulnerables).to.be.deep.equal([lastInvulnerable]);
220
221 // restore the invulnerables to the previous state
222 nonce = await helper.chain.getNonce(superuser.address);
223 await Promise.all(invulnerables.map((i: any) =>
224 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i], true, {nonce: nonce++})));
225 });
226
227 itSub('Cannot have too many invulnerables', async ({helper}) => {
228 const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length;
229 const invulnerablesUntilLimit = 30 - invulnerablesLength;
230 const newInvulnerables = await helper.arrange.createAccounts(Array(invulnerablesUntilLimit).fill(10n), superuser);
231 const [lastInvulnerable] = await helper.arrange.createAccounts([10n], superuser);
232
233 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>
234 helper.collatorSelection.setOwnKeys(i)));
235 await helper.collatorSelection.setOwnKeys(lastInvulnerable);
236
237 let nonce = await helper.chain.getNonce(superuser.address);
238 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>
239 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i.address], true, {nonce: nonce++})));
240
241 await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, lastInvulnerable.address))
242 .to.be.rejected; // todo:collator With(/collatorSelection.TooManyInvulnerables/);
243
244 // restore the invulnerables to the previous state
245 nonce = await helper.chain.getNonce(superuser.address);
246 await Promise.all(newInvulnerables.map((i: IKeyringPair) =>
247 helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i.address], true, {nonce: nonce++})));
248 });
249
250 itSub('Forbids a non-sudo to add an invulnerable', async ({helper}) => {
251 const [account] = await helper.arrange.createAccounts([10n], bob);
252 const invulnerables = await helper.collatorSelection.getInvulnerables();
253
254 await helper.collatorSelection.setOwnKeys(account);
255 await expect(helper.collatorSelection.addInvulnerable(bob, account.address))
256 .to.be.rejectedWith(/BadOrigin/);
94257
95 after(async () => {258 const newInvulnerables = await helper.collatorSelection.getInvulnerables();
96 await usingPlaygrounds(async (helper) => {259 expect(newInvulnerables).to.be.members(invulnerables);
97 if (helper.fetchMissingPalletNames([Pallets.AppPromotion]).length != 0) return;260 });
98261
99 const tx = helper.constructApiCall('api.tx.collatorSelection.setInvulnerables', [[262 itSub('Forbids a non-sudo to remove an invulnerable', async ({helper}) => {
100 aliceAddress,263 const invulnerables = await helper.collatorSelection.getInvulnerables();
101 bobAddress,264 await expect(helper.collatorSelection.removeInvulnerable(superuser, invulnerables[0]))
102 ]]);265 .to.be.rejectedWith(/BadOrigin/);
103 await expect(helper.executeExtrinsic(superuser, 'api.tx.sudo.sudo', [tx])).to.be.fulfilled;266 expect(await helper.collatorSelection.getInvulnerables()).to.have.all.members(invulnerables);
267 });
104 });268 });
269
270 // todo:collator after
105 });271 });
106});272});
107
modifiedtests/src/util/playgrounds/unique.tsdiffbeforeafterboth
11import {IKeyringPair} from '@polkadot/types/types';11import {IKeyringPair} from '@polkadot/types/types';
12import {hexToU8a} from '@polkadot/util/hex';12import {hexToU8a} from '@polkadot/util/hex';
13import {u8aConcat} from '@polkadot/util/u8a';13import {u8aConcat} from '@polkadot/util/u8a';
14import {BN} from '@polkadot/util/bn';
14import {15import {
15 IApiListeners,16 IApiListeners,
16 IBlock,17 IBlock,
2642 }2643 }
2643}2644}
2645
2646class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {
2647 //todo:collator documentation
2648 setKeys(signer: TSigner, key: string) {
2649 return this.helper.executeExtrinsic(
2650 signer,
2651 'api.tx.session.setKeys',
2652 [
2653 key,
2654 '0x0',
2655 ],
2656 true,
2657 );
2658 }
2659
2660 setOwnKeys(signer: TSigner) {
2661 return this.setKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));
2662 }
2663
2664 addInvulnerable(signer: TSigner, address: string) {
2665 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);
2666 }
2667
2668 removeInvulnerable(signer: TSigner, address: string) {
2669 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);
2670 }
2671
2672 async getInvulnerables() {
2673 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());
2674 }
2675}
26442676
2645class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {2677class ForeignAssetsGroup extends HelperGroup<UniqueHelper> {
2646 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {2678 async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) {
2808 ft: FTGroup;2840 ft: FTGroup;
2809 staking: StakingGroup;2841 staking: StakingGroup;
2810 scheduler: SchedulerGroup;2842 scheduler: SchedulerGroup;
2843 collatorSelection: CollatorSelectionGroup;
2811 foreignAssets: ForeignAssetsGroup;2844 foreignAssets: ForeignAssetsGroup;
2812 xcm: XcmGroup<UniqueHelper>;2845 xcm: XcmGroup<UniqueHelper>;
2813 xTokens: XTokensGroup<UniqueHelper>;2846 xTokens: XTokensGroup<UniqueHelper>;
2823 this.ft = new FTGroup(this);2856 this.ft = new FTGroup(this);
2824 this.staking = new StakingGroup(this);2857 this.staking = new StakingGroup(this);
2825 this.scheduler = new SchedulerGroup(this);2858 this.scheduler = new SchedulerGroup(this);
2859 this.collatorSelection = new CollatorSelectionGroup(this);
2826 this.foreignAssets = new ForeignAssetsGroup(this);2860 this.foreignAssets = new ForeignAssetsGroup(this);
2827 this.xcm = new XcmGroup(this, 'polkadotXcm');2861 this.xcm = new XcmGroup(this, 'polkadotXcm');
2828 this.xTokens = new XTokensGroup(this);2862 this.xTokens = new XTokensGroup(this);
2988 super(...args);3022 super(...args);
2989 }3023 }
29903024
2991 executeExtrinsic (3025 async executeExtrinsic(
2992 sender: IKeyringPair,3026 sender: IKeyringPair,
2993 extrinsic: string,3027 extrinsic: string,
2994 params: any[],3028 params: any[],
2995 expectSuccess?: boolean,3029 expectSuccess?: boolean,
3030 options: Partial<SignerOptions>|null = null,
2996 ): Promise<ITransactionResult> {3031 ): Promise<ITransactionResult> {
2997 const call = this.constructApiCall(extrinsic, params);3032 const call = this.constructApiCall(extrinsic, params);
2998 return super.executeExtrinsic(3033 const result = await super.executeExtrinsic(
2999 sender,3034 sender,
3000 'api.tx.sudo.sudo',3035 'api.tx.sudo.sudo',
3001 [call],3036 [call],
3002 expectSuccess,3037 expectSuccess,
3038 options,
3003 );3039 );
3040
3041 if (result.status === 'Fail') return result;
3042
3043 const data = this.eventHelper.extractEvents(result.result.events).find(x => x.section == 'sudo')?.data[0];
3044 if (data.err) {
3045 const error = data.err.module;
3046 // todo:collator
3047 const metaError = super.getApi()?.registry.findMetaError({index: new BN(error.index), error: new BN(9)});
3048 throw new Error(`${data.err.module.error} ${metaError.section}.${metaError.name}`);
3049 }
3050 return result;
3004 }3051 }
3005 };3052 };
3006}3053}