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 { delay_secs: None },
265 rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
266 service_user: None,
267 stop_on_upgrade: true,
268 write_older_cache_files: false,
269 }
270 }
271
272 fn create_custom_evm_network_builder() -> InstallNodeServiceCtxBuilder {
273 InstallNodeServiceCtxBuilder {
274 alpha: false,
275 antnode_path: PathBuf::from("/bin/antnode"),
276 autostart: true,
277 data_dir_path: PathBuf::from("/data"),
278 env_variables: None,
279 evm_network: EvmNetwork::Custom(CustomNetwork {
280 rpc_url_http: "http://localhost:8545".parse().unwrap(),
281 payment_token_address: RewardsAddress::from_str(
282 "0x5FbDB2315678afecb367f032d93F642f64180aa3",
283 )
284 .unwrap(),
285 data_payments_address: RewardsAddress::from_str(
286 "0x8464135c8F25Da09e49BC8782676a84730C318bC",
287 )
288 .unwrap(),
289 merkle_payments_address: None,
290 }),
291 init_peers_config: InitialPeersConfig::default(),
292 log_dir_path: PathBuf::from("/logs"),
293 log_format: None,
294 max_archived_log_files: None,
295 max_log_files: None,
296 metrics_port: None,
297 name: "test-node".to_string(),
298 network_id: None,
299 no_upnp: false,
300 node_ip: None,
301 node_port: None,
302 relay: false,
303 restart_policy: RestartPolicy::OnSuccess { delay_secs: None },
304 rewards_address: RewardsAddress::from_str("0x03B770D9cD32077cC0bF330c13C114a87643B124")
305 .unwrap(),
306 rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
307 service_user: None,
308 stop_on_upgrade: false,
309 write_older_cache_files: false,
310 }
311 }
312
313 fn create_builder_with_all_options_enabled() -> InstallNodeServiceCtxBuilder {
314 InstallNodeServiceCtxBuilder {
315 alpha: true,
316 antnode_path: PathBuf::from("/bin/antnode"),
317 autostart: true,
318 data_dir_path: PathBuf::from("/data"),
319 env_variables: None,
320 evm_network: EvmNetwork::Custom(CustomNetwork {
321 rpc_url_http: "http://localhost:8545".parse().unwrap(),
322 payment_token_address: RewardsAddress::from_str(
323 "0x5FbDB2315678afecb367f032d93F642f64180aa3",
324 )
325 .unwrap(),
326 data_payments_address: RewardsAddress::from_str(
327 "0x8464135c8F25Da09e49BC8782676a84730C318bC",
328 )
329 .unwrap(),
330 merkle_payments_address: Some(
331 RewardsAddress::from_str("0x742D35CC6634C0532925A3B844BC9E7595F0BE3A").unwrap(),
332 ),
333 }),
334 log_dir_path: PathBuf::from("/logs"),
335 log_format: None,
336 init_peers_config: InitialPeersConfig::default(),
337 max_archived_log_files: Some(10),
338 max_log_files: Some(10),
339 metrics_port: None,
340 name: "test-node".to_string(),
341 network_id: Some(5),
342 no_upnp: false,
343 node_ip: None,
344 node_port: None,
345 relay: false,
346 restart_policy: RestartPolicy::OnSuccess { delay_secs: None },
347 rewards_address: RewardsAddress::from_str("0x03B770D9cD32077cC0bF330c13C114a87643B124")
348 .unwrap(),
349 rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
350 service_user: None,
351 stop_on_upgrade: true,
352 write_older_cache_files: false,
353 }
354 }
355
356 #[test]
357 fn build_should_assign_expected_values_when_mandatory_options_are_provided() {
358 let builder = create_default_builder();
359 let result = builder.build().unwrap();
360
361 assert_eq!(result.label.to_string(), "test-node");
362 assert_eq!(result.program, PathBuf::from("/bin/antnode"));
363 assert!(result.autostart);
364 assert_eq!(result.username, None);
365 assert_eq!(result.working_directory, None);
366
367 let expected_args = vec![
368 "--rpc",
369 "127.0.0.1:8080",
370 "--root-dir",
371 "/data",
372 "--log-output-dest",
373 "/logs",
374 "--rewards-address",
375 "0x03B770D9cD32077cC0bF330c13C114a87643B124",
376 "--stop-on-upgrade",
377 "evm-arbitrum-one",
378 ];
379 assert_eq!(
380 result
381 .args
382 .iter()
383 .map(|os| os.to_str().unwrap())
384 .collect::<Vec<_>>(),
385 expected_args
386 );
387 }
388
389 #[test]
390 fn build_should_assign_expected_values_when_a_custom_evm_network_is_provided() {
391 let builder = create_custom_evm_network_builder();
392 let result = builder.build().unwrap();
393
394 assert_eq!(result.label.to_string(), "test-node");
395 assert_eq!(result.program, PathBuf::from("/bin/antnode"));
396 assert!(result.autostart);
397 assert_eq!(result.username, None);
398 assert_eq!(result.working_directory, None);
399
400 let expected_args = vec![
401 "--rpc",
402 "127.0.0.1:8080",
403 "--root-dir",
404 "/data",
405 "--log-output-dest",
406 "/logs",
407 "--rewards-address",
408 "0x03B770D9cD32077cC0bF330c13C114a87643B124",
409 "evm-custom",
410 "--rpc-url",
411 "http://localhost:8545/",
412 "--payment-token-address",
413 "0x5FbDB2315678afecb367f032d93F642f64180aa3",
414 "--data-payments-address",
415 "0x8464135c8F25Da09e49BC8782676a84730C318bC",
416 ];
417 assert_eq!(
418 result
419 .args
420 .iter()
421 .map(|os| os.to_str().unwrap())
422 .collect::<Vec<_>>(),
423 expected_args
424 );
425 }
426
427 #[test]
428 fn build_should_assign_expected_values_when_all_options_are_enabled() {
429 let mut builder = create_builder_with_all_options_enabled();
430 builder.alpha = true;
431 builder.relay = true;
432 builder.log_format = Some(LogFormat::Json);
433 builder.no_upnp = true;
434 builder.node_ip = Some(Ipv4Addr::new(192, 168, 1, 1));
435 builder.node_port = Some(12345);
436 builder.metrics_port = Some(9090);
437 builder.init_peers_config.addrs = vec![
438 "/ip4/127.0.0.1/tcp/8080".parse().unwrap(),
439 "/ip4/192.168.1.1/tcp/8081".parse().unwrap(),
440 ];
441 builder.init_peers_config.first = true;
442 builder.init_peers_config.local = true;
443 builder.init_peers_config.network_contacts_url =
444 vec!["http://localhost:8080".parse().unwrap()];
445 builder.init_peers_config.ignore_cache = true;
446 builder.service_user = Some("antnode-user".to_string());
447 builder.write_older_cache_files = true;
448
449 let result = builder.build().unwrap();
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 "--first",
459 "--local",
460 "--peer",
461 "/ip4/127.0.0.1/tcp/8080,/ip4/192.168.1.1/tcp/8081",
462 "--network-contacts-url",
463 "http://localhost:8080",
464 "--ignore-cache",
465 "--alpha",
466 "--network-id",
467 "5",
468 "--relay",
469 "--log-format",
470 "json",
471 "--no-upnp",
472 "--ip",
473 "192.168.1.1",
474 "--port",
475 "12345",
476 "--metrics-server-port",
477 "9090",
478 "--max-archived-log-files",
479 "10",
480 "--max-log-files",
481 "10",
482 "--rewards-address",
483 "0x03B770D9cD32077cC0bF330c13C114a87643B124",
484 "--write-older-cache-files",
485 "--stop-on-upgrade",
486 "evm-custom",
487 "--rpc-url",
488 "http://localhost:8545/",
489 "--payment-token-address",
490 "0x5FbDB2315678afecb367f032d93F642f64180aa3",
491 "--data-payments-address",
492 "0x8464135c8F25Da09e49BC8782676a84730C318bC",
493 "--merkle-payments-address",
494 "0x742d35cC6634C0532925A3b844bC9E7595F0be3A",
495 ];
496 assert_eq!(
497 result
498 .args
499 .iter()
500 .map(|os| os.to_str().unwrap())
501 .collect::<Vec<_>>(),
502 expected_args
503 );
504 assert_eq!(result.username, Some("antnode-user".to_string()));
505 }
506
507 #[test]
508 fn build_should_assign_expected_values_when_environment_variables_are_provided() {
509 let mut builder = create_default_builder();
510 builder.env_variables = Some(vec![
511 ("VAR1".to_string(), "value1".to_string()),
512 ("VAR2".to_string(), "value2".to_string()),
513 ]);
514
515 let result = builder.build().unwrap();
516
517 assert_eq!(
518 result.environment,
519 Some(vec![
520 ("VAR1".to_string(), "value1".to_string()),
521 ("VAR2".to_string(), "value2".to_string()),
522 ])
523 );
524 }
525}