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
extern crate alloc;
32

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

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

            
40
pub use weights::WeightInfo;
41

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

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

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

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

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

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

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

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

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

            
170
    use super::*;
171

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

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

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

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

            
188
        type ForceEmptyOrchestrator: Get<bool>;
189

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
455
146
            SessionChangeOutcome {
456
146
                prev_config,
457
146
                new_config,
458
146
            }
459
4769
        }
460

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

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

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

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

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

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

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

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

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

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

            
577
94
            Ok(())
578
99
        }
579

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

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

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

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

            
599
33206
            config
600
33206
        }
601
    }
602

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

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

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

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

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

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

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

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