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, RuntimeAppPublic, Saturating},
50
    sp_std::prelude::*,
51
    tp_traits::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
1584
    scale_info::TypeInfo,
64
    Serialize,
65
    Deserialize,
66
)]
67
pub struct HostConfiguration {
68
    /// Maximum number of collators, in total, including orchestrator and containers
69
    pub max_collators: u32,
70
    /// Minimum number of collators to be assigned to orchestrator chain
71
    pub min_orchestrator_collators: u32,
72
    /// Maximum number of collators to be assigned to orchestrator chain after all the container chains have been
73
    /// assigned collators.
74
    pub max_orchestrator_collators: u32,
75
    /// How many collators to assign to one container chain
76
    pub collators_per_container: u32,
77
    /// Rotate all collators once every n sessions. If this value is 0 means that there is no rotation
78
    pub full_rotation_period: u32,
79
    /// How many collators to assign to one parathread
80
    // TODO: for now we only support 1 collator per parathread because using Aura for consensus conflicts with
81
    // the idea of being able to create blocks every n slots: if there are 2 collators and we create blocks
82
    // every 2 slots, 1 collator will create all the blocks.
83
    pub collators_per_parathread: u32,
84
    /// How many parathreads can be assigned to one collator
85
    pub parathreads_per_collator: u32,
86
    /// Ratio of collators that we expect to be assigned to container chains. Affects fees.
87
    pub target_container_chain_fullness: Perbill,
88
}
89

            
90
impl Default for HostConfiguration {
91
2940
    fn default() -> Self {
92
2940
        Self {
93
2940
            max_collators: 100u32,
94
2940
            min_orchestrator_collators: 2u32,
95
2940
            max_orchestrator_collators: 5u32,
96
2940
            collators_per_container: 2u32,
97
2940
            full_rotation_period: 24u32,
98
2940
            collators_per_parathread: 1,
99
2940
            parathreads_per_collator: 1,
100
2940
            target_container_chain_fullness: Perbill::from_percent(80),
101
2940
        }
102
2940
    }
103
}
104

            
105
/// Enumerates the possible inconsistencies of `HostConfiguration`.
106
#[derive(Debug)]
107
pub enum InconsistentError {
108
    /// `max_orchestrator_collators` is lower than `min_orchestrator_collators`
109
    MaxCollatorsLowerThanMinCollators,
110
    /// `min_orchestrator_collators` must be at least 1
111
    MinOrchestratorCollatorsTooLow,
112
    /// `max_collators` must be at least 1
113
    MaxCollatorsTooLow,
114
    /// Tried to modify an unimplemented parameter
115
    UnimplementedParameter,
116
}
117

            
118
impl HostConfiguration {
119
    /// Checks that this instance is consistent with the requirements on each individual member.
120
    ///
121
    /// # Errors
122
    ///
123
    /// This function returns an error if the configuration is inconsistent.
124
1511
    pub fn check_consistency(&self) -> Result<(), InconsistentError> {
125
1511
        if self.max_collators < 1 {
126
1
            return Err(InconsistentError::MaxCollatorsTooLow);
127
1510
        }
128
1510
        if self.min_orchestrator_collators < 1 {
129
2
            return Err(InconsistentError::MinOrchestratorCollatorsTooLow);
130
1508
        }
131
1508
        if self.max_orchestrator_collators < self.min_orchestrator_collators {
132
            return Err(InconsistentError::MaxCollatorsLowerThanMinCollators);
133
1508
        }
134
1508
        if self.parathreads_per_collator != 1 {
135
            return Err(InconsistentError::UnimplementedParameter);
136
1508
        }
137
1508
        if self.max_collators < self.min_orchestrator_collators {
138
2
            return Err(InconsistentError::MaxCollatorsLowerThanMinCollators);
139
1506
        }
140
1506
        Ok(())
141
1511
    }
142

            
143
    /// Checks that this instance is consistent with the requirements on each individual member.
144
    ///
145
    /// # Panics
146
    ///
147
    /// This function panics if the configuration is inconsistent.
148
1275
    pub fn panic_if_not_consistent(&self) {
149
1275
        if let Err(err) = self.check_consistency() {
150
            panic!("Host configuration is inconsistent: {:?}", err);
151
1275
        }
152
1275
    }
153
}
154

            
155
#[frame_support::pallet]
156
pub mod pallet {
157
    use tp_traits::GetHostConfiguration;
158

            
159
    use super::*;
160

            
161
13612
    #[pallet::pallet]
162
    #[pallet::without_storage_info]
163
    pub struct Pallet<T>(_);
164

            
165
    /// Configure the pallet by specifying the parameters and types on which it depends.
166
    #[pallet::config]
167
    pub trait Config: frame_system::Config {
168
        type SessionIndex: parity_scale_codec::FullCodec + TypeInfo + Copy + AtLeast32BitUnsigned;
169

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

            
176
        type CurrentSessionIndex: GetSessionIndex<Self::SessionIndex>;
177

            
178
        /// The identifier type for an authority.
179
        type AuthorityId: Member
180
            + Parameter
181
            + RuntimeAppPublic
182
            + MaybeSerializeDeserialize
183
            + MaxEncodedLen;
184

            
185
        /// Weight information for extrinsics in this pallet.
186
        type WeightInfo: WeightInfo;
187
    }
188

            
189
10
    #[pallet::error]
190
    pub enum Error<T> {
191
        /// The new value for a configuration parameter is invalid.
192
        InvalidNewValue,
193
    }
194

            
195
    /// The active configuration for the current session.
196
45454
    #[pallet::storage]
197
    #[pallet::getter(fn config)]
198
    pub(crate) type ActiveConfig<T: Config> = StorageValue<_, HostConfiguration, ValueQuery>;
199

            
200
    /// Pending configuration changes.
201
    ///
202
    /// This is a list of configuration changes, each with a session index at which it should
203
    /// be applied.
204
    ///
205
    /// The list is sorted ascending by session index. Also, this list can only contain at most
206
    /// 2 items: for the next session and for the `scheduled_session`.
207
37255
    #[pallet::storage]
208
    #[pallet::getter(fn pending_configs)]
209
    pub(crate) type PendingConfigs<T: Config> =
210
        StorageValue<_, Vec<(T::SessionIndex, HostConfiguration)>, ValueQuery>;
211

            
212
    /// If this is set, then the configuration setters will bypass the consistency checks. This
213
    /// is meant to be used only as the last resort.
214
92
    #[pallet::storage]
215
    pub(crate) type BypassConsistencyCheck<T: Config> = StorageValue<_, bool, ValueQuery>;
216

            
217
    #[pallet::genesis_config]
218
    #[derive(frame_support::DefaultNoBound)]
219
    pub struct GenesisConfig<T: Config> {
220
        pub config: HostConfiguration,
221
        #[serde(skip)]
222
        pub _config: sp_std::marker::PhantomData<T>,
223
    }
224

            
225
502
    #[pallet::genesis_build]
226
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
227
263
        fn build(&self) {
228
263
            self.config.panic_if_not_consistent();
229
263
            ActiveConfig::<T>::put(&self.config);
230
263
        }
231
    }
232

            
233
113
    #[pallet::call]
234
    impl<T: Config> Pallet<T> {
235
        #[pallet::call_index(0)]
236
        #[pallet::weight((
237
			T::WeightInfo::set_config_with_u32(),
238
			DispatchClass::Operational,
239
		))]
240
14
        pub fn set_max_collators(origin: OriginFor<T>, new: u32) -> DispatchResult {
241
14
            ensure_root(origin)?;
242
12
            Self::schedule_config_update(|config| {
243
12
                config.max_collators = new;
244
12
            })
245
        }
246

            
247
        #[pallet::call_index(1)]
248
        #[pallet::weight((
249
			T::WeightInfo::set_config_with_u32(),
250
			DispatchClass::Operational,
251
		))]
252
13
        pub fn set_min_orchestrator_collators(origin: OriginFor<T>, new: u32) -> DispatchResult {
253
13
            ensure_root(origin)?;
254
11
            Self::schedule_config_update(|config| {
255
11
                if config.max_orchestrator_collators < new {
256
8
                    config.max_orchestrator_collators = new;
257
8
                }
258
11
                config.min_orchestrator_collators = new;
259
11
            })
260
        }
261

            
262
        #[pallet::call_index(2)]
263
        #[pallet::weight((
264
			T::WeightInfo::set_config_with_u32(),
265
			DispatchClass::Operational,
266
		))]
267
9
        pub fn set_max_orchestrator_collators(origin: OriginFor<T>, new: u32) -> DispatchResult {
268
9
            ensure_root(origin)?;
269
7
            Self::schedule_config_update(|config| {
270
7
                if config.min_orchestrator_collators > new {
271
3
                    config.min_orchestrator_collators = new;
272
5
                }
273
7
                config.max_orchestrator_collators = new;
274
7
            })
275
        }
276

            
277
        #[pallet::call_index(3)]
278
        #[pallet::weight((
279
			T::WeightInfo::set_config_with_u32(),
280
			DispatchClass::Operational,
281
		))]
282
13
        pub fn set_collators_per_container(origin: OriginFor<T>, new: u32) -> DispatchResult {
283
13
            ensure_root(origin)?;
284
11
            Self::schedule_config_update(|config| {
285
11
                config.collators_per_container = new;
286
11
            })
287
        }
288

            
289
        #[pallet::call_index(4)]
290
        #[pallet::weight((
291
			T::WeightInfo::set_config_with_u32(),
292
			DispatchClass::Operational,
293
		))]
294
3
        pub fn set_full_rotation_period(origin: OriginFor<T>, new: u32) -> DispatchResult {
295
3
            ensure_root(origin)?;
296
3
            Self::schedule_config_update(|config| {
297
3
                config.full_rotation_period = new;
298
3
            })
299
        }
300

            
301
        #[pallet::call_index(5)]
302
        #[pallet::weight((
303
        T::WeightInfo::set_config_with_u32(),
304
        DispatchClass::Operational,
305
        ))]
306
        pub fn set_collators_per_parathread(origin: OriginFor<T>, new: u32) -> DispatchResult {
307
            ensure_root(origin)?;
308
            Self::schedule_config_update(|config| {
309
                config.collators_per_parathread = new;
310
            })
311
        }
312

            
313
        #[pallet::call_index(6)]
314
        #[pallet::weight((
315
        T::WeightInfo::set_config_with_u32(),
316
        DispatchClass::Operational,
317
        ))]
318
        pub fn set_parathreads_per_collator(origin: OriginFor<T>, new: u32) -> DispatchResult {
319
            ensure_root(origin)?;
320
            Self::schedule_config_update(|config| {
321
                config.parathreads_per_collator = new;
322
            })
323
        }
324

            
325
        #[pallet::call_index(7)]
326
        #[pallet::weight((
327
        T::WeightInfo::set_config_with_u32(),
328
        DispatchClass::Operational,
329
        ))]
330
        pub fn set_target_container_chain_fullness(
331
            origin: OriginFor<T>,
332
            new: Perbill,
333
2
        ) -> DispatchResult {
334
2
            ensure_root(origin)?;
335
2
            Self::schedule_config_update(|config| {
336
2
                config.target_container_chain_fullness = new;
337
2
            })
338
        }
339

            
340
        /// Setting this to true will disable consistency checks for the configuration setters.
341
        /// Use with caution.
342
        #[pallet::call_index(44)]
343
        #[pallet::weight((
344
			T::DbWeight::get().writes(1),
345
			DispatchClass::Operational,
346
		))]
347
        pub fn set_bypass_consistency_check(origin: OriginFor<T>, new: bool) -> DispatchResult {
348
            ensure_root(origin)?;
349
            BypassConsistencyCheck::<T>::put(new);
350
            Ok(())
351
        }
352
    }
353

            
354
    /// A struct that holds the configuration that was active before the session change and optionally
355
    /// a configuration that became active after the session change.
356
    pub struct SessionChangeOutcome {
357
        /// Previously active configuration.
358
        pub prev_config: HostConfiguration,
359
        /// If new configuration was applied during the session change, this is the new configuration.
360
        pub new_config: Option<HostConfiguration>,
361
    }
362

            
363
    impl<T: Config> Pallet<T> {
364
        /// Called by the initializer to note that a new session has started.
365
        ///
366
        /// Returns the configuration that was actual before the session change and the configuration
367
        /// that became active after the session change. If there were no scheduled changes, both will
368
        /// be the same.
369
1790
        pub fn initializer_on_new_session(session_index: &T::SessionIndex) -> SessionChangeOutcome {
370
1790
            let pending_configs = <PendingConfigs<T>>::get();
371
1790
            let prev_config = ActiveConfig::<T>::get();
372
1790

            
373
1790
            // No pending configuration changes, so we're done.
374
1790
            if pending_configs.is_empty() {
375
1714
                return SessionChangeOutcome {
376
1714
                    prev_config,
377
1714
                    new_config: None,
378
1714
                };
379
76
            }
380
76

            
381
76
            // We partition those configs scheduled for the present
382
76
            // and those for the future
383
76
            let (mut past_and_present, future) = pending_configs
384
76
                .into_iter()
385
86
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| {
386
86
                    apply_at_session <= *session_index
387
86
                });
388
76

            
389
76
            if past_and_present.len() > 1 {
390
                // This should never happen since we schedule configuration changes only into the future
391
                // sessions and this handler called for each session change.
392
                log::error!(
393
                    target: LOG_TARGET,
394
                    "Skipping applying configuration changes scheduled sessions in the past",
395
                );
396
76
            }
397

            
398
76
            let new_config = past_and_present.pop().map(|(_, config)| config);
399
76
            if let Some(ref new_config) = new_config {
400
43
                // Apply the new configuration.
401
43
                ActiveConfig::<T>::put(new_config);
402
43
            }
403

            
404
            // We insert future as PendingConfig
405
76
            <PendingConfigs<T>>::put(future);
406
76

            
407
76
            SessionChangeOutcome {
408
76
                prev_config,
409
76
                new_config,
410
76
            }
411
1790
        }
412

            
413
        /// Return the session index that should be used for any future scheduled changes.
414
41
        fn scheduled_session() -> T::SessionIndex {
415
41
            T::CurrentSessionIndex::session_index().saturating_add(T::SessionDelay::get())
416
41
        }
417

            
418
        /// Forcibly set the active config. This should be used with extreme care, and typically
419
        /// only when enabling parachains runtime pallets for the first time on a chain which has
420
        /// been running without them.
421
        pub fn force_set_active_config(config: HostConfiguration) {
422
            ActiveConfig::<T>::set(config);
423
        }
424

            
425
        /// This function should be used to update members of the configuration.
426
        ///
427
        /// This function is used to update the configuration in a way that is safe. It will check the
428
        /// resulting configuration and ensure that the update is valid. If the update is invalid, it
429
        /// will check if the previous configuration was valid. If it was invalid, we proceed with
430
        /// updating the configuration, giving a chance to recover from such a condition.
431
        ///
432
        /// The actual configuration change take place after a couple of sessions have passed. In case
433
        /// this function is called more than once in a session, then the pending configuration change
434
        /// will be updated and the changes will be applied at once.
435
        // NOTE: Explicitly tell rustc not to inline this because otherwise heuristics note the incoming
436
        // closure making it's attractive to inline. However, in this case, we will end up with lots of
437
        // duplicated code (making this function to show up in the top of heaviest functions) only for
438
        // the sake of essentially avoiding an indirect call. Doesn't worth it.
439
        #[inline(never)]
440
46
        fn schedule_config_update(updater: impl FnOnce(&mut HostConfiguration)) -> DispatchResult {
441
46
            let mut pending_configs = <PendingConfigs<T>>::get();
442
46

            
443
46
            // 1. pending_configs = []
444
46
            //    No pending configuration changes.
445
46
            //
446
46
            //    That means we should use the active config as the base configuration. We will insert
447
46
            //    the new pending configuration as (cur+2, new_config) into the list.
448
46
            //
449
46
            // 2. pending_configs = [(cur+2, X)]
450
46
            //    There is a configuration that is pending for the scheduled session.
451
46
            //
452
46
            //    We will use X as the base configuration. We can update the pending configuration X
453
46
            //    directly.
454
46
            //
455
46
            // 3. pending_configs = [(cur+1, X)]
456
46
            //    There is a pending configuration scheduled and it will be applied in the next session.
457
46
            //
458
46
            //    We will use X as the base configuration. We need to schedule a new configuration change
459
46
            //    for the `scheduled_session` and use X as the base for the new configuration.
460
46
            //
461
46
            // 4. pending_configs = [(cur+1, X), (cur+2, Y)]
462
46
            //    There is a pending configuration change in the next session and for the scheduled
463
46
            //    session. Due to case â„–3, we can be sure that Y is based on top of X. This means we
464
46
            //    can use Y as the base configuration and update Y directly.
465
46
            //
466
46
            // There cannot be (cur, X) because those are applied in the session change handler for the
467
46
            // current session.
468
46

            
469
46
            // First, we need to decide what we should use as the base configuration.
470
46
            let mut base_config = pending_configs
471
46
                .last()
472
46
                .map(|(_, config)| config.clone())
473
46
                .unwrap_or_else(Self::config);
474
46
            let base_config_consistent = base_config.check_consistency().is_ok();
475
46

            
476
46
            // Now, we need to decide what the new configuration should be.
477
46
            // We also move the `base_config` to `new_config` to empahsize that the base config was
478
46
            // destroyed by the `updater`.
479
46
            updater(&mut base_config);
480
46
            let new_config = base_config;
481
46

            
482
46
            if BypassConsistencyCheck::<T>::get() {
483
                // This will emit a warning each configuration update if the consistency check is
484
                // bypassed. This is an attempt to make sure the bypass is not accidentally left on.
485
                log::warn!(
486
                    target: LOG_TARGET,
487
                    "Bypassing the consistency check for the configuration change!",
488
                );
489
46
            } else if let Err(e) = new_config.check_consistency() {
490
5
                if base_config_consistent {
491
                    // Base configuration is consistent and the new configuration is inconsistent.
492
                    // This means that the value set by the `updater` is invalid and we can return
493
                    // it as an error.
494
5
                    log::warn!(
495
                        target: LOG_TARGET,
496
                        "Configuration change rejected due to invalid configuration: {:?}",
497
                        e,
498
                    );
499
5
                    return Err(Error::<T>::InvalidNewValue.into());
500
                } else {
501
                    // The configuration was already broken, so we can as well proceed with the update.
502
                    // You cannot break something that is already broken.
503
                    //
504
                    // That will allow to call several functions and ultimately return the configuration
505
                    // into consistent state.
506
                    log::warn!(
507
                        target: LOG_TARGET,
508
                        "The new configuration is broken but the old is broken as well. Proceeding",
509
                    );
510
                }
511
41
            }
512

            
513
41
            let scheduled_session = Self::scheduled_session();
514

            
515
41
            if let Some(&mut (_, ref mut config)) = pending_configs
516
41
                .iter_mut()
517
41
                .find(|&&mut (apply_at_session, _)| apply_at_session >= scheduled_session)
518
10
            {
519
10
                *config = new_config;
520
31
            } else {
521
31
                // We are scheduling a new configuration change for the scheduled session.
522
31
                pending_configs.push((scheduled_session, new_config));
523
31
            }
524

            
525
41
            <PendingConfigs<T>>::put(pending_configs);
526
41

            
527
41
            Ok(())
528
46
        }
529
    }
530

            
531
    impl<T: Config> GetHostConfiguration<T::SessionIndex> for Pallet<T> {
532
2272
        fn max_collators(session_index: T::SessionIndex) -> u32 {
533
2272
            let (past_and_present, _) = Pallet::<T>::pending_configs()
534
2272
                .into_iter()
535
2272
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
536

            
537
2272
            let config = if let Some(last) = past_and_present.last() {
538
64
                last.1.clone()
539
            } else {
540
2208
                Pallet::<T>::config()
541
            };
542
2272
            config.max_collators
543
2272
        }
544

            
545
1768
        fn collators_per_container(session_index: T::SessionIndex) -> u32 {
546
1768
            let (past_and_present, _) = Pallet::<T>::pending_configs()
547
1768
                .into_iter()
548
1768
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
549

            
550
1768
            let config = if let Some(last) = past_and_present.last() {
551
36
                last.1.clone()
552
            } else {
553
1732
                Pallet::<T>::config()
554
            };
555
1768
            config.collators_per_container
556
1768
        }
557

            
558
1768
        fn collators_per_parathread(session_index: T::SessionIndex) -> u32 {
559
1768
            let (past_and_present, _) = Pallet::<T>::pending_configs()
560
1768
                .into_iter()
561
1768
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
562

            
563
1768
            let config = if let Some(last) = past_and_present.last() {
564
36
                last.1.clone()
565
            } else {
566
1732
                Pallet::<T>::config()
567
            };
568
1768
            config.collators_per_parathread
569
1768
        }
570

            
571
3536
        fn min_collators_for_orchestrator(session_index: T::SessionIndex) -> u32 {
572
3536
            let (past_and_present, _) = Pallet::<T>::pending_configs()
573
3536
                .into_iter()
574
3536
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
575

            
576
3536
            let config = if let Some(last) = past_and_present.last() {
577
72
                last.1.clone()
578
            } else {
579
3464
                Pallet::<T>::config()
580
            };
581
3536
            config.min_orchestrator_collators
582
3536
        }
583

            
584
1768
        fn max_collators_for_orchestrator(session_index: T::SessionIndex) -> u32 {
585
1768
            let (past_and_present, _) = Pallet::<T>::pending_configs()
586
1768
                .into_iter()
587
1768
                .partition::<Vec<_>, _>(|&(apply_at_session, _)| apply_at_session <= session_index);
588

            
589
1768
            let config = if let Some(last) = past_and_present.last() {
590
36
                last.1.clone()
591
            } else {
592
1732
                Pallet::<T>::config()
593
            };
594
1768
            config.max_orchestrator_collators
595
1768
        }
596
    }
597
}