Skip to content

Commit 7e0c630

Browse files
committed
Add inbound request size limiter
1 parent c0ae632 commit 7e0c630

File tree

4 files changed

+278
-54
lines changed

4 files changed

+278
-54
lines changed

beacon_node/lighthouse_network/src/rpc/mod.rs

Lines changed: 46 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use libp2p::swarm::{
1313
use libp2p::swarm::{FromSwarm, SubstreamProtocol, THandlerInEvent};
1414
use libp2p::PeerId;
1515
use parking_lot::Mutex;
16-
use rate_limiter::{RPCRateLimiter as RateLimiter, RateLimitedErr};
16+
use rate_limiter::RPCRateLimiter as RateLimiter;
1717
use slog::{crit, debug, o};
1818
use std::marker::PhantomData;
1919
use std::sync::Arc;
@@ -25,6 +25,7 @@ pub(crate) use handler::{HandlerErr, HandlerEvent};
2525
pub(crate) use methods::{MetaData, MetaDataV1, MetaDataV2, Ping, RPCCodedResponse, RPCResponse};
2626
pub(crate) use protocol::InboundRequest;
2727

28+
use crate::rpc::rate_limiter::InboundRequestSizeLimiter;
2829
pub use handler::SubstreamId;
2930
pub use methods::{
3031
BlocksByRangeRequest, BlocksByRootRequest, GoodbyeReason, LightClientBootstrapRequest,
@@ -124,6 +125,8 @@ pub struct RPC<Id: ReqId, E: EthSpec> {
124125
response_limiter: Option<Arc<Mutex<RateLimiter>>>,
125126
/// Rate limiter for our own requests.
126127
self_limiter: Option<SelfRateLimiter<Id, E>>,
128+
/// Limiter for our inbound requests, which checks the request size.
129+
inbound_request_size_limiter: Option<InboundRequestSizeLimiter>,
127130
/// Queue of events to be processed.
128131
events: Vec<BehaviourAction<Id, E>>,
129132
fork_context: Arc<ForkContext>,
@@ -152,14 +155,19 @@ impl<Id: ReqId, E: EthSpec> RPC<Id, E> {
152155
// });
153156
let inbound_limiter = None; // TODO
154157

155-
let response_limiter = inbound_rate_limiter_config.map(|config| {
158+
let response_limiter = inbound_rate_limiter_config.clone().map(|config| {
156159
debug!(log, "Using response rate limiting params"; "config" => ?config);
157160
Arc::new(Mutex::new(
158161
RateLimiter::new_with_config(config.0)
159162
.expect("Inbound limiter configuration parameters are valid"),
160163
))
161164
});
162165

166+
let inbound_request_size_limiter = inbound_rate_limiter_config.map(|config| {
167+
InboundRequestSizeLimiter::new_with_config(config.0)
168+
.expect("Inbound limiter configuration parameters are valid")
169+
});
170+
163171
let self_limiter = outbound_rate_limiter_config.map(|config| {
164172
SelfRateLimiter::new(config, log.clone()).expect("Configuration parameters are valid")
165173
});
@@ -168,6 +176,7 @@ impl<Id: ReqId, E: EthSpec> RPC<Id, E> {
168176
limiter: inbound_limiter,
169177
response_limiter,
170178
self_limiter,
179+
inbound_request_size_limiter,
171180
events: Vec::new(),
172181
fork_context,
173182
enable_light_client_server,
@@ -315,57 +324,42 @@ where
315324
) {
316325
match event {
317326
HandlerEvent::Ok(RPCReceived::Request(ref id, ref req)) => {
318-
if let Some(limiter) = self.limiter.as_mut() {
319-
// check if the request is conformant to the quota
320-
match limiter.allows(&peer_id, req) {
321-
Ok(()) => {
322-
// send the event to the user
323-
self.events.push(ToSwarm::GenerateEvent(RPCMessage {
324-
peer_id,
325-
conn_id,
326-
event,
327-
}))
328-
}
329-
Err(RateLimitedErr::TooLarge) => {
330-
// we set the batch sizes, so this is a coding/config err for most protocols
331-
let protocol = req.versioned_protocol().protocol();
332-
if matches!(
333-
protocol,
334-
Protocol::BlocksByRange
335-
| Protocol::BlobsByRange
336-
| Protocol::BlocksByRoot
337-
| Protocol::BlobsByRoot
338-
) {
339-
debug!(self.log, "Request too large to process"; "request" => %req, "protocol" => %protocol);
340-
} else {
341-
// Other protocols shouldn't be sending large messages, we should flag the peer kind
342-
crit!(self.log, "Request size too large to ever be processed"; "protocol" => %protocol);
343-
}
344-
// send an error code to the peer.
345-
// the handler upon receiving the error code will send it back to the behaviour
346-
self.send_response(
347-
peer_id,
348-
(conn_id, *id),
349-
RPCCodedResponse::Error(
350-
RPCResponseErrorCode::RateLimited,
351-
"Rate limited. Request too large".into(),
352-
),
353-
);
354-
}
355-
Err(RateLimitedErr::TooSoon(wait_time)) => {
356-
debug!(self.log, "Request exceeds the rate limit";
357-
"request" => %req, "peer_id" => %peer_id, "wait_time_ms" => wait_time.as_millis());
358-
// send an error code to the peer.
359-
// the handler upon receiving the error code will send it back to the behaviour
360-
self.send_response(
361-
peer_id,
362-
(conn_id, *id),
363-
RPCCodedResponse::Error(
364-
RPCResponseErrorCode::RateLimited,
365-
format!("Wait {:?}", wait_time).into(),
366-
),
367-
);
327+
// TODO: Send error response if there is ongoing request with the same protocol.
328+
329+
if let Some(limiter) = self.inbound_request_size_limiter.as_ref() {
330+
// Check if the request is conformant to the quota
331+
if limiter.allows(req) {
332+
// Send the event to the user
333+
self.events.push(ToSwarm::GenerateEvent(RPCMessage {
334+
peer_id,
335+
conn_id,
336+
event,
337+
}))
338+
} else {
339+
// We set the batch sizes, so this is a coding/config err for most protocols
340+
let protocol = req.versioned_protocol().protocol();
341+
if matches!(
342+
protocol,
343+
Protocol::BlocksByRange
344+
| Protocol::BlobsByRange
345+
| Protocol::BlocksByRoot
346+
| Protocol::BlobsByRoot
347+
) {
348+
debug!(self.log, "Request too large to process"; "request" => %req, "protocol" => %protocol);
349+
} else {
350+
// Other protocols shouldn't be sending large messages, we should flag the peer kind
351+
crit!(self.log, "Request size too large to ever be processed"; "protocol" => %protocol);
368352
}
353+
// Send an error code to the peer.
354+
// The handler upon receiving the error code will send it back to the behaviour
355+
self.send_response(
356+
peer_id,
357+
(conn_id, *id),
358+
RPCCodedResponse::Error(
359+
RPCResponseErrorCode::RateLimited,
360+
"Rate limited. Request too large".into(),
361+
),
362+
);
369363
}
370364
} else {
371365
// No rate limiting, send the event to the user

beacon_node/lighthouse_network/src/rpc/rate_limiter.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,94 @@ impl<Key: Hash + Eq + Clone> Limiter<Key> {
434434
}
435435
}
436436

437+
pub(super) struct InboundRequestSizeLimiter {
438+
ping: (Nanosecs, Nanosecs),
439+
status: (Nanosecs, Nanosecs),
440+
meta_data: (Nanosecs, Nanosecs),
441+
goodbye: (Nanosecs, Nanosecs),
442+
blocks_by_range: (Nanosecs, Nanosecs),
443+
blocks_by_root: (Nanosecs, Nanosecs),
444+
blobs_by_range: (Nanosecs, Nanosecs),
445+
blobs_by_root: (Nanosecs, Nanosecs),
446+
light_client_bootstrap: (Nanosecs, Nanosecs),
447+
light_client_optimistic_update: (Nanosecs, Nanosecs),
448+
light_client_finality_update: (Nanosecs, Nanosecs),
449+
}
450+
451+
impl InboundRequestSizeLimiter {
452+
pub fn new_with_config(config: RateLimiterConfig) -> Result<Self, &'static str> {
453+
// Destructure to make sure every configuration value is used.
454+
let RateLimiterConfig {
455+
ping_quota,
456+
meta_data_quota,
457+
status_quota,
458+
goodbye_quota,
459+
blocks_by_range_quota,
460+
blocks_by_root_quota,
461+
blobs_by_range_quota,
462+
blobs_by_root_quota,
463+
light_client_bootstrap_quota,
464+
light_client_optimistic_update_quota,
465+
light_client_finality_update_quota,
466+
} = config;
467+
468+
let tau_and_t = |quota: &Quota| {
469+
let tau = quota.replenish_all_every.as_nanos();
470+
if tau == 0 {
471+
return Err("Replenish time must be positive");
472+
}
473+
let t = (tau / quota.max_tokens as u128)
474+
.try_into()
475+
.map_err(|_| "total replenish time is too long")?;
476+
let tau = tau
477+
.try_into()
478+
.map_err(|_| "total replenish time is too long")?;
479+
Ok((tau, t))
480+
};
481+
482+
Ok(Self {
483+
ping: tau_and_t(&ping_quota)?,
484+
meta_data: tau_and_t(&meta_data_quota)?,
485+
status: tau_and_t(&status_quota)?,
486+
goodbye: tau_and_t(&goodbye_quota)?,
487+
blocks_by_range: tau_and_t(&blocks_by_range_quota)?,
488+
blocks_by_root: tau_and_t(&blocks_by_root_quota)?,
489+
blobs_by_range: tau_and_t(&blobs_by_range_quota)?,
490+
blobs_by_root: tau_and_t(&blobs_by_root_quota)?,
491+
light_client_bootstrap: tau_and_t(&light_client_bootstrap_quota)?,
492+
light_client_optimistic_update: tau_and_t(&light_client_optimistic_update_quota)?,
493+
light_client_finality_update: tau_and_t(&light_client_finality_update_quota)?,
494+
})
495+
}
496+
497+
pub fn allows<Item: RateLimiterItem>(&self, request: &Item) -> bool {
498+
let tokens = request.max_responses().max(1);
499+
let (tau, t) = match request.protocol() {
500+
Protocol::Ping => self.ping,
501+
Protocol::Status => self.status,
502+
Protocol::MetaData => self.meta_data,
503+
Protocol::Goodbye => self.goodbye,
504+
Protocol::BlocksByRange => self.blocks_by_range,
505+
Protocol::BlocksByRoot => self.blocks_by_root,
506+
Protocol::BlobsByRange => self.blobs_by_range,
507+
Protocol::BlobsByRoot => self.blobs_by_root,
508+
Protocol::LightClientBootstrap => self.light_client_bootstrap,
509+
Protocol::LightClientOptimisticUpdate => self.light_client_optimistic_update,
510+
Protocol::LightClientFinalityUpdate => self.light_client_finality_update,
511+
};
512+
513+
// how long does it take to replenish these tokens
514+
let additional_time = t * tokens;
515+
516+
if additional_time > tau {
517+
// the time required to process this amount of tokens is longer than the time that
518+
// makes the bucket full. So, this batch can _never_ be processed
519+
return false;
520+
}
521+
true
522+
}
523+
}
524+
437525
#[cfg(test)]
438526
mod tests {
439527
use crate::rpc::rate_limiter::{Limiter, Quota};

beacon_node/lighthouse_network/tests/common.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ pub fn build_log(level: slog::Level, enabled: bool) -> slog::Logger {
7979

8080
pub fn build_config(
8181
mut boot_nodes: Vec<Enr>,
82+
disable_peer_scoring: bool,
8283
inbound_rate_limiter: Option<InboundRateLimiterConfig>,
8384
) -> NetworkConfig {
8485
let mut config = NetworkConfig::default();
@@ -96,6 +97,7 @@ pub fn build_config(
9697
config.enr_address = (Some(std::net::Ipv4Addr::LOCALHOST), None);
9798
config.boot_nodes_enr.append(&mut boot_nodes);
9899
config.network_dir = path.into_path();
100+
config.disable_peer_scoring = disable_peer_scoring;
99101
config.inbound_rate_limiter_config = inbound_rate_limiter;
100102
config
101103
}
@@ -106,9 +108,10 @@ pub async fn build_libp2p_instance(
106108
log: slog::Logger,
107109
fork_name: ForkName,
108110
spec: &ChainSpec,
111+
disable_peer_scoring: bool,
109112
inbound_rate_limiter: Option<InboundRateLimiterConfig>,
110113
) -> Libp2pInstance {
111-
let config = build_config(boot_nodes, inbound_rate_limiter);
114+
let config = build_config(boot_nodes, disable_peer_scoring, inbound_rate_limiter);
112115
// launch libp2p service
113116

114117
let (signal, exit) = async_channel::bounded(1);
@@ -150,6 +153,7 @@ pub async fn build_node_pair(
150153
fork_name: ForkName,
151154
spec: &ChainSpec,
152155
protocol: Protocol,
156+
disable_peer_scoring: bool,
153157
inbound_rate_limiter: Option<InboundRateLimiterConfig>,
154158
) -> (Libp2pInstance, Libp2pInstance) {
155159
let sender_log = log.new(o!("who" => "sender"));
@@ -161,6 +165,7 @@ pub async fn build_node_pair(
161165
sender_log,
162166
fork_name,
163167
spec,
168+
disable_peer_scoring,
164169
inbound_rate_limiter.clone(),
165170
)
166171
.await;
@@ -170,6 +175,7 @@ pub async fn build_node_pair(
170175
receiver_log,
171176
fork_name,
172177
spec,
178+
disable_peer_scoring,
173179
inbound_rate_limiter,
174180
)
175181
.await;
@@ -246,7 +252,16 @@ pub async fn build_linear(
246252
let mut nodes = Vec::with_capacity(n);
247253
for _ in 0..n {
248254
nodes.push(
249-
build_libp2p_instance(rt.clone(), vec![], log.clone(), fork_name, spec, None).await,
255+
build_libp2p_instance(
256+
rt.clone(),
257+
vec![],
258+
log.clone(),
259+
fork_name,
260+
spec,
261+
false,
262+
None,
263+
)
264+
.await,
250265
);
251266
}
252267

0 commit comments

Comments
 (0)