introduce black swan event e.g. disable everybody

Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
This commit is contained in:
Uncle Stinky 2025-11-12 19:03:58 +03:00
parent 092679eb0c
commit 55a77cd3d4
Signed by: st1nky
GPG Key ID: 016064BD97603B40
4 changed files with 52 additions and 50 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-slow-clap" name = "ghost-slow-clap"
version = "0.3.52" version = "0.3.53"
description = "Applause protocol for the EVM bridge" description = "Applause protocol for the EVM bridge"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true

View File

@ -9,8 +9,7 @@ use frame_support::{
pallet_prelude::*, pallet_prelude::*,
traits::{ traits::{
tokens::fungible::{Inspect, Mutate}, tokens::fungible::{Inspect, Mutate},
DisabledValidators, EstimateNextSessionRotation, Get, OneSessionHandler, ValidatorSet, DisabledValidators, Get, OneSessionHandler, ValidatorSet, ValidatorSetWithIdentification,
ValidatorSetWithIdentification,
}, },
WeakBoundedVec, WeakBoundedVec,
}; };
@ -35,11 +34,7 @@ use sp_staking::{
offence::{Kind, Offence, ReportOffence}, offence::{Kind, Offence, ReportOffence},
SessionIndex, SessionIndex,
}; };
use sp_std::{ use sp_std::{collections::btree_map::BTreeMap, prelude::*, vec::Vec};
collections::btree_map::BTreeMap,
prelude::*,
vec::Vec,
};
use ghost_networks::{ use ghost_networks::{
NetworkData, NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler, NetworkData, NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler,
@ -186,7 +181,6 @@ pub mod pallet {
+ MaybeSerializeDeserialize + MaybeSerializeDeserialize
+ MaxEncodedLen; + MaxEncodedLen;
type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
type ValidatorSet: ValidatorSetWithIdentification<Self::AccountId>; type ValidatorSet: ValidatorSetWithIdentification<Self::AccountId>;
type Currency: Inspect<Self::AccountId> + Mutate<Self::AccountId>; type Currency: Inspect<Self::AccountId> + Mutate<Self::AccountId>;
type NetworkDataHandler: NetworkDataBasicHandler type NetworkDataHandler: NetworkDataBasicHandler
@ -215,12 +209,16 @@ pub mod pallet {
#[pallet::constant] #[pallet::constant]
type HistoryDepth: Get<SessionIndex>; type HistoryDepth: Get<SessionIndex>;
#[pallet::constant]
type MinAuthoritiesNumber: Get<u32>;
type WeightInfo: WeightInfo; type WeightInfo: WeightInfo;
} }
#[pallet::event] #[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)] #[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> { pub enum Event<T: Config> {
BlackSwan,
AuthoritiesEquilibrium, AuthoritiesEquilibrium,
SomeAuthoritiesTrottling { SomeAuthoritiesTrottling {
throttling: Vec<IdentificationTuple<T>>, throttling: Vec<IdentificationTuple<T>>,
@ -628,7 +626,8 @@ impl<T: Config> Pallet<T> {
let curr_received_claps_key = (curr_session_index, &transaction_hash, &clap_unique_hash); let curr_received_claps_key = (curr_session_index, &transaction_hash, &clap_unique_hash);
let mut previous_claps = ClapsInSession::<T>::get(&prev_session_index); let mut previous_claps = ClapsInSession::<T>::get(&prev_session_index);
let mut total_received_claps = ReceivedClaps::<T>::get(&prev_received_claps_key).into_inner(); let mut total_received_claps =
ReceivedClaps::<T>::get(&prev_received_claps_key).into_inner();
for (auth_index, info) in ClapsInSession::<T>::get(&curr_session_index).iter() { for (auth_index, info) in ClapsInSession::<T>::get(&curr_session_index).iter() {
if !info.disabled { if !info.disabled {
@ -656,14 +655,9 @@ impl<T: Config> Pallet<T> {
} }
} }
let disabled_authorities = previous_claps let disabled_authorities = previous_claps.values().filter(|info| info.disabled).count();
.values()
.filter(|info| info.disabled)
.count();
let active_authorities = prev_authorities let active_authorities = prev_authorities.len().saturating_sub(disabled_authorities);
.len()
.saturating_sub(disabled_authorities);
let clap = Clap { let clap = Clap {
authority_index: Default::default(), authority_index: Default::default(),
@ -676,10 +670,9 @@ impl<T: Config> Pallet<T> {
amount, amount,
}; };
let enough_authorities = Perbill::from_rational( let enough_authorities =
total_received_claps.len() as u32, Perbill::from_rational(total_received_claps.len() as u32, active_authorities as u32)
active_authorities as u32, > Perbill::from_percent(T::ApplauseThreshold::get());
) > Perbill::from_percent(T::ApplauseThreshold::get());
ensure!(enough_authorities, Error::<T>::NotEnoughClaps); ensure!(enough_authorities, Error::<T>::NotEnoughClaps);
Self::try_applause(&clap, &prev_received_claps_key)?; Self::try_applause(&clap, &prev_received_claps_key)?;
@ -1225,8 +1218,21 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
}) })
.collect::<Vec<IdentificationTuple<T>>>(); .collect::<Vec<IdentificationTuple<T>>>();
if offenders.is_empty() { let disabled_validators = T::DisabledValidators::disabled_validators()
.into_iter()
.count();
let offenders_length = offenders.len();
let authorities_left: u32 = authorities_len
.saturating_sub(disabled_validators)
.saturating_sub(offenders_length)
.try_into()
.unwrap_or_default();
if offenders_length == 0 {
Self::deposit_event(Event::<T>::AuthoritiesEquilibrium); Self::deposit_event(Event::<T>::AuthoritiesEquilibrium);
} else if authorities_left < T::MinAuthoritiesNumber::get() {
Self::deposit_event(Event::<T>::BlackSwan);
} else { } else {
Self::deposit_event(Event::<T>::SomeAuthoritiesTrottling { Self::deposit_event(Event::<T>::SomeAuthoritiesTrottling {
throttling: offenders.clone(), throttling: offenders.clone(),

View File

@ -3,7 +3,6 @@
use frame_support::{ use frame_support::{
derive_impl, parameter_types, derive_impl, parameter_types,
traits::{ConstU32, ConstU64}, traits::{ConstU32, ConstU64},
weights::Weight,
}; };
use frame_system::EnsureRoot; use frame_system::EnsureRoot;
use pallet_session::historical as pallet_session_historical; use pallet_session::historical as pallet_session_historical;
@ -140,27 +139,6 @@ parameter_types! {
pub static MockAverageSessionLength: Option<u64> = None; pub static MockAverageSessionLength: Option<u64> = None;
} }
pub struct TestNextSessionRotation;
impl frame_support::traits::EstimateNextSessionRotation<u64> for TestNextSessionRotation {
fn average_session_length() -> u64 {
let mock = MockAverageSessionLength::mutate(|p| p.take());
mock.unwrap_or(pallet_session::PeriodicSessions::<Period, Offset>::average_session_length())
}
fn estimate_current_session_progress(now: u64) -> (Option<Permill>, Weight) {
let (estimate, weight) =
pallet_session::PeriodicSessions::<Period, Offset>::estimate_current_session_progress(
now,
);
let mock = MockCurrentSessionProgress::mutate(|p| p.take());
(mock.unwrap_or(estimate), weight)
}
fn estimate_next_session_rotation(now: u64) -> (Option<u64>, Weight) {
pallet_session::PeriodicSessions::<Period, Offset>::estimate_next_session_rotation(now)
}
}
impl ghost_networks::Config for Runtime { impl ghost_networks::Config for Runtime {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type Currency = Balances; type Currency = Balances;
@ -200,7 +178,6 @@ impl Config for Runtime {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type AuthorityId = UintAuthorityId; type AuthorityId = UintAuthorityId;
type NextSessionRotation = TestNextSessionRotation;
type ValidatorSet = Historical; type ValidatorSet = Historical;
type Currency = Balances; type Currency = Balances;
type NetworkDataHandler = Networks; type NetworkDataHandler = Networks;
@ -210,9 +187,10 @@ impl Config for Runtime {
type MaxAuthorities = ConstU32<5>; type MaxAuthorities = ConstU32<5>;
type ApplauseThreshold = ConstU32<50>; type ApplauseThreshold = ConstU32<50>;
type OffenceThreshold = ConstU32<75>; type OffenceThreshold = ConstU32<0>;
type UnsignedPriority = ConstU64<{ 1 << 20 }>; type UnsignedPriority = ConstU64<{ 1 << 20 }>;
type HistoryDepth = HistoryDepth; type HistoryDepth = HistoryDepth;
type MinAuthoritiesNumber = ConstU32<2>;
type WeightInfo = (); type WeightInfo = ();
} }

View File

@ -979,7 +979,8 @@ fn should_self_applause_after_diabled() {
let curr_session_index = Session::session_index(); let curr_session_index = Session::session_index();
pallet::ClapsInSession::<Runtime>::mutate(&session_index, |claps| { pallet::ClapsInSession::<Runtime>::mutate(&session_index, |claps| {
claps.entry(1 as AuthIndex) claps
.entry(1 as AuthIndex)
.and_modify(|individual| (*individual).disabled = true) .and_modify(|individual| (*individual).disabled = true)
.or_insert(SessionAuthorityInfo { .or_insert(SessionAuthorityInfo {
claps: 0u32, claps: 0u32,
@ -988,7 +989,8 @@ fn should_self_applause_after_diabled() {
}); });
pallet::ClapsInSession::<Runtime>::mutate(&curr_session_index, |claps| { pallet::ClapsInSession::<Runtime>::mutate(&curr_session_index, |claps| {
claps.entry(2 as AuthIndex) claps
.entry(2 as AuthIndex)
.and_modify(|individual| (*individual).disabled = true) .and_modify(|individual| (*individual).disabled = true)
.or_insert(SessionAuthorityInfo { .or_insert(SessionAuthorityInfo {
claps: 0u32, claps: 0u32,
@ -1207,6 +1209,22 @@ fn should_not_fail_on_sub_existential_balance() {
}); });
} }
#[test]
fn should_emit_black_swan_if_not_enough_authorities_left() {
let (network_id, _, _) = generate_unique_hash(None, None, None, None);
new_test_ext().execute_with(|| {
let session_index = advance_session_and_get_index();
assert_ok!(do_clap_from(session_index, network_id, 0, false));
Session::disable_index(1);
Session::disable_index(2);
advance_session();
advance_session();
System::assert_has_event(RuntimeEvent::SlowClap(crate::Event::BlackSwan));
});
}
fn advance_session_and_get_index() -> u32 { fn advance_session_and_get_index() -> u32 {
advance_session(); advance_session();
assert_eq!(Session::validators(), Vec::<u64>::new()); assert_eq!(Session::validators(), Vec::<u64>::new());