From 496d044f6481027f3a6f822770429f3e41d8d246 Mon Sep 17 00:00:00 2001 From: tmpolaczyk <44604217+tmpolaczyk@users.noreply.github.com> Date: Mon, 22 Jan 2024 15:41:39 +0100 Subject: [PATCH] Rewrite collator assignment to support parathreads (#385) --- pallets/collator-assignment/src/assignment.rs | 436 +++++++++++++ pallets/collator-assignment/src/lib.rs | 199 ++---- pallets/collator-assignment/src/mock.rs | 32 +- pallets/collator-assignment/src/tests.rs | 593 ++++++++++++------ .../src/tests/assign_full.rs | 90 +++ .../src/tests/prioritize_invulnerables.rs | 186 ++++++ .../src/tests/select_chains.rs | 176 ++++++ pallets/configuration/src/lib.rs | 18 +- pallets/registrar/src/lib.rs | 5 + primitives/traits/src/lib.rs | 2 + runtime/dancebox/tests/integration_test.rs | 7 +- runtime/flashbox/tests/integration_test.rs | 7 +- test/scripts/build-spec-warp-sync.sh | 2 +- test/scripts/build-spec.sh | 2 +- .../test_remove_session_key_staking.ts | 21 +- .../staking/test_staking_rewards_balanced.ts | 14 +- .../test_staking_rewards_non_balanced.ts | 14 +- 17 files changed, 1433 insertions(+), 371 deletions(-) create mode 100644 pallets/collator-assignment/src/assignment.rs create mode 100644 pallets/collator-assignment/src/tests/assign_full.rs create mode 100644 pallets/collator-assignment/src/tests/prioritize_invulnerables.rs create mode 100644 pallets/collator-assignment/src/tests/select_chains.rs diff --git a/pallets/collator-assignment/src/assignment.rs b/pallets/collator-assignment/src/assignment.rs new file mode 100644 index 000000000..fb17885b6 --- /dev/null +++ b/pallets/collator-assignment/src/assignment.rs @@ -0,0 +1,436 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + +use { + dp_collator_assignment::AssignedCollators, + sp_std::{ + cmp, + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + marker::PhantomData, + mem, vec, + vec::Vec, + }, + tp_traits::{ParaId, RemoveInvulnerables as RemoveInvulnerablesT}, +}; + +/// Helper methods to implement collator assignment algorithm +pub struct Assignment(PhantomData); + +impl Assignment +where + T: crate::Config, +{ + /// Recompute collator assignment from scratch. If the list of collators and the list of + /// container chains are shuffled, this returns a random assignment. + pub fn assign_collators_rotate_all( + collators: Vec, + orchestrator_chain: ChainNumCollators, + chains: Vec, + ) -> Result, AssignmentError> { + // This is just the "always_keep_old" algorithm but with an empty "old" + let old_assigned = Default::default(); + + Self::assign_collators_always_keep_old(collators, orchestrator_chain, chains, old_assigned) + } + + /// Assign new collators to missing container_chains. + /// Old collators always have preference to remain on the same chain. + /// If there are no missing collators, nothing is changed. + /// + /// `chains` should be shuffled or at least rotated on every session to ensure + /// a fair distribution, because the order of that list affects container chain priority: + /// the first chain on that list will be the first one to get new collators. + pub fn assign_collators_always_keep_old( + collators: Vec, + orchestrator_chain: ChainNumCollators, + mut chains: Vec, + mut old_assigned: AssignedCollators, + ) -> Result, AssignmentError> { + if collators.is_empty() { + return Err(AssignmentError::ZeroCollators); + } + // The rest of this function mostly treats orchestrator chain as another container chain, so move it into + // `old_assigned.container_chains` + let old_orchestrator_assigned = mem::take(&mut old_assigned.orchestrator_chain); + old_assigned + .container_chains + .insert(orchestrator_chain.para_id, old_orchestrator_assigned); + let mut old_assigned = old_assigned.container_chains; + // Orchestrator chain must be the first one in the list because it always has priority + chains.insert(0, orchestrator_chain); + let all_para_ids: Vec = chains.iter().map(|cc| cc.para_id).collect(); + let collators_set = BTreeSet::from_iter(collators.iter().cloned()); + let chains_with_collators = + Self::select_chains_with_collators(collators.len() as u32, &chains); + let chains_with_collators_set: BTreeSet = chains_with_collators + .iter() + .map(|(para_id, _num_collators)| *para_id) + .collect(); + Self::retain_valid_old_assigned( + &mut old_assigned, + &chains_with_collators_set, + &collators_set, + ); + + // Ensure the first `min_orchestrator_collators` of orchestrator chain are invulnerables + Self::prioritize_invulnerables(&collators, orchestrator_chain, &mut old_assigned); + + let new_assigned_chains = + Self::assign_full(collators, chains_with_collators, old_assigned)?; + let mut new_assigned = AssignedCollators::default(); + new_assigned.container_chains = new_assigned_chains; + + // Add container chains with 0 collators so that they are shown in UI + for para_id in all_para_ids { + new_assigned.container_chains.entry(para_id).or_default(); + } + + // The rest of this function mostly treats orchestrator chain as another container chain, remove it from + // container chains before returning the final assignment. + let orchestrator_assigned = new_assigned + .container_chains + .remove(&orchestrator_chain.para_id) + .unwrap(); + // Sanity check to avoid bricking orchestrator chain + if orchestrator_assigned.is_empty() { + return Err(AssignmentError::EmptyOrchestrator); + } + new_assigned.orchestrator_chain = orchestrator_assigned; + + Ok(new_assigned) + } + + /// Select which container chains will be assigned collators and how many collators, but do not specify which + /// collator goes to which chain. + /// + /// Each chain has a min and max number of collators. If the number of collators is not enough to reach the min, + /// no collators are assigned to that chain. + /// + /// If the available number of collators is: + /// * lower than the min of the first chain: we assign all the collators to the first chain. This is the + /// orchestrator chain and we always want it to have collators. + /// * lower than the sum of all the min: we cannot assign collators to all the chains. So remove chains until + /// we can. The order is important, the first chains will be assigned collators and the last ones will not. + /// * lower than the sum of all the max: we can assign the min value to all the chains, and have some leftover. + /// We use the same order to decide where this extra collators will go, by filling the max of the first chain, + /// then the max of the second chain, and so on. + /// * greater than the sum of all the max: all the chains will be assigned their max number of collators. + /// + /// # Params + /// + /// The first item of `chains` should be the orchestrator chain, because it will be the first one to be assigned + /// collators. + /// + /// # Returns + /// + /// A list of `(para_id, num_collators)`. + pub fn select_chains_with_collators( + num_collators: u32, + chains: &[ChainNumCollators], + ) -> Vec<(ParaId, u32)> { + if chains.is_empty() { + // Avoid panic if chains is empty + return vec![]; + } + // Let's count how many container chains we can support with the current number of collators + let mut available_collators = num_collators; + // Handle orchestrator chain in a special way, we always want to assign collators to it, even if we don't + // reach the min. + let min_orchestrator_collators = chains[0].min_collators; + available_collators = available_collators.saturating_sub(min_orchestrator_collators); + + let mut container_chains_with_collators = vec![chains[0]]; + // Skipping orchestrator chain because it was handled above + for cc in chains.iter().skip(1) { + if available_collators >= cc.min_collators { + available_collators -= cc.min_collators; + container_chains_with_collators.push(*cc); + } else if available_collators == 0 { + // Do not break if there are still some available collators. Even if they were not enough to reach the + // `min` of this chain, it is possible that one of the chains with less priority has a lower `min`, so + // that chain should be assigned collators. + break; + } + } + + let mut required_collators_min = 0; + for cc in &container_chains_with_collators { + required_collators_min += cc.min_collators; + } + + if num_collators < min_orchestrator_collators { + // Edge case: num collators less than min orchestrator collators: fill as much as we can + vec![(chains[0].para_id, num_collators)] + } else { + // After assigning the min to all the chains we have this remainder. The remainder will be assigned until + // all the chains reach the max value. + let mut required_collators_remainder = num_collators - required_collators_min; + let mut container_chains_variable = vec![]; + for cc in &container_chains_with_collators { + // Each chain will have `min + extra` collators, where extra is capped so `min + extra <= max`. + let extra = cmp::min( + required_collators_remainder, + cc.max_collators.saturating_sub(cc.min_collators), + ); + let num = cc.min_collators + extra; + required_collators_remainder -= extra; + container_chains_variable.push((cc.para_id, num)); + } + + container_chains_variable + } + } + + /// Same as `prioritize_invulnerables` but return the invulnerables instead of inserting them into `old_assigned`. + /// + /// Mutates `old_assigned` by removing invulnerables from their old chain, even if they will later be assigned to + /// the same chain. + pub fn remove_invulnerables( + collators: &[T::AccountId], + orchestrator_chain: ChainNumCollators, + old_assigned: &mut BTreeMap>, + ) -> Vec { + // TODO: clean this up, maybe change remove_invulnerables trait into something more ergonomic + let min_orchestrator_collators = orchestrator_chain.min_collators as usize; + let invulnerables_already_assigned = T::RemoveInvulnerables::remove_invulnerables( + &mut old_assigned + .get(&orchestrator_chain.para_id) + .cloned() + .unwrap_or_default(), + min_orchestrator_collators, + ); + let mut new_invulnerables = invulnerables_already_assigned; + if new_invulnerables.len() >= min_orchestrator_collators { + // We already had invulnerables, we will just move them to the front of the list if they weren't already + return new_invulnerables; + } + + // Not enough invulnerables currently assigned, get rest from new_collators + let mut new_collators = collators.to_vec(); + for (_id, cs) in old_assigned.iter() { + new_collators.retain(|c| !cs.contains(c)); + } + let num_missing_invulnerables = min_orchestrator_collators - new_invulnerables.len(); + let invulnerables_not_assigned = T::RemoveInvulnerables::remove_invulnerables( + &mut new_collators, + num_missing_invulnerables, + ); + new_invulnerables.extend(invulnerables_not_assigned); + + if new_invulnerables.len() >= min_orchestrator_collators { + // Got invulnerables from new_collators, and maybe some were already assigned + return new_invulnerables; + } + + // Still not enough invulnerables, try to get an invulnerable that is currently assigned somewhere else + let num_missing_invulnerables = min_orchestrator_collators - new_invulnerables.len(); + let mut collators = collators.to_vec(); + let new_invulnerables_set = BTreeSet::from_iter(new_invulnerables.iter().cloned()); + collators.retain(|c| { + // Remove collators already selected + !new_invulnerables_set.contains(c) + }); + let invulnerables_assigned_elsewhere = + T::RemoveInvulnerables::remove_invulnerables(&mut collators, num_missing_invulnerables); + + if invulnerables_assigned_elsewhere.is_empty() { + // If at this point we still do not have enough invulnerables, it means that there are no + // enough invulnerables, so no problem, but return the invulnerables + return new_invulnerables; + } + + new_invulnerables.extend(invulnerables_assigned_elsewhere.iter().cloned()); + + // In this case we must delete the old assignment of the invulnerables + let reassigned_invulnerables_set = BTreeSet::from_iter(invulnerables_assigned_elsewhere); + // old_assigned.remove_collators_in_set + for (_id, cs) in old_assigned.iter_mut() { + cs.retain(|c| !reassigned_invulnerables_set.contains(c)); + } + + new_invulnerables + } + + /// Ensure orchestrator chain has `min_orchestrator` invulnerables. If that's not possible, it tries to add as + /// many invulnerables as possible. + /// + /// Get invulnerables from: + /// * old_assigned in orchestrator + /// * new collators + /// * old_assigned elsewhere + /// + /// In that order. + /// + /// Mutates `old_assigned` because invulnerables will be inserted there, and if invulnerables were already + /// assigned to some other chain, they will be removed from that other chain as well. + /// + /// # Params + /// + /// * `old_assigned` must be a subset of `collators` + /// * `old_assigned` must not have duplicate collators. + /// + /// # Returns + /// + /// The number of invulnerables assigned to the orchestrator chain, capped to `min_collators`. + pub fn prioritize_invulnerables( + collators: &[T::AccountId], + orchestrator_chain: ChainNumCollators, + old_assigned: &mut BTreeMap>, + ) -> usize { + let new_invulnerables = + Self::remove_invulnerables(collators, orchestrator_chain, old_assigned); + + if !new_invulnerables.is_empty() { + Self::insert_invulnerables( + old_assigned.entry(orchestrator_chain.para_id).or_default(), + &new_invulnerables, + ); + } + + new_invulnerables.len() + } + + /// Assign collators assuming that the number of collators is greater than or equal to the required. + /// The order of both container chains and collators is important to ensure randomness when `old_assigned` is + /// empty. + /// + /// # Params + /// + /// * `old_assigned` does not need to be a subset of `collators`: collators are checked and removed. + /// * `old_assigned` does not need to be a subset of `chains`, unused para ids are removed. Collators + /// assigned to a para_id not present in `chains` may be reassigned to another para_id. + /// * `chains` `num_collators` can be 0. In that case an empty vec is returned for that para id. + /// * `old_assigned` must not have duplicate collators. + /// + /// # Returns + /// + /// The collator assigment, a map from `ParaId` to `Vec`. + /// + /// Or an error if the number of collators is not enough to fill all the chains, or if the required number + /// of collators overflows a `u32`. + pub fn assign_full( + collators: Vec, + chains: Vec<(ParaId, u32)>, + mut old_assigned: BTreeMap>, + ) -> Result>, AssignmentError> { + let mut required_collators = 0usize; + for (_para_id, num_collators) in chains.iter() { + let num_collators = + usize::try_from(*num_collators).map_err(|_| AssignmentError::NotEnoughCollators)?; + required_collators = required_collators + .checked_add(num_collators) + .ok_or(AssignmentError::NotEnoughCollators)?; + } + + // This check is necessary to ensure priority: if the number of collators is less than required, it is + // possible that the chain with the least priority could be assigned collators (since they are in + // old_assigned), while some chains with higher priority might have no collators. + if collators.len() < required_collators { + return Err(AssignmentError::NotEnoughCollators); + } + // We checked that the sum of all `num_collators` fits in `usize`, so we can safely use `as usize`. + + // Remove invalid collators and para ids from `old_assigned` + let para_ids_set = + BTreeSet::from_iter(chains.iter().map(|(para_id, _num_collators)| *para_id)); + let collators_set = BTreeSet::from_iter(collators.iter().cloned()); + Self::retain_valid_old_assigned(&mut old_assigned, ¶_ids_set, &collators_set); + + // Truncate num collators to required + for (para_id, num_collators) in chains.iter() { + let entry = old_assigned.entry(*para_id).or_default(); + entry.truncate(*num_collators as usize); + } + + let assigned_collators: BTreeSet = old_assigned + .iter() + .flat_map(|(_para_id, para_collators)| para_collators.iter().cloned()) + .collect(); + let mut new_collators = collators.into_iter().filter(|x| { + // Keep collators not already assigned + !assigned_collators.contains(x) + }); + + // Fill missing collators + for (para_id, num_collators) in chains.iter() { + let cs = old_assigned.entry(*para_id).or_default(); + + while cs.len() < *num_collators as usize { + // This error should never happen because we checked that `collators.len() >= required_collators` + let nc = new_collators + .next() + .ok_or(AssignmentError::NotEnoughCollators)?; + cs.push(nc); + } + } + + Ok(old_assigned) + } + + /// Insert invulnerables ensuring that they are always the first in the list. + /// The order of both lists is preserved. + /// `assigned` may already contain the invulnerables, in that case they are only moved to the front. + /// + /// Invulnerables need to be the first of the list because we may truncate the list of collators if the number of + /// collators changes, and in that case we want invulnerables to stay assigned there. + pub fn insert_invulnerables(assigned: &mut Vec, invulnerables: &[T::AccountId]) { + assigned.retain(|item| !invulnerables.contains(item)); + + let mut new_assigned = invulnerables.to_vec(); + new_assigned.extend(mem::take(assigned)); + + *assigned = new_assigned; + } + + /// Removes invalid entries from `old_assigned`: + /// + /// * para ids not in `chains_with_collators` + /// * collators not in `collators` + pub fn retain_valid_old_assigned( + old_assigned: &mut BTreeMap>, + chains_with_collators: &BTreeSet, + collators: &BTreeSet, + ) { + // old_assigned.remove_container_chains_not_in_set + old_assigned.retain(|id, _cs| chains_with_collators.contains(id)); + // old_assigned.remove_collators_not_in_set + for (_id, cs) in old_assigned.iter_mut() { + cs.retain(|c| collators.contains(c)); + } + } +} + +/// Errors than can happen during collator assignment +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AssignmentError { + /// An empty list of collators was passed to `assign_collators_always_keep_old` + ZeroCollators, + /// The required number of collators for `assign_full` is greater than the provided number of collators. + /// Also includes possible overflows in number of collators. + NotEnoughCollators, + /// No collators were assigned to orchestrator chain + EmptyOrchestrator, +} + +/// A `ParaId` and a range of collators that need to be assigned to it. +/// This can be a container chain, a parathread, or the orchestrator chain. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct ChainNumCollators { + pub para_id: ParaId, + pub min_collators: u32, + // This will only be filled if all the other min have been reached + pub max_collators: u32, +} diff --git a/pallets/collator-assignment/src/lib.rs b/pallets/collator-assignment/src/lib.rs index eb10e53de..5df502dec 100644 --- a/pallets/collator-assignment/src/lib.rs +++ b/pallets/collator-assignment/src/lib.rs @@ -43,7 +43,10 @@ pub use pallet::*; use { - crate::weights::WeightInfo, + crate::{ + assignment::{Assignment, ChainNumCollators}, + weights::WeightInfo, + }, dp_collator_assignment::AssignedCollators, frame_support::pallet_prelude::*, frame_system::pallet_prelude::BlockNumberFor, @@ -60,6 +63,7 @@ use { }, }; +mod assignment; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; pub mod weights; @@ -164,6 +168,7 @@ pub mod pallet { // We get the containerChains that we will have at the target session let mut container_chain_ids = T::ContainerChains::session_container_chains(target_session_index); + let mut parathreads = T::ContainerChains::session_parathreads(target_session_index); let num_total_registered_paras = container_chain_ids.len() as u32; // Remove the containerChains that do not have enough credits for block production T::RemoveParaIdsWithNoCredits::remove_para_ids_with_no_credits( @@ -176,11 +181,47 @@ pub mod pallet { if random_seed != [0; 32] { let mut rng: ChaCha20Rng = SeedableRng::from_seed(random_seed); collators.shuffle(&mut rng); + // TODO: in the future, instead of shuffling the list of para ids, we need to use the priority fee to + // determine priority container_chain_ids.shuffle(&mut rng); + parathreads.shuffle(&mut rng); } // We read current assigned collators let old_assigned = Self::read_assigned_collators(); + let orchestrator_chain = ChainNumCollators { + para_id: T::SelfParaId::get(), + min_collators: T::HostConfiguration::min_collators_for_orchestrator( + target_session_index, + ), + max_collators: T::HostConfiguration::max_collators_for_orchestrator( + target_session_index, + ), + }; + // Initialize list of chains as `[container1, container2, parathread1, parathread2]`. + // The order means priority: the first chain in the list will be the first one to get assigned collators. + // Chains will not be assigned less than `min_collators`, except the orchestrator chain. + // First all chains will be assigned `min_collators`, and then the first one will be assigned up to `max`, + // then the second one, and so on. + let mut chains = vec![]; + let collators_per_container = + T::HostConfiguration::collators_per_container(target_session_index); + for para_id in &container_chain_ids { + chains.push(ChainNumCollators { + para_id: *para_id, + min_collators: collators_per_container, + max_collators: collators_per_container, + }); + } + let collators_per_parathread = + T::HostConfiguration::collators_per_parathread(target_session_index); + for para_id in ¶threads { + chains.push(ChainNumCollators { + para_id: *para_id, + min_collators: collators_per_parathread, + max_collators: collators_per_parathread, + }); + } // We assign new collators // we use the config scheduled at the target_session_index let new_assigned = @@ -197,15 +238,10 @@ pub mod pallet { target_session: target_session_index, }); - Self::assign_collators_rotate_all( + Assignment::::assign_collators_rotate_all( collators, - &container_chain_ids, - T::HostConfiguration::min_collators_for_orchestrator(target_session_index) - as usize, - T::HostConfiguration::max_collators_for_orchestrator(target_session_index) - as usize, - T::HostConfiguration::collators_per_container(target_session_index) - as usize, + orchestrator_chain, + chains, ) } else { log::info!( @@ -220,19 +256,26 @@ pub mod pallet { target_session: target_session_index, }); - Self::assign_collators_always_keep_old( + Assignment::::assign_collators_always_keep_old( collators, - &container_chain_ids, - T::HostConfiguration::min_collators_for_orchestrator(target_session_index) - as usize, - T::HostConfiguration::max_collators_for_orchestrator(target_session_index) - as usize, - T::HostConfiguration::collators_per_container(target_session_index) - as usize, + orchestrator_chain, + chains, old_assigned.clone(), ) }; + let new_assigned = match new_assigned { + Ok(x) => x, + Err(e) => { + log::error!( + "Error in collator assignment, will keep previous assignment. {:?}", + e + ); + + old_assigned.clone() + } + }; + let mut pending = PendingCollatorContainerChain::::get(); let old_assigned_changed = old_assigned != new_assigned; let mut pending_changed = false; @@ -267,128 +310,6 @@ pub mod pallet { } } - /// Recompute collator assignment from scratch. If the list of collators and the list of - /// container chains are shuffled, this returns a random assignment. - fn assign_collators_rotate_all( - collators: Vec, - container_chain_ids: &[ParaId], - min_num_orchestrator_chain: usize, - max_num_orchestrator_chain: usize, - num_each_container_chain: usize, - ) -> AssignedCollators { - // This is just the "always_keep_old" algorithm but with an empty "old" - let old_assigned = Default::default(); - - Self::assign_collators_always_keep_old( - collators, - container_chain_ids, - min_num_orchestrator_chain, - max_num_orchestrator_chain, - num_each_container_chain, - old_assigned, - ) - } - - /// Assign new collators to missing container_chains. - /// Old collators always have preference to remain on the same chain. - /// If there are no missing collators, nothing is changed. - /// - /// `container_chain_ids` should be shuffled or at least rotated on every session to ensure - /// a fair distribution, because the order of that list affects container chain priority: - /// the first container chain on that list will be the first one to get new collators. - fn assign_collators_always_keep_old( - collators: Vec, - container_chain_ids: &[ParaId], - min_num_orchestrator_chain: usize, - max_num_orchestrator_chain: usize, - num_each_container_chain: usize, - old_assigned: AssignedCollators, - ) -> AssignedCollators { - // TODO: the performance of this function is sad, could be improved by having sets of - // old_collators and new_collators instead of doing array.contains() every time. - let mut new_assigned = old_assigned; - new_assigned.remove_collators_not_in_list(&collators); - new_assigned.remove_container_chains_not_in_list(container_chain_ids); - let extra_orchestrator_collators = - new_assigned.remove_orchestrator_chain_excess_collators(min_num_orchestrator_chain); - // Only need to do this if the config params change - new_assigned.remove_container_chain_excess_collators(num_each_container_chain); - - // Collators that are not present in old_assigned - // This is used to keep track of which collators are old and which ones are new, to keep - // the old collators on the same chain if possible. - let mut new_collators = vec![]; - for c in collators { - if !new_assigned.find_collator(&c) && !extra_orchestrator_collators.contains(&c) { - new_collators.push(c); - } - } - - // Fill orchestrator chain collators up to min_num_orchestrator_chain - // Give priority to invulnerables - let num_missing_orchestrator_collators = - min_num_orchestrator_chain.saturating_sub(new_assigned.orchestrator_chain.len()); - let invulnerables_for_orchestrator = T::RemoveInvulnerables::remove_invulnerables( - &mut new_collators, - num_missing_orchestrator_collators, - ); - new_assigned.fill_orchestrator_chain_collators( - min_num_orchestrator_chain, - &mut invulnerables_for_orchestrator.into_iter(), - ); - // If there are no enough invulnerables, or if the invulnerables are currently assigned to other chains, - // fill orchestrator chain with regular collators - let mut new_collators = new_collators.into_iter(); - new_assigned - .fill_orchestrator_chain_collators(min_num_orchestrator_chain, &mut new_collators); - - // Fill container chain collators using new collators and also the extra - // collators that were previously assigned to the orchestrator chain, - // but give preference to new collators - let mut extra_orchestrator_collators = extra_orchestrator_collators.into_iter(); - let mut new_plus_extra_collators = new_collators - .by_ref() - .chain(&mut extra_orchestrator_collators); - - new_assigned.add_and_fill_new_container_chains_in_order( - num_each_container_chain, - container_chain_ids, - &mut new_plus_extra_collators, - ); - - // Fill orchestrator chain collators back up to max_num_orchestrator_chain, - // but give preference to collators that were already there - let mut extra_collators_plus_new = extra_orchestrator_collators - .by_ref() - .chain(&mut new_collators); - new_assigned.fill_orchestrator_chain_collators( - max_num_orchestrator_chain, - &mut extra_collators_plus_new, - ); - - // Reorganize container chain collators to fill the maximum number of container - // chains. For example, if num_each_container_chain == 2 and the number of collators - // in each container chain is - // [1, 1, 1, 1, 1] - // Then we can convert that into - // [2, 2, 0, 0, 0] - // and assign 1 extra collator to the orchestrator chain, if needed. - let incomplete_container_chains_collators = new_assigned - .reorganize_incomplete_container_chains_collators( - container_chain_ids, - num_each_container_chain, - ); - - // Assign collators from container chains that do not reach - // "num_each_container_chain" to orchestrator chain - new_assigned.fill_orchestrator_chain_collators( - max_num_orchestrator_chain, - &mut incomplete_container_chains_collators.into_iter(), - ); - - new_assigned - } - // Returns the assigned collators as read from storage. // If there is any item in PendingCollatorContainerChain, returns that element. // Otherwise, reads and returns the current CollatorContainerChain diff --git a/pallets/collator-assignment/src/mock.rs b/pallets/collator-assignment/src/mock.rs index f5c9e69ef..e7f9b31f8 100644 --- a/pallets/collator-assignment/src/mock.rs +++ b/pallets/collator-assignment/src/mock.rs @@ -25,7 +25,7 @@ use { }, frame_system as system, parity_scale_codec::{Decode, Encode}, - sp_core::H256, + sp_core::{Get, H256}, sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, BuildStorage, @@ -111,9 +111,13 @@ pub struct Mocks { pub min_orchestrator_chain_collators: u32, pub max_orchestrator_chain_collators: u32, pub collators_per_container: u32, + pub collators_per_parathread: u32, pub collators: Vec, pub container_chains: Vec, + pub parathreads: Vec, pub random_seed: [u8; 32], + // None means 5 + pub full_rotation_period: Option, } impl mock_data::Config for Test {} @@ -123,7 +127,7 @@ impl mock_data::Config for Test {} pub struct HostConfigurationGetter; parameter_types! { - pub const ParachainId: ParaId = ParaId::new(200); + pub const ParachainId: ParaId = ParaId::new(1000); } impl pallet_collator_assignment::GetHostConfiguration for HostConfigurationGetter { @@ -138,6 +142,10 @@ impl pallet_collator_assignment::GetHostConfiguration for HostConfiguration fn collators_per_container(_session_index: u32) -> u32 { MockData::mock().collators_per_container } + + fn collators_per_parathread(_session_index: u32) -> u32 { + MockData::mock().collators_per_parathread + } } pub struct CollatorsGetter; @@ -160,6 +168,15 @@ impl tp_traits::GetSessionContainerChains for ContainerChainsGetter { .collect() } + fn session_parathreads(_session_index: u32) -> Vec { + MockData::mock() + .parathreads + .iter() + .cloned() + .map(ParaId::from) + .collect() + } + #[cfg(feature = "runtime-benchmarks")] fn set_session_container_chains(_session_index: u32, para_ids: &[ParaId]) { MockData::mutate(|mocks| { @@ -184,13 +201,22 @@ parameter_types! { pub const CollatorRotationSessionPeriod: u32 = 5; } +pub struct MockCollatorRotationSessionPeriod; + +impl Get for MockCollatorRotationSessionPeriod { + fn get() -> u32 { + MockData::mock().full_rotation_period.unwrap_or(5) + } +} + impl pallet_collator_assignment::Config for Test { type RuntimeEvent = RuntimeEvent; type SessionIndex = u32; type HostConfiguration = HostConfigurationGetter; type ContainerChains = ContainerChainsGetter; type SelfParaId = ParachainId; - type ShouldRotateAllCollators = RotateCollatorsEveryNSessions; + type ShouldRotateAllCollators = + RotateCollatorsEveryNSessions; type GetRandomnessForNextBlock = MockGetRandomnessForNextBlock; type RemoveInvulnerables = RemoveAccountIdsAbove100; type RemoveParaIdsWithNoCredits = RemoveParaIdsAbove5000; diff --git a/pallets/collator-assignment/src/tests.rs b/pallets/collator-assignment/src/tests.rs index 5706ad1e3..b0bf4e29e 100644 --- a/pallets/collator-assignment/src/tests.rs +++ b/pallets/collator-assignment/src/tests.rs @@ -14,11 +14,16 @@ // You should have received a copy of the GNU General Public License // along with Tanssi. If not, see +use dp_collator_assignment::AssignedCollators; use { crate::{mock::*, CollatorContainerChain, Event, PendingCollatorContainerChain}, std::collections::BTreeMap, }; +mod assign_full; +mod prioritize_invulnerables; +mod select_chains; + fn assigned_collators() -> BTreeMap { let assigned_collators = CollatorContainerChain::::get(); @@ -31,7 +36,7 @@ fn assigned_collators() -> BTreeMap { } for collator in assigned_collators.orchestrator_chain { - h.insert(collator, 999); + h.insert(collator, 1000); } h @@ -44,6 +49,7 @@ fn assign_initial_collators() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 5; m.max_orchestrator_chain_collators = 5; @@ -60,11 +66,11 @@ fn assign_initial_collators() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -81,6 +87,7 @@ fn assign_collators_after_one_leaves_container() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 5; m.max_orchestrator_chain_collators = 5; @@ -97,11 +104,11 @@ fn assign_collators_after_one_leaves_container() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -120,11 +127,11 @@ fn assign_collators_after_one_leaves_container() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), //(6, 1001), (7, 1001), (8, 1002), @@ -143,6 +150,7 @@ fn assign_collators_after_one_leaves_orchestrator_chain() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 5; m.max_orchestrator_chain_collators = 5; @@ -156,11 +164,11 @@ fn assign_collators_after_one_leaves_orchestrator_chain() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -177,17 +185,17 @@ fn assign_collators_after_one_leaves_orchestrator_chain() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - //(4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + //(4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), (9, 1002), // 10 is assigned in place of 4 - (10, 999), + (10, 1000), ]), ); }); @@ -200,6 +208,7 @@ fn assign_collators_if_config_orchestrator_chain_collators_increases() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 5; m.max_orchestrator_chain_collators = 5; @@ -212,11 +221,11 @@ fn assign_collators_if_config_orchestrator_chain_collators_increases() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -235,18 +244,18 @@ fn assign_collators_if_config_orchestrator_chain_collators_increases() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), (9, 1002), - (10, 999), - (11, 999), - (12, 999), + (10, 1000), + (11, 1000), + (12, 1000), ]), ); }); @@ -259,6 +268,7 @@ fn assign_collators_if_config_orchestrator_chain_collators_decreases() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 5; m.max_orchestrator_chain_collators = 5; @@ -271,11 +281,11 @@ fn assign_collators_if_config_orchestrator_chain_collators_decreases() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -303,6 +313,7 @@ fn assign_collators_if_config_collators_per_container_increases() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 5; m.max_orchestrator_chain_collators = 5; @@ -316,11 +327,11 @@ fn assign_collators_if_config_collators_per_container_increases() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -338,11 +349,11 @@ fn assign_collators_if_config_collators_per_container_increases() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -363,6 +374,7 @@ fn assign_collators_if_container_chain_is_removed() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 5; m.max_orchestrator_chain_collators = 5; @@ -375,11 +387,11 @@ fn assign_collators_if_container_chain_is_removed() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -397,11 +409,11 @@ fn assign_collators_if_container_chain_is_removed() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), ]), @@ -416,6 +428,7 @@ fn assign_collators_if_container_chain_is_added() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 5; m.max_orchestrator_chain_collators = 5; @@ -428,11 +441,11 @@ fn assign_collators_if_container_chain_is_added() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -450,11 +463,11 @@ fn assign_collators_if_container_chain_is_added() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -473,6 +486,7 @@ fn assign_collators_after_decrease_num_collators() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 5; m.max_orchestrator_chain_collators = 5; @@ -482,27 +496,26 @@ fn assign_collators_after_decrease_num_collators() { assert_eq!(assigned_collators(), BTreeMap::new(),); run_to_block(11); - assert_eq!( - assigned_collators(), - BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), - (6, 1001), - (7, 1001), - (8, 1002), - (9, 1002), - ]), - ); + let initial_assignment = BTreeMap::from_iter(vec![ + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), + (6, 1001), + (7, 1001), + (8, 1002), + (9, 1002), + ]); + assert_eq!(assigned_collators(), initial_assignment,); MockData::mutate(|m| { m.collators = vec![]; }); run_to_block(21); - assert_eq!(assigned_collators(), BTreeMap::from_iter(vec![])); + // There are no collators but that would brick the chain, so we keep the old assignment + assert_eq!(assigned_collators(), initial_assignment); }); } @@ -513,6 +526,7 @@ fn assign_collators_stay_constant_if_new_collators_can_take_new_chains() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -524,7 +538,7 @@ fn assign_collators_stay_constant_if_new_collators_can_take_new_chains() { assert_eq!( assigned_collators(), - BTreeMap::from_iter(vec![(1, 999), (2, 999), (3, 999), (4, 999), (5, 999),]), + BTreeMap::from_iter(vec![(1, 1000), (2, 1000), (3, 1000), (4, 1000), (5, 1000),]), ); MockData::mutate(|m| { @@ -535,11 +549,11 @@ fn assign_collators_stay_constant_if_new_collators_can_take_new_chains() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 999), - (4, 999), - (5, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), (6, 1001), (7, 1001), (8, 1002), @@ -556,6 +570,7 @@ fn assign_collators_move_extra_container_chain_to_orchestrator_chain_if_not_enou MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -567,7 +582,7 @@ fn assign_collators_move_extra_container_chain_to_orchestrator_chain_if_not_enou assert_eq!( assigned_collators(), - BTreeMap::from_iter(vec![(1, 999), (2, 999), (3, 999), (4, 999),]), + BTreeMap::from_iter(vec![(1, 1000), (2, 1000), (3, 1000), (4, 1000),]), ); MockData::mutate(|m| { @@ -578,7 +593,7 @@ fn assign_collators_move_extra_container_chain_to_orchestrator_chain_if_not_enou assert_eq!( assigned_collators(), - BTreeMap::from_iter(vec![(1, 999), (2, 999), (5, 1001), (3, 1001), (4, 999),]), + BTreeMap::from_iter(vec![(1, 1000), (2, 1000), (3, 1000), (4, 1001), (5, 1001),]), ); }); } @@ -590,6 +605,7 @@ fn assign_collators_reorganize_container_chains_if_not_enough_collators() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -602,8 +618,8 @@ fn assign_collators_reorganize_container_chains_if_not_enough_collators() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), + (1, 1000), + (2, 1000), (3, 1001), (4, 1001), (5, 1002), @@ -627,13 +643,13 @@ fn assign_collators_reorganize_container_chains_if_not_enough_collators() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 1005), - (5, 1004), - (7, 999), - (9, 1004), - (11, 1005) + (1, 1000), + (2, 1000), + (3, 1001), + (5, 1002), + (7, 1000), + (9, 1001), + (11, 1002) ]), ); }); @@ -646,6 +662,7 @@ fn assign_collators_set_zero_per_container() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -658,18 +675,18 @@ fn assign_collators_set_zero_per_container() { assert_eq!( assigned_collators(), BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 1001), - (4, 1001), - (5, 1002), - (6, 1002), - (7, 1003), - (8, 1003), - (9, 1004), - (10, 1004), - (11, 999), - (12, 999) + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1001), + (6, 1001), + (7, 1002), + (8, 1002), + (9, 1003), + (10, 1003), + (11, 1004), + (12, 1004), ]), ); @@ -682,7 +699,7 @@ fn assign_collators_set_zero_per_container() { // There are 5 collators in total: 0x4 container chains, plus 5 in the orchestrator chain assert_eq!( assigned_collators(), - BTreeMap::from_iter(vec![(1, 999), (2, 999), (3, 999), (11, 999), (12, 999),]), + BTreeMap::from_iter(vec![(1, 1000), (2, 1000), (3, 1000), (4, 1000), (5, 1000),]), ); }); } @@ -694,6 +711,7 @@ fn assign_collators_rotation() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -704,18 +722,18 @@ fn assign_collators_rotation() { run_to_block(11); let initial_assignment = BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 1001), - (4, 1001), - (5, 1002), - (6, 1002), - (7, 1003), - (8, 1003), - (9, 1004), - (10, 1004), - (11, 999), - (12, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1001), + (6, 1001), + (7, 1002), + (8, 1002), + (9, 1003), + (10, 1003), + (11, 1004), + (12, 1004), ]); assert_eq!(assigned_collators(), initial_assignment,); @@ -743,18 +761,18 @@ fn assign_collators_rotation() { // Random assignment depends on the seed, shouldn't change unless the algorithm changes let shuffled_assignment = BTreeMap::from_iter(vec![ - (1, 1004), - (2, 999), - (3, 999), - (4, 1003), - (5, 1001), - (6, 1001), - (7, 999), - (8, 1002), - (9, 999), - (10, 1003), - (11, 1004), - (12, 1002), + (1, 1000), + (2, 1002), + (3, 1000), + (4, 1004), + (5, 1003), + (6, 1003), + (7, 1000), + (8, 1001), + (9, 1002), + (10, 1004), + (11, 1000), + (12, 1001), ]); assert_eq!(assigned_collators(), shuffled_assignment,); @@ -768,6 +786,7 @@ fn assign_collators_rotation_container_chains_are_shuffled() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -779,7 +798,7 @@ fn assign_collators_rotation_container_chains_are_shuffled() { run_to_block(11); let initial_assignment = - BTreeMap::from_iter(vec![(1, 999), (2, 999), (3, 1001), (4, 1001)]); + BTreeMap::from_iter(vec![(1, 1000), (2, 1000), (3, 1001), (4, 1001)]); assert_eq!(assigned_collators(), initial_assignment,); @@ -793,7 +812,46 @@ fn assign_collators_rotation_container_chains_are_shuffled() { // Random assignment depends on the seed, shouldn't change unless the algorithm changes // Test that container chains are shuffled because 1001 does not have priority let shuffled_assignment = - BTreeMap::from_iter(vec![(1, 999), (2, 1002), (3, 999), (4, 1002)]); + BTreeMap::from_iter(vec![(1, 1000), (2, 1002), (3, 1000), (4, 1002)]); + + assert_eq!(assigned_collators(), shuffled_assignment,); + }); +} + +#[test] +fn assign_collators_rotation_parathreads_are_shuffled() { + new_test_ext().execute_with(|| { + run_to_block(1); + + MockData::mutate(|m| { + m.collators_per_container = 2; + m.collators_per_parathread = 2; + m.min_orchestrator_chain_collators = 2; + m.max_orchestrator_chain_collators = 5; + + // 4 collators so we can only assign to one parathread + m.collators = vec![1, 2, 3, 4]; + m.parathreads = vec![5001, 5002]; + }); + assert_eq!(assigned_collators(), BTreeMap::new(),); + run_to_block(11); + + let initial_assignment = + BTreeMap::from_iter(vec![(1, 1000), (2, 1000), (3, 5001), (4, 5001)]); + + assert_eq!(assigned_collators(), initial_assignment,); + + MockData::mutate(|m| { + // Seed chosen manually to see the case where parathread 5002 is given priority + m.random_seed = [2; 32]; + }); + + run_to_block(26); + + // Random assignment depends on the seed, shouldn't change unless the algorithm changes + // Test that container chains are shuffled because 1001 does not have priority + let shuffled_assignment = + BTreeMap::from_iter(vec![(1, 1000), (2, 5002), (3, 1000), (4, 5002)]); assert_eq!(assigned_collators(), shuffled_assignment,); }); @@ -806,6 +864,7 @@ fn assign_collators_rotation_collators_are_shuffled() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -817,15 +876,15 @@ fn assign_collators_rotation_collators_are_shuffled() { run_to_block(11); let initial_assignment = BTreeMap::from_iter(vec![ - (1, 999), - (2, 999), - (3, 1001), - (4, 1001), - (5, 1002), - (6, 1002), - (7, 999), - (8, 999), - (9, 999), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1000), + (6, 1001), + (7, 1001), + (8, 1002), + (9, 1002), ]); assert_eq!(assigned_collators(), initial_assignment,); @@ -840,15 +899,15 @@ fn assign_collators_rotation_collators_are_shuffled() { // Test that collators are shuffled because collator 10 should be the last one to be assigned, // and here it is present let shuffled_assignment = BTreeMap::from_iter(vec![ - (1, 999), - (3, 1001), - (4, 1001), - (5, 1002), - (6, 999), - (7, 999), - (8, 999), - (9, 1002), - (10, 999), + (1, 1000), + (3, 1000), + (4, 1000), + (5, 1001), + (6, 1002), + (7, 1000), + (8, 1001), + (9, 1000), + (10, 1002), ]); assert_eq!(assigned_collators(), shuffled_assignment,); @@ -862,6 +921,7 @@ fn assign_collators_invulnerables_priority_orchestrator() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -874,18 +934,97 @@ fn assign_collators_invulnerables_priority_orchestrator() { run_to_block(11); let initial_assignment = BTreeMap::from_iter(vec![ - (100, 999), - (1, 999), - (2, 1001), - (3, 1001), - (4, 1002), - (5, 1002), - (6, 999), - (7, 999), - (8, 999), + (100, 1000), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1000), + (5, 1001), + (6, 1001), + (7, 1002), + (8, 1002), + ]); + + assert_eq!(assigned_collators(), initial_assignment,); + }); +} + +#[test] +fn assign_collators_invulnerables_priority_orchestrator_reassigned() { + new_test_ext().execute_with(|| { + run_to_block(1); + + MockData::mutate(|m| { + m.collators_per_container = 2; + m.collators_per_parathread = 2; + m.min_orchestrator_chain_collators = 2; + m.max_orchestrator_chain_collators = 5; + // Disable rotation because this test is long + m.full_rotation_period = Some(0); + + // 10 collators but we only need 9, so 1 collator will not be assigned + // ids >= 100 are invulnerables so 2 of them will always be assigned to the orchestrator + m.collators = vec![1, 2, 3, 4, 5, 100, 101, 102, 103, 104]; + m.container_chains = vec![1001, 1002]; + }); + assert_eq!(assigned_collators(), BTreeMap::new(),); + run_to_block(11); + + let initial_assignment = BTreeMap::from_iter(vec![ + (100, 1000), + (101, 1000), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1001), + (5, 1001), + (102, 1002), + (103, 1002), ]); assert_eq!(assigned_collators(), initial_assignment,); + + MockData::mutate(|m| { + // Remove invulnerable from orchestrator, the unassigned invulnerable will take its place + m.collators = vec![1, 2, 3, 4, 5, 101, 102, 103, 104]; + }); + + run_to_block(21); + + let assignment = BTreeMap::from_iter(vec![ + (104, 1000), + (101, 1000), + (1, 1000), + (2, 1000), + (3, 1000), + (4, 1001), + (5, 1001), + (102, 1002), + (103, 1002), + ]); + + assert_eq!(assigned_collators(), assignment,); + + MockData::mutate(|m| { + // Remove another invulnerable from orchestrator, there are no unassigned invulnerables so the ones in a + // container chain will move from the container chain to the orchestrator + m.collators = vec![1, 2, 3, 4, 5, 102, 103, 104]; + }); + + run_to_block(31); + + let assignment = BTreeMap::from_iter(vec![ + (104, 1000), + (102, 1000), + (1, 1000), + (2, 1000), + (3, 1002), + (4, 1001), + (5, 1001), + (103, 1002), + ]); + + assert_eq!(assigned_collators(), assignment,); }); } @@ -896,6 +1035,7 @@ fn assign_collators_all_invulnerables() { MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -907,15 +1047,15 @@ fn assign_collators_all_invulnerables() { run_to_block(11); let initial_assignment = BTreeMap::from_iter(vec![ - (101, 999), - (102, 999), - (103, 1001), - (104, 1001), - (105, 1002), - (106, 1002), - (107, 999), - (108, 999), - (109, 999), + (101, 1000), + (102, 1000), + (103, 1000), + (104, 1000), + (105, 1000), + (106, 1001), + (107, 1001), + (108, 1002), + (109, 1002), ]); assert_eq!(assigned_collators(), initial_assignment,); @@ -924,12 +1064,13 @@ fn assign_collators_all_invulnerables() { #[test] fn rotation_events() { - // Ensure that the NewPendingAssignment is correct + // Ensure that the NewPendingAssignment event is correct new_test_ext().execute_with(|| { run_to_block(1); MockData::mutate(|m| { m.collators_per_container = 2; + m.collators_per_parathread = 2; m.min_orchestrator_chain_collators = 2; m.max_orchestrator_chain_collators = 5; @@ -1014,3 +1155,89 @@ fn rotation_events() { } }); } + +#[test] +fn assign_collators_remove_from_orchestator_when_all_assigned() { + new_test_ext().execute_with(|| { + run_to_block(1); + + MockData::mutate(|m| { + m.collators_per_container = 2; + m.collators_per_parathread = 2; + m.min_orchestrator_chain_collators = 2; + m.max_orchestrator_chain_collators = 2; + + m.collators = vec![1, 2]; + m.container_chains = vec![1001]; + }); + assert_eq!(assigned_collators(), BTreeMap::new(),); + run_to_block(11); + + let initial_assignment = BTreeMap::from_iter(vec![(1, 1000), (2, 1000)]); + + assert_eq!(assigned_collators(), initial_assignment,); + + MockData::mutate(|m| { + m.collators = vec![1, 2, 3, 4]; + }); + + run_to_block(26); + + let assignment = BTreeMap::from_iter(vec![(1, 1000), (2, 1000), (3, 1001), (4, 1001)]); + assert_eq!(assigned_collators(), assignment,); + + MockData::mutate(|m| { + m.collators = vec![1, 3, 4]; + }); + + run_to_block(36); + + let assignment = BTreeMap::from_iter(vec![(1, 1000), (3, 1000)]); + + assert_eq!(assigned_collators(), assignment,); + + MockData::mutate(|m| { + m.collators = vec![3, 4]; + }); + + run_to_block(46); + + let assignment = BTreeMap::from_iter(vec![(3, 1000), (4, 1000)]); + + assert_eq!(assigned_collators(), assignment,); + }); +} + +#[test] +fn collator_assignment_includes_empty_chains() { + new_test_ext().execute_with(|| { + run_to_block(1); + + MockData::mutate(|m| { + m.collators_per_container = 2; + m.collators_per_parathread = 2; + m.min_orchestrator_chain_collators = 2; + m.max_orchestrator_chain_collators = 2; + + m.collators = vec![1, 2]; + m.container_chains = vec![2000, 2001, 2002]; + m.parathreads = vec![3000, 3001, 3002] + }); + assert_eq!(assigned_collators(), BTreeMap::new(),); + run_to_block(11); + + let assigned_collators = CollatorContainerChain::::get(); + let expected = AssignedCollators { + orchestrator_chain: vec![1, 2], + container_chains: BTreeMap::from_iter(vec![ + (2000.into(), vec![]), + (2001.into(), vec![]), + (2002.into(), vec![]), + (3000.into(), vec![]), + (3001.into(), vec![]), + (3002.into(), vec![]), + ]), + }; + assert_eq!(assigned_collators, expected); + }); +} diff --git a/pallets/collator-assignment/src/tests/assign_full.rs b/pallets/collator-assignment/src/tests/assign_full.rs new file mode 100644 index 000000000..2ac36e2f3 --- /dev/null +++ b/pallets/collator-assignment/src/tests/assign_full.rs @@ -0,0 +1,90 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + +use crate::assignment::AssignmentError; +use { + crate::{assignment::Assignment, tests::Test}, + sp_std::collections::btree_map::BTreeMap, +}; + +#[test] +fn assign_full_old_assigned_priority() { + // Collators in old_assigned will be selected before other collators + let collators = vec![1, 2, 3, 4, 5]; + let container_chains = vec![(1000.into(), 5)]; + let old_assigned = BTreeMap::from_iter(vec![(1000.into(), vec![3, 4])]); + + let new_assigned = + Assignment::::assign_full(collators, container_chains, old_assigned).unwrap(); + let expected = BTreeMap::from_iter(vec![(1000.into(), vec![3, 4, 1, 2, 5])]); + assert_eq!(new_assigned, expected); +} + +#[test] +fn assign_full_invalid_old_assigned_collators_removed() { + // If the collators in old_assigned are no longer collators, they are not assigned + let collators = vec![1, 2, 3, 4, 5]; + let container_chains = vec![(1000.into(), 5)]; + let old_assigned = BTreeMap::from_iter(vec![(1000.into(), vec![20, 21])]); + + let new_assigned = + Assignment::::assign_full(collators, container_chains, old_assigned).unwrap(); + let expected = BTreeMap::from_iter(vec![(1000.into(), vec![1, 2, 3, 4, 5])]); + assert_eq!(new_assigned, expected); +} + +#[test] +fn assign_full_invalid_chains_removed() { + // Mark all collators as already assigned to a chain that does not exist. Should treat them as not assigned. + let collators = vec![1, 2, 3, 4, 5]; + let container_chains = vec![(1000.into(), 5)]; + let old_assigned = BTreeMap::from_iter(vec![(1001.into(), vec![1, 2, 3, 4, 5])]); + + let new_assigned = + Assignment::::assign_full(collators, container_chains, old_assigned).unwrap(); + let expected = BTreeMap::from_iter(vec![(1000.into(), vec![1, 2, 3, 4, 5])]); + assert_eq!(new_assigned, expected); +} + +#[test] +fn assign_full_truncates_collators() { + // Need 2 collators for each chain, when old_assigned has more than 2. Should truncate old_assigned to 2. + let collators = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let container_chains = vec![(1000.into(), 2), (2000.into(), 2)]; + let old_assigned = BTreeMap::from_iter(vec![ + (1000.into(), vec![1, 2, 3, 4, 5]), + (2000.into(), vec![6, 7, 8, 9, 10]), + ]); + + let new_assigned = + Assignment::::assign_full(collators, container_chains, old_assigned).unwrap(); + let expected = BTreeMap::from_iter(vec![(1000.into(), vec![1, 2]), (2000.into(), vec![6, 7])]); + assert_eq!(new_assigned, expected); +} + +#[test] +fn assign_full_old_assigned_error_if_not_enough_collators() { + // Need 4 collators, only have 2, and all 2 were assigned to the second chain. If the function did not panic, we + // would have 0 collators assigned to the first chain, which is supposed to have priority. + let collators = vec![1, 2]; + let container_chains = vec![(1000.into(), 2), (2000.into(), 2)]; + let old_assigned = BTreeMap::from_iter(vec![(2000.into(), vec![1, 2])]); + let new_assigned = Assignment::::assign_full(collators, container_chains, old_assigned); + assert_eq!( + new_assigned.unwrap_err(), + AssignmentError::NotEnoughCollators + ); +} diff --git a/pallets/collator-assignment/src/tests/prioritize_invulnerables.rs b/pallets/collator-assignment/src/tests/prioritize_invulnerables.rs new file mode 100644 index 000000000..b9c57769b --- /dev/null +++ b/pallets/collator-assignment/src/tests/prioritize_invulnerables.rs @@ -0,0 +1,186 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + +use { + crate::{ + assignment::{Assignment, ChainNumCollators}, + tests::Test, + }, + sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}, +}; + +#[test] +fn invulnerable_priority_0_collators() { + let collators = vec![]; + let orchestrator_chain = ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }; + let mut old_assigned = BTreeMap::new(); + + let num_invulnerables = Assignment::::prioritize_invulnerables( + &collators, + orchestrator_chain, + &mut old_assigned, + ); + + assert_eq!(num_invulnerables, 0); +} + +#[test] +fn invulnerable_priority_0_invulnerables() { + let collators = vec![1, 2, 3, 4, 5]; + let orchestrator_chain = ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }; + let mut old_assigned = BTreeMap::from_iter(vec![(1000.into(), vec![1, 2])]); + + let num_invulnerables = Assignment::::prioritize_invulnerables( + &collators, + orchestrator_chain, + &mut old_assigned, + ); + + assert_eq!(num_invulnerables, 0); +} + +#[test] +fn invulnerable_priority_1_invulnerable_orchestrator() { + let collators = vec![1, 2, 3, 4, 5, 101]; + let orchestrator_chain = ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }; + let mut old_assigned = BTreeMap::from_iter(vec![(1000.into(), vec![101])]); + + let num_invulnerables = Assignment::::prioritize_invulnerables( + &collators, + orchestrator_chain, + &mut old_assigned, + ); + + assert_eq!(num_invulnerables, 1); +} + +#[test] +fn invulnerable_priority_1_invulnerable_not_assigned() { + let collators = vec![1, 2, 3, 4, 5, 101]; + let orchestrator_chain = ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }; + let mut old_assigned = BTreeMap::new(); + + let num_invulnerables = Assignment::::prioritize_invulnerables( + &collators, + orchestrator_chain, + &mut old_assigned, + ); + + assert_eq!(num_invulnerables, 1); +} + +#[test] +fn invulnerable_priority_1_invulnerable_assigned_to_another_chain() { + let collators = vec![1, 2, 3, 4, 5, 101]; + let orchestrator_chain = ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }; + let mut old_assigned = + BTreeMap::from_iter(vec![(1000.into(), vec![]), (2000.into(), vec![101])]); + + let num_invulnerables = Assignment::::prioritize_invulnerables( + &collators, + orchestrator_chain, + &mut old_assigned, + ); + + assert_eq!(num_invulnerables, 1); +} + +#[test] +fn bug_same_invulnerable_selected_twice() { + let collators = vec![100]; + let orchestrator_chain = ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }; + let mut old_assigned = BTreeMap::from_iter(vec![(1000.into(), vec![100])]); + + let num_invulnerables = Assignment::::prioritize_invulnerables( + &collators, + orchestrator_chain, + &mut old_assigned, + ); + + assert_eq!(num_invulnerables, 1); +} + +#[test] +fn bug_not_using_assigned_invulnerables() { + // There are 3 invulnerables, 1 assigned to orchestrator and 2 assigned to a container chain. + // After `prioritize_invulnerables` the first one from the container should move to orchestrator + let collators = vec![1, 2, 3, 4, 5, 102, 103, 104]; + + let container_chains = vec![ + ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }, + ChainNumCollators { + para_id: 2000.into(), + min_collators: 2, + max_collators: 2, + }, + ChainNumCollators { + para_id: 2001.into(), + min_collators: 2, + max_collators: 2, + }, + ]; + let orchestrator_chain = container_chains[0]; + + let mut old_assigned = BTreeMap::from_iter(vec![ + (1000.into(), vec![101, 104, 1, 2, 3]), + (2000.into(), vec![4, 5]), + (2001.into(), vec![102, 103]), + ]); + + let chains_with_collators_set = + BTreeSet::from_iter(container_chains.iter().map(|cc| cc.para_id)); + let collators_set = BTreeSet::from_iter(collators.iter().cloned()); + Assignment::::retain_valid_old_assigned( + &mut old_assigned, + &chains_with_collators_set, + &collators_set, + ); + let num_invulnerables = Assignment::::prioritize_invulnerables( + &collators, + orchestrator_chain, + &mut old_assigned, + ); + + assert_eq!(num_invulnerables, 2); +} diff --git a/pallets/collator-assignment/src/tests/select_chains.rs b/pallets/collator-assignment/src/tests/select_chains.rs new file mode 100644 index 000000000..c524c0c86 --- /dev/null +++ b/pallets/collator-assignment/src/tests/select_chains.rs @@ -0,0 +1,176 @@ +// Copyright (C) Moondance Labs Ltd. +// This file is part of Tanssi. + +// Tanssi is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Tanssi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Tanssi. If not, see + +use crate::{ + assignment::{Assignment, ChainNumCollators}, + tests::Test, +}; + +#[test] +fn select_chains_not_enough_to_reach_min_container() { + // 10 collators when the orchestrator needs 2 and the containers need 10 result in no containers having collators + let container_chains = vec![ + ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }, + ChainNumCollators { + para_id: 2000.into(), + min_collators: 10, + max_collators: 10, + }, + ChainNumCollators { + para_id: 2001.into(), + min_collators: 10, + max_collators: 10, + }, + ]; + let new_assigned = Assignment::::select_chains_with_collators(10, &container_chains); + assert_eq!(new_assigned, vec![(1000.into(), 5),]); +} + +#[test] +fn select_chains_not_enough_to_reach_min_orchestrator() { + // 1 collator when the orchestrator needs 2 results in 1 collators being assigned to orchestrator + let container_chains = vec![ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }]; + let new_assigned = Assignment::::select_chains_with_collators(1, &container_chains); + assert_eq!(new_assigned, vec![(1000.into(), 1),]); +} + +#[test] +fn select_chains_not_enough_for_all_min() { + // Need 6 collators to support 3 chains, only have 5. The last chain will be removed and the remaining collator + // will be assigned to orchestrator. + let container_chains = vec![ + ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }, + ChainNumCollators { + para_id: 2000.into(), + min_collators: 2, + max_collators: 2, + }, + ChainNumCollators { + para_id: 2001.into(), + min_collators: 2, + max_collators: 2, + }, + ]; + let new_assigned = Assignment::::select_chains_with_collators(5, &container_chains); + assert_eq!(new_assigned, vec![(1000.into(), 3), (2000.into(), 2),]); +} + +#[test] +fn select_chains_not_enough_for_all_max() { + // Need 6 collators to support 3 chains at min, but 15 collators to support them at max. + // The last chain will be removed and the remaining collator + let container_chains = vec![ + ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }, + ChainNumCollators { + para_id: 2000.into(), + min_collators: 2, + max_collators: 5, + }, + ChainNumCollators { + para_id: 2001.into(), + min_collators: 2, + max_collators: 5, + }, + ]; + let new_assigned = Assignment::::select_chains_with_collators(7, &container_chains); + assert_eq!( + new_assigned, + vec![(1000.into(), 3), (2000.into(), 2), (2001.into(), 2),] + ); + let new_assigned = Assignment::::select_chains_with_collators(10, &container_chains); + assert_eq!( + new_assigned, + vec![(1000.into(), 5), (2000.into(), 3), (2001.into(), 2),] + ); + let new_assigned = Assignment::::select_chains_with_collators(13, &container_chains); + assert_eq!( + new_assigned, + vec![(1000.into(), 5), (2000.into(), 5), (2001.into(), 3),] + ); + let new_assigned = Assignment::::select_chains_with_collators(15, &container_chains); + assert_eq!( + new_assigned, + vec![(1000.into(), 5), (2000.into(), 5), (2001.into(), 5),] + ); +} + +#[test] +fn select_chains_more_than_max() { + // When the number of collators is greater than the sum of the max, all the chains are assigned max collators + let container_chains = vec![ + ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }, + ChainNumCollators { + para_id: 2000.into(), + min_collators: 2, + max_collators: 5, + }, + ChainNumCollators { + para_id: 2001.into(), + min_collators: 2, + max_collators: 5, + }, + ]; + let new_assigned = Assignment::::select_chains_with_collators(20, &container_chains); + assert_eq!( + new_assigned, + vec![(1000.into(), 5), (2000.into(), 5), (2001.into(), 5),] + ); +} + +#[test] +fn select_chains_not_enough_to_reach_min_container_but_enough_for_parathread() { + // Chain 2000 has more priority than parathread 3000, but we do not have enough min collators so the container + // chain gets 0 collator and the parathread gets 1 + let container_chains = vec![ + ChainNumCollators { + para_id: 1000.into(), + min_collators: 2, + max_collators: 5, + }, + ChainNumCollators { + para_id: 2000.into(), + min_collators: 2, + max_collators: 2, + }, + ChainNumCollators { + para_id: 3000.into(), + min_collators: 1, + max_collators: 1, + }, + ]; + let new_assigned = Assignment::::select_chains_with_collators(3, &container_chains); + assert_eq!(new_assigned, vec![(1000.into(), 2), (3000.into(), 1)]); +} diff --git a/pallets/configuration/src/lib.rs b/pallets/configuration/src/lib.rs index ecbcd6d87..0a7a035ca 100644 --- a/pallets/configuration/src/lib.rs +++ b/pallets/configuration/src/lib.rs @@ -74,9 +74,12 @@ pub struct HostConfiguration { pub max_orchestrator_collators: u32, /// How many collators to assign to one container chain pub collators_per_container: u32, - /// If this value is 0 means that there is no rotation + /// Rotate all collators once every n sessions. If this value is 0 means that there is no rotation pub full_rotation_period: u32, /// How many collators to assign to one parathread + // TODO: for now we only support 1 collator per parathread because using Aura for consensus conflicts with + // the idea of being able to create blocks every n slots: if there are 2 collators and we create blocks + // every 2 slots, 1 collator will create all the blocks. pub collators_per_parathread: u32, /// How many parathreads can be assigned to one collator pub parathreads_per_collator: u32, @@ -535,6 +538,19 @@ pub mod pallet { config.collators_per_container } + fn collators_per_parathread(session_index: T::SessionIndex) -> u32 { + let (past_and_present, _) = Pallet::::pending_configs() + .into_iter() + .partition::, _>(|&(apply_at_session, _)| apply_at_session <= session_index); + + let config = if let Some(last) = past_and_present.last() { + last.1.clone() + } else { + Pallet::::config() + }; + config.collators_per_parathread + } + fn min_collators_for_orchestrator(session_index: T::SessionIndex) -> u32 { let (past_and_present, _) = Pallet::::pending_configs() .into_iter() diff --git a/pallets/registrar/src/lib.rs b/pallets/registrar/src/lib.rs index 3dc3d8b38..994cd4644 100644 --- a/pallets/registrar/src/lib.rs +++ b/pallets/registrar/src/lib.rs @@ -905,6 +905,11 @@ pub mod pallet { paras.into_iter().collect() } + fn session_parathreads(_session_index: T::SessionIndex) -> Vec { + // FIXME(parathreads) + vec![] + } + #[cfg(feature = "runtime-benchmarks")] fn set_session_container_chains( _session_index: T::SessionIndex, diff --git a/primitives/traits/src/lib.rs b/primitives/traits/src/lib.rs index 32aba7f6c..3b0537cee 100644 --- a/primitives/traits/src/lib.rs +++ b/primitives/traits/src/lib.rs @@ -76,6 +76,7 @@ pub trait GetCurrentContainerChains { /// session index. pub trait GetSessionContainerChains { fn session_container_chains(session_index: SessionIndex) -> Vec; + fn session_parathreads(session_index: SessionIndex) -> Vec; #[cfg(feature = "runtime-benchmarks")] fn set_session_container_chains(session_index: SessionIndex, container_chains: &[ParaId]); } @@ -93,6 +94,7 @@ pub trait GetHostConfiguration { fn min_collators_for_orchestrator(session_index: SessionIndex) -> u32; fn max_collators_for_orchestrator(session_index: SessionIndex) -> u32; fn collators_per_container(session_index: SessionIndex) -> u32; + fn collators_per_parathread(session_index: SessionIndex) -> u32; } /// Returns current session index. diff --git a/runtime/dancebox/tests/integration_test.rs b/runtime/dancebox/tests/integration_test.rs index c5843a778..4f340e3e2 100644 --- a/runtime/dancebox/tests/integration_test.rs +++ b/runtime/dancebox/tests/integration_test.rs @@ -1297,12 +1297,9 @@ fn test_author_collation_aura_add_assigned_to_paras_runtime_api() { run_to_session(4u32); assert_eq!( Runtime::parachain_collators(100.into()), - Some(vec![ALICE.into()]) - ); - assert_eq!( - Runtime::parachain_collators(1001.into()), - Some(vec![CHARLIE.into(), DAVE.into()]) + Some(vec![ALICE.into(), CHARLIE.into()]) ); + assert_eq!(Runtime::parachain_collators(1001.into()), Some(vec![])); assert_eq!( Runtime::current_collator_parachain_assignment(BOB.into()), None diff --git a/runtime/flashbox/tests/integration_test.rs b/runtime/flashbox/tests/integration_test.rs index 8ff1c6e36..59dd94bb8 100644 --- a/runtime/flashbox/tests/integration_test.rs +++ b/runtime/flashbox/tests/integration_test.rs @@ -1283,12 +1283,9 @@ fn test_author_collation_aura_add_assigned_to_paras_runtime_api() { run_to_session(4u32); assert_eq!( Runtime::parachain_collators(100.into()), - Some(vec![ALICE.into()]) - ); - assert_eq!( - Runtime::parachain_collators(1001.into()), - Some(vec![CHARLIE.into(), DAVE.into()]) + Some(vec![ALICE.into(), CHARLIE.into()]) ); + assert_eq!(Runtime::parachain_collators(1001.into()), Some(vec![])); assert_eq!( Runtime::current_collator_parachain_assignment(BOB.into()), None diff --git a/test/scripts/build-spec-warp-sync.sh b/test/scripts/build-spec-warp-sync.sh index ec4e70c49..0215a1942 100755 --- a/test/scripts/build-spec-warp-sync.sh +++ b/test/scripts/build-spec-warp-sync.sh @@ -8,4 +8,4 @@ cd $(dirname $0)/.. mkdir -p specs ../target/release/container-chain-template-simple-node build-spec --disable-default-bootnode --add-bootnode "/ip4/127.0.0.1/tcp/33049/ws/p2p/12D3KooWHVMhQDHBpj9vQmssgyfspYecgV6e3hH1dQVDUkUbCYC9" --parachain-id 2000 --raw > specs/warp-sync-template-container-2000.json -../target/release/tanssi-node build-spec --chain dancebox-local --parachain-id 1000 --add-container-chain specs/warp-sync-template-container-2000.json --invulnerable "Collator1000-01" --invulnerable "Collator1000-02" --invulnerable "Collator2000-01" --invulnerable "Collator2000-02" --invulnerable "Collator1000-03" > specs/warp-sync-tanssi-1000.json +../target/release/tanssi-node build-spec --chain dancebox-local --parachain-id 1000 --add-container-chain specs/warp-sync-template-container-2000.json --invulnerable "Collator1000-01" --invulnerable "Collator1000-02" --invulnerable "Collator1000-03" --invulnerable "Collator2000-01" --invulnerable "Collator2000-02" > specs/warp-sync-tanssi-1000.json diff --git a/test/scripts/build-spec.sh b/test/scripts/build-spec.sh index d95622194..b02ab2375 100755 --- a/test/scripts/build-spec.sh +++ b/test/scripts/build-spec.sh @@ -10,4 +10,4 @@ mkdir -p specs ../target/release/container-chain-template-simple-node build-spec --disable-default-bootnode --add-bootnode "/ip4/127.0.0.1/tcp/33049/ws/p2p/12D3KooWHVMhQDHBpj9vQmssgyfspYecgV6e3hH1dQVDUkUbCYC9" --parachain-id 2000 --raw > specs/template-container-2000.json ../target/release/container-chain-template-frontier-node build-spec --disable-default-bootnode --add-bootnode "/ip4/127.0.0.1/tcp/33050/ws/p2p/12D3KooWFGaw1rxB6MSuN3ucuBm7hMq5pBFJbEoqTyth4cG483Cc" --parachain-id 2001 --raw > specs/template-container-2001.json ../target/release/container-chain-template-simple-node build-spec --disable-default-bootnode --parachain-id 2002 --raw > specs/template-container-2002.json -../target/release/tanssi-node build-spec --chain dancebox-local --parachain-id 1000 --add-container-chain specs/template-container-2000.json --add-container-chain specs/template-container-2001.json --invulnerable "Collator1000-01" --invulnerable "Collator1000-02" --invulnerable "Collator2000-01" --invulnerable "Collator2000-02" --invulnerable "Collator2001-01" --invulnerable "Collator2001-02" --invulnerable "Collator2002-01" --invulnerable "Collator2002-02" > specs/tanssi-1000.json +../target/release/tanssi-node build-spec --chain dancebox-local --parachain-id 1000 --add-container-chain specs/template-container-2000.json --add-container-chain specs/template-container-2001.json --invulnerable "Collator1000-01" --invulnerable "Collator1000-02" --invulnerable "Collator2002-01" --invulnerable "Collator2002-02" --invulnerable "Collator2000-01" --invulnerable "Collator2000-02" --invulnerable "Collator2001-01" --invulnerable "Collator2001-02" > specs/tanssi-1000.json diff --git a/test/suites/dev-tanssi/session-keys/test_remove_session_key_staking.ts b/test/suites/dev-tanssi/session-keys/test_remove_session_key_staking.ts index be36e8601..3b64c8970 100644 --- a/test/suites/dev-tanssi/session-keys/test_remove_session_key_staking.ts +++ b/test/suites/dev-tanssi/session-keys/test_remove_session_key_staking.ts @@ -3,7 +3,7 @@ import { describeSuite, expect, beforeAll } from "@moonwall/cli"; import { KeyringPair } from "@moonwall/util"; import { ApiPromise } from "@polkadot/api"; import { jumpSessions } from "../../../util/block"; -import { DANCE, STAKING_ACCOUNT } from "util/constants"; +import { DANCE } from "util/constants"; describeSuite({ id: "DT0202", @@ -13,10 +13,12 @@ describeSuite({ let polkadotJs: ApiPromise; let alice: KeyringPair; let bob: KeyringPair; + let charlie: KeyringPair; beforeAll(async () => { alice = context.keyring.alice; bob = context.keyring.bob; + charlie = context.keyring.charlie; polkadotJs = context.polkadotJs(); let aliceNonce = (await polkadotJs.rpc.system.accountNextIndex(alice.address)).toNumber(); @@ -25,16 +27,13 @@ describeSuite({ // We need to remove from invulnerables and add to staking // for that we need to remove Alice and Bob from invulnerables first - // Additionally, we need to pass to the staking account the minimum balance // We delegate with manual rewards to make sure the candidate does not update position - const existentialDeposit = polkadotJs.consts.balances.existentialDeposit; - + // We also need charlie to join staking because the settings for the dev environment are 1 collator for + // tanssi and 2 collators for containers, so we need 3 collators for bob to be assigned. await context.createBlock([ + // Remove all invulnerables, otherwise they have priority await polkadotJs.tx.sudo - .sudo(polkadotJs.tx.invulnerables.removeInvulnerable(alice.address)) - .signAsync(context.keyring.alice, { nonce: aliceNonce++ }), - await polkadotJs.tx.sudo - .sudo(polkadotJs.tx.invulnerables.removeInvulnerable(bob.address)) + .sudo(polkadotJs.tx.invulnerables.setInvulnerables([])) .signAsync(context.keyring.alice, { nonce: aliceNonce++ }), await polkadotJs.tx.pooledStaking .requestDelegate(alice.address, "ManualRewards", 10000n * DANCE) @@ -42,9 +41,9 @@ describeSuite({ await polkadotJs.tx.pooledStaking .requestDelegate(bob.address, "ManualRewards", 10000n * DANCE) .signAsync(context.keyring.bob, { nonce: bobNonce++ }), - await polkadotJs.tx.balances - .transferAllowDeath(STAKING_ACCOUNT, existentialDeposit) - .signAsync(context.keyring.bob, { nonce: bobNonce++ }), + await polkadotJs.tx.pooledStaking + .requestDelegate(charlie.address, "ManualRewards", 10000n * DANCE) + .signAsync(context.keyring.charlie, { nonce: 0 }), ]); // At least 2 sessions for the change to have effect await jumpSessions(context, 2); diff --git a/test/suites/dev-tanssi/staking/test_staking_rewards_balanced.ts b/test/suites/dev-tanssi/staking/test_staking_rewards_balanced.ts index ce2c1ac74..07d80f50b 100644 --- a/test/suites/dev-tanssi/staking/test_staking_rewards_balanced.ts +++ b/test/suites/dev-tanssi/staking/test_staking_rewards_balanced.ts @@ -9,7 +9,7 @@ import { filterRewardStakingDelegators, jumpSessions, } from "util/block"; -import { DANCE, STAKING_ACCOUNT } from "util/constants"; +import { DANCE } from "util/constants"; describeSuite({ id: "DT0302", @@ -32,15 +32,10 @@ describeSuite({ // we will make each of them delegate the other with 50% // Alice autocompounding, Bob will be manual - // Additionally, we need to pass to the staking account the minimum balance - const existentialDeposit = polkadotJs.consts.balances.existentialDeposit; - await context.createBlock([ + // Remove all invulnerables, otherwise they have priority await polkadotJs.tx.sudo - .sudo(polkadotJs.tx.invulnerables.removeInvulnerable(alice.address)) - .signAsync(context.keyring.alice, { nonce: aliceNonce++ }), - await polkadotJs.tx.sudo - .sudo(polkadotJs.tx.invulnerables.removeInvulnerable(bob.address)) + .sudo(polkadotJs.tx.invulnerables.setInvulnerables([])) .signAsync(context.keyring.alice, { nonce: aliceNonce++ }), await polkadotJs.tx.pooledStaking .requestDelegate(alice.address, "AutoCompounding", 10000n * DANCE) @@ -54,9 +49,6 @@ describeSuite({ await polkadotJs.tx.pooledStaking .requestDelegate(bob.address, "ManualRewards", 10000n * DANCE) .signAsync(context.keyring.bob, { nonce: bobNonce++ }), - await polkadotJs.tx.balances - .transferAllowDeath(STAKING_ACCOUNT, existentialDeposit) - .signAsync(context.keyring.bob, { nonce: bobNonce++ }), ]); // At least 2 sessions for the change to have effect await jumpSessions(context, 2); diff --git a/test/suites/dev-tanssi/staking/test_staking_rewards_non_balanced.ts b/test/suites/dev-tanssi/staking/test_staking_rewards_non_balanced.ts index b2b2174b3..7b57ee5c5 100644 --- a/test/suites/dev-tanssi/staking/test_staking_rewards_non_balanced.ts +++ b/test/suites/dev-tanssi/staking/test_staking_rewards_non_balanced.ts @@ -9,7 +9,7 @@ import { filterRewardStakingDelegators, jumpSessions, } from "util/block"; -import { DANCE, STAKING_ACCOUNT } from "util/constants"; +import { DANCE } from "util/constants"; describeSuite({ id: "DT0303", @@ -32,15 +32,10 @@ describeSuite({ // we will make each of them delegate the other with 50% // Alice autocompounding, Bob will be manual - // Additionally, we need to pass to the staking account the minimum balance - const existentialDeposit = polkadotJs.consts.balances.existentialDeposit; - await context.createBlock([ + // Remove all invulnerables, otherwise they have priority await polkadotJs.tx.sudo - .sudo(polkadotJs.tx.invulnerables.removeInvulnerable(alice.address)) - .signAsync(context.keyring.alice, { nonce: aliceNonce++ }), - await polkadotJs.tx.sudo - .sudo(polkadotJs.tx.invulnerables.removeInvulnerable(bob.address)) + .sudo(polkadotJs.tx.invulnerables.setInvulnerables([])) .signAsync(context.keyring.alice, { nonce: aliceNonce++ }), await polkadotJs.tx.pooledStaking .requestDelegate(alice.address, "AutoCompounding", 18000n * DANCE) @@ -54,9 +49,6 @@ describeSuite({ await polkadotJs.tx.pooledStaking .requestDelegate(bob.address, "ManualRewards", 2000n * DANCE) .signAsync(context.keyring.bob, { nonce: bobNonce++ }), - await polkadotJs.tx.balances - .transferAllowDeath(STAKING_ACCOUNT, existentialDeposit) - .signAsync(context.keyring.bob, { nonce: bobNonce++ }), ]); // At least 2 sessions for the change to have effect await jumpSessions(context, 2);