1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
use frame_system::pallet_prelude::BlockNumberFor;
use log::debug;
use pallet_session::SessionManager;
use core_primitives::{EraManager, FinalityCommitteeManager, SessionCommittee};
use sp_staking::{EraIndex, SessionIndex};
use sp_std::{marker::PhantomData, vec::Vec};

use crate::{
    pallet::{Config, Pallet, SessionValidatorBlockCount},
    traits::EraInfoProvider,
    LOG_TARGET,
};

/// We assume that block `B` ends session nr `S`, and current era index is `E`.
///
/// 1. Block `B` initialized
/// 2. `End_session(S)` is called
/// * Based on block count we might mark the session for a given validator as underperformed
/// * We update rewards and clear block count for the session `S`.
/// 3. `Start_session(S + 1)` is called.
/// * If session `S+1` starts a new era,
/// we populate totals and unban all validators whose ban expired.
/// * If session `S+1` % `clean_session_counter_delay`
/// == 0, we clean up underperformed session counter.
/// * `Clean_session_counter_delay` is read from pallet's storage
/// 4. `New_session(S + 2)` is called.
/// * If session `S+2` starts a new era, we emit fresh bans events
/// * We rotate the validators for session`S + 2`
/// using the information about reserved and non-reserved validators.

impl<T> pallet_authorship::EventHandler<T::AccountId, BlockNumberFor<T>> for Pallet<T>
where
    T: Config,
{
    fn note_author(validator: T::AccountId) {
        SessionValidatorBlockCount::<T>::mutate(&validator, |count| {
            *count = count.saturating_add(1);
        });
    }
}

/// SessionManager that also fires EraManager functions. It is responsible for rotation of the committee,
/// bans and rewards logic.
///
/// The order of the calls is as follows:
/// First call is always from the inner SessionManager then the call to EraManager fn if applicable.
/// * New session is planned:
/// 1. Inner T::new_session invoked
/// 2. If session starts era EM::on_new_era invoked
/// 3. Logic related to new session from this pallet is invoked
/// * Session ends:
/// 1. Inner T::end_session invoked
/// 2. Logic related to new session from this pallet is invoked
/// * Session starts:
/// 1. Inner T::start_session invoked
/// 2. Logic related to the new session from this pallet is invoked
/// 3. If session starts era EM::new_era_start invoked
/// 4. If session starts era logic related to new era from this pallet is invoked
///
/// In the runtime we set EM to pallet_elections and T to combination of staking and historical_session.
pub struct SessionAndEraManager<E, EM, T, C>(PhantomData<(E, EM, T, C)>)
where
    E: EraInfoProvider,
    EM: EraManager,
    T: SessionManager<C::AccountId>,
    C: Config;

impl<E, EM, T, C> SessionAndEraManager<E, EM, T, C>
where
    E: EraInfoProvider,
    EM: EraManager,
    T: SessionManager<C::AccountId>,
    C: Config,
{
    fn session_starts_era(session: SessionIndex) -> Option<EraIndex> {
        let active_era = match E::active_era() {
            Some(ae) => ae,
            // no active era, session can't start it
            _ => return None,
        };

        if Self::is_start_of_the_era(active_era, session) {
            return Some(active_era);
        }

        None
    }

    fn session_starts_next_era(session: SessionIndex) -> Option<EraIndex> {
        let active_era = match E::active_era() {
            Some(ae) => ae.saturating_add(1),
            // no active era, session can't start it
            _ => return None,
        };

        if Self::is_start_of_the_era(active_era, session) {
            return Some(active_era);
        }

        None
    }

    fn is_start_of_the_era(era: EraIndex, session: SessionIndex) -> bool {
        if let Some(era_start_index) = E::era_start_session_index(era) {
            return era_start_index == session;
        }

        false
    }
}

impl<E, EM, T, C> SessionManager<C::AccountId> for SessionAndEraManager<E, EM, T, C>
where
    E: EraInfoProvider,
    EM: EraManager,
    T: SessionManager<C::AccountId>,
    C: Config,
{
    fn new_session(new_index: SessionIndex) -> Option<Vec<C::AccountId>> {
        T::new_session(new_index);
        if let Some(era) = Self::session_starts_next_era(new_index) {
            EM::on_new_era(era);
            Pallet::<C>::emit_fresh_bans_event();
        }

        let SessionCommittee {
            finality_committee,
            block_producers,
        } = Pallet::<C>::rotate_committee(new_index)?;
        // Notify about elected next session finality committee
        C::FinalityCommitteeManager::on_next_session_finality_committee(finality_committee);

        Some(block_producers)
    }

    fn end_session(end_index: SessionIndex) {
        T::end_session(end_index);
        Pallet::<C>::adjust_rewards_for_session();
        Pallet::<C>::calculate_underperforming_validators();
        // clear block count after calculating stats for underperforming validators, as they use
        // SessionValidatorBlockCount for that
        let result = SessionValidatorBlockCount::<C>::clear(u32::MAX, None);
        debug!(
            target: LOG_TARGET,
            "Result of clearing the `SessionValidatorBlockCount`, {:?}",
            result.deconstruct()
        );
    }

    fn start_session(start_index: SessionIndex) {
        T::start_session(start_index);
        Pallet::<C>::clear_underperformance_session_counter(start_index);

        if let Some(era) = Self::session_starts_era(start_index) {
            Pallet::<C>::update_validator_total_rewards(era);
            Pallet::<C>::clear_expired_bans(era);
        }
    }
}