use std::{
fs::File,
io::Write,
panic,
process,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};
use clap::Parser;
use log::*;
use minotari_app_utilities::{consts, identity_management::setup_node_identity, utilities::setup_runtime};
use minotari_node::{ApplicationConfig, cli::Cli, run_base_node_with_cli};
use tari_common::{exit_codes::ExitError, initialize_logging, load_configuration, print_env_vars};
use tari_comms::peer_manager::PeerFeatures;
#[cfg(all(unix, feature = "libtor"))]
use tari_libtor::tor::Tor;
use tari_shutdown::Shutdown;
#[cfg(feature = "dhat-heap")]
#[global_allocator]
static ALLOC: dhat::Alloc = dhat::Alloc;
const LOG_TARGET: &str = "minotari::base_node::app";
fn format_system_time(time: SystemTime) -> String {
let datetime = time.duration_since(UNIX_EPOCH).unwrap();
let seconds = datetime.as_secs();
let nanos = datetime.subsec_nanos();
let naive = chrono::DateTime::from_timestamp(seconds.try_into().unwrap(), nanos).unwrap();
naive.format("%Y-%m-%d %H:%M:%S").to_string()
}
fn main() {
#[cfg(feature = "dhat-heap")]
let dhat_profiler = dhat::Profiler::new_heap();
#[cfg(feature = "dhat-heap")]
println!("\n\nDHAT: Profiling enabled. Run `dhat-heap` to view the results.\n\n");
panic::set_hook(Box::new(|panic_info| {
let location = panic_info
.location()
.map(|loc| {
format!(
"{} file: '{}', line: {}",
format_system_time(SystemTime::now()),
loc.file(),
loc.line()
)
})
.unwrap_or_else(|| "unknown location".to_string());
let message = if let Some(s) = panic_info.payload().downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = panic_info.payload().downcast_ref::<String>() {
s.clone()
} else {
"Unknown panic message".to_string()
};
error!(target: "minotari::base_node", "Panic occurred at {location}: {message}");
let mut file = File::create("minotari-node-panic.log").unwrap();
file.write_all(format!("Panic at {location}: {message}").as_bytes())
.unwrap();
eprintln!("Panic occurred at {location}: {message}");
std::process::exit(500);
}));
if let Err(err) = main_inner() {
eprintln!("{err:?}");
let exit_code = err.exit_code;
if let Some(hint) = exit_code.hint() {
eprintln!("{hint}");
eprintln!();
}
error!(
target: LOG_TARGET,
"Exiting with code ({}): {:?}", exit_code as i32, err
);
#[cfg(feature = "dhat-heap")]
drop(dhat_profiler);
process::exit(exit_code as i32);
}
#[cfg(feature = "dhat-heap")]
{
println!("\nCtrl-C pressed, exiting...\n");
drop(dhat_profiler);
}
}
fn main_inner() -> Result<(), ExitError> {
let cli = Cli::parse();
if cli.print_env {
print_env_vars(&cli.common.config_property_overrides);
return Ok(());
}
let base_path = cli.common.get_base_path();
initialize_logging(
&cli.common.log_config_path("base_node"),
cli.common.log_path.as_ref().unwrap_or(&base_path),
include_str!("../log4rs_sample.yml"),
)?;
info!(
target: LOG_TARGET,
"Starting Minotari Base Node version: {}",
consts::APP_VERSION
);
let config_path = cli.common.config_path();
let cfg = load_configuration(config_path, true, cli.non_interactive_mode, &cli, cli.common.network)?;
if cli.profile_with_tokio_console {
console_subscriber::init();
}
#[cfg(all(unix, feature = "libtor"))]
let mut config = ApplicationConfig::load_from(&cfg).inspect_err(|e| {
if e.is_unknown_field_error() {
eprintln!(
"⚠️ Configuration error: an environment variable or -p override references an unrecognized config \
field.\n Run with --print-env to inspect active environment variables and -p overrides."
);
}
})?;
#[cfg(not(all(unix, feature = "libtor")))]
let config = ApplicationConfig::load_from(&cfg).map_err(|e| {
if e.is_unknown_field_error() {
eprintln!(
"⚠️ Configuration error: an environment variable or -p override references an unrecognized config \
field.\n Run with --print-env to inspect active environment variables and -p overrides."
);
}
e
})?;
debug!(target: LOG_TARGET, "Using base node configuration: {config:?}");
let node_identity = setup_node_identity(
&config.base_node.identity_file,
config.base_node.p2p.public_addresses.clone().into_vec(),
cli.non_interactive_mode || cli.init,
PeerFeatures::COMMUNICATION_NODE,
config.base_node.p2p.transport.transport_type,
)?;
if cli.init {
info!(target: LOG_TARGET, "Default configuration created. Done.");
return Ok(());
}
let shutdown = Shutdown::new();
let runtime = setup_runtime()?;
#[cfg(all(unix, feature = "libtor"))]
if config.base_node.use_libtor && config.base_node.p2p.transport.is_tor() {
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!(
target: LOG_TARGET,
"Updated Tor comms transport: {:?}", config.base_node.p2p.transport
);
}
runtime.block_on(run_base_node_with_cli(node_identity, Arc::new(config), cli, shutdown))?;
Ok(())
}