minotari_node 5.4.0-pre.0

The tari full base node implementation
// Copyright 2019. The Tari Project
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
// following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
// disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
// following disclaimer in the documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
// products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// 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.

/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣶⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣾⣿⡿⠋⠀⠀⠀⠀⠉⠛⠿⣿⣿⣶⣤⣀⠀⠀⠀⠀⠀⠀⢰⣿⣾⣾⣾⣾⣾⣾⣾⣾⣾⣿⠀⠀⠀⣾⣾⣾⡀⠀⠀⠀⠀⢰⣾⣾⣾⣾⣿⣶⣶⡀⠀⠀⠀⢸⣾⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣶⣶⣤⣄⡀⠀⠀⠀⠀⠀⠉⠛⣿⣿⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⣿⣿⡏⠉⠉⠉⠉⠀⠀⣰⣿⣿⣿⣿⠀⠀⠀⠀⢸⣿⣿⠉⠉⠉⠛⣿⣿⡆⠀⠀⢸⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⠀⠀⠀⠈⠙⣿⡿⠿⣿⣿⣿⣶⣶⣤⣤⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⢠⣿⣿⠃⣿⣿⣷⠀⠀⠀⢸⣿⣿⣀⣀⣀⣴⣿⣿⠃⠀⠀⢸⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣤⠀⠀⠀⢸⣿⡟⠀⠀⠀⠀⠀⠉⣽⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⠀⣿⣿⣿⣤⣬⣿⣿⣆⠀⠀⢸⣿⣿⣿⣿⣿⡿⠟⠉⠀⠀⠀⢸⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣿⣤⠀⢸⣿⡟⠀⠀⠀⣠⣾⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇⠀⠀⠀⠀⣾⣿⣿⠿⠿⠿⢿⣿⣿⡀⠀⢸⣿⣿⠙⣿⣿⣿⣄⠀⠀⠀⠀⢸⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣿⣼⣿⡟⣀⣶⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⡇⠀⠀⠀⣰⣿⣿⠃⠀⠀⠀⠀⣿⣿⣿⠀⢸⣿⣿⠀⠀⠙⣿⣿⣷⣄⠀⠀⢸⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣿⣿⣿⠛⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
/// ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀
///
/// # Minotari Base Node
///
/// The Minotari Base Node is a major application in the Tari Network
///
/// ## Running the Minotari Base Node
///
/// Tor needs to be started first
/// ```
/// tor --allow-missing-torrc --ignore-missing-torrc \
///  --clientonly 1 --socksport 9050 --controlport 127.0.0.1:9051 \
///  --log "warn stdout" --clientuseipv6 1
/// ```
///
/// For the first run
/// `cargo run minotari_node -- --init
///
/// Subsequent runs
/// `cargo run minotari_node`
///
/// ## Commands
///
/// `help` - Displays a list of commands
/// `get-balance` - Displays the balance of the wallet (available, pending incoming, pending outgoing)
/// `send-minotari` - Sends Minotari, the amount needs to be specified, followed by the destination (public key or
/// emoji id) and an optional message `get-chain-metadata` - Lists information about the blockchain of this Base
/// Node `list-peers` - Lists information about peers known by this base node
/// `ban-peer` - Bans a peer
/// `unban-peer` - Removes a ban for a peer
/// `list-connections` - Lists active connections to this Base Node
/// `list-headers` - Lists header information. Either the first header height and the last header height needs to
/// be specified, or the amount of headers from the top `check-db` - Checks the blockchain database for missing
/// blocks and headers `calc-timing` - Calculates the time average time taken to mine a given range of blocks
/// `discover-peer` - Attempts to discover a peer on the network, a public key or emoji id needs to be specified
/// `get-block` - Retrieves a block, the height of the block needs to be specified
/// `get-mempool-stats` - Displays information about the mempool
/// `get-mempool-state` - Displays state information for the mempool
/// `whoami` - Displays identity information about this Base Node and it's wallet
/// `quit` - Exits the Base Node
/// `exit` - Same as quit
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()
}

/// Application entry point
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}");

        // Optionally, write a custom message directly to the file
        let mut file = File::create("minotari-node-panic.log").unwrap();
        file.write_all(format!("Panic at {location}: {message}").as_bytes())
            .unwrap();
        // if cfg!(debug_assertions) {
        // In debug mode, we want to see the panic message
        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();

    // Handle --print-env before doing anything else so it works even without a valid config
    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:?}");

    // Load or create the Node identity
    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(());
    }

    // The shutdown trigger for the system
    let shutdown = Shutdown::new();

    // Set up the Tokio runtime
    let runtime = setup_runtime()?;

    // Run our own Tor instance, if configured
    // 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 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
        );
    }

    // Run the base node
    runtime.block_on(run_base_node_with_cli(node_identity, Arc::new(config), cli, shutdown))?;

    Ok(())
}