diff --git a/Cargo.lock b/Cargo.lock index c05ebcfde..ea0100dcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10134,6 +10134,18 @@ dependencies = [ "ts-rs", ] +[[package]] +name = "tari_watcher" +version = "0.7.0" +dependencies = [ + "anyhow", + "clap 3.2.25", + "log", + "serde", + "tokio", + "toml 0.8.15", +] + [[package]] name = "tariswap_bench" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index 214249591..cf7ae9c84 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -56,7 +56,7 @@ members = [ "utilities/tariswap_test_bench", "utilities/transaction_submitter", "utilities/transaction_submitter", - "utilities/generate_ristretto_value_lookup", + "utilities/generate_ristretto_value_lookup", "applications/tari_watcher", ] resolver = "2" diff --git a/applications/tari_watcher/Cargo.toml b/applications/tari_watcher/Cargo.toml new file mode 100644 index 000000000..0367c410d --- /dev/null +++ b/applications/tari_watcher/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "tari_watcher" +version.workspace = true +edition.workspace = true +authors.workspace = true +repository.workspace = true +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 } + +toml = "0.8.12" diff --git a/applications/tari_watcher/src/cli.rs b/applications/tari_watcher/src/cli.rs new file mode 100644 index 000000000..ae1c1954d --- /dev/null +++ b/applications/tari_watcher/src/cli.rs @@ -0,0 +1,60 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::path::PathBuf; + +use clap::Parser; + +use crate::config::Config; + +#[derive(Clone, Debug, Parser)] +pub struct Cli { + #[clap(flatten)] + pub common: CommonCli, + #[clap(subcommand)] + pub command: Commands, +} + +impl Cli { + pub fn init() -> Self { + Self::parse() + } + + pub fn get_config_path(&self) -> PathBuf { + let Some(ref base_dir) = self.common.base_dir else { + return self.common.config_path.clone(); + }; + if self.common.config_path.is_relative() { + base_dir.join(&self.common.config_path) + } else { + self.common.config_path.clone() + } + } +} + +#[derive(Debug, Clone, clap::Args)] +pub struct CommonCli { + #[clap(short = 'b', long, parse(from_os_str))] + pub base_dir: Option, + #[clap(short = 'c', long, parse(from_os_str), default_value = "./data/watcher/config.toml")] + pub config_path: PathBuf, +} + +#[derive(Clone, Debug, clap::Subcommand)] +pub enum Commands { + Init(InitArgs), + Start, +} + +#[derive(Clone, Debug, clap::Args)] +pub struct InitArgs { + #[clap(long)] + /// Disable initial and auto registration of the validator node + pub no_auto_register: bool, +} + +impl InitArgs { + pub fn apply(&self, config: &mut Config) { + config.auto_register = !self.no_auto_register; + } +} diff --git a/applications/tari_watcher/src/config.rs b/applications/tari_watcher/src/config.rs new file mode 100644 index 000000000..ee6c3250b --- /dev/null +++ b/applications/tari_watcher/src/config.rs @@ -0,0 +1,180 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::{ + collections::HashMap, + fmt::{self, Display}, + path::PathBuf, +}; + +use tokio::io::{self, AsyncWriteExt}; + +use crate::Cli; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct Config { + /// Allow watcher to submit a new validator node registration transaction initially and before + /// the current registration expires + pub auto_register: bool, + + /// The Minotari node gRPC address + pub base_node_grpc_address: String, + + /// The Minotari console wallet gRPC address + pub base_wallet_grpc_address: String, + + /// 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, + + /// The sidechain ID to use. If not provided, the default Tari sidechain ID will be used. + pub sidechain_id: Option, + + /// The configuration for managing one or multiple processes + pub instance_config: Vec, + + /// The process specific configuration for the executables + pub executable_config: Vec, + + /// The channel configuration for alerting and monitoring + pub channel_config: Vec, +} + +impl Config { + pub(crate) async fn write(&self, mut writer: W) -> anyhow::Result<()> { + let toml = toml::to_string_pretty(self)?; + writer.write_all(toml.as_bytes()).await?; + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum InstanceType { + TariValidatorNode, + MinoTariConsoleWallet, +} + +impl Display for InstanceType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self, f) + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ChannelConfig { + pub name: String, + pub enabled: bool, + pub credentials: String, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +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, + pub instance_type: InstanceType, + pub num_instances: u32, + #[serde(alias = "extra_args")] + pub settings: HashMap, +} + +impl InstanceConfig { + pub fn new(instance_type: InstanceType) -> Self { + Self { + name: instance_type.to_string(), + instance_type, + num_instances: 1, + settings: HashMap::new(), + } + } + + pub fn with_name>(mut self, name: S) -> Self { + self.name = name.into(); + self + } + + pub fn with_num_instances(mut self, num_instances: u32) -> Self { + self.num_instances = num_instances; + self + } +} + +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, + }), + 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![], + }, + ]; + let instances = [ + InstanceConfig::new(InstanceType::TariValidatorNode) + .with_name("tari_validator_node") + .with_num_instances(1), + InstanceConfig::new(InstanceType::MinoTariConsoleWallet) + .with_name("minotari_wallet") + .with_num_instances(1), + ]; + + let base_dir = cli + .common + .base_dir + .clone() + .or_else(|| { + cli.get_config_path() + .canonicalize() + .ok() + .and_then(|p| p.parent().map(|p| p.to_path_buf())) + }) + .unwrap_or_else(|| std::env::current_dir().unwrap()); + + Ok(Config { + auto_register: true, + 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"), + instance_config: instances.to_vec(), + executable_config: executables, + channel_config: vec![ + ChannelConfig { + name: "mattermost".to_string(), + enabled: true, + credentials: "".to_string(), + }, + ChannelConfig { + name: "telegram".to_string(), + enabled: true, + credentials: "".to_string(), + }, + ], + }) +} diff --git a/applications/tari_watcher/src/main.rs b/applications/tari_watcher/src/main.rs new file mode 100644 index 000000000..cb7c923b9 --- /dev/null +++ b/applications/tari_watcher/src/main.rs @@ -0,0 +1,49 @@ +// Copyright 2024 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use anyhow::{anyhow, Context}; +use tokio::fs; + +use crate::{ + cli::{Cli, Commands}, + config::get_base_config, +}; + +mod cli; +mod config; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let cli = Cli::init(); + let config_path = cli.get_config_path(); + + match cli.command { + Commands::Init(ref args) => { + // set by default in CommonCli + let parent = config_path.parent().unwrap(); + fs::create_dir_all(parent).await?; + + let mut config = get_base_config(&cli)?; + // optionally disables auto register + args.apply(&mut config); + + let file = fs::File::create(&config_path) + .await + .with_context(|| anyhow!("Failed to open config path {}", config_path.display()))?; + config.write(file).await.context("Writing config failed")?; + + let config_path = config_path + .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"); + }, + } + + Ok(()) +}