difftreelog
feat(collator-selection) add+remove invulnerable methods + tests + miscellaneous changes before more refactoring
in: master
11 files changed
node/cli/src/chain_spec.rsdiffbeforeafterboth24use serde_json::map::Map;24use serde_json::map::Map;252526use 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};282829#[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 {pallets/collator-selection/src/benchmarking.rsdiffbeforeafterboth200 whitelist!(leaving);205 whitelist!(leaving);201 }: _(RawOrigin::Signed(leaving.clone()))206 }: _(RawOrigin::Signed(leaving.clone()))202 verify {207 verify {208 // todo:collator verify these203 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 }205211206 // worse case is paying a non-existing candidate account.212 // worse case is paying a non-existing candidate account.pallets/collator-selection/src/lib.rsdiffbeforeafterboth30// See the License for the specific language governing permissions and30// See the License for the specific language governing permissions and31// limitations under the License.31// limitations under the License.323233// todo:collator documentation33//! 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;114118115 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>;142143 /// Account Identifier that holds the chain's treasury.144 type TreasuryAccountId: Get<Self::AccountId>;138145139 /// 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>;154161155 // 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>;157164158 /// 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 >;209210 /// 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>;202216203 /// 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 }229244233 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 }258276259 <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 }264283265 #[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 }274311289 NotCandidate,326 NotCandidate,290 /// Too many invulnerables327 /// Too many invulnerables291 TooManyInvulnerables,328 TooManyInvulnerables,329 /// Too few invulnerables330 TooFewInvulnerables,292 /// User is already an Invulnerable331 /// User is already an Invulnerable293 AlreadyInvulnerable,332 AlreadyInvulnerable,333 /// User is not an Invulnerable334 NotInvulnerable,294 /// Account has no associated validator ID335 /// Account has no associated validator ID295 NoAssociatedValidatorId,336 NoAssociatedValidatorId,296 /// Validator ID is not yet registered337 /// Validator ID is not yet registered302343303 #[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 weight307 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)?;314353315 // check if the invulnerables have associated validator keys before they are set354 // check if the new invulnerable has associated validator keys before it is added316 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>::ValidatorNotRegistered322 );360 );323 }361 // ensure!(!Self::invulnerables().contains(&new), Error::<T>::AlreadyInvulnerable);362 if Self::invulnerables().contains(&new) {363 return Ok(().into());364 }324365325 <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 }371372 /// Remove a collator from the list of invulnerable (fixed) collators.373 #[pallet::weight(T::WeightInfo::set_invulnerables(1))] // todo:collator weight374 pub fn remove_invulnerable(375 origin: OriginFor<T>,376 who: T::AccountId,377 ) -> DispatchResultWithPostInfo {378 T::UpdateOrigin::ensure_origin(origin)?;379380 // 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 }385386 let index = invulnerables387 .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)?;395396 <Invulnerables<T>>::put(&bounded_invulnerables);*/397 Self::deposit_event(Event::InvulnerableRemoved { invulnerable: who });398 Ok(().into())399 }331400332 /// 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 }432433 /// 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 weight436 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 }361448362 /// 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 have363 /// 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>::TooManyCandidates462 );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>::AlreadyInvulnerable383 // 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 delay384 let incoming = CandidateInfo { who: who.clone(), deposit };478 let incoming = CandidateInfo {479 who: who.clone(),480 deposit,481 };385482386 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 })?;400499401 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 together414 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>::TooFewCandidates417 );520 );418 let current_count = Self::try_remove_candidate(&who)?;521 let current_count = Self::try_remove_candidate(&who, false)?;419522420 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 }429532430 /// 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 = candidates435 .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;547548 if should_slash {549 let slashed = T::SlashRatio::get() * deposit;550 let remaining = deposit - slashed;551552 let (imbalance, _) = T::Currency::slash_reserved(who, slashed);553 //T::Currency::unreserve(who, remaining);554 deposit_returned = remaining;555556 T::Currency::resolve_creating(&T::TreasuryAccountId::get(), imbalance);557558 // Self::deposit_event(Event::CandidateSlashed(who.clone()));559 } else {560 //T::Currency::unreserve(who, deposit);561 deposit_returned = deposit;562 }563439 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 }457586458 /// 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 threshold459 /// 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 candidates466 .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);pallets/collator-selection/src/mock.rsdiffbeforeafterboth43use 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};484849type 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}214215215pub struct IsRegistered;216pub struct IsRegistered;224}225}225226226impl Config for Test {227impl Config for Test {228 // todo:collator mocks and stocks227 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 = balances248 .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 };pallets/collator-selection/src/tests.rsdiffbeforeafterboth196 ));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);276300277 let collator = CandidateInfo { who: 4, deposit: 10 };301 let collator = CandidateInfo {302 who: 4,303 deposit: 10,304 };278305279 assert_eq!(CollatorSelection::candidates(), vec![collator]);306 assert_eq!(CollatorSelection::candidates(), vec![collator]);301330302 let collator = CandidateInfo { who: 4, deposit: 10 };331 let collator = CandidateInfo {332 who: 4,333 deposit: 10,334 };303335304 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);pallets/collator-selection/src/weights.rsdiffbeforeafterboth41};41};42use sp_std::marker::PhantomData;42use sp_std::marker::PhantomData;434344// todo:collator re-generate weights44// 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;primitives/common/src/constants.rsdiffbeforeafterboth46pub 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;495150// Targeting 0.1 UNQ per transfer52// Targeting 0.1 UNQ per transfer51pub const WEIGHT_TO_FEE_COEFF: u32 = /*<weight2fee>*/175_199_920/*</weight2fee>*/;53pub const WEIGHT_TO_FEE_COEFF: u32 = /*<weight2fee>*/175_199_920/*</weight2fee>*/;runtime/common/config/pallets/collator_selection.rsdiffbeforeafterboth18use 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::*;242525parameter_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}2930545555parameter_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 invulnerables58 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}616362impl 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 UpdateOrigin72 // 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;runtime/common/runtime_apis.rsdiffbeforeafterboth703 #[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);705706 // todo:collator check benchmarks707 #[cfg(feature = "collator-selection")]708 list_benchmark!(list, extra, pallet_collator_selection, CollatorSelection);705709706 #[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);772773 // todo:collator check benchmarks774 #[cfg(feature = "collator-selection")]775 add_benchmark!(params, batches, pallet_collator_selection, CollatorSelection);768776769 #[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);tests/src/collatorSelection.test.tsdiffbeforeafterboth17import {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';191920async 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 ]);3536 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}4445// 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 change21describe('Integration Test: Dynamic shuffling of collators', () => {47describe('Integration Test: Collator Selection', () => {22 let superuser: IKeyringPair;48 let superuser: IKeyringPair;234924 // 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;275328 let charlie: IKeyringPair;54 let charlie: IKeyringPair;29 let dave: IKeyringPair;55 let dave: IKeyringPair;30 //let eve: IKeyringPair;56 //let eve: IKeyringPair;315732 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]);356162 //todo:collator36 //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');406941 superuser = await privateKey('//Alice');70 superuser = await privateKey('//Alice');42 aliceAddress = (await privateKey('//Alice')).address;71 });43 bobAddress = (await privateKey('//Bob')).address;72 });447345 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 general83 // 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-executor124 currentSessionIndex = await expect(helper.wait.withTimeout(new Promise(async (resolve) => {125 //todo:collator126 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, eh147 // register candidate without sudos and the like148 149 after(async () => {150 await usingPlaygrounds(async (helper) => {151 if (helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) return;4915250 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 });5916560 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 tests167 // 28 non-functioning collators, teehee.6116862 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 });6717368 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();7017871 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 });7518576 while (currentSessionIndex < expectedSessionIndex) {186 itSub('Removes an invulnerable', async ({helper}) => {77 // eslint-disable-next-line no-async-promise-executor78 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 }8418985 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 equal193 expect(newInvulnerables).to.have.all.members(invulnerables);194 });195 });8719688 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 either92 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 });206207 itSub('Cannot allow invulnerables to be empty', async ({helper}) => {208 const invulnerables = await helper.collatorSelection.getInvulnerables();209 const lastInvulnerable = invulnerables.pop();210211 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++})));214215 await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable))216 .to.be.rejected;//todo:collator With(/collatorSelection.TooFewInvulnerables/);217218 const newInvulnerables = await helper.collatorSelection.getInvulnerables();219 expect(newInvulnerables).to.be.deep.equal([lastInvulnerable]);220 221 // restore the invulnerables to the previous state222 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 });226227 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);232233 await Promise.all(newInvulnerables.map((i: IKeyringPair) => 234 helper.collatorSelection.setOwnKeys(i)));235 await helper.collatorSelection.setOwnKeys(lastInvulnerable);236237 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++})));240241 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 state245 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 });249250 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();253254 await helper.collatorSelection.setOwnKeys(account);255 await expect(helper.collatorSelection.addInvulnerable(bob, account.address))256 .to.be.rejectedWith(/BadOrigin/);9425795 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 });9826199 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 after105 });271 });106});272});107tests/src/util/playgrounds/unique.tsdiffbeforeafterboth11import {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}26452646class CollatorSelectionGroup extends HelperGroup<UniqueHelper> {2647 //todo:collator documentation2648 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 }26592660 setOwnKeys(signer: TSigner) {2661 return this.setKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex'));2662 }26632664 addInvulnerable(signer: TSigner, address: string) {2665 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]);2666 }26672668 removeInvulnerable(signer: TSigner, address: string) {2669 return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]);2670 }26712672 async getInvulnerables() {2673 return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman());2674 }2675}264426762645class 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 }299030242991 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 );30403041 if (result.status === 'Fail') return result;30423043 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:collator3047 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}