diff --git a/pallets/slow-clap/Cargo.toml b/pallets/slow-clap/Cargo.toml index 64c013e..b0c06ed 100644 --- a/pallets/slow-clap/Cargo.toml +++ b/pallets/slow-clap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-slow-clap" -version = "0.3.50" +version = "0.3.51" description = "Applause protocol for the EVM bridge" license.workspace = true authors.workspace = true diff --git a/pallets/slow-clap/src/lib.rs b/pallets/slow-clap/src/lib.rs index f925205..75bd679 100644 --- a/pallets/slow-clap/src/lib.rs +++ b/pallets/slow-clap/src/lib.rs @@ -36,7 +36,7 @@ use sp_staking::{ SessionIndex, }; use sp_std::{ - collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + collections::btree_map::BTreeMap, prelude::*, vec::Vec, }; @@ -118,6 +118,7 @@ enum OffchainErr { UnknownNetworkType(NetworkId), OffchainTimeoutPeriod(NetworkId), TooManyRequests(NetworkId), + AuthorityDisabled(AuthIndex), } impl core::fmt::Debug for OffchainErr { @@ -143,7 +144,7 @@ impl core::fmt::Debug for OffchainErr { OffchainErr::UnknownNetworkType(ref network_id) => write!(fmt, "Unknown type for network #{:?}.", network_id), OffchainErr::OffchainTimeoutPeriod(ref network_id) => write!(fmt, "Offchain request should be in-flight for network #{:?}.", network_id), OffchainErr::TooManyRequests(ref network_id) => write!(fmt, "Too many requests over RPC endpoint for network #{:?}.", network_id), - + OffchainErr::AuthorityDisabled(ref authority_index) => write!(fmt, "Authority index {:?} is disabled in current session.", authority_index), } } } @@ -243,7 +244,6 @@ pub mod pallet { #[pallet::error] pub enum Error { NotEnoughClaps, - CurrentValidatorIsDisabled, AlreadyClapped, UnregisteredClapRemove, TooMuchAuthorities, @@ -507,14 +507,6 @@ impl Pallet { let (session_index, clap_unique_hash) = Self::mended_session_index(&clap); let mut claps_in_session = ClapsInSession::::get(&session_index); - ensure!( - claps_in_session - .get(&clap.authority_index) - .map(|info| !info.disabled) - .unwrap_or(true), - Error::::CurrentValidatorIsDisabled - ); - let disabled_authorities = claps_in_session .values() .filter(|info| info.disabled) @@ -627,34 +619,53 @@ impl Pallet { let curr_session_index = prev_session_index.saturating_add(1); let clap_unique_hash = Self::generate_unique_hash(&receiver, &amount, &network_id); - let prev_authorities = Authorities::::get(&prev_session_index); + let prev_authorities = Authorities::::get(&prev_session_index) + .into_iter() + .enumerate() + .map(|(i, auth)| (auth, i as AuthIndex)) + .collect::>(); let curr_authorities = Authorities::::get(&curr_session_index); let prev_received_claps_key = (prev_session_index, &transaction_hash, &clap_unique_hash); let curr_received_claps_key = (curr_session_index, &transaction_hash, &clap_unique_hash); - let prev_received_claps = ReceivedClaps::::get(&prev_received_claps_key) - .into_iter() - .filter_map(|auth_index| prev_authorities.get(auth_index as usize)) - .cloned() - .collect::>(); + let mut previous_claps = ClapsInSession::::get(&prev_session_index); + let mut total_received_claps = ReceivedClaps::::get(&prev_received_claps_key).into_inner(); - let curr_received_claps = ReceivedClaps::::get(&curr_received_claps_key) - .into_iter() - .filter_map(|auth_index| curr_authorities.get(auth_index as usize)) - .cloned() - .collect::>(); + for (auth_index, info) in ClapsInSession::::get(&curr_session_index).iter() { + if !info.disabled { + continue; + } - let disabled_authorities = ClapsInSession::::get(&curr_session_index) + if let Some(curr_authority) = curr_authorities.get(*auth_index as usize) { + if let Some(prev_position) = prev_authorities.get(&curr_authority) { + previous_claps + .entry(*prev_position as AuthIndex) + .and_modify(|individual| (*individual).disabled = true) + .or_insert(SessionAuthorityInfo { + claps: 0u32, + disabled: true, + }); + } + } + } + + for auth_index in ReceivedClaps::::get(&curr_received_claps_key).into_iter() { + if let Some(curr_authority) = curr_authorities.get(auth_index as usize) { + if let Some(prev_position) = prev_authorities.get(&curr_authority) { + let _ = total_received_claps.insert(*prev_position as AuthIndex); + } + } + } + + let disabled_authorities = previous_claps .values() .filter(|info| info.disabled) .count(); - let active_authorities = prev_authorities.len().saturating_sub(disabled_authorities); - - let summary_authority_claps_length = curr_received_claps - .symmetric_difference(&prev_received_claps) - .count(); + let active_authorities = prev_authorities + .len() + .saturating_sub(disabled_authorities); let clap = Clap { authority_index: Default::default(), @@ -668,7 +679,7 @@ impl Pallet { }; let enough_authorities = Perbill::from_rational( - summary_authority_claps_length as u32, + total_received_claps.len() as u32, active_authorities as u32, ) > Perbill::from_percent(T::ApplauseThreshold::get()); @@ -757,6 +768,14 @@ impl Pallet { network_id: NetworkIdOf, network_data: &NetworkData, ) -> OffchainResult { + if ClapsInSession::::get(&session_index) + .get(&authority_index) + .map(|info| info.disabled) + .unwrap_or_default() + { + return Err(OffchainErr::AuthorityDisabled(authority_index)); + } + let network_id_encoded = network_id.encode(); let block_number_key = Self::create_storage_key(b"block-", &network_id_encoded); diff --git a/pallets/slow-clap/src/tests.rs b/pallets/slow-clap/src/tests.rs index 7d95591..a0d6068 100644 --- a/pallets/slow-clap/src/tests.rs +++ b/pallets/slow-clap/src/tests.rs @@ -944,6 +944,74 @@ fn should_avoid_applause_during_nullification_period() { }); } +#[test] +fn should_self_applause_after_diabled() { + let zero = 0u64; + let (network_id, transaction_hash, unique_transaction_hash) = + generate_unique_hash(None, None, None, None); + let (_, receiver, amount) = get_mocked_metadata(); + + new_test_ext().execute_with(|| { + let _ = prepare_evm_network(Some(network_id), Some(0)); + let session_index = advance_session_and_get_index(); + let storage_key = (session_index, transaction_hash, unique_transaction_hash); + + assert_err!( + SlowClap::self_applause( + RuntimeOrigin::signed(receiver), + network_id, + session_index, + transaction_hash, + receiver, + amount, + ), + Error::::NotEnoughClaps, + ); + + assert_eq!( + pallet::ApplausesForTransaction::::get(&storage_key), + false + ); + assert_eq!(Balances::balance(&receiver), zero); + + assert_ok!(do_clap_from(session_index, network_id, 0, false)); + advance_session(); + let curr_session_index = Session::session_index(); + + pallet::ClapsInSession::::mutate(&session_index, |claps| { + claps.entry(1 as AuthIndex) + .and_modify(|individual| (*individual).disabled = true) + .or_insert(SessionAuthorityInfo { + claps: 0u32, + disabled: true, + }); + }); + + pallet::ClapsInSession::::mutate(&curr_session_index, |claps| { + claps.entry(2 as AuthIndex) + .and_modify(|individual| (*individual).disabled = true) + .or_insert(SessionAuthorityInfo { + claps: 0u32, + disabled: true, + }); + }); + + assert_ok!(SlowClap::self_applause( + RuntimeOrigin::signed(receiver), + network_id, + session_index, + transaction_hash, + receiver, + amount, + )); + assert_eq!( + pallet::ApplausesForTransaction::::get(&storage_key), + true + ); + assert_eq!(Balances::balance(&receiver), amount); + }); +} + #[test] fn should_self_applause_if_enough_claps() { let zero = 0u64;