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
+19 -21
View File
@@ -23,15 +23,14 @@ mod tests;
use std::sync::Arc;
use crate::SubscriptionTaskExecutor;
use crate::{
utils::{pipe_from_stream, spawn_subscription_task},
SubscriptionTaskExecutor,
};
use codec::{Decode, Encode};
use futures::{FutureExt, TryFutureExt};
use jsonrpsee::{
core::{async_trait, Error as JsonRpseeError, RpcResult},
types::SubscriptionResult,
SubscriptionSink,
};
use futures::TryFutureExt;
use jsonrpsee::{core::async_trait, types::ErrorObject, PendingSubscriptionSink};
use sc_rpc_api::DenyUnsafe;
use sc_transaction_pool_api::{
error::IntoPoolError, BlockHash, InPoolTransaction, TransactionFor, TransactionPool,
@@ -91,7 +90,7 @@ where
P::Hash: Unpin,
<P::Block as BlockT>::Hash: Unpin,
{
async fn submit_extrinsic(&self, ext: Bytes) -> RpcResult<TxHash<P>> {
async fn submit_extrinsic(&self, ext: Bytes) -> Result<TxHash<P>> {
let xt = match Decode::decode(&mut &ext[..]) {
Ok(xt) => xt,
Err(err) => return Err(Error::Client(Box::new(err)).into()),
@@ -105,7 +104,7 @@ where
})
}
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> RpcResult<()> {
fn insert_key(&self, key_type: String, suri: String, public: Bytes) -> Result<()> {
self.deny_unsafe.check_if_safe()?;
let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?;
@@ -115,7 +114,7 @@ where
Ok(())
}
fn rotate_keys(&self) -> RpcResult<Bytes> {
fn rotate_keys(&self) -> Result<Bytes> {
self.deny_unsafe.check_if_safe()?;
let best_block_hash = self.client.info().best_hash;
@@ -129,7 +128,7 @@ where
.map_err(|api_err| Error::Client(Box::new(api_err)).into())
}
fn has_session_keys(&self, session_keys: Bytes) -> RpcResult<bool> {
fn has_session_keys(&self, session_keys: Bytes) -> Result<bool> {
self.deny_unsafe.check_if_safe()?;
let best_block_hash = self.client.info().best_hash;
@@ -143,21 +142,21 @@ where
Ok(self.keystore.has_keys(&keys))
}
fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult<bool> {
fn has_key(&self, public_key: Bytes, key_type: String) -> Result<bool> {
self.deny_unsafe.check_if_safe()?;
let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?;
Ok(self.keystore.has_keys(&[(public_key.to_vec(), key_type)]))
}
fn pending_extrinsics(&self) -> RpcResult<Vec<Bytes>> {
fn pending_extrinsics(&self) -> Result<Vec<Bytes>> {
Ok(self.pool.ready().map(|tx| tx.data().encode().into()).collect())
}
fn remove_extrinsic(
&self,
bytes_or_hash: Vec<hash::ExtrinsicOrHash<TxHash<P>>>,
) -> RpcResult<Vec<TxHash<P>>> {
) -> Result<Vec<TxHash<P>>> {
self.deny_unsafe.check_if_safe()?;
let hashes = bytes_or_hash
.into_iter()
@@ -178,13 +177,13 @@ where
.collect())
}
fn watch_extrinsic(&self, mut sink: SubscriptionSink, xt: Bytes) -> SubscriptionResult {
fn watch_extrinsic(&self, pending: PendingSubscriptionSink, xt: Bytes) {
let best_block_hash = self.client.info().best_hash;
let dxt = match TransactionFor::<P>::decode(&mut &xt[..]).map_err(|e| Error::from(e)) {
Ok(dxt) => dxt,
Err(e) => {
let _ = sink.reject(JsonRpseeError::from(e));
return Ok(())
spawn_subscription_task(&self.executor, pending.reject(e));
return
},
};
@@ -198,15 +197,14 @@ where
let stream = match submit.await {
Ok(stream) => stream,
Err(err) => {
let _ = sink.reject(JsonRpseeError::from(err));
let _ = pending.reject(ErrorObject::from(err)).await;
return
},
};
sink.pipe_from_stream(stream).await;
pipe_from_stream(pending, stream).await;
};
self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed());
Ok(())
spawn_subscription_task(&self.executor, fut);
}
}
+6 -7
View File
@@ -22,8 +22,7 @@ use crate::testing::{test_executor, timeout_secs};
use assert_matches::assert_matches;
use codec::Encode;
use jsonrpsee::{
core::Error as RpcError,
types::{error::CallError, EmptyServerParams as EmptyParams},
core::{EmptyServerParams as EmptyParams, Error as RpcError},
RpcModule,
};
use sc_transaction_pool::{BasicPool, FullChainApi};
@@ -104,7 +103,7 @@ async fn author_submit_transaction_should_not_cause_error() {
assert_matches!(
api.call::<_, H256>("author_submitExtrinsic", [xt]).await,
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Already Imported") && err.code() == 1013
Err(RpcError::Call(err)) if err.message().contains("Already Imported") && err.code() == 1013
);
}
@@ -119,7 +118,7 @@ async fn author_should_watch_extrinsic() {
true,
);
let mut sub = api.subscribe("author_submitAndWatchExtrinsic", [xt]).await.unwrap();
let mut sub = api.subscribe_unbounded("author_submitAndWatchExtrinsic", [xt]).await.unwrap();
let (tx, sub_id) = timeout_secs(10, sub.next::<TransactionStatus<H256, Block>>())
.await
.unwrap()
@@ -157,11 +156,11 @@ async fn author_should_return_watch_validation_error() {
let invalid_xt = ExtrinsicBuilder::new_fill_block(Perbill::from_percent(100)).build();
let api = TestSetup::into_rpc();
let failed_sub = api.subscribe(METHOD, [to_hex(&invalid_xt.encode(), true)]).await;
let failed_sub = api.subscribe_unbounded(METHOD, [to_hex(&invalid_xt.encode(), true)]).await;
assert_matches!(
failed_sub,
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Invalid Transaction") && err.code() == 1010
Err(RpcError::Call(err)) if err.message().contains("Invalid Transaction") && err.code() == 1010
);
}
@@ -277,7 +276,7 @@ async fn author_has_session_keys() {
assert_matches!(
api.call::<_, bool>("author_hasSessionKeys", vec![Bytes::from(vec![1, 2, 3])]).await,
Err(RpcError::Call(CallError::Custom(err))) if err.message().contains("Session keys are not encoded correctly")
Err(RpcError::Call(err)) if err.message().contains("Session keys are not encoded correctly")
);
}