1use ant_bootstrap::InitialPeersConfig;
10use ant_evm::{EvmNetwork, RewardsAddress};
11use ant_logging::LogFormat;
12use ant_service_management::node::push_arguments_from_initial_peers_config;
13use color_eyre::{eyre::eyre, Result};
14use service_manager::{ServiceInstallCtx, ServiceLabel};
15use std::{
16 ffi::OsString,
17 net::{Ipv4Addr, SocketAddr},
18 path::PathBuf,
19 str::FromStr,
20};
21
22#[derive(Clone, Debug)]
23pub enum PortRange {
24 Single(u16),
25 Range(u16, u16),
26}
27
28impl PortRange {
29 pub fn parse(s: &str) -> Result<Self> {
30 if let Ok(port) = u16::from_str(s) {
31 Ok(Self::Single(port))
32 } else {
33 let parts: Vec<&str> = s.split('-').collect();
34 if parts.len() != 2 {
35 return Err(eyre!("Port range must be in the format 'start-end'"));
36 }
37 let start = parts[0].parse::<u16>()?;
38 let end = parts[1].parse::<u16>()?;
39 if start >= end {
40 return Err(eyre!("End port must be greater than start port"));
41 }
42 Ok(Self::Range(start, end))
43 }
44 }
45
46 pub fn validate(&self, count: u16) -> Result<()> {
48 match self {
49 Self::Single(_) => {
50 if count != 1 {
51 error!("The count ({count}) does not match the number of ports (1)");
52 return Err(eyre!(
53 "The count ({count}) does not match the number of ports (1)"
54 ));
55 }
56 }
57 Self::Range(start, end) => {
58 let port_count = end - start + 1;
59 if count != port_count {
60 error!("The count ({count}) does not match the number of ports ({port_count})");
61 return Err(eyre!(
62 "The count ({count}) does not match the number of ports ({port_count})"
63 ));
64 }
65 }
66 }
67 Ok(())
68 }
69}
70
71#[derive(Debug, PartialEq)]
72pub struct InstallNodeServiceCtxBuilder {
73 pub alpha: bool,
74 pub antnode_path: PathBuf,
75 pub autostart: bool,
76 pub data_dir_path: PathBuf,
77 pub env_variables: Option<Vec<(String, String)>>,
78 pub evm_network: EvmNetwork,
79 pub log_dir_path: PathBuf,
80 pub log_format: Option<LogFormat>,
81 pub name: String,
82 pub network_id: Option<u8>,
83 pub no_upnp: bool,
84 pub max_archived_log_files: Option<usize>,
85 pub max_log_files: Option<usize>,
86 pub metrics_port: Option<u16>,
87 pub node_ip: Option<Ipv4Addr>,
88 pub node_port: Option<u16>,
89 pub init_peers_config: InitialPeersConfig,
90 pub rewards_address: RewardsAddress,
91 pub relay: bool,
92 pub rpc_socket_addr: SocketAddr,
93 pub service_user: Option<String>,
94}
95
96impl InstallNodeServiceCtxBuilder {
97 pub fn build(self) -> Result<ServiceInstallCtx> {
98 let label: ServiceLabel = self.name.parse()?;
99 let mut args = vec![
100 OsString::from("--rpc"),
101 OsString::from(self.rpc_socket_addr.to_string()),
102 OsString::from("--root-dir"),
103 OsString::from(self.data_dir_path.to_string_lossy().to_string()),
104 OsString::from("--log-output-dest"),
105 OsString::from(self.log_dir_path.to_string_lossy().to_string()),
106 ];
107
108 push_arguments_from_initial_peers_config(&self.init_peers_config, &mut args);
109 if self.alpha {
110 args.push(OsString::from("--alpha"));
111 }
112 if let Some(id) = self.network_id {
113 args.push(OsString::from("--network-id"));
114 args.push(OsString::from(id.to_string()));
115 }
116 if self.relay {
117 args.push(OsString::from("--relay"));
118 }
119 if let Some(log_format) = self.log_format {
120 args.push(OsString::from("--log-format"));
121 args.push(OsString::from(log_format.as_str()));
122 }
123 if self.no_upnp {
124 args.push(OsString::from("--no-upnp"));
125 }
126 if let Some(node_ip) = self.node_ip {
127 args.push(OsString::from("--ip"));
128 args.push(OsString::from(node_ip.to_string()));
129 }
130 if let Some(node_port) = self.node_port {
131 args.push(OsString::from("--port"));
132 args.push(OsString::from(node_port.to_string()));
133 }
134 if let Some(metrics_port) = self.metrics_port {
135 args.push(OsString::from("--metrics-server-port"));
136 args.push(OsString::from(metrics_port.to_string()));
137 }
138 if let Some(log_files) = self.max_archived_log_files {
139 args.push(OsString::from("--max-archived-log-files"));
140 args.push(OsString::from(log_files.to_string()));
141 }
142 if let Some(log_files) = self.max_log_files {
143 args.push(OsString::from("--max-log-files"));
144 args.push(OsString::from(log_files.to_string()));
145 }
146
147 args.push(OsString::from("--rewards-address"));
148 args.push(OsString::from(self.rewards_address.to_string()));
149
150 args.push(OsString::from(self.evm_network.to_string()));
151 if let EvmNetwork::Custom(custom_network) = &self.evm_network {
152 args.push(OsString::from("--rpc-url"));
153 args.push(OsString::from(custom_network.rpc_url_http.to_string()));
154 args.push(OsString::from("--payment-token-address"));
155 args.push(OsString::from(
156 custom_network.payment_token_address.to_string(),
157 ));
158 args.push(OsString::from("--data-payments-address"));
159 args.push(OsString::from(
160 custom_network.data_payments_address.to_string(),
161 ));
162 }
163
164 Ok(ServiceInstallCtx {
165 args,
166 autostart: self.autostart,
167 contents: None,
168 environment: self.env_variables,
169 label: label.clone(),
170 program: self.antnode_path.to_path_buf(),
171 username: self.service_user.clone(),
172 working_directory: None,
173 disable_restart_on_failure: true,
174 })
175 }
176}
177
178pub struct AddNodeServiceOptions {
179 pub alpha: bool,
180 pub antnode_dir_path: PathBuf,
181 pub antnode_src_path: PathBuf,
182 pub auto_restart: bool,
183 pub auto_set_nat_flags: bool,
184 pub count: Option<u16>,
185 pub delete_antnode_src: bool,
186 pub enable_metrics_server: bool,
187 pub env_variables: Option<Vec<(String, String)>>,
188 pub evm_network: EvmNetwork,
189 pub init_peers_config: InitialPeersConfig,
190 pub log_format: Option<LogFormat>,
191 pub max_archived_log_files: Option<usize>,
192 pub max_log_files: Option<usize>,
193 pub metrics_port: Option<PortRange>,
194 pub network_id: Option<u8>,
195 pub node_ip: Option<Ipv4Addr>,
196 pub node_port: Option<PortRange>,
197 pub no_upnp: bool,
198 pub relay: bool,
199 pub rewards_address: RewardsAddress,
200 pub rpc_address: Option<Ipv4Addr>,
201 pub rpc_port: Option<PortRange>,
202 pub service_data_dir_path: PathBuf,
203 pub service_log_dir_path: PathBuf,
204 pub user: Option<String>,
205 pub user_mode: bool,
206 pub version: String,
207}
208
209#[derive(Debug, PartialEq)]
210pub struct InstallAuditorServiceCtxBuilder {
211 pub auditor_path: PathBuf,
212 pub beta_encryption_key: Option<String>,
213 pub env_variables: Option<Vec<(String, String)>>,
214 pub log_dir_path: PathBuf,
215 pub name: String,
216 pub service_user: String,
217}
218
219impl InstallAuditorServiceCtxBuilder {
220 pub fn build(self) -> Result<ServiceInstallCtx> {
221 let mut args = vec![
222 OsString::from("--log-output-dest"),
223 OsString::from(self.log_dir_path.to_string_lossy().to_string()),
224 ];
225
226 if let Some(beta_encryption_key) = self.beta_encryption_key {
227 args.push(OsString::from("--beta-encryption-key"));
228 args.push(OsString::from(beta_encryption_key));
229 }
230
231 Ok(ServiceInstallCtx {
232 args,
233 autostart: true,
234 contents: None,
235 environment: self.env_variables,
236 label: self.name.parse()?,
237 program: self.auditor_path.to_path_buf(),
238 username: Some(self.service_user.to_string()),
239 working_directory: None,
240 disable_restart_on_failure: false,
241 })
242 }
243}
244
245#[derive(Debug, PartialEq)]
246pub struct InstallFaucetServiceCtxBuilder {
247 pub env_variables: Option<Vec<(String, String)>>,
248 pub faucet_path: PathBuf,
249 pub local: bool,
250 pub log_dir_path: PathBuf,
251 pub name: String,
252 pub service_user: String,
253}
254
255impl InstallFaucetServiceCtxBuilder {
256 pub fn build(self) -> Result<ServiceInstallCtx> {
257 let mut args = vec![
258 OsString::from("--log-output-dest"),
259 OsString::from(self.log_dir_path.to_string_lossy().to_string()),
260 ];
261
262 args.push(OsString::from("server"));
263
264 Ok(ServiceInstallCtx {
265 args,
266 autostart: true,
267 contents: None,
268 environment: self.env_variables,
269 label: self.name.parse()?,
270 program: self.faucet_path.to_path_buf(),
271 username: Some(self.service_user.to_string()),
272 working_directory: None,
273 disable_restart_on_failure: false,
274 })
275 }
276}
277
278pub struct AddAuditorServiceOptions {
279 pub auditor_install_bin_path: PathBuf,
280 pub auditor_src_bin_path: PathBuf,
281 pub beta_encryption_key: Option<String>,
282 pub env_variables: Option<Vec<(String, String)>>,
283 pub service_log_dir_path: PathBuf,
284 pub user: String,
285 pub version: String,
286}
287
288pub struct AddFaucetServiceOptions {
289 pub env_variables: Option<Vec<(String, String)>>,
290 pub faucet_install_bin_path: PathBuf,
291 pub faucet_src_bin_path: PathBuf,
292 pub local: bool,
293 pub service_data_dir_path: PathBuf,
294 pub service_log_dir_path: PathBuf,
295 pub user: String,
296 pub version: String,
297}
298
299pub struct AddDaemonServiceOptions {
300 pub address: Ipv4Addr,
301 pub env_variables: Option<Vec<(String, String)>>,
302 pub daemon_install_bin_path: PathBuf,
303 pub daemon_src_bin_path: PathBuf,
304 pub port: u16,
305 pub user: String,
306 pub version: String,
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use ant_evm::{CustomNetwork, RewardsAddress};
313 use std::net::{IpAddr, Ipv4Addr};
314
315 fn create_default_builder() -> InstallNodeServiceCtxBuilder {
316 InstallNodeServiceCtxBuilder {
317 alpha: false,
318 antnode_path: PathBuf::from("/bin/antnode"),
319 autostart: true,
320 data_dir_path: PathBuf::from("/data"),
321 env_variables: None,
322 evm_network: EvmNetwork::ArbitrumOne,
323 relay: false,
324 log_dir_path: PathBuf::from("/logs"),
325 log_format: None,
326 max_archived_log_files: None,
327 max_log_files: None,
328 metrics_port: None,
329 name: "test-node".to_string(),
330 network_id: None,
331 node_ip: None,
332 node_port: None,
333 init_peers_config: InitialPeersConfig::default(),
334 rewards_address: RewardsAddress::from_str("0x03B770D9cD32077cC0bF330c13C114a87643B124")
335 .unwrap(),
336 rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
337 service_user: None,
338 no_upnp: false,
339 }
340 }
341
342 fn create_custom_evm_network_builder() -> InstallNodeServiceCtxBuilder {
343 InstallNodeServiceCtxBuilder {
344 alpha: false,
345 autostart: true,
346 data_dir_path: PathBuf::from("/data"),
347 env_variables: None,
348 evm_network: EvmNetwork::Custom(CustomNetwork {
349 rpc_url_http: "http://localhost:8545".parse().unwrap(),
350 payment_token_address: RewardsAddress::from_str(
351 "0x5FbDB2315678afecb367f032d93F642f64180aa3",
352 )
353 .unwrap(),
354 data_payments_address: RewardsAddress::from_str(
355 "0x8464135c8F25Da09e49BC8782676a84730C318bC",
356 )
357 .unwrap(),
358 }),
359 relay: false,
360 log_dir_path: PathBuf::from("/logs"),
361 log_format: None,
362 max_archived_log_files: None,
363 max_log_files: None,
364 metrics_port: None,
365 name: "test-node".to_string(),
366 network_id: None,
367 node_ip: None,
368 node_port: None,
369 init_peers_config: InitialPeersConfig::default(),
370 rewards_address: RewardsAddress::from_str("0x03B770D9cD32077cC0bF330c13C114a87643B124")
371 .unwrap(),
372 rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
373 antnode_path: PathBuf::from("/bin/antnode"),
374 service_user: None,
375 no_upnp: false,
376 }
377 }
378
379 fn create_builder_with_all_options_enabled() -> InstallNodeServiceCtxBuilder {
380 InstallNodeServiceCtxBuilder {
381 alpha: true,
382 autostart: true,
383 data_dir_path: PathBuf::from("/data"),
384 env_variables: None,
385 evm_network: EvmNetwork::Custom(CustomNetwork {
386 rpc_url_http: "http://localhost:8545".parse().unwrap(),
387 payment_token_address: RewardsAddress::from_str(
388 "0x5FbDB2315678afecb367f032d93F642f64180aa3",
389 )
390 .unwrap(),
391 data_payments_address: RewardsAddress::from_str(
392 "0x8464135c8F25Da09e49BC8782676a84730C318bC",
393 )
394 .unwrap(),
395 }),
396 relay: false,
397 log_dir_path: PathBuf::from("/logs"),
398 log_format: None,
399 max_archived_log_files: Some(10),
400 max_log_files: Some(10),
401 metrics_port: None,
402 name: "test-node".to_string(),
403 network_id: Some(5),
404 node_ip: None,
405 node_port: None,
406 init_peers_config: InitialPeersConfig::default(),
407 rewards_address: RewardsAddress::from_str("0x03B770D9cD32077cC0bF330c13C114a87643B124")
408 .unwrap(),
409 rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
410 antnode_path: PathBuf::from("/bin/antnode"),
411 service_user: None,
412 no_upnp: false,
413 }
414 }
415
416 #[test]
417 fn build_should_assign_expected_values_when_mandatory_options_are_provided() {
418 let builder = create_default_builder();
419 let result = builder.build().unwrap();
420
421 assert_eq!(result.label.to_string(), "test-node");
422 assert_eq!(result.program, PathBuf::from("/bin/antnode"));
423 assert!(result.autostart);
424 assert_eq!(result.username, None);
425 assert_eq!(result.working_directory, None);
426
427 let expected_args = vec![
428 "--rpc",
429 "127.0.0.1:8080",
430 "--root-dir",
431 "/data",
432 "--log-output-dest",
433 "/logs",
434 "--rewards-address",
435 "0x03B770D9cD32077cC0bF330c13C114a87643B124",
436 "evm-arbitrum-one",
437 ];
438 assert_eq!(
439 result
440 .args
441 .iter()
442 .map(|os| os.to_str().unwrap())
443 .collect::<Vec<_>>(),
444 expected_args
445 );
446 }
447
448 #[test]
449 fn build_should_assign_expected_values_when_a_custom_evm_network_is_provided() {
450 let builder = create_custom_evm_network_builder();
451 let result = builder.build().unwrap();
452
453 assert_eq!(result.label.to_string(), "test-node");
454 assert_eq!(result.program, PathBuf::from("/bin/antnode"));
455 assert!(result.autostart);
456 assert_eq!(result.username, None);
457 assert_eq!(result.working_directory, None);
458
459 let expected_args = vec![
460 "--rpc",
461 "127.0.0.1:8080",
462 "--root-dir",
463 "/data",
464 "--log-output-dest",
465 "/logs",
466 "--rewards-address",
467 "0x03B770D9cD32077cC0bF330c13C114a87643B124",
468 "evm-custom",
469 "--rpc-url",
470 "http://localhost:8545/",
471 "--payment-token-address",
472 "0x5FbDB2315678afecb367f032d93F642f64180aa3",
473 "--data-payments-address",
474 "0x8464135c8F25Da09e49BC8782676a84730C318bC",
475 ];
476 assert_eq!(
477 result
478 .args
479 .iter()
480 .map(|os| os.to_str().unwrap())
481 .collect::<Vec<_>>(),
482 expected_args
483 );
484 }
485
486 #[test]
487 fn build_should_assign_expected_values_when_all_options_are_enabled() {
488 let mut builder = create_builder_with_all_options_enabled();
489 builder.alpha = true;
490 builder.relay = true;
491 builder.log_format = Some(LogFormat::Json);
492 builder.no_upnp = true;
493 builder.node_ip = Some(Ipv4Addr::new(192, 168, 1, 1));
494 builder.node_port = Some(12345);
495 builder.metrics_port = Some(9090);
496 builder.init_peers_config.addrs = vec![
497 "/ip4/127.0.0.1/tcp/8080".parse().unwrap(),
498 "/ip4/192.168.1.1/tcp/8081".parse().unwrap(),
499 ];
500 builder.init_peers_config.first = true;
501 builder.init_peers_config.local = true;
502 builder.init_peers_config.network_contacts_url =
503 vec!["http://localhost:8080".parse().unwrap()];
504 builder.init_peers_config.ignore_cache = true;
505 builder.service_user = Some("antnode-user".to_string());
506
507 let result = builder.build().unwrap();
508
509 let expected_args = vec![
510 "--rpc",
511 "127.0.0.1:8080",
512 "--root-dir",
513 "/data",
514 "--log-output-dest",
515 "/logs",
516 "--first",
517 "--local",
518 "--peer",
519 "/ip4/127.0.0.1/tcp/8080,/ip4/192.168.1.1/tcp/8081",
520 "--network-contacts-url",
521 "http://localhost:8080",
522 "--ignore-cache",
523 "--alpha",
524 "--network-id",
525 "5",
526 "--relay",
527 "--log-format",
528 "json",
529 "--no-upnp",
530 "--ip",
531 "192.168.1.1",
532 "--port",
533 "12345",
534 "--metrics-server-port",
535 "9090",
536 "--max-archived-log-files",
537 "10",
538 "--max-log-files",
539 "10",
540 "--rewards-address",
541 "0x03B770D9cD32077cC0bF330c13C114a87643B124",
542 "evm-custom",
543 "--rpc-url",
544 "http://localhost:8545/",
545 "--payment-token-address",
546 "0x5FbDB2315678afecb367f032d93F642f64180aa3",
547 "--data-payments-address",
548 "0x8464135c8F25Da09e49BC8782676a84730C318bC",
549 ];
550 assert_eq!(
551 result
552 .args
553 .iter()
554 .map(|os| os.to_str().unwrap())
555 .collect::<Vec<_>>(),
556 expected_args
557 );
558 assert_eq!(result.username, Some("antnode-user".to_string()));
559 }
560
561 #[test]
562 fn build_should_assign_expected_values_when_environment_variables_are_provided() {
563 let mut builder = create_default_builder();
564 builder.env_variables = Some(vec![
565 ("VAR1".to_string(), "value1".to_string()),
566 ("VAR2".to_string(), "value2".to_string()),
567 ]);
568
569 let result = builder.build().unwrap();
570
571 assert_eq!(
572 result.environment,
573 Some(vec![
574 ("VAR1".to_string(), "value1".to_string()),
575 ("VAR2".to_string(), "value2".to_string()),
576 ])
577 );
578 }
579}