Skip to content

Commit 712d459

Browse files
committed
Add migration for RateLimitKey
1 parent 98c500b commit 712d459

File tree

5 files changed

+359
-0
lines changed

5 files changed

+359
-0
lines changed

pallets/subtensor/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2295,16 +2295,22 @@ impl<T: Config + pallet_balances::Config<Balance = u64>>
22952295
#[derive(Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo)]
22962296
pub enum RateLimitKey<AccountId> {
22972297
// The setting sn owner hotkey operation is rate limited per netuid
2298+
#[codec(index = 0)]
22982299
SetSNOwnerHotkey(NetUid),
22992300
// Generic rate limit for subnet-owner hyperparameter updates (per netuid)
2301+
#[codec(index = 1)]
23002302
OwnerHyperparamUpdate(NetUid, Hyperparameter),
23012303
// Subnet registration rate limit
2304+
#[codec(index = 2)]
23022305
NetworkLastRegistered,
23032306
// Last tx block limit per account ID
2307+
#[codec(index = 3)]
23042308
LastTxBlock(AccountId),
23052309
// Last tx block child key limit per account ID
2310+
#[codec(index = 4)]
23062311
LastTxBlockChildKeyTake(AccountId),
23072312
// Last tx block delegate key limit per account ID
2313+
#[codec(index = 5)]
23082314
LastTxBlockDelegateTake(AccountId),
23092315
}
23102316

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: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
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 encoded_key = &old_storage_key[prefix.len()..];
78+
if encoded_key.is_empty() {
79+
continue;
80+
}
81+
82+
let Some(decoded_legacy) = decode_legacy::<T>(&encoded_key) else {
83+
// Unknown entry – skip to avoid clobbering valid data.
84+
continue;
85+
};
86+
87+
let legacy_value = match decode_value(&value_bytes) {
88+
Some(v) => v,
89+
None => continue,
90+
};
91+
92+
let Some(modern_key) =
93+
legacy_to_modern(decoded_legacy, &child_accounts, &delegate_accounts)
94+
else {
95+
continue;
96+
};
97+
let new_storage_key = LastRateLimitedBlock::<T>::hashed_key_for(&modern_key);
98+
weight = weight.saturating_add(T::DbWeight::get().reads(1));
99+
100+
let merged_value = storage::get(&new_storage_key)
101+
.and_then(|data| decode_value(&data))
102+
.map_or(legacy_value, |current| {
103+
core::cmp::max(current, legacy_value)
104+
});
105+
106+
storage::set(&new_storage_key, &merged_value.encode());
107+
if new_storage_key != old_storage_key {
108+
storage::clear(&old_storage_key);
109+
weight = weight.saturating_add(T::DbWeight::get().writes(1));
110+
}
111+
112+
weight = weight.saturating_add(T::DbWeight::get().writes(1));
113+
match &modern_key {
114+
RateLimitKey::NetworkLastRegistered => {
115+
migrated_network = migrated_network.saturating_add(1);
116+
}
117+
RateLimitKey::LastTxBlock(_) => {
118+
migrated_last_tx = migrated_last_tx.saturating_add(1);
119+
}
120+
RateLimitKey::LastTxBlockChildKeyTake(_) => {
121+
migrated_child_take = migrated_child_take.saturating_add(1);
122+
}
123+
RateLimitKey::LastTxBlockDelegateTake(_) => {
124+
migrated_delegate_take = migrated_delegate_take.saturating_add(1);
125+
}
126+
_ => {}
127+
}
128+
}
129+
130+
HasMigrationRun::<T>::insert(MIGRATION_NAME, true);
131+
weight = weight.saturating_add(T::DbWeight::get().writes(1));
132+
133+
log::info!(
134+
"Migration '{}' completed. network={}, last_tx={}, child_take={}, delegate_take={}",
135+
String::from_utf8_lossy(MIGRATION_NAME),
136+
migrated_network,
137+
migrated_last_tx,
138+
migrated_child_take,
139+
migrated_delegate_take
140+
);
141+
142+
weight
143+
}
144+
145+
fn storage_prefix(pallet: &str, storage: &str) -> Vec<u8> {
146+
let pallet_hash = twox_128(pallet.as_bytes());
147+
let storage_hash = twox_128(storage.as_bytes());
148+
[pallet_hash, storage_hash].concat()
149+
}
150+
151+
fn decode_legacy<T: Config>(bytes: &[u8]) -> Option<RateLimitKeyV0<T::AccountId>> {
152+
let mut slice = bytes;
153+
let decoded = RateLimitKeyV0::<T::AccountId>::decode(&mut slice).ok()?;
154+
if slice.is_empty() {
155+
Some(decoded)
156+
} else {
157+
None
158+
}
159+
}
160+
161+
fn decode_value(bytes: &[u8]) -> Option<u64> {
162+
let mut slice = bytes;
163+
u64::decode(&mut slice).ok()
164+
}
165+
166+
fn legacy_to_modern<AccountId: Ord + Clone>(
167+
legacy: RateLimitKeyV0<AccountId>,
168+
child_accounts: &BTreeSet<AccountId>,
169+
delegate_accounts: &BTreeSet<AccountId>,
170+
) -> Option<RateLimitKey<AccountId>> {
171+
match legacy {
172+
RateLimitKeyV0::SetSNOwnerHotkey(_) => None,
173+
RateLimitKeyV0::NetworkLastRegistered => Some(RateLimitKey::NetworkLastRegistered),
174+
RateLimitKeyV0::LastTxBlock(account) => Some(RateLimitKey::LastTxBlock(account)),
175+
RateLimitKeyV0::LastTxBlockChildKeyTake(account) => {
176+
if child_accounts.contains(&account) {
177+
Some(RateLimitKey::LastTxBlockChildKeyTake(account))
178+
} else {
179+
None
180+
}
181+
}
182+
RateLimitKeyV0::LastTxBlockDelegateTake(account) => {
183+
if delegate_accounts.contains(&account) {
184+
Some(RateLimitKey::LastTxBlockDelegateTake(account))
185+
} else {
186+
None
187+
}
188+
}
189+
}
190+
}
191+
192+
fn collect_child_related_accounts<T: Config>() -> (BTreeSet<T::AccountId>, Weight)
193+
where
194+
T::AccountId: Ord + Clone,
195+
{
196+
let mut accounts = BTreeSet::new();
197+
let mut reads = 0u64;
198+
199+
for (parent, _, children) in ChildKeys::<T>::iter() {
200+
accounts.insert(parent.clone());
201+
for (_, child) in children {
202+
accounts.insert(child.clone());
203+
}
204+
reads = reads.saturating_add(1);
205+
}
206+
207+
for (_, parent, (children, _)) in PendingChildKeys::<T>::iter() {
208+
accounts.insert(parent.clone());
209+
for (_, child) in children {
210+
accounts.insert(child.clone());
211+
}
212+
reads = reads.saturating_add(1);
213+
}
214+
215+
for (child, _, parents) in ParentKeys::<T>::iter() {
216+
accounts.insert(child.clone());
217+
for (_, parent) in parents {
218+
accounts.insert(parent.clone());
219+
}
220+
reads = reads.saturating_add(1);
221+
}
222+
223+
(accounts, T::DbWeight::get().reads(reads))
224+
}
225+
226+
fn collect_delegate_accounts<T: Config>() -> (BTreeSet<T::AccountId>, Weight)
227+
where
228+
T::AccountId: Ord + Clone,
229+
{
230+
let mut accounts = BTreeSet::new();
231+
let mut reads = 0u64;
232+
233+
for (account, _) in Delegates::<T>::iter() {
234+
accounts.insert(account.clone());
235+
reads = reads.saturating_add(1);
236+
}
237+
238+
(accounts, T::DbWeight::get().reads(reads))
239+
}

pallets/subtensor/src/migrations/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub mod migrate_network_lock_reduction_interval;
2727
pub mod migrate_orphaned_storage_items;
2828
pub mod migrate_populate_owned_hotkeys;
2929
pub mod migrate_rao;
30+
pub mod migrate_rate_limit_keys;
3031
pub mod migrate_rate_limiting_last_blocks;
3132
pub mod migrate_remove_commitments_rate_limit;
3233
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)