diff --git a/Cargo.lock b/Cargo.lock index 8a7ba656af12..b1b50732f3fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5565,6 +5565,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "forwarded-header-value" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" +dependencies = [ + "nonempty", + "thiserror", +] + [[package]] name = "fraction" version = "0.13.1" @@ -9031,6 +9041,12 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + [[package]] name = "nonzero_ext" version = "0.3.0" @@ -17277,10 +17293,12 @@ dependencies = [ name = "sc-rpc-server" version = "11.0.0" dependencies = [ + "forwarded-header-value", "futures", "governor", "http", "hyper", + "ip_network", "jsonrpsee", "log", "serde_json", diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs index 11aa2e5b9f35..f2a612803861 100644 --- a/cumulus/test/service/src/lib.rs +++ b/cumulus/test/service/src/lib.rs @@ -824,6 +824,8 @@ pub fn node_config( rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs index 87fbc7c20f31..35156a3a9372 100644 --- a/polkadot/node/test/service/src/lib.rs +++ b/polkadot/node/test/service/src/lib.rs @@ -216,6 +216,8 @@ pub fn node_config( rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/prdoc/pr_3701.prdoc b/prdoc/pr_3701.prdoc new file mode 100644 index 000000000000..6f9fcc92ad30 --- /dev/null +++ b/prdoc/pr_3701.prdoc @@ -0,0 +1,11 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: add option to whitelist peers in rpc rate limiting + +doc: + - audience: Node Operator + description: | + This PR adds two new CLI options to disable rate limiting for certain ip addresses and whether to trust "proxy headers". + +crates: [ ] diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs index f60610873d8c..ef7ae4fdf263 100644 --- a/substrate/bin/node/cli/benches/block_production.rs +++ b/substrate/bin/node/cli/benches/block_production.rs @@ -86,6 +86,8 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs index 1906ae697e90..c4488415b983 100644 --- a/substrate/bin/node/cli/benches/transaction_pool.rs +++ b/substrate/bin/node/cli/benches/transaction_pool.rs @@ -82,6 +82,8 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs index 221c32affd5a..c1288b502c95 100644 --- a/substrate/client/cli/src/commands/run_cmd.rs +++ b/substrate/client/cli/src/commands/run_cmd.rs @@ -30,7 +30,9 @@ use crate::{ use clap::Parser; use regex::Regex; use sc_service::{ - config::{BasePath, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions}, + config::{ + BasePath, IpNetwork, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions, + }, ChainSpec, Role, }; use sc_telemetry::TelemetryEndpoints; @@ -94,6 +96,22 @@ pub struct RunCmd { #[arg(long)] pub rpc_rate_limit: Option, + /// Disable RPC rate limiting for certain ip addresses. + /// + /// Each IP address must be in CIDR notation such as `1.2.3.4/24`. + #[arg(long, num_args = 1..)] + pub rpc_rate_limit_whitelisted_ips: Vec, + + /// Trust proxy headers for disable rate limiting. + /// + /// By default the rpc server will not trust headers such `X-Real-IP`, `X-Forwarded-For` and + /// `Forwarded` and this option will make the rpc server to trust these headers. + /// + /// For instance this may be secure if the rpc server is behind a reverse proxy and that the + /// proxy always sets these headers. + #[arg(long)] + pub rpc_rate_limit_trust_proxy_headers: bool, + /// Set the maximum RPC request payload size for both HTTP and WS in megabytes. #[arg(long, default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB)] pub rpc_max_request_size: u32, @@ -439,6 +457,14 @@ impl CliConfiguration for RunCmd { Ok(self.rpc_rate_limit) } + fn rpc_rate_limit_whitelisted_ips(&self) -> Result> { + Ok(self.rpc_rate_limit_whitelisted_ips.clone()) + } + + fn rpc_rate_limit_trust_proxy_headers(&self) -> Result { + Ok(self.rpc_rate_limit_trust_proxy_headers) + } + fn transaction_pool(&self, is_dev: bool) -> Result { Ok(self.pool_config.transaction_pool(is_dev)) } diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index 70a4885e5eef..783c9313121f 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -26,7 +26,7 @@ use log::warn; use names::{Generator, Name}; use sc_service::{ config::{ - BasePath, Configuration, DatabaseSource, KeystoreConfig, NetworkConfiguration, + BasePath, Configuration, DatabaseSource, IpNetwork, KeystoreConfig, NetworkConfiguration, NodeKeyConfig, OffchainWorkerConfig, OutputFormat, PrometheusConfig, PruningMode, Role, RpcBatchRequestConfig, RpcMethods, TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod, @@ -349,6 +349,16 @@ pub trait CliConfiguration: Sized { Ok(None) } + /// RPC rate limit whitelisted ip addresses. + fn rpc_rate_limit_whitelisted_ips(&self) -> Result> { + Ok(vec![]) + } + + /// RPC rate limit trust proxy headers. + fn rpc_rate_limit_trust_proxy_headers(&self) -> Result { + Ok(false) + } + /// Get the prometheus configuration (`None` if disabled) /// /// By default this is `None`. @@ -523,6 +533,8 @@ pub trait CliConfiguration: Sized { rpc_message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?, rpc_batch_config: self.rpc_batch_config()?, rpc_rate_limit: self.rpc_rate_limit()?, + rpc_rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips()?, + rpc_rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers()?, prometheus_config: self .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, telemetry_endpoints, diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs index 4201a0f4062f..3bf276807840 100644 --- a/substrate/client/cli/src/runner.rs +++ b/substrate/client/cli/src/runner.rs @@ -273,6 +273,8 @@ mod tests { rpc_port: 9944, rpc_batch_config: sc_service::config::RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, diff --git a/substrate/client/rpc-servers/Cargo.toml b/substrate/client/rpc-servers/Cargo.toml index bc21b5b1582f..7837c852a1c9 100644 --- a/substrate/client/rpc-servers/Cargo.toml +++ b/substrate/client/rpc-servers/Cargo.toml @@ -16,14 +16,16 @@ workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +forwarded-header-value = "0.1.1" +futures = "0.3.30" +governor = "0.6.0" +http = "0.2.8" +hyper = "0.14.27" +ip_network = "0.4.1" jsonrpsee = { version = "0.22", features = ["server"] } log = { workspace = true, default-features = true } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } serde_json = { workspace = true, default-features = true } tokio = { version = "1.22.0", features = ["parking_lot"] } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus" } -tower-http = { version = "0.4.0", features = ["cors"] } tower = { version = "0.4.13", features = ["util"] } -http = "0.2.8" -hyper = "0.14.27" -futures = "0.3.30" -governor = "0.6.0" +tower-http = { version = "0.4.0", features = ["cors"] } diff --git a/substrate/client/rpc-servers/src/lib.rs b/substrate/client/rpc-servers/src/lib.rs index ad4b444c7ff4..ba1fcf5e3677 100644 --- a/substrate/client/rpc-servers/src/lib.rs +++ b/substrate/client/rpc-servers/src/lib.rs @@ -21,27 +21,28 @@ #![warn(missing_docs)] pub mod middleware; +pub mod utils; use std::{ convert::Infallible, error::Error as StdError, net::SocketAddr, num::NonZeroU32, time::Duration, }; -use http::header::HeaderValue; use hyper::{ server::conn::AddrStream, service::{make_service_fn, service_fn}, }; use jsonrpsee::{ server::{ - middleware::http::{HostFilterLayer, ProxyGetRequestLayer}, - stop_channel, ws, PingConfig, StopHandle, TowerServiceBuilder, + middleware::http::ProxyGetRequestLayer, stop_channel, ws, PingConfig, StopHandle, + TowerServiceBuilder, }, Methods, RpcModule, }; use tokio::net::TcpListener; use tower::Service; -use tower_http::cors::{AllowOrigin, CorsLayer}; +use utils::{build_rpc_api, format_cors, get_proxy_ip, host_filtering, try_into_cors}; +pub use ip_network::IpNetwork; pub use jsonrpsee::{ core::{ id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, @@ -85,6 +86,10 @@ pub struct Config<'a, M: Send + Sync + 'static> { pub batch_config: BatchRequestConfig, /// Rate limit calls per minute. pub rate_limit: Option, + /// Disable rate limit for certain ips. + pub rate_limit_whitelisted_ips: Vec, + /// Trust proxy headers for rate limiting. + pub rate_limit_trust_proxy_headers: bool, } #[derive(Debug, Clone)] @@ -117,11 +122,13 @@ where tokio_handle, rpc_api, rate_limit, + rate_limit_whitelisted_ips, + rate_limit_trust_proxy_headers, } = config; let std_listener = TcpListener::bind(addrs.as_slice()).await?.into_std()?; let local_addr = std_listener.local_addr().ok(); - let host_filter = hosts_filtering(cors.is_some(), local_addr); + let host_filter = host_filtering(cors.is_some(), local_addr); let http_middleware = tower::ServiceBuilder::new() .option_layer(host_filter) @@ -160,20 +167,39 @@ where stop_handle: stop_handle.clone(), }; - let make_service = make_service_fn(move |_conn: &AddrStream| { + let make_service = make_service_fn(move |addr: &AddrStream| { let cfg = cfg.clone(); + let rate_limit_whitelisted_ips = rate_limit_whitelisted_ips.clone(); + let ip = addr.remote_addr().ip(); async move { let cfg = cfg.clone(); + let rate_limit_whitelisted_ips = rate_limit_whitelisted_ips.clone(); Ok::<_, Infallible>(service_fn(move |req| { + let proxy_ip = + if rate_limit_trust_proxy_headers { get_proxy_ip(&req) } else { None }; + + let rate_limit_cfg = if rate_limit_whitelisted_ips + .iter() + .any(|ips| ips.contains(proxy_ip.unwrap_or(ip))) + { + log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is trusted, disabling rate-limit", proxy_ip); + None + } else { + if !rate_limit_whitelisted_ips.is_empty() { + log::debug!(target: "rpc", "ip={ip}, proxy_ip={:?} is not trusted, rate-limit enabled", proxy_ip); + } + rate_limit + }; + let PerConnection { service_builder, metrics, tokio_handle, stop_handle, methods } = cfg.clone(); let is_websocket = ws::is_upgrade_request(&req); let transport_label = if is_websocket { "ws" } else { "http" }; - let middleware_layer = match (metrics, rate_limit) { + let middleware_layer = match (metrics, rate_limit_cfg) { (None, None) => None, (Some(metrics), None) => Some( MiddlewareLayer::new().with_metrics(Metrics::new(metrics, transport_label)), @@ -227,57 +253,3 @@ where Ok(server_handle) } - -fn hosts_filtering(enabled: bool, addr: Option) -> Option { - // If the local_addr failed, fallback to wildcard. - let port = addr.map_or("*".to_string(), |p| p.port().to_string()); - - if enabled { - // NOTE: The listening addresses are whitelisted by default. - let hosts = - [format!("localhost:{port}"), format!("127.0.0.1:{port}"), format!("[::1]:{port}")]; - Some(HostFilterLayer::new(hosts).expect("Valid hosts; qed")) - } else { - None - } -} - -fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModule { - let mut available_methods = rpc_api.method_names().collect::>(); - // The "rpc_methods" is defined below and we want it to be part of the reported methods. - available_methods.push("rpc_methods"); - available_methods.sort(); - - rpc_api - .register_method("rpc_methods", move |_, _| { - serde_json::json!({ - "methods": available_methods, - }) - }) - .expect("infallible all other methods have their own address space; qed"); - - rpc_api -} - -fn try_into_cors( - maybe_cors: Option<&Vec>, -) -> Result> { - if let Some(cors) = maybe_cors { - let mut list = Vec::new(); - for origin in cors { - list.push(HeaderValue::from_str(origin)?); - } - Ok(CorsLayer::new().allow_origin(AllowOrigin::list(list))) - } else { - // allow all cors - Ok(CorsLayer::permissive()) - } -} - -fn format_cors(maybe_cors: Option<&Vec>) -> String { - if let Some(cors) = maybe_cors { - format!("{:?}", cors) - } else { - format!("{:?}", ["*"]) - } -} diff --git a/substrate/client/rpc-servers/src/utils.rs b/substrate/client/rpc-servers/src/utils.rs new file mode 100644 index 000000000000..d99b8e637d9d --- /dev/null +++ b/substrate/client/rpc-servers/src/utils.rs @@ -0,0 +1,189 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Substrate RPC server utils. + +use std::{ + error::Error as StdError, + net::{IpAddr, SocketAddr}, + str::FromStr, +}; + +use forwarded_header_value::ForwardedHeaderValue; +use hyper::{ + header::{HeaderName, HeaderValue}, + Request, +}; +use jsonrpsee::{server::middleware::http::HostFilterLayer, RpcModule}; +use tower_http::cors::{AllowOrigin, CorsLayer}; + +const X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); +const X_REAL_IP: HeaderName = HeaderName::from_static("x-real-ip"); +const FORWARDED: HeaderName = HeaderName::from_static("forwarded"); + +pub(crate) fn host_filtering(enabled: bool, addr: Option) -> Option { + // If the local_addr failed, fallback to wildcard. + let port = addr.map_or("*".to_string(), |p| p.port().to_string()); + + if enabled { + // NOTE: The listening addresses are whitelisted by default. + let hosts = + [format!("localhost:{port}"), format!("127.0.0.1:{port}"), format!("[::1]:{port}")]; + Some(HostFilterLayer::new(hosts).expect("Valid hosts; qed")) + } else { + None + } +} + +pub(crate) fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModule { + let mut available_methods = rpc_api.method_names().collect::>(); + // The "rpc_methods" is defined below and we want it to be part of the reported methods. + available_methods.push("rpc_methods"); + available_methods.sort(); + + rpc_api + .register_method("rpc_methods", move |_, _| { + serde_json::json!({ + "methods": available_methods, + }) + }) + .expect("infallible all other methods have their own address space; qed"); + + rpc_api +} + +pub(crate) fn try_into_cors( + maybe_cors: Option<&Vec>, +) -> Result> { + if let Some(cors) = maybe_cors { + let mut list = Vec::new(); + for origin in cors { + list.push(HeaderValue::from_str(origin)?); + } + Ok(CorsLayer::new().allow_origin(AllowOrigin::list(list))) + } else { + // allow all cors + Ok(CorsLayer::permissive()) + } +} + +pub(crate) fn format_cors(maybe_cors: Option<&Vec>) -> String { + if let Some(cors) = maybe_cors { + format!("{:?}", cors) + } else { + format!("{:?}", ["*"]) + } +} + +/// Extracts the IP addr from the HTTP request. +/// +/// It is extracted in the following order: +/// 1. `Forwarded` header. +/// 2. `X-Forwarded-For` header. +/// 3. `X-Real-Ip`. +pub(crate) fn get_proxy_ip(req: &Request) -> Option { + if let Some(ip) = req + .headers() + .get(&FORWARDED) + .and_then(|v| v.to_str().ok()) + .and_then(|v| ForwardedHeaderValue::from_forwarded(v).ok()) + .and_then(|v| v.remotest_forwarded_for_ip()) + { + return Some(ip); + } + + if let Some(ip) = req + .headers() + .get(&X_FORWARDED_FOR) + .and_then(|v| v.to_str().ok()) + .and_then(|v| ForwardedHeaderValue::from_x_forwarded_for(v).ok()) + .and_then(|v| v.remotest_forwarded_for_ip()) + { + return Some(ip); + } + + if let Some(ip) = req + .headers() + .get(&X_REAL_IP) + .and_then(|v| v.to_str().ok()) + .and_then(|v| IpAddr::from_str(v).ok()) + { + return Some(ip); + } + + None +} + +#[cfg(test)] +mod tests { + use super::*; + use hyper::header::HeaderValue; + + fn request() -> hyper::Request { + hyper::Request::builder().body(hyper::Body::empty()).unwrap() + } + + #[test] + fn empty_works() { + let req = request(); + let host = get_proxy_ip(&req); + assert!(host.is_none()) + } + + #[test] + fn host_from_x_real_ip() { + let mut req = request(); + + req.headers_mut().insert(&X_REAL_IP, HeaderValue::from_static("127.0.0.1")); + let ip = get_proxy_ip(&req); + assert_eq!(Some(IpAddr::from_str("127.0.0.1").unwrap()), ip); + } + + #[test] + fn ip_from_forwarded_works() { + let mut req = request(); + + req.headers_mut().insert( + &FORWARDED, + HeaderValue::from_static("for=192.0.2.60;proto=http;by=203.0.113.43;host=example.com"), + ); + let ip = get_proxy_ip(&req); + assert_eq!(Some(IpAddr::from_str("192.0.2.60").unwrap()), ip); + } + + #[test] + fn ip_from_forwarded_multiple() { + let mut req = request(); + + req.headers_mut().append(&FORWARDED, HeaderValue::from_static("for=127.0.0.1")); + req.headers_mut().append(&FORWARDED, HeaderValue::from_static("for=192.0.2.60")); + req.headers_mut().append(&FORWARDED, HeaderValue::from_static("for=192.0.2.61")); + let ip = get_proxy_ip(&req); + assert_eq!(Some(IpAddr::from_str("127.0.0.1").unwrap()), ip); + } + + #[test] + fn ip_from_x_forwarded_works() { + let mut req = request(); + + req.headers_mut() + .insert(&X_FORWARDED_FOR, HeaderValue::from_static("127.0.0.1,192.0.2.60,0.0.0.1")); + let ip = get_proxy_ip(&req); + assert_eq!(Some(IpAddr::from_str("127.0.0.1").unwrap()), ip); + } +} diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs index 59e307d7f93b..187e18aa3cac 100644 --- a/substrate/client/service/src/config.rs +++ b/substrate/client/service/src/config.rs @@ -34,6 +34,7 @@ pub use sc_network::{ }, Multiaddr, }; +pub use sc_rpc_server::IpNetwork; pub use sc_telemetry::TelemetryEndpoints; pub use sc_transaction_pool::Options as TransactionPoolOptions; use sp_core::crypto::SecretString; @@ -108,6 +109,10 @@ pub struct Configuration { pub rpc_batch_config: RpcBatchRequestConfig, /// RPC rate limit per minute. pub rpc_rate_limit: Option, + /// RPC rate limit whitelisted ip addresses. + pub rpc_rate_limit_whitelisted_ips: Vec, + /// RPC rate limit trust proxy headers. + pub rpc_rate_limit_trust_proxy_headers: bool, /// Prometheus endpoint configuration. `None` if disabled. pub prometheus_config: Option, /// Telemetry service URL. `None` if disabled. diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs index d0f315c30c89..444cb4a06eb9 100644 --- a/substrate/client/service/src/lib.rs +++ b/substrate/client/service/src/lib.rs @@ -407,6 +407,8 @@ where cors: config.rpc_cors.as_ref(), tokio_handle: config.tokio_handle.clone(), rate_limit: config.rpc_rate_limit, + rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(), + rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers, }; // TODO: https://github.com/paritytech/substrate/issues/13773 diff --git a/substrate/client/service/test/src/lib.rs b/substrate/client/service/test/src/lib.rs index b9abd8446f7d..f19b5a19739e 100644 --- a/substrate/client/service/test/src/lib.rs +++ b/substrate/client/service/test/src/lib.rs @@ -252,6 +252,8 @@ fn node_config< rpc_message_buffer_capacity: Default::default(), rpc_batch_config: RpcBatchRequestConfig::Unlimited, rpc_rate_limit: None, + rpc_rate_limit_whitelisted_ips: Default::default(), + rpc_rate_limit_trust_proxy_headers: Default::default(), prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None,