diff --git a/cumulus/client/relay-chain-minimal-node/src/lib.rs b/cumulus/client/relay-chain-minimal-node/src/lib.rs
index 732a242e72957214ce8161d5f785d20b8c2484b3..e65a78f16d79bf0f76c18af771a72c29308a6c4d 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 025ac7a81a21c6a23622cac37882994309c54e85..afe83a2a12f91dcaa2ee61230ee79a0203175748 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 15d21235d1a15207203c950c515c65678ce4c932..349dc01d8a4f25e16fe2a4f19ed772096cd981dd 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 55e042aed87e7d3879eb3e4c412d52f13d4ca358..8e19cf304b07cbf0677eab98b10b2b246dbcf536 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 739c2d4bda160e588db43a82082b459db6f3dab9..220b0449f3392727228f99b3d10f2ccbc2165693 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 bc0fe9090d38ed533c784b58598f856ed5738e10..a600dcce3d66e789cdcd8164fa9c51aa3d69d306 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 a907d310c10529ffc91056e8fbb5b100f557bbcb..ab91c6b9b92b12b41ca6584e087533ec02ef5743 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 a4e58253bb1706c9face2ded9dbbfb66a93ebb40..b12387884861fd5694bc82884a8543c379ead50a 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 0000000000000000000000000000000000000000..35a72f65fb1e20997dca27cec035b2b5aa9e7fb0
--- /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 9ac9d8b4f67dbe5f1dd825c34372c1594a3508de..de883d1051f50e1cf8a24cf83eef4f48d771073a 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 9a71a4ec585d4621f5cfd141f55ad9fb2a5ab76a..efec081427f4b6a4eab597b955e56e75eba9972a 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 8fdcc7261b55fc00c4c69a726eac967b7a8f072e..4f63473f632d7680309337bd71b38de514b40f22 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 e0e25707e31b06b7d53f31bde9a0b809211d8dce..b9e5e55be8ef3b273e6d05b46be8490b70838f4b 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 7245b46e2f7d01990a39d4403b27f7db50b793b6..f47baf2644e7e6512597b05d07630f9b8ec743f3 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 7c235847761132822952d9942fb9b382d2a99b83..59238b3307cf25bced2bfa3c5b99391214954bca 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 1bb9fec0e27690f9b7ec21918f3dce329100de3a..4db70f99c803bf2271a3384de64e347f1ce3a2ab 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 0e12c7a2a2d3742ef3d9aa3681f900036e67a467..cdd637888114af4e7a3f8ab8213fc9c8b2c9f730 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 b0dbccfa634c881afe7b90b5e3a98a2fdbab6904..9c5834d8d80ae5ec2b929691d29c227036642f60 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 11b7a7924c46ad90c4662221db7c50866e81e195..e2218b37f0887433d84883447cb1f81bb03de48c 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 9a6c3fde37d93733dd66b015be33e119fed025d5..0dc28d1361cb0dbd21e838fd2db39733a071471f 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 8387f818e68bc545e4cc355efee08ac7f562ed82..6f65c2e2d81b12172637a240d38f9da4d7f00b48 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 9e8aed39c897c6e48ec82f1f63f697aa889d4273..251eef97be844933c6020b57b16c1324a6387975 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 a411a83a784e92f5fa424fd7e68e1d410d948ada..2bcb83f4197900659b6f5750f77b9e4cdc177564 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 5b9bb537563d9505295c03c35979185b30d20dcd..d64581480cdb862ba4d89fbcb8aa464bc8a0a53d 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 ce3afe1e6b761e8afe3fdf92c164052a683d82e4..a42eb10ccec652ee66b89549009a59d09d37ef6d 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 f346ae4386a68334fa9d4395ce6b73f85638f33c..fa94d50a8bedd12e7c049c1283587940c8468e68 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 b46b6ecfde18384836953797cc58f915ca254753..fc0bcba4c0d1a4fab4816bb62bf6b173e3c61296 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 2b43ecfa1ce2a708de83585baf0461e08f76f784..7d37c5ce87f8cd10f2103359f4c3a06b788b1648 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();