Skip to content

Commit

Permalink
feat: add libtor exiting logic (#6782)
Browse files Browse the repository at this point in the history
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

<!-- Checklist -->
<!-- 1. Is the title of your PR in the form that would make nice release
notes? The title, excluding the conventional commit
tag, will be included exactly as is in the CHANGELOG, so please think
about it carefully. -->


Breaking Changes
---

- [x] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [ ] Other - Please specify

<!-- Does this include a breaking change? If so, include this line as a
footer -->
<!-- BREAKING CHANGE: Description what the user should do, e.g. delete a
database, resync the chain -->
  • Loading branch information
hansieodendaal authored Feb 5, 2025
1 parent 2698a58 commit eeee441
Show file tree
Hide file tree
Showing 11 changed files with 69 additions and 23 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ target
.vscode/
*.code-workspace
tags
null

# Ignore OS files
.DS_Store
Expand Down
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions applications/minotari_console_wallet/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ pub struct Cli {
pub view_private_key: Option<String>,
#[clap(long)]
pub spend_key: Option<String>,
/// Path to the libtor data directory
#[clap(short, long, parse(from_os_str))]
pub libtor_data_dir: Option<PathBuf>,
}

impl ConfigOverrideProvider for Cli {
Expand Down
10 changes: 8 additions & 2 deletions applications/minotari_console_wallet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ mod recovery;
mod ui;
mod utils;
mod wallet_modes;

pub use cli::{
BurnMinotariArgs,
Cli,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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!(
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions applications/minotari_node/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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<PathBuf>,
}

impl ConfigOverrideProvider for Cli {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ impl HandleCommand<ArgsTestPeerLiveness> for CommandContext {
}

// Wait for the liveness test to complete
let mut count = 0;
loop {
tokio::select! {
_ = rx.changed() => {
Expand Down Expand Up @@ -153,7 +154,20 @@ impl HandleCommand<ArgsTestPeerLiveness> 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;
}
}
},
}
}

Expand Down
2 changes: 1 addition & 1 deletion applications/minotari_node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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!");
Expand Down
7 changes: 6 additions & 1 deletion applications/minotari_node/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
1 change: 0 additions & 1 deletion infrastructure/libtor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
45 changes: 29 additions & 16 deletions infrastructure/libtor/src/tor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@
// 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};
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";
Expand All @@ -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<TempDir>,
temp_file: Option<TempPath>,
}

impl Default for Tor {
Expand All @@ -64,8 +61,6 @@ impl Default for Tor {
log_level: LogLevel::Err,
passphrase: TorPassword(None),
socks_port: 0,
temp_dir: None,
temp_file: None,
}
}
}
Expand All @@ -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<Tor, ExitError> {
pub fn initialize(data_dir: PathBuf) -> Result<Tor, ExitError> {
debug!(target: LOG_TARGET, "Initializing libtor");
let mut instance = Tor::default();

Expand All @@ -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)
Expand Down Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions integration_tests/src/wallet_process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down

0 comments on commit eeee441

Please sign in to comment.