Contractless/src/startup/node_runtime.rs

182 lines
6.6 KiB
Rust
Raw Normal View History

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-05-24 17:56:57 +00:00
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;
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(())
}
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.
install_shutdown_cleanup(db.clone());
}
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}"),
}
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 {
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.
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}"))?;
create_genesis_transaction(
&db,
verification_service.clone(),
wallet.clone(),
2026-05-24 17:56:57 +00:00
map.clone(),
)
.await;
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(())
}