1use configuration::types::Arg;
2use support::constants::THIS_IS_A_BUG;
3
4use super::arg_filter::{apply_arg_removals, parse_removal_args};
5use crate::{network_spec::node::NodeSpec, shared::constants::*};
6
7pub struct GenCmdOptions<'a> {
8 pub relay_chain_name: &'a str,
9 pub cfg_path: &'a str,
10 pub data_path: &'a str,
11 pub relay_data_path: &'a str,
12 pub use_wrapper: bool,
13 pub bootnode_addr: Vec<String>,
14 pub use_default_ports_in_cmd: bool,
15 pub is_native: bool,
16}
17
18impl Default for GenCmdOptions<'_> {
19 fn default() -> Self {
20 Self {
21 relay_chain_name: "rococo-local",
22 cfg_path: "/cfg",
23 data_path: "/data",
24 relay_data_path: "/relay-data",
25 use_wrapper: true,
26 bootnode_addr: vec![],
27 use_default_ports_in_cmd: false,
28 is_native: true,
29 }
30 }
31}
32
33const FLAGS_ADDED_BY_US: [&str; 3] = ["--no-telemetry", "--collator", "--"];
34const OPS_ADDED_BY_US: [&str; 6] = [
35 "--chain",
36 "--name",
37 "--rpc-cors",
38 "--rpc-methods",
39 "--parachain-id",
40 "--node-key",
41];
42
43pub fn generate_for_cumulus_node(
45 node: &NodeSpec,
46 options: GenCmdOptions,
47 para_id: u32,
48) -> (String, Vec<String>) {
49 let NodeSpec {
50 key,
51 args,
52 is_validator,
53 bootnodes_addresses,
54 ..
55 } = node;
56
57 let mut tmp_args: Vec<String> = vec!["--node-key".into(), key.clone()];
58
59 if !args.contains(&Arg::Flag("--prometheus-external".into())) {
60 tmp_args.push("--prometheus-external".into())
61 }
62
63 if *is_validator && !args.contains(&Arg::Flag("--validator".into())) {
64 tmp_args.push("--collator".into())
65 }
66
67 if !bootnodes_addresses.is_empty() {
68 tmp_args.push("--bootnodes".into());
69 let bootnodes = bootnodes_addresses
70 .iter()
71 .map(|m| m.to_string())
72 .collect::<Vec<String>>()
73 .join(" ");
74 tmp_args.push(bootnodes)
75 }
76
77 let (prometheus_port, rpc_port, p2p_port) =
79 resolve_ports(node, options.use_default_ports_in_cmd);
80
81 tmp_args.push("--prometheus-port".into());
82 tmp_args.push(prometheus_port.to_string());
83
84 tmp_args.push("--rpc-port".into());
85 tmp_args.push(rpc_port.to_string());
86
87 tmp_args.push("--listen-addr".into());
88 tmp_args.push(format!("/ip4/0.0.0.0/tcp/{p2p_port}/ws"));
89
90 let mut collator_args: &[Arg] = &[];
91 let mut full_node_args: &[Arg] = &[];
92 if !args.is_empty() {
93 if let Some(index) = args.iter().position(|arg| match arg {
94 Arg::Flag(flag) => flag.eq("--"),
95 Arg::Option(..) => false,
96 Arg::Array(..) => false,
97 Arg::Positional(..) => false,
98 }) {
99 (collator_args, full_node_args) = args.split_at(index);
100 } else {
101 collator_args = args;
103 }
104 }
105
106 tmp_args.push("--base-path".into());
108 tmp_args.push(options.data_path.into());
109
110 let node_specific_bootnodes: Vec<String> = node
111 .bootnodes_addresses
112 .iter()
113 .map(|b| b.to_string())
114 .collect();
115 let full_bootnodes = [node_specific_bootnodes, options.bootnode_addr].concat();
116 if !full_bootnodes.is_empty() {
117 tmp_args.push("--bootnodes".into());
118 tmp_args.push(full_bootnodes.join(" "));
119 }
120
121 let mut full_node_p2p_needs_to_be_injected = true;
122 let mut full_node_prometheus_needs_to_be_injected = true;
123 let mut full_node_args_filtered = full_node_args
124 .iter()
125 .filter_map(|arg| match arg {
126 Arg::Flag(flag) => {
127 if flag.starts_with("-:") || FLAGS_ADDED_BY_US.contains(&flag.as_str()) {
128 None
129 } else {
130 Some(vec![flag.to_owned()])
131 }
132 },
133 Arg::Option(k, v) => {
134 if OPS_ADDED_BY_US.contains(&k.as_str()) {
135 None
136 } else if k.eq(&"port") {
137 if v.eq(&"30333") {
138 full_node_p2p_needs_to_be_injected = true;
139 None
140 } else {
141 full_node_p2p_needs_to_be_injected = false;
143 Some(vec![k.to_owned(), v.to_owned()])
144 }
145 } else if k.eq(&"--prometheus-port") {
146 if v.eq(&"9616") {
147 full_node_prometheus_needs_to_be_injected = true;
148 None
149 } else {
150 full_node_prometheus_needs_to_be_injected = false;
152 Some(vec![k.to_owned(), v.to_owned()])
153 }
154 } else {
155 Some(vec![k.to_owned(), v.to_owned()])
156 }
157 },
158 Arg::Array(k, v) => {
159 let mut args = vec![k.to_owned()];
160 args.extend(v.to_owned());
161 Some(args)
162 },
163 Arg::Positional(value) => Some(vec![value.to_owned()]),
164 })
165 .flatten()
166 .collect::<Vec<String>>();
167
168 let full_p2p_port = node
169 .full_node_p2p_port
170 .as_ref()
171 .expect(&format!(
172 "full node p2p_port should be specifed: {THIS_IS_A_BUG}"
173 ))
174 .0;
175 let full_prometheus_port = node
176 .full_node_prometheus_port
177 .as_ref()
178 .expect(&format!(
179 "full node prometheus_port should be specifed: {THIS_IS_A_BUG}"
180 ))
181 .0;
182
183 if full_node_p2p_needs_to_be_injected {
185 full_node_args_filtered.push("--port".into());
186 full_node_args_filtered.push(full_p2p_port.to_string());
187 }
188
189 if full_node_prometheus_needs_to_be_injected {
191 full_node_args_filtered.push("--prometheus-port".into());
192 full_node_args_filtered.push(full_prometheus_port.to_string());
193 }
194
195 let mut args_filtered = collator_args
196 .iter()
197 .filter_map(|arg| match arg {
198 Arg::Flag(flag) => {
199 if flag.starts_with("-:") || FLAGS_ADDED_BY_US.contains(&flag.as_str()) {
200 None
201 } else {
202 Some(vec![flag.to_owned()])
203 }
204 },
205 Arg::Option(k, v) => {
206 if OPS_ADDED_BY_US.contains(&k.as_str()) {
207 None
208 } else {
209 Some(vec![k.to_owned(), v.to_owned()])
210 }
211 },
212 Arg::Array(k, v) => {
213 let mut args = vec![k.to_owned()];
214 args.extend(v.to_owned());
215 Some(args)
216 },
217 Arg::Positional(value) => Some(vec![value.to_owned()]),
218 })
219 .flatten()
220 .collect::<Vec<String>>();
221
222 tmp_args.append(&mut args_filtered);
223
224 let parachain_spec_path = format!("{}/{}.json", options.cfg_path, para_id);
225 let mut final_args = vec![
226 node.command.as_str().to_string(),
227 "--chain".into(),
228 parachain_spec_path,
229 "--name".into(),
230 node.name.clone(),
231 "--rpc-cors".into(),
232 "all".into(),
233 "--rpc-methods".into(),
234 "unsafe".into(),
235 ];
236
237 if !options.is_native {
242 final_args.push("--unsafe-rpc-external".into());
243 }
244
245 final_args.append(&mut tmp_args);
246
247 let relaychain_spec_path = format!("{}/{}.json", options.cfg_path, options.relay_chain_name);
248 let mut full_node_injected: Vec<String> = vec![
249 "--".into(),
250 "--base-path".into(),
251 options.relay_data_path.into(),
252 "--chain".into(),
253 relaychain_spec_path,
254 "--execution".into(),
255 "wasm".into(),
256 ];
257
258 final_args.append(&mut full_node_injected);
259 final_args.append(&mut full_node_args_filtered);
260
261 let removals = parse_removal_args(args);
262 final_args = apply_arg_removals(final_args, &removals);
263
264 if options.use_wrapper {
265 ("/cfg/zombie-wrapper.sh".to_string(), final_args)
266 } else {
267 (final_args.remove(0), final_args)
268 }
269}
270
271pub fn generate_for_node(
272 node: &NodeSpec,
273 options: GenCmdOptions,
274 para_id: Option<u32>,
275) -> (String, Vec<String>) {
276 let NodeSpec {
277 key,
278 args,
279 is_validator,
280 bootnodes_addresses,
281 ..
282 } = node;
283 let mut tmp_args: Vec<String> = vec![
284 "--node-key".into(),
285 key.clone(),
286 "--no-telemetry".into(),
288 ];
289
290 if !args.contains(&Arg::Flag("--prometheus-external".into())) {
291 tmp_args.push("--prometheus-external".into())
292 }
293
294 if let Some(para_id) = para_id {
295 tmp_args.push("--parachain-id".into());
296 tmp_args.push(para_id.to_string());
297 }
298
299 if *is_validator && !args.contains(&Arg::Flag("--validator".into())) {
300 tmp_args.push("--validator".into());
301 if node.supports_arg("--insecure-validator-i-know-what-i-do") {
302 tmp_args.push("--insecure-validator-i-know-what-i-do".into());
303 }
304 }
305
306 if !bootnodes_addresses.is_empty() {
307 tmp_args.push("--bootnodes".into());
308 let bootnodes = bootnodes_addresses
309 .iter()
310 .map(|m| m.to_string())
311 .collect::<Vec<String>>()
312 .join(" ");
313 tmp_args.push(bootnodes)
314 }
315
316 let (prometheus_port, rpc_port, p2p_port) =
318 resolve_ports(node, options.use_default_ports_in_cmd);
319
320 tmp_args.push("--prometheus-port".into());
322 tmp_args.push(prometheus_port.to_string());
323
324 tmp_args.push("--rpc-port".into());
327 tmp_args.push(rpc_port.to_string());
328
329 let listen_value = if let Some(listen_val) = args.iter().find_map(|arg| match arg {
330 Arg::Flag(_) => None,
331 Arg::Option(k, v) => {
332 if k.eq("--listen-addr") {
333 Some(v)
334 } else {
335 None
336 }
337 },
338 Arg::Array(..) => None,
339 Arg::Positional(..) => None,
340 }) {
341 let mut parts = listen_val.split('/').collect::<Vec<&str>>();
342 let port_part = parts
344 .get_mut(4)
345 .expect(&format!("should have at least 5 parts {THIS_IS_A_BUG}"));
346 let port_to_use = p2p_port.to_string();
347 *port_part = port_to_use.as_str();
348 parts.join("/")
349 } else {
350 format!("/ip4/0.0.0.0/tcp/{p2p_port}/ws")
351 };
352
353 tmp_args.push("--listen-addr".into());
354 tmp_args.push(listen_value);
355
356 tmp_args.push("--base-path".into());
358 tmp_args.push(options.data_path.into());
359
360 let node_specific_bootnodes: Vec<String> = node
361 .bootnodes_addresses
362 .iter()
363 .map(|b| b.to_string())
364 .collect();
365 let full_bootnodes = [node_specific_bootnodes, options.bootnode_addr].concat();
366 if !full_bootnodes.is_empty() {
367 tmp_args.push("--bootnodes".into());
368 tmp_args.push(full_bootnodes.join(" "));
369 }
370
371 let mut args_filtered = args
373 .iter()
374 .filter_map(|arg| match arg {
375 Arg::Flag(flag) => {
376 if flag.starts_with("-:") || FLAGS_ADDED_BY_US.contains(&flag.as_str()) {
377 None
378 } else {
379 Some(vec![flag.to_owned()])
380 }
381 },
382 Arg::Option(k, v) => {
383 if OPS_ADDED_BY_US.contains(&k.as_str()) {
384 None
385 } else {
386 Some(vec![k.to_owned(), v.to_owned()])
387 }
388 },
389 Arg::Array(k, v) => {
390 let mut args = vec![k.to_owned()];
391 args.extend(v.to_owned());
392 Some(args)
393 },
394 Arg::Positional(value) => Some(vec![value.to_owned()]),
395 })
396 .flatten()
397 .collect::<Vec<String>>();
398
399 tmp_args.append(&mut args_filtered);
400
401 let chain_spec_path = format!("{}/{}.json", options.cfg_path, options.relay_chain_name);
402 let mut final_args = vec![
403 node.command.as_str().to_string(),
404 "--chain".into(),
405 chain_spec_path,
406 "--name".into(),
407 node.name.clone(),
408 "--rpc-cors".into(),
409 "all".into(),
410 "--rpc-methods".into(),
411 "unsafe".into(),
412 ];
413
414 if !options.is_native {
419 final_args.push("--unsafe-rpc-external".into());
420 }
421
422 final_args.append(&mut tmp_args);
423
424 if let Some(ref subcommand) = node.subcommand {
425 final_args.insert(1, subcommand.as_str().to_string());
426 }
427
428 let removals = parse_removal_args(args);
429 final_args = apply_arg_removals(final_args, &removals);
430
431 if options.use_wrapper {
432 ("/cfg/zombie-wrapper.sh".to_string(), final_args)
433 } else {
434 (final_args.remove(0), final_args)
435 }
436}
437
438fn resolve_ports(node: &NodeSpec, use_default_ports_in_cmd: bool) -> (u16, u16, u16) {
440 if use_default_ports_in_cmd {
441 (PROMETHEUS_PORT, RPC_PORT, P2P_PORT)
442 } else {
443 (node.prometheus_port.0, node.rpc_port.0, node.p2p_port.0)
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450 use crate::{generators, shared::types::NodeAccounts};
451
452 fn get_node_spec(full_node_present: bool) -> NodeSpec {
453 let mut name = String::from("luca");
454 let initial_balance = 1_000_000_000_000_u128;
455 let seed = format!("//{}{name}", name.remove(0).to_uppercase());
456 let accounts = NodeAccounts {
457 accounts: generators::generate_node_keys(&seed).unwrap(),
458 seed,
459 };
460 let (full_node_p2p_port, full_node_prometheus_port) = if full_node_present {
461 (
462 Some(generators::generate_node_port(None).unwrap()),
463 Some(generators::generate_node_port(None).unwrap()),
464 )
465 } else {
466 (None, None)
467 };
468 NodeSpec {
469 name,
470 accounts,
471 initial_balance,
472 full_node_p2p_port,
473 full_node_prometheus_port,
474 ..Default::default()
475 }
476 }
477
478 #[test]
479 fn generate_for_native_cumulus_node_works() {
480 let node = get_node_spec(true);
481 let opts = GenCmdOptions {
482 use_wrapper: false,
483 is_native: true,
484 ..GenCmdOptions::default()
485 };
486
487 let (program, args) = generate_for_cumulus_node(&node, opts, 1000);
488 assert_eq!(program.as_str(), "polkadot");
489
490 let divider_flag = args.iter().position(|x| x == "--").unwrap();
491
492 let i = args[divider_flag..]
494 .iter()
495 .position(|x| {
496 x == node
497 .full_node_p2p_port
498 .as_ref()
499 .unwrap()
500 .0
501 .to_string()
502 .as_str()
503 })
504 .unwrap();
505 assert_eq!(&args[divider_flag + i - 1], "--port");
506
507 let i = args[divider_flag..]
508 .iter()
509 .position(|x| {
510 x == node
511 .full_node_prometheus_port
512 .as_ref()
513 .unwrap()
514 .0
515 .to_string()
516 .as_str()
517 })
518 .unwrap();
519 assert_eq!(&args[divider_flag + i - 1], "--prometheus-port");
520
521 assert!(!args.iter().any(|arg| arg == "--unsafe-rpc-external"));
522 }
523
524 #[test]
525 fn generate_for_native_cumulus_node_rpc_external_is_not_removed_if_is_set_by_user() {
526 let mut node = get_node_spec(true);
527 node.args.push("--unsafe-rpc-external".into());
528 let opts = GenCmdOptions {
529 use_wrapper: false,
530 is_native: true,
531 ..GenCmdOptions::default()
532 };
533
534 let (_, args) = generate_for_cumulus_node(&node, opts, 1000);
535
536 assert!(args.iter().any(|arg| arg == "--unsafe-rpc-external"));
537 }
538
539 #[test]
540 fn generate_for_non_native_cumulus_node_works() {
541 let node = get_node_spec(true);
542 let opts = GenCmdOptions {
543 use_wrapper: false,
544 is_native: false,
545 ..GenCmdOptions::default()
546 };
547
548 let (program, args) = generate_for_cumulus_node(&node, opts, 1000);
549 assert_eq!(program.as_str(), "polkadot");
550
551 let divider_flag = args.iter().position(|x| x == "--").unwrap();
552
553 let i = args[divider_flag..]
555 .iter()
556 .position(|x| {
557 x == node
558 .full_node_p2p_port
559 .as_ref()
560 .unwrap()
561 .0
562 .to_string()
563 .as_str()
564 })
565 .unwrap();
566 assert_eq!(&args[divider_flag + i - 1], "--port");
567
568 let i = args[divider_flag..]
569 .iter()
570 .position(|x| {
571 x == node
572 .full_node_prometheus_port
573 .as_ref()
574 .unwrap()
575 .0
576 .to_string()
577 .as_str()
578 })
579 .unwrap();
580 assert_eq!(&args[divider_flag + i - 1], "--prometheus-port");
581
582 assert!(&args[0..divider_flag]
584 .iter()
585 .any(|arg| arg == "--unsafe-rpc-external"));
586 }
587
588 #[test]
589 fn generate_for_native_node_rpc_external_works() {
590 let node = get_node_spec(false);
591 let opts = GenCmdOptions {
592 use_wrapper: false,
593 is_native: true,
594 ..GenCmdOptions::default()
595 };
596
597 let (program, args) = generate_for_node(&node, opts, Some(1000));
598 assert_eq!(program.as_str(), "polkadot");
599
600 assert!(!args.iter().any(|arg| arg == "--unsafe-rpc-external"));
601 }
602
603 #[test]
604 fn generate_for_non_native_node_rpc_external_works() {
605 let node = get_node_spec(false);
606 let opts = GenCmdOptions {
607 use_wrapper: false,
608 is_native: false,
609 ..GenCmdOptions::default()
610 };
611
612 let (program, args) = generate_for_node(&node, opts, Some(1000));
613 assert_eq!(program.as_str(), "polkadot");
614
615 assert!(args.iter().any(|arg| arg == "--unsafe-rpc-external"));
616 }
617
618 #[test]
619 fn test_arg_removal_removes_insecure_validator_flag() {
620 let mut node = get_node_spec(false);
621 node.args
622 .push(Arg::Flag("-:--insecure-validator-i-know-what-i-do".into()));
623 node.is_validator = true;
624 node.available_args_output = Some("--insecure-validator-i-know-what-i-do".to_string());
625
626 let opts = GenCmdOptions {
627 use_wrapper: false,
628 is_native: true,
629 ..GenCmdOptions::default()
630 };
631
632 let (program, args) = generate_for_node(&node, opts, Some(1000));
633 assert_eq!(program.as_str(), "polkadot");
634 assert!(args.iter().any(|arg| arg == "--validator"));
635 assert!(!args
636 .iter()
637 .any(|arg| arg == "--insecure-validator-i-know-what-i-do"));
638 }
639}