Skip to content

Commit 1e8db7d

Browse files
committed
Add api to migrate scope and usage keys
1 parent 5c8a8ab commit 1e8db7d

File tree

2 files changed

+174
-0
lines changed

2 files changed

+174
-0
lines changed

pallets/rate-limiting/src/lib.rs

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,76 @@ pub mod pallet {
370370
true
371371
}
372372

373+
/// Migrates a stored rate limit configuration from one scope to another.
374+
///
375+
/// Returns `true` when an entry was moved. Passing identical `from`/`to` scopes simply
376+
/// checks that a configuration exists.
377+
pub fn migrate_limit_scope(
378+
identifier: &TransactionIdentifier,
379+
from: Option<<T as Config<I>>::LimitScope>,
380+
to: Option<<T as Config<I>>::LimitScope>,
381+
) -> bool {
382+
if from == to {
383+
return Limits::<T, I>::contains_key(identifier);
384+
}
385+
386+
let mut migrated = false;
387+
Limits::<T, I>::mutate(identifier, |maybe_config| {
388+
if let Some(config) = maybe_config {
389+
match (from.as_ref(), to.as_ref()) {
390+
(None, Some(target)) => {
391+
if let RateLimit::Global(kind) = config {
392+
*config = RateLimit::scoped_single(target.clone(), *kind);
393+
migrated = true;
394+
}
395+
}
396+
(Some(source), Some(target)) => {
397+
if let RateLimit::Scoped(map) = config {
398+
if let Some(kind) = map.remove(source) {
399+
map.insert(target.clone(), kind);
400+
migrated = true;
401+
}
402+
}
403+
}
404+
(Some(source), None) => {
405+
if let RateLimit::Scoped(map) = config {
406+
if map.len() == 1 && map.contains_key(source) {
407+
if let Some(kind) = map.remove(source) {
408+
*config = RateLimit::global(kind);
409+
migrated = true;
410+
}
411+
}
412+
}
413+
}
414+
_ => {}
415+
}
416+
}
417+
});
418+
419+
migrated
420+
}
421+
422+
/// Migrates the cached usage information for a rate-limited call to a new key.
423+
///
424+
/// Returns `true` when an entry was moved. Passing identical keys simply checks that an
425+
/// entry exists.
426+
pub fn migrate_usage_key(
427+
identifier: &TransactionIdentifier,
428+
from: Option<<T as Config<I>>::UsageKey>,
429+
to: Option<<T as Config<I>>::UsageKey>,
430+
) -> bool {
431+
if from == to {
432+
return LastSeen::<T, I>::contains_key(identifier, &to);
433+
}
434+
435+
let Some(block) = LastSeen::<T, I>::take(identifier, from) else {
436+
return false;
437+
};
438+
439+
LastSeen::<T, I>::insert(identifier, to, block);
440+
true
441+
}
442+
373443
/// Returns the configured limit for the specified pallet/extrinsic names, if any.
374444
pub fn limit_for_call_names(
375445
pallet_name: &str,

pallets/rate-limiting/src/tests.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,110 @@ fn is_within_limit_true_after_required_span() {
176176
});
177177
}
178178

179+
#[test]
180+
fn migrate_limit_scope_global_to_scoped() {
181+
new_test_ext().execute_with(|| {
182+
let target_call =
183+
RuntimeCall::System(frame_system::Call::<Test>::remark { remark: Vec::new() });
184+
let identifier = identifier_for(&target_call);
185+
186+
Limits::<Test, ()>::insert(identifier, RateLimit::global(RateLimitKind::Exact(3)));
187+
188+
assert!(RateLimiting::migrate_limit_scope(
189+
&identifier,
190+
None,
191+
Some(9)
192+
));
193+
194+
match RateLimiting::limits(identifier).expect("config") {
195+
RateLimit::Scoped(map) => {
196+
assert_eq!(map.len(), 1);
197+
assert_eq!(map.get(&9), Some(&RateLimitKind::Exact(3)));
198+
}
199+
other => panic!("unexpected config: {:?}", other),
200+
}
201+
});
202+
}
203+
204+
#[test]
205+
fn migrate_limit_scope_scoped_to_scoped() {
206+
new_test_ext().execute_with(|| {
207+
let target_call =
208+
RuntimeCall::RateLimiting(RateLimitingCall::set_default_rate_limit { block_span: 0 });
209+
let identifier = identifier_for(&target_call);
210+
211+
let mut map = sp_std::collections::btree_map::BTreeMap::new();
212+
map.insert(1u16, RateLimitKind::Exact(4));
213+
map.insert(2u16, RateLimitKind::Exact(6));
214+
Limits::<Test, ()>::insert(identifier, RateLimit::Scoped(map));
215+
216+
assert!(RateLimiting::migrate_limit_scope(
217+
&identifier,
218+
Some(1),
219+
Some(3)
220+
));
221+
222+
match RateLimiting::limits(identifier).expect("config") {
223+
RateLimit::Scoped(map) => {
224+
assert!(map.get(&1).is_none());
225+
assert_eq!(map.get(&3), Some(&RateLimitKind::Exact(4)));
226+
assert_eq!(map.get(&2), Some(&RateLimitKind::Exact(6)));
227+
}
228+
other => panic!("unexpected config: {:?}", other),
229+
}
230+
});
231+
}
232+
233+
#[test]
234+
fn migrate_limit_scope_scoped_to_global() {
235+
new_test_ext().execute_with(|| {
236+
let target_call =
237+
RuntimeCall::RateLimiting(RateLimitingCall::set_default_rate_limit { block_span: 0 });
238+
let identifier = identifier_for(&target_call);
239+
240+
let mut map = sp_std::collections::btree_map::BTreeMap::new();
241+
map.insert(7u16, RateLimitKind::Exact(8));
242+
Limits::<Test, ()>::insert(identifier, RateLimit::Scoped(map));
243+
244+
assert!(RateLimiting::migrate_limit_scope(
245+
&identifier,
246+
Some(7),
247+
None
248+
));
249+
250+
match RateLimiting::limits(identifier).expect("config") {
251+
RateLimit::Global(kind) => assert_eq!(kind, RateLimitKind::Exact(8)),
252+
other => panic!("unexpected config: {:?}", other),
253+
}
254+
});
255+
}
256+
257+
#[test]
258+
fn migrate_usage_key_moves_entry() {
259+
new_test_ext().execute_with(|| {
260+
let target_call =
261+
RuntimeCall::RateLimiting(RateLimitingCall::set_default_rate_limit { block_span: 0 });
262+
let identifier = identifier_for(&target_call);
263+
264+
LastSeen::<Test, ()>::insert(identifier, Some(5u16), 11);
265+
266+
assert!(RateLimiting::migrate_usage_key(
267+
&identifier,
268+
Some(5),
269+
Some(6)
270+
));
271+
assert!(LastSeen::<Test, ()>::get(identifier, Some(5u16)).is_none());
272+
assert_eq!(LastSeen::<Test, ()>::get(identifier, Some(6u16)), Some(11));
273+
274+
assert!(RateLimiting::migrate_usage_key(&identifier, Some(6), None));
275+
assert!(LastSeen::<Test, ()>::get(identifier, Some(6u16)).is_none());
276+
assert_eq!(
277+
LastSeen::<Test, ()>::get(identifier, None::<UsageKey>),
278+
Some(11)
279+
);
280+
});
281+
}
282+
179283
#[test]
180284
fn set_rate_limit_updates_storage_and_emits_event() {
181285
new_test_ext().execute_with(|| {

0 commit comments

Comments
 (0)