From 1416248b7fbf760b760c0e31ae88545cd2964003 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:18:47 -0700 Subject: [PATCH 01/11] implement stake & unstake rate limits --- pallets/admin-utils/tests/mock.rs | 5 +++ pallets/subtensor/src/lib.rs | 62 ++++++++++++++++++++++++++----- pallets/subtensor/src/root.rs | 3 ++ pallets/subtensor/src/staking.rs | 52 ++++++++++++++++++++++---- pallets/subtensor/src/utils.rs | 20 ++++++++++ pallets/subtensor/tests/mock.rs | 4 ++ runtime/src/lib.rs | 4 ++ 7 files changed, 133 insertions(+), 17 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index f0e613fe89..4656d39c5d 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -110,6 +110,9 @@ parameter_types! { pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; + pub const InitialTargetStakesPerInterval: u16 = 1; + pub const InitialTargetUnstakesPerInterval: u16 = 1; + } impl pallet_subtensor::Config for Test { @@ -158,6 +161,8 @@ impl pallet_subtensor::Config for Test { type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; + type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; + type InitialTargetUnstakesPerInterval = InitialTargetUnstakesPerInterval; } impl system::Config for Test { diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index d4a3559d16..de89da6dbe 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -182,6 +182,9 @@ pub mod pallet { type InitialSubnetLimit: Get; #[pallet::constant] // Initial network creation rate limit type InitialNetworkRateLimit: Get; + #[pallet::constant] // Initial target stakes per interval issuance. + type InitialTargetStakesPerInterval: Get; + type InitialTargetUnstakesPerInterval: Get; } pub type AccountIdOf = ::AccountId; @@ -223,6 +226,14 @@ pub mod pallet { pub fn DefaultAccount() -> T::AccountId { T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap() } + #[pallet::type_value] + pub fn DefaultTargetStakesPerInterval() -> u64 { + T::InitialTargetStakesPerInterval::get() + } + #[pallet::type_value] + pub fn DefaultTargetUnstakesPerInterval() -> u64 { + T::InitialTargetUnstakesPerInterval::get() + } #[pallet::storage] // --- ITEM ( total_stake ) pub type TotalStake = StorageValue<_, u64, ValueQuery>; @@ -232,12 +243,24 @@ pub mod pallet { pub type BlockEmission = StorageValue<_, u64, ValueQuery, DefaultBlockEmission>; #[pallet::storage] // --- ITEM ( total_issuance ) pub type TotalIssuance = StorageValue<_, u64, ValueQuery, DefaultTotalIssuance>; + #[pallet::storage] // --- ITEM (target_stakes_per_interval) + pub type TargetStakesPerInterval = + StorageValue<_, u64, ValueQuery, DefaultTargetStakesPerInterval>; + #[pallet::storage] // --- ITEM (target_unstakes_per_interval) + pub type TargetUnstakesPerInterval = + StorageValue<_, u64, ValueQuery, DefaultTargetUnstakesPerInterval>; #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total amount of stake under a hotkey. pub type TotalHotkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; #[pallet::storage] // --- MAP ( cold ) --> stake | Returns the total amount of stake under a coldkey. pub type TotalColdkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; + #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total number of stakes under a hotkey this interval + pub type TotalHotkeyStakesThisInterval = + StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; + #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total number of unstakes under a hotkey this interval + pub type TotalHotkeyUnstakesThisInterval = + StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; #[pallet::storage] // --- MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey. pub type Owner = StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; @@ -920,7 +943,9 @@ pub mod pallet { MaxAllowedUidsExceeded, // --- Thrown when number of accounts going to be registered exceeds MaxAllowedUids for the network. TooManyUids, // ---- Thrown when the caller attempts to set weights with more uids than allowed. TxRateLimitExceeded, // --- Thrown when a transactor exceeds the rate limit for transactions. - RegistrationDisabled, // --- Thrown when registration is disabled + StakeRateLimitExceeded, // --- Thrown when a transactor exceeds the rate limit for stakes. + UnstakeRateLimitExceeded, // --- Thrown when a transactor exceeds the rate limit for unstakes. + RegistrationDisabled, // --- Thrown when registration is disabled TooManyRegistrationsThisInterval, // --- Thrown when registration attempt exceeds allowed in interval BenchmarkingOnly, // --- Thrown when a function is only available for benchmarking HotkeyOriginMismatch, // --- Thrown when the hotkey passed is not the origin, but it should be @@ -1817,14 +1842,33 @@ where return Err(InvalidTransaction::Call.into()); } } - Some(Call::add_stake { .. }) => Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }), - Some(Call::remove_stake { .. }) => Ok(ValidTransaction { - priority: Self::get_priority_vanilla(), - ..Default::default() - }), + Some(Call::add_stake { hotkey, .. }) => { + let stakes_this_interval = Pallet::::get_stakes_this_interval_for_hotkey(hotkey); + let max_stakes_per_interval = Pallet::::get_target_stakes_per_interval(); + + if stakes_this_interval >= max_stakes_per_interval { + return InvalidTransaction::ExhaustsResources.into(); + } + + Ok(ValidTransaction { + priority: Self::get_priority_vanilla(), + ..Default::default() + }) + } + Some(Call::remove_stake { hotkey, .. }) => { + let unstakes_this_interval = + Pallet::::get_unstakes_this_interval_for_hotkey(hotkey); + let max_unstakes_per_interval = Pallet::::get_target_unstakes_per_interval(); + + if unstakes_this_interval >= max_unstakes_per_interval { + return InvalidTransaction::ExhaustsResources.into(); + } + + Ok(ValidTransaction { + priority: Self::get_priority_vanilla(), + ..Default::default() + }) + } Some(Call::register { .. }) => Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index dd5d7f7841..08679ee510 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -246,6 +246,9 @@ impl Pallet { return Err("Not the block to update emission values."); } + // Resets the trackers for stakes and unstakes during a given interval + Self::reset_stakes_and_unstakes_this_interval(); + // --- 1. Retrieves the number of root validators on subnets. let n: u16 = Self::get_num_root_validators(); log::debug!("n:\n{:?}\n", n); diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 7f723cb718..bac83305bb 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -164,19 +164,29 @@ impl Pallet { Error::::TxRateLimitExceeded ); - // --- 7. Ensure the remove operation from the coldkey is a success. + // --- 7. Ensure we don't exceed stake rate limit + let stakes_this_interval = Self::get_stakes_this_interval_for_hotkey(&hotkey); + ensure!( + stakes_this_interval < Self::get_target_stakes_per_interval(), + Error::::StakeRateLimitExceeded + ); + + // --- 8. Ensure the remove operation from the coldkey is a success. ensure!( Self::remove_balance_from_coldkey_account(&coldkey, stake_as_balance.unwrap()) == true, Error::::BalanceWithdrawalError ); - // --- 8. If we reach here, add the balance to the hotkey. + // --- 9. If we reach here, add the balance to the hotkey. Self::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake_to_be_added); + // --- 10. Increment stakes this interval for the given hotkey + Self::set_stakes_this_interval_for_hotkey(&hotkey, stakes_this_interval + 1); + // Set last block for rate limiting Self::set_last_tx_block(&coldkey, block); - // --- 9. Emit the staking event. + // --- 11. Emit the staking event. log::info!( "StakeAdded( hotkey:{:?}, stake_to_be_added:{:?} )", hotkey, @@ -184,7 +194,7 @@ impl Pallet { ); Self::deposit_event(Event::StakeAdded(hotkey, stake_to_be_added)); - // --- 10. Ok and return. + // --- 12. Ok and return. Ok(()) } @@ -273,16 +283,26 @@ impl Pallet { Error::::TxRateLimitExceeded ); - // --- 7. We remove the balance from the hotkey. + // --- 7. Ensure we don't exceed stake rate limit + let unstakes_this_interval = Self::get_unstakes_this_interval_for_hotkey(&hotkey); + ensure!( + unstakes_this_interval < Self::get_target_unstakes_per_interval(), + Error::::UnstakeRateLimitExceeded + ); + + // --- 8. We remove the balance from the hotkey. Self::decrease_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake_to_be_removed); - // --- 8. We add the balancer to the coldkey. If the above fails we will not credit this coldkey. + // --- 9. We add the balancer to the coldkey. If the above fails we will not credit this coldkey. Self::add_balance_to_coldkey_account(&coldkey, stake_to_be_added_as_currency.unwrap()); + // --- 10. Increment stakes this interval for the given hotkey + Self::set_unstakes_this_interval_for_hotkey(&hotkey, unstakes_this_interval + 1); + // Set last block for rate limiting Self::set_last_tx_block(&coldkey, block); - // --- 9. Emit the unstaking event. + // --- 11. Emit the unstaking event. log::info!( "StakeRemoved( hotkey:{:?}, stake_to_be_removed:{:?} )", hotkey, @@ -290,7 +310,7 @@ impl Pallet { ); Self::deposit_event(Event::StakeRemoved(hotkey, stake_to_be_removed)); - // --- 10. Done and ok. + // --- 12. Done and ok. Ok(()) } @@ -342,6 +362,22 @@ impl Pallet { return Stake::::get(hotkey, coldkey); } + pub fn get_stakes_this_interval_for_hotkey(hotkey: &T::AccountId) -> u64 { + return TotalHotkeyStakesThisInterval::::get(hotkey); + } + + pub fn get_target_stakes_per_interval() -> u64 { + return TargetStakesPerInterval::::get(); + } + + pub fn get_unstakes_this_interval_for_hotkey(hotkey: &T::AccountId) -> u64 { + return TotalHotkeyUnstakesThisInterval::::get(hotkey); + } + + pub fn get_target_unstakes_per_interval() -> u64 { + return TargetUnstakesPerInterval::::get(); + } + // Creates a cold - hot pairing account if the hotkey is not already an active account. // pub fn create_account_if_non_existent(coldkey: &T::AccountId, hotkey: &T::AccountId) { diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index 66744ba3ad..1cdb44991e 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -140,6 +140,26 @@ impl Pallet { WeightsMinStake::::put(min_stake); Self::deposit_event(Event::WeightsMinStake(min_stake)); } + pub fn set_target_stakes_per_interval(target_stakes_per_interval: u64) { + TargetStakesPerInterval::::set(target_stakes_per_interval) + } + pub fn set_stakes_this_interval_for_hotkey(hotkey: &T::AccountId, stakes_this_interval: u64) { + TotalHotkeyStakesThisInterval::::insert(hotkey, stakes_this_interval); + } + pub fn set_target_unstakes_per_interval(target_stakes_per_interval: u64) { + TargetUnstakesPerInterval::::set(target_stakes_per_interval) + } + pub fn set_unstakes_this_interval_for_hotkey( + hotkey: &T::AccountId, + registrations_this_interval: u64, + ) { + TotalHotkeyUnstakesThisInterval::::insert(hotkey, registrations_this_interval); + } + pub fn reset_stakes_and_unstakes_this_interval() { + // This removes all key-value pairs from the storage map + let _ = TotalHotkeyStakesThisInterval::::clear(u32::MAX, None); + let _ = TotalHotkeyUnstakesThisInterval::::clear(u32::MAX, None); + } pub fn get_rank_for_uid(netuid: u16, uid: u16) -> u16 { let vec = Rank::::get(netuid); diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index db50f03d09..3b11869d2d 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -160,6 +160,8 @@ parameter_types! { pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks. pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; + pub const InitialTargetStakesPerInterval: u16 = 1; + pub const InitialTargetUnstakesPerInterval: u16 = 1; } // Configure collective pallet for council @@ -357,6 +359,8 @@ impl pallet_subtensor::Config for Test { type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval; type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; + type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; + type InitialTargetUnstakesPerInterval = InitialTargetUnstakesPerInterval; } impl pallet_utility::Config for Test { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 26d71cd0c1..c7e164e733 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -665,6 +665,8 @@ parameter_types! { pub const SubtensorInitialSubnetLimit: u16 = 12; pub const SubtensorInitialNetworkLockReductionInterval: u64 = 14 * 7200; pub const SubtensorInitialNetworkRateLimit: u64 = 1 * 7200; + pub const SubtensorInitialTargetStakesPerInterval: u16 = 1; + pub const SubtensorInitialTargetUnstakesPerInterval: u16 = 1; } impl pallet_subtensor::Config for Runtime { @@ -713,6 +715,8 @@ impl pallet_subtensor::Config for Runtime { type InitialSubnetOwnerCut = SubtensorInitialSubnetOwnerCut; type InitialSubnetLimit = SubtensorInitialSubnetLimit; type InitialNetworkRateLimit = SubtensorInitialNetworkRateLimit; + type InitialTargetStakesPerInterval = SubtensorInitialTargetStakesPerInterval; + type InitialTargetUnstakesPerInterval = SubtensorInitialTargetUnstakesPerInterval; } use sp_runtime::BoundedVec; From 25db03685fa1588cb437b1167e6377a4e86a9e66 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:19:14 -0700 Subject: [PATCH 02/11] add tests for stake & unstake rate limits --- pallets/subtensor/tests/staking.rs | 210 ++++++++++++++++++++++++++++- 1 file changed, 207 insertions(+), 3 deletions(-) diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 5d907c20bf..70f9986b3d 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1,11 +1,12 @@ -use frame_support::{assert_noop, assert_ok, traits::Currency}; +use frame_support::{assert_err, assert_noop, assert_ok, traits::Currency}; use frame_system::Config; mod mock; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}; -use frame_support::sp_runtime::DispatchError; +use frame_support::sp_runtime::{transaction_validity::InvalidTransaction, DispatchError}; use mock::*; -use pallet_subtensor::Error; +use pallet_subtensor::{Error, Error::StakeRateLimitExceeded, SubtensorSignedExtension}; use sp_core::{H256, U256}; +use sp_runtime::traits::{DispatchInfoOf, SignedExtension}; /*********************************************************** staking::add_stake() tests @@ -333,9 +334,212 @@ fn test_add_stake_total_issuance_no_change() { }); } +#[test] +fn test_reset_stakes_per_interval() { + new_test_ext().execute_with(|| { + let hotkey = U256::from(561337); + + SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey, 5); + assert_eq!( + SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), + 5 + ); + + SubtensorModule::reset_stakes_and_unstakes_this_interval(); + assert_eq!( + SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), + 0 + ); + + SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey, 6); + SubtensorModule::set_tempo(0, 3); + step_block(3); + assert_eq!( + SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), + 0 + ); + }); +} +#[test] +fn test_add_stake_under_limit() { + new_test_ext().execute_with(|| { + let hotkey_account_id = U256::from(561337); + let coldkey_account_id = U256::from(61337); + let who: ::AccountId = hotkey_account_id.into(); + let netuid: u16 = 1; + let start_nonce: u64 = 0; + let tempo: u16 = 13; + let max_stakes = 2; + + SubtensorModule::set_target_stakes_per_interval(max_stakes); + + let call: pallet_subtensor::Call = pallet_subtensor::Call::add_stake { + hotkey: hotkey_account_id, + amount_staked: 1, + }; + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorSignedExtension::::new(); + let result = extension.validate(&who, &call.into(), &info, 10); + + assert_ok!(result); + + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey_account_id), + hotkey_account_id, + 1, + )); + assert_ok!(SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey_account_id), + hotkey_account_id, + 1, + )); + + let current_stakes = + SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey_account_id); + assert!(current_stakes <= max_stakes); + }); +} + +#[test] +fn test_add_stake_rate_limit_exceeded() { + new_test_ext().execute_with(|| { + let hotkey_account_id = U256::from(561337); + let coldkey_account_id = U256::from(61337); + let who: ::AccountId = hotkey_account_id.into(); + let netuid: u16 = 1; + let start_nonce: u64 = 0; + let tempo: u16 = 13; + let max_stakes = 2; + + SubtensorModule::set_target_stakes_per_interval(max_stakes); + SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey_account_id, max_stakes); + + let call: pallet_subtensor::Call = pallet_subtensor::Call::add_stake { + hotkey: hotkey_account_id, + amount_staked: 1, + }; + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorSignedExtension::::new(); + let result = extension.validate(&who, &call.into(), &info, 10); + + assert_err!(result, InvalidTransaction::ExhaustsResources); + + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + assert_err!( + SubtensorModule::add_stake( + <::RuntimeOrigin>::signed(coldkey_account_id), + hotkey_account_id, + 1, + ), + Error::::StakeRateLimitExceeded + ); + + let current_stakes = + SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey_account_id); + assert_eq!(current_stakes, max_stakes); + }); +} + // /*********************************************************** // staking::remove_stake() tests // ************************************************************/ +#[test] +fn test_remove_stake_under_limit() { + new_test_ext().execute_with(|| { + let hotkey_account_id = U256::from(561337); + let coldkey_account_id = U256::from(61337); + let who: ::AccountId = hotkey_account_id.into(); + let netuid: u16 = 1; + let start_nonce: u64 = 0; + let tempo: u16 = 13; + let max_unstakes = 2; + + SubtensorModule::set_target_unstakes_per_interval(max_unstakes); + + let call = pallet_subtensor::Call::remove_stake { + hotkey: hotkey_account_id, + amount_unstaked: 1, + }; + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorSignedExtension::::new(); + let result = extension.validate(&who, &call.into(), &info, 10); + + assert_ok!(result); + + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 2); + + assert_ok!(SubtensorModule::remove_stake( + <::RuntimeOrigin>::signed(coldkey_account_id), + hotkey_account_id, + 1, + )); + assert_ok!(SubtensorModule::remove_stake( + <::RuntimeOrigin>::signed(coldkey_account_id), + hotkey_account_id, + 1, + )); + + let current_unstakes = + SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey_account_id); + assert!(current_unstakes <= max_unstakes); + }); +} + +#[test] +fn test_remove_stake_rate_limit_exceeded() { + new_test_ext().execute_with(|| { + let hotkey_account_id = U256::from(561337); + let coldkey_account_id = U256::from(61337); + let who: ::AccountId = hotkey_account_id.into(); + let netuid: u16 = 1; + let start_nonce: u64 = 0; + let tempo: u16 = 13; + let max_unstakes = 1; + + SubtensorModule::set_target_unstakes_per_interval(max_unstakes); + SubtensorModule::set_unstakes_this_interval_for_hotkey(&hotkey_account_id, max_unstakes); + + let call = pallet_subtensor::Call::remove_stake { + hotkey: hotkey_account_id, + amount_unstaked: 1, + }; + let info: DispatchInfo = + DispatchInfoOf::<::RuntimeCall>::default(); + let extension = SubtensorSignedExtension::::new(); + let result = extension.validate(&who, &call.into(), &info, 10); + + assert_err!(result, InvalidTransaction::ExhaustsResources); + + add_network(netuid, tempo, 0); + register_ok_neuron(netuid, hotkey_account_id, coldkey_account_id, start_nonce); + SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, 60000); + SubtensorModule::increase_stake_on_hotkey_account(&hotkey_account_id, 2); + assert_err!( + SubtensorModule::remove_stake( + <::RuntimeOrigin>::signed(coldkey_account_id), + hotkey_account_id, + 2, + ), + Error::::UnstakeRateLimitExceeded + ); + + let current_unstakes = + SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey_account_id); + assert_eq!(current_unstakes, max_unstakes); + }); +} + #[test] #[cfg(not(tarpaulin))] fn test_remove_stake_dispatch_info_ok() { From f6c1d342d9ee6e42ec40e51af6ea64b53b7978fe Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:29:51 -0700 Subject: [PATCH 03/11] fix new errors in old tests --- pallets/subtensor/tests/staking.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 70f9986b3d..b709dd39bf 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -1222,6 +1222,8 @@ fn test_full_with_delegating() { SubtensorModule::set_max_registrations_per_block(netuid, 4); SubtensorModule::set_target_registrations_per_interval(netuid, 4); SubtensorModule::set_max_allowed_uids(netuid, 4); // Allow all 4 to be registered at once + SubtensorModule::set_target_stakes_per_interval(10); // Increase max stakes per interval + SubtensorModule::set_target_unstakes_per_interval(10); // Neither key can add stake because they dont have fundss. assert_eq!( @@ -1801,6 +1803,7 @@ fn test_full_with_delegating_some_servers() { let coldkey1 = U256::from(4); SubtensorModule::set_max_registrations_per_block(netuid, 4); SubtensorModule::set_max_allowed_uids(netuid, 10); // Allow at least 10 to be registered at once, so no unstaking occurs + SubtensorModule::set_target_stakes_per_interval(10); // Increase max stakes per interval // Neither key can add stake because they dont have fundss. assert_eq!( @@ -2128,6 +2131,7 @@ fn test_full_block_emission_occurs() { let coldkey1 = U256::from(4); SubtensorModule::set_max_registrations_per_block(netuid, 4); SubtensorModule::set_max_allowed_uids(netuid, 10); // Allow at least 10 to be registered at once, so no unstaking occurs + SubtensorModule::set_target_stakes_per_interval(10); // Increase max stakes per interval // Neither key can add stake because they dont have fundss. assert_eq!( From 9ff231d88a6d07824846d65766f4bd3b4eb4748b Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:46:40 -0700 Subject: [PATCH 04/11] add unstakes to test_reset_stakes_per_interval --- pallets/subtensor/tests/staking.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index b709dd39bf..95bf11d8ed 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -339,6 +339,7 @@ fn test_reset_stakes_per_interval() { new_test_ext().execute_with(|| { let hotkey = U256::from(561337); + // ** Stakes ** SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey, 5); assert_eq!( SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), @@ -358,6 +359,27 @@ fn test_reset_stakes_per_interval() { SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), 0 ); + + // ** Unstakes ** + SubtensorModule::set_unstakes_this_interval_for_hotkey(&hotkey, 5); + assert_eq!( + SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey), + 5 + ); + + SubtensorModule::reset_stakes_and_unstakes_this_interval(); + assert_eq!( + SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey), + 0 + ); + + SubtensorModule::set_unstakes_this_interval_for_hotkey(&hotkey, 6); + SubtensorModule::set_tempo(0, 3); + step_block(3); + assert_eq!( + SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey), + 0 + ); }); } #[test] From 3c4a9f34dc67d5d86c242ad77100502a274d775d Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 27 Mar 2024 08:52:56 -0700 Subject: [PATCH 05/11] remove unstake trackers --- pallets/admin-utils/tests/mock.rs | 2 -- pallets/subtensor/src/lib.rs | 18 +++------------ pallets/subtensor/src/staking.rs | 14 +++--------- pallets/subtensor/src/utils.rs | 10 --------- pallets/subtensor/tests/mock.rs | 2 -- pallets/subtensor/tests/staking.rs | 35 +++++------------------------- runtime/src/lib.rs | 2 -- 7 files changed, 12 insertions(+), 71 deletions(-) diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index 4656d39c5d..3a67264b11 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -111,7 +111,6 @@ parameter_types! { pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; pub const InitialTargetStakesPerInterval: u16 = 1; - pub const InitialTargetUnstakesPerInterval: u16 = 1; } @@ -162,7 +161,6 @@ impl pallet_subtensor::Config for Test { type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; - type InitialTargetUnstakesPerInterval = InitialTargetUnstakesPerInterval; } impl system::Config for Test { diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index de89da6dbe..0bb1772dbc 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -184,7 +184,6 @@ pub mod pallet { type InitialNetworkRateLimit: Get; #[pallet::constant] // Initial target stakes per interval issuance. type InitialTargetStakesPerInterval: Get; - type InitialTargetUnstakesPerInterval: Get; } pub type AccountIdOf = ::AccountId; @@ -230,10 +229,6 @@ pub mod pallet { pub fn DefaultTargetStakesPerInterval() -> u64 { T::InitialTargetStakesPerInterval::get() } - #[pallet::type_value] - pub fn DefaultTargetUnstakesPerInterval() -> u64 { - T::InitialTargetUnstakesPerInterval::get() - } #[pallet::storage] // --- ITEM ( total_stake ) pub type TotalStake = StorageValue<_, u64, ValueQuery>; @@ -246,9 +241,6 @@ pub mod pallet { #[pallet::storage] // --- ITEM (target_stakes_per_interval) pub type TargetStakesPerInterval = StorageValue<_, u64, ValueQuery, DefaultTargetStakesPerInterval>; - #[pallet::storage] // --- ITEM (target_unstakes_per_interval) - pub type TargetUnstakesPerInterval = - StorageValue<_, u64, ValueQuery, DefaultTargetUnstakesPerInterval>; #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total amount of stake under a hotkey. pub type TotalHotkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; @@ -258,9 +250,6 @@ pub mod pallet { #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total number of stakes under a hotkey this interval pub type TotalHotkeyStakesThisInterval = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; - #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total number of unstakes under a hotkey this interval - pub type TotalHotkeyUnstakesThisInterval = - StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; #[pallet::storage] // --- MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey. pub type Owner = StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; @@ -1856,11 +1845,10 @@ where }) } Some(Call::remove_stake { hotkey, .. }) => { - let unstakes_this_interval = - Pallet::::get_unstakes_this_interval_for_hotkey(hotkey); - let max_unstakes_per_interval = Pallet::::get_target_unstakes_per_interval(); + let stakes_this_interval = Pallet::::get_stakes_this_interval_for_hotkey(hotkey); + let max_stakes_per_interval = Pallet::::get_target_stakes_per_interval(); - if unstakes_this_interval >= max_unstakes_per_interval { + if stakes_this_interval >= max_stakes_per_interval { return InvalidTransaction::ExhaustsResources.into(); } diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index bac83305bb..a2344c83aa 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -284,9 +284,9 @@ impl Pallet { ); // --- 7. Ensure we don't exceed stake rate limit - let unstakes_this_interval = Self::get_unstakes_this_interval_for_hotkey(&hotkey); + let unstakes_this_interval = Self::get_stakes_this_interval_for_hotkey(&hotkey); ensure!( - unstakes_this_interval < Self::get_target_unstakes_per_interval(), + unstakes_this_interval < Self::get_target_stakes_per_interval(), Error::::UnstakeRateLimitExceeded ); @@ -297,7 +297,7 @@ impl Pallet { Self::add_balance_to_coldkey_account(&coldkey, stake_to_be_added_as_currency.unwrap()); // --- 10. Increment stakes this interval for the given hotkey - Self::set_unstakes_this_interval_for_hotkey(&hotkey, unstakes_this_interval + 1); + Self::set_stakes_this_interval_for_hotkey(&hotkey, unstakes_this_interval + 1); // Set last block for rate limiting Self::set_last_tx_block(&coldkey, block); @@ -370,14 +370,6 @@ impl Pallet { return TargetStakesPerInterval::::get(); } - pub fn get_unstakes_this_interval_for_hotkey(hotkey: &T::AccountId) -> u64 { - return TotalHotkeyUnstakesThisInterval::::get(hotkey); - } - - pub fn get_target_unstakes_per_interval() -> u64 { - return TargetUnstakesPerInterval::::get(); - } - // Creates a cold - hot pairing account if the hotkey is not already an active account. // pub fn create_account_if_non_existent(coldkey: &T::AccountId, hotkey: &T::AccountId) { diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index 1cdb44991e..7f71c33d34 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -146,19 +146,9 @@ impl Pallet { pub fn set_stakes_this_interval_for_hotkey(hotkey: &T::AccountId, stakes_this_interval: u64) { TotalHotkeyStakesThisInterval::::insert(hotkey, stakes_this_interval); } - pub fn set_target_unstakes_per_interval(target_stakes_per_interval: u64) { - TargetUnstakesPerInterval::::set(target_stakes_per_interval) - } - pub fn set_unstakes_this_interval_for_hotkey( - hotkey: &T::AccountId, - registrations_this_interval: u64, - ) { - TotalHotkeyUnstakesThisInterval::::insert(hotkey, registrations_this_interval); - } pub fn reset_stakes_and_unstakes_this_interval() { // This removes all key-value pairs from the storage map let _ = TotalHotkeyStakesThisInterval::::clear(u32::MAX, None); - let _ = TotalHotkeyUnstakesThisInterval::::clear(u32::MAX, None); } pub fn get_rank_for_uid(netuid: u16, uid: u16) -> u16 { diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 3b11869d2d..f2fd10d3e9 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -161,7 +161,6 @@ parameter_types! { pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets. pub const InitialNetworkRateLimit: u64 = 0; pub const InitialTargetStakesPerInterval: u16 = 1; - pub const InitialTargetUnstakesPerInterval: u16 = 1; } // Configure collective pallet for council @@ -360,7 +359,6 @@ impl pallet_subtensor::Config for Test { type InitialSubnetLimit = InitialSubnetLimit; type InitialNetworkRateLimit = InitialNetworkRateLimit; type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval; - type InitialTargetUnstakesPerInterval = InitialTargetUnstakesPerInterval; } impl pallet_utility::Config for Test { diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 95bf11d8ed..31093786a1 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -4,7 +4,7 @@ mod mock; use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}; use frame_support::sp_runtime::{transaction_validity::InvalidTransaction, DispatchError}; use mock::*; -use pallet_subtensor::{Error, Error::StakeRateLimitExceeded, SubtensorSignedExtension}; +use pallet_subtensor::{Error, SubtensorSignedExtension}; use sp_core::{H256, U256}; use sp_runtime::traits::{DispatchInfoOf, SignedExtension}; @@ -339,7 +339,6 @@ fn test_reset_stakes_per_interval() { new_test_ext().execute_with(|| { let hotkey = U256::from(561337); - // ** Stakes ** SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey, 5); assert_eq!( SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), @@ -359,27 +358,6 @@ fn test_reset_stakes_per_interval() { SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), 0 ); - - // ** Unstakes ** - SubtensorModule::set_unstakes_this_interval_for_hotkey(&hotkey, 5); - assert_eq!( - SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey), - 5 - ); - - SubtensorModule::reset_stakes_and_unstakes_this_interval(); - assert_eq!( - SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey), - 0 - ); - - SubtensorModule::set_unstakes_this_interval_for_hotkey(&hotkey, 6); - SubtensorModule::set_tempo(0, 3); - step_block(3); - assert_eq!( - SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey), - 0 - ); }); } #[test] @@ -483,7 +461,7 @@ fn test_remove_stake_under_limit() { let tempo: u16 = 13; let max_unstakes = 2; - SubtensorModule::set_target_unstakes_per_interval(max_unstakes); + SubtensorModule::set_target_stakes_per_interval(max_unstakes); let call = pallet_subtensor::Call::remove_stake { hotkey: hotkey_account_id, @@ -513,7 +491,7 @@ fn test_remove_stake_under_limit() { )); let current_unstakes = - SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey_account_id); + SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey_account_id); assert!(current_unstakes <= max_unstakes); }); } @@ -529,8 +507,8 @@ fn test_remove_stake_rate_limit_exceeded() { let tempo: u16 = 13; let max_unstakes = 1; - SubtensorModule::set_target_unstakes_per_interval(max_unstakes); - SubtensorModule::set_unstakes_this_interval_for_hotkey(&hotkey_account_id, max_unstakes); + SubtensorModule::set_target_stakes_per_interval(max_unstakes); + SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey_account_id, max_unstakes); let call = pallet_subtensor::Call::remove_stake { hotkey: hotkey_account_id, @@ -557,7 +535,7 @@ fn test_remove_stake_rate_limit_exceeded() { ); let current_unstakes = - SubtensorModule::get_unstakes_this_interval_for_hotkey(&hotkey_account_id); + SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey_account_id); assert_eq!(current_unstakes, max_unstakes); }); } @@ -1245,7 +1223,6 @@ fn test_full_with_delegating() { SubtensorModule::set_target_registrations_per_interval(netuid, 4); SubtensorModule::set_max_allowed_uids(netuid, 4); // Allow all 4 to be registered at once SubtensorModule::set_target_stakes_per_interval(10); // Increase max stakes per interval - SubtensorModule::set_target_unstakes_per_interval(10); // Neither key can add stake because they dont have fundss. assert_eq!( diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index c7e164e733..9b8fe1591c 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -666,7 +666,6 @@ parameter_types! { pub const SubtensorInitialNetworkLockReductionInterval: u64 = 14 * 7200; pub const SubtensorInitialNetworkRateLimit: u64 = 1 * 7200; pub const SubtensorInitialTargetStakesPerInterval: u16 = 1; - pub const SubtensorInitialTargetUnstakesPerInterval: u16 = 1; } impl pallet_subtensor::Config for Runtime { @@ -716,7 +715,6 @@ impl pallet_subtensor::Config for Runtime { type InitialSubnetLimit = SubtensorInitialSubnetLimit; type InitialNetworkRateLimit = SubtensorInitialNetworkRateLimit; type InitialTargetStakesPerInterval = SubtensorInitialTargetStakesPerInterval; - type InitialTargetUnstakesPerInterval = SubtensorInitialTargetUnstakesPerInterval; } use sp_runtime::BoundedVec; From fdff1014ed29b008fb258f145ea965c51784075d Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:07:38 -0700 Subject: [PATCH 06/11] prevent multiple stake TXs in pool --- pallets/subtensor/src/lib.rs | 2 ++ pallets/subtensor/src/staking.rs | 14 ++++---------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 0bb1772dbc..dd07518a3e 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1839,6 +1839,7 @@ where return InvalidTransaction::ExhaustsResources.into(); } + Pallet::::set_stakes_this_interval_for_hotkey(hotkey, stakes_this_interval + 1); Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() @@ -1852,6 +1853,7 @@ where return InvalidTransaction::ExhaustsResources.into(); } + Pallet::::set_stakes_this_interval_for_hotkey(hotkey, stakes_this_interval + 1); Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index a2344c83aa..1dc0ff32c4 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -180,13 +180,10 @@ impl Pallet { // --- 9. If we reach here, add the balance to the hotkey. Self::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake_to_be_added); - // --- 10. Increment stakes this interval for the given hotkey - Self::set_stakes_this_interval_for_hotkey(&hotkey, stakes_this_interval + 1); - // Set last block for rate limiting Self::set_last_tx_block(&coldkey, block); - // --- 11. Emit the staking event. + // --- 10. Emit the staking event. log::info!( "StakeAdded( hotkey:{:?}, stake_to_be_added:{:?} )", hotkey, @@ -194,7 +191,7 @@ impl Pallet { ); Self::deposit_event(Event::StakeAdded(hotkey, stake_to_be_added)); - // --- 12. Ok and return. + // --- 11. Ok and return. Ok(()) } @@ -296,13 +293,10 @@ impl Pallet { // --- 9. We add the balancer to the coldkey. If the above fails we will not credit this coldkey. Self::add_balance_to_coldkey_account(&coldkey, stake_to_be_added_as_currency.unwrap()); - // --- 10. Increment stakes this interval for the given hotkey - Self::set_stakes_this_interval_for_hotkey(&hotkey, unstakes_this_interval + 1); - // Set last block for rate limiting Self::set_last_tx_block(&coldkey, block); - // --- 11. Emit the unstaking event. + // --- 10. Emit the unstaking event. log::info!( "StakeRemoved( hotkey:{:?}, stake_to_be_removed:{:?} )", hotkey, @@ -310,7 +304,7 @@ impl Pallet { ); Self::deposit_event(Event::StakeRemoved(hotkey, stake_to_be_removed)); - // --- 12. Done and ok. + // --- 11. Done and ok. Ok(()) } From 9af876e69230ef52e28cfede94b43ab729cda9c3 Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 2 Apr 2024 18:38:16 -0700 Subject: [PATCH 07/11] add changeable staking interval --- pallets/subtensor/src/lib.rs | 18 ++++++++++++++---- pallets/subtensor/src/root.rs | 3 --- pallets/subtensor/src/staking.rs | 25 ++++++++++++++++++++++++- pallets/subtensor/src/utils.rs | 10 ++++------ pallets/subtensor/tests/staking.rs | 30 ++++++++++++++++++++++-------- 5 files changed, 64 insertions(+), 22 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index dd07518a3e..c2be4d7386 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -210,6 +210,10 @@ pub mod pallet { 0 } #[pallet::type_value] + pub fn DefaultStakesPerInterval() -> (u64, u64) { + (0, 0) + } + #[pallet::type_value] pub fn DefaultBlockEmission() -> u64 { 1_000_000_000 } @@ -229,6 +233,10 @@ pub mod pallet { pub fn DefaultTargetStakesPerInterval() -> u64 { T::InitialTargetStakesPerInterval::get() } + #[pallet::type_value] + pub fn DefaultStakeInterval() -> u64 { + 360 + } #[pallet::storage] // --- ITEM ( total_stake ) pub type TotalStake = StorageValue<_, u64, ValueQuery>; @@ -241,15 +249,19 @@ pub mod pallet { #[pallet::storage] // --- ITEM (target_stakes_per_interval) pub type TargetStakesPerInterval = StorageValue<_, u64, ValueQuery, DefaultTargetStakesPerInterval>; + #[pallet::storage] // --- ITEM (default_stake_interval) + pub type StakeInterval = StorageValue<_, u64, ValueQuery, DefaultStakeInterval>; #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total amount of stake under a hotkey. pub type TotalHotkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; #[pallet::storage] // --- MAP ( cold ) --> stake | Returns the total amount of stake under a coldkey. pub type TotalColdkeyStake = StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; - #[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total number of stakes under a hotkey this interval + #[pallet::storage] + // --- MAP (hot) --> stake | Returns a tuple (u64: stakes, u64: block_number) pub type TotalHotkeyStakesThisInterval = - StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake>; + StorageMap<_, Identity, T::AccountId, (u64, u64), ValueQuery, DefaultStakesPerInterval>; + #[pallet::storage] // --- MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey. pub type Owner = StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount>; @@ -1839,7 +1851,6 @@ where return InvalidTransaction::ExhaustsResources.into(); } - Pallet::::set_stakes_this_interval_for_hotkey(hotkey, stakes_this_interval + 1); Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() @@ -1853,7 +1864,6 @@ where return InvalidTransaction::ExhaustsResources.into(); } - Pallet::::set_stakes_this_interval_for_hotkey(hotkey, stakes_this_interval + 1); Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() diff --git a/pallets/subtensor/src/root.rs b/pallets/subtensor/src/root.rs index 08679ee510..dd5d7f7841 100644 --- a/pallets/subtensor/src/root.rs +++ b/pallets/subtensor/src/root.rs @@ -246,9 +246,6 @@ impl Pallet { return Err("Not the block to update emission values."); } - // Resets the trackers for stakes and unstakes during a given interval - Self::reset_stakes_and_unstakes_this_interval(); - // --- 1. Retrieves the number of root validators on subnets. let n: u16 = Self::get_num_root_validators(); log::debug!("n:\n{:?}\n", n); diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index 1dc0ff32c4..df0cf4b8bd 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -356,8 +356,31 @@ impl Pallet { return Stake::::get(hotkey, coldkey); } + // Retrieves the total stakes for a given hotkey (account ID) for the current staking interval. pub fn get_stakes_this_interval_for_hotkey(hotkey: &T::AccountId) -> u64 { - return TotalHotkeyStakesThisInterval::::get(hotkey); + // Retrieve the configured stake interval duration from storage. + let stake_interval = StakeInterval::::get(); + + // Obtain the current block number as an unsigned 64-bit integer. + let current_block = Self::get_current_block_as_u64(); + + // Fetch the total stakes and the last block number when stakes were made for the hotkey. + let (stakes, block_last_staked_at) = TotalHotkeyStakesThisInterval::::get(hotkey); + + // Calculate the block number after which the stakes for the hotkey should be reset. + let block_to_reset_after = block_last_staked_at + stake_interval; + + // If the current block number is beyond the reset point, + // it indicates the end of the staking interval for the hotkey. + if block_to_reset_after <= current_block { + // Reset the stakes for this hotkey for the current interval. + Self::set_stakes_this_interval_for_hotkey(hotkey, 0, block_last_staked_at); + // Return 0 as the stake amount since we've just reset the stakes. + return 0; + } + + // If the staking interval has not yet ended, return the current stake amount. + stakes } pub fn get_target_stakes_per_interval() -> u64 { diff --git a/pallets/subtensor/src/utils.rs b/pallets/subtensor/src/utils.rs index 7f71c33d34..6e30eb1f5a 100644 --- a/pallets/subtensor/src/utils.rs +++ b/pallets/subtensor/src/utils.rs @@ -143,14 +143,12 @@ impl Pallet { pub fn set_target_stakes_per_interval(target_stakes_per_interval: u64) { TargetStakesPerInterval::::set(target_stakes_per_interval) } - pub fn set_stakes_this_interval_for_hotkey(hotkey: &T::AccountId, stakes_this_interval: u64) { - TotalHotkeyStakesThisInterval::::insert(hotkey, stakes_this_interval); + pub fn set_stakes_this_interval_for_hotkey(hotkey: &T::AccountId, stakes_this_interval: u64, last_staked_block_number: u64) { + TotalHotkeyStakesThisInterval::::insert(hotkey, (stakes_this_interval, last_staked_block_number)); } - pub fn reset_stakes_and_unstakes_this_interval() { - // This removes all key-value pairs from the storage map - let _ = TotalHotkeyStakesThisInterval::::clear(u32::MAX, None); + pub fn set_stake_interval(block: u64) { + StakeInterval::::set(block); } - pub fn get_rank_for_uid(netuid: u16, uid: u16) -> u16 { let vec = Rank::::get(netuid); if (uid as usize) < vec.len() { diff --git a/pallets/subtensor/tests/staking.rs b/pallets/subtensor/tests/staking.rs index 31093786a1..7f905bebad 100644 --- a/pallets/subtensor/tests/staking.rs +++ b/pallets/subtensor/tests/staking.rs @@ -339,27 +339,31 @@ fn test_reset_stakes_per_interval() { new_test_ext().execute_with(|| { let hotkey = U256::from(561337); - SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey, 5); + SubtensorModule::set_stake_interval(7); + SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey, 5, 1); + step_block(1); + assert_eq!( SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), 5 ); - SubtensorModule::reset_stakes_and_unstakes_this_interval(); + // block: 7 interval not yet passed + step_block(6); assert_eq!( SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), - 0 + 5 ); - SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey, 6); - SubtensorModule::set_tempo(0, 3); - step_block(3); + // block 8: interval passed + step_block(1); assert_eq!( SubtensorModule::get_stakes_this_interval_for_hotkey(&hotkey), 0 ); }); } + #[test] fn test_add_stake_under_limit() { new_test_ext().execute_with(|| { @@ -414,9 +418,14 @@ fn test_add_stake_rate_limit_exceeded() { let start_nonce: u64 = 0; let tempo: u16 = 13; let max_stakes = 2; + let block_number = 1; SubtensorModule::set_target_stakes_per_interval(max_stakes); - SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey_account_id, max_stakes); + SubtensorModule::set_stakes_this_interval_for_hotkey( + &hotkey_account_id, + max_stakes, + block_number, + ); let call: pallet_subtensor::Call = pallet_subtensor::Call::add_stake { hotkey: hotkey_account_id, @@ -506,9 +515,14 @@ fn test_remove_stake_rate_limit_exceeded() { let start_nonce: u64 = 0; let tempo: u16 = 13; let max_unstakes = 1; + let block_number = 1; SubtensorModule::set_target_stakes_per_interval(max_unstakes); - SubtensorModule::set_stakes_this_interval_for_hotkey(&hotkey_account_id, max_unstakes); + SubtensorModule::set_stakes_this_interval_for_hotkey( + &hotkey_account_id, + max_unstakes, + block_number, + ); let call = pallet_subtensor::Call::remove_stake { hotkey: hotkey_account_id, From 81119da4979df8379017b6f6576678c79c8ebd8f Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Tue, 2 Apr 2024 20:24:44 -0700 Subject: [PATCH 08/11] readd setters --- pallets/subtensor/src/lib.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c2be4d7386..52c1b4354b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1851,6 +1851,12 @@ where return InvalidTransaction::ExhaustsResources.into(); } + let current_block_number = Pallet::::get_current_block_as_u64(); + Pallet::::set_stakes_this_interval_for_hotkey( + hotkey, + stakes_this_interval + 1, + current_block_number, + ); Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() @@ -1864,6 +1870,12 @@ where return InvalidTransaction::ExhaustsResources.into(); } + let current_block_number = Pallet::::get_current_block_as_u64(); + Pallet::::set_stakes_this_interval_for_hotkey( + hotkey, + stakes_this_interval + 1, + current_block_number, + ); Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() From 27a1a276d643674afe5892f374eb294f787c2f6b Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:10:32 -0700 Subject: [PATCH 09/11] move setters to execution --- pallets/subtensor/src/lib.rs | 12 ------------ pallets/subtensor/src/staking.rs | 10 ++++++++++ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 52c1b4354b..c2be4d7386 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1851,12 +1851,6 @@ where return InvalidTransaction::ExhaustsResources.into(); } - let current_block_number = Pallet::::get_current_block_as_u64(); - Pallet::::set_stakes_this_interval_for_hotkey( - hotkey, - stakes_this_interval + 1, - current_block_number, - ); Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() @@ -1870,12 +1864,6 @@ where return InvalidTransaction::ExhaustsResources.into(); } - let current_block_number = Pallet::::get_current_block_as_u64(); - Pallet::::set_stakes_this_interval_for_hotkey( - hotkey, - stakes_this_interval + 1, - current_block_number, - ); Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default() diff --git a/pallets/subtensor/src/staking.rs b/pallets/subtensor/src/staking.rs index df0cf4b8bd..df760d02e2 100644 --- a/pallets/subtensor/src/staking.rs +++ b/pallets/subtensor/src/staking.rs @@ -184,6 +184,11 @@ impl Pallet { Self::set_last_tx_block(&coldkey, block); // --- 10. Emit the staking event. + Self::set_stakes_this_interval_for_hotkey( + &hotkey, + stakes_this_interval + 1, + block, + ); log::info!( "StakeAdded( hotkey:{:?}, stake_to_be_added:{:?} )", hotkey, @@ -297,6 +302,11 @@ impl Pallet { Self::set_last_tx_block(&coldkey, block); // --- 10. Emit the unstaking event. + Self::set_stakes_this_interval_for_hotkey( + &hotkey, + unstakes_this_interval + 1, + block, + ); log::info!( "StakeRemoved( hotkey:{:?}, stake_to_be_removed:{:?} )", hotkey, From ffa8811d2b04038672ef6f567f6a3d4bdf7b059d Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:10:44 -0700 Subject: [PATCH 10/11] fix new bug in old test --- pallets/subtensor/tests/senate.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pallets/subtensor/tests/senate.rs b/pallets/subtensor/tests/senate.rs index 4177476fd7..ca252c39c7 100644 --- a/pallets/subtensor/tests/senate.rs +++ b/pallets/subtensor/tests/senate.rs @@ -505,6 +505,8 @@ fn test_senate_not_leave_when_stake_removed() { let burn_cost = 1000; let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har + SubtensorModule::set_target_stakes_per_interval(2); + //add network SubtensorModule::set_burn(netuid, burn_cost); add_network(netuid, tempo, 0); From 4d0b7b23887d686cb7abb34ee5417e7c87aca3ef Mon Sep 17 00:00:00 2001 From: John Reed <87283488+JohnReedV@users.noreply.github.com> Date: Wed, 3 Apr 2024 17:47:49 -0700 Subject: [PATCH 11/11] cargo fmt --- pallets/subtensor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index b3538bef40..1ded32dd2c 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1868,7 +1868,7 @@ where if stakes_this_interval >= max_stakes_per_interval { return InvalidTransaction::ExhaustsResources.into(); } - + Ok(ValidTransaction { priority: Self::get_priority_vanilla(), ..Default::default()