Skip to content

Commit 19ce3c4

Browse files
committed
add rate limiter
1 parent e5c3a5c commit 19ce3c4

File tree

3 files changed

+56
-4
lines changed

3 files changed

+56
-4
lines changed

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ pub mod logger;
9494
mod message_handler;
9595
pub mod payment;
9696
mod peer_store;
97+
mod rate_limiter;
9798
mod runtime;
9899
mod static_invoice_store;
99100
mod tx_broadcaster;

src/rate_limiter.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use std::collections::HashMap;
2+
use std::time::{Duration, Instant};
3+
4+
pub(crate) struct RateLimiter {
5+
users: HashMap<Vec<u8>, Instant>,
6+
min_interval: Duration,
7+
}
8+
9+
impl RateLimiter {
10+
pub(crate) fn new(min_interval: Duration) -> Self {
11+
Self { users: HashMap::new(), min_interval }
12+
}
13+
14+
pub(crate) fn allow(&mut self, user_id: &[u8]) -> bool {
15+
let now = Instant::now();
16+
match self.users.get(user_id) {
17+
Some(&last) if now.duration_since(last) < self.min_interval => false,
18+
_ => {
19+
let new_user = self.users.insert(user_id.to_vec(), now).is_none();
20+
21+
// Each time a new user is added, we take the opportunity to clean up old rate limits.
22+
if new_user {
23+
self.garbage_collect();
24+
}
25+
true
26+
},
27+
}
28+
}
29+
30+
fn garbage_collect(&mut self) {
31+
let now = Instant::now();
32+
self.users.retain(|_, &mut last| now.duration_since(last) < self.min_interval);
33+
}
34+
}

src/static_invoice_store.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,41 @@
1-
use crate::{hex_utils, types::DynStore};
1+
use crate::{hex_utils, rate_limiter::RateLimiter, types::DynStore};
22
use bitcoin::hashes::{sha256, Hash};
33
use lightning::{offers::static_invoice::StaticInvoice, util::ser::Writeable};
4-
use std::sync::Arc;
4+
use std::{
5+
sync::{Arc, Mutex},
6+
time::Duration,
7+
};
58

69
pub(crate) struct StaticInvoiceStore {
710
kv_store: Arc<DynStore>,
11+
rate_limiter: Mutex<RateLimiter>,
812
}
913

1014
impl StaticInvoiceStore {
1115
// Static invoices are stored at "static_invoices/<sha256(recipient_id)>/<invoice_slot>".
1216
//
1317
// Example:
14-
// static_invoices/039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81/0000
18+
// static_invoices/039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81/001f
1519
const PRIMARY_NAMESPACE: &str = "static_invoices";
1620

1721
pub(crate) fn new(kv_store: Arc<DynStore>) -> Self {
18-
Self { kv_store }
22+
Self { kv_store, rate_limiter: Mutex::new(RateLimiter::new(Duration::from_millis(100))) }
23+
}
24+
25+
fn check_rate_limit(&self, recipient_id: &[u8]) -> Result<(), lightning::io::Error> {
26+
let mut limiter = self.rate_limiter.lock().unwrap();
27+
if !limiter.allow(recipient_id) {
28+
Err(lightning::io::Error::new(lightning::io::ErrorKind::Other, "Rate limit exceeded"))
29+
} else {
30+
Ok(())
31+
}
1932
}
2033

2134
pub(crate) async fn handle_static_invoice_requested(
2235
&self, recipient_id: Vec<u8>, invoice_slot: u16,
2336
) -> Result<Option<StaticInvoice>, lightning::io::Error> {
37+
self.check_rate_limit(&recipient_id)?;
38+
2439
let (secondary_namespace, key) = Self::get_storage_location(invoice_slot, recipient_id);
2540

2641
self.kv_store.read(Self::PRIMARY_NAMESPACE, &secondary_namespace, &key).and_then(|data| {
@@ -36,6 +51,8 @@ impl StaticInvoiceStore {
3651
pub(crate) async fn handle_persist_static_invoice(
3752
&self, invoice: StaticInvoice, invoice_slot: u16, recipient_id: Vec<u8>,
3853
) -> Result<(), lightning::io::Error> {
54+
self.check_rate_limit(&recipient_id)?;
55+
3956
let (secondary_namespace, key) = Self::get_storage_location(invoice_slot, recipient_id);
4057

4158
let mut buf = Vec::new();

0 commit comments

Comments
 (0)