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
//! # Collator Assignment Pallet
18
//!
19
//! This pallet assigns a list of collators to:
20
//!    - the orchestrator chain
21
//!    - a set of container chains
22
//!
23
//! The set of container chains is retrieved thanks to the GetContainerChains trait
24
//! The number of collators to assign to the orchestrator chain and the number
25
//! of collators to assign to each container chain is retrieved through the GetHostConfiguration
26
//! trait.
27
//!  
28
//! The pallet uses the following approach:
29
//!
30
//! - First, it aims at filling the necessary collators to serve the orchestrator chain
31
//! - Second, it aims at filling in-order (FIFO) the existing containerChains
32
//!
33
//! Upon new session, this pallet takes whatever assignation was in the PendingCollatorContainerChain
34
//! storage, and assigns it as the current CollatorContainerChain. In addition, it takes the next
35
//! queued set of parachains and collators and calculates the assignment for the next session, storing
36
//! it in the PendingCollatorContainerChain storage item.
37
//!
38
//! The reason for the collator-assignment pallet to work with a one-session delay assignment is because
39
//! we want collators to know at least one session in advance the container chain/orchestrator that they
40
//! are assigned to.
41

            
42
#![cfg_attr(not(feature = "std"), no_std)]
43

            
44
use {
45
    crate::assignment::{Assignment, ChainNumCollators},
46
    core::ops::Mul,
47
    frame_support::{pallet_prelude::*, traits::Currency},
48
    frame_system::pallet_prelude::BlockNumberFor,
49
    rand::{seq::SliceRandom, SeedableRng},
50
    rand_chacha::ChaCha20Rng,
51
    sp_runtime::{
52
        traits::{AtLeast32BitUnsigned, One, Zero},
53
        Perbill, Saturating,
54
    },
55
    sp_std::{collections::btree_set::BTreeSet, fmt::Debug, prelude::*, vec},
56
    tp_traits::{
57
        CollatorAssignmentHook, CollatorAssignmentTip, GetContainerChainAuthor,
58
        GetHostConfiguration, GetSessionContainerChains, ParaId, RemoveInvulnerables,
59
        RemoveParaIdsWithNoCredits, ShouldRotateAllCollators, Slot,
60
    },
61
};
62
pub use {dp_collator_assignment::AssignedCollators, pallet::*};
63

            
64
mod assignment;
65
#[cfg(feature = "runtime-benchmarks")]
66
mod benchmarking;
67
pub mod weights;
68
pub use weights::WeightInfo;
69

            
70
#[cfg(test)]
71
mod mock;
72

            
73
#[cfg(test)]
74
mod tests;
75

            
76
#[derive(Encode, Decode, Debug, TypeInfo)]
77
pub struct CoreAllocationConfiguration {
78
    pub core_count: u32,
79
    pub max_parachain_percentage: Perbill,
80
}
81

            
82
4075
#[frame_support::pallet]
83
pub mod pallet {
84
    use super::*;
85

            
86
590
    #[pallet::pallet]
87
    pub struct Pallet<T>(_);
88

            
89
    /// Configure the pallet by specifying the parameters and types on which it depends.
90
    #[pallet::config]
91
    pub trait Config: frame_system::Config {
92
        /// The overarching event type.
93
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
94
        type SessionIndex: parity_scale_codec::FullCodec
95
            + TypeInfo
96
            + Copy
97
            + AtLeast32BitUnsigned
98
            + Debug;
99
        // `SESSION_DELAY` is used to delay any changes to Paras registration or configurations.
100
        // Wait until the session index is 2 larger then the current index to apply any changes,
101
        // which guarantees that at least one full session has passed before any changes are applied.
102
        type HostConfiguration: GetHostConfiguration<Self::SessionIndex>;
103
        type ContainerChains: GetSessionContainerChains<Self::SessionIndex>;
104
        type SelfParaId: Get<ParaId>;
105
        type ShouldRotateAllCollators: ShouldRotateAllCollators<Self::SessionIndex>;
106
        type GetRandomnessForNextBlock: GetRandomnessForNextBlock<BlockNumberFor<Self>>;
107
        type RemoveInvulnerables: RemoveInvulnerables<Self::AccountId>;
108
        type RemoveParaIdsWithNoCredits: RemoveParaIdsWithNoCredits;
109
        type CollatorAssignmentHook: CollatorAssignmentHook<BalanceOf<Self>>;
110
        type Currency: Currency<Self::AccountId>;
111
        type CollatorAssignmentTip: CollatorAssignmentTip<BalanceOf<Self>>;
112
        type ForceEmptyOrchestrator: Get<bool>;
113
        type CoreAllocationConfiguration: Get<Option<CoreAllocationConfiguration>>;
114
        /// The weight information of this pallet.
115
        type WeightInfo: WeightInfo;
116
    }
117

            
118
    #[pallet::event]
119
3087
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
120
    pub enum Event<T: Config> {
121
14
        NewPendingAssignment {
122
            random_seed: [u8; 32],
123
            full_rotation: bool,
124
            target_session: T::SessionIndex,
125
        },
126
    }
127

            
128
187582
    #[pallet::storage]
129
    #[pallet::unbounded]
130
    pub(crate) type CollatorContainerChain<T: Config> =
131
        StorageValue<_, AssignedCollators<T::AccountId>, ValueQuery>;
132

            
133
    /// Pending configuration changes.
134
    ///
135
    /// This is a list of configuration changes, each with a session index at which it should
136
    /// be applied.
137
    ///
138
    /// The list is sorted ascending by session index. Also, this list can only contain at most
139
    /// 2 items: for the next session and for the `scheduled_session`.
140
14958
    #[pallet::storage]
141
    #[pallet::unbounded]
142
    pub(crate) type PendingCollatorContainerChain<T: Config> =
143
        StorageValue<_, Option<AssignedCollators<T::AccountId>>, ValueQuery>;
144

            
145
    /// Randomness from previous block. Used to shuffle collators on session change.
146
    /// Should only be set on the last block of each session and should be killed on the on_initialize of the next block.
147
    /// The default value of [0; 32] disables randomness in the pallet.
148
11408
    #[pallet::storage]
149
    pub(crate) type Randomness<T: Config> = StorageValue<_, [u8; 32], ValueQuery>;
150

            
151
    /// Ratio of assigned collators to max collators.
152
6174
    #[pallet::storage]
153
    pub type CollatorFullnessRatio<T: Config> = StorageValue<_, Perbill, OptionQuery>;
154

            
155
582
    #[pallet::call]
156
    impl<T: Config> Pallet<T> {}
157

            
158
    /// A struct that holds the assignment that is active after the session change and optionally
159
    /// the assignment that becomes active after the next session change.
160
    pub struct SessionChangeOutcome<T: Config> {
161
        /// New active assignment.
162
        pub active_assignment: AssignedCollators<T::AccountId>,
163
        /// Next session active assignment.
164
        pub next_assignment: AssignedCollators<T::AccountId>,
165
        /// Total number of registered parachains before filtering them out, used as a weight hint
166
        pub num_total_registered_paras: u32,
167
    }
168

            
169
    impl<T: Config> Pallet<T> {
170
3095
        pub(crate) fn enough_collators_for_all_chains(
171
3095
            bulk_paras: &Vec<ChainNumCollators>,
172
3095
            pool_paras: &Vec<ChainNumCollators>,
173
3095
            target_session_index: T::SessionIndex,
174
3095
            number_of_collators: u32,
175
3095
            collators_per_container: u32,
176
3095
            collators_per_parathread: u32,
177
3095
        ) -> bool {
178
3095
            number_of_collators
179
3095
                >= T::HostConfiguration::min_collators_for_orchestrator(target_session_index)
180
3095
                    .saturating_add(collators_per_container.saturating_mul(bulk_paras.len() as u32))
181
3095
                    .saturating_add(
182
3095
                        collators_per_parathread.saturating_mul(pool_paras.len() as u32),
183
3095
                    )
184
3095
        }
185

            
186
        /// Takes the bulk paras (parachains) and pool paras (parathreads)
187
        /// and checks if we if a) Do we have enough collators? b) Do we have enough cores?
188
        /// If either of the answer is yes. We  separately sort bulk_paras and pool_paras and
189
        /// then append the two vectors.
190
393
        pub(crate) fn order_paras_with_core_config(
191
393
            mut bulk_paras: Vec<ChainNumCollators>,
192
393
            mut pool_paras: Vec<ChainNumCollators>,
193
393
            core_allocation_configuration: &CoreAllocationConfiguration,
194
393
            target_session_index: T::SessionIndex,
195
393
            number_of_collators: u32,
196
393
            collators_per_container: u32,
197
393
            collators_per_parathread: u32,
198
393
        ) -> (Vec<ChainNumCollators>, bool) {
199
393
            let core_count = core_allocation_configuration.core_count;
200
393
            let max_number_of_bulk_paras = core_allocation_configuration
201
393
                .max_parachain_percentage
202
393
                .mul(core_count);
203
393

            
204
393
            let enough_cores_for_bulk_paras = bulk_paras.len() <= max_number_of_bulk_paras as usize;
205
393

            
206
393
            let enough_collators = Self::enough_collators_for_all_chains(
207
393
                &bulk_paras,
208
393
                &pool_paras,
209
393
                target_session_index,
210
393
                number_of_collators,
211
393
                collators_per_container,
212
393
                collators_per_parathread,
213
393
            );
214

            
215
            // We should charge tip if parachain demand exceeds the `max_number_of_bulk_paras` OR
216
            // if `num_collators` is not enough to satisfy  collation need of all paras.
217
393
            let should_charge_tip = !enough_cores_for_bulk_paras || !enough_collators;
218

            
219
            // Currently, we are sorting both bulk and pool paras by tip, even when for example
220
            // only number of bulk paras are restricted due to core availability since we deduct tip from
221
            // all paras.
222
            // We need to sort both separately as we have fixed space for parachains at the moment
223
            // which means even when we have some parathread cores empty we cannot schedule parachain there.
224
393
            if should_charge_tip {
225
285
                bulk_paras.sort_by(|a, b| {
226
196
                    T::CollatorAssignmentTip::get_para_tip(b.para_id)
227
196
                        .cmp(&T::CollatorAssignmentTip::get_para_tip(a.para_id))
228
285
                });
229
228

            
230
257
                pool_paras.sort_by(|a, b| {
231
71
                    T::CollatorAssignmentTip::get_para_tip(b.para_id)
232
71
                        .cmp(&T::CollatorAssignmentTip::get_para_tip(a.para_id))
233
257
                });
234
228
            }
235

            
236
393
            bulk_paras.truncate(max_number_of_bulk_paras as usize);
237
393
            // We are not truncating pool paras, since their workload is not continuous one core
238
393
            // can be shared by many paras during the session.
239
393

            
240
393
            let chains: Vec<_> = bulk_paras.into_iter().chain(pool_paras).collect();
241
393

            
242
393
            (chains, should_charge_tip)
243
393
        }
244

            
245
2702
        pub(crate) fn order_paras(
246
2702
            bulk_paras: Vec<ChainNumCollators>,
247
2702
            pool_paras: Vec<ChainNumCollators>,
248
2702
            target_session_index: T::SessionIndex,
249
2702
            number_of_collators: u32,
250
2702
            collators_per_container: u32,
251
2702
            collators_per_parathread: u32,
252
2702
        ) -> (Vec<ChainNumCollators>, bool) {
253
2702
            // Are there enough collators to satisfy the minimum demand?
254
2702
            let enough_collators_for_all_chain = Self::enough_collators_for_all_chains(
255
2702
                &bulk_paras,
256
2702
                &pool_paras,
257
2702
                target_session_index,
258
2702
                number_of_collators,
259
2702
                collators_per_container,
260
2702
                collators_per_parathread,
261
2702
            );
262
2702

            
263
2702
            let mut chains: Vec<_> = bulk_paras.into_iter().chain(pool_paras).collect();
264
2702

            
265
2702
            // Prioritize paras by tip on congestion
266
2702
            // As of now this doesn't distinguish between bulk paras and pool paras
267
2702
            if !enough_collators_for_all_chain {
268
2072
                chains.sort_by(|a, b| {
269
2072
                    T::CollatorAssignmentTip::get_para_tip(b.para_id)
270
2072
                        .cmp(&T::CollatorAssignmentTip::get_para_tip(a.para_id))
271
2072
                });
272
2062
            }
273

            
274
2702
            (chains, !enough_collators_for_all_chain)
275
2702
        }
276

            
277
        /// Assign new collators
278
        /// collators should be queued collators
279
3087
        pub fn assign_collators(
280
3087
            current_session_index: &T::SessionIndex,
281
3087
            random_seed: [u8; 32],
282
3087
            collators: Vec<T::AccountId>,
283
3087
        ) -> SessionChangeOutcome<T> {
284
3087
            let maybe_core_allocation_configuration = T::CoreAllocationConfiguration::get();
285
3087
            // We work with one session delay to calculate assignments
286
3087
            let session_delay = T::SessionIndex::one();
287
3087
            let target_session_index = current_session_index.saturating_add(session_delay);
288
3087

            
289
3087
            let collators_per_container =
290
3087
                T::HostConfiguration::collators_per_container(target_session_index);
291
3087
            let collators_per_parathread =
292
3087
                T::HostConfiguration::collators_per_parathread(target_session_index);
293
3087

            
294
3087
            // We get the containerChains that we will have at the target session
295
3087
            let container_chains =
296
3087
                T::ContainerChains::session_container_chains(target_session_index);
297
3087
            let num_total_registered_paras =
298
3087
                (container_chains.parachains.len() + container_chains.parathreads.len()) as u32;
299
3087
            let mut container_chain_ids = container_chains.parachains;
300
3087
            let mut parathreads: Vec<_> = container_chains
301
3087
                .parathreads
302
3087
                .into_iter()
303
3087
                .map(|(para_id, _)| para_id)
304
3087
                .collect();
305
3087

            
306
3087
            // We read current assigned collators
307
3087
            let old_assigned = Self::read_assigned_collators();
308
3087
            let old_assigned_para_ids: BTreeSet<ParaId> =
309
3087
                old_assigned.container_chains.keys().cloned().collect();
310
3087

            
311
3087
            // Remove the containerChains that do not have enough credits for block production
312
3087
            T::RemoveParaIdsWithNoCredits::remove_para_ids_with_no_credits(
313
3087
                &mut container_chain_ids,
314
3087
                &old_assigned_para_ids,
315
3087
            );
316
3087
            // TODO: parathreads should be treated a bit differently, they don't need to have the same amount of credits
317
3087
            // as parathreads because they will not be producing blocks on every slot.
318
3087
            T::RemoveParaIdsWithNoCredits::remove_para_ids_with_no_credits(
319
3087
                &mut parathreads,
320
3087
                &old_assigned_para_ids,
321
3087
            );
322
3087

            
323
3087
            let mut shuffle_collators = None;
324
3087
            // If the random_seed is all zeros, we don't shuffle the list of collators nor the list
325
3087
            // of container chains.
326
3087
            // This should only happen in tests_without_core_config, and in the genesis block.
327
3087
            if random_seed != [0; 32] {
328
50
                let mut rng: ChaCha20Rng = SeedableRng::from_seed(random_seed);
329
50
                container_chain_ids.shuffle(&mut rng);
330
50
                parathreads.shuffle(&mut rng);
331
50
                shuffle_collators = Some(move |collators: &mut Vec<T::AccountId>| {
332
50
                    collators.shuffle(&mut rng);
333
50
                })
334
3037
            }
335

            
336
3087
            let orchestrator_chain: ChainNumCollators = if T::ForceEmptyOrchestrator::get() {
337
385
                ChainNumCollators {
338
385
                    para_id: T::SelfParaId::get(),
339
385
                    min_collators: 0u32,
340
385
                    max_collators: 0u32,
341
385
                }
342
            } else {
343
2702
                ChainNumCollators {
344
2702
                    para_id: T::SelfParaId::get(),
345
2702
                    min_collators: T::HostConfiguration::min_collators_for_orchestrator(
346
2702
                        target_session_index,
347
2702
                    ),
348
2702
                    max_collators: T::HostConfiguration::max_collators_for_orchestrator(
349
2702
                        target_session_index,
350
2702
                    ),
351
2702
                }
352
            };
353

            
354
            // Initialize list of chains as `[container1, container2, parathread1, parathread2]`.
355
            // The order means priority: the first chain in the list will be the first one to get assigned collators.
356
            // Chains will not be assigned less than `min_collators`, except the orchestrator chain.
357
            // First all chains will be assigned `min_collators`, and then the first one will be assigned up to `max`,
358
            // then the second one, and so on.
359
3087
            let mut bulk_paras = vec![];
360
3087
            let mut pool_paras = vec![];
361

            
362
7722
            for para_id in &container_chain_ids {
363
4635
                bulk_paras.push(ChainNumCollators {
364
4635
                    para_id: *para_id,
365
4635
                    min_collators: collators_per_container,
366
4635
                    max_collators: collators_per_container,
367
4635
                });
368
4635
            }
369
3276
            for para_id in &parathreads {
370
189
                pool_paras.push(ChainNumCollators {
371
189
                    para_id: *para_id,
372
189
                    min_collators: collators_per_parathread,
373
189
                    max_collators: collators_per_parathread,
374
189
                });
375
189
            }
376

            
377
3087
            let (chains, need_to_charge_tip) =
378
3087
                if let Some(core_allocation_configuration) = maybe_core_allocation_configuration {
379
385
                    Self::order_paras_with_core_config(
380
385
                        bulk_paras,
381
385
                        pool_paras,
382
385
                        &core_allocation_configuration,
383
385
                        target_session_index,
384
385
                        collators.len() as u32,
385
385
                        collators_per_container,
386
385
                        collators_per_parathread,
387
385
                    )
388
                } else {
389
2702
                    Self::order_paras(
390
2702
                        bulk_paras,
391
2702
                        pool_paras,
392
2702
                        target_session_index,
393
2702
                        collators.len() as u32,
394
2702
                        collators_per_container,
395
2702
                        collators_per_parathread,
396
2702
                    )
397
                };
398

            
399
            // We assign new collators
400
            // we use the config scheduled at the target_session_index
401
3087
            let new_assigned =
402
3087
                if T::ShouldRotateAllCollators::should_rotate_all_collators(target_session_index) {
403
349
                    log::debug!(
404
                        "Collator assignment: rotating collators. Session {:?}, Seed: {:?}",
405
                        current_session_index.encode(),
406
                        random_seed
407
                    );
408

            
409
349
                    Self::deposit_event(Event::NewPendingAssignment {
410
349
                        random_seed,
411
349
                        full_rotation: true,
412
349
                        target_session: target_session_index,
413
349
                    });
414
349

            
415
349
                    Assignment::<T>::assign_collators_rotate_all(
416
349
                        collators,
417
349
                        orchestrator_chain,
418
349
                        chains,
419
349
                        shuffle_collators,
420
349
                    )
421
                } else {
422
2738
                    log::debug!(
423
                        "Collator assignment: keep old assigned. Session {:?}, Seed: {:?}",
424
                        current_session_index.encode(),
425
                        random_seed
426
                    );
427

            
428
2738
                    Self::deposit_event(Event::NewPendingAssignment {
429
2738
                        random_seed,
430
2738
                        full_rotation: false,
431
2738
                        target_session: target_session_index,
432
2738
                    });
433
2738

            
434
2738
                    Assignment::<T>::assign_collators_always_keep_old(
435
2738
                        collators,
436
2738
                        orchestrator_chain,
437
2738
                        chains,
438
2738
                        old_assigned.clone(),
439
2738
                        shuffle_collators,
440
2738
                    )
441
                };
442

            
443
3087
            let mut new_assigned = match new_assigned {
444
3083
                Ok(x) => x,
445
4
                Err(e) => {
446
4
                    log::error!(
447
3
                        "Error in collator assignment, will keep previous assignment. {:?}",
448
                        e
449
                    );
450

            
451
4
                    old_assigned.clone()
452
                }
453
            };
454

            
455
3087
            let mut assigned_containers = new_assigned.container_chains.clone();
456
4880
            assigned_containers.retain(|_, v| !v.is_empty());
457

            
458
            // On congestion, prioritized chains need to pay the minimum tip of the prioritized chains
459
3087
            let maybe_tip: Option<BalanceOf<T>> = if !need_to_charge_tip {
460
906
                None
461
            } else {
462
2181
                assigned_containers
463
2181
                    .into_keys()
464
2181
                    .filter_map(T::CollatorAssignmentTip::get_para_tip)
465
2181
                    .min()
466
            };
467

            
468
            // TODO: this probably is asking for a refactor
469
            // only apply the onCollatorAssignedHook if sufficient collators
470
7722
            for para_id in &container_chain_ids {
471
4635
                if !new_assigned
472
4635
                    .container_chains
473
4635
                    .get(para_id)
474
4635
                    .unwrap_or(&vec![])
475
4635
                    .is_empty()
476
                {
477
2357
                    if let Err(e) = T::CollatorAssignmentHook::on_collators_assigned(
478
2357
                        *para_id,
479
2357
                        maybe_tip.as_ref(),
480
2357
                        false,
481
2357
                    ) {
482
                        // On error remove para from assignment
483
2
                        log::warn!(
484
                            "CollatorAssignmentHook error! Removing para {} from assignment: {:?}",
485
                            u32::from(*para_id),
486
                            e
487
                        );
488
2
                        new_assigned.container_chains.remove(para_id);
489
2355
                    }
490
2278
                }
491
            }
492

            
493
3276
            for para_id in &parathreads {
494
189
                if !new_assigned
495
189
                    .container_chains
496
189
                    .get(para_id)
497
189
                    .unwrap_or(&vec![])
498
189
                    .is_empty()
499
                {
500
148
                    if let Err(e) = T::CollatorAssignmentHook::on_collators_assigned(
501
148
                        *para_id,
502
148
                        maybe_tip.as_ref(),
503
148
                        true,
504
148
                    ) {
505
                        // On error remove para from assignment
506
                        log::warn!(
507
                            "CollatorAssignmentHook error! Removing para {} from assignment: {:?}",
508
                            u32::from(*para_id),
509
                            e
510
                        );
511
                        new_assigned.container_chains.remove(para_id);
512
148
                    }
513
41
                }
514
            }
515

            
516
3087
            Self::store_collator_fullness(
517
3087
                &new_assigned,
518
3087
                T::HostConfiguration::max_collators(target_session_index),
519
3087
            );
520
3087

            
521
3087
            let mut pending = PendingCollatorContainerChain::<T>::get();
522
3087

            
523
3087
            let old_assigned_changed = old_assigned != new_assigned;
524
3087
            let mut pending_changed = false;
525
            // Update CollatorContainerChain using last entry of pending, if needed
526
3087
            if let Some(current) = pending.take() {
527
820
                pending_changed = true;
528
820
                CollatorContainerChain::<T>::put(current);
529
2286
            }
530
3087
            if old_assigned_changed {
531
764
                pending = Some(new_assigned.clone());
532
764
                pending_changed = true;
533
2503
            }
534
            // Update PendingCollatorContainerChain, if it changed
535
3087
            if pending_changed {
536
1278
                PendingCollatorContainerChain::<T>::put(pending);
537
2287
            }
538

            
539
            // Only applies to session index 0
540
3087
            if current_session_index == &T::SessionIndex::zero() {
541
388
                CollatorContainerChain::<T>::put(new_assigned.clone());
542
388
                return SessionChangeOutcome {
543
388
                    active_assignment: new_assigned.clone(),
544
388
                    next_assignment: new_assigned,
545
388
                    num_total_registered_paras,
546
388
                };
547
2699
            }
548
2699

            
549
2699
            SessionChangeOutcome {
550
2699
                active_assignment: old_assigned,
551
2699
                next_assignment: new_assigned,
552
2699
                num_total_registered_paras,
553
2699
            }
554
3087
        }
555

            
556
        /// Count number of collators assigned to any chain, divide that by `max_collators` and store
557
        /// in pallet storage.
558
3087
        fn store_collator_fullness(
559
3087
            new_assigned: &AssignedCollators<T::AccountId>,
560
3087
            max_collators: u32,
561
3087
        ) {
562
3087
            // Count number of assigned collators
563
3087
            let mut num_collators = 0;
564
3087
            num_collators += new_assigned.orchestrator_chain.len();
565
7893
            for (_para_id, collators) in &new_assigned.container_chains {
566
4806
                num_collators += collators.len();
567
4806
            }
568

            
569
3087
            let mut num_collators = num_collators as u32;
570
3087
            if num_collators > max_collators {
571
145
                // Shouldn't happen but just in case
572
145
                num_collators = max_collators;
573
3085
            }
574

            
575
3087
            let ratio = Perbill::from_rational(num_collators, max_collators);
576
3087

            
577
3087
            CollatorFullnessRatio::<T>::put(ratio);
578
3087
        }
579

            
580
        // Returns the assigned collators as read from storage.
581
        // If there is any item in PendingCollatorContainerChain, returns that element.
582
        // Otherwise, reads and returns the current CollatorContainerChain
583
3087
        fn read_assigned_collators() -> AssignedCollators<T::AccountId> {
584
3087
            let mut pending_collator_list = PendingCollatorContainerChain::<T>::get();
585

            
586
3087
            if let Some(assigned_collators) = pending_collator_list.take() {
587
820
                assigned_collators
588
            } else {
589
                // Read current
590
2267
                CollatorContainerChain::<T>::get()
591
            }
592
3087
        }
593

            
594
3087
        pub fn initializer_on_new_session(
595
3087
            session_index: &T::SessionIndex,
596
3087
            collators: Vec<T::AccountId>,
597
3087
        ) -> SessionChangeOutcome<T> {
598
3087
            let random_seed = Randomness::<T>::take();
599
3087
            let num_collators = collators.len();
600
3087
            let assigned_collators = Self::assign_collators(session_index, random_seed, collators);
601
3087
            let num_total_registered_paras = assigned_collators.num_total_registered_paras;
602
3087

            
603
3087
            frame_system::Pallet::<T>::register_extra_weight_unchecked(
604
3087
                T::WeightInfo::new_session(num_collators as u32, num_total_registered_paras),
605
3087
                DispatchClass::Mandatory,
606
3087
            );
607
3087

            
608
3087
            assigned_collators
609
3087
        }
610

            
611
90236
        pub fn collator_container_chain() -> AssignedCollators<T::AccountId> {
612
90236
            CollatorContainerChain::<T>::get()
613
90236
        }
614

            
615
25
        pub fn pending_collator_container_chain() -> Option<AssignedCollators<T::AccountId>> {
616
25
            PendingCollatorContainerChain::<T>::get()
617
25
        }
618

            
619
1
        pub fn randomness() -> [u8; 32] {
620
1
            Randomness::<T>::get()
621
1
        }
622
    }
623

            
624
    impl<T: Config> GetContainerChainAuthor<T::AccountId> for Pallet<T> {
625
        // TODO: pending collator container chain if the block is a session change!
626
89952
        fn author_for_slot(slot: Slot, para_id: ParaId) -> Option<T::AccountId> {
627
89952
            let assigned_collators = Pallet::<T>::collator_container_chain();
628
89952
            let collators = if para_id == T::SelfParaId::get() {
629
48530
                Some(&assigned_collators.orchestrator_chain)
630
            } else {
631
41422
                assigned_collators.container_chains.get(&para_id)
632
2262
            }?;
633

            
634
87690
            if collators.is_empty() {
635
                // Avoid division by zero below
636
18936
                return None;
637
68754
            }
638
68754
            let author_index = u64::from(slot) % collators.len() as u64;
639
68754
            collators.get(author_index as usize).cloned()
640
89952
        }
641

            
642
        #[cfg(feature = "runtime-benchmarks")]
643
        fn set_authors_for_para_id(para_id: ParaId, authors: Vec<T::AccountId>) {
644
            let mut assigned_collators = Pallet::<T>::collator_container_chain();
645
            assigned_collators.container_chains.insert(para_id, authors);
646
            CollatorContainerChain::<T>::put(assigned_collators);
647
        }
648
    }
649

            
650
54218
    #[pallet::hooks]
651
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
652
27849
        fn on_initialize(n: BlockNumberFor<T>) -> Weight {
653
27849
            let mut weight = Weight::zero();
654
27849

            
655
27849
            // Account reads and writes for on_finalize
656
27849
            if T::GetRandomnessForNextBlock::should_end_session(n.saturating_add(One::one())) {
657
2619
                weight += T::DbWeight::get().reads_writes(1, 1);
658
25230
            }
659

            
660
27849
            weight
661
27849
        }
662

            
663
27571
        fn on_finalize(n: BlockNumberFor<T>) {
664
27571
            // If the next block is a session change, read randomness and store in pallet storage
665
27571
            if T::GetRandomnessForNextBlock::should_end_session(n.saturating_add(One::one())) {
666
2616
                let random_seed = T::GetRandomnessForNextBlock::get_randomness();
667
2616
                Randomness::<T>::put(random_seed);
668
24955
            }
669
27571
        }
670
    }
671
}
672

            
673
/// Balance used by this pallet
674
pub type BalanceOf<T> =
675
    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
676

            
677
pub struct RotateCollatorsEveryNSessions<Period>(PhantomData<Period>);
678

            
679
impl<Period> ShouldRotateAllCollators<u32> for RotateCollatorsEveryNSessions<Period>
680
where
681
    Period: Get<u32>,
682
{
683
2924
    fn should_rotate_all_collators(session_index: u32) -> bool {
684
2924
        let period = Period::get();
685
2924

            
686
2924
        if period == 0 {
687
            // A period of 0 disables rotation
688
420
            false
689
        } else {
690
2504
            session_index % Period::get() == 0
691
        }
692
2924
    }
693
}
694

            
695
pub trait GetRandomnessForNextBlock<BlockNumber> {
696
    fn should_end_session(block_number: BlockNumber) -> bool;
697
    fn get_randomness() -> [u8; 32];
698
}
699

            
700
impl<BlockNumber> GetRandomnessForNextBlock<BlockNumber> for () {
701
2040
    fn should_end_session(_block_number: BlockNumber) -> bool {
702
2040
        false
703
2040
    }
704

            
705
    fn get_randomness() -> [u8; 32] {
706
        [0; 32]
707
    }
708
}