From fa2cb811a843f4478972fb37201b06d3b3fd85df Mon Sep 17 00:00:00 2001 From: Uncle Stinky Date: Sat, 31 May 2025 14:30:15 +0300 Subject: [PATCH] update for slow-claps: remove companions, updgrade block range storage, new way of commission accumulation Signed-off-by: Uncle Stinky --- Cargo.lock | 2 +- pallets/slow-clap/Cargo.toml | 2 +- pallets/slow-clap/src/lib.rs | 1023 +++++++++++------------------- pallets/slow-clap/src/weights.rs | 15 +- 4 files changed, 365 insertions(+), 677 deletions(-) mode change 100755 => 100644 pallets/slow-clap/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 6bfebf7..e95e0db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3834,7 +3834,7 @@ dependencies = [ [[package]] name = "ghost-slow-clap" -version = "0.3.15" +version = "0.3.16" dependencies = [ "frame-benchmarking", "frame-support", diff --git a/pallets/slow-clap/Cargo.toml b/pallets/slow-clap/Cargo.toml index 3e88662..302d83d 100755 --- a/pallets/slow-clap/Cargo.toml +++ b/pallets/slow-clap/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ghost-slow-clap" -version = "0.3.15" +version = "0.3.16" 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 old mode 100755 new mode 100644 index a9d996e..0f72707 --- a/pallets/slow-clap/src/lib.rs +++ b/pallets/slow-clap/src/lib.rs @@ -9,14 +9,11 @@ pub use pallet::*; use frame_support::{ pallet_prelude::*, traits::{ - tokens::{ - fungible::{Inspect, Mutate}, - Preservation, Precision, Fortitude, - }, + tokens::fungible::{Inspect, Mutate}, EstimateNextSessionRotation, ValidatorSet, ValidatorSetWithIdentification, - OneSessionHandler, Get, + OneSessionHandler, Get, }, - PalletId, BoundedSlice, BoundedVec, WeakBoundedVec, + PalletId, BoundedSlice, WeakBoundedVec, }; use frame_system::{ @@ -24,19 +21,14 @@ use frame_system::{ pallet_prelude::*, }; -use sp_core::{H160, H256}; +use sp_core::H256; use sp_runtime::{ - traits::{ - TrailingZeroInput, Saturating, BlockNumberProvider, Convert, - AccountIdConversion, - }, - offchain as rt_offchain, + Perbill, RuntimeAppPublic, RuntimeDebug, SaturatedConversion, offchain::{ - HttpError, - storage::StorageValueRef, - storage_lock::{BlockAndTime, StorageLock}, + self as rt_offchain, HttpError, + storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, }, - RuntimeAppPublic, Perbill, RuntimeDebug, SaturatedConversion, + traits::{CheckedSub, BlockNumberProvider, Convert, Saturating}, }; use sp_std::{ vec::Vec, prelude::*, @@ -46,11 +38,10 @@ use sp_staking::{ offence::{Kind, Offence, ReportOffence}, SessionIndex, }; -use sp_io::hashing::keccak_256; use ghost_networks::{ NetworkData, NetworkDataBasicHandler, NetworkDataInspectHandler, - NetworkDataMutateHandler, + NetworkDataMutateHandler, NetworkType, }; pub mod weights; @@ -67,13 +58,10 @@ pub mod sr25519 { } sp_application_crypto::with_pair! { - /// A staking keypair sr25519 at its crypto. pub type AuthorityPair = app_sr25519::Pair; } - /// A staking signature using sr25519 as its crypto. pub type AuthoritySignature = app_sr25519::Signature; - /// A staking identifier using sr25519 as its crypto. pub type AuthorityId = app_sr25519::Public; } @@ -81,13 +69,9 @@ const LOG_TARGET: &str = "runtime::ghost-slow-clap"; const DB_PREFIX: &[u8] = b"slow_clap::"; const FETCH_TIMEOUT_PERIOD: u64 = 3_000; -const LOCK_BLOCK_EXPIRATION: u32 = 5; -const LOCK_TIMEOUT_EXPIRATION: u64 = FETCH_TIMEOUT_PERIOD + 1_000; +const LOCK_BLOCK_EXPIRATION: u64 = 10; +const NUMBER_OF_TOPICS: usize = 3; -const PERCENT_DIVISOR: u32 = 4; -const COMPANIONS_LIMIT: usize = 20; - -pub type CompanionId = u128; pub type AuthIndex = u32; #[derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode)] @@ -126,6 +110,14 @@ struct Log { removed: bool, } +impl Log { + fn is_sufficient(&self) -> bool { + self.transaction_hash.is_some() && + self.block_number.is_some() && + self.topics.len() == NUMBER_OF_TOPICS + } +} + pub fn de_string_to_bytes<'de, D>(de: D) -> Result>, D::Error> where D: Deserializer<'de> { let s: &str = Deserialize::deserialize(de)?; @@ -194,26 +186,23 @@ pub struct Clap { pub network_id: NetworkId, pub receiver: AccountId, pub amount: Balance, - pub companions: BTreeMap, } -#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct Companion { - pub network_id: NetworkId, - pub receiver: H160, - pub amount: Balance, +#[derive(Default, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct AuthorityClapsInfo { + pub total: u32, + pub individual: BTreeMap, } -#[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +#[derive(Default, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct SessionAuthorityInfo { - pub actions: u32, + pub claps: u32, pub disabled: bool, } #[cfg_attr(test, derive(PartialEq))] enum OffchainErr { FailedSigning, - FailedToAcquireLock(NetworkId), SubmitTransaction, HttpJsonParsingError, HttpBytesParsingError, @@ -221,15 +210,16 @@ enum OffchainErr { RequestUncompleted, HttpResponseNotOk(u16), ErrorInEvmResponse, - NoEvmLogsFound(NetworkId), - OnlyPendingEvmLogs(NetworkId), + StorageRetrievalError(NetworkId), + ConcurrentModificationError(NetworkId), + UtxoNotImplemented(NetworkId), + UnknownNetworkType(NetworkId), } impl core::fmt::Debug for OffchainErr { fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { match *self { OffchainErr::FailedSigning => write!(fmt, "Failed to sign clap."), - OffchainErr::FailedToAcquireLock(ref network_id) => write!(fmt, "Failed to acquire lock for network #{:?}.", network_id), OffchainErr::SubmitTransaction => write!(fmt, "Failed to submit transaction."), OffchainErr::HttpJsonParsingError => write!(fmt, "Failed to parse evm response as JSON."), OffchainErr::HttpBytesParsingError => write!(fmt, "Failed to parse evm response as bytes."), @@ -238,11 +228,13 @@ impl core::fmt::Debug for OffchainErr { HttpError::IoError => write!(fmt, "There was an IO error while processing the request."), HttpError::Invalid => write!(fmt, "The ID of the request is invalid in this context"), }, + OffchainErr::ConcurrentModificationError(ref network_id) => write!(fmt, "The underlying DB failed to update due to a concurrent modification for network #{:?}", network_id), + OffchainErr::StorageRetrievalError(ref network_id) => write!(fmt, "Storage value found for network #{:?} but it's undecodable", network_id), OffchainErr::RequestUncompleted => write!(fmt, "Failed to complete request."), OffchainErr::HttpResponseNotOk(code) => write!(fmt, "Http response returned code {:?}", code), OffchainErr::ErrorInEvmResponse => write!(fmt, "Error in evm reponse."), - OffchainErr::NoEvmLogsFound(ref network_id) => write!(fmt, "No incoming evm logs for network #{:?}.", network_id), - OffchainErr::OnlyPendingEvmLogs(ref network_id) => write!(fmt, "Only pending evm logs for network #{:?}.", network_id), + OffchainErr::UtxoNotImplemented(ref network_id) => write!(fmt, "Network #{:?} is marked as UTXO, which is not implemented yet.", network_id), + OffchainErr::UnknownNetworkType(ref network_id) => write!(fmt, "Unknown type for network #{:?}.", network_id), } } } @@ -271,7 +263,6 @@ type OffchainResult = Result>>; pub mod pallet { use super::*; - /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] @@ -294,7 +285,7 @@ pub mod pallet { type Currency: Inspect + Mutate; type NetworkDataHandler: NetworkDataBasicHandler + NetworkDataInspectHandler + - NetworkDataMutateHandler; + NetworkDataMutateHandler>; type BlockNumberProvider: BlockNumberProvider>; type ReportUnresponsiveness: ReportOffence< Self::AccountId, @@ -304,13 +295,7 @@ pub mod pallet { #[pallet::constant] type MaxAuthorities: Get; - - #[pallet::constant] - type MaxAuthorityInfoInSession: Get; - #[pallet::constant] - type MaxNumberOfClaps: Get; - #[pallet::constant] type ApplauseThreshold: Get; @@ -329,12 +314,11 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - AllGood, - SomeTrottling { throttling: Vec> }, + AuthoritiesEquilibrium, + SomeAuthoritiesTrottling { throttling: Vec> }, Clapped { - authority_id: T::AuthorityId, + authority_id: AuthIndex, network_id: NetworkIdOf, - clap_hash: H256, transaction_hash: H256, receiver: T::AccountId, amount: BalanceOf, @@ -343,62 +327,44 @@ pub mod pallet { network_id: NetworkIdOf, receiver: T::AccountId, received_amount: BalanceOf, - number_of_companions: u32, - }, - CompanionCreated { - companion_id: CompanionId, - owner: T::AccountId, - companion: Companion, BalanceOf>, - }, - CompanionReleased { - network_id: NetworkIdOf, - companion_id: CompanionId, - who: T::AccountId, - release_block: BlockNumberFor, - }, - CompanionKilled { - network_id: NetworkIdOf, - who: T::AccountId, - companion_id: CompanionId, - freed_balance: BalanceOf, }, } #[pallet::error] pub enum Error { NotAnAuthority, + ClapForThePastSession, CurrentValidatorIsDisabled, - CouldNotHashCompanions, AlreadyClapped, - CompanionAlreadyRegistered, - CompanionDetailsAlreadyRegistered, - CompanionDetailsNotRegistered, - CompanionAmountNotExistent, - CompanionAmountUnderflow, - NoMoreCompanions, - NotValidCompanion, - NonRegisteredNetwork, - ReleaseTimeHasNotCome, + UnregisteredClapRemove, + TooMuchAuthorities, + NotEnoughToApplause, + CouldNotAccumulateCommission, + CouldNotIncreaseGatekeeperAmount, } - + #[pallet::storage] #[pallet::getter(fn received_claps)] - pub(super) type ReceivedClaps = StorageDoubleMap< + pub(super) type ReceivedClaps = StorageNMap< _, - Twox64Concat, - H256, - Twox64Concat, - T::AuthorityId, - bool, + ( + NMapKey, + NMapKey, + NMapKey, + ), + BoundedBTreeSet, ValueQuery >; #[pallet::storage] #[pallet::getter(fn applauses_for_transaction)] - pub(super) type ApplausesForTransaction = StorageMap< + pub(super) type ApplausesForTransaction = StorageNMap< _, - Twox64Concat, - H256, + ( + NMapKey, + NMapKey, + NMapKey, + ), bool, ValueQuery >; @@ -416,53 +382,15 @@ pub mod pallet { >; #[pallet::storage] - #[pallet::getter(fn authority_info_in_session)] - pub(super) type AuthorityInfoInSession = StorageMap< + #[pallet::getter(fn claps_in_session)] + pub(super) type ClapsInSession = StorageMap< _, Twox64Concat, SessionIndex, - BoundedVec, + AuthorityClapsInfo, ValueQuery, >; - #[pallet::storage] - #[pallet::getter(fn companions)] - pub(super) type Companions = StorageDoubleMap< - _, - Twox64Concat, - NetworkIdOf, - Twox64Concat, - CompanionId, - T::AccountId, - OptionQuery, - >; - - - #[pallet::storage] - #[pallet::getter(fn companion_details)] - pub(super) type CompanionDetails = StorageMap< - _, - Twox64Concat, - CompanionId, - Companion, BalanceOf>, - OptionQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn release_blocks)] - pub(super) type ReleaseBlocks = StorageMap< - _, - Twox64Concat, - CompanionId, - BlockNumberFor, - ValueQuery, - >; - - #[pallet::storage] - #[pallet::getter(fn current_companion_id)] - pub(super) type CurrentCompanionId = - StorageValue<_, CompanionId, ValueQuery>; - #[pallet::storage] #[pallet::getter(fn keys)] pub(super) type Authorities = @@ -478,138 +406,32 @@ pub mod pallet { impl BuildGenesisConfig for GenesisConfig { fn build(&self) { Pallet::::initialize_authorities(&self.keys); - Pallet::::restart_clap_round(1, self.keys.len()); } } #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight(( - T::WeightInfo::slow_clap( - claps.len() as u32, - claps.iter().fold(0u32, |acc, c| { - acc + c.companions.len() as u32 - }) - ), - frame_support::dispatch::Pays::No, - ))] + #[pallet::weight((T::WeightInfo::slow_clap(), DispatchClass::Normal, Pays::No))] pub fn slow_clap( origin: OriginFor, - claps: Vec, BalanceOf>>, + clap: Clap, BalanceOf>, // since signature verification is done in `validate_unsigned` // we can skip doing it here again. _signature: ::Signature, ) -> DispatchResult { ensure_none(origin)?; - Self::clap_if_possible(claps)?; + Self::try_slow_clap(&clap)?; Ok(()) } #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::propose_companion())] - pub fn propose_companion( + #[pallet::weight(T::WeightInfo::applause())] + pub fn applause( origin: OriginFor, - network_id: NetworkIdOf, - companion: Companion, BalanceOf>, + _clap: Clap, BalanceOf>, ) -> DispatchResult { - let who = ensure_signed(origin)?; - let companion_id = CurrentCompanionId::::get(); - - ensure!(companion.amount > T::Currency::minimum_balance(), - Error::::CompanionAmountNotExistent); - ensure!(T::NetworkDataHandler::get(&network_id).is_some(), - Error::::NonRegisteredNetwork); - - T::Currency::burn_from( - &who, - companion.amount, - Preservation::Expendable, - Precision::Exact, - Fortitude::Force, - )?; - - Companions::::set(network_id, companion_id, Some(who.clone())); - CompanionDetails::::set(companion_id, Some(companion.clone())); - CurrentCompanionId::::set(companion_id - .checked_add(1) - .ok_or(Error::::NoMoreCompanions)?); - - Self::deposit_event(Event::::CompanionCreated { - companion_id, - owner: who, - companion, - }); - - Ok(()) - } - - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::release_companion())] - pub fn release_companion( - origin: OriginFor, - network_id: NetworkIdOf, - companion_id: CompanionId, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - let owner = Companions::::get(network_id, companion_id); - let network = T::NetworkDataHandler::get(&network_id); - - ensure!(owner.is_some_and(|o| o == who), Error::::NotValidCompanion); - ensure!(CompanionDetails::::get(companion_id).is_some(), - Error::::CompanionDetailsNotRegistered); - - let offset = T::BlockNumberProvider::current_block_number() + - network - .ok_or(Error::::NonRegisteredNetwork)? - .release_delay - .unwrap_or_default() - .saturated_into::>(); - - ReleaseBlocks::::set(companion_id, offset); - - Self::deposit_event(Event::::CompanionReleased { - network_id, - companion_id, - who, - release_block: offset, - }); - - Ok(()) - } - - #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::kill_companion())] - pub fn kill_companion( - origin: OriginFor, - network_id: NetworkIdOf, - companion_id: CompanionId, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - - ensure!(ReleaseBlocks::::get(companion_id) <= - T::BlockNumberProvider::current_block_number(), - Error::::ReleaseTimeHasNotCome); - - ensure!(Companions::::get(network_id, companion_id).is_some_and(|o| o == who), - Error::::NotValidCompanion); - - let companion = CompanionDetails::::get(companion_id) - .ok_or(Error::::CompanionDetailsNotRegistered)?; - - Companions::::remove(network_id, companion_id); - CompanionDetails::::remove(companion_id); - - T::Currency::mint_into(&who, companion.amount)?; - - Self::deposit_event(Event::::CompanionKilled { - network_id, - who, - companion_id, - freed_balance: companion.amount, - }); - + let _ = ensure_signed(origin)?; Ok(()) } } @@ -629,7 +451,7 @@ pub mod pallet { } else { for result in Self::start_slow_clapping(now, networks_len).into_iter().flatten() { if let Err(e) = result { - log::debug!( + log::info!( target: LOG_TARGET, "👏 Skipping slow clap at {:?}: {:?}", now, @@ -653,42 +475,15 @@ pub mod pallet { type Call = Call; fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { - if let Call::slow_clap { claps, signature } = call { - if claps.is_empty() || claps.len() > T::MaxNumberOfClaps::get() as usize { - return InvalidTransaction::BadProof.into(); - } - - if claps - .iter() - .fold(0usize, |max_len, c| { - usize::max(max_len, c.companions.len()) - }) > COMPANIONS_LIMIT { - return InvalidTransaction::BadProof.into(); - } - - let authority_index = match &claps[..] { - [head, tail @ ..] => tail - .iter() - .all(|x| x.authority_index == head.authority_index) - .then(|| head.authority_index), - _ => None, - }; - - if authority_index.is_none() { - return InvalidTransaction::BadProof.into(); - } - - let authority_index = authority_index - .expect("authority index info already checked; qed"); - + if let Call::slow_clap { clap, signature } = call { let authorities = Authorities::::get(); - let authority = match authorities.get(authority_index as usize) { - Some(id) => id, + let authority = match authorities.get(clap.authority_index as usize) { + Some(authority) => authority, None => return InvalidTransaction::BadProof.into(), }; - let signature_valid = claps.using_encoded(|encoded_claps| { - authority.verify(&encoded_claps, signature) + let signature_valid = clap.using_encoded(|encoded_clap| { + authority.verify(&encoded_clap, signature) }); if !signature_valid { @@ -698,7 +493,7 @@ pub mod pallet { ValidTransaction::with_tag_prefix("SlowClap") .priority(T::UnsignedPriority::get()) .and_provides(authority) - .longevity(LOCK_BLOCK_EXPIRATION.saturated_into::()) + .longevity(LOCK_BLOCK_EXPIRATION) .propagate(true) .build() } else { @@ -709,161 +504,121 @@ pub mod pallet { } impl Pallet { - fn treasury_account_id() -> T::AccountId { - T::TreasuryPalletId::get().into_account_truncating() + fn generate_unique_hash( + receiver: &T::AccountId, + amount: &BalanceOf, + network_id: &NetworkIdOf, + ) -> H256 { + let mut clap_args_str = receiver.encode(); + clap_args_str.extend(&amount.encode()); + clap_args_str.extend(&network_id.encode()); + + H256::from_slice(&sp_io::hashing::keccak_256(&clap_args_str)[..]) } - fn clap_if_possible( - claps: Vec, BalanceOf>>, - ) -> DispatchResult { - let current_authorities = Authorities::::get(); - for clap in claps.iter() { - let Ok(mut clap_str) = serde_json::to_vec(&clap.companions) else { - continue; - }; - clap_str.extend(clap.transaction_hash.to_fixed_bytes()); - clap_str.extend(clap.network_id.encode()); - let clap_hash = H256::from_slice(&keccak_256(&clap_str)[..]); + fn try_slow_clap(clap: &Clap, BalanceOf>) -> DispatchResult { + let current_session_index = T::ValidatorSet::session_index(); + ensure!(current_session_index == clap.session_index, Error::::ClapForThePastSession); - let Some(public) = current_authorities.get(clap.authority_index as usize) else { - continue; - }; + let authorities = Authorities::::get(); + ensure!(authorities.get(clap.authority_index as usize).is_some(), Error::::NotAnAuthority); + let clap_unique_hash = Self::generate_unique_hash(&clap.receiver, &clap.amount, &clap.network_id); + let received_claps_key = (clap.session_index, &clap.transaction_hash, &clap_unique_hash); - let mut claps_in_session = AuthorityInfoInSession::::get(&clap.session_index); - if let Some(_) = claps_in_session.get(clap.authority_index as usize) { - if claps_in_session[clap.authority_index as usize].disabled { - continue; - } + let number_of_received_claps = ReceivedClaps::::try_mutate(&received_claps_key, |tree_of_claps| { + let number_of_claps = tree_of_claps.len(); + match (tree_of_claps.contains(&clap.authority_index), clap.removed) { + (true, true) => tree_of_claps + .remove(&clap.authority_index) + .then(|| number_of_claps.saturating_sub(1)) + .ok_or(Error::::UnregisteredClapRemove), + (true, false) => Err(Error::::AlreadyClapped), + (false, true) => Err(Error::::UnregisteredClapRemove), + (false, false) => tree_of_claps + .try_insert(clap.authority_index) + .map(|_| number_of_claps.saturating_add(1)) + .map_err(|_| Error::::TooMuchAuthorities), + } + })?; - let received_clap = ReceivedClaps::::get(&clap_hash, &public); - match (received_clap, clap.removed) { - (true, false) => ReceivedClaps::::set(&clap_hash, &public, false), - (false, true) => ReceivedClaps::::set(&clap_hash, &public, true), - _ => continue, - } - - claps_in_session[clap.authority_index as usize].actions.saturating_inc(); - AuthorityInfoInSession::::set(&clap.session_index, claps_in_session); - - Self::deposit_event(Event::::Clapped { - authority_id: public.clone(), - network_id: clap.network_id, - clap_hash, - transaction_hash: clap.transaction_hash, - receiver: clap.receiver.clone(), - amount: clap.amount, - }); + ClapsInSession::::try_mutate(&clap.session_index, |claps_details| { + if claps_details.individual.get(&clap.authority_index).map(|x| x.disabled).unwrap_or_default() { + return Err(Error::::CurrentValidatorIsDisabled); } - Self::applause_if_possible(clap_hash, &clap, current_authorities.clone())?; - } + (*claps_details).total.saturating_inc(); + (*claps_details).individual + .entry(clap.authority_index) + .and_modify(|individual| (*individual).claps.saturating_inc()) + .or_default(); - Ok(()) - } + Ok(()) + })?; - fn applause_if_possible( - clap_hash: H256, - clap: &Clap, BalanceOf>, - current_authorities: WeakBoundedVec, - ) -> DispatchResult { - if !ApplausesForTransaction::::get(&clap_hash) { - let clappers_len = ReceivedClaps::::iter_prefix(&clap_hash) - .filter(|(authority, yes_vote)| current_authorities.contains(authority) && *yes_vote) - .count(); - - let enough_authorities = Perbill::from_rational( - clappers_len as u32, - current_authorities.len() as u32, - ) > Perbill::from_percent(T::ApplauseThreshold::get()); - - if enough_authorities { - Self::reward_companions( - clap.network_id, - clap.receiver.clone(), - clap.amount, - clap.companions.clone(), - )?; - } - } - - Ok(()) - } - - fn reward_companions( - network_id: NetworkIdOf, - receiver: T::AccountId, - amount: BalanceOf, - companions: BTreeMap>, - ) -> DispatchResult { - let mut final_bridge_amount = amount; - let mut number_of_companions: u32 = 0; - - let existential_balance = T::Currency::minimum_balance(); - - for (companion_id, companion_amount) in companions.iter() { - if final_bridge_amount < existential_balance { break; } - number_of_companions.saturating_inc(); - CompanionDetails::::mutate(companion_id, |maybe_stored_companion| { - match maybe_stored_companion { - Some(stored_companion) if (stored_companion.amount >= *companion_amount) => { - stored_companion.amount = stored_companion - .amount - .saturating_sub(*companion_amount); - if stored_companion.amount > existential_balance { - *maybe_stored_companion = Some(stored_companion.clone()); - } else { - *maybe_stored_companion = None; - } - }, - _ => { - final_bridge_amount = final_bridge_amount - .saturating_sub(*companion_amount); - }, - } - }); - } - - let incoming_fee = match T::NetworkDataHandler::get(&network_id) { - Some(network_data) => network_data.incoming_fee, - None => 0, - }; - - let gatekeeper_fees = Perbill::from_parts(incoming_fee) - .mul_floor(final_bridge_amount); - - let final_bridge_amount = final_bridge_amount - .saturating_sub(gatekeeper_fees); - - if gatekeeper_fees >= existential_balance { - T::Currency::mint_into( - &Self::treasury_account_id(), - gatekeeper_fees, - )?; - } - - if final_bridge_amount >= existential_balance { - T::Currency::mint_into( - &receiver, - final_bridge_amount, - )?; - } - - Self::deposit_event(Event::::Applaused { - network_id, - receiver, - received_amount: final_bridge_amount, - number_of_companions, + Self::deposit_event(Event::::Clapped { + authority_id: clap.authority_index, + network_id: clap.network_id, + transaction_hash: clap.transaction_hash, + receiver: clap.receiver.clone(), + amount: clap.amount, }); + let enough_authorities = Perbill::from_rational( + number_of_received_claps as u32, + authorities.len() as u32, + ) > Perbill::from_percent(T::ApplauseThreshold::get()); + + if enough_authorities { + Self::try_applause(&clap, &received_claps_key)?; + } + Ok(()) } - fn create_key(first: Vec, second: Vec) -> Vec { - let mut key = DB_PREFIX.to_vec(); - key.extend(first); - key.extend(second); - key + fn try_applause( + clap: &Clap, BalanceOf>, + received_claps_key: &(SessionIndex, &H256, &H256), + ) -> DispatchResult { + ApplausesForTransaction::::try_mutate(received_claps_key, |is_applaused| { + if *is_applaused { + return Ok(()) + } + + let incoming_fee = T::NetworkDataHandler::get(&clap.network_id) + .map(|network_data| Perbill::from_parts(network_data.incoming_fee)) + .unwrap_or_default(); + + let commission = incoming_fee.mul_ceil(clap.amount); + let final_amount = clap.amount + .checked_sub(&commission) + .map(|value| T::Currency::minimum_balance() + .lt(&value) + .then(|| value) + ) + .flatten() + .unwrap_or_default(); + + let _ = T::NetworkDataHandler::increase_gatekeeper_amount(&clap.network_id, &final_amount) + .map_err(|_| Error::::CouldNotIncreaseGatekeeperAmount)?; + let _ = T::NetworkDataHandler::accumulate_commission(&commission) + .map_err(|_| Error::::CouldNotAccumulateCommission)?; + + T::Currency::mint_into( + &clap.receiver, + final_amount + )?; + + *is_applaused = true; + + Self::deposit_event(Event::::Applaused { + network_id: clap.network_id, + receiver: clap.receiver.clone(), + received_amount: final_amount, + }); + + Ok(()) + }) } fn start_slow_clapping( @@ -871,113 +626,115 @@ impl Pallet { networks_len: usize, ) -> OffchainResult>> { let session_index = T::ValidatorSet::session_index(); - let index = block_number.into().as_u128() % networks_len as u128; let network_in_use = T::NetworkDataHandler::iter() - .nth(index as usize) + .nth(block_number.into().as_usize() % networks_len) .expect("network should exist; qed"); - let network_id = network_in_use.0; - let gatekeeper = network_in_use.1.gatekeeper; - let topic_name = network_in_use.1.topic_name; - - let key = Self::create_key(network_id.encode(), b"endpoint".to_vec()); - let rpc_endpoint = match StorageValueRef::persistent(&key).get() { - Ok(Some(endpoint)) => endpoint, - _ => network_in_use.1.default_endpoint, - }; - - let finality_delay = - network_in_use.1.finality_delay.unwrap_or(1u64); - Ok(Self::local_authorities().map(move |(authority_index, authority_key)| { Self::do_evm_claps_or_save_block( authority_index, authority_key, session_index, - network_id, - finality_delay, - gatekeeper.clone(), - topic_name.clone(), - rpc_endpoint.clone(), + network_in_use.0, + &network_in_use.1, ) })) } + fn create_storage_key(first: &[u8], second: &[u8]) -> Vec { + let mut key = DB_PREFIX.to_vec(); + key.extend(first); + key.extend(second); + key + } + fn do_evm_claps_or_save_block( authority_index: AuthIndex, authority_key: T::AuthorityId, session_index: SessionIndex, network_id: NetworkIdOf, - finality_delay: u64, - gatekeeper: Vec, - topic_name: Vec, - rpc_endpoint: Vec, + network_data: &NetworkData, ) -> OffchainResult { - let key = Self::create_key(network_id.encode(), b"lock".to_vec()); - let mut network_lock = StorageLock::>::with_block_and_time_deadline( - &key, - LOCK_BLOCK_EXPIRATION, - rt_offchain::Duration::from_millis(LOCK_TIMEOUT_EXPIRATION), - ); + let network_id_encoded = network_id.encode(); - network_lock - .try_lock() - .map_err(|_| OffchainErr::FailedToAcquireLock(network_id))? - .forget(); + let block_number_key = Self::create_storage_key(b"block-", &network_id_encoded); + let endpoint_key = Self::create_storage_key(b"endpoint-", &network_id_encoded); - let key = Self::create_key(network_id.encode(), b"block".to_vec()); - let mut evm_block_info = StorageValueRef::persistent(&key); - let evm_block: Option = match evm_block_info.get() { - Ok(option_block) => option_block, - _ => None, - }; + let rpc_endpoint = StorageValueRef::persistent(&endpoint_key) + .get() + .ok() + .flatten() + .unwrap_or(network_data.default_endpoint.clone()); - let evm_response = Self::fetch_and_parse( - evm_block, finality_delay, - gatekeeper, topic_name, rpc_endpoint - )?; + let mutation_result = StorageValueRef::persistent(&block_number_key).mutate(|result_block_range: Result, StorageRetrievalError>| { + match result_block_range { + Ok(maybe_block_range) => { + let request_body = match maybe_block_range { + Some((from_block, to_block)) if from_block < to_block => + Self::prepare_request_body_for_latest_transfers(from_block, to_block, network_data), + _ => Self::prepare_request_body_for_latest_block(network_data), + }; - match evm_response { - EvmResponseType::BlockNumber(evm_block) => { - let deviation = Self::random_u64_deviation(finality_delay); - let evm_start_block = evm_block.saturating_sub(deviation); - evm_block_info.set(&evm_start_block); + let response_bytes = Self::fetch_from_remote(&rpc_endpoint, &request_body)?; + + match network_data.network_type { + NetworkType::Evm => { + let new_evm_block = Self::apply_evm_response( + &response_bytes, + authority_index, + authority_key, + session_index, + network_id + )?; + + let finality_delay = network_data.finality_delay.unwrap_or_default(); + let estimated_block = new_evm_block.saturating_sub(finality_delay); + + Ok(match maybe_block_range { + Some((from_block, to_block)) => match new_evm_block { + 0 => (to_block, new_evm_block), + _ => (from_block, estimated_block), + }, + None => (estimated_block, estimated_block), + }) + }, + NetworkType::Utxo => Err(OffchainErr::UtxoNotImplemented(network_id).into()), + _ => Err(OffchainErr::UnknownNetworkType(network_id).into()), + } + } + Err(_) => Err(OffchainErr::StorageRetrievalError(network_id).into()) + } + }); + + match mutation_result { + Ok(_) => Ok(()), + Err(MutateStorageError::ValueFunctionFailed(offchain_error)) => Err(offchain_error), + Err(MutateStorageError::ConcurrentModification(_)) => Err(OffchainErr::ConcurrentModificationError(network_id).into()), + } + } + + fn apply_evm_response( + response_bytes: &[u8], + authority_index: AuthIndex, + authority_key: T::AuthorityId, + session_index: SessionIndex, + network_id: NetworkIdOf + ) -> OffchainResult { + match Self::parse_evm_response(&response_bytes)? { + EvmResponseType::BlockNumber(new_evm_block) => { log::info!( target: LOG_TARGET, - "🧐 New evm block #{:?} found for {:?} network", - evm_start_block, + "🧐 New evm block #{:?} found for network {:?}", + new_evm_block, network_id, ); + Ok(new_evm_block) }, EvmResponseType::TransactionLogs(evm_logs) => { - if evm_logs.is_empty() { - return Err(OffchainErr::NoEvmLogsFound(network_id).into()); - } - - let evm_logs: Vec<_> = evm_logs + let claps: Vec<_> = evm_logs .iter() - .filter(|log| { - log.block_number.is_some() && - log.transaction_hash.is_some() && - log.topics.len() == 3 && - log.data.len() <= COMPANIONS_LIMIT - }) - .collect(); - - if evm_logs.is_empty() { - return Err(OffchainErr::OnlyPendingEvmLogs(network_id)); - } - - log::info!( - target: LOG_TARGET, - "🧐 {:?} evm logs found for network #{:?}", - evm_logs.len(), - network_id, - ); - - let mut claps: Vec<_> = evm_logs - .iter() - .map(|log| Clap { + .filter_map(|log| log.is_sufficient().then(|| { + Clap { authority_index, session_index, network_id, @@ -988,67 +745,33 @@ impl Pallet { .try_into() .expect("amount is valid hex; qed")) .saturated_into::>(), - companions: log - .data - .clone() - .into_iter() - .map(|(key, value)| ( - key.saturated_into::(), - value.saturated_into::>(), - )) - .collect(), - transaction_hash: log - .transaction_hash + transaction_hash: log.transaction_hash .clone() .expect("tx hash exists; qed"), - block_number: log - .block_number + block_number: log.block_number .expect("block number exists; qed"), - }) + } + })) .collect(); - claps.sort_by(|a, b| a.block_number.cmp(&b.block_number)); + log::info!( + target: LOG_TARGET, + "🧐 {:?} evm logs found for network {:?}", + claps.len(), + network_id, + ); - let maximum_claps = T::MaxNumberOfClaps::get() as usize; - claps.truncate(maximum_claps); - match claps.get(maximum_claps) { - Some(last_clap) => { - evm_block_info.set(&last_clap.block_number); - log::info!( - target: LOG_TARGET, - "🧐 New evm block #{:?} found for {:?} network", - last_clap.block_number, - network_id, - ); - }, - None => evm_block_info.clear(), + for clap in claps { + let signature = authority_key.sign(&clap.encode()) + .ok_or(OffchainErr::FailedSigning)?; + let call = Call::slow_clap { clap, signature }; + SubmitTransaction::>::submit_unsigned_transaction(call.into()) + .map_err(|_| OffchainErr::SubmitTransaction)?; } - let signature = authority_key.sign(&claps.encode()) - .ok_or(OffchainErr::FailedSigning)?; - let call = Call::slow_clap { claps, signature }; - - SubmitTransaction::>::submit_unsigned_transaction(call.into()) - .map_err(|_| OffchainErr::SubmitTransaction)?; + Ok(0u64) } } - Ok(()) - } - - fn random_u64_deviation(original: u64) -> u64 { - let seed = sp_io::offchain::random_seed(); - let random = ::decode(&mut TrailingZeroInput::new(seed.as_ref())) - .expect("input is padded with zeroes; qed"); - - let maximum = Perbill::one() - .saturating_div( - Perbill::from_parts(PERCENT_DIVISOR), - sp_runtime::Rounding::Down, - ) - .deconstruct(); - - let random = Perbill::from_parts(random % maximum); - original.saturating_add(random.mul_ceil(original)) } fn local_authorities() -> impl Iterator { @@ -1064,21 +787,17 @@ impl Pallet { } fn fetch_from_remote( - rpc_endpoint: Vec, - request_body: Vec, + rpc_endpoint: &[u8], + request_body: &[u8], ) -> OffchainResult> { - let rpc_str = core::str::from_utf8(&rpc_endpoint) - .expect("rpc endpoint valid str; qed"); + let rpc_endpoint_str = core::str::from_utf8(rpc_endpoint).expect("rpc endpoint valid str; qed"); + let request_body_str = core::str::from_utf8(request_body).expect("request body valid str: qed"); - let body_str = core::str::from_utf8(&request_body) - .expect("request body valid str: qed"); + let deadline = sp_io::offchain::timestamp().add(rt_offchain::Duration::from_millis(FETCH_TIMEOUT_PERIOD)); - let deadline = sp_io::offchain::timestamp() - .add(rt_offchain::Duration::from_millis(FETCH_TIMEOUT_PERIOD)); - - let pending = rt_offchain::http::Request::post(&rpc_str, vec![body_str]) - .add_header("ACCEPT", "APPLICATION/JSON") - .add_header("CONTENT-TYPE", "APPLICATION/JSON") + let pending = rt_offchain::http::Request::post(&rpc_endpoint_str, vec![request_body_str]) + .add_header("Accept", "application/json") + .add_header("Content-Type", "application/json") .deadline(deadline) .send() .map_err(|err| OffchainErr::HttpRequestError(err))?; @@ -1095,66 +814,68 @@ impl Pallet { Ok(response.body().collect::>()) } - fn prepare_request_body( - maybe_block: Option, - finality_delay: u64, - gatekeeper: Vec, - topic_name: Vec, - ) -> Vec { - match maybe_block { - Some(to_block) => { - let from_block: u64 = to_block.saturating_sub(finality_delay); - let convert_number_to_hex_vector = |value: u64| -> Vec { - let mut r = Vec::new(); - let mut value = value; - loop { - r.push((value % 10 + 48) as u8); - value /= 10; - if value == 0 { break; } - } - r.reverse(); - r - }; + fn u64_to_hexadecimal_bytes(value: u64) -> Vec { + let mut hex_str = Vec::new(); + hex_str.push(b'0'); + hex_str.push(b'x'); + if value == 0 { + hex_str.push(b'0'); + return hex_str; + } + + for i in (0..16).rev() { + let nibble = (value >> (i * 4)) & 0xF; + if nibble != 0 || hex_str.len() > 2 { + hex_str.push(match nibble { + 0..=9 => b'0' + nibble as u8, + 10..=15 => b'a' + (nibble - 10) as u8, + _ => unreachable!(), + }); + } + } + + hex_str + } + + fn prepare_request_body_for_latest_block(network_data: &NetworkData) -> Vec { + match network_data.network_type { + NetworkType::Evm => b"{\"id\":0,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\"}".to_vec(), + _ => Default::default(), + } + } + + fn prepare_request_body_for_latest_transfers(from_block: u64, to_block: u64, network_data: &NetworkData) -> Vec { + match network_data.network_type { + NetworkType::Evm => { let mut body = b"{\"id\":0,\"jsonrpc\":\"2.0\",\"method\":\"eth_getLogs\",\"params\":[{".to_vec(); - body.extend(b"\"fromBlock\":".to_vec()); - body.extend(convert_number_to_hex_vector(from_block)); - body.extend(b",\"toBlock\":".to_vec()); - body.extend(convert_number_to_hex_vector(to_block)); - body.extend(b",\"address\":\"".to_vec()); - body.extend(gatekeeper); + body.extend(b"\"fromBlock\":\"".to_vec()); + body.extend(Self::u64_to_hexadecimal_bytes(from_block)); + body.extend(b"\",\"toBlock\":\"".to_vec()); + body.extend(Self::u64_to_hexadecimal_bytes(to_block)); + body.extend(b"\",\"address\":\"".to_vec()); + body.extend(network_data.gatekeeper.to_vec()); body.extend(b"\",\"topics\":[\"".to_vec()); - body.extend(topic_name); + body.extend(network_data.topic_name.to_vec()); body.extend(b"\"]}]}".to_vec()); body }, - None => b"{\"id\":0,\"jsonrpc\":\"2.0\",\"method\":\"eth_blockNumber\"}".to_vec() - }.to_vec() + _ => Default::default(), + } } - fn fetch_and_parse( - evm_block: Option, - finality_delay: u64, - gatekeeper: Vec, - topic_name: Vec, - rpc_endpoint: Vec, - ) -> OffchainResult { - let request_body = Self::prepare_request_body( - evm_block, finality_delay, gatekeeper, topic_name); - - let resp_bytes = Self::fetch_from_remote(rpc_endpoint, request_body)?; - - let resp_str = sp_std::str::from_utf8(&resp_bytes) + fn parse_evm_response(response_bytes: &[u8]) -> OffchainResult { + let response_str = sp_std::str::from_utf8(&response_bytes) .map_err(|_| OffchainErr::HttpBytesParsingError)?; - let result: EvmResponse = serde_json::from_str(&resp_str) + let response_result: EvmResponse = serde_json::from_str(&response_str) .map_err(|_| OffchainErr::HttpJsonParsingError)?; - if result.error.is_some() { + if response_result.error.is_some() { return Err(OffchainErr::ErrorInEvmResponse); } - Ok(result.result.ok_or(OffchainErr::ErrorInEvmResponse)?) + Ok(response_result.result.ok_or(OffchainErr::ErrorInEvmResponse)?) } fn is_good_actor( @@ -1166,19 +887,18 @@ impl Pallet { return true; } - let claps_in_session = AuthorityInfoInSession::::get(session_index); - match claps_in_session.get(authority_index) { - Some(clap_in_session) => { - let number_of_claps = &clap_in_session.actions; - let authority_deviation = if *number_of_claps < average_claps { - Perbill::from_rational(average_claps - *number_of_claps, average_claps) - } else { - Perbill::from_rational(*number_of_claps - average_claps, average_claps) - }; - authority_deviation < Perbill::from_percent(T::OffenceThreshold::get()) - }, - None => false, - } + let number_of_claps = ClapsInSession::::get(session_index) + .individual + .entry(authority_index as AuthIndex) + .or_default() + .claps; + + let authority_deviation = if number_of_claps < average_claps { + Perbill::from_rational(average_claps - number_of_claps, average_claps) + } else { + Perbill::from_rational(number_of_claps - average_claps, average_claps) + }; + authority_deviation < Perbill::from_percent(T::OffenceThreshold::get()) } fn initialize_authorities(authorities: &[T::AuthorityId]) { @@ -1189,26 +909,6 @@ impl Pallet { Authorities::::put(bounded_authorities); } } - - fn restart_clap_round(session_index: SessionIndex, length: usize) { - let empty_authority_info = SessionAuthorityInfo { - actions: 0u32, - disabled: false, - }; - let empty_authorities_vec = - BoundedVec::::try_from( - vec![empty_authority_info; length]) - .expect("authorities length should be correct"); - AuthorityInfoInSession::::set(session_index, empty_authorities_vec); - } - - // #[cfg(test)] - // fn set_authorities(authorities: Vec) { - // let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::try_from(authorities) - // .expect("more than the maximum number of clappers"); - // Authorities::::put(&bounded_authorities); - // Self::restart_clap_round(T::ValidatorSet::session_index(), bounded_authorities.len()); - // } } impl sp_runtime::BoundToRuntimeAppPublic for Pallet { @@ -1231,39 +931,35 @@ impl OneSessionHandler for Pallet { { let authorities = validators.map(|x| x.1).collect::>(); Self::initialize_authorities(&authorities); - Self::restart_clap_round(1, authorities.len()); } fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, _queued_validators: I) where I: Iterator, { + let previous_session = T::ValidatorSet::session_index().saturating_sub(1); let authorities = validators.map(|x| x.1).collect::>(); - let bounded_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from( - authorities, - Some( - "Warning: The session has more authorities than expected. \ - A runtime configuration adjustment may be needed.", - ), - ); - let bounded_authorities_len = (&bounded_authorities).len(); - Authorities::::put(bounded_authorities); - Self::restart_clap_round(T::ValidatorSet::session_index(), bounded_authorities_len); + Self::initialize_authorities(&authorities); + ClapsInSession::::set(previous_session, Default::default()); } fn on_before_session_ending() { let session_index = T::ValidatorSet::session_index(); let validators = T::ValidatorSet::validators(); let authorities = Authorities::::get(); - let claps_in_session = AuthorityInfoInSession::::get(session_index); - let average_claps = claps_in_session + let claps_details = ClapsInSession::::get(session_index); + let total_claps_in_session = claps_details.total; + let average_claps = claps_details + .individual .iter() - .filter(|x| !x.disabled) - .fold(0u32, |acc, x| acc + x.actions) - .checked_div(claps_in_session.len() as u32) + .filter(|(_, value)| !value.disabled) + .fold(0u32, |acc, (_, value)| acc.saturating_add(value.claps)) + .checked_div(total_claps_in_session) .unwrap_or_default(); + // TODO: seems like it's not working + let offenders = validators .into_iter() .enumerate() @@ -1276,9 +972,9 @@ impl OneSessionHandler for Pallet { .collect::>>(); if offenders.is_empty() { - Self::deposit_event(Event::::AllGood); + Self::deposit_event(Event::::AuthoritiesEquilibrium); } else { - Self::deposit_event(Event::::SomeTrottling { throttling: offenders.clone() }); + Self::deposit_event(Event::::SomeAuthoritiesTrottling { throttling: offenders.clone() }); let validator_set_count = authorities.len() as u32; let offence = ThrottlingOffence { session_index, validator_set_count, offenders }; @@ -1290,22 +986,21 @@ impl OneSessionHandler for Pallet { fn on_disabled(validator_index: u32) { let session_index = T::ValidatorSet::session_index(); - let mut claps_in_session = AuthorityInfoInSession::::get(&session_index); - if let Some(_) = claps_in_session.get(validator_index as usize) { - claps_in_session[validator_index as usize].disabled = true; - AuthorityInfoInSession::::set(&session_index, claps_in_session); - } + ClapsInSession::::mutate(&session_index, |claps_details| { + (*claps_details) + .individual + .entry(validator_index as AuthIndex) + .and_modify(|individual| (*individual).disabled = true) + .or_insert(SessionAuthorityInfo { claps: 0u32, disabled: true }); + }); } } #[derive(RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))] pub struct ThrottlingOffence { - /// Current session index in which we report the unresponsive validators. pub session_index: SessionIndex, - /// The size pf the validator set in current session. pub validator_set_count: u32, - /// Authorities that were unresponsive during the current session. pub offenders: Vec } diff --git a/pallets/slow-clap/src/weights.rs b/pallets/slow-clap/src/weights.rs index 3eb62b0..b479cbe 100644 --- a/pallets/slow-clap/src/weights.rs +++ b/pallets/slow-clap/src/weights.rs @@ -1,18 +1,11 @@ use frame_support::weights::Weight; pub trait WeightInfo { - fn slow_clap(claps_len: u32, companions_len: u32) -> Weight; - fn propose_companion() -> Weight; - fn release_companion() -> Weight; - fn kill_companion() -> Weight; + fn slow_clap() -> Weight; + fn applause()-> Weight; } impl WeightInfo for () { - fn slow_clap( - _claps_len: u32, - _companions_len: u32, - ) -> Weight { Weight::zero() } - fn propose_companion() -> Weight { Weight::zero() } - fn release_companion() -> Weight { Weight::zero() } - fn kill_companion() -> Weight { Weight::zero() } + fn slow_clap()-> Weight { Weight::zero() } + fn applause()-> Weight { Weight::zero() } }