From da6541030f72da5a8c5a60c2ab2dd2c2c136b471 Mon Sep 17 00:00:00 2001
From: Nazar Mokrynskyi <nazar@mokrynskyi.com>
Date: Mon, 2 Sep 2024 16:57:07 +0300
Subject: [PATCH] Improve `sc-service` API (#5364)

This improves `sc-service` API by not requiring the whole
`&Configuration`, using specific configuration options instead.
`RpcConfiguration` was also extracted from `Configuration` to group all
RPC options together.

We don't use Substrate's CLI and would rather not use `Configuration`
either, but some key public functions require it even though they
ignored most of the fields anyway.

`RpcConfiguration` is very helpful not just for consolidation of the
fields, but also to finally make RPC optional for our use case, while
Substrate still runs RPC server on localhost even if listening address
is explicitly set to `None`, which is annoying (and I suspect there is a
reason for it, so didn't want to change the default just yet).

While this is a breaking change, most developers will not notice it if
they use higher-level APIs.

Fixes https://github.com/paritytech/polkadot-sdk/issues/2897

---------

Co-authored-by: Niklas Adolfsson <niklasadolfsson1@gmail.com>
---
 .../relay-chain-minimal-node/src/lib.rs       |   2 +-
 .../relay-chain-minimal-node/src/network.rs   |   2 +-
 .../polkadot-parachain-lib/src/cli.rs         |   3 +-
 .../polkadot-parachain-lib/src/common/spec.rs |  13 ++-
 cumulus/test/service/src/cli.rs               |   3 +-
 cumulus/test/service/src/lib.rs               |  77 +++++++------
 polkadot/node/service/src/lib.rs              |   9 +-
 polkadot/node/test/service/src/lib.rs         |  41 +++----
 prdoc/pr_5364.prdoc                           |  39 +++++++
 .../bin/node/cli/benches/block_production.rs  |  41 +++----
 .../bin/node/cli/benches/transaction_pool.rs  |  36 +++---
 substrate/bin/node/cli/src/service.rs         |   6 +-
 substrate/bin/node/inspect/src/command.rs     |   2 +-
 substrate/client/cli/src/commands/run_cmd.rs  |  12 +-
 substrate/client/cli/src/config.rs            |  68 +++++-------
 substrate/client/cli/src/lib.rs               |   6 +-
 .../client/cli/src/params/node_key_params.rs  |   1 -
 substrate/client/cli/src/runner.rs            |  40 +++----
 substrate/client/network/common/src/role.rs   |   2 +-
 substrate/client/service/src/builder.rs       |  97 ++++++++++------
 substrate/client/service/src/config.rs        | 105 +++++++++++-------
 substrate/client/service/src/lib.rs           |  64 ++++++-----
 substrate/client/service/src/metrics.rs       |  31 +++---
 substrate/client/service/test/src/lib.rs      |  40 +++----
 templates/minimal/node/src/service.rs         |   2 +-
 templates/parachain/node/src/command.rs       |  10 +-
 templates/parachain/node/src/service.rs       |   7 +-
 templates/solochain/node/src/service.rs       |   4 +-
 28 files changed, 436 insertions(+), 327 deletions(-)
 create mode 100644 prdoc/pr_5364.prdoc

diff --git a/cumulus/client/relay-chain-minimal-node/src/lib.rs b/cumulus/client/relay-chain-minimal-node/src/lib.rs
index 732a242e729..e65a78f16d7 100644
--- a/cumulus/client/relay-chain-minimal-node/src/lib.rs
+++ b/cumulus/client/relay-chain-minimal-node/src/lib.rs
@@ -175,7 +175,7 @@ async fn new_minimal_relay_chain<Block: BlockT, Network: NetworkBackend<RelayBlo
 	collator_pair: CollatorPair,
 	relay_chain_rpc_client: Arc<BlockChainRpcClient>,
 ) -> Result<NewMinimalNode, RelayChainError> {
-	let role = config.role.clone();
+	let role = config.role;
 	let mut net_config = sc_network::config::FullNetworkConfiguration::<_, _, Network>::new(
 		&config.network,
 		config.prometheus_config.as_ref().map(|cfg| cfg.registry.clone()),
diff --git a/cumulus/client/relay-chain-minimal-node/src/network.rs b/cumulus/client/relay-chain-minimal-node/src/network.rs
index 025ac7a81a2..afe83a2a12f 100644
--- a/cumulus/client/relay-chain-minimal-node/src/network.rs
+++ b/cumulus/client/relay-chain-minimal-node/src/network.rs
@@ -65,7 +65,7 @@ pub(crate) fn build_collator_network<Network: NetworkBackend<Block, Hash>>(
 	spawn_handle.spawn("peer-store", Some("networking"), peer_store.run());
 
 	let network_params = sc_network::config::Params::<Block, Hash, Network> {
-		role: config.role.clone(),
+		role: config.role,
 		executor: {
 			let spawn_handle = Clone::clone(&spawn_handle);
 			Box::new(move |fut| {
diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs
index 15d21235d1a..349dc01d8a4 100644
--- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs
+++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs
@@ -322,10 +322,9 @@ impl<Config: CliConfig> CliConfiguration<Self> for RelayChainCli<Config> {
 		_support_url: &String,
 		_impl_version: &String,
 		_logger_hook: F,
-		_config: &sc_service::Configuration,
 	) -> sc_cli::Result<()>
 	where
-		F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration),
+		F: FnOnce(&mut sc_cli::LoggerBuilder),
 	{
 		unreachable!("PolkadotCli is never initialized; qed");
 	}
diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs
index 55e042aed87..8e19cf304b0 100644
--- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs
+++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/common/spec.rs
@@ -127,14 +127,15 @@ pub(crate) trait NodeSpec {
 			})
 			.transpose()?;
 
-		let heap_pages = config.default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| {
-			HeapAllocStrategy::Static { extra_pages: h as _ }
-		});
+		let heap_pages =
+			config.executor.default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| {
+				HeapAllocStrategy::Static { extra_pages: h as _ }
+			});
 
 		let executor = sc_executor::WasmExecutor::<ParachainHostFunctions>::builder()
-			.with_execution_method(config.wasm_method)
-			.with_max_runtime_instances(config.max_runtime_instances)
-			.with_runtime_cache_size(config.runtime_cache_size)
+			.with_execution_method(config.executor.wasm_method)
+			.with_max_runtime_instances(config.executor.max_runtime_instances)
+			.with_runtime_cache_size(config.executor.runtime_cache_size)
 			.with_onchain_heap_alloc_strategy(heap_pages)
 			.with_offchain_heap_alloc_strategy(heap_pages)
 			.build();
diff --git a/cumulus/test/service/src/cli.rs b/cumulus/test/service/src/cli.rs
index 739c2d4bda1..220b0449f33 100644
--- a/cumulus/test/service/src/cli.rs
+++ b/cumulus/test/service/src/cli.rs
@@ -139,10 +139,9 @@ impl CliConfiguration<Self> for RelayChainCli {
 		_support_url: &String,
 		_impl_version: &String,
 		_logger_hook: F,
-		_config: &sc_service::Configuration,
 	) -> CliResult<()>
 	where
-		F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration),
+		F: FnOnce(&mut sc_cli::LoggerBuilder),
 	{
 		unreachable!("PolkadotCli is never initialized; qed");
 	}
diff --git a/cumulus/test/service/src/lib.rs b/cumulus/test/service/src/lib.rs
index bc0fe9090d3..a600dcce3d6 100644
--- a/cumulus/test/service/src/lib.rs
+++ b/cumulus/test/service/src/lib.rs
@@ -78,8 +78,9 @@ use sc_network::{
 };
 use sc_service::{
 	config::{
-		BlocksPruning, DatabaseSource, KeystoreConfig, MultiaddrWithPeerId, NetworkConfiguration,
-		OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig, RpcEndpoint, WasmExecutionMethod,
+		BlocksPruning, DatabaseSource, ExecutorConfiguration, KeystoreConfig, MultiaddrWithPeerId,
+		NetworkConfiguration, OffchainWorkerConfig, PruningMode, RpcBatchRequestConfig,
+		RpcConfiguration, RpcEndpoint, WasmExecutionMethod,
 	},
 	BasePath, ChainSpec as ChainSpecService, Configuration, Error as ServiceError,
 	PartialComponents, Role, RpcHandlers, TFullBackend, TFullClient, TaskManager,
@@ -194,15 +195,16 @@ pub fn new_partial(
 	enable_import_proof_record: bool,
 ) -> Result<Service, sc_service::Error> {
 	let heap_pages = config
+		.executor
 		.default_heap_pages
 		.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static { extra_pages: h as _ });
 
 	let executor = WasmExecutor::builder()
-		.with_execution_method(config.wasm_method)
+		.with_execution_method(config.executor.wasm_method)
 		.with_onchain_heap_alloc_strategy(heap_pages)
 		.with_offchain_heap_alloc_strategy(heap_pages)
-		.with_max_runtime_instances(config.max_runtime_instances)
-		.with_runtime_cache_size(config.runtime_cache_size)
+		.with_max_runtime_instances(config.executor.max_runtime_instances)
+		.with_runtime_cache_size(config.executor.runtime_cache_size)
 		.build();
 
 	let (client, backend, keystore_container, task_manager) =
@@ -863,38 +865,41 @@ pub fn node_config(
 		state_pruning: Some(PruningMode::ArchiveAll),
 		blocks_pruning: BlocksPruning::KeepAll,
 		chain_spec: spec,
-		wasm_method: WasmExecutionMethod::Compiled {
-			instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite,
+		executor: ExecutorConfiguration {
+			wasm_method: WasmExecutionMethod::Compiled {
+				instantiation_strategy:
+					sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite,
+			},
+			..ExecutorConfiguration::default()
+		},
+		rpc: RpcConfiguration {
+			addr: None,
+			max_connections: Default::default(),
+			cors: None,
+			methods: Default::default(),
+			max_request_size: Default::default(),
+			max_response_size: Default::default(),
+			id_provider: None,
+			max_subs_per_conn: Default::default(),
+			port: 9945,
+			message_buffer_capacity: Default::default(),
+			batch_config: RpcBatchRequestConfig::Unlimited,
+			rate_limit: None,
+			rate_limit_whitelisted_ips: Default::default(),
+			rate_limit_trust_proxy_headers: Default::default(),
 		},
-		rpc_addr: None,
-		rpc_max_connections: Default::default(),
-		rpc_cors: None,
-		rpc_methods: Default::default(),
-		rpc_max_request_size: Default::default(),
-		rpc_max_response_size: Default::default(),
-		rpc_id_provider: None,
-		rpc_max_subs_per_conn: Default::default(),
-		rpc_port: 9945,
-		rpc_message_buffer_capacity: Default::default(),
-		rpc_batch_config: RpcBatchRequestConfig::Unlimited,
-		rpc_rate_limit: None,
-		rpc_rate_limit_whitelisted_ips: Default::default(),
-		rpc_rate_limit_trust_proxy_headers: Default::default(),
 		prometheus_config: None,
 		telemetry_endpoints: None,
-		default_heap_pages: None,
 		offchain_worker: OffchainWorkerConfig { enabled: true, indexing_enabled: false },
 		force_authoring: false,
 		disable_grandpa: false,
 		dev_key_seed: Some(key_seed),
 		tracing_targets: None,
 		tracing_receiver: Default::default(),
-		max_runtime_instances: 8,
 		announce_block: true,
 		data_path: root,
 		base_path,
 		wasm_runtime_overrides: None,
-		runtime_cache_size: 2,
 	})
 }
 
@@ -1006,19 +1011,19 @@ pub fn run_relay_chain_validator_node(
 	);
 
 	if let Some(port) = port {
-		config.rpc_addr = Some(vec![RpcEndpoint {
-			batch_config: config.rpc_batch_config,
-			cors: config.rpc_cors.clone(),
+		config.rpc.addr = Some(vec![RpcEndpoint {
+			batch_config: config.rpc.batch_config,
+			cors: config.rpc.cors.clone(),
 			listen_addr: SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, port)),
-			max_connections: config.rpc_max_connections,
-			max_payload_in_mb: config.rpc_max_request_size,
-			max_payload_out_mb: config.rpc_max_response_size,
-			max_subscriptions_per_connection: config.rpc_max_subs_per_conn,
-			max_buffer_capacity_per_connection: config.rpc_message_buffer_capacity,
-			rpc_methods: config.rpc_methods,
-			rate_limit: config.rpc_rate_limit,
-			rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers,
-			rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(),
+			max_connections: config.rpc.max_connections,
+			max_payload_in_mb: config.rpc.max_request_size,
+			max_payload_out_mb: config.rpc.max_response_size,
+			max_subscriptions_per_connection: config.rpc.max_subs_per_conn,
+			max_buffer_capacity_per_connection: config.rpc.message_buffer_capacity,
+			rpc_methods: config.rpc.methods,
+			rate_limit: config.rpc.rate_limit,
+			rate_limit_trust_proxy_headers: config.rpc.rate_limit_trust_proxy_headers,
+			rate_limit_whitelisted_ips: config.rpc.rate_limit_whitelisted_ips.clone(),
 			retry_random_port: true,
 			is_optional: false,
 		}]);
diff --git a/polkadot/node/service/src/lib.rs b/polkadot/node/service/src/lib.rs
index a907d310c10..ab91c6b9b92 100644
--- a/polkadot/node/service/src/lib.rs
+++ b/polkadot/node/service/src/lib.rs
@@ -437,15 +437,16 @@ fn new_partial_basics(
 		.transpose()?;
 
 	let heap_pages = config
+		.executor
 		.default_heap_pages
 		.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static { extra_pages: h as _ });
 
 	let executor = WasmExecutor::builder()
-		.with_execution_method(config.wasm_method)
+		.with_execution_method(config.executor.wasm_method)
 		.with_onchain_heap_alloc_strategy(heap_pages)
 		.with_offchain_heap_alloc_strategy(heap_pages)
-		.with_max_runtime_instances(config.max_runtime_instances)
-		.with_runtime_cache_size(config.runtime_cache_size)
+		.with_max_runtime_instances(config.executor.max_runtime_instances)
+		.with_runtime_cache_size(config.executor.runtime_cache_size)
 		.build();
 
 	let (client, backend, keystore_container, task_manager) =
@@ -764,7 +765,7 @@ pub fn new_full<
 	use sc_network_sync::WarpSyncConfig;
 
 	let is_offchain_indexing_enabled = config.offchain_worker.indexing_enabled;
-	let role = config.role.clone();
+	let role = config.role;
 	let force_authoring = config.force_authoring;
 	let backoff_authoring_blocks = if !force_authoring_backoff &&
 		(config.chain_spec.is_polkadot() || config.chain_spec.is_kusama())
diff --git a/polkadot/node/test/service/src/lib.rs b/polkadot/node/test/service/src/lib.rs
index a4e58253bb1..b1238788486 100644
--- a/polkadot/node/test/service/src/lib.rs
+++ b/polkadot/node/test/service/src/lib.rs
@@ -68,6 +68,7 @@ use substrate_test_client::{
 pub type Client = FullClient;
 
 pub use polkadot_service::{FullBackend, GetLastTimestamp};
+use sc_service::config::{ExecutorConfiguration, RpcConfiguration};
 
 /// Create a new full node.
 #[sc_tracing::logging::prefix_logs_with(config.network.node_name.as_str())]
@@ -200,35 +201,37 @@ pub fn node_config(
 		state_pruning: Default::default(),
 		blocks_pruning: BlocksPruning::KeepFinalized,
 		chain_spec: Box::new(spec),
-		wasm_method: WasmExecutionMethod::Compiled {
-			instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
+		executor: ExecutorConfiguration {
+			wasm_method: WasmExecutionMethod::Compiled {
+				instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
+			},
+			..ExecutorConfiguration::default()
 		},
 		wasm_runtime_overrides: Default::default(),
-		rpc_addr: Default::default(),
-		rpc_max_request_size: Default::default(),
-		rpc_max_response_size: Default::default(),
-		rpc_max_connections: Default::default(),
-		rpc_cors: None,
-		rpc_methods: Default::default(),
-		rpc_id_provider: None,
-		rpc_max_subs_per_conn: Default::default(),
-		rpc_port: 9944,
-		rpc_message_buffer_capacity: Default::default(),
-		rpc_batch_config: RpcBatchRequestConfig::Unlimited,
-		rpc_rate_limit: None,
-		rpc_rate_limit_whitelisted_ips: Default::default(),
-		rpc_rate_limit_trust_proxy_headers: Default::default(),
+		rpc: RpcConfiguration {
+			addr: Default::default(),
+			max_request_size: Default::default(),
+			max_response_size: Default::default(),
+			max_connections: Default::default(),
+			cors: None,
+			methods: Default::default(),
+			id_provider: None,
+			max_subs_per_conn: Default::default(),
+			port: 9944,
+			message_buffer_capacity: Default::default(),
+			batch_config: RpcBatchRequestConfig::Unlimited,
+			rate_limit: None,
+			rate_limit_whitelisted_ips: Default::default(),
+			rate_limit_trust_proxy_headers: Default::default(),
+		},
 		prometheus_config: None,
 		telemetry_endpoints: None,
-		default_heap_pages: None,
 		offchain_worker: Default::default(),
 		force_authoring: false,
 		disable_grandpa: false,
 		dev_key_seed: Some(key_seed),
 		tracing_targets: None,
 		tracing_receiver: Default::default(),
-		max_runtime_instances: 8,
-		runtime_cache_size: 2,
 		announce_block: true,
 		data_path: root,
 		base_path,
diff --git a/prdoc/pr_5364.prdoc b/prdoc/pr_5364.prdoc
new file mode 100644
index 00000000000..35a72f65fb1
--- /dev/null
+++ b/prdoc/pr_5364.prdoc
@@ -0,0 +1,39 @@
+title: Improve `sc-service` API
+
+doc:
+  - audience: Node Dev
+    description: |
+      This improves `sc-service` API by not requiring the whole `&Configuration`, using specific configuration options
+      instead. `RpcConfiguration` and `ExecutorConfiguration` were also extracted from `Configuration` to group all RPC
+      and executor options together.
+      If `sc-service` is used as a library with lower-level APIs, `Configuration` can now be avoided in most cases.
+
+      This mainly impacts you on your node implementation. There you need to change this:
+      ```
+      with_execution_method(config.wasm_method)
+      ```
+
+      to this:
+      ```
+      with_execution_method(config.executor.wasm_method)
+      ```
+
+      There are similar changes required as well, but all are around the initialization of the wasm executor.
+
+crates:
+  - name: sc-service
+    bump: major
+  - name: sc-network-common
+    bump: patch
+  - name: sc-cli
+    bump: major
+  - name: polkadot-service
+    bump: patch
+  - name: cumulus-relay-chain-minimal-node
+    bump: none
+  - name: polkadot-parachain-bin
+    bump: major
+  - name: polkadot-parachain-lib
+    bump: major
+  - name: staging-node-inspect
+    bump: major
diff --git a/substrate/bin/node/cli/benches/block_production.rs b/substrate/bin/node/cli/benches/block_production.rs
index 9ac9d8b4f67..de883d1051f 100644
--- a/substrate/bin/node/cli/benches/block_production.rs
+++ b/substrate/bin/node/cli/benches/block_production.rs
@@ -22,6 +22,7 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughpu
 
 use kitchensink_runtime::{constants::currency::*, BalancesCall};
 use node_cli::service::{create_extrinsic, FullClient};
+use polkadot_sdk::sc_service::config::{ExecutorConfiguration, RpcConfiguration};
 use sc_block_builder::{BlockBuilderBuilder, BuiltBlock};
 use sc_consensus::{
 	block_import::{BlockImportParams, ForkChoiceStrategy},
@@ -73,34 +74,36 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
 		state_pruning: Some(PruningMode::ArchiveAll),
 		blocks_pruning: BlocksPruning::KeepAll,
 		chain_spec: spec,
-		wasm_method: WasmExecutionMethod::Compiled {
-			instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
+		executor: ExecutorConfiguration {
+			wasm_method: WasmExecutionMethod::Compiled {
+				instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
+			},
+			..ExecutorConfiguration::default()
+		},
+		rpc: RpcConfiguration {
+			addr: None,
+			max_connections: Default::default(),
+			cors: None,
+			methods: Default::default(),
+			max_request_size: Default::default(),
+			max_response_size: Default::default(),
+			id_provider: Default::default(),
+			max_subs_per_conn: Default::default(),
+			port: 9944,
+			message_buffer_capacity: Default::default(),
+			batch_config: RpcBatchRequestConfig::Unlimited,
+			rate_limit: None,
+			rate_limit_whitelisted_ips: Default::default(),
+			rate_limit_trust_proxy_headers: Default::default(),
 		},
-		rpc_addr: None,
-		rpc_max_connections: Default::default(),
-		rpc_cors: None,
-		rpc_methods: Default::default(),
-		rpc_max_request_size: Default::default(),
-		rpc_max_response_size: Default::default(),
-		rpc_id_provider: Default::default(),
-		rpc_max_subs_per_conn: Default::default(),
-		rpc_port: 9944,
-		rpc_message_buffer_capacity: Default::default(),
-		rpc_batch_config: RpcBatchRequestConfig::Unlimited,
-		rpc_rate_limit: None,
-		rpc_rate_limit_whitelisted_ips: Default::default(),
-		rpc_rate_limit_trust_proxy_headers: Default::default(),
 		prometheus_config: None,
 		telemetry_endpoints: None,
-		default_heap_pages: None,
 		offchain_worker: OffchainWorkerConfig { enabled: true, indexing_enabled: false },
 		force_authoring: false,
 		disable_grandpa: false,
 		dev_key_seed: Some(Sr25519Keyring::Alice.to_seed()),
 		tracing_targets: None,
 		tracing_receiver: Default::default(),
-		max_runtime_instances: 8,
-		runtime_cache_size: 2,
 		announce_block: true,
 		data_path: base_path.path().into(),
 		base_path,
diff --git a/substrate/bin/node/cli/benches/transaction_pool.rs b/substrate/bin/node/cli/benches/transaction_pool.rs
index 9a71a4ec585..efec081427f 100644
--- a/substrate/bin/node/cli/benches/transaction_pool.rs
+++ b/substrate/bin/node/cli/benches/transaction_pool.rs
@@ -24,6 +24,7 @@ use futures::{future, StreamExt};
 use kitchensink_runtime::{constants::currency::*, BalancesCall, SudoCall};
 use node_cli::service::{create_extrinsic, fetch_nonce, FullClient, TransactionPool};
 use node_primitives::AccountId;
+use polkadot_sdk::sc_service::config::{ExecutorConfiguration, RpcConfiguration};
 use sc_service::{
 	config::{
 		BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig,
@@ -70,32 +71,31 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase {
 		state_pruning: Some(PruningMode::ArchiveAll),
 		blocks_pruning: BlocksPruning::KeepAll,
 		chain_spec: spec,
-		wasm_method: Default::default(),
-		rpc_addr: None,
-		rpc_max_connections: Default::default(),
-		rpc_cors: None,
-		rpc_methods: Default::default(),
-		rpc_max_request_size: Default::default(),
-		rpc_max_response_size: Default::default(),
-		rpc_id_provider: Default::default(),
-		rpc_max_subs_per_conn: Default::default(),
-		rpc_port: 9944,
-		rpc_message_buffer_capacity: Default::default(),
-		rpc_batch_config: RpcBatchRequestConfig::Unlimited,
-		rpc_rate_limit: None,
-		rpc_rate_limit_whitelisted_ips: Default::default(),
-		rpc_rate_limit_trust_proxy_headers: Default::default(),
+		executor: ExecutorConfiguration::default(),
+		rpc: RpcConfiguration {
+			addr: None,
+			max_connections: Default::default(),
+			cors: None,
+			methods: Default::default(),
+			max_request_size: Default::default(),
+			max_response_size: Default::default(),
+			id_provider: Default::default(),
+			max_subs_per_conn: Default::default(),
+			port: 9944,
+			message_buffer_capacity: Default::default(),
+			batch_config: RpcBatchRequestConfig::Unlimited,
+			rate_limit: None,
+			rate_limit_whitelisted_ips: Default::default(),
+			rate_limit_trust_proxy_headers: Default::default(),
+		},
 		prometheus_config: None,
 		telemetry_endpoints: None,
-		default_heap_pages: None,
 		offchain_worker: OffchainWorkerConfig { enabled: true, indexing_enabled: false },
 		force_authoring: false,
 		disable_grandpa: false,
 		dev_key_seed: Some(Sr25519Keyring::Alice.to_seed()),
 		tracing_targets: None,
 		tracing_receiver: Default::default(),
-		max_runtime_instances: 8,
-		runtime_cache_size: 2,
 		announce_block: true,
 		data_path: base_path.path().into(),
 		base_path,
diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs
index 8fdcc7261b5..4f63473f632 100644
--- a/substrate/bin/node/cli/src/service.rs
+++ b/substrate/bin/node/cli/src/service.rs
@@ -208,7 +208,7 @@ pub fn new_partial(
 		})
 		.transpose()?;
 
-	let executor = sc_service::new_wasm_executor(&config);
+	let executor = sc_service::new_wasm_executor(&config.executor);
 
 	let (client, backend, keystore_container, task_manager) =
 		sc_service::new_full_parts::<Block, RuntimeApi, _>(
@@ -404,7 +404,7 @@ pub fn new_full_base<N: NetworkBackend<Block, <Block as BlockT>::Hash>>(
 	),
 ) -> Result<NewFullBase, ServiceError> {
 	let is_offchain_indexing_enabled = config.offchain_worker.indexing_enabled;
-	let role = config.role.clone();
+	let role = config.role;
 	let force_authoring = config.force_authoring;
 	let backoff_authoring_blocks =
 		Some(sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging::default());
@@ -717,7 +717,7 @@ pub fn new_full_base<N: NetworkBackend<Block, <Block as BlockT>::Hash>>(
 		name: Some(name),
 		observer_enabled: false,
 		keystore,
-		local_role: role.clone(),
+		local_role: role,
 		telemetry: telemetry.as_ref().map(|x| x.handle()),
 		protocol_name: grandpa_protocol_name,
 	};
diff --git a/substrate/bin/node/inspect/src/command.rs b/substrate/bin/node/inspect/src/command.rs
index e0e25707e31..b9e5e55be8e 100644
--- a/substrate/bin/node/inspect/src/command.rs
+++ b/substrate/bin/node/inspect/src/command.rs
@@ -36,7 +36,7 @@ impl InspectCmd {
 		B: Block,
 		RA: Send + Sync + 'static,
 	{
-		let executor = sc_service::new_wasm_executor::<HostFunctions>(&config);
+		let executor = sc_service::new_wasm_executor::<HostFunctions>(&config.executor);
 		let client = sc_service::new_full_client::<B, RA, _>(&config, None, executor)?;
 		let inspect = Inspector::<B>::new(client);
 
diff --git a/substrate/client/cli/src/commands/run_cmd.rs b/substrate/client/cli/src/commands/run_cmd.rs
index 7245b46e2f7..f47baf2644e 100644
--- a/substrate/client/cli/src/commands/run_cmd.rs
+++ b/substrate/client/cli/src/commands/run_cmd.rs
@@ -345,7 +345,17 @@ impl CliConfiguration for RunCmd {
 	}
 
 	fn network_params(&self) -> Option<&NetworkParams> {
-		Some(&self.network_params)
+		let network_params = &self.network_params;
+		let is_authority = self.role(self.is_dev().ok()?).ok()?.is_authority();
+		if is_authority && network_params.public_addr.is_empty() {
+			eprintln!(
+				"WARNING: No public address specified, validator node may not be reachable.
+				Consider setting `--public-addr` to the public IP address of this node.
+				This will become a hard requirement in future versions."
+			);
+		}
+
+		Some(network_params)
 	}
 
 	fn keystore_params(&self) -> Option<&KeystoreParams> {
diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs
index 7c235847761..59238b3307c 100644
--- a/substrate/client/cli/src/config.rs
+++ b/substrate/client/cli/src/config.rs
@@ -27,10 +27,10 @@ use log::warn;
 use names::{Generator, Name};
 use sc_service::{
 	config::{
-		BasePath, Configuration, DatabaseSource, IpNetwork, KeystoreConfig, NetworkConfiguration,
-		NodeKeyConfig, OffchainWorkerConfig, PrometheusConfig, PruningMode, Role,
-		RpcBatchRequestConfig, RpcMethods, TelemetryEndpoints, TransactionPoolOptions,
-		WasmExecutionMethod,
+		BasePath, Configuration, DatabaseSource, ExecutorConfiguration, IpNetwork, KeystoreConfig,
+		NetworkConfiguration, NodeKeyConfig, OffchainWorkerConfig, PrometheusConfig, PruningMode,
+		Role, RpcBatchRequestConfig, RpcConfiguration, RpcMethods, TelemetryEndpoints,
+		TransactionPoolOptions, WasmExecutionMethod,
 	},
 	BlocksPruning, ChainSpec, TracingReceiver,
 };
@@ -530,26 +530,32 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
 			trie_cache_maximum_size: self.trie_cache_maximum_size()?,
 			state_pruning: self.state_pruning()?,
 			blocks_pruning: self.blocks_pruning()?,
-			wasm_method: self.wasm_method()?,
+			executor: ExecutorConfiguration {
+				wasm_method: self.wasm_method()?,
+				default_heap_pages: self.default_heap_pages()?,
+				max_runtime_instances,
+				runtime_cache_size,
+			},
 			wasm_runtime_overrides: self.wasm_runtime_overrides(),
-			rpc_addr: rpc_addrs,
-			rpc_methods: self.rpc_methods()?,
-			rpc_max_connections: self.rpc_max_connections()?,
-			rpc_cors: self.rpc_cors(is_dev)?,
-			rpc_max_request_size: self.rpc_max_request_size()?,
-			rpc_max_response_size: self.rpc_max_response_size()?,
-			rpc_id_provider: None,
-			rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?,
-			rpc_port: DCV::rpc_listen_port(),
-			rpc_message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?,
-			rpc_batch_config: self.rpc_batch_config()?,
-			rpc_rate_limit: self.rpc_rate_limit()?,
-			rpc_rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips()?,
-			rpc_rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers()?,
+			rpc: RpcConfiguration {
+				addr: rpc_addrs,
+				methods: self.rpc_methods()?,
+				max_connections: self.rpc_max_connections()?,
+				cors: self.rpc_cors(is_dev)?,
+				max_request_size: self.rpc_max_request_size()?,
+				max_response_size: self.rpc_max_response_size()?,
+				id_provider: None,
+				max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?,
+				port: DCV::rpc_listen_port(),
+				message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?,
+				batch_config: self.rpc_batch_config()?,
+				rate_limit: self.rpc_rate_limit()?,
+				rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips()?,
+				rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers()?,
+			},
 			prometheus_config: self
 				.prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?,
 			telemetry_endpoints,
-			default_heap_pages: self.default_heap_pages()?,
 			offchain_worker: self.offchain_worker(&role)?,
 			force_authoring: self.force_authoring()?,
 			disable_grandpa: self.disable_grandpa()?,
@@ -557,11 +563,9 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
 			tracing_targets: self.tracing_targets()?,
 			tracing_receiver: self.tracing_receiver()?,
 			chain_spec,
-			max_runtime_instances,
 			announce_block: self.announce_block()?,
 			role,
 			base_path,
-			runtime_cache_size,
 		})
 	}
 
@@ -618,15 +622,9 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
 	/// 	}
 	/// }
 	/// ```
-	fn init<F>(
-		&self,
-		support_url: &String,
-		impl_version: &String,
-		logger_hook: F,
-		config: &Configuration,
-	) -> Result<()>
+	fn init<F>(&self, support_url: &String, impl_version: &String, logger_hook: F) -> Result<()>
 	where
-		F: FnOnce(&mut LoggerBuilder, &Configuration),
+		F: FnOnce(&mut LoggerBuilder),
 	{
 		sp_panic_handler::set(support_url, impl_version);
 
@@ -645,18 +643,10 @@ pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
 		}
 
 		// Call hook for custom profiling setup.
-		logger_hook(&mut logger, config);
+		logger_hook(&mut logger);
 
 		logger.init()?;
 
-		if config.role.is_authority() && config.network.public_addresses.is_empty() {
-			warn!(
-				"WARNING: No public address specified, validator node may not be reachable.
-				Consider setting `--public-addr` to the public IP address of this node.
-				This will become a hard requirement in future versions."
-			);
-		}
-
 		match fdlimit::raise_fd_limit() {
 			Ok(fdlimit::Outcome::LimitRaised { to, .. }) =>
 				if to < RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT {
diff --git a/substrate/client/cli/src/lib.rs b/substrate/client/cli/src/lib.rs
index 1bb9fec0e27..4db70f99c80 100644
--- a/substrate/client/cli/src/lib.rs
+++ b/substrate/client/cli/src/lib.rs
@@ -25,6 +25,7 @@
 #![warn(unused_imports)]
 
 use clap::{CommandFactory, FromArgMatches, Parser};
+use log::warn;
 use sc_service::Configuration;
 
 pub mod arg_enums;
@@ -242,7 +243,10 @@ pub trait SubstrateCli: Sized {
 
 		let config = command.create_configuration(self, tokio_runtime.handle().clone())?;
 
-		command.init(&Self::support_url(), &Self::impl_version(), logger_hook, &config)?;
+		command.init(&Self::support_url(), &Self::impl_version(), |logger_builder| {
+			logger_hook(logger_builder, &config)
+		})?;
+
 		Runner::new(config, tokio_runtime, signals)
 	}
 }
diff --git a/substrate/client/cli/src/params/node_key_params.rs b/substrate/client/cli/src/params/node_key_params.rs
index 0e12c7a2a2d..cdd63788811 100644
--- a/substrate/client/cli/src/params/node_key_params.rs
+++ b/substrate/client/cli/src/params/node_key_params.rs
@@ -237,7 +237,6 @@ mod tests {
 				|params| {
 					let dir = PathBuf::from(net_config_dir.clone());
 					let typ = params.node_key_type;
-					let role = role.clone();
 					params.node_key(net_config_dir, role, is_dev).and_then(move |c| match c {
 						NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f))
 							if typ == NodeKeyType::Ed25519 &&
diff --git a/substrate/client/cli/src/runner.rs b/substrate/client/cli/src/runner.rs
index b0dbccfa634..9c5834d8d80 100644
--- a/substrate/client/cli/src/runner.rs
+++ b/substrate/client/cli/src/runner.rs
@@ -193,7 +193,10 @@ pub fn print_node_infos<C: SubstrateCli>(config: &Configuration) {
 mod tests {
 	use super::*;
 	use sc_network::config::NetworkConfiguration;
-	use sc_service::{Arc, ChainType, GenericChainSpec, NoExtension};
+	use sc_service::{
+		config::{ExecutorConfiguration, RpcConfiguration},
+		Arc, ChainType, GenericChainSpec, NoExtension,
+	};
 	use std::{
 		path::PathBuf,
 		sync::atomic::{AtomicU64, Ordering},
@@ -262,36 +265,35 @@ mod tests {
 					.with_genesis_config_patch(Default::default())
 					.build(),
 				),
-				wasm_method: Default::default(),
+				executor: ExecutorConfiguration::default(),
 				wasm_runtime_overrides: None,
-				rpc_addr: None,
-				rpc_max_connections: Default::default(),
-				rpc_cors: None,
-				rpc_methods: Default::default(),
-				rpc_max_request_size: Default::default(),
-				rpc_max_response_size: Default::default(),
-				rpc_id_provider: Default::default(),
-				rpc_max_subs_per_conn: Default::default(),
-				rpc_message_buffer_capacity: Default::default(),
-				rpc_port: 9944,
-				rpc_batch_config: sc_service::config::RpcBatchRequestConfig::Unlimited,
-				rpc_rate_limit: None,
-				rpc_rate_limit_whitelisted_ips: Default::default(),
-				rpc_rate_limit_trust_proxy_headers: Default::default(),
+				rpc: RpcConfiguration {
+					addr: None,
+					max_connections: Default::default(),
+					cors: None,
+					methods: Default::default(),
+					max_request_size: Default::default(),
+					max_response_size: Default::default(),
+					id_provider: Default::default(),
+					max_subs_per_conn: Default::default(),
+					message_buffer_capacity: Default::default(),
+					port: 9944,
+					batch_config: sc_service::config::RpcBatchRequestConfig::Unlimited,
+					rate_limit: None,
+					rate_limit_whitelisted_ips: Default::default(),
+					rate_limit_trust_proxy_headers: Default::default(),
+				},
 				prometheus_config: None,
 				telemetry_endpoints: None,
-				default_heap_pages: None,
 				offchain_worker: Default::default(),
 				force_authoring: false,
 				disable_grandpa: false,
 				dev_key_seed: None,
 				tracing_targets: None,
 				tracing_receiver: Default::default(),
-				max_runtime_instances: 8,
 				announce_block: true,
 				base_path: sc_service::BasePath::new(root.clone()),
 				data_path: root,
-				runtime_cache_size: 2,
 			},
 			runtime,
 			Signals::dummy(),
diff --git a/substrate/client/network/common/src/role.rs b/substrate/client/network/common/src/role.rs
index 11b7a7924c4..e2218b37f08 100644
--- a/substrate/client/network/common/src/role.rs
+++ b/substrate/client/network/common/src/role.rs
@@ -58,7 +58,7 @@ impl From<Roles> for ObservedRole {
 }
 
 /// Role of the local node.
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy)]
 pub enum Role {
 	/// Regular full node.
 	Full,
diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs
index 9a6c3fde37d..0dc28d1361c 100644
--- a/substrate/client/service/src/builder.rs
+++ b/substrate/client/service/src/builder.rs
@@ -19,7 +19,7 @@
 use crate::{
 	build_network_future, build_system_rpc_future,
 	client::{Client, ClientConfig},
-	config::{Configuration, KeystoreConfig, PrometheusConfig},
+	config::{Configuration, ExecutorConfiguration, KeystoreConfig, PrometheusConfig},
 	error::Error,
 	metrics::MetricsService,
 	start_rpc_servers, BuildGenesisBlock, GenesisBlockBuilder, RpcHandlers, SpawnTaskHandle,
@@ -29,12 +29,12 @@ use futures::{channel::oneshot, future::ready, FutureExt, StreamExt};
 use jsonrpsee::RpcModule;
 use log::info;
 use prometheus_endpoint::Registry;
-use sc_chain_spec::get_extension;
+use sc_chain_spec::{get_extension, ChainSpec};
 use sc_client_api::{
 	execution_extensions::ExecutionExtensions, proof_provider::ProofProvider, BadBlocks,
 	BlockBackend, BlockchainEvents, ExecutorProvider, ForkBlocks, StorageProvider, UsageProvider,
 };
-use sc_client_db::{Backend, DatabaseSettings};
+use sc_client_db::{Backend, BlocksPruning, DatabaseSettings, PruningMode};
 use sc_consensus::import_queue::ImportQueue;
 use sc_executor::{
 	sp_wasm_interface::HostFunctions, HeapAllocStrategy, NativeExecutionDispatch, RuntimeVersionOf,
@@ -212,10 +212,7 @@ where
 		.unwrap_or_default();
 
 	let client = {
-		let extensions = sc_client_api::execution_extensions::ExecutionExtensions::new(
-			None,
-			Arc::new(executor.clone()),
-		);
+		let extensions = ExecutionExtensions::new(None, Arc::new(executor.clone()));
 
 		let wasm_runtime_substitutes = config
 			.chain_spec
@@ -268,11 +265,11 @@ pub fn new_native_or_wasm_executor<D: NativeExecutionDispatch>(
 	config: &Configuration,
 ) -> sc_executor::NativeElseWasmExecutor<D> {
 	#[allow(deprecated)]
-	sc_executor::NativeElseWasmExecutor::new_with_wasm_executor(new_wasm_executor(config))
+	sc_executor::NativeElseWasmExecutor::new_with_wasm_executor(new_wasm_executor(&config.executor))
 }
 
-/// Creates a [`WasmExecutor`] according to [`Configuration`].
-pub fn new_wasm_executor<H: HostFunctions>(config: &Configuration) -> WasmExecutor<H> {
+/// Creates a [`WasmExecutor`] according to [`ExecutorConfiguration`].
+pub fn new_wasm_executor<H: HostFunctions>(config: &ExecutorConfiguration) -> WasmExecutor<H> {
 	let strategy = config
 		.default_heap_pages
 		.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| HeapAllocStrategy::Static { extra_pages: p as _ });
@@ -434,7 +431,17 @@ where
 
 	let telemetry = telemetry
 		.map(|telemetry| {
-			init_telemetry(&mut config, network.clone(), client.clone(), telemetry, Some(sysinfo))
+			init_telemetry(
+				config.network.node_name.clone(),
+				config.impl_name.clone(),
+				config.impl_version.clone(),
+				config.chain_spec.name().to_string(),
+				config.role.is_authority(),
+				network.clone(),
+				client.clone(),
+				telemetry,
+				Some(sysinfo),
+			)
 		})
 		.transpose()?;
 
@@ -463,7 +470,13 @@ where
 	let metrics_service =
 		if let Some(PrometheusConfig { port, registry }) = config.prometheus_config.clone() {
 			// Set static metrics.
-			let metrics = MetricsService::with_prometheus(telemetry, &registry, &config)?;
+			let metrics = MetricsService::with_prometheus(
+				telemetry,
+				&registry,
+				config.role,
+				&config.network.node_name,
+				&config.impl_version,
+			)?;
 			spawn_handle.spawn(
 				"prometheus-endpoint",
 				None,
@@ -487,7 +500,7 @@ where
 		),
 	);
 
-	let rpc_id_provider = config.rpc_id_provider.take();
+	let rpc_id_provider = config.rpc.id_provider.take();
 
 	// jsonrpsee RPC
 	let gen_rpc_module = || {
@@ -497,13 +510,23 @@ where
 			transaction_pool.clone(),
 			keystore.clone(),
 			system_rpc_tx.clone(),
-			&config,
+			config.impl_name.clone(),
+			config.impl_version.clone(),
+			config.chain_spec.as_ref(),
+			&config.state_pruning,
+			config.blocks_pruning,
 			backend.clone(),
 			&*rpc_builder,
 		)
 	};
 
-	let rpc_server_handle = start_rpc_servers(&config, gen_rpc_module, rpc_id_provider)?;
+	let rpc_server_handle = start_rpc_servers(
+		&config.rpc,
+		config.prometheus_registry(),
+		&config.tokio_handle,
+		gen_rpc_module,
+		rpc_id_provider,
+	)?;
 	let in_memory_rpc = {
 		let mut module = gen_rpc_module()?;
 		module.extensions_mut().insert(DenyUnsafe::No);
@@ -555,7 +578,11 @@ pub async fn propagate_transaction_notifications<Block, ExPool>(
 
 /// Initialize telemetry with provided configuration and return telemetry handle
 pub fn init_telemetry<Block, Client, Network>(
-	config: &mut Configuration,
+	name: String,
+	implementation: String,
+	version: String,
+	chain: String,
+	authority: bool,
 	network: Network,
 	client: Arc<Client>,
 	telemetry: &mut Telemetry,
@@ -568,16 +595,16 @@ where
 {
 	let genesis_hash = client.block_hash(Zero::zero()).ok().flatten().unwrap_or_default();
 	let connection_message = ConnectionMessage {
-		name: config.network.node_name.to_owned(),
-		implementation: config.impl_name.to_owned(),
-		version: config.impl_version.to_owned(),
+		name,
+		implementation,
+		version,
 		target_os: sc_sysinfo::TARGET_OS.into(),
 		target_arch: sc_sysinfo::TARGET_ARCH.into(),
 		target_env: sc_sysinfo::TARGET_ENV.into(),
 		config: String::new(),
-		chain: config.chain_spec.name().to_owned(),
+		chain,
 		genesis_hash: format!("{:?}", genesis_hash),
-		authority: config.role.is_authority(),
+		authority,
 		startup_time: SystemTime::UNIX_EPOCH
 			.elapsed()
 			.map(|dur| dur.as_millis())
@@ -599,7 +626,11 @@ pub fn gen_rpc_module<TBl, TBackend, TCl, TRpc, TExPool>(
 	transaction_pool: Arc<TExPool>,
 	keystore: KeystorePtr,
 	system_rpc_tx: TracingUnboundedSender<sc_rpc::system::Request<TBl>>,
-	config: &Configuration,
+	impl_name: String,
+	impl_version: String,
+	chain_spec: &dyn ChainSpec,
+	state_pruning: &Option<PruningMode>,
+	blocks_pruning: BlocksPruning,
 	backend: Arc<TBackend>,
 	rpc_builder: &(dyn Fn(SubscriptionTaskExecutor) -> Result<RpcModule<TRpc>, Error>),
 ) -> Result<RpcModule<()>, Error>
@@ -624,11 +655,11 @@ where
 	TBl::Header: Unpin,
 {
 	let system_info = sc_rpc::system::SystemInfo {
-		chain_name: config.chain_spec.name().into(),
-		impl_name: config.impl_name.clone(),
-		impl_version: config.impl_version.clone(),
-		properties: config.chain_spec.properties(),
-		chain_type: config.chain_spec.chain_type(),
+		chain_name: chain_spec.name().into(),
+		impl_name,
+		impl_version,
+		properties: chain_spec.properties(),
+		chain_type: chain_spec.chain_type(),
 	};
 
 	let mut rpc_api = RpcModule::new(());
@@ -673,8 +704,8 @@ where
 	// An archive node that can respond to the `archive` RPC-v2 queries is a node with:
 	// - state pruning in archive mode: The storage of blocks is kept around
 	// - block pruning in archive mode: The block's body is kept around
-	let is_archive_node = config.state_pruning.as_ref().map(|sp| sp.is_archive()).unwrap_or(false) &&
-		config.blocks_pruning.is_archive();
+	let is_archive_node = state_pruning.as_ref().map(|sp| sp.is_archive()).unwrap_or(false) &&
+		blocks_pruning.is_archive();
 	let genesis_hash = client.hash(Zero::zero()).ok().flatten().expect("Genesis block exists; qed");
 	if is_archive_node {
 		let archive_v2 = sc_rpc_spec_v2::archive::Archive::new(
@@ -690,9 +721,9 @@ where
 
 	// ChainSpec RPC-v2.
 	let chain_spec_v2 = sc_rpc_spec_v2::chain_spec::ChainSpec::new(
-		config.chain_spec.name().into(),
+		chain_spec.name().into(),
 		genesis_hash,
-		config.chain_spec.properties(),
+		chain_spec.properties(),
 	)
 	.into_rpc();
 
@@ -953,7 +984,7 @@ where
 
 	let genesis_hash = client.hash(Zero::zero()).ok().flatten().expect("Genesis block exists; qed");
 	let network_params = sc_network::config::Params::<TBl, <TBl as BlockT>::Hash, TNet> {
-		role: config.role.clone(),
+		role: config.role,
 		executor: {
 			let spawn_handle = Clone::clone(&spawn_handle);
 			Box::new(move |fut| {
@@ -999,7 +1030,7 @@ where
 		"system-rpc-handler",
 		Some("networking"),
 		build_system_rpc_future::<_, _, <TBl as BlockT>::Hash>(
-			config.role.clone(),
+			config.role,
 			network_mut.network_service(),
 			sync_service.clone(),
 			client.clone(),
diff --git a/substrate/client/service/src/config.rs b/substrate/client/service/src/config.rs
index 8387f818e68..6f65c2e2d81 100644
--- a/substrate/client/service/src/config.rs
+++ b/substrate/client/service/src/config.rs
@@ -78,48 +78,18 @@ pub struct Configuration {
 	pub blocks_pruning: BlocksPruning,
 	/// Chain configuration.
 	pub chain_spec: Box<dyn ChainSpec>,
-	/// Wasm execution method.
-	pub wasm_method: WasmExecutionMethod,
+	/// Runtime executor configuration.
+	pub executor: ExecutorConfiguration,
 	/// Directory where local WASM runtimes live. These runtimes take precedence
 	/// over on-chain runtimes when the spec version matches. Set to `None` to
 	/// disable overrides (default).
 	pub wasm_runtime_overrides: Option<PathBuf>,
-	/// JSON-RPC server endpoints.
-	pub rpc_addr: Option<Vec<RpcEndpoint>>,
-	/// Maximum number of connections for JSON-RPC server.
-	pub rpc_max_connections: u32,
-	/// CORS settings for HTTP & WS servers. `None` if all origins are allowed.
-	pub rpc_cors: Option<Vec<String>>,
-	/// RPC methods to expose (by default only a safe subset or all of them).
-	pub rpc_methods: RpcMethods,
-	/// Maximum payload of a rpc request
-	pub rpc_max_request_size: u32,
-	/// Maximum payload of a rpc response.
-	pub rpc_max_response_size: u32,
-	/// Custom JSON-RPC subscription ID provider.
-	///
-	/// Default: [`crate::RandomStringSubscriptionId`].
-	pub rpc_id_provider: Option<Box<dyn RpcSubscriptionIdProvider>>,
-	/// Maximum allowed subscriptions per rpc connection
-	pub rpc_max_subs_per_conn: u32,
-	/// JSON-RPC server default port.
-	pub rpc_port: u16,
-	/// The number of messages the JSON-RPC server is allowed to keep in memory.
-	pub rpc_message_buffer_capacity: u32,
-	/// JSON-RPC server batch config.
-	pub rpc_batch_config: RpcBatchRequestConfig,
-	/// RPC rate limit per minute.
-	pub rpc_rate_limit: Option<NonZeroU32>,
-	/// RPC rate limit whitelisted ip addresses.
-	pub rpc_rate_limit_whitelisted_ips: Vec<IpNetwork>,
-	/// RPC rate limit trust proxy headers.
-	pub rpc_rate_limit_trust_proxy_headers: bool,
+	/// RPC configuration.
+	pub rpc: RpcConfiguration,
 	/// Prometheus endpoint configuration. `None` if disabled.
 	pub prometheus_config: Option<PrometheusConfig>,
 	/// Telemetry service URL. `None` if disabled.
 	pub telemetry_endpoints: Option<TelemetryEndpoints>,
-	/// The default number of 64KB pages to allocate for Wasm execution
-	pub default_heap_pages: Option<u64>,
 	/// Should offchain workers be executed.
 	pub offchain_worker: OffchainWorkerConfig,
 	/// Enable authoring even when offline.
@@ -137,18 +107,12 @@ pub struct Configuration {
 	pub tracing_targets: Option<String>,
 	/// Tracing receiver
 	pub tracing_receiver: sc_tracing::TracingReceiver,
-	/// The size of the instances cache.
-	///
-	/// The default value is 8.
-	pub max_runtime_instances: usize,
 	/// Announce block automatically after they have been imported
 	pub announce_block: bool,
 	/// Data path root for the configured chain.
 	pub data_path: PathBuf,
 	/// Base path of the configuration. This is shared between chains.
 	pub base_path: BasePath,
-	/// Maximum number of different runtime versions that can be cached.
-	pub runtime_cache_size: u8,
 }
 
 /// Type for tasks spawned by the executor.
@@ -323,3 +287,64 @@ impl From<PathBuf> for BasePath {
 		BasePath::new(path)
 	}
 }
+
+/// RPC configuration.
+#[derive(Debug)]
+pub struct RpcConfiguration {
+	/// JSON-RPC server endpoints.
+	pub addr: Option<Vec<RpcEndpoint>>,
+	/// Maximum number of connections for JSON-RPC server.
+	pub max_connections: u32,
+	/// CORS settings for HTTP & WS servers. `None` if all origins are allowed.
+	pub cors: Option<Vec<String>>,
+	/// RPC methods to expose (by default only a safe subset or all of them).
+	pub methods: RpcMethods,
+	/// Maximum payload of a rpc request
+	pub max_request_size: u32,
+	/// Maximum payload of a rpc response.
+	pub max_response_size: u32,
+	/// Custom JSON-RPC subscription ID provider.
+	///
+	/// Default: [`crate::RandomStringSubscriptionId`].
+	pub id_provider: Option<Box<dyn RpcSubscriptionIdProvider>>,
+	/// Maximum allowed subscriptions per rpc connection
+	pub max_subs_per_conn: u32,
+	/// JSON-RPC server default port.
+	pub port: u16,
+	/// The number of messages the JSON-RPC server is allowed to keep in memory.
+	pub message_buffer_capacity: u32,
+	/// JSON-RPC server batch config.
+	pub batch_config: RpcBatchRequestConfig,
+	/// RPC rate limit per minute.
+	pub rate_limit: Option<NonZeroU32>,
+	/// RPC rate limit whitelisted ip addresses.
+	pub rate_limit_whitelisted_ips: Vec<IpNetwork>,
+	/// RPC rate limit trust proxy headers.
+	pub rate_limit_trust_proxy_headers: bool,
+}
+
+/// Runtime executor configuration.
+#[derive(Debug, Clone)]
+pub struct ExecutorConfiguration {
+	/// Wasm execution method.
+	pub wasm_method: WasmExecutionMethod,
+	/// The size of the instances cache.
+	///
+	/// The default value is 8.
+	pub max_runtime_instances: usize,
+	/// The default number of 64KB pages to allocate for Wasm execution
+	pub default_heap_pages: Option<u64>,
+	/// Maximum number of different runtime versions that can be cached.
+	pub runtime_cache_size: u8,
+}
+
+impl Default for ExecutorConfiguration {
+	fn default() -> Self {
+		Self {
+			wasm_method: WasmExecutionMethod::default(),
+			max_runtime_instances: 8,
+			default_heap_pages: None,
+			runtime_cache_size: 2,
+		}
+	}
+}
diff --git a/substrate/client/service/src/lib.rs b/substrate/client/service/src/lib.rs
index 9e8aed39c89..251eef97be8 100644
--- a/substrate/client/service/src/lib.rs
+++ b/substrate/client/service/src/lib.rs
@@ -83,6 +83,8 @@ pub use sc_chain_spec::{
 	Properties,
 };
 
+use crate::config::RpcConfiguration;
+use prometheus_endpoint::Registry;
 pub use sc_consensus::ImportQueue;
 pub use sc_executor::NativeExecutionDispatch;
 pub use sc_network_sync::WarpSyncConfig;
@@ -95,6 +97,7 @@ pub use sc_transaction_pool_api::{error::IntoPoolError, InPoolTransaction, Trans
 #[doc(hidden)]
 pub use std::{ops::Deref, result::Result, sync::Arc};
 pub use task_manager::{SpawnTaskHandle, Task, TaskManager, TaskRegistry, DEFAULT_GROUP_NAME};
+use tokio::runtime::Handle;
 
 const DEFAULT_PROTOCOL_ID: &str = "sup";
 
@@ -376,7 +379,9 @@ mod waiting {
 
 /// Starts RPC servers.
 pub fn start_rpc_servers<R>(
-	config: &Configuration,
+	rpc_configuration: &RpcConfiguration,
+	registry: Option<&Registry>,
+	tokio_handle: &Handle,
 	gen_rpc_module: R,
 	rpc_id_provider: Option<Box<dyn sc_rpc_server::SubscriptionIdProvider>>,
 ) -> Result<Box<dyn std::any::Any + Send + Sync>, error::Error>
@@ -384,50 +389,51 @@ where
 	R: Fn() -> Result<RpcModule<()>, Error>,
 {
 	let endpoints: Vec<sc_rpc_server::RpcEndpoint> = if let Some(endpoints) =
-		config.rpc_addr.as_ref()
+		rpc_configuration.addr.as_ref()
 	{
 		endpoints.clone()
 	} else {
-		let ipv6 = SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, config.rpc_port, 0, 0));
-		let ipv4 = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, config.rpc_port));
+		let ipv6 =
+			SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, rpc_configuration.port, 0, 0));
+		let ipv4 = SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, rpc_configuration.port));
 
 		vec![
 			sc_rpc_server::RpcEndpoint {
-				batch_config: config.rpc_batch_config,
-				cors: config.rpc_cors.clone(),
+				batch_config: rpc_configuration.batch_config,
+				cors: rpc_configuration.cors.clone(),
 				listen_addr: ipv4,
-				max_buffer_capacity_per_connection: config.rpc_message_buffer_capacity,
-				max_connections: config.rpc_max_connections,
-				max_payload_in_mb: config.rpc_max_request_size,
-				max_payload_out_mb: config.rpc_max_response_size,
-				max_subscriptions_per_connection: config.rpc_max_subs_per_conn,
-				rpc_methods: config.rpc_methods.into(),
-				rate_limit: config.rpc_rate_limit,
-				rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers,
-				rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(),
+				max_buffer_capacity_per_connection: rpc_configuration.message_buffer_capacity,
+				max_connections: rpc_configuration.max_connections,
+				max_payload_in_mb: rpc_configuration.max_request_size,
+				max_payload_out_mb: rpc_configuration.max_response_size,
+				max_subscriptions_per_connection: rpc_configuration.max_subs_per_conn,
+				rpc_methods: rpc_configuration.methods.into(),
+				rate_limit: rpc_configuration.rate_limit,
+				rate_limit_trust_proxy_headers: rpc_configuration.rate_limit_trust_proxy_headers,
+				rate_limit_whitelisted_ips: rpc_configuration.rate_limit_whitelisted_ips.clone(),
 				retry_random_port: true,
 				is_optional: false,
 			},
 			sc_rpc_server::RpcEndpoint {
-				batch_config: config.rpc_batch_config,
-				cors: config.rpc_cors.clone(),
+				batch_config: rpc_configuration.batch_config,
+				cors: rpc_configuration.cors.clone(),
 				listen_addr: ipv6,
-				max_buffer_capacity_per_connection: config.rpc_message_buffer_capacity,
-				max_connections: config.rpc_max_connections,
-				max_payload_in_mb: config.rpc_max_request_size,
-				max_payload_out_mb: config.rpc_max_response_size,
-				max_subscriptions_per_connection: config.rpc_max_subs_per_conn,
-				rpc_methods: config.rpc_methods.into(),
-				rate_limit: config.rpc_rate_limit,
-				rate_limit_trust_proxy_headers: config.rpc_rate_limit_trust_proxy_headers,
-				rate_limit_whitelisted_ips: config.rpc_rate_limit_whitelisted_ips.clone(),
+				max_buffer_capacity_per_connection: rpc_configuration.message_buffer_capacity,
+				max_connections: rpc_configuration.max_connections,
+				max_payload_in_mb: rpc_configuration.max_request_size,
+				max_payload_out_mb: rpc_configuration.max_response_size,
+				max_subscriptions_per_connection: rpc_configuration.max_subs_per_conn,
+				rpc_methods: rpc_configuration.methods.into(),
+				rate_limit: rpc_configuration.rate_limit,
+				rate_limit_trust_proxy_headers: rpc_configuration.rate_limit_trust_proxy_headers,
+				rate_limit_whitelisted_ips: rpc_configuration.rate_limit_whitelisted_ips.clone(),
 				retry_random_port: true,
 				is_optional: true,
 			},
 		]
 	};
 
-	let metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?;
+	let metrics = sc_rpc_server::RpcMetrics::new(registry)?;
 	let rpc_api = gen_rpc_module()?;
 
 	let server_config = sc_rpc_server::Config {
@@ -435,7 +441,7 @@ where
 		rpc_api,
 		metrics,
 		id_provider: rpc_id_provider,
-		tokio_handle: config.tokio_handle.clone(),
+		tokio_handle: tokio_handle.clone(),
 	};
 
 	// TODO: https://github.com/paritytech/substrate/issues/13773
@@ -443,7 +449,7 @@ where
 	// `block_in_place` is a hack to allow callers to call `block_on` prior to
 	// calling `start_rpc_servers`.
 	match tokio::task::block_in_place(|| {
-		config.tokio_handle.block_on(sc_rpc_server::start_server(server_config))
+		tokio_handle.block_on(sc_rpc_server::start_server(server_config))
 	}) {
 		Ok(server) => Ok(Box::new(waiting::Server(Some(server)))),
 		Err(e) => Err(Error::Application(e)),
diff --git a/substrate/client/service/src/metrics.rs b/substrate/client/service/src/metrics.rs
index a411a83a784..2bcb83f4197 100644
--- a/substrate/client/service/src/metrics.rs
+++ b/substrate/client/service/src/metrics.rs
@@ -16,9 +16,6 @@
 // You should have received a copy of the GNU General Public License
 // along with this program. If not, see <https://www.gnu.org/licenses/>.
 
-use std::time::SystemTime;
-
-use crate::config::Configuration;
 use futures_timer::Delay;
 use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64};
 use sc_client_api::{ClientInfo, UsageProvider};
@@ -31,7 +28,7 @@ use sp_api::ProvideRuntimeApi;
 use sp_runtime::traits::{Block, NumberFor, SaturatedConversion, UniqueSaturatedInto};
 use std::{
 	sync::Arc,
-	time::{Duration, Instant},
+	time::{Duration, Instant, SystemTime},
 };
 
 struct PrometheusMetrics {
@@ -149,26 +146,24 @@ impl MetricsService {
 	pub fn with_prometheus(
 		telemetry: Option<TelemetryHandle>,
 		registry: &Registry,
-		config: &Configuration,
+		role: Role,
+		node_name: &str,
+		impl_version: &str,
 	) -> Result<Self, PrometheusError> {
-		let role_bits = match config.role {
+		let role_bits = match role {
 			Role::Full => 1u64,
 			// 2u64 used to represent light client role
 			Role::Authority { .. } => 4u64,
 		};
 
-		PrometheusMetrics::setup(
-			registry,
-			&config.network.node_name,
-			&config.impl_version,
-			role_bits,
-		)
-		.map(|p| MetricsService {
-			metrics: Some(p),
-			last_total_bytes_inbound: 0,
-			last_total_bytes_outbound: 0,
-			last_update: Instant::now(),
-			telemetry,
+		PrometheusMetrics::setup(registry, node_name, impl_version, role_bits).map(|p| {
+			MetricsService {
+				metrics: Some(p),
+				last_total_bytes_inbound: 0,
+				last_total_bytes_outbound: 0,
+				last_update: Instant::now(),
+				telemetry,
+			}
 		})
 	}
 
diff --git a/substrate/client/service/test/src/lib.rs b/substrate/client/service/test/src/lib.rs
index 5b9bb537563..d64581480cd 100644
--- a/substrate/client/service/test/src/lib.rs
+++ b/substrate/client/service/test/src/lib.rs
@@ -29,7 +29,10 @@ use sc_network::{
 use sc_network_sync::SyncingService;
 use sc_service::{
 	client::Client,
-	config::{BasePath, DatabaseSource, KeystoreConfig, RpcBatchRequestConfig},
+	config::{
+		BasePath, DatabaseSource, ExecutorConfiguration, KeystoreConfig, RpcBatchRequestConfig,
+		RpcConfiguration,
+	},
 	BlocksPruning, ChainSpecExtension, Configuration, Error, GenericChainSpec, Role,
 	SpawnTaskHandle, TaskManager,
 };
@@ -235,36 +238,35 @@ fn node_config<E: ChainSpecExtension + Clone + 'static + Send + Sync>(
 		state_pruning: Default::default(),
 		blocks_pruning: BlocksPruning::KeepFinalized,
 		chain_spec: Box::new((*spec).clone()),
-		wasm_method: Default::default(),
+		executor: ExecutorConfiguration::default(),
 		wasm_runtime_overrides: Default::default(),
-		rpc_addr: Default::default(),
-		rpc_max_connections: Default::default(),
-		rpc_cors: None,
-		rpc_methods: Default::default(),
-		rpc_max_request_size: Default::default(),
-		rpc_max_response_size: Default::default(),
-		rpc_id_provider: Default::default(),
-		rpc_max_subs_per_conn: Default::default(),
-		rpc_port: 9944,
-		rpc_message_buffer_capacity: Default::default(),
-		rpc_batch_config: RpcBatchRequestConfig::Unlimited,
-		rpc_rate_limit: None,
-		rpc_rate_limit_whitelisted_ips: Default::default(),
-		rpc_rate_limit_trust_proxy_headers: Default::default(),
+		rpc: RpcConfiguration {
+			addr: Default::default(),
+			max_connections: Default::default(),
+			cors: None,
+			methods: Default::default(),
+			max_request_size: Default::default(),
+			max_response_size: Default::default(),
+			id_provider: Default::default(),
+			max_subs_per_conn: Default::default(),
+			port: 9944,
+			message_buffer_capacity: Default::default(),
+			batch_config: RpcBatchRequestConfig::Unlimited,
+			rate_limit: None,
+			rate_limit_whitelisted_ips: Default::default(),
+			rate_limit_trust_proxy_headers: Default::default(),
+		},
 		prometheus_config: None,
 		telemetry_endpoints: None,
-		default_heap_pages: None,
 		offchain_worker: Default::default(),
 		force_authoring: false,
 		disable_grandpa: false,
 		dev_key_seed: key_seed,
 		tracing_targets: None,
 		tracing_receiver: Default::default(),
-		max_runtime_instances: 8,
 		announce_block: true,
 		base_path: BasePath::new(root.clone()),
 		data_path: root,
-		runtime_cache_size: 2,
 	}
 }
 
diff --git a/templates/minimal/node/src/service.rs b/templates/minimal/node/src/service.rs
index ce3afe1e6b7..a42eb10ccec 100644
--- a/templates/minimal/node/src/service.rs
+++ b/templates/minimal/node/src/service.rs
@@ -61,7 +61,7 @@ pub fn new_partial(config: &Configuration) -> Result<Service, ServiceError> {
 		})
 		.transpose()?;
 
-	let executor = sc_service::new_wasm_executor(config);
+	let executor = sc_service::new_wasm_executor(&config.executor);
 
 	let (client, backend, keystore_container, task_manager) =
 		sc_service::new_full_parts::<Block, RuntimeApi, _>(
diff --git a/templates/parachain/node/src/command.rs b/templates/parachain/node/src/command.rs
index f346ae4386a..fa94d50a8be 100644
--- a/templates/parachain/node/src/command.rs
+++ b/templates/parachain/node/src/command.rs
@@ -307,15 +307,9 @@ impl CliConfiguration<Self> for RelayChainCli {
 		self.base.base.prometheus_config(default_listen_port, chain_spec)
 	}
 
-	fn init<F>(
-		&self,
-		_support_url: &String,
-		_impl_version: &String,
-		_logger_hook: F,
-		_config: &sc_service::Configuration,
-	) -> Result<()>
+	fn init<F>(&self, _support_url: &String, _impl_version: &String, _logger_hook: F) -> Result<()>
 	where
-		F: FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration),
+		F: FnOnce(&mut sc_cli::LoggerBuilder),
 	{
 		unreachable!("PolkadotCli is never initialized; qed");
 	}
diff --git a/templates/parachain/node/src/service.rs b/templates/parachain/node/src/service.rs
index b46b6ecfde1..fc0bcba4c0d 100644
--- a/templates/parachain/node/src/service.rs
+++ b/templates/parachain/node/src/service.rs
@@ -77,15 +77,16 @@ pub fn new_partial(config: &Configuration) -> Result<Service, sc_service::Error>
 		.transpose()?;
 
 	let heap_pages = config
+		.executor
 		.default_heap_pages
 		.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static { extra_pages: h as _ });
 
 	let executor = ParachainExecutor::builder()
-		.with_execution_method(config.wasm_method)
+		.with_execution_method(config.executor.wasm_method)
 		.with_onchain_heap_alloc_strategy(heap_pages)
 		.with_offchain_heap_alloc_strategy(heap_pages)
-		.with_max_runtime_instances(config.max_runtime_instances)
-		.with_runtime_cache_size(config.runtime_cache_size)
+		.with_max_runtime_instances(config.executor.max_runtime_instances)
+		.with_runtime_cache_size(config.executor.runtime_cache_size)
 		.build();
 
 	let (client, backend, keystore_container, task_manager) =
diff --git a/templates/solochain/node/src/service.rs b/templates/solochain/node/src/service.rs
index 2b43ecfa1ce..7d37c5ce87f 100644
--- a/templates/solochain/node/src/service.rs
+++ b/templates/solochain/node/src/service.rs
@@ -48,7 +48,7 @@ pub fn new_partial(config: &Configuration) -> Result<Service, ServiceError> {
 		})
 		.transpose()?;
 
-	let executor = sc_service::new_wasm_executor::<sp_io::SubstrateHostFunctions>(config);
+	let executor = sc_service::new_wasm_executor::<sp_io::SubstrateHostFunctions>(&config.executor);
 	let (client, backend, keystore_container, task_manager) =
 		sc_service::new_full_parts::<Block, RuntimeApi, _>(
 			config,
@@ -201,7 +201,7 @@ pub fn new_full<
 		);
 	}
 
-	let role = config.role.clone();
+	let role = config.role;
 	let force_authoring = config.force_authoring;
 	let backoff_authoring_blocks: Option<()> = None;
 	let name = config.network.node_name.clone();
-- 
GitLab