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