sn_node_manager/
lib.rs

1// Copyright (C) 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9#[macro_use]
10extern crate tracing;
11
12pub mod add_services;
13pub mod cmd;
14pub mod config;
15pub mod error;
16pub mod helpers;
17pub mod local;
18pub mod rpc;
19pub mod rpc_client;
20
21pub const DEFAULT_NODE_STARTUP_CONNECTION_TIMEOUT_S: u64 = 300;
22
23#[derive(Clone, Copy, PartialEq, Debug)]
24pub enum VerbosityLevel {
25    Minimal,
26    Normal,
27    Full,
28}
29
30impl From<u8> for VerbosityLevel {
31    fn from(verbosity: u8) -> Self {
32        match verbosity {
33            1 => VerbosityLevel::Minimal,
34            2 => VerbosityLevel::Normal,
35            3 => VerbosityLevel::Full,
36            _ => VerbosityLevel::Normal,
37        }
38    }
39}
40
41use crate::error::{Error, Result};
42use colored::Colorize;
43use semver::Version;
44use sn_evm::AttoTokens;
45use sn_service_management::rpc::RpcActions;
46use sn_service_management::{
47    control::ServiceControl, error::Error as ServiceError, rpc::RpcClient, NodeRegistry,
48    NodeService, NodeServiceData, ServiceStateActions, ServiceStatus, UpgradeOptions,
49    UpgradeResult,
50};
51use sn_transfers::HotWallet;
52use tracing::debug;
53
54pub const DAEMON_DEFAULT_PORT: u16 = 12500;
55pub const DAEMON_SERVICE_NAME: &str = "safenodemand";
56
57const RPC_START_UP_DELAY_MS: u64 = 3000;
58
59pub struct ServiceManager<T: ServiceStateActions + Send> {
60    pub service: T,
61    pub service_control: Box<dyn ServiceControl + Send>,
62    pub verbosity: VerbosityLevel,
63}
64
65impl<T: ServiceStateActions + Send> ServiceManager<T> {
66    pub fn new(
67        service: T,
68        service_control: Box<dyn ServiceControl + Send>,
69        verbosity: VerbosityLevel,
70    ) -> Self {
71        ServiceManager {
72            service,
73            service_control,
74            verbosity,
75        }
76    }
77
78    pub async fn start(&mut self) -> Result<()> {
79        info!("Starting the {} service", self.service.name());
80        if ServiceStatus::Running == self.service.status() {
81            // The last time we checked the service was running, but it doesn't mean it's actually
82            // running now. If it is running, we don't need to do anything. If it stopped because
83            // of a fault, we will drop to the code below and attempt to start it again.
84            // We use `get_process_pid` because it searches for the process with the service binary
85            // path, and this path is unique to each service.
86            if self
87                .service_control
88                .get_process_pid(&self.service.bin_path())
89                .is_ok()
90            {
91                debug!("The {} service is already running", self.service.name());
92                if self.verbosity != VerbosityLevel::Minimal {
93                    println!("The {} service is already running", self.service.name());
94                }
95                return Ok(());
96            }
97        }
98
99        // At this point the service either hasn't been started for the first time or it has been
100        // stopped. If it was stopped, it was either intentional or because it crashed.
101        if self.verbosity != VerbosityLevel::Minimal {
102            println!("Attempting to start {}...", self.service.name());
103        }
104        self.service_control
105            .start(&self.service.name(), self.service.is_user_mode())?;
106        self.service_control.wait(RPC_START_UP_DELAY_MS);
107
108        // This is an attempt to see whether the service process has actually launched. You don't
109        // always get an error from the service infrastructure.
110        //
111        // There might be many different `safenode` processes running, but since each service has
112        // its own isolated binary, we use the binary path to uniquely identify it.
113        match self
114            .service_control
115            .get_process_pid(&self.service.bin_path())
116        {
117            Ok(pid) => {
118                debug!(
119                    "Service process started for {} with PID {}",
120                    self.service.name(),
121                    pid
122                );
123                self.service.on_start(Some(pid), true).await?;
124
125                info!(
126                    "Service {} has been started successfully",
127                    self.service.name()
128                );
129            }
130            Err(sn_service_management::error::Error::ServiceProcessNotFound(_)) => {
131                error!("The '{}' service has failed to start because ServiceProcessNotFound when fetching PID", self.service.name());
132                return Err(Error::PidNotFoundAfterStarting);
133            }
134            Err(err) => {
135                error!("Failed to start service, because PID could not be obtained: {err}");
136                return Err(err.into());
137            }
138        };
139
140        if self.verbosity != VerbosityLevel::Minimal {
141            println!("{} Started {} service", "✓".green(), self.service.name());
142            println!(
143                "  - PID: {}",
144                self.service
145                    .pid()
146                    .map_or("-".to_string(), |p| p.to_string())
147            );
148            println!(
149                "  - Bin path: {}",
150                self.service.bin_path().to_string_lossy()
151            );
152            println!(
153                "  - Data path: {}",
154                self.service.data_dir_path().to_string_lossy()
155            );
156            println!(
157                "  - Logs path: {}",
158                self.service.log_dir_path().to_string_lossy()
159            );
160        }
161        Ok(())
162    }
163
164    pub async fn stop(&mut self) -> Result<()> {
165        info!("Stopping the {} service", self.service.name());
166        match self.service.status() {
167            ServiceStatus::Added => {
168                debug!(
169                    "The {} service has not been started since it was installed",
170                    self.service.name()
171                );
172                if self.verbosity != VerbosityLevel::Minimal {
173                    println!(
174                        "Service {} has not been started since it was installed",
175                        self.service.name()
176                    );
177                }
178                Ok(())
179            }
180            ServiceStatus::Removed => {
181                debug!("The {} service has been removed", self.service.name());
182                if self.verbosity != VerbosityLevel::Minimal {
183                    println!("Service {} has been removed", self.service.name());
184                }
185                Ok(())
186            }
187            ServiceStatus::Running => {
188                let pid = self.service.pid().ok_or(Error::PidNotSet)?;
189                let name = self.service.name();
190
191                if self
192                    .service_control
193                    .get_process_pid(&self.service.bin_path())
194                    .is_ok()
195                {
196                    if self.verbosity != VerbosityLevel::Minimal {
197                        println!("Attempting to stop {}...", name);
198                    }
199                    self.service_control
200                        .stop(&name, self.service.is_user_mode())?;
201                    if self.verbosity != VerbosityLevel::Minimal {
202                        println!(
203                            "{} Service {} with PID {} was stopped",
204                            "✓".green(),
205                            name,
206                            pid
207                        );
208                    }
209                } else if self.verbosity != VerbosityLevel::Minimal {
210                    debug!("Service {name} was already stopped");
211                    println!("{} Service {} was already stopped", "✓".green(), name);
212                }
213
214                self.service.on_stop().await?;
215                info!("Service {name} has been stopped successfully.");
216                Ok(())
217            }
218            ServiceStatus::Stopped => {
219                debug!("Service {} was already stopped", self.service.name());
220                if self.verbosity != VerbosityLevel::Minimal {
221                    println!(
222                        "{} Service {} was already stopped",
223                        "✓".green(),
224                        self.service.name()
225                    );
226                }
227                Ok(())
228            }
229        }
230    }
231
232    pub async fn remove(&mut self, keep_directories: bool) -> Result<()> {
233        if let ServiceStatus::Running = self.service.status() {
234            if self
235                .service_control
236                .get_process_pid(&self.service.bin_path())
237                .is_ok()
238            {
239                error!(
240                    "Service {} is already running. Stop it before removing it",
241                    self.service.name()
242                );
243                return Err(Error::ServiceAlreadyRunning(vec![self.service.name()]));
244            } else {
245                // If the node wasn't actually running, we should give the user an opportunity to
246                // check why it may have failed before removing everything.
247                self.service.on_stop().await?;
248                error!(
249                "The service: {} was marked as running but it had actually stopped. You may want to check the logs for errors before removing it. To remove the service, run the command again.",
250                self.service.name()
251            );
252                return Err(Error::ServiceStatusMismatch {
253                    expected: ServiceStatus::Running,
254                });
255            }
256        }
257
258        match self
259            .service_control
260            .uninstall(&self.service.name(), self.service.is_user_mode())
261        {
262            Ok(()) => {
263                debug!("Service {} has been uninstalled", self.service.name());
264            }
265            Err(err) => match err {
266                ServiceError::ServiceRemovedManually(name) => {
267                    warn!("The user appears to have removed the {name} service manually. Skipping the error.",);
268                    // The user has deleted the service definition file, which the service manager
269                    // crate treats as an error. We then return our own error type, which allows us
270                    // to handle it here and just proceed with removing the service from the
271                    // registry.
272                    println!("The user appears to have removed the {name} service manually");
273                }
274                ServiceError::ServiceDoesNotExists(name) => {
275                    warn!("The service {name} has most probably been removed already, it does not exists. Skipping the error.");
276                }
277                _ => {
278                    error!("Error uninstalling the service: {err}");
279                    return Err(err.into());
280                }
281            },
282        }
283
284        if !keep_directories {
285            debug!(
286                "Removing data and log directories for {}",
287                self.service.name()
288            );
289            // It's possible the user deleted either of these directories manually.
290            // We can just proceed with removing the service from the registry.
291            if self.service.data_dir_path().exists() {
292                debug!("Removing data directory {:?}", self.service.data_dir_path());
293                std::fs::remove_dir_all(self.service.data_dir_path())?;
294            }
295            if self.service.log_dir_path().exists() {
296                debug!("Removing log directory {:?}", self.service.log_dir_path());
297                std::fs::remove_dir_all(self.service.log_dir_path())?;
298            }
299        }
300
301        self.service.on_remove();
302        info!(
303            "Service {} has been removed successfully.",
304            self.service.name()
305        );
306
307        if self.verbosity != VerbosityLevel::Minimal {
308            println!(
309                "{} Service {} was removed",
310                "✓".green(),
311                self.service.name()
312            );
313        }
314
315        Ok(())
316    }
317
318    pub async fn upgrade(&mut self, options: UpgradeOptions) -> Result<UpgradeResult> {
319        let current_version = Version::parse(&self.service.version())?;
320        if !options.force
321            && (current_version == options.target_version
322                || options.target_version < current_version)
323        {
324            info!(
325                "The service {} is already at the latest version. No upgrade is required.",
326                self.service.name()
327            );
328            return Ok(UpgradeResult::NotRequired);
329        }
330
331        debug!("Stopping the service and copying the binary");
332        self.stop().await?;
333        std::fs::copy(options.clone().target_bin_path, self.service.bin_path())?;
334
335        self.service_control
336            .uninstall(&self.service.name(), self.service.is_user_mode())?;
337        self.service_control.install(
338            self.service
339                .build_upgrade_install_context(options.clone())?,
340            self.service.is_user_mode(),
341        )?;
342
343        if options.start_service {
344            match self.start().await {
345                Ok(start_duration) => start_duration,
346                Err(err) => {
347                    self.service
348                        .set_version(&options.target_version.to_string());
349                    info!("The service has been upgraded but could not be started: {err}");
350                    return Ok(UpgradeResult::UpgradedButNotStarted(
351                        current_version.to_string(),
352                        options.target_version.to_string(),
353                        err.to_string(),
354                    ));
355                }
356            }
357        }
358        self.service
359            .set_version(&options.target_version.to_string());
360
361        if options.force {
362            Ok(UpgradeResult::Forced(
363                current_version.to_string(),
364                options.target_version.to_string(),
365            ))
366        } else {
367            Ok(UpgradeResult::Upgraded(
368                current_version.to_string(),
369                options.target_version.to_string(),
370            ))
371        }
372    }
373}
374
375pub async fn status_report(
376    node_registry: &mut NodeRegistry,
377    service_control: &dyn ServiceControl,
378    detailed_view: bool,
379    output_json: bool,
380    fail: bool,
381    is_local_network: bool,
382) -> Result<()> {
383    refresh_node_registry(
384        node_registry,
385        service_control,
386        !output_json,
387        true,
388        is_local_network,
389    )
390    .await?;
391
392    if output_json {
393        let json = serde_json::to_string_pretty(&node_registry.to_status_summary())?;
394        println!("{json}");
395    } else if detailed_view {
396        for node in &node_registry.nodes {
397            print_banner(&format!(
398                "{} - {}",
399                &node.service_name,
400                format_status_without_colour(&node.status)
401            ));
402            println!("Version: {}", node.version);
403            println!(
404                "Peer ID: {}",
405                node.peer_id.map_or("-".to_string(), |p| p.to_string())
406            );
407            println!("RPC Socket: {}", node.rpc_socket_addr);
408            println!("Listen Addresses: {:?}", node.listen_addr);
409            println!(
410                "PID: {}",
411                node.pid.map_or("-".to_string(), |p| p.to_string())
412            );
413            println!("Data path: {}", node.data_dir_path.to_string_lossy());
414            println!("Log path: {}", node.log_dir_path.to_string_lossy());
415            println!("Bin path: {}", node.safenode_path.to_string_lossy());
416            println!(
417                "Connected peers: {}",
418                node.connected_peers
419                    .as_ref()
420                    .map_or("-".to_string(), |p| p.len().to_string())
421            );
422            println!(
423                "Reward balance: {}",
424                node.reward_balance
425                    .map_or("-".to_string(), |b| b.to_string())
426            );
427            println!("Rewards address: {}", node.rewards_address);
428            println!();
429        }
430
431        if let Some(daemon) = &node_registry.daemon {
432            print_banner(&format!(
433                "{} - {}",
434                &daemon.service_name,
435                format_status(&daemon.status)
436            ));
437            println!("Version: {}", daemon.version);
438            println!("Bin path: {}", daemon.daemon_path.to_string_lossy());
439        }
440
441        if let Some(faucet) = &node_registry.faucet {
442            print_banner(&format!(
443                "{} - {}",
444                &faucet.service_name,
445                format_status(&faucet.status)
446            ));
447            println!("Version: {}", faucet.version);
448            println!("Bin path: {}", faucet.faucet_path.to_string_lossy());
449            println!("Log path: {}", faucet.log_dir_path.to_string_lossy());
450        }
451    } else {
452        println!(
453            "{:<18} {:<52} {:<7} {:>15}",
454            "Service Name", "Peer ID", "Status", "Connected Peers"
455        );
456        let nodes = node_registry
457            .nodes
458            .iter()
459            .filter(|n| n.status != ServiceStatus::Removed)
460            .collect::<Vec<&NodeServiceData>>();
461        for node in nodes {
462            let peer_id = node.peer_id.map_or("-".to_string(), |p| p.to_string());
463            let connected_peers = node
464                .connected_peers
465                .clone()
466                .map_or("-".to_string(), |p| p.len().to_string());
467            println!(
468                "{:<18} {:<52} {:<7} {:>15}",
469                node.service_name,
470                peer_id,
471                format_status(&node.status),
472                connected_peers
473            );
474        }
475        if let Some(daemon) = &node_registry.daemon {
476            println!(
477                "{:<18} {:<52} {:<7} {:>15}",
478                daemon.service_name,
479                "-",
480                format_status(&daemon.status),
481                "-"
482            );
483        }
484        if let Some(faucet) = &node_registry.faucet {
485            println!(
486                "{:<18} {:<52} {:<7} {:>15}",
487                faucet.service_name,
488                "-",
489                format_status(&faucet.status),
490                "-"
491            );
492        }
493    }
494
495    if fail {
496        let non_running_services = node_registry
497            .nodes
498            .iter()
499            .filter_map(|n| {
500                if n.status != ServiceStatus::Running {
501                    Some(n.service_name.clone())
502                } else {
503                    None
504                }
505            })
506            .collect::<Vec<String>>();
507        if non_running_services.is_empty() {
508            info!("Fail is set to true, but all services are running.");
509        } else {
510            error!(
511                "One or more nodes are not in a running state: {non_running_services:?}
512            "
513            );
514
515            return Err(Error::ServiceNotRunning(non_running_services));
516        }
517    }
518
519    Ok(())
520}
521
522/// Refreshes the status of the node registry's services.
523///
524/// The mechanism is different, depending on whether it's a service-based network or a local
525/// network.
526///
527/// For a service-based network, at a minimum, the refresh determines if each service is running.
528/// It does that by trying to find a process whose binary path matches the path of the binary for
529/// the service. Since each service uses its own binary, the path is a unique identifer. So you can
530/// know if any *particular* service is running or not. A full refresh uses the RPC client to
531/// connect to the node's RPC service to determine things like the number of connected peers.
532///
533/// For a local network, the node paths are not unique, so we can't use that. We consider the node
534/// running if we can connect to its RPC service; otherwise it is considered stopped.
535pub async fn refresh_node_registry(
536    node_registry: &mut NodeRegistry,
537    service_control: &dyn ServiceControl,
538    print_refresh_message: bool,
539    full_refresh: bool,
540    is_local_network: bool,
541) -> Result<()> {
542    // This message is useful for users, but needs to be suppressed when a JSON output is
543    // requested.
544    if print_refresh_message {
545        println!("Refreshing the node registry...");
546    }
547    info!("Refreshing the node registry");
548
549    for node in &mut node_registry.nodes {
550        // The `status` command can run before a node is started and therefore before its wallet
551        // exists.
552        match HotWallet::try_load_from(&node.data_dir_path) {
553            Ok(wallet) => {
554                node.reward_balance = Some(AttoTokens::from_u64(wallet.balance().as_nano()));
555                trace!(
556                    "Wallet balance for node {}: {}",
557                    node.service_name,
558                    wallet.balance()
559                );
560            }
561            Err(_) => node.reward_balance = None,
562        }
563
564        let mut rpc_client = RpcClient::from_socket_addr(node.rpc_socket_addr);
565        rpc_client.set_max_attempts(1);
566        let mut service = NodeService::new(node, Box::new(rpc_client.clone()));
567
568        if is_local_network {
569            // For a local network, retrieving the process by its path does not work, because the
570            // paths are not unique: they are all launched from the same binary. Instead we will
571            // just determine whether the node is running by connecting to its RPC service. We
572            // only need to distinguish between `RUNNING` and `STOPPED` for a local network.
573            match rpc_client.node_info().await {
574                Ok(info) => {
575                    let pid = info.pid;
576                    debug!(
577                        "local node {} is running with PID {pid}",
578                        service.service_data.service_name
579                    );
580                    service.on_start(Some(pid), full_refresh).await?;
581                }
582                Err(_) => {
583                    debug!(
584                        "Failed to retrieve PID for local node {}",
585                        service.service_data.service_name
586                    );
587                    service.on_stop().await?;
588                }
589            }
590        } else {
591            match service_control.get_process_pid(&service.bin_path()) {
592                Ok(pid) => {
593                    debug!(
594                        "{} is running with PID {pid}",
595                        service.service_data.service_name
596                    );
597                    service.on_start(Some(pid), full_refresh).await?;
598                }
599                Err(_) => {
600                    match service.status() {
601                        ServiceStatus::Added => {
602                            // If the service is still at `Added` status, there hasn't been an attempt
603                            // to start it since it was installed. It's useful to keep this status
604                            // rather than setting it to `STOPPED`, so that the user can differentiate.
605                            debug!(
606                                "{} has not been started since it was installed",
607                                service.service_data.service_name
608                            );
609                        }
610                        ServiceStatus::Removed => {
611                            // In the case of the service being removed, we want to retain that state
612                            // and not have it marked `STOPPED`.
613                            debug!("{} has been removed", service.service_data.service_name);
614                        }
615                        _ => {
616                            debug!(
617                                "Failed to retrieve PID for {}",
618                                service.service_data.service_name
619                            );
620                            service.on_stop().await?;
621                        }
622                    }
623                }
624            }
625        }
626    }
627    Ok(())
628}
629
630pub fn print_banner(text: &str) {
631    let padding = 2;
632    let text_width = text.len() + padding * 2;
633    let border_chars = 2;
634    let total_width = text_width + border_chars;
635    let top_bottom = "═".repeat(total_width);
636
637    println!("╔{}╗", top_bottom);
638    println!("║ {:^width$} ║", text, width = text_width);
639    println!("╚{}╝", top_bottom);
640}
641
642fn format_status(status: &ServiceStatus) -> String {
643    match status {
644        ServiceStatus::Running => "RUNNING".green().to_string(),
645        ServiceStatus::Stopped => "STOPPED".red().to_string(),
646        ServiceStatus::Added => "ADDED".yellow().to_string(),
647        ServiceStatus::Removed => "REMOVED".red().to_string(),
648    }
649}
650
651fn format_status_without_colour(status: &ServiceStatus) -> String {
652    match status {
653        ServiceStatus::Running => "RUNNING".to_string(),
654        ServiceStatus::Stopped => "STOPPED".to_string(),
655        ServiceStatus::Added => "ADDED".to_string(),
656        ServiceStatus::Removed => "REMOVED".to_string(),
657    }
658}
659
660#[cfg(test)]
661mod tests {
662    use super::*;
663    use assert_fs::prelude::*;
664    use assert_matches::assert_matches;
665    use async_trait::async_trait;
666    use color_eyre::eyre::Result;
667    use libp2p_identity::PeerId;
668    use mockall::{mock, predicate::*};
669    use predicates::prelude::*;
670    use service_manager::ServiceInstallCtx;
671    use sn_evm::{AttoTokens, CustomNetwork, EvmNetwork, RewardsAddress};
672    use sn_logging::LogFormat;
673    use sn_service_management::{
674        error::{Error as ServiceControlError, Result as ServiceControlResult},
675        node::{NodeService, NodeServiceData},
676        rpc::{NetworkInfo, NodeInfo, RecordAddress, RpcActions},
677        UpgradeOptions, UpgradeResult,
678    };
679    use std::{
680        ffi::OsString,
681        net::{IpAddr, Ipv4Addr, SocketAddr},
682        path::{Path, PathBuf},
683        str::FromStr,
684        time::Duration,
685    };
686
687    mock! {
688        pub RpcClient {}
689        #[async_trait]
690        impl RpcActions for RpcClient {
691            async fn node_info(&self) -> ServiceControlResult<NodeInfo>;
692            async fn network_info(&self) -> ServiceControlResult<NetworkInfo>;
693            async fn record_addresses(&self) -> ServiceControlResult<Vec<RecordAddress>>;
694            async fn node_restart(&self, delay_millis: u64, retain_peer_id: bool) -> ServiceControlResult<()>;
695            async fn node_stop(&self, delay_millis: u64) -> ServiceControlResult<()>;
696            async fn node_update(&self, delay_millis: u64) -> ServiceControlResult<()>;
697            async fn is_node_connected_to_network(&self, timeout: std::time::Duration) -> ServiceControlResult<()>;
698            async fn update_log_level(&self, log_levels: String) -> ServiceControlResult<()>;
699        }
700    }
701
702    mock! {
703        pub ServiceControl {}
704        impl ServiceControl for ServiceControl {
705            fn create_service_user(&self, username: &str) -> ServiceControlResult<()>;
706            fn get_available_port(&self) -> ServiceControlResult<u16>;
707            fn install(&self, install_ctx: ServiceInstallCtx, user_mode: bool) -> ServiceControlResult<()>;
708            fn get_process_pid(&self, bin_path: &Path) -> ServiceControlResult<u32>;
709            fn start(&self, service_name: &str, user_mode: bool) -> ServiceControlResult<()>;
710            fn stop(&self, service_name: &str, user_mode: bool) -> ServiceControlResult<()>;
711            fn uninstall(&self, service_name: &str, user_mode: bool) -> ServiceControlResult<()>;
712            fn wait(&self, delay: u64);
713        }
714    }
715
716    #[tokio::test]
717    async fn start_should_start_a_newly_installed_service() -> Result<()> {
718        let mut mock_service_control = MockServiceControl::new();
719        let mut mock_rpc_client = MockRpcClient::new();
720
721        mock_service_control
722            .expect_start()
723            .with(eq("safenode1"), eq(false))
724            .times(1)
725            .returning(|_, _| Ok(()));
726        mock_service_control
727            .expect_wait()
728            .with(eq(3000))
729            .times(1)
730            .returning(|_| ());
731        mock_service_control
732            .expect_get_process_pid()
733            .with(eq(PathBuf::from(
734                "/var/safenode-manager/services/safenode1/safenode",
735            )))
736            .times(1)
737            .returning(|_| Ok(1000));
738
739        mock_rpc_client.expect_node_info().times(1).returning(|| {
740            Ok(NodeInfo {
741                pid: 1000,
742                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
743                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
744                log_path: PathBuf::from("/var/log/safenode/safenode1"),
745                version: "0.98.1".to_string(),
746                uptime: std::time::Duration::from_secs(1), // the service was just started
747                wallet_balance: 0,
748            })
749        });
750        mock_rpc_client
751            .expect_network_info()
752            .times(1)
753            .returning(|| {
754                Ok(NetworkInfo {
755                    connected_peers: vec![PeerId::from_str(
756                        "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
757                    )?],
758                    listeners: Vec::new(),
759                })
760            });
761
762        let mut service_data = NodeServiceData {
763            auto_restart: false,
764            connected_peers: None,
765            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
766            evm_network: EvmNetwork::Custom(CustomNetwork {
767                rpc_url_http: "http://localhost:8545".parse()?,
768                payment_token_address: RewardsAddress::from_str(
769                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
770                )?,
771                data_payments_address: RewardsAddress::from_str(
772                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
773                )?,
774            }),
775            genesis: false,
776            home_network: false,
777            listen_addr: None,
778            local: false,
779            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
780            log_format: None,
781            max_archived_log_files: None,
782            max_log_files: None,
783            metrics_port: None,
784            node_ip: None,
785            node_port: None,
786            number: 1,
787            owner: None,
788            peer_id: None,
789            pid: None,
790            rewards_address: RewardsAddress::from_str(
791                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
792            )?,
793            reward_balance: Some(AttoTokens::zero()),
794            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
795            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
796            service_name: "safenode1".to_string(),
797            status: ServiceStatus::Added,
798            upnp: false,
799            user: Some("safe".to_string()),
800            user_mode: false,
801            version: "0.98.1".to_string(),
802        };
803        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
804
805        let mut service_manager = ServiceManager::new(
806            service,
807            Box::new(mock_service_control),
808            VerbosityLevel::Normal,
809        );
810
811        service_manager.start().await?;
812
813        assert_eq!(
814            service_manager.service.service_data.connected_peers,
815            Some(vec![PeerId::from_str(
816                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
817            )?,])
818        );
819        assert_eq!(service_manager.service.service_data.pid, Some(1000));
820        assert_eq!(
821            service_manager.service.service_data.peer_id,
822            Some(PeerId::from_str(
823                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR"
824            )?)
825        );
826        assert_matches!(
827            service_manager.service.service_data.status,
828            ServiceStatus::Running
829        );
830
831        Ok(())
832    }
833
834    #[tokio::test]
835    async fn start_should_start_a_stopped_service() -> Result<()> {
836        let mut mock_service_control = MockServiceControl::new();
837        let mut mock_rpc_client = MockRpcClient::new();
838
839        mock_service_control
840            .expect_start()
841            .with(eq("safenode1"), eq(false))
842            .times(1)
843            .returning(|_, _| Ok(()));
844        mock_service_control
845            .expect_wait()
846            .with(eq(3000))
847            .times(1)
848            .returning(|_| ());
849        mock_service_control
850            .expect_get_process_pid()
851            .with(eq(PathBuf::from(
852                "/var/safenode-manager/services/safenode1/safenode",
853            )))
854            .times(1)
855            .returning(|_| Ok(1000));
856
857        mock_rpc_client.expect_node_info().times(1).returning(|| {
858            Ok(NodeInfo {
859                pid: 1000,
860                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
861                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
862                log_path: PathBuf::from("/var/log/safenode/safenode1"),
863                version: "0.98.1".to_string(),
864                uptime: std::time::Duration::from_secs(1), // the service was just started
865                wallet_balance: 0,
866            })
867        });
868        mock_rpc_client
869            .expect_network_info()
870            .times(1)
871            .returning(|| {
872                Ok(NetworkInfo {
873                    connected_peers: Vec::new(),
874                    listeners: Vec::new(),
875                })
876            });
877
878        let mut service_data = NodeServiceData {
879            auto_restart: false,
880            connected_peers: None,
881            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
882            evm_network: EvmNetwork::Custom(CustomNetwork {
883                rpc_url_http: "http://localhost:8545".parse()?,
884                payment_token_address: RewardsAddress::from_str(
885                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
886                )?,
887                data_payments_address: RewardsAddress::from_str(
888                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
889                )?,
890            }),
891            genesis: false,
892            home_network: false,
893            listen_addr: None,
894            local: false,
895            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
896            log_format: None,
897            max_archived_log_files: None,
898            max_log_files: None,
899            metrics_port: None,
900            node_ip: None,
901            node_port: None,
902            number: 1,
903            owner: None,
904            peer_id: Some(PeerId::from_str(
905                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
906            )?),
907            pid: None,
908            rewards_address: RewardsAddress::from_str(
909                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
910            )?,
911            reward_balance: Some(AttoTokens::zero()),
912            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
913            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
914            service_name: "safenode1".to_string(),
915            status: ServiceStatus::Stopped,
916            upnp: false,
917            user: Some("safe".to_string()),
918            user_mode: false,
919            version: "0.98.1".to_string(),
920        };
921        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
922
923        let mut service_manager = ServiceManager::new(
924            service,
925            Box::new(mock_service_control),
926            VerbosityLevel::Normal,
927        );
928
929        service_manager.start().await?;
930
931        assert_eq!(service_manager.service.service_data.pid, Some(1000));
932        assert_eq!(
933            service_manager.service.service_data.peer_id,
934            Some(PeerId::from_str(
935                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR"
936            )?)
937        );
938        assert_matches!(
939            service_manager.service.service_data.status,
940            ServiceStatus::Running
941        );
942
943        Ok(())
944    }
945
946    #[tokio::test]
947    async fn start_should_not_attempt_to_start_a_running_service() -> Result<()> {
948        let mut mock_service_control = MockServiceControl::new();
949        let mock_rpc_client = MockRpcClient::new();
950
951        mock_service_control
952            .expect_get_process_pid()
953            .with(eq(PathBuf::from(
954                "/var/safenode-manager/services/safenode1/safenode",
955            )))
956            .times(1)
957            .returning(|_| Ok(100));
958
959        let mut service_data = NodeServiceData {
960            auto_restart: false,
961            connected_peers: None,
962            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
963            evm_network: EvmNetwork::Custom(CustomNetwork {
964                rpc_url_http: "http://localhost:8545".parse()?,
965                payment_token_address: RewardsAddress::from_str(
966                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
967                )?,
968                data_payments_address: RewardsAddress::from_str(
969                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
970                )?,
971            }),
972            genesis: false,
973            home_network: false,
974            listen_addr: None,
975            local: false,
976            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
977            log_format: None,
978            max_archived_log_files: None,
979            max_log_files: None,
980            metrics_port: None,
981            node_ip: None,
982            node_port: None,
983            number: 1,
984            owner: None,
985            peer_id: Some(PeerId::from_str(
986                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
987            )?),
988            pid: Some(1000),
989            rewards_address: RewardsAddress::from_str(
990                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
991            )?,
992            reward_balance: Some(AttoTokens::zero()),
993            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
994            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
995            service_name: "safenode1".to_string(),
996            status: ServiceStatus::Running,
997            upnp: false,
998            user: Some("safe".to_string()),
999            user_mode: false,
1000            version: "0.98.1".to_string(),
1001        };
1002        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
1003
1004        let mut service_manager = ServiceManager::new(
1005            service,
1006            Box::new(mock_service_control),
1007            VerbosityLevel::Normal,
1008        );
1009
1010        service_manager.start().await?;
1011
1012        assert_eq!(service_manager.service.service_data.pid, Some(1000));
1013        assert_eq!(
1014            service_manager.service.service_data.peer_id,
1015            Some(PeerId::from_str(
1016                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR"
1017            )?)
1018        );
1019        assert_matches!(
1020            service_manager.service.service_data.status,
1021            ServiceStatus::Running
1022        );
1023
1024        Ok(())
1025    }
1026
1027    #[tokio::test]
1028    async fn start_should_start_a_service_marked_as_running_but_had_since_stopped() -> Result<()> {
1029        let mut mock_service_control = MockServiceControl::new();
1030        let mut mock_rpc_client = MockRpcClient::new();
1031
1032        mock_service_control
1033            .expect_get_process_pid()
1034            .with(eq(PathBuf::from(
1035                "/var/safenode-manager/services/safenode1/safenode",
1036            )))
1037            .times(1)
1038            .returning(|_| {
1039                Err(ServiceError::ServiceProcessNotFound(
1040                    "Could not find process at '/var/safenode-manager/services/safenode1/safenode'"
1041                        .to_string(),
1042                ))
1043            });
1044        mock_service_control
1045            .expect_start()
1046            .with(eq("safenode1"), eq(false))
1047            .times(1)
1048            .returning(|_, _| Ok(()));
1049        mock_service_control
1050            .expect_wait()
1051            .with(eq(3000))
1052            .times(1)
1053            .returning(|_| ());
1054        mock_service_control
1055            .expect_get_process_pid()
1056            .with(eq(PathBuf::from(
1057                "/var/safenode-manager/services/safenode1/safenode",
1058            )))
1059            .times(1)
1060            .returning(|_| Ok(1000));
1061
1062        mock_rpc_client.expect_node_info().times(1).returning(|| {
1063            Ok(NodeInfo {
1064                pid: 1000,
1065                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
1066                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1067                log_path: PathBuf::from("/var/log/safenode/safenode1"),
1068                version: "0.98.1".to_string(),
1069                uptime: std::time::Duration::from_secs(1), // the service was just started
1070                wallet_balance: 0,
1071            })
1072        });
1073        mock_rpc_client
1074            .expect_network_info()
1075            .times(1)
1076            .returning(|| {
1077                Ok(NetworkInfo {
1078                    connected_peers: Vec::new(),
1079                    listeners: Vec::new(),
1080                })
1081            });
1082
1083        let mut service_data = NodeServiceData {
1084            auto_restart: false,
1085            connected_peers: None,
1086            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1087            evm_network: EvmNetwork::Custom(CustomNetwork {
1088                rpc_url_http: "http://localhost:8545".parse()?,
1089                payment_token_address: RewardsAddress::from_str(
1090                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1091                )?,
1092                data_payments_address: RewardsAddress::from_str(
1093                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1094                )?,
1095            }),
1096            genesis: false,
1097            home_network: false,
1098            listen_addr: None,
1099            local: false,
1100            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1101            log_format: None,
1102            max_archived_log_files: None,
1103            max_log_files: None,
1104            metrics_port: None,
1105            node_ip: None,
1106            node_port: None,
1107            number: 1,
1108            owner: None,
1109            peer_id: Some(PeerId::from_str(
1110                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
1111            )?),
1112            pid: Some(1000),
1113            rewards_address: RewardsAddress::from_str(
1114                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1115            )?,
1116            reward_balance: Some(AttoTokens::zero()),
1117            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1118            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
1119            service_name: "safenode1".to_string(),
1120            status: ServiceStatus::Running,
1121            upnp: false,
1122            user: Some("safe".to_string()),
1123            user_mode: false,
1124            version: "0.98.1".to_string(),
1125        };
1126        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
1127
1128        let mut service_manager = ServiceManager::new(
1129            service,
1130            Box::new(mock_service_control),
1131            VerbosityLevel::Normal,
1132        );
1133
1134        service_manager.start().await?;
1135
1136        assert_eq!(service_manager.service.service_data.pid, Some(1000));
1137        assert_eq!(
1138            service_manager.service.service_data.peer_id,
1139            Some(PeerId::from_str(
1140                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR"
1141            )?)
1142        );
1143        assert_matches!(
1144            service_manager.service.service_data.status,
1145            ServiceStatus::Running
1146        );
1147
1148        Ok(())
1149    }
1150
1151    #[tokio::test]
1152    async fn start_should_return_an_error_if_the_process_was_not_found() -> Result<()> {
1153        let mut mock_service_control = MockServiceControl::new();
1154
1155        mock_service_control
1156            .expect_start()
1157            .with(eq("safenode1"), eq(false))
1158            .times(1)
1159            .returning(|_, _| Ok(()));
1160        mock_service_control
1161            .expect_wait()
1162            .with(eq(3000))
1163            .times(1)
1164            .returning(|_| ());
1165        mock_service_control
1166            .expect_get_process_pid()
1167            .with(eq(PathBuf::from(
1168                "/var/safenode-manager/services/safenode1/safenode",
1169            )))
1170            .times(1)
1171            .returning(|_| {
1172                Err(ServiceControlError::ServiceProcessNotFound(
1173                    "/var/safenode-manager/services/safenode1/safenode".to_string(),
1174                ))
1175            });
1176
1177        let mut service_data = NodeServiceData {
1178            auto_restart: false,
1179            connected_peers: None,
1180            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1181            evm_network: EvmNetwork::Custom(CustomNetwork {
1182                rpc_url_http: "http://localhost:8545".parse()?,
1183                payment_token_address: RewardsAddress::from_str(
1184                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1185                )?,
1186                data_payments_address: RewardsAddress::from_str(
1187                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1188                )?,
1189            }),
1190            genesis: false,
1191            home_network: false,
1192            listen_addr: None,
1193            local: false,
1194            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1195            log_format: None,
1196            max_archived_log_files: None,
1197            max_log_files: None,
1198            metrics_port: None,
1199            node_ip: None,
1200            node_port: None,
1201            number: 1,
1202            owner: None,
1203            peer_id: None,
1204            pid: None,
1205            rewards_address: RewardsAddress::from_str(
1206                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1207            )?,
1208            reward_balance: Some(AttoTokens::zero()),
1209            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1210            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
1211            service_name: "safenode1".to_string(),
1212            status: ServiceStatus::Added,
1213            upnp: false,
1214            user: Some("safe".to_string()),
1215            user_mode: false,
1216            version: "0.98.1".to_string(),
1217        };
1218        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
1219        let mut service_manager = ServiceManager::new(
1220            service,
1221            Box::new(mock_service_control),
1222            VerbosityLevel::Normal,
1223        );
1224
1225        let result = service_manager.start().await;
1226        match result {
1227            Ok(_) => panic!("This test should have resulted in an error"),
1228            Err(e) => assert_eq!(
1229                "The PID of the process was not found after starting it.",
1230                e.to_string()
1231            ),
1232        }
1233
1234        Ok(())
1235    }
1236
1237    #[tokio::test]
1238    async fn start_should_start_a_user_mode_service() -> Result<()> {
1239        let mut mock_service_control = MockServiceControl::new();
1240        let mut mock_rpc_client = MockRpcClient::new();
1241
1242        mock_service_control
1243            .expect_start()
1244            .with(eq("safenode1"), eq(true))
1245            .times(1)
1246            .returning(|_, _| Ok(()));
1247        mock_service_control
1248            .expect_wait()
1249            .with(eq(3000))
1250            .times(1)
1251            .returning(|_| ());
1252        mock_service_control
1253            .expect_get_process_pid()
1254            .with(eq(PathBuf::from(
1255                "/var/safenode-manager/services/safenode1/safenode",
1256            )))
1257            .times(1)
1258            .returning(|_| Ok(100));
1259
1260        mock_rpc_client.expect_node_info().times(1).returning(|| {
1261            Ok(NodeInfo {
1262                pid: 1000,
1263                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
1264                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1265                log_path: PathBuf::from("/var/log/safenode/safenode1"),
1266                version: "0.98.1".to_string(),
1267                uptime: std::time::Duration::from_secs(1), // the service was just started
1268                wallet_balance: 0,
1269            })
1270        });
1271        mock_rpc_client
1272            .expect_network_info()
1273            .times(1)
1274            .returning(|| {
1275                Ok(NetworkInfo {
1276                    connected_peers: Vec::new(),
1277                    listeners: Vec::new(),
1278                })
1279            });
1280
1281        let mut service_data = NodeServiceData {
1282            auto_restart: false,
1283            connected_peers: None,
1284            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1285            evm_network: EvmNetwork::Custom(CustomNetwork {
1286                rpc_url_http: "http://localhost:8545".parse()?,
1287                payment_token_address: RewardsAddress::from_str(
1288                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1289                )?,
1290                data_payments_address: RewardsAddress::from_str(
1291                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1292                )?,
1293            }),
1294            genesis: false,
1295            home_network: false,
1296            listen_addr: None,
1297            local: false,
1298            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1299            log_format: None,
1300            max_archived_log_files: None,
1301            max_log_files: None,
1302            metrics_port: None,
1303            node_ip: None,
1304            node_port: None,
1305            number: 1,
1306            owner: None,
1307            peer_id: None,
1308            pid: None,
1309            rewards_address: RewardsAddress::from_str(
1310                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1311            )?,
1312            reward_balance: Some(AttoTokens::zero()),
1313            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1314            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
1315            service_name: "safenode1".to_string(),
1316            status: ServiceStatus::Added,
1317            upnp: false,
1318            user: Some("safe".to_string()),
1319            user_mode: true,
1320            version: "0.98.1".to_string(),
1321        };
1322        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
1323
1324        let mut service_manager = ServiceManager::new(
1325            service,
1326            Box::new(mock_service_control),
1327            VerbosityLevel::Normal,
1328        );
1329
1330        service_manager.start().await?;
1331
1332        Ok(())
1333    }
1334
1335    #[tokio::test]
1336    async fn start_should_use_dynamic_startup_delay_if_set() -> Result<()> {
1337        let mut mock_service_control = MockServiceControl::new();
1338        let mut mock_rpc_client = MockRpcClient::new();
1339
1340        mock_service_control
1341            .expect_start()
1342            .with(eq("safenode1"), eq(false))
1343            .times(1)
1344            .returning(|_, _| Ok(()));
1345        mock_service_control
1346            .expect_wait()
1347            .with(eq(3000))
1348            .times(1)
1349            .returning(|_| ());
1350        mock_service_control
1351            .expect_get_process_pid()
1352            .with(eq(PathBuf::from(
1353                "/var/safenode-manager/services/safenode1/safenode",
1354            )))
1355            .times(1)
1356            .returning(|_| Ok(1000));
1357        mock_rpc_client
1358            .expect_is_node_connected_to_network()
1359            .times(1)
1360            .returning(|_| Ok(()));
1361        mock_rpc_client.expect_node_info().times(1).returning(|| {
1362            Ok(NodeInfo {
1363                pid: 1000,
1364                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
1365                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1366                log_path: PathBuf::from("/var/log/safenode/safenode1"),
1367                version: "0.98.1".to_string(),
1368                uptime: std::time::Duration::from_secs(1), // the service was just started
1369                wallet_balance: 0,
1370            })
1371        });
1372        mock_rpc_client
1373            .expect_network_info()
1374            .times(1)
1375            .returning(|| {
1376                Ok(NetworkInfo {
1377                    connected_peers: vec![PeerId::from_str(
1378                        "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
1379                    )?],
1380                    listeners: Vec::new(),
1381                })
1382            });
1383
1384        let mut service_data = NodeServiceData {
1385            auto_restart: false,
1386            connected_peers: None,
1387            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1388            evm_network: EvmNetwork::Custom(CustomNetwork {
1389                rpc_url_http: "http://localhost:8545".parse()?,
1390                payment_token_address: RewardsAddress::from_str(
1391                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1392                )?,
1393                data_payments_address: RewardsAddress::from_str(
1394                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1395                )?,
1396            }),
1397            genesis: false,
1398            home_network: false,
1399            listen_addr: None,
1400            local: false,
1401            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1402            log_format: None,
1403            max_archived_log_files: None,
1404            max_log_files: None,
1405            metrics_port: None,
1406            node_ip: None,
1407            node_port: None,
1408            number: 1,
1409            owner: None,
1410            peer_id: None,
1411            pid: None,
1412            rewards_address: RewardsAddress::from_str(
1413                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1414            )?,
1415            reward_balance: Some(AttoTokens::zero()),
1416            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1417            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
1418            service_name: "safenode1".to_string(),
1419            status: ServiceStatus::Added,
1420            upnp: false,
1421            user: Some("safe".to_string()),
1422            user_mode: false,
1423            version: "0.98.1".to_string(),
1424        };
1425        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client))
1426            .with_connection_timeout(Duration::from_secs(
1427                DEFAULT_NODE_STARTUP_CONNECTION_TIMEOUT_S,
1428            ));
1429        let mut service_manager = ServiceManager::new(
1430            service,
1431            Box::new(mock_service_control),
1432            VerbosityLevel::Normal,
1433        );
1434
1435        service_manager.start().await?;
1436
1437        Ok(())
1438    }
1439
1440    #[tokio::test]
1441    async fn stop_should_stop_a_running_service() -> Result<()> {
1442        let mut mock_service_control = MockServiceControl::new();
1443
1444        mock_service_control
1445            .expect_stop()
1446            .with(eq("safenode1"), eq(false))
1447            .times(1)
1448            .returning(|_, _| Ok(()));
1449        mock_service_control
1450            .expect_get_process_pid()
1451            .with(eq(PathBuf::from(
1452                "/var/safenode-manager/services/safenode1/safenode",
1453            )))
1454            .times(1)
1455            .returning(|_| Ok(100));
1456
1457        let mut service_data = NodeServiceData {
1458            auto_restart: false,
1459            connected_peers: None,
1460            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1461            evm_network: EvmNetwork::Custom(CustomNetwork {
1462                rpc_url_http: "http://localhost:8545".parse()?,
1463                payment_token_address: RewardsAddress::from_str(
1464                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1465                )?,
1466                data_payments_address: RewardsAddress::from_str(
1467                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1468                )?,
1469            }),
1470            genesis: false,
1471            home_network: false,
1472            listen_addr: None,
1473            local: false,
1474            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1475            log_format: None,
1476            max_archived_log_files: None,
1477            max_log_files: None,
1478            metrics_port: None,
1479            node_ip: None,
1480            node_port: None,
1481            number: 1,
1482            owner: None,
1483            peer_id: Some(PeerId::from_str(
1484                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
1485            )?),
1486            pid: Some(1000),
1487            rewards_address: RewardsAddress::from_str(
1488                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1489            )?,
1490            reward_balance: Some(AttoTokens::zero()),
1491            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1492            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
1493            service_name: "safenode1".to_string(),
1494            status: ServiceStatus::Running,
1495            upnp: false,
1496            user: Some("safe".to_string()),
1497            user_mode: false,
1498            version: "0.98.1".to_string(),
1499        };
1500        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
1501        let mut service_manager = ServiceManager::new(
1502            service,
1503            Box::new(mock_service_control),
1504            VerbosityLevel::Normal,
1505        );
1506
1507        service_manager.stop().await?;
1508
1509        assert_eq!(service_manager.service.service_data.pid, None);
1510        assert_eq!(service_manager.service.service_data.connected_peers, None);
1511        assert_matches!(
1512            service_manager.service.service_data.status,
1513            ServiceStatus::Stopped
1514        );
1515        Ok(())
1516    }
1517
1518    #[tokio::test]
1519    async fn stop_should_not_return_error_for_attempt_to_stop_installed_service() -> Result<()> {
1520        let mut service_data = NodeServiceData {
1521            auto_restart: false,
1522            connected_peers: None,
1523            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1524            evm_network: EvmNetwork::Custom(CustomNetwork {
1525                rpc_url_http: "http://localhost:8545".parse()?,
1526                payment_token_address: RewardsAddress::from_str(
1527                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1528                )?,
1529                data_payments_address: RewardsAddress::from_str(
1530                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1531                )?,
1532            }),
1533            genesis: false,
1534            home_network: false,
1535            listen_addr: None,
1536            local: false,
1537            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1538            log_format: None,
1539            max_archived_log_files: None,
1540            max_log_files: None,
1541            metrics_port: None,
1542            node_ip: None,
1543            node_port: None,
1544            number: 1,
1545            owner: None,
1546            peer_id: None,
1547            pid: None,
1548            rewards_address: RewardsAddress::from_str(
1549                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1550            )?,
1551            reward_balance: Some(AttoTokens::zero()),
1552            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1553            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
1554            service_name: "safenode1".to_string(),
1555            status: ServiceStatus::Added,
1556            upnp: false,
1557            user: Some("safe".to_string()),
1558            user_mode: false,
1559            version: "0.98.1".to_string(),
1560        };
1561        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
1562        let mut service_manager = ServiceManager::new(
1563            service,
1564            Box::new(MockServiceControl::new()),
1565            VerbosityLevel::Normal,
1566        );
1567
1568        let result = service_manager.stop().await;
1569
1570        match result {
1571            Ok(()) => Ok(()),
1572            Err(_) => {
1573                panic!("The stop command should be idempotent and do nothing for an added service");
1574            }
1575        }
1576    }
1577
1578    #[tokio::test]
1579    async fn stop_should_return_ok_when_attempting_to_stop_service_that_was_already_stopped(
1580    ) -> Result<()> {
1581        let mut service_data = NodeServiceData {
1582            auto_restart: false,
1583            connected_peers: None,
1584            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1585            evm_network: EvmNetwork::Custom(CustomNetwork {
1586                rpc_url_http: "http://localhost:8545".parse()?,
1587                payment_token_address: RewardsAddress::from_str(
1588                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1589                )?,
1590                data_payments_address: RewardsAddress::from_str(
1591                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1592                )?,
1593            }),
1594            genesis: false,
1595            home_network: false,
1596            listen_addr: None,
1597            local: false,
1598            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1599            log_format: None,
1600            max_archived_log_files: None,
1601            max_log_files: None,
1602            metrics_port: None,
1603            node_ip: None,
1604            node_port: None,
1605            number: 1,
1606            owner: None,
1607            peer_id: Some(PeerId::from_str(
1608                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
1609            )?),
1610            pid: None,
1611            rewards_address: RewardsAddress::from_str(
1612                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1613            )?,
1614            reward_balance: Some(AttoTokens::zero()),
1615            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1616            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
1617            service_name: "safenode1".to_string(),
1618            status: ServiceStatus::Stopped,
1619            upnp: false,
1620            user: Some("safe".to_string()),
1621            user_mode: false,
1622            version: "0.98.1".to_string(),
1623        };
1624        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
1625        let mut service_manager = ServiceManager::new(
1626            service,
1627            Box::new(MockServiceControl::new()),
1628            VerbosityLevel::Normal,
1629        );
1630
1631        let result = service_manager.stop().await;
1632
1633        match result {
1634            Ok(()) => Ok(()),
1635            Err(_) => {
1636                panic!(
1637                    "The stop command should be idempotent and do nothing for an stopped service"
1638                );
1639            }
1640        }
1641    }
1642
1643    #[tokio::test]
1644    async fn stop_should_return_ok_when_attempting_to_stop_a_removed_service() -> Result<()> {
1645        let mut service_data = NodeServiceData {
1646            auto_restart: false,
1647            connected_peers: None,
1648            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1649            evm_network: EvmNetwork::Custom(CustomNetwork {
1650                rpc_url_http: "http://localhost:8545".parse()?,
1651                payment_token_address: RewardsAddress::from_str(
1652                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1653                )?,
1654                data_payments_address: RewardsAddress::from_str(
1655                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1656                )?,
1657            }),
1658            genesis: false,
1659            home_network: false,
1660            listen_addr: None,
1661            local: false,
1662            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1663            log_format: None,
1664            max_archived_log_files: None,
1665            max_log_files: None,
1666            metrics_port: None,
1667            node_ip: None,
1668            node_port: None,
1669            number: 1,
1670            owner: None,
1671            peer_id: None,
1672            pid: None,
1673            rewards_address: RewardsAddress::from_str(
1674                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1675            )?,
1676            reward_balance: Some(AttoTokens::zero()),
1677            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1678            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
1679            service_name: "safenode1".to_string(),
1680            status: ServiceStatus::Removed,
1681            upnp: false,
1682            user: Some("safe".to_string()),
1683            user_mode: false,
1684            version: "0.98.1".to_string(),
1685        };
1686        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
1687        let mut service_manager = ServiceManager::new(
1688            service,
1689            Box::new(MockServiceControl::new()),
1690            VerbosityLevel::Normal,
1691        );
1692
1693        let result = service_manager.stop().await;
1694
1695        match result {
1696            Ok(()) => Ok(()),
1697            Err(_) => {
1698                panic!(
1699                    "The stop command should be idempotent and do nothing for a removed service"
1700                );
1701            }
1702        }
1703    }
1704
1705    #[tokio::test]
1706    async fn stop_should_stop_a_user_mode_service() -> Result<()> {
1707        let mut mock_service_control = MockServiceControl::new();
1708
1709        mock_service_control
1710            .expect_stop()
1711            .with(eq("safenode1"), eq(true))
1712            .times(1)
1713            .returning(|_, _| Ok(()));
1714        mock_service_control
1715            .expect_get_process_pid()
1716            .with(eq(PathBuf::from(
1717                "/var/safenode-manager/services/safenode1/safenode",
1718            )))
1719            .times(1)
1720            .returning(|_| Ok(100));
1721
1722        let mut service_data = NodeServiceData {
1723            auto_restart: false,
1724            connected_peers: None,
1725            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1726            evm_network: EvmNetwork::Custom(CustomNetwork {
1727                rpc_url_http: "http://localhost:8545".parse()?,
1728                payment_token_address: RewardsAddress::from_str(
1729                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1730                )?,
1731                data_payments_address: RewardsAddress::from_str(
1732                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1733                )?,
1734            }),
1735            genesis: false,
1736            home_network: false,
1737            listen_addr: None,
1738            local: false,
1739            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1740            log_format: None,
1741            max_archived_log_files: None,
1742            max_log_files: None,
1743            metrics_port: None,
1744            node_ip: None,
1745            node_port: None,
1746            number: 1,
1747            owner: None,
1748            peer_id: Some(PeerId::from_str(
1749                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
1750            )?),
1751            pid: Some(1000),
1752            rewards_address: RewardsAddress::from_str(
1753                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1754            )?,
1755            reward_balance: Some(AttoTokens::zero()),
1756            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1757            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
1758            service_name: "safenode1".to_string(),
1759            status: ServiceStatus::Running,
1760            upnp: false,
1761            user: None,
1762            user_mode: true,
1763            version: "0.98.1".to_string(),
1764        };
1765        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
1766        let mut service_manager = ServiceManager::new(
1767            service,
1768            Box::new(mock_service_control),
1769            VerbosityLevel::Normal,
1770        );
1771
1772        service_manager.stop().await?;
1773
1774        assert_eq!(service_manager.service.service_data.pid, None);
1775        assert_eq!(service_manager.service.service_data.connected_peers, None);
1776        assert_matches!(
1777            service_manager.service.service_data.status,
1778            ServiceStatus::Stopped
1779        );
1780        Ok(())
1781    }
1782
1783    #[tokio::test]
1784    async fn upgrade_should_upgrade_a_service_to_a_new_version() -> Result<()> {
1785        let current_version = "0.1.0";
1786        let target_version = "0.2.0";
1787
1788        let tmp_data_dir = assert_fs::TempDir::new()?;
1789        let current_install_dir = tmp_data_dir.child("safenode_install");
1790        current_install_dir.create_dir_all()?;
1791
1792        let current_node_bin = current_install_dir.child("safenode");
1793        current_node_bin.write_binary(b"fake safenode binary")?;
1794        let target_node_bin = tmp_data_dir.child("safenode");
1795        target_node_bin.write_binary(b"fake safenode binary")?;
1796
1797        let mut mock_service_control = MockServiceControl::new();
1798        let mut mock_rpc_client = MockRpcClient::new();
1799
1800        // before binary upgrade
1801        mock_service_control
1802            .expect_get_process_pid()
1803            .with(eq(current_node_bin.to_path_buf().clone()))
1804            .times(1)
1805            .returning(|_| Ok(1000));
1806        mock_service_control
1807            .expect_stop()
1808            .with(eq("safenode1"), eq(false))
1809            .times(1)
1810            .returning(|_, _| Ok(()));
1811
1812        // after binary upgrade
1813        mock_service_control
1814            .expect_uninstall()
1815            .with(eq("safenode1"), eq(false))
1816            .times(1)
1817            .returning(|_, _| Ok(()));
1818        mock_service_control
1819            .expect_install()
1820            .with(always(), always())
1821            .times(1)
1822            .returning(|_, _| Ok(()));
1823
1824        // after service restart
1825        mock_service_control
1826            .expect_start()
1827            .with(eq("safenode1"), eq(false))
1828            .times(1)
1829            .returning(|_, _| Ok(()));
1830        mock_service_control
1831            .expect_wait()
1832            .with(eq(3000))
1833            .times(1)
1834            .returning(|_| ());
1835        mock_service_control
1836            .expect_get_process_pid()
1837            .with(eq(current_node_bin.to_path_buf().clone()))
1838            .times(1)
1839            .returning(|_| Ok(2000));
1840
1841        mock_rpc_client.expect_node_info().times(1).returning(|| {
1842            Ok(NodeInfo {
1843                pid: 2000,
1844                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
1845                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1846                log_path: PathBuf::from("/var/log/safenode/safenode1"),
1847                version: target_version.to_string(),
1848                uptime: std::time::Duration::from_secs(1), // the service was just started
1849                wallet_balance: 0,
1850            })
1851        });
1852        mock_rpc_client
1853            .expect_network_info()
1854            .times(1)
1855            .returning(|| {
1856                Ok(NetworkInfo {
1857                    connected_peers: Vec::new(),
1858                    listeners: Vec::new(),
1859                })
1860            });
1861
1862        let mut service_data = NodeServiceData {
1863            auto_restart: false,
1864            connected_peers: None,
1865            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1866            evm_network: EvmNetwork::Custom(CustomNetwork {
1867                rpc_url_http: "http://localhost:8545".parse()?,
1868                payment_token_address: RewardsAddress::from_str(
1869                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1870                )?,
1871                data_payments_address: RewardsAddress::from_str(
1872                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1873                )?,
1874            }),
1875            genesis: false,
1876            home_network: false,
1877            listen_addr: None,
1878            local: false,
1879            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1880            log_format: None,
1881            max_archived_log_files: None,
1882            max_log_files: None,
1883            metrics_port: None,
1884            node_ip: None,
1885            node_port: None,
1886            number: 1,
1887            owner: None,
1888            peer_id: Some(PeerId::from_str(
1889                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
1890            )?),
1891            pid: Some(1000),
1892            rewards_address: RewardsAddress::from_str(
1893                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1894            )?,
1895            reward_balance: Some(AttoTokens::zero()),
1896            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1897            safenode_path: current_node_bin.to_path_buf(),
1898            service_name: "safenode1".to_string(),
1899            status: ServiceStatus::Running,
1900            upnp: false,
1901            user: Some("safe".to_string()),
1902            user_mode: false,
1903            version: current_version.to_string(),
1904        };
1905        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
1906        let mut service_manager = ServiceManager::new(
1907            service,
1908            Box::new(mock_service_control),
1909            VerbosityLevel::Normal,
1910        );
1911
1912        let upgrade_result = service_manager
1913            .upgrade(UpgradeOptions {
1914                auto_restart: false,
1915                bootstrap_peers: Vec::new(),
1916                env_variables: None,
1917                force: false,
1918                start_service: true,
1919                target_bin_path: target_node_bin.to_path_buf(),
1920                target_version: Version::parse(target_version).unwrap(),
1921            })
1922            .await?;
1923
1924        match upgrade_result {
1925            UpgradeResult::Upgraded(old_version, new_version) => {
1926                assert_eq!(old_version, current_version);
1927                assert_eq!(new_version, target_version);
1928            }
1929            _ => panic!(
1930                "Expected UpgradeResult::Upgraded but was {:#?}",
1931                upgrade_result
1932            ),
1933        }
1934
1935        assert_eq!(service_manager.service.service_data.pid, Some(2000));
1936        assert_eq!(
1937            service_manager.service.service_data.peer_id,
1938            Some(PeerId::from_str(
1939                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
1940            )?)
1941        );
1942        assert_eq!(service_manager.service.service_data.version, target_version);
1943
1944        Ok(())
1945    }
1946
1947    #[tokio::test]
1948    async fn upgrade_should_not_be_required_if_target_is_less_than_current_version() -> Result<()> {
1949        let current_version = "0.2.0";
1950        let target_version = "0.1.0";
1951
1952        let tmp_data_dir = assert_fs::TempDir::new()?;
1953        let current_install_dir = tmp_data_dir.child("safenode_install");
1954        current_install_dir.create_dir_all()?;
1955
1956        let current_node_bin = current_install_dir.child("safenode");
1957        current_node_bin.write_binary(b"fake safenode binary")?;
1958        let target_node_bin = tmp_data_dir.child("safenode");
1959        target_node_bin.write_binary(b"fake safenode binary")?;
1960
1961        let mock_service_control = MockServiceControl::new();
1962        let mock_rpc_client = MockRpcClient::new();
1963
1964        let mut service_data = NodeServiceData {
1965            auto_restart: false,
1966            connected_peers: None,
1967            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
1968            evm_network: EvmNetwork::Custom(CustomNetwork {
1969                rpc_url_http: "http://localhost:8545".parse()?,
1970                payment_token_address: RewardsAddress::from_str(
1971                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
1972                )?,
1973                data_payments_address: RewardsAddress::from_str(
1974                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
1975                )?,
1976            }),
1977            genesis: false,
1978            home_network: false,
1979            listen_addr: None,
1980            local: false,
1981            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
1982            log_format: None,
1983            max_archived_log_files: None,
1984            max_log_files: None,
1985            metrics_port: None,
1986            node_ip: None,
1987            node_port: None,
1988            number: 1,
1989            owner: None,
1990            peer_id: Some(PeerId::from_str(
1991                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
1992            )?),
1993            pid: Some(1000),
1994            rewards_address: RewardsAddress::from_str(
1995                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
1996            )?,
1997            reward_balance: Some(AttoTokens::zero()),
1998            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
1999            safenode_path: current_node_bin.to_path_buf(),
2000            service_name: "safenode1".to_string(),
2001            status: ServiceStatus::Running,
2002            upnp: false,
2003            user: Some("safe".to_string()),
2004            user_mode: false,
2005            version: current_version.to_string(),
2006        };
2007        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
2008
2009        let mut service_manager = ServiceManager::new(
2010            service,
2011            Box::new(mock_service_control),
2012            VerbosityLevel::Normal,
2013        );
2014
2015        let upgrade_result = service_manager
2016            .upgrade(UpgradeOptions {
2017                auto_restart: false,
2018                bootstrap_peers: Vec::new(),
2019                env_variables: None,
2020                force: false,
2021                start_service: true,
2022                target_bin_path: target_node_bin.to_path_buf(),
2023                target_version: Version::parse(target_version).unwrap(),
2024            })
2025            .await?;
2026
2027        assert_matches!(upgrade_result, UpgradeResult::NotRequired);
2028
2029        Ok(())
2030    }
2031
2032    #[tokio::test]
2033    async fn upgrade_should_downgrade_to_a_previous_version_if_force_is_used() -> Result<()> {
2034        let current_version = "0.1.0";
2035        let target_version = "0.2.0";
2036
2037        let tmp_data_dir = assert_fs::TempDir::new()?;
2038        let current_install_dir = tmp_data_dir.child("safenode_install");
2039        current_install_dir.create_dir_all()?;
2040
2041        let current_node_bin = current_install_dir.child("safenode");
2042        current_node_bin.write_binary(b"fake safenode binary")?;
2043        let target_node_bin = tmp_data_dir.child("safenode");
2044        target_node_bin.write_binary(b"fake safenode binary")?;
2045
2046        let mut mock_service_control = MockServiceControl::new();
2047        let mut mock_rpc_client = MockRpcClient::new();
2048
2049        // before binary upgrade
2050        mock_service_control
2051            .expect_get_process_pid()
2052            .with(eq(current_node_bin.to_path_buf().clone()))
2053            .times(1)
2054            .returning(|_| Ok(1000));
2055        mock_service_control
2056            .expect_stop()
2057            .with(eq("safenode1"), eq(false))
2058            .times(1)
2059            .returning(|_, _| Ok(()));
2060
2061        // after binary upgrade
2062        mock_service_control
2063            .expect_uninstall()
2064            .with(eq("safenode1"), eq(false))
2065            .times(1)
2066            .returning(|_, _| Ok(()));
2067        mock_service_control
2068            .expect_install()
2069            .with(always(), always())
2070            .times(1)
2071            .returning(|_, _| Ok(()));
2072
2073        // after service restart
2074        mock_service_control
2075            .expect_start()
2076            .with(eq("safenode1"), eq(false))
2077            .times(1)
2078            .returning(|_, _| Ok(()));
2079        mock_service_control
2080            .expect_wait()
2081            .with(eq(3000))
2082            .times(1)
2083            .returning(|_| ());
2084        mock_service_control
2085            .expect_get_process_pid()
2086            .with(eq(current_node_bin.to_path_buf().clone()))
2087            .times(1)
2088            .returning(|_| Ok(2000));
2089
2090        mock_rpc_client.expect_node_info().times(1).returning(|| {
2091            Ok(NodeInfo {
2092                pid: 2000,
2093                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
2094                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2095                log_path: PathBuf::from("/var/log/safenode/safenode1"),
2096                version: target_version.to_string(),
2097                uptime: std::time::Duration::from_secs(1), // the service was just started
2098                wallet_balance: 0,
2099            })
2100        });
2101        mock_rpc_client
2102            .expect_network_info()
2103            .times(1)
2104            .returning(|| {
2105                Ok(NetworkInfo {
2106                    connected_peers: Vec::new(),
2107                    listeners: Vec::new(),
2108                })
2109            });
2110
2111        let mut service_data = NodeServiceData {
2112            auto_restart: false,
2113            connected_peers: None,
2114            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2115            evm_network: EvmNetwork::Custom(CustomNetwork {
2116                rpc_url_http: "http://localhost:8545".parse()?,
2117                payment_token_address: RewardsAddress::from_str(
2118                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
2119                )?,
2120                data_payments_address: RewardsAddress::from_str(
2121                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
2122                )?,
2123            }),
2124            genesis: false,
2125            home_network: false,
2126            listen_addr: None,
2127            local: false,
2128            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
2129            log_format: None,
2130            max_archived_log_files: None,
2131            max_log_files: None,
2132            metrics_port: None,
2133            node_ip: None,
2134            node_port: None,
2135            number: 1,
2136            owner: None,
2137            peer_id: Some(PeerId::from_str(
2138                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
2139            )?),
2140            pid: Some(1000),
2141            rewards_address: RewardsAddress::from_str(
2142                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
2143            )?,
2144            reward_balance: Some(AttoTokens::zero()),
2145            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
2146            safenode_path: current_node_bin.to_path_buf(),
2147            service_name: "safenode1".to_string(),
2148            status: ServiceStatus::Running,
2149            upnp: false,
2150            user: Some("safe".to_string()),
2151            user_mode: false,
2152            version: current_version.to_string(),
2153        };
2154        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
2155
2156        let mut service_manager = ServiceManager::new(
2157            service,
2158            Box::new(mock_service_control),
2159            VerbosityLevel::Normal,
2160        );
2161
2162        let upgrade_result = service_manager
2163            .upgrade(UpgradeOptions {
2164                auto_restart: false,
2165                bootstrap_peers: Vec::new(),
2166                env_variables: None,
2167                force: true,
2168                start_service: true,
2169                target_bin_path: target_node_bin.to_path_buf(),
2170                target_version: Version::parse(target_version).unwrap(),
2171            })
2172            .await?;
2173
2174        match upgrade_result {
2175            UpgradeResult::Forced(old_version, new_version) => {
2176                assert_eq!(old_version, current_version);
2177                assert_eq!(new_version, target_version);
2178            }
2179            _ => panic!(
2180                "Expected UpgradeResult::Forced but was {:#?}",
2181                upgrade_result
2182            ),
2183        }
2184
2185        assert_eq!(service_manager.service.service_data.pid, Some(2000));
2186        assert_eq!(
2187            service_manager.service.service_data.peer_id,
2188            Some(PeerId::from_str(
2189                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
2190            )?)
2191        );
2192        assert_eq!(service_manager.service.service_data.version, target_version);
2193
2194        Ok(())
2195    }
2196
2197    #[tokio::test]
2198    async fn upgrade_should_upgrade_and_not_start_the_service() -> Result<()> {
2199        let current_version = "0.1.0";
2200        let target_version = "0.2.0";
2201
2202        let tmp_data_dir = assert_fs::TempDir::new()?;
2203        let current_install_dir = tmp_data_dir.child("safenode_install");
2204        current_install_dir.create_dir_all()?;
2205
2206        let current_node_bin = current_install_dir.child("safenode");
2207        current_node_bin.write_binary(b"fake safenode binary")?;
2208        let target_node_bin = tmp_data_dir.child("safenode");
2209        target_node_bin.write_binary(b"fake safenode binary")?;
2210
2211        let mut mock_service_control = MockServiceControl::new();
2212        let mut mock_rpc_client = MockRpcClient::new();
2213
2214        // before binary upgrade
2215        mock_service_control
2216            .expect_get_process_pid()
2217            .with(eq(current_node_bin.to_path_buf().clone()))
2218            .times(1)
2219            .returning(|_| Ok(1000));
2220        mock_service_control
2221            .expect_stop()
2222            .with(eq("safenode1"), eq(false))
2223            .times(1)
2224            .returning(|_, _| Ok(()));
2225
2226        // after binary upgrade
2227        mock_service_control
2228            .expect_uninstall()
2229            .with(eq("safenode1"), eq(false))
2230            .times(1)
2231            .returning(|_, _| Ok(()));
2232        mock_service_control
2233            .expect_install()
2234            .with(always(), always())
2235            .times(1)
2236            .returning(|_, _| Ok(()));
2237
2238        // after service restart
2239        mock_service_control
2240            .expect_start()
2241            .with(eq("safenode1"), eq(false))
2242            .times(0)
2243            .returning(|_, _| Ok(()));
2244        mock_service_control
2245            .expect_wait()
2246            .with(eq(3000))
2247            .times(0)
2248            .returning(|_| ());
2249        mock_rpc_client.expect_node_info().times(0).returning(|| {
2250            Ok(NodeInfo {
2251                pid: 2000,
2252                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
2253                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2254                log_path: PathBuf::from("/var/log/safenode/safenode1"),
2255                version: target_version.to_string(),
2256                uptime: std::time::Duration::from_secs(1), // the service was just started
2257                wallet_balance: 0,
2258            })
2259        });
2260        mock_rpc_client
2261            .expect_network_info()
2262            .times(0)
2263            .returning(|| {
2264                Ok(NetworkInfo {
2265                    connected_peers: Vec::new(),
2266                    listeners: Vec::new(),
2267                })
2268            });
2269
2270        let mut service_data = NodeServiceData {
2271            auto_restart: false,
2272            connected_peers: None,
2273            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2274            evm_network: EvmNetwork::Custom(CustomNetwork {
2275                rpc_url_http: "http://localhost:8545".parse()?,
2276                payment_token_address: RewardsAddress::from_str(
2277                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
2278                )?,
2279                data_payments_address: RewardsAddress::from_str(
2280                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
2281                )?,
2282            }),
2283            genesis: false,
2284            home_network: false,
2285            listen_addr: None,
2286            local: false,
2287            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
2288            log_format: None,
2289            max_archived_log_files: None,
2290            max_log_files: None,
2291            metrics_port: None,
2292            node_ip: None,
2293            node_port: None,
2294            number: 1,
2295            owner: None,
2296            peer_id: Some(PeerId::from_str(
2297                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
2298            )?),
2299            pid: Some(1000),
2300            rewards_address: RewardsAddress::from_str(
2301                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
2302            )?,
2303            reward_balance: Some(AttoTokens::zero()),
2304            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
2305            safenode_path: current_node_bin.to_path_buf(),
2306            service_name: "safenode1".to_string(),
2307            status: ServiceStatus::Running,
2308            upnp: false,
2309            user: Some("safe".to_string()),
2310            user_mode: false,
2311            version: current_version.to_string(),
2312        };
2313        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
2314
2315        let mut service_manager = ServiceManager::new(
2316            service,
2317            Box::new(mock_service_control),
2318            VerbosityLevel::Normal,
2319        );
2320
2321        let upgrade_result = service_manager
2322            .upgrade(UpgradeOptions {
2323                auto_restart: false,
2324                bootstrap_peers: Vec::new(),
2325                env_variables: None,
2326                force: false,
2327                start_service: false,
2328                target_bin_path: target_node_bin.to_path_buf(),
2329                target_version: Version::parse(target_version).unwrap(),
2330            })
2331            .await?;
2332
2333        match upgrade_result {
2334            UpgradeResult::Upgraded(old_version, new_version) => {
2335                assert_eq!(old_version, current_version);
2336                assert_eq!(new_version, target_version);
2337            }
2338            _ => panic!(
2339                "Expected UpgradeResult::Upgraded but was {:#?}",
2340                upgrade_result
2341            ),
2342        }
2343
2344        assert_eq!(service_manager.service.service_data.pid, None);
2345        assert_eq!(
2346            service_manager.service.service_data.peer_id,
2347            Some(PeerId::from_str(
2348                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
2349            )?)
2350        );
2351        assert_eq!(service_manager.service.service_data.version, target_version);
2352        assert_matches!(
2353            service_manager.service.service_data.status,
2354            ServiceStatus::Stopped
2355        );
2356
2357        Ok(())
2358    }
2359
2360    #[tokio::test]
2361    async fn upgrade_should_return_upgraded_but_not_started_if_service_did_not_start() -> Result<()>
2362    {
2363        let current_version = "0.1.0";
2364        let target_version = "0.2.0";
2365
2366        let tmp_data_dir = assert_fs::TempDir::new()?;
2367        let current_install_dir = tmp_data_dir.child("safenode_install");
2368        current_install_dir.create_dir_all()?;
2369
2370        let current_node_bin = current_install_dir.child("safenode");
2371        current_node_bin.write_binary(b"fake safenode binary")?;
2372        let target_node_bin = tmp_data_dir.child("safenode");
2373        target_node_bin.write_binary(b"fake safenode binary")?;
2374
2375        let current_node_bin_str = current_node_bin.to_path_buf().to_string_lossy().to_string();
2376
2377        let mut mock_service_control = MockServiceControl::new();
2378
2379        // before binary upgrade
2380        mock_service_control
2381            .expect_get_process_pid()
2382            .with(eq(current_node_bin.to_path_buf().clone()))
2383            .times(1)
2384            .returning(|_| Ok(1000));
2385        mock_service_control
2386            .expect_stop()
2387            .with(eq("safenode1"), eq(false))
2388            .times(1)
2389            .returning(|_, _| Ok(()));
2390
2391        // after binary upgrade
2392        mock_service_control
2393            .expect_uninstall()
2394            .with(eq("safenode1"), eq(false))
2395            .times(1)
2396            .returning(|_, _| Ok(()));
2397        mock_service_control
2398            .expect_install()
2399            .with(always(), always())
2400            .times(1)
2401            .returning(|_, _| Ok(()));
2402
2403        // after service restart
2404        mock_service_control
2405            .expect_start()
2406            .with(eq("safenode1"), eq(false))
2407            .times(1)
2408            .returning(|_, _| Ok(()));
2409        mock_service_control
2410            .expect_wait()
2411            .with(eq(3000))
2412            .times(1)
2413            .returning(|_| ());
2414        mock_service_control
2415            .expect_get_process_pid()
2416            .with(eq(current_node_bin.to_path_buf().clone()))
2417            .times(1)
2418            .returning(move |_| {
2419                Err(ServiceControlError::ServiceProcessNotFound(
2420                    current_node_bin_str.clone(),
2421                ))
2422            });
2423
2424        let mut service_data = NodeServiceData {
2425            auto_restart: false,
2426            connected_peers: None,
2427            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2428            evm_network: EvmNetwork::Custom(CustomNetwork {
2429                rpc_url_http: "http://localhost:8545".parse()?,
2430                payment_token_address: RewardsAddress::from_str(
2431                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
2432                )?,
2433                data_payments_address: RewardsAddress::from_str(
2434                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
2435                )?,
2436            }),
2437            genesis: false,
2438            home_network: false,
2439            listen_addr: None,
2440            local: false,
2441            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
2442            log_format: None,
2443            max_archived_log_files: None,
2444            max_log_files: None,
2445            metrics_port: None,
2446            node_ip: None,
2447            node_port: None,
2448            number: 1,
2449            owner: None,
2450            peer_id: Some(PeerId::from_str(
2451                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
2452            )?),
2453            pid: Some(1000),
2454            rewards_address: RewardsAddress::from_str(
2455                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
2456            )?,
2457            reward_balance: Some(AttoTokens::zero()),
2458            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
2459            safenode_path: current_node_bin.to_path_buf(),
2460            service_name: "safenode1".to_string(),
2461            status: ServiceStatus::Running,
2462            upnp: false,
2463            user: Some("safe".to_string()),
2464            user_mode: false,
2465            version: current_version.to_string(),
2466        };
2467        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
2468        let mut service_manager = ServiceManager::new(
2469            service,
2470            Box::new(mock_service_control),
2471            VerbosityLevel::Normal,
2472        );
2473
2474        let upgrade_result = service_manager
2475            .upgrade(UpgradeOptions {
2476                auto_restart: false,
2477                bootstrap_peers: Vec::new(),
2478                env_variables: None,
2479                force: false,
2480                start_service: true,
2481                target_bin_path: target_node_bin.to_path_buf(),
2482                target_version: Version::parse(target_version).unwrap(),
2483            })
2484            .await?;
2485
2486        match upgrade_result {
2487            UpgradeResult::UpgradedButNotStarted(old_version, new_version, _) => {
2488                assert_eq!(old_version, current_version);
2489                assert_eq!(new_version, target_version);
2490            }
2491            _ => panic!(
2492                "Expected UpgradeResult::UpgradedButNotStarted but was {:#?}",
2493                upgrade_result
2494            ),
2495        }
2496
2497        Ok(())
2498    }
2499
2500    #[tokio::test]
2501    async fn upgrade_should_upgrade_a_service_in_user_mode() -> Result<()> {
2502        let current_version = "0.1.0";
2503        let target_version = "0.2.0";
2504
2505        let tmp_data_dir = assert_fs::TempDir::new()?;
2506        let current_install_dir = tmp_data_dir.child("safenode_install");
2507        current_install_dir.create_dir_all()?;
2508
2509        let current_node_bin = current_install_dir.child("safenode");
2510        current_node_bin.write_binary(b"fake safenode binary")?;
2511        let target_node_bin = tmp_data_dir.child("safenode");
2512        target_node_bin.write_binary(b"fake safenode binary")?;
2513
2514        let mut mock_service_control = MockServiceControl::new();
2515        let mut mock_rpc_client = MockRpcClient::new();
2516
2517        // before binary upgrade
2518        mock_service_control
2519            .expect_get_process_pid()
2520            .with(eq(current_node_bin.to_path_buf().clone()))
2521            .times(1)
2522            .returning(|_| Ok(1000));
2523        mock_service_control
2524            .expect_stop()
2525            .with(eq("safenode1"), eq(true))
2526            .times(1)
2527            .returning(|_, _| Ok(()));
2528
2529        // after binary upgrade
2530        mock_service_control
2531            .expect_uninstall()
2532            .with(eq("safenode1"), eq(true))
2533            .times(1)
2534            .returning(|_, _| Ok(()));
2535        mock_service_control
2536            .expect_install()
2537            .with(always(), eq(true))
2538            .times(1)
2539            .returning(|_, _| Ok(()));
2540
2541        // after service restart
2542        mock_service_control
2543            .expect_start()
2544            .with(eq("safenode1"), eq(true))
2545            .times(1)
2546            .returning(|_, _| Ok(()));
2547        mock_service_control
2548            .expect_wait()
2549            .with(eq(3000))
2550            .times(1)
2551            .returning(|_| ());
2552        mock_service_control
2553            .expect_get_process_pid()
2554            .with(eq(current_node_bin.to_path_buf().clone()))
2555            .times(1)
2556            .returning(|_| Ok(2000));
2557
2558        mock_rpc_client.expect_node_info().times(1).returning(|| {
2559            Ok(NodeInfo {
2560                pid: 2000,
2561                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
2562                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2563                log_path: PathBuf::from("/var/log/safenode/safenode1"),
2564                version: target_version.to_string(),
2565                uptime: std::time::Duration::from_secs(1), // the service was just started
2566                wallet_balance: 0,
2567            })
2568        });
2569        mock_rpc_client
2570            .expect_network_info()
2571            .times(1)
2572            .returning(|| {
2573                Ok(NetworkInfo {
2574                    connected_peers: Vec::new(),
2575                    listeners: Vec::new(),
2576                })
2577            });
2578
2579        let mut service_data = NodeServiceData {
2580            auto_restart: false,
2581            connected_peers: None,
2582            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2583            evm_network: EvmNetwork::Custom(CustomNetwork {
2584                rpc_url_http: "http://localhost:8545".parse()?,
2585                payment_token_address: RewardsAddress::from_str(
2586                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
2587                )?,
2588                data_payments_address: RewardsAddress::from_str(
2589                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
2590                )?,
2591            }),
2592            genesis: false,
2593            home_network: false,
2594            listen_addr: None,
2595            local: false,
2596            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
2597            log_format: None,
2598            max_archived_log_files: None,
2599            max_log_files: None,
2600            metrics_port: None,
2601            node_ip: None,
2602            node_port: None,
2603            number: 1,
2604            owner: None,
2605            peer_id: Some(PeerId::from_str(
2606                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
2607            )?),
2608            pid: Some(1000),
2609            rewards_address: RewardsAddress::from_str(
2610                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
2611            )?,
2612            reward_balance: Some(AttoTokens::zero()),
2613            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
2614            safenode_path: current_node_bin.to_path_buf(),
2615            service_name: "safenode1".to_string(),
2616            status: ServiceStatus::Running,
2617            upnp: false,
2618            user: None,
2619            user_mode: true,
2620            version: current_version.to_string(),
2621        };
2622        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
2623
2624        let mut service_manager = ServiceManager::new(
2625            service,
2626            Box::new(mock_service_control),
2627            VerbosityLevel::Normal,
2628        );
2629
2630        let upgrade_result = service_manager
2631            .upgrade(UpgradeOptions {
2632                auto_restart: false,
2633                bootstrap_peers: Vec::new(),
2634                env_variables: None,
2635                force: false,
2636                start_service: true,
2637                target_bin_path: target_node_bin.to_path_buf(),
2638                target_version: Version::parse(target_version).unwrap(),
2639            })
2640            .await?;
2641
2642        match upgrade_result {
2643            UpgradeResult::Upgraded(old_version, new_version) => {
2644                assert_eq!(old_version, current_version);
2645                assert_eq!(new_version, target_version);
2646            }
2647            _ => panic!(
2648                "Expected UpgradeResult::Upgraded but was {:#?}",
2649                upgrade_result
2650            ),
2651        }
2652
2653        assert_eq!(service_manager.service.service_data.pid, Some(2000));
2654        assert_eq!(
2655            service_manager.service.service_data.peer_id,
2656            Some(PeerId::from_str(
2657                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
2658            )?)
2659        );
2660        assert_eq!(service_manager.service.service_data.version, target_version);
2661
2662        Ok(())
2663    }
2664
2665    #[tokio::test]
2666    async fn upgrade_should_retain_the_upnp_flag() -> Result<()> {
2667        let current_version = "0.1.0";
2668        let target_version = "0.2.0";
2669
2670        let tmp_data_dir = assert_fs::TempDir::new()?;
2671        let current_install_dir = tmp_data_dir.child("safenode_install");
2672        current_install_dir.create_dir_all()?;
2673
2674        let current_node_bin = current_install_dir.child("safenode");
2675        current_node_bin.write_binary(b"fake safenode binary")?;
2676        let target_node_bin = tmp_data_dir.child("safenode");
2677        target_node_bin.write_binary(b"fake safenode binary")?;
2678
2679        let mut mock_service_control = MockServiceControl::new();
2680        let mut mock_rpc_client = MockRpcClient::new();
2681
2682        // before binary upgrade
2683        mock_service_control
2684            .expect_get_process_pid()
2685            .with(eq(current_node_bin.to_path_buf().clone()))
2686            .times(1)
2687            .returning(|_| Ok(1000));
2688        mock_service_control
2689            .expect_stop()
2690            .with(eq("safenode1"), eq(false))
2691            .times(1)
2692            .returning(|_, _| Ok(()));
2693
2694        // after binary upgrade
2695        mock_service_control
2696            .expect_uninstall()
2697            .with(eq("safenode1"), eq(false))
2698            .times(1)
2699            .returning(|_, _| Ok(()));
2700        mock_service_control
2701            .expect_install()
2702            .with(
2703                eq(ServiceInstallCtx {
2704                    args: vec![
2705                        OsString::from("--rpc"),
2706                        OsString::from("127.0.0.1:8081"),
2707                        OsString::from("--root-dir"),
2708                        OsString::from("/var/safenode-manager/services/safenode1"),
2709                        OsString::from("--log-output-dest"),
2710                        OsString::from("/var/log/safenode/safenode1"),
2711                        OsString::from("--upnp"),
2712                        OsString::from("--rewards-address"),
2713                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
2714                        OsString::from("evm-arbitrum-one"),
2715                    ],
2716                    autostart: false,
2717                    contents: None,
2718                    environment: None,
2719                    label: "safenode1".parse()?,
2720                    program: current_node_bin.to_path_buf(),
2721                    username: Some("safe".to_string()),
2722                    working_directory: None,
2723                }),
2724                eq(false),
2725            )
2726            .times(1)
2727            .returning(|_, _| Ok(()));
2728
2729        // after service restart
2730        mock_service_control
2731            .expect_start()
2732            .with(eq("safenode1"), eq(false))
2733            .times(1)
2734            .returning(|_, _| Ok(()));
2735        mock_service_control
2736            .expect_wait()
2737            .with(eq(3000))
2738            .times(1)
2739            .returning(|_| ());
2740        mock_service_control
2741            .expect_get_process_pid()
2742            .with(eq(current_node_bin.to_path_buf().clone()))
2743            .times(1)
2744            .returning(|_| Ok(100));
2745
2746        mock_rpc_client.expect_node_info().times(1).returning(|| {
2747            Ok(NodeInfo {
2748                pid: 2000,
2749                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
2750                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2751                log_path: PathBuf::from("/var/log/safenode/safenode1"),
2752                version: target_version.to_string(),
2753                uptime: std::time::Duration::from_secs(1), // the service was just started
2754                wallet_balance: 0,
2755            })
2756        });
2757        mock_rpc_client
2758            .expect_network_info()
2759            .times(1)
2760            .returning(|| {
2761                Ok(NetworkInfo {
2762                    connected_peers: Vec::new(),
2763                    listeners: Vec::new(),
2764                })
2765            });
2766
2767        let mut service_data = NodeServiceData {
2768            auto_restart: false,
2769            connected_peers: None,
2770            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2771            evm_network: EvmNetwork::ArbitrumOne,
2772            genesis: false,
2773            home_network: false,
2774            listen_addr: None,
2775            local: false,
2776            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
2777            log_format: None,
2778            max_archived_log_files: None,
2779            max_log_files: None,
2780            metrics_port: None,
2781            node_ip: None,
2782            node_port: None,
2783            number: 1,
2784            owner: None,
2785            peer_id: Some(PeerId::from_str(
2786                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
2787            )?),
2788            pid: Some(1000),
2789            rewards_address: RewardsAddress::from_str(
2790                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
2791            )?,
2792            reward_balance: Some(AttoTokens::zero()),
2793            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
2794            safenode_path: current_node_bin.to_path_buf(),
2795            service_name: "safenode1".to_string(),
2796            status: ServiceStatus::Running,
2797            upnp: true,
2798            user: Some("safe".to_string()),
2799            user_mode: false,
2800            version: current_version.to_string(),
2801        };
2802        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
2803
2804        let mut service_manager = ServiceManager::new(
2805            service,
2806            Box::new(mock_service_control),
2807            VerbosityLevel::Normal,
2808        );
2809
2810        service_manager
2811            .upgrade(UpgradeOptions {
2812                auto_restart: false,
2813                bootstrap_peers: Vec::new(),
2814                env_variables: None,
2815                force: false,
2816                start_service: true,
2817                target_bin_path: target_node_bin.to_path_buf(),
2818                target_version: Version::parse(target_version).unwrap(),
2819            })
2820            .await?;
2821
2822        assert!(service_manager.service.service_data.upnp);
2823
2824        Ok(())
2825    }
2826
2827    #[tokio::test]
2828    async fn upgrade_should_retain_the_log_format_flag() -> Result<()> {
2829        let current_version = "0.1.0";
2830        let target_version = "0.2.0";
2831
2832        let tmp_data_dir = assert_fs::TempDir::new()?;
2833        let current_install_dir = tmp_data_dir.child("safenode_install");
2834        current_install_dir.create_dir_all()?;
2835
2836        let current_node_bin = current_install_dir.child("safenode");
2837        current_node_bin.write_binary(b"fake safenode binary")?;
2838        let target_node_bin = tmp_data_dir.child("safenode");
2839        target_node_bin.write_binary(b"fake safenode binary")?;
2840
2841        let mut mock_service_control = MockServiceControl::new();
2842        let mut mock_rpc_client = MockRpcClient::new();
2843
2844        // before binary upgrade
2845        mock_service_control
2846            .expect_get_process_pid()
2847            .with(eq(current_node_bin.to_path_buf().clone()))
2848            .times(1)
2849            .returning(|_| Ok(1000));
2850        mock_service_control
2851            .expect_stop()
2852            .with(eq("safenode1"), eq(false))
2853            .times(1)
2854            .returning(|_, _| Ok(()));
2855
2856        // after binary upgrade
2857        mock_service_control
2858            .expect_uninstall()
2859            .with(eq("safenode1"), eq(false))
2860            .times(1)
2861            .returning(|_, _| Ok(()));
2862        mock_service_control
2863            .expect_install()
2864            .with(
2865                eq(ServiceInstallCtx {
2866                    args: vec![
2867                        OsString::from("--rpc"),
2868                        OsString::from("127.0.0.1:8081"),
2869                        OsString::from("--root-dir"),
2870                        OsString::from("/var/safenode-manager/services/safenode1"),
2871                        OsString::from("--log-output-dest"),
2872                        OsString::from("/var/log/safenode/safenode1"),
2873                        OsString::from("--log-format"),
2874                        OsString::from("json"),
2875                        OsString::from("--rewards-address"),
2876                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
2877                        OsString::from("evm-arbitrum-one"),
2878                    ],
2879                    autostart: false,
2880                    contents: None,
2881                    environment: None,
2882                    label: "safenode1".parse()?,
2883                    program: current_node_bin.to_path_buf(),
2884                    username: Some("safe".to_string()),
2885                    working_directory: None,
2886                }),
2887                eq(false),
2888            )
2889            .times(1)
2890            .returning(|_, _| Ok(()));
2891
2892        // after service restart
2893        mock_service_control
2894            .expect_start()
2895            .with(eq("safenode1"), eq(false))
2896            .times(1)
2897            .returning(|_, _| Ok(()));
2898        mock_service_control
2899            .expect_wait()
2900            .with(eq(3000))
2901            .times(1)
2902            .returning(|_| ());
2903        mock_service_control
2904            .expect_get_process_pid()
2905            .with(eq(current_node_bin.to_path_buf().clone()))
2906            .times(1)
2907            .returning(|_| Ok(100));
2908
2909        mock_rpc_client.expect_node_info().times(1).returning(|| {
2910            Ok(NodeInfo {
2911                pid: 2000,
2912                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
2913                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2914                log_path: PathBuf::from("/var/log/safenode/safenode1"),
2915                version: target_version.to_string(),
2916                uptime: std::time::Duration::from_secs(1), // the service was just started
2917                wallet_balance: 0,
2918            })
2919        });
2920        mock_rpc_client
2921            .expect_network_info()
2922            .times(1)
2923            .returning(|| {
2924                Ok(NetworkInfo {
2925                    connected_peers: Vec::new(),
2926                    listeners: Vec::new(),
2927                })
2928            });
2929
2930        let mut service_data = NodeServiceData {
2931            auto_restart: false,
2932            connected_peers: None,
2933            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
2934            evm_network: EvmNetwork::ArbitrumOne,
2935            genesis: false,
2936            home_network: false,
2937            listen_addr: None,
2938            local: false,
2939            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
2940            log_format: Some(LogFormat::Json),
2941            max_archived_log_files: None,
2942            max_log_files: None,
2943            metrics_port: None,
2944            node_ip: None,
2945            node_port: None,
2946            owner: None,
2947            number: 1,
2948            peer_id: Some(PeerId::from_str(
2949                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
2950            )?),
2951            pid: Some(1000),
2952            rewards_address: RewardsAddress::from_str(
2953                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
2954            )?,
2955            reward_balance: Some(AttoTokens::zero()),
2956            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
2957            safenode_path: current_node_bin.to_path_buf(),
2958            service_name: "safenode1".to_string(),
2959            status: ServiceStatus::Running,
2960            upnp: false,
2961            user: Some("safe".to_string()),
2962            user_mode: false,
2963            version: current_version.to_string(),
2964        };
2965        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
2966
2967        let mut service_manager = ServiceManager::new(
2968            service,
2969            Box::new(mock_service_control),
2970            VerbosityLevel::Normal,
2971        );
2972
2973        service_manager
2974            .upgrade(UpgradeOptions {
2975                auto_restart: false,
2976                bootstrap_peers: Vec::new(),
2977                env_variables: None,
2978                force: false,
2979                start_service: true,
2980                target_bin_path: target_node_bin.to_path_buf(),
2981                target_version: Version::parse(target_version).unwrap(),
2982            })
2983            .await?;
2984
2985        assert!(service_manager.service.service_data.log_format.is_some());
2986        assert_eq!(
2987            service_manager.service.service_data.log_format,
2988            Some(LogFormat::Json)
2989        );
2990
2991        Ok(())
2992    }
2993
2994    #[tokio::test]
2995    async fn upgrade_should_retain_the_home_network_flag() -> Result<()> {
2996        let current_version = "0.1.0";
2997        let target_version = "0.2.0";
2998
2999        let tmp_data_dir = assert_fs::TempDir::new()?;
3000        let current_install_dir = tmp_data_dir.child("safenode_install");
3001        current_install_dir.create_dir_all()?;
3002
3003        let current_node_bin = current_install_dir.child("safenode");
3004        current_node_bin.write_binary(b"fake safenode binary")?;
3005        let target_node_bin = tmp_data_dir.child("safenode");
3006        target_node_bin.write_binary(b"fake safenode binary")?;
3007
3008        let mut mock_service_control = MockServiceControl::new();
3009        let mut mock_rpc_client = MockRpcClient::new();
3010
3011        // before binary upgrade
3012        mock_service_control
3013            .expect_get_process_pid()
3014            .with(eq(current_node_bin.to_path_buf().clone()))
3015            .times(1)
3016            .returning(|_| Ok(1000));
3017        mock_service_control
3018            .expect_stop()
3019            .with(eq("safenode1"), eq(false))
3020            .times(1)
3021            .returning(|_, _| Ok(()));
3022
3023        // after binary upgrade
3024        mock_service_control
3025            .expect_uninstall()
3026            .with(eq("safenode1"), eq(false))
3027            .times(1)
3028            .returning(|_, _| Ok(()));
3029        mock_service_control
3030            .expect_install()
3031            .with(
3032                eq(ServiceInstallCtx {
3033                    args: vec![
3034                        OsString::from("--rpc"),
3035                        OsString::from("127.0.0.1:8081"),
3036                        OsString::from("--root-dir"),
3037                        OsString::from("/var/safenode-manager/services/safenode1"),
3038                        OsString::from("--log-output-dest"),
3039                        OsString::from("/var/log/safenode/safenode1"),
3040                        OsString::from("--home-network"),
3041                        OsString::from("--rewards-address"),
3042                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
3043                        OsString::from("evm-arbitrum-one"),
3044                    ],
3045                    autostart: false,
3046                    contents: None,
3047                    environment: None,
3048                    label: "safenode1".parse()?,
3049                    program: current_node_bin.to_path_buf(),
3050                    username: Some("safe".to_string()),
3051                    working_directory: None,
3052                }),
3053                eq(false),
3054            )
3055            .times(1)
3056            .returning(|_, _| Ok(()));
3057
3058        // after service restart
3059        mock_service_control
3060            .expect_start()
3061            .with(eq("safenode1"), eq(false))
3062            .times(1)
3063            .returning(|_, _| Ok(()));
3064        mock_service_control
3065            .expect_wait()
3066            .with(eq(3000))
3067            .times(1)
3068            .returning(|_| ());
3069        mock_service_control
3070            .expect_get_process_pid()
3071            .with(eq(current_node_bin.to_path_buf().clone()))
3072            .times(1)
3073            .returning(|_| Ok(100));
3074
3075        mock_rpc_client.expect_node_info().times(1).returning(|| {
3076            Ok(NodeInfo {
3077                pid: 2000,
3078                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
3079                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3080                log_path: PathBuf::from("/var/log/safenode/safenode1"),
3081                version: target_version.to_string(),
3082                uptime: std::time::Duration::from_secs(1), // the service was just started
3083                wallet_balance: 0,
3084            })
3085        });
3086        mock_rpc_client
3087            .expect_network_info()
3088            .times(1)
3089            .returning(|| {
3090                Ok(NetworkInfo {
3091                    connected_peers: Vec::new(),
3092                    listeners: Vec::new(),
3093                })
3094            });
3095
3096        let mut service_data = NodeServiceData {
3097            auto_restart: false,
3098            connected_peers: None,
3099            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3100            evm_network: EvmNetwork::ArbitrumOne,
3101            genesis: false,
3102            home_network: true,
3103            listen_addr: None,
3104            local: false,
3105            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
3106            log_format: None,
3107            max_archived_log_files: None,
3108            max_log_files: None,
3109            metrics_port: None,
3110            node_ip: None,
3111            node_port: None,
3112            number: 1,
3113            owner: None,
3114            peer_id: Some(PeerId::from_str(
3115                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
3116            )?),
3117            pid: Some(1000),
3118            rewards_address: RewardsAddress::from_str(
3119                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
3120            )?,
3121            reward_balance: Some(AttoTokens::zero()),
3122            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
3123            safenode_path: current_node_bin.to_path_buf(),
3124            service_name: "safenode1".to_string(),
3125            status: ServiceStatus::Running,
3126            upnp: false,
3127            user: Some("safe".to_string()),
3128            user_mode: false,
3129            version: current_version.to_string(),
3130        };
3131        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
3132
3133        let mut service_manager = ServiceManager::new(
3134            service,
3135            Box::new(mock_service_control),
3136            VerbosityLevel::Normal,
3137        );
3138
3139        service_manager
3140            .upgrade(UpgradeOptions {
3141                auto_restart: false,
3142                bootstrap_peers: Vec::new(),
3143                env_variables: None,
3144                force: false,
3145                start_service: true,
3146                target_bin_path: target_node_bin.to_path_buf(),
3147                target_version: Version::parse(target_version).unwrap(),
3148            })
3149            .await?;
3150
3151        assert!(service_manager.service.service_data.home_network);
3152
3153        Ok(())
3154    }
3155
3156    #[tokio::test]
3157    async fn upgrade_should_retain_custom_node_ip() -> Result<()> {
3158        let current_version = "0.1.0";
3159        let target_version = "0.2.0";
3160
3161        let tmp_data_dir = assert_fs::TempDir::new()?;
3162        let current_install_dir = tmp_data_dir.child("safenode_install");
3163        current_install_dir.create_dir_all()?;
3164
3165        let current_node_bin = current_install_dir.child("safenode");
3166        current_node_bin.write_binary(b"fake safenode binary")?;
3167        let target_node_bin = tmp_data_dir.child("safenode");
3168        target_node_bin.write_binary(b"fake safenode binary")?;
3169
3170        let mut mock_service_control = MockServiceControl::new();
3171        let mut mock_rpc_client = MockRpcClient::new();
3172
3173        // before binary upgrade
3174        mock_service_control
3175            .expect_get_process_pid()
3176            .with(eq(current_node_bin.to_path_buf().clone()))
3177            .times(1)
3178            .returning(|_| Ok(1000));
3179        mock_service_control
3180            .expect_stop()
3181            .with(eq("safenode1"), eq(false))
3182            .times(1)
3183            .returning(|_, _| Ok(()));
3184
3185        // after binary upgrade
3186        mock_service_control
3187            .expect_uninstall()
3188            .with(eq("safenode1"), eq(false))
3189            .times(1)
3190            .returning(|_, _| Ok(()));
3191        mock_service_control
3192            .expect_install()
3193            .with(
3194                eq(ServiceInstallCtx {
3195                    args: vec![
3196                        OsString::from("--rpc"),
3197                        OsString::from("127.0.0.1:8081"),
3198                        OsString::from("--root-dir"),
3199                        OsString::from("/var/safenode-manager/services/safenode1"),
3200                        OsString::from("--log-output-dest"),
3201                        OsString::from("/var/log/safenode/safenode1"),
3202                        OsString::from("--ip"),
3203                        OsString::from("192.168.1.1"),
3204                        OsString::from("--rewards-address"),
3205                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
3206                        OsString::from("evm-arbitrum-one"),
3207                    ],
3208                    autostart: false,
3209                    contents: None,
3210                    environment: None,
3211                    label: "safenode1".parse()?,
3212                    program: current_node_bin.to_path_buf(),
3213                    username: Some("safe".to_string()),
3214                    working_directory: None,
3215                }),
3216                eq(false),
3217            )
3218            .times(1)
3219            .returning(|_, _| Ok(()));
3220
3221        // after service restart
3222        mock_service_control
3223            .expect_start()
3224            .with(eq("safenode1"), eq(false))
3225            .times(1)
3226            .returning(|_, _| Ok(()));
3227        mock_service_control
3228            .expect_wait()
3229            .with(eq(3000))
3230            .times(1)
3231            .returning(|_| ());
3232        mock_service_control
3233            .expect_get_process_pid()
3234            .with(eq(current_node_bin.to_path_buf().clone()))
3235            .times(1)
3236            .returning(|_| Ok(100));
3237
3238        mock_rpc_client.expect_node_info().times(1).returning(|| {
3239            Ok(NodeInfo {
3240                pid: 2000,
3241                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
3242                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3243                log_path: PathBuf::from("/var/log/safenode/safenode1"),
3244                version: target_version.to_string(),
3245                uptime: std::time::Duration::from_secs(1), // the service was just started
3246                wallet_balance: 0,
3247            })
3248        });
3249        mock_rpc_client
3250            .expect_network_info()
3251            .times(1)
3252            .returning(|| {
3253                Ok(NetworkInfo {
3254                    connected_peers: Vec::new(),
3255                    listeners: Vec::new(),
3256                })
3257            });
3258
3259        let mut service_data = NodeServiceData {
3260            auto_restart: false,
3261            connected_peers: None,
3262            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3263            evm_network: EvmNetwork::ArbitrumOne,
3264            genesis: false,
3265            home_network: false,
3266            listen_addr: None,
3267            local: false,
3268            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
3269            log_format: None,
3270            max_archived_log_files: None,
3271            max_log_files: None,
3272            metrics_port: None,
3273            number: 1,
3274            node_ip: Some(Ipv4Addr::new(192, 168, 1, 1)),
3275            node_port: None,
3276            owner: None,
3277            peer_id: Some(PeerId::from_str(
3278                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
3279            )?),
3280            pid: Some(1000),
3281            rewards_address: RewardsAddress::from_str(
3282                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
3283            )?,
3284            reward_balance: Some(AttoTokens::zero()),
3285            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
3286            safenode_path: current_node_bin.to_path_buf(),
3287            service_name: "safenode1".to_string(),
3288            status: ServiceStatus::Running,
3289            upnp: false,
3290            user: Some("safe".to_string()),
3291            user_mode: false,
3292            version: current_version.to_string(),
3293        };
3294        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
3295
3296        let mut service_manager = ServiceManager::new(
3297            service,
3298            Box::new(mock_service_control),
3299            VerbosityLevel::Normal,
3300        );
3301
3302        service_manager
3303            .upgrade(UpgradeOptions {
3304                auto_restart: false,
3305                bootstrap_peers: Vec::new(),
3306                env_variables: None,
3307                force: false,
3308                start_service: true,
3309                target_bin_path: target_node_bin.to_path_buf(),
3310                target_version: Version::parse(target_version).unwrap(),
3311            })
3312            .await?;
3313
3314        assert_eq!(
3315            service_manager.service.service_data.node_ip,
3316            Some(Ipv4Addr::new(192, 168, 1, 1))
3317        );
3318
3319        Ok(())
3320    }
3321
3322    #[tokio::test]
3323    async fn upgrade_should_retain_custom_node_ports() -> Result<()> {
3324        let current_version = "0.1.0";
3325        let target_version = "0.2.0";
3326
3327        let tmp_data_dir = assert_fs::TempDir::new()?;
3328        let current_install_dir = tmp_data_dir.child("safenode_install");
3329        current_install_dir.create_dir_all()?;
3330
3331        let current_node_bin = current_install_dir.child("safenode");
3332        current_node_bin.write_binary(b"fake safenode binary")?;
3333        let target_node_bin = tmp_data_dir.child("safenode");
3334        target_node_bin.write_binary(b"fake safenode binary")?;
3335
3336        let mut mock_service_control = MockServiceControl::new();
3337        let mut mock_rpc_client = MockRpcClient::new();
3338
3339        // before binary upgrade
3340        mock_service_control
3341            .expect_get_process_pid()
3342            .with(eq(current_node_bin.to_path_buf().clone()))
3343            .times(1)
3344            .returning(|_| Ok(1000));
3345        mock_service_control
3346            .expect_stop()
3347            .with(eq("safenode1"), eq(false))
3348            .times(1)
3349            .returning(|_, _| Ok(()));
3350
3351        // after binary upgrade
3352        mock_service_control
3353            .expect_uninstall()
3354            .with(eq("safenode1"), eq(false))
3355            .times(1)
3356            .returning(|_, _| Ok(()));
3357        mock_service_control
3358            .expect_install()
3359            .with(
3360                eq(ServiceInstallCtx {
3361                    args: vec![
3362                        OsString::from("--rpc"),
3363                        OsString::from("127.0.0.1:8081"),
3364                        OsString::from("--root-dir"),
3365                        OsString::from("/var/safenode-manager/services/safenode1"),
3366                        OsString::from("--log-output-dest"),
3367                        OsString::from("/var/log/safenode/safenode1"),
3368                        OsString::from("--port"),
3369                        OsString::from("12000"),
3370                        OsString::from("--rewards-address"),
3371                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
3372                        OsString::from("evm-arbitrum-one"),
3373                    ],
3374                    autostart: false,
3375                    contents: None,
3376                    environment: None,
3377                    label: "safenode1".parse()?,
3378                    program: current_node_bin.to_path_buf(),
3379                    username: Some("safe".to_string()),
3380                    working_directory: None,
3381                }),
3382                eq(false),
3383            )
3384            .times(1)
3385            .returning(|_, _| Ok(()));
3386
3387        // after service restart
3388        mock_service_control
3389            .expect_start()
3390            .with(eq("safenode1"), eq(false))
3391            .times(1)
3392            .returning(|_, _| Ok(()));
3393        mock_service_control
3394            .expect_wait()
3395            .with(eq(3000))
3396            .times(1)
3397            .returning(|_| ());
3398        mock_service_control
3399            .expect_get_process_pid()
3400            .with(eq(current_node_bin.to_path_buf().clone()))
3401            .times(1)
3402            .returning(|_| Ok(100));
3403
3404        mock_rpc_client.expect_node_info().times(1).returning(|| {
3405            Ok(NodeInfo {
3406                pid: 2000,
3407                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
3408                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3409                log_path: PathBuf::from("/var/log/safenode/safenode1"),
3410                version: target_version.to_string(),
3411                uptime: std::time::Duration::from_secs(1), // the service was just started
3412                wallet_balance: 0,
3413            })
3414        });
3415        mock_rpc_client
3416            .expect_network_info()
3417            .times(1)
3418            .returning(|| {
3419                Ok(NetworkInfo {
3420                    connected_peers: Vec::new(),
3421                    listeners: Vec::new(),
3422                })
3423            });
3424
3425        let mut service_data = NodeServiceData {
3426            auto_restart: false,
3427            connected_peers: None,
3428            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3429            evm_network: EvmNetwork::ArbitrumOne,
3430            genesis: false,
3431            home_network: false,
3432            listen_addr: None,
3433            local: false,
3434            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
3435            log_format: None,
3436            max_archived_log_files: None,
3437            max_log_files: None,
3438            metrics_port: None,
3439            number: 1,
3440            node_ip: None,
3441            node_port: Some(12000),
3442            owner: None,
3443            peer_id: Some(PeerId::from_str(
3444                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
3445            )?),
3446            pid: Some(1000),
3447            rewards_address: RewardsAddress::from_str(
3448                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
3449            )?,
3450            reward_balance: Some(AttoTokens::zero()),
3451            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
3452            safenode_path: current_node_bin.to_path_buf(),
3453            service_name: "safenode1".to_string(),
3454            status: ServiceStatus::Running,
3455            upnp: false,
3456            user: Some("safe".to_string()),
3457            user_mode: false,
3458            version: current_version.to_string(),
3459        };
3460        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
3461
3462        let mut service_manager = ServiceManager::new(
3463            service,
3464            Box::new(mock_service_control),
3465            VerbosityLevel::Normal,
3466        );
3467
3468        service_manager
3469            .upgrade(UpgradeOptions {
3470                auto_restart: false,
3471                bootstrap_peers: Vec::new(),
3472                env_variables: None,
3473                force: false,
3474                start_service: true,
3475                target_bin_path: target_node_bin.to_path_buf(),
3476                target_version: Version::parse(target_version).unwrap(),
3477            })
3478            .await?;
3479
3480        assert_eq!(service_manager.service.service_data.node_port, Some(12000));
3481
3482        Ok(())
3483    }
3484
3485    #[tokio::test]
3486    async fn upgrade_should_retain_max_archived_log_files() -> Result<()> {
3487        let current_version = "0.1.0";
3488        let target_version = "0.2.0";
3489
3490        let tmp_data_dir = assert_fs::TempDir::new()?;
3491        let current_install_dir = tmp_data_dir.child("safenode_install");
3492        current_install_dir.create_dir_all()?;
3493
3494        let current_node_bin = current_install_dir.child("safenode");
3495        current_node_bin.write_binary(b"fake safenode binary")?;
3496        let target_node_bin = tmp_data_dir.child("safenode");
3497        target_node_bin.write_binary(b"fake safenode binary")?;
3498
3499        let mut mock_service_control = MockServiceControl::new();
3500        let mut mock_rpc_client = MockRpcClient::new();
3501
3502        // before binary upgrade
3503        mock_service_control
3504            .expect_get_process_pid()
3505            .with(eq(current_node_bin.to_path_buf().clone()))
3506            .times(1)
3507            .returning(|_| Ok(1000));
3508        mock_service_control
3509            .expect_stop()
3510            .with(eq("safenode1"), eq(false))
3511            .times(1)
3512            .returning(|_, _| Ok(()));
3513
3514        // after binary upgrade
3515        mock_service_control
3516            .expect_uninstall()
3517            .with(eq("safenode1"), eq(false))
3518            .times(1)
3519            .returning(|_, _| Ok(()));
3520        mock_service_control
3521            .expect_install()
3522            .with(
3523                eq(ServiceInstallCtx {
3524                    args: vec![
3525                        OsString::from("--rpc"),
3526                        OsString::from("127.0.0.1:8081"),
3527                        OsString::from("--root-dir"),
3528                        OsString::from("/var/safenode-manager/services/safenode1"),
3529                        OsString::from("--log-output-dest"),
3530                        OsString::from("/var/log/safenode/safenode1"),
3531                        OsString::from("--max-archived-log-files"),
3532                        OsString::from("20"),
3533                        OsString::from("--rewards-address"),
3534                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
3535                        OsString::from("evm-arbitrum-one"),
3536                    ],
3537                    autostart: false,
3538                    contents: None,
3539                    environment: None,
3540                    label: "safenode1".parse()?,
3541                    program: current_node_bin.to_path_buf(),
3542                    username: Some("safe".to_string()),
3543                    working_directory: None,
3544                }),
3545                eq(false),
3546            )
3547            .times(1)
3548            .returning(|_, _| Ok(()));
3549
3550        // after service restart
3551        mock_service_control
3552            .expect_start()
3553            .with(eq("safenode1"), eq(false))
3554            .times(1)
3555            .returning(|_, _| Ok(()));
3556        mock_service_control
3557            .expect_wait()
3558            .with(eq(3000))
3559            .times(1)
3560            .returning(|_| ());
3561        mock_service_control
3562            .expect_get_process_pid()
3563            .with(eq(current_node_bin.to_path_buf().clone()))
3564            .times(1)
3565            .returning(|_| Ok(100));
3566
3567        mock_rpc_client.expect_node_info().times(1).returning(|| {
3568            Ok(NodeInfo {
3569                pid: 2000,
3570                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
3571                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3572                log_path: PathBuf::from("/var/log/safenode/safenode1"),
3573                version: target_version.to_string(),
3574                uptime: std::time::Duration::from_secs(1), // the service was just started
3575                wallet_balance: 0,
3576            })
3577        });
3578        mock_rpc_client
3579            .expect_network_info()
3580            .times(1)
3581            .returning(|| {
3582                Ok(NetworkInfo {
3583                    connected_peers: Vec::new(),
3584                    listeners: Vec::new(),
3585                })
3586            });
3587
3588        let mut service_data = NodeServiceData {
3589            auto_restart: false,
3590            connected_peers: None,
3591            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3592            genesis: false,
3593            home_network: false,
3594            listen_addr: None,
3595            local: false,
3596            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
3597            log_format: None,
3598            max_archived_log_files: Some(20),
3599            max_log_files: None,
3600            metrics_port: None,
3601            node_ip: None,
3602            node_port: None,
3603            number: 1,
3604            owner: None,
3605            peer_id: Some(PeerId::from_str(
3606                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
3607            )?),
3608            pid: Some(1000),
3609            reward_balance: Some(AttoTokens::zero()),
3610            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
3611            safenode_path: current_node_bin.to_path_buf(),
3612            service_name: "safenode1".to_string(),
3613            status: ServiceStatus::Running,
3614            upnp: false,
3615            user: Some("safe".to_string()),
3616            user_mode: false,
3617            version: current_version.to_string(),
3618            evm_network: EvmNetwork::ArbitrumOne,
3619            rewards_address: RewardsAddress::from_str(
3620                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
3621            )?,
3622        };
3623        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
3624
3625        let mut service_manager = ServiceManager::new(
3626            service,
3627            Box::new(mock_service_control),
3628            VerbosityLevel::Normal,
3629        );
3630
3631        service_manager
3632            .upgrade(UpgradeOptions {
3633                auto_restart: false,
3634                bootstrap_peers: Vec::new(),
3635                env_variables: None,
3636                force: false,
3637                start_service: true,
3638                target_bin_path: target_node_bin.to_path_buf(),
3639                target_version: Version::parse(target_version).unwrap(),
3640            })
3641            .await?;
3642
3643        assert_matches!(
3644            service_manager.service.service_data.max_archived_log_files,
3645            Some(20)
3646        );
3647
3648        Ok(())
3649    }
3650
3651    #[tokio::test]
3652    async fn upgrade_should_retain_max_log_files() -> Result<()> {
3653        let current_version = "0.1.0";
3654        let target_version = "0.2.0";
3655
3656        let tmp_data_dir = assert_fs::TempDir::new()?;
3657        let current_install_dir = tmp_data_dir.child("safenode_install");
3658        current_install_dir.create_dir_all()?;
3659
3660        let current_node_bin = current_install_dir.child("safenode");
3661        current_node_bin.write_binary(b"fake safenode binary")?;
3662        let target_node_bin = tmp_data_dir.child("safenode");
3663        target_node_bin.write_binary(b"fake safenode binary")?;
3664
3665        let mut mock_service_control = MockServiceControl::new();
3666        let mut mock_rpc_client = MockRpcClient::new();
3667
3668        // before binary upgrade
3669        mock_service_control
3670            .expect_get_process_pid()
3671            .with(eq(current_node_bin.to_path_buf().clone()))
3672            .times(1)
3673            .returning(|_| Ok(1000));
3674        mock_service_control
3675            .expect_stop()
3676            .with(eq("safenode1"), eq(false))
3677            .times(1)
3678            .returning(|_, _| Ok(()));
3679
3680        // after binary upgrade
3681        mock_service_control
3682            .expect_uninstall()
3683            .with(eq("safenode1"), eq(false))
3684            .times(1)
3685            .returning(|_, _| Ok(()));
3686        mock_service_control
3687            .expect_install()
3688            .with(
3689                eq(ServiceInstallCtx {
3690                    args: vec![
3691                        OsString::from("--rpc"),
3692                        OsString::from("127.0.0.1:8081"),
3693                        OsString::from("--root-dir"),
3694                        OsString::from("/var/safenode-manager/services/safenode1"),
3695                        OsString::from("--log-output-dest"),
3696                        OsString::from("/var/log/safenode/safenode1"),
3697                        OsString::from("--max-log-files"),
3698                        OsString::from("20"),
3699                        OsString::from("--rewards-address"),
3700                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
3701                        OsString::from("evm-arbitrum-one"),
3702                    ],
3703                    autostart: false,
3704                    contents: None,
3705                    environment: None,
3706                    label: "safenode1".parse()?,
3707                    program: current_node_bin.to_path_buf(),
3708                    username: Some("safe".to_string()),
3709                    working_directory: None,
3710                }),
3711                eq(false),
3712            )
3713            .times(1)
3714            .returning(|_, _| Ok(()));
3715
3716        // after service restart
3717        mock_service_control
3718            .expect_start()
3719            .with(eq("safenode1"), eq(false))
3720            .times(1)
3721            .returning(|_, _| Ok(()));
3722        mock_service_control
3723            .expect_wait()
3724            .with(eq(3000))
3725            .times(1)
3726            .returning(|_| ());
3727        mock_service_control
3728            .expect_get_process_pid()
3729            .with(eq(current_node_bin.to_path_buf().clone()))
3730            .times(1)
3731            .returning(|_| Ok(100));
3732
3733        mock_rpc_client.expect_node_info().times(1).returning(|| {
3734            Ok(NodeInfo {
3735                pid: 2000,
3736                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
3737                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3738                log_path: PathBuf::from("/var/log/safenode/safenode1"),
3739                version: target_version.to_string(),
3740                uptime: std::time::Duration::from_secs(1), // the service was just started
3741                wallet_balance: 0,
3742            })
3743        });
3744        mock_rpc_client
3745            .expect_network_info()
3746            .times(1)
3747            .returning(|| {
3748                Ok(NetworkInfo {
3749                    connected_peers: Vec::new(),
3750                    listeners: Vec::new(),
3751                })
3752            });
3753
3754        let mut service_data = NodeServiceData {
3755            auto_restart: false,
3756            connected_peers: None,
3757            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3758            genesis: false,
3759            home_network: false,
3760            listen_addr: None,
3761            local: false,
3762            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
3763            log_format: None,
3764            max_archived_log_files: None,
3765            max_log_files: Some(20),
3766            metrics_port: None,
3767            node_ip: None,
3768            node_port: None,
3769            number: 1,
3770            owner: None,
3771            peer_id: Some(PeerId::from_str(
3772                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
3773            )?),
3774            pid: Some(1000),
3775            reward_balance: Some(AttoTokens::zero()),
3776            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
3777            safenode_path: current_node_bin.to_path_buf(),
3778            service_name: "safenode1".to_string(),
3779            status: ServiceStatus::Running,
3780            upnp: false,
3781            user: Some("safe".to_string()),
3782            user_mode: false,
3783            version: current_version.to_string(),
3784            evm_network: EvmNetwork::ArbitrumOne,
3785            rewards_address: RewardsAddress::from_str(
3786                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
3787            )?,
3788        };
3789        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
3790
3791        let mut service_manager = ServiceManager::new(
3792            service,
3793            Box::new(mock_service_control),
3794            VerbosityLevel::Normal,
3795        );
3796
3797        service_manager
3798            .upgrade(UpgradeOptions {
3799                auto_restart: false,
3800                bootstrap_peers: Vec::new(),
3801                env_variables: None,
3802                force: false,
3803                start_service: true,
3804                target_bin_path: target_node_bin.to_path_buf(),
3805                target_version: Version::parse(target_version).unwrap(),
3806            })
3807            .await?;
3808
3809        assert_matches!(service_manager.service.service_data.max_log_files, Some(20));
3810
3811        Ok(())
3812    }
3813
3814    #[tokio::test]
3815    async fn upgrade_should_retain_custom_metrics_ports() -> Result<()> {
3816        let current_version = "0.1.0";
3817        let target_version = "0.2.0";
3818
3819        let tmp_data_dir = assert_fs::TempDir::new()?;
3820        let current_install_dir = tmp_data_dir.child("safenode_install");
3821        current_install_dir.create_dir_all()?;
3822
3823        let current_node_bin = current_install_dir.child("safenode");
3824        current_node_bin.write_binary(b"fake safenode binary")?;
3825        let target_node_bin = tmp_data_dir.child("safenode");
3826        target_node_bin.write_binary(b"fake safenode binary")?;
3827
3828        let mut mock_service_control = MockServiceControl::new();
3829        let mut mock_rpc_client = MockRpcClient::new();
3830
3831        // before binary upgrade
3832        mock_service_control
3833            .expect_get_process_pid()
3834            .with(eq(current_node_bin.to_path_buf().clone()))
3835            .times(1)
3836            .returning(|_| Ok(1000));
3837        mock_service_control
3838            .expect_stop()
3839            .with(eq("safenode1"), eq(false))
3840            .times(1)
3841            .returning(|_, _| Ok(()));
3842
3843        // after binary upgrade
3844        mock_service_control
3845            .expect_uninstall()
3846            .with(eq("safenode1"), eq(false))
3847            .times(1)
3848            .returning(|_, _| Ok(()));
3849        mock_service_control
3850            .expect_install()
3851            .with(
3852                eq(ServiceInstallCtx {
3853                    args: vec![
3854                        OsString::from("--rpc"),
3855                        OsString::from("127.0.0.1:8081"),
3856                        OsString::from("--root-dir"),
3857                        OsString::from("/var/safenode-manager/services/safenode1"),
3858                        OsString::from("--log-output-dest"),
3859                        OsString::from("/var/log/safenode/safenode1"),
3860                        OsString::from("--metrics-server-port"),
3861                        OsString::from("12000"),
3862                        OsString::from("--rewards-address"),
3863                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
3864                        OsString::from("evm-arbitrum-one"),
3865                    ],
3866                    autostart: false,
3867                    contents: None,
3868                    environment: None,
3869                    label: "safenode1".parse()?,
3870                    program: current_node_bin.to_path_buf(),
3871                    username: Some("safe".to_string()),
3872                    working_directory: None,
3873                }),
3874                eq(false),
3875            )
3876            .times(1)
3877            .returning(|_, _| Ok(()));
3878
3879        // after service restart
3880        mock_service_control
3881            .expect_start()
3882            .with(eq("safenode1"), eq(false))
3883            .times(1)
3884            .returning(|_, _| Ok(()));
3885        mock_service_control
3886            .expect_wait()
3887            .with(eq(3000))
3888            .times(1)
3889            .returning(|_| ());
3890        mock_service_control
3891            .expect_get_process_pid()
3892            .with(eq(current_node_bin.to_path_buf().clone()))
3893            .times(1)
3894            .returning(|_| Ok(100));
3895
3896        mock_rpc_client.expect_node_info().times(1).returning(|| {
3897            Ok(NodeInfo {
3898                pid: 2000,
3899                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
3900                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3901                log_path: PathBuf::from("/var/log/safenode/safenode1"),
3902                version: target_version.to_string(),
3903                uptime: std::time::Duration::from_secs(1), // the service was just started
3904                wallet_balance: 0,
3905            })
3906        });
3907        mock_rpc_client
3908            .expect_network_info()
3909            .times(1)
3910            .returning(|| {
3911                Ok(NetworkInfo {
3912                    connected_peers: Vec::new(),
3913                    listeners: Vec::new(),
3914                })
3915            });
3916
3917        let mut service_data = NodeServiceData {
3918            auto_restart: false,
3919            connected_peers: None,
3920            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
3921            evm_network: EvmNetwork::ArbitrumOne,
3922            genesis: false,
3923            home_network: false,
3924            listen_addr: None,
3925            local: false,
3926            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
3927            log_format: None,
3928            max_archived_log_files: None,
3929            max_log_files: None,
3930            metrics_port: Some(12000),
3931            node_ip: None,
3932            node_port: None,
3933            number: 1,
3934            owner: None,
3935            peer_id: Some(PeerId::from_str(
3936                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
3937            )?),
3938            pid: Some(1000),
3939            rewards_address: RewardsAddress::from_str(
3940                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
3941            )?,
3942            reward_balance: Some(AttoTokens::zero()),
3943            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
3944            safenode_path: current_node_bin.to_path_buf(),
3945            service_name: "safenode1".to_string(),
3946            status: ServiceStatus::Running,
3947            upnp: false,
3948            user: Some("safe".to_string()),
3949            user_mode: false,
3950            version: current_version.to_string(),
3951        };
3952        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
3953
3954        let mut service_manager = ServiceManager::new(
3955            service,
3956            Box::new(mock_service_control),
3957            VerbosityLevel::Normal,
3958        );
3959
3960        service_manager
3961            .upgrade(UpgradeOptions {
3962                auto_restart: false,
3963                bootstrap_peers: Vec::new(),
3964                env_variables: None,
3965                force: false,
3966                start_service: true,
3967                target_bin_path: target_node_bin.to_path_buf(),
3968                target_version: Version::parse(target_version).unwrap(),
3969            })
3970            .await?;
3971
3972        assert_eq!(
3973            service_manager.service.service_data.metrics_port,
3974            Some(12000)
3975        );
3976
3977        Ok(())
3978    }
3979
3980    #[tokio::test]
3981    async fn upgrade_should_retain_custom_rpc_ports() -> Result<()> {
3982        let current_version = "0.1.0";
3983        let target_version = "0.2.0";
3984
3985        let tmp_data_dir = assert_fs::TempDir::new()?;
3986        let current_install_dir = tmp_data_dir.child("safenode_install");
3987        current_install_dir.create_dir_all()?;
3988
3989        let current_node_bin = current_install_dir.child("safenode");
3990        current_node_bin.write_binary(b"fake safenode binary")?;
3991        let target_node_bin = tmp_data_dir.child("safenode");
3992        target_node_bin.write_binary(b"fake safenode binary")?;
3993
3994        let mut mock_service_control = MockServiceControl::new();
3995        let mut mock_rpc_client = MockRpcClient::new();
3996
3997        // before binary upgrade
3998        mock_service_control
3999            .expect_get_process_pid()
4000            .with(eq(current_node_bin.to_path_buf().clone()))
4001            .times(1)
4002            .returning(|_| Ok(1000));
4003        mock_service_control
4004            .expect_stop()
4005            .with(eq("safenode1"), eq(false))
4006            .times(1)
4007            .returning(|_, _| Ok(()));
4008
4009        // after binary upgrade
4010        mock_service_control
4011            .expect_uninstall()
4012            .with(eq("safenode1"), eq(false))
4013            .times(1)
4014            .returning(|_, _| Ok(()));
4015        mock_service_control
4016            .expect_install()
4017            .with(
4018                eq(ServiceInstallCtx {
4019                    args: vec![
4020                        OsString::from("--rpc"),
4021                        OsString::from("127.0.0.1:8081"),
4022                        OsString::from("--root-dir"),
4023                        OsString::from("/var/safenode-manager/services/safenode1"),
4024                        OsString::from("--log-output-dest"),
4025                        OsString::from("/var/log/safenode/safenode1"),
4026                        OsString::from("--metrics-server-port"),
4027                        OsString::from("12000"),
4028                        OsString::from("--rewards-address"),
4029                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
4030                        OsString::from("evm-arbitrum-one"),
4031                    ],
4032                    autostart: false,
4033                    contents: None,
4034                    environment: None,
4035                    label: "safenode1".parse()?,
4036                    program: current_node_bin.to_path_buf(),
4037                    username: Some("safe".to_string()),
4038                    working_directory: None,
4039                }),
4040                eq(false),
4041            )
4042            .times(1)
4043            .returning(|_, _| Ok(()));
4044
4045        // after service restart
4046        mock_service_control
4047            .expect_start()
4048            .with(eq("safenode1"), eq(false))
4049            .times(1)
4050            .returning(|_, _| Ok(()));
4051        mock_service_control
4052            .expect_wait()
4053            .with(eq(3000))
4054            .times(1)
4055            .returning(|_| ());
4056        mock_service_control
4057            .expect_get_process_pid()
4058            .with(eq(current_node_bin.to_path_buf().clone()))
4059            .times(1)
4060            .returning(|_| Ok(100));
4061
4062        mock_rpc_client.expect_node_info().times(1).returning(|| {
4063            Ok(NodeInfo {
4064                pid: 2000,
4065                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
4066                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4067                log_path: PathBuf::from("/var/log/safenode/safenode1"),
4068                version: target_version.to_string(),
4069                uptime: std::time::Duration::from_secs(1), // the service was just started
4070                wallet_balance: 0,
4071            })
4072        });
4073        mock_rpc_client
4074            .expect_network_info()
4075            .times(1)
4076            .returning(|| {
4077                Ok(NetworkInfo {
4078                    connected_peers: Vec::new(),
4079                    listeners: Vec::new(),
4080                })
4081            });
4082
4083        let mut service_data = NodeServiceData {
4084            auto_restart: false,
4085            connected_peers: None,
4086            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4087            evm_network: EvmNetwork::ArbitrumOne,
4088            genesis: false,
4089            home_network: false,
4090            listen_addr: None,
4091            local: false,
4092            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
4093            log_format: None,
4094            max_archived_log_files: None,
4095            max_log_files: None,
4096            metrics_port: Some(12000),
4097            node_ip: None,
4098            node_port: None,
4099            number: 1,
4100            owner: None,
4101            peer_id: Some(PeerId::from_str(
4102                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
4103            )?),
4104            pid: Some(1000),
4105            rewards_address: RewardsAddress::from_str(
4106                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
4107            )?,
4108            reward_balance: Some(AttoTokens::zero()),
4109            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
4110            safenode_path: current_node_bin.to_path_buf(),
4111            service_name: "safenode1".to_string(),
4112            status: ServiceStatus::Running,
4113            upnp: false,
4114            user: Some("safe".to_string()),
4115            user_mode: false,
4116            version: current_version.to_string(),
4117        };
4118        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
4119
4120        let mut service_manager = ServiceManager::new(
4121            service,
4122            Box::new(mock_service_control),
4123            VerbosityLevel::Normal,
4124        );
4125
4126        service_manager
4127            .upgrade(UpgradeOptions {
4128                auto_restart: false,
4129                bootstrap_peers: Vec::new(),
4130                env_variables: None,
4131                force: false,
4132                start_service: true,
4133                target_bin_path: target_node_bin.to_path_buf(),
4134                target_version: Version::parse(target_version).unwrap(),
4135            })
4136            .await?;
4137
4138        assert_eq!(
4139            service_manager.service.service_data.rpc_socket_addr,
4140            SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081)
4141        );
4142
4143        Ok(())
4144    }
4145
4146    #[tokio::test]
4147    async fn upgrade_should_retain_owner() -> Result<()> {
4148        let current_version = "0.1.0";
4149        let target_version = "0.2.0";
4150
4151        let tmp_data_dir = assert_fs::TempDir::new()?;
4152        let current_install_dir = tmp_data_dir.child("safenode_install");
4153        current_install_dir.create_dir_all()?;
4154
4155        let current_node_bin = current_install_dir.child("safenode");
4156        current_node_bin.write_binary(b"fake safenode binary")?;
4157        let target_node_bin = tmp_data_dir.child("safenode");
4158        target_node_bin.write_binary(b"fake safenode binary")?;
4159
4160        let mut mock_service_control = MockServiceControl::new();
4161        let mut mock_rpc_client = MockRpcClient::new();
4162
4163        // before binary upgrade
4164        mock_service_control
4165            .expect_get_process_pid()
4166            .with(eq(current_node_bin.to_path_buf().clone()))
4167            .times(1)
4168            .returning(|_| Ok(1000));
4169        mock_service_control
4170            .expect_stop()
4171            .with(eq("safenode1"), eq(false))
4172            .times(1)
4173            .returning(|_, _| Ok(()));
4174
4175        // after binary upgrade
4176        mock_service_control
4177            .expect_uninstall()
4178            .with(eq("safenode1"), eq(false))
4179            .times(1)
4180            .returning(|_, _| Ok(()));
4181        mock_service_control
4182            .expect_install()
4183            .with(
4184                eq(ServiceInstallCtx {
4185                    args: vec![
4186                        OsString::from("--rpc"),
4187                        OsString::from("127.0.0.1:8081"),
4188                        OsString::from("--root-dir"),
4189                        OsString::from("/var/safenode-manager/services/safenode1"),
4190                        OsString::from("--log-output-dest"),
4191                        OsString::from("/var/log/safenode/safenode1"),
4192                        OsString::from("--owner"),
4193                        OsString::from("discord_username"),
4194                        OsString::from("--rewards-address"),
4195                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
4196                        OsString::from("evm-arbitrum-one"),
4197                    ],
4198                    autostart: false,
4199                    contents: None,
4200                    environment: None,
4201                    label: "safenode1".parse()?,
4202                    program: current_node_bin.to_path_buf(),
4203                    username: Some("safe".to_string()),
4204                    working_directory: None,
4205                }),
4206                eq(false),
4207            )
4208            .times(1)
4209            .returning(|_, _| Ok(()));
4210
4211        // after service restart
4212        mock_service_control
4213            .expect_start()
4214            .with(eq("safenode1"), eq(false))
4215            .times(1)
4216            .returning(|_, _| Ok(()));
4217        mock_service_control
4218            .expect_wait()
4219            .with(eq(3000))
4220            .times(1)
4221            .returning(|_| ());
4222        mock_service_control
4223            .expect_get_process_pid()
4224            .with(eq(current_node_bin.to_path_buf().clone()))
4225            .times(1)
4226            .returning(|_| Ok(100));
4227
4228        mock_rpc_client.expect_node_info().times(1).returning(|| {
4229            Ok(NodeInfo {
4230                pid: 2000,
4231                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
4232                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4233                log_path: PathBuf::from("/var/log/safenode/safenode1"),
4234                version: target_version.to_string(),
4235                uptime: std::time::Duration::from_secs(1), // the service was just started
4236                wallet_balance: 0,
4237            })
4238        });
4239        mock_rpc_client
4240            .expect_network_info()
4241            .times(1)
4242            .returning(|| {
4243                Ok(NetworkInfo {
4244                    connected_peers: Vec::new(),
4245                    listeners: Vec::new(),
4246                })
4247            });
4248
4249        let mut service_data = NodeServiceData {
4250            auto_restart: false,
4251            connected_peers: None,
4252            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4253            evm_network: EvmNetwork::ArbitrumOne,
4254            genesis: false,
4255            home_network: false,
4256            listen_addr: None,
4257            local: false,
4258            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
4259            log_format: None,
4260            max_archived_log_files: None,
4261            max_log_files: None,
4262            metrics_port: None,
4263            node_ip: None,
4264            node_port: None,
4265            number: 1,
4266            owner: Some("discord_username".to_string()),
4267            peer_id: Some(PeerId::from_str(
4268                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
4269            )?),
4270            pid: Some(1000),
4271            rewards_address: RewardsAddress::from_str(
4272                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
4273            )?,
4274            reward_balance: Some(AttoTokens::zero()),
4275            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
4276            safenode_path: current_node_bin.to_path_buf(),
4277            service_name: "safenode1".to_string(),
4278            status: ServiceStatus::Running,
4279            upnp: false,
4280            user: Some("safe".to_string()),
4281            user_mode: false,
4282            version: current_version.to_string(),
4283        };
4284        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
4285
4286        let mut service_manager = ServiceManager::new(
4287            service,
4288            Box::new(mock_service_control),
4289            VerbosityLevel::Normal,
4290        );
4291
4292        service_manager
4293            .upgrade(UpgradeOptions {
4294                auto_restart: false,
4295                bootstrap_peers: Vec::new(),
4296                env_variables: None,
4297                force: false,
4298                start_service: true,
4299                target_bin_path: target_node_bin.to_path_buf(),
4300                target_version: Version::parse(target_version).unwrap(),
4301            })
4302            .await?;
4303
4304        assert_eq!(
4305            service_manager.service.service_data.owner,
4306            Some("discord_username".to_string())
4307        );
4308
4309        Ok(())
4310    }
4311
4312    #[tokio::test]
4313    async fn upgrade_should_retain_auto_restart() -> Result<()> {
4314        let current_version = "0.1.0";
4315        let target_version = "0.2.0";
4316
4317        let tmp_data_dir = assert_fs::TempDir::new()?;
4318        let current_install_dir = tmp_data_dir.child("safenode_install");
4319        current_install_dir.create_dir_all()?;
4320
4321        let current_node_bin = current_install_dir.child("safenode");
4322        current_node_bin.write_binary(b"fake safenode binary")?;
4323        let target_node_bin = tmp_data_dir.child("safenode");
4324        target_node_bin.write_binary(b"fake safenode binary")?;
4325
4326        let mut mock_service_control = MockServiceControl::new();
4327        let mut mock_rpc_client = MockRpcClient::new();
4328
4329        // before binary upgrade
4330        mock_service_control
4331            .expect_get_process_pid()
4332            .with(eq(current_node_bin.to_path_buf().clone()))
4333            .times(1)
4334            .returning(|_| Ok(1000));
4335        mock_service_control
4336            .expect_stop()
4337            .with(eq("safenode1"), eq(false))
4338            .times(1)
4339            .returning(|_, _| Ok(()));
4340
4341        // after binary upgrade
4342        mock_service_control
4343            .expect_uninstall()
4344            .with(eq("safenode1"), eq(false))
4345            .times(1)
4346            .returning(|_, _| Ok(()));
4347        mock_service_control
4348            .expect_install()
4349            .with(
4350                eq(ServiceInstallCtx {
4351                    args: vec![
4352                        OsString::from("--rpc"),
4353                        OsString::from("127.0.0.1:8081"),
4354                        OsString::from("--root-dir"),
4355                        OsString::from("/var/safenode-manager/services/safenode1"),
4356                        OsString::from("--log-output-dest"),
4357                        OsString::from("/var/log/safenode/safenode1"),
4358                        OsString::from("--owner"),
4359                        OsString::from("discord_username"),
4360                        OsString::from("--rewards-address"),
4361                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
4362                        OsString::from("evm-arbitrum-one"),
4363                    ],
4364                    autostart: true,
4365                    contents: None,
4366                    environment: None,
4367                    label: "safenode1".parse()?,
4368                    program: current_node_bin.to_path_buf(),
4369                    username: Some("safe".to_string()),
4370                    working_directory: None,
4371                }),
4372                eq(false),
4373            )
4374            .times(1)
4375            .returning(|_, _| Ok(()));
4376
4377        // after service restart
4378        mock_service_control
4379            .expect_start()
4380            .with(eq("safenode1"), eq(false))
4381            .times(1)
4382            .returning(|_, _| Ok(()));
4383        mock_service_control
4384            .expect_wait()
4385            .with(eq(3000))
4386            .times(1)
4387            .returning(|_| ());
4388        mock_service_control
4389            .expect_get_process_pid()
4390            .with(eq(current_node_bin.to_path_buf().clone()))
4391            .times(1)
4392            .returning(|_| Ok(100));
4393
4394        mock_rpc_client.expect_node_info().times(1).returning(|| {
4395            Ok(NodeInfo {
4396                pid: 2000,
4397                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
4398                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4399                log_path: PathBuf::from("/var/log/safenode/safenode1"),
4400                version: target_version.to_string(),
4401                uptime: std::time::Duration::from_secs(1), // the service was just started
4402                wallet_balance: 0,
4403            })
4404        });
4405        mock_rpc_client
4406            .expect_network_info()
4407            .times(1)
4408            .returning(|| {
4409                Ok(NetworkInfo {
4410                    connected_peers: Vec::new(),
4411                    listeners: Vec::new(),
4412                })
4413            });
4414
4415        let mut service_data = NodeServiceData {
4416            auto_restart: true,
4417            connected_peers: None,
4418            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4419            evm_network: EvmNetwork::ArbitrumOne,
4420            genesis: false,
4421            home_network: false,
4422            listen_addr: None,
4423            local: false,
4424            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
4425            log_format: None,
4426            max_archived_log_files: None,
4427            max_log_files: None,
4428            metrics_port: None,
4429            node_ip: None,
4430            node_port: None,
4431            number: 1,
4432            owner: Some("discord_username".to_string()),
4433            peer_id: Some(PeerId::from_str(
4434                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
4435            )?),
4436            pid: Some(1000),
4437            rewards_address: RewardsAddress::from_str(
4438                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
4439            )?,
4440            reward_balance: Some(AttoTokens::zero()),
4441            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
4442            safenode_path: current_node_bin.to_path_buf(),
4443            service_name: "safenode1".to_string(),
4444            status: ServiceStatus::Running,
4445            upnp: false,
4446            user: Some("safe".to_string()),
4447            user_mode: false,
4448            version: current_version.to_string(),
4449        };
4450        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
4451
4452        let mut service_manager = ServiceManager::new(
4453            service,
4454            Box::new(mock_service_control),
4455            VerbosityLevel::Normal,
4456        );
4457
4458        service_manager
4459            .upgrade(UpgradeOptions {
4460                auto_restart: true,
4461                bootstrap_peers: Vec::new(),
4462                env_variables: None,
4463                force: false,
4464                start_service: true,
4465                target_bin_path: target_node_bin.to_path_buf(),
4466                target_version: Version::parse(target_version).unwrap(),
4467            })
4468            .await?;
4469
4470        assert!(service_manager.service.service_data.auto_restart,);
4471
4472        Ok(())
4473    }
4474
4475    #[tokio::test]
4476    async fn upgrade_should_retain_evm_network_settings() -> Result<()> {
4477        let current_version = "0.1.0";
4478        let target_version = "0.2.0";
4479
4480        let tmp_data_dir = assert_fs::TempDir::new()?;
4481        let current_install_dir = tmp_data_dir.child("safenode_install");
4482        current_install_dir.create_dir_all()?;
4483
4484        let current_node_bin = current_install_dir.child("safenode");
4485        current_node_bin.write_binary(b"fake safenode binary")?;
4486        let target_node_bin = tmp_data_dir.child("safenode");
4487        target_node_bin.write_binary(b"fake safenode binary")?;
4488
4489        let mut mock_service_control = MockServiceControl::new();
4490        let mut mock_rpc_client = MockRpcClient::new();
4491
4492        // before binary upgrade
4493        mock_service_control
4494            .expect_get_process_pid()
4495            .with(eq(current_node_bin.to_path_buf().clone()))
4496            .times(1)
4497            .returning(|_| Ok(1000));
4498        mock_service_control
4499            .expect_stop()
4500            .with(eq("safenode1"), eq(false))
4501            .times(1)
4502            .returning(|_, _| Ok(()));
4503
4504        // after binary upgrade
4505        mock_service_control
4506            .expect_uninstall()
4507            .with(eq("safenode1"), eq(false))
4508            .times(1)
4509            .returning(|_, _| Ok(()));
4510        mock_service_control
4511            .expect_install()
4512            .with(
4513                eq(ServiceInstallCtx {
4514                    args: vec![
4515                        OsString::from("--rpc"),
4516                        OsString::from("127.0.0.1:8081"),
4517                        OsString::from("--root-dir"),
4518                        OsString::from("/var/safenode-manager/services/safenode1"),
4519                        OsString::from("--log-output-dest"),
4520                        OsString::from("/var/log/safenode/safenode1"),
4521                        OsString::from("--owner"),
4522                        OsString::from("discord_username"),
4523                        OsString::from("--rewards-address"),
4524                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
4525                        OsString::from("evm-custom"),
4526                        OsString::from("--rpc-url"),
4527                        OsString::from("http://localhost:8545/"),
4528                        OsString::from("--payment-token-address"),
4529                        OsString::from("0x5FbDB2315678afecb367f032d93F642f64180aa3"),
4530                        OsString::from("--data-payments-address"),
4531                        OsString::from("0x8464135c8F25Da09e49BC8782676a84730C318bC"),
4532                    ],
4533                    autostart: true,
4534                    contents: None,
4535                    environment: None,
4536                    label: "safenode1".parse()?,
4537                    program: current_node_bin.to_path_buf(),
4538                    username: Some("safe".to_string()),
4539                    working_directory: None,
4540                }),
4541                eq(false),
4542            )
4543            .times(1)
4544            .returning(|_, _| Ok(()));
4545
4546        // after service restart
4547        mock_service_control
4548            .expect_start()
4549            .with(eq("safenode1"), eq(false))
4550            .times(1)
4551            .returning(|_, _| Ok(()));
4552        mock_service_control
4553            .expect_wait()
4554            .with(eq(3000))
4555            .times(1)
4556            .returning(|_| ());
4557        mock_service_control
4558            .expect_get_process_pid()
4559            .with(eq(current_node_bin.to_path_buf().clone()))
4560            .times(1)
4561            .returning(|_| Ok(100));
4562
4563        mock_rpc_client.expect_node_info().times(1).returning(|| {
4564            Ok(NodeInfo {
4565                pid: 2000,
4566                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
4567                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4568                log_path: PathBuf::from("/var/log/safenode/safenode1"),
4569                version: target_version.to_string(),
4570                uptime: std::time::Duration::from_secs(1), // the service was just started
4571                wallet_balance: 0,
4572            })
4573        });
4574        mock_rpc_client
4575            .expect_network_info()
4576            .times(1)
4577            .returning(|| {
4578                Ok(NetworkInfo {
4579                    connected_peers: Vec::new(),
4580                    listeners: Vec::new(),
4581                })
4582            });
4583
4584        let mut service_data = NodeServiceData {
4585            auto_restart: true,
4586            connected_peers: None,
4587            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4588            evm_network: EvmNetwork::Custom(CustomNetwork {
4589                rpc_url_http: "http://localhost:8545".parse()?,
4590                payment_token_address: RewardsAddress::from_str(
4591                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
4592                )?,
4593                data_payments_address: RewardsAddress::from_str(
4594                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
4595                )?,
4596            }),
4597            genesis: false,
4598            home_network: false,
4599            listen_addr: None,
4600            local: false,
4601            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
4602            log_format: None,
4603            max_archived_log_files: None,
4604            max_log_files: None,
4605            metrics_port: None,
4606            node_ip: None,
4607            node_port: None,
4608            number: 1,
4609            owner: Some("discord_username".to_string()),
4610            peer_id: Some(PeerId::from_str(
4611                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
4612            )?),
4613            pid: Some(1000),
4614            rewards_address: RewardsAddress::from_str(
4615                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
4616            )?,
4617            reward_balance: Some(AttoTokens::zero()),
4618
4619            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
4620            safenode_path: current_node_bin.to_path_buf(),
4621            service_name: "safenode1".to_string(),
4622            status: ServiceStatus::Running,
4623            upnp: false,
4624            user: Some("safe".to_string()),
4625            user_mode: false,
4626            version: current_version.to_string(),
4627        };
4628        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
4629
4630        let mut service_manager = ServiceManager::new(
4631            service,
4632            Box::new(mock_service_control),
4633            VerbosityLevel::Normal,
4634        );
4635
4636        service_manager
4637            .upgrade(UpgradeOptions {
4638                auto_restart: true,
4639                bootstrap_peers: Vec::new(),
4640                env_variables: None,
4641                force: false,
4642                start_service: true,
4643                target_bin_path: target_node_bin.to_path_buf(),
4644                target_version: Version::parse(target_version).unwrap(),
4645            })
4646            .await?;
4647
4648        assert!(service_manager.service.service_data.auto_restart,);
4649
4650        Ok(())
4651    }
4652
4653    #[tokio::test]
4654    async fn upgrade_should_retain_the_rewards_address() -> Result<()> {
4655        let current_version = "0.1.0";
4656        let target_version = "0.2.0";
4657
4658        let tmp_data_dir = assert_fs::TempDir::new()?;
4659        let current_install_dir = tmp_data_dir.child("safenode_install");
4660        current_install_dir.create_dir_all()?;
4661
4662        let current_node_bin = current_install_dir.child("safenode");
4663        current_node_bin.write_binary(b"fake safenode binary")?;
4664        let target_node_bin = tmp_data_dir.child("safenode");
4665        target_node_bin.write_binary(b"fake safenode binary")?;
4666
4667        let mut mock_service_control = MockServiceControl::new();
4668        let mut mock_rpc_client = MockRpcClient::new();
4669
4670        // before binary upgrade
4671        mock_service_control
4672            .expect_get_process_pid()
4673            .with(eq(current_node_bin.to_path_buf().clone()))
4674            .times(1)
4675            .returning(|_| Ok(1000));
4676        mock_service_control
4677            .expect_stop()
4678            .with(eq("safenode1"), eq(false))
4679            .times(1)
4680            .returning(|_, _| Ok(()));
4681
4682        // after binary upgrade
4683        mock_service_control
4684            .expect_uninstall()
4685            .with(eq("safenode1"), eq(false))
4686            .times(1)
4687            .returning(|_, _| Ok(()));
4688        mock_service_control
4689            .expect_install()
4690            .with(
4691                eq(ServiceInstallCtx {
4692                    args: vec![
4693                        OsString::from("--rpc"),
4694                        OsString::from("127.0.0.1:8081"),
4695                        OsString::from("--root-dir"),
4696                        OsString::from("/var/safenode-manager/services/safenode1"),
4697                        OsString::from("--log-output-dest"),
4698                        OsString::from("/var/log/safenode/safenode1"),
4699                        OsString::from("--owner"),
4700                        OsString::from("discord_username"),
4701                        OsString::from("--rewards-address"),
4702                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
4703                        OsString::from("evm-custom"),
4704                        OsString::from("--rpc-url"),
4705                        OsString::from("http://localhost:8545/"),
4706                        OsString::from("--payment-token-address"),
4707                        OsString::from("0x5FbDB2315678afecb367f032d93F642f64180aa3"),
4708                        OsString::from("--data-payments-address"),
4709                        OsString::from("0x8464135c8F25Da09e49BC8782676a84730C318bC"),
4710                    ],
4711                    autostart: true,
4712                    contents: None,
4713                    environment: None,
4714                    label: "safenode1".parse()?,
4715                    program: current_node_bin.to_path_buf(),
4716                    username: Some("safe".to_string()),
4717                    working_directory: None,
4718                }),
4719                eq(false),
4720            )
4721            .times(1)
4722            .returning(|_, _| Ok(()));
4723
4724        // after service restart
4725        mock_service_control
4726            .expect_start()
4727            .with(eq("safenode1"), eq(false))
4728            .times(1)
4729            .returning(|_, _| Ok(()));
4730        mock_service_control
4731            .expect_wait()
4732            .with(eq(3000))
4733            .times(1)
4734            .returning(|_| ());
4735        mock_service_control
4736            .expect_get_process_pid()
4737            .with(eq(current_node_bin.to_path_buf().clone()))
4738            .times(1)
4739            .returning(|_| Ok(100));
4740
4741        mock_rpc_client.expect_node_info().times(1).returning(|| {
4742            Ok(NodeInfo {
4743                pid: 2000,
4744                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
4745                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4746                log_path: PathBuf::from("/var/log/safenode/safenode1"),
4747                version: target_version.to_string(),
4748                uptime: std::time::Duration::from_secs(1), // the service was just started
4749                wallet_balance: 0,
4750            })
4751        });
4752        mock_rpc_client
4753            .expect_network_info()
4754            .times(1)
4755            .returning(|| {
4756                Ok(NetworkInfo {
4757                    connected_peers: Vec::new(),
4758                    listeners: Vec::new(),
4759                })
4760            });
4761
4762        let mut service_data = NodeServiceData {
4763            auto_restart: true,
4764            connected_peers: None,
4765            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4766            evm_network: EvmNetwork::Custom(CustomNetwork {
4767                rpc_url_http: "http://localhost:8545".parse()?,
4768                payment_token_address: RewardsAddress::from_str(
4769                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
4770                )?,
4771                data_payments_address: RewardsAddress::from_str(
4772                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
4773                )?,
4774            }),
4775            genesis: false,
4776            home_network: false,
4777            listen_addr: None,
4778            local: false,
4779            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
4780            log_format: None,
4781            max_archived_log_files: None,
4782            max_log_files: None,
4783            metrics_port: None,
4784            node_ip: None,
4785            node_port: None,
4786            number: 1,
4787            owner: Some("discord_username".to_string()),
4788            peer_id: Some(PeerId::from_str(
4789                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
4790            )?),
4791            pid: Some(1000),
4792            rewards_address: RewardsAddress::from_str(
4793                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
4794            )?,
4795            reward_balance: Some(AttoTokens::zero()),
4796
4797            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
4798            safenode_path: current_node_bin.to_path_buf(),
4799            service_name: "safenode1".to_string(),
4800            status: ServiceStatus::Running,
4801            upnp: false,
4802            user: Some("safe".to_string()),
4803            user_mode: false,
4804            version: current_version.to_string(),
4805        };
4806        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client));
4807
4808        let mut service_manager = ServiceManager::new(
4809            service,
4810            Box::new(mock_service_control),
4811            VerbosityLevel::Normal,
4812        );
4813
4814        service_manager
4815            .upgrade(UpgradeOptions {
4816                auto_restart: true,
4817                bootstrap_peers: Vec::new(),
4818                env_variables: None,
4819                force: false,
4820                start_service: true,
4821                target_bin_path: target_node_bin.to_path_buf(),
4822                target_version: Version::parse(target_version).unwrap(),
4823            })
4824            .await?;
4825
4826        assert!(service_manager.service.service_data.auto_restart,);
4827
4828        Ok(())
4829    }
4830
4831    #[tokio::test]
4832    async fn upgrade_should_use_dynamic_startup_delay_if_set() -> Result<()> {
4833        let current_version = "0.1.0";
4834        let target_version = "0.2.0";
4835
4836        let tmp_data_dir = assert_fs::TempDir::new()?;
4837        let current_install_dir = tmp_data_dir.child("safenode_install");
4838        current_install_dir.create_dir_all()?;
4839
4840        let current_node_bin = current_install_dir.child("safenode");
4841        current_node_bin.write_binary(b"fake safenode binary")?;
4842        let target_node_bin = tmp_data_dir.child("safenode");
4843        target_node_bin.write_binary(b"fake safenode binary")?;
4844
4845        let mut mock_service_control = MockServiceControl::new();
4846        let mut mock_rpc_client = MockRpcClient::new();
4847
4848        // before binary upgrade
4849        mock_service_control
4850            .expect_get_process_pid()
4851            .with(eq(current_node_bin.to_path_buf().clone()))
4852            .times(1)
4853            .returning(|_| Ok(1000));
4854        mock_service_control
4855            .expect_stop()
4856            .with(eq("safenode1"), eq(false))
4857            .times(1)
4858            .returning(|_, _| Ok(()));
4859
4860        // after binary upgrade
4861        mock_service_control
4862            .expect_uninstall()
4863            .with(eq("safenode1"), eq(false))
4864            .times(1)
4865            .returning(|_, _| Ok(()));
4866        mock_service_control
4867            .expect_install()
4868            .with(
4869                eq(ServiceInstallCtx {
4870                    args: vec![
4871                        OsString::from("--rpc"),
4872                        OsString::from("127.0.0.1:8081"),
4873                        OsString::from("--root-dir"),
4874                        OsString::from("/var/safenode-manager/services/safenode1"),
4875                        OsString::from("--log-output-dest"),
4876                        OsString::from("/var/log/safenode/safenode1"),
4877                        OsString::from("--upnp"),
4878                        OsString::from("--rewards-address"),
4879                        OsString::from("0x03B770D9cD32077cC0bF330c13C114a87643B124"),
4880                        OsString::from("evm-arbitrum-one"),
4881                    ],
4882                    autostart: false,
4883                    contents: None,
4884                    environment: None,
4885                    label: "safenode1".parse()?,
4886                    program: current_node_bin.to_path_buf(),
4887                    username: Some("safe".to_string()),
4888                    working_directory: None,
4889                }),
4890                eq(false),
4891            )
4892            .times(1)
4893            .returning(|_, _| Ok(()));
4894
4895        // after service restart
4896        mock_service_control
4897            .expect_start()
4898            .with(eq("safenode1"), eq(false))
4899            .times(1)
4900            .returning(|_, _| Ok(()));
4901        mock_service_control
4902            .expect_wait()
4903            .with(eq(3000))
4904            .times(1)
4905            .returning(|_| ());
4906        mock_service_control
4907            .expect_get_process_pid()
4908            .with(eq(current_node_bin.to_path_buf().clone()))
4909            .times(1)
4910            .returning(|_| Ok(100));
4911        mock_rpc_client
4912            .expect_is_node_connected_to_network()
4913            .times(1)
4914            .returning(|_| Ok(()));
4915        mock_rpc_client.expect_node_info().times(1).returning(|| {
4916            Ok(NodeInfo {
4917                pid: 2000,
4918                peer_id: PeerId::from_str("12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR")?,
4919                data_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4920                log_path: PathBuf::from("/var/log/safenode/safenode1"),
4921                version: target_version.to_string(),
4922                uptime: std::time::Duration::from_secs(1), // the service was just started
4923                wallet_balance: 0,
4924            })
4925        });
4926        mock_rpc_client
4927            .expect_network_info()
4928            .times(1)
4929            .returning(|| {
4930                Ok(NetworkInfo {
4931                    connected_peers: Vec::new(),
4932                    listeners: Vec::new(),
4933                })
4934            });
4935
4936        let mut service_data = NodeServiceData {
4937            auto_restart: false,
4938            connected_peers: None,
4939            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
4940            evm_network: EvmNetwork::ArbitrumOne,
4941            genesis: false,
4942            home_network: false,
4943            listen_addr: None,
4944            local: false,
4945            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
4946            log_format: None,
4947            max_archived_log_files: None,
4948            max_log_files: None,
4949            metrics_port: None,
4950            node_ip: None,
4951            node_port: None,
4952            number: 1,
4953            owner: None,
4954            peer_id: Some(PeerId::from_str(
4955                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
4956            )?),
4957            pid: Some(1000),
4958            rewards_address: RewardsAddress::from_str(
4959                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
4960            )?,
4961            reward_balance: Some(AttoTokens::zero()),
4962            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
4963            safenode_path: current_node_bin.to_path_buf(),
4964            service_name: "safenode1".to_string(),
4965            status: ServiceStatus::Running,
4966            upnp: true,
4967            user: Some("safe".to_string()),
4968            user_mode: false,
4969            version: current_version.to_string(),
4970        };
4971        let service = NodeService::new(&mut service_data, Box::new(mock_rpc_client))
4972            .with_connection_timeout(Duration::from_secs(
4973                DEFAULT_NODE_STARTUP_CONNECTION_TIMEOUT_S,
4974            ));
4975
4976        let mut service_manager = ServiceManager::new(
4977            service,
4978            Box::new(mock_service_control),
4979            VerbosityLevel::Normal,
4980        );
4981
4982        service_manager
4983            .upgrade(UpgradeOptions {
4984                auto_restart: false,
4985                bootstrap_peers: Vec::new(),
4986                env_variables: None,
4987                force: false,
4988                start_service: true,
4989                target_bin_path: target_node_bin.to_path_buf(),
4990                target_version: Version::parse(target_version).unwrap(),
4991            })
4992            .await?;
4993
4994        Ok(())
4995    }
4996
4997    #[tokio::test]
4998    async fn remove_should_remove_an_added_node() -> Result<()> {
4999        let temp_dir = assert_fs::TempDir::new()?;
5000        let log_dir = temp_dir.child("safenode1-logs");
5001        log_dir.create_dir_all()?;
5002        let data_dir = temp_dir.child("safenode1-data");
5003        data_dir.create_dir_all()?;
5004        let safenode_bin = data_dir.child("safenode");
5005        safenode_bin.write_binary(b"fake safenode binary")?;
5006
5007        let mut mock_service_control = MockServiceControl::new();
5008        mock_service_control
5009            .expect_uninstall()
5010            .with(eq("safenode1"), eq(false))
5011            .times(1)
5012            .returning(|_, _| Ok(()));
5013
5014        let mut service_data = NodeServiceData {
5015            auto_restart: false,
5016            connected_peers: None,
5017            data_dir_path: data_dir.to_path_buf(),
5018            evm_network: EvmNetwork::Custom(CustomNetwork {
5019                rpc_url_http: "http://localhost:8545".parse()?,
5020                payment_token_address: RewardsAddress::from_str(
5021                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
5022                )?,
5023                data_payments_address: RewardsAddress::from_str(
5024                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
5025                )?,
5026            }),
5027            genesis: false,
5028            home_network: false,
5029            listen_addr: None,
5030            local: false,
5031            log_dir_path: log_dir.to_path_buf(),
5032            log_format: None,
5033            max_archived_log_files: None,
5034            max_log_files: None,
5035            metrics_port: None,
5036            node_ip: None,
5037            node_port: None,
5038            number: 1,
5039            owner: None,
5040            pid: None,
5041            peer_id: None,
5042            rewards_address: RewardsAddress::from_str(
5043                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
5044            )?,
5045            reward_balance: Some(AttoTokens::zero()),
5046            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
5047            safenode_path: safenode_bin.to_path_buf(),
5048            status: ServiceStatus::Stopped,
5049            service_name: "safenode1".to_string(),
5050            version: "0.98.1".to_string(),
5051            upnp: false,
5052            user: Some("safe".to_string()),
5053            user_mode: false,
5054        };
5055        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
5056        let mut service_manager = ServiceManager::new(
5057            service,
5058            Box::new(mock_service_control),
5059            VerbosityLevel::Normal,
5060        );
5061
5062        service_manager.remove(false).await?;
5063
5064        assert_matches!(
5065            service_manager.service.service_data.status,
5066            ServiceStatus::Removed
5067        );
5068        log_dir.assert(predicate::path::missing());
5069        data_dir.assert(predicate::path::missing());
5070
5071        Ok(())
5072    }
5073
5074    #[tokio::test]
5075    async fn remove_should_return_an_error_if_attempting_to_remove_a_running_node() -> Result<()> {
5076        let mut mock_service_control = MockServiceControl::new();
5077        mock_service_control
5078            .expect_get_process_pid()
5079            .with(eq(PathBuf::from(
5080                "/var/safenode-manager/services/safenode1/safenode",
5081            )))
5082            .times(1)
5083            .returning(|_| Ok(1000));
5084
5085        let mut service_data = NodeServiceData {
5086            auto_restart: false,
5087            connected_peers: None,
5088            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
5089            evm_network: EvmNetwork::Custom(CustomNetwork {
5090                rpc_url_http: "http://localhost:8545".parse()?,
5091                payment_token_address: RewardsAddress::from_str(
5092                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
5093                )?,
5094                data_payments_address: RewardsAddress::from_str(
5095                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
5096                )?,
5097            }),
5098            genesis: false,
5099            home_network: false,
5100            listen_addr: None,
5101            local: false,
5102            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
5103            log_format: None,
5104            max_archived_log_files: None,
5105            max_log_files: None,
5106            metrics_port: None,
5107            node_ip: None,
5108            node_port: None,
5109            number: 1,
5110            owner: None,
5111            pid: Some(1000),
5112            peer_id: Some(PeerId::from_str(
5113                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
5114            )?),
5115            rewards_address: RewardsAddress::from_str(
5116                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
5117            )?,
5118            reward_balance: Some(AttoTokens::zero()),
5119            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
5120            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
5121            service_name: "safenode1".to_string(),
5122            status: ServiceStatus::Running,
5123            upnp: false,
5124            user: Some("safe".to_string()),
5125            user_mode: false,
5126            version: "0.98.1".to_string(),
5127        };
5128        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
5129        let mut service_manager = ServiceManager::new(
5130            service,
5131            Box::new(mock_service_control),
5132            VerbosityLevel::Normal,
5133        );
5134
5135        let result = service_manager.remove(false).await;
5136        match result {
5137            Ok(_) => panic!("This test should result in an error"),
5138            Err(e) => assert_eq!(
5139                "The service(s) is already running: [\"safenode1\"]",
5140                e.to_string()
5141            ),
5142        }
5143
5144        Ok(())
5145    }
5146
5147    #[tokio::test]
5148    async fn remove_should_return_an_error_for_a_node_that_was_marked_running_but_was_not_actually_running(
5149    ) -> Result<()> {
5150        let temp_dir = assert_fs::TempDir::new()?;
5151        let log_dir = temp_dir.child("safenode1-logs");
5152        log_dir.create_dir_all()?;
5153        let data_dir = temp_dir.child("safenode1-data");
5154        data_dir.create_dir_all()?;
5155        let safenode_bin = data_dir.child("safenode");
5156        safenode_bin.write_binary(b"fake safenode binary")?;
5157
5158        let mut mock_service_control = MockServiceControl::new();
5159        mock_service_control
5160            .expect_get_process_pid()
5161            .with(eq(PathBuf::from(
5162                "/var/safenode-manager/services/safenode1/safenode",
5163            )))
5164            .times(1)
5165            .returning(|_| {
5166                Err(ServiceError::ServiceProcessNotFound(
5167                    "Could not find process at '/var/safenode-manager/services/safenode1/safenode'"
5168                        .to_string(),
5169                ))
5170            });
5171
5172        let mut service_data = NodeServiceData {
5173            auto_restart: false,
5174            connected_peers: None,
5175            data_dir_path: PathBuf::from("/var/safenode-manager/services/safenode1"),
5176            evm_network: EvmNetwork::Custom(CustomNetwork {
5177                rpc_url_http: "http://localhost:8545".parse()?,
5178                payment_token_address: RewardsAddress::from_str(
5179                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
5180                )?,
5181                data_payments_address: RewardsAddress::from_str(
5182                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
5183                )?,
5184            }),
5185            genesis: false,
5186            home_network: false,
5187            listen_addr: None,
5188            local: false,
5189            log_dir_path: PathBuf::from("/var/log/safenode/safenode1"),
5190            log_format: None,
5191            max_archived_log_files: None,
5192            max_log_files: None,
5193            metrics_port: None,
5194            node_ip: None,
5195            node_port: None,
5196            number: 1,
5197            owner: None,
5198            pid: Some(1000),
5199            peer_id: Some(PeerId::from_str(
5200                "12D3KooWS2tpXGGTmg2AHFiDh57yPQnat49YHnyqoggzXZWpqkCR",
5201            )?),
5202            rewards_address: RewardsAddress::from_str(
5203                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
5204            )?,
5205            reward_balance: Some(AttoTokens::zero()),
5206            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
5207            safenode_path: PathBuf::from("/var/safenode-manager/services/safenode1/safenode"),
5208            service_name: "safenode1".to_string(),
5209            status: ServiceStatus::Running,
5210            upnp: false,
5211            user: Some("safe".to_string()),
5212            user_mode: false,
5213            version: "0.98.1".to_string(),
5214        };
5215        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
5216        let mut service_manager = ServiceManager::new(
5217            service,
5218            Box::new(mock_service_control),
5219            VerbosityLevel::Normal,
5220        );
5221
5222        let result = service_manager.remove(false).await;
5223        match result {
5224            Ok(_) => panic!("This test should result in an error"),
5225            Err(e) => assert_eq!(
5226                "The service status is not as expected. Expected: Running",
5227                e.to_string()
5228            ),
5229        }
5230
5231        Ok(())
5232    }
5233
5234    #[tokio::test]
5235    async fn remove_should_remove_an_added_node_and_keep_directories() -> Result<()> {
5236        let temp_dir = assert_fs::TempDir::new()?;
5237        let log_dir = temp_dir.child("safenode1-logs");
5238        log_dir.create_dir_all()?;
5239        let data_dir = temp_dir.child("safenode1-data");
5240        data_dir.create_dir_all()?;
5241        let safenode_bin = data_dir.child("safenode");
5242        safenode_bin.write_binary(b"fake safenode binary")?;
5243
5244        let mut mock_service_control = MockServiceControl::new();
5245        mock_service_control
5246            .expect_uninstall()
5247            .with(eq("safenode1"), eq(false))
5248            .times(1)
5249            .returning(|_, _| Ok(()));
5250
5251        let mut service_data = NodeServiceData {
5252            auto_restart: false,
5253            connected_peers: None,
5254            data_dir_path: data_dir.to_path_buf(),
5255            evm_network: EvmNetwork::Custom(CustomNetwork {
5256                rpc_url_http: "http://localhost:8545".parse()?,
5257                payment_token_address: RewardsAddress::from_str(
5258                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
5259                )?,
5260                data_payments_address: RewardsAddress::from_str(
5261                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
5262                )?,
5263            }),
5264            genesis: false,
5265            home_network: false,
5266            listen_addr: None,
5267            local: false,
5268            log_dir_path: log_dir.to_path_buf(),
5269            log_format: None,
5270            max_archived_log_files: None,
5271            max_log_files: None,
5272            metrics_port: None,
5273            node_ip: None,
5274            node_port: None,
5275            number: 1,
5276            owner: None,
5277            pid: None,
5278            peer_id: None,
5279            rewards_address: RewardsAddress::from_str(
5280                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
5281            )?,
5282            reward_balance: Some(AttoTokens::zero()),
5283            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
5284            safenode_path: safenode_bin.to_path_buf(),
5285            service_name: "safenode1".to_string(),
5286            status: ServiceStatus::Stopped,
5287            upnp: false,
5288            user: Some("safe".to_string()),
5289            user_mode: false,
5290            version: "0.98.1".to_string(),
5291        };
5292        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
5293        let mut service_manager = ServiceManager::new(
5294            service,
5295            Box::new(mock_service_control),
5296            VerbosityLevel::Normal,
5297        );
5298
5299        service_manager.remove(true).await?;
5300
5301        assert_matches!(
5302            service_manager.service.service_data.status,
5303            ServiceStatus::Removed
5304        );
5305        log_dir.assert(predicate::path::is_dir());
5306        data_dir.assert(predicate::path::is_dir());
5307
5308        Ok(())
5309    }
5310
5311    #[tokio::test]
5312    async fn remove_should_remove_a_user_mode_service() -> Result<()> {
5313        let temp_dir = assert_fs::TempDir::new()?;
5314        let log_dir = temp_dir.child("safenode1-logs");
5315        log_dir.create_dir_all()?;
5316        let data_dir = temp_dir.child("safenode1-data");
5317        data_dir.create_dir_all()?;
5318        let safenode_bin = data_dir.child("safenode");
5319        safenode_bin.write_binary(b"fake safenode binary")?;
5320
5321        let mut mock_service_control = MockServiceControl::new();
5322        mock_service_control
5323            .expect_uninstall()
5324            .with(eq("safenode1"), eq(true))
5325            .times(1)
5326            .returning(|_, _| Ok(()));
5327
5328        let mut service_data = NodeServiceData {
5329            auto_restart: false,
5330            connected_peers: None,
5331            data_dir_path: data_dir.to_path_buf(),
5332            evm_network: EvmNetwork::Custom(CustomNetwork {
5333                rpc_url_http: "http://localhost:8545".parse()?,
5334                payment_token_address: RewardsAddress::from_str(
5335                    "0x5FbDB2315678afecb367f032d93F642f64180aa3",
5336                )?,
5337                data_payments_address: RewardsAddress::from_str(
5338                    "0x8464135c8F25Da09e49BC8782676a84730C318bC",
5339                )?,
5340            }),
5341            genesis: false,
5342            home_network: false,
5343            listen_addr: None,
5344            local: false,
5345            log_dir_path: log_dir.to_path_buf(),
5346            log_format: None,
5347            max_archived_log_files: None,
5348            max_log_files: None,
5349            metrics_port: None,
5350            node_ip: None,
5351            node_port: None,
5352            number: 1,
5353            owner: None,
5354            pid: None,
5355            peer_id: None,
5356            rewards_address: RewardsAddress::from_str(
5357                "0x03B770D9cD32077cC0bF330c13C114a87643B124",
5358            )?,
5359            reward_balance: Some(AttoTokens::zero()),
5360            rpc_socket_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8081),
5361            safenode_path: safenode_bin.to_path_buf(),
5362            status: ServiceStatus::Stopped,
5363            service_name: "safenode1".to_string(),
5364            upnp: false,
5365            user: None,
5366            user_mode: true,
5367            version: "0.98.1".to_string(),
5368        };
5369        let service = NodeService::new(&mut service_data, Box::new(MockRpcClient::new()));
5370        let mut service_manager = ServiceManager::new(
5371            service,
5372            Box::new(mock_service_control),
5373            VerbosityLevel::Normal,
5374        );
5375
5376        service_manager.remove(false).await?;
5377
5378        assert_matches!(
5379            service_manager.service.service_data.status,
5380            ServiceStatus::Removed
5381        );
5382        log_dir.assert(predicate::path::missing());
5383        data_dir.assert(predicate::path::missing());
5384
5385        Ok(())
5386    }
5387}