use subtensor_runtime_common::NetUid; use super::*; /// Enum representing different types of transactions #[derive(Copy, Clone)] #[non_exhaustive] pub enum TransactionType { SetChildren, SetChildkeyTake, Unknown, RegisterNetwork, SetWeightsVersionKey, SetSNOwnerHotkey, OwnerHyperparamUpdate(Hyperparameter), MechanismCountUpdate, MechanismEmission, MaxUidsTrimming, AddStakeBurn, TempoUpdate, } impl TransactionType { /// Get the rate limit for a specific transaction type pub fn rate_limit(&self) -> u64 { match self { Self::SetChildren => 150, // 30 minutes Self::SetChildkeyTake => TxChildkeyTakeRateLimit::::get(), Self::RegisterNetwork => NetworkRateLimit::::get(), Self::MechanismCountUpdate => MechanismCountSetRateLimit::::get(), Self::MechanismEmission => MechanismEmissionRateLimit::::get(), Self::MaxUidsTrimming => MaxUidsTrimmingRateLimit::::get(), Self::Unknown => 0, // Default to no limit for unknown types (no limit) _ => 0, } } pub fn rate_limit_on_subnet(&self, netuid: NetUid) -> u64 { #[allow(clippy::match_single_binding)] match self { Self::SetWeightsVersionKey => (Tempo::::get(netuid) as u64) .saturating_mul(WeightsVersionKeyRateLimit::::get()), // Owner hyperparameter updates are rate-limited by N tempos on the subnet (sudo configurable) Self::OwnerHyperparamUpdate(_) => { let epochs = OwnerHyperparamRateLimit::::get() as u64; (Tempo::::get(netuid) as u64).saturating_mul(epochs) } Self::SetSNOwnerHotkey => DefaultSetSNOwnerHotkeyRateLimit::::get(), Self::AddStakeBurn => Tempo::::get(netuid) as u64, Self::TempoUpdate => MIN_TEMPO as u64, _ => self.rate_limit::(), } } pub fn passes_rate_limit(&self, key: &T::AccountId) -> bool { let block = Pallet::::get_current_block_as_u64(); let limit = self.rate_limit::(); let last_block = self.last_block::(key); Self::check_passes_rate_limit(limit, block, last_block) } pub fn check_passes_rate_limit(limit: u64, block: u64, last_block: u64) -> bool { // Allow the first transaction (when last_block is 0) or if the rate limit has passed last_block == 0 || block.saturating_sub(last_block) >= limit } /// Check if a transaction should be rate limited on a specific subnet pub fn passes_rate_limit_on_subnet( &self, hotkey: &T::AccountId, netuid: NetUid, ) -> bool { let block = Pallet::::get_current_block_as_u64(); let limit = self.rate_limit_on_subnet::(netuid); let last_block = self.last_block_on_subnet::(hotkey, netuid); Self::check_passes_rate_limit(limit, block, last_block) } /// Get the block number of the last transaction for a specific key, and transaction type pub fn last_block(&self, key: &T::AccountId) -> u64 { match self { Self::RegisterNetwork => Pallet::::get_network_last_lock_block(), _ => self.last_block_on_subnet::(key, NetUid::ROOT), } } /// Get the block number of the last transaction for a specific hotkey, network, and transaction /// type pub fn last_block_on_subnet(&self, hotkey: &T::AccountId, netuid: NetUid) -> u64 { match self { Self::RegisterNetwork => Pallet::::get_network_last_lock_block(), Self::SetSNOwnerHotkey => { Pallet::::get_rate_limited_last_block(&RateLimitKey::SetSNOwnerHotkey(netuid)) } Self::OwnerHyperparamUpdate(hparam) => Pallet::::get_rate_limited_last_block( &RateLimitKey::OwnerHyperparamUpdate(netuid, *hparam), ), _ => { let tx_type: u16 = (*self).into(); TransactionKeyLastBlock::::get((hotkey, netuid, tx_type)) } } } /// Set the block number of the last transaction for a specific hotkey, network, and transaction /// type pub fn set_last_block_on_subnet( &self, key: &T::AccountId, netuid: NetUid, block: u64, ) { match self { Self::RegisterNetwork => Pallet::::set_network_last_lock_block(block), Self::SetSNOwnerHotkey => Pallet::::set_rate_limited_last_block( &RateLimitKey::SetSNOwnerHotkey(netuid), block, ), Self::OwnerHyperparamUpdate(hparam) => Pallet::::set_rate_limited_last_block( &RateLimitKey::OwnerHyperparamUpdate(netuid, *hparam), block, ), _ => { let tx_type: u16 = (*self).into(); TransactionKeyLastBlock::::insert((key, netuid, tx_type), block); } } } } /// Implement conversion from TransactionType to u16 impl From for u16 { fn from(tx_type: TransactionType) -> Self { match tx_type { TransactionType::SetChildren => 0, TransactionType::SetChildkeyTake => 1, TransactionType::Unknown => 2, TransactionType::RegisterNetwork => 3, TransactionType::SetWeightsVersionKey => 4, TransactionType::SetSNOwnerHotkey => 5, TransactionType::OwnerHyperparamUpdate(_) => 6, TransactionType::MechanismCountUpdate => 7, TransactionType::MechanismEmission => 8, TransactionType::MaxUidsTrimming => 9, TransactionType::AddStakeBurn => 10, TransactionType::TempoUpdate => 11, } } } /// Implement conversion from u16 to TransactionType impl From for TransactionType { fn from(value: u16) -> Self { match value { 0 => TransactionType::SetChildren, 1 => TransactionType::SetChildkeyTake, 3 => TransactionType::RegisterNetwork, 4 => TransactionType::SetWeightsVersionKey, 5 => TransactionType::SetSNOwnerHotkey, 6 => TransactionType::OwnerHyperparamUpdate(Hyperparameter::Unknown), 7 => TransactionType::MechanismCountUpdate, 8 => TransactionType::MechanismEmission, 9 => TransactionType::MaxUidsTrimming, 10 => TransactionType::AddStakeBurn, 11 => TransactionType::TempoUpdate, _ => TransactionType::Unknown, } } } impl From for TransactionType { fn from(param: Hyperparameter) -> Self { Self::OwnerHyperparamUpdate(param) } } #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, Debug, TypeInfo)] #[non_exhaustive] pub enum Hyperparameter { Unknown = 0, ServingRateLimit = 1, MaxDifficulty = 2, AdjustmentAlpha = 3, MaxWeightLimit = 4, ImmunityPeriod = 5, MinAllowedWeights = 6, Kappa = 7, Rho = 8, ActivityCutoff = 9, PowRegistrationAllowed = 10, MinBurn = 11, MaxBurn = 12, BondsMovingAverage = 13, BondsPenalty = 14, CommitRevealEnabled = 15, LiquidAlphaEnabled = 16, AlphaValues = 17, WeightCommitInterval = 18, TransferEnabled = 19, AlphaSigmoidSteepness = 20, Yuma3Enabled = 21, BondsResetEnabled = 22, ImmuneNeuronLimit = 23, RecycleOrBurn = 24, MaxAllowedUids = 25, BurnHalfLife = 26, BurnIncreaseMult = 27, SubnetEmissionEnabled = 28, MinChildkeyTake = 29, ActivityCutoffFactorMilli = 30, TriggerEpoch = 31, } impl Pallet { // ======================== // ==== Rate Limiting ===== // ======================== pub fn remove_last_tx_block(key: &T::AccountId) { Self::remove_rate_limited_last_block(&RateLimitKey::LastTxBlock(key.clone())) } pub fn set_last_tx_block(key: &T::AccountId, block: u64) { Self::set_rate_limited_last_block(&RateLimitKey::LastTxBlock(key.clone()), block); } pub fn get_last_tx_block(key: &T::AccountId) -> u64 { Self::get_rate_limited_last_block(&RateLimitKey::LastTxBlock(key.clone())) } pub fn remove_last_tx_block_delegate_take(key: &T::AccountId) { Self::remove_rate_limited_last_block(&RateLimitKey::LastTxBlockDelegateTake(key.clone())) } pub fn set_last_tx_block_delegate_take(key: &T::AccountId, block: u64) { Self::set_rate_limited_last_block( &RateLimitKey::LastTxBlockDelegateTake(key.clone()), block, ); } pub fn get_last_tx_block_delegate_take(key: &T::AccountId) -> u64 { Self::get_rate_limited_last_block(&RateLimitKey::LastTxBlockDelegateTake(key.clone())) } pub fn get_last_tx_block_childkey_take(key: &T::AccountId) -> u64 { Self::get_rate_limited_last_block(&RateLimitKey::LastTxBlockChildKeyTake(key.clone())) } pub fn remove_last_tx_block_childkey(key: &T::AccountId) { Self::remove_rate_limited_last_block(&RateLimitKey::LastTxBlockChildKeyTake(key.clone())) } pub fn set_last_tx_block_childkey(key: &T::AccountId, block: u64) { Self::set_rate_limited_last_block( &RateLimitKey::LastTxBlockChildKeyTake(key.clone()), block, ); } pub fn exceeds_tx_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { let rate_limit: u64 = Self::get_tx_rate_limit(); if rate_limit == 0 || prev_tx_block == 0 { return false; } current_block.saturating_sub(prev_tx_block) <= rate_limit } pub fn exceeds_tx_delegate_take_rate_limit(prev_tx_block: u64, current_block: u64) -> bool { let rate_limit: u64 = Self::get_tx_delegate_take_rate_limit(); if rate_limit == 0 || prev_tx_block == 0 { return false; } current_block.saturating_sub(prev_tx_block) <= rate_limit } }