Skip to content

Commit 4b83813

Browse files
authored
Merge pull request #2147 from opentensor/fix/rate-limit-key
Add migration for RateLimitKey
2 parents fc0f611 + 5477ccb commit 4b83813

File tree

5 files changed

+361
-0
lines changed

5 files changed

+361
-0
lines changed

pallets/subtensor/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2206,16 +2206,22 @@ impl<T: Config + pallet_balances::Config<Balance = u64>>
22062206
#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo)]
22072207
pub enum RateLimitKey<AccountId> {
22082208
// The setting sn owner hotkey operation is rate limited per netuid
2209+
#[codec(index = 0)]
22092210
SetSNOwnerHotkey(NetUid),
22102211
// Generic rate limit for subnet-owner hyperparameter updates (per netuid)
2212+
#[codec(index = 1)]
22112213
OwnerHyperparamUpdate(NetUid, Hyperparameter),
22122214
// Subnet registration rate limit
2215+
#[codec(index = 2)]
22132216
NetworkLastRegistered,
22142217
// Last tx block limit per account ID
2218+
#[codec(index = 3)]
22152219
LastTxBlock(AccountId),
22162220
// Last tx block child key limit per account ID
2221+
#[codec(index = 4)]
22172222
LastTxBlockChildKeyTake(AccountId),
22182223
// Last tx block delegate key limit per account ID
2224+
#[codec(index = 5)]
22192225
LastTxBlockDelegateTake(AccountId),
22202226
}
22212227

pallets/subtensor/src/macros/hooks.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ mod hooks {
138138
.saturating_add(migrations::migrate_fix_root_tao_and_alpha_in::migrate_fix_root_tao_and_alpha_in::<T>())
139139
// Migrate last block rate limiting storage items
140140
.saturating_add(migrations::migrate_rate_limiting_last_blocks::migrate_obsolete_rate_limiting_last_blocks_storage::<T>())
141+
// Re-encode rate limit keys after introducing OwnerHyperparamUpdate variant
142+
.saturating_add(migrations::migrate_rate_limit_keys::migrate_rate_limit_keys::<T>())
141143
// Migrate remove network modality
142144
.saturating_add(migrations::migrate_remove_network_modality::migrate_remove_network_modality::<T>())
143145
// Migrate Immunity Period
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
use alloc::string::String;
2+
use codec::{Decode, Encode};
3+
use frame_support::traits::Get;
4+
use frame_support::weights::Weight;
5+
use sp_io::hashing::twox_128;
6+
use sp_io::storage;
7+
use sp_std::{collections::btree_set::BTreeSet, vec::Vec};
8+
use subtensor_runtime_common::NetUid;
9+
10+
use crate::{
11+
ChildKeys, Config, Delegates, HasMigrationRun, LastRateLimitedBlock, ParentKeys,
12+
PendingChildKeys, RateLimitKey,
13+
};
14+
15+
const MIGRATION_NAME: &[u8] = b"migrate_rate_limit_keys";
16+
17+
#[allow(dead_code)]
18+
#[derive(Decode)]
19+
enum RateLimitKeyV0<AccountId> {
20+
SetSNOwnerHotkey(NetUid),
21+
NetworkLastRegistered,
22+
LastTxBlock(AccountId),
23+
LastTxBlockChildKeyTake(AccountId),
24+
LastTxBlockDelegateTake(AccountId),
25+
}
26+
27+
pub fn migrate_rate_limit_keys<T: Config>() -> Weight
28+
where
29+
T::AccountId: Ord + Clone,
30+
{
31+
let mut weight = T::DbWeight::get().reads(1);
32+
33+
if HasMigrationRun::<T>::get(MIGRATION_NAME) {
34+
log::info!(
35+
"Migration '{}' already executed - skipping",
36+
String::from_utf8_lossy(MIGRATION_NAME)
37+
);
38+
return weight;
39+
}
40+
41+
log::info!(
42+
"Running migration '{}'",
43+
String::from_utf8_lossy(MIGRATION_NAME)
44+
);
45+
46+
let (child_accounts, child_weight) = collect_child_related_accounts::<T>();
47+
let (delegate_accounts, delegate_weight) = collect_delegate_accounts::<T>();
48+
weight = weight.saturating_add(child_weight);
49+
weight = weight.saturating_add(delegate_weight);
50+
51+
let prefix = storage_prefix("SubtensorModule", "LastRateLimitedBlock");
52+
let mut cursor = prefix.clone();
53+
let mut entries = Vec::new();
54+
55+
while let Some(next_key) = storage::next_key(&cursor) {
56+
if !next_key.starts_with(&prefix) {
57+
break;
58+
}
59+
if let Some(value) = storage::get(&next_key) {
60+
entries.push((next_key.clone(), value));
61+
}
62+
cursor = next_key;
63+
}
64+
65+
weight = weight.saturating_add(T::DbWeight::get().reads(entries.len() as u64));
66+
67+
let mut migrated_network = 0u64;
68+
let mut migrated_last_tx = 0u64;
69+
let mut migrated_child_take = 0u64;
70+
let mut migrated_delegate_take = 0u64;
71+
72+
for (old_storage_key, value_bytes) in entries {
73+
if value_bytes.is_empty() {
74+
continue;
75+
}
76+
77+
let Some(encoded_key) = old_storage_key.get(prefix.len()..) else {
78+
continue;
79+
};
80+
if encoded_key.is_empty() {
81+
continue;
82+
}
83+
84+
let Some(decoded_legacy) = decode_legacy::<T>(encoded_key) else {
85+
// Unknown entry – skip to avoid clobbering valid data.
86+
continue;
87+
};
88+
89+
let legacy_value = match decode_value(&value_bytes) {
90+
Some(v) => v,
91+
None => continue,
92+
};
93+
94+
let Some(modern_key) =
95+
legacy_to_modern(decoded_legacy, &child_accounts, &delegate_accounts)
96+
else {
97+
continue;
98+
};
99+
let new_storage_key = LastRateLimitedBlock::<T>::hashed_key_for(&modern_key);
100+
weight = weight.saturating_add(T::DbWeight::get().reads(1));
101+
102+
let merged_value = storage::get(&new_storage_key)
103+
.and_then(|data| decode_value(&data))
104+
.map_or(legacy_value, |current| {
105+
core::cmp::max(current, legacy_value)
106+
});
107+
108+
storage::set(&new_storage_key, &merged_value.encode());
109+
if new_storage_key != old_storage_key {
110+
storage::clear(&old_storage_key);
111+
weight = weight.saturating_add(T::DbWeight::get().writes(1));
112+
}
113+
114+
weight = weight.saturating_add(T::DbWeight::get().writes(1));
115+
match &modern_key {
116+
RateLimitKey::NetworkLastRegistered => {
117+
migrated_network = migrated_network.saturating_add(1);
118+
}
119+
RateLimitKey::LastTxBlock(_) => {
120+
migrated_last_tx = migrated_last_tx.saturating_add(1);
121+
}
122+
RateLimitKey::LastTxBlockChildKeyTake(_) => {
123+
migrated_child_take = migrated_child_take.saturating_add(1);
124+
}
125+
RateLimitKey::LastTxBlockDelegateTake(_) => {
126+
migrated_delegate_take = migrated_delegate_take.saturating_add(1);
127+
}
128+
_ => {}
129+
}
130+
}
131+
132+
HasMigrationRun::<T>::insert(MIGRATION_NAME, true);
133+
weight = weight.saturating_add(T::DbWeight::get().writes(1));
134+
135+
log::info!(
136+
"Migration '{}' completed. network={}, last_tx={}, child_take={}, delegate_take={}",
137+
String::from_utf8_lossy(MIGRATION_NAME),
138+
migrated_network,
139+
migrated_last_tx,
140+
migrated_child_take,
141+
migrated_delegate_take
142+
);
143+
144+
weight
145+
}
146+
147+
fn storage_prefix(pallet: &str, storage: &str) -> Vec<u8> {
148+
let pallet_hash = twox_128(pallet.as_bytes());
149+
let storage_hash = twox_128(storage.as_bytes());
150+
[pallet_hash, storage_hash].concat()
151+
}
152+
153+
fn decode_legacy<T: Config>(bytes: &[u8]) -> Option<RateLimitKeyV0<T::AccountId>> {
154+
let mut slice = bytes;
155+
let decoded = RateLimitKeyV0::<T::AccountId>::decode(&mut slice).ok()?;
156+
if slice.is_empty() {
157+
Some(decoded)
158+
} else {
159+
None
160+
}
161+
}
162+
163+
fn decode_value(bytes: &[u8]) -> Option<u64> {
164+
let mut slice = bytes;
165+
u64::decode(&mut slice).ok()
166+
}
167+
168+
fn legacy_to_modern<AccountId: Ord + Clone>(
169+
legacy: RateLimitKeyV0<AccountId>,
170+
child_accounts: &BTreeSet<AccountId>,
171+
delegate_accounts: &BTreeSet<AccountId>,
172+
) -> Option<RateLimitKey<AccountId>> {
173+
match legacy {
174+
RateLimitKeyV0::SetSNOwnerHotkey(_) => None,
175+
RateLimitKeyV0::NetworkLastRegistered => Some(RateLimitKey::NetworkLastRegistered),
176+
RateLimitKeyV0::LastTxBlock(account) => Some(RateLimitKey::LastTxBlock(account)),
177+
RateLimitKeyV0::LastTxBlockChildKeyTake(account) => {
178+
if child_accounts.contains(&account) {
179+
Some(RateLimitKey::LastTxBlockChildKeyTake(account))
180+
} else {
181+
None
182+
}
183+
}
184+
RateLimitKeyV0::LastTxBlockDelegateTake(account) => {
185+
if delegate_accounts.contains(&account) {
186+
Some(RateLimitKey::LastTxBlockDelegateTake(account))
187+
} else {
188+
None
189+
}
190+
}
191+
}
192+
}
193+
194+
fn collect_child_related_accounts<T: Config>() -> (BTreeSet<T::AccountId>, Weight)
195+
where
196+
T::AccountId: Ord + Clone,
197+
{
198+
let mut accounts = BTreeSet::new();
199+
let mut reads = 0u64;
200+
201+
for (parent, _, children) in ChildKeys::<T>::iter() {
202+
accounts.insert(parent.clone());
203+
for (_, child) in children {
204+
accounts.insert(child.clone());
205+
}
206+
reads = reads.saturating_add(1);
207+
}
208+
209+
for (_, parent, (children, _)) in PendingChildKeys::<T>::iter() {
210+
accounts.insert(parent.clone());
211+
for (_, child) in children {
212+
accounts.insert(child.clone());
213+
}
214+
reads = reads.saturating_add(1);
215+
}
216+
217+
for (child, _, parents) in ParentKeys::<T>::iter() {
218+
accounts.insert(child.clone());
219+
for (_, parent) in parents {
220+
accounts.insert(parent.clone());
221+
}
222+
reads = reads.saturating_add(1);
223+
}
224+
225+
(accounts, T::DbWeight::get().reads(reads))
226+
}
227+
228+
fn collect_delegate_accounts<T: Config>() -> (BTreeSet<T::AccountId>, Weight)
229+
where
230+
T::AccountId: Ord + Clone,
231+
{
232+
let mut accounts = BTreeSet::new();
233+
let mut reads = 0u64;
234+
235+
for (account, _) in Delegates::<T>::iter() {
236+
accounts.insert(account.clone());
237+
reads = reads.saturating_add(1);
238+
}
239+
240+
(accounts, T::DbWeight::get().reads(reads))
241+
}

pallets/subtensor/src/migrations/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub mod migrate_network_lock_reduction_interval;
2828
pub mod migrate_orphaned_storage_items;
2929
pub mod migrate_populate_owned_hotkeys;
3030
pub mod migrate_rao;
31+
pub mod migrate_rate_limit_keys;
3132
pub mod migrate_rate_limiting_last_blocks;
3233
pub mod migrate_remove_commitments_rate_limit;
3334
pub mod migrate_remove_network_modality;

pallets/subtensor/src/tests/migration.rs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,6 +1027,117 @@ fn test_migrate_last_tx_block_delegate_take() {
10271027
});
10281028
}
10291029

1030+
#[test]
1031+
fn test_migrate_rate_limit_keys() {
1032+
new_test_ext(1).execute_with(|| {
1033+
const MIGRATION_NAME: &[u8] = b"migrate_rate_limit_keys";
1034+
let prefix = {
1035+
let pallet_prefix = twox_128("SubtensorModule".as_bytes());
1036+
let storage_prefix = twox_128("LastRateLimitedBlock".as_bytes());
1037+
[pallet_prefix, storage_prefix].concat()
1038+
};
1039+
1040+
// Seed new-format entries that must survive the migration untouched.
1041+
let new_last_account = U256::from(10);
1042+
SubtensorModule::set_last_tx_block(&new_last_account, 555);
1043+
let new_child_account = U256::from(11);
1044+
SubtensorModule::set_last_tx_block_childkey(&new_child_account, 777);
1045+
let new_delegate_account = U256::from(12);
1046+
SubtensorModule::set_last_tx_block_delegate_take(&new_delegate_account, 888);
1047+
1048+
// Legacy NetworkLastRegistered entry (index 1)
1049+
let mut legacy_network_key = prefix.clone();
1050+
legacy_network_key.push(1u8);
1051+
sp_io::storage::set(&legacy_network_key, &111u64.encode());
1052+
1053+
// Legacy LastTxBlock entry (index 2) for an account that already has a new-format value.
1054+
let mut legacy_last_key = prefix.clone();
1055+
legacy_last_key.push(2u8);
1056+
legacy_last_key.extend_from_slice(&new_last_account.encode());
1057+
sp_io::storage::set(&legacy_last_key, &666u64.encode());
1058+
1059+
// Legacy LastTxBlockChildKeyTake entry (index 3)
1060+
let legacy_child_account = U256::from(3);
1061+
ChildKeys::<Test>::insert(
1062+
legacy_child_account,
1063+
NetUid::from(0),
1064+
vec![(0u64, U256::from(99))],
1065+
);
1066+
let mut legacy_child_key = prefix.clone();
1067+
legacy_child_key.push(3u8);
1068+
legacy_child_key.extend_from_slice(&legacy_child_account.encode());
1069+
sp_io::storage::set(&legacy_child_key, &333u64.encode());
1070+
1071+
// Legacy LastTxBlockDelegateTake entry (index 4)
1072+
let legacy_delegate_account = U256::from(4);
1073+
Delegates::<Test>::insert(legacy_delegate_account, 500u16);
1074+
let mut legacy_delegate_key = prefix.clone();
1075+
legacy_delegate_key.push(4u8);
1076+
legacy_delegate_key.extend_from_slice(&legacy_delegate_account.encode());
1077+
sp_io::storage::set(&legacy_delegate_key, &444u64.encode());
1078+
1079+
let weight = crate::migrations::migrate_rate_limit_keys::migrate_rate_limit_keys::<Test>();
1080+
assert!(
1081+
HasMigrationRun::<Test>::get(MIGRATION_NAME.to_vec()),
1082+
"Migration should be marked as executed"
1083+
);
1084+
assert!(!weight.is_zero(), "Migration weight should be non-zero");
1085+
1086+
// Legacy entries were migrated and cleared.
1087+
assert_eq!(
1088+
SubtensorModule::get_network_last_lock_block(),
1089+
111u64,
1090+
"Network last lock block should match migrated value"
1091+
);
1092+
assert!(
1093+
sp_io::storage::get(&legacy_network_key).is_none(),
1094+
"Legacy network entry should be cleared"
1095+
);
1096+
1097+
assert_eq!(
1098+
SubtensorModule::get_last_tx_block(&new_last_account),
1099+
666u64,
1100+
"LastTxBlock should reflect the merged legacy value"
1101+
);
1102+
assert!(
1103+
sp_io::storage::get(&legacy_last_key).is_none(),
1104+
"Legacy LastTxBlock entry should be cleared"
1105+
);
1106+
1107+
assert_eq!(
1108+
SubtensorModule::get_last_tx_block_childkey_take(&legacy_child_account),
1109+
333u64,
1110+
"Child key take block should be migrated"
1111+
);
1112+
assert!(
1113+
sp_io::storage::get(&legacy_child_key).is_none(),
1114+
"Legacy child take entry should be cleared"
1115+
);
1116+
1117+
assert_eq!(
1118+
SubtensorModule::get_last_tx_block_delegate_take(&legacy_delegate_account),
1119+
444u64,
1120+
"Delegate take block should be migrated"
1121+
);
1122+
assert!(
1123+
sp_io::storage::get(&legacy_delegate_key).is_none(),
1124+
"Legacy delegate take entry should be cleared"
1125+
);
1126+
1127+
// New-format entries remain untouched.
1128+
assert_eq!(
1129+
SubtensorModule::get_last_tx_block_childkey_take(&new_child_account),
1130+
777u64,
1131+
"Existing child take entry should be preserved"
1132+
);
1133+
assert_eq!(
1134+
SubtensorModule::get_last_tx_block_delegate_take(&new_delegate_account),
1135+
888u64,
1136+
"Existing delegate take entry should be preserved"
1137+
);
1138+
});
1139+
}
1140+
10301141
#[test]
10311142
fn test_migrate_fix_root_subnet_tao() {
10321143
new_test_ext(1).execute_with(|| {

0 commit comments

Comments
 (0)