2024-10-03 15:38:52 +03:00
// 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 ::{
2025-05-31 14:30:15 +03:00
tokens ::fungible ::{ Inspect , Mutate } ,
2024-10-03 15:38:52 +03:00
EstimateNextSessionRotation , ValidatorSet , ValidatorSetWithIdentification ,
2025-05-31 14:30:15 +03:00
OneSessionHandler , Get ,
2024-10-03 15:38:52 +03:00
} ,
2025-06-11 22:56:48 +03:00
WeakBoundedVec ,
2024-10-03 15:38:52 +03:00
} ;
use frame_system ::{
offchain ::{ SendTransactionTypes , SubmitTransaction } ,
pallet_prelude ::* ,
} ;
2025-05-31 14:30:15 +03:00
use sp_core ::H256 ;
2024-10-03 15:38:52 +03:00
use sp_runtime ::{
2025-05-31 14:30:15 +03:00
Perbill , RuntimeAppPublic , RuntimeDebug , SaturatedConversion ,
2024-10-03 15:38:52 +03:00
offchain ::{
2025-05-31 14:30:15 +03:00
self as rt_offchain , HttpError ,
storage ::{ MutateStorageError , StorageRetrievalError , StorageValueRef } ,
2024-10-03 15:38:52 +03:00
} ,
2025-06-13 16:42:14 +03:00
traits ::{ BlockNumberProvider , Convert , Saturating } ,
2024-10-03 15:38:52 +03:00
} ;
use sp_std ::{
vec ::Vec , prelude ::* ,
collections ::btree_map ::BTreeMap ,
} ;
use sp_staking ::{
offence ::{ Kind , Offence , ReportOffence } ,
SessionIndex ,
} ;
use ghost_networks ::{
NetworkData , NetworkDataBasicHandler , NetworkDataInspectHandler ,
2025-05-31 14:30:15 +03:00
NetworkDataMutateHandler , NetworkType ,
2024-10-03 15:38:52 +03:00
} ;
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 ;
2025-05-31 14:30:15 +03:00
const LOCK_BLOCK_EXPIRATION : u64 = 10 ;
const NUMBER_OF_TOPICS : usize = 3 ;
2024-10-03 15:38:52 +03:00
pub type AuthIndex = u32 ;
#[ derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode) ]
struct EvmResponse {
#[ serde(default) ]
id : Option < u32 > ,
#[ serde(default, deserialize_with = " de_string_to_bytes " ) ]
jsonrpc : Option < Vec < u8 > > ,
#[ serde(default, deserialize_with = " de_string_to_bytes " ) ]
error : Option < Vec < u8 > > ,
#[ serde(default) ]
result : Option < EvmResponseType > ,
}
#[ derive(RuntimeDebug, Clone, PartialEq, Deserialize, Encode, Decode) ]
#[ serde(untagged) ]
enum EvmResponseType {
#[ serde(deserialize_with = " de_string_to_u64_pure " ) ]
BlockNumber ( u64 ) ,
TransactionLogs ( Vec < Log > ) ,
}
#[ 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 < H256 > ,
#[ serde(default, deserialize_with = " de_string_to_u64 " ) ]
block_number : Option < u64 > ,
#[ serde(default, deserialize_with = " de_string_to_vec_of_bytes " ) ]
topics : Vec < Vec < u8 > > ,
#[ serde(default, deserialize_with = " de_string_to_bytes " ) ]
address : Option < Vec < u8 > > ,
#[ serde(default, deserialize_with = " de_string_to_btree_map " ) ]
data : BTreeMap < u128 , u128 > ,
removed : bool ,
}
2025-05-31 14:30:15 +03:00
impl Log {
fn is_sufficient ( & self ) -> bool {
self . transaction_hash . is_some ( ) & &
self . block_number . is_some ( ) & &
self . topics . len ( ) = = NUMBER_OF_TOPICS
}
}
2024-10-03 15:38:52 +03:00
pub fn de_string_to_bytes < ' de , D > ( de : D ) -> Result < Option < Vec < u8 > > , 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 < Option < u64 > , 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 < u64 , 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 ) . unwrap_or_default ( ) )
}
pub fn de_string_to_h256 < ' de , D > ( de : D ) -> Result < Option < H256 > , 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 < Vec < Vec < u8 > > , 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 ::< Vec < u8 > > ( )
} )
. collect ::< Vec < Vec < u8 > > > ( ) )
}
pub fn de_string_to_btree_map < ' de , D > ( de : D ) -> Result < BTreeMap < u128 , u128 > , 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 < AccountId , NetworkId , Balance > {
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 ,
}
2025-05-31 14:30:15 +03:00
#[ derive(Default, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo) ]
2024-10-03 15:38:52 +03:00
pub struct SessionAuthorityInfo {
2025-05-31 14:30:15 +03:00
pub claps : u32 ,
2024-10-03 15:38:52 +03:00
pub disabled : bool ,
}
#[ cfg_attr(test, derive(PartialEq)) ]
enum OffchainErr < NetworkId > {
FailedSigning ,
SubmitTransaction ,
HttpJsonParsingError ,
HttpBytesParsingError ,
HttpRequestError ( HttpError ) ,
RequestUncompleted ,
HttpResponseNotOk ( u16 ) ,
ErrorInEvmResponse ,
2025-05-31 14:30:15 +03:00
StorageRetrievalError ( NetworkId ) ,
ConcurrentModificationError ( NetworkId ) ,
UtxoNotImplemented ( NetworkId ) ,
UnknownNetworkType ( NetworkId ) ,
2024-10-03 15:38:52 +03:00
}
impl < NetworkId : core ::fmt ::Debug > core ::fmt ::Debug for OffchainErr < NetworkId > {
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 " ) ,
} ,
2025-05-31 14:30:15 +03:00
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 ) ,
2024-10-03 15:38:52 +03:00
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. " ) ,
2025-05-31 14:30:15 +03:00
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 ) ,
2024-10-03 15:38:52 +03:00
}
}
}
pub type NetworkIdOf < T > =
< < T as Config > ::NetworkDataHandler as NetworkDataBasicHandler > ::NetworkId ;
pub type BalanceOf < T > = < < T as Config > ::Currency as Inspect <
< T as frame_system ::Config > ::AccountId ,
> > ::Balance ;
pub type ValidatorId < T > = < < T as Config > ::ValidatorSet as ValidatorSet <
< T as frame_system ::Config > ::AccountId ,
> > ::ValidatorId ;
pub type IdentificationTuple < T > = (
ValidatorId < T > ,
< < T as Config > ::ValidatorSet as ValidatorSetWithIdentification <
< T as frame_system ::Config > ::AccountId ,
> > ::Identification ,
) ;
type OffchainResult < T , A > = Result < A , OffchainErr < NetworkIdOf < T > > > ;
#[ 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 < T > ( _ ) ;
#[ pallet::config ]
pub trait Config : SendTransactionTypes < Call < Self > > + frame_system ::Config {
type RuntimeEvent : From < Event < Self > > + IsType < < Self as frame_system ::Config > ::RuntimeEvent > ;
type AuthorityId : Member
+ Parameter
+ RuntimeAppPublic
+ Ord
+ MaybeSerializeDeserialize
+ MaxEncodedLen ;
type NextSessionRotation : EstimateNextSessionRotation < BlockNumberFor < Self > > ;
type ValidatorSet : ValidatorSetWithIdentification < Self ::AccountId > ;
type Currency : Inspect < Self ::AccountId > + Mutate < Self ::AccountId > ;
type NetworkDataHandler : NetworkDataBasicHandler +
NetworkDataInspectHandler < NetworkData > +
2025-05-31 14:30:15 +03:00
NetworkDataMutateHandler < NetworkData , BalanceOf < Self > > ;
2024-10-03 15:38:52 +03:00
type BlockNumberProvider : BlockNumberProvider < BlockNumber = BlockNumberFor < Self > > ;
type ReportUnresponsiveness : ReportOffence <
Self ::AccountId ,
IdentificationTuple < Self > ,
ThrottlingOffence < IdentificationTuple < Self > > ,
> ;
#[ pallet::constant ]
type MaxAuthorities : Get < u32 > ;
#[ pallet::constant ]
type ApplauseThreshold : Get < u32 > ;
#[ pallet::constant ]
type OffenceThreshold : Get < u32 > ;
#[ pallet::constant ]
type UnsignedPriority : Get < TransactionPriority > ;
2025-06-18 13:47:13 +03:00
#[ pallet::constant ]
type HistoryDepth : Get < SessionIndex > ;
2024-10-03 15:38:52 +03:00
type WeightInfo : WeightInfo ;
}
#[ pallet::event ]
#[ pallet::generate_deposit(pub(super) fn deposit_event) ]
pub enum Event < T : Config > {
2025-05-31 14:30:15 +03:00
AuthoritiesEquilibrium ,
SomeAuthoritiesTrottling { throttling : Vec < IdentificationTuple < T > > } ,
2024-10-03 15:38:52 +03:00
Clapped {
2025-05-31 14:30:15 +03:00
authority_id : AuthIndex ,
2024-10-03 15:38:52 +03:00
network_id : NetworkIdOf < T > ,
transaction_hash : H256 ,
receiver : T ::AccountId ,
amount : BalanceOf < T > ,
} ,
Applaused {
network_id : NetworkIdOf < T > ,
receiver : T ::AccountId ,
received_amount : BalanceOf < T > ,
} ,
}
#[ pallet::error ]
pub enum Error < T > {
2025-06-04 15:44:53 +03:00
NotEnoughClaps ,
2024-10-03 15:38:52 +03:00
NotAnAuthority ,
CurrentValidatorIsDisabled ,
AlreadyClapped ,
2025-05-31 14:30:15 +03:00
UnregisteredClapRemove ,
TooMuchAuthorities ,
CouldNotAccumulateCommission ,
2025-06-13 16:42:14 +03:00
CouldNotAccumulateIncomingImbalance ,
2025-05-31 14:30:15 +03:00
CouldNotIncreaseGatekeeperAmount ,
2024-10-03 15:38:52 +03:00
}
2025-05-31 14:30:15 +03:00
2024-10-03 15:38:52 +03:00
#[ pallet::storage ]
#[ pallet::getter(fn received_claps) ]
2025-05-31 14:30:15 +03:00
pub ( super ) type ReceivedClaps < T : Config > = StorageNMap <
2024-10-03 15:38:52 +03:00
_ ,
2025-05-31 14:30:15 +03:00
(
2025-06-18 13:47:13 +03:00
NMapKey < Twox64Concat , SessionIndex > ,
2025-05-31 14:30:15 +03:00
NMapKey < Twox64Concat , H256 > ,
NMapKey < Twox64Concat , H256 > ,
) ,
2025-06-18 13:47:13 +03:00
BoundedBTreeSet < AuthIndex , T ::MaxAuthorities > ,
2024-10-03 15:38:52 +03:00
ValueQuery
> ;
#[ pallet::storage ]
#[ pallet::getter(fn applauses_for_transaction) ]
2025-05-31 14:30:15 +03:00
pub ( super ) type ApplausesForTransaction < T : Config > = StorageNMap <
2024-10-03 15:38:52 +03:00
_ ,
2025-05-31 14:30:15 +03:00
(
2025-06-18 13:47:13 +03:00
NMapKey < Twox64Concat , SessionIndex > ,
2025-05-31 14:30:15 +03:00
NMapKey < Twox64Concat , H256 > ,
NMapKey < Twox64Concat , H256 > ,
) ,
2024-10-03 15:38:52 +03:00
bool ,
ValueQuery
> ;
#[ pallet::storage ]
2025-05-31 14:30:15 +03:00
#[ pallet::getter(fn claps_in_session) ]
pub ( super ) type ClapsInSession < T : Config > = StorageMap <
2024-10-03 15:38:52 +03:00
_ ,
Twox64Concat ,
SessionIndex ,
2025-06-02 19:20:14 +03:00
BTreeMap < AuthIndex , SessionAuthorityInfo > ,
2024-10-03 15:38:52 +03:00
ValueQuery ,
> ;
#[ pallet::storage ]
2025-06-11 22:56:48 +03:00
#[ pallet::getter(fn authorities) ]
pub ( super ) type Authorities < T : Config > = StorageMap <
_ ,
Twox64Concat ,
SessionIndex ,
WeakBoundedVec < T ::AuthorityId , T ::MaxAuthorities > ,
ValueQuery ,
> ;
2024-10-03 15:38:52 +03:00
#[ pallet::genesis_config ]
#[ derive(frame_support::DefaultNoBound) ]
pub struct GenesisConfig < T : Config > {
2025-06-11 22:56:48 +03:00
pub authorities : Vec < T ::AuthorityId > ,
2024-10-03 15:38:52 +03:00
}
#[ pallet::genesis_build ]
impl < T : Config > BuildGenesisConfig for GenesisConfig < T > {
fn build ( & self ) {
2025-06-11 22:56:48 +03:00
if ! self . authorities . is_empty ( ) {
Pallet ::< T > ::initialize_authorities ( self . authorities . clone ( ) ) ;
}
2024-10-03 15:38:52 +03:00
}
}
#[ pallet::call ]
impl < T : Config > Pallet < T > {
#[ pallet::call_index(0) ]
2025-05-31 14:30:15 +03:00
#[ pallet::weight((T::WeightInfo::slow_clap(), DispatchClass::Normal, Pays::No)) ]
2024-10-03 15:38:52 +03:00
pub fn slow_clap (
origin : OriginFor < T > ,
2025-05-31 14:30:15 +03:00
clap : Clap < T ::AccountId , NetworkIdOf < T > , BalanceOf < T > > ,
2024-10-03 15:38:52 +03:00
// since signature verification is done in `validate_unsigned`
// we can skip doing it here again.
_signature : < T ::AuthorityId as RuntimeAppPublic > ::Signature ,
) -> DispatchResult {
ensure_none ( origin ) ? ;
2025-05-31 14:30:15 +03:00
Self ::try_slow_clap ( & clap ) ? ;
2024-10-03 15:38:52 +03:00
Ok ( ( ) )
}
#[ pallet::call_index(1) ]
2025-06-04 15:44:53 +03:00
#[ pallet::weight(T::WeightInfo::self_applause()) ]
pub fn self_applause (
2024-10-03 15:38:52 +03:00
origin : OriginFor < T > ,
2025-06-04 15:44:53 +03:00
network_id : NetworkIdOf < T > ,
session_index : SessionIndex ,
transaction_hash : H256 ,
receiver : T ::AccountId ,
amount : BalanceOf < T > ,
2024-10-03 15:38:52 +03:00
) -> DispatchResult {
2025-05-31 14:30:15 +03:00
let _ = ensure_signed ( origin ) ? ;
2025-06-04 15:44:53 +03:00
Self ::applause_if_posible (
network_id ,
session_index ,
transaction_hash ,
receiver ,
amount ,
)
2024-10-03 15:38:52 +03:00
}
}
#[ pallet::hooks ]
impl < T : Config > Hooks < BlockNumberFor < T > > for Pallet < T > {
fn offchain_worker ( now : BlockNumberFor < T > ) {
// 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 {
2025-05-31 14:30:15 +03:00
log ::info! (
2024-10-03 15:38:52 +03:00
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 < T : Config > ValidateUnsigned for Pallet < T > {
type Call = Call < T > ;
fn validate_unsigned ( _source : TransactionSource , call : & Self ::Call ) -> TransactionValidity {
2025-05-31 14:30:15 +03:00
if let Call ::slow_clap { clap , signature } = call {
2025-06-11 22:56:48 +03:00
let authorities = Authorities ::< T > ::get ( & clap . session_index ) ;
2025-05-31 14:30:15 +03:00
let authority = match authorities . get ( clap . authority_index as usize ) {
Some ( authority ) = > authority ,
2025-06-02 19:20:14 +03:00
None = > return InvalidTransaction ::BadSigner . into ( ) ,
2024-10-03 15:38:52 +03:00
} ;
2025-05-31 14:30:15 +03:00
let signature_valid = clap . using_encoded ( | encoded_clap | {
authority . verify ( & encoded_clap , signature )
2024-10-03 15:38:52 +03:00
} ) ;
if ! signature_valid {
return InvalidTransaction ::BadProof . into ( ) ;
}
ValidTransaction ::with_tag_prefix ( " SlowClap " )
. priority ( T ::UnsignedPriority ::get ( ) )
2025-06-17 13:50:38 +03:00
. and_provides ( signature )
2025-05-31 14:30:15 +03:00
. longevity ( LOCK_BLOCK_EXPIRATION )
2024-10-03 15:38:52 +03:00
. propagate ( true )
. build ( )
} else {
InvalidTransaction ::Call . into ( )
}
}
}
}
impl < T : Config > Pallet < T > {
2025-05-31 14:30:15 +03:00
fn generate_unique_hash (
receiver : & T ::AccountId ,
amount : & BalanceOf < T > ,
network_id : & NetworkIdOf < T > ,
) -> 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 ) [ .. ] )
2024-10-03 15:38:52 +03:00
}
2025-05-31 14:30:15 +03:00
fn try_slow_clap ( clap : & Clap < T ::AccountId , NetworkIdOf < T > , BalanceOf < T > > ) -> DispatchResult {
2025-06-11 22:56:48 +03:00
let authorities = Authorities ::< T > ::get ( & clap . session_index ) ;
2025-05-31 14:30:15 +03:00
ensure! ( authorities . get ( clap . authority_index as usize ) . is_some ( ) , Error ::< T > ::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 ::< T > ::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 ::< T > ::UnregisteredClapRemove ) ,
( true , false ) = > Err ( Error ::< T > ::AlreadyClapped ) ,
( false , true ) = > Err ( Error ::< T > ::UnregisteredClapRemove ) ,
( false , false ) = > tree_of_claps
. try_insert ( clap . authority_index )
. map ( | _ | number_of_claps . saturating_add ( 1 ) )
. map_err ( | _ | Error ::< T > ::TooMuchAuthorities ) ,
}
} ) ? ;
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
ClapsInSession ::< T > ::try_mutate ( & clap . session_index , | claps_details | {
2025-06-02 19:20:14 +03:00
if claps_details . get ( & clap . authority_index ) . map ( | x | x . disabled ) . unwrap_or_default ( ) {
2025-05-31 14:30:15 +03:00
return Err ( Error ::< T > ::CurrentValidatorIsDisabled ) ;
2024-10-03 15:38:52 +03:00
}
2025-06-02 19:20:14 +03:00
( * claps_details )
2025-05-31 14:30:15 +03:00
. entry ( clap . authority_index )
. and_modify ( | individual | ( * individual ) . claps . saturating_inc ( ) )
2025-06-02 19:20:14 +03:00
. or_insert ( SessionAuthorityInfo { claps : 1 u32 , disabled : false } ) ;
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
Ok ( ( ) )
} ) ? ;
Self ::deposit_event ( Event ::< T > ::Clapped {
authority_id : clap . authority_index ,
network_id : clap . network_id ,
transaction_hash : clap . transaction_hash ,
receiver : clap . receiver . clone ( ) ,
amount : clap . amount ,
} ) ;
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
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 {
2025-06-18 17:03:09 +03:00
if let Err ( error_msg ) = Self ::try_applause ( & clap , & received_claps_key ) {
log ::info! (
target : LOG_TARGET ,
" 👏 Could not applause because of: {:?} " ,
error_msg ,
) ;
}
2024-10-03 15:38:52 +03:00
}
Ok ( ( ) )
}
2025-05-31 14:30:15 +03:00
fn try_applause (
clap : & Clap < T ::AccountId , NetworkIdOf < T > , BalanceOf < T > > ,
received_claps_key : & ( SessionIndex , & H256 , & H256 ) ,
2024-10-03 15:38:52 +03:00
) -> DispatchResult {
2025-05-31 14:30:15 +03:00
ApplausesForTransaction ::< T > ::try_mutate ( received_claps_key , | is_applaused | {
2025-06-03 20:41:51 +03:00
if * is_applaused | | T ::NetworkDataHandler ::is_nullification_period ( ) {
2025-05-31 14:30:15 +03:00
return Ok ( ( ) )
}
2024-10-03 15:38:52 +03:00
2025-06-04 16:33:03 +03:00
let commission = T ::NetworkDataHandler ::get ( & clap . network_id )
2025-05-31 14:30:15 +03:00
. map ( | network_data | Perbill ::from_parts ( network_data . incoming_fee ) )
2025-06-04 16:33:03 +03:00
. unwrap_or_default ( )
. mul_ceil ( clap . amount ) ;
2025-06-13 16:42:14 +03:00
let final_amount = clap . amount . saturating_sub ( commission ) ;
2025-05-31 14:30:15 +03:00
2025-06-13 16:42:14 +03:00
let _ = T ::NetworkDataHandler ::increase_gatekeeper_amount ( & clap . network_id , & clap . amount )
2025-05-31 14:30:15 +03:00
. map_err ( | _ | Error ::< T > ::CouldNotIncreaseGatekeeperAmount ) ? ;
2025-06-13 16:42:14 +03:00
let _ = T ::NetworkDataHandler ::accumulate_incoming_imbalance ( & final_amount )
. map_err ( | _ | Error ::< T > ::CouldNotAccumulateIncomingImbalance ) ? ;
2025-05-31 14:30:15 +03:00
let _ = T ::NetworkDataHandler ::accumulate_commission ( & commission )
. map_err ( | _ | Error ::< T > ::CouldNotAccumulateCommission ) ? ;
2024-10-03 15:38:52 +03:00
2025-06-13 16:42:14 +03:00
if final_amount > T ::Currency ::minimum_balance ( ) {
T ::Currency ::mint_into (
& clap . receiver ,
final_amount
) ? ;
}
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
* is_applaused = true ;
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
Self ::deposit_event ( Event ::< T > ::Applaused {
network_id : clap . network_id ,
receiver : clap . receiver . clone ( ) ,
received_amount : final_amount ,
} ) ;
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
Ok ( ( ) )
} )
2024-10-03 15:38:52 +03:00
}
2025-06-04 15:44:53 +03:00
fn applause_if_posible (
network_id : NetworkIdOf < T > ,
session_index : SessionIndex ,
transaction_hash : H256 ,
receiver : T ::AccountId ,
amount : BalanceOf < T > ,
) -> DispatchResult {
let clap_unique_hash = Self ::generate_unique_hash ( & receiver , & amount , & network_id ) ;
let received_claps_key = ( session_index , & transaction_hash , & clap_unique_hash ) ;
let clap = Clap {
authority_index : Default ::default ( ) ,
block_number : Default ::default ( ) ,
removed : false ,
session_index ,
network_id ,
receiver ,
amount ,
transaction_hash ,
} ;
let enough_authorities = Perbill ::from_rational (
ReceivedClaps ::< T > ::get ( & received_claps_key ) . len ( ) as u32 ,
2025-06-11 22:56:48 +03:00
Authorities ::< T > ::get ( session_index ) . len ( ) as u32 ,
2025-06-04 15:44:53 +03:00
) > Perbill ::from_percent ( T ::ApplauseThreshold ::get ( ) ) ;
ensure! ( enough_authorities , Error ::< T > ::NotEnoughClaps ) ;
Self ::try_applause ( & clap , & received_claps_key ) ? ;
Ok ( ( ) )
}
2024-10-03 15:38:52 +03:00
fn start_slow_clapping (
block_number : BlockNumberFor < T > ,
networks_len : usize ,
) -> OffchainResult < T , impl Iterator < Item = OffchainResult < T , ( ) > > > {
let session_index = T ::ValidatorSet ::session_index ( ) ;
let network_in_use = T ::NetworkDataHandler ::iter ( )
2025-05-31 14:30:15 +03:00
. nth ( block_number . into ( ) . as_usize ( ) % networks_len )
2024-10-03 15:38:52 +03:00
. 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 ,
2025-05-31 14:30:15 +03:00
network_in_use . 0 ,
& network_in_use . 1 ,
2024-10-03 15:38:52 +03:00
)
} ) )
}
2025-05-31 14:30:15 +03:00
fn create_storage_key ( first : & [ u8 ] , second : & [ u8 ] ) -> Vec < u8 > {
let mut key = DB_PREFIX . to_vec ( ) ;
key . extend ( first ) ;
key . extend ( second ) ;
key
}
2024-10-03 15:38:52 +03:00
fn do_evm_claps_or_save_block (
authority_index : AuthIndex ,
authority_key : T ::AuthorityId ,
session_index : SessionIndex ,
network_id : NetworkIdOf < T > ,
2025-05-31 14:30:15 +03:00
network_data : & NetworkData ,
2024-10-03 15:38:52 +03:00
) -> OffchainResult < T , ( ) > {
2025-05-31 14:30:15 +03:00
let network_id_encoded = network_id . encode ( ) ;
let block_number_key = Self ::create_storage_key ( b " block- " , & network_id_encoded ) ;
2025-06-19 14:31:33 +03:00
let block_distance_key = Self ::create_storage_key ( b " block-distance- " , & network_id_encoded ) ;
2025-05-31 14:30:15 +03:00
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 ( ) ) ;
2025-06-19 14:31:33 +03:00
let max_block_distance = StorageValueRef ::persistent ( & block_distance_key )
. get ( )
. ok ( )
. flatten ( )
. unwrap_or ( network_data . block_distance ) ;
2025-05-31 14:30:15 +03:00
let mutation_result = StorageValueRef ::persistent ( & block_number_key ) . mutate ( | result_block_range : Result < Option < ( u64 , u64 ) > , StorageRetrievalError > | {
match result_block_range {
Ok ( maybe_block_range ) = > {
let request_body = match maybe_block_range {
2025-06-18 17:20:25 +03:00
Some ( ( from_block , to_block ) ) if from_block < to_block . saturating_sub ( 1 ) = >
Self ::prepare_request_body_for_latest_transfers ( from_block , to_block . saturating_sub ( 1 ) , network_data ) ,
2025-05-31 14:30:15 +03:00
_ = > 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 = > {
2025-06-19 14:31:33 +03:00
let maybe_new_evm_block = Self ::apply_evm_response (
2025-05-31 14:30:15 +03:00
& response_bytes ,
authority_index ,
authority_key ,
session_index ,
network_id
) ? ;
2025-06-19 14:31:33 +03:00
let estimated_block = maybe_new_evm_block
2025-06-19 15:00:30 +03:00
. map ( | new_evm_block | new_evm_block . saturating_sub ( network_data . finality_delay ) )
2025-06-19 14:31:33 +03:00
. unwrap_or_default ( ) ;
2025-05-31 14:30:15 +03:00
Ok ( match maybe_block_range {
2025-06-19 14:31:33 +03:00
Some ( ( from_block , to_block ) ) = > match maybe_new_evm_block {
Some ( _ ) = > match estimated_block . checked_sub ( from_block ) {
Some ( current_distance ) if current_distance < max_block_distance = >
( from_block , estimated_block ) ,
_ = > ( from_block , from_block
. saturating_add ( max_block_distance )
. min ( estimated_block ) ) ,
} ,
None = > ( to_block , to_block ) ,
2025-05-31 14:30:15 +03:00
} ,
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 ( ) )
}
} ) ;
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
match mutation_result {
Ok ( _ ) = > Ok ( ( ) ) ,
Err ( MutateStorageError ::ValueFunctionFailed ( offchain_error ) ) = > Err ( offchain_error ) ,
Err ( MutateStorageError ::ConcurrentModification ( _ ) ) = > Err ( OffchainErr ::ConcurrentModificationError ( network_id ) . into ( ) ) ,
}
}
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
fn apply_evm_response (
response_bytes : & [ u8 ] ,
authority_index : AuthIndex ,
authority_key : T ::AuthorityId ,
session_index : SessionIndex ,
network_id : NetworkIdOf < T >
2025-06-19 14:31:33 +03:00
) -> OffchainResult < T , Option < u64 > > {
2025-05-31 14:30:15 +03:00
match Self ::parse_evm_response ( & response_bytes ) ? {
EvmResponseType ::BlockNumber ( new_evm_block ) = > {
2024-10-03 15:38:52 +03:00
log ::info! (
target : LOG_TARGET ,
2025-05-31 14:30:15 +03:00
" 🧐 New evm block #{:?} found for network {:?} " ,
new_evm_block ,
2024-10-03 15:38:52 +03:00
network_id ,
) ;
2025-06-19 14:31:33 +03:00
Ok ( Some ( new_evm_block ) )
2024-10-03 15:38:52 +03:00
} ,
EvmResponseType ::TransactionLogs ( evm_logs ) = > {
2025-05-31 14:30:15 +03:00
let claps : Vec < _ > = evm_logs
2024-10-03 15:38:52 +03:00
. iter ( )
2025-05-31 14:30:15 +03:00
. filter_map ( | log | log . is_sufficient ( ) . then ( | | {
Clap {
2024-10-03 15:38:52 +03:00
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 ::< BalanceOf < T > > ( ) ,
2025-05-31 14:30:15 +03:00
transaction_hash : log . transaction_hash
2024-10-03 15:38:52 +03:00
. clone ( )
. expect ( " tx hash exists; qed " ) ,
2025-05-31 14:30:15 +03:00
block_number : log . block_number
2024-10-03 15:38:52 +03:00
. expect ( " block number exists; qed " ) ,
2025-05-31 14:30:15 +03:00
}
} ) )
2024-10-03 15:38:52 +03:00
. collect ( ) ;
2025-05-31 14:30:15 +03:00
log ::info! (
target : LOG_TARGET ,
" 🧐 {:?} evm logs found for network {:?} " ,
claps . len ( ) ,
network_id ,
) ;
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
for clap in claps {
let signature = authority_key . sign ( & clap . encode ( ) )
. ok_or ( OffchainErr ::FailedSigning ) ? ;
let call = Call ::slow_clap { clap , signature } ;
2025-06-17 13:50:38 +03:00
2025-05-31 14:30:15 +03:00
SubmitTransaction ::< T , Call < T > > ::submit_unsigned_transaction ( call . into ( ) )
. map_err ( | _ | OffchainErr ::SubmitTransaction ) ? ;
}
2024-10-03 15:38:52 +03:00
2025-06-19 14:31:33 +03:00
Ok ( None )
2024-10-03 15:38:52 +03:00
}
}
}
fn local_authorities ( ) -> impl Iterator < Item = ( u32 , T ::AuthorityId ) > {
2025-06-11 22:56:48 +03:00
let session_index = T ::ValidatorSet ::session_index ( ) ;
let authorities = Authorities ::< T > ::get ( & session_index ) ;
2024-10-03 15:38:52 +03:00
let mut local_authorities = T ::AuthorityId ::all ( ) ;
local_authorities . sort ( ) ;
2025-06-17 13:50:38 +03:00
2024-10-03 15:38:52 +03:00
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 (
2025-05-31 14:30:15 +03:00
rpc_endpoint : & [ u8 ] ,
request_body : & [ u8 ] ,
2024-10-03 15:38:52 +03:00
) -> OffchainResult < T , Vec < u8 > > {
2025-05-31 14:30:15 +03:00
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 " ) ;
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
let deadline = sp_io ::offchain ::timestamp ( ) . add ( rt_offchain ::Duration ::from_millis ( FETCH_TIMEOUT_PERIOD ) ) ;
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
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 " )
2024-10-03 15:38:52 +03:00
. 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 ::< Vec < u8 > > ( ) )
}
2025-05-31 14:30:15 +03:00
fn u64_to_hexadecimal_bytes ( value : u64 ) -> Vec < u8 > {
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! ( ) ,
} ) ;
}
}
2024-10-03 15:38:52 +03:00
2025-05-31 14:30:15 +03:00
hex_str
}
fn prepare_request_body_for_latest_block ( network_data : & NetworkData ) -> Vec < u8 > {
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 < u8 > {
match network_data . network_type {
NetworkType ::Evm = > {
2024-10-03 15:38:52 +03:00
let mut body = b " { \" id \" :0, \" jsonrpc \" : \" 2.0 \" , \" method \" : \" eth_getLogs \" , \" params \" :[{ " . to_vec ( ) ;
2025-05-31 14:30:15 +03:00
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 ( ) ) ;
2024-10-03 15:38:52 +03:00
body . extend ( b " \" , \" topics \" :[ \" " . to_vec ( ) ) ;
2025-05-31 14:30:15 +03:00
body . extend ( network_data . topic_name . to_vec ( ) ) ;
2024-10-03 15:38:52 +03:00
body . extend ( b " \" ]}]} " . to_vec ( ) ) ;
body
} ,
2025-05-31 14:30:15 +03:00
_ = > Default ::default ( ) ,
}
2024-10-03 15:38:52 +03:00
}
2025-05-31 14:30:15 +03:00
fn parse_evm_response ( response_bytes : & [ u8 ] ) -> OffchainResult < T , EvmResponseType > {
let response_str = sp_std ::str ::from_utf8 ( & response_bytes )
2024-10-03 15:38:52 +03:00
. map_err ( | _ | OffchainErr ::HttpBytesParsingError ) ? ;
2025-05-31 14:30:15 +03:00
let response_result : EvmResponse = serde_json ::from_str ( & response_str )
2024-10-03 15:38:52 +03:00
. map_err ( | _ | OffchainErr ::HttpJsonParsingError ) ? ;
2025-05-31 14:30:15 +03:00
if response_result . error . is_some ( ) {
2024-10-03 15:38:52 +03:00
return Err ( OffchainErr ::ErrorInEvmResponse ) ;
}
2025-05-31 14:30:15 +03:00
Ok ( response_result . result . ok_or ( OffchainErr ::ErrorInEvmResponse ) ? )
2024-10-03 15:38:52 +03:00
}
fn is_good_actor (
authority_index : usize ,
session_index : SessionIndex ,
2025-06-03 19:38:07 +03:00
median_claps : u32 ,
2024-10-03 15:38:52 +03:00
) -> bool {
2025-06-03 19:38:07 +03:00
if median_claps = = 0 {
2025-01-30 15:12:55 +03:00
return true ;
}
2025-05-31 14:30:15 +03:00
let number_of_claps = ClapsInSession ::< T > ::get ( session_index )
. entry ( authority_index as AuthIndex )
. or_default ( )
. claps ;
2025-06-03 19:38:07 +03:00
let authority_deviation = if number_of_claps < median_claps {
Perbill ::from_rational ( median_claps - number_of_claps , median_claps )
2025-05-31 14:30:15 +03:00
} else {
2025-06-03 19:38:07 +03:00
Perbill ::from_rational ( number_of_claps - median_claps , median_claps )
2025-05-31 14:30:15 +03:00
} ;
authority_deviation < Perbill ::from_percent ( T ::OffenceThreshold ::get ( ) )
2024-10-03 15:38:52 +03:00
}
2025-06-11 22:56:48 +03:00
fn initialize_authorities ( authorities : Vec < T ::AuthorityId > ) {
let session_index = T ::ValidatorSet ::session_index ( ) ;
assert! ( Authorities ::< T > ::get ( & session_index ) . is_empty ( ) , " Authorities are already initilized! " ) ;
let bounded_authorities = WeakBoundedVec ::< _ , T ::MaxAuthorities > ::try_from ( authorities )
. expect ( " more than the maximum number of authorities " ) ;
2025-06-18 13:47:13 +03:00
if let Some ( target_session_index ) = session_index . checked_sub ( T ::HistoryDepth ::get ( ) ) {
Self ::clear_history ( & target_session_index ) ;
}
2025-06-11 22:56:48 +03:00
Authorities ::< T > ::set ( & session_index , bounded_authorities ) ;
ClapsInSession ::< T > ::set ( & session_index , Default ::default ( ) ) ;
2024-10-03 15:38:52 +03:00
}
2025-06-02 19:20:14 +03:00
2025-06-18 13:47:13 +03:00
fn clear_history ( target_session_index : & SessionIndex ) {
ClapsInSession ::< T > ::remove ( target_session_index ) ;
let mut cursor = ReceivedClaps ::< T > ::clear_prefix ( ( target_session_index , ) , u32 ::MAX , None ) ;
debug_assert! ( cursor . maybe_cursor . is_none ( ) ) ;
cursor = ApplausesForTransaction ::< T > ::clear_prefix ( ( target_session_index , ) , u32 ::MAX , None ) ;
debug_assert! ( cursor . maybe_cursor . is_none ( ) ) ;
}
2025-06-03 19:38:07 +03:00
fn calculate_median_claps ( session_index : & SessionIndex ) -> u32 {
let mut claps_in_session = ClapsInSession ::< T > ::get ( session_index )
. values ( )
. filter_map ( | value | ( ! value . disabled ) . then ( | | value . claps ) )
. collect ::< Vec < _ > > ( ) ;
if claps_in_session . is_empty ( ) {
return 0 ;
}
claps_in_session . sort ( ) ;
let number_of_claps = claps_in_session . len ( ) ;
if number_of_claps % 2 = = 0 {
let mid_left = claps_in_session [ number_of_claps / 2 - 1 ] ;
let mid_right = claps_in_session [ number_of_claps / 2 ] ;
( mid_left + mid_right ) / 2
} else {
claps_in_session [ number_of_claps / 2 ]
}
}
2025-06-02 19:20:14 +03:00
#[ cfg(test) ]
2025-06-11 22:56:48 +03:00
fn set_test_authorities ( session_index : SessionIndex , authorities : Vec < T ::AuthorityId > ) {
2025-06-02 19:20:14 +03:00
let bounded_authorities = WeakBoundedVec ::< _ , T ::MaxAuthorities > ::try_from ( authorities )
. expect ( " more than the maximum number of authorities " ) ;
2025-06-11 22:56:48 +03:00
Authorities ::< T > ::set ( session_index , bounded_authorities ) ;
2025-06-02 19:20:14 +03:00
}
2025-06-04 15:44:53 +03:00
#[ cfg(feature = " runtime-benchmarks " ) ]
fn trigger_nullification_for_benchmark ( ) {
T ::NetworkDataHandler ::trigger_nullification ( ) ;
}
2024-10-03 15:38:52 +03:00
}
impl < T : Config > sp_runtime ::BoundToRuntimeAppPublic for Pallet < T > {
type Public = T ::AuthorityId ;
}
impl < T : Config > BlockNumberProvider for Pallet < T > {
type BlockNumber = BlockNumberFor < T > ;
fn current_block_number ( ) -> Self ::BlockNumber {
T ::BlockNumberProvider ::current_block_number ( )
}
}
impl < T : Config > OneSessionHandler < T ::AccountId > for Pallet < T > {
type Key = T ::AuthorityId ;
fn on_genesis_session < ' a , I : ' a > ( validators : I )
where
I : Iterator < Item = ( & ' a T ::AccountId , T ::AuthorityId ) > ,
{
let authorities = validators . map ( | x | x . 1 ) . collect ::< Vec < _ > > ( ) ;
2025-06-11 22:56:48 +03:00
Self ::initialize_authorities ( authorities ) ;
2024-10-03 15:38:52 +03:00
}
fn on_new_session < ' a , I : ' a > ( _changed : bool , validators : I , _queued_validators : I )
where
I : Iterator < Item = ( & ' a T ::AccountId , T ::AuthorityId ) > ,
{
let authorities = validators . map ( | x | x . 1 ) . collect ::< Vec < _ > > ( ) ;
2025-06-11 22:56:48 +03:00
Self ::initialize_authorities ( authorities ) ;
2024-10-03 15:38:52 +03:00
}
fn on_before_session_ending ( ) {
let session_index = T ::ValidatorSet ::session_index ( ) ;
let validators = T ::ValidatorSet ::validators ( ) ;
2025-06-11 22:56:48 +03:00
let authorities = Authorities ::< T > ::get ( & session_index ) ;
2024-10-03 15:38:52 +03:00
2025-06-03 19:38:07 +03:00
let median_claps = Self ::calculate_median_claps ( & session_index ) ;
2025-05-31 14:30:15 +03:00
2024-10-03 15:38:52 +03:00
let offenders = validators
. into_iter ( )
. enumerate ( )
2025-06-03 19:38:07 +03:00
. filter ( | ( index , _ ) | ! Self ::is_good_actor ( * index , session_index , median_claps ) )
2024-10-03 15:38:52 +03:00
. filter_map ( | ( _ , id ) | {
< T ::ValidatorSet as ValidatorSetWithIdentification < T ::AccountId > > ::IdentificationOf ::convert (
id . clone ( ) ,
) . map ( | full_id | ( id , full_id ) )
} )
. collect ::< Vec < IdentificationTuple < T > > > ( ) ;
if offenders . is_empty ( ) {
2025-05-31 14:30:15 +03:00
Self ::deposit_event ( Event ::< T > ::AuthoritiesEquilibrium ) ;
2024-10-03 15:38:52 +03:00
} else {
2025-05-31 14:30:15 +03:00
Self ::deposit_event ( Event ::< T > ::SomeAuthoritiesTrottling { throttling : offenders . clone ( ) } ) ;
2024-10-03 15:38:52 +03:00
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 ( ) ;
2025-05-31 14:30:15 +03:00
ClapsInSession ::< T > ::mutate ( & session_index , | claps_details | {
( * claps_details )
. entry ( validator_index as AuthIndex )
. and_modify ( | individual | ( * individual ) . disabled = true )
. or_insert ( SessionAuthorityInfo { claps : 0 u32 , disabled : true } ) ;
} ) ;
2024-10-03 15:38:52 +03:00
}
}
#[ derive(RuntimeDebug, TypeInfo) ]
#[ cfg_attr(feature = " std " , derive(Clone, PartialEq, Eq)) ]
pub struct ThrottlingOffence < Offender > {
pub session_index : SessionIndex ,
pub validator_set_count : u32 ,
pub offenders : Vec < Offender >
}
impl < Offender : Clone > Offence < Offender > for ThrottlingOffence < Offender > {
const ID : Kind = * b " slow-clap:throtl " ;
type TimeSlot = SessionIndex ;
fn offenders ( & self ) -> Vec < Offender > {
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 ( )
}
}
}