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

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

            
105
8476
            let collators = old_assigned.get_mut(&chain.para_id);
106
8476
            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
3285
        Self::prioritize_invulnerables(&collators, orchestrator_chain, &mut old_assigned);
113

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

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

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

            
139
3285
        Ok(new_assigned)
140
3289
    }
141

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

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

            
165
893
        if num_to_keep == 0 {
166
            // Remove all
167
721
            collators.clear();
168
721
            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
8483
    }
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
3296
    pub fn select_chains_with_collators(
209
3296
        num_collators: u32,
210
3296
        chains: &[ChainNumCollators],
211
3296
    ) -> Vec<(ParaId, u32)> {
212
3296
        if chains.is_empty() {
213
            // Avoid panic if chains is empty
214
            return vec![];
215
3296
        }
216
3296
        // Let's count how many container chains we can support with the current number of collators
217
3296
        let mut available_collators = num_collators;
218
3296
        // Handle orchestrator chain in a special way, we always want to assign collators to it, even if we don't
219
3296
        // reach the min.
220
3296
        let min_orchestrator_collators = chains[0].min_collators;
221
3296
        available_collators = available_collators.saturating_sub(min_orchestrator_collators);
222
3296

            
223
3296
        let mut container_chains_with_collators = vec![chains[0]];
224
        // Skipping orchestrator chain because it was handled above
225
5222
        for cc in chains.iter().skip(1) {
226
5040
            if available_collators >= cc.min_collators {
227
2914
                available_collators -= cc.min_collators;
228
2914
                container_chains_with_collators.push(*cc);
229
2944
            } 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
379
                break;
234
1747
            }
235
        }
236

            
237
3296
        let mut required_collators_min = 0;
238
9506
        for cc in &container_chains_with_collators {
239
6210
            required_collators_min += cc.min_collators;
240
6210
        }
241

            
242
3296
        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
3275
            let mut required_collators_remainder = num_collators - required_collators_min;
249
3275
            let mut container_chains_variable = vec![];
250
9464
            for cc in &container_chains_with_collators {
251
6189
                // Each chain will have `min + extra` collators, where extra is capped so `min + extra <= max`.
252
6189
                let extra = cmp::min(
253
6189
                    required_collators_remainder,
254
6189
                    cc.max_collators.saturating_sub(cc.min_collators),
255
6189
                );
256
6189
                let num = cc.min_collators + extra;
257
6189
                required_collators_remainder -= extra;
258
6189
                container_chains_variable.push((cc.para_id, num));
259
6189
            }
260

            
261
3275
            container_chains_variable
262
        }
263
3296
    }
264

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

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

            
301
817
        if new_invulnerables.len() >= min_orchestrator_collators {
302
            // Got invulnerables from new_collators, and maybe some were already assigned
303
625
            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 = min_orchestrator_collators - new_invulnerables.len();
308
192
        let mut collators = collators.to_vec();
309
192
        let new_invulnerables_set = BTreeSet::from_iter(new_invulnerables.iter().cloned());
310
1637
        collators.retain(|c| {
311
1637
            // Remove collators already selected
312
1637
            !new_invulnerables_set.contains(c)
313
1637
        });
314
192
        let invulnerables_assigned_elsewhere =
315
192
            T::RemoveInvulnerables::remove_invulnerables(&mut collators, num_missing_invulnerables);
316
192

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

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

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

            
332
3
        new_invulnerables
333
3292
    }
334

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

            
364
3292
        if !new_invulnerables.is_empty() {
365
2636
            Self::insert_invulnerables(
366
2636
                old_assigned.entry(orchestrator_chain.para_id).or_default(),
367
2636
                &new_invulnerables,
368
2636
            );
369
3201
        }
370

            
371
3292
        new_invulnerables.len()
372
3292
    }
373

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

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

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

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

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

            
441
3293
        let assigned_collators: BTreeSet<T::AccountId> = old_assigned
442
3293
            .iter()
443
6198
            .flat_map(|(_para_id, para_collators)| para_collators.iter().cloned())
444
3293
            .collect();
445
3293

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

            
464
        // Fill missing collators
465
6198
        for (para_id, num_collators) in chains.iter() {
466
6198
            let cs = old_assigned.entry(*para_id).or_default();
467

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

            
478
3293
        Ok(old_assigned)
479
3294
    }
480

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

            
490
2636
        let mut new_assigned = invulnerables.to_vec();
491
2636
        new_assigned.extend(mem::take(assigned));
492
2636

            
493
2636
        *assigned = new_assigned;
494
2636
    }
495

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

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

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