// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with this program. If not, see . //! Configuration trait for a CLI based on substrate use crate::{ arg_enums::Database, error::Result, DatabaseParams, ImportParams, KeystoreParams, NetworkParams, NodeKeyParams, OffchainWorkerParams, PruningParams, SharedParams, SubstrateCli, }; use log::warn; use names::{Generator, Name}; use sc_service::{ config::{ BasePath, Configuration, DatabaseSource, KeystoreConfig, NetworkConfiguration, NodeKeyConfig, OffchainWorkerConfig, OutputFormat, PrometheusConfig, PruningMode, Role, RpcBatchRequestConfig, RpcMethods, TelemetryEndpoints, TransactionPoolOptions, WasmExecutionMethod, }, BlocksPruning, ChainSpec, TracingReceiver, }; use sc_tracing::logging::LoggerBuilder; use std::{net::SocketAddr, num::NonZeroU32, path::PathBuf}; /// The maximum number of characters for a node name. pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64; /// Default sub directory to store network config. pub(crate) const DEFAULT_NETWORK_CONFIG_PATH: &str = "network"; /// The recommended open file descriptor limit to be configured for the process. const RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT: u64 = 10_000; /// The default port. pub const RPC_DEFAULT_PORT: u16 = 9944; /// The default max number of subscriptions per connection. pub const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024; /// The default max request size in MB. pub const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15; /// The default max response size in MB. pub const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 15; /// The default concurrent connection limit. pub const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 100; /// The default number of messages the RPC server /// is allowed to keep in memory per connection. pub const RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN: u32 = 64; /// Default configuration values used by Substrate /// /// These values will be used by [`CliConfiguration`] to set /// default values for e.g. the listen port or the RPC port. pub trait DefaultConfigurationValues { /// The port Substrate should listen on for p2p connections. /// /// By default this is `30333`. fn p2p_listen_port() -> u16 { 30333 } /// The port Substrate should listen on for JSON-RPC connections. /// /// By default this is `9944`. fn rpc_listen_port() -> u16 { RPC_DEFAULT_PORT } /// The port Substrate should listen on for prometheus connections. /// /// By default this is `9615`. fn prometheus_listen_port() -> u16 { 9615 } } impl DefaultConfigurationValues for () {} /// A trait that allows converting an object to a Configuration pub trait CliConfiguration: Sized { /// Get the SharedParams for this object fn shared_params(&self) -> &SharedParams; /// Get the ImportParams for this object fn import_params(&self) -> Option<&ImportParams> { None } /// Get the PruningParams for this object fn pruning_params(&self) -> Option<&PruningParams> { self.import_params().map(|x| &x.pruning_params) } /// Get the KeystoreParams for this object fn keystore_params(&self) -> Option<&KeystoreParams> { None } /// Get the NetworkParams for this object fn network_params(&self) -> Option<&NetworkParams> { None } /// Get a reference to `OffchainWorkerParams` for this object. fn offchain_worker_params(&self) -> Option<&OffchainWorkerParams> { None } /// Get the NodeKeyParams for this object fn node_key_params(&self) -> Option<&NodeKeyParams> { self.network_params().map(|x| &x.node_key_params) } /// Get the DatabaseParams for this object fn database_params(&self) -> Option<&DatabaseParams> { self.import_params().map(|x| &x.database_params) } /// Get the base path of the configuration (if any) /// /// By default this is retrieved from `SharedParams`. fn base_path(&self) -> Result> { self.shared_params().base_path() } /// Returns `true` if the node is for development or not /// /// By default this is retrieved from `SharedParams`. fn is_dev(&self) -> Result { Ok(self.shared_params().is_dev()) } /// Gets the role /// /// By default this is `Role::Full`. fn role(&self, _is_dev: bool) -> Result { Ok(Role::Full) } /// Get the transaction pool options /// /// By default this is `TransactionPoolOptions::default()`. fn transaction_pool(&self, _is_dev: bool) -> Result { Ok(Default::default()) } /// Get the network configuration /// /// By default this is retrieved from `NetworkParams` if it is available otherwise it creates /// a default `NetworkConfiguration` based on `node_name`, `client_id`, `node_key` and /// `net_config_dir`. fn network_config( &self, chain_spec: &Box, is_dev: bool, is_validator: bool, net_config_dir: PathBuf, client_id: &str, node_name: &str, node_key: NodeKeyConfig, default_listen_port: u16, ) -> Result { Ok(if let Some(network_params) = self.network_params() { network_params.network_config( chain_spec, is_dev, is_validator, Some(net_config_dir), client_id, node_name, node_key, default_listen_port, ) } else { NetworkConfiguration::new(node_name, client_id, node_key, Some(net_config_dir)) }) } /// Get the keystore configuration. /// /// By default this is retrieved from `KeystoreParams` if it is available. Otherwise it uses /// `KeystoreConfig::InMemory`. fn keystore_config(&self, config_dir: &PathBuf) -> Result { self.keystore_params() .map(|x| x.keystore_config(config_dir)) .unwrap_or_else(|| Ok(KeystoreConfig::InMemory)) } /// Get the database cache size. /// /// By default this is retrieved from `DatabaseParams` if it is available. Otherwise its `None`. fn database_cache_size(&self) -> Result> { Ok(self.database_params().map(|x| x.database_cache_size()).unwrap_or_default()) } /// Get the database backend variant. /// /// By default this is retrieved from `DatabaseParams` if it is available. Otherwise its `None`. fn database(&self) -> Result> { Ok(self.database_params().and_then(|x| x.database())) } /// Get the database configuration object for the parameters provided fn database_config( &self, base_path: &PathBuf, cache_size: usize, database: Database, ) -> Result { let role_dir = "full"; let rocksdb_path = base_path.join("db").join(role_dir); let paritydb_path = base_path.join("paritydb").join(role_dir); Ok(match database { #[cfg(feature = "rocksdb")] Database::RocksDb => DatabaseSource::RocksDb { path: rocksdb_path, cache_size }, Database::ParityDb => DatabaseSource::ParityDb { path: paritydb_path }, Database::ParityDbDeprecated => { eprintln!( "WARNING: \"paritydb-experimental\" database setting is deprecated and will be removed in future releases. \ Please update your setup to use the new value: \"paritydb\"." ); DatabaseSource::ParityDb { path: paritydb_path } }, Database::Auto => DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size }, }) } /// Get the trie cache maximum size. /// /// By default this is retrieved from `ImportParams` if it is available. Otherwise its `0`. /// If `None` is returned the trie cache is disabled. fn trie_cache_maximum_size(&self) -> Result> { Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default()) } /// Get the state pruning mode. /// /// By default this is retrieved from `PruningMode` if it is available. Otherwise its /// `PruningMode::default()`. fn state_pruning(&self) -> Result> { self.pruning_params() .map(|x| x.state_pruning()) .unwrap_or_else(|| Ok(Default::default())) } /// Get the block pruning mode. /// /// By default this is retrieved from `block_pruning` if it is available. Otherwise its /// `BlocksPruning::KeepFinalized`. fn blocks_pruning(&self) -> Result { self.pruning_params() .map(|x| x.blocks_pruning()) .unwrap_or_else(|| Ok(BlocksPruning::KeepFinalized)) } /// Get the chain ID (string). /// /// By default this is retrieved from `SharedParams`. fn chain_id(&self, is_dev: bool) -> Result { Ok(self.shared_params().chain_id(is_dev)) } /// Get the name of the node. /// /// By default a random name is generated. fn node_name(&self) -> Result { Ok(generate_node_name()) } /// Get the WASM execution method. /// /// By default this is retrieved from `ImportParams` if it is available. Otherwise its /// `WasmExecutionMethod::default()`. fn wasm_method(&self) -> Result { Ok(self.import_params().map(|x| x.wasm_method()).unwrap_or_default()) } /// Get the path where WASM overrides live. /// /// By default this is `None`. fn wasm_runtime_overrides(&self) -> Option { self.import_params().map(|x| x.wasm_runtime_overrides()).unwrap_or_default() } /// Get the RPC address. fn rpc_addr(&self, _default_listen_port: u16) -> Result> { Ok(None) } /// Returns the RPC method set to expose. /// /// By default this is `RpcMethods::Auto` (unsafe RPCs are denied iff /// `rpc_external` returns true, respectively). fn rpc_methods(&self) -> Result { Ok(Default::default()) } /// Get the maximum number of RPC server connections. fn rpc_max_connections(&self) -> Result { Ok(RPC_DEFAULT_MAX_CONNECTIONS) } /// Get the RPC cors (`None` if disabled) /// /// By default this is `Some(Vec::new())`. fn rpc_cors(&self, _is_dev: bool) -> Result>> { Ok(Some(Vec::new())) } /// Get maximum RPC request payload size. fn rpc_max_request_size(&self) -> Result { Ok(RPC_DEFAULT_MAX_REQUEST_SIZE_MB) } /// Get maximum RPC response payload size. fn rpc_max_response_size(&self) -> Result { Ok(RPC_DEFAULT_MAX_RESPONSE_SIZE_MB) } /// Get maximum number of subscriptions per connection. fn rpc_max_subscriptions_per_connection(&self) -> Result { Ok(RPC_DEFAULT_MAX_SUBS_PER_CONN) } /// The number of messages the RPC server is allowed to keep in memory per connection. fn rpc_buffer_capacity_per_connection(&self) -> Result { Ok(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN) } /// RPC server batch request configuration. fn rpc_batch_config(&self) -> Result { Ok(RpcBatchRequestConfig::Unlimited) } /// RPC rate limit configuration. fn rpc_rate_limit(&self) -> Result> { Ok(None) } /// Get the prometheus configuration (`None` if disabled) /// /// By default this is `None`. fn prometheus_config( &self, _default_listen_port: u16, _chain_spec: &Box, ) -> Result> { Ok(None) } /// Get the telemetry endpoints (if any) /// /// By default this is retrieved from the chain spec loaded by `load_spec`. fn telemetry_endpoints( &self, chain_spec: &Box, ) -> Result> { Ok(chain_spec.telemetry_endpoints().clone()) } /// Get the default value for heap pages /// /// By default this is `None`. fn default_heap_pages(&self) -> Result> { Ok(None) } /// Returns an offchain worker config wrapped in `Ok(_)` /// /// By default offchain workers are disabled. fn offchain_worker(&self, role: &Role) -> Result { self.offchain_worker_params() .map(|x| x.offchain_worker(role)) .unwrap_or_else(|| Ok(OffchainWorkerConfig::default())) } /// Returns `Ok(true)` if authoring should be forced /// /// By default this is `false`. fn force_authoring(&self) -> Result { Ok(Default::default()) } /// Returns `Ok(true)` if grandpa should be disabled /// /// By default this is `false`. fn disable_grandpa(&self) -> Result { Ok(Default::default()) } /// Get the development key seed from the current object /// /// By default this is `None`. fn dev_key_seed(&self, _is_dev: bool) -> Result> { Ok(Default::default()) } /// Get the tracing targets from the current object (if any) /// /// By default this is retrieved from [`SharedParams`] if it is available. Otherwise its /// `None`. fn tracing_targets(&self) -> Result> { Ok(self.shared_params().tracing_targets()) } /// Get the TracingReceiver value from the current object /// /// By default this is retrieved from [`SharedParams`] if it is available. Otherwise its /// `TracingReceiver::default()`. fn tracing_receiver(&self) -> Result { Ok(self.shared_params().tracing_receiver()) } /// Get the node key from the current object /// /// By default this is retrieved from `NodeKeyParams` if it is available. Otherwise its /// `NodeKeyConfig::default()`. fn node_key(&self, net_config_dir: &PathBuf) -> Result { self.node_key_params() .map(|x| x.node_key(net_config_dir)) .unwrap_or_else(|| Ok(Default::default())) } /// Get maximum runtime instances /// /// By default this is `None`. fn max_runtime_instances(&self) -> Result> { Ok(Default::default()) } /// Get maximum different runtimes in cache /// /// By default this is `2`. fn runtime_cache_size(&self) -> Result { Ok(2) } /// Activate or not the automatic announcing of blocks after import /// /// By default this is `false`. fn announce_block(&self) -> Result { Ok(true) } /// Create a Configuration object from the current object fn create_configuration( &self, cli: &C, tokio_handle: tokio::runtime::Handle, ) -> Result { let is_dev = self.is_dev()?; let chain_id = self.chain_id(is_dev)?; let chain_spec = cli.load_spec(&chain_id)?; let base_path = self .base_path()? .unwrap_or_else(|| BasePath::from_project("", "", &C::executable_name())); let config_dir = base_path.config_dir(chain_spec.id()); let net_config_dir = config_dir.join(DEFAULT_NETWORK_CONFIG_PATH); let client_id = C::client_id(); let database_cache_size = self.database_cache_size()?.unwrap_or(1024); let database = self.database()?.unwrap_or( #[cfg(feature = "rocksdb")] { Database::RocksDb }, #[cfg(not(feature = "rocksdb"))] { Database::ParityDb }, ); let node_key = self.node_key(&net_config_dir)?; let role = self.role(is_dev)?; let max_runtime_instances = self.max_runtime_instances()?.unwrap_or(8); let is_validator = role.is_authority(); let keystore = self.keystore_config(&config_dir)?; let telemetry_endpoints = self.telemetry_endpoints(&chain_spec)?; let runtime_cache_size = self.runtime_cache_size()?; Ok(Configuration { impl_name: C::impl_name(), impl_version: C::impl_version(), tokio_handle, transaction_pool: self.transaction_pool(is_dev)?, network: self.network_config( &chain_spec, is_dev, is_validator, net_config_dir, client_id.as_str(), self.node_name()?.as_str(), node_key, DCV::p2p_listen_port(), )?, keystore, database: self.database_config(&config_dir, database_cache_size, database)?, data_path: config_dir, trie_cache_maximum_size: self.trie_cache_maximum_size()?, state_pruning: self.state_pruning()?, blocks_pruning: self.blocks_pruning()?, wasm_method: self.wasm_method()?, wasm_runtime_overrides: self.wasm_runtime_overrides(), rpc_addr: self.rpc_addr(DCV::rpc_listen_port())?, 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()?, 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()?, dev_key_seed: self.dev_key_seed(is_dev)?, tracing_targets: self.tracing_targets()?, tracing_receiver: self.tracing_receiver()?, chain_spec, max_runtime_instances, announce_block: self.announce_block()?, role, base_path, informant_output_format: OutputFormat { enable_color: !self.disable_log_color()? }, runtime_cache_size, }) } /// Get the filters for the logging. /// /// This should be a list of comma-separated values. /// Example: `foo=trace,bar=debug,baz=info` /// /// By default this is retrieved from `SharedParams`. fn log_filters(&self) -> Result { Ok(self.shared_params().log_filters().join(",")) } /// Should the detailed log output be enabled. fn detailed_log_output(&self) -> Result { Ok(self.shared_params().detailed_log_output()) } /// Is log reloading enabled? fn enable_log_reloading(&self) -> Result { Ok(self.shared_params().enable_log_reloading()) } /// Should the log color output be disabled? fn disable_log_color(&self) -> Result { Ok(self.shared_params().disable_log_color()) } /// Initialize substrate. This must be done only once per process. /// /// This method: /// /// 1. Sets the panic handler /// 2. Optionally customize logger/profiling /// 2. Initializes the logger /// 3. Raises the FD limit /// /// The `logger_hook` closure is executed before the logger is constructed /// and initialized. It is useful for setting up a custom profiler. /// /// Example: /// ``` /// use sc_tracing::{SpanDatum, TraceEvent}; /// struct TestProfiler; /// /// impl sc_tracing::TraceHandler for TestProfiler { /// fn handle_span(&self, sd: &SpanDatum) {} /// fn handle_event(&self, _event: &TraceEvent) {} /// }; /// /// fn logger_hook() -> impl FnOnce(&mut sc_cli::LoggerBuilder, &sc_service::Configuration) -> () { /// |logger_builder, config| { /// logger_builder.with_custom_profiling(Box::new(TestProfiler{})); /// } /// } /// ``` fn init( &self, support_url: &String, impl_version: &String, logger_hook: F, config: &Configuration, ) -> Result<()> where F: FnOnce(&mut LoggerBuilder, &Configuration), { sp_panic_handler::set(support_url, impl_version); let mut logger = LoggerBuilder::new(self.log_filters()?); logger .with_log_reloading(self.enable_log_reloading()?) .with_detailed_output(self.detailed_log_output()?); if let Some(tracing_targets) = self.tracing_targets()? { let tracing_receiver = self.tracing_receiver()?; logger.with_profiling(tracing_receiver, tracing_targets); } if self.disable_log_color()? { logger.with_colors(false); } // Call hook for custom profiling setup. logger_hook(&mut logger, config); logger.init()?; match fdlimit::raise_fd_limit() { Ok(fdlimit::Outcome::LimitRaised { to, .. }) => if to < RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT { warn!( "Low open file descriptor limit configured for the process. \ Current value: {:?}, recommended value: {:?}.", to, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT, ); }, Ok(fdlimit::Outcome::Unsupported) => { // Unsupported platform (non-Linux) }, Err(error) => { warn!( "Failed to configure file descriptor limit for the process: \ {}, recommended value: {:?}.", error, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT, ); }, } Ok(()) } } /// Generate a valid random name for the node pub fn generate_node_name() -> String { loop { let node_name = Generator::with_naming(Name::Numbered) .next() .expect("RNG is available on all supported platforms; qed"); let count = node_name.chars().count(); if count < NODE_NAME_MAX_LENGTH { return node_name } } }