use frame_support::pallet_prelude::Get;
use log::info;
use parity_scale_codec::Encode;
use core_primitives::{
BanHandler, BanInfo, BanReason, BannedValidators, CommitteeSeats, EraValidators,
SessionCommittee, SessionValidatorError, SessionValidators, ValidatorProvider,
};
use rand::{seq::SliceRandom, SeedableRng};
use rand_pcg::Pcg32;
use sp_runtime::{Perbill, Perquintill};
use sp_staking::{EraIndex, SessionIndex};
use sp_std::{
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
vec::Vec,
};
use crate::{
pallet::{
BanConfig, Banned, Config, CurrentAndNextSessionValidatorsStorage, Event, Pallet,
SessionValidatorBlockCount, UnderperformedValidatorSessionCount, ValidatorEraTotalReward,
},
traits::{EraInfoProvider, ValidatorRewardsHandler},
BanConfigStruct, CurrentAndNextSessionValidators, LenientThreshold, ValidatorExtractor,
ValidatorTotalRewards, LOG_TARGET,
};
pub(crate) const MAX_REWARD: u32 = 1_000_000_000;
impl<T: Config> BannedValidators for Pallet<T> {
type AccountId = T::AccountId;
fn banned() -> Vec<Self::AccountId> {
let active_era = T::EraInfoProvider::active_era().unwrap_or(0);
let ban_period = BanConfig::<T>::get().ban_period;
Banned::<T>::iter()
.filter(|(_, info)| !ban_expired(info.start, ban_period, active_era + 1))
.map(|(v, _)| v)
.collect()
}
}
fn choose_for_session<T: Clone>(validators: &[T], count: usize, session: usize) -> Option<Vec<T>> {
if validators.is_empty() || count == 0 {
return None;
}
let validators_len = validators.len();
let first_index = session.saturating_mul(count) % validators_len;
let mut chosen = Vec::new();
for i in 0..count.min(validators_len) {
chosen.push(validators[first_index.saturating_add(i) % validators_len].clone());
}
Some(chosen)
}
fn shuffle_order_for_session<T>(
producers: &mut Vec<T>,
validators: &mut Vec<T>,
session: SessionIndex,
) {
let mut rng = Pcg32::seed_from_u64(session as u64);
producers.shuffle(&mut rng);
validators.shuffle(&mut rng);
}
fn choose_finality_committee<T: Clone>(
reserved: &Option<Vec<T>>,
non_reserved: &Option<Vec<T>>,
non_reserved_seats: usize,
session: usize,
) -> Vec<T> {
let non_reserved_finality_committee = non_reserved
.as_ref()
.and_then(|nr| choose_for_session(nr, non_reserved_seats, session))
.unwrap_or_default();
let mut finality_committee = reserved.clone().unwrap_or_default();
finality_committee.extend(non_reserved_finality_committee);
finality_committee
}
pub(crate) fn select_committee_inner<AccountId: Clone + PartialEq>(
current_session: SessionIndex,
reserved_seats: usize,
non_reserved_seats: usize,
non_reserved_finality_seats: usize,
reserved: &[AccountId],
non_reserved: &[AccountId],
) -> Option<SessionCommittee<AccountId>> {
let reserved_committee = choose_for_session(reserved, reserved_seats, current_session as usize);
let non_reserved_committee =
choose_for_session(non_reserved, non_reserved_seats, current_session as usize);
let mut finality_committee = choose_finality_committee(
&reserved_committee,
&non_reserved_committee,
non_reserved_finality_seats,
current_session as usize,
);
let mut block_producers = match (reserved_committee, non_reserved_committee) {
(Some(rc), Some(nrc)) => Some(rc.into_iter().chain(nrc.into_iter()).collect()),
(Some(rc), _) => Some(rc),
(_, Some(nrc)) => Some(nrc),
_ => None,
}?;
shuffle_order_for_session(
&mut block_producers,
&mut finality_committee,
current_session,
);
Some(SessionCommittee {
block_producers,
finality_committee,
})
}
pub(crate) fn calculate_adjusted_session_points(
sessions_per_era: EraIndex,
blocks_to_produce_per_session: u32,
blocks_created: u32,
total_possible_reward: u32,
lenient_threshold: Perquintill,
) -> u32 {
let performance =
Perquintill::from_rational(blocks_created as u64, blocks_to_produce_per_session as u64);
if performance >= lenient_threshold {
return (Perquintill::from_rational(1, sessions_per_era as u64)
* total_possible_reward as u64) as u32;
}
(Perquintill::from_rational(
blocks_created as u64,
(blocks_to_produce_per_session * sessions_per_era) as u64,
) * total_possible_reward as u64) as u32
}
pub fn compute_validator_scaled_total_rewards<V>(
validator_totals: Vec<(V, u128)>,
) -> Vec<(V, u32)> {
let sum_totals: u128 = validator_totals.iter().map(|(_, t)| t).sum();
if sum_totals == 0 {
return validator_totals.into_iter().map(|(v, _)| (v, 0)).collect();
}
validator_totals
.into_iter()
.map(|(v, t)| {
(
v,
(t.saturating_mul(MAX_REWARD as u128) / sum_totals) as u32,
)
})
.collect()
}
pub fn ban_expired(start: EraIndex, period: EraIndex, active_era: EraIndex) -> bool {
start.saturating_add(period) <= active_era
}
impl<T: Config> Pallet<T> {
pub(crate) fn update_validator_total_rewards(era: EraIndex) {
let validator_totals = T::ValidatorRewardsHandler::validator_totals(era);
let scaled_totals = compute_validator_scaled_total_rewards(validator_totals).into_iter();
ValidatorEraTotalReward::<T>::put(ValidatorTotalRewards(scaled_totals.collect()));
}
fn reward_for_session_non_committee(
non_committee: Vec<T::AccountId>,
nr_of_sessions: SessionIndex,
blocks_per_session: u32,
validator_totals: &BTreeMap<T::AccountId, u32>,
threshold: Perquintill,
) -> impl IntoIterator<Item=(T::AccountId, u32)> + '_ {
non_committee.into_iter().map(move |validator| {
let total = BTreeMap::<_, _>::get(validator_totals, &validator).unwrap_or(&0);
(
validator,
calculate_adjusted_session_points(
nr_of_sessions,
blocks_per_session,
blocks_per_session,
*total,
threshold,
),
)
})
}
fn reward_for_session_committee(
committee: Vec<T::AccountId>,
nr_of_sessions: SessionIndex,
blocks_per_session: u32,
validator_totals: &BTreeMap<T::AccountId, u32>,
threshold: Perquintill,
) -> impl IntoIterator<Item=(T::AccountId, u32)> + '_ {
committee.into_iter().map(move |validator| {
let total = BTreeMap::<_, _>::get(validator_totals, &validator).unwrap_or(&0);
let blocks_created = SessionValidatorBlockCount::<T>::get(&validator);
(
validator,
calculate_adjusted_session_points(
nr_of_sessions,
blocks_per_session,
blocks_created,
*total,
threshold,
),
)
})
}
fn blocks_to_produce_per_session() -> u32 {
T::SessionPeriod::get()
.saturating_div(T::ValidatorProvider::current_era_committee_size().size())
}
pub fn adjust_rewards_for_session() {
let CurrentAndNextSessionValidators {
current:
SessionValidators {
committee,
non_committee,
},
..
} = CurrentAndNextSessionValidatorsStorage::<T>::get();
let nr_of_sessions = T::EraInfoProvider::sessions_per_era();
let blocks_per_session = Self::blocks_to_produce_per_session();
let validator_total_rewards = ValidatorEraTotalReward::<T>::get()
.unwrap_or_else(|| ValidatorTotalRewards(BTreeMap::new()))
.0;
let lenient_threshold = LenientThreshold::<T>::get();
let rewards = Self::reward_for_session_non_committee(
non_committee,
nr_of_sessions,
blocks_per_session,
&validator_total_rewards,
lenient_threshold,
)
.into_iter()
.chain(
Self::reward_for_session_committee(
committee,
nr_of_sessions,
blocks_per_session,
&validator_total_rewards,
lenient_threshold,
),
);
T::ValidatorRewardsHandler::add_rewards(rewards);
}
fn store_session_validators(
committee: &[T::AccountId],
reserved: Vec<T::AccountId>,
non_reserved: Vec<T::AccountId>,
) {
let committee: BTreeSet<T::AccountId> = committee.iter().cloned().collect();
let non_committee = non_reserved
.into_iter()
.chain(reserved)
.filter(|a| !committee.contains(a))
.collect();
let mut session_validators = CurrentAndNextSessionValidatorsStorage::<T>::get();
session_validators.current = session_validators.next;
session_validators.next = SessionValidators {
committee: committee.into_iter().collect(),
non_committee,
};
CurrentAndNextSessionValidatorsStorage::<T>::put(session_validators);
}
pub(crate) fn select_committee(
era_validators: &EraValidators<T::AccountId>,
committee_seats: CommitteeSeats,
current_session: SessionIndex,
) -> Option<SessionCommittee<T::AccountId>> {
let EraValidators {
reserved,
non_reserved,
} = era_validators;
let CommitteeSeats {
reserved_seats,
non_reserved_seats,
non_reserved_finality_seats,
} = committee_seats;
select_committee_inner(
current_session,
reserved_seats as usize,
non_reserved_seats as usize,
non_reserved_finality_seats as usize,
reserved,
non_reserved,
)
}
pub(crate) fn rotate_committee(
current_session: SessionIndex,
) -> Option<SessionCommittee<T::AccountId>>
where
T::AccountId: Clone + PartialEq,
{
let era_validators = T::ValidatorProvider::current_era_validators();
let committee_seats = T::ValidatorProvider::current_era_committee_size();
let committee = Self::select_committee(&era_validators, committee_seats, current_session);
if let Some(c) = &committee {
Self::store_session_validators(
&c.block_producers,
era_validators.reserved,
era_validators.non_reserved,
);
}
committee
}
pub(crate) fn calculate_underperforming_validators() {
let thresholds = BanConfig::<T>::get();
let CurrentAndNextSessionValidators {
current:
SessionValidators {
committee: current_committee,
..
},
..
} = CurrentAndNextSessionValidatorsStorage::<T>::get();
let expected_blocks_per_validator = Self::blocks_to_produce_per_session();
for validator in current_committee {
let underperformance = match SessionValidatorBlockCount::<T>::try_get(&validator) {
Ok(block_count) => {
Perbill::from_rational(block_count, expected_blocks_per_validator)
<= thresholds.minimal_expected_performance
}
Err(_) => true,
};
if underperformance {
Self::mark_validator_underperformance(&thresholds, &validator);
}
}
}
fn mark_validator_underperformance(thresholds: &BanConfigStruct, validator: &T::AccountId) {
let counter = UnderperformedValidatorSessionCount::<T>::mutate(validator, |count| {
*count = count.saturating_add(1);
*count
});
if counter >= thresholds.underperformed_session_count_threshold {
let reason = BanReason::InsufficientUptime(counter);
Self::ban_validator(validator, reason);
UnderperformedValidatorSessionCount::<T>::remove(validator);
}
}
pub(crate) fn clear_underperformance_session_counter(session: SessionIndex) {
let clean_session_counter_delay = BanConfig::<T>::get().clean_session_counter_delay;
if session % clean_session_counter_delay == 0 {
info!(
target: LOG_TARGET,
"Clearing UnderperformedValidatorSessionCount"
);
let _result = UnderperformedValidatorSessionCount::<T>::clear(u32::MAX, None);
}
}
pub fn clear_expired_bans(active_era: EraIndex) {
let ban_period = BanConfig::<T>::get().ban_period;
let unban = Banned::<T>::iter().filter_map(|(v, ban_info)| {
if ban_expired(ban_info.start, ban_period, active_era) {
return Some(v);
}
None
});
unban.for_each(Banned::<T>::remove);
}
pub fn ban_validator(validator: &T::AccountId, reason: BanReason) {
let start: EraIndex = T::EraInfoProvider::current_era()
.unwrap_or(0)
.saturating_add(1);
if T::BanHandler::can_ban(validator) {
Banned::<T>::insert(validator, BanInfo { reason, start });
T::ValidatorExtractor::remove_validator(validator);
}
}
pub fn emit_fresh_bans_event() {
let active_era = <T as Config>::EraInfoProvider::active_era().unwrap_or(1);
let fresh_bans = Banned::<T>::iter()
.filter(|(_acc, info)| info.start == active_era + 1)
.collect::<Vec<_>>();
if !fresh_bans.is_empty() {
info!(
target: LOG_TARGET,
"Fresh bans in era {}: {:?}", active_era, fresh_bans
);
Self::deposit_event(Event::BanValidators(fresh_bans));
}
}
pub fn predict_session_committee_for_session(
session: SessionIndex,
) -> Result<SessionCommittee<T::AccountId>, SessionValidatorError> {
let ce = T::EraInfoProvider::current_era()
.ok_or_else(|| SessionValidatorError::Other("No current era".encode()))?;
let current_starting_index =
T::EraInfoProvider::era_start_session_index(ce).ok_or_else(|| {
SessionValidatorError::Other("No known starting session for current era".encode())
})?;
let planned_era_end = current_starting_index + T::EraInfoProvider::sessions_per_era() - 1;
if session < current_starting_index || session > planned_era_end {
return Err(SessionValidatorError::SessionNotWithinRange {
lower_limit: current_starting_index,
upper_limit: planned_era_end,
});
}
let era_validators = T::ValidatorProvider::current_era_validators();
let committee_seats = T::ValidatorProvider::current_era_committee_size();
Self::select_committee(&era_validators, committee_seats, session)
.ok_or_else(|| SessionValidatorError::Other("Internal error".encode()))
}
}