use crate::common::network_paths_and_settings::block_extension_and_paths; use crate::common::network_startup::get_listen_socket; use crate::create_dir_all; use crate::exit; use crate::flexi_logger::{ Cleanup, Criterion, DeferredNow, FileSpec, Logger, LoggerHandle, Naming, Record, WriteMode, }; use crate::log::{error, info}; use crate::miner::genesis::create_genesis_transaction; use crate::miner::mining::mine_block; use crate::panic; use crate::records::memory::chain_state::rebuild_chain_state_cache; use crate::records::memory::mempool::{init_db, setup_mempool}; use crate::records::memory::network_mapping::NodeInfo; use crate::records::memory::response_channels::Command; use crate::records::record_chain::rewards_tx::rebuild_immature_reward_window; use crate::rpc::server::start_rpc::start_rpc; use crate::sled::Db; use crate::startup::connections::handle_connections; use crate::startup::daemonize::{install_shutdown_cleanup, remove_registered_pid_file}; use crate::startup::initialize_startup::open_chain_state; use crate::verifications::verification_service::initialize_global_verification_service; use crate::wallets::structures::Wallet; use crate::Arc; use crate::Error; use crate::HashMap; use crate::Mutex; use crate::Settings; use std::io::Write; use std::path::Path; // The runtime logger is shared by the normal console node path and the Windows service path. fn format_log( writer: &mut dyn Write, now: &mut DeferredNow, record: &Record<'_>, ) -> Result<(), std::io::Error> { write!( writer, "[{}] {} [{}] {}", now.now().format("%Y-%m-%d %H:%M:%S"), record.level(), record.target(), record.args() ) } pub async fn initialize_node_logging() -> Result> { let ( _network_name, _padded_base_coin, _suffix, _torrent_path, _wallet_path, _block_path, _db_path, _balance_path, log_path, ) = block_extension_and_paths(); // Create the log folder before flexi_logger tries to open the rotating file. create_dir_all(Path::new(&log_path)).await?; // Runtime logging goes to a rotating node log shared by service and console startup paths. let handle = Logger::try_with_str(&Settings::load()?.log_level)? .format(format_log) .log_to_file(FileSpec::default().directory(&log_path).basename("node")) .rotate( Criterion::Size(10_000_000), Naming::Numbers, Cleanup::KeepLogFiles(10), ) .write_mode(WriteMode::BufferAndFlush) .start()?; Ok(handle) } pub fn install_panic_cleanup() { // Panic cleanup removes the Linux PID file so later restarts do not trip over stale state. panic::set_hook(Box::new(|info| { eprintln!("Application panicked: {info}"); error!("Application panicked: {info}"); remove_registered_pid_file(); exit(1); })); } pub fn clear_ip_scores(db: &Db) -> sled::Result<()> { // IP reputation is runtime-only state, so each process start begins with a clean score table. let tree = db.open_tree("ip_rep_system")?; tree.clear()?; Ok(()) } pub async fn run_unlocked_node(wallet: Arc, install_shutdown: bool) -> Result<(), String> { // Once the wallet is available, the shared node runtime performs the remaining // startup work for both Linux foreground/daemon mode and the Windows service path. info!( "Initializing PostgreSQL mempool connection for host={} port={} dbname={}", Settings::load().map_err(|e| e.to_string())?.pg_host, Settings::load().map_err(|e| e.to_string())?.pg_port, Settings::load().map_err(|e| e.to_string())?.pg_dbname ); init_db().await.map_err(|e| e.to_string())?; info!("Creating or validating PostgreSQL mempool tables."); setup_mempool().await.map_err(|e| e.to_string())?; info!("PostgreSQL mempool tables are ready."); // Open sled after Postgres is ready so block state and mempool state start together. let db = open_chain_state().await; if let Err(err) = rebuild_chain_state_cache(&db).await { error!("[chain_state] failed to rebuild startup chain state cache: {err}"); } if let Err(err) = rebuild_immature_reward_window(&db).await { error!("[rewards] failed to rebuild immature reward window: {err}"); } if install_shutdown { // Console/daemon mode owns signal cleanup; Windows service shutdown is handled separately. #[cfg(unix)] install_shutdown_cleanup(db.clone()); #[cfg(not(unix))] install_shutdown_cleanup(); } if let Err(e) = clear_ip_scores(&db) { eprintln!("Failed to clear IP scores: {e}"); error!("Failed to clear IP scores: {e}"); } match NodeInfo::load_recovery_snapshot().await { Ok(loaded) if loaded > 0 => { info!("[network_map] loaded {loaded} recovered node records with monitors cleared"); if let Err(err) = NodeInfo::rebuild_mined_counts_from_chain(&db).await { error!("[network_map] failed to rebuild recovered mined counts: {err}"); } } Ok(_) => {} Err(err) => error!("[network_map] failed to load recovery snapshot: {err}"), } let wallet_for_server = wallet.clone(); let map: Arc> = Arc::new(Mutex::new(HashMap::new())); let map_cloned = Arc::clone(&map); let db_server = db.clone(); let verification_service = Arc::new(initialize_global_verification_service()); let server_address = get_listen_socket().await; // Mainnet is intentionally blocked until launch. #[cfg(feature = "mainnet")] { compile_error!("The 'mainnet' feature is not allowed to be built."); } // The RPC server starts first so handshake traffic can begin while the rest of the // node initialization continues. tokio::spawn(async move { start_rpc(&db_server, server_address, wallet_for_server, map_cloned).await; }); // Connection management, genesis creation, and mining then proceed in the same // unlocked runtime regardless of how the process was launched. handle_connections(db.clone(), wallet.clone(), map.clone()) .await .map_err(|e| format!("Startup connection error: {e}"))?; create_genesis_transaction( &db, verification_service.clone(), wallet.clone(), map.clone(), ) .await; mine_block(&db, verification_service, wallet.clone(), map.clone()) .await .map_err(|e| format!("Mining loop error: {e}"))?; // If mining exits normally, cleanup any daemon PID file left by startup. remove_registered_pid_file(); Ok(()) }