use std::path::PathBuf; use std::process::ExitCode; use montana_node::commands::{init, inspect, start, status, time}; use montana_node::NodeError; fn main() -> ExitCode { let argv: Vec = std::env::args().collect(); if argv.len() < 2 { eprintln!("{}", help_text()); return ExitCode::from(2); } let result = match argv[1].as_str() { "init" => parse_init(&argv[2..]).and_then(init::run), "inspect" => parse_inspect(&argv[2..]).and_then(inspect::run), "status" => parse_status(&argv[2..]).and_then(status::run), "time" => parse_time(&argv[2..]).and_then(time::run), "start" => parse_start(&argv[2..]).and_then(start::run), "help" | "-h" | "--help" => { println!("{}", help_text()); return ExitCode::SUCCESS; }, other => { eprintln!("неизвестная команда: {other}"); eprintln!(); eprintln!("{}", help_text()); return ExitCode::from(2); }, }; match result { Ok(()) => ExitCode::SUCCESS, Err(e) => { eprintln!("ошибка: {e}"); ExitCode::from(1) }, } } fn help_text() -> String { String::from( "montana-node — узел Montana (singleton либо cross-machine M8 mode)\n\ \n\ Использование:\n\ \n\ montana-node init [--data-dir ] [--mnemonic \"<24 слова>\"]\n\ [--entropy ] [--force]\n\ \n\ montana-node inspect [--data-dir ] [--reveal-master-seed]\n\ \n\ montana-node status [--data-dir ]\n\ \n\ montana-node time [--data-dir ]\n\ \n\ montana-node start [--data-dir ] [--max-windows ]\n\ [--d-test-override ]\n\ [--listen ] [--genesis-manifest ]\n\ \n\ Команды:\n\ \n\ init Создать identity (мнемоника + ключи) и сохранить в identity.bin.\n\ Если ни --mnemonic, ни --entropy не указаны — генерируется\n\ случайная энтропия через системный ГСЧ.\n\ \n\ inspect Прочитать identity.bin и вывести account_id / node_id /\n\ fingerprint. Секретные ключи на экран не выводятся.\n\ \n\ status Показать содержимое локального state: AccountTable,\n\ NodeTable, CandidatePool, статус узла, current_window,\n\ phase lifecycle (Bootstrap/CandidateVdf/Registered/Active),\n\ supply, балансы.\n\ \n\ time Показать current_window локального узла, ближайшее\n\ selection-окно, эпоху τ₂.\n\ \n\ start БОЕВОЙ РЕЖИМ — запуск узла Montana через canonical\n\ apply_proposal pipeline. Узел проходит lifecycle:\n\ Bootstrap → CandidateVdf (тикает VDF до vdf_chain_length\n\ ≥ τ₂ = 20160 окон, ~10 часов\n\ wall-clock на M-class Mac)\n\ CandidateVdf → Registered (формирует NodeRegistration\n\ через apply_noderegistrations_batch)\n\ Registered → Active (через apply_selection_event на\n\ следующем W % 336 == 0)\n\ Active: per окно VdfReveal + BundledConfirmation +\n\ ProposalHeader + apply_proposal + archive_proposal,\n\ state_root self-verify, эмиссия 13 Ɉ оператору\n\ Ctrl-C — корректная остановка с сохранением state.\n\ \n\ Опции:\n\ \n\ --data-dir Каталог данных узла. По умолчанию на macOS:\n\ $HOME/Library/Application Support/Montana/node\n\ --mnemonic \"...\" Восстановить identity из 24-словной фразы.\n\ --entropy Использовать 32-байтную энтропию (64 hex).\n\ --force Перезаписать существующий identity.bin.\n\ --reveal-master-seed В inspect: показать полный master_seed.\n\ --max-windows В start: остановиться после N окон.\n\ --d-test-override В start: TEST-ONLY override D = N итераций.\n\ Production использует params.d0 = 300_000_000.\n\ Override используется в тестах для скорости.\n", ) } fn parse_init(args: &[String]) -> Result { let mut data_dir: Option = None; let mut mnemonic: Option = None; let mut entropy_hex: Option = None; let mut force = false; let mut i = 0; while i < args.len() { match args[i].as_str() { "--data-dir" => { data_dir = Some(PathBuf::from(expect_value(args, i, "--data-dir")?)); i += 2; }, "--mnemonic" => { mnemonic = Some(expect_value(args, i, "--mnemonic")?.to_string()); i += 2; }, "--entropy" => { entropy_hex = Some(expect_value(args, i, "--entropy")?.to_string()); i += 2; }, "--force" => { force = true; i += 1; }, other => { return Err(NodeError::InvalidArguments(format!( "неизвестный флаг для init: {other}" ))) }, } } Ok(init::InitArgs { data_dir, mnemonic, entropy_hex, force, }) } fn parse_inspect(args: &[String]) -> Result { let mut data_dir: Option = None; let mut reveal_master_seed = false; let mut i = 0; while i < args.len() { match args[i].as_str() { "--data-dir" => { data_dir = Some(PathBuf::from(expect_value(args, i, "--data-dir")?)); i += 2; }, "--reveal-master-seed" => { reveal_master_seed = true; i += 1; }, other => { return Err(NodeError::InvalidArguments(format!( "неизвестный флаг для inspect: {other}" ))) }, } } Ok(inspect::InspectArgs { data_dir, reveal_master_seed, }) } fn parse_status(args: &[String]) -> Result { let mut data_dir: Option = None; let mut i = 0; while i < args.len() { match args[i].as_str() { "--data-dir" => { data_dir = Some(PathBuf::from(expect_value(args, i, "--data-dir")?)); i += 2; }, other => { return Err(NodeError::InvalidArguments(format!( "неизвестный флаг для status: {other}" ))) }, } } Ok(status::StatusArgs { data_dir }) } fn parse_time(args: &[String]) -> Result { let mut data_dir: Option = None; let mut i = 0; while i < args.len() { match args[i].as_str() { "--data-dir" => { data_dir = Some(PathBuf::from(expect_value(args, i, "--data-dir")?)); i += 2; }, other => { return Err(NodeError::InvalidArguments(format!( "неизвестный флаг для time: {other}" ))) }, } } Ok(time::TimeArgs { data_dir }) } fn parse_start(args: &[String]) -> Result { let mut data_dir: Option = None; let mut max_windows: Option = None; let mut d_test_override: Option = None; let mut listen_multiaddr: Option = None; let mut genesis_manifest: Option = None; let mut i = 0; while i < args.len() { match args[i].as_str() { "--data-dir" => { data_dir = Some(PathBuf::from(expect_value(args, i, "--data-dir")?)); i += 2; }, "--max-windows" => { max_windows = Some(expect_value(args, i, "--max-windows")?.parse().map_err( |_| NodeError::InvalidArguments("--max-windows должен быть u64".into()), )?); i += 2; }, "--d-test-override" => { d_test_override = Some( expect_value(args, i, "--d-test-override")? .parse() .map_err(|_| { NodeError::InvalidArguments("--d-test-override должен быть u64".into()) })?, ); i += 2; }, "--listen" => { listen_multiaddr = Some(expect_value(args, i, "--listen")?.to_string()); i += 2; }, "--genesis-manifest" => { genesis_manifest = Some(PathBuf::from(expect_value(args, i, "--genesis-manifest")?)); i += 2; }, other => { return Err(NodeError::InvalidArguments(format!( "неизвестный флаг для start: {other}" ))) }, } } if listen_multiaddr.is_some() != genesis_manifest.is_some() { return Err(NodeError::InvalidArguments( "--listen и --genesis-manifest должны указываться вместе (cross-machine mode) либо оба отсутствовать (singleton mode)" .into(), )); } Ok(start::StartArgs { data_dir, max_windows, d_test_override, listen_multiaddr, genesis_manifest, }) } fn expect_value<'a>(args: &'a [String], i: usize, flag: &str) -> Result<&'a str, NodeError> { args.get(i + 1) .map(|s| s.as_str()) .ok_or_else(|| NodeError::InvalidArguments(format!("флаг {flag} требует значения"))) }