diff --git a/rusk-recovery/src/bin/keys.rs b/rusk-recovery/src/bin/keys.rs index 3ea82f062d..816f49f4e6 100644 --- a/rusk-recovery/src/bin/keys.rs +++ b/rusk-recovery/src/bin/keys.rs @@ -38,7 +38,7 @@ struct Cli { fn main() -> Result<(), Box> { let args = Cli::parse(); task::run( - || Ok(rusk_recovery_tools::keys::exec(args.keep)?), + || rusk_recovery_tools::keys::exec(args.keep), args.profile, args.verbose, ) diff --git a/rusk-recovery/src/bin/state.rs b/rusk-recovery/src/bin/state.rs index 5c564322cb..868c46883b 100644 --- a/rusk-recovery/src/bin/state.rs +++ b/rusk-recovery/src/bin/state.rs @@ -16,7 +16,9 @@ use std::{fs, path::PathBuf}; use tracing::info; use version::VERSION_BUILD; -use rusk_recovery_tools::state::{deploy, restore_state, tar, Snapshot}; +use rusk_recovery_tools::state::{ + deploy, restore_state, tar, Snapshot, DEFAULT_SNAPSHOT, +}; #[derive(Parser, Debug)] #[clap(name = "rusk-recovery-state")] @@ -55,7 +57,7 @@ fn main() -> Result<(), Box> { let config = match args.init { Some(path) => fs::read_to_string(path)?, - None => include_str!("../../config/testnet_remote.toml").into(), + None => DEFAULT_SNAPSHOT.into(), }; let snapshot = toml::from_str(&config)?; diff --git a/rusk-recovery/src/keys.rs b/rusk-recovery/src/keys.rs index 374be3c55a..795233664c 100644 --- a/rusk-recovery/src/keys.rs +++ b/rusk-recovery/src/keys.rs @@ -115,7 +115,7 @@ fn run_stored_circuits_checks( check_circuits_cache(circuit_list).map(|_| ()) } -pub fn exec(keep_circuits: bool) -> Result<(), io::Error> { +pub fn exec(keep_circuits: bool) -> Result<(), Box> { // This force init is needed to check CRS and create it (if not available) // See also: https://github.com/dusk-network/rusk/issues/767 Lazy::force(&PUB_PARAMS); diff --git a/rusk-recovery/src/state.rs b/rusk-recovery/src/state.rs index b0c047ff5d..a88a433db1 100644 --- a/rusk-recovery/src/state.rs +++ b/rusk-recovery/src/state.rs @@ -31,6 +31,8 @@ pub mod tar; mod zip; pub const MINIMUM_STAKE: Dusk = dusk(1000.0); +pub const DEFAULT_SNAPSHOT: &str = + include_str!("../config/testnet_remote.toml"); const GENESIS_BLOCK_HEIGHT: u64 = 0; diff --git a/rusk/Cargo.toml b/rusk/Cargo.toml index 1aa9018a33..f88f927f33 100644 --- a/rusk/Cargo.toml +++ b/rusk/Cargo.toml @@ -72,8 +72,8 @@ async-graphql = "5.0" ## Ephemeral dependencies -rusk-recovery = { version = "0.6", path = "../rusk-recovery", features = ["state"], optional = true} tempfile = { version = "3.2", optional = true } +rusk-recovery = { version = "0.6", path = "../rusk-recovery", optional = true} [dev-dependencies] test-context = "0.1" @@ -85,5 +85,7 @@ ff = { version = "0.13", default-features = false } rustc_tools_util = "0.3" [features] -default = ["ephemeral"] -ephemeral = ["dep:rusk-recovery", "dep:tempfile"] +default = ["ephemeral", "recovery-state", "recovery-keys"] +ephemeral = ["dep:rusk-recovery", "dep:tempfile", "recovery-state"] +recovery-state = ["rusk-recovery/state", "dep:tempfile"] +recovery-keys = ["rusk-recovery/keys"] diff --git a/rusk/src/bin/args.rs b/rusk/src/bin/args.rs index ee6f731b08..d07f57f48c 100644 --- a/rusk/src/bin/args.rs +++ b/rusk/src/bin/args.rs @@ -4,6 +4,11 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. +#[cfg(any(feature = "recovery-state", feature = "recovery-keys"))] +mod command; +#[cfg(feature = "recovery-state")] +mod state; + use std::path::PathBuf; use clap::builder::PossibleValuesParser; @@ -83,4 +88,9 @@ pub struct Args { #[clap(short = 'n', long = "network-id")] /// Kadcast network id pub kadcast_network_id: Option, + + #[cfg(any(feature = "recovery-state", feature = "recovery-keys"))] + /// Command + #[clap(subcommand)] + pub command: Option, } diff --git a/rusk/src/bin/args/command.rs b/rusk/src/bin/args/command.rs new file mode 100644 index 0000000000..4b130b3ac3 --- /dev/null +++ b/rusk/src/bin/args/command.rs @@ -0,0 +1,78 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use clap::builder::BoolishValueParser; +use clap::Subcommand; +use rusk_recovery_tools::Theme; +use std::io; +use tracing::info; + +#[allow(clippy::large_enum_variant)] +#[derive(PartialEq, Eq, Hash, Clone, Subcommand, Debug)] +pub enum Command { + #[cfg(feature = "recovery-keys")] + RecoveryKeys { + /// Keeps untracked keys + #[clap(short, long, value_parser = BoolishValueParser::new(), env = "RUSK_KEEP_KEYS")] + keep: bool, + }, + + #[cfg(feature = "recovery-state")] + RecoveryState { + /// Forces a build/download even if the state is in the profile path. + #[clap(short = 'f', value_parser = BoolishValueParser::new(), long, env = "RUSK_FORCE_STATE")] + force: bool, + + /// Create a state applying the init config specified in this file. + #[clap(short, long, value_parser, env = "RUSK_RECOVERY_INPUT")] + init: Option, + + /// If specified, the generated state is written on this file instead + /// of save the state in the profile path. + #[clap(short, long, value_parser, num_args(1))] + output: Option, + }, +} + +impl Command { + fn display_env(theme: &Theme) -> io::Result<()> { + let profile_dir = rusk_profile::get_rusk_profile_dir()?; + let circuits_dir = rusk_profile::get_rusk_circuits_dir()?; + let keys_dir = rusk_profile::get_rusk_keys_dir()?; + let state_dir = rusk_profile::get_rusk_state_dir()?; + + info!("{} {}", theme.info("PROFILE"), profile_dir.display()); + info!("{} {}", theme.info("CIRCUITS"), circuits_dir.display()); + info!("{} {}", theme.info("KEYS"), keys_dir.display()); + info!("{} {}", theme.info("STATE"), state_dir.display()); + Ok(()) + } + + pub fn run(self) -> Result<(), Box> { + let theme = Theme::default(); + + Self::display_env(&theme)?; + + let result = match self { + #[cfg(feature = "recovery-state")] + Self::RecoveryState { + force, + init, + output, + } => super::state::recovery_state(init, force, output), + #[cfg(feature = "recovery-keys")] + Self::RecoveryKeys { keep } => { + rusk_recovery_tools::keys::exec(keep) + } + }; + + if let Err(e) = &result { + tracing::error!("{} {e}", theme.error("Error")); + } + + result + } +} diff --git a/rusk/src/bin/args/state.rs b/rusk/src/bin/args/state.rs new file mode 100644 index 0000000000..0eb55388fe --- /dev/null +++ b/rusk/src/bin/args/state.rs @@ -0,0 +1,104 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use super::*; + +use std::{env, fs, io}; + +use rusk_recovery_tools::state::{deploy, restore_state, tar}; +use rusk_recovery_tools::Theme; +use tracing::info; + +pub fn recovery_state( + init: Option, + force: bool, + output_file: Option, +) -> Result<(), Box> { + let config = match &init { + Some(path) => fs::read_to_string(path) + .map_err(|_| format!("file {path:?} not found"))?, + None => rusk_recovery_tools::state::DEFAULT_SNAPSHOT.into(), + }; + let init = toml::from_str(&config)?; + + let theme = Theme::default(); + info!("{} Network state", theme.action("Checking")); + + let _tmpdir = match output_file.clone() { + Some(output) if output.exists() => Err("Output already exists")?, + Some(_) => { + let tmp_dir = tempfile::tempdir()?; + env::set_var("RUSK_STATE_PATH", tmp_dir.path()); + Some(tmp_dir) + } + None => None, + }; + + if force { + clean_state()?; + } + + let state_dir = rusk_profile::get_rusk_state_dir()?; + let state_id_path = rusk_profile::to_rusk_state_id_path(&state_dir); + + let _ = rusk_abi::new_vm(&state_dir)?; + + // if the state already exists in the expected path, stop early. + if state_dir.exists() && state_id_path.exists() { + info!("{} existing state", theme.info("Found")); + + let (_, commit_id) = restore_state(state_dir)?; + info!( + "{} state id at {}", + theme.success("Checked"), + state_id_path.display() + ); + info!("{} {}", theme.action("Root"), hex::encode(commit_id)); + + return Ok(()); + } + + info!("{} new state", theme.info("Building")); + + let (_, commit_id) = deploy(&state_dir, &init)?; + + info!("{} {}", theme.action("Final Root"), hex::encode(commit_id)); + + info!( + "{} network state at {}", + theme.success("Stored"), + state_dir.display() + ); + info!( + "{} persisted id at {}", + theme.success("Stored"), + state_id_path.display() + ); + + if let Some(output) = output_file { + let state_folder = rusk_profile::get_rusk_state_dir()?; + info!( + "{} state into {}", + theme.info("Compressing"), + output.display() + ); + tar::archive(&state_folder, &output)?; + } + + Ok(()) +} + +fn clean_state() -> Result<(), io::Error> { + let state_path = rusk_profile::get_rusk_state_dir()?; + + fs::remove_dir_all(state_path).or_else(|e| { + if e.kind() == io::ErrorKind::NotFound { + Ok(()) + } else { + Err(e) + } + }) +} diff --git a/rusk/src/bin/main.rs b/rusk/src/bin/main.rs index 905a6d3ccc..7d1ec2c617 100644 --- a/rusk/src/bin/main.rs +++ b/rusk/src/bin/main.rs @@ -46,6 +46,19 @@ async fn main() -> Result<(), Box> { let subscriber = tracing_subscriber::fmt::Subscriber::builder() .with_env_filter(EnvFilter::new(log_filter).add_directive(log.into())); + #[cfg(any(feature = "recovery-state", feature = "recovery-keys"))] + // Set custom tracing format if subcommand is specified + if let Some(command) = args.command { + let subscriber = subscriber + .with_level(false) + .without_time() + .with_target(false) + .finish(); + tracing::subscriber::set_global_default(subscriber)?; + command.run()?; + return Ok(()); + } + // Set the subscriber as global. // so this subscriber will be used as the default in all threads for the // remainder of the duration of the program, similar to how `loggers`