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
//! # Configuration Pallet
18
//!
19
//! This pallet stores the configuration for an orchestration-collator assignation chain. In
20
//! particular stores:
21
//!
22
//!    - How many collators are taken.
23
//!    - How many of those collators should be serving the orchestrator chain
24
//!    - Howe many of those collators should be serving the containerChains
25
//!
26
//! All configuration changes are protected behind the root origin
27
//! CHanges to the configuration are not immeditaly applied, but rather we wait
28
//! T::SessionDelay to apply these changes
29

            
30
#![cfg_attr(not(feature = "std"), no_std)]
31

            
32
#[cfg(test)]
33
mod mock;
34

            
35
#[cfg(test)]
36
mod tests;
37
pub mod weights;
38

            
39
pub use weights::WeightInfo;
40

            
41
#[cfg(any(test, feature = "runtime-benchmarks"))]
42
mod benchmarks;
43

            
44
pub use pallet::*;
45
use {
46
    frame_support::pallet_prelude::*,
47
    frame_system::pallet_prelude::*,
48
    serde::{Deserialize, Serialize},
49
    sp_runtime::{traits::AtLeast32BitUnsigned, Perbill, Saturating},
50
    sp_std::prelude::*,
51
    tp_traits::{FullRotationModes, GetSessionIndex},
52
};
53

            
54
const LOG_TARGET: &str = "pallet_configuration";
55

            
56
/// All configuration of the runtime with respect to parachains and parathreads.
57
#[derive(
58
    Clone,
59
    Encode,
60
    Decode,
61
    PartialEq,
62
    sp_core::RuntimeDebug,
63
12360
    scale_info::TypeInfo,
64
    Serialize,
65
    Deserialize,
66
    MaxEncodedLen,
67
)]
68
pub struct HostConfiguration {
69
    /// Maximum number of collators, in total, including orchestrator and containers
70
    pub max_collators: u32,
71
    /// Minimum number of collators to be assigned to orchestrator chain
72
    pub min_orchestrator_collators: u32,
73
    /// Maximum number of collators to be assigned to orchestrator chain after all the container chains have been
74
    /// assigned collators.
75
    pub max_orchestrator_collators: u32,
76
    /// How many collators to assign to one container chain
77
    pub collators_per_container: u32,
78
    /// Rotate all collators once every n sessions. If this value is 0 means that there is no rotation
79
    pub full_rotation_period: u32,
80
    /// How many collators to assign to one parathread
81
    // TODO: for now we only support 1 collator per parathread because using Aura for consensus conflicts with
82
    // the idea of being able to create blocks every n slots: if there are 2 collators and we create blocks
83
    // every 2 slots, 1 collator will create all the blocks.
84
    pub collators_per_parathread: u32,
85
    /// How many parathreads can be assigned to one collator
86
    pub parathreads_per_collator: u32,
87
    /// Ratio of collators that we expect to be assigned to container chains. Affects fees.
88
    pub target_container_chain_fullness: Perbill,
89
    ///  Maximum number of cores that can be allocated to parachains (only applicable for solo chain)
90
    pub max_parachain_cores_percentage: Option<Perbill>,
91
    /// Full rotation mode
92
    pub full_rotation_mode: FullRotationModes,
93
}
94

            
95
impl Default for HostConfiguration {
96
37344
    fn default() -> Self {
97
37344
        Self {
98
37344
            max_collators: 100u32,
99
37344
            min_orchestrator_collators: 2u32,
100
37344
            max_orchestrator_collators: 5u32,
101
37344
            collators_per_container: 2u32,
102
37344
            full_rotation_period: 24u32,
103
37344
            collators_per_parathread: 1,
104
37344
            parathreads_per_collator: 1,
105
37344
            target_container_chain_fullness: Perbill::from_percent(80),
106
37344
            max_parachain_cores_percentage: None,
107
37344
            full_rotation_mode: Default::default(),
108
37344
        }
109
37344
    }
110
}
111

            
112
/// Enumerates the possible inconsistencies of `HostConfiguration`.
113
#[derive(Debug)]
114
pub enum InconsistentError {
115
    /// `max_orchestrator_collators` is lower than `min_orchestrator_collators`
116
    MaxCollatorsLowerThanMinCollators,
117
    /// `min_orchestrator_collators` must be at least 1
118
    MinOrchestratorCollatorsTooLow,
119
    /// `max_collators` must be at least 1
120
    MaxCollatorsTooLow,
121
    /// Tried to modify an unimplemented parameter
122
    UnimplementedParameter,
123
}
124

            
125
impl HostConfiguration {
126
    /// Checks that this instance is consistent with the requirements on each individual member.
127
    ///
128
    /// # Errors
129
    ///
130
    /// This function returns an error if the configuration is inconsistent.
131
17833
    pub fn check_consistency(
132
17833
        &self,
133
17833
        allow_empty_orchestrator: bool,
134
17833
    ) -> Result<(), InconsistentError> {
135
17833
        if self.max_collators < 1 {
136
1
            return Err(InconsistentError::MaxCollatorsTooLow);
137
17832
        }
138
17832
        if self.min_orchestrator_collators < 1 && !allow_empty_orchestrator {
139
2
            return Err(InconsistentError::MinOrchestratorCollatorsTooLow);
140
17830
        }
141
17830
        if self.max_orchestrator_collators < self.min_orchestrator_collators {
142
            return Err(InconsistentError::MaxCollatorsLowerThanMinCollators);
143
17830
        }
144
17830
        if self.parathreads_per_collator != 1 {
145
            return Err(InconsistentError::UnimplementedParameter);
146
17830
        }
147
17830
        if self.max_collators < self.min_orchestrator_collators {
148
2
            return Err(InconsistentError::MaxCollatorsLowerThanMinCollators);
149
17828
        }
150
17828
        Ok(())
151
17833
    }
152

            
153
    /// Checks that this instance is consistent with the requirements on each individual member.
154
    ///
155
    /// # Panics
156
    ///
157
    /// This function panics if the configuration is inconsistent.
158
16135
    pub fn panic_if_not_consistent(&self, allow_empty_orchestrator: bool) {
159
16135
        if let Err(err) = self.check_consistency(allow_empty_orchestrator) {
160
            panic!("Host configuration is inconsistent: {:?}", err);
161
16135
        }
162
16135
    }
163
}
164

            
165
11171
#[frame_support::pallet]
166
pub mod pallet {
167
    use tp_traits::{FullRotationMode, GetHostConfiguration};
168

            
169
    use super::*;
170

            
171
46877
    #[pallet::pallet]
172
    pub struct Pallet<T>(_);
173

            
174
    /// Configure the pallet by specifying the parameters and types on which it depends.
175
    #[pallet::config]
176
    pub trait Config: frame_system::Config {
177
        type SessionIndex: parity_scale_codec::FullCodec + TypeInfo + Copy + AtLeast32BitUnsigned;
178

            
179
        // `SESSION_DELAY` is used to delay any changes to Paras registration or configurations.
180
        // Wait until the session index is 2 larger then the current index to apply any changes,
181
        // which guarantees that at least one full session has passed before any changes are applied.
182
        #[pallet::constant]
183
        type SessionDelay: Get<Self::SessionIndex>;
184

            
185
        type CurrentSessionIndex: GetSessionIndex<Self::SessionIndex>;
186

            
187
        type ForceEmptyOrchestrator: Get<bool>;
188

            
189
        /// Weight information for extrinsics in this pallet.
190
        type WeightInfo: WeightInfo;
191
    }
192

            
193
20
    #[pallet::error]
194
    pub enum Error<T> {
195
        /// The new value for a configuration parameter is invalid.
196
        InvalidNewValue,
197
    }
198

            
199
    /// The active configuration for the current session.
200
92622
    #[pallet::storage]
201
    pub type ActiveConfig<T: Config> = StorageValue<_, HostConfiguration, ValueQuery>;
202

            
203
    /// Pending configuration changes.
204
    ///
205
    /// This is a list of configuration changes, each with a session index at which it should
206
    /// be applied.
207
    ///
208
    /// The list is sorted ascending by session index. Also, this list can only contain at most
209
    /// 2 items: for the next session and for the `scheduled_session`.
210
76522
    #[pallet::storage]
211
    // TODO: instead of making this unbounded, we could refactor into a BoundedVec<X, Const<2>>
212
    // since it can have at most 2 items anyway. But the upstream pallet doesn't do that so low
213
    // priority.
214
    #[pallet::unbounded]
215
    pub type PendingConfigs<T: Config> =
216
        StorageValue<_, Vec<(T::SessionIndex, HostConfiguration)>, ValueQuery>;
217

            
218
    /// If this is set, then the configuration setters will bypass the consistency checks. This
219
    /// is meant to be used only as the last resort.
220
816
    #[pallet::storage]
221
    pub(crate) type BypassConsistencyCheck<T: Config> = StorageValue<_, bool, ValueQuery>;
222

            
223
    #[pallet::genesis_config]
224
    #[derive(frame_support::DefaultNoBound)]
225
    pub struct GenesisConfig<T: Config> {
226
        pub config: HostConfiguration,
227
        #[serde(skip)]
228
        pub _config: sp_std::marker::PhantomData<T>,
229
    }
230

            
231
537
    #[pallet::genesis_build]
232
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
233
1327
        fn build(&self) {
234
1327
            self.config
235
1327
                .panic_if_not_consistent(T::ForceEmptyOrchestrator::get());
236
1327
            ActiveConfig::<T>::put(&self.config);
237
1327
        }
238
    }
239

            
240
668
    #[pallet::call]
241
    impl<T: Config> Pallet<T> {
242
        #[pallet::call_index(0)]
243
        #[pallet::weight((
244
			T::WeightInfo::set_config_with_u32(),
245
			DispatchClass::Operational,
246
		))]
247
24
        pub fn set_max_collators(origin: OriginFor<T>, new: u32) -> DispatchResult {
248
24
            ensure_root(origin)?;
249
18
            Self::schedule_config_update(|config| {
250
18
                config.max_collators = new;
251
18
            })
252
        }
253

            
254
        #[pallet::call_index(1)]
255
        #[pallet::weight((
256
			T::WeightInfo::set_config_with_u32(),
257
			DispatchClass::Operational,
258
		))]
259
23
        pub fn set_min_orchestrator_collators(origin: OriginFor<T>, new: u32) -> DispatchResult {
260
23
            ensure_root(origin)?;
261
17
            Self::schedule_config_update(|config| {
262
17
                if config.max_orchestrator_collators < new {
263
14
                    config.max_orchestrator_collators = new;
264
14
                }
265
17
                config.min_orchestrator_collators = new;
266
17
            })
267
        }
268

            
269
        #[pallet::call_index(2)]
270
        #[pallet::weight((
271
			T::WeightInfo::set_config_with_u32(),
272
			DispatchClass::Operational,
273
		))]
274
18
        pub fn set_max_orchestrator_collators(origin: OriginFor<T>, new: u32) -> DispatchResult {
275
18
            ensure_root(origin)?;
276
12
            Self::schedule_config_update(|config| {
277
12
                if config.min_orchestrator_collators > new {
278
4
                    config.min_orchestrator_collators = new;
279
10
                }
280
12
                config.max_orchestrator_collators = new;
281
12
            })
282
        }
283

            
284
        #[pallet::call_index(3)]
285
        #[pallet::weight((
286
			T::WeightInfo::set_config_with_u32(),
287
			DispatchClass::Operational,
288
		))]
289
27
        pub fn set_collators_per_container(origin: OriginFor<T>, new: u32) -> DispatchResult {
290
27
            ensure_root(origin)?;
291
21
            Self::schedule_config_update(|config| {
292
21
                config.collators_per_container = new;
293
21
            })
294
        }
295

            
296
        #[pallet::call_index(4)]
297
        #[pallet::weight((
298
			T::WeightInfo::set_config_with_u32(),
299
			DispatchClass::Operational,
300
		))]
301
7
        pub fn set_full_rotation_period(origin: OriginFor<T>, new: u32) -> DispatchResult {
302
7
            ensure_root(origin)?;
303
7
            Self::schedule_config_update(|config| {
304
7
                config.full_rotation_period = new;
305
7
            })
306
        }
307

            
308
        #[pallet::call_index(5)]
309
        #[pallet::weight((
310
        T::WeightInfo::set_config_with_u32(),
311
        DispatchClass::Operational,
312
        ))]
313
6
        pub fn set_collators_per_parathread(origin: OriginFor<T>, new: u32) -> DispatchResult {
314
6
            ensure_root(origin)?;
315
6
            Self::schedule_config_update(|config| {
316
6
                config.collators_per_parathread = new;
317
6
            })
318
        }
319

            
320
        #[pallet::call_index(6)]
321
        #[pallet::weight((
322
        T::WeightInfo::set_config_with_u32(),
323
        DispatchClass::Operational,
324
        ))]
325
        pub fn set_parathreads_per_collator(origin: OriginFor<T>, new: u32) -> DispatchResult {
326
            ensure_root(origin)?;
327
            Self::schedule_config_update(|config| {
328
                config.parathreads_per_collator = new;
329
            })
330
        }
331

            
332
        #[pallet::call_index(7)]
333
        #[pallet::weight((
334
        T::WeightInfo::set_config_with_u32(),
335
        DispatchClass::Operational,
336
        ))]
337
        pub fn set_target_container_chain_fullness(
338
            origin: OriginFor<T>,
339
            new: Perbill,
340
6
        ) -> DispatchResult {
341
6
            ensure_root(origin)?;
342
6
            Self::schedule_config_update(|config| {
343
6
                config.target_container_chain_fullness = new;
344
6
            })
345
        }
346

            
347
        #[pallet::call_index(8)]
348
        #[pallet::weight((
349
        T::WeightInfo::set_config_with_u32(),
350
        DispatchClass::Operational,
351
        ))]
352
        pub fn set_max_parachain_cores_percentage(
353
            origin: OriginFor<T>,
354
            new: Option<Perbill>,
355
        ) -> DispatchResult {
356
            ensure_root(origin)?;
357
            Self::schedule_config_update(|config| {
358
                config.max_parachain_cores_percentage = new;
359
            })
360
        }
361

            
362
        #[pallet::call_index(9)]
363
        #[pallet::weight((
364
        T::WeightInfo::set_config_with_u32(),
365
        DispatchClass::Operational,
366
        ))]
367
        pub fn set_full_rotation_mode(
368
            origin: OriginFor<T>,
369
            orchestrator: Option<FullRotationMode>,
370
            parachain: Option<FullRotationMode>,
371
            parathread: Option<FullRotationMode>,
372
12
        ) -> DispatchResult {
373
12
            ensure_root(origin)?;
374
12
            Self::schedule_config_update(|config| {
375
12
                if let Some(orchestrator) = orchestrator {
376
12
                    config.full_rotation_mode.orchestrator = orchestrator;
377
12
                }
378
12
                if let Some(parachain) = parachain {
379
12
                    config.full_rotation_mode.parachain = parachain;
380
12
                }
381
12
                if let Some(parathread) = parathread {
382
12
                    config.full_rotation_mode.parathread = parathread;
383
12
                }
384
12
            })
385
        }
386

            
387
        /// Setting this to true will disable consistency checks for the configuration setters.
388
        /// Use with caution.
389
        #[pallet::call_index(44)]
390
        #[pallet::weight((
391
			T::DbWeight::get().writes(1),
392
			DispatchClass::Operational,
393
		))]
394
        pub fn set_bypass_consistency_check(origin: OriginFor<T>, new: bool) -> DispatchResult {
395
            ensure_root(origin)?;
396
            BypassConsistencyCheck::<T>::put(new);
397
            Ok(())
398
        }
399
    }
400

            
401
    /// A struct that holds the configuration that was active before the session change and optionally
402
    /// a configuration that became active after the session change.
403
    pub struct SessionChangeOutcome {
404
        /// Previously active configuration.
405
        pub prev_config: HostConfiguration,
406
        /// If new configuration was applied during the session change, this is the new configuration.
407
        pub new_config: Option<HostConfiguration>,
408
    }
409

            
410
    impl<T: Config> Pallet<T> {
411
        /// Called by the initializer to note that a new session has started.
412
        ///
413
        /// Returns the configuration that was actual before the session change and the configuration
414
        /// that became active after the session change. If there were no scheduled changes, both will
415
        /// be the same.
416
4721
        pub fn initializer_on_new_session(session_index: &T::SessionIndex) -> SessionChangeOutcome {
417
4721
            let pending_configs = <PendingConfigs<T>>::get();
418
4721
            let prev_config = ActiveConfig::<T>::get();
419
4721

            
420
4721
            // No pending configuration changes, so we're done.
421
4721
            if pending_configs.is_empty() {
422
4575
                return SessionChangeOutcome {
423
4575
                    prev_config,
424
4575
                    new_config: None,
425
4575
                };
426
146
            }
427
146

            
428
146
            // We partition those configs scheduled for the present
429
146
            // and those for the future
430
146
            let (mut past_and_present, future) = pending_configs
431
146
                .into_iter()
432
156
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| {
433
156
                    apply_at_session <= *session_index
434
156
                });
435
146

            
436
146
            if past_and_present.len() > 1 {
437
                // This should never happen since we schedule configuration changes only into the future
438
                // sessions and this handler called for each session change.
439
                log::error!(
440
                    target: LOG_TARGET,
441
                    "Skipping applying configuration changes scheduled sessions in the past",
442
                );
443
146
            }
444

            
445
146
            let new_config = past_and_present.pop().map(|(_, config)| config);
446
146
            if let Some(ref new_config) = new_config {
447
78
                // Apply the new configuration.
448
78
                ActiveConfig::<T>::put(new_config);
449
78
            }
450

            
451
            // We insert future as PendingConfig
452
146
            <PendingConfigs<T>>::put(future);
453
146

            
454
146
            SessionChangeOutcome {
455
146
                prev_config,
456
146
                new_config,
457
146
            }
458
4721
        }
459

            
460
        /// Return the session index that should be used for any future scheduled changes.
461
94
        fn scheduled_session() -> T::SessionIndex {
462
94
            T::CurrentSessionIndex::session_index().saturating_add(T::SessionDelay::get())
463
94
        }
464

            
465
        /// Forcibly set the active config. This should be used with extreme care, and typically
466
        /// only when enabling parachains runtime pallets for the first time on a chain which has
467
        /// been running without them.
468
        pub fn force_set_active_config(config: HostConfiguration) {
469
            ActiveConfig::<T>::set(config);
470
        }
471

            
472
        /// This function should be used to update members of the configuration.
473
        ///
474
        /// This function is used to update the configuration in a way that is safe. It will check the
475
        /// resulting configuration and ensure that the update is valid. If the update is invalid, it
476
        /// will check if the previous configuration was valid. If it was invalid, we proceed with
477
        /// updating the configuration, giving a chance to recover from such a condition.
478
        ///
479
        /// The actual configuration change take place after a couple of sessions have passed. In case
480
        /// this function is called more than once in a session, then the pending configuration change
481
        /// will be updated and the changes will be applied at once.
482
        // NOTE: Explicitly tell rustc not to inline this because otherwise heuristics note the incoming
483
        // closure making it's attractive to inline. However, in this case, we will end up with lots of
484
        // duplicated code (making this function to show up in the top of heaviest functions) only for
485
        // the sake of essentially avoiding an indirect call. Doesn't worth it.
486
        #[inline(never)]
487
99
        fn schedule_config_update(updater: impl FnOnce(&mut HostConfiguration)) -> DispatchResult {
488
99
            let mut pending_configs = <PendingConfigs<T>>::get();
489
99

            
490
99
            // 1. pending_configs = []
491
99
            //    No pending configuration changes.
492
99
            //
493
99
            //    That means we should use the active config as the base configuration. We will insert
494
99
            //    the new pending configuration as (cur+2, new_config) into the list.
495
99
            //
496
99
            // 2. pending_configs = [(cur+2, X)]
497
99
            //    There is a configuration that is pending for the scheduled session.
498
99
            //
499
99
            //    We will use X as the base configuration. We can update the pending configuration X
500
99
            //    directly.
501
99
            //
502
99
            // 3. pending_configs = [(cur+1, X)]
503
99
            //    There is a pending configuration scheduled and it will be applied in the next session.
504
99
            //
505
99
            //    We will use X as the base configuration. We need to schedule a new configuration change
506
99
            //    for the `scheduled_session` and use X as the base for the new configuration.
507
99
            //
508
99
            // 4. pending_configs = [(cur+1, X), (cur+2, Y)]
509
99
            //    There is a pending configuration change in the next session and for the scheduled
510
99
            //    session. Due to case â„–3, we can be sure that Y is based on top of X. This means we
511
99
            //    can use Y as the base configuration and update Y directly.
512
99
            //
513
99
            // There cannot be (cur, X) because those are applied in the session change handler for the
514
99
            // current session.
515
99

            
516
99
            // First, we need to decide what we should use as the base configuration.
517
99
            let mut base_config = pending_configs
518
99
                .last()
519
99
                .map(|(_, config)| config.clone())
520
99
                .unwrap_or_else(Self::config);
521
99
            let base_config_consistent = base_config
522
99
                .check_consistency(T::ForceEmptyOrchestrator::get())
523
99
                .is_ok();
524
99

            
525
99
            // Now, we need to decide what the new configuration should be.
526
99
            // We also move the `base_config` to `new_config` to empahsize that the base config was
527
99
            // destroyed by the `updater`.
528
99
            updater(&mut base_config);
529
99
            let new_config = base_config;
530
99

            
531
99
            if BypassConsistencyCheck::<T>::get() {
532
                // This will emit a warning each configuration update if the consistency check is
533
                // bypassed. This is an attempt to make sure the bypass is not accidentally left on.
534
                log::warn!(
535
                    target: LOG_TARGET,
536
                    "Bypassing the consistency check for the configuration change!",
537
                );
538
99
            } else if let Err(e) = new_config.check_consistency(T::ForceEmptyOrchestrator::get()) {
539
5
                if base_config_consistent {
540
                    // Base configuration is consistent and the new configuration is inconsistent.
541
                    // This means that the value set by the `updater` is invalid and we can return
542
                    // it as an error.
543
5
                    log::warn!(
544
                        target: LOG_TARGET,
545
                        "Configuration change rejected due to invalid configuration: {:?}",
546
                        e,
547
                    );
548
5
                    return Err(Error::<T>::InvalidNewValue.into());
549
                } else {
550
                    // The configuration was already broken, so we can as well proceed with the update.
551
                    // You cannot break something that is already broken.
552
                    //
553
                    // That will allow to call several functions and ultimately return the configuration
554
                    // into consistent state.
555
                    log::warn!(
556
                        target: LOG_TARGET,
557
                        "The new configuration is broken but the old is broken as well. Proceeding",
558
                    );
559
                }
560
94
            }
561

            
562
94
            let scheduled_session = Self::scheduled_session();
563

            
564
94
            if let Some(&mut (_, ref mut config)) = pending_configs
565
94
                .iter_mut()
566
94
                .find(|&&mut (apply_at_session, _)| apply_at_session >= scheduled_session)
567
16
            {
568
16
                *config = new_config;
569
78
            } else {
570
78
                // We are scheduling a new configuration change for the scheduled session.
571
78
                pending_configs.push((scheduled_session, new_config));
572
78
            }
573

            
574
94
            <PendingConfigs<T>>::put(pending_configs);
575
94

            
576
94
            Ok(())
577
99
        }
578

            
579
39870
        pub fn config() -> HostConfiguration {
580
39870
            ActiveConfig::<T>::get()
581
39870
        }
582

            
583
32881
        pub fn pending_configs() -> Vec<(T::SessionIndex, HostConfiguration)> {
584
32881
            PendingConfigs::<T>::get()
585
32881
        }
586

            
587
32878
        pub fn config_at_session(session_index: T::SessionIndex) -> HostConfiguration {
588
32878
            let (past_and_present, _) = Pallet::<T>::pending_configs()
589
32878
                .into_iter()
590
32878
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
591

            
592
32878
            let config = if let Some(last) = past_and_present.last() {
593
544
                last.1.clone()
594
            } else {
595
32334
                Pallet::<T>::config()
596
            };
597

            
598
32878
            config
599
32878
        }
600
    }
601

            
602
    impl<T: Config> GetHostConfiguration<T::SessionIndex> for Pallet<T> {
603
10200
        fn max_collators(session_index: T::SessionIndex) -> u32 {
604
10200
            Self::config_at_session(session_index).max_collators
605
10200
        }
606

            
607
4699
        fn collators_per_container(session_index: T::SessionIndex) -> u32 {
608
4699
            Self::config_at_session(session_index).collators_per_container
609
4699
        }
610

            
611
4699
        fn collators_per_parathread(session_index: T::SessionIndex) -> u32 {
612
4699
            Self::config_at_session(session_index).collators_per_parathread
613
4699
        }
614

            
615
8205
        fn min_collators_for_orchestrator(session_index: T::SessionIndex) -> u32 {
616
8205
            Self::config_at_session(session_index).min_orchestrator_collators
617
8205
        }
618

            
619
3506
        fn max_collators_for_orchestrator(session_index: T::SessionIndex) -> u32 {
620
3506
            Self::config_at_session(session_index).max_orchestrator_collators
621
3506
        }
622

            
623
        fn target_container_chain_fullness(session_index: T::SessionIndex) -> Perbill {
624
            Self::config_at_session(session_index).target_container_chain_fullness
625
        }
626

            
627
1199
        fn max_parachain_cores_percentage(session_index: T::SessionIndex) -> Option<Perbill> {
628
1199
            Self::config_at_session(session_index).max_parachain_cores_percentage
629
1199
        }
630

            
631
370
        fn full_rotation_mode(session_index: T::SessionIndex) -> FullRotationModes {
632
370
            Self::config_at_session(session_index).full_rotation_mode
633
370
        }
634
    }
635
}