2026-05-24 17:56:57 +00:00
|
|
|
use crate::common::network_paths_and_settings::block_extension_and_paths;
|
|
|
|
|
use crate::common::network_startup::get_listen_socket;
|
2026-05-26 06:24:57 +00:00
|
|
|
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};
|
2026-05-24 17:56:57 +00:00
|
|
|
use crate::miner::genesis::create_genesis_transaction;
|
|
|
|
|
use crate::miner::mining::mine_block;
|
2026-05-26 06:24:57 +00:00
|
|
|
use crate::panic;
|
2026-06-13 19:51:54 +00:00
|
|
|
use crate::records::memory::chain_state::rebuild_chain_state_cache;
|
2026-05-24 17:56:57 +00:00
|
|
|
use crate::records::memory::mempool::{init_db, setup_mempool};
|
2026-06-11 08:03:09 +00:00
|
|
|
use crate::records::memory::network_mapping::NodeInfo;
|
2026-05-26 06:24:57 +00:00
|
|
|
use crate::records::memory::response_channels::Command;
|
2026-06-18 17:38:52 +00:00
|
|
|
use crate::records::record_chain::rewards_tx::rebuild_immature_reward_window;
|
2026-05-24 17:56:57 +00:00
|
|
|
use crate::rpc::server::start_rpc::start_rpc;
|
2026-05-26 06:24:57 +00:00
|
|
|
use crate::sled::Db;
|
2026-07-01 15:18:12 +00:00
|
|
|
use crate::startup::connections::{handle_connections, spawn_isolated_bootstrap_recovery};
|
2026-05-24 17:56:57 +00:00
|
|
|
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;
|
2026-06-01 14:29:11 +00:00
|
|
|
use crate::wallets::structures::Wallet;
|
2026-05-24 17:56:57 +00:00
|
|
|
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<LoggerHandle, Box<dyn Error>> {
|
|
|
|
|
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)
|
2026-05-26 06:24:57 +00:00
|
|
|
.log_to_file(FileSpec::default().directory(&log_path).basename("node"))
|
2026-05-24 17:56:57 +00:00
|
|
|
.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(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 14:29:11 +00:00
|
|
|
pub async fn run_unlocked_node(wallet: Arc<Wallet>, install_shutdown: bool) -> Result<(), String> {
|
2026-05-24 17:56:57 +00:00
|
|
|
// 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;
|
2026-06-13 19:51:54 +00:00
|
|
|
if let Err(err) = rebuild_chain_state_cache(&db).await {
|
|
|
|
|
error!("[chain_state] failed to rebuild startup chain state cache: {err}");
|
|
|
|
|
}
|
2026-06-18 17:38:52 +00:00
|
|
|
if let Err(err) = rebuild_immature_reward_window(&db).await {
|
|
|
|
|
error!("[rewards] failed to rebuild immature reward window: {err}");
|
|
|
|
|
}
|
2026-05-24 17:56:57 +00:00
|
|
|
|
|
|
|
|
if install_shutdown {
|
|
|
|
|
// Console/daemon mode owns signal cleanup; Windows service shutdown is handled separately.
|
2026-06-23 15:17:12 +00:00
|
|
|
#[cfg(unix)]
|
2026-05-24 17:56:57 +00:00
|
|
|
install_shutdown_cleanup(db.clone());
|
2026-06-23 15:17:12 +00:00
|
|
|
#[cfg(not(unix))]
|
|
|
|
|
install_shutdown_cleanup();
|
2026-05-24 17:56:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Err(e) = clear_ip_scores(&db) {
|
|
|
|
|
eprintln!("Failed to clear IP scores: {e}");
|
|
|
|
|
error!("Failed to clear IP scores: {e}");
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-11 08:03:09 +00:00
|
|
|
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}"),
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-01 14:29:11 +00:00
|
|
|
let wallet_for_server = wallet.clone();
|
2026-05-24 17:56:57 +00:00
|
|
|
let map: Arc<Mutex<Command>> = 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 {
|
2026-06-01 14:29:11 +00:00
|
|
|
start_rpc(&db_server, server_address, wallet_for_server, map_cloned).await;
|
2026-05-24 17:56:57 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Connection management, genesis creation, and mining then proceed in the same
|
|
|
|
|
// unlocked runtime regardless of how the process was launched.
|
2026-06-01 14:29:11 +00:00
|
|
|
handle_connections(db.clone(), wallet.clone(), map.clone())
|
2026-05-24 17:56:57 +00:00
|
|
|
.await
|
|
|
|
|
.map_err(|e| format!("Startup connection error: {e}"))?;
|
2026-07-01 15:18:12 +00:00
|
|
|
spawn_isolated_bootstrap_recovery(db.clone(), wallet.clone(), map.clone());
|
2026-05-24 17:56:57 +00:00
|
|
|
|
|
|
|
|
create_genesis_transaction(
|
|
|
|
|
&db,
|
|
|
|
|
verification_service.clone(),
|
2026-06-01 14:29:11 +00:00
|
|
|
wallet.clone(),
|
2026-05-24 17:56:57 +00:00
|
|
|
map.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
2026-06-01 14:29:11 +00:00
|
|
|
mine_block(&db, verification_service, wallet.clone(), map.clone())
|
2026-05-24 17:56:57 +00:00
|
|
|
.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(())
|
|
|
|
|
}
|