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:
Niklas Adolfsson
2024-01-23 09:55:13 +01:00
committed by GitHub
parent 76c37c930b
commit e16ef0861f
117 changed files with 1245 additions and 1090 deletions
+15 -15
View File
@@ -19,14 +19,17 @@
//! Blockchain API backend for full nodes.
use super::{client_err, ChainBackend, Error};
use crate::SubscriptionTaskExecutor;
use crate::{
utils::{pipe_from_stream, spawn_subscription_task},
SubscriptionTaskExecutor,
};
use std::{marker::PhantomData, sync::Arc};
use futures::{
future::{self, FutureExt},
future::{self},
stream::{self, Stream, StreamExt},
};
use jsonrpsee::SubscriptionSink;
use jsonrpsee::{core::async_trait, PendingSubscriptionSink};
use sc_client_api::{BlockBackend, BlockchainEvents};
use sp_blockchain::HeaderBackend;
use sp_runtime::{generic::SignedBlock, traits::Block as BlockT};
@@ -48,6 +51,7 @@ impl<Block: BlockT, Client> FullChain<Block, Client> {
}
}
#[async_trait]
impl<Block, Client> ChainBackend<Client, Block> for FullChain<Block, Client>
where
Block: BlockT + 'static,
@@ -66,11 +70,11 @@ where
self.client.block(self.unwrap_or_best(hash)).map_err(client_err)
}
fn subscribe_all_heads(&self, sink: SubscriptionSink) {
fn subscribe_all_heads(&self, pending: PendingSubscriptionSink) {
subscribe_headers(
&self.client,
&self.executor,
sink,
pending,
|| self.client().info().best_hash,
|| {
self.client()
@@ -80,11 +84,11 @@ where
)
}
fn subscribe_new_heads(&self, sink: SubscriptionSink) {
fn subscribe_new_heads(&self, pending: PendingSubscriptionSink) {
subscribe_headers(
&self.client,
&self.executor,
sink,
pending,
|| self.client().info().best_hash,
|| {
self.client()
@@ -95,11 +99,11 @@ where
)
}
fn subscribe_finalized_heads(&self, sink: SubscriptionSink) {
fn subscribe_finalized_heads(&self, pending: PendingSubscriptionSink) {
subscribe_headers(
&self.client,
&self.executor,
sink,
pending,
|| self.client().info().finalized_hash,
|| {
self.client()
@@ -114,7 +118,7 @@ where
fn subscribe_headers<Block, Client, F, G, S>(
client: &Arc<Client>,
executor: &SubscriptionTaskExecutor,
mut sink: SubscriptionSink,
pending: PendingSubscriptionSink,
best_block_hash: G,
stream: F,
) where
@@ -139,9 +143,5 @@ fn subscribe_headers<Block, Client, F, G, S>(
// duplicates at the beginning of the stream though.
let stream = stream::iter(maybe_header).chain(stream());
let fut = async move {
sink.pipe_from_stream(stream).await;
};
executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
spawn_subscription_task(executor, pipe_from_stream(pending, stream));
}
+20 -21
View File
@@ -27,7 +27,7 @@ use std::sync::Arc;
use crate::SubscriptionTaskExecutor;
use jsonrpsee::{core::RpcResult, types::SubscriptionResult, SubscriptionSink};
use jsonrpsee::{core::async_trait, PendingSubscriptionSink};
use sc_client_api::BlockchainEvents;
use sp_rpc::{list::ListOrValue, number::NumberOrHex};
use sp_runtime::{
@@ -42,6 +42,7 @@ pub use sc_rpc_api::chain::*;
use sp_blockchain::HeaderBackend;
/// Blockchain backend API
#[async_trait]
trait ChainBackend<Client, Block: BlockT>: Send + Sync + 'static
where
Block: BlockT + 'static,
@@ -91,13 +92,13 @@ where
}
/// All new head subscription
fn subscribe_all_heads(&self, sink: SubscriptionSink);
fn subscribe_all_heads(&self, pending: PendingSubscriptionSink);
/// New best head subscription
fn subscribe_new_heads(&self, sink: SubscriptionSink);
fn subscribe_new_heads(&self, pending: PendingSubscriptionSink);
/// Finalized head subscription
fn subscribe_finalized_heads(&self, sink: SubscriptionSink);
fn subscribe_finalized_heads(&self, pending: PendingSubscriptionSink);
}
/// Create new state API that works on full node.
@@ -118,6 +119,7 @@ pub struct Chain<Block: BlockT, Client> {
backend: Box<dyn ChainBackend<Client, Block>>,
}
#[async_trait]
impl<Block, Client> ChainApiServer<NumberFor<Block>, Block::Hash, Block::Header, SignedBlock<Block>>
for Chain<Block, Client>
where
@@ -125,20 +127,20 @@ where
Block::Header: Unpin,
Client: HeaderBackend<Block> + BlockchainEvents<Block> + 'static,
{
fn header(&self, hash: Option<Block::Hash>) -> RpcResult<Option<Block::Header>> {
self.backend.header(hash).map_err(Into::into)
fn header(&self, hash: Option<Block::Hash>) -> Result<Option<Block::Header>, Error> {
self.backend.header(hash)
}
fn block(&self, hash: Option<Block::Hash>) -> RpcResult<Option<SignedBlock<Block>>> {
self.backend.block(hash).map_err(Into::into)
fn block(&self, hash: Option<Block::Hash>) -> Result<Option<SignedBlock<Block>>, Error> {
self.backend.block(hash)
}
fn block_hash(
&self,
number: Option<ListOrValue<NumberOrHex>>,
) -> RpcResult<ListOrValue<Option<Block::Hash>>> {
) -> Result<ListOrValue<Option<Block::Hash>>, Error> {
match number {
None => self.backend.block_hash(None).map(ListOrValue::Value).map_err(Into::into),
None => self.backend.block_hash(None).map(ListOrValue::Value),
Some(ListOrValue::Value(number)) => self
.backend
.block_hash(Some(number))
@@ -152,23 +154,20 @@ where
}
}
fn finalized_head(&self) -> RpcResult<Block::Hash> {
self.backend.finalized_head().map_err(Into::into)
fn finalized_head(&self) -> Result<Block::Hash, Error> {
self.backend.finalized_head()
}
fn subscribe_all_heads(&self, sink: SubscriptionSink) -> SubscriptionResult {
self.backend.subscribe_all_heads(sink);
Ok(())
fn subscribe_all_heads(&self, pending: PendingSubscriptionSink) {
self.backend.subscribe_all_heads(pending);
}
fn subscribe_new_heads(&self, sink: SubscriptionSink) -> SubscriptionResult {
self.backend.subscribe_new_heads(sink);
Ok(())
fn subscribe_new_heads(&self, pending: PendingSubscriptionSink) {
self.backend.subscribe_new_heads(pending)
}
fn subscribe_finalized_heads(&self, sink: SubscriptionSink) -> SubscriptionResult {
self.backend.subscribe_finalized_heads(sink);
Ok(())
fn subscribe_finalized_heads(&self, pending: PendingSubscriptionSink) {
self.backend.subscribe_finalized_heads(pending)
}
}
+2 -2
View File
@@ -19,7 +19,7 @@
use super::*;
use crate::testing::{test_executor, timeout_secs};
use assert_matches::assert_matches;
use jsonrpsee::types::EmptyServerParams as EmptyParams;
use jsonrpsee::core::EmptyServerParams as EmptyParams;
use sc_block_builder::BlockBuilderBuilder;
use sp_consensus::BlockOrigin;
use sp_rpc::list::ListOrValue;
@@ -252,7 +252,7 @@ async fn test_head_subscription(method: &str) {
let mut sub = {
let api = new_full(client.clone(), test_executor()).into_rpc();
let sub = api.subscribe(method, EmptyParams::new()).await.unwrap();
let sub = api.subscribe_unbounded(method, EmptyParams::new()).await.unwrap();
let block = BlockBuilderBuilder::new(&*client)
.on_parent_block(client.chain_info().best_hash)
.with_parent_block_number(client.chain_info().best_number)