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
    alloc::{
18
        collections::{btree_map::BTreeMap, btree_set::BTreeSet},
19
        vec::Vec,
20
    },
21
    core::{cmp, marker::PhantomData, mem},
22
    dp_collator_assignment::AssignedCollators,
23
    frame_support::traits::Get,
24
    sp_runtime::Saturating,
25
    tp_traits::{
26
        FullRotationMode, FullRotationModes, ParaId, RemoveInvulnerables as RemoveInvulnerablesT,
27
    },
28
};
29

            
30
// Separate import of `alloc::vec!` macro, which cause issues with rustfmt if grouped
31
// with `alloc::vec::Vec`.
32
use alloc::vec;
33

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

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

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

            
103
11109
            let collators = old_assigned.get_mut(&chain.para_id);
104
11109
            Self::keep_collator_subset(collators, mode, chain.max_collators, shuffle.as_mut());
105
        }
106

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

            
112
4891
        let new_assigned_chains =
113
4891
            Self::assign_full(collators, chains_with_collators, old_assigned, shuffle)?;
114

            
115
4891
        let mut new_assigned = AssignedCollators {
116
4891
            container_chains: new_assigned_chains,
117
4891
            ..Default::default()
118
4891
        };
119

            
120
        // Add container chains with 0 collators so that they are shown in UI
121
16000
        for para_id in all_para_ids {
122
11109
            new_assigned.container_chains.entry(para_id).or_default();
123
11109
        }
124

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

            
137
4891
        Ok(new_assigned)
138
4895
    }
139

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

            
154
8017
        let num_to_keep = match full_rotation_mode {
155
790
            FullRotationMode::RotateAll => 0,
156
            FullRotationMode::KeepAll => {
157
7055
                return;
158
            }
159
87
            FullRotationMode::KeepCollators { keep } => keep,
160
85
            FullRotationMode::KeepPerbill { percentage: keep } => keep * max_collators,
161
        };
162

            
163
962
        if num_to_keep == 0 {
164
            // Remove all
165
790
            collators.clear();
166
790
            return;
167
172
        }
168
172

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

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

            
179
98
        collators.truncate(num_to_keep as usize);
180
11116
    }
181

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

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

            
235
4902
        let mut required_collators_min = 0;
236
13458
        for cc in &container_chains_with_collators {
237
8556
            required_collators_min.saturating_accrue(cc.min_collators);
238
8556
        }
239

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

            
260
4881
            container_chains_variable
261
        }
262
4902
    }
263

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

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

            
301
1391
        if new_invulnerables.len() >= min_orchestrator_collators {
302
            // Got invulnerables from new_collators, and maybe some were already assigned
303
1199
            return new_invulnerables;
304
192
        }
305
192

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

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

            
324
3
        new_invulnerables.extend(invulnerables_assigned_elsewhere.iter().cloned());
325
3

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

            
333
3
        new_invulnerables
334
4898
    }
335

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

            
365
4898
        if !new_invulnerables.is_empty() {
366
3531
            Self::insert_invulnerables(
367
3531
                old_assigned.entry(orchestrator_chain.para_id).or_default(),
368
3531
                &new_invulnerables,
369
3531
            );
370
4807
        }
371

            
372
4898
        new_invulnerables.len()
373
4898
    }
374

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

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

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

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

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

            
444
4899
        let assigned_collators: BTreeSet<T::AccountId> = old_assigned
445
4899
            .iter()
446
8544
            .flat_map(|(_para_id, para_collators)| para_collators.iter().cloned())
447
4899
            .collect();
448
4899

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

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

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

            
481
4899
        Ok(old_assigned)
482
4900
    }
483

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

            
493
3531
        let mut new_assigned = invulnerables.to_vec();
494
3531
        new_assigned.extend(mem::take(assigned));
495
3531

            
496
3531
        *assigned = new_assigned;
497
3531
    }
498

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

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

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