Compare commits

...

6 Commits

Author SHA1 Message Date
5dd0c73f7a
move block commitment check to the on_initialize hook
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 17:57:49 +03:00
03262539aa
adapt trait; add extra test; fix benchmarking
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 17:55:26 +03:00
f524b01b03
make function name more obvious
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 17:52:42 +03:00
f99eee2e1a
remove nullification functionality from trait
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 14:17:35 +03:00
d3cc3ac1b3
remove unnecessary storage entry in pallet
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 14:15:51 +03:00
7a7712df0b
replace custom muldiv with multiply_by_rational_with_rounding from substrate
Signed-off-by: Uncle Stinky <uncle.stinky@ghostchain.io>
2026-02-21 12:54:29 +03:00
14 changed files with 306 additions and 507 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-networks" name = "ghost-networks"
version = "0.1.20" version = "0.1.23"
license.workspace = true license.workspace = true
authors.workspace = true authors.workspace = true
edition.workspace = true edition.workspace = true

View File

@ -85,13 +85,15 @@ benchmarks! {
let (chain_id, network) = prepare_network::<T>(i, j, k); let (chain_id, network) = prepare_network::<T>(i, j, k);
let authority = T::RegisterOrigin::try_successful_origin() let authority = T::RegisterOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let prev_network = GhostNetworks::<T>::networks(chain_id.clone()); assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), None);
assert!(GhostNetworks::<T>::network_indexes().is_empty());
}: _<T::RuntimeOrigin>(authority, chain_id.clone(), network.clone()) }: _<T::RuntimeOrigin>(authority, chain_id.clone(), network.clone())
verify { verify {
assert_last_event::<T>(Event::NetworkRegistered { assert_last_event::<T>(Event::NetworkRegistered {
chain_id: chain_id.clone(), network, chain_id: chain_id.clone(), network: network.clone(),
}.into()); }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network); assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), Some(network));
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id]);
} }
update_network_name { update_network_name {
@ -251,20 +253,23 @@ benchmarks! {
assert_last_event::<T>(Event::NetworkAvgBlockSpeedUpdated { assert_last_event::<T>(Event::NetworkAvgBlockSpeedUpdated {
chain_id: chain_id.clone(), avg_block_speed, chain_id: chain_id.clone(), avg_block_speed,
}.into()); }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network); assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), None);
} }
remove_network { remove_network {
let (chain_id, network) = prepare_network::<T>(1, 1, 1); let (chain_id, network) = prepare_network::<T>(1, 1, 1);
let authority = T::RemoveOrigin::try_successful_origin() let authority = T::RemoveOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?; .map_err(|_| BenchmarkError::Weightless)?;
let prev_network = create_network::<T>(chain_id.clone(), network.clone())?; let _ = create_network::<T>(chain_id.clone(), network.clone())?;
assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), Some(network));
assert_eq!(GhostNetworks::<T>::network_indexes(), vec![chain_id.clone()]);
}: _<T::RuntimeOrigin>(authority, chain_id.clone()) }: _<T::RuntimeOrigin>(authority, chain_id.clone())
verify { verify {
assert_last_event::<T>(Event::NetworkRemoved { assert_last_event::<T>(Event::NetworkRemoved {
chain_id: chain_id.clone(), chain_id: chain_id.clone(),
}.into()); }.into());
assert_ne!(GhostNetworks::<T>::networks(chain_id.clone()), prev_network); assert_eq!(GhostNetworks::<T>::networks(chain_id.clone()), None);
assert!(GhostNetworks::<T>::network_indexes().is_empty());
} }
impl_benchmark_test_suite!(GhostNetworks, crate::mock::ExtBuilder::build(), crate::mock::Test); impl_benchmark_test_suite!(GhostNetworks, crate::mock::ExtBuilder::build(), crate::mock::Test);

View File

@ -12,7 +12,7 @@ use scale_info::TypeInfo;
use sp_runtime::{ use sp_runtime::{
curve::PiecewiseLinear, curve::PiecewiseLinear,
traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Member}, traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Member, UniqueSaturatedInto},
DispatchResult, DispatchResult,
}; };
use sp_std::{convert::TryInto, prelude::*}; use sp_std::{convert::TryInto, prelude::*};
@ -21,12 +21,10 @@ pub use ghost_traits::networks::{
NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler, NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler,
}; };
mod math;
pub mod migrations; pub mod migrations;
mod weights; mod weights;
pub use crate::weights::WeightInfo; pub use crate::weights::WeightInfo;
use math::MulDiv;
pub use module::*; pub use module::*;
#[cfg(any(feature = "runtime-benchmarks", test))] #[cfg(any(feature = "runtime-benchmarks", test))]
@ -99,23 +97,26 @@ where
_era_duration_in_millis: u64, _era_duration_in_millis: u64,
) -> (Balance, Balance) { ) -> (Balance, Balance) {
let reward_curve = RewardCurve::get(); let reward_curve = RewardCurve::get();
let bridged_imbalance = BridgedImbalance::<T>::get(); let bridged_imbalance = BridgedImbalance::<T>::take();
let accumulated_commission = AccumulatedCommission::<T>::get(); let accumulated_commission = AccumulatedCommission::<T>::take();
let accumulated_commission: Balance = accumulated_commission.into(); let accumulated_commission: Balance = accumulated_commission.into();
let adjusted_issuance: Balance = total_issuance let adjusted_issuance: Balance = total_issuance
.saturating_add(bridged_imbalance.bridged_out.into()) .saturating_add(bridged_imbalance.bridged_out.into())
.saturating_sub(bridged_imbalance.bridged_in.into()); .saturating_sub(bridged_imbalance.bridged_in.into());
NullifyNeeded::<T>::set(true);
let estimated_reward = let estimated_reward =
reward_curve.calculate_for_fraction_times_denominator(total_staked, adjusted_issuance); reward_curve.calculate_for_fraction_times_denominator(total_staked, adjusted_issuance);
let payout = MulDiv::<Balance>::calculate(
estimated_reward, let payout: Balance = sp_runtime::helpers_128bit::multiply_by_rational_with_rounding(
accumulated_commission, estimated_reward.unique_saturated_into(),
adjusted_issuance, accumulated_commission.unique_saturated_into(),
); adjusted_issuance.unique_saturated_into(),
sp_runtime::Rounding::NearestPrefUp,
)
.map(|result| result.unique_saturated_into())
.unwrap_or_default();
let rest_payout = accumulated_commission.saturating_sub(payout); let rest_payout = accumulated_commission.saturating_sub(payout);
(payout, rest_payout) (payout, rest_payout)
@ -240,10 +241,6 @@ pub mod module {
}, },
} }
#[pallet::storage]
#[pallet::getter(fn nullify_needed)]
pub type NullifyNeeded<T: Config> = StorageValue<_, bool, ValueQuery>;
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn bridged_imbalance)] #[pallet::getter(fn bridged_imbalance)]
pub type BridgedImbalance<T: Config> = pub type BridgedImbalance<T: Config> =
@ -301,20 +298,6 @@ pub mod module {
#[pallet::without_storage_info] #[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>); pub struct Pallet<T>(PhantomData<T>);
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
T::DbWeight::get().reads_writes(1, 1)
}
fn on_finalize(_: BlockNumberFor<T>) {
if Self::nullify_needed() {
Self::nullify_commission();
NullifyNeeded::<T>::put(false);
}
}
}
#[pallet::call] #[pallet::call]
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
#[pallet::call_index(0)] #[pallet::call_index(0)]
@ -754,9 +737,7 @@ impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
Networks::<T>::get(n) Networks::<T>::get(n)
} }
fn next_network_for_block( fn network_for_block(block_number: impl Into<usize>) -> Option<(Self::NetworkId, NetworkData)> {
block_number: impl Into<usize>,
) -> Option<(Self::NetworkId, NetworkData)> {
let network_indexes = NetworkIndexes::<T>::get(); let network_indexes = NetworkIndexes::<T>::get();
block_number block_number
.into() .into()
@ -772,10 +753,6 @@ impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
fn iter() -> PrefixIterator<(Self::NetworkId, NetworkData)> { fn iter() -> PrefixIterator<(Self::NetworkId, NetworkData)> {
Networks::<T>::iter() Networks::<T>::iter()
} }
fn is_nullification_period() -> bool {
NullifyNeeded::<T>::get()
}
} }
impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> { impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> {
@ -862,18 +839,4 @@ impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T
} }
}) })
} }
fn nullify_commission() {
AccumulatedCommission::<T>::set(Default::default());
BridgedImbalance::<T>::set(Default::default());
}
fn trigger_nullification() {
if NullifyNeeded::<T>::get() {
Self::nullify_commission();
NullifyNeeded::<T>::put(false);
} else {
NullifyNeeded::<T>::put(true);
}
}
} }

View File

@ -1,134 +0,0 @@
use crate::AtLeast32BitUnsigned;
pub struct MulDiv<Balance>(core::marker::PhantomData<Balance>);
impl<Balance> MulDiv<Balance>
where
Balance: Copy
+ AtLeast32BitUnsigned
+ num_traits::ops::wrapping::WrappingAdd
+ num_traits::ops::overflowing::OverflowingAdd
+ sp_std::ops::AddAssign
+ sp_std::ops::Not<Output = Balance>
+ sp_std::ops::Shl<Output = Balance>
+ sp_std::ops::Shr<Output = Balance>
+ sp_std::ops::BitAnd<Balance, Output = Balance>,
{
fn zero(&self) -> Balance {
0u32.into()
}
fn one(&self) -> Balance {
1u32.into()
}
fn bit_shift(&self) -> Balance {
let u32_shift: u32 = core::mem::size_of::<Balance>()
.saturating_mul(4)
.try_into()
.unwrap_or_default();
u32_shift.into()
}
fn least_significant_bits(&self, a: Balance) -> Balance {
a & ((self.one() << self.bit_shift()) - self.one())
}
fn most_significant_bits(&self, a: Balance) -> Balance {
a >> self.bit_shift()
}
fn two_complement(&self, a: Balance) -> Balance {
(!a).wrapping_add(&self.one())
}
fn adjusted_ratio(&self, a: Balance) -> Balance {
(self.two_complement(a) / a).wrapping_add(&self.one())
}
fn modulo(&self, a: Balance) -> Balance {
self.two_complement(a) % a
}
fn overflow_resistant_addition(
&self,
a0: Balance,
a1: Balance,
b0: Balance,
b1: Balance,
) -> (Balance, Balance) {
let (r0, overflow) = a0.overflowing_add(&b0);
let overflow: Balance = overflow.then(|| 1u32).unwrap_or_default().into();
let r1 = a1.wrapping_add(&b1).wrapping_add(&overflow);
(r0, r1)
}
fn overflow_resistant_multiplication(&self, a: Balance, b: Balance) -> (Balance, Balance) {
let (a0, a1) = (
self.least_significant_bits(a),
self.most_significant_bits(a),
);
let (b0, b1) = (
self.least_significant_bits(b),
self.most_significant_bits(b),
);
let (x, y) = (a1 * b0, b1 * a0);
let (r0, r1) = (a0 * b0, a1 * b1);
let (r0, r1) = self.overflow_resistant_addition(
r0,
r1,
self.least_significant_bits(x) << self.bit_shift(),
self.most_significant_bits(x),
);
let (r0, r1) = self.overflow_resistant_addition(
r0,
r1,
self.least_significant_bits(y) << self.bit_shift(),
self.most_significant_bits(y),
);
(r0, r1)
}
fn overflow_resistant_division(
&self,
mut a0: Balance,
mut a1: Balance,
b: Balance,
) -> (Balance, Balance) {
if b == self.one() {
return (a0, a1);
}
let zero: Balance = 0u32.into();
let (q, r) = (self.adjusted_ratio(b), self.modulo(b));
let (mut x0, mut x1) = (zero, zero);
while a1 != zero {
let (t0, t1) = self.overflow_resistant_multiplication(a1, q);
let (new_x0, new_x1) = self.overflow_resistant_addition(x0, x1, t0, t1);
x0 = new_x0;
x1 = new_x1;
let (t0, t1) = self.overflow_resistant_multiplication(a1, r);
let (new_a0, new_a1) = self.overflow_resistant_addition(t0, t1, a0, zero);
a0 = new_a0;
a1 = new_a1;
}
self.overflow_resistant_addition(x0, x1, a0 / b, zero)
}
fn mul_div(&self, a: Balance, b: Balance, c: Balance) -> Balance {
let (t0, t1) = self.overflow_resistant_multiplication(a, b);
self.overflow_resistant_division(t0, t1, c).0
}
pub fn calculate(a: Balance, b: Balance, c: Balance) -> Balance {
let inner = MulDiv(core::marker::PhantomData);
if c == inner.zero() {
return c;
}
inner.mul_div(a, b, c)
}
}

View File

@ -1,10 +1,11 @@
use frame_support::{ use frame_support::{
traits::{Get, UncheckedOnRuntimeUpgrade}, migration::clear_storage_prefix,
traits::{Get, PalletInfoAccess, UncheckedOnRuntimeUpgrade},
weights::Weight, weights::Weight,
}; };
use sp_std::marker::PhantomData; use sp_std::marker::PhantomData;
use crate::{BoundedVec, Config, NetworkIndexes, Networks, Vec, LOG_TARGET}; use crate::{BoundedVec, Config, NetworkIndexes, Networks, Pallet, Vec, LOG_TARGET};
pub struct StoreNetworkIdsIntoBoundedVec<T>(PhantomData<T>); pub struct StoreNetworkIdsIntoBoundedVec<T>(PhantomData<T>);
impl<T: Config> UncheckedOnRuntimeUpgrade for StoreNetworkIdsIntoBoundedVec<T> { impl<T: Config> UncheckedOnRuntimeUpgrade for StoreNetworkIdsIntoBoundedVec<T> {
@ -16,11 +17,28 @@ impl<T: Config> UncheckedOnRuntimeUpgrade for StoreNetworkIdsIntoBoundedVec<T> {
weight = weight.saturating_add(T::DbWeight::get().reads(networks_count as u64)); weight = weight.saturating_add(T::DbWeight::get().reads(networks_count as u64));
let clear_results = clear_storage_prefix(
Pallet::<T>::name().as_bytes(),
"NullifyNeeded".as_bytes(),
&[],
Some(1),
None,
);
log::info!(
target: LOG_TARGET,
"⛓️ NullifyNeeded storage succesfully removed: Loops (reads): {}, Unique removed (writes): {}",
clear_results.loops,
clear_results.unique,
);
weight = weight.saturating_add(T::DbWeight::get().reads(clear_results.loops as u64));
weight = weight.saturating_add(T::DbWeight::get().writes(clear_results.unique as u64));
let writes = BoundedVec::<T::NetworkId, T::MaxNetworks>::try_from(network_ids) let writes = BoundedVec::<T::NetworkId, T::MaxNetworks>::try_from(network_ids)
.inspect_err(|err| { .inspect_err(|err| {
log::error!( log::error!(
target: LOG_TARGET, target: LOG_TARGET,
"⛓️ Network ids to bounded_vec migration failed: {:?}", "⛓️ Network ids to bounded_vec migration failed: {:?}",
err, err,
) )
}) })
@ -29,7 +47,7 @@ impl<T: Config> UncheckedOnRuntimeUpgrade for StoreNetworkIdsIntoBoundedVec<T> {
NetworkIndexes::<T>::put(bounded_networks); NetworkIndexes::<T>::put(bounded_networks);
log::info!( log::info!(
target: LOG_TARGET, target: LOG_TARGET,
"⛓️ Network ids to bounded_vec migration success: {} networks moved", "⛓️ Network ids to bounded_vec migration success: {} networks moved",
networks_count, networks_count,
); );
1u64 1u64

View File

@ -82,6 +82,7 @@ pallet_staking_reward_curve::build! {
parameter_types! { parameter_types! {
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub const MaxNetworks: u32 = 3;
} }
ord_parameter_types! { ord_parameter_types! {
@ -98,7 +99,7 @@ impl ghost_networks::Config for Test {
type RegisterOrigin = EnsureSignedBy<RegistererAccount, AccountId>; type RegisterOrigin = EnsureSignedBy<RegistererAccount, AccountId>;
type UpdateOrigin = EnsureSignedBy<UpdaterAccount, AccountId>; type UpdateOrigin = EnsureSignedBy<UpdaterAccount, AccountId>;
type RemoveOrigin = EnsureSignedBy<RemoverAccount, AccountId>; type RemoveOrigin = EnsureSignedBy<RemoverAccount, AccountId>;
type MaxNetworks = ConstU32<3>; type MaxNetworks = MaxNetworks;
type WeightInfo = (); type WeightInfo = ();
} }

View File

@ -1,8 +1,8 @@
use super::*; use super::*;
use frame_support::{assert_err, assert_ok}; use frame_support::{assert_err, assert_ok};
use mock::{ use mock::{
ExtBuilder, GhostNetworks, RandomAccount, RegistererAccount, RemoverAccount, RewardCurve, ExtBuilder, GhostNetworks, MaxNetworks, RandomAccount, RegistererAccount, RemoverAccount,
RuntimeEvent, RuntimeOrigin, System, Test, UpdaterAccount, RewardCurve, RuntimeEvent, RuntimeOrigin, System, Test, UpdaterAccount,
}; };
use pallet_staking::EraPayout; use pallet_staking::EraPayout;
use sp_runtime::DispatchError; use sp_runtime::DispatchError;
@ -1278,7 +1278,7 @@ fn bridged_amount_overflow_and_underflow_emits_error() {
} }
#[test] #[test]
fn accumulated_commission_could_be_nullified() { fn accumulated_commission_nullified_after_era_payout() {
ExtBuilder::build().execute_with(|| { ExtBuilder::build().execute_with(|| {
let commission_first: u128 = 420; let commission_first: u128 = 420;
let commission_second: u128 = 69; let commission_second: u128 = 69;
@ -1292,12 +1292,20 @@ fn accumulated_commission_could_be_nullified() {
GhostNetworks::accumulate_commission(&commission_second,), GhostNetworks::accumulate_commission(&commission_second,),
commission_first + commission_second commission_first + commission_second
); );
assert_eq!( assert_eq!(
AccumulatedCommission::<Test>::get(), AccumulatedCommission::<Test>::get(),
commission_first + commission_second commission_first + commission_second
); );
GhostNetworks::nullify_commission(); assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout(1, 1, 1),
(0, commission_first + commission_second)
);
assert_eq!(AccumulatedCommission::<Test>::get(), 0); assert_eq!(AccumulatedCommission::<Test>::get(), 0);
assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout(1, 1, 1),
(0, 0)
);
}); });
} }
@ -1385,7 +1393,6 @@ fn bridged_inlation_reward_works() {
assert_ok!(GhostNetworks::accumulate_commission(&commission)); assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount)); assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
total_staked_ideal * 1_000, total_staked_ideal * 1_000,
@ -1394,6 +1401,9 @@ fn bridged_inlation_reward_works() {
), ),
(commission, 0) (commission, 0)
); );
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
total_staked_ideal * 1_000_000_000_000, total_staked_ideal * 1_000_000_000_000,
@ -1402,6 +1412,9 @@ fn bridged_inlation_reward_works() {
), ),
(commission, 0) (commission, 0)
); );
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
total_staked_ideal * 1_000_000_000_000_000_000_000_000, total_staked_ideal * 1_000_000_000_000_000_000_000_000,
@ -1411,6 +1424,8 @@ fn bridged_inlation_reward_works() {
(commission, 0) (commission, 0)
); );
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
total_staked_not_ideal * 1_000, total_staked_not_ideal * 1_000,
@ -1419,6 +1434,9 @@ fn bridged_inlation_reward_works() {
), ),
(13177472000, 192528000) (13177472000, 192528000)
); );
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!(13177472000 + 192528000, commission); assert_eq!(13177472000 + 192528000, commission);
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
@ -1428,6 +1446,9 @@ fn bridged_inlation_reward_works() {
), ),
(13177568884, 192431116) (13177568884, 192431116)
); );
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!(13177568884 + 192431116, commission); assert_eq!(13177568884 + 192431116, commission);
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
@ -1437,8 +1458,10 @@ fn bridged_inlation_reward_works() {
), ),
(13177568884, 192431116) (13177568884, 192431116)
); );
assert_eq!(13177568884 + 192431116, commission);
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!(13177568884 + 192431116, commission);
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
1, 1,
@ -1447,6 +1470,9 @@ fn bridged_inlation_reward_works() {
), ),
(92386700, 13277613300) (92386700, 13277613300)
); );
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!(92386700 + 13277613300, commission); assert_eq!(92386700 + 13277613300, commission);
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
@ -1456,6 +1482,9 @@ fn bridged_inlation_reward_works() {
), ),
(92253000, 13277747000) (92253000, 13277747000)
); );
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!(92253000 + 13277747000, commission); assert_eq!(92253000 + 13277747000, commission);
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
@ -1465,10 +1494,8 @@ fn bridged_inlation_reward_works() {
), ),
(92253000, 13277747000) (92253000, 13277747000)
); );
assert_eq!(92253000 + 13277747000, commission); assert_eq!(92253000 + 13277747000, commission);
GhostNetworks::nullify_commission();
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
total_staked_ideal * 1_000, total_staked_ideal * 1_000,
@ -1547,7 +1574,7 @@ fn bridged_inlation_reward_works() {
} }
#[test] #[test]
fn bridged_inflation_era_payout_triggers_need_of_nullification() { fn bridged_inflation_era_payout_clears_storage() {
ExtBuilder::build().execute_with(|| { ExtBuilder::build().execute_with(|| {
let amount_full: u128 = 1337 * 1_000_000_000; let amount_full: u128 = 1337 * 1_000_000_000;
let commission: u128 = amount_full / 100; // 1% commission let commission: u128 = amount_full / 100; // 1% commission
@ -1558,7 +1585,13 @@ fn bridged_inflation_era_payout_triggers_need_of_nullification() {
assert_ok!(GhostNetworks::accumulate_commission(&commission)); assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount)); assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
assert_eq!(NullifyNeeded::<Test>::get(), false);
let bridged_adjustment = BridgeAdjustment {
bridged_in: amount,
bridged_out: 0,
};
assert_eq!(BridgedImbalance::<Test>::get(), bridged_adjustment);
assert_eq!(AccumulatedCommission::<Test>::get(), commission);
assert_eq!( assert_eq!(
BridgedInflationCurve::<RewardCurve, Test>::era_payout( BridgedInflationCurve::<RewardCurve, Test>::era_payout(
total_staked_ideal * 1_000, total_staked_ideal * 1_000,
@ -1567,37 +1600,8 @@ fn bridged_inflation_era_payout_triggers_need_of_nullification() {
), ),
(commission, 0) (commission, 0)
); );
assert_eq!(NullifyNeeded::<Test>::get(), true); assert_eq!(BridgedImbalance::<Test>::get(), Default::default());
GhostNetworks::on_finalize(69); assert_eq!(AccumulatedCommission::<Test>::get(), Default::default());
assert_eq!(NullifyNeeded::<Test>::get(), false);
});
}
#[test]
fn trigger_nullification_works_as_expected() {
ExtBuilder::build().execute_with(|| {
let commission: u128 = 69;
let imbalance = BridgeAdjustment {
bridged_in: 1337u128,
bridged_out: 420u128,
};
assert_eq!(AccumulatedCommission::<Test>::get(), 0);
assert_eq!(BridgedImbalance::<Test>::get(), BridgeAdjustment::default());
AccumulatedCommission::<Test>::set(commission);
BridgedImbalance::<Test>::set(imbalance.clone());
assert_eq!(AccumulatedCommission::<Test>::get(), commission);
assert_eq!(BridgedImbalance::<Test>::get(), imbalance);
assert_eq!(NullifyNeeded::<Test>::get(), false);
GhostNetworks::trigger_nullification();
assert_eq!(NullifyNeeded::<Test>::get(), true);
GhostNetworks::trigger_nullification();
assert_eq!(NullifyNeeded::<Test>::get(), false);
assert_eq!(AccumulatedCommission::<Test>::get(), 0);
assert_eq!(BridgedImbalance::<Test>::get(), BridgeAdjustment::default());
}); });
} }
@ -1673,123 +1677,6 @@ fn check_substrate_guarantees_not_to_overflow_u32() {
}); });
} }
#[test]
fn check_muldiv_guarantees_not_to_overflow_for_u128() {
ExtBuilder::build().execute_with(|| {
let mut a: u128 = 2;
let mut b: u128 = 3;
let mut c: u128 = 6;
let mut result: u128 = 1;
loop {
a = match a.checked_mul(1_000) {
Some(value) => value,
None => break,
};
b = match b.checked_mul(1_000) {
Some(value) => value,
None => break,
};
c = match c.checked_mul(1_000) {
Some(value) => value,
None => break,
};
result = match result.checked_mul(1_000) {
Some(value) => value,
None => break,
};
assert_eq!(MulDiv::<u128>::calculate(a, b, c), result);
}
assert_eq!(
MulDiv::<u128>::calculate(u128::MAX, u128::MAX, u128::MAX),
u128::MAX
);
assert_eq!(MulDiv::<u128>::calculate(u128::MAX, 0, 0), 0);
assert_eq!(MulDiv::<u128>::calculate(0, u128::MAX, 0), 0);
assert_eq!(MulDiv::<u128>::calculate(0, 0, u128::MAX), 0);
});
}
#[test]
fn check_muldiv_guarantees_not_to_overflow_for_u64() {
ExtBuilder::build().execute_with(|| {
let mut a: u64 = 2;
let mut b: u64 = 3;
let mut c: u64 = 6;
let mut result: u64 = 1;
loop {
a = match a.checked_mul(1_000) {
Some(value) => value,
None => break,
};
b = match b.checked_mul(1_000) {
Some(value) => value,
None => break,
};
c = match c.checked_mul(1_000) {
Some(value) => value,
None => break,
};
result = match result.checked_mul(1_000) {
Some(value) => value,
None => break,
};
assert_eq!(MulDiv::<u64>::calculate(a, b, c), result);
}
assert_eq!(
MulDiv::<u64>::calculate(u64::MAX, u64::MAX, u64::MAX),
u64::MAX
);
assert_eq!(MulDiv::<u64>::calculate(u64::MAX, 0, 0), 0);
assert_eq!(MulDiv::<u64>::calculate(0, u64::MAX, 0), 0);
assert_eq!(MulDiv::<u64>::calculate(0, 0, u64::MAX), 0);
});
}
#[test]
fn check_muldiv_guarantees_not_to_overflow_for_u32() {
ExtBuilder::build().execute_with(|| {
let mut a: u32 = 2;
let mut b: u32 = 3;
let mut c: u32 = 6;
let mut result: u32 = 1;
loop {
a = match a.checked_mul(1_000) {
Some(value) => value,
None => break,
};
b = match b.checked_mul(1_000) {
Some(value) => value,
None => break,
};
c = match c.checked_mul(1_000) {
Some(value) => value,
None => break,
};
result = match result.checked_mul(1_000) {
Some(value) => value,
None => break,
};
assert_eq!(MulDiv::<u32>::calculate(a, b, c), result);
}
assert_eq!(
MulDiv::<u32>::calculate(u32::MAX, u32::MAX, u32::MAX),
u32::MAX
);
assert_eq!(MulDiv::<u32>::calculate(u32::MAX, 0, 0), 0);
assert_eq!(MulDiv::<u32>::calculate(0, u32::MAX, 0), 0);
assert_eq!(MulDiv::<u32>::calculate(0, 0, u32::MAX), 0);
});
}
#[test] #[test]
fn check_bridged_inflation_curve_for_overflow() { fn check_bridged_inflation_curve_for_overflow() {
ExtBuilder::build().execute_with(|| { ExtBuilder::build().execute_with(|| {
@ -1797,7 +1684,6 @@ fn check_bridged_inflation_curve_for_overflow() {
let commission: u128 = amount_full / 100; // 1% commission let commission: u128 = amount_full / 100; // 1% commission
let amount: u128 = amount_full - commission; let amount: u128 = amount_full - commission;
let tollerance: u128 = commission / 100; // 1% tollerance
let precomputed_payout: u128 = 13177568884; let precomputed_payout: u128 = 13177568884;
let precomputed_rest: u128 = 192431116; let precomputed_rest: u128 = 192431116;
assert_eq!(precomputed_payout + precomputed_rest, commission); assert_eq!(precomputed_payout + precomputed_rest, commission);
@ -1806,10 +1692,10 @@ fn check_bridged_inflation_curve_for_overflow() {
let mut total_staked_not_ideal: u128 = 68_000; let mut total_staked_not_ideal: u128 = 68_000;
let mut total_issuance: u128 = 100_000; let mut total_issuance: u128 = 100_000;
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
loop { loop {
assert_ok!(GhostNetworks::accumulate_commission(&commission));
assert_ok!(GhostNetworks::accumulate_incoming_imbalance(&amount));
total_staked_ideal = match total_staked_ideal.checked_mul(1_000) { total_staked_ideal = match total_staked_ideal.checked_mul(1_000) {
Some(value) => value, Some(value) => value,
None => break, None => break,
@ -1830,27 +1716,6 @@ fn check_bridged_inflation_curve_for_overflow() {
), ),
(commission, 0) (commission, 0)
); );
let (payout, rest) = BridgedInflationCurve::<RewardCurve, Test>::era_payout(
total_staked_not_ideal,
total_issuance + amount,
0,
);
let payout_deviation = if precomputed_payout > payout {
precomputed_payout - payout
} else {
payout - precomputed_payout
};
let rest_deviation = if precomputed_rest > rest {
precomputed_rest - rest
} else {
rest - precomputed_rest
};
assert!(payout_deviation < tollerance);
assert!(rest_deviation < tollerance);
} }
}); });
} }
@ -1913,6 +1778,17 @@ fn migration_from_v0_to_v1_works() {
let (chain_id, network) = prepare_network_data(); let (chain_id, network) = prepare_network_data();
let other_chain_id = chain_id.saturating_add(1); let other_chain_id = chain_id.saturating_add(1);
let removed_pallet_name = <Pallet<Test> as PalletInfoAccess>::name();
let removed_storage = "NullifyNeeded";
let key = [
sp_io::hashing::twox_128(removed_pallet_name.as_bytes()).to_vec(),
sp_io::hashing::twox_128(removed_storage.as_bytes()).to_vec(),
]
.concat();
frame_support::storage::unhashed::put_raw(&key, &true.encode());
assert_eq!(Networks::<Test>::get(chain_id), None); assert_eq!(Networks::<Test>::get(chain_id), None);
assert_ok!(GhostNetworks::register_network( assert_ok!(GhostNetworks::register_network(
RuntimeOrigin::signed(RegistererAccount::get()), RuntimeOrigin::signed(RegistererAccount::get()),
@ -1926,12 +1802,15 @@ fn migration_from_v0_to_v1_works() {
)); ));
NetworkIndexes::<Test>::kill(); NetworkIndexes::<Test>::kill();
assert!(frame_support::storage::unhashed::exists(&key));
assert_eq!(Networks::<Test>::get(chain_id), Some(network.clone())); assert_eq!(Networks::<Test>::get(chain_id), Some(network.clone()));
assert_eq!(Networks::<Test>::get(other_chain_id), Some(network)); assert_eq!(Networks::<Test>::get(other_chain_id), Some(network));
assert_eq!(NetworkIndexes::<Test>::get(), vec![]); assert_eq!(NetworkIndexes::<Test>::get(), vec![]);
type Migrate = crate::migrations::MigrateV0ToV1<Test>; type Migrate = crate::migrations::MigrateV0ToV1<Test>;
<Migrate as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); <Migrate as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade();
assert!(!frame_support::storage::unhashed::exists(&key));
assert_eq!( assert_eq!(
NetworkIndexes::<Test>::get(), NetworkIndexes::<Test>::get(),
vec![chain_id, other_chain_id] vec![chain_id, other_chain_id]
@ -1939,6 +1818,32 @@ fn migration_from_v0_to_v1_works() {
}); });
} }
#[test]
fn error_on_max_networks_overflow() {
ExtBuilder::build().execute_with(|| {
let (chain_id, network) = prepare_network_data();
let max_networks = MaxNetworks::get();
for index in 0..max_networks {
let other_chain_id = chain_id.saturating_add(index);
assert_ok!(GhostNetworks::register_network(
RuntimeOrigin::signed(RegistererAccount::get()),
other_chain_id,
network.clone(),
));
}
assert_err!(
GhostNetworks::register_network(
RuntimeOrigin::signed(RegistererAccount::get()),
chain_id.saturating_add(max_networks),
network.clone(),
),
crate::Error::<Test>::TooManyNetworks,
);
});
}
#[test] #[test]
fn migration_from_v0_to_v1_does_not_run_twice() { fn migration_from_v0_to_v1_does_not_run_twice() {
ExtBuilder::build().execute_with(|| { ExtBuilder::build().execute_with(|| {

View File

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

@ -28,7 +28,7 @@ use sp_runtime::{
storage::StorageValueRef, storage::StorageValueRef,
storage_lock::{StorageLock, Time}, storage_lock::{StorageLock, Time},
}, },
traits::{BlockNumberProvider, Convert, Saturating}, traits::{BlockNumberProvider, Convert, Saturating, UniqueSaturatedInto},
Perbill, RuntimeAppPublic, RuntimeDebug, Perbill, RuntimeAppPublic, RuntimeDebug,
}; };
use sp_staking::{ use sp_staking::{
@ -80,6 +80,7 @@ const LOCK_BLOCK_EXPIRATION: u64 = 20;
const COMMITMENT_DELAY_MILLIS: u64 = 600_000; const COMMITMENT_DELAY_MILLIS: u64 = 600_000;
const ONE_HOUR_MILLIS: u64 = 3_600_000; const ONE_HOUR_MILLIS: u64 = 3_600_000;
const CHECK_BLOCK_INTERVAL: u64 = 300;
pub type AuthIndex = u32; pub type AuthIndex = u32;
@ -347,6 +348,10 @@ pub mod pallet {
authority_id: AuthIndex, authority_id: AuthIndex,
network_id: NetworkIdOf<T>, network_id: NetworkIdOf<T>,
}, },
BlockCommitmentsCheck {
network_id: NetworkIdOf<T>,
block_number: BlockNumberFor<T>,
},
} }
#[pallet::error] #[pallet::error]
@ -470,6 +475,68 @@ pub mod pallet {
#[pallet::hooks] #[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(current_block: BlockNumberFor<T>) -> Weight {
// TODO: what about start of the session???
let mut weight = T::DbWeight::get().reads(1);
let networks_count = T::NetworkDataHandler::count();
let current_block_number: u64 = current_block.unique_saturated_into();
//let cycle_start = (current_block_number / CHECK_BLOCK_INTERVAL) * CHECK_BLOCK_INTERVAL;
let cycle_offset = current_block_number % CHECK_BLOCK_INTERVAL;
if cycle_offset >= networks_count.into() {
return Default::default();
}
let converted_block: usize = current_block_number.unique_saturated_into();
if let Some((network_id, network_data)) =
T::NetworkDataHandler::network_for_block(converted_block)
{
weight = weight.saturating_add(T::DbWeight::get().reads_writes(3, 1));
let session_index = T::ValidatorSet::session_index();
let block_commitments = BlockCommitments::<T>::get(&network_id);
let validators = Validators::<T>::get(&session_index);
if validators.len() == 0 {
return weight;
}
let disabled_bitmap = DisabledAuthorityIndexes::<T>::get(&session_index)
.unwrap_or(BitMap::new(validators.len() as u32));
let max_block_deviation = ONE_HOUR_MILLIS
.saturating_mul(6) // TODO: make it constant or calculate
.saturating_div(network_data.avg_block_speed);
let offence_bitmap = Self::capture_deviation_in_commitments_and_remove(
&disabled_bitmap,
&block_commitments,
&validators,
max_block_deviation,
);
let offence_type = OffenceType::CommitmentOffence;
let extra_weight = Self::try_offend_validators(
&session_index,
&validators,
offence_bitmap,
disabled_bitmap,
offence_type,
);
Self::deposit_event(Event::<T>::BlockCommitmentsCheck {
block_number: current_block,
network_id,
});
weight = weight.saturating_add(extra_weight);
}
weight
}
fn offchain_worker(now: BlockNumberFor<T>) { fn offchain_worker(now: BlockNumberFor<T>) {
match Self::start_slow_clapping(now) { match Self::start_slow_clapping(now) {
Ok(_) => { Ok(_) => {
@ -695,10 +762,6 @@ impl<T: Config> Pallet<T> {
} }
fn try_applause(clap: &Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>) -> DispatchResult { fn try_applause(clap: &Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>) -> DispatchResult {
if T::NetworkDataHandler::is_nullification_period() {
return Ok(());
}
let commission = T::NetworkDataHandler::get(&clap.network_id) let commission = T::NetworkDataHandler::get(&clap.network_id)
.map(|network_data| Perbill::from_parts(network_data.incoming_fee)) .map(|network_data| Perbill::from_parts(network_data.incoming_fee))
.unwrap_or_default() .unwrap_or_default()
@ -740,73 +803,35 @@ impl<T: Config> Pallet<T> {
Error::<T>::CommitInWrongSession, Error::<T>::CommitInWrongSession,
); );
let block_commitments = BlockCommitments::<T>::try_mutate( BlockCommitments::<T>::try_mutate(&network_id, |current_commitments| -> DispatchResult {
&network_id, let mut new_commitment_details = new_commitment.commitment;
|current_commitments| -> Result<BTreeMap<AuthIndex, CommitmentDetails>, DispatchError> {
let mut new_commitment_details = new_commitment.commitment;
let (current_commits, current_last_updated) = current_commitments let (current_commits, current_last_updated) = current_commitments
.get(&authority_index) .get(&authority_index)
.map(|details| { .map(|details| {
( (
details.commits.saturating_add(1), details.commits.saturating_add(1),
details.last_updated.saturating_add(COMMITMENT_DELAY_MILLIS), details.last_updated.saturating_add(COMMITMENT_DELAY_MILLIS),
) )
}) })
.unwrap_or((1, 0)); .unwrap_or((1, 0));
ensure!( ensure!(
new_commitment_details.last_updated > current_last_updated, new_commitment_details.last_updated > current_last_updated,
Error::<T>::TimeWentBackwards Error::<T>::TimeWentBackwards
); );
new_commitment_details.commits = current_commits; new_commitment_details.commits = current_commits;
current_commitments.insert(authority_index, new_commitment_details); current_commitments.insert(authority_index, new_commitment_details);
Ok(current_commitments.clone()) Ok(())
}, })?;
)?;
let current_commits = block_commitments
.get(&authority_index)
.map(|details| details.commits)
.unwrap_or(1);
Self::deposit_event(Event::<T>::BlockCommited { Self::deposit_event(Event::<T>::BlockCommited {
network_id, network_id,
authority_id: authority_index, authority_id: authority_index,
}); });
let validators = Validators::<T>::get(&session_index);
let disabled_bitmap = DisabledAuthorityIndexes::<T>::get(&session_index)
.unwrap_or(BitMap::new(validators.len() as u32));
let max_block_deviation = T::NetworkDataHandler::get(&network_id)
.map(|network| {
ONE_HOUR_MILLIS
.saturating_mul(6)
.saturating_div(network.avg_block_speed)
})
.unwrap_or_default();
if current_commits % 3 == 0 && validators.len() > 0 {
let offence_bitmap = Self::capture_deviation_in_commitments_and_remove(
&disabled_bitmap,
&block_commitments,
&validators,
max_block_deviation,
);
let offence_type = OffenceType::CommitmentOffence;
Self::try_offend_validators(
&session_index,
&validators,
offence_bitmap,
disabled_bitmap,
offence_type,
);
}
Ok(()) Ok(())
} }
@ -1394,7 +1419,7 @@ impl<T: Config> Pallet<T> {
offence_bitmap: BitMap, offence_bitmap: BitMap,
disabled_bitmap: BitMap, disabled_bitmap: BitMap,
offence_type: OffenceType, offence_type: OffenceType,
) { ) -> Weight {
let validator_set_count = validators.len() as u32; let validator_set_count = validators.len() as u32;
let offenders = validators let offenders = validators
@ -1417,16 +1442,17 @@ impl<T: Config> Pallet<T> {
if not_enough_validators_left && offenders.len() > 0 { if not_enough_validators_left && offenders.len() > 0 {
Self::deposit_event(Event::<T>::BlackSwan); Self::deposit_event(Event::<T>::BlackSwan);
return; return T::DbWeight::get().writes(1);
} }
if offenders.len() == 0 { let offenders_len = offenders.len() as u32;
if offenders_len == 0 {
let equilibrium_event = match offence_type { let equilibrium_event = match offence_type {
OffenceType::CommitmentOffence => Event::<T>::AuthoritiesCommitmentEquilibrium, OffenceType::CommitmentOffence => Event::<T>::AuthoritiesCommitmentEquilibrium,
OffenceType::ThrottlingOffence(_) => Event::<T>::AuthoritiesApplauseEquilibrium, OffenceType::ThrottlingOffence(_) => Event::<T>::AuthoritiesApplauseEquilibrium,
}; };
Self::deposit_event(equilibrium_event); Self::deposit_event(equilibrium_event);
return; return T::DbWeight::get().writes(1);
} }
let offence_event = match offence_type { let offence_event = match offence_type {
@ -1460,6 +1486,8 @@ impl<T: Config> Pallet<T> {
if let Err(e) = T::ReportUnresponsiveness::report_offence(reporters, offence) { if let Err(e) = T::ReportUnresponsiveness::report_offence(reporters, offence) {
sp_runtime::print(e); sp_runtime::print(e);
} }
T::WeightInfo::try_offend_validators(offenders_len)
} }
} }

View File

@ -130,6 +130,7 @@ parameter_types! {
parameter_types! { parameter_types! {
pub static MockAverageSessionLength: Option<u64> = None; pub static MockAverageSessionLength: Option<u64> = None;
pub const MaxNetworks: u32 = 5;
} }
impl ghost_networks::Config for Runtime { impl ghost_networks::Config for Runtime {
@ -139,6 +140,7 @@ impl ghost_networks::Config for Runtime {
type RegisterOrigin = EnsureRoot<Self::AccountId>; type RegisterOrigin = EnsureRoot<Self::AccountId>;
type UpdateOrigin = EnsureRoot<Self::AccountId>; type UpdateOrigin = EnsureRoot<Self::AccountId>;
type RemoveOrigin = EnsureRoot<Self::AccountId>; type RemoveOrigin = EnsureRoot<Self::AccountId>;
type MaxNetworks = MaxNetworks;
type WeightInfo = (); type WeightInfo = ();
} }

View File

@ -1,6 +1,9 @@
#![cfg(test)] #![cfg(test)]
use std::time::{SystemTime, UNIX_EPOCH}; use std::{
collections::HashMap,
time::{SystemTime, UNIX_EPOCH},
};
use super::*; use super::*;
use crate::evm_types::Log; use crate::evm_types::Log;
@ -892,7 +895,6 @@ fn should_nullify_commission_on_finalize() {
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));
assert_eq!(Networks::accumulated_commission(), amount); assert_eq!(Networks::accumulated_commission(), amount);
assert_eq!(Networks::is_nullification_period(), false);
assert_applaused(&session_index, &unique_hash); assert_applaused(&session_index, &unique_hash);
assert_eq!( assert_eq!(
@ -903,63 +905,13 @@ fn should_nullify_commission_on_finalize() {
), ),
(amount, 0u64) (amount, 0u64)
); // precomputed values ); // precomputed values
assert_eq!(Networks::is_nullification_period(), true);
Networks::on_finalize(System::block_number());
assert_eq!(Networks::is_nullification_period(), false);
assert_eq!(Networks::accumulated_commission(), 0); assert_eq!(Networks::accumulated_commission(), 0);
assert_ok!(do_clap_from(session_index, network_id, 3, false)); assert_ok!(do_clap_from(session_index, network_id, 3, false));
assert_eq!(Networks::accumulated_commission(), 0); assert_eq!(Networks::accumulated_commission(), 0);
}); });
} }
#[test]
fn should_avoid_applause_during_nullification_period() {
let zero: u64 = 0u64;
let total_staked = 69_000_000;
let total_issuance = 100_000_000;
let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);
let (_, receiver, amount, _) = get_mocked_metadata();
new_test_ext().execute_with(|| {
let _ = prepare_evm_network(None, None);
let session_index = advance_session_and_get_index();
assert_eq!(Networks::is_nullification_period(), false);
assert_eq!(
BridgedInflationCurve::<RewardCurve, Runtime>::era_payout(
total_staked,
total_issuance,
zero
),
(zero, zero)
);
assert_eq!(Networks::is_nullification_period(), true);
assert_ok!(do_clap_from(session_index, network_id, 0, false));
assert_ok!(do_clap_from(session_index, network_id, 1, false));
assert_eq!(Balances::total_balance(&receiver), 0);
assert_clapped(&session_index, &unique_hash, 0, true);
assert_clapped(&session_index, &unique_hash, 1, true);
assert_clapped(&session_index, &unique_hash, 2, false);
assert_clapped(&session_index, &unique_hash, 3, false);
Networks::on_finalize(System::block_number());
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, 3, false));
assert_eq!(Balances::total_balance(&receiver), amount);
assert_applaused(&session_index, &unique_hash);
assert_clapped(&session_index, &unique_hash, 0, true);
assert_clapped(&session_index, &unique_hash, 1, true);
assert_clapped(&session_index, &unique_hash, 2, true);
assert_clapped(&session_index, &unique_hash, 3, true);
});
}
#[test] #[test]
fn should_avoid_session_overlap_on_mended_session_index() { fn should_avoid_session_overlap_on_mended_session_index() {
let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None); let (network_id, _, unique_hash) = generate_unique_hash(None, None, None, None, None);
@ -1194,6 +1146,7 @@ fn should_disable_on_commitment_inactivity() {
} }
} }
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap( System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::SomeAuthoritiesDelayed { crate::Event::SomeAuthoritiesDelayed {
delayed: vec![(3, 3)], delayed: vec![(3, 3)],
@ -1259,6 +1212,7 @@ fn should_disable_on_commitment_block_deviation() {
)); ));
} }
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap( System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::SomeAuthoritiesDelayed { crate::Event::SomeAuthoritiesDelayed {
delayed: vec![(3, 3)], delayed: vec![(3, 3)],
@ -1372,6 +1326,7 @@ fn should_not_offend_disabled_authorities() {
} }
} }
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap( System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::AuthoritiesCommitmentEquilibrium, crate::Event::AuthoritiesCommitmentEquilibrium,
)); ));
@ -1417,6 +1372,7 @@ fn should_not_slash_by_applause_if_disabled_by_commitment() {
} }
} }
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap( System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::AuthoritiesCommitmentEquilibrium, crate::Event::AuthoritiesCommitmentEquilibrium,
)); ));
@ -1501,6 +1457,7 @@ fn should_split_commit_slash_between_active_validators() {
} }
} }
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap( System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::SomeAuthoritiesDelayed { crate::Event::SomeAuthoritiesDelayed {
delayed: vec![(3, 3)], delayed: vec![(3, 3)],
@ -1508,7 +1465,7 @@ fn should_split_commit_slash_between_active_validators() {
)); ));
let offences = Offences::get(); let offences = Offences::get();
assert_eq!(offences.len(), 3); assert_eq!(offences.len(), 1);
for offence in offences { for offence in offences {
assert_eq!(offence.0, vec![0, 1, 2]); assert_eq!(offence.0, vec![0, 1, 2]);
assert_eq!(offence.1.session_index, session_index); assert_eq!(offence.1.session_index, session_index);
@ -1520,6 +1477,55 @@ fn should_split_commit_slash_between_active_validators() {
}); });
} }
#[test]
fn should_check_different_networks_during_on_initialize() {
let times = 69;
let networks_count = 3;
let mut check_commitment_events = HashMap::new();
new_test_ext().execute_with(|| {
let _ = advance_session_and_get_index();
for network_id in 0..networks_count {
prepare_evm_network(Some(network_id), None);
}
let check_interval = times * CHECK_BLOCK_INTERVAL;
for check_block in 0..check_interval {
SlowClap::on_initialize(check_block);
}
let binding = System::events();
let total_number_of_events = binding
.iter()
.filter(|x| {
x.event == RuntimeEvent::SlowClap(crate::Event::AuthoritiesCommitmentEquilibrium)
})
.count();
binding
.iter()
.map(|record| record.event.clone())
.for_each(|event| {
if let RuntimeEvent::SlowClap(crate::Event::BlockCommitmentsCheck {
network_id,
..
}) = event
{
*check_commitment_events.entry(network_id).or_insert(0) += 1;
}
});
let expected_events = networks_count * (times as u32);
assert_eq!(total_number_of_events, expected_events as usize);
assert_eq!(check_commitment_events.len() as u32, networks_count);
for network_id in 0..networks_count {
let network_id_count = check_commitment_events.get(&network_id).unwrap();
assert_eq!(*network_id_count, times);
}
});
}
#[test] #[test]
fn should_check_responses_correctly() { fn should_check_responses_correctly() {
new_test_ext().execute_with(|| { new_test_ext().execute_with(|| {
@ -1884,6 +1890,7 @@ fn evm_block_response(state: &mut testing::OffchainState) {
headers: vec![ headers: vec![
("Accept".to_string(), "application/json".to_string()), ("Accept".to_string(), "application/json".to_string()),
("Content-Type".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()),
("User-Agent".to_string(), "curl/8.9.0".to_string()),
], ],
response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()), response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()),
body: expected_body.clone(), body: expected_body.clone(),
@ -1897,6 +1904,7 @@ fn evm_block_response(state: &mut testing::OffchainState) {
headers: vec![ headers: vec![
("Accept".to_string(), "application/json".to_string()), ("Accept".to_string(), "application/json".to_string()),
("Content-Type".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()),
("User-Agent".to_string(), "curl/8.9.0".to_string()),
], ],
response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()), response: Some(b"{\"id\":0,\"jsonrpc\":\"2.0\",\"result\":\"0x1364c81\"}".to_vec()),
body: expected_body, body: expected_body,
@ -1951,6 +1959,7 @@ fn evm_logs_response(state: &mut testing::OffchainState) {
headers: vec![ headers: vec![
("Accept".to_string(), "application/json".to_string()), ("Accept".to_string(), "application/json".to_string()),
("Content-Type".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()),
("User-Agent".to_string(), "curl/8.9.0".to_string()),
], ],
response_headers: vec![ response_headers: vec![
("Accept".to_string(), "application/json".to_string()), ("Accept".to_string(), "application/json".to_string()),
@ -1968,6 +1977,7 @@ fn evm_logs_response(state: &mut testing::OffchainState) {
headers: vec![ headers: vec![
("Accept".to_string(), "application/json".to_string()), ("Accept".to_string(), "application/json".to_string()),
("Content-Type".to_string(), "application/json".to_string()), ("Content-Type".to_string(), "application/json".to_string()),
("User-Agent".to_string(), "curl/8.9.0".to_string()),
], ],
response_headers: vec![ response_headers: vec![
("Accept".to_string(), "application/json".to_string()), ("Accept".to_string(), "application/json".to_string()),

View File

@ -49,6 +49,7 @@ use core::marker::PhantomData;
pub trait WeightInfo { pub trait WeightInfo {
fn slow_clap() -> Weight; fn slow_clap() -> Weight;
fn commit_block()-> Weight; fn commit_block()-> Weight;
fn try_offend_validators(offenders_len: u32) -> Weight;
} }
impl WeightInfo for () { impl WeightInfo for () {
@ -86,4 +87,8 @@ impl WeightInfo for () {
fn commit_block()-> Weight { fn commit_block()-> Weight {
Default::default() Default::default()
} }
fn try_offend_validators(offenders_len: u32) -> Weight {
Default::default()
}
} }

View File

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

View File

@ -17,10 +17,9 @@ pub trait NetworkDataBasicHandler {
pub trait NetworkDataInspectHandler<Network>: NetworkDataBasicHandler { pub trait NetworkDataInspectHandler<Network>: NetworkDataBasicHandler {
fn count() -> u32; fn count() -> u32;
fn next_network_for_block(b: impl Into<usize>) -> Option<(Self::NetworkId, Network)>; fn network_for_block(b: impl Into<usize>) -> Option<(Self::NetworkId, Network)>;
fn get(n: &Self::NetworkId) -> Option<Network>; fn get(n: &Self::NetworkId) -> Option<Network>;
fn iter() -> PrefixIterator<(Self::NetworkId, Network)>; fn iter() -> PrefixIterator<(Self::NetworkId, Network)>;
fn is_nullification_period() -> bool;
} }
pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<Network> { pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<Network> {
@ -38,8 +37,5 @@ pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<
fn accumulate_outgoing_imbalance(amount: &Balance) -> Result<Balance, ()>; fn accumulate_outgoing_imbalance(amount: &Balance) -> Result<Balance, ()>;
fn accumulate_incoming_imbalance(amount: &Balance) -> Result<Balance, ()>; fn accumulate_incoming_imbalance(amount: &Balance) -> Result<Balance, ()>;
fn accumulate_commission(commission: &Balance) -> Result<Balance, ()>; fn accumulate_commission(commission: &Balance) -> Result<Balance, ()>;
fn nullify_commission();
fn trigger_nullification();
} }