diff --git a/chain-extensions/src/mock.rs b/chain-extensions/src/mock.rs index 98ea096199..0efea56696 100644 --- a/chain-extensions/src/mock.rs +++ b/chain-extensions/src/mock.rs @@ -327,6 +327,7 @@ parameter_types! { pub const InitialYuma3On: bool = false; // Default value for Yuma3On // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialDeregistrationPriorityScheduleDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. @@ -398,6 +399,7 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index 08589e530b..a1b61fa5c8 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -485,6 +485,12 @@ mod benchmarks { _(RawOrigin::Root, 100u32.into()); } + #[benchmark] + fn sudo_set_deregistration_priority_schedule_delay() { + #[extrinsic_call] + _(RawOrigin::Root, 100u32.into()); + } + #[benchmark] fn sudo_set_dissolve_network_schedule_duration() { #[extrinsic_call] diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index de6ac5825b..5f0d845ead 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1386,6 +1386,26 @@ pub mod pallet { Ok(()) } + /// Sets the delay for deregistration priority scheduling. + #[pallet::call_index(55)] + #[pallet::weight(( + Weight::from_parts(5_000_000, 0) + .saturating_add(T::DbWeight::get().reads(0_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn sudo_set_deregistration_priority_schedule_delay( + origin: OriginFor, + duration: BlockNumberFor, + ) -> DispatchResult { + ensure_root(origin)?; + pallet_subtensor::Pallet::::set_deregistration_priority_schedule_delay(duration); + log::trace!("DeregistrationPriorityScheduleDelaySet( duration: {duration:?} )"); + + Ok(()) + } + /// Sets the duration of the dissolve network schedule. /// /// This extrinsic allows the root account to set the duration for the dissolve network schedule. @@ -1400,7 +1420,7 @@ pub mod pallet { /// /// # Weight /// Weight is handled by the `#[pallet::weight]` attribute. - #[pallet::call_index(55)] + #[pallet::call_index(56)] #[pallet::weight(( Weight::from_parts(5_000_000, 0) .saturating_add(T::DbWeight::get().reads(0_u64)) diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index 0140808baa..58e00e70ba 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -141,6 +141,7 @@ parameter_types! { // pub const InitialHotkeyEmissionTempo: u64 = 1; // (DEPRECATED) // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialDeregistrationPriorityScheduleDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. @@ -211,6 +212,7 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = (); type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index 1aaefc8f8d..be222888e8 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -1418,6 +1418,39 @@ fn test_sudo_set_coldkey_swap_schedule_duration() { }); } +#[test] +fn test_sudo_set_deregistration_priority_schedule_delay() { + new_test_ext().execute_with(|| { + let root = RuntimeOrigin::root(); + let non_root = RuntimeOrigin::signed(U256::from(1)); + let new_duration = 150u32.into(); + + assert_noop!( + AdminUtils::sudo_set_deregistration_priority_schedule_delay(non_root, new_duration), + DispatchError::BadOrigin + ); + + assert_ok!(AdminUtils::sudo_set_deregistration_priority_schedule_delay( + root.clone(), + new_duration + )); + + assert_eq!( + pallet_subtensor::DeregistrationPriorityScheduleDelay::::get(), + new_duration + ); + + System::assert_last_event( + Event::DeregistrationPriorityScheduleDelaySet(new_duration).into(), + ); + + assert_ok!(AdminUtils::sudo_set_deregistration_priority_schedule_delay( + root, + new_duration + )); + }); +} + #[test] fn test_sudo_set_dissolve_network_schedule_duration() { new_test_ext().execute_with(|| { diff --git a/pallets/subtensor/src/coinbase/root.rs b/pallets/subtensor/src/coinbase/root.rs index 893f855f3e..15275878a5 100644 --- a/pallets/subtensor/src/coinbase/root.rs +++ b/pallets/subtensor/src/coinbase/root.rs @@ -244,6 +244,7 @@ impl Pallet { // --- 5. Remove various network-related storages. NetworkRegisteredAt::::remove(netuid); + let _ = Self::remove_subnet_from_deregistration_priority_queue(netuid); // --- 6. Remove incentive mechanism memory. let _ = Uids::::clear_prefix(netuid, u32::MAX, None); @@ -592,6 +593,10 @@ impl Pallet { pub fn get_network_to_prune() -> Option { let current_block: u64 = Self::get_current_block_as_u64(); + if let Some(priority_netuid) = Self::pop_ready_subnet_from_deregistration_priority_queue() { + return Some(priority_netuid); + } + let mut candidate_netuid: Option = None; let mut candidate_price: U96F32 = U96F32::saturating_from_num(u128::MAX); let mut candidate_timestamp: u64 = u64::MAX; @@ -610,8 +615,8 @@ impl Pallet { let price: U96F32 = Self::get_moving_alpha_price(netuid); - // If tie on price, earliest registration wins. - if price < candidate_price + if candidate_netuid.is_none() + || price < candidate_price || (price == candidate_price && registered_at < candidate_timestamp) { candidate_netuid = Some(netuid); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index bbec3ce934..eba1b7d48d 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -955,6 +955,12 @@ pub mod pallet { T::InitialColdkeySwapScheduleDuration::get() } + /// Default value for deregistration priority schedule delay + #[pallet::type_value] + pub fn DefaultDeregistrationPriorityScheduleDelay() -> BlockNumberFor { + T::InitialDeregistrationPriorityScheduleDelay::get() + } + /// Default value for coldkey swap reschedule duration #[pallet::type_value] pub fn DefaultColdkeySwapRescheduleDuration() -> BlockNumberFor { @@ -1605,6 +1611,25 @@ pub mod pallet { pub type NetworkRegisteredAt = StorageMap<_, Identity, NetUid, u64, ValueQuery, DefaultNetworkRegisteredAt>; + /// --- FIFO queue of netuids pending deregistration + #[pallet::storage] + pub type SubnetDeregistrationPriorityQueue = + StorageValue<_, sp_std::vec::Vec, ValueQuery>; + + /// --- MAP ( netuid ) --> scheduled block for enqueuing deregistration priority + #[pallet::storage] + pub type SubnetDeregistrationPrioritySchedule = + StorageMap<_, Identity, NetUid, BlockNumberFor, OptionQuery>; + + /// --- Global delay for scheduled deregistration priority activations + #[pallet::storage] + pub type DeregistrationPriorityScheduleDelay = StorageValue< + _, + BlockNumberFor, + ValueQuery, + DefaultDeregistrationPriorityScheduleDelay, + >; + /// --- MAP ( netuid ) --> pending_server_emission #[pallet::storage] pub type PendingServerEmission = diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index a735bde1e1..54d94086bc 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -221,6 +221,9 @@ mod config { /// Coldkey swap schedule duartion. #[pallet::constant] type InitialColdkeySwapScheduleDuration: Get>; + /// Deregistration priority schedule delay. + #[pallet::constant] + type InitialDeregistrationPriorityScheduleDelay: Get>; /// Coldkey swap reschedule duration. #[pallet::constant] type InitialColdkeySwapRescheduleDuration: Get>; diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index d534dbb0c6..3bfbad7fab 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -1411,6 +1411,7 @@ mod dispatches { .map_err(|_| Error::::FailedToSchedule)?; ColdkeySwapScheduled::::insert(&who, (when, new_coldkey.clone())); + Self::cancel_deregistration_priority_schedule_for_owner(&who); // Emit the SwapScheduled event Self::deposit_event(Event::ColdkeySwapScheduled { old_coldkey: who.clone(), @@ -2152,6 +2153,139 @@ mod dispatches { Ok(()) } + /// Schedules the subnet to be appended to the deregistration priority queue. + /// + /// Accessible by the subnet owner or root origin. + /// + /// # Errors + /// * [`Error::SubnetNotExists`] if the subnet does not exist. + /// * [`Error::SubnetDeregistrationPriorityAlreadyScheduled`] if a set operation is already + /// scheduled. + #[pallet::call_index(125)] + #[pallet::weight(( + Weight::from_parts(40_000_000, 0) + .saturating_add(T::DbWeight::get().reads_writes(4, 2)), + DispatchClass::Normal, + Pays::Yes + ))] + pub fn schedule_deregistration_priority( + origin: OriginFor, + netuid: NetUid, + ) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + Self::ensure_subnet_owner_or_root(origin, netuid)?; + + ensure!( + SubnetDeregistrationPrioritySchedule::::get(netuid).is_none(), + Error::::SubnetDeregistrationPriorityAlreadyScheduled + ); + + let current_block: BlockNumberFor = >::block_number(); + let delay: BlockNumberFor = DeregistrationPriorityScheduleDelay::::get(); + let when: BlockNumberFor = current_block.saturating_add(delay); + + let call = Call::::enqueue_subnet_deregistration { netuid }; + let bound_call = ::Preimages::bound(LocalCallOf::::from(call)) + .map_err(|_| Error::::FailedToSchedule)?; + + T::Scheduler::schedule( + DispatchTime::At(when), + None, + 63, + frame_system::RawOrigin::Root.into(), + bound_call, + ) + .map_err(|_| Error::::FailedToSchedule)?; + + SubnetDeregistrationPrioritySchedule::::insert(netuid, when); + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleAdded( + netuid, when, + )); + + Ok(()) + } + + /// Enqueues the subnet for deregistration immediately. + /// + /// This call is intended to be used by the scheduler and requires root origin. + #[pallet::call_index(126)] + #[pallet::weight(( + Weight::from_parts(15_000_000, 0) + .saturating_add(T::DbWeight::get().reads_writes(3, 2)), + DispatchClass::Operational, + Pays::Yes + ))] + pub fn enqueue_subnet_deregistration( + origin: OriginFor, + netuid: NetUid, + ) -> DispatchResult { + ensure_root(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + + if SubnetDeregistrationPrioritySchedule::::take(netuid).is_some() { + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleRemoved(netuid)); + } + + Self::enqueue_subnet_to_deregistration_priority_queue(netuid); + Self::deposit_event(Event::SubnetDeregistrationPriorityQueueAdded(netuid)); + + Ok(()) + } + + /// Cancels a pending deregistration priority schedule. + /// + /// Accessible by the subnet owner or root origin. + #[pallet::call_index(127)] + #[pallet::weight(( + Weight::from_parts(20_000_000, 0) + .saturating_add(T::DbWeight::get().reads_writes(2, 1)), + DispatchClass::Normal, + Pays::Yes + ))] + pub fn cancel_deregistration_priority_schedules( + origin: OriginFor, + netuid: NetUid, + ) -> DispatchResult { + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + Self::ensure_subnet_owner_or_root(origin, netuid)?; + + if SubnetDeregistrationPrioritySchedule::::take(netuid).is_some() { + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleRemoved(netuid)); + } + + Ok(()) + } + + /// Clears any deregistration priority queue entry and pending schedule for a subnet. + /// + /// Only callable by root origin. + #[pallet::call_index(128)] + #[pallet::weight(( + Weight::from_parts(20_000_000, 0) + .saturating_add(T::DbWeight::get().reads_writes(2, 2)), + DispatchClass::Normal, + Pays::Yes + ))] + pub fn clear_deregistration_priority( + origin: OriginFor, + netuid: NetUid, + ) -> DispatchResult { + ensure_root(origin)?; + ensure!(Self::if_subnet_exist(netuid), Error::::SubnetNotExists); + + let was_queued = Self::remove_subnet_from_deregistration_priority_queue(netuid); + let had_schedule = SubnetDeregistrationPrioritySchedule::::take(netuid).is_some(); + + if was_queued { + Self::deposit_event(Event::SubnetDeregistrationPriorityQueueRemoved(netuid)); + } + if had_schedule { + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleRemoved(netuid)); + } + + Ok(()) + } + /// ---- Used to commit timelock encrypted commit-reveal weight values to later be revealed. /// /// # Args: diff --git a/pallets/subtensor/src/macros/errors.rs b/pallets/subtensor/src/macros/errors.rs index 5a15330075..d87d0f5938 100644 --- a/pallets/subtensor/src/macros/errors.rs +++ b/pallets/subtensor/src/macros/errors.rs @@ -152,6 +152,8 @@ mod errors { TxRateLimitExceeded, /// Swap already scheduled. SwapAlreadyScheduled, + /// Deregistration priority already scheduled. + SubnetDeregistrationPriorityAlreadyScheduled, /// failed to swap coldkey FailedToSchedule, /// New coldkey is hotkey diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index d015205d4d..61bc7d9e6c 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -13,6 +13,16 @@ mod events { NetworkAdded(NetUid, u16), /// a network is removed. NetworkRemoved(NetUid), + /// a subnet's deregistration queue entry was scheduled. + SubnetDeregistrationPriorityScheduleAdded(NetUid, BlockNumberFor), + /// a subnet was enqueued for deregistration. + SubnetDeregistrationPriorityQueueAdded(NetUid), + /// network deregistration queue entry cleared or canceled. + SubnetDeregistrationPriorityQueueRemoved(NetUid), + /// pending deregistration schedule cleared or canceled. + SubnetDeregistrationPriorityScheduleRemoved(NetUid), + /// deregistration priority schedule delay updated. + DeregistrationPriorityScheduleDelaySet(BlockNumberFor), /// stake has been transferred from the a coldkey account onto the hotkey staking account. StakeAdded( T::AccountId, diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index 090fcf8f75..2ea61ef7f5 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -214,6 +214,7 @@ parameter_types! { pub const InitialYuma3On: bool = false; // Default value for Yuma3On // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days + pub const InitialDeregistrationPriorityScheduleDelay: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // Default as 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // Default as 5 days pub const InitialTaoWeight: u64 = 0; // 100% global weight. @@ -285,6 +286,7 @@ impl crate::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; diff --git a/pallets/subtensor/src/tests/networks.rs b/pallets/subtensor/src/tests/networks.rs index 0fa27a99d6..80700ad6a1 100644 --- a/pallets/subtensor/src/tests/networks.rs +++ b/pallets/subtensor/src/tests/networks.rs @@ -6,6 +6,7 @@ use crate::*; use frame_support::{assert_err, assert_ok}; use frame_system::Config; use sp_core::U256; +use sp_runtime::DispatchError; use sp_std::collections::{btree_map::BTreeMap, vec_deque::VecDeque}; use substrate_fixed::types::{I96F32, U64F64, U96F32}; use subtensor_runtime_common::{MechId, NetUidStorageIndex, TaoCurrency}; @@ -55,6 +56,8 @@ fn dissolve_no_stakers_no_alpha_no_emission() { SubtensorModule::set_subnet_locked_balance(net, TaoCurrency::from(0)); SubnetTAO::::insert(net, TaoCurrency::from(0)); Emission::::insert(net, Vec::::new()); + SubnetDeregistrationPriorityQueue::::mutate(|queue| queue.push(net)); + assert!(SubnetDeregistrationPriorityQueue::::get().contains(&net)); let before = SubtensorModule::get_coldkey_balance(&cold); assert_ok!(SubtensorModule::do_dissolve_network(net)); @@ -63,6 +66,142 @@ fn dissolve_no_stakers_no_alpha_no_emission() { // Balance should be unchanged (whatever the network-lock bookkeeping left there) assert_eq!(after, before); assert!(!SubtensorModule::if_subnet_exist(net)); + assert!(!SubnetDeregistrationPriorityQueue::::get().contains(&net)); + }); +} + +#[test] +fn schedule_priority_and_force_set() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(11); + let owner_hot = U256::from(22); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + assert_ok!(SubtensorModule::schedule_deregistration_priority( + RuntimeOrigin::signed(owner_cold), + net + )); + + assert!(!SubnetDeregistrationPriorityQueue::::get().contains(&net)); + let when = + SubnetDeregistrationPrioritySchedule::::get(net).expect("Expected to not panic"); + assert!(when > 0); + + assert_ok!(SubtensorModule::enqueue_subnet_deregistration( + RuntimeOrigin::root(), + net + )); + + assert!(SubnetDeregistrationPriorityQueue::::get().contains(&net)); + assert!(!SubnetDeregistrationPrioritySchedule::::contains_key( + net + )); + }); +} + +#[test] +fn cancel_priority_schedule_only() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(13); + let owner_hot = U256::from(26); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + SubnetDeregistrationPriorityQueue::::mutate(|queue| queue.push(net)); + SubnetDeregistrationPrioritySchedule::::insert(net, 42); + + assert_ok!(SubtensorModule::cancel_deregistration_priority_schedules( + RuntimeOrigin::signed(owner_cold), + net + )); + + assert!(SubnetDeregistrationPriorityQueue::::get().contains(&net)); + assert!(!SubnetDeregistrationPrioritySchedule::::contains_key( + net + )); + }); +} + +#[test] +fn clear_priority_requires_root() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(19); + let owner_hot = U256::from(38); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + SubnetDeregistrationPriorityQueue::::mutate(|queue| queue.push(net)); + SubnetDeregistrationPrioritySchedule::::insert(net, 55); + + assert_ok!(SubtensorModule::clear_deregistration_priority( + RuntimeOrigin::root(), + net + )); + + assert!(!SubnetDeregistrationPriorityQueue::::get().contains(&net)); + assert!(!SubnetDeregistrationPrioritySchedule::::contains_key( + net + )); + }); +} + +#[test] +fn schedule_priority_requires_owner_or_root() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(15); + let owner_hot = U256::from(30); + let net = add_dynamic_network(&owner_hot, &owner_cold); + let intruder = U256::from(999); + + assert_err!( + SubtensorModule::schedule_deregistration_priority(RuntimeOrigin::signed(intruder), net), + DispatchError::BadOrigin + ); + + assert_ok!(SubtensorModule::schedule_deregistration_priority( + RuntimeOrigin::root(), + net + )); + }); +} + +#[test] +fn enqueue_subnet_deregistration_is_noop_without_schedule() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(17); + let owner_hot = U256::from(34); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + assert_ok!(SubtensorModule::enqueue_subnet_deregistration( + RuntimeOrigin::root(), + net + )); + + assert!(!SubnetDeregistrationPriorityQueue::::get().contains(&net)); + }); +} + +#[test] +fn schedule_swap_coldkey_cancels_priority_schedule() { + new_test_ext(0).execute_with(|| { + let owner_cold = U256::from(21); + let owner_hot = U256::from(42); + let new_cold = U256::from(84); + let net = add_dynamic_network(&owner_hot, &owner_cold); + + let swap_cost = SubtensorModule::get_key_swap_cost(); + SubtensorModule::add_balance_to_coldkey_account(&owner_cold, swap_cost.to_u64() + 1_000); + + SubnetDeregistrationPriorityQueue::::mutate(|queue| queue.push(net)); + SubnetDeregistrationPrioritySchedule::::insert(net, 5); + + assert_ok!(SubtensorModule::schedule_swap_coldkey( + RuntimeOrigin::signed(owner_cold), + new_cold + )); + + assert!(SubnetDeregistrationPriorityQueue::::get().contains(&net)); + assert!(!SubnetDeregistrationPrioritySchedule::::contains_key( + net + )); }); } @@ -1158,6 +1297,27 @@ fn prune_tie_on_price_earlier_registration_wins() { }); } +#[test] +fn prune_prefers_higher_priority_over_price() { + new_test_ext(0).execute_with(|| { + let n1 = add_dynamic_network(&U256::from(210), &U256::from(201)); + let n2 = add_dynamic_network(&U256::from(420), &U256::from(401)); + + let imm = SubtensorModule::get_network_immunity_period(); + System::set_block_number(imm + 5); + + SubnetMovingPrice::::insert(n1, I96F32::from_num(100)); + SubnetMovingPrice::::insert(n2, I96F32::from_num(1)); + + SubnetDeregistrationPriorityQueue::::mutate(|queue| { + queue.push(n1); + queue.push(n2); + }); + + assert_eq!(SubtensorModule::get_network_to_prune(), Some(n1)); + }); +} + #[test] fn prune_selection_complex_state_exhaustive() { new_test_ext(0).execute_with(|| { diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index cf2d55d168..53cab078c4 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -96,6 +96,52 @@ impl Pallet { Self::deposit_event(Event::OwnerHyperparamRateLimitSet(epochs)); } + pub fn cancel_deregistration_priority_schedule_for_owner(owner: &T::AccountId) { + let nets: sp_std::vec::Vec = SubnetOwner::::iter() + .filter_map(|(netuid, acct)| if acct == *owner { Some(netuid) } else { None }) + .collect(); + + for netuid in nets { + if SubnetDeregistrationPrioritySchedule::::take(netuid).is_some() { + Self::deposit_event(Event::SubnetDeregistrationPriorityScheduleRemoved(netuid)); + } + } + } + + pub fn enqueue_subnet_to_deregistration_priority_queue(netuid: NetUid) { + SubnetDeregistrationPriorityQueue::::mutate(|queue| { + if !queue.contains(&netuid) { + queue.push(netuid); + } + }) + } + + pub fn remove_subnet_from_deregistration_priority_queue(netuid: NetUid) -> bool { + SubnetDeregistrationPriorityQueue::::mutate(|queue| { + let original_len = queue.len(); + queue.retain(|&existing| existing != netuid); + original_len != queue.len() + }) + } + + pub fn pop_ready_subnet_from_deregistration_priority_queue() -> Option { + SubnetDeregistrationPriorityQueue::::mutate(|queue| { + let first_valid = queue + .iter() + .copied() + .enumerate() + .find(|&(_, netuid)| Self::if_subnet_exist(netuid) && netuid != NetUid::ROOT); + + if let Some((pos, netuid)) = first_valid { + queue.drain(..=pos); + Some(netuid) + } else { + queue.clear(); + None + } + }) + } + /// If owner is `Some`, record last-blocks for the provided `TransactionType`s. pub fn record_owner_rl( maybe_owner: Option<::AccountId>, @@ -845,6 +891,12 @@ impl Pallet { Self::deposit_event(Event::ColdkeySwapScheduleDurationSet(duration)); } + /// Set the delay applied when scheduling deregistration priority. + pub fn set_deregistration_priority_schedule_delay(duration: BlockNumberFor) { + DeregistrationPriorityScheduleDelay::::set(duration); + Self::deposit_event(Event::DeregistrationPriorityScheduleDelaySet(duration)); + } + /// Set the duration for dissolve network /// /// # Arguments diff --git a/pallets/transaction-fee/src/tests/mock.rs b/pallets/transaction-fee/src/tests/mock.rs index ee5b1693ba..2cfc87ca85 100644 --- a/pallets/transaction-fee/src/tests/mock.rs +++ b/pallets/transaction-fee/src/tests/mock.rs @@ -206,6 +206,7 @@ parameter_types! { // pub const InitialHotkeyEmissionTempo: u64 = 1; // (DEPRECATED) // pub const InitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialDeregistrationPriorityScheduleDelay: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialColdkeySwapRescheduleDuration: u64 = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: u64 = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialTaoWeight: u64 = u64::MAX/10; // 10% global weight. @@ -276,6 +277,7 @@ impl pallet_subtensor::Config for Test { type Yuma3On = InitialYuma3On; type Preimages = (); type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialTaoWeight = InitialTaoWeight; diff --git a/precompiles/src/solidity/subnet.abi b/precompiles/src/solidity/subnet.abi index 4531f59246..692f39bf32 100644 --- a/precompiles/src/solidity/subnet.abi +++ b/precompiles/src/solidity/subnet.abi @@ -1028,8 +1028,5 @@ "outputs": [], "stateMutability": "payable", "type": "function" - }, - { - "inputs" } ] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index f6843c50ee..9dd95b941b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1029,6 +1029,7 @@ parameter_types! { pub const InitialYuma3On: bool = false; // Default value for Yuma3On // pub const SubtensorInitialNetworkMaxStake: u64 = u64::MAX; // (DEPRECATED) pub const InitialColdkeySwapScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days + pub const InitialDeregistrationPriorityScheduleDelay: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const InitialColdkeySwapRescheduleDuration: BlockNumber = 24 * 60 * 60 / 12; // 1 day pub const InitialDissolveNetworkScheduleDuration: BlockNumber = 5 * 24 * 60 * 60 / 12; // 5 days pub const SubtensorInitialTaoWeight: u64 = 971_718_665_099_567_868; // 0.05267697438728329% tao weight. @@ -1101,6 +1102,7 @@ impl pallet_subtensor::Config for Runtime { type InitialTaoWeight = SubtensorInitialTaoWeight; type Preimages = Preimage; type InitialColdkeySwapScheduleDuration = InitialColdkeySwapScheduleDuration; + type InitialDeregistrationPriorityScheduleDelay = InitialDeregistrationPriorityScheduleDelay; type InitialColdkeySwapRescheduleDuration = InitialColdkeySwapRescheduleDuration; type InitialDissolveNetworkScheduleDuration = InitialDissolveNetworkScheduleDuration; type InitialEmaPriceHalvingPeriod = InitialEmaPriceHalvingPeriod;