diff --git a/client/cli/src/commands/run_cmd.rs b/client/cli/src/commands/run_cmd.rs index 3e5823ef733aa..285ffc9fdca16 100644 --- a/client/cli/src/commands/run_cmd.rs +++ b/client/cli/src/commands/run_cmd.rs @@ -42,12 +42,11 @@ pub struct RunCmd { /// The node will be started with the authority role and actively /// participate in any consensus task that it can (e.g. depending on /// availability of local keys). - #[structopt( - long = "validator" - )] + #[structopt(long)] pub validator: bool, - /// Disable GRANDPA voter when running in validator mode, otherwise disable the GRANDPA observer. + /// Disable GRANDPA voter when running in validator mode, otherwise disable the GRANDPA + /// observer. #[structopt(long)] pub no_grandpa: bool, @@ -57,8 +56,8 @@ pub struct RunCmd { /// Listen to all RPC interfaces. /// - /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC proxy - /// server to filter out dangerous methods. More details: + /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC + /// proxy server to filter out dangerous methods. More details: /// . /// Use `--unsafe-rpc-external` to suppress the warning if you understand the risks. #[structopt(long = "rpc-external")] @@ -74,8 +73,8 @@ pub struct RunCmd { /// /// - `Unsafe`: Exposes every RPC method. /// - `Safe`: Exposes only a safe subset of RPC methods, denying unsafe RPC methods. - /// - `Auto`: Acts as `Safe` if RPC is served externally, e.g. when `--{rpc,ws}-external` is passed, - /// otherwise acts as `Unsafe`. + /// - `Auto`: Acts as `Safe` if RPC is served externally, e.g. when `--{rpc,ws}-external` is + /// passed, otherwise acts as `Unsafe`. #[structopt( long, value_name = "METHOD SET", @@ -88,8 +87,9 @@ pub struct RunCmd { /// Listen to all Websocket interfaces. /// - /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC proxy - /// server to filter out dangerous methods. More details: . + /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC + /// proxy server to filter out dangerous methods. More details: + /// . /// Use `--unsafe-ws-external` to suppress the warning if you understand the risks. #[structopt(long = "ws-external")] pub ws_external: bool, @@ -100,6 +100,11 @@ pub struct RunCmd { #[structopt(long = "unsafe-ws-external")] pub unsafe_ws_external: bool, + /// Set the the maximum RPC payload size for both requests and responses (both http and ws), in + /// megabytes. Default is 15MiB. + #[structopt(long = "rpc-max-payload")] + pub rpc_max_payload: Option, + /// Listen to all Prometheus data source interfaces. /// /// Default is local. @@ -194,7 +199,8 @@ pub struct RunCmd { #[structopt(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])] pub bob: bool, - /// Shortcut for `--name Charlie --validator` with session keys for `Charlie` added to keystore. + /// Shortcut for `--name Charlie --validator` with session keys for `Charlie` added to + /// keystore. #[structopt(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])] pub charlie: bool, @@ -435,6 +441,10 @@ impl CliConfiguration for RunCmd { Ok(self.rpc_methods.into()) } + fn rpc_max_payload(&self) -> Result> { + Ok(self.rpc_max_payload) + } + fn transaction_pool(&self) -> Result { Ok(self.pool_config.transaction_pool()) } diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index 62afc849c09fb..8e435da253c04 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -372,6 +372,11 @@ pub trait CliConfiguration: Sized { Ok(Some(Vec::new())) } + /// Get maximum RPC payload. + fn rpc_max_payload(&self) -> Result> { + Ok(None) + } + /// Get the prometheus configuration (`None` if disabled) /// /// By default this is `None`. @@ -535,6 +540,7 @@ pub trait CliConfiguration: Sized { rpc_ws_max_connections: self.rpc_ws_max_connections()?, rpc_http_threads: self.rpc_http_threads()?, rpc_cors: self.rpc_cors(is_dev)?, + rpc_max_payload: self.rpc_max_payload()?, prometheus_config: self.prometheus_config(DCV::prometheus_listen_port())?, telemetry_endpoints, telemetry_external_transport: self.telemetry_external_transport()?, diff --git a/client/executor/src/native_executor.rs b/client/executor/src/native_executor.rs index c94088a155260..6fc34b6f1a322 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/native_executor.rs @@ -291,7 +291,7 @@ impl NativeExecutor { default_heap_pages: Option, max_runtime_instances: usize, ) -> Self { - let extended = D::ExtendHostFunctions::host_functions(); + let extended = D::ExtendHostFunctions::host_functions(); let mut host_functions = sp_io::SubstrateHostFunctions::host_functions() .into_iter() // filter out any host function overrides provided. diff --git a/client/rpc-servers/src/lib.rs b/client/rpc-servers/src/lib.rs index cb2704efc82ab..c93451e5cc678 100644 --- a/client/rpc-servers/src/lib.rs +++ b/client/rpc-servers/src/lib.rs @@ -27,8 +27,10 @@ use jsonrpc_core::{IoHandlerExtension, MetaIoHandler}; use log::error; use pubsub::PubSubMetadata; +const MEGABYTE: usize = 1024 * 1024; + /// Maximal payload accepted by RPC servers. -pub const MAX_PAYLOAD: usize = 15 * 1024 * 1024; +pub const RPC_MAX_PAYLOAD_DEFAULT: usize = 15 * MEGABYTE; /// Default maximum number of connections for WS RPC servers. const WS_MAX_CONNECTIONS: usize = 100; @@ -85,7 +87,10 @@ mod inner { thread_pool_size: Option, cors: Option<&Vec>, io: RpcHandler, + maybe_max_payload_mb: Option, ) -> io::Result { + let max_request_body_size = maybe_max_payload_mb.map(|mb| mb.saturating_mul(MEGABYTE)) + .unwrap_or(RPC_MAX_PAYLOAD_DEFAULT); http::ServerBuilder::new(io) .threads(thread_pool_size.unwrap_or(HTTP_THREADS)) .health_api(("/health", "system_health")) @@ -96,7 +101,7 @@ mod inner { http::RestApi::Unsecure }) .cors(map_cors::(cors)) - .max_request_body_size(MAX_PAYLOAD) + .max_request_body_size(max_request_body_size) .start_http(addr) } @@ -120,14 +125,19 @@ mod inner { /// Start WS server listening on given address. /// /// **Note**: Only available if `not(target_os = "unknown")`. - pub fn start_ws>> ( + pub fn start_ws< + M: pubsub::PubSubMetadata + From>, + >( addr: &std::net::SocketAddr, max_connections: Option, cors: Option<&Vec>, io: RpcHandler, + maybe_max_payload_mb: Option, ) -> io::Result { + let rpc_max_payload = maybe_max_payload_mb.map(|mb| mb.saturating_mul(MEGABYTE)) + .unwrap_or(RPC_MAX_PAYLOAD_DEFAULT); ws::ServerBuilder::with_meta_extractor(io, |context: &ws::RequestContext| context.sender().into()) - .max_payload(MAX_PAYLOAD) + .max_payload(rpc_max_payload) .max_connections(max_connections.unwrap_or(WS_MAX_CONNECTIONS)) .allowed_origins(map_cors(cors)) .allowed_hosts(hosts_filtering(cors.is_some())) diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index 803fc6797ee9a..ad9712a41db6b 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -182,6 +182,7 @@ pub fn new_full( client: Arc, subscriptions: SubscriptionManager, deny_unsafe: DenyUnsafe, + rpc_max_payload: Option, ) -> (State, ChildState) where Block: BlockT + 'static, @@ -193,9 +194,11 @@ pub fn new_full( Client::Api: Metadata, { let child_backend = Box::new( - self::state_full::FullState::new(client.clone(), subscriptions.clone()) + self::state_full::FullState::new( + client.clone(), subscriptions.clone(), rpc_max_payload + ) ); - let backend = Box::new(self::state_full::FullState::new(client, subscriptions)); + let backend = Box::new(self::state_full::FullState::new(client, subscriptions, rpc_max_payload)); (State { backend, deny_unsafe }, ChildState { backend: child_backend }) } diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index bea7ddfbb3b76..218cb35f0086e 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -67,7 +67,8 @@ struct QueryStorageRange { pub struct FullState { client: Arc, subscriptions: SubscriptionManager, - _phantom: PhantomData<(BE, Block)> + _phantom: PhantomData<(BE, Block)>, + rpc_max_payload: Option, } impl FullState @@ -78,8 +79,12 @@ impl FullState Block: BlockT + 'static, { /// Create new state API backend for full nodes. - pub fn new(client: Arc, subscriptions: SubscriptionManager) -> Self { - Self { client, subscriptions, _phantom: PhantomData } + pub fn new( + client: Arc, + subscriptions: SubscriptionManager, + rpc_max_payload: Option, + ) -> Self { + Self { client, subscriptions, _phantom: PhantomData, rpc_max_payload } } /// Returns given block hash or best block hash if None is passed. @@ -540,9 +545,15 @@ impl StateBackend for FullState, storage_keys: Option, ) -> FutureResult { + let block_executor = sc_tracing::block::BlockExecutor::new( + self.client.clone(), + block, + targets, + storage_keys, + self.rpc_max_payload, + ); Box::new(result( - sc_tracing::block::BlockExecutor::new(self.client.clone(), block, targets, storage_keys) - .trace_block() + block_executor.trace_block() .map_err(|e| invalid_block::(block, None, e.to_string())) )) } diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index cfc27c7bf525e..e413827552c9d 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -63,6 +63,7 @@ fn should_return_storage() { Arc::new(client), SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, + None, ); let key = StorageKey(KEY.to_vec()); @@ -105,6 +106,7 @@ fn should_return_child_storage() { client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, + None, ); let child_key = prefixed_storage_key(); let key = StorageKey(b"key".to_vec()); @@ -144,6 +146,7 @@ fn should_call_contract() { client, SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, + None, ); assert_matches!( @@ -162,6 +165,7 @@ fn should_notify_about_storage_changes() { client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, + None, ); api.subscribe_storage(Default::default(), subscriber, None.into()); @@ -200,6 +204,7 @@ fn should_send_initial_storage_changes_and_notifications() { client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, + None, ); let alice_balance_key = blake2_256(&runtime::system::balance_of_key(AccountKeyring::Alice.into())); @@ -242,6 +247,7 @@ fn should_query_storage() { client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, + None, ); let mut add_block = |nonce| { @@ -463,6 +469,7 @@ fn should_return_runtime_version() { client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, + None, ); let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ @@ -490,6 +497,7 @@ fn should_notify_on_runtime_version_initially() { client.clone(), SubscriptionManager::new(Arc::new(TaskExecutor)), DenyUnsafe::No, + None, ); api.subscribe_runtime_version(Default::default(), subscriber); diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index ebf600b12f020..ca22322798463 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -804,6 +804,7 @@ fn gen_handler( client.clone(), subscriptions.clone(), deny_unsafe, + config.rpc_max_payload, ); (chain, state, child_state) }; diff --git a/client/service/src/config.rs b/client/service/src/config.rs index f2c5f2c6ed407..c91cf0a4ef5c3 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -95,6 +95,8 @@ pub struct Configuration { pub rpc_cors: Option>, /// RPC methods to expose (by default only a safe subset or all of them). pub rpc_methods: RpcMethods, + /// Maximum payload of rpc request/responses. + pub rpc_max_payload: Option, /// Prometheus endpoint configuration. `None` if disabled. pub prometheus_config: Option, /// Telemetry service URL. `None` if disabled. diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 51ee0965ebcf4..afc1209280322 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -387,6 +387,7 @@ fn start_rpc_servers< deny_unsafe(&address, &config.rpc_methods), sc_rpc_server::RpcMiddleware::new(rpc_metrics.clone(), "http") ), + config.rpc_max_payload ), )?.map(|s| waiting::HttpServer(Some(s))), maybe_start_server( @@ -399,6 +400,7 @@ fn start_rpc_servers< deny_unsafe(&address, &config.rpc_methods), sc_rpc_server::RpcMiddleware::new(rpc_metrics.clone(), "ws") ), + config.rpc_max_payload ), )?.map(|s| waiting::WsServer(Some(s))), ))) diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index 3999b852ac74c..eb437b1aba0af 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -265,6 +265,7 @@ fn node_config = Result; @@ -174,6 +175,7 @@ pub struct BlockExecutor { block: Block::Hash, targets: Option, storage_keys: Option, + rpc_max_payload: usize, } impl BlockExecutor @@ -189,8 +191,11 @@ impl BlockExecutor block: Block::Hash, targets: Option, storage_keys: Option, + rpc_max_payload: Option, ) -> Self { - Self { client, block, targets, storage_keys } + let rpc_max_payload = rpc_max_payload.map(|mb| mb.saturating_mul(MEGABYTE)) + .unwrap_or(RPC_MAX_PAYLOAD_DEFAULT); + Self { client, block, targets, storage_keys, rpc_max_payload } } /// Execute block, record all spans and events belonging to `Self::targets` @@ -260,7 +265,7 @@ impl BlockExecutor tracing::debug!(target: "state_tracing", "Captured {} spans and {} events", spans.len(), events.len()); let approx_payload_size = BASE_PAYLOAD + events.len() * AVG_EVENT + spans.len() * AVG_SPAN; - let response = if approx_payload_size > MAX_PAYLOAD { + let response = if approx_payload_size > self.rpc_max_payload { TraceBlockResponse::TraceError(TraceError { error: "Payload likely exceeds max payload size of RPC server.".to_string() diff --git a/test-utils/test-runner/src/utils.rs b/test-utils/test-runner/src/utils.rs index 4f5390a7eb863..fae527ededf97 100644 --- a/test-utils/test-runner/src/utils.rs +++ b/test-utils/test-runner/src/utils.rs @@ -127,6 +127,7 @@ pub fn default_config(task_executor: TaskExecutor, mut chain_spec: Box