From eeee4416eb496ca351a4608642bee9c22529bc04 Mon Sep 17 00:00:00 2001 From: Hansie Odendaal <39146854+hansieodendaal@users.noreply.github.com> Date: Wed, 5 Feb 2025 16:23:47 +0200 Subject: [PATCH] feat: add libtor exiting logic (#6782) Description --- The base node and console wallet have various immediate exit paths through 'process::exit', as well as controlled exit via user/shutdown command. - This implementation ~~uses a unique but predictable handshake file containing the libtor temporary directory for each base node or console wallet running instance to cover all exit paths. When the main thread exits, via user command or 'process::exit', the libtor temporary files and the handshake file are removed.~~ does away with the temporary libtor data folder and log file and instead uses a fixed or user-controllable location via the `--libtor-data-dir` command line option. - ~~Added libtor exiting logic to:~~ - ~~log any shutdown errors;~~ - ~~remove temporary files.~~ - Added a failsafe timeout for test-liveness. Closes #6739 and #6741 Motivation and Context --- See #6739 and #6741 How Has This Been Tested? --- System-level testing: - Base node (Ubuntu), executing test liveness from the command line, with and without using libtor ``` ./minotari_node --base-path $(realpath .) --log-config $(realpath ./config)/log4rs_node.yml --network esmeralda --disable-splash-screen -p esmeralda.p2p.seeds.dns_seeds= -p esmeralda.p2p.seeds.peer_seeds= --watch "test-peer-liveness f6186e6f15af070d01d5aec8c5a53d81088c34b777cd0b25292d1cd522532462 /onion3/5uqpxi6ando6jrtcpj3aok4z7ab2nm36knfv2pzdf2hefdpkdcjnazid:18141 true true false" --non-interactive-mode -p base_node.use_libtor=true ``` - Base node (Ubuntu), with console, using libtor - Console wallet (Ubuntu), with console, using libtor - Console wallet (Ubuntu), recovery into console, using libtor What process can a PR reviewer use to test or verify this change? --- System-level testing Code review Breaking Changes --- - [x] None - [ ] Requires data directory on base node to be deleted - [ ] Requires hard fork - [ ] Other - Please specify --- .gitignore | 1 + Cargo.lock | 1 - .../minotari_console_wallet/src/cli.rs | 3 ++ .../minotari_console_wallet/src/lib.rs | 10 ++++- applications/minotari_node/src/cli.rs | 5 +++ .../commands/command/test_peer_liveness.rs | 16 ++++++- applications/minotari_node/src/lib.rs | 2 +- applications/minotari_node/src/main.rs | 7 ++- infrastructure/libtor/Cargo.toml | 1 - infrastructure/libtor/src/tor.rs | 45 ++++++++++++------- integration_tests/src/wallet_process.rs | 1 + 11 files changed, 69 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 08666063a4..241c3a428f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ target .vscode/ *.code-workspace tags +null # Ignore OS files .DS_Store diff --git a/Cargo.lock b/Cargo.lock index 50104ccfaa..1268665064 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7292,7 +7292,6 @@ dependencies = [ "rand", "tari_common", "tari_p2p", - "tempfile", "tor-hash-passwd", ] diff --git a/applications/minotari_console_wallet/src/cli.rs b/applications/minotari_console_wallet/src/cli.rs index fd81288c88..45be632dcf 100644 --- a/applications/minotari_console_wallet/src/cli.rs +++ b/applications/minotari_console_wallet/src/cli.rs @@ -94,6 +94,9 @@ pub struct Cli { pub view_private_key: Option, #[clap(long)] pub spend_key: Option, + /// Path to the libtor data directory + #[clap(short, long, parse(from_os_str))] + pub libtor_data_dir: Option, } impl ConfigOverrideProvider for Cli { diff --git a/applications/minotari_console_wallet/src/lib.rs b/applications/minotari_console_wallet/src/lib.rs index ce4aa9722e..4eaca951cf 100644 --- a/applications/minotari_console_wallet/src/lib.rs +++ b/applications/minotari_console_wallet/src/lib.rs @@ -33,7 +33,6 @@ mod recovery; mod ui; mod utils; mod wallet_modes; - pub use cli::{ BurnMinotariArgs, Cli, @@ -108,6 +107,7 @@ pub fn run_wallet(shutdown: &mut Shutdown, runtime: Runtime, config: &mut Applic profile_with_tokio_console: false, view_private_key: None, spend_key: None, + libtor_data_dir: None, }; run_wallet_with_cli(shutdown, runtime, config, cli) @@ -165,7 +165,12 @@ pub fn run_wallet_with_cli( // This is currently only possible on linux/macos #[cfg(all(unix, feature = "libtor"))] if config.wallet.use_libtor && config.wallet.p2p.transport.is_tor() { - let tor = Tor::initialize()?; + let data_dir = if let Some(dir) = cli.libtor_data_dir.clone() { + dir.join("libtor").join("wallet") + } else { + cli.common.get_base_path().join("libtor").join("wallet") + }; + let tor = Tor::initialize(data_dir)?; tor.update_comms_transport(&mut config.wallet.p2p.transport)?; tor.run_background(); debug!( @@ -265,6 +270,7 @@ pub fn run_wallet_with_cli( print!("\nShutting down wallet... "); shutdown.trigger(); runtime.block_on(wallet.wait_until_shutdown()); + println!("Done."); result diff --git a/applications/minotari_node/src/cli.rs b/applications/minotari_node/src/cli.rs index 091f621f86..3c805948d6 100644 --- a/applications/minotari_node/src/cli.rs +++ b/applications/minotari_node/src/cli.rs @@ -20,6 +20,8 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::path::PathBuf; + use clap::Parser; use minotari_app_utilities::common_cli_args::CommonCliArgs; use tari_common::configuration::{ConfigOverrideProvider, Network}; @@ -58,6 +60,9 @@ pub struct Cli { /// Disable the splash screen #[clap(long)] pub disable_splash_screen: bool, + /// Path to the libtor data directory + #[clap(short, long, parse(from_os_str))] + pub libtor_data_dir: Option, } impl ConfigOverrideProvider for Cli { diff --git a/applications/minotari_node/src/commands/command/test_peer_liveness.rs b/applications/minotari_node/src/commands/command/test_peer_liveness.rs index bda5c3a30a..b933cf8916 100644 --- a/applications/minotari_node/src/commands/command/test_peer_liveness.rs +++ b/applications/minotari_node/src/commands/command/test_peer_liveness.rs @@ -119,6 +119,7 @@ impl HandleCommand for CommandContext { } // Wait for the liveness test to complete + let mut count = 0; loop { tokio::select! { _ = rx.changed() => { @@ -153,7 +154,20 @@ impl HandleCommand for CommandContext { break; }, - _ = tokio::time::sleep(Duration::from_secs(1)) => {}, + _ = tokio::time::sleep(Duration::from_secs(1)) => { + count += 1; + if count >= 180 { + if let Some(true) = args.exit { + println!(" >> The liveness test failed to complete and base node will now exit\n"); + self.shutdown.trigger(); + tokio::time::sleep(Duration::from_secs(1)).await; + process::exit(1) + } else { + println!(" >> The liveness test failed to complete\n"); + break; + } + } + }, } } diff --git a/applications/minotari_node/src/lib.rs b/applications/minotari_node/src/lib.rs index 726ae11a57..9b2d11f844 100644 --- a/applications/minotari_node/src/lib.rs +++ b/applications/minotari_node/src/lib.rs @@ -92,6 +92,7 @@ pub async fn run_base_node( mining_enabled: false, second_layer_grpc_enabled: false, disable_splash_screen: false, + libtor_data_dir: None, }; run_base_node_with_cli(node_identity, config, cli, shutdown).await @@ -169,7 +170,6 @@ pub async fn run_base_node_with_cli( info!(target: LOG_TARGET, "Minotari base node has STARTED"); main_loop.cli_loop(cli.disable_splash_screen).await; - ctx.wait_for_shutdown().await; println!("Goodbye!"); diff --git a/applications/minotari_node/src/main.rs b/applications/minotari_node/src/main.rs index 3f76c71abd..272ad865bc 100644 --- a/applications/minotari_node/src/main.rs +++ b/applications/minotari_node/src/main.rs @@ -152,7 +152,12 @@ fn main_inner() -> Result<(), ExitError> { // This is currently only possible on linux/macos #[cfg(all(unix, feature = "libtor"))] if config.base_node.use_libtor && config.base_node.p2p.transport.is_tor() { - let tor = Tor::initialize()?; + let data_dir = if let Some(dir) = cli.libtor_data_dir.clone() { + dir.join("libtor").join("base_node") + } else { + cli.common.get_base_path().join("libtor").join("base_node") + }; + let tor = Tor::initialize(data_dir)?; tor.update_comms_transport(&mut config.base_node.p2p.transport)?; tor.run_background(); debug!( diff --git a/infrastructure/libtor/Cargo.toml b/infrastructure/libtor/Cargo.toml index 8c9fe89781..795dcdb78f 100644 --- a/infrastructure/libtor/Cargo.toml +++ b/infrastructure/libtor/Cargo.toml @@ -11,7 +11,6 @@ tari_p2p = { path = "../../base_layer/p2p" } derivative = "2.2.0" log = "0.4.8" rand = "0.8" -tempfile = "3.1.0" tor-hash-passwd = "1.0.1" [target.'cfg(unix)'.dependencies] diff --git a/infrastructure/libtor/src/tor.rs b/infrastructure/libtor/src/tor.rs index 218e6f1928..f427cd33fe 100644 --- a/infrastructure/libtor/src/tor.rs +++ b/infrastructure/libtor/src/tor.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{fmt, io, net::TcpListener, path::PathBuf, thread}; +use std::{fmt, fs, io, net::TcpListener, path::PathBuf, thread}; use derivative::Derivative; use libtor::{LogDestination, LogLevel, TorFlag}; @@ -28,7 +28,6 @@ use log::*; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use tari_common::exit_codes::{ExitCode, ExitError}; use tari_p2p::{TorControlAuthentication, TransportConfig, TransportType}; -use tempfile::{tempdir, NamedTempFile, TempDir, TempPath}; use tor_hash_passwd::EncryptedKey; const LOG_TARGET: &str = "tari_libtor"; @@ -46,13 +45,11 @@ impl fmt::Debug for TorPassword { pub struct Tor { control_port: u16, data_dir: PathBuf, - log_destination: String, + log_destination: PathBuf, log_level: LogLevel, #[derivative(Debug = "ignore")] passphrase: TorPassword, socks_port: u16, - temp_dir: Option, - temp_file: Option, } impl Default for Tor { @@ -64,8 +61,6 @@ impl Default for Tor { log_level: LogLevel::Err, passphrase: TorPassword(None), socks_port: 0, - temp_dir: None, - temp_file: None, } } } @@ -76,7 +71,7 @@ impl Tor { /// Two TCP ports will be provided by the operating system. /// These ports are used for the control and socks ports, the onion address and port info are still loaded from the /// node identity file. - pub fn initialize() -> Result { + pub fn initialize(data_dir: PathBuf) -> Result { debug!(target: LOG_TARGET, "Initializing libtor"); let mut instance = Tor::default(); @@ -95,15 +90,33 @@ impl Tor { instance.passphrase = TorPassword(Some(passphrase)); // data dir - let temp = tempdir()?; - instance.data_dir = temp.path().to_path_buf(); - instance.temp_dir = Some(temp); + instance.data_dir = data_dir.join("data"); + + let exists = fs::exists(instance.data_dir.clone()).map_err(|e| { + ExitError::new( + ExitCode::InputError, + format!( + "Could not verify libtor data directory: {} ({})", + instance.data_dir.display(), + e + ), + ) + })?; + if !exists { + fs::create_dir_all(&instance.data_dir).map_err(|e| { + ExitError::new( + ExitCode::InputError, + format!( + "Could not create libtor data directory: {} ({})", + instance.data_dir.display(), + e + ), + ) + })?; + } // log destination - let temp = NamedTempFile::new()?.into_temp_path(); - let file = temp.to_string_lossy().to_string(); - instance.temp_file = Some(temp); - instance.log_destination = file; + instance.log_destination = data_dir.join("tor.log"); debug!(target: LOG_TARGET, "tor instance: {:?}", instance); Ok(instance) @@ -152,7 +165,7 @@ impl Tor { // Write the final control port to a file. This could be used to configure the node to use this port when auto is set. .flag(TorFlag::ControlPortWriteToFile(data_dir.join("control_port").to_string_lossy().to_string())) .flag(TorFlag::Hush()) - .flag(TorFlag::LogTo(log_level, LogDestination::File(log_destination))); + .flag(TorFlag::LogTo(log_level, LogDestination::File(log_destination.to_string_lossy().to_string()))); if socks_port == 0 { tor.flag(TorFlag::SocksPortAuto); diff --git a/integration_tests/src/wallet_process.rs b/integration_tests/src/wallet_process.rs index 2e3fcea656..464508a9f3 100644 --- a/integration_tests/src/wallet_process.rs +++ b/integration_tests/src/wallet_process.rs @@ -235,6 +235,7 @@ pub fn get_default_cli() -> Cli { profile_with_tokio_console: false, view_private_key: None, spend_key: None, + libtor_data_dir: None, } }