From 223f5373f68e481fdb1d4b2af4eca236081a234a Mon Sep 17 00:00:00 2001 From: apoorvsadana <95699312+apoorvsadana@users.noreply.github.com> Date: Wed, 10 Jan 2024 01:41:18 +0530 Subject: [PATCH] ux fixes (#8) * fix: UX --- src/cli/init.rs | 21 ++++++++--- src/cli/run.rs | 21 ++++++++++- src/da/avail.rs | 91 ++++++++++++++++++++++++++++++++------------- src/da/da_layers.rs | 44 +++++++++++++++++----- src/da/errors.rs | 14 ------- src/da/mod.rs | 2 - src/da/no_da.rs | 14 ++++--- src/main.rs | 7 +++- src/utils/cmd.rs | 2 +- src/utils/errors.rs | 3 ++ src/utils/madara.rs | 26 ++++++------- 11 files changed, 167 insertions(+), 78 deletions(-) delete mode 100644 src/da/errors.rs diff --git a/src/cli/init.rs b/src/cli/init.rs index f458511..3260752 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -39,7 +39,8 @@ pub fn init() { panic!("Failed to write config: {}", err); } }; - // fund_msg(&config.da_layer); + + log::info!("\n"); log::info!("✅ New app chain initialised."); } @@ -50,13 +51,23 @@ fn generate_config() -> Result { let binding = app_chains_home.join(format!("{}/data", app_chain)); let default_base_path = binding.to_str().unwrap_or("madara-data"); - let base_path = get_text_input("Enter base path for data directory of your app chain:", Some(default_base_path))?; let mode = get_option("Select mode for your app chain:", RollupMode::iter().collect::>())?; let da_layer = get_option("Select DA layer for your app chain:", DALayer::iter().collect::>())?; let madara_version = get_latest_commit_hash(MADARA_REPO_ORG, MADARA_REPO_NAME)?; let config_version = ConfigVersion::Version1; - match DAFactory::new_da(&da_layer).setup_and_generate_keypair(&app_chain) { + log::info!("\n"); + + let config = AppChainConfig { + app_chain, + base_path: default_base_path.to_string(), + mode, + da_layer: da_layer.clone(), + madara_version, + config_version, + }; + + match DAFactory::new_da(&da_layer).setup_and_generate_keypair(&config) { Ok(_) => (), Err(err) => { log::error!("Failed to generate keypair: {}", err); @@ -64,7 +75,7 @@ fn generate_config() -> Result { } }; - Ok(AppChainConfig { app_chain, base_path, mode, da_layer, madara_version, config_version }) + Ok(config) } fn write_config(config: &AppChainConfig) -> Result<(), InitError> { @@ -74,7 +85,7 @@ fn write_config(config: &AppChainConfig) -> Result<(), InitError> { if let Err(err) = fs::write(file_path, toml) { panic!("Error writing to file: {}", err); } else { - log::info!("Config file saved!"); + log::debug!("Config file saved!"); } Ok(()) diff --git a/src/cli/run.rs b/src/cli/run.rs index e60d74a..7d7fc35 100644 --- a/src/cli/run.rs +++ b/src/cli/run.rs @@ -3,8 +3,10 @@ use thiserror::Error; use crate::cli::list::get_apps_list; use crate::cli::prompt::get_option; +use crate::da::da_layers::{DAFactory, DaError}; use crate::utils::errors::MadaraError; use crate::utils::madara; +use crate::utils::toml::regenerate_app_config; #[derive(Debug, Error)] pub enum RunError { @@ -14,6 +16,10 @@ pub enum RunError { FailedToStartMadara(#[from] MadaraError), #[error("Failed to get app chains: {0}")] FailedToGetAppChains(#[from] std::io::Error), + #[error("Failed to regenerate config: {0}")] + FailedToRegenerateConfig(String), + #[error("Failed with DA error: {0}")] + FailedWithDaError(#[from] DaError), } pub fn run() { match start_app_chain() { @@ -21,7 +27,7 @@ pub fn run() { log::info!("Madara setup successful"); } Err(err) => { - log::error!("Failed to setup Madara: {:?}", err); + log::error!("Failed to setup Madara: {}", err); } } } @@ -31,8 +37,19 @@ fn start_app_chain() -> Result<(), RunError> { let app = get_option("Select the app chain:", app_chains_list)?; let app_chain: &str = &app; + let (config, _) = match regenerate_app_config(app_chain) { + Ok((config, valid)) => (config, valid), + Err(err) => { + log::error!("Failed to fetch the required app chain: {}", err); + return Err(RunError::FailedToRegenerateConfig(app_chain.to_string())); + } + }; + madara::clone_madara_and_build_repo()?; - madara::setup_and_run_madara(app_chain)?; + + DAFactory::new_da(&config.da_layer).confirm_minimum_balance(&config)?; + + madara::setup_and_run_madara(config)?; Ok(()) } diff --git a/src/da/avail.rs b/src/da/avail.rs index 196c1cc..d38fff0 100644 --- a/src/da/avail.rs +++ b/src/da/avail.rs @@ -1,19 +1,38 @@ use std::fs; +use crate::app::config::AppChainConfig; +use crate::cli::prompt::get_boolean_input; use hex::encode; -use serde_json::json; +use serde::{Deserialize, Serialize}; use sp_core::{sr25519, Pair}; +use thiserror::Error; -use crate::da::da_layers::DaConfig; -use crate::da::errors::KeyGenError; -use crate::utils::constants::{APP_DA_CONFIG_NAME, APP_SECRET_PHRASE}; -use crate::utils::paths::get_app_home; +use crate::da::da_layers::{DaClient, DaError}; -pub struct AvailConfig {} +pub struct AvailClient; -impl DaConfig for AvailConfig { - fn setup_and_generate_keypair(&self, app_chain: &str) -> Result<(), KeyGenError> { - let file_path = get_app_home(app_chain)?.join(APP_SECRET_PHRASE); +#[derive(Debug, Serialize, Deserialize)] +pub struct AvailConfig { + pub ws_provider: String, + pub mode: String, + pub seed: String, + pub app_id: u32, + pub address: String, +} + +#[derive(Error, Debug)] +pub enum AvailError { + #[error("Failed to serialize config: {0}")] + FailedToSerializeConfig(#[from] serde_json::Error), + #[error("Faucet funds needed for DA to be submitted")] + FaucetFundsNeeded, +} + +const AVAIL_DOCS: &str = "https://docs.availproject.org/about/faucet/"; + +impl DaClient for AvailClient { + fn setup_and_generate_keypair(&self, config: &AppChainConfig) -> Result<(), DaError> { + let file_path = self.get_da_config_path(config)?; let file_path_str = file_path.to_string_lossy().to_string(); let (pair, phrase, seed) = ::generate_with_phrase(None); let seed_str = format!("0x{}", encode(seed.as_ref())); @@ -21,34 +40,54 @@ impl DaConfig for AvailConfig { if let Err(err) = fs::write(file_path, phrase) { panic!("Error writing to file: {}", err); } else { - log::info!("Secret phrase stored in app home: {}", file_path_str); + log::info!("🔑 Secret phrase stored in app home: {}", file_path_str); + log::info!("💧 Avail address: {}", pair.public()); log::info!( - "Please fund {} with atleast 1 AVL (https://docs.availproject.org/about/faucet/)", - pair.public() - ); + "=> Please fund your Avail address to be able to submit blobs to the goldberg network. Docs: {}", + AVAIL_DOCS + ) } - generate_config(app_chain, &seed_str)?; + generate_config(file_path_str.as_str(), &seed_str, pair.public().to_string().as_str())?; Ok(()) } -} -fn generate_config(app_chain: &str, seed: &str) -> Result<(), KeyGenError> { - let file_path = get_app_home(app_chain)?.join(APP_DA_CONFIG_NAME); + fn confirm_minimum_balance(&self, config: &AppChainConfig) -> Result<(), DaError> { + let avail_config_path = self.get_da_config_path(config)?; + let avail_config: AvailConfig = serde_json::from_str( + fs::read_to_string(avail_config_path).map_err(DaError::FailedToReadDaConfigFile)?.as_str(), + ) + .map_err(DaError::FailedToDeserializeDaConfig)?; + match get_boolean_input( + format!( + "Have you funded your Avail address {} using the faucet? Docs: {}", + avail_config.address, AVAIL_DOCS + ) + .as_str(), + Some(true), + )? { + true => Ok(()), + false => Err(DaError::AvailError(AvailError::FaucetFundsNeeded)), + } + } +} - let avail_config = json! ({ - "ws_provider": "wss://goldberg.avail.tools:443/ws", - "mode": "sovereign", - "seed": seed, - "app_id": 0, - }) - .to_string(); +fn generate_config(da_config_path: &str, seed: &str, address: &str) -> Result<(), DaError> { + let avail_config = AvailConfig { + ws_provider: "wss://goldberg.avail.tools:443/ws".to_string(), + mode: "sovereign".to_string(), + seed: seed.to_string(), + app_id: 0, + address: address.to_string(), + }; - if let Err(err) = fs::write(file_path, avail_config) { + if let Err(err) = + fs::write(da_config_path, serde_json::to_string(&avail_config).map_err(DaError::FailedToSerializeDaConfig)?) + { panic!("Error writing to file: {}", err); } else { - log::info!("Successfully generated Avail config!"); + log::debug!("Successfully generated Avail config!"); } Ok(()) diff --git a/src/da/da_layers.rs b/src/da/da_layers.rs index 737633f..7fa6cd9 100644 --- a/src/da/da_layers.rs +++ b/src/da/da_layers.rs @@ -1,28 +1,54 @@ +use std::io; +use std::path::PathBuf; + use serde::{Deserialize, Serialize}; use strum_macros::{Display, EnumIter}; +use thiserror::Error; -use crate::da::avail::AvailConfig; -use crate::da::errors::KeyGenError; +use crate::app::config::AppChainConfig; +use crate::da::avail::{AvailClient, AvailError}; use crate::da::no_da::NoDAConfig; +use crate::utils::constants::APP_DA_CONFIG_NAME; +use crate::utils::paths::get_app_home; -#[derive(Debug, Serialize, Deserialize, EnumIter, Display)] +#[derive(Debug, Serialize, Deserialize, EnumIter, Display, Clone)] pub enum DALayer { Avail, - Celestia, - Ethereum, NoDA, } -pub trait DaConfig { - fn setup_and_generate_keypair(&self, app_chain: &str) -> Result<(), KeyGenError>; +#[derive(Error, Debug)] +pub enum DaError { + #[error("avail error: {0}")] + AvailError(#[from] AvailError), + #[error("failed to read app home: {0}")] + FailedToReadAppHome(io::Error), + #[error("inquire error")] + InquireError(#[from] inquire::InquireError), + #[error("Failed to read DA config file")] + FailedToReadDaConfigFile(io::Error), + #[error("Failed to deserialize config")] + FailedToDeserializeDaConfig(serde_json::Error), + #[error("Failed to serialize config")] + FailedToSerializeDaConfig(serde_json::Error), +} + +pub trait DaClient { + fn setup_and_generate_keypair(&self, config: &AppChainConfig) -> Result<(), DaError>; + + fn confirm_minimum_balance(&self, config: &AppChainConfig) -> Result<(), DaError>; + + fn get_da_config_path(&self, config: &AppChainConfig) -> Result { + Ok(get_app_home(&config.app_chain).map_err(DaError::FailedToReadAppHome)?.join(APP_DA_CONFIG_NAME)) + } } pub struct DAFactory; impl DAFactory { - pub fn new_da(da: &DALayer) -> Box { + pub fn new_da(da: &DALayer) -> Box { match da { - DALayer::Avail => Box::new(AvailConfig {}), + DALayer::Avail => Box::new(AvailClient {}), _ => Box::new(NoDAConfig {}), } } diff --git a/src/da/errors.rs b/src/da/errors.rs deleted file mode 100644 index 798f55a..0000000 --- a/src/da/errors.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::io; -use std::string::FromUtf8Error; - -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum KeyGenError { - #[error("Failed to read file: {0}")] - FailedToIoFilesystem(#[from] io::Error), - #[error("Failed to parse output: {0}")] - FailedToParseOutput(#[from] FromUtf8Error), - #[error("Failed to parse to json: {0}")] - FailedToParseToJson(#[from] serde_json::Error), -} diff --git a/src/da/mod.rs b/src/da/mod.rs index aeb7dcb..1960e6a 100644 --- a/src/da/mod.rs +++ b/src/da/mod.rs @@ -2,6 +2,4 @@ pub mod da_layers; pub mod avail; -pub mod errors; - pub mod no_da; diff --git a/src/da/no_da.rs b/src/da/no_da.rs index c766a4c..fed8b0b 100644 --- a/src/da/no_da.rs +++ b/src/da/no_da.rs @@ -1,11 +1,15 @@ pub struct NoDAConfig; -use crate::da::da_layers::DaConfig; -use crate::da::errors::KeyGenError; +use crate::app::config::AppChainConfig; +use crate::da::da_layers::{DaClient, DaError}; -impl DaConfig for NoDAConfig { - fn setup_and_generate_keypair(&self, app_chain: &str) -> Result<(), KeyGenError> { - log::info!("Launching {} without any DA mode", app_chain); +impl DaClient for NoDAConfig { + fn setup_and_generate_keypair(&self, config: &AppChainConfig) -> Result<(), DaError> { + log::info!("Launching {} without any DA mode", config.app_chain); + Ok(()) + } + + fn confirm_minimum_balance(&self, _config: &AppChainConfig) -> Result<(), DaError> { Ok(()) } } diff --git a/src/main.rs b/src/main.rs index c458311..8ab4d80 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,7 +22,12 @@ enum Commands { } fn main() { - env_logger::Builder::from_default_env().filter_level(LevelFilter::Info).init(); + env_logger::Builder::from_default_env() + .filter_level(LevelFilter::Info) + .format_timestamp(None) + .format_level(false) + .format_target(false) + .init(); let cli = Cli::parse(); diff --git a/src/utils/cmd.rs b/src/utils/cmd.rs index 158216b..ba37b7a 100644 --- a/src/utils/cmd.rs +++ b/src/utils/cmd.rs @@ -13,7 +13,7 @@ pub fn execute_cmd_stdout(program: &str, args: &[&str], dir: &PathBuf, out: Stdi match result { Ok(output) => { if output.status.success() { - log::info!("Successfully executed {}", program); + log::debug!("Successfully executed {}", program); Ok(output) } else { Err(Error::new(ErrorKind::Other, "Unable to execute command")) diff --git a/src/utils/errors.rs b/src/utils/errors.rs index f2d1f80..3ab73f2 100644 --- a/src/utils/errors.rs +++ b/src/utils/errors.rs @@ -1,3 +1,4 @@ +use std::ffi::OsString; use thiserror::Error; #[derive(Debug, Error)] @@ -26,6 +27,8 @@ pub enum MadaraError { FailedToRegenerateConfig, #[error("Failed to get DA config")] FailedToGetDAConfig, + #[error("Unable to fetch remote")] + FailedToConvertToString(OsString), } #[derive(Debug, Error)] diff --git a/src/utils/madara.rs b/src/utils/madara.rs index e47a68f..76084ce 100644 --- a/src/utils/madara.rs +++ b/src/utils/madara.rs @@ -1,10 +1,11 @@ +use crate::app::config::AppChainConfig; use crate::da::da_layers::DALayer; use crate::utils::cmd::execute_cmd; use crate::utils::constants::{APP_DA_CONFIG_NAME, BRANCH_NAME, KARNOT_REPO_ORG, MADARA_REPO_NAME}; use crate::utils::errors::MadaraError; use crate::utils::github::git_clone; use crate::utils::paths::{get_app_home, get_madara_home}; -use crate::utils::toml::regenerate_app_config; + pub const GITHUB_BASE_URL: &str = "https://github.com"; pub fn clone_madara_and_build_repo() -> Result<(), MadaraError> { @@ -20,23 +21,15 @@ pub fn clone_madara_and_build_repo() -> Result<(), MadaraError> { return Err(MadaraError::FailedToCloneRepo); } } - execute_cmd("cargo", &["build", "--release"], &madara_path)?; + execute_cmd("cargo", &["build", "--release", "--features", "avail", "--features", "celestia"], &madara_path)?; Ok(()) } -pub fn setup_and_run_madara(app_chain: &str) -> Result<(), MadaraError> { +pub fn setup_and_run_madara(config: AppChainConfig) -> Result<(), MadaraError> { let madara_path = get_madara_home()?.join("madara"); - let (config, _) = match regenerate_app_config(app_chain) { - Ok((config, valid)) => (config, valid), - Err(err) => { - log::error!("Failed to fetch the required app chain: {}", err); - return Err(MadaraError::FailedToRegenerateConfig); - } - }; - - let app_home = get_app_home(app_chain)?; + let app_home = get_app_home(config.app_chain.as_str())?; let binding = app_home.join(APP_DA_CONFIG_NAME); let da_config_path = match binding.to_str() { Some(path) => path, @@ -56,7 +49,14 @@ pub fn setup_and_run_madara(app_chain: &str) -> Result<(), MadaraError> { args.extend(avail_conf); }; - execute_cmd("./target/release/madara", &["setup", "--chain=dev", "--from-remote", &base_path], &madara_path)?; + let config_path = + madara_path.join("configs").into_os_string().into_string().map_err(MadaraError::FailedToConvertToString)?; + + execute_cmd( + "./target/release/madara", + &["setup", "--chain=dev", "--from-local", config_path.as_str(), &base_path], + &madara_path, + )?; execute_cmd("./target/release/madara", args.as_slice(), &madara_path)?;