Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pre check for rate limit #194

Merged
merged 7 commits into from
Jun 27, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
43 changes: 42 additions & 1 deletion src/config/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use anyhow::Context;
use anyhow::{anyhow, bail, Context};
use regex::{Captures, Regex};
use std::env;
use std::fs;
use std::num::NonZeroU32;
use std::path;
use std::time::Duration;

use garde::Validate;
use governor::RateLimiter;
use serde::Deserialize;

use crate::extensions::rate_limit::build_quota;
use crate::extensions::ExtensionsConfig;
pub use rpc::*;

Expand Down Expand Up @@ -208,6 +212,43 @@ pub async fn validate(config: &Config) -> Result<(), anyhow::Error> {

// validate use garde::Validate
config.validate(&())?;

if let Some(rate_limit) = config.extensions.rate_limit.as_ref() {
if let Some(ref rule) = rate_limit.ip {
let burst = NonZeroU32::new(rule.burst).ok_or(anyhow!("burst could not be zero"))?;
let period = Duration::from_secs(rule.period_secs);
let quota = build_quota(burst, period);
let limiter = RateLimiter::direct(quota);

for method in &config.rpcs.methods {
if let Some(n) = NonZeroU32::new(method.rate_limit_weight) {
if limiter.check_n(n).is_err() {
bail!("`{}` weight config too big for ip rate limit: {}", method.method, n);
}
}
}
}

if let Some(ref rule) = rate_limit.connection {
let burst = NonZeroU32::new(rule.burst).ok_or(anyhow!("burst could not be zero"))?;
let period = Duration::from_secs(rule.period_secs);
let quota = build_quota(burst, period);
let limiter = RateLimiter::direct(quota);

for method in &config.rpcs.methods {
if let Some(n) = NonZeroU32::new(method.rate_limit_weight) {
if limiter.check_n(n).is_err() {
bail!(
"`{}` weight config too big for connection rate limit: {}",
method.method,
n
);
}
}
}
}
}

// since endpoints connection test is async
// we can't intergrate it into garde::Validate
// and it's not a static validation like format, length, .etc
Expand Down
9 changes: 5 additions & 4 deletions src/extensions/rate_limit/connection.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{extensions::rate_limit::MethodWeights, utils::errors};
use crate::extensions::rate_limit::MethodWeights;
use futures::{future::BoxFuture, FutureExt};
use governor::{DefaultDirectRateLimiter, Jitter, RateLimiter};
use jsonrpsee::{
Expand Down Expand Up @@ -75,9 +75,10 @@ where

async move {
if let Some(n) = NonZeroU32::new(weight) {
if limiter.until_n_ready_with_jitter(n, jitter).await.is_err() {
return MethodResponse::error(req.id, errors::failed("rate limit exceeded"));
}
limiter
.until_n_ready_with_jitter(n, jitter)
.await
.expect("check_n have been done during init");
}
service.call(req).await
}
Expand Down
9 changes: 3 additions & 6 deletions src/extensions/rate_limit/ip.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{extensions::rate_limit::MethodWeights, utils::errors};
use crate::extensions::rate_limit::MethodWeights;
use futures::{future::BoxFuture, FutureExt};
use governor::{DefaultKeyedRateLimiter, Jitter};
use jsonrpsee::{
Expand Down Expand Up @@ -86,13 +86,10 @@ where
let weight = self.method_weights.get(req.method_name());
async move {
if let Some(n) = NonZeroU32::new(weight) {
if limiter
limiter
.until_key_n_ready_with_jitter(&ip_addr, n, jitter)
.await
.is_err()
{
return MethodResponse::error(req.id, errors::failed("rate limit exceeded"));
}
.expect("check_n have been done during init");
}
service.call(req).await
}
Expand Down
1 change: 1 addition & 0 deletions src/extensions/rate_limit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ impl RateLimitBuilder {
}
}
}

pub fn connection_limit(&self, method_weights: MethodWeights) -> Option<ConnectionRateLimitLayer> {
if let Some(ref rule) = self.config.connection {
let burst = NonZeroU32::new(rule.burst).unwrap();
Expand Down
Loading