Compare commits

..

No commits in common. "028afc089f40470ea5316bdd4b1e93c5b1d80ba3" and "5dd0c73f7a56a54b82cc69206a6e364a25b10ee0" have entirely different histories.

10 changed files with 390 additions and 392 deletions

View File

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

View File

@ -730,13 +730,7 @@ impl<T: Config> NetworkDataBasicHandler for Pallet<T> {
impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> { impl<T: Config> NetworkDataInspectHandler<NetworkData> for Pallet<T> {
fn count() -> u32 { fn count() -> u32 {
NetworkIndexes::<T>::decode_len() NetworkIndexes::<T>::get().len() as u32
.map(|len| len as u32)
.unwrap_or_default()
}
fn contains_key(n: &Self::NetworkId) -> bool {
Networks::<T>::contains_key(n)
} }
fn get(n: &Self::NetworkId) -> Option<NetworkData> { fn get(n: &Self::NetworkId) -> Option<NetworkData> {
@ -759,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 iter_indexes() -> impl Iterator<Item = Self::NetworkId> {
NetworkIndexes::<T>::get().into_iter()
}
} }
impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> { impl<T: Config> NetworkDataMutateHandler<NetworkData, BalanceOf<T>> for Pallet<T> {

View File

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

@ -114,36 +114,6 @@ benchmarks! {
assert_eq!(stored_commitment.last_updated, 1337); assert_eq!(stored_commitment.last_updated, 1337);
} }
try_offend_validators {
let n in 4 .. T::MaxAuthorities::get();
let session_index = T::ValidatorSet::session_index();
let mut validators_vec = Vec::new();
for i in 0..T::MaxAuthorities::get() {
let v = account("validator", i, 0);
validators_vec.push(v);
}
let validators = WeakBoundedVec::<ValidatorId<T>, T::MaxAuthorities>::try_from(validators_vec)
.expect("weak bounded vec should be constructed; qed");
let offence_type = OffenceType::CommitmentOffence;
let disabled_bitmap = BitMap::new(T::MaxAuthorities::get());
let mut offence_bitmap = BitMap::new(T::MaxAuthorities::get());
for i in 0 .. (n / 3) {
offence_bitmap.set(i);
}
}: {
let _ = Pallet::<T>::try_offend_validators(
&session_index,
&validators,
offence_bitmap,
disabled_bitmap,
offence_type
);
}
impl_benchmark_test_suite!( impl_benchmark_test_suite!(
Pallet, Pallet,
crate::mock::new_test_ext(), crate::mock::new_test_ext(),

View File

@ -1,4 +1,4 @@
use sp_runtime::{traits::UniqueSaturatedInto, SaturatedConversion, Saturating}; use sp_runtime::SaturatedConversion;
use sp_staking::SessionIndex; use sp_staking::SessionIndex;
use crate::{ use crate::{
@ -6,9 +6,9 @@ use crate::{
de_string_to_bytes, de_string_to_h256, de_string_to_u64, de_string_to_u64_pure, de_string_to_bytes, de_string_to_h256, de_string_to_u64, de_string_to_u64_pure,
de_string_to_vec_of_bytes, de_string_to_vec_of_bytes,
}, },
AuthIndex, BalanceOf, BlockCommitment, BlockCommitments, BlockNumberFor, Call, Clap, Config, AuthIndex, BalanceOf, BlockCommitment, BlockCommitments, Call, Clap, CommitmentDetails, Config,
Decode, Deserialize, Encode, NetworkIdOf, RuntimeAppPublic, RuntimeDebug, SubmitTransaction, Decode, Deserialize, Encode, NetworkIdOf, RuntimeAppPublic, RuntimeDebug, SubmitTransaction,
Vec, BLOCK_COMMITMENT_DELAY, H256, LOG_TARGET, Vec, COMMITMENT_DELAY_MILLIS, H256, LOG_TARGET,
}; };
const NUMBER_OF_TOPICS: usize = 3; const NUMBER_OF_TOPICS: usize = 3;
@ -46,6 +46,26 @@ pub struct Log {
} }
impl EvmResponseType { impl EvmResponseType {
fn prepare_block_commitment<T: Config>(
&self,
from_block: u64,
authority_index: AuthIndex,
session_index: SessionIndex,
network_id: NetworkIdOf<T>,
) -> BlockCommitment<NetworkIdOf<T>> {
let last_updated = sp_io::offchain::timestamp().unix_millis();
BlockCommitment {
session_index,
authority_index,
network_id,
commitment: CommitmentDetails {
last_stored_block: from_block,
last_updated,
commits: 420,
},
}
}
fn prepare_clap<T: Config>( fn prepare_clap<T: Config>(
&self, &self,
authority_index: AuthIndex, authority_index: AuthIndex,
@ -144,30 +164,30 @@ impl EvmResponseType {
fn sign_and_submit_block_commitment<T: Config>( fn sign_and_submit_block_commitment<T: Config>(
&self, &self,
block_now: BlockNumberFor<T>,
from_block: u64, from_block: u64,
authority_index: AuthIndex, authority_index: AuthIndex,
authority_key: T::AuthorityId, authority_key: T::AuthorityId,
session_index: SessionIndex, session_index: SessionIndex,
network_id: NetworkIdOf<T>, network_id: NetworkIdOf<T>,
) { ) {
let block_commitment = BlockCommitment { let block_commitment = self.prepare_block_commitment::<T>(
session_index, from_block,
authority_index, authority_index,
session_index,
network_id, network_id,
last_stored_block: from_block, );
};
let stored_last_updated = BlockCommitments::<T>::get(&network_id) let stored_last_updated = BlockCommitments::<T>::get(&network_id)
.get(&authority_index) .get(&authority_index)
.map(|details| { .map(|details| details.last_updated)
details
.last_updated
.saturating_add(BLOCK_COMMITMENT_DELAY.unique_saturated_into())
})
.unwrap_or_default(); .unwrap_or_default();
if block_now < stored_last_updated { let current_last_updated = block_commitment
.commitment
.last_updated
.saturating_sub(COMMITMENT_DELAY_MILLIS);
if current_last_updated < stored_last_updated {
return; return;
} }
@ -209,7 +229,6 @@ impl EvmResponseType {
pub fn sign_and_submit<T: Config>( pub fn sign_and_submit<T: Config>(
&self, &self,
block_now: BlockNumberFor<T>,
from_block: u64, from_block: u64,
authority_index: AuthIndex, authority_index: AuthIndex,
authority_key: T::AuthorityId, authority_key: T::AuthorityId,
@ -224,7 +243,6 @@ impl EvmResponseType {
network_id, network_id,
), ),
EvmResponseType::BlockNumber(_) => self.sign_and_submit_block_commitment::<T>( EvmResponseType::BlockNumber(_) => self.sign_and_submit_block_commitment::<T>(
block_now,
from_block, from_block,
authority_index, authority_index,
authority_key, authority_key,

View File

@ -28,7 +28,7 @@ use sp_runtime::{
storage::StorageValueRef, storage::StorageValueRef,
storage_lock::{StorageLock, Time}, storage_lock::{StorageLock, Time},
}, },
traits::{AtLeast32BitUnsigned, BlockNumberProvider, Convert, Saturating, UniqueSaturatedInto}, traits::{BlockNumberProvider, Convert, Saturating, UniqueSaturatedInto},
Perbill, RuntimeAppPublic, RuntimeDebug, Perbill, RuntimeAppPublic, RuntimeDebug,
}; };
use sp_staking::{ use sp_staking::{
@ -78,13 +78,11 @@ const MIN_LOCK_GUARD_PERIOD: u64 = 15_000;
const FETCH_TIMEOUT_PERIOD: u64 = 3_000; const FETCH_TIMEOUT_PERIOD: u64 = 3_000;
const LOCK_BLOCK_EXPIRATION: u64 = 20; const LOCK_BLOCK_EXPIRATION: u64 = 20;
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; const CHECK_BLOCK_INTERVAL: u64 = 300;
const BLOCK_COMMITMENT_DELAY: u64 = 100;
pub type AuthIndex = u32; pub type AuthIndex = u32;
pub type ExternalBlockNumber = u64;
#[derive( #[derive(
RuntimeDebug, RuntimeDebug,
@ -100,10 +98,10 @@ pub type ExternalBlockNumber = u64;
TypeInfo, TypeInfo,
MaxEncodedLen, MaxEncodedLen,
)] )]
pub struct CommitmentDetails<BlockNumberFor> { pub struct CommitmentDetails {
pub last_stored_block: ExternalBlockNumber, pub last_stored_block: u64,
pub last_updated: BlockNumberFor, pub last_updated: u64,
pub commits: u8, pub commits: u64,
} }
#[derive( #[derive(
@ -113,7 +111,7 @@ pub struct BlockCommitment<NetworkId> {
pub session_index: SessionIndex, pub session_index: SessionIndex,
pub authority_index: AuthIndex, pub authority_index: AuthIndex,
pub network_id: NetworkId, pub network_id: NetworkId,
pub last_stored_block: ExternalBlockNumber, pub commitment: CommitmentDetails,
} }
#[derive( #[derive(
@ -368,7 +366,6 @@ pub mod pallet {
CouldNotIncreaseGatekeeperAmount, CouldNotIncreaseGatekeeperAmount,
NonExistentAuthorityIndex, NonExistentAuthorityIndex,
TimeWentBackwards, TimeWentBackwards,
InnerTimeWentBackwards,
DisabledAuthority, DisabledAuthority,
ExecutedBlockIsHigher, ExecutedBlockIsHigher,
CommitInWrongSession, CommitInWrongSession,
@ -389,7 +386,7 @@ pub mod pallet {
_, _,
Twox64Concat, Twox64Concat,
NetworkIdOf<T>, NetworkIdOf<T>,
BTreeMap<AuthIndex, CommitmentDetails<BlockNumberFor<T>>>, BTreeMap<AuthIndex, CommitmentDetails>,
ValueQuery, ValueQuery,
>; >;
@ -498,53 +495,28 @@ pub mod pallet {
{ {
weight = weight.saturating_add(T::DbWeight::get().reads_writes(3, 1)); weight = weight.saturating_add(T::DbWeight::get().reads_writes(3, 1));
// TODO: put wrapper function with precalculated weight here
let session_index = T::ValidatorSet::session_index(); let session_index = T::ValidatorSet::session_index();
let block_commitments = BlockCommitments::<T>::get(&network_id); let block_commitments = BlockCommitments::<T>::get(&network_id);
let validators = Validators::<T>::get(&session_index); let validators = Validators::<T>::get(&session_index);
let validators_len = validators.len() as u32; if validators.len() == 0 {
if validators_len == 0 {
return weight; return weight;
} }
let disabled_bitmap = DisabledAuthorityIndexes::<T>::get(&session_index) let disabled_bitmap = DisabledAuthorityIndexes::<T>::get(&session_index)
.unwrap_or(BitMap::new(validators.len() as u32)); .unwrap_or(BitMap::new(validators.len() as u32));
let max_external_block_deviation = ONE_HOUR_MILLIS let max_block_deviation = ONE_HOUR_MILLIS
.saturating_mul(6) // TODO: make it constant or calculate .saturating_mul(6) // TODO: make it constant or calculate
.saturating_div(network_data.avg_block_speed); .saturating_div(network_data.avg_block_speed);
let mut stored_blocks: Vec<(AuthIndex, ExternalBlockNumber)> = Vec::new(); let offence_bitmap = Self::capture_deviation_in_commitments_and_remove(
let mut block_updates: Vec<(AuthIndex, BlockNumberFor<T>)> = Vec::new();
for authority_index in 0..validators_len {
let data = block_commitments
.get(&authority_index)
.copied()
.unwrap_or_default();
stored_blocks.push((authority_index, data.last_stored_block));
block_updates.push((authority_index, data.last_updated));
}
let stored_blocks_offence_bitmap = Self::capture_deviation_in_commitments(
&disabled_bitmap, &disabled_bitmap,
&mut stored_blocks, &block_commitments,
validators_len, &validators,
max_external_block_deviation, max_block_deviation,
); );
let block_updates_offence_bitmap = Self::capture_deviation_in_commitments(
&disabled_bitmap,
&mut block_updates,
validators_len,
CHECK_BLOCK_INTERVAL.unique_saturated_into(), // TODO: revisit
);
let offence_bitmap =
stored_blocks_offence_bitmap.bitor(block_updates_offence_bitmap);
let offence_type = OffenceType::CommitmentOffence; let offence_type = OffenceType::CommitmentOffence;
let extra_weight = Self::try_offend_validators( let extra_weight = Self::try_offend_validators(
&session_index, &session_index,
@ -600,7 +572,7 @@ pub mod pallet {
ValidTransaction::with_tag_prefix("SlowClap") ValidTransaction::with_tag_prefix("SlowClap")
.priority(T::UnsignedPriority::get()) .priority(T::UnsignedPriority::get())
.and_provides(block_commitment.encode()) .and_provides(block_commitment.commitment.encode())
.longevity(LOCK_BLOCK_EXPIRATION) .longevity(LOCK_BLOCK_EXPIRATION)
.propagate(true) .propagate(true)
.build() .build()
@ -690,7 +662,7 @@ impl<T: Config> Pallet<T> {
let clap_unique_hash = Self::generate_unique_hash(&clap); let clap_unique_hash = Self::generate_unique_hash(&clap);
let session_index = let session_index =
if ApplauseDetails::<T>::contains_key(&prev_session_index, &clap_unique_hash) { if ApplauseDetails::<T>::get(&prev_session_index, &clap_unique_hash).is_some() {
prev_session_index prev_session_index
} else { } else {
clap.session_index clap.session_index
@ -702,35 +674,44 @@ impl<T: Config> Pallet<T> {
fn try_slow_clap(clap: &Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>) -> DispatchResult { fn try_slow_clap(clap: &Clap<T::AccountId, NetworkIdOf<T>, BalanceOf<T>>) -> DispatchResult {
let network_id = clap.network_id; let network_id = clap.network_id;
ensure!( ensure!(
T::NetworkDataHandler::contains_key(&network_id), T::NetworkDataHandler::get(&network_id).is_some(),
Error::<T>::UnregistedNetwork Error::<T>::UnregistedNetwork
); );
ensure!(
LatestExecutedBlock::<T>::get(&network_id) <= clap.block_number,
Error::<T>::ExecutedBlockIsHigher,
);
let (session_index, clap_unique_hash) = Self::mended_session_index(&clap); let (session_index, clap_unique_hash) = Self::mended_session_index(&clap);
let authorities = Authorities::<T>::get(&session_index); let authorities = Authorities::<T>::get(&session_index);
let authorities_length = authorities.len(); let authorities_length = authorities.len();
let not_disabled = DisabledAuthorityIndexes::<T>::get(&session_index) let is_disabled = DisabledAuthorityIndexes::<T>::get(&session_index)
.map(|bitmap| !bitmap.exists(&clap.authority_index)) .map(|bitmap| bitmap.exists(&clap.authority_index))
.unwrap_or_default(); .unwrap_or(true);
ensure!(not_disabled, Error::<T>::DisabledAuthority); ensure!(!is_disabled, Error::<T>::DisabledAuthority);
let applause_threshold = Perbill::from_parts(T::ApplauseThreshold::get()); let applause_threshold = Perbill::from_parts(T::ApplauseThreshold::get());
let threshold_amount = applause_threshold.mul_floor(TotalExposure::<T>::get()); let threshold_amount = applause_threshold.mul_floor(TotalExposure::<T>::get());
let account_id = T::ExposureListener::get_account_by_index(clap.authority_index as usize) let maybe_account_id =
.ok_or(Error::<T>::NonExistentAuthorityIndex)?; T::ExposureListener::get_account_by_index(clap.authority_index as usize);
ensure!(
maybe_account_id.is_some(),
Error::<T>::NonExistentAuthorityIndex
);
let account_id = maybe_account_id.unwrap();
let new_clapped_amount = T::ExposureListener::get_validator_exposure(&account_id); let new_clapped_amount = T::ExposureListener::get_validator_exposure(&account_id);
let mut applause_details = let mut applause_details =
ApplauseDetails::<T>::try_get(&session_index, &clap_unique_hash).unwrap_or( match ApplauseDetails::<T>::take(&session_index, &clap_unique_hash) {
ApplauseDetail::new(network_id, clap.block_number, authorities_length), Some(applause_details) => applause_details,
None => {
ensure!(
LatestExecutedBlock::<T>::get(&network_id) <= clap.block_number,
Error::<T>::ExecutedBlockIsHigher,
); );
ApplauseDetail::new(network_id, clap.block_number, authorities_length)
}
};
let total_clapped = if clap.removed { let total_clapped = if clap.removed {
ensure!( ensure!(
@ -769,9 +750,9 @@ impl<T: Config> Pallet<T> {
.gt(&(authorities_length as u32 / 2)); .gt(&(authorities_length as u32 / 2));
if total_clapped > threshold_amount && is_enough && !applause_details.finalized { if total_clapped > threshold_amount && is_enough && !applause_details.finalized {
match Self::try_applause(&clap) { applause_details.finalized = true;
Ok(_) => applause_details.finalized = true, if let Err(e) = Self::try_applause(&clap) {
Err(e) => sp_runtime::print(e), sp_runtime::print(e);
} }
} }
@ -810,12 +791,10 @@ impl<T: Config> Pallet<T> {
fn try_commit_block(new_commitment: &BlockCommitment<NetworkIdOf<T>>) -> DispatchResult { fn try_commit_block(new_commitment: &BlockCommitment<NetworkIdOf<T>>) -> DispatchResult {
let authority_index = new_commitment.authority_index; let authority_index = new_commitment.authority_index;
let network_id = new_commitment.network_id; let network_id = new_commitment.network_id;
let session_index = T::ValidatorSet::session_index(); let session_index = T::ValidatorSet::session_index();
let latest_executed_block = LatestExecutedBlock::<T>::get(&network_id);
ensure!( ensure!(
T::NetworkDataHandler::contains_key(&network_id), T::NetworkDataHandler::get(&network_id).is_some(),
Error::<T>::UnregistedNetwork Error::<T>::UnregistedNetwork
); );
@ -825,36 +804,24 @@ impl<T: Config> Pallet<T> {
); );
BlockCommitments::<T>::try_mutate(&network_id, |current_commitments| -> DispatchResult { BlockCommitments::<T>::try_mutate(&network_id, |current_commitments| -> DispatchResult {
let current_block_number = Self::current_block_number(); 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 details.last_updated.saturating_add(COMMITMENT_DELAY_MILLIS),
.last_updated
.saturating_add(BLOCK_COMMITMENT_DELAY.unique_saturated_into()),
) )
}) })
.unwrap_or((1, Default::default())); .unwrap_or((1, 0));
ensure!( ensure!(
current_block_number >= current_last_updated, new_commitment_details.last_updated > current_last_updated,
Error::<T>::TimeWentBackwards, Error::<T>::TimeWentBackwards
); );
ensure!( new_commitment_details.commits = current_commits;
new_commitment.last_stored_block > latest_executed_block,
Error::<T>::InnerTimeWentBackwards,
);
let new_commitment_details = CommitmentDetails {
last_stored_block: new_commitment.last_stored_block,
last_updated: Self::current_block_number(),
commits: current_commits,
};
current_commitments.insert(authority_index, new_commitment_details); current_commitments.insert(authority_index, new_commitment_details);
Ok(()) Ok(())
@ -1001,7 +968,6 @@ impl<T: Config> Pallet<T> {
for (authority_index, authority_key) in Self::local_authorities(&session_index) { for (authority_index, authority_key) in Self::local_authorities(&session_index) {
parsed_evm_response.sign_and_submit::<T>( parsed_evm_response.sign_and_submit::<T>(
block_number,
new_block_range.0, new_block_range.0,
authority_index, authority_index,
authority_key, authority_key,
@ -1318,7 +1284,6 @@ impl<T: Config> Pallet<T> {
Authorities::<T>::set(&session_index, bounded_authorities); Authorities::<T>::set(&session_index, bounded_authorities);
let mut disabled_bitmap = BitMap::new(authorities_len as AuthIndex); let mut disabled_bitmap = BitMap::new(authorities_len as AuthIndex);
// TODO: make me better
for disabled_index in T::DisabledValidators::disabled_validators() { for disabled_index in T::DisabledValidators::disabled_validators() {
disabled_bitmap.set(disabled_index); disabled_bitmap.set(disabled_index);
} }
@ -1334,47 +1299,73 @@ impl<T: Config> Pallet<T> {
debug_assert!(cursor.maybe_cursor.is_none()); debug_assert!(cursor.maybe_cursor.is_none());
} }
fn calculate_median_value<InnerValue>(values: &mut Vec<(AuthIndex, InnerValue)>) -> InnerValue fn calculate_median_value(values: &mut Vec<(AuthIndex, u64)>) -> u64 {
where
InnerValue: AtLeast32BitUnsigned + Copy,
{
values.sort_by_key(|data| data.1); values.sort_by_key(|data| data.1);
let length = values.len(); let length = values.len();
if length % 2 == 0 { if length % 2 == 0 {
let mid_left = values[length / 2 - 1].1; let mid_left = values[length / 2 - 1].1;
let mid_right = values[length / 2].1; let mid_right = values[length / 2].1;
(mid_left + mid_right) / 2u32.into() (mid_left + mid_right) / 2
} else { } else {
values[length / 2].1 values[length / 2].1
} }
} }
fn capture_deviation_in_commitments<InnerValue>( fn apply_median_deviation(
disabled_bitmap: &BitMap, bitmap: &mut BitMap,
mut values: &mut Vec<(AuthIndex, InnerValue)>, disabled: &BitMap,
validators_len: u32, values: &Vec<(AuthIndex, u64)>,
max_deviation: InnerValue, median: u64,
) -> BitMap max_deviation: u64,
where ) {
InnerValue: AtLeast32BitUnsigned + Copy,
{
let mut delayed_bitmap = BitMap::new(validators_len);
let median_value = Self::calculate_median_value(&mut values);
values.iter().for_each(|(authority_index, value)| { values.iter().for_each(|(authority_index, value)| {
let abs_diff = if *value > median_value { if !disabled.exists(authority_index) && value.abs_diff(median) > max_deviation {
*value - median_value bitmap.set(*authority_index);
} else { }
median_value - *value })
};
if !disabled_bitmap.exists(authority_index) && abs_diff > max_deviation {
delayed_bitmap.set(*authority_index);
} }
});
delayed_bitmap fn capture_deviation_in_commitments_and_remove(
disabled_bitmap: &BitMap,
block_commitments: &BTreeMap<AuthIndex, CommitmentDetails>,
validators: &WeakBoundedVec<ValidatorId<T>, T::MaxAuthorities>,
max_block_deviation: u64,
) -> BitMap {
let validators_len = validators.len() as u32;
let mut delayed = BitMap::new(validators_len);
let mut stored_blocks: Vec<(AuthIndex, u64)> = Vec::new();
let mut time_updates: Vec<(AuthIndex, u64)> = Vec::new();
for authority_index in 0..validators_len {
let data = block_commitments
.get(&authority_index)
.copied()
.unwrap_or_default();
stored_blocks.push((authority_index, data.last_stored_block));
time_updates.push((authority_index, data.last_updated));
}
let stored_block_median = Self::calculate_median_value(&mut stored_blocks);
let time_update_median = Self::calculate_median_value(&mut time_updates);
Self::apply_median_deviation(
&mut delayed,
disabled_bitmap,
&stored_blocks,
stored_block_median,
max_block_deviation,
);
Self::apply_median_deviation(
&mut delayed,
disabled_bitmap,
&time_updates,
time_update_median,
ONE_HOUR_MILLIS,
);
delayed
} }
fn get_cumulative_missing_clapped_amount( fn get_cumulative_missing_clapped_amount(
@ -1526,8 +1517,8 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
where where
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>, I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
{ {
for network_id in T::NetworkDataHandler::iter_indexes() { for (network_id, _) in BlockCommitments::<T>::iter() {
BlockCommitments::<T>::remove(&network_id); BlockCommitments::<T>::remove(network_id);
} }
let total_exposure = T::ExposureListener::get_total_exposure(); let total_exposure = T::ExposureListener::get_total_exposure();

View File

@ -1,6 +1,9 @@
#![cfg(test)] #![cfg(test)]
use std::collections::HashMap; use std::{
collections::HashMap,
time::{SystemTime, UNIX_EPOCH},
};
use super::*; use super::*;
use crate::evm_types::Log; use crate::evm_types::Log;
@ -1061,38 +1064,47 @@ fn should_register_block_commitments() {
let session_index = advance_session_and_get_index(); let session_index = advance_session_and_get_index();
prepare_evm_network(None, None); prepare_evm_network(None, None);
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0); for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
assert_eq!(commitment_details.last_stored_block, 0);
let last_stored_block = 69; assert_eq!(commitment_details.last_updated, 0);
let current_block = SlowClap::current_block_number(); assert_eq!(commitment_details.commits, 0);
}
let mut block_commitment = CommitmentDetails {
last_stored_block: 69,
commits: 420,
last_updated: 1337,
};
for i in 0..=3 { for i in 0..=3 {
assert_ok!(do_block_commitment( assert_ok!(do_block_commitment(
session_index, session_index,
network_id, network_id,
i, i,
last_stored_block, &block_commitment
)); ));
} }
block_commitment.last_updated = 1000;
for i in 0..=3 { for i in 0..=3 {
assert_err!( assert_err!(
do_block_commitment(session_index, network_id, i, last_stored_block), do_block_commitment(session_index, network_id, i, &block_commitment),
Error::<Runtime>::TimeWentBackwards, Error::<Runtime>::TimeWentBackwards,
); );
} }
let block_commitments = BlockCommitments::<Runtime>::get(network_id); for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
assert_eq!(block_commitments.len(), 4); assert_eq!(commitment_details.last_stored_block, 69);
block_commitments.values().for_each(|details| { assert_eq!(commitment_details.last_updated, 1337);
assert_eq!(details.last_stored_block, last_stored_block); assert_eq!(commitment_details.commits, 1);
assert_eq!(details.last_updated, current_block); }
assert_eq!(details.commits, 1);
});
advance_session(); advance_session();
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0); for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
assert_eq!(commitment_details.last_stored_block, 0);
assert_eq!(commitment_details.last_updated, 0);
assert_eq!(commitment_details.commits, 0);
}
}); });
} }
@ -1104,23 +1116,35 @@ fn should_disable_on_commitment_inactivity() {
let session_index = advance_session_and_get_index(); let session_index = advance_session_and_get_index();
prepare_evm_network(None, None); prepare_evm_network(None, None);
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0); for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
System::set_block_number(420); assert_eq!(commitment_details.last_stored_block, 0);
assert_eq!(commitment_details.last_updated, 0);
assert_eq!(commitment_details.commits, 0);
}
let last_stored_block = 1337; let timestamp = SystemTime::now()
let current_block = SlowClap::current_block_number(); .duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
let mut block_commitment = CommitmentDetails {
last_stored_block: 69,
commits: 420,
last_updated: timestamp,
};
// two block commitments
for extra_time in 1..=3 {
block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
for i in 0..3 { for i in 0..3 {
assert_ok!(do_block_commitment( assert_ok!(do_block_commitment(
session_index, session_index,
network_id, network_id,
i, i,
last_stored_block, &block_commitment
)); ));
} }
}
let delay = ONE_HOUR_MILLIS.saturating_mul(6);
System::set_block_number(current_block + delay + 1);
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL); SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap( System::assert_has_event(RuntimeEvent::SlowClap(
@ -1129,50 +1153,12 @@ fn should_disable_on_commitment_inactivity() {
}, },
)); ));
let block_commitments = BlockCommitments::<Runtime>::get(network_id); for commitment_details in BlockCommitments::<Runtime>::get(network_id)
assert_eq!(block_commitments.get(&3), None); .values()
block_commitments.values().for_each(|details| { .take(2)
assert_eq!(details.commits, 1); {
assert_eq!(details.last_stored_block, last_stored_block); assert_eq!(commitment_details.commits, 3);
assert_eq!(details.last_updated, current_block);
})
});
}
#[test]
fn should_ignore_commitments_below_latest_executed_block() {
let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);
let (_, _, _, block_number) = get_mocked_metadata();
new_test_ext().execute_with(|| {
let session_index = advance_session_and_get_index();
prepare_evm_network(None, None);
assert_eq!(LatestExecutedBlock::<Runtime>::get(network_id), 0);
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0);
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, 2, false));
assert_ok!(do_clap_from(session_index, network_id, 3, false));
let latest_executed_block = LatestExecutedBlock::<Runtime>::get(&network_id);
assert_eq!(latest_executed_block, block_number);
for low_block in 0..=block_number {
assert_err!(
do_block_commitment(session_index, network_id, 0, low_block),
Error::<Runtime>::InnerTimeWentBackwards,
);
} }
let next_block_number = block_number.saturating_add(1);
assert_ok!(do_block_commitment(
session_index,
network_id,
0,
next_block_number,
));
}); });
} }
@ -1184,26 +1170,47 @@ fn should_disable_on_commitment_block_deviation() {
let session_index = advance_session_and_get_index(); let session_index = advance_session_and_get_index();
prepare_evm_network(None, None); prepare_evm_network(None, None);
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0); for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
assert_eq!(commitment_details.last_stored_block, 0);
assert_eq!(commitment_details.last_updated, 0);
}
let good_last_stored_block = 9_500_000; let timestamp = SystemTime::now()
let bad_last_stored_block = 9_100_000; .duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
let current_block = SlowClap::current_block_number(); let mut block_commitment = CommitmentDetails {
last_stored_block: 9_500_000,
commits: 420,
last_updated: timestamp,
};
let mut bad_block_commitment = CommitmentDetails {
last_stored_block: 9_100_000,
commits: 420,
last_updated: timestamp,
};
// two block commitments
for extra_time in 1..=3 {
block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
bad_block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
for i in 0..3 { for i in 0..3 {
assert_ok!(do_block_commitment( assert_ok!(do_block_commitment(
session_index, session_index,
network_id, network_id,
i, i,
good_last_stored_block, &block_commitment
)); ));
} }
assert_ok!(do_block_commitment( assert_ok!(do_block_commitment(
session_index, session_index,
network_id, network_id,
3, 3,
bad_last_stored_block, &bad_block_commitment
)); ));
}
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL); SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap( System::assert_has_event(RuntimeEvent::SlowClap(
@ -1211,20 +1218,6 @@ fn should_disable_on_commitment_block_deviation() {
delayed: vec![(3, 3)], delayed: vec![(3, 3)],
}, },
)); ));
BlockCommitments::<Runtime>::get(network_id)
.iter()
.for_each(|(account_id, details)| {
let block_to_be_stored = if *account_id == 3 {
bad_last_stored_block
} else {
good_last_stored_block
};
assert_eq!(details.commits, 1);
assert_eq!(details.last_stored_block, block_to_be_stored);
assert_eq!(details.last_updated, current_block);
})
}); });
} }
@ -1236,50 +1229,107 @@ fn should_throw_error_on_fast_commitments() {
let session_index = advance_session_and_get_index(); let session_index = advance_session_and_get_index();
prepare_evm_network(None, None); prepare_evm_network(None, None);
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0); let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
let last_stored_block = 9_500_000; let mut block_commitment = CommitmentDetails {
let next_stored_block = 9_600_000; last_stored_block: 9_500_000,
let current_block = SlowClap::current_block_number(); commits: 420,
last_updated: timestamp,
};
assert_ok!(do_block_commitment( assert_ok!(do_block_commitment(
session_index, session_index,
network_id, network_id,
0, 0,
last_stored_block, &block_commitment
)); ));
assert_err!( assert_err!(
do_block_commitment(session_index, network_id, 0, next_stored_block), do_block_commitment(session_index, network_id, 0, &block_commitment),
Error::<Runtime>::TimeWentBackwards, Error::<Runtime>::TimeWentBackwards,
); );
BlockCommitments::<Runtime>::get(network_id) block_commitment.last_updated += COMMITMENT_DELAY_MILLIS / 2;
.get(&0) assert_err!(
.map(|details| { do_block_commitment(session_index, network_id, 0, &block_commitment),
assert_eq!(details.last_stored_block, last_stored_block); Error::<Runtime>::TimeWentBackwards,
assert_eq!(details.last_updated, current_block); );
assert_eq!(details.commits, 1);
})
.expect("authority_index 0 has voted; qed");
System::set_block_number(current_block + BLOCK_COMMITMENT_DELAY); block_commitment.last_updated += COMMITMENT_DELAY_MILLIS / 2;
assert_err!(
do_block_commitment(session_index, network_id, 0, &block_commitment),
Error::<Runtime>::TimeWentBackwards,
);
let new_current_block = SlowClap::current_block_number(); block_commitment.last_updated += 1;
assert_ok!(do_block_commitment( assert_ok!(do_block_commitment(
session_index, session_index,
network_id, network_id,
0, 0,
next_stored_block, &block_commitment
)); ));
BlockCommitments::<Runtime>::get(network_id) block_commitment.last_updated = timestamp;
.get(&0) assert_err!(
.map(|details| { do_block_commitment(session_index, network_id, 0, &block_commitment),
assert_eq!(details.last_stored_block, next_stored_block); Error::<Runtime>::TimeWentBackwards,
assert_eq!(details.last_updated, new_current_block); );
assert_eq!(details.commits, 2);
}) for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
.expect("authority_index 0 has voted; qed"); assert_eq!(commitment_details.last_stored_block, 9_500_000);
assert_eq!(
commitment_details.last_updated,
timestamp + COMMITMENT_DELAY_MILLIS + 1
);
assert_eq!(commitment_details.commits, 2);
}
});
}
#[test]
fn should_not_offend_disabled_authorities() {
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_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, 2, false));
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
let mut block_commitment = CommitmentDetails {
last_stored_block: 9_500_000,
commits: 420,
last_updated: timestamp,
};
Session::disable_index(3);
// two block commitments
for extra_time in 1..=3 {
block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
for i in 0..3 {
assert_ok!(do_block_commitment(
session_index,
network_id,
i,
&block_commitment
));
}
}
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::AuthoritiesCommitmentEquilibrium,
));
}); });
} }
@ -1291,90 +1341,83 @@ fn should_not_slash_by_applause_if_disabled_by_commitment() {
let session_index = advance_session_and_get_index(); let session_index = advance_session_and_get_index();
prepare_evm_network(None, None); prepare_evm_network(None, None);
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0); for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
assert_eq!(commitment_details.last_stored_block, 0);
assert_eq!(commitment_details.last_updated, 0);
}
let last_stored_block = 9_500_000; let timestamp = SystemTime::now()
let current_block = SlowClap::current_block_number(); .duration_since(UNIX_EPOCH)
let mut disabled_bitmp = BitMap::new(4); .expect("Time went backwards")
.as_millis() as u64;
let mut block_commitment = CommitmentDetails {
last_stored_block: 9_500_000,
commits: 420,
last_updated: timestamp,
};
assert_eq!(
DisabledAuthorityIndexes::<Runtime>::get(&session_index),
Some(disabled_bitmp.clone())
);
Session::disable_index(3); Session::disable_index(3);
disabled_bitmp.set(3);
assert_eq!(
DisabledAuthorityIndexes::<Runtime>::get(&session_index),
Some(disabled_bitmp.clone())
);
for i in 0..=3 { // two block commitments
for extra_time in 1..=3 {
block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
for i in 0..3 {
assert_ok!(do_block_commitment( assert_ok!(do_block_commitment(
session_index, session_index,
network_id, network_id,
i, i,
last_stored_block, &block_commitment
)); ));
} }
}
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL); SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap( System::assert_has_event(RuntimeEvent::SlowClap(
crate::Event::AuthoritiesCommitmentEquilibrium, crate::Event::AuthoritiesCommitmentEquilibrium,
)); ));
let block_commitments = BlockCommitments::<Runtime>::get(network_id);
assert_eq!(
DisabledAuthorityIndexes::<Runtime>::get(&session_index),
Some(disabled_bitmp)
);
assert_eq!(block_commitments.len(), 4);
block_commitments.values().for_each(|details| {
assert_eq!(details.last_stored_block, last_stored_block);
assert_eq!(details.last_updated, current_block);
assert_eq!(details.commits, 1);
});
}); });
} }
#[test] #[test]
fn should_not_register_block_commitment_on_old_blocks() { fn should_not_nullify_on_incorrect_block_commitment() {
let (network_id, _, _) = generate_unique_hash(None, None, None, None, None); let (network_id, _, _) = generate_unique_hash(None, None, None, None, None);
new_test_ext().execute_with(|| { new_test_ext().execute_with(|| {
let session_index = advance_session_and_get_index(); let session_index = advance_session_and_get_index();
prepare_evm_network(None, None); prepare_evm_network(None, None);
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0); let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
let very_old_block = 9_499_999; let mut block_commitment = CommitmentDetails {
let last_stored_block = 9_500_000; last_stored_block: 9_500_000,
let current_block = SlowClap::current_block_number(); commits: 420,
last_updated: timestamp,
};
for i in 0..4 { for i in 0..4 {
assert_ok!(do_block_commitment( assert_ok!(do_block_commitment(
session_index, session_index,
network_id, network_id,
i, i,
last_stored_block, &block_commitment
)); ));
} }
block_commitment.last_updated = 0;
assert_err!( assert_err!(
do_block_commitment(session_index, network_id, 3, last_stored_block), do_block_commitment(session_index, network_id, 3, &block_commitment),
Error::<Runtime>::TimeWentBackwards
);
assert_err!(
do_block_commitment(session_index, network_id, 3, very_old_block),
Error::<Runtime>::TimeWentBackwards Error::<Runtime>::TimeWentBackwards
); );
BlockCommitments::<Runtime>::get(network_id) for commitment_details in BlockCommitments::<Runtime>::get(network_id).values() {
.values() assert_eq!(commitment_details.last_stored_block, 9_500_000);
.for_each(|details| { assert_eq!(commitment_details.last_updated, timestamp);
assert_eq!(details.commits, 1); assert_eq!(commitment_details.commits, 1);
assert_eq!(details.last_stored_block, last_stored_block); }
assert_eq!(details.last_updated, current_block);
})
}); });
} }
@ -1386,21 +1429,33 @@ fn should_split_commit_slash_between_active_validators() {
let session_index = advance_session_and_get_index(); let session_index = advance_session_and_get_index();
prepare_evm_network(None, None); prepare_evm_network(None, None);
assert_eq!(BlockCommitments::<Runtime>::get(network_id).len(), 0); let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("Time went backwards")
.as_millis() as u64;
let last_stored_block = 9_500_000; let mut block_commitment = CommitmentDetails {
let current_block = SlowClap::current_block_number(); last_stored_block: 9_500_000,
commits: 420,
last_updated: timestamp,
};
for extra_time in 1..=3 {
if extra_time < 3 {
let offences = Offences::get();
assert_eq!(offences.len(), 0);
}
block_commitment.last_updated += COMMITMENT_DELAY_MILLIS + extra_time;
for i in 0..3 { for i in 0..3 {
assert_ok!(do_block_commitment( assert_ok!(do_block_commitment(
session_index, session_index,
network_id, network_id,
i, i,
last_stored_block, &block_commitment
)); ));
} }
let offences = Offences::get(); }
assert_eq!(offences.len(), 0);
SlowClap::on_initialize(CHECK_BLOCK_INTERVAL); SlowClap::on_initialize(CHECK_BLOCK_INTERVAL);
System::assert_has_event(RuntimeEvent::SlowClap( System::assert_has_event(RuntimeEvent::SlowClap(
@ -1416,16 +1471,9 @@ fn should_split_commit_slash_between_active_validators() {
assert_eq!(offence.1.session_index, session_index); assert_eq!(offence.1.session_index, session_index);
assert_eq!(offence.1.validator_set_count, 4); assert_eq!(offence.1.validator_set_count, 4);
assert_eq!(offence.1.offenders, vec![(3, 3)]); assert_eq!(offence.1.offenders, vec![(3, 3)]);
assert_eq!(offence.1.validator_set_count, 4);
assert_eq!(offence.1.offence_type, OffenceType::CommitmentOffence); assert_eq!(offence.1.offence_type, OffenceType::CommitmentOffence);
} }
let block_commitments = BlockCommitments::<Runtime>::get(network_id);
assert_eq!(block_commitments.get(&3), None);
block_commitments.values().for_each(|details| {
assert_eq!(details.last_stored_block, last_stored_block);
assert_eq!(details.last_updated, current_block);
assert_eq!(details.commits, 1);
});
}); });
} }
@ -1672,13 +1720,13 @@ fn do_block_commitment(
session_index: u32, session_index: u32,
network_id: u32, network_id: u32,
authority_index: u32, authority_index: u32,
last_stored_block: ExternalBlockNumber, commitment: &CommitmentDetails,
) -> dispatch::DispatchResult { ) -> dispatch::DispatchResult {
let block_commitment = BlockCommitment { let block_commitment = BlockCommitment {
session_index, session_index,
authority_index, authority_index,
network_id, network_id,
last_stored_block, commitment: *commitment,
}; };
let authority = UintAuthorityId::from(authority_index as u64); let authority = UintAuthorityId::from(authority_index as u64);
let signature = authority.sign(&block_commitment.encode()).unwrap(); let signature = authority.sign(&block_commitment.encode()).unwrap();

View File

@ -84,28 +84,11 @@ impl WeightInfo for () {
.saturating_add(RocksDbWeight::get().writes(7)) .saturating_add(RocksDbWeight::get().writes(7))
} }
/// Storage: `GhostNetworks::Networks` (r:1 w:0) fn commit_block()-> Weight {
/// Proof: `GhostNetworks::Networks` (`max_values`: None, `max_size`: None, mode: `Measured`) Default::default()
/// Storage: `GhostSlowClaps::BlockCommitments` (r:1 w:1)
/// Proof: `GhostSlowClaps::BlockCommitments` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `Session::CurrentIndex` (r:1 w:0)
/// Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
/// Storage: `GhostSlowClaps::Validators` (r:1 w:0)
/// Proof: `GhostSlowClaps::Validators` (`max_values`: None, `max_size`: None, mode: `Measured`)
/// Storage: `GhostSlowClaps::DisabledAuthorityIndexes` (r:1 w:0)
/// Proof: `GhostSlowClaps::DisabledAuthorityIndexes` (`max_values`: None, `max_size`: None, mode: `Measured`)
fn commit_block() -> Weight {
// Proof Size summary in bytes:
// Measured: `859`
// Estimated: `4324`
// Minimum execution time: 108_966_000 picoseconds.
Weight::from_parts(110_454_000, 0)
.saturating_add(Weight::from_parts(0, 4324))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(1))
} }
fn try_offend_validators(_offenders_len: u32) -> Weight { fn try_offend_validators(offenders_len: u32) -> Weight {
Default::default() Default::default()
} }
} }

View File

@ -1,6 +1,6 @@
[package] [package]
name = "ghost-traits" name = "ghost-traits"
version = "0.3.31" 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,11 +17,9 @@ pub trait NetworkDataBasicHandler {
pub trait NetworkDataInspectHandler<Network>: NetworkDataBasicHandler { pub trait NetworkDataInspectHandler<Network>: NetworkDataBasicHandler {
fn count() -> u32; fn count() -> u32;
fn contains_key(n: &Self::NetworkId) -> bool;
fn 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 iter_indexes() -> impl Iterator<Item = Self::NetworkId>;
} }
pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<Network> { pub trait NetworkDataMutateHandler<Network, Balance>: NetworkDataInspectHandler<Network> {