mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 04:01:10 +00:00
rpc: backpressured RPC server (bump jsonrpsee 0.20) (#1313)
This is a rather big change in jsonrpsee, the major things in this bump are: - Server backpressure (the subscription impls are modified to deal with that) - Allow custom error types / return types (remove jsonrpsee::core::Error and jsonrpee::core::CallError) - Bug fixes (graceful shutdown in particular not used by substrate anyway) - Less dependencies for the clients in particular - Return type requires Clone in method call responses - Moved to tokio channels - Async subscription API (not used in this PR) Major changes in this PR: - The subscriptions are now bounded and if subscription can't keep up with the server it is dropped - CLI: add parameter to configure the jsonrpc server bounded message buffer (default is 64) - Add our own subscription helper to deal with the unbounded streams in substrate The most important things in this PR to review is the added helpers functions in `substrate/client/rpc/src/utils.rs` and the rest is pretty much chore. Regarding the "bounded buffer limit" it may cause the server to handle the JSON-RPC calls slower than before. The message size limit is bounded by "--rpc-response-size" thus "by default 10MB * 64 = 640MB" but the subscription message size is not covered by this limit and could be capped as well. Hopefully the last release prior to 1.0, sorry in advance for a big PR Previous attempt: https://github.com/paritytech/substrate/pull/13992 Resolves https://github.com/paritytech/polkadot-sdk/issues/748, resolves https://github.com/paritytech/polkadot-sdk/issues/627
This commit is contained in:
@@ -22,15 +22,14 @@
|
||||
|
||||
pub mod middleware;
|
||||
|
||||
use std::{error::Error as StdError, net::SocketAddr, time::Duration};
|
||||
|
||||
use http::header::HeaderValue;
|
||||
use jsonrpsee::{
|
||||
server::{
|
||||
middleware::proxy_get_request::ProxyGetRequestLayer, AllowHosts, ServerBuilder,
|
||||
ServerHandle,
|
||||
},
|
||||
server::middleware::{HostFilterLayer, ProxyGetRequestLayer},
|
||||
RpcModule,
|
||||
};
|
||||
use std::{error::Error as StdError, net::SocketAddr};
|
||||
use tokio::net::TcpListener;
|
||||
use tower_http::cors::{AllowOrigin, CorsLayer};
|
||||
|
||||
pub use crate::middleware::RpcMetrics;
|
||||
@@ -42,7 +41,7 @@ pub use jsonrpsee::core::{
|
||||
const MEGABYTE: u32 = 1024 * 1024;
|
||||
|
||||
/// Type alias for the JSON-RPC server.
|
||||
pub type Server = ServerHandle;
|
||||
pub type Server = jsonrpsee::server::ServerHandle;
|
||||
|
||||
/// RPC server configuration.
|
||||
#[derive(Debug)]
|
||||
@@ -61,6 +60,8 @@ pub struct Config<'a, M: Send + Sync + 'static> {
|
||||
pub max_payload_out_mb: u32,
|
||||
/// Metrics.
|
||||
pub metrics: Option<RpcMetrics>,
|
||||
/// Message buffer size
|
||||
pub message_buffer_capacity: u32,
|
||||
/// RPC API.
|
||||
pub rpc_api: RpcModule<M>,
|
||||
/// Subscription ID provider.
|
||||
@@ -72,7 +73,7 @@ pub struct Config<'a, M: Send + Sync + 'static> {
|
||||
/// Start RPC server listening on given address.
|
||||
pub async fn start_server<M: Send + Sync + 'static>(
|
||||
config: Config<'_, M>,
|
||||
) -> Result<ServerHandle, Box<dyn StdError + Send + Sync>> {
|
||||
) -> Result<Server, Box<dyn StdError + Send + Sync>> {
|
||||
let Config {
|
||||
addrs,
|
||||
cors,
|
||||
@@ -81,26 +82,30 @@ pub async fn start_server<M: Send + Sync + 'static>(
|
||||
max_connections,
|
||||
max_subs_per_conn,
|
||||
metrics,
|
||||
message_buffer_capacity,
|
||||
id_provider,
|
||||
tokio_handle,
|
||||
rpc_api,
|
||||
} = config;
|
||||
|
||||
let host_filter = hosts_filtering(cors.is_some(), &addrs);
|
||||
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 middleware = tower::ServiceBuilder::new()
|
||||
.option_layer(host_filter)
|
||||
// Proxy `GET /health` requests to internal `system_health` method.
|
||||
.layer(ProxyGetRequestLayer::new("/health", "system_health")?)
|
||||
.layer(try_into_cors(cors)?);
|
||||
|
||||
let mut builder = ServerBuilder::new()
|
||||
let mut builder = jsonrpsee::server::Server::builder()
|
||||
.max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE))
|
||||
.max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE))
|
||||
.max_connections(max_connections)
|
||||
.max_subscriptions_per_connection(max_subs_per_conn)
|
||||
.ping_interval(std::time::Duration::from_secs(30))
|
||||
.set_host_filtering(host_filter)
|
||||
.ping_interval(Duration::from_secs(30))
|
||||
.set_middleware(middleware)
|
||||
.set_message_buffer_capacity(message_buffer_capacity)
|
||||
.custom_tokio_runtime(tokio_handle);
|
||||
|
||||
if let Some(provider) = id_provider {
|
||||
@@ -110,36 +115,34 @@ pub async fn start_server<M: Send + Sync + 'static>(
|
||||
};
|
||||
|
||||
let rpc_api = build_rpc_api(rpc_api);
|
||||
let (handle, addr) = if let Some(metrics) = metrics {
|
||||
let server = builder.set_logger(metrics).build(&addrs[..]).await?;
|
||||
let addr = server.local_addr();
|
||||
(server.start(rpc_api)?, addr)
|
||||
let handle = if let Some(metrics) = metrics {
|
||||
let server = builder.set_logger(metrics).build_from_tcp(std_listener)?;
|
||||
server.start(rpc_api)
|
||||
} else {
|
||||
let server = builder.build(&addrs[..]).await?;
|
||||
let addr = server.local_addr();
|
||||
(server.start(rpc_api)?, addr)
|
||||
let server = builder.build_from_tcp(std_listener)?;
|
||||
server.start(rpc_api)
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"Running JSON-RPC server: addr={}, allowed origins={}",
|
||||
addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()),
|
||||
local_addr.map_or_else(|| "unknown".to_string(), |a| a.to_string()),
|
||||
format_cors(cors)
|
||||
);
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
fn hosts_filtering(enabled: bool, addrs: &[SocketAddr]) -> AllowHosts {
|
||||
fn hosts_filtering(enabled: bool, addr: Option<SocketAddr>) -> Option<HostFilterLayer> {
|
||||
// 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 mut hosts = Vec::with_capacity(addrs.len() * 2);
|
||||
for addr in addrs {
|
||||
hosts.push(format!("localhost:{}", addr.port()).into());
|
||||
hosts.push(format!("127.0.0.1:{}", addr.port()).into());
|
||||
}
|
||||
AllowHosts::Only(hosts)
|
||||
// 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 {
|
||||
AllowHosts::Any
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,9 +154,9 @@ fn build_rpc_api<M: Send + Sync + 'static>(mut rpc_api: RpcModule<M>) -> RpcModu
|
||||
|
||||
rpc_api
|
||||
.register_method("rpc_methods", move |_, _| {
|
||||
Ok(serde_json::json!({
|
||||
serde_json::json!({
|
||||
"methods": available_methods,
|
||||
}))
|
||||
})
|
||||
})
|
||||
.expect("infallible all other methods have their own address space; qed");
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
|
||||
//! RPC middleware to collect prometheus metrics on RPC calls.
|
||||
|
||||
use jsonrpsee::server::logger::{HttpRequest, Logger, MethodKind, Params, TransportProtocol};
|
||||
use jsonrpsee::server::logger::{
|
||||
HttpRequest, Logger, MethodKind, Params, SuccessOrError, TransportProtocol,
|
||||
};
|
||||
use prometheus_endpoint::{
|
||||
register, Counter, CounterVec, HistogramOpts, HistogramVec, Opts, PrometheusError, Registry,
|
||||
U64,
|
||||
@@ -176,7 +178,7 @@ impl Logger for RpcMetrics {
|
||||
fn on_result(
|
||||
&self,
|
||||
name: &str,
|
||||
success: bool,
|
||||
success_or_error: SuccessOrError,
|
||||
started_at: Self::Instant,
|
||||
transport: TransportProtocol,
|
||||
) {
|
||||
@@ -197,7 +199,7 @@ impl Logger for RpcMetrics {
|
||||
name,
|
||||
// the label "is_error", so `success` should be regarded as false
|
||||
// and vice-versa to be registrered correctly.
|
||||
if success { "false" } else { "true" },
|
||||
if success_or_error.is_success() { "false" } else { "true" },
|
||||
])
|
||||
.inc();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user