Contractless/src/startup/node_runtime.rs

186 lines
6.8 KiB
Rust

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, spawn_isolated_bootstrap_recovery};
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<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)
.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<Wallet>, 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<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 {
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}"))?;
spawn_isolated_bootstrap_recovery(db.clone(), wallet.clone(), map.clone());
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(())
}