mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-15 03:21:06 +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:
@@ -21,10 +21,7 @@ use super::*;
|
||||
use crate::testing::{test_executor, timeout_secs};
|
||||
use assert_matches::assert_matches;
|
||||
use futures::executor;
|
||||
use jsonrpsee::{
|
||||
core::Error as RpcError,
|
||||
types::{error::CallError as RpcCallError, EmptyServerParams as EmptyParams, ErrorObject},
|
||||
};
|
||||
use jsonrpsee::core::{EmptyServerParams as EmptyParams, Error as RpcError};
|
||||
use sc_block_builder::BlockBuilderBuilder;
|
||||
use sc_rpc_api::DenyUnsafe;
|
||||
use sp_consensus::BlockOrigin;
|
||||
@@ -42,6 +39,14 @@ fn prefixed_storage_key() -> PrefixedStorageKey {
|
||||
child_info.prefixed_storage_key()
|
||||
}
|
||||
|
||||
fn init_logger() {
|
||||
use tracing_subscriber::{EnvFilter, FmtSubscriber};
|
||||
|
||||
let _ = FmtSubscriber::builder()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.try_init();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_return_storage() {
|
||||
const KEY: &[u8] = b":mock";
|
||||
@@ -200,22 +205,25 @@ async fn should_call_contract() {
|
||||
let genesis_hash = client.genesis_hash();
|
||||
let (client, _child) = new_full(client, test_executor(), DenyUnsafe::No);
|
||||
|
||||
use jsonrpsee::{core::Error, types::error::CallError};
|
||||
|
||||
assert_matches!(
|
||||
client.call("balanceOf".into(), Bytes(vec![1, 2, 3]), Some(genesis_hash).into()),
|
||||
Err(Error::Call(CallError::Failed(_)))
|
||||
Err(Error::Client(_))
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_notify_about_storage_changes() {
|
||||
init_logger();
|
||||
|
||||
let mut sub = {
|
||||
let mut client = Arc::new(substrate_test_runtime_client::new());
|
||||
let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No);
|
||||
|
||||
let api_rpc = api.into_rpc();
|
||||
let sub = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await.unwrap();
|
||||
let sub = api_rpc
|
||||
.subscribe_unbounded("state_subscribeStorage", EmptyParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Cause a change:
|
||||
let mut builder = BlockBuilderBuilder::new(&*client)
|
||||
@@ -241,11 +249,12 @@ async fn should_notify_about_storage_changes() {
|
||||
// NOTE: previous versions of the subscription code used to return an empty value for the
|
||||
// "initial" storage change here
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(Some(_)));
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(None));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_send_initial_storage_changes_and_notifications() {
|
||||
init_logger();
|
||||
|
||||
let mut sub = {
|
||||
let mut client = Arc::new(substrate_test_runtime_client::new());
|
||||
let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No);
|
||||
@@ -263,7 +272,10 @@ async fn should_send_initial_storage_changes_and_notifications() {
|
||||
|
||||
let api_rpc = api.into_rpc();
|
||||
let sub = api_rpc
|
||||
.subscribe("state_subscribeStorage", [[StorageKey(alice_balance_key)]])
|
||||
.subscribe_unbounded(
|
||||
"state_subscribeStorage",
|
||||
[[StorageKey(alice_balance_key.to_vec())]],
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -288,9 +300,6 @@ async fn should_send_initial_storage_changes_and_notifications() {
|
||||
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(Some(_)));
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(Some(_)));
|
||||
|
||||
// No more messages to follow
|
||||
assert_matches!(timeout_secs(1, sub.next::<StorageChangeSet<H256>>()).await, Ok(None));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -393,108 +402,48 @@ async fn should_query_storage() {
|
||||
assert_eq!(result.unwrap(), expected);
|
||||
|
||||
// Inverted range.
|
||||
let result = api.query_storage(keys.clone(), block1_hash, Some(genesis_hash));
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("1 ({:?})", block1_hash),
|
||||
to: format!("0 ({:?})", genesis_hash),
|
||||
details: "from number > to number".to_owned(),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>,
|
||||
))))
|
||||
.map_err(|e| e.to_string())
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), block1_hash, Some(genesis_hash)),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("1 ({:?})", block1_hash) && to == format!("0 ({:?})", genesis_hash) && details == "from number > to number".to_owned()
|
||||
);
|
||||
|
||||
let random_hash1 = H256::random();
|
||||
let random_hash2 = H256::random();
|
||||
|
||||
// Invalid second hash.
|
||||
let result = api.query_storage(keys.clone(), genesis_hash, Some(random_hash1));
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("{:?}", genesis_hash),
|
||||
to: format!("{:?}", Some(random_hash1)),
|
||||
details: format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>,
|
||||
))))
|
||||
.map_err(|e| e.to_string())
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), genesis_hash, Some(random_hash1)),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", genesis_hash) && to == format!("{:?}", Some(random_hash1)) && details == format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
)
|
||||
);
|
||||
|
||||
// Invalid first hash with Some other hash.
|
||||
let result = api.query_storage(keys.clone(), random_hash1, Some(genesis_hash));
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("{:?}", random_hash1),
|
||||
to: format!("{:?}", Some(genesis_hash)),
|
||||
details: format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>,
|
||||
))))
|
||||
.map_err(|e| e.to_string()),
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), random_hash1, Some(genesis_hash)),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(genesis_hash)) && details == format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
)
|
||||
);
|
||||
|
||||
// Invalid first hash with None.
|
||||
let result = api.query_storage(keys.clone(), random_hash1, None);
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("{:?}", random_hash1),
|
||||
to: format!("{:?}", Some(block2_hash)), // Best block hash.
|
||||
details: format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>,
|
||||
))))
|
||||
.map_err(|e| e.to_string()),
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), random_hash1, None),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(block2_hash)) && details == format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
)
|
||||
);
|
||||
|
||||
// Both hashes invalid.
|
||||
let result = api.query_storage(keys.clone(), random_hash1, Some(random_hash2));
|
||||
|
||||
assert_eq!(
|
||||
result.map_err(|e| e.to_string()),
|
||||
Err(RpcError::Call(RpcCallError::Custom(ErrorObject::owned(
|
||||
4001,
|
||||
Error::InvalidBlockRange {
|
||||
from: format!("{:?}", random_hash1), // First hash not found.
|
||||
to: format!("{:?}", Some(random_hash2)),
|
||||
details: format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
),
|
||||
}
|
||||
.to_string(),
|
||||
None::<()>
|
||||
))))
|
||||
.map_err(|e| e.to_string()),
|
||||
assert_matches!(
|
||||
api.query_storage(keys.clone(), random_hash1, Some(random_hash2)),
|
||||
Err(Error::InvalidBlockRange { from, to, details }) if from == format!("{:?}", random_hash1) && to == format!("{:?}", Some(random_hash2)) && details == format!(
|
||||
"UnknownBlock: Header was not found in the database: {:?}",
|
||||
random_hash1
|
||||
)
|
||||
);
|
||||
|
||||
// single block range
|
||||
@@ -548,7 +497,7 @@ async fn should_notify_on_runtime_version_initially() {
|
||||
|
||||
let api_rpc = api.into_rpc();
|
||||
let sub = api_rpc
|
||||
.subscribe("state_subscribeRuntimeVersion", EmptyParams::new())
|
||||
.subscribe_unbounded("state_subscribeRuntimeVersion", EmptyParams::new())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -557,9 +506,6 @@ async fn should_notify_on_runtime_version_initially() {
|
||||
|
||||
// assert initial version sent.
|
||||
assert_matches!(timeout_secs(10, sub.next::<RuntimeVersion>()).await, Ok(Some(_)));
|
||||
|
||||
sub.close();
|
||||
assert_matches!(timeout_secs(10, sub.next::<RuntimeVersion>()).await, Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -572,12 +518,14 @@ fn should_deserialize_storage_key() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn wildcard_storage_subscriptions_are_rpc_unsafe() {
|
||||
init_logger();
|
||||
|
||||
let client = Arc::new(substrate_test_runtime_client::new());
|
||||
let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes);
|
||||
|
||||
let api_rpc = api.into_rpc();
|
||||
let err = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await;
|
||||
assert_matches!(err, Err(RpcError::Call(RpcCallError::Custom(e))) if e.message() == "RPC call is unsafe to be called externally");
|
||||
let err = api_rpc.subscribe_unbounded("state_subscribeStorage", EmptyParams::new()).await;
|
||||
assert_matches!(err, Err(RpcError::Call(e)) if e.message() == "RPC call is unsafe to be called externally");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -587,7 +535,7 @@ async fn concrete_storage_subscriptions_are_rpc_safe() {
|
||||
let api_rpc = api.into_rpc();
|
||||
|
||||
let key = StorageKey(STORAGE_KEY.to_vec());
|
||||
let sub = api_rpc.subscribe("state_subscribeStorage", [[key]]).await;
|
||||
let sub = api_rpc.subscribe_unbounded("state_subscribeStorage", [[key]]).await;
|
||||
|
||||
assert!(sub.is_ok());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user