Compare commits

...

6 Commits

Author SHA1 Message Date
75268b4c0a
casper runtime upgrade
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-09 16:09:56 +03:00
275567ef79
Merge branch 'pallet-slow-clap'
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2025-11-09 15:31:43 +03:00
64de0027bf
align self-applause logic and disable offchain worker if authority index is disabled
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-09 15:29:50 +03:00
a3ed395689
take disabled from the next session and rustfmt pallet code
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-08 14:02:04 +03:00
7a710ec9cb
check if validator disabled during the validate_unsigned and small typo fixes
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-08 13:48:14 +03:00
cc141105bb
propagate disabled authorities to upcoming sessions in era
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2025-11-08 13:38:15 +03:00
7 changed files with 151 additions and 42 deletions

4
Cargo.lock generated
View File

@ -1186,7 +1186,7 @@ dependencies = [
[[package]]
name = "casper-runtime"
version = "3.5.33"
version = "3.5.34"
dependencies = [
"casper-runtime-constants",
"frame-benchmarking",
@ -3836,7 +3836,7 @@ dependencies = [
[[package]]
name = "ghost-slow-clap"
version = "0.3.47"
version = "0.3.51"
dependencies = [
"frame-benchmarking",
"frame-support",

View File

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

View File

@ -9,7 +9,7 @@ use frame_support::{
pallet_prelude::*,
traits::{
tokens::fungible::{Inspect, Mutate},
EstimateNextSessionRotation, Get, OneSessionHandler, ValidatorSet,
DisabledValidators, EstimateNextSessionRotation, Get, OneSessionHandler, ValidatorSet,
ValidatorSetWithIdentification,
},
WeakBoundedVec,
@ -36,7 +36,7 @@ use sp_staking::{
SessionIndex,
};
use sp_std::{
collections::{btree_map::BTreeMap, btree_set::BTreeSet},
collections::btree_map::BTreeMap,
prelude::*,
vec::Vec,
};
@ -118,6 +118,7 @@ enum OffchainErr<NetworkId> {
UnknownNetworkType(NetworkId),
OffchainTimeoutPeriod(NetworkId),
TooManyRequests(NetworkId),
AuthorityDisabled(AuthIndex),
}
impl<NetworkId: core::fmt::Debug> core::fmt::Debug for OffchainErr<NetworkId> {
@ -143,7 +144,7 @@ impl<NetworkId: core::fmt::Debug> core::fmt::Debug for OffchainErr<NetworkId> {
OffchainErr::UnknownNetworkType(ref network_id) => write!(fmt, "Unknown type for network #{:?}.", network_id),
OffchainErr::OffchainTimeoutPeriod(ref network_id) => write!(fmt, "Offchain request should be in-flight for network #{:?}.", network_id),
OffchainErr::TooManyRequests(ref network_id) => write!(fmt, "Too many requests over RPC endpoint for network #{:?}.", network_id),
OffchainErr::AuthorityDisabled(ref authority_index) => write!(fmt, "Authority index {:?} is disabled in current session.", authority_index),
}
}
}
@ -199,6 +200,7 @@ pub mod pallet {
IdentificationTuple<Self>,
ThrottlingOffence<IdentificationTuple<Self>>,
>;
type DisabledValidators: DisabledValidators;
#[pallet::constant]
type MaxAuthorities: Get<u32>;
@ -242,7 +244,6 @@ pub mod pallet {
#[pallet::error]
pub enum Error<T> {
NotEnoughClaps,
CurrentValidatorIsDisabled,
AlreadyClapped,
UnregisteredClapRemove,
TooMuchAuthorities,
@ -398,6 +399,14 @@ pub mod pallet {
None => return InvalidTransaction::BadSigner.into(),
};
if ClapsInSession::<T>::get(&session_index)
.get(&clap.authority_index)
.map(|info| info.disabled)
.unwrap_or_default()
{
return InvalidTransaction::BadSigner.into();
}
let signature_valid =
clap.using_encoded(|encoded_clap| authority.verify(&encoded_clap, signature));
@ -498,22 +507,14 @@ impl<T: Config> Pallet<T> {
let (session_index, clap_unique_hash) = Self::mended_session_index(&clap);
let mut claps_in_session = ClapsInSession::<T>::get(&session_index);
ensure!(
claps_in_session
.get(&clap.authority_index)
.map(|info| !info.disabled)
.unwrap_or(true),
Error::<T>::CurrentValidatorIsDisabled
);
let disabled_authorites = claps_in_session
let disabled_authorities = claps_in_session
.values()
.filter(|info| info.disabled)
.count();
let active_authorities = Authorities::<T>::get(&session_index)
.len()
.saturating_sub(disabled_authorites);
.saturating_sub(disabled_authorities);
let received_claps_key = (session_index, &clap.transaction_hash, &clap_unique_hash);
@ -618,34 +619,53 @@ impl<T: Config> Pallet<T> {
let curr_session_index = prev_session_index.saturating_add(1);
let clap_unique_hash = Self::generate_unique_hash(&receiver, &amount, &network_id);
let prev_authorities = Authorities::<T>::get(&prev_session_index);
let prev_authorities = Authorities::<T>::get(&prev_session_index)
.into_iter()
.enumerate()
.map(|(i, auth)| (auth, i as AuthIndex))
.collect::<BTreeMap<T::AuthorityId, AuthIndex>>();
let curr_authorities = Authorities::<T>::get(&curr_session_index);
let prev_received_claps_key = (prev_session_index, &transaction_hash, &clap_unique_hash);
let curr_received_claps_key = (curr_session_index, &transaction_hash, &clap_unique_hash);
let prev_received_claps = ReceivedClaps::<T>::get(&prev_received_claps_key)
.into_iter()
.filter_map(|auth_index| prev_authorities.get(auth_index as usize))
.cloned()
.collect::<BTreeSet<T::AuthorityId>>();
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 curr_received_claps = ReceivedClaps::<T>::get(&curr_received_claps_key)
.into_iter()
.filter_map(|auth_index| curr_authorities.get(auth_index as usize))
.cloned()
.collect::<BTreeSet<T::AuthorityId>>();
for (auth_index, info) in ClapsInSession::<T>::get(&curr_session_index).iter() {
if !info.disabled {
continue;
}
let disabled_authorites = ClapsInSession::<T>::get(&prev_session_index)
if let Some(curr_authority) = curr_authorities.get(*auth_index as usize) {
if let Some(prev_position) = prev_authorities.get(&curr_authority) {
previous_claps
.entry(*prev_position as AuthIndex)
.and_modify(|individual| (*individual).disabled = true)
.or_insert(SessionAuthorityInfo {
claps: 0u32,
disabled: true,
});
}
}
}
for auth_index in ReceivedClaps::<T>::get(&curr_received_claps_key).into_iter() {
if let Some(curr_authority) = curr_authorities.get(auth_index as usize) {
if let Some(prev_position) = prev_authorities.get(&curr_authority) {
let _ = total_received_claps.insert(*prev_position as AuthIndex);
}
}
}
let disabled_authorities = previous_claps
.values()
.filter(|info| info.disabled)
.count();
let active_authorities = prev_authorities.len().saturating_sub(disabled_authorites);
let summary_authority_claps_length = curr_received_claps
.symmetric_difference(&prev_received_claps)
.count();
let active_authorities = prev_authorities
.len()
.saturating_sub(disabled_authorities);
let clap = Clap {
authority_index: Default::default(),
@ -659,7 +679,7 @@ impl<T: Config> Pallet<T> {
};
let enough_authorities = Perbill::from_rational(
summary_authority_claps_length as u32,
total_received_claps.len() as u32,
active_authorities as u32,
) > Perbill::from_percent(T::ApplauseThreshold::get());
@ -748,6 +768,14 @@ impl<T: Config> Pallet<T> {
network_id: NetworkIdOf<T>,
network_data: &NetworkData,
) -> OffchainResult<T, ()> {
if ClapsInSession::<T>::get(&session_index)
.get(&authority_index)
.map(|info| info.disabled)
.unwrap_or_default()
{
return Err(OffchainErr::AuthorityDisabled(authority_index));
}
let network_id_encoded = network_id.encode();
let block_number_key = Self::create_storage_key(b"block-", &network_id_encoded);
@ -1104,7 +1132,18 @@ impl<T: Config> Pallet<T> {
Validators::<T>::insert(&session_index, bounded_validators);
Authorities::<T>::set(&session_index, bounded_authorities);
ClapsInSession::<T>::set(&session_index, Default::default());
let mut disabled_validators: BTreeMap<AuthIndex, SessionAuthorityInfo> = Default::default();
for disabled_index in T::DisabledValidators::disabled_validators().iter() {
let _ = disabled_validators.insert(
*disabled_index,
SessionAuthorityInfo {
claps: 0u32,
disabled: true,
},
);
}
ClapsInSession::<T>::set(&session_index, disabled_validators);
}
fn clear_history(target_session_index: &SessionIndex) {

View File

@ -206,6 +206,7 @@ impl Config for Runtime {
type NetworkDataHandler = Networks;
type BlockNumberProvider = System;
type ReportUnresponsiveness = OffenceHandler;
type DisabledValidators = Session;
type MaxAuthorities = ConstU32<5>;
type ApplauseThreshold = ConstU32<50>;

View File

@ -710,7 +710,7 @@ fn should_throw_error_if_validator_disabled_and_ignore_later() {
assert_eq!(Session::disable_index(0), true);
assert_err!(
do_clap_from(session_index, network_id, 0, false),
Error::<Runtime>::CurrentValidatorIsDisabled
DispatchError::Other("Invalid signing address")
);
assert_eq!(pallet::ReceivedClaps::<Runtime>::get(&storage_key).len(), 0);
@ -944,6 +944,74 @@ fn should_avoid_applause_during_nullification_period() {
});
}
#[test]
fn should_self_applause_after_diabled() {
let zero = 0u64;
let (network_id, transaction_hash, unique_transaction_hash) =
generate_unique_hash(None, None, None, None);
let (_, receiver, amount) = get_mocked_metadata();
new_test_ext().execute_with(|| {
let _ = prepare_evm_network(Some(network_id), Some(0));
let session_index = advance_session_and_get_index();
let storage_key = (session_index, transaction_hash, unique_transaction_hash);
assert_err!(
SlowClap::self_applause(
RuntimeOrigin::signed(receiver),
network_id,
session_index,
transaction_hash,
receiver,
amount,
),
Error::<Runtime>::NotEnoughClaps,
);
assert_eq!(
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key),
false
);
assert_eq!(Balances::balance(&receiver), zero);
assert_ok!(do_clap_from(session_index, network_id, 0, false));
advance_session();
let curr_session_index = Session::session_index();
pallet::ClapsInSession::<Runtime>::mutate(&session_index, |claps| {
claps.entry(1 as AuthIndex)
.and_modify(|individual| (*individual).disabled = true)
.or_insert(SessionAuthorityInfo {
claps: 0u32,
disabled: true,
});
});
pallet::ClapsInSession::<Runtime>::mutate(&curr_session_index, |claps| {
claps.entry(2 as AuthIndex)
.and_modify(|individual| (*individual).disabled = true)
.or_insert(SessionAuthorityInfo {
claps: 0u32,
disabled: true,
});
});
assert_ok!(SlowClap::self_applause(
RuntimeOrigin::signed(receiver),
network_id,
session_index,
transaction_hash,
receiver,
amount,
));
assert_eq!(
pallet::ApplausesForTransaction::<Runtime>::get(&storage_key),
true
);
assert_eq!(Balances::balance(&receiver), amount);
});
}
#[test]
fn should_self_applause_if_enough_claps() {
let zero = 0u64;

View File

@ -1,6 +1,6 @@
[package]
name = "casper-runtime"
version = "3.5.33"
version = "3.5.34"
build = "build.rs"
description = "Runtime of the Casper Network"
edition.workspace = true

View File

@ -117,8 +117,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("casper"),
impl_name: create_runtime_str!("casper-svengali"),
authoring_version: 0,
spec_version: 3,
impl_version: 1,
spec_version: 4,
impl_version: 2,
apis: RUNTIME_API_VERSIONS,
transaction_version: 1,
state_version: 1,
@ -1059,9 +1059,9 @@ impl ghost_claims::Config<CultCollectiveInstance> for Runtime {
parameter_types! {
// will be used in `Perbill::from_percent()`
pub const ApplauseThreshold: u32 = 70;
pub const ApplauseThreshold: u32 = 66;
// will be used in `Perbill::from_percent()`
pub const OffenceThreshold: u32 = 40;
pub const OffenceThreshold: u32 = 5;
pub const SlowClapUnsignedPriority: TransactionPriority = TransactionPriority::MAX;
pub const SlowClapHistoryDepth: sp_staking::SessionIndex =
StakingHistoryDepth::get() * SessionsPerEra::get();
@ -1077,6 +1077,7 @@ impl ghost_slow_clap::Config for Runtime {
type NetworkDataHandler = GhostNetworks;
type BlockNumberProvider = System;
type ReportUnresponsiveness = Offences;
type DisabledValidators = Session;
type MaxAuthorities = MaxAuthorities;
type ApplauseThreshold = ApplauseThreshold;