Skip to content

Commit b0d696f

Browse files
authored
A0-4577: Implement era abft performance calculation (#1883)
# Description Calculate a performance stat for every finalizer at the end of a session and emit an event that a validator is underperforming when a threshold is met. In future, implement banning such validator. ## Type of change - New feature (non-breaking change which adds functionality) --------- Co-authored-by: Michal Swietek <[email protected]>
1 parent 079ccc9 commit b0d696f

File tree

9 files changed

+599
-1034
lines changed

9 files changed

+599
-1034
lines changed

aleph-client/src/aleph_zero.rs

+279-85
Large diffs are not rendered by default.

bin/runtime/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,7 @@ impl pallet_committee_management::Config for Runtime {
376376
type ValidatorExtractor = Staking;
377377
type FinalityCommitteeManager = Aleph;
378378
type SessionPeriod = SessionPeriod;
379+
type AbftScoresProvider = Aleph;
379380
}
380381

381382
impl pallet_insecure_randomness_collective_flip::Config for Runtime {}

e2e-tests/Cargo.lock

+123-902
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e-tests/src/elections.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub async fn compute_session_committee<C: AsConnection + Sync>(
2929
.get_session_committee(session, first_block_in_era)
3030
.await?
3131
.expect("Committee should be known at this point")
32-
.block_producers;
32+
.producers;
3333

3434
Ok(committee
3535
.into_iter()

pallets/aleph/src/impls.rs

+17-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use primitives::{FinalityCommitteeManager, SessionIndex};
1+
use primitives::{AbftScoresProvider, FinalityCommitteeManager, Score, SessionIndex};
22
use sp_std::vec::Vec;
33

44
use crate::{
5-
Config, Event, FinalityScheduledVersionChange, FinalityVersion, NextFinalityCommittee, Pallet,
5+
AbftScores, Config, Event, FinalityScheduledVersionChange, FinalityVersion, LastScoreNonce,
6+
NextFinalityCommittee, Pallet,
67
};
78

89
impl<T> pallet_session::SessionManager<T::AccountId> for Pallet<T>
@@ -58,3 +59,17 @@ impl<T: Config> FinalityCommitteeManager<T::AccountId> for Pallet<T> {
5859
NextFinalityCommittee::<T>::put(committee);
5960
}
6061
}
62+
63+
impl<T: Config> AbftScoresProvider for Pallet<T> {
64+
fn scores_for_session(session_id: SessionIndex) -> Option<Score> {
65+
AbftScores::<T>::get(session_id)
66+
}
67+
68+
fn clear_scores() {
69+
let _result = AbftScores::<T>::clear(u32::MAX, None);
70+
}
71+
72+
fn clear_nonce() {
73+
LastScoreNonce::<T>::kill();
74+
}
75+
}

pallets/committee-management/src/impls.rs

+62-28
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,28 @@ use frame_support::pallet_prelude::Get;
22
use log::info;
33
use parity_scale_codec::Encode;
44
use primitives::{
5-
BanHandler, BanInfo, BanReason, BannedValidators, CommitteeSeats, EraValidators,
6-
SessionCommittee, SessionValidatorError, SessionValidators, ValidatorProvider,
5+
AbftScoresProvider, BanHandler, BanInfo, BanReason, BannedValidators, CommitteeSeats,
6+
EraValidators, SessionCommittee, SessionValidatorError, SessionValidators, ValidatorProvider,
77
};
88
use rand::{seq::SliceRandom, SeedableRng};
99
use rand_pcg::Pcg32;
1010
use sp_runtime::{Perbill, Perquintill};
1111
use sp_staking::{EraIndex, SessionIndex};
1212
use sp_std::{
1313
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
14+
vec,
1415
vec::Vec,
1516
};
1617

1718
use crate::{
1819
pallet::{
1920
BanConfig, Banned, Config, CurrentAndNextSessionValidatorsStorage, Event, Pallet,
20-
SessionValidatorBlockCount, UnderperformedValidatorSessionCount, ValidatorEraTotalReward,
21+
SessionValidatorBlockCount, UnderperformedFinalizerSessionCount,
22+
UnderperformedValidatorSessionCount, ValidatorEraTotalReward,
2123
},
2224
traits::{EraInfoProvider, ValidatorRewardsHandler},
23-
BanConfigStruct, CurrentAndNextSessionValidators, LenientThreshold, ValidatorExtractor,
24-
ValidatorTotalRewards, LOG_TARGET,
25+
BanConfigStruct, CurrentAndNextSessionValidators, FinalityBanConfig, LenientThreshold,
26+
ValidatorExtractor, ValidatorTotalRewards, LOG_TARGET,
2527
};
2628

2729
const MAX_REWARD: u32 = 1_000_000_000;
@@ -104,30 +106,26 @@ fn select_committee_inner<AccountId: Clone + PartialEq>(
104106
let non_reserved_committee =
105107
choose_for_session(non_reserved, non_reserved_seats, current_session as usize);
106108

107-
let mut finality_committee = choose_finality_committee(
109+
let mut finalizers = choose_finality_committee(
108110
&reserved_committee,
109111
&non_reserved_committee,
110112
non_reserved_finality_seats,
111113
current_session as usize,
112114
);
113115

114-
let mut block_producers = match (reserved_committee, non_reserved_committee) {
116+
let mut producers = match (reserved_committee, non_reserved_committee) {
115117
(Some(rc), Some(nrc)) => Some(rc.into_iter().chain(nrc.into_iter()).collect()),
116118
(Some(rc), _) => Some(rc),
117119
(_, Some(nrc)) => Some(nrc),
118120
_ => None,
119121
}?;
120122

121123
// randomize order of the producers and committee
122-
shuffle_order_for_session(
123-
&mut block_producers,
124-
&mut finality_committee,
125-
current_session,
126-
);
124+
shuffle_order_for_session(&mut producers, &mut finalizers, current_session);
127125

128126
Some(SessionCommittee {
129-
block_producers,
130-
finality_committee,
127+
producers,
128+
finalizers,
131129
})
132130
}
133131

@@ -241,8 +239,9 @@ impl<T: Config> Pallet<T> {
241239
let CurrentAndNextSessionValidators {
242240
current:
243241
SessionValidators {
244-
committee,
242+
producers,
245243
non_committee,
244+
..
246245
},
247246
..
248247
} = CurrentAndNextSessionValidatorsStorage::<T>::get();
@@ -263,7 +262,7 @@ impl<T: Config> Pallet<T> {
263262
)
264263
.into_iter()
265264
.chain(Self::reward_for_session_committee(
266-
committee,
265+
producers,
267266
nr_of_sessions,
268267
blocks_per_session,
269268
&validator_total_rewards,
@@ -274,23 +273,25 @@ impl<T: Config> Pallet<T> {
274273
}
275274

276275
fn store_session_validators(
277-
committee: &[T::AccountId],
276+
producers: &[T::AccountId],
277+
finalizers: &[T::AccountId],
278278
reserved: Vec<T::AccountId>,
279279
non_reserved: Vec<T::AccountId>,
280280
) {
281-
let committee: BTreeSet<T::AccountId> = committee.iter().cloned().collect();
281+
let producers_set: BTreeSet<T::AccountId> = producers.iter().cloned().collect();
282282

283283
let non_committee = non_reserved
284284
.into_iter()
285285
.chain(reserved)
286-
.filter(|a| !committee.contains(a))
286+
.filter(|a| !producers_set.contains(a))
287287
.collect();
288288

289289
let mut session_validators = CurrentAndNextSessionValidatorsStorage::<T>::get();
290290

291291
session_validators.current = session_validators.next;
292292
session_validators.next = SessionValidators {
293-
committee: committee.into_iter().collect(),
293+
producers: producers.to_vec(),
294+
finalizers: finalizers.to_vec(),
294295
non_committee,
295296
};
296297

@@ -336,7 +337,8 @@ impl<T: Config> Pallet<T> {
336337

337338
if let Some(c) = &committee {
338339
Self::store_session_validators(
339-
&c.block_producers,
340+
&c.producers,
341+
&c.finalizers,
340342
era_validators.reserved,
341343
era_validators.non_reserved,
342344
);
@@ -345,18 +347,48 @@ impl<T: Config> Pallet<T> {
345347
committee
346348
}
347349

350+
pub(crate) fn calculate_underperforming_finalizers(session_id: SessionIndex) {
351+
let CurrentAndNextSessionValidators {
352+
current: SessionValidators { finalizers, .. },
353+
..
354+
} = CurrentAndNextSessionValidatorsStorage::<T>::get();
355+
356+
let underperformed_session_count_threshold =
357+
FinalityBanConfig::<T>::get().underperformed_session_count_threshold;
358+
let minimal_expected_performance =
359+
FinalityBanConfig::<T>::get().minimal_expected_performance;
360+
361+
let is_underperforming = |score| score > minimal_expected_performance;
362+
363+
let finalizers_perf = T::AbftScoresProvider::scores_for_session(session_id)
364+
.map(|score| score.points)
365+
.unwrap_or(vec![minimal_expected_performance; finalizers.len()])
366+
.into_iter()
367+
.map(is_underperforming);
368+
369+
for (underperf, validator) in finalizers_perf.zip(finalizers.iter()) {
370+
if underperf {
371+
let counter =
372+
UnderperformedFinalizerSessionCount::<T>::mutate(validator, |count| {
373+
*count += 1;
374+
*count
375+
});
376+
if counter >= underperformed_session_count_threshold {
377+
// In future, additionally ban underperforming validator
378+
Self::deposit_event(Event::ValidatorUnderperforming(validator.clone()));
379+
}
380+
}
381+
}
382+
}
383+
348384
pub(crate) fn calculate_underperforming_validators() {
349385
let thresholds = BanConfig::<T>::get();
350386
let CurrentAndNextSessionValidators {
351-
current:
352-
SessionValidators {
353-
committee: current_committee,
354-
..
355-
},
387+
current: SessionValidators { producers, .. },
356388
..
357389
} = CurrentAndNextSessionValidatorsStorage::<T>::get();
358390
let expected_blocks_per_validator = Self::blocks_to_produce_per_session();
359-
for validator in current_committee {
391+
for validator in producers {
360392
let underperformance = match SessionValidatorBlockCount::<T>::try_get(&validator) {
361393
Ok(block_count) => {
362394
Perbill::from_rational(block_count, expected_blocks_per_validator)
@@ -390,6 +422,8 @@ impl<T: Config> Pallet<T> {
390422
"Clearing UnderperformedValidatorSessionCount"
391423
);
392424
let _result = UnderperformedValidatorSessionCount::<T>::clear(u32::MAX, None);
425+
let _result = UnderperformedFinalizerSessionCount::<T>::clear(u32::MAX, None);
426+
T::AbftScoresProvider::clear_scores();
393427
}
394428
}
395429

@@ -610,7 +644,7 @@ mod tests {
610644
&non_reserved,
611645
)
612646
.expect("Expected non-empty rotated committee!")
613-
.block_producers,
647+
.producers,
614648
);
615649

616650
assert_eq!(expected_committee, committee,);

0 commit comments

Comments
 (0)