Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
a37cc79
Initialize pallet-rate-limiting
ales-otf Oct 17, 2025
e903915
Add transaction extension for rate limiting
ales-otf Oct 17, 2025
ea289ef
Implement contextual scope
ales-otf Oct 17, 2025
ebcec75
Add context resolver
ales-otf Oct 17, 2025
56333c9
Add default rate limit
ales-otf Oct 21, 2025
1b6b03c
Add genesis config for pallet-rate-limiting
ales-otf Oct 21, 2025
220c905
Add rpc method to fetch rate limit
ales-otf Oct 21, 2025
69151b1
Add tests to pallet-rate-limiting
ales-otf Oct 27, 2025
13b8774
Add crate-level documentation for pallet-rate-limiting
ales-otf Oct 27, 2025
a4a1c88
Add benchmarks for pallet-rate-limiting
ales-otf Oct 27, 2025
6414907
Merge branch 'devnet-ready' into feat/rate-limit-pallet
ales-otf Oct 29, 2025
6ccc421
Extend rate limit setting with context
ales-otf Oct 29, 2025
be9d4f7
Make rate limiting pallet instantiable
ales-otf Oct 29, 2025
61eb4d2
Refactor RateLimit type
ales-otf Oct 30, 2025
c84cb92
Separate context between usage key and limit scope
ales-otf Oct 30, 2025
f85320e
Merge branch 'devnet-ready' into feat/rate-limit-pallet
ales-otf Oct 30, 2025
2df6d4b
Update docs
ales-otf Oct 31, 2025
15c9aa9
Add an extrinsic to clear all scoped rate limits in pallet-rate-limiting
ales-otf Oct 31, 2025
e74d650
Clear LastSeen on clear_rate_limit call
ales-otf Oct 31, 2025
fc60465
Add rate limit bypassing
ales-otf Oct 31, 2025
5c8a8ab
Add rate limit adjuster
ales-otf Oct 31, 2025
1e8db7d
Add api to migrate scope and usage keys
ales-otf Nov 3, 2025
d65baff
Merge branch 'devnet-ready' into feat/rate-limit-pallet
ales-otf Nov 3, 2025
232de64
Pass origin to rate limit context resolvers
ales-otf Nov 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ members = [
"common",
"node",
"pallets/*",
"pallets/rate-limiting/runtime-api",
"pallets/rate-limiting/rpc",
"precompiles",
"primitives/*",
"runtime",
Expand Down Expand Up @@ -59,6 +61,9 @@ pallet-subtensor = { path = "pallets/subtensor", default-features = false }
pallet-subtensor-swap = { path = "pallets/swap", default-features = false }
pallet-subtensor-swap-runtime-api = { path = "pallets/swap/runtime-api", default-features = false }
pallet-subtensor-swap-rpc = { path = "pallets/swap/rpc", default-features = false }
pallet-rate-limiting = { path = "pallets/rate-limiting", default-features = false }
pallet-rate-limiting-runtime-api = { path = "pallets/rate-limiting/runtime-api", default-features = false }
pallet-rate-limiting-rpc = { path = "pallets/rate-limiting/rpc", default-features = false }
procedural-fork = { path = "support/procedural-fork", default-features = false }
safe-math = { path = "primitives/safe-math", default-features = false }
share-pool = { path = "primitives/share-pool", default-features = false }
Expand Down
47 changes: 47 additions & 0 deletions pallets/rate-limiting/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[package]
name = "pallet-rate-limiting"
version = "0.1.0"
edition.workspace = true

[lints]
workspace = true

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support.workspace = true
frame-system.workspace = true
scale-info = { workspace = true, features = ["derive"] }
serde = { workspace = true, features = ["derive"], optional = true }
sp-std.workspace = true
sp-runtime.workspace = true
subtensor-runtime-common.workspace = true

[dev-dependencies]
sp-core.workspace = true
sp-io.workspace = true
sp-runtime.workspace = true

[features]
default = ["std"]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
"serde",
"sp-std/std",
"sp-runtime/std",
"subtensor-runtime-common/std",
]
runtime-benchmarks = [
"frame-benchmarking",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
]
22 changes: 22 additions & 0 deletions pallets/rate-limiting/rpc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "pallet-rate-limiting-rpc"
version = "0.1.0"
description = "RPC interface for the rate limiting pallet"
edition.workspace = true

[dependencies]
jsonrpsee = { workspace = true, features = ["client-core", "server", "macros"] }
sp-api.workspace = true
sp-blockchain.workspace = true
sp-runtime.workspace = true
pallet-rate-limiting-runtime-api.workspace = true
subtensor-runtime-common = { workspace = true, default-features = false }

[features]
default = ["std"]
std = [
"sp-api/std",
"sp-runtime/std",
"pallet-rate-limiting-runtime-api/std",
"subtensor-runtime-common/std",
]
82 changes: 82 additions & 0 deletions pallets/rate-limiting/rpc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//! RPC interface for the rate limiting pallet.

use jsonrpsee::{
core::RpcResult,
proc_macros::rpc,
types::{ErrorObjectOwned, error::ErrorObject},
};
use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend;
use sp_runtime::traits::Block as BlockT;
use std::sync::Arc;

pub use pallet_rate_limiting_runtime_api::{RateLimitRpcResponse, RateLimitingRuntimeApi};

#[rpc(client, server)]
pub trait RateLimitingRpcApi<BlockHash> {
#[method(name = "rateLimiting_getRateLimit")]
fn get_rate_limit(
&self,
pallet: Vec<u8>,
extrinsic: Vec<u8>,
at: Option<BlockHash>,
) -> RpcResult<Option<RateLimitRpcResponse>>;
}

/// Error type of this RPC api.
pub enum Error {
/// The call to runtime failed.
RuntimeError(String),
}

impl From<Error> for ErrorObjectOwned {
fn from(e: Error) -> Self {
match e {
Error::RuntimeError(e) => ErrorObject::owned(1, e, None::<()>),
}
}
}

impl From<Error> for i32 {
fn from(e: Error) -> i32 {
match e {
Error::RuntimeError(_) => 1,
}
}
}

/// RPC implementation for the rate limiting pallet.
pub struct RateLimiting<C, Block> {
client: Arc<C>,
_marker: std::marker::PhantomData<Block>,
}

impl<C, Block> RateLimiting<C, Block> {
/// Creates a new instance of the rate limiting RPC helper.
pub fn new(client: Arc<C>) -> Self {
Self {
client,
_marker: Default::default(),
}
}
}

impl<C, Block> RateLimitingRpcApiServer<<Block as BlockT>::Hash> for RateLimiting<C, Block>
where
Block: BlockT,
C: ProvideRuntimeApi<Block> + HeaderBackend<Block> + Send + Sync + 'static,
C::Api: RateLimitingRuntimeApi<Block>,
{
fn get_rate_limit(
&self,
pallet: Vec<u8>,
extrinsic: Vec<u8>,
at: Option<<Block as BlockT>::Hash>,
) -> RpcResult<Option<RateLimitRpcResponse>> {
let api = self.client.runtime_api();
let at = at.unwrap_or_else(|| self.client.info().best_hash);

api.get_rate_limit(at, pallet, extrinsic)
.map_err(|e| Error::RuntimeError(format!("Unable to fetch rate limit: {e:?}")).into())
}
}
26 changes: 26 additions & 0 deletions pallets/rate-limiting/runtime-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "pallet-rate-limiting-runtime-api"
version = "0.1.0"
description = "Runtime API for the rate limiting pallet"
edition.workspace = true

[dependencies]
codec = { workspace = true, features = ["derive"] }
scale-info = { workspace = true, features = ["derive"] }
sp-api.workspace = true
sp-std.workspace = true
pallet-rate-limiting.workspace = true
subtensor-runtime-common = { workspace = true, default-features = false }
serde = { workspace = true, features = ["derive"], optional = true }

[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"sp-api/std",
"sp-std/std",
"pallet-rate-limiting/std",
"subtensor-runtime-common/std",
"serde",
]
25 changes: 25 additions & 0 deletions pallets/rate-limiting/runtime-api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#![cfg_attr(not(feature = "std"), no_std)]

use codec::{Decode, Encode};
use pallet_rate_limiting::RateLimitKind;
use scale_info::TypeInfo;
use sp_std::vec::Vec;
use subtensor_runtime_common::BlockNumber;

#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};

#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)]
pub struct RateLimitRpcResponse {
pub global: Option<RateLimitKind<BlockNumber>>,
pub contextual: Vec<(Vec<u8>, RateLimitKind<BlockNumber>)>,
pub default_limit: BlockNumber,
pub resolved: Option<BlockNumber>,
}

sp_api::decl_runtime_apis! {
pub trait RateLimitingRuntimeApi {
fn get_rate_limit(pallet: Vec<u8>, extrinsic: Vec<u8>) -> Option<RateLimitRpcResponse>;
}
}
98 changes: 98 additions & 0 deletions pallets/rate-limiting/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Benchmarking setup for pallet-rate-limiting
#![cfg(feature = "runtime-benchmarks")]
#![allow(clippy::arithmetic_side_effects)]

use codec::Decode;
use frame_benchmarking::v2::*;
use frame_system::{RawOrigin, pallet_prelude::BlockNumberFor};
use sp_runtime::traits::DispatchOriginOf;

use super::*;

pub trait BenchmarkHelper<Call> {
fn sample_call() -> Call;
}

impl<Call> BenchmarkHelper<Call> for ()
where
Call: Decode,
{
fn sample_call() -> Call {
Decode::decode(&mut &[][..]).expect("Provide a call via BenchmarkHelper::sample_call")
}
}

fn sample_call<T: Config>() -> Box<<T as Config>::RuntimeCall>
where
T::BenchmarkHelper: BenchmarkHelper<<T as Config>::RuntimeCall>,
{
Box::new(T::BenchmarkHelper::sample_call())
}

#[benchmarks]
mod benchmarks {
use super::*;

#[benchmark]
fn set_rate_limit() {
let call = sample_call::<T>();
let limit = RateLimitKind::<BlockNumberFor<T>>::Exact(BlockNumberFor::<T>::from(10u32));
let origin = T::RuntimeOrigin::from(RawOrigin::Root);
let resolver_origin: DispatchOriginOf<<T as Config>::RuntimeCall> =
Into::<DispatchOriginOf<<T as Config>::RuntimeCall>>::into(origin.clone());
let scope = <T as Config>::LimitScopeResolver::context(&resolver_origin, call.as_ref());
let identifier =
TransactionIdentifier::from_call::<T, ()>(call.as_ref()).expect("identifier");

#[extrinsic_call]
_(RawOrigin::Root, call, limit.clone());

let stored = Limits::<T, ()>::get(&identifier).expect("limit stored");
match (scope, &stored) {
(Some(ref sc), RateLimit::Scoped(map)) => {
assert_eq!(map.get(sc), Some(&limit));
}
(None, RateLimit::Global(kind)) | (Some(_), RateLimit::Global(kind)) => {
assert_eq!(kind, &limit);
}
(None, RateLimit::Scoped(map)) => {
assert!(map.values().any(|k| k == &limit));
}
}
}

#[benchmark]
fn clear_rate_limit() {
let call = sample_call::<T>();
let limit = RateLimitKind::<BlockNumberFor<T>>::Exact(BlockNumberFor::<T>::from(10u32));
let origin = T::RuntimeOrigin::from(RawOrigin::Root);
let resolver_origin: DispatchOriginOf<<T as Config>::RuntimeCall> =
Into::<DispatchOriginOf<<T as Config>::RuntimeCall>>::into(origin.clone());
let scope = <T as Config>::LimitScopeResolver::context(&resolver_origin, call.as_ref());

// Pre-populate limit for benchmark call
let identifier =
TransactionIdentifier::from_call::<T, ()>(call.as_ref()).expect("identifier");
match scope.clone() {
Some(sc) => Limits::<T, ()>::insert(identifier, RateLimit::scoped_single(sc, limit)),
None => Limits::<T, ()>::insert(identifier, RateLimit::global(limit)),
}

#[extrinsic_call]
_(RawOrigin::Root, call);

assert!(Limits::<T, ()>::get(identifier).is_none());
}

#[benchmark]
fn set_default_rate_limit() {
let block_span = BlockNumberFor::<T>::from(10u32);

#[extrinsic_call]
_(RawOrigin::Root, block_span);

assert_eq!(DefaultLimit::<T, ()>::get(), block_span);
}

impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
Loading
Loading