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
//! This pallet keep tracks of the validators reward points.
18
//! Storage will be cleared after a period of time.
19

            
20
#![cfg_attr(not(feature = "std"), no_std)]
21

            
22
#[cfg(test)]
23
mod mock;
24

            
25
#[cfg(test)]
26
mod tests;
27

            
28
pub use pallet::*;
29

            
30
use {
31
    frame_support::traits::{Defensive, Get, ValidatorSet},
32
    polkadot_primitives::ValidatorIndex,
33
    runtime_parachains::session_info,
34
    sp_staking::SessionIndex,
35
    sp_std::collections::btree_set::BTreeSet,
36
};
37

            
38
1
#[frame_support::pallet]
39
pub mod pallet {
40
    use {
41
        frame_support::pallet_prelude::*, sp_std::collections::btree_map::BTreeMap,
42
        tp_traits::EraIndexProvider,
43
    };
44

            
45
    /// The current storage version.
46
    const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
47

            
48
    pub type RewardPoints = u32;
49
    pub type EraIndex = u32;
50

            
51
    #[pallet::config]
52
    pub trait Config: frame_system::Config {
53
        /// How to fetch the current era info.
54
        type EraIndexProvider: EraIndexProvider;
55

            
56
        /// For how many eras points are kept in storage.
57
        #[pallet::constant]
58
        type HistoryDepth: Get<EraIndex>;
59

            
60
        /// The amount of era points given by backing a candidate that is included.
61
        #[pallet::constant]
62
        type BackingPoints: Get<u32>;
63

            
64
        /// The amount of era points given by dispute voting on a candidate.
65
        #[pallet::constant]
66
        type DisputeStatementPoints: Get<u32>;
67
    }
68

            
69
4
    #[pallet::pallet]
70
    #[pallet::storage_version(STORAGE_VERSION)]
71
    pub struct Pallet<T>(_);
72

            
73
    /// Keep tracks of distributed points per validator and total.
74
    #[derive(RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo)]
75
    pub struct EraRewardPoints<AccountId> {
76
        pub total: RewardPoints,
77
        pub individual: BTreeMap<AccountId, RewardPoints>,
78
    }
79

            
80
    impl<AccountId> Default for EraRewardPoints<AccountId> {
81
3
        fn default() -> Self {
82
3
            EraRewardPoints {
83
3
                total: Default::default(),
84
3
                individual: BTreeMap::new(),
85
3
            }
86
3
        }
87
    }
88

            
89
    /// Store reward points per era.
90
    /// Note: EraRewardPoints is actually bounded by the amount of validators.
91
21
    #[pallet::storage]
92
    #[pallet::unbounded]
93
    pub type RewardPointsForEra<T: Config> =
94
        StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints<T::AccountId>, ValueQuery>;
95

            
96
    impl<T: Config> Pallet<T> {
97
4
        pub fn reward_by_ids(points: impl IntoIterator<Item = (T::AccountId, RewardPoints)>) {
98
4
            let active_era = T::EraIndexProvider::active_era();
99
4

            
100
4
            RewardPointsForEra::<T>::mutate(active_era.index, |era_rewards| {
101
10
                for (validator, points) in points.into_iter() {
102
10
                    *era_rewards.individual.entry(validator).or_default() += points;
103
10
                    era_rewards.total += points;
104
10
                }
105
4
            })
106
4
        }
107
    }
108

            
109
    impl<T: Config> tp_traits::OnEraStart for Pallet<T> {
110
175
        fn on_era_start(era_index: EraIndex, _session_start: u32) {
111
175
            let Some(era_index_to_delete) = era_index.checked_sub(T::HistoryDepth::get()) else {
112
173
                return;
113
            };
114

            
115
2
            RewardPointsForEra::<T>::remove(era_index_to_delete);
116
175
        }
117
    }
118
}
119

            
120
/// Rewards validators for participating in parachains with era points in pallet-staking.
121
pub struct RewardValidatorsWithEraPoints<C>(core::marker::PhantomData<C>);
122

            
123
impl<C> RewardValidatorsWithEraPoints<C>
124
where
125
    C: pallet::Config + session_info::Config,
126
    C::ValidatorSet: ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
127
{
128
    /// Reward validators in session with points, but only if they are in the active set.
129
1
    fn reward_only_active(
130
1
        session_index: SessionIndex,
131
1
        indices: impl IntoIterator<Item = ValidatorIndex>,
132
1
        points: u32,
133
1
    ) {
134
1
        let validators = session_info::AccountKeys::<C>::get(&session_index);
135
1
        let validators = match validators
136
1
            .defensive_proof("account_keys are present for dispute_period sessions")
137
        {
138
1
            Some(validators) => validators,
139
            None => return,
140
        };
141
        // limit rewards to the active validator set
142
1
        let active_set: BTreeSet<_> = C::ValidatorSet::validators().into_iter().collect();
143
1

            
144
1
        let rewards = indices
145
1
            .into_iter()
146
1
            .filter_map(|i| validators.get(i.0 as usize).cloned())
147
1
            .filter(|v| active_set.contains(v))
148
1
            .map(|v| (v, points));
149
1

            
150
1
        pallet::Pallet::<C>::reward_by_ids(rewards);
151
1
    }
152
}
153

            
154
impl<C> runtime_parachains::inclusion::RewardValidators for RewardValidatorsWithEraPoints<C>
155
where
156
    C: pallet::Config + runtime_parachains::shared::Config + session_info::Config,
157
    C::ValidatorSet: ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
158
{
159
1
    fn reward_backing(indices: impl IntoIterator<Item = ValidatorIndex>) {
160
1
        let session_index = runtime_parachains::shared::CurrentSessionIndex::<C>::get();
161
1
        Self::reward_only_active(session_index, indices, C::BackingPoints::get());
162
1
    }
163

            
164
    fn reward_bitfields(_validators: impl IntoIterator<Item = ValidatorIndex>) {}
165
}
166

            
167
impl<C> runtime_parachains::disputes::RewardValidators for RewardValidatorsWithEraPoints<C>
168
where
169
    C: pallet::Config + session_info::Config,
170
    C::ValidatorSet: ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
171
{
172
    fn reward_dispute_statement(
173
        session: SessionIndex,
174
        validators: impl IntoIterator<Item = ValidatorIndex>,
175
    ) {
176
        Self::reward_only_active(session, validators, C::DisputeStatementPoints::get());
177
    }
178
}