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