1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16
use {
17
    dp_collator_assignment::AssignedCollators,
18
    frame_support::traits::Get,
19
    sp_runtime::Saturating,
20
    sp_std::{
21
        cmp,
22
        collections::{btree_map::BTreeMap, btree_set::BTreeSet},
23
        marker::PhantomData,
24
        mem,
25
        vec::Vec,
26
    },
27
    tp_traits::{
28
        FullRotationMode, FullRotationModes, ParaId, RemoveInvulnerables as RemoveInvulnerablesT,
29
    },
30
};
31

            
32
// Separate import of `sp_std::vec!` macro, which cause issues with rustfmt if grouped
33
// with `sp_std::vec::Vec`.
34
use sp_std::vec;
35

            
36
/// Helper methods to implement collator assignment algorithm
37
pub struct Assignment<T>(PhantomData<T>);
38

            
39
impl<T> Assignment<T>
40
where
41
    T: crate::Config,
42
{
43
    /// Assign new collators to missing container_chains.
44
    /// Old collators always have preference to remain on the same chain.
45
    /// If there are no missing collators, nothing is changed.
46
    ///
47
    /// `chains` should be shuffled or at least rotated on every session to ensure
48
    /// a fair distribution, because the order of that list affects container chain priority:
49
    /// the first chain on that list will be the first one to get new collators.
50
    ///
51
    /// Similarly, in the `collators` list order means priority, the first collators will be more
52
    /// likely to get assigned. Unlike the list of `chains` which should already be shuffled,
53
    /// collators will be shuffled using the `shuffle` callback when needed. This allows the
54
    /// algorithm to truncate the list of collators and only shuffle the first N. This ensures that
55
    /// shuffling doesn't cause a collator with low priority to be assigned instead of a collator
56
    /// with higher priority.
57
4831
    pub fn assign_collators_always_keep_old<TShuffle>(
58
4831
        collators: Vec<T::AccountId>,
59
4831
        orchestrator_chain: ChainNumCollators,
60
4831
        mut chains: Vec<ChainNumCollators>,
61
4831
        mut old_assigned: AssignedCollators<T::AccountId>,
62
4831
        mut shuffle: Option<TShuffle>,
63
4831
        full_rotation_mode: FullRotationModes,
64
4831
    ) -> Result<AssignedCollators<T::AccountId>, AssignmentError>
65
4831
    where
66
4831
        TShuffle: FnMut(&mut Vec<T::AccountId>),
67
4831
    {
68
4831
        if collators.is_empty() && !T::ForceEmptyOrchestrator::get() {
69
4
            return Err(AssignmentError::ZeroCollators);
70
4827
        }
71
4827
        // The rest of this function mostly treats orchestrator chain as another container chain, so move it into
72
4827
        // `old_assigned.container_chains`
73
4827
        let old_orchestrator_assigned = mem::take(&mut old_assigned.orchestrator_chain);
74
4827
        old_assigned
75
4827
            .container_chains
76
4827
            .insert(orchestrator_chain.para_id, old_orchestrator_assigned);
77
4827
        let mut old_assigned = old_assigned.container_chains;
78
4827
        // Orchestrator chain must be the first one in the list because it always has priority
79
4827
        chains.insert(0, orchestrator_chain);
80
10935
        let all_para_ids: Vec<ParaId> = chains.iter().map(|cc| cc.para_id).collect();
81
4827
        let collators_set = BTreeSet::from_iter(collators.iter().cloned());
82
4827
        let chains_with_collators =
83
4827
            Self::select_chains_with_collators(collators.len() as u32, &chains);
84
4827
        let chains_with_collators_set: BTreeSet<ParaId> = chains_with_collators
85
4827
            .iter()
86
8393
            .map(|(para_id, _num_collators)| *para_id)
87
4827
            .collect();
88
4827
        Self::retain_valid_old_assigned(
89
4827
            &mut old_assigned,
90
4827
            &chains_with_collators_set,
91
4827
            &collators_set,
92
4827
        );
93

            
94
        // Remove some previously assigned collators to allow new collators to take their place.
95
        // Based on full_rotation_mode. In regular sessions this is FullRotationMode::KeepAll, a no-op.
96
10935
        for chain in chains.iter() {
97
10935
            let mode = if chain.para_id == orchestrator_chain.para_id {
98
4827
                full_rotation_mode.orchestrator.clone()
99
6108
            } else if chain.parathread {
100
531
                full_rotation_mode.parathread.clone()
101
            } else {
102
5577
                full_rotation_mode.parachain.clone()
103
            };
104

            
105
10935
            let collators = old_assigned.get_mut(&chain.para_id);
106
10935
            Self::keep_collator_subset(collators, mode, chain.max_collators, shuffle.as_mut());
107
        }
108

            
109
        // Ensure the first `min_orchestrator_collators` of orchestrator chain are invulnerables
110
        // Invulnerables can be unassigned by `keep_collator_subset`, but here we will assign other
111
        // invulnerables again. The downside is that the new invulnerables can be different.
112
4827
        Self::prioritize_invulnerables(&collators, orchestrator_chain, &mut old_assigned);
113

            
114
4827
        let new_assigned_chains =
115
4827
            Self::assign_full(collators, chains_with_collators, old_assigned, shuffle)?;
116

            
117
4827
        let mut new_assigned = AssignedCollators {
118
4827
            container_chains: new_assigned_chains,
119
4827
            ..Default::default()
120
4827
        };
121

            
122
        // Add container chains with 0 collators so that they are shown in UI
123
15762
        for para_id in all_para_ids {
124
10935
            new_assigned.container_chains.entry(para_id).or_default();
125
10935
        }
126

            
127
        // The rest of this function mostly treats orchestrator chain as another container chain, remove it from
128
        // container chains before returning the final assignment.
129
4827
        let orchestrator_assigned = new_assigned
130
4827
            .container_chains
131
4827
            .remove(&orchestrator_chain.para_id)
132
4827
            .unwrap();
133
4827
        // Sanity check to avoid bricking orchestrator chain
134
4827
        if orchestrator_assigned.is_empty() && !T::ForceEmptyOrchestrator::get() {
135
            return Err(AssignmentError::EmptyOrchestrator);
136
4827
        }
137
4827
        new_assigned.orchestrator_chain = orchestrator_assigned;
138
4827

            
139
4827
        Ok(new_assigned)
140
4831
    }
141

            
142
    /// Keep a subset of collators instead of rotating all of them.
143
10942
    pub fn keep_collator_subset<TShuffle>(
144
10942
        collators: Option<&mut Vec<T::AccountId>>,
145
10942
        full_rotation_mode: FullRotationMode,
146
10942
        max_collators: u32,
147
10942
        shuffle: Option<&mut TShuffle>,
148
10942
    ) where
149
10942
        TShuffle: FnMut(&mut Vec<T::AccountId>),
150
10942
    {
151
10942
        let collators = match collators {
152
7893
            Some(x) => x,
153
3049
            None => return,
154
        };
155

            
156
7893
        let num_to_keep = match full_rotation_mode {
157
778
            FullRotationMode::RotateAll => 0,
158
            FullRotationMode::KeepAll => {
159
6943
                return;
160
            }
161
87
            FullRotationMode::KeepCollators { keep } => keep,
162
85
            FullRotationMode::KeepPerbill { percentage: keep } => keep * max_collators,
163
        };
164

            
165
950
        if num_to_keep == 0 {
166
            // Remove all
167
778
            collators.clear();
168
778
            return;
169
172
        }
170
172

            
171
172
        // Less than N collators, no need to shuffle
172
172
        if collators.len() as u32 <= num_to_keep {
173
74
            return;
174
98
        }
175

            
176
        // Shuffle and keep first N
177
98
        if let Some(shuffle) = shuffle {
178
98
            shuffle(collators);
179
98
        }
180

            
181
98
        collators.truncate(num_to_keep as usize);
182
10942
    }
183

            
184
    /// Select which container chains will be assigned collators and how many collators, but do not specify which
185
    /// collator goes to which chain.
186
    ///
187
    /// Each chain has a min and max number of collators. If the number of collators is not enough to reach the min,
188
    /// no collators are assigned to that chain.
189
    ///
190
    /// If the available number of collators is:
191
    /// * lower than the min of the first chain: we assign all the collators to the first chain. This is the
192
    ///   orchestrator chain and we always want it to have collators.
193
    /// * lower than the sum of all the min: we cannot assign collators to all the chains. So remove chains until
194
    ///   we can. The order is important, the first chains will be assigned collators and the last ones will not.
195
    /// * lower than the sum of all the max: we can assign the min value to all the chains, and have some leftover.
196
    ///   We use the same order to decide where this extra collators will go, by filling the max of the first chain,
197
    ///   then the max of the second chain, and so on.
198
    /// * greater than the sum of all the max: all the chains will be assigned their max number of collators.
199
    ///
200
    /// # Params
201
    ///
202
    /// The first item of `chains` should be the orchestrator chain, because it will be the first one to be assigned
203
    /// collators.
204
    ///
205
    /// # Returns
206
    ///
207
    /// A list of `(para_id, num_collators)`.
208
4838
    pub fn select_chains_with_collators(
209
4838
        num_collators: u32,
210
4838
        chains: &[ChainNumCollators],
211
4838
    ) -> Vec<(ParaId, u32)> {
212
4838
        if chains.is_empty() {
213
            // Avoid panic if chains is empty
214
            return vec![];
215
4838
        }
216
4838
        // Let's count how many container chains we can support with the current number of collators
217
4838
        let mut available_collators = num_collators;
218
4838
        // Handle orchestrator chain in a special way, we always want to assign collators to it, even if we don't
219
4838
        // reach the min.
220
4838
        let min_orchestrator_collators = chains[0].min_collators;
221
4838
        available_collators.saturating_reduce(min_orchestrator_collators);
222
4838

            
223
4838
        let mut container_chains_with_collators = vec![chains[0]];
224
        // Skipping orchestrator chain because it was handled above
225
6987
        for cc in chains.iter().skip(1) {
226
5888
            if available_collators >= cc.min_collators {
227
3580
                available_collators.saturating_reduce(cc.min_collators);
228
3580
                container_chains_with_collators.push(*cc);
229
3619
            } else if available_collators == 0 {
230
                // Do not break if there are still some available collators. Even if they were not enough to reach the
231
                // `min` of this chain, it is possible that one of the chains with less priority has a lower `min`, so
232
                // that chain should be assigned collators.
233
456
                break;
234
1852
            }
235
        }
236

            
237
4838
        let mut required_collators_min = 0;
238
13256
        for cc in &container_chains_with_collators {
239
8418
            required_collators_min.saturating_accrue(cc.min_collators);
240
8418
        }
241

            
242
4838
        if num_collators < min_orchestrator_collators {
243
            // Edge case: num collators less than min orchestrator collators: fill as much as we can
244
21
            vec![(chains[0].para_id, num_collators)]
245
        } else {
246
            // After assigning the min to all the chains we have this remainder. The remainder will be assigned until
247
            // all the chains reach the max value.
248
4817
            let mut required_collators_remainder =
249
4817
                num_collators.saturating_sub(required_collators_min);
250
4817
            let mut container_chains_variable = vec![];
251
13214
            for cc in &container_chains_with_collators {
252
8397
                // Each chain will have `min + extra` collators, where extra is capped so `min + extra <= max`.
253
8397
                let extra = cmp::min(
254
8397
                    required_collators_remainder,
255
8397
                    cc.max_collators.saturating_sub(cc.min_collators),
256
8397
                );
257
8397
                let num = cc.min_collators.saturating_add(extra);
258
8397
                required_collators_remainder.saturating_reduce(extra);
259
8397
                container_chains_variable.push((cc.para_id, num));
260
8397
            }
261

            
262
4817
            container_chains_variable
263
        }
264
4838
    }
265

            
266
    /// Same as `prioritize_invulnerables` but return the invulnerables instead of inserting them into `old_assigned`.
267
    ///
268
    /// Mutates `old_assigned` by removing invulnerables from their old chain, even if they will later be assigned to
269
    /// the same chain.
270
4834
    pub fn remove_invulnerables(
271
4834
        collators: &[T::AccountId],
272
4834
        orchestrator_chain: ChainNumCollators,
273
4834
        old_assigned: &mut BTreeMap<ParaId, Vec<T::AccountId>>,
274
4834
    ) -> Vec<T::AccountId> {
275
4834
        // TODO: clean this up, maybe change remove_invulnerables trait into something more ergonomic
276
4834
        let min_orchestrator_collators = orchestrator_chain.min_collators as usize;
277
4834
        let invulnerables_already_assigned = T::RemoveInvulnerables::remove_invulnerables(
278
4834
            &mut old_assigned
279
4834
                .get(&orchestrator_chain.para_id)
280
4834
                .cloned()
281
4834
                .unwrap_or_default(),
282
4834
            min_orchestrator_collators,
283
4834
        );
284
4834
        let mut new_invulnerables = invulnerables_already_assigned;
285
4834
        if new_invulnerables.len() >= min_orchestrator_collators {
286
            // We already had invulnerables, we will just move them to the front of the list if they weren't already
287
3451
            return new_invulnerables;
288
1383
        }
289
1383

            
290
1383
        // Not enough invulnerables currently assigned, get rest from new_collators
291
1383
        let mut new_collators = collators.to_vec();
292
2051
        for (_id, cs) in old_assigned.iter() {
293
12500
            new_collators.retain(|c| !cs.contains(c));
294
2051
        }
295
1383
        let num_missing_invulnerables =
296
1383
            min_orchestrator_collators.saturating_sub(new_invulnerables.len());
297
1383
        let invulnerables_not_assigned = T::RemoveInvulnerables::remove_invulnerables(
298
1383
            &mut new_collators,
299
1383
            num_missing_invulnerables,
300
1383
        );
301
1383
        new_invulnerables.extend(invulnerables_not_assigned);
302
1383

            
303
1383
        if new_invulnerables.len() >= min_orchestrator_collators {
304
            // Got invulnerables from new_collators, and maybe some were already assigned
305
1191
            return new_invulnerables;
306
192
        }
307
192

            
308
192
        // Still not enough invulnerables, try to get an invulnerable that is currently assigned somewhere else
309
192
        let num_missing_invulnerables =
310
192
            min_orchestrator_collators.saturating_sub(new_invulnerables.len());
311
192
        let mut collators = collators.to_vec();
312
192
        let new_invulnerables_set = BTreeSet::from_iter(new_invulnerables.iter().cloned());
313
1637
        collators.retain(|c| {
314
1637
            // Remove collators already selected
315
1637
            !new_invulnerables_set.contains(c)
316
1637
        });
317
192
        let invulnerables_assigned_elsewhere =
318
192
            T::RemoveInvulnerables::remove_invulnerables(&mut collators, num_missing_invulnerables);
319
192

            
320
192
        if invulnerables_assigned_elsewhere.is_empty() {
321
            // If at this point we still do not have enough invulnerables, it means that there are no
322
            // enough invulnerables, so no problem, but return the invulnerables
323
189
            return new_invulnerables;
324
3
        }
325
3

            
326
3
        new_invulnerables.extend(invulnerables_assigned_elsewhere.iter().cloned());
327
3

            
328
3
        // In this case we must delete the old assignment of the invulnerables
329
3
        let reassigned_invulnerables_set = BTreeSet::from_iter(invulnerables_assigned_elsewhere);
330
        // old_assigned.remove_collators_in_set
331
8
        for (_id, cs) in old_assigned.iter_mut() {
332
17
            cs.retain(|c| !reassigned_invulnerables_set.contains(c));
333
8
        }
334

            
335
3
        new_invulnerables
336
4834
    }
337

            
338
    /// Ensure orchestrator chain has `min_orchestrator` invulnerables. If that's not possible, it tries to add as
339
    /// many invulnerables as possible.
340
    ///
341
    /// Get invulnerables from:
342
    /// * old_assigned in orchestrator
343
    /// * new collators
344
    /// * old_assigned elsewhere
345
    ///
346
    /// In that order.
347
    ///
348
    /// Mutates `old_assigned` because invulnerables will be inserted there, and if invulnerables were already
349
    /// assigned to some other chain, they will be removed from that other chain as well.
350
    ///
351
    /// # Params
352
    ///
353
    /// * `old_assigned` must be a subset of `collators`
354
    /// * `old_assigned` must not have duplicate collators.
355
    ///
356
    /// # Returns
357
    ///
358
    /// The number of invulnerables assigned to the orchestrator chain, capped to `min_collators`.
359
4834
    pub fn prioritize_invulnerables(
360
4834
        collators: &[T::AccountId],
361
4834
        orchestrator_chain: ChainNumCollators,
362
4834
        old_assigned: &mut BTreeMap<ParaId, Vec<T::AccountId>>,
363
4834
    ) -> usize {
364
4834
        let new_invulnerables =
365
4834
            Self::remove_invulnerables(collators, orchestrator_chain, old_assigned);
366
4834

            
367
4834
        if !new_invulnerables.is_empty() {
368
3499
            Self::insert_invulnerables(
369
3499
                old_assigned.entry(orchestrator_chain.para_id).or_default(),
370
3499
                &new_invulnerables,
371
3499
            );
372
4743
        }
373

            
374
4834
        new_invulnerables.len()
375
4834
    }
376

            
377
    /// Assign collators assuming that the number of collators is greater than or equal to the required.
378
    /// The order of both container chains and collators is important to ensure randomness when `old_assigned` is
379
    /// empty.
380
    ///
381
    /// # Params
382
    ///
383
    /// * `old_assigned` does not need to be a subset of `collators`: collators are checked and removed.
384
    /// * `old_assigned` does not need to be a subset of `chains`, unused para ids are removed. Collators
385
    ///   assigned to a para_id not present in `chains` may be reassigned to another para_id.
386
    /// * `chains` `num_collators` can be 0. In that case an empty vec is returned for that para id.
387
    /// * `old_assigned` must not have duplicate collators.
388
    /// * `shuffle` is used to shuffle the list collators. The list will be truncated to only have
389
    ///   the number of required collators, to ensure that shuffling doesn't cause a collator with low
390
    ///   priority to be assigned instead of a collator with higher priority.
391
    ///
392
    /// # Returns
393
    ///
394
    /// The collator assigment, a map from `ParaId` to `Vec<T>`.
395
    ///
396
    /// Or an error if the number of collators is not enough to fill all the chains, or if the required number
397
    /// of collators overflows a `u32`.
398
4836
    pub fn assign_full<TShuffle>(
399
4836
        collators: Vec<T::AccountId>,
400
4836
        chains: Vec<(ParaId, u32)>,
401
4836
        mut old_assigned: BTreeMap<ParaId, Vec<T::AccountId>>,
402
4836
        shuffle: Option<TShuffle>,
403
4836
    ) -> Result<BTreeMap<ParaId, Vec<T::AccountId>>, AssignmentError>
404
4836
    where
405
4836
        TShuffle: FnOnce(&mut Vec<T::AccountId>),
406
4836
    {
407
4836
        let mut required_collators = 0usize;
408
8408
        for (_para_id, num_collators) in chains.iter() {
409
8408
            let num_collators =
410
8408
                usize::try_from(*num_collators).map_err(|_| AssignmentError::NotEnoughCollators)?;
411
8408
            required_collators = required_collators
412
8408
                .checked_add(num_collators)
413
8408
                .ok_or(AssignmentError::NotEnoughCollators)?;
414
        }
415

            
416
        // This check is necessary to ensure priority: if the number of collators is less than required, it is
417
        // possible that the chain with the least priority could be assigned collators (since they are in
418
        // old_assigned), while some chains with higher priority might have no collators.
419
4836
        if collators.len() < required_collators {
420
1
            return Err(AssignmentError::NotEnoughCollators);
421
4835
        }
422
4835
        // We checked that the sum of all `num_collators` fits in `usize`, so we can safely use `as usize`.
423
4835

            
424
4835
        // Remove invalid collators and para ids from `old_assigned`
425
4835
        let para_ids_set =
426
8406
            BTreeSet::from_iter(chains.iter().map(|(para_id, _num_collators)| *para_id));
427
4835
        let collators_set = BTreeSet::from_iter(collators.iter().cloned());
428
4835
        Self::retain_valid_old_assigned(&mut old_assigned, &para_ids_set, &collators_set);
429

            
430
        // Truncate num collators to required
431
8406
        for (para_id, num_collators) in chains.iter() {
432
8406
            let entry = old_assigned.entry(*para_id).or_default();
433
8406
            entry.truncate(*num_collators as usize);
434
8406
        }
435

            
436
        // Count number of needed new collators. This is equivalent to:
437
        // `required_collators - old_assigned.iter().map(|cs| cs.len()).sum()`.
438
4835
        let mut needed_new_collators = 0;
439
8406
        for (para_id, num_collators) in chains.iter() {
440
8406
            let cs = old_assigned.entry(*para_id).or_default();
441
8406
            needed_new_collators = needed_new_collators
442
8406
                .saturating_add(*num_collators as usize)
443
8406
                .saturating_sub(cs.len());
444
8406
        }
445

            
446
4835
        let assigned_collators: BTreeSet<T::AccountId> = old_assigned
447
4835
            .iter()
448
8406
            .flat_map(|(_para_id, para_collators)| para_collators.iter().cloned())
449
4835
            .collect();
450
4835

            
451
4835
        // Truncate list of new_collators to `needed_new_collators` and shuffle it.
452
4835
        // This has the effect of keeping collator priority (the first collator of that list is more
453
4835
        // likely to be assigned to a chain than the last collator of that list), while also
454
4835
        // ensuring randomness (the original order does not directly affect which chain the
455
4835
        // collators are assigned to).
456
4835
        let mut new_collators: Vec<_> = collators
457
4835
            .into_iter()
458
5559
            .filter(|x| {
459
3371
                // Keep collators not already assigned
460
3371
                !assigned_collators.contains(x)
461
5559
            })
462
4835
            .take(needed_new_collators)
463
4835
            .collect();
464
4835
        if let Some(shuffle) = shuffle {
465
122
            shuffle(&mut new_collators);
466
4713
        }
467
4835
        let mut new_collators = new_collators.into_iter();
468

            
469
        // Fill missing collators
470
8406
        for (para_id, num_collators) in chains.iter() {
471
8406
            let cs = old_assigned.entry(*para_id).or_default();
472

            
473
10622
            while cs.len() < *num_collators as usize {
474
                // This error should never happen because we calculated `needed_new_collators`
475
                // using the same algorithm
476
2216
                let nc = new_collators
477
2216
                    .next()
478
2216
                    .ok_or(AssignmentError::NotEnoughCollators)?;
479
2216
                cs.push(nc);
480
            }
481
        }
482

            
483
4835
        Ok(old_assigned)
484
4836
    }
485

            
486
    /// Insert invulnerables ensuring that they are always the first in the list.
487
    /// The order of both lists is preserved.
488
    /// `assigned` may already contain the invulnerables, in that case they are only moved to the front.
489
    ///
490
    /// Invulnerables need to be the first of the list because we may truncate the list of collators if the number of
491
    /// collators changes, and in that case we want invulnerables to stay assigned there.
492
3499
    pub fn insert_invulnerables(assigned: &mut Vec<T::AccountId>, invulnerables: &[T::AccountId]) {
493
3630
        assigned.retain(|item| !invulnerables.contains(item));
494
3499

            
495
3499
        let mut new_assigned = invulnerables.to_vec();
496
3499
        new_assigned.extend(mem::take(assigned));
497
3499

            
498
3499
        *assigned = new_assigned;
499
3499
    }
500

            
501
    /// Removes invalid entries from `old_assigned`:
502
    ///
503
    /// * para ids not in `chains_with_collators`
504
    /// * collators not in `collators`
505
9663
    pub fn retain_valid_old_assigned(
506
9663
        old_assigned: &mut BTreeMap<ParaId, Vec<T::AccountId>>,
507
9663
        chains_with_collators: &BTreeSet<ParaId>,
508
9663
        collators: &BTreeSet<T::AccountId>,
509
9663
    ) {
510
9663
        // old_assigned.remove_container_chains_not_in_set
511
18272
        old_assigned.retain(|id, _cs| chains_with_collators.contains(id));
512
        // old_assigned.remove_collators_not_in_set
513
15781
        for (_id, cs) in old_assigned.iter_mut() {
514
20796
            cs.retain(|c| collators.contains(c));
515
15781
        }
516
9663
    }
517
}
518

            
519
/// Errors than can happen during collator assignment
520
#[derive(Debug, Clone, PartialEq, Eq)]
521
pub enum AssignmentError {
522
    /// An empty list of collators was passed to `assign_collators_always_keep_old`
523
    ZeroCollators,
524
    /// The required number of collators for `assign_full` is greater than the provided number of collators.
525
    /// Also includes possible overflows in number of collators.
526
    NotEnoughCollators,
527
    /// No collators were assigned to orchestrator chain
528
    EmptyOrchestrator,
529
}
530

            
531
/// A `ParaId` and a range of collators that need to be assigned to it.
532
/// This can be a container chain, a parathread, or the orchestrator chain.
533
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
534
pub struct ChainNumCollators {
535
    /// Para id.
536
    pub para_id: ParaId,
537
    /// Min collators.
538
    pub min_collators: u32,
539
    /// Max collators. This will only be filled if all the other chains have reached min_collators
540
    pub max_collators: u32,
541
    /// True if this a parathread. False means parachain or orchestrator.
542
    pub parathread: bool,
543
}