diff --git a/Cargo.lock b/Cargo.lock index ea0100dcf..67156c6ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10140,6 +10140,8 @@ version = "0.7.0" dependencies = [ "anyhow", "clap 3.2.25", + "fern", + "humantime 2.1.0", "log", "serde", "tokio", diff --git a/applications/tari_watcher/Cargo.toml b/applications/tari_watcher/Cargo.toml index 0367c410d..ae8cd5a4d 100644 --- a/applications/tari_watcher/Cargo.toml +++ b/applications/tari_watcher/Cargo.toml @@ -9,12 +9,13 @@ license.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -#tari_common = { workspace = true } clap = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] } anyhow = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "macros", "signal", "process", "time", "fs", "io-util"] } log = { workspace = true } +fern = { workspace = true, features = ["colored"] } toml = "0.8.12" +humantime = "2.1.0" diff --git a/applications/tari_watcher/src/cli.rs b/applications/tari_watcher/src/cli.rs index ae1c1954d..485be8cc1 100644 --- a/applications/tari_watcher/src/cli.rs +++ b/applications/tari_watcher/src/cli.rs @@ -5,7 +5,7 @@ use std::path::PathBuf; use clap::Parser; -use crate::config::Config; +use crate::config::{Config, InstanceType}; #[derive(Clone, Debug, Parser)] pub struct Cli { @@ -43,7 +43,7 @@ pub struct CommonCli { #[derive(Clone, Debug, clap::Subcommand)] pub enum Commands { Init(InitArgs), - Start, + Start(Overrides), } #[derive(Clone, Debug, clap::Args)] @@ -58,3 +58,30 @@ impl InitArgs { config.auto_register = !self.no_auto_register; } } + +#[derive(Clone, Debug, clap::Args)] +pub struct Overrides { + #[clap(long)] + // The path to the validator node binary (optional) + pub vn_node_path: Option, +} + +impl Overrides { + pub fn apply(&self, config: &mut Config) { + if self.vn_node_path.is_none() { + return; + } + + if let Some(exec_config) = config + .executable_config + .iter_mut() + .find(|c| c.instance_type == InstanceType::TariValidatorNode) + { + exec_config.executable_path = self.vn_node_path.clone(); + } + log::info!( + "Overriding validator node binary path to {:?}", + self.vn_node_path.as_ref().unwrap() + ); + } +} diff --git a/applications/tari_watcher/src/config.rs b/applications/tari_watcher/src/config.rs index ee6c3250b..230f972a0 100644 --- a/applications/tari_watcher/src/config.rs +++ b/applications/tari_watcher/src/config.rs @@ -23,6 +23,9 @@ pub struct Config { /// The Minotari console wallet gRPC address pub base_wallet_grpc_address: String, + /// The base directory of the watcher with configuration and data files + pub base_dir: PathBuf, + /// The path of the validator node registration file, containing signed information required to /// submit a registration transaction on behalf of the node pub vn_registration_file: PathBuf, @@ -71,17 +74,9 @@ pub struct ChannelConfig { pub struct ExecutableConfig { pub instance_type: InstanceType, pub executable_path: Option, - pub compile: Option, pub env: Vec<(String, String)>, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] -pub struct CompileConfig { - pub working_dir: Option, - pub package_name: String, - pub target_dir: Option, -} - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct InstanceConfig { pub name: String, @@ -116,22 +111,12 @@ pub fn get_base_config(cli: &Cli) -> anyhow::Result { let executables = vec![ ExecutableConfig { instance_type: InstanceType::TariValidatorNode, - executable_path: Some("target/release/minotari_node".into()), - compile: Some(CompileConfig { - working_dir: Some("../tari".into()), - package_name: "minotari_node".to_string(), - target_dir: None, - }), + executable_path: Some("target/release/tari_validator_node".into()), env: vec![], }, ExecutableConfig { instance_type: InstanceType::MinoTariConsoleWallet, executable_path: Some("target/release/minotari_wallet".into()), - compile: Some(CompileConfig { - working_dir: Some("../tari".into()), - package_name: "minotari_wallet".to_string(), - target_dir: None, - }), env: vec![], }, ]; @@ -161,7 +146,8 @@ pub fn get_base_config(cli: &Cli) -> anyhow::Result { base_node_grpc_address: "localhost:18142".to_string(), base_wallet_grpc_address: "localhost:18143".to_string(), sidechain_id: None, - vn_registration_file: base_dir.join("vn_registration.toml"), + base_dir: base_dir.clone(), + vn_registration_file: base_dir.join("registration.json"), instance_config: instances.to_vec(), executable_config: executables, channel_config: vec![ diff --git a/applications/tari_watcher/src/forker.rs b/applications/tari_watcher/src/forker.rs new file mode 100644 index 000000000..d49033e33 --- /dev/null +++ b/applications/tari_watcher/src/forker.rs @@ -0,0 +1,85 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::{ + env, + net::IpAddr, + path::{Path, PathBuf}, + process::Stdio, +}; + +use tokio::process::{Child, Command}; + +use crate::{ + config::{ExecutableConfig, InstanceType}, + port::PortAllocator, +}; + +#[allow(dead_code)] +pub struct Forker { + // Used for the validator to connect to the base (L1) node + base_node_grpc_address: String, + // The base directory of calling the application + base_dir: PathBuf, + // The Tari L2 validator instance + validator: Option, + // The Minotari L1 wallet instance + wallet: Option, +} + +impl Forker { + pub fn new(base_node_grpc_address: String, base_dir: PathBuf) -> Self { + Self { + validator: None, + wallet: None, + base_node_grpc_address, + base_dir, + } + } + + pub async fn start_validator(&mut self, config: ExecutableConfig) -> anyhow::Result { + let instance = Instance::new(InstanceType::TariValidatorNode, config.clone()); + self.validator = Some(instance.clone()); + + let mut cmd = Command::new( + config + .executable_path + .unwrap_or_else(|| Path::new("tari_validator_node").to_path_buf()), + ); + + // TODO: stdout logs + // let process_dir = self.base_dir.join("processes").join("TariValidatorNode"); + // let stdout_log_path = process_dir.join("stdout.log"); + // let stderr_log_path = process_dir.join("stderr.log"); + cmd.envs(env::vars()) + //.arg(format!("--config={validator_node_config_path}")) + .kill_on_drop(true) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .stdin(Stdio::null()); + + let child = cmd.spawn()?; + + Ok(child) + } +} + +#[allow(dead_code)] +#[derive(Clone)] +struct Instance { + app: InstanceType, + config: ExecutableConfig, + listen_ip: Option, + port: PortAllocator, +} + +impl Instance { + pub fn new(app: InstanceType, config: ExecutableConfig) -> Self { + Self { + app, + config, + listen_ip: None, + port: PortAllocator::new(), + } + } +} diff --git a/applications/tari_watcher/src/main.rs b/applications/tari_watcher/src/main.rs index cb7c923b9..9c7ed437e 100644 --- a/applications/tari_watcher/src/main.rs +++ b/applications/tari_watcher/src/main.rs @@ -1,22 +1,30 @@ // Copyright 2024 The Tari Project // SPDX-License-Identifier: BSD-3-Clause +use std::time::SystemTime; + use anyhow::{anyhow, Context}; use tokio::fs; use crate::{ cli::{Cli, Commands}, - config::get_base_config, + config::{get_base_config, Config}, + manager::ProcessManager, }; mod cli; mod config; +mod forker; +mod manager; +mod port; #[tokio::main] async fn main() -> anyhow::Result<()> { let cli = Cli::init(); let config_path = cli.get_config_path(); + setup_logger()?; + match cli.command { Commands::Init(ref args) => { // set by default in CommonCli @@ -36,14 +44,47 @@ async fn main() -> anyhow::Result<()> { .canonicalize() .context("Failed to canonicalize config path")?; - // TODO: use standardised logging - // if let Err(e) = initialize_logging(..) log::info!("Config file created at {}", config_path.display()); }, - Commands::Start => { - unimplemented!("Start command not implemented"); + Commands::Start(ref args) => { + let mut config = get_base_config(&cli)?; + // optionally override config values + args.apply(&mut config); + start(config).await?; }, } Ok(()) } + +async fn start(config: Config) -> anyhow::Result<()> { + let mut manager = ProcessManager::new(config.clone()); + manager.forker.start_validator(manager.validator_config).await?; + + Ok(()) +} + +fn setup_logger() -> Result<(), fern::InitError> { + let colors = fern::colors::ColoredLevelConfig::new() + .info(fern::colors::Color::Green) + .debug(fern::colors::Color::Cyan) + .warn(fern::colors::Color::Yellow) + .error(fern::colors::Color::Red); + + fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "[{} {} {}] {}", + humantime::format_rfc3339_seconds(SystemTime::now()), + colors.color(record.level()), + record.target(), + message + )) + }) + .level(log::LevelFilter::Debug) + .chain(std::io::stdout()) + .chain(fern::log_file("output.log")?) + .apply()?; + + Ok(()) +} diff --git a/applications/tari_watcher/src/manager.rs b/applications/tari_watcher/src/manager.rs new file mode 100644 index 000000000..1ca7af08d --- /dev/null +++ b/applications/tari_watcher/src/manager.rs @@ -0,0 +1,23 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use crate::{ + config::{Config, ExecutableConfig}, + forker::Forker, +}; + +pub struct ProcessManager { + pub validator_config: ExecutableConfig, + pub wallet_config: ExecutableConfig, + pub forker: Forker, +} + +impl ProcessManager { + pub fn new(config: Config) -> Self { + Self { + validator_config: config.executable_config[0].clone(), + wallet_config: config.executable_config[1].clone(), + forker: Forker::new(config.base_node_grpc_address, config.base_dir), + } + } +} diff --git a/applications/tari_watcher/src/port.rs b/applications/tari_watcher/src/port.rs new file mode 100644 index 000000000..0938d0b4c --- /dev/null +++ b/applications/tari_watcher/src/port.rs @@ -0,0 +1,38 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +#[derive(Clone)] +pub struct PortAllocator { + pub wallet: MinotariPorts, +} + +impl PortAllocator { + pub fn new() -> Self { + Self { + wallet: MinotariPorts::new(), + } + } +} + +#[derive(Clone)] +pub struct MinotariPorts { + pub p2p: Option, + pub grpc: Option, +} + +#[allow(dead_code)] +impl MinotariPorts { + fn new() -> Self { + Self { p2p: None, grpc: None } + } + + pub fn p2p_port_as_string(&self) -> Option { + self.p2p?; + Some(format!("{}", self.p2p.unwrap())) + } + + pub fn grpc_port_as_string(&self) -> Option { + self.grpc?; + Some(format!("{}", self.grpc.unwrap())) + } +}