diff --git a/common/src/lib.rs b/common/src/lib.rs index 6e2f2900eb..fa9e713b8c 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -66,6 +66,14 @@ impl NetUid { pub fn next(&self) -> NetUid { Self(self.0.saturating_add(1)) } + + pub fn prev(&self) -> NetUid { + Self(self.0.saturating_sub(1)) + } + + pub fn inner(&self) -> u16 { + self.0 + } } impl Display for NetUid { @@ -130,6 +138,7 @@ pub enum ProxyType { RootWeights, ChildKeys, SudoUncheckedSetCode, + SwapHotkey, } impl Default for ProxyType { diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 1a48024bb0..ea252ccb37 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1685,6 +1685,17 @@ pub mod pallet { #[pallet::storage] // --- Storage for migration run status pub type HasMigrationRun = StorageMap<_, Identity, Vec, bool, ValueQuery>; + #[pallet::type_value] + /// Default value for pending childkey cooldown (settable by root, default 0) + pub fn DefaultPendingChildKeyCooldown() -> u64 { + 0 + } + + #[pallet::storage] + /// Storage value for pending childkey cooldown, settable by root. + pub type PendingChildKeyCooldown = + StorageValue<_, u64, ValueQuery, DefaultPendingChildKeyCooldown>; + #[pallet::genesis_config] pub struct GenesisConfig { /// Stakes record in genesis. diff --git a/pallets/subtensor/src/macros/dispatches.rs b/pallets/subtensor/src/macros/dispatches.rs index 7f07c5e29e..7d4f1a0850 100644 --- a/pallets/subtensor/src/macros/dispatches.rs +++ b/pallets/subtensor/src/macros/dispatches.rs @@ -2058,5 +2058,17 @@ mod dispatches { ) -> DispatchResult { Self::do_burn_alpha(origin, hotkey, amount, netuid) } + + /// Sets the pending childkey cooldown (in blocks). Root only. + #[pallet::call_index(109)] + #[pallet::weight((Weight::from_parts(10_000, 0), DispatchClass::Operational, Pays::No))] + pub fn set_pending_childkey_cooldown( + origin: OriginFor, + cooldown: u64, + ) -> DispatchResult { + ensure_root(origin)?; + PendingChildKeyCooldown::::put(cooldown); + Ok(()) + } } } diff --git a/pallets/subtensor/src/staking/set_children.rs b/pallets/subtensor/src/staking/set_children.rs index dc2da75785..35867f82ca 100644 --- a/pallets/subtensor/src/staking/set_children.rs +++ b/pallets/subtensor/src/staking/set_children.rs @@ -1,5 +1,5 @@ use super::*; -use sp_core::Get; + use subtensor_runtime_common::NetUid; impl Pallet { @@ -124,7 +124,7 @@ impl Pallet { // Calculate cool-down block let cooldown_block = - Self::get_current_block_as_u64().saturating_add(DefaultPendingCooldown::::get()); + Self::get_current_block_as_u64().saturating_add(PendingChildKeyCooldown::::get()); // Insert or update PendingChildKeys PendingChildKeys::::insert(netuid, hotkey.clone(), (children.clone(), cooldown_block)); diff --git a/pallets/subtensor/src/swap/swap_hotkey.rs b/pallets/subtensor/src/swap/swap_hotkey.rs index 4b598c77d6..9cb79a1002 100644 --- a/pallets/subtensor/src/swap/swap_hotkey.rs +++ b/pallets/subtensor/src/swap/swap_hotkey.rs @@ -55,36 +55,36 @@ impl Pallet { weight.saturating_accrue(T::DbWeight::get().reads(2)); - // 7. Swap LastTxBlock + // 7. Ensure the new hotkey is not already registered on any network + ensure!( + !Self::is_hotkey_registered_on_any_network(new_hotkey), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + + // 8. Swap LastTxBlock // LastTxBlock( hotkey ) --> u64 -- the last transaction block for the hotkey. let last_tx_block: u64 = LastTxBlock::::get(old_hotkey); LastTxBlock::::insert(new_hotkey, last_tx_block); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 8. Swap LastTxBlockDelegateTake + // 9. Swap LastTxBlockDelegateTake // LastTxBlockDelegateTake( hotkey ) --> u64 -- the last transaction block for the hotkey delegate take. let last_tx_block_delegate_take: u64 = LastTxBlockDelegateTake::::get(old_hotkey); LastTxBlockDelegateTake::::insert(new_hotkey, last_tx_block_delegate_take); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 9. Swap LastTxBlockChildKeyTake + // 10. Swap LastTxBlockChildKeyTake // LastTxBlockChildKeyTake( hotkey ) --> u64 -- the last transaction block for the hotkey child key take. let last_tx_block_child_key_take: u64 = LastTxBlockChildKeyTake::::get(old_hotkey); LastTxBlockChildKeyTake::::insert(new_hotkey, last_tx_block_child_key_take); weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - // 10. fork for swap hotkey on a specific subnet case after do the common check + // 11. fork for swap hotkey on a specific subnet case after do the common check if let Some(netuid) = netuid { return Self::swap_hotkey_on_subnet(&coldkey, old_hotkey, new_hotkey, netuid, weight); }; // Start to do everything for swap hotkey on all subnets case - // 11. Ensure the new hotkey is not already registered on any network - ensure!( - !Self::is_hotkey_registered_on_any_network(new_hotkey), - Error::::HotKeyAlreadyRegisteredInSubNet - ); - // 12. Get the cost for swapping the key let swap_cost = Self::get_key_swap_cost(); log::debug!("Swap cost: {:?}", swap_cost); diff --git a/pallets/subtensor/src/tests/children.rs b/pallets/subtensor/src/tests/children.rs index 556b19c380..63b6d8fc94 100644 --- a/pallets/subtensor/src/tests/children.rs +++ b/pallets/subtensor/src/tests/children.rs @@ -3953,14 +3953,14 @@ fn test_dividend_distribution_with_children_same_coldkey_owner() { } #[test] -fn test_pending_cooldown_one_day() { +fn test_pending_cooldown_as_expected() { let curr_block = 1; - - let expected_cooldown = if cfg!(feature = "fast-blocks") { - 15 - } else { - 7_200 - }; + // TODO: Fix when CHK splitting patched + // let expected_cooldown = if cfg!(feature = "fast-blocks") { + // 15 + // } else { + // 7200 + // }; new_test_ext(curr_block).execute_with(|| { let coldkey = U256::from(1); @@ -3970,6 +3970,7 @@ fn test_pending_cooldown_one_day() { let netuid = NetUid::from(1); let proportion1: u64 = 1000; let proportion2: u64 = 2000; + let expected_cooldown = PendingChildKeyCooldown::::get(); // Add network and register hotkey add_network(netuid, 13, 0); diff --git a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs index 7ed66cd8ac..effef6f8ab 100644 --- a/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs +++ b/pallets/subtensor/src/tests/swap_hotkey_with_subnet.rs @@ -497,7 +497,8 @@ fn test_swap_hotkey_with_multiple_subnets() { new_test_ext(1).execute_with(|| { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); - let coldkey = U256::from(3); + let new_hotkey_2 = U256::from(3); + let coldkey = U256::from(4); SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); @@ -519,12 +520,12 @@ fn test_swap_hotkey_with_multiple_subnets() { assert_ok!(SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(coldkey), &old_hotkey, - &new_hotkey, + &new_hotkey_2, Some(netuid2) )); assert!(IsNetworkMember::::get(new_hotkey, netuid1)); - assert!(IsNetworkMember::::get(new_hotkey, netuid2)); + assert!(IsNetworkMember::::get(new_hotkey_2, netuid2)); assert!(!IsNetworkMember::::get(old_hotkey, netuid1)); assert!(!IsNetworkMember::::get(old_hotkey, netuid2)); }); @@ -628,8 +629,9 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { new_test_ext(1).execute_with(|| { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); - let coldkey1 = U256::from(3); - let coldkey2 = U256::from(4); + let new_hotkey_2 = U256::from(3); + let coldkey1 = U256::from(4); + let coldkey2 = U256::from(5); let netuid1 = NetUid::from(1); let netuid2 = NetUid::from(2); let stake = DefaultMinStake::::get() * 10; @@ -687,7 +689,7 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { assert_ok!(SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(coldkey1), &old_hotkey, - &new_hotkey, + &new_hotkey_2, Some(netuid2) )); @@ -697,6 +699,11 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { coldkey1 ); assert!(!SubtensorModule::get_owned_hotkeys(&coldkey2).contains(&new_hotkey)); + assert_eq!( + SubtensorModule::get_owning_coldkey_for_hotkey(&new_hotkey_2), + coldkey1 + ); + assert!(!SubtensorModule::get_owned_hotkeys(&coldkey2).contains(&new_hotkey_2)); // Check stake transfer assert_eq!( @@ -709,7 +716,7 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { ); assert_eq!( SubtensorModule::get_stake_for_hotkey_and_coldkey_on_subnet( - &new_hotkey, + &new_hotkey_2, &coldkey2, netuid2 ), @@ -739,7 +746,7 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { )); assert!(SubtensorModule::is_hotkey_registered_on_network( netuid2, - &new_hotkey + &new_hotkey_2 )); assert!(!SubtensorModule::is_hotkey_registered_on_network( netuid1, @@ -752,7 +759,8 @@ fn test_swap_hotkey_with_multiple_coldkeys_and_subnets() { // Check total stake transfer assert_eq!( - SubtensorModule::get_total_stake_for_hotkey(&new_hotkey), + SubtensorModule::get_total_stake_for_hotkey(&new_hotkey) + + SubtensorModule::get_total_stake_for_hotkey(&new_hotkey_2), total_hk_stake ); assert_eq!(SubtensorModule::get_total_stake_for_hotkey(&old_hotkey), 0); @@ -1143,7 +1151,8 @@ fn test_swap_multiple_subnets() { new_test_ext(1).execute_with(|| { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); - let coldkey = U256::from(3); + let new_hotkey_2 = U256::from(3); + let coldkey = U256::from(4); let netuid1 = add_dynamic_network(&old_hotkey, &coldkey); let netuid2 = add_dynamic_network(&old_hotkey, &coldkey); @@ -1169,13 +1178,13 @@ fn test_swap_multiple_subnets() { assert_ok!(SubtensorModule::do_swap_hotkey( RuntimeOrigin::signed(coldkey), &old_hotkey, - &new_hotkey, + &new_hotkey_2, Some(netuid2) ),); // Verify the swap for both subnets assert_eq!(ChildKeys::::get(new_hotkey, netuid1), children1); - assert_eq!(ChildKeys::::get(new_hotkey, netuid2), children2); + assert_eq!(ChildKeys::::get(new_hotkey_2, netuid2), children2); assert!(ChildKeys::::get(old_hotkey, netuid1).is_empty()); assert!(ChildKeys::::get(old_hotkey, netuid2).is_empty()); }); @@ -1490,7 +1499,6 @@ fn test_swap_owner_check_swap_record_clean_up() { let old_hotkey = U256::from(1); let new_hotkey = U256::from(2); let coldkey = U256::from(3); - let netuid = add_dynamic_network(&old_hotkey, &coldkey); SubtensorModule::add_balance_to_coldkey_account(&coldkey, u64::MAX); Owner::::insert(old_hotkey, coldkey); @@ -1514,3 +1522,37 @@ fn test_swap_owner_check_swap_record_clean_up() { )); }); } + +// SKIP_WASM_BUILD=1 RUST_LOG=debug cargo test --test swap_hotkey_with_subnet -- test_swap_hotkey_error_cases --exact --nocapture +#[test] +fn test_swap_hotkey_registered_on_other_subnet() { + new_test_ext(1).execute_with(|| { + let old_hotkey = U256::from(1); + let new_hotkey = U256::from(2); + let coldkey = U256::from(3); + let wrong_coldkey = U256::from(4); + let netuid = add_dynamic_network(&old_hotkey, &coldkey); + let other_netuid = add_dynamic_network(&old_hotkey, &coldkey); + + // Set up initial state + Owner::::insert(old_hotkey, coldkey); + TotalNetworks::::put(1); + LastTxBlock::::insert(coldkey, 0); + + let initial_balance = SubtensorModule::get_key_swap_cost() + 1000; + SubtensorModule::add_balance_to_coldkey_account(&coldkey, initial_balance); + + // Test new hotkey already registered on other subnet + IsNetworkMember::::insert(new_hotkey, other_netuid, true); + System::set_block_number(System::block_number() + HotkeySwapOnSubnetInterval::get()); + assert_noop!( + SubtensorModule::do_swap_hotkey( + RuntimeOrigin::signed(coldkey), + &old_hotkey, + &new_hotkey, + Some(netuid) + ), + Error::::HotKeyAlreadyRegisteredInSubNet + ); + }); +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 581e3d2252..815e0a156b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -209,7 +209,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 276, + spec_version: 278, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -829,6 +829,10 @@ impl InstanceFilter for ProxyType { } _ => false, }, + ProxyType::SwapHotkey => matches!( + c, + RuntimeCall::SubtensorModule(pallet_subtensor::Call::swap_hotkey { .. }) + ), } } fn is_superset(&self, o: &Self) -> bool {