applause based on the external expousre

Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
This commit is contained in:
Uncle Stinky 2025-11-21 13:46:22 +03:00
parent 0bb46482b2
commit 6a2b5a34d2
Signed by: st1nky
GPG Key ID: 016064BD97603B40
4 changed files with 144 additions and 57 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-slow-clap" name = "ghost-slow-clap"
version = "0.3.55" version = "0.3.56"
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

@ -8,8 +8,8 @@ use serde::{Deserialize, Deserializer};
use frame_support::{ use frame_support::{
pallet_prelude::*, pallet_prelude::*,
traits::{ traits::{
tokens::fungible::{Inspect, Mutate}, Currency, DisabledValidators, Get, OneSessionHandler, ValidatorSet,
DisabledValidators, Get, OneSessionHandler, ValidatorSet, ValidatorSetWithIdentification, ValidatorSetWithIdentification,
}, },
WeakBoundedVec, WeakBoundedVec,
}; };
@ -17,6 +17,7 @@ use frame_system::{
offchain::{SendTransactionTypes, SubmitTransaction}, offchain::{SendTransactionTypes, SubmitTransaction},
pallet_prelude::*, pallet_prelude::*,
}; };
pub use pallet::*; pub use pallet::*;
use sp_core::H256; use sp_core::H256;
@ -27,12 +28,12 @@ use sp_runtime::{
storage_lock::{StorageLock, Time}, storage_lock::{StorageLock, Time},
HttpError, HttpError,
}, },
traits::{BlockNumberProvider, Convert, Saturating, TrailingZeroInput}, traits::{AtLeast32BitUnsigned, BlockNumberProvider, Convert, Saturating, TrailingZeroInput},
Perbill, RuntimeAppPublic, RuntimeDebug, Perbill, RuntimeAppPublic, RuntimeDebug,
}; };
use sp_staking::{ use sp_staking::{
offence::{Kind, Offence, ReportOffence}, offence::{Kind, Offence, ReportOffence},
SessionIndex, EraIndex, SessionIndex,
}; };
use sp_std::{collections::btree_map::BTreeMap, prelude::*, vec::Vec}; use sp_std::{collections::btree_map::BTreeMap, prelude::*, vec::Vec};
@ -202,7 +203,7 @@ impl<NetworkId: core::fmt::Debug> core::fmt::Debug for OffchainErr<NetworkId> {
pub type NetworkIdOf<T> = <<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId; pub type NetworkIdOf<T> = <<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId;
pub type BalanceOf<T> = pub type BalanceOf<T> =
<<T as Config>::Currency as Inspect<<T as frame_system::Config>::AccountId>>::Balance; <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
pub type ValidatorId<T> = <<T as Config>::ValidatorSet as ValidatorSet< pub type ValidatorId<T> = <<T as Config>::ValidatorSet as ValidatorSet<
<T as frame_system::Config>::AccountId, <T as frame_system::Config>::AccountId,
@ -217,6 +218,12 @@ pub type IdentificationTuple<T> = (
type OffchainResult<T, A> = Result<A, OffchainErr<NetworkIdOf<T>>>; type OffchainResult<T, A> = Result<A, OffchainErr<NetworkIdOf<T>>>;
pub trait ApplauseListener<Balance: AtLeast32BitUnsigned> {
fn get_current_era() -> EraIndex;
fn get_threshold_amount(era: EraIndex) -> Balance;
fn get_validator_total_exposure(era: EraIndex, index: usize) -> Balance;
}
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
@ -239,7 +246,7 @@ pub mod pallet {
+ MaxEncodedLen; + MaxEncodedLen;
type ValidatorSet: ValidatorSetWithIdentification<Self::AccountId>; type ValidatorSet: ValidatorSetWithIdentification<Self::AccountId>;
type Currency: Inspect<Self::AccountId> + Mutate<Self::AccountId>; type Currency: Currency<Self::AccountId>;
type NetworkDataHandler: NetworkDataBasicHandler type NetworkDataHandler: NetworkDataBasicHandler
+ NetworkDataInspectHandler<NetworkData> + NetworkDataInspectHandler<NetworkData>
+ NetworkDataMutateHandler<NetworkData, BalanceOf<Self>>; + NetworkDataMutateHandler<NetworkData, BalanceOf<Self>>;
@ -250,6 +257,7 @@ pub mod pallet {
ThrottlingOffence<IdentificationTuple<Self>>, ThrottlingOffence<IdentificationTuple<Self>>,
>; >;
type DisabledValidators: DisabledValidators; type DisabledValidators: DisabledValidators;
type ApplauseListener: ApplauseListener<BalanceOf<Self>>;
#[pallet::constant] #[pallet::constant]
type MaxAuthorities: Get<u32>; type MaxAuthorities: Get<u32>;
@ -311,6 +319,19 @@ pub mod pallet {
TimeWentBackwards, TimeWentBackwards,
} }
#[pallet::storage]
#[pallet::getter(fn clapped_amount)]
pub(super) type ClappedAmount<T: Config> = StorageNMap<
_,
(
NMapKey<Twox64Concat, SessionIndex>,
NMapKey<Twox64Concat, H256>,
NMapKey<Twox64Concat, H256>,
),
BalanceOf<T>,
ValueQuery,
>;
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn block_commitments)] #[pallet::getter(fn block_commitments)]
pub(super) type BlockCommitments<T: Config> = StorageMap< pub(super) type BlockCommitments<T: Config> = StorageMap<
@ -584,18 +605,8 @@ impl<T: Config> Pallet<T> {
let (session_index, clap_unique_hash) = Self::mended_session_index(&clap); let (session_index, clap_unique_hash) = Self::mended_session_index(&clap);
let mut claps_in_session = ClapsInSession::<T>::get(&session_index); let mut claps_in_session = ClapsInSession::<T>::get(&session_index);
let disabled_authorities = claps_in_session
.values()
.filter(|info| info.disabled)
.count();
let active_authorities = Authorities::<T>::get(&session_index)
.len()
.saturating_sub(disabled_authorities);
let received_claps_key = (session_index, &clap.transaction_hash, &clap_unique_hash); let received_claps_key = (session_index, &clap.transaction_hash, &clap_unique_hash);
let number_of_received_claps =
ReceivedClaps::<T>::try_mutate(&received_claps_key, |tree_of_claps| { ReceivedClaps::<T>::try_mutate(&received_claps_key, |tree_of_claps| {
let number_of_claps = tree_of_claps.len(); let number_of_claps = tree_of_claps.len();
match (tree_of_claps.contains(&clap.authority_index), clap.removed) { match (tree_of_claps.contains(&clap.authority_index), clap.removed) {
@ -631,8 +642,7 @@ impl<T: Config> Pallet<T> {
}); });
let enough_authorities = let enough_authorities =
Perbill::from_rational(number_of_received_claps as u32, active_authorities as u32) Self::validator_clap_by_amount(clap.authority_index, &received_claps_key);
> Perbill::from_percent(T::ApplauseThreshold::get());
if enough_authorities { if enough_authorities {
let _ = Self::try_applause(&clap, &received_claps_key).inspect_err(|error_msg| { let _ = Self::try_applause(&clap, &received_claps_key).inspect_err(|error_msg| {
@ -647,6 +657,24 @@ impl<T: Config> Pallet<T> {
Ok(()) Ok(())
} }
fn validator_clap_by_amount(
authority_index: AuthIndex,
received_claps_key: &(SessionIndex, &H256, &H256),
) -> bool {
let era = T::ApplauseListener::get_current_era();
let threshold_amount = T::ApplauseListener::get_threshold_amount(era);
let new_clapped_amount =
T::ApplauseListener::get_validator_total_exposure(era, authority_index as usize);
let total_clapped = ClappedAmount::<T>::mutate(received_claps_key, |clapped_amount| {
let total_clapped = clapped_amount.saturating_add(new_clapped_amount);
*clapped_amount = total_clapped;
total_clapped
});
total_clapped >= threshold_amount
}
fn try_applause( fn try_applause(
clap: &Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>, clap: &Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>,
received_claps_key: &(SessionIndex, &H256, &H256), received_claps_key: &(SessionIndex, &H256, &H256),
@ -670,9 +698,7 @@ impl<T: Config> Pallet<T> {
let _ = T::NetworkDataHandler::accumulate_commission(&commission) let _ = T::NetworkDataHandler::accumulate_commission(&commission)
.map_err(|_| Error::<T>::CouldNotAccumulateCommission)?; .map_err(|_| Error::<T>::CouldNotAccumulateCommission)?;
if final_amount > T::Currency::minimum_balance() { let _ = T::Currency::deposit_creating(&clap.receiver, final_amount);
T::Currency::mint_into(&clap.receiver, final_amount)?;
}
*is_applaused = true; *is_applaused = true;

View File

@ -9,7 +9,7 @@ use pallet_session::historical as pallet_session_historical;
use sp_runtime::{ use sp_runtime::{
curve::PiecewiseLinear, curve::PiecewiseLinear,
testing::{TestXt, UintAuthorityId}, testing::{TestXt, UintAuthorityId},
traits::ConvertInto, traits::{AtLeast32BitUnsigned, ConvertInto},
Permill, Permill,
}; };
use sp_staking::{ use sp_staking::{
@ -20,7 +20,7 @@ use sp_staking::{
use sp_runtime::BuildStorage; use sp_runtime::BuildStorage;
use crate as slow_clap; use crate as slow_clap;
use crate::Config; use crate::{ApplauseListener, Config, EraIndex};
type Block = frame_system::mocking::MockBlock<Runtime>; type Block = frame_system::mocking::MockBlock<Runtime>;
@ -174,6 +174,32 @@ impl pallet_balances::Config for Runtime {
type WeightInfo = (); type WeightInfo = ();
} }
type Balance = u64;
pub struct TestSomeCoolTrait;
impl ApplauseListener<Balance> for TestSomeCoolTrait
where
Balance: AtLeast32BitUnsigned + From<u64>,
{
fn get_current_era() -> EraIndex {
1
}
fn get_threshold_amount(_era: EraIndex) -> Balance {
666_666_667u64.into()
}
fn get_validator_total_exposure(_era: EraIndex, index: usize) -> Balance {
match index {
0 => 500_000_000u64,
1 => 300_000_000u64,
2 => 200_000_000u64,
_ => 0,
}
.into()
}
}
impl Config for Runtime { impl Config for Runtime {
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type AuthorityId = UintAuthorityId; type AuthorityId = UintAuthorityId;
@ -184,6 +210,7 @@ impl Config for Runtime {
type BlockNumberProvider = System; type BlockNumberProvider = System;
type ReportUnresponsiveness = OffenceHandler; type ReportUnresponsiveness = OffenceHandler;
type DisabledValidators = Session; type DisabledValidators = Session;
type ApplauseListener = TestSomeCoolTrait;
type MaxAuthorities = ConstU32<5>; type MaxAuthorities = ConstU32<5>;
type ApplauseThreshold = ConstU32<50>; type ApplauseThreshold = ConstU32<50>;

View File

@ -562,25 +562,25 @@ fn should_applause_and_take_next_claps() {
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), pallet::ApplausesForTransaction::<Runtime>::get(&storage_key),
false false
); );
assert_eq!(Balances::balance(&receiver), 0); assert_eq!(Balances::total_balance(&receiver), 0);
assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 0, false));
assert_eq!( assert_eq!(
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), pallet::ApplausesForTransaction::<Runtime>::get(&storage_key),
false false
); );
assert_eq!(Balances::balance(&receiver), 0); assert_eq!(Balances::total_balance(&receiver), 0);
assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false));
assert_eq!( assert_eq!(
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), pallet::ApplausesForTransaction::<Runtime>::get(&storage_key),
true true
); );
assert_eq!(Balances::balance(&receiver), amount); assert_eq!(Balances::total_balance(&receiver), amount);
assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false));
assert_eq!( assert_eq!(
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), pallet::ApplausesForTransaction::<Runtime>::get(&storage_key),
true true
); );
assert_eq!(Balances::balance(&receiver), amount); assert_eq!(Balances::total_balance(&receiver), amount);
}); });
} }
@ -812,8 +812,8 @@ fn should_clap_without_applause_on_gatekeeper_amount_overflow() {
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key_first), pallet::ApplausesForTransaction::<Runtime>::get(&storage_key_first),
true true
); );
assert_eq!(Balances::balance(&first_receiver), big_amount); assert_eq!(Balances::total_balance(&first_receiver), big_amount);
assert_eq!(Balances::balance(&second_receiver), 0); assert_eq!(Balances::total_balance(&second_receiver), 0);
for authority_index in 0..=2 { for authority_index in 0..=2 {
let clap = Clap { let clap = Clap {
@ -831,8 +831,8 @@ fn should_clap_without_applause_on_gatekeeper_amount_overflow() {
assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature));
} }
assert_eq!(Balances::balance(&first_receiver), big_amount); assert_eq!(Balances::total_balance(&first_receiver), big_amount);
assert_eq!(Balances::balance(&second_receiver), 0); assert_eq!(Balances::total_balance(&second_receiver), 0);
assert_eq!(Networks::gatekeeper_amount(network_id), big_amount); assert_eq!(Networks::gatekeeper_amount(network_id), big_amount);
assert_eq!(Networks::bridged_imbalance().bridged_in, big_amount); assert_eq!(Networks::bridged_imbalance().bridged_in, big_amount);
@ -876,8 +876,8 @@ fn should_clap_without_applause_on_commission_overflow() {
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key_first), pallet::ApplausesForTransaction::<Runtime>::get(&storage_key_first),
true true
); );
assert_eq!(Balances::balance(&first_receiver), big_amount); assert_eq!(Balances::total_balance(&first_receiver), big_amount);
assert_eq!(Balances::balance(&second_receiver), 0); assert_eq!(Balances::total_balance(&second_receiver), 0);
for authority_index in 0..=2 { for authority_index in 0..=2 {
let clap = Clap { let clap = Clap {
@ -895,8 +895,8 @@ fn should_clap_without_applause_on_commission_overflow() {
assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature)); assert_ok!(SlowClap::slow_clap(RuntimeOrigin::none(), clap, signature));
} }
assert_eq!(Balances::balance(&first_receiver), big_amount); assert_eq!(Balances::total_balance(&first_receiver), big_amount);
assert_eq!(Balances::balance(&second_receiver), 0); assert_eq!(Balances::total_balance(&second_receiver), 0);
assert_eq!(Networks::gatekeeper_amount(network_id), big_amount); assert_eq!(Networks::gatekeeper_amount(network_id), big_amount);
assert_eq!(Networks::gatekeeper_amount(network_id_other), big_amount); assert_eq!(Networks::gatekeeper_amount(network_id_other), big_amount);
@ -967,13 +967,13 @@ fn should_avoid_applause_during_nullification_period() {
assert_ok!(do_clap_from(session_index, network_id, 0, false)); assert_ok!(do_clap_from(session_index, network_id, 0, false));
assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false));
assert_eq!(Balances::balance(&receiver), 0); assert_eq!(Balances::total_balance(&receiver), 0);
Networks::on_finalize(System::block_number()); Networks::on_finalize(System::block_number());
assert_eq!(Networks::is_nullification_period(), false); assert_eq!(Networks::is_nullification_period(), false);
assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false));
assert_eq!(Balances::balance(&receiver), amount); assert_eq!(Balances::total_balance(&receiver), amount);
}); });
} }
@ -995,7 +995,7 @@ fn should_avoid_session_overlap_on_mended_session_index() {
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), pallet::ApplausesForTransaction::<Runtime>::get(&storage_key),
false false
); );
assert_eq!(Balances::balance(&receiver), 0u64); assert_eq!(Balances::total_balance(&receiver), 0u64);
assert_ok!(do_clap_from(session_index, network_id, 1, false)); assert_ok!(do_clap_from(session_index, network_id, 1, false));
assert_ok!(do_clap_from(session_index, network_id, 2, false)); assert_ok!(do_clap_from(session_index, network_id, 2, false));
@ -1004,7 +1004,7 @@ fn should_avoid_session_overlap_on_mended_session_index() {
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key), pallet::ApplausesForTransaction::<Runtime>::get(&storage_key),
true true
); );
assert_eq!(Balances::balance(&receiver), amount); assert_eq!(Balances::total_balance(&receiver), amount);
}); });
} }
@ -1094,7 +1094,7 @@ fn should_not_fail_on_sub_existential_balance() {
assert_eq!(Networks::gatekeeper_amount(network_id), 0); assert_eq!(Networks::gatekeeper_amount(network_id), 0);
assert_eq!(Networks::bridged_imbalance().bridged_in, 0); assert_eq!(Networks::bridged_imbalance().bridged_in, 0);
assert_eq!(Networks::bridged_imbalance().bridged_out, 0); assert_eq!(Networks::bridged_imbalance().bridged_out, 0);
assert_eq!(Balances::balance(&receiver), 0); assert_eq!(Balances::total_balance(&receiver), 0);
assert_eq!( assert_eq!(
SlowClap::applauses_for_transaction(&received_claps_key), SlowClap::applauses_for_transaction(&received_claps_key),
false false
@ -1108,7 +1108,7 @@ fn should_not_fail_on_sub_existential_balance() {
assert_eq!(Networks::gatekeeper_amount(network_id), amount); assert_eq!(Networks::gatekeeper_amount(network_id), amount);
assert_eq!(Networks::bridged_imbalance().bridged_in, 0); assert_eq!(Networks::bridged_imbalance().bridged_in, 0);
assert_eq!(Networks::bridged_imbalance().bridged_out, 0); assert_eq!(Networks::bridged_imbalance().bridged_out, 0);
assert_eq!(Balances::balance(&receiver), 0); assert_eq!(Balances::total_balance(&receiver), 0);
assert_eq!( assert_eq!(
SlowClap::applauses_for_transaction(&received_claps_key), SlowClap::applauses_for_transaction(&received_claps_key),
true true
@ -1175,6 +1175,40 @@ fn should_register_block_commitments() {
}); });
} }
#[test]
fn should_accumulate_clapped_amount() {
let (network_id, transaction_hash, unique_transaction_hash) =
generate_unique_hash(None, None, None, None);
new_test_ext().execute_with(|| {
let session_index = advance_session_and_get_index();
let storage_key = (session_index, transaction_hash, unique_transaction_hash);
let era = <Runtime as Config>::ApplauseListener::get_current_era();
let threshold_amount = <Runtime as Config>::ApplauseListener::get_threshold_amount(era);
assert_eq!(
ClappedAmount::<Runtime>::get(&storage_key) < threshold_amount,
true
);
assert_ok!(do_clap_from(session_index, network_id, 2, false));
assert_eq!(
ClappedAmount::<Runtime>::get(&storage_key) < threshold_amount,
true
);
assert_ok!(do_clap_from(session_index, network_id, 1, false));
assert_eq!(
ClappedAmount::<Runtime>::get(&storage_key) < threshold_amount,
true
);
assert_ok!(do_clap_from(session_index, network_id, 0, false));
assert_eq!(
ClappedAmount::<Runtime>::get(&storage_key) > threshold_amount,
true
);
});
}
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());