Compare commits

...

6 Commits

Author SHA1 Message Date
0e228d890d
rustfmt changes
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-18 14:41:58 +03:00
5e17c12677
use new logic from slow-clap for casper runtime
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-18 14:40:52 +03:00
c112ad22c2
apply eviction logic during the offence preparation
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-18 14:38:34 +03:00
0dd27d6429
eviction trait added
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-18 14:36:51 +03:00
8252107e96
make multiplication to round up during slash fraction calculation
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-10 11:14:06 +03:00
6419d4ba16
add hoodi network for the rpc-tester
Signed-off-by: Uncle Stretch <uncle.stretch@ghostchain.io>
2026-06-10 11:11:46 +03:00
12 changed files with 109 additions and 14 deletions

6
Cargo.lock generated
View File

@ -1186,7 +1186,7 @@ dependencies = [
[[package]] [[package]]
name = "casper-runtime" name = "casper-runtime"
version = "3.5.41" version = "3.5.42"
dependencies = [ dependencies = [
"casper-runtime-constants", "casper-runtime-constants",
"frame-benchmarking", "frame-benchmarking",
@ -3838,7 +3838,7 @@ dependencies = [
[[package]] [[package]]
name = "ghost-slow-clap" name = "ghost-slow-clap"
version = "0.4.28" version = "0.4.30"
dependencies = [ dependencies = [
"frame-benchmarking", "frame-benchmarking",
"frame-support", "frame-support",
@ -3890,7 +3890,7 @@ dependencies = [
[[package]] [[package]]
name = "ghost-traits" name = "ghost-traits"
version = "0.3.32" version = "0.3.33"
dependencies = [ dependencies = [
"frame-support", "frame-support",
"sp-runtime 31.0.1", "sp-runtime 31.0.1",

View File

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

@ -41,6 +41,7 @@ use ghost_networks::{
NetworkData, NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler, NetworkData, NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler,
NetworkType, NetworkType,
}; };
use ghost_traits::evictor::StakingEvictor;
use ghost_traits::exposure::ExposureListener; use ghost_traits::exposure::ExposureListener;
pub mod migrations; pub mod migrations;
@ -309,6 +310,7 @@ pub mod pallet {
>; >;
type DisabledValidators: DisabledValidators; type DisabledValidators: DisabledValidators;
type ExposureListener: ExposureListener<BalanceOf<Self>, Self::AccountId>; type ExposureListener: ExposureListener<BalanceOf<Self>, Self::AccountId>;
type StakingEvictor: StakingEvictor<Self::AccountId>;
#[pallet::constant] #[pallet::constant]
type MaxAuthorities: Get<u32>; type MaxAuthorities: Get<u32>;
@ -1455,7 +1457,15 @@ impl<T: Config> Pallet<T> {
let authority_index = index as AuthIndex; let authority_index = index as AuthIndex;
if offence_bitmap.exists(&authority_index) { if offence_bitmap.exists(&authority_index) {
weight.saturating_accrue(T::DbWeight::get().reads(1)); weight.saturating_accrue(T::DbWeight::get().reads(2));
if let Some(account_id) = T::ExposureListener::get_account_by_index(index) {
weight.saturating_accrue(T::DbWeight::get().reads(1));
if T::StakingEvictor::try_evict_validator(&account_id) {
weight.saturating_accrue(T::DbWeight::get().writes(2));
}
}
if let Some(full_id) = <T::ValidatorSet as ValidatorSetWithIdentification< if let Some(full_id) = <T::ValidatorSet as ValidatorSetWithIdentification<
T::AccountId, T::AccountId,
>>::IdentificationOf::convert(id.clone()) >>::IdentificationOf::convert(id.clone())
@ -1637,7 +1647,7 @@ impl<Offender: Clone> Offence<Offender> for SlowClapOffence<Offender> {
missed_percent.saturating_mul(Perbill::from_rational(1, offenders_count)) missed_percent.saturating_mul(Perbill::from_rational(1, offenders_count))
} }
OffenceType::CommitmentOffence => offenders_count OffenceType::CommitmentOffence => offenders_count
.checked_sub(Perbill::from_percent(9).mul_ceil(self.validator_set_count)) .checked_sub(Perbill::from_percent(9).mul_floor(self.validator_set_count))
.map(|threshold| { .map(|threshold| {
Perbill::from_rational(threshold.saturating_mul(4), self.validator_set_count) Perbill::from_rational(threshold.saturating_mul(4), self.validator_set_count)
.square() .square()

View File

@ -5,6 +5,7 @@ use frame_support::{
traits::{ConstU32, ConstU64}, traits::{ConstU32, ConstU64},
}; };
use frame_system::EnsureRoot; use frame_system::EnsureRoot;
use ghost_traits::evictor::StakingEvictor;
use pallet_session::historical as pallet_session_historical; use pallet_session::historical as pallet_session_historical;
use sp_runtime::{ use sp_runtime::{
curve::PiecewiseLinear, curve::PiecewiseLinear,
@ -36,6 +37,7 @@ frame_support::construct_runtime!(
); );
parameter_types! { parameter_types! {
pub static EvicatedValidators: Vec<u64> = vec![];
pub static FixedValidators: Vec<u64> = vec![0, 1, 2, 3]; pub static FixedValidators: Vec<u64> = vec![0, 1, 2, 3];
} }
@ -172,6 +174,25 @@ impl pallet_balances::Config for Runtime {
type Balance = u64; type Balance = u64;
pub struct TestStakingEvictor;
impl StakingEvictor<u64> for TestStakingEvictor {
fn try_evict_validator(who: &u64) -> bool {
if !FixedValidators::get().contains(who) {
return false;
}
let mut evicted_validators = EvicatedValidators::get();
match evicted_validators.iter().position(|x| x == who) {
Some(_) => false,
None => {
evicted_validators.push(*who);
EvicatedValidators::set(evicted_validators);
true
}
}
}
}
pub struct TestExposureListener; pub struct TestExposureListener;
impl ExposureListener<Balance, u64> for TestExposureListener impl ExposureListener<Balance, u64> for TestExposureListener
where where
@ -217,6 +238,7 @@ impl Config for Runtime {
type ReportUnresponsiveness = OffenceHandler; type ReportUnresponsiveness = OffenceHandler;
type DisabledValidators = Session; type DisabledValidators = Session;
type ExposureListener = TestExposureListener; type ExposureListener = TestExposureListener;
type StakingEvictor = TestStakingEvictor;
type MaxAuthorities = ConstU32<5>; type MaxAuthorities = ConstU32<5>;
type ApplauseThreshold = ConstU32<500_000_000>; type ApplauseThreshold = ConstU32<500_000_000>;

View File

@ -1691,6 +1691,40 @@ fn migration_from_v2_to_v3_works_fine() {
}); });
} }
#[test]
fn validators_are_evicted_during_offence() {
let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);
new_test_ext().execute_with(|| {
let session_index = advance_session_and_get_index();
prepare_evm_network(None, None);
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);
System::set_block_number(5 * EpochDuration::get() / 2);
let last_stored_block = 69;
for i in 0..3 {
assert_ok!(do_block_commitment(
session_index,
network_id,
i,
last_stored_block,
));
}
assert_eq!(EvicatedValidators::get().len(), 0);
let current_block = SlowClap::current_block_number();
SlowClap::on_initialize(current_block);
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::SomeAuthoritiesDelayed {
delayed: vec![(3, 3)],
},
));
assert_eq!(EvicatedValidators::get().len(), 1);
});
}
fn assert_clapped_amount( fn assert_clapped_amount(
session_index: &SessionIndex, session_index: &SessionIndex,
unique_hash: &H256, unique_hash: &H256,

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-traits" name = "ghost-traits"
version = "0.3.32" version = "0.3.33"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true

View File

@ -0,0 +1,3 @@
pub trait StakingEvictor<AccountId> {
fn try_evict_validator(who: &AccountId) -> bool;
}

View File

@ -1,4 +1,5 @@
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
pub mod evictor;
pub mod exposure; pub mod exposure;
pub mod networks; pub mod networks;

View File

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

View File

@ -1,5 +1,6 @@
use super::*; use super::*;
use frame_support::{dispatch::DispatchResultWithPostInfo, traits::PrivilegeCmp}; use frame_support::{dispatch::DispatchResultWithPostInfo, traits::PrivilegeCmp};
use ghost_traits::evictor::StakingEvictor;
use ghost_traits::exposure::ExposureListener; use ghost_traits::exposure::ExposureListener;
use pallet_alliance::{ProposalIndex, ProposalProvider}; use pallet_alliance::{ProposalIndex, ProposalProvider};
use primitives::Balance; use primitives::Balance;
@ -78,6 +79,17 @@ impl PrivilegeCmp<OriginCaller> for EqualOrGreatestRootCmp {
} }
} }
/// Used to evict validators and nominators from the staking pallet.
pub struct RuntimeStakingEvictor<T>(PhantomData<T>);
impl<T> StakingEvictor<AccountIdOf<T>> for RuntimeStakingEvictor<T>
where
T: pallet_staking::Config,
{
fn try_evict_validator(who: &AccountIdOf<T>) -> bool {
pallet_staking::Pallet::<T>::do_remove_validator(who)
}
}
/// Used to get the exposure information out of staking pallet directly. /// Used to get the exposure information out of staking pallet directly.
pub struct StakingExposureListener<T>(PhantomData<T>); pub struct StakingExposureListener<T>(PhantomData<T>);
impl<T> ExposureListener<Balance, AccountIdOf<T>> for StakingExposureListener<T> impl<T> ExposureListener<Balance, AccountIdOf<T>> for StakingExposureListener<T>

View File

@ -79,7 +79,10 @@ mod genesis_config_presets;
mod impls; mod impls;
mod weights; mod weights;
pub use impls::{AllianceProposalProvider, EqualOrGreatestRootCmp, StakingExposureListener}; pub use impls::{
AllianceProposalProvider, EqualOrGreatestRootCmp, RuntimeStakingEvictor,
StakingExposureListener,
};
// Governance configuration. // Governance configuration.
pub mod cult; pub mod cult;
@ -119,8 +122,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("casper"), spec_name: create_runtime_str!("casper"),
impl_name: create_runtime_str!("casper-svengali"), impl_name: create_runtime_str!("casper-svengali"),
authoring_version: 0, authoring_version: 0,
spec_version: 6, spec_version: 7,
impl_version: 4, impl_version: 5,
apis: RUNTIME_API_VERSIONS, apis: RUNTIME_API_VERSIONS,
transaction_version: 1, transaction_version: 1,
state_version: 1, state_version: 1,
@ -1102,6 +1105,7 @@ impl ghost_slow_clap::Config for Runtime {
type ReportUnresponsiveness = Offences; type ReportUnresponsiveness = Offences;
type DisabledValidators = Session; type DisabledValidators = Session;
type ExposureListener = StakingExposureListener<Runtime>; type ExposureListener = StakingExposureListener<Runtime>;
type StakingEvictor = RuntimeStakingEvictor<Runtime>;
type ApplauseThreshold = ApplauseThreshold; type ApplauseThreshold = ApplauseThreshold;

View File

@ -17,15 +17,21 @@ MORDOR_RPC=(
"https://rpc.mordor.etccooperative.org" "https://rpc.mordor.etccooperative.org"
) )
HOODI_RPC=(
"https://rpc.hoodi.ethpandaops.io"
"https://0xrpc.io/hoodi"
"https://ethereum-hoodi.gateway.tatum.io"
"https://rpc.sentio.xyz/hoodi"
)
show_help() { show_help() {
cat << EOF cat << EOF
Usage: $(basename "$0") [OPTIONS] Usage: $(basename "$0") [OPTIONS]
Options: Options:
--chain <name> Specify network: 'sepolia' or 'mordor'. If omitted, checks both. --chain <name> Specify network: 'sepolia', 'hoodi' or 'mordor'. If omitted, checks all.
--output <dir> Base directory to save results. Creates a subfolder with Unix Timestamp. --output <dir> Base directory to save results. Creates a subfolder with Unix Timestamp.
--rpcs <urls> Comma-separated list of custom RPC endpoints. --rpcs <urls> Comma-separated list of custom RPC endpoints. Disables default networks lists.
Disables default Sepolia/Mordor lists.
--help Show this help message. --help Show this help message.
Example: Example:
@ -129,8 +135,11 @@ else
check_network "Sepolia" "${SEPOLIA_RPC[@]}" check_network "Sepolia" "${SEPOLIA_RPC[@]}"
elif [ "$CHOSEN_CHAIN" == "mordor" ]; then elif [ "$CHOSEN_CHAIN" == "mordor" ]; then
check_network "Mordor" "${MORDOR_RPC[@]}" check_network "Mordor" "${MORDOR_RPC[@]}"
elif [ "$CHOSEN_CHAIN" == "hoodi" ]; then
check_network "Hoodi" "${HOODI_RPC[@]}"
else else
check_network "Sepolia" "${SEPOLIA_RPC[@]}" check_network "Sepolia" "${SEPOLIA_RPC[@]}"
check_network "Mordor" "${MORDOR_RPC[@]}" check_network "Mordor" "${MORDOR_RPC[@]}"
check_network "Hoodi" "${HOODI_RPC[@]}"
fi fi
fi fi