diff --git a/pallets/slow-clap/Cargo.toml b/pallets/slow-clap/Cargo.toml index 3d3cdb4..e95b160 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.30" +version = "0.3.31" 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 c830bb7..2897465 100644 --- a/pallets/slow-clap/src/lib.rs +++ b/pallets/slow-clap/src/lib.rs @@ -27,6 +27,7 @@ use sp_runtime::{ offchain::{ self as rt_offchain, HttpError, storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, + storage_lock::{StorageLock, Time}, }, traits::{BlockNumberProvider, Convert, Saturating}, }; @@ -204,10 +205,14 @@ enum OffchainErr { RequestUncompleted, HttpResponseNotOk(u16), ErrorInEvmResponse, + NoStoredNetworks, + NotValidator, StorageRetrievalError(NetworkId), ConcurrentModificationError(NetworkId), UtxoNotImplemented(NetworkId), UnknownNetworkType(NetworkId), + OffchainTimeoutPeriod(NetworkId), + TooManyRequests(NetworkId), } impl core::fmt::Debug for OffchainErr { @@ -220,15 +225,20 @@ impl core::fmt::Debug for OffchainErr { OffchainErr::HttpRequestError(http_error) => match http_error { HttpError::DeadlineReached => write!(fmt, "Requested action couldn't been completed within a deadline."), 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"), + 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::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::HttpResponseNotOk(code) => write!(fmt, "Http response returned code {:?}.", code), OffchainErr::ErrorInEvmResponse => write!(fmt, "Error in evm reponse."), + OffchainErr::NoStoredNetworks => write!(fmt, "No networks stored for the offchain slow claps."), + OffchainErr::NotValidator => write!(fmt, "Not a validator for slow clap, `--validator` flag needed."), 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), + 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), + } } } @@ -438,33 +448,23 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn offchain_worker(now: BlockNumberFor) { - // Only send messages of we are a potential validator. - if sp_io::offchain::is_validator() { - let networks_len = T::NetworkDataHandler::iter().count(); - if networks_len == 0 { - log::info!( - target: LOG_TARGET, - "👏 Skipping slow clap at {:?}: no evm networks", - now, - ); - } else { - for result in Self::start_slow_clapping(now, networks_len).into_iter().flatten() { - if let Err(e) = result { - log::info!( - target: LOG_TARGET, - "👏 Skipping slow clap at {:?}: {:?}", - now, - e, - ); - } + match Self::start_slow_clapping(now) { + Ok(iter) => for result in iter.into_iter() { + if let Err(e) = result { + log::info!( + target: LOG_TARGET, + "👏 Skipping slow clap at {:?}: {:?}", + now, + e, + ) } - } - } else { - log::info!( + }, + Err(e) => log::info!( target: LOG_TARGET, - "🤥 Not a validator, skipping slow clap at {:?}.", + "👏 Could not start slow clap at {:?}: {:?}", now, - ); + e, + ), } } } @@ -503,6 +503,21 @@ pub mod pallet { } impl Pallet { + fn create_storage_key(first: &[u8], second: &[u8]) -> Vec { + let mut key = DB_PREFIX.to_vec(); + key.extend(first); + key.extend(second); + key + } + + fn read_persistent_offchain_storage(storage_key: &[u8], default_value: R) -> R { + StorageValueRef::persistent(&storage_key) + .get::() + .ok() + .flatten() + .unwrap_or(default_value) + } + fn generate_unique_hash( receiver: &T::AccountId, amount: &BalanceOf, @@ -515,6 +530,30 @@ impl Pallet { H256::from_slice(&sp_io::hashing::keccak_256(&clap_args_str)[..]) } + 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 try_slow_clap(clap: &Clap, BalanceOf>) -> DispatchResult { let authorities = Authorities::::get(&clap.session_index); ensure!(authorities.get(clap.authority_index as usize).is_some(), Error::::NotAnAuthority); @@ -565,13 +604,12 @@ impl Pallet { ) > Perbill::from_percent(T::ApplauseThreshold::get()); if enough_authorities { - if let Err(error_msg) = Self::try_applause(&clap, &received_claps_key) { - log::info!( - target: LOG_TARGET, - "👏 Could not applause because of: {:?}", - error_msg, - ); - } + let _ = Self::try_applause(&clap, &received_claps_key) + .inspect_err(|error_msg| log::info!( + target: LOG_TARGET, + "👏 Could not applause because of: {:?}", + error_msg, + )); } Ok(()) @@ -651,15 +689,57 @@ impl Pallet { } fn start_slow_clapping( - block_number: BlockNumberFor, - networks_len: usize, + block_number: BlockNumberFor ) -> OffchainResult>> { - let session_index = T::ValidatorSet::session_index(); - let network_in_use = T::NetworkDataHandler::iter() - .nth(block_number.into().as_usize() % networks_len) - .expect("network should exist; qed"); + sp_io::offchain::is_validator() + .then(|| ()) + .ok_or(OffchainErr::NotValidator)?; - Ok(Self::local_authorities().map(move |(authority_index, authority_key)| { + let session_index = T::ValidatorSet::session_index(); + let networks_len = T::NetworkDataHandler::iter().count(); + let network_in_use = T::NetworkDataHandler::iter() + .nth(block_number.into() + .as_usize() + .checked_rem(networks_len) + .unwrap_or_default()) + .ok_or(OffchainErr::NoStoredNetworks)?; + + let network_id_encoded = network_in_use.0.encode(); + + let last_timestamp_key = Self::create_storage_key(b"last-timestamp-", &network_id_encoded); + let rate_limit_delay_key = Self::create_storage_key(b"rate-limit-", &network_id_encoded); + let rate_limit_delay = Self::read_persistent_offchain_storage( + &rate_limit_delay_key, + network_in_use.1.rate_limit_delay, + ); + + let network_lock_key = Self::create_storage_key(b"network-lock-", &network_id_encoded); + let block_until = rt_offchain::Duration::from_millis(networks_len as u64 * FETCH_TIMEOUT_PERIOD); + let mut network_lock = StorageLock::