Skip to content

Commit a014285

Browse files
authored
Merge pull request #287 from opentensor/add_rate_limit_on_staking_transactions
Add rate limit on staking transactions
2 parents 34617b2 + 4d0b7b2 commit a014285

File tree

8 files changed

+358
-21
lines changed

8 files changed

+358
-21
lines changed

pallets/admin-utils/tests/mock.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ parameter_types! {
110110
pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks.
111111
pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets.
112112
pub const InitialNetworkRateLimit: u64 = 0;
113+
pub const InitialTargetStakesPerInterval: u16 = 1;
114+
113115
}
114116

115117
impl pallet_subtensor::Config for Test {
@@ -158,6 +160,7 @@ impl pallet_subtensor::Config for Test {
158160
type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval;
159161
type InitialSubnetLimit = InitialSubnetLimit;
160162
type InitialNetworkRateLimit = InitialNetworkRateLimit;
163+
type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval;
161164
}
162165

163166
impl system::Config for Test {

pallets/subtensor/src/lib.rs

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ pub mod pallet {
182182
type InitialSubnetLimit: Get<u16>;
183183
#[pallet::constant] // Initial network creation rate limit
184184
type InitialNetworkRateLimit: Get<u64>;
185+
#[pallet::constant] // Initial target stakes per interval issuance.
186+
type InitialTargetStakesPerInterval: Get<u64>;
185187
}
186188

187189
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
@@ -212,6 +214,10 @@ pub mod pallet {
212214
0
213215
}
214216
#[pallet::type_value]
217+
pub fn DefaultStakesPerInterval<T: Config>() -> (u64, u64) {
218+
(0, 0)
219+
}
220+
#[pallet::type_value]
215221
pub fn DefaultBlockEmission<T: Config>() -> u64 {
216222
1_000_000_000
217223
}
@@ -227,6 +233,14 @@ pub mod pallet {
227233
pub fn DefaultAccount<T: Config>() -> T::AccountId {
228234
T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap()
229235
}
236+
#[pallet::type_value]
237+
pub fn DefaultTargetStakesPerInterval<T: Config>() -> u64 {
238+
T::InitialTargetStakesPerInterval::get()
239+
}
240+
#[pallet::type_value]
241+
pub fn DefaultStakeInterval<T: Config>() -> u64 {
242+
360
243+
}
230244

231245
#[pallet::storage] // --- ITEM ( total_stake )
232246
pub type TotalStake<T> = StorageValue<_, u64, ValueQuery>;
@@ -236,12 +250,22 @@ pub mod pallet {
236250
pub type BlockEmission<T> = StorageValue<_, u64, ValueQuery, DefaultBlockEmission<T>>;
237251
#[pallet::storage] // --- ITEM ( total_issuance )
238252
pub type TotalIssuance<T> = StorageValue<_, u64, ValueQuery, DefaultTotalIssuance<T>>;
253+
#[pallet::storage] // --- ITEM (target_stakes_per_interval)
254+
pub type TargetStakesPerInterval<T> =
255+
StorageValue<_, u64, ValueQuery, DefaultTargetStakesPerInterval<T>>;
256+
#[pallet::storage] // --- ITEM (default_stake_interval)
257+
pub type StakeInterval<T> = StorageValue<_, u64, ValueQuery, DefaultStakeInterval<T>>;
239258
#[pallet::storage] // --- MAP ( hot ) --> stake | Returns the total amount of stake under a hotkey.
240259
pub type TotalHotkeyStake<T: Config> =
241260
StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake<T>>;
242261
#[pallet::storage] // --- MAP ( cold ) --> stake | Returns the total amount of stake under a coldkey.
243262
pub type TotalColdkeyStake<T: Config> =
244263
StorageMap<_, Identity, T::AccountId, u64, ValueQuery, DefaultAccountTake<T>>;
264+
#[pallet::storage]
265+
// --- MAP (hot) --> stake | Returns a tuple (u64: stakes, u64: block_number)
266+
pub type TotalHotkeyStakesThisInterval<T: Config> =
267+
StorageMap<_, Identity, T::AccountId, (u64, u64), ValueQuery, DefaultStakesPerInterval<T>>;
268+
245269
#[pallet::storage] // --- MAP ( hot ) --> cold | Returns the controlling coldkey for a hotkey.
246270
pub type Owner<T: Config> =
247271
StorageMap<_, Blake2_128Concat, T::AccountId, T::AccountId, ValueQuery, DefaultAccount<T>>;
@@ -924,7 +948,9 @@ pub mod pallet {
924948
MaxAllowedUidsExceeded, // --- Thrown when number of accounts going to be registered exceeds MaxAllowedUids for the network.
925949
TooManyUids, // ---- Thrown when the caller attempts to set weights with more uids than allowed.
926950
TxRateLimitExceeded, // --- Thrown when a transactor exceeds the rate limit for transactions.
927-
RegistrationDisabled, // --- Thrown when registration is disabled
951+
StakeRateLimitExceeded, // --- Thrown when a transactor exceeds the rate limit for stakes.
952+
UnstakeRateLimitExceeded, // --- Thrown when a transactor exceeds the rate limit for unstakes.
953+
RegistrationDisabled, // --- Thrown when registration is disabled
928954
TooManyRegistrationsThisInterval, // --- Thrown when registration attempt exceeds allowed in interval
929955
BenchmarkingOnly, // --- Thrown when a function is only available for benchmarking
930956
HotkeyOriginMismatch, // --- Thrown when the hotkey passed is not the origin, but it should be
@@ -1822,14 +1848,32 @@ where
18221848
return Err(InvalidTransaction::Call.into());
18231849
}
18241850
}
1825-
Some(Call::add_stake { .. }) => Ok(ValidTransaction {
1826-
priority: Self::get_priority_vanilla(),
1827-
..Default::default()
1828-
}),
1829-
Some(Call::remove_stake { .. }) => Ok(ValidTransaction {
1830-
priority: Self::get_priority_vanilla(),
1831-
..Default::default()
1832-
}),
1851+
Some(Call::add_stake { hotkey, .. }) => {
1852+
let stakes_this_interval = Pallet::<T>::get_stakes_this_interval_for_hotkey(hotkey);
1853+
let max_stakes_per_interval = Pallet::<T>::get_target_stakes_per_interval();
1854+
1855+
if stakes_this_interval >= max_stakes_per_interval {
1856+
return InvalidTransaction::ExhaustsResources.into();
1857+
}
1858+
1859+
Ok(ValidTransaction {
1860+
priority: Self::get_priority_vanilla(),
1861+
..Default::default()
1862+
})
1863+
}
1864+
Some(Call::remove_stake { hotkey, .. }) => {
1865+
let stakes_this_interval = Pallet::<T>::get_stakes_this_interval_for_hotkey(hotkey);
1866+
let max_stakes_per_interval = Pallet::<T>::get_target_stakes_per_interval();
1867+
1868+
if stakes_this_interval >= max_stakes_per_interval {
1869+
return InvalidTransaction::ExhaustsResources.into();
1870+
}
1871+
1872+
Ok(ValidTransaction {
1873+
priority: Self::get_priority_vanilla(),
1874+
..Default::default()
1875+
})
1876+
}
18331877
Some(Call::register { netuid, .. } | Call::burned_register { netuid, .. }) => {
18341878
let registrations_this_interval =
18351879
Pallet::<T>::get_registrations_this_interval(*netuid);

pallets/subtensor/src/staking.rs

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -164,27 +164,39 @@ impl<T: Config> Pallet<T> {
164164
Error::<T>::TxRateLimitExceeded
165165
);
166166

167-
// --- 7. Ensure the remove operation from the coldkey is a success.
167+
// --- 7. Ensure we don't exceed stake rate limit
168+
let stakes_this_interval = Self::get_stakes_this_interval_for_hotkey(&hotkey);
169+
ensure!(
170+
stakes_this_interval < Self::get_target_stakes_per_interval(),
171+
Error::<T>::StakeRateLimitExceeded
172+
);
173+
174+
// --- 8. Ensure the remove operation from the coldkey is a success.
168175
ensure!(
169176
Self::remove_balance_from_coldkey_account(&coldkey, stake_as_balance.unwrap()) == true,
170177
Error::<T>::BalanceWithdrawalError
171178
);
172179

173-
// --- 8. If we reach here, add the balance to the hotkey.
180+
// --- 9. If we reach here, add the balance to the hotkey.
174181
Self::increase_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake_to_be_added);
175182

176183
// Set last block for rate limiting
177184
Self::set_last_tx_block(&coldkey, block);
178185

179-
// --- 9. Emit the staking event.
186+
// --- 10. Emit the staking event.
187+
Self::set_stakes_this_interval_for_hotkey(
188+
&hotkey,
189+
stakes_this_interval + 1,
190+
block,
191+
);
180192
log::info!(
181193
"StakeAdded( hotkey:{:?}, stake_to_be_added:{:?} )",
182194
hotkey,
183195
stake_to_be_added
184196
);
185197
Self::deposit_event(Event::StakeAdded(hotkey, stake_to_be_added));
186198

187-
// --- 10. Ok and return.
199+
// --- 11. Ok and return.
188200
Ok(())
189201
}
190202

@@ -273,24 +285,36 @@ impl<T: Config> Pallet<T> {
273285
Error::<T>::TxRateLimitExceeded
274286
);
275287

276-
// --- 7. We remove the balance from the hotkey.
288+
// --- 7. Ensure we don't exceed stake rate limit
289+
let unstakes_this_interval = Self::get_stakes_this_interval_for_hotkey(&hotkey);
290+
ensure!(
291+
unstakes_this_interval < Self::get_target_stakes_per_interval(),
292+
Error::<T>::UnstakeRateLimitExceeded
293+
);
294+
295+
// --- 8. We remove the balance from the hotkey.
277296
Self::decrease_stake_on_coldkey_hotkey_account(&coldkey, &hotkey, stake_to_be_removed);
278297

279-
// --- 8. We add the balancer to the coldkey. If the above fails we will not credit this coldkey.
298+
// --- 9. We add the balancer to the coldkey. If the above fails we will not credit this coldkey.
280299
Self::add_balance_to_coldkey_account(&coldkey, stake_to_be_added_as_currency.unwrap());
281300

282301
// Set last block for rate limiting
283302
Self::set_last_tx_block(&coldkey, block);
284303

285-
// --- 9. Emit the unstaking event.
304+
// --- 10. Emit the unstaking event.
305+
Self::set_stakes_this_interval_for_hotkey(
306+
&hotkey,
307+
unstakes_this_interval + 1,
308+
block,
309+
);
286310
log::info!(
287311
"StakeRemoved( hotkey:{:?}, stake_to_be_removed:{:?} )",
288312
hotkey,
289313
stake_to_be_removed
290314
);
291315
Self::deposit_event(Event::StakeRemoved(hotkey, stake_to_be_removed));
292316

293-
// --- 10. Done and ok.
317+
// --- 11. Done and ok.
294318
Ok(())
295319
}
296320

@@ -342,6 +366,37 @@ impl<T: Config> Pallet<T> {
342366
return Stake::<T>::get(hotkey, coldkey);
343367
}
344368

369+
// Retrieves the total stakes for a given hotkey (account ID) for the current staking interval.
370+
pub fn get_stakes_this_interval_for_hotkey(hotkey: &T::AccountId) -> u64 {
371+
// Retrieve the configured stake interval duration from storage.
372+
let stake_interval = StakeInterval::<T>::get();
373+
374+
// Obtain the current block number as an unsigned 64-bit integer.
375+
let current_block = Self::get_current_block_as_u64();
376+
377+
// Fetch the total stakes and the last block number when stakes were made for the hotkey.
378+
let (stakes, block_last_staked_at) = TotalHotkeyStakesThisInterval::<T>::get(hotkey);
379+
380+
// Calculate the block number after which the stakes for the hotkey should be reset.
381+
let block_to_reset_after = block_last_staked_at + stake_interval;
382+
383+
// If the current block number is beyond the reset point,
384+
// it indicates the end of the staking interval for the hotkey.
385+
if block_to_reset_after <= current_block {
386+
// Reset the stakes for this hotkey for the current interval.
387+
Self::set_stakes_this_interval_for_hotkey(hotkey, 0, block_last_staked_at);
388+
// Return 0 as the stake amount since we've just reset the stakes.
389+
return 0;
390+
}
391+
392+
// If the staking interval has not yet ended, return the current stake amount.
393+
stakes
394+
}
395+
396+
pub fn get_target_stakes_per_interval() -> u64 {
397+
return TargetStakesPerInterval::<T>::get();
398+
}
399+
345400
// Creates a cold - hot pairing account if the hotkey is not already an active account.
346401
//
347402
pub fn create_account_if_non_existent(coldkey: &T::AccountId, hotkey: &T::AccountId) {

pallets/subtensor/src/utils.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,15 @@ impl<T: Config> Pallet<T> {
137137
WeightsMinStake::<T>::put(min_stake);
138138
Self::deposit_event(Event::WeightsMinStake(min_stake));
139139
}
140-
140+
pub fn set_target_stakes_per_interval(target_stakes_per_interval: u64) {
141+
TargetStakesPerInterval::<T>::set(target_stakes_per_interval)
142+
}
143+
pub fn set_stakes_this_interval_for_hotkey(hotkey: &T::AccountId, stakes_this_interval: u64, last_staked_block_number: u64) {
144+
TotalHotkeyStakesThisInterval::<T>::insert(hotkey, (stakes_this_interval, last_staked_block_number));
145+
}
146+
pub fn set_stake_interval(block: u64) {
147+
StakeInterval::<T>::set(block);
148+
}
141149
pub fn get_rank_for_uid(netuid: u16, uid: u16) -> u16 {
142150
let vec = Rank::<T>::get(netuid);
143151
if (uid as usize) < vec.len() {

pallets/subtensor/tests/mock.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ parameter_types! {
160160
pub const InitialNetworkLockReductionInterval: u64 = 2; // 2 blocks.
161161
pub const InitialSubnetLimit: u16 = 10; // Max 10 subnets.
162162
pub const InitialNetworkRateLimit: u64 = 0;
163+
pub const InitialTargetStakesPerInterval: u16 = 1;
163164
}
164165

165166
// Configure collective pallet for council
@@ -357,6 +358,7 @@ impl pallet_subtensor::Config for Test {
357358
type InitialNetworkLockReductionInterval = InitialNetworkLockReductionInterval;
358359
type InitialSubnetLimit = InitialSubnetLimit;
359360
type InitialNetworkRateLimit = InitialNetworkRateLimit;
361+
type InitialTargetStakesPerInterval = InitialTargetStakesPerInterval;
360362
}
361363

362364
impl pallet_utility::Config for Test {

pallets/subtensor/tests/senate.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,8 @@ fn test_senate_not_leave_when_stake_removed() {
505505
let burn_cost = 1000;
506506
let coldkey_account_id = U256::from(667); // Neighbour of the beast, har har
507507

508+
SubtensorModule::set_target_stakes_per_interval(2);
509+
508510
//add network
509511
SubtensorModule::set_burn(netuid, burn_cost);
510512
add_network(netuid, tempo, 0);

0 commit comments

Comments
 (0)