// Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use serde::{Deserializer, Deserialize}; pub use pallet::*; use frame_support::{ pallet_prelude::*, traits::{ tokens::fungible::{Inspect, Mutate}, EstimateNextSessionRotation, ValidatorSet, ValidatorSetWithIdentification, OneSessionHandler, Get, }, PalletId, BoundedSlice, WeakBoundedVec, }; use frame_system::{ offchain::{SendTransactionTypes, SubmitTransaction}, pallet_prelude::*, }; use sp_core::H256; use sp_runtime::{ Perbill, RuntimeAppPublic, RuntimeDebug, SaturatedConversion, offchain::{ self as rt_offchain, HttpError, storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, }, traits::{CheckedSub, BlockNumberProvider, Convert, Saturating}, }; use sp_std::{ vec::Vec, prelude::*, collections::btree_map::BTreeMap, }; use sp_staking::{ offence::{Kind, Offence, ReportOffence}, SessionIndex, }; use ghost_networks::{ NetworkData, NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler, NetworkType, }; pub mod weights; pub use crate::weights::WeightInfo; mod tests; mod mock; mod benchmarking; pub mod sr25519 { mod app_sr25519 { use sp_application_crypto::{app_crypto, sr25519, KeyTypeId}; const SLOW_CLAP: KeyTypeId = KeyTypeId(*b"slow"); app_crypto!(sr25519, SLOW_CLAP); } sp_application_crypto::with_pair! { pub type AuthorityPair = app_sr25519::Pair; } pub type AuthoritySignature = app_sr25519::Signature; pub type AuthorityId = app_sr25519::Public; } 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: u64 = 10; const NUMBER_OF_TOPICS: usize = 3; pub type AuthIndex = u32; #[derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode)] struct EvmResponse { #[serde(default)] id: Option, #[serde(default, deserialize_with = "de_string_to_bytes")] jsonrpc: Option>, #[serde(default, deserialize_with = "de_string_to_bytes")] error: Option>, #[serde(default)] result: Option, } #[derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode)] #[serde(untagged)] enum EvmResponseType { #[serde(deserialize_with = "de_string_to_u64_pure")] BlockNumber(u64), TransactionLogs(Vec), } #[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Deserialize, Encode, Decode)] #[serde(rename_all = "camelCase")] struct Log { #[serde(default, deserialize_with = "de_string_to_h256")] transaction_hash: Option, #[serde(default, deserialize_with = "de_string_to_u64")] block_number: Option, #[serde(default, deserialize_with = "de_string_to_vec_of_bytes")] topics: Vec>, #[serde(default, deserialize_with = "de_string_to_bytes")] address: Option>, #[serde(default, deserialize_with = "de_string_to_btree_map")] data: BTreeMap, 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)?; Ok(Some(s.as_bytes().to_vec())) } pub fn de_string_to_u64<'de, D>(de: D) -> Result, D::Error> where D: Deserializer<'de> { let s: &str = Deserialize::deserialize(de)?; let s = if s.starts_with("0x") { &s[2..] } else { &s }; Ok(u64::from_str_radix(s, 16).ok()) } pub fn de_string_to_u64_pure<'de, D>(de: D) -> Result where D: Deserializer<'de> { let s: &str = Deserialize::deserialize(de)?; let s = if s.starts_with("0x") { &s[2..] } else { &s }; Ok(u64::from_str_radix(s, 16).unwrap_or_default()) } pub fn de_string_to_h256<'de, D>(de: D) -> Result, D::Error> where D: Deserializer<'de> { let s: &str = Deserialize::deserialize(de)?; let start_index = if s.starts_with("0x") { 2 } else { 0 }; let h256: Vec<_> = (start_index..s.len()) .step_by(2) .map(|i| u8::from_str_radix(&s[i..i+2], 16).expect("valid u8 symbol; qed")) .collect(); Ok(Some(H256::from_slice(&h256))) } pub fn de_string_to_vec_of_bytes<'de, D>(de: D) -> Result>, D::Error> where D: Deserializer<'de> { let strings: Vec<&str> = Deserialize::deserialize(de)?; Ok(strings .iter() .map(|s| { let start_index = if s.starts_with("0x") { 2 } else { 0 }; (start_index..s.len()) .step_by(2) .map(|i| u8::from_str_radix(&s[i..i+2], 16).expect("valid u8 symbol; qed")) .collect::>() }) .collect::>>()) } pub fn de_string_to_btree_map<'de, D>(de: D) -> Result, D::Error> where D: Deserializer<'de> { let s: &str = Deserialize::deserialize(de)?; let start_index = if s.starts_with("0x") { 2 } else { 0 }; Ok(BTreeMap::from_iter((start_index..s.len()) .step_by(64) .map(|i| ( u128::from_str_radix(&s[i..i+32], 16).expect("valid u8 symbol; qed"), u128::from_str_radix(&s[i+32..i+64], 16).expect("valid u8 symbol; qed"), )))) } #[derive(RuntimeDebug, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct Clap { pub session_index: SessionIndex, pub authority_index: AuthIndex, pub transaction_hash: H256, pub block_number: u64, pub removed: bool, pub network_id: NetworkId, pub receiver: AccountId, pub amount: Balance, } #[derive(Default, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct AuthorityClapsInfo { pub total: u32, pub individual: BTreeMap, } #[derive(Default, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct SessionAuthorityInfo { pub claps: u32, pub disabled: bool, } #[cfg_attr(test, derive(PartialEq))] enum OffchainErr { FailedSigning, SubmitTransaction, HttpJsonParsingError, HttpBytesParsingError, HttpRequestError(HttpError), RequestUncompleted, HttpResponseNotOk(u16), ErrorInEvmResponse, 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::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."), 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"), }, 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::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), } } } pub type NetworkIdOf = <::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId; pub type BalanceOf = <::Currency as Inspect< ::AccountId, >>::Balance; pub type ValidatorId = <::ValidatorSet as ValidatorSet< ::AccountId, >>::ValidatorId; pub type IdentificationTuple = ( ValidatorId, <::ValidatorSet as ValidatorSetWithIdentification< ::AccountId, >>::Identification, ); type OffchainResult = Result>>; #[frame_support::pallet] pub mod pallet { use super::*; const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::config] pub trait Config: SendTransactionTypes> + frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; type AuthorityId: Member + Parameter + RuntimeAppPublic + Ord + MaybeSerializeDeserialize + MaxEncodedLen; type NextSessionRotation: EstimateNextSessionRotation>; type ValidatorSet: ValidatorSetWithIdentification; type Currency: Inspect + Mutate; type NetworkDataHandler: NetworkDataBasicHandler + NetworkDataInspectHandler + NetworkDataMutateHandler>; type BlockNumberProvider: BlockNumberProvider>; type ReportUnresponsiveness: ReportOffence< Self::AccountId, IdentificationTuple, ThrottlingOffence>, >; #[pallet::constant] type MaxAuthorities: Get; #[pallet::constant] type ApplauseThreshold: Get; #[pallet::constant] type OffenceThreshold: Get; #[pallet::constant] type UnsignedPriority: Get; #[pallet::constant] type TreasuryPalletId: Get; type WeightInfo: WeightInfo; } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { AuthoritiesEquilibrium, SomeAuthoritiesTrottling { throttling: Vec> }, Clapped { authority_id: AuthIndex, network_id: NetworkIdOf, transaction_hash: H256, receiver: T::AccountId, amount: BalanceOf, }, Applaused { network_id: NetworkIdOf, receiver: T::AccountId, received_amount: BalanceOf, }, } #[pallet::error] pub enum Error { NotAnAuthority, ClapForThePastSession, CurrentValidatorIsDisabled, AlreadyClapped, UnregisteredClapRemove, TooMuchAuthorities, NotEnoughToApplause, CouldNotAccumulateCommission, CouldNotIncreaseGatekeeperAmount, } #[pallet::storage] #[pallet::getter(fn received_claps)] pub(super) type ReceivedClaps = StorageNMap< _, ( NMapKey, NMapKey, NMapKey, ), BoundedBTreeSet, ValueQuery >; #[pallet::storage] #[pallet::getter(fn applauses_for_transaction)] pub(super) type ApplausesForTransaction = StorageNMap< _, ( NMapKey, NMapKey, NMapKey, ), bool, ValueQuery >; #[pallet::storage] #[pallet::getter(fn authorities_claps)] pub(super) type AuthoritiesClaps = StorageDoubleMap< _, Twox64Concat, T::AuthorityId, Twox64Concat, H256, bool, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn claps_in_session)] pub(super) type ClapsInSession = StorageMap< _, Twox64Concat, SessionIndex, AuthorityClapsInfo, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn keys)] pub(super) type Authorities = StorageValue<_, WeakBoundedVec, ValueQuery>; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub keys: Vec, } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { Pallet::::initialize_authorities(&self.keys); } } #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[pallet::weight((T::WeightInfo::slow_clap(), DispatchClass::Normal, Pays::No))] pub fn slow_clap( origin: OriginFor, 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::try_slow_clap(&clap)?; Ok(()) } #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::applause())] pub fn applause( origin: OriginFor, _clap: Clap, BalanceOf>, ) -> DispatchResult { let _ = ensure_signed(origin)?; Ok(()) } } #[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, ); } } } } else { log::info!( target: LOG_TARGET, "🤥 Not a validator, skipping slow clap at {:?}.", now, ); } } } #[pallet::validate_unsigned] impl ValidateUnsigned for Pallet { type Call = Call; fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { if let Call::slow_clap { clap, signature } = call { let authorities = Authorities::::get(); let authority = match authorities.get(clap.authority_index as usize) { Some(authority) => authority, None => return InvalidTransaction::BadProof.into(), }; let signature_valid = clap.using_encoded(|encoded_clap| { authority.verify(&encoded_clap, signature) }); if !signature_valid { return InvalidTransaction::BadProof.into(); } ValidTransaction::with_tag_prefix("SlowClap") .priority(T::UnsignedPriority::get()) .and_provides(authority) .longevity(LOCK_BLOCK_EXPIRATION) .propagate(true) .build() } else { InvalidTransaction::Call.into() } } } } impl Pallet { 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 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 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 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), } })?; 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); } (*claps_details).total.saturating_inc(); (*claps_details).individual .entry(clap.authority_index) .and_modify(|individual| (*individual).claps.saturating_inc()) .or_default(); Ok(()) })?; 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 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( block_number: BlockNumberFor, networks_len: usize, ) -> 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"); Ok(Self::local_authorities().map(move |(authority_index, authority_key)| { Self::do_evm_claps_or_save_block( authority_index, authority_key, session_index, 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, network_data: &NetworkData, ) -> OffchainResult { let network_id_encoded = network_id.encode(); 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 rpc_endpoint = StorageValueRef::persistent(&endpoint_key) .get() .ok() .flatten() .unwrap_or(network_data.default_endpoint.clone()); 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), }; 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 {:?}", new_evm_block, network_id, ); Ok(new_evm_block) }, EvmResponseType::TransactionLogs(evm_logs) => { let claps: Vec<_> = evm_logs .iter() .filter_map(|log| log.is_sufficient().then(|| { Clap { authority_index, session_index, network_id, removed: log.removed, receiver: T::AccountId::decode(&mut &log.topics[1][0..32]) .expect("32 bytes always construct an AccountId32"), amount: u128::from_be_bytes(log.topics[2][16..32] .try_into() .expect("amount is valid hex; qed")) .saturated_into::>(), transaction_hash: log.transaction_hash .clone() .expect("tx hash exists; qed"), block_number: log.block_number .expect("block number exists; qed"), } })) .collect(); log::info!( target: LOG_TARGET, "🧐 {:?} evm logs found for network {:?}", claps.len(), network_id, ); 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)?; } Ok(0u64) } } } fn local_authorities() -> impl Iterator { let authorities = Authorities::::get(); let mut local_authorities = T::AuthorityId::all(); local_authorities.sort(); authorities.into_iter().enumerate().filter_map(move |(index, authority)| { local_authorities .binary_search(&authority) .ok() .map(|location| (index as u32, local_authorities[location].clone())) }) } fn fetch_from_remote( rpc_endpoint: &[u8], request_body: &[u8], ) -> OffchainResult> { 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 deadline = sp_io::offchain::timestamp().add(rt_offchain::Duration::from_millis(FETCH_TIMEOUT_PERIOD)); 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))?; let response = pending .try_wait(deadline) .map_err(|_| OffchainErr::RequestUncompleted)? .map_err(|_| OffchainErr::RequestUncompleted)?; if response.code != 200 { return Err(OffchainErr::HttpResponseNotOk(response.code)) } Ok(response.body().collect::>()) } 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(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(network_data.topic_name.to_vec()); body.extend(b"\"]}]}".to_vec()); body }, _ => Default::default(), } } fn parse_evm_response(response_bytes: &[u8]) -> OffchainResult { let response_str = sp_std::str::from_utf8(&response_bytes) .map_err(|_| OffchainErr::HttpBytesParsingError)?; let response_result: EvmResponse = serde_json::from_str(&response_str) .map_err(|_| OffchainErr::HttpJsonParsingError)?; if response_result.error.is_some() { return Err(OffchainErr::ErrorInEvmResponse); } Ok(response_result.result.ok_or(OffchainErr::ErrorInEvmResponse)?) } fn is_good_actor( authority_index: usize, session_index: SessionIndex, average_claps: u32, ) -> bool { if average_claps == 0 { return true; } 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]) { if !authorities.is_empty() { assert!(Authorities::::get().is_empty(), "Authorities are already initilized!"); let bounded_authorities = BoundedSlice::<'_, _, T::MaxAuthorities>::try_from(authorities) .expect("more than the maximum number of clappers"); Authorities::::put(bounded_authorities); } } } impl sp_runtime::BoundToRuntimeAppPublic for Pallet { type Public = T::AuthorityId; } impl BlockNumberProvider for Pallet { type BlockNumber = BlockNumberFor; fn current_block_number() -> Self::BlockNumber { T::BlockNumberProvider::current_block_number() } } impl OneSessionHandler for Pallet { type Key = T::AuthorityId; fn on_genesis_session<'a, I: 'a>(validators: I) where I: Iterator, { let authorities = validators.map(|x| x.1).collect::>(); Self::initialize_authorities(&authorities); } 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::>(); 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_details = ClapsInSession::::get(session_index); let total_claps_in_session = claps_details.total; let average_claps = claps_details .individual .iter() .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() .filter(|(index, _)| !Self::is_good_actor(*index, session_index, average_claps)) .filter_map(|(_, id)| { >::IdentificationOf::convert( id.clone(), ).map(|full_id| (id, full_id)) }) .collect::>>(); if offenders.is_empty() { Self::deposit_event(Event::::AuthoritiesEquilibrium); } else { 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 }; if let Err(e) = T::ReportUnresponsiveness::report_offence(vec![], offence) { sp_runtime::print(e) } } } fn on_disabled(validator_index: u32) { let session_index = T::ValidatorSet::session_index(); 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 { pub session_index: SessionIndex, pub validator_set_count: u32, pub offenders: Vec } impl Offence for ThrottlingOffence { const ID: Kind = *b"slow-clap:throtl"; type TimeSlot = SessionIndex; fn offenders(&self) -> Vec { self.offenders.clone() } fn session_index(&self) -> SessionIndex { self.session_index } fn validator_set_count(&self) -> u32 { self.validator_set_count } fn time_slot(&self) -> Self::TimeSlot { self.session_index } fn slash_fraction(&self, offenders_count: u32) -> Perbill { if let Some(threshold) = offenders_count.checked_sub(self.validator_set_count / 10 + 1) { let x = Perbill::from_rational(3 * threshold, self.validator_set_count); x.saturating_mul(Perbill::from_percent(7)) } else { Perbill::default() } } }