Skip to content

Commit 09ba878

Browse files
committed
Introduce BypassDesicion in pallet-rate-limiting
- fix bypassing in pallet-rate-limiting - fix conditional bypassing for legacy rate-limited calls
1 parent 0d3a1d5 commit 09ba878

File tree

7 files changed

+188
-63
lines changed

7 files changed

+188
-63
lines changed

pallets/rate-limiting/src/lib.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,12 @@
9797
//! }
9898
//! }
9999
//!
100-
//! fn should_bypass(origin: &RuntimeOrigin, _call: &RuntimeCall) -> bool {
101-
//! matches!(origin, RuntimeOrigin::Root)
100+
//! fn should_bypass(origin: &RuntimeOrigin, _call: &RuntimeCall) -> BypassDecision {
101+
//! if matches!(origin, RuntimeOrigin::Root) {
102+
//! BypassDecision::bypass_and_skip()
103+
//! } else {
104+
//! BypassDecision::enforce_and_record()
105+
//! }
102106
//! }
103107
//!
104108
//! fn adjust_span(_origin: &RuntimeOrigin, _call: &RuntimeCall, span: BlockNumber) -> BlockNumber {
@@ -140,7 +144,7 @@ pub use benchmarking::BenchmarkHelper;
140144
pub use pallet::*;
141145
pub use tx_extension::RateLimitTransactionExtension;
142146
pub use types::{
143-
GroupSharing, RateLimit, RateLimitGroup, RateLimitKind, RateLimitScopeResolver,
147+
BypassDecision, GroupSharing, RateLimit, RateLimitGroup, RateLimitKind, RateLimitScopeResolver,
144148
RateLimitTarget, RateLimitUsageResolver, TransactionIdentifier,
145149
};
146150

@@ -172,8 +176,8 @@ pub mod pallet {
172176
#[cfg(feature = "runtime-benchmarks")]
173177
use crate::benchmarking::BenchmarkHelper as BenchmarkHelperTrait;
174178
use crate::types::{
175-
GroupSharing, RateLimit, RateLimitGroup, RateLimitKind, RateLimitScopeResolver,
176-
RateLimitTarget, RateLimitUsageResolver, TransactionIdentifier,
179+
BypassDecision, GroupSharing, RateLimit, RateLimitGroup, RateLimitKind,
180+
RateLimitScopeResolver, RateLimitTarget, RateLimitUsageResolver, TransactionIdentifier,
177181
};
178182

179183
type GroupNameOf<T, I> = BoundedVec<u8, <T as Config<I>>::MaxGroupNameLength>;
@@ -542,7 +546,9 @@ pub mod pallet {
542546
scope: &Option<<T as Config<I>>::LimitScope>,
543547
usage_key: &Option<<T as Config<I>>::UsageKey>,
544548
) -> Result<bool, DispatchError> {
545-
if <T as Config<I>>::LimitScopeResolver::should_bypass(origin, call) {
549+
let bypass: BypassDecision =
550+
<T as Config<I>>::LimitScopeResolver::should_bypass(origin, call);
551+
if bypass.bypass_enforcement {
546552
return Ok(true);
547553
}
548554

@@ -571,8 +577,8 @@ pub mod pallet {
571577
})
572578
}
573579

574-
/// Resolves the span for a target/scope and applies the configured span adjustment
575-
/// (e.g., tempo scaling) using the pallet's scope resolver.
580+
/// Resolves the span for a target/scope and applies the configured span adjustment (e.g.,
581+
/// tempo scaling) using the pallet's scope resolver.
576582
pub fn effective_span(
577583
origin: &DispatchOriginOf<<T as Config<I>>::RuntimeCall>,
578584
call: &<T as Config<I>>::RuntimeCall,
@@ -594,7 +600,7 @@ pub mod pallet {
594600
return true;
595601
}
596602

597-
if let Some(last) = LastSeen::<T, I>::get(target, usage_key.clone()) {
603+
if let Some(last) = LastSeen::<T, I>::get(target, usage_key) {
598604
let current = frame_system::Pallet::<T>::block_number();
599605
let delta = current.saturating_sub(last);
600606
if delta < block_span {
@@ -753,6 +759,12 @@ pub mod pallet {
753759
Ok(())
754760
}
755761

762+
/// Returns true when the call has been registered (either directly or via a group).
763+
pub fn is_registered(identifier: &TransactionIdentifier) -> bool {
764+
let tx_target = RateLimitTarget::Transaction(*identifier);
765+
Limits::<T, I>::contains_key(tx_target) || CallGroups::<T, I>::contains_key(identifier)
766+
}
767+
756768
fn call_metadata(
757769
identifier: &TransactionIdentifier,
758770
) -> Result<(Vec<u8>, Vec<u8>), DispatchError> {

pallets/rate-limiting/src/mock.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,19 @@ impl pallet_rate_limiting::RateLimitScopeResolver<RuntimeOrigin, RuntimeCall, Li
7575
}
7676
}
7777

78-
fn should_bypass(_origin: &RuntimeOrigin, call: &RuntimeCall) -> bool {
79-
matches!(
80-
call,
81-
RuntimeCall::RateLimiting(RateLimitingCall::remove_call_from_group { .. })
82-
)
78+
fn should_bypass(
79+
_origin: &RuntimeOrigin,
80+
call: &RuntimeCall,
81+
) -> pallet_rate_limiting::types::BypassDecision {
82+
match call {
83+
RuntimeCall::RateLimiting(RateLimitingCall::remove_call_from_group { .. }) => {
84+
pallet_rate_limiting::types::BypassDecision::bypass_and_skip()
85+
}
86+
RuntimeCall::RateLimiting(RateLimitingCall::set_default_rate_limit { .. }) => {
87+
pallet_rate_limiting::types::BypassDecision::bypass_and_record()
88+
}
89+
_ => pallet_rate_limiting::types::BypassDecision::enforce_and_record(),
90+
}
8391
}
8492

8593
fn adjust_span(_origin: &RuntimeOrigin, call: &RuntimeCall, span: u64) -> u64 {

pallets/rate-limiting/src/tests.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ fn remark_call() -> RuntimeCall {
1616
}
1717

1818
fn scoped_call() -> RuntimeCall {
19-
RuntimeCall::RateLimiting(RateLimitingCall::set_default_rate_limit { block_span: 1 })
19+
RuntimeCall::RateLimiting(RateLimitingCall::set_rate_limit {
20+
target: RateLimitTarget::Transaction(TransactionIdentifier::new(0, 0)),
21+
scope: Some(1),
22+
limit: RateLimitKind::Default,
23+
})
2024
}
2125

2226
fn register(call: RuntimeCall, group: Option<GroupId>) -> TransactionIdentifier {
@@ -577,15 +581,15 @@ fn is_within_limit_detects_rate_limited_scope() {
577581
let tx_target = target(identifier);
578582
Limits::<Test, ()>::insert(
579583
tx_target,
580-
RateLimit::scoped_single(7u16, RateLimitKind::Exact(3)),
584+
RateLimit::scoped_single(1u16, RateLimitKind::Exact(3)),
581585
);
582586
LastSeen::<Test, ()>::insert(tx_target, Some(1u16), 9);
583587
System::set_block_number(11);
584588
let result = RateLimiting::is_within_limit(
585589
&RuntimeOrigin::signed(1),
586590
&call,
587591
&identifier,
588-
&Some(7u16),
592+
&Some(1u16),
589593
&Some(1u16),
590594
)
591595
.expect("ok");
@@ -714,12 +718,9 @@ fn limit_for_call_names_prefers_scoped_value() {
714718
target(identifier),
715719
RateLimit::scoped_single(9u16, RateLimitKind::Exact(8)),
716720
);
717-
let fetched = RateLimiting::limit_for_call_names(
718-
"RateLimiting",
719-
"set_default_rate_limit",
720-
Some(9u16),
721-
)
722-
.expect("limit");
721+
let fetched =
722+
RateLimiting::limit_for_call_names("RateLimiting", "set_rate_limit", Some(9u16))
723+
.expect("limit");
723724
assert_eq!(fetched, RateLimitKind::Exact(8));
724725
});
725726
}

pallets/rate-limiting/src/tx_extension.rs

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,30 +117,40 @@ where
117117
_inherited_implication: &impl Implication,
118118
_source: TransactionSource,
119119
) -> ValidateResult<Self::Val, <T as Config<I>>::RuntimeCall> {
120-
if <T as Config<I>>::LimitScopeResolver::should_bypass(&origin, call) {
121-
return Ok((ValidTransaction::default(), None, origin));
122-
}
123-
124120
let identifier = match TransactionIdentifier::from_call::<T, I>(call) {
125121
Ok(identifier) => identifier,
126122
Err(_) => return Err(TransactionValidityError::Invalid(InvalidTransaction::Call)),
127123
};
128124

125+
if !Pallet::<T, I>::is_registered(&identifier) {
126+
return Ok((ValidTransaction::default(), None, origin));
127+
}
128+
129129
let scope = <T as Config<I>>::LimitScopeResolver::context(&origin, call);
130130
let usage = <T as Config<I>>::UsageResolver::context(&origin, call);
131131

132132
let config_target = Pallet::<T, I>::config_target(&identifier)
133133
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Call))?;
134134
let usage_target = Pallet::<T, I>::usage_target(&identifier)
135135
.map_err(|_| TransactionValidityError::Invalid(InvalidTransaction::Call))?;
136-
let should_record = Pallet::<T, I>::should_record_usage(&identifier, &usage_target);
136+
let bypass = <T as Config<I>>::LimitScopeResolver::should_bypass(&origin, call);
137+
let should_record =
138+
bypass.record_usage && Pallet::<T, I>::should_record_usage(&identifier, &usage_target);
137139

138140
let Some(block_span) =
139141
Pallet::<T, I>::effective_span(&origin, call, &config_target, &scope)
140142
else {
141143
return Ok((ValidTransaction::default(), None, origin));
142144
};
143145

146+
if bypass.bypass_enforcement {
147+
return Ok((
148+
ValidTransaction::default(),
149+
should_record.then_some((usage_target, usage, true)),
150+
origin,
151+
));
152+
}
153+
144154
if block_span.is_zero() {
145155
return Ok((ValidTransaction::default(), None, origin));
146156
}
@@ -340,6 +350,52 @@ mod tests {
340350
});
341351
}
342352

353+
#[test]
354+
fn tx_extension_records_usage_on_bypass() {
355+
new_test_ext().execute_with(|| {
356+
let extension = new_tx_extension();
357+
let call = RuntimeCall::RateLimiting(RateLimitingCall::set_default_rate_limit {
358+
block_span: 2,
359+
});
360+
let identifier = identifier_for(&call);
361+
let target = RateLimitTarget::Transaction(identifier);
362+
363+
assert_ok!(RateLimiting::register_call(
364+
RuntimeOrigin::root(),
365+
Box::new(call.clone()),
366+
None,
367+
));
368+
369+
System::set_block_number(5);
370+
371+
let (_valid, val, origin) =
372+
validate_with_tx_extension(&extension, &call).expect("bypass should succeed");
373+
assert!(val.is_some(), "bypass decision should still record usage");
374+
375+
let info = call.get_dispatch_info();
376+
let len = call.encode().len();
377+
let pre = extension
378+
.clone()
379+
.prepare(val.clone(), &origin, &call, &info, len)
380+
.expect("prepare succeeds");
381+
382+
let mut post = PostDispatchInfo::default();
383+
RateLimitTransactionExtension::<Test>::post_dispatch(
384+
pre,
385+
&info,
386+
&mut post,
387+
len,
388+
&Ok(()),
389+
)
390+
.expect("post_dispatch succeeds");
391+
392+
assert_eq!(
393+
LastSeen::<Test, ()>::get(target, Some(2u16)),
394+
Some(5u64.into())
395+
);
396+
});
397+
}
398+
343399
#[test]
344400
fn tx_extension_records_last_seen_for_successful_call() {
345401
new_test_ext().execute_with(|| {

pallets/rate-limiting/src/types.rs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,9 @@ pub trait RateLimitScopeResolver<Origin, Call, Scope, Span> {
1111
/// limits.
1212
fn context(origin: &Origin, call: &Call) -> Option<Scope>;
1313

14-
/// Returns `true` when the rate limit should be bypassed for the provided origin/call pair.
15-
/// Defaults to `false`.
16-
fn should_bypass(_origin: &Origin, _call: &Call) -> bool {
17-
false
14+
/// Returns how the call should interact with enforcement and usage tracking.
15+
fn should_bypass(_origin: &Origin, _call: &Call) -> BypassDecision {
16+
BypassDecision::enforce_and_record()
1817
}
1918

2019
/// Optionally adjusts the effective span used during enforcement. Defaults to the original
@@ -24,6 +23,34 @@ pub trait RateLimitScopeResolver<Origin, Call, Scope, Span> {
2423
}
2524
}
2625

26+
/// Controls whether enforcement should run and whether usage should be recorded for a call.
27+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28+
pub struct BypassDecision {
29+
pub bypass_enforcement: bool,
30+
pub record_usage: bool,
31+
}
32+
33+
impl BypassDecision {
34+
pub const fn new(bypass_enforcement: bool, record_usage: bool) -> Self {
35+
Self {
36+
bypass_enforcement,
37+
record_usage,
38+
}
39+
}
40+
41+
pub const fn enforce_and_record() -> Self {
42+
Self::new(false, true)
43+
}
44+
45+
pub const fn bypass_and_record() -> Self {
46+
Self::new(true, true)
47+
}
48+
49+
pub const fn bypass_and_skip() -> Self {
50+
Self::new(true, false)
51+
}
52+
}
53+
2754
/// Resolves the optional usage tracking key applied when enforcing limits.
2855
pub trait RateLimitUsageResolver<Origin, Call, Usage> {
2956
/// Returns `Some(usage)` when usage should be tracked per-key, or `None` for global usage

runtime/src/rate_limiting/migration.rs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -404,10 +404,6 @@ pub fn rate_limited_calls() -> Vec<RateLimitedCall> {
404404
subtensor_identifier(3), // remove_stake
405405
subtensor_identifier(89), // remove_stake_limit
406406
subtensor_identifier(103), // remove_stake_full_limit
407-
subtensor_identifier(86), // transfer_stake
408-
subtensor_identifier(85), // move_stake
409-
subtensor_identifier(87), // swap_stake
410-
subtensor_identifier(90), // swap_stake_limit
411407
],
412408
legacy: sources(&["TxRateLimit"], &[]),
413409
},
@@ -423,7 +419,10 @@ pub fn rate_limited_calls() -> Vec<RateLimitedCall> {
423419
scope: LimitScopeKind::Global,
424420
usage: UsageKind::AccountSubnet,
425421
read_only: Vec::new(),
426-
legacy: sources(&["TxChildkeyTakeRateLimit"], &["TransactionKeyLastBlock::SetChildkeyTake"]),
422+
legacy: sources(
423+
&["TxChildkeyTakeRateLimit"],
424+
&["TransactionKeyLastBlock::SetChildkeyTake"],
425+
),
427426
},
428427
RateLimitedCall {
429428
target: TargetKind::Standalone(subtensor_identifier(67)), // set_children
@@ -462,10 +461,7 @@ pub fn rate_limited_calls() -> Vec<RateLimitedCall> {
462461
scope: LimitScopeKind::Global,
463462
usage: UsageKind::SubnetNeuron,
464463
read_only: Vec::new(),
465-
legacy: sources(
466-
&["EvmKeyAssociateRateLimit"],
467-
&["AssociatedEvmAddress"],
468-
),
464+
legacy: sources(&["EvmKeyAssociateRateLimit"], &["AssociatedEvmAddress"]),
469465
},
470466
RateLimitedCall {
471467
target: TargetKind::Standalone(admin_utils_identifier(76)), // sudo_set_mechanism_count
@@ -548,10 +544,10 @@ type LimitImporter<T> = fn(&Grouping, &mut LimitEntries<T>) -> u64;
548544

549545
fn limit_importers<T: SubtensorConfig>() -> [LimitImporter<T>; 4] {
550546
[
551-
import_simple_limits::<T>, // Tx/childkey/delegate/staking lock, register, sudo, evm, children
547+
import_simple_limits::<T>, // Tx/childkey/delegate/staking lock, register, sudo, evm, children
552548
import_owner_hparam_limits::<T>, // Owner hyperparams
553-
import_serving_limits::<T>, // Axon/prometheus serving rate limit per subnet
554-
import_weight_limits::<T>, // Weight/commit/reveal per subnet and mechanism
549+
import_serving_limits::<T>, // Axon/prometheus serving rate limit per subnet
550+
import_weight_limits::<T>, // Weight/commit/reveal per subnet and mechanism
555551
]
556552
}
557553

@@ -581,9 +577,9 @@ fn import_simple_limits<T: SubtensorConfig>(
581577
);
582578
}
583579

584-
// Share the TxRateLimit span across staking operations; add_* are marker-only via span tweaks.
585-
if let Some(span) = block_number::<T>(TxRateLimit::<T>::get()) {
586-
if let Some(members) = grouping.members(GROUP_STAKING_OPS) {
580+
// Staking ops are gated to one operation per block in legacy (marker cleared each block).
581+
if let Some(members) = grouping.members(GROUP_STAKING_OPS) {
582+
if let Some(span) = block_number::<T>(1) {
587583
for call in members {
588584
set_global_limit::<T>(limits, grouping.config_target(*call), span);
589585
}
@@ -761,9 +757,9 @@ fn last_seen_importers<T: SubtensorConfig>() -> [LastSeenImporter<T>; 5] {
761757
[
762758
import_last_rate_limited_blocks::<T>, // LastRateLimitedBlock (tx, delegate, owner hyperparams, sn owner)
763759
import_transaction_key_last_blocks::<T>, // TransactionKeyLastBlock (children, version key, mechanisms)
764-
import_last_update_entries::<T>, // LastUpdate (weights/mechanism weights)
765-
import_serving_entries::<T>, // Axons/Prometheus
766-
import_evm_entries::<T>, // AssociatedEvmAddress
760+
import_last_update_entries::<T>, // LastUpdate (weights/mechanism weights)
761+
import_serving_entries::<T>, // Axons/Prometheus
762+
import_evm_entries::<T>, // AssociatedEvmAddress
767763
]
768764
}
769765

@@ -866,8 +862,8 @@ fn import_last_update_entries<T: SubtensorConfig>(
866862
let mut reads: u64 = 0;
867863
for (index, blocks) in LastUpdate::<T>::iter() {
868864
reads += 1;
869-
let (netuid, mecid) = Pallet::<T>::get_netuid_and_subid(index)
870-
.unwrap_or((NetUid::ROOT, 0.into()));
865+
let (netuid, mecid) =
866+
Pallet::<T>::get_netuid_and_subid(index).unwrap_or((NetUid::ROOT, 0.into()));
871867
let subnet_calls = if mecid == 0.into() {
872868
weight_calls_subnet(grouping)
873869
} else {
@@ -882,7 +878,10 @@ fn import_last_update_entries<T: SubtensorConfig>(
882878
continue;
883879
};
884880
let usage = if mecid == 0.into() {
885-
RateLimitUsageKey::SubnetNeuron { netuid, uid: uid_u16 }
881+
RateLimitUsageKey::SubnetNeuron {
882+
netuid,
883+
uid: uid_u16,
884+
}
886885
} else {
887886
RateLimitUsageKey::SubnetMechanismNeuron {
888887
netuid,

0 commit comments

Comments
 (0)