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
6167
    pub fn assign_collators_always_keep_old<TShuffle>(
56
6167
        collators: Vec<T::AccountId>,
57
6167
        orchestrator_chain: ChainNumCollators,
58
6167
        mut chains: Vec<ChainNumCollators>,
59
6167
        mut old_assigned: AssignedCollators<T::AccountId>,
60
6167
        mut shuffle: Option<TShuffle>,
61
6167
        full_rotation_mode: FullRotationModes,
62
6167
    ) -> Result<AssignedCollators<T::AccountId>, AssignmentError>
63
6167
    where
64
6167
        TShuffle: FnMut(&mut Vec<T::AccountId>),
65
    {
66
6167
        if collators.is_empty() && !T::ForceEmptyOrchestrator::get() {
67
4
            return Err(AssignmentError::ZeroCollators);
68
6163
        }
69
        // The rest of this function mostly treats orchestrator chain as another container chain, so move it into
70
        // `old_assigned.container_chains`
71
6163
        let old_orchestrator_assigned = mem::take(&mut old_assigned.orchestrator_chain);
72
6163
        old_assigned
73
6163
            .container_chains
74
6163
            .insert(orchestrator_chain.para_id, old_orchestrator_assigned);
75
6163
        let mut old_assigned = old_assigned.container_chains;
76
        // Orchestrator chain must be the first one in the list because it always has priority
77
6163
        chains.insert(0, orchestrator_chain);
78
6163
        let all_para_ids: Vec<ParaId> = chains.iter().map(|cc| cc.para_id).collect();
79
6163
        let collators_set = BTreeSet::from_iter(collators.iter().cloned());
80
6163
        let chains_with_collators =
81
6163
            Self::select_chains_with_collators(collators.len() as u32, &chains);
82
6163
        let chains_with_collators_set: BTreeSet<ParaId> = chains_with_collators
83
6163
            .iter()
84
6163
            .map(|(para_id, _num_collators)| *para_id)
85
6163
            .collect();
86
6163
        Self::retain_valid_old_assigned(
87
6163
            &mut old_assigned,
88
6163
            &chains_with_collators_set,
89
6163
            &collators_set,
90
        );
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
12381
        for chain in chains.iter() {
95
12381
            let mode = if chain.para_id == orchestrator_chain.para_id {
96
6163
                full_rotation_mode.orchestrator.clone()
97
6218
            } else if chain.parathread {
98
516
                full_rotation_mode.parathread.clone()
99
            } else {
100
5702
                full_rotation_mode.parachain.clone()
101
            };
102

            
103
12381
            let collators = old_assigned.get_mut(&chain.para_id);
104
12381
            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
6163
        Self::prioritize_invulnerables(&collators, orchestrator_chain, &mut old_assigned);
111

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

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

            
120
        // Add container chains with 0 collators so that they are shown in UI
121
18544
        for para_id in all_para_ids {
122
12381
            new_assigned.container_chains.entry(para_id).or_default();
123
12381
        }
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
6163
        let orchestrator_assigned = new_assigned
128
6163
            .container_chains
129
6163
            .remove(&orchestrator_chain.para_id)
130
6163
            .unwrap();
131
        // Sanity check to avoid bricking orchestrator chain
132
6163
        if orchestrator_assigned.is_empty() && !T::ForceEmptyOrchestrator::get() {
133
            return Err(AssignmentError::EmptyOrchestrator);
134
6163
        }
135
6163
        new_assigned.orchestrator_chain = orchestrator_assigned;
136

            
137
6163
        Ok(new_assigned)
138
6167
    }
139

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

            
154
8368
        let num_to_keep = match full_rotation_mode {
155
118
            FullRotationMode::RotateAll => 0,
156
            FullRotationMode::KeepAll => {
157
8102
                return;
158
            }
159
75
            FullRotationMode::KeepCollators { keep } => keep,
160
73
            FullRotationMode::KeepPerbill { percentage: keep } => keep * max_collators,
161
        };
162

            
163
266
        if num_to_keep == 0 {
164
            // Remove all
165
118
            collators.clear();
166
118
            return;
167
148
        }
168

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

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

            
179
74
        collators.truncate(num_to_keep as usize);
180
12388
    }
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
6174
    pub fn select_chains_with_collators(
207
6174
        num_collators: u32,
208
6174
        chains: &[ChainNumCollators],
209
6174
    ) -> Vec<(ParaId, u32)> {
210
6174
        if chains.is_empty() {
211
            // Avoid panic if chains is empty
212
            return vec![];
213
6174
        }
214
        // Let's count how many container chains we can support with the current number of collators
215
6174
        let mut available_collators = num_collators;
216
        // Handle orchestrator chain in a special way, we always want to assign collators to it, even if we don't
217
        // reach the min.
218
6174
        let min_orchestrator_collators = chains[0].min_collators;
219
6174
        available_collators.saturating_reduce(min_orchestrator_collators);
220

            
221
6174
        let mut container_chains_with_collators = vec![chains[0]];
222
        // Skipping orchestrator chain because it was handled above
223
6444
        for cc in chains.iter().skip(1) {
224
5623
            if available_collators >= cc.min_collators {
225
3282
                available_collators.saturating_reduce(cc.min_collators);
226
3282
                container_chains_with_collators.push(*cc);
227
3304
            } 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
2055
                break;
232
286
            }
233
        }
234

            
235
6174
        let mut required_collators_min = 0;
236
15630
        for cc in &container_chains_with_collators {
237
9456
            required_collators_min.saturating_accrue(cc.min_collators);
238
9456
        }
239

            
240
6174
        if num_collators < min_orchestrator_collators {
241
            // Edge case: num collators less than min orchestrator collators: fill as much as we can
242
120
            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
6054
            let mut required_collators_remainder =
247
6054
                num_collators.saturating_sub(required_collators_min);
248
6054
            let mut container_chains_variable = vec![];
249
15390
            for cc in &container_chains_with_collators {
250
9336
                // Each chain will have `min + extra` collators, where extra is capped so `min + extra <= max`.
251
9336
                let extra = cmp::min(
252
9336
                    required_collators_remainder,
253
9336
                    cc.max_collators.saturating_sub(cc.min_collators),
254
9336
                );
255
9336
                let num = cc.min_collators.saturating_add(extra);
256
9336
                required_collators_remainder.saturating_reduce(extra);
257
9336
                container_chains_variable.push((cc.para_id, num));
258
9336
            }
259

            
260
6054
            container_chains_variable
261
        }
262
6174
    }
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
6170
    pub fn remove_invulnerables(
269
6170
        collators: &[T::AccountId],
270
6170
        orchestrator_chain: ChainNumCollators,
271
6170
        old_assigned: &mut BTreeMap<ParaId, Vec<T::AccountId>>,
272
6170
    ) -> Vec<T::AccountId> {
273
        // TODO: clean this up, maybe change remove_invulnerables trait into something more ergonomic
274
6170
        let min_orchestrator_collators = orchestrator_chain.min_collators as usize;
275
6170
        let invulnerables_already_assigned = T::RemoveInvulnerables::remove_invulnerables(
276
6170
            &mut old_assigned
277
6170
                .get(&orchestrator_chain.para_id)
278
6170
                .cloned()
279
6170
                .unwrap_or_default(),
280
6170
            min_orchestrator_collators,
281
        );
282
6170
        let mut new_invulnerables = invulnerables_already_assigned;
283
6170
        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
3741
            return new_invulnerables;
286
2429
        }
287

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

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

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

            
318
297
        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
294
            return new_invulnerables;
322
3
        }
323

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

            
326
        // 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
        }
332

            
333
3
        new_invulnerables
334
6170
    }
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
6170
    pub fn prioritize_invulnerables(
358
6170
        collators: &[T::AccountId],
359
6170
        orchestrator_chain: ChainNumCollators,
360
6170
        old_assigned: &mut BTreeMap<ParaId, Vec<T::AccountId>>,
361
6170
    ) -> usize {
362
6170
        let new_invulnerables =
363
6170
            Self::remove_invulnerables(collators, orchestrator_chain, old_assigned);
364

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

            
372
6170
        new_invulnerables.len()
373
6170
    }
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
6172
    pub fn assign_full<TShuffle>(
397
6172
        collators: Vec<T::AccountId>,
398
6172
        chains: Vec<(ParaId, u32)>,
399
6172
        mut old_assigned: BTreeMap<ParaId, Vec<T::AccountId>>,
400
6172
        shuffle: Option<TShuffle>,
401
6172
    ) -> Result<BTreeMap<ParaId, Vec<T::AccountId>>, AssignmentError>
402
6172
    where
403
6172
        TShuffle: FnOnce(&mut Vec<T::AccountId>),
404
    {
405
6172
        let mut required_collators = 0usize;
406
9446
        for (_para_id, num_collators) in chains.iter() {
407
9446
            let num_collators =
408
9446
                usize::try_from(*num_collators).map_err(|_| AssignmentError::NotEnoughCollators)?;
409
9446
            required_collators = required_collators
410
9446
                .checked_add(num_collators)
411
9446
                .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
6172
        if collators.len() < required_collators {
418
1
            return Err(AssignmentError::NotEnoughCollators);
419
6171
        }
420
        // We checked that the sum of all `num_collators` fits in `usize`, so we can safely use `as usize`.
421

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

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

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

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

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

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

            
471
12465
            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
3021
                let nc = new_collators
475
3021
                    .next()
476
3021
                    .ok_or(AssignmentError::NotEnoughCollators)?;
477
3021
                cs.push(nc);
478
            }
479
        }
480

            
481
6171
        Ok(old_assigned)
482
6172
    }
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
4629
    pub fn insert_invulnerables(assigned: &mut Vec<T::AccountId>, invulnerables: &[T::AccountId]) {
491
4895
        assigned.retain(|item| !invulnerables.contains(item));
492

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

            
496
4629
        *assigned = new_assigned;
497
4629
    }
498

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