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
//! # Inactivity Tracking Pallet
17
//!
18
//! This pallet tracks and stores the activity of container chain and orchestrator chain collators
19
//! for configurable number of sessions. It is used to determine if a collator is inactive
20
//! for that period of time.
21
//!
22
//! The tracking functionality can be enabled or disabled with root privileges.
23
//! By default, the tracking is enabled.
24
#![cfg_attr(not(feature = "std"), no_std)]
25
extern crate alloc;
26

            
27
use {
28
    alloc::collections::btree_set::BTreeSet,
29
    frame_support::{
30
        dispatch::DispatchResult, dispatch::DispatchResultWithPostInfo, ensure,
31
        pallet_prelude::Weight,
32
    },
33
    parity_scale_codec::{Decode, Encode},
34
    scale_info::TypeInfo,
35
    serde::{Deserialize, Serialize},
36
    sp_core::{MaxEncodedLen, RuntimeDebug},
37
    sp_runtime::{traits::Get, BoundedBTreeSet},
38
    sp_staking::SessionIndex,
39
    tp_traits::{
40
        AuthorNotingHook, AuthorNotingInfo, ForSession, GetContainerChainsWithCollators,
41
        GetSessionIndex, InvulnerablesHelper, MaybeSelfChainBlockAuthor,
42
        NodeActivityTrackingHelper, ParaId, ParathreadHelper, StakingCandidateHelper,
43
    },
44
};
45

            
46
#[cfg(test)]
47
mod mock;
48

            
49
#[cfg(test)]
50
mod tests;
51

            
52
#[cfg(feature = "runtime-benchmarks")]
53
mod benchmarking;
54

            
55
pub mod weights;
56
pub use weights::WeightInfo;
57

            
58
#[cfg(feature = "runtime-benchmarks")]
59
use tp_traits::BlockNumber;
60

            
61
pub mod migrations;
62

            
63
pub use pallet::*;
64
15600
#[frame_support::pallet]
65
pub mod pallet {
66
    use {
67
        super::*,
68
        crate::weights::WeightInfo,
69
        alloc::collections::btree_set::BTreeSet,
70
        core::marker::PhantomData,
71
        frame_support::{pallet_prelude::*, storage::types::StorageMap},
72
        frame_system::pallet_prelude::*,
73
    };
74

            
75
    pub type Collator<T> = <T as frame_system::Config>::AccountId;
76

            
77
    /// The status of collator activity tracking
78
    #[derive(
79
        Clone,
80
        PartialEq,
81
        Eq,
82
        Encode,
83
        DecodeWithMemTracking,
84
        Decode,
85
3744
        TypeInfo,
86
        Serialize,
87
        Deserialize,
88
        RuntimeDebug,
89
        MaxEncodedLen,
90
    )]
91
    pub enum ActivityTrackingStatus {
92
        Enabled {
93
            /// The session in which we will start recording the collator activity after enabling it
94
            start: SessionIndex,
95
            /// The session after which the activity tracking can be disabled
96
            end: SessionIndex,
97
        },
98
        Disabled {
99
            /// The session after which the activity tracking can be enabled
100
            end: SessionIndex,
101
        },
102
    }
103

            
104
    /// The offline status of a collator
105
    #[derive(
106
        Clone,
107
        PartialEq,
108
        Eq,
109
        Encode,
110
        DecodeWithMemTracking,
111
        Decode,
112
2496
        TypeInfo,
113
        Serialize,
114
        Deserialize,
115
        RuntimeDebug,
116
        MaxEncodedLen,
117
    )]
118
    pub enum OfflineStatus {
119
57
        Disabled,
120
        Notified {
121
            /// The session in which the collator can become online again
122
            cooldown_end: SessionIndex,
123
        },
124
    }
125

            
126
    impl Default for ActivityTrackingStatus {
127
222391
        fn default() -> Self {
128
222391
            ActivityTrackingStatus::Enabled { start: 0, end: 0 }
129
222391
        }
130
    }
131

            
132
3590
    #[pallet::pallet]
133
    pub struct Pallet<T>(PhantomData<T>);
134
    #[pallet::config]
135
    pub trait Config: frame_system::Config {
136
        /// Overarching event type.
137
        type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
138

            
139
        /// The maximum number of sessions for which a collator can be inactive
140
        /// before being moved to the offline queue
141
        #[pallet::constant]
142
        type MaxInactiveSessions: Get<u32>;
143

            
144
        /// The maximum amount of collators that can be stored for a session
145
        #[pallet::constant]
146
        type MaxCollatorsPerSession: Get<u32>;
147

            
148
        /// The maximum amount of container chains that can be stored
149
        #[pallet::constant]
150
        type MaxContainerChains: Get<u32>;
151

            
152
        /// Helper that returns the current session index.
153
        type CurrentSessionIndex: GetSessionIndex<SessionIndex>;
154

            
155
        /// Helper that fetches a list of collators eligible to produce blocks for the current session
156
        type CurrentCollatorsFetcher: GetContainerChainsWithCollators<Collator<Self>>;
157

            
158
        /// Helper that returns the block author for the orchestrator chain (if it exists)
159
        type GetSelfChainBlockAuthor: MaybeSelfChainBlockAuthor<Collator<Self>>;
160

            
161
        /// Helper that checks if a ParaId is a parathread
162
        type ParaFilter: ParathreadHelper;
163

            
164
        /// Helper for dealing with invulnerables.
165
        type InvulnerablesFilter: InvulnerablesHelper<Collator<Self>>;
166

            
167
        /// Helper for dealing with collator's stake
168
        type CollatorStakeHelper: StakingCandidateHelper<Collator<Self>>;
169

            
170
        /// The cooldown period length in sessions when a collator is notified as inactive.
171
        #[pallet::constant]
172
        type CooldownLength: Get<u32>;
173

            
174
        /// The weight information of this pallet.
175
        type WeightInfo: weights::WeightInfo;
176
    }
177

            
178
    /// Switch to enable/disable activity tracking
179
126746
    #[pallet::storage]
180
    pub type CurrentActivityTrackingStatus<T: Config> =
181
        StorageValue<_, ActivityTrackingStatus, ValueQuery>;
182

            
183
    /// A storage map of inactive collators for a session
184
8406
    #[pallet::storage]
185
    pub type InactiveCollators<T: Config> = StorageMap<
186
        _,
187
        Twox64Concat,
188
        SessionIndex,
189
        BoundedBTreeSet<Collator<T>, T::MaxCollatorsPerSession>,
190
        ValueQuery,
191
    >;
192

            
193
    /// A list of active collators for a session. Repopulated at the start of every session
194
256024
    #[pallet::storage]
195
    pub type ActiveCollatorsForCurrentSession<T: Config> =
196
        StorageValue<_, BoundedBTreeSet<Collator<T>, T::MaxCollatorsPerSession>, ValueQuery>;
197

            
198
    /// A list of active container chains for a session. Repopulated at the start of every session
199
117974
    #[pallet::storage]
200
    pub type ActiveContainerChainsForCurrentSession<T: Config> =
201
        StorageValue<_, BoundedBTreeSet<ParaId, T::MaxContainerChains>, ValueQuery>;
202

            
203
    /// Switch to enable/disable offline marking.
204
736
    #[pallet::storage]
205
    pub type EnableMarkingOffline<T: Config> = StorageValue<_, bool, ValueQuery>;
206

            
207
    /// Storage map indicating the offline status of a collator
208
1379
    #[pallet::storage]
209
    pub type OfflineCollators<T: Config> =
210
        StorageMap<_, Blake2_128Concat, Collator<T>, OfflineStatus, OptionQuery>;
211

            
212
624
    #[pallet::event]
213
41
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
214
    pub enum Event<T: Config> {
215
        /// Event emitted when the activity tracking status is updated
216
        ActivityTrackingStatusSet { status: ActivityTrackingStatus },
217
        /// Collator online status updated
218
        CollatorStatusUpdated {
219
            collator: Collator<T>,
220
            is_offline: bool,
221
        },
222
    }
223

            
224
624
    #[pallet::error]
225
    pub enum Error<T> {
226
        /// The size of a collator set for a session has already reached MaxCollatorsPerSession value
227
        MaxCollatorsPerSessionReached,
228
        /// Error returned when the activity tracking status is attempted to be updated before the end session
229
        ActivityTrackingStatusUpdateSuspended,
230
        /// Error returned when the activity tracking status is attempted to be enabled when it is already enabled
231
        ActivityTrackingStatusAlreadyEnabled,
232
        /// Error returned when the activity tracking status is attempted to be disabled when it is already disabled
233
        ActivityTrackingStatusAlreadyDisabled,
234
        /// Error returned when the collator status is attempted to be set to offline when offline marking is disabled
235
        MarkingOfflineNotEnabled,
236
        /// Error returned when the collator is not part of the sorted eligible candidates list
237
        CollatorNotEligibleCandidate,
238
        /// Error returned when the collator status is attempted to be set to offline when it is already offline
239
        CollatorNotOnline,
240
        /// Error returned when the collator status is attempted to be set to offline when it has already been notified as offline
241
        CollatorAlreadyNotifiedOffline,
242
        /// Error returned when the collator status is attempted to be set to online before its cooldown period is over
243
        CollatorNotReadyToBeOnline,
244
        /// Error returned when the collator status is attempted to be set to online when it is already online
245
        CollatorNotOffline,
246
        /// Error returned when the collator attempted to be set offline is invulnerable
247
        MarkingInvulnerableOfflineInvalid,
248
        /// Error returned when the collator attempted to be set offline is not inactive
249
        CollatorCannotBeNotifiedAsInactive,
250
    }
251

            
252
648
    #[pallet::call]
253
    impl<T: Config> Pallet<T> {
254
        /// Enables or disables inactivity tracking.
255
        #[pallet::call_index(0)]
256
        #[pallet::weight(T::WeightInfo::set_inactivity_tracking_status())]
257
        pub fn set_inactivity_tracking_status(
258
            origin: OriginFor<T>,
259
            enable_inactivity_tracking: bool,
260
18
        ) -> DispatchResult {
261
18
            ensure_root(origin)?;
262
17
            let current_status_end_session_index = match <CurrentActivityTrackingStatus<T>>::get() {
263
11
                ActivityTrackingStatus::Enabled { start: _, end } => {
264
11
                    ensure!(
265
11
                        !enable_inactivity_tracking,
266
1
                        Error::<T>::ActivityTrackingStatusAlreadyEnabled
267
                    );
268
10
                    end
269
                }
270
6
                ActivityTrackingStatus::Disabled { end } => {
271
6
                    ensure!(
272
6
                        enable_inactivity_tracking,
273
1
                        Error::<T>::ActivityTrackingStatusAlreadyDisabled
274
                    );
275
5
                    end
276
                }
277
            };
278
15
            let current_session_index = T::CurrentSessionIndex::session_index();
279
15
            ensure!(
280
15
                current_session_index > current_status_end_session_index,
281
1
                Error::<T>::ActivityTrackingStatusUpdateSuspended
282
            );
283
14
            Self::set_inactivity_tracking_status_inner(
284
14
                current_session_index,
285
14
                enable_inactivity_tracking,
286
14
            );
287
14
            Ok(())
288
        }
289

            
290
        /// Enables or disables the marking of collators as offline.
291
        #[pallet::call_index(1)]
292
        #[pallet::weight(T::WeightInfo::enable_offline_marking())]
293
27
        pub fn enable_offline_marking(origin: OriginFor<T>, value: bool) -> DispatchResult {
294
27
            ensure_root(origin)?;
295
26
            <EnableMarkingOffline<T>>::set(value);
296
26
            Ok(())
297
        }
298

            
299
        /// Allows a collator to mark itself offline.
300
        #[pallet::call_index(2)]
301
        #[pallet::weight(T::WeightInfo::set_offline())]
302
20
        pub fn set_offline(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
303
20
            let collator = ensure_signed(origin)?;
304
20
            Self::mark_collator_offline(&collator, None)
305
        }
306

            
307
        /// Allows a collator to mark itself online.
308
        #[pallet::call_index(3)]
309
        #[pallet::weight(T::WeightInfo::set_online())]
310
9
        pub fn set_online(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
311
9
            let collator = ensure_signed(origin)?;
312
9
            Self::mark_collator_online(&collator)
313
        }
314

            
315
        /// Allows an account to notify inactive collator to be marked offline.
316
        #[pallet::call_index(4)]
317
        #[pallet::weight(T::WeightInfo::notify_inactive_collator())]
318
        pub fn notify_inactive_collator(
319
            origin: OriginFor<T>,
320
            collator: Collator<T>,
321
8
        ) -> DispatchResultWithPostInfo {
322
8
            ensure_signed(origin)?;
323
8
            ensure!(
324
8
                Self::is_node_inactive(&collator),
325
1
                Error::<T>::CollatorCannotBeNotifiedAsInactive
326
            );
327

            
328
7
            Self::mark_collator_offline(&collator, Some(T::CooldownLength::get()))
329
        }
330
    }
331

            
332
66658
    #[pallet::hooks]
333
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
334
32929
        fn on_initialize(_n: BlockNumberFor<T>) -> Weight {
335
32929
            let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
336
            // Process the orchestrator chain block author (if it exists) and activity tracking is enabled
337
32929
            if let Some(orchestrator_chain_author) = T::GetSelfChainBlockAuthor::get_block_author()
338
            {
339
28886
                total_weight.saturating_accrue(T::DbWeight::get().reads(1));
340
28886
                if let ActivityTrackingStatus::Enabled { start, end: _ } =
341
28886
                    <CurrentActivityTrackingStatus<T>>::get()
342
                {
343
28886
                    total_weight.saturating_accrue(T::DbWeight::get().reads(1));
344
28886
                    if start <= T::CurrentSessionIndex::session_index() {
345
28886
                        let authors: &[T::AccountId] = &[orchestrator_chain_author];
346
28886
                        total_weight
347
28886
                            .saturating_accrue(Self::on_authors_noted(authors.iter().cloned()));
348
28886
                    }
349
                }
350
4043
            }
351
32929
            total_weight
352
32929
        }
353
    }
354

            
355
    impl<T: Config> Pallet<T> {
356
        /// Internal function to set the activity tracking status and
357
        /// clear ActiveCollatorsForCurrentSession if disabled
358
17
        fn set_inactivity_tracking_status_inner(
359
17
            current_session_index: SessionIndex,
360
17
            enable_inactivity_tracking: bool,
361
17
        ) {
362
17
            let new_status_end_session_index =
363
17
                current_session_index.saturating_add(T::MaxInactiveSessions::get());
364
17
            let new_status = if enable_inactivity_tracking {
365
5
                ActivityTrackingStatus::Enabled {
366
5
                    start: current_session_index.saturating_add(1),
367
5
                    end: new_status_end_session_index,
368
5
                }
369
            } else {
370
12
                <ActiveCollatorsForCurrentSession<T>>::put(BoundedBTreeSet::new());
371
12
                ActivityTrackingStatus::Disabled {
372
12
                    end: new_status_end_session_index,
373
12
                }
374
            };
375
17
            <CurrentActivityTrackingStatus<T>>::put(new_status.clone());
376
17
            Self::deposit_event(Event::<T>::ActivityTrackingStatusSet { status: new_status })
377
17
        }
378

            
379
        /// Internal function to clear the active collators for the current session
380
        /// and remove the collators records that are outside the activity period.
381
        /// Triggered at the beginning of each session.
382
8437
        pub fn process_ended_session() {
383
8437
            let current_session_index = T::CurrentSessionIndex::session_index();
384
8437
            <ActiveCollatorsForCurrentSession<T>>::put(BoundedBTreeSet::new());
385
8437
            <ActiveContainerChainsForCurrentSession<T>>::put(BoundedBTreeSet::new());
386
8437

            
387
8437
            // Cleanup active collator info for sessions that are older than the maximum allowed
388
8437
            if current_session_index > T::MaxInactiveSessions::get() {
389
2015
                <crate::pallet::InactiveCollators<T>>::remove(
390
2015
                    current_session_index
391
2015
                        .saturating_sub(T::MaxInactiveSessions::get())
392
2015
                        .saturating_sub(1),
393
2015
                );
394
6962
            }
395
8437
        }
396

            
397
        /// Internal function to populate the inactivity tracking storage used for marking collator
398
        /// as inactive. Triggered at the end of a session.
399
5643
        pub fn on_before_session_ending() {
400
5643
            let current_session_index = T::CurrentSessionIndex::session_index();
401
5643
            Self::process_inactive_chains_for_session();
402
5643
            match <CurrentActivityTrackingStatus<T>>::get() {
403
16
                ActivityTrackingStatus::Disabled { .. } => return,
404
5627
                ActivityTrackingStatus::Enabled { start, end: _ } => {
405
5627
                    if start > current_session_index {
406
5
                        return;
407
5622
                    }
408
                }
409
            }
410
5622
            if let Ok(inactive_collators) =
411
5622
                BoundedBTreeSet::<Collator<T>, T::MaxCollatorsPerSession>::try_from(
412
5622
                    T::CurrentCollatorsFetcher::get_all_collators_assigned_to_chains(
413
5622
                        ForSession::Current,
414
5622
                    )
415
5622
                    .difference(&<ActiveCollatorsForCurrentSession<T>>::get())
416
5622
                    .cloned()
417
5622
                    .collect::<BTreeSet<Collator<T>>>(),
418
5622
                )
419
5622
            {
420
5622
                InactiveCollators::<T>::insert(current_session_index, inactive_collators);
421
5622
            } else {
422
                // If we reach MaxCollatorsPerSession limit there must be a bug in the pallet
423
                // so we disable the activity tracking
424
                Self::set_inactivity_tracking_status_inner(current_session_index, false);
425
            }
426
5643
        }
427

            
428
        /// Internal function to populate the current session active collator records with collators
429
        /// part of inactive chains.
430
5643
        pub fn process_inactive_chains_for_session() {
431
5643
            match <CurrentActivityTrackingStatus<T>>::get() {
432
15
                ActivityTrackingStatus::Disabled { .. } => return,
433
5628
                ActivityTrackingStatus::Enabled { start, end: _ } => {
434
5628
                    if start > T::CurrentSessionIndex::session_index() {
435
5
                        return;
436
5623
                    }
437
5623
                }
438
5623
            }
439
5623
            let mut active_chains = <ActiveContainerChainsForCurrentSession<T>>::get().into_inner();
440
5623
            // Removing the parathreads for the current session from the active chains array.
441
5623
            // In this way we handle all parathreads as inactive chains.
442
5623
            // This solution would only work if a collator either:
443
5623
            // - is assigned to one chain only
444
5623
            // - is assigned to multiple chains but all of them are parathreads
445
5623
            active_chains = active_chains
446
5623
                .difference(&T::ParaFilter::get_parathreads_for_session())
447
5623
                .cloned()
448
5623
                .collect::<BTreeSet<ParaId>>();
449
5623

            
450
5623
            let _ = <ActiveCollatorsForCurrentSession<T>>::try_mutate(
451
5623
                |active_collators| -> DispatchResult {
452
5623
                    let container_chains_with_collators =
453
5623
                        T::CurrentCollatorsFetcher::container_chains_with_collators(
454
5623
                            ForSession::Current,
455
5623
                        );
456

            
457
8498
                    for (para_id, collator_ids) in container_chains_with_collators.iter() {
458
8420
                        if !active_chains.contains(para_id) {
459
                            // Collators assigned to inactive chain are added
460
                            // to the current active collators storage
461
9997
                            for collator_id in collator_ids {
462
3802
                                if active_collators.try_insert(collator_id.clone()).is_err() {
463
                                    // If we reach MaxCollatorsPerSession limit there must be a bug in the pallet
464
                                    // so we disable the activity tracking
465
1
                                    Self::set_inactivity_tracking_status_inner(
466
1
                                        T::CurrentSessionIndex::session_index(),
467
1
                                        false,
468
1
                                    );
469
1
                                    return Err(Error::<T>::MaxCollatorsPerSessionReached.into());
470
3801
                                }
471
                            }
472
2224
                        }
473
                    }
474
5622
                    Ok(())
475
5623
                },
476
5623
            );
477
5643
        }
478

            
479
        /// Internal update the current session active collator records.
480
        /// This function is called when a container chain or orchestrator chain collator is noted.
481
51663
        pub fn on_authors_noted(authors: impl IntoIterator<Item = T::AccountId>) -> Weight {
482
51663
            let authors: BTreeSet<_> = authors.into_iter().collect();
483
51663
            if authors.is_empty() {
484
504
                return Weight::zero();
485
51159
            }
486
51159

            
487
51159
            let mut total_weight = T::DbWeight::get().reads(1);
488
51159

            
489
51159
            let result = <ActiveCollatorsForCurrentSession<T>>::try_mutate(|active_collators| {
490
51159
                let mut temp_set: BTreeSet<Collator<T>> =
491
51159
                    core::mem::take(active_collators).into_inner();
492
51159

            
493
51159
                temp_set.extend(authors);
494
51159

            
495
51159
                match BoundedBTreeSet::<Collator<T>, T::MaxCollatorsPerSession>::try_from(temp_set)
496
                {
497
51158
                    Ok(bounded_set) => {
498
51158
                        *active_collators = bounded_set;
499
51158
                        Ok(())
500
                    }
501
1
                    Err(_) => Err(()),
502
                }
503
51159
            });
504
51159

            
505
51159
            match result {
506
                Ok(_) => {
507
51158
                    total_weight.saturating_accrue(T::DbWeight::get().writes(1));
508
51158
                    total_weight
509
                }
510
                Err(_) => {
511
1
                    log::error!(
512
1
                        "Limit of active collators per session reached. Disabling activity tracking."
513
                    );
514
                    // If we reach MaxCollatorsPerSession limit there must be a bug in the pallet
515
                    // so we disable the activity tracking
516
1
                    Self::set_inactivity_tracking_status_inner(
517
1
                        T::CurrentSessionIndex::session_index(),
518
1
                        false,
519
1
                    );
520
1
                    total_weight.saturating_accrue(T::DbWeight::get().writes(2));
521
1
                    total_weight
522
                }
523
            }
524
51663
        }
525

            
526
        /// Internal update the current session active chains records.
527
        /// This function is called when a container chain is noted.
528
22777
        pub fn on_chains_noted(chains: impl IntoIterator<Item = ParaId>) -> Weight {
529
22777
            let chains: BTreeSet<_> = chains.into_iter().collect();
530
22777
            if chains.is_empty() {
531
504
                return Weight::zero();
532
22273
            }
533
22273

            
534
22273
            let mut total_weight = T::DbWeight::get().reads(1);
535
22273

            
536
22273
            let result = <ActiveContainerChainsForCurrentSession<T>>::try_mutate(|active_chains| {
537
22273
                let mut temp_set: BTreeSet<ParaId> = core::mem::take(active_chains).into_inner();
538
22273

            
539
22273
                temp_set.extend(chains);
540
22273

            
541
22273
                match BoundedBTreeSet::<ParaId, T::MaxContainerChains>::try_from(temp_set) {
542
22272
                    Ok(bounded_set) => {
543
22272
                        *active_chains = bounded_set;
544
22272
                        Ok(())
545
                    }
546
1
                    Err(_) => Err(()),
547
                }
548
22273
            });
549
22273

            
550
22273
            match result {
551
22272
                Ok(_) => {
552
22272
                    total_weight.saturating_accrue(T::DbWeight::get().writes(1));
553
22272
                }
554
                Err(_) => {
555
                    // If we reach MaxContainerChains limit there must be a bug in the pallet
556
                    // so we disable the activity tracking
557
1
                    log::error!(
558
1
                        "Limit of active container chains reached. Disabling activity tracking."
559
                    );
560
1
                    Self::set_inactivity_tracking_status_inner(
561
1
                        T::CurrentSessionIndex::session_index(),
562
1
                        false,
563
1
                    );
564
1
                    total_weight.saturating_accrue(T::DbWeight::get().writes(2));
565
                }
566
            }
567

            
568
22273
            total_weight
569
22777
        }
570

            
571
        /// Internal function to mark a collator as offline.
572
27
        pub fn mark_collator_offline(
573
27
            collator: &Collator<T>,
574
27
            cooldown: Option<u32>,
575
27
        ) -> DispatchResultWithPostInfo {
576
27
            ensure!(
577
27
                <EnableMarkingOffline<T>>::get(),
578
2
                Error::<T>::MarkingOfflineNotEnabled
579
            );
580
25
            ensure!(
581
25
                T::CollatorStakeHelper::is_candidate_selected(collator),
582
2
                Error::<T>::CollatorNotEligibleCandidate
583
            );
584
23
            ensure!(
585
23
                !T::InvulnerablesFilter::is_invulnerable(collator),
586
2
                Error::<T>::MarkingInvulnerableOfflineInvalid
587
            );
588
21
            match cooldown {
589
4
                Some(cooldown_value) => {
590
4
                    ensure!(
591
4
                        <OfflineCollators<T>>::get(collator.clone()).is_none()
592
2
                            || <OfflineCollators<T>>::get(collator.clone())
593
2
                                == Some(OfflineStatus::Disabled),
594
1
                        Error::<T>::CollatorAlreadyNotifiedOffline
595
                    );
596
3
                    let cooldown_end =
597
3
                        T::CurrentSessionIndex::session_index().saturating_add(cooldown_value);
598
3
                    <OfflineCollators<T>>::insert(
599
3
                        collator.clone(),
600
3
                        OfflineStatus::Notified { cooldown_end },
601
3
                    );
602
                }
603
                None => {
604
17
                    ensure!(
605
17
                        <OfflineCollators<T>>::get(collator.clone()).is_none(),
606
2
                        Error::<T>::CollatorNotOnline
607
                    );
608
15
                    <OfflineCollators<T>>::insert(collator.clone(), OfflineStatus::Disabled);
609
                }
610
            }
611

            
612
            // Updates the SortedEligibleCandidates list. Has to be called after the collator is marked offline.
613
18
            T::CollatorStakeHelper::on_online_status_change(collator, false)?;
614
18
            Self::deposit_event(Event::<T>::CollatorStatusUpdated {
615
18
                collator: collator.clone(),
616
18
                is_offline: true,
617
18
            });
618
18
            Ok(().into())
619
27
        }
620

            
621
        /// Internal function to mark a collator as online.
622
9
        pub fn mark_collator_online(collator: &Collator<T>) -> DispatchResultWithPostInfo {
623
9
            ensure!(
624
9
                <OfflineCollators<T>>::get(collator).is_some(),
625
1
                Error::<T>::CollatorNotOffline
626
            );
627
8
            ensure!(
628
8
                Self::is_collator_ready_to_be_online(<OfflineCollators<T>>::get(collator).unwrap()),
629
2
                Error::<T>::CollatorNotReadyToBeOnline
630
            );
631
6
            <OfflineCollators<T>>::remove(collator.clone());
632
6
            // Updates the SortedEligibleCandidates list. Has to be called after the collator is marked online.
633
6
            T::CollatorStakeHelper::on_online_status_change(collator, true)?;
634
6
            Self::deposit_event(Event::<T>::CollatorStatusUpdated {
635
6
                collator: collator.clone(),
636
6
                is_offline: false,
637
6
            });
638
6
            Ok(().into())
639
9
        }
640

            
641
        /// Internal function helping to check if a collator is ready to be marked online
642
        /// based on its offline status.
643
8
        pub fn is_collator_ready_to_be_online(offline_status: OfflineStatus) -> bool {
644
8
            match offline_status {
645
5
                OfflineStatus::Disabled => true,
646
3
                OfflineStatus::Notified { cooldown_end } => {
647
3
                    T::CurrentSessionIndex::session_index() > cooldown_end
648
                }
649
            }
650
8
        }
651
    }
652
}
653

            
654
impl<T: Config> NodeActivityTrackingHelper<Collator<T>> for Pallet<T> {
655
57
    fn is_node_inactive(node: &Collator<T>) -> bool {
656
57
        // If inactivity tracking is not enabled all nodes are considered active.
657
57
        // We don't need to check the activity records and can return false
658
57
        // Inactivity tracking is not enabled if
659
57
        // - the status is disabled
660
57
        // - the CurrentSessionIndex < start session + MaxInactiveSessions index since there won't be
661
57
        // sufficient activity records to determine inactivity
662
57
        let current_session_index = T::CurrentSessionIndex::session_index();
663
57
        let minimum_sessions_required = T::MaxInactiveSessions::get();
664
57
        match <CurrentActivityTrackingStatus<T>>::get() {
665
2
            ActivityTrackingStatus::Disabled { .. } => return false,
666
55
            ActivityTrackingStatus::Enabled { start, end: _ } => {
667
55
                if start.saturating_add(minimum_sessions_required) > current_session_index {
668
21
                    return false;
669
34
                }
670
34
            }
671
34
        }
672
34

            
673
34
        let start_session_index = current_session_index.saturating_sub(minimum_sessions_required);
674
86
        for session_index in start_session_index..current_session_index {
675
86
            if !<InactiveCollators<T>>::get(session_index).contains(node) {
676
15
                return false;
677
71
            }
678
        }
679
19
        true
680
57
    }
681
661
    fn is_node_offline(node: &Collator<T>) -> bool {
682
661
        <OfflineCollators<T>>::get(node).is_some()
683
661
    }
684

            
685
    #[cfg(feature = "runtime-benchmarks")]
686
    fn make_node_online(node: &Collator<T>) {
687
        let _ = Self::mark_collator_online(node);
688
    }
689

            
690
    #[cfg(feature = "runtime-benchmarks")]
691
    fn make_node_inactive(node: &Collator<T>) {
692
        // First we need to make sure that eniugh sessions had pass
693
        // so the node can be marked as inactive
694
        let max_inactive_sessions = T::MaxInactiveSessions::get();
695
        if T::CurrentSessionIndex::session_index() < max_inactive_sessions {
696
            T::CurrentSessionIndex::skip_to_session(max_inactive_sessions)
697
        }
698

            
699
        // Now we can insert the node as inactive for all sessions in the current inactivity window
700
        let mut inactive_nodes_set: BoundedBTreeSet<
701
            Collator<T>,
702
            <T as Config>::MaxCollatorsPerSession,
703
        > = BoundedBTreeSet::new();
704
        let _ = inactive_nodes_set.try_insert(node.clone());
705
        for session_index in 0..max_inactive_sessions {
706
            <InactiveCollators<T>>::insert(session_index, inactive_nodes_set.clone());
707
        }
708
    }
709
}
710

            
711
impl<T: Config> AuthorNotingHook<Collator<T>> for Pallet<T> {
712
22781
    fn on_container_authors_noted(info: &[AuthorNotingInfo<Collator<T>>]) -> Weight {
713
22781
        let mut total_weight = T::DbWeight::get().reads_writes(1, 0);
714
22779
        if let ActivityTrackingStatus::Enabled { start, end: _ } =
715
22781
            <CurrentActivityTrackingStatus<T>>::get()
716
        {
717
22779
            if start <= T::CurrentSessionIndex::session_index() {
718
22777
                let (authors, chains): (BTreeSet<_>, BTreeSet<_>) = info
719
22777
                    .iter()
720
22777
                    .map(
721
22777
                        |AuthorNotingInfo {
722
                             author, para_id, ..
723
22922
                         }| (author.clone(), *para_id),
724
22777
                    )
725
22777
                    .unzip();
726
22777

            
727
22777
                total_weight.saturating_accrue(Self::on_authors_noted(authors));
728
22777
                total_weight.saturating_accrue(Self::on_chains_noted(chains));
729
22777
            }
730
2
        }
731
22781
        total_weight
732
22781
    }
733
    #[cfg(feature = "runtime-benchmarks")]
734
    fn prepare_worst_case_for_bench(_a: &Collator<T>, _b: BlockNumber, _para_id: ParaId) {}
735
}