1use crate::{
8 error::{Error, Result},
9 params::{
10 ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, RpcEndpoint,
11 SharedParams, TransactionPoolParams,
12 },
13 CliConfiguration, PrometheusParams, RpcParams, RuntimeParams, TelemetryParams,
14};
15use clap::Parser;
16use regex::Regex;
17use soil_service::{
18 config::{
19 BasePath, IpNetwork, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions,
20 },
21 ChainSpec, Role,
22};
23use soil_telemetry::TelemetryEndpoints;
24use std::num::NonZeroU32;
25
26#[derive(Debug, Clone, Parser)]
28pub struct RunCmd {
29 #[arg(long)]
35 pub validator: bool,
36
37 #[arg(long)]
42 pub no_grandpa: bool,
43
44 #[arg(long, value_name = "NAME")]
48 pub name: Option<String>,
49
50 #[allow(missing_docs)]
51 #[clap(flatten)]
52 pub rpc_params: RpcParams,
53
54 #[allow(missing_docs)]
55 #[clap(flatten)]
56 pub telemetry_params: TelemetryParams,
57
58 #[allow(missing_docs)]
59 #[clap(flatten)]
60 pub prometheus_params: PrometheusParams,
61
62 #[allow(missing_docs)]
63 #[clap(flatten)]
64 pub runtime_params: RuntimeParams,
65
66 #[allow(missing_docs)]
67 #[clap(flatten)]
68 pub offchain_worker_params: OffchainWorkerParams,
69
70 #[allow(missing_docs)]
71 #[clap(flatten)]
72 pub shared_params: SharedParams,
73
74 #[allow(missing_docs)]
75 #[clap(flatten)]
76 pub import_params: ImportParams,
77
78 #[allow(missing_docs)]
79 #[clap(flatten)]
80 pub network_params: NetworkParams,
81
82 #[allow(missing_docs)]
83 #[clap(flatten)]
84 pub pool_config: TransactionPoolParams,
85
86 #[allow(missing_docs)]
87 #[clap(flatten)]
88 pub keystore_params: KeystoreParams,
89
90 #[arg(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])]
94 pub alice: bool,
95
96 #[arg(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])]
100 pub bob: bool,
101
102 #[arg(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])]
106 pub charlie: bool,
107
108 #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])]
112 pub dave: bool,
113
114 #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])]
118 pub eve: bool,
119
120 #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])]
124 pub ferdie: bool,
125
126 #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])]
130 pub one: bool,
131
132 #[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])]
136 pub two: bool,
137
138 #[arg(long)]
140 pub force_authoring: bool,
141
142 #[arg(long, conflicts_with = "base_path")]
152 pub tmp: bool,
153}
154
155impl RunCmd {
156 pub fn get_keyring(&self) -> Option<subsoil::keyring::Sr25519Keyring> {
158 use subsoil::keyring::Sr25519Keyring::*;
159
160 if self.alice {
161 Some(Alice)
162 } else if self.bob {
163 Some(Bob)
164 } else if self.charlie {
165 Some(Charlie)
166 } else if self.dave {
167 Some(Dave)
168 } else if self.eve {
169 Some(Eve)
170 } else if self.ferdie {
171 Some(Ferdie)
172 } else if self.one {
173 Some(One)
174 } else if self.two {
175 Some(Two)
176 } else {
177 None
178 }
179 }
180}
181
182impl CliConfiguration for RunCmd {
183 fn shared_params(&self) -> &SharedParams {
184 &self.shared_params
185 }
186
187 fn import_params(&self) -> Option<&ImportParams> {
188 Some(&self.import_params)
189 }
190
191 fn network_params(&self) -> Option<&NetworkParams> {
192 Some(&self.network_params)
193 }
194
195 fn keystore_params(&self) -> Option<&KeystoreParams> {
196 Some(&self.keystore_params)
197 }
198
199 fn offchain_worker_params(&self) -> Option<&OffchainWorkerParams> {
200 Some(&self.offchain_worker_params)
201 }
202
203 fn node_name(&self) -> Result<String> {
204 let name: String = match (self.name.as_ref(), self.get_keyring()) {
205 (Some(name), _) => name.to_string(),
206 (_, Some(keyring)) => keyring.to_string(),
207 (None, None) => crate::generate_node_name(),
208 };
209
210 is_node_name_valid(&name).map_err(|msg| {
211 Error::Input(format!(
212 "Invalid node name '{}'. Reason: {}. If unsure, use none.",
213 name, msg
214 ))
215 })?;
216
217 Ok(name)
218 }
219
220 fn dev_key_seed(&self, is_dev: bool) -> Result<Option<String>> {
221 Ok(self.get_keyring().map(|a| format!("//{}", a)).or_else(|| {
222 if is_dev {
223 Some("//Alice".into())
224 } else {
225 None
226 }
227 }))
228 }
229
230 fn telemetry_endpoints(
231 &self,
232 chain_spec: &Box<dyn ChainSpec>,
233 ) -> Result<Option<TelemetryEndpoints>> {
234 let params = &self.telemetry_params;
235 Ok(if params.no_telemetry {
236 None
237 } else if !params.telemetry_endpoints.is_empty() {
238 Some(
239 TelemetryEndpoints::new(params.telemetry_endpoints.clone())
240 .map_err(|e| e.to_string())?,
241 )
242 } else {
243 chain_spec.telemetry_endpoints().clone()
244 })
245 }
246
247 fn role(&self, is_dev: bool) -> Result<Role> {
248 let keyring = self.get_keyring();
249 let is_authority = self.validator || is_dev || keyring.is_some();
250
251 Ok(if is_authority { Role::Authority } else { Role::Full })
252 }
253
254 fn force_authoring(&self) -> Result<bool> {
255 Ok(self.shared_params.dev || self.force_authoring)
257 }
258
259 fn prometheus_config(
260 &self,
261 default_listen_port: u16,
262 chain_spec: &Box<dyn ChainSpec>,
263 ) -> Result<Option<PrometheusConfig>> {
264 Ok(self
265 .prometheus_params
266 .prometheus_config(default_listen_port, chain_spec.id().to_string()))
267 }
268
269 fn disable_grandpa(&self) -> Result<bool> {
270 Ok(self.no_grandpa)
271 }
272
273 fn rpc_max_connections(&self) -> Result<u32> {
274 Ok(self.rpc_params.rpc_max_connections)
275 }
276
277 fn rpc_cors(&self, is_dev: bool) -> Result<Option<Vec<String>>> {
278 self.rpc_params.rpc_cors(is_dev)
279 }
280
281 fn rpc_addr(&self, default_listen_port: u16) -> Result<Option<Vec<RpcEndpoint>>> {
282 self.rpc_params.rpc_addr(self.is_dev()?, self.validator, default_listen_port)
283 }
284
285 fn rpc_methods(&self) -> Result<soil_service::config::RpcMethods> {
286 Ok(self.rpc_params.rpc_methods.into())
287 }
288
289 fn rpc_max_request_size(&self) -> Result<u32> {
290 Ok(self.rpc_params.rpc_max_request_size)
291 }
292
293 fn rpc_max_response_size(&self) -> Result<u32> {
294 Ok(self.rpc_params.rpc_max_response_size)
295 }
296
297 fn rpc_max_subscriptions_per_connection(&self) -> Result<u32> {
298 Ok(self.rpc_params.rpc_max_subscriptions_per_connection)
299 }
300
301 fn rpc_buffer_capacity_per_connection(&self) -> Result<u32> {
302 Ok(self.rpc_params.rpc_message_buffer_capacity_per_connection)
303 }
304
305 fn rpc_batch_config(&self) -> Result<RpcBatchRequestConfig> {
306 self.rpc_params.rpc_batch_config()
307 }
308
309 fn rpc_rate_limit(&self) -> Result<Option<NonZeroU32>> {
310 Ok(self.rpc_params.rpc_rate_limit)
311 }
312
313 fn rpc_rate_limit_whitelisted_ips(&self) -> Result<Vec<IpNetwork>> {
314 Ok(self.rpc_params.rpc_rate_limit_whitelisted_ips.clone())
315 }
316
317 fn rpc_rate_limit_trust_proxy_headers(&self) -> Result<bool> {
318 Ok(self.rpc_params.rpc_rate_limit_trust_proxy_headers)
319 }
320
321 fn transaction_pool(&self, is_dev: bool) -> Result<TransactionPoolOptions> {
322 Ok(self.pool_config.transaction_pool(is_dev))
323 }
324
325 fn max_runtime_instances(&self) -> Result<Option<usize>> {
326 Ok(Some(self.runtime_params.max_runtime_instances))
327 }
328
329 fn runtime_cache_size(&self) -> Result<u8> {
330 Ok(self.runtime_params.runtime_cache_size)
331 }
332
333 fn base_path(&self) -> Result<Option<BasePath>> {
334 Ok(if self.tmp {
335 Some(BasePath::new_temp_dir()?)
336 } else {
337 match self.shared_params().base_path()? {
338 Some(r) => Some(r),
339 None if self.shared_params().is_dev() => Some(BasePath::new_temp_dir()?),
341 None => None,
342 }
343 })
344 }
345}
346
347pub fn is_node_name_valid(_name: &str) -> std::result::Result<(), &str> {
349 let name = _name.to_string();
350
351 if name.is_empty() {
352 return Err("Node name cannot be empty");
353 }
354
355 if name.chars().count() >= crate::NODE_NAME_MAX_LENGTH {
356 return Err("Node name too long");
357 }
358
359 let invalid_chars = r"[\\.@]";
360 let re = Regex::new(invalid_chars).unwrap();
361 if re.is_match(&name) {
362 return Err("Node name should not contain invalid chars such as '.' and '@'");
363 }
364
365 let invalid_patterns = r"^https?:";
366 let re = Regex::new(invalid_patterns).unwrap();
367 if re.is_match(&name) {
368 return Err("Node name should not contain urls");
369 }
370
371 Ok(())
372}
373
374#[cfg(test)]
375mod tests {
376 use super::*;
377
378 #[test]
379 fn tests_node_name_good() {
380 assert!(is_node_name_valid("short name").is_ok());
381 assert!(is_node_name_valid("www").is_ok());
382 assert!(is_node_name_valid("aawww").is_ok());
383 assert!(is_node_name_valid("wwwaa").is_ok());
384 assert!(is_node_name_valid("www aa").is_ok());
385 }
386
387 #[test]
388 fn tests_node_name_bad() {
389 assert!(is_node_name_valid("").is_err());
390 assert!(is_node_name_valid(
391 "very very long names are really not very cool for the ui at all, really they're not"
392 )
393 .is_err());
394 assert!(is_node_name_valid("Dots.not.Ok").is_err());
395 assert!(is_node_name_valid("http://visitme").is_err());
398 assert!(is_node_name_valid("http:/visitme").is_err());
399 assert!(is_node_name_valid("http:visitme").is_err());
400 assert!(is_node_name_valid("https://visitme").is_err());
401 assert!(is_node_name_valid("https:/visitme").is_err());
402 assert!(is_node_name_valid("https:visitme").is_err());
403 assert!(is_node_name_valid("www.visit.me").is_err());
404 assert!(is_node_name_valid("www.visit").is_err());
405 assert!(is_node_name_valid("hello\\world").is_err());
406 assert!(is_node_name_valid("visit.www").is_err());
407 assert!(is_node_name_valid("email@domain").is_err());
408 }
409}