use super::*; use crate::Error; use frame_support::pallet_prelude::DispatchResult; use sp_runtime::DispatchError; use subtensor_runtime_common::NetUid; use crate::system::pallet_prelude::OriginFor; use crate::utils::rate_limiting::{Hyperparameter, TransactionType}; impl Pallet { /// `set_tempo` implementation, dispatched by `AdminUtils::sudo_set_tempo`. /// Callable by the subnet owner (bounded to `[MinTempo, MaxTempo]` and /// cooled down by a fixed `MinTempo` blocks via `TransactionType::TempoUpdate`) /// or by root (any u16, no rate limit). Both respect the admin freeze window /// and reset the cycle (`LastEpochBlock = current_block`) on success. pub fn do_set_tempo(origin: OriginFor, netuid: NetUid, tempo: u16) -> DispatchResult { let maybe_who = Self::ensure_subnet_owner_or_root(origin, netuid)?; ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); if maybe_who.is_some() { ensure!( (MIN_TEMPO..=MAX_TEMPO).contains(&tempo), Error::::TempoOutOfBounds ); } Self::ensure_admin_window_open(netuid)?; let tx = TransactionType::TempoUpdate; if let Some(who) = maybe_who.as_ref() { ensure!( tx.passes_rate_limit_on_subnet::(who, netuid), Error::::TxRateLimitExceeded ); } Self::apply_tempo_with_cycle_reset(netuid, tempo); if let Some(who) = maybe_who.as_ref() { let now = Self::get_current_block_as_u64(); tx.set_last_block_on_subnet::(who, netuid, now); } Ok(()) } /// `set_activity_cutoff_factor` implementation. Callable by the subnet owner /// (subject to admin freeze window + rate limit) or by root (bypasses both). pub fn do_set_activity_cutoff_factor( origin: OriginFor, netuid: NetUid, factor_milli: u32, ) -> DispatchResult { let maybe_who = Self::ensure_subnet_owner_or_root(origin, netuid)?; ensure!( (MIN_ACTIVITY_CUTOFF_FACTOR_MILLI..=MAX_ACTIVITY_CUTOFF_FACTOR_MILLI) .contains(&factor_milli), Error::::ActivityCutoffFactorMilliOutOfBounds ); let tx = TransactionType::OwnerHyperparamUpdate(Hyperparameter::ActivityCutoffFactorMilli); // Admin freeze window and per-owner rate limit apply only to the subnet // owner. Root bypasses both as a governance override. if let Some(who) = maybe_who.as_ref() { Self::ensure_admin_window_open(netuid)?; ensure!( tx.passes_rate_limit_on_subnet::(who, netuid), Error::::TxRateLimitExceeded ); } Self::set_activity_cutoff_factor_milli(netuid, factor_milli); if let Some(who) = maybe_who.as_ref() { let now = Self::get_current_block_as_u64(); tx.set_last_block_on_subnet::(who, netuid, now); } Ok(()) } /// Owner-side `trigger_epoch` implementation. /// Schedules the triggered epoch to fire after `AdminFreezeWindow` blocks; that /// countdown engages the freeze window for the subnet via `is_in_admin_freeze_window`. pub fn do_trigger_epoch(origin: OriginFor, netuid: NetUid) -> Result<(), DispatchError> { let who = Self::ensure_subnet_owner(origin, netuid)?; // Block triggering to avoid breaking CRv3 reveal ensure!( !Self::get_commit_reveal_weights_enabled(netuid), Error::::DynamicTempoBlockedByCommitReveal ); // No `ensure_admin_window_open` here: trigger *defines* the next epoch. ensure!( PendingEpochAt::::get(netuid) == 0, Error::::EpochTriggerAlreadyPending ); let now = Self::get_current_block_as_u64(); let window = AdminFreezeWindow::::get() as u64; let tempo = Self::get_tempo(netuid); let remaining = Self::blocks_until_next_auto_epoch(netuid, tempo, now); ensure!(remaining >= window, Error::::AutoEpochAlreadyImminent); let tx = TransactionType::OwnerHyperparamUpdate(Hyperparameter::TriggerEpoch); ensure!( tx.passes_rate_limit_on_subnet::(&who, netuid), Error::::TxRateLimitExceeded ); let fires_at = now.saturating_add(window); PendingEpochAt::::insert(netuid, fires_at); tx.set_last_block_on_subnet::(&who, netuid, now); Self::deposit_event(Event::EpochTriggered { netuid, by: who, fires_at, }); Ok(()) } }