From 87923ef59e2cb6e84ff6e065146d3484ee91b33b Mon Sep 17 00:00:00 2001 From: Anshal Shukla Date: Tue, 16 Jan 2024 15:32:49 +0530 Subject: [PATCH 1/7] celestia --- Cargo.lock | 2 + Cargo.toml | 2 + src/cli/explorer.rs | 5 +- src/cli/init.rs | 2 +- src/da/avail.rs | 12 ++-- src/da/celestia.rs | 168 ++++++++++++++++++++++++++++++++++++++++++++ src/da/da_layers.rs | 14 ++-- src/da/ethereum.rs | 31 ++++---- src/da/mod.rs | 3 + src/da/no_da.rs | 5 +- src/utils/docker.rs | 6 +- src/utils/errors.rs | 1 + src/utils/paths.rs | 15 +++- 13 files changed, 233 insertions(+), 33 deletions(-) create mode 100644 src/da/celestia.rs diff --git a/Cargo.lock b/Cargo.lock index ced0a12..855650f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2627,6 +2627,7 @@ dependencies = [ "inquire", "log", "rand 0.8.5", + "regex", "reqwest", "serde", "serde_json", @@ -2636,6 +2637,7 @@ dependencies = [ "thiserror", "tokio", "toml", + "url", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6666b10..8d1a52c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,5 @@ strum_macros = { version = "0.25.3", features = [] } thiserror = "1.0.52" tokio = { version = "1.35.1", features = ["rt", "rt-multi-thread", "macros"] } toml = "0.8.8" +url = "2.5.0" +regex = "1.10.2" diff --git a/src/cli/explorer.rs b/src/cli/explorer.rs index 07b50fd..f451f8d 100644 --- a/src/cli/explorer.rs +++ b/src/cli/explorer.rs @@ -1,9 +1,9 @@ +use std::collections::HashMap; + use bollard::models::{HostConfig, PortBinding}; use rand::distributions::Alphanumeric; use rand::Rng; -use std::collections::HashMap; - use crate::utils::docker::{container_exists, kill_container, run_docker_image}; pub async fn explorer() { @@ -41,6 +41,7 @@ pub async fn explorer() { CONTAINER_NAME, Some(env), Some(host_config), + None, ) .await; log::info!("🧭 Explorer is running on http://localhost:4000"); diff --git a/src/cli/init.rs b/src/cli/init.rs index 3260752..1a0a945 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -67,7 +67,7 @@ fn generate_config() -> Result { config_version, }; - match DAFactory::new_da(&da_layer).setup_and_generate_keypair(&config) { + match DAFactory::new_da(&da_layer).generate_da_config(&config) { Ok(_) => (), Err(err) => { log::error!("Failed to generate keypair: {}", err); diff --git a/src/da/avail.rs b/src/da/avail.rs index 7f47fb0..f864df8 100644 --- a/src/da/avail.rs +++ b/src/da/avail.rs @@ -1,13 +1,13 @@ -use async_trait::async_trait; use std::fs; -use crate::app::config::AppChainConfig; -use crate::cli::prompt::get_boolean_input; +use async_trait::async_trait; use hex::encode; use serde::{Deserialize, Serialize}; use sp_core::{sr25519, Pair}; use thiserror::Error; +use crate::app::config::AppChainConfig; +use crate::cli::prompt::get_boolean_input; use crate::da::da_layers::{DaClient, DaError}; pub struct AvailClient; @@ -33,7 +33,7 @@ const AVAIL_DOCS: &str = "https://docs.availproject.org/about/faucet/"; #[async_trait] impl DaClient for AvailClient { - fn setup_and_generate_keypair(&self, config: &AppChainConfig) -> Result<(), DaError> { + fn generate_da_config(&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); @@ -50,7 +50,7 @@ impl DaClient for AvailClient { ) } - generate_config(file_path_str.as_str(), &seed_str, pair.public().to_string().as_str())?; + write_config(file_path_str.as_str(), &seed_str, pair.public().to_string().as_str())?; Ok(()) } @@ -79,7 +79,7 @@ impl DaClient for AvailClient { } } -fn generate_config(da_config_path: &str, seed: &str, address: &str) -> Result<(), DaError> { +fn write_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(), diff --git a/src/da/celestia.rs b/src/da/celestia.rs new file mode 100644 index 0000000..fe97f8d --- /dev/null +++ b/src/da/celestia.rs @@ -0,0 +1,168 @@ +use std::collections::HashMap; +use std::fs; + +use async_trait::async_trait; +use bollard::models::{HostConfig, Mount, PortBinding}; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +use crate::app::config::AppChainConfig; +use crate::cli::prompt::get_boolean_input; +use crate::da::da_layers::{DaClient, DaError}; +use crate::utils::docker::{container_exists, kill_container, run_docker_image}; +use crate::utils::paths::get_celestia_home; +pub struct CelestiaClient; + +#[derive(Debug, Serialize, Deserialize)] +pub struct CelestiaConfig { + pub ws_provides: String, + pub http_provider: String, + pub nid: String, + pub auth_token: String, + pub address: String, +} + +#[derive(Error, Debug)] +pub enum CelestiaError { + #[error("Faucet funds needed for DA to be submitted")] + FaucetFundsNeeded, + #[error("Celestia light node setup failed")] + SetupError, +} + +const CELESTIA_DOCS: &str = "https://docs.celestia.org/developers/celestia-app-wallet#fund-a-wallet"; + +#[async_trait] +impl DaClient for CelestiaClient { + fn generate_da_config(&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 celestia_home = get_celestia_home().unwrap(); + let file_keys_txt = celestia_home.join("keys.txt"); + let file_auth_txt = celestia_home.join("auth.txt"); + + let keys_txt_content = match fs::read_to_string(file_keys_txt) { + Ok(content) => content, + Err(_) => { + return Err(DaError::CelestiaError(CelestiaError::SetupError)); + } + }; + + let auth_token = match fs::read_to_string(file_auth_txt) { + Ok(content) => content, + Err(_) => { + return Err(DaError::CelestiaError(CelestiaError::SetupError)); + } + }; + + let mut address: &str = ""; + for line in keys_txt_content.lines() { + if line.trim().starts_with("ADDRESS:") { + address = line.trim_start_matches("ADDRESS:").trim(); + log::info!("🔑 Secret phrase stored in app home: {}", celestia_home.to_string_lossy().to_string()); + log::info!("💧 Celestia address: {}", address); + log::info!( + "=> Please fund your Celestia address to be able to submit blobs to the mocha network. Docs: {}", + CELESTIA_DOCS + ) + } + } + + if address.eq("") || auth_token.eq("") { + return Err(DaError::CelestiaError(CelestiaError::SetupError)); + } + + write_config(file_path_str.as_str(), auth_token.as_str(), address)?; + + Ok(()) + } + + fn confirm_minimum_balance(&self, config: &AppChainConfig) -> Result<(), DaError> { + let celestia_config_path = self.get_da_config_path(config)?; + let celestia_config: CelestiaConfig = serde_json::from_str( + fs::read_to_string(celestia_config_path).map_err(DaError::FailedToReadDaConfigFile)?.as_str(), + ) + .map_err(DaError::FailedToDeserializeDaConfig)?; + match get_boolean_input( + format!( + "Have you funded your Celestia address {} using the faucet? Docs: {}", + celestia_config.address, CELESTIA_DOCS + ) + .as_str(), + Some(true), + )? { + true => Ok(()), + false => Err(DaError::CelestiaError(CelestiaError::FaucetFundsNeeded)), + } + } + + async fn setup(&self, _config: &AppChainConfig) -> eyre::Result<()> { + start_celestia_light_node().await + } +} + +pub async fn start_celestia_light_node() -> eyre::Result<()> { + let celestia_home = get_celestia_home()?; + let celestia_home_str = celestia_home.to_str().unwrap_or("~/.madara/celestia"); + + let env = vec!["NODE_TYPE=light", "P2P_NETWORK=mocha"]; + + let mut port_bindings = HashMap::new(); + port_bindings.insert( + "26658/tcp".to_string(), + Some(vec![PortBinding { host_ip: Some("0.0.0.0".to_string()), host_port: Some("26658".to_string()) }]), + ); + + let host_config = HostConfig { + mounts: Some(vec![Mount { + target: Some("/home/celestia".to_string()), + source: Some(celestia_home_str.to_string()), + typ: Some(bollard::models::MountTypeEnum::BIND), + ..Default::default() + }]), + port_bindings: Some(port_bindings), + ..Default::default() + }; + + let start_cmd = vec![ + "sh", + "-c", + "celestia light init --p2p.network=mocha > /home/celestia/keys.txt &&celestia light auth admin \ + --p2p.network=mocha > /home/celestia/auth.txt &&celestia light start --core.ip=rpc-mocha.pops.one \ + --p2p.network=mocha", + ]; + + const CONTAINER_NAME: &str = "celestia-light-client"; + + if container_exists(CONTAINER_NAME).await { + // TODO: handle error + let _ = kill_container(CONTAINER_NAME).await; + } + + run_docker_image( + "ghcr.io/celestiaorg/celestia-node:v0.12.2", + CONTAINER_NAME, + Some(env), + Some(host_config), + Some(start_cmd), + ) + .await; + log::info!("🧭 Celestia light client is running"); + + Ok(()) +} + +fn write_config(da_config_path: &str, auth_token: &str, address: &str) -> Result<(), DaError> { + let celestia_config = CelestiaConfig { + ws_provides: "http://127.0.0.1:26658".to_string(), + http_provider: "http://127.0.0.1:26658".to_string(), + nid: "Madara".to_string(), + auth_token: auth_token.to_string(), + address: address.to_string(), + }; + + fs::write(da_config_path, serde_json::to_string(&celestia_config).map_err(DaError::FailedToSerializeDaConfig)?) + .map_err(DaError::FailedToWriteDaConfigToFile)?; + + Ok(()) +} diff --git a/src/da/da_layers.rs b/src/da/da_layers.rs index 4674df4..87cadb9 100644 --- a/src/da/da_layers.rs +++ b/src/da/da_layers.rs @@ -1,23 +1,24 @@ -use async_trait::async_trait; use std::io; use std::path::PathBuf; +use async_trait::async_trait; +use eyre::Result as EyreResult; use serde::{Deserialize, Serialize}; use strum_macros::{Display, EnumIter}; use thiserror::Error; use crate::app::config::AppChainConfig; use crate::da::avail::{AvailClient, AvailError}; -use crate::da::ethereum::EthereumClient; -use crate::da::ethereum::EthereumError; +use crate::da::celestia::{CelestiaClient, CelestiaError}; +use crate::da::ethereum::{EthereumClient, EthereumError}; use crate::da::no_da::NoDAConfig; use crate::utils::constants::APP_DA_CONFIG_NAME; use crate::utils::paths::get_app_home; -use eyre::Result as EyreResult; #[derive(Debug, Serialize, Deserialize, EnumIter, Display, Clone)] pub enum DALayer { Avail, + Celestia, Ethereum, NoDA, } @@ -28,6 +29,8 @@ pub enum DaError { AvailError(#[from] AvailError), #[error("ethereum error: {0}")] EthereumError(#[from] EthereumError), + #[error("celestia error: {0}")] + CelestiaError(#[from] CelestiaError), #[error("failed to read app home: {0}")] FailedToReadAppHome(io::Error), #[error("inquire error")] @@ -44,7 +47,7 @@ pub enum DaError { #[async_trait] pub trait DaClient { - fn setup_and_generate_keypair(&self, config: &AppChainConfig) -> Result<(), DaError>; + fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError>; fn confirm_minimum_balance(&self, config: &AppChainConfig) -> Result<(), DaError>; @@ -61,6 +64,7 @@ impl DAFactory { pub fn new_da(da: &DALayer) -> Box { match da { DALayer::Avail => Box::new(AvailClient {}), + DALayer::Celestia => Box::new(CelestiaClient {}), DALayer::Ethereum => Box::new(EthereumClient {}), _ => Box::new(NoDAConfig {}), } diff --git a/src/da/ethereum.rs b/src/da/ethereum.rs index 9332ae0..83d693e 100644 --- a/src/da/ethereum.rs +++ b/src/da/ethereum.rs @@ -1,23 +1,21 @@ -use crate::app::config::AppChainConfig; -use crate::da::da_layers::{DaClient, DaError}; -use crate::utils::serde::bytes_from_hex_str; -use async_trait::async_trait; -use eyre::Result as EyreResult; +use std::fs; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; +use async_trait::async_trait; use ethers::contract::abigen; - use ethers::middleware::SignerMiddleware; use ethers::providers::{Http, Provider}; use ethers::signers::{LocalWallet, Signer, WalletError}; - +use eyre::Result as EyreResult; use serde::{Deserialize, Serialize}; -use std::fs; +use thiserror::Error; +use crate::app::config::AppChainConfig; use crate::cli::prompt::get_boolean_input; -use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; -use thiserror::Error; +use crate::da::da_layers::{DaClient, DaError}; +use crate::utils::serde::bytes_from_hex_str; pub struct EthereumClient; @@ -45,7 +43,7 @@ const ANVIL_DOCS: &str = "https://github.com/foundry-rs/foundry/tree/master/crat #[async_trait] impl DaClient for EthereumClient { - fn setup_and_generate_keypair(&self, config: &AppChainConfig) -> Result<(), DaError> { + fn generate_da_config(&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(); @@ -72,7 +70,12 @@ impl DaClient for EthereumClient { async fn setup(&self, config: &AppChainConfig) -> EyreResult<()> { match get_boolean_input( - format!("Are you running an Anvil node locally? The CLI tool has been tested on Anvil version 0.2.0 (c312c0d). Docs: {}", ANVIL_DOCS).as_str(), + format!( + "Are you running an Anvil node locally? The CLI tool has been tested on Anvil version 0.2.0 \ + (c312c0d). Docs: {}", + ANVIL_DOCS + ) + .as_str(), Some(true), )? { true => Ok(()), diff --git a/src/da/mod.rs b/src/da/mod.rs index b7c44f5..9bc4a96 100644 --- a/src/da/mod.rs +++ b/src/da/mod.rs @@ -2,5 +2,8 @@ pub mod da_layers; pub mod avail; +pub mod celestia; + mod ethereum; + pub mod no_da; diff --git a/src/da/no_da.rs b/src/da/no_da.rs index 6c97157..8ada35c 100644 --- a/src/da/no_da.rs +++ b/src/da/no_da.rs @@ -1,12 +1,13 @@ pub struct NoDAConfig; +use async_trait::async_trait; + use crate::app::config::AppChainConfig; use crate::da::da_layers::{DaClient, DaError}; -use async_trait::async_trait; #[async_trait] impl DaClient for NoDAConfig { - fn setup_and_generate_keypair(&self, config: &AppChainConfig) -> Result<(), DaError> { + fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { log::info!("Launching {} without any DA mode", config.app_chain); Ok(()) } diff --git a/src/utils/docker.rs b/src/utils/docker.rs index f4e2bc2..5dd846e 100644 --- a/src/utils/docker.rs +++ b/src/utils/docker.rs @@ -11,10 +11,11 @@ pub async fn run_docker_image( container_name: &str, env: Option>, host_config: Option, + start_cmd: Option>, ) { is_docker_installed().await; log::info!("🐳 Running docker image: {}", image); - match pull_and_start_docker_image(image, container_name, env, host_config).await { + match pull_and_start_docker_image(image, container_name, env, host_config, start_cmd).await { Ok(..) => { log::debug!("Successfully ran {}", container_name); } @@ -72,6 +73,7 @@ pub async fn pull_and_start_docker_image( container_name: &str, env: Option>, host_config: Option, + start_cmd: Option>, ) -> Result<(), Box> { let docker = Docker::connect_with_local_defaults().unwrap(); @@ -80,7 +82,7 @@ pub async fn pull_and_start_docker_image( .try_collect::>() .await?; - let config = Config { image: Some(image), tty: Some(true), env, host_config, ..Default::default() }; + let config = Config { image: Some(image), cmd: start_cmd, tty: Some(true), env, host_config, ..Default::default() }; let container_option = Some(CreateContainerOptions { name: container_name, ..Default::default() }); diff --git a/src/utils/errors.rs b/src/utils/errors.rs index 3ab73f2..2cb0112 100644 --- a/src/utils/errors.rs +++ b/src/utils/errors.rs @@ -1,4 +1,5 @@ use std::ffi::OsString; + use thiserror::Error; #[derive(Debug, Error)] diff --git a/src/utils/paths.rs b/src/utils/paths.rs index 98b1843..4311eed 100644 --- a/src/utils/paths.rs +++ b/src/utils/paths.rs @@ -5,7 +5,10 @@ use std::path::PathBuf; pub fn get_madara_home() -> Result { if let Some(home_dir) = dirs::home_dir() { let madara_home = home_dir.join(".madara"); + + // Creates the `madara_home` directory if not present fs::create_dir_all(&madara_home)?; + return Ok(madara_home); } @@ -26,8 +29,18 @@ pub fn get_app_home(app: &str) -> Result { let app_chains = get_app_chains_home()?; let app_home = app_chains.join(app); - // Creates the $app_home directory if not present + // Creates the `app_home` directory if not present fs::create_dir_all(&app_home)?; Ok(app_home) } + +pub fn get_celestia_home() -> Result { + let madara_home = get_madara_home()?; + let celestia_home = madara_home.join("celestia"); + + // Creates the `celestia` directory if not present + fs::create_dir_all(&celestia_home)?; + + Ok(celestia_home) +} From 35a719ef8b6afda55e1d615356dc8748c09fbfc7 Mon Sep 17 00:00:00 2001 From: Anshal Shukla Date: Tue, 16 Jan 2024 15:35:55 +0530 Subject: [PATCH 2/7] lint: toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8d1a52c..a5e6b52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ hex = { version = "0.4.3", features = [] } inquire = "0.6.2" log = "0.4.20" rand = "0.8.5" +regex = "1.10.2" reqwest = { version = "0.11.23", features = ["json", "blocking"] } serde = { version = "1.0.193", features = ["derive"] } serde_json = "1.0.109" @@ -33,4 +34,3 @@ thiserror = "1.0.52" tokio = { version = "1.35.1", features = ["rt", "rt-multi-thread", "macros"] } toml = "0.8.8" url = "2.5.0" -regex = "1.10.2" From d92eeea78961c4462c05fcdbd02ad275bf7445f2 Mon Sep 17 00:00:00 2001 From: Anshal Shukla Date: Tue, 16 Jan 2024 21:55:24 +0530 Subject: [PATCH 3/7] fix celestia init --- Cargo.lock | 1 + Cargo.toml | 1 + src/cli/init.rs | 8 +++---- src/da/avail.rs | 2 +- src/da/celestia.rs | 51 ++++++++++++++++++++++++++++++++------------- src/da/da_layers.rs | 2 +- src/da/ethereum.rs | 2 +- src/da/no_da.rs | 2 +- src/main.rs | 2 +- src/utils/github.rs | 1 - 10 files changed, 47 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 855650f..e9d8f30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2635,6 +2635,7 @@ dependencies = [ "strum", "strum_macros", "thiserror", + "time", "tokio", "toml", "url", diff --git a/Cargo.toml b/Cargo.toml index a5e6b52..8ce5136 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,3 +34,4 @@ thiserror = "1.0.52" tokio = { version = "1.35.1", features = ["rt", "rt-multi-thread", "macros"] } toml = "0.8.8" url = "2.5.0" +time = "0.3.31" diff --git a/src/cli/init.rs b/src/cli/init.rs index 1a0a945..1ded847 100644 --- a/src/cli/init.rs +++ b/src/cli/init.rs @@ -26,8 +26,8 @@ pub enum InitError { FailedToGenerateKeypair, } -pub fn init() { - let config = match generate_config() { +pub async fn init() { + let config = match generate_config().await { Ok(config) => config, Err(err) => { panic!("Failed to get input: {}", err); @@ -44,7 +44,7 @@ pub fn init() { log::info!("✅ New app chain initialised."); } -fn generate_config() -> Result { +async fn generate_config() -> Result { let app_chain = get_text_input("Enter you app chain name:", Some("madara"))?; let app_chains_home = get_app_chains_home()?; @@ -67,7 +67,7 @@ fn generate_config() -> Result { config_version, }; - match DAFactory::new_da(&da_layer).generate_da_config(&config) { + match DAFactory::new_da(&da_layer).generate_da_config(&config).await { Ok(_) => (), Err(err) => { log::error!("Failed to generate keypair: {}", err); diff --git a/src/da/avail.rs b/src/da/avail.rs index f864df8..ae0777e 100644 --- a/src/da/avail.rs +++ b/src/da/avail.rs @@ -33,7 +33,7 @@ const AVAIL_DOCS: &str = "https://docs.availproject.org/about/faucet/"; #[async_trait] impl DaClient for AvailClient { - fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { + async fn generate_da_config(&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); diff --git a/src/da/celestia.rs b/src/da/celestia.rs index fe97f8d..074062f 100644 --- a/src/da/celestia.rs +++ b/src/da/celestia.rs @@ -11,6 +11,9 @@ use crate::cli::prompt::get_boolean_input; use crate::da::da_layers::{DaClient, DaError}; use crate::utils::docker::{container_exists, kill_container, run_docker_image}; use crate::utils::paths::get_celestia_home; +use std::thread; +use std::time::Duration; + pub struct CelestiaClient; #[derive(Debug, Serialize, Deserialize)] @@ -34,13 +37,26 @@ const CELESTIA_DOCS: &str = "https://docs.celestia.org/developers/celestia-app-w #[async_trait] impl DaClient for CelestiaClient { - fn generate_da_config(&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(); + async fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { let celestia_home = get_celestia_home().unwrap(); let file_keys_txt = celestia_home.join("keys.txt"); let file_auth_txt = celestia_home.join("auth.txt"); + if !file_keys_txt.exists() || !file_auth_txt.exists() { + let run_cmd = vec![ + "sh", + "-c", + "celestia light init --p2p.network=mocha > /home/celestia/keys.txt &&\ + celestia light auth admin --p2p.network=mocha > /home/celestia/auth.txt" + ]; + run_celestia_light_node(run_cmd).await.unwrap(); + // Waits for docker container to execute the commands and generate the keys + thread::sleep(Duration::from_secs(5)); + } + + let file_path = self.get_da_config_path(config)?; + let file_path_str = file_path.to_string_lossy().to_string(); + let keys_txt_content = match fs::read_to_string(file_keys_txt) { Ok(content) => content, Err(_) => { @@ -72,7 +88,7 @@ impl DaClient for CelestiaClient { return Err(DaError::CelestiaError(CelestiaError::SetupError)); } - write_config(file_path_str.as_str(), auth_token.as_str(), address)?; + write_config(file_path_str.as_str(), auth_token.trim(), address)?; Ok(()) } @@ -97,11 +113,16 @@ impl DaClient for CelestiaClient { } async fn setup(&self, _config: &AppChainConfig) -> eyre::Result<()> { - start_celestia_light_node().await + let run_cmd = vec![ + "sh", + "-c", + "celestia light start --core.ip=rpc-mocha.pops.one --p2p.network=mocha", + ]; + run_celestia_light_node(run_cmd).await } } -pub async fn start_celestia_light_node() -> eyre::Result<()> { +pub async fn run_celestia_light_node(run_cmd: Vec<&str>) -> eyre::Result<()> { let celestia_home = get_celestia_home()?; let celestia_home_str = celestia_home.to_str().unwrap_or("~/.madara/celestia"); @@ -124,13 +145,13 @@ pub async fn start_celestia_light_node() -> eyre::Result<()> { ..Default::default() }; - let start_cmd = vec![ - "sh", - "-c", - "celestia light init --p2p.network=mocha > /home/celestia/keys.txt &&celestia light auth admin \ - --p2p.network=mocha > /home/celestia/auth.txt &&celestia light start --core.ip=rpc-mocha.pops.one \ - --p2p.network=mocha", - ]; + // let start_cmd = vec![ + // "sh", + // "-c", + // "celestia light init --p2p.network=mocha > /home/celestia/keys.txt &&celestia light auth admin \ + // --p2p.network=mocha > /home/celestia/auth.txt &&celestia light start --core.ip=rpc-mocha.pops.one \ + // --p2p.network=mocha", + // ]; const CONTAINER_NAME: &str = "celestia-light-client"; @@ -144,10 +165,10 @@ pub async fn start_celestia_light_node() -> eyre::Result<()> { CONTAINER_NAME, Some(env), Some(host_config), - Some(start_cmd), + Some(run_cmd), ) .await; - log::info!("🧭 Celestia light client is running"); + log::info!("🧭 Command ran on Celestia light client\n"); Ok(()) } diff --git a/src/da/da_layers.rs b/src/da/da_layers.rs index 87cadb9..e26d7be 100644 --- a/src/da/da_layers.rs +++ b/src/da/da_layers.rs @@ -47,7 +47,7 @@ pub enum DaError { #[async_trait] pub trait DaClient { - fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError>; + async fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError>; fn confirm_minimum_balance(&self, config: &AppChainConfig) -> Result<(), DaError>; diff --git a/src/da/ethereum.rs b/src/da/ethereum.rs index 83d693e..883c496 100644 --- a/src/da/ethereum.rs +++ b/src/da/ethereum.rs @@ -43,7 +43,7 @@ const ANVIL_DOCS: &str = "https://github.com/foundry-rs/foundry/tree/master/crat #[async_trait] impl DaClient for EthereumClient { - fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { + async fn generate_da_config(&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(); diff --git a/src/da/no_da.rs b/src/da/no_da.rs index 8ada35c..1fe3513 100644 --- a/src/da/no_da.rs +++ b/src/da/no_da.rs @@ -7,7 +7,7 @@ use crate::da::da_layers::{DaClient, DaError}; #[async_trait] impl DaClient for NoDAConfig { - fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { + async fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { log::info!("Launching {} without any DA mode", config.app_chain); Ok(()) } diff --git a/src/main.rs b/src/main.rs index 4d9af55..001f17e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,7 +33,7 @@ async fn main() { let cli = Cli::parse(); match &cli.command { - Some(Commands::Init) => cli::init::init(), + Some(Commands::Init) => cli::init::init().await, Some(Commands::List) => cli::list::list(), Some(Commands::Run) => cli::run::run().await, Some(Commands::Explorer) => cli::explorer::explorer().await, diff --git a/src/utils/github.rs b/src/utils/github.rs index b4bf704..4262967 100644 --- a/src/utils/github.rs +++ b/src/utils/github.rs @@ -16,7 +16,6 @@ struct Commit { pub fn get_latest_commit_hash(org: &str, repo: &str) -> Result { let github_api_url = format!("{}/repos/{}/{}/commits", GITHUB_API_BASE_URL, org, repo); - let client = Client::new(); let response = client.get(github_api_url).header("User-Agent", "reqwest").send(); From a1bdde726694b5f2802c643af220a39d137e863f Mon Sep 17 00:00:00 2001 From: Anshal Shukla Date: Mon, 29 Jan 2024 01:31:32 +0530 Subject: [PATCH 4/7] fix --- src/da/avail.rs | 3 +- src/da/celestia.rs | 81 ++++++++++++++++++++++++--------------------- src/da/da_layers.rs | 2 +- src/da/ethereum.rs | 2 +- src/da/no_da.rs | 3 +- src/utils/docker.rs | 10 ++++++ src/utils/paths.rs | 10 ------ 7 files changed, 59 insertions(+), 52 deletions(-) diff --git a/src/da/avail.rs b/src/da/avail.rs index ae0777e..44673af 100644 --- a/src/da/avail.rs +++ b/src/da/avail.rs @@ -4,6 +4,7 @@ use async_trait::async_trait; use hex::encode; use serde::{Deserialize, Serialize}; use sp_core::{sr25519, Pair}; +use eyre::Result as EyreResult; use thiserror::Error; use crate::app::config::AppChainConfig; @@ -33,7 +34,7 @@ const AVAIL_DOCS: &str = "https://docs.availproject.org/about/faucet/"; #[async_trait] impl DaClient for AvailClient { - async fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { + async fn generate_da_config(&self, config: &AppChainConfig) -> EyreResult<()> { 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); diff --git a/src/da/celestia.rs b/src/da/celestia.rs index 074062f..ed8deef 100644 --- a/src/da/celestia.rs +++ b/src/da/celestia.rs @@ -1,17 +1,20 @@ use std::collections::HashMap; use std::fs; +use std::io::Error; +use std::path::PathBuf; use async_trait::async_trait; use bollard::models::{HostConfig, Mount, PortBinding}; use serde::{Deserialize, Serialize}; +use eyre::Result as EyreResult; +use eyre::Report as EyreReport; use thiserror::Error; use crate::app::config::AppChainConfig; use crate::cli::prompt::get_boolean_input; use crate::da::da_layers::{DaClient, DaError}; -use crate::utils::docker::{container_exists, kill_container, run_docker_image}; -use crate::utils::paths::get_celestia_home; -use std::thread; +use crate::utils::docker::{container_exists, is_container_running, kill_container, run_docker_image}; +use crate::utils::paths::{get_madara_home}; use std::time::Duration; pub struct CelestiaClient; @@ -31,45 +34,47 @@ pub enum CelestiaError { FaucetFundsNeeded, #[error("Celestia light node setup failed")] SetupError, + #[error("Failed to read celestia home")] + FailedToReadCelestiaHome, + #[error("Failed to run in celestia container")] + FailedToRunInCelestiaContainer, } const CELESTIA_DOCS: &str = "https://docs.celestia.org/developers/celestia-app-wallet#fund-a-wallet"; +const CELESTIA_CONTAINER_NAME: &str = "celestia-light-client"; #[async_trait] impl DaClient for CelestiaClient { - async fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { - let celestia_home = get_celestia_home().unwrap(); + async fn generate_da_config(&self, config: &AppChainConfig) -> EyreResult<()> { + let celestia_home = get_celestia_home()?; let file_keys_txt = celestia_home.join("keys.txt"); let file_auth_txt = celestia_home.join("auth.txt"); - if !file_keys_txt.exists() || !file_auth_txt.exists() { + if !file_keys_txt.exists() || !file_auth_txt.exists() { let run_cmd = vec![ "sh", "-c", "celestia light init --p2p.network=mocha > /home/celestia/keys.txt &&\ celestia light auth admin --p2p.network=mocha > /home/celestia/auth.txt" ]; - run_celestia_light_node(run_cmd).await.unwrap(); + exec_cmd_in_celestia_container(run_cmd).await?; // Waits for docker container to execute the commands and generate the keys - thread::sleep(Duration::from_secs(5)); + loop { + let container_exists = is_container_running(&CELESTIA_CONTAINER_NAME).await; + if !container_exists { + break; // Container has exited + } + + // Sleep for a brief period to avoid excessive polling + tokio::time::sleep(Duration::from_secs(1)).await; + } } let file_path = self.get_da_config_path(config)?; let file_path_str = file_path.to_string_lossy().to_string(); - let keys_txt_content = match fs::read_to_string(file_keys_txt) { - Ok(content) => content, - Err(_) => { - return Err(DaError::CelestiaError(CelestiaError::SetupError)); - } - }; - - let auth_token = match fs::read_to_string(file_auth_txt) { - Ok(content) => content, - Err(_) => { - return Err(DaError::CelestiaError(CelestiaError::SetupError)); - } - }; + let keys_txt_content = fs::read_to_string(file_keys_txt)?; + let auth_token = fs::read_to_string(file_auth_txt)?; let mut address: &str = ""; for line in keys_txt_content.lines() { @@ -84,8 +89,8 @@ impl DaClient for CelestiaClient { } } - if address.eq("") || auth_token.eq("") { - return Err(DaError::CelestiaError(CelestiaError::SetupError)); + if address.is_empty()|| auth_token.is_empty() { + return Err(EyreReport::from(DaError::CelestiaError(CelestiaError::SetupError))); } write_config(file_path_str.as_str(), auth_token.trim(), address)?; @@ -118,11 +123,11 @@ impl DaClient for CelestiaClient { "-c", "celestia light start --core.ip=rpc-mocha.pops.one --p2p.network=mocha", ]; - run_celestia_light_node(run_cmd).await + exec_cmd_in_celestia_container(run_cmd).await } } -pub async fn run_celestia_light_node(run_cmd: Vec<&str>) -> eyre::Result<()> { +pub async fn exec_cmd_in_celestia_container(run_cmd: Vec<&str>) -> EyreResult<()> { let celestia_home = get_celestia_home()?; let celestia_home_str = celestia_home.to_str().unwrap_or("~/.madara/celestia"); @@ -145,24 +150,14 @@ pub async fn run_celestia_light_node(run_cmd: Vec<&str>) -> eyre::Result<()> { ..Default::default() }; - // let start_cmd = vec![ - // "sh", - // "-c", - // "celestia light init --p2p.network=mocha > /home/celestia/keys.txt &&celestia light auth admin \ - // --p2p.network=mocha > /home/celestia/auth.txt &&celestia light start --core.ip=rpc-mocha.pops.one \ - // --p2p.network=mocha", - // ]; - - const CONTAINER_NAME: &str = "celestia-light-client"; - - if container_exists(CONTAINER_NAME).await { + if container_exists(CELESTIA_CONTAINER_NAME).await { // TODO: handle error - let _ = kill_container(CONTAINER_NAME).await; + let _ = kill_container(CELESTIA_CONTAINER_NAME).await; } run_docker_image( "ghcr.io/celestiaorg/celestia-node:v0.12.2", - CONTAINER_NAME, + CELESTIA_CONTAINER_NAME, Some(env), Some(host_config), Some(run_cmd), @@ -187,3 +182,13 @@ fn write_config(da_config_path: &str, auth_token: &str, address: &str) -> Result Ok(()) } + +pub fn get_celestia_home() -> Result { + let madara_home = get_madara_home()?; + let celestia_home = madara_home.join("celestia"); + + // Creates the `celestia` directory if not present + fs::create_dir_all(&celestia_home)?; + + Ok(celestia_home) +} diff --git a/src/da/da_layers.rs b/src/da/da_layers.rs index e26d7be..dde110f 100644 --- a/src/da/da_layers.rs +++ b/src/da/da_layers.rs @@ -47,7 +47,7 @@ pub enum DaError { #[async_trait] pub trait DaClient { - async fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError>; + async fn generate_da_config(&self, config: &AppChainConfig) -> EyreResult<()>; fn confirm_minimum_balance(&self, config: &AppChainConfig) -> Result<(), DaError>; diff --git a/src/da/ethereum.rs b/src/da/ethereum.rs index 883c496..593dd75 100644 --- a/src/da/ethereum.rs +++ b/src/da/ethereum.rs @@ -43,7 +43,7 @@ const ANVIL_DOCS: &str = "https://github.com/foundry-rs/foundry/tree/master/crat #[async_trait] impl DaClient for EthereumClient { - async fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { + async fn generate_da_config(&self, config: &AppChainConfig) -> EyreResult<()> { let file_path = self.get_da_config_path(config)?; let file_path_str = file_path.to_string_lossy().to_string(); diff --git a/src/da/no_da.rs b/src/da/no_da.rs index 1fe3513..e8ca1f2 100644 --- a/src/da/no_da.rs +++ b/src/da/no_da.rs @@ -4,10 +4,11 @@ use async_trait::async_trait; use crate::app::config::AppChainConfig; use crate::da::da_layers::{DaClient, DaError}; +use eyre::Result as EyreResult; #[async_trait] impl DaClient for NoDAConfig { - async fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError> { + async fn generate_da_config(&self, config: &AppChainConfig) -> EyreResult<()> { log::info!("Launching {} without any DA mode", config.app_chain); Ok(()) } diff --git a/src/utils/docker.rs b/src/utils/docker.rs index 5dd846e..0bb66bc 100644 --- a/src/utils/docker.rs +++ b/src/utils/docker.rs @@ -60,6 +60,16 @@ pub async fn container_exists(container_name: &str) -> bool { } } +pub async fn is_container_running(container_name: &str) -> bool { + let docker = Docker::connect_with_local_defaults().unwrap(); + + if let Some(state) = docker.inspect_container(container_name, None).await.unwrap_or_default().state { + return state.running.unwrap_or(false); + } + + false +} + pub async fn kill_container(container_name: &str) -> eyre::Result<()> { let docker = Docker::connect_with_local_defaults().unwrap(); // TODO: handle the error diff --git a/src/utils/paths.rs b/src/utils/paths.rs index 4311eed..70bf5d2 100644 --- a/src/utils/paths.rs +++ b/src/utils/paths.rs @@ -34,13 +34,3 @@ pub fn get_app_home(app: &str) -> Result { Ok(app_home) } - -pub fn get_celestia_home() -> Result { - let madara_home = get_madara_home()?; - let celestia_home = madara_home.join("celestia"); - - // Creates the `celestia` directory if not present - fs::create_dir_all(&celestia_home)?; - - Ok(celestia_home) -} From 983b5752edd3bdf4baf0949a38959c9d07578995 Mon Sep 17 00:00:00 2001 From: Anshal Shukla Date: Mon, 29 Jan 2024 01:36:19 +0530 Subject: [PATCH 5/7] fix --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 8ce5136..d32ab97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ sp-core = "27.0.0" strum = { version = "0.25.0", features = ["derive"] } strum_macros = { version = "0.25.3", features = [] } thiserror = "1.0.52" +time = "0.3.31" tokio = { version = "1.35.1", features = ["rt", "rt-multi-thread", "macros"] } toml = "0.8.8" url = "2.5.0" -time = "0.3.31" From 08d7a02a6a79fd004178a9b90067d166f53981b5 Mon Sep 17 00:00:00 2001 From: Anshal Shukla Date: Mon, 29 Jan 2024 01:40:04 +0530 Subject: [PATCH 6/7] fix --- src/da/avail.rs | 2 +- src/da/celestia.rs | 18 +++++++----------- src/utils/docker.rs | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/da/avail.rs b/src/da/avail.rs index a41839d..0191b50 100644 --- a/src/da/avail.rs +++ b/src/da/avail.rs @@ -1,10 +1,10 @@ use std::fs; use async_trait::async_trait; +use eyre::Result as EyreResult; use hex::encode; use serde::{Deserialize, Serialize}; use sp_core::{sr25519, Pair}; -use eyre::Result as EyreResult; use thiserror::Error; use crate::app::config::AppChainConfig; diff --git a/src/da/celestia.rs b/src/da/celestia.rs index ed8deef..8eeadb5 100644 --- a/src/da/celestia.rs +++ b/src/da/celestia.rs @@ -5,16 +5,16 @@ use std::path::PathBuf; use async_trait::async_trait; use bollard::models::{HostConfig, Mount, PortBinding}; -use serde::{Deserialize, Serialize}; -use eyre::Result as EyreResult; use eyre::Report as EyreReport; +use eyre::Result as EyreResult; +use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::app::config::AppChainConfig; use crate::cli::prompt::get_boolean_input; use crate::da::da_layers::{DaClient, DaError}; use crate::utils::docker::{container_exists, is_container_running, kill_container, run_docker_image}; -use crate::utils::paths::{get_madara_home}; +use crate::utils::paths::get_madara_home; use std::time::Duration; pub struct CelestiaClient; @@ -50,12 +50,12 @@ impl DaClient for CelestiaClient { let file_keys_txt = celestia_home.join("keys.txt"); let file_auth_txt = celestia_home.join("auth.txt"); - if !file_keys_txt.exists() || !file_auth_txt.exists() { + if !file_keys_txt.exists() || !file_auth_txt.exists() { let run_cmd = vec![ "sh", "-c", "celestia light init --p2p.network=mocha > /home/celestia/keys.txt &&\ - celestia light auth admin --p2p.network=mocha > /home/celestia/auth.txt" + celestia light auth admin --p2p.network=mocha > /home/celestia/auth.txt", ]; exec_cmd_in_celestia_container(run_cmd).await?; // Waits for docker container to execute the commands and generate the keys @@ -89,7 +89,7 @@ impl DaClient for CelestiaClient { } } - if address.is_empty()|| auth_token.is_empty() { + if address.is_empty() || auth_token.is_empty() { return Err(EyreReport::from(DaError::CelestiaError(CelestiaError::SetupError))); } @@ -118,11 +118,7 @@ impl DaClient for CelestiaClient { } async fn setup(&self, _config: &AppChainConfig) -> eyre::Result<()> { - let run_cmd = vec![ - "sh", - "-c", - "celestia light start --core.ip=rpc-mocha.pops.one --p2p.network=mocha", - ]; + let run_cmd = vec!["sh", "-c", "celestia light start --core.ip=rpc-mocha.pops.one --p2p.network=mocha"]; exec_cmd_in_celestia_container(run_cmd).await } } diff --git a/src/utils/docker.rs b/src/utils/docker.rs index 3a7b1cc..402ffdd 100644 --- a/src/utils/docker.rs +++ b/src/utils/docker.rs @@ -64,7 +64,7 @@ pub async fn is_container_running(container_name: &str) -> bool { let docker = Docker::connect_with_local_defaults().unwrap(); if let Some(state) = docker.inspect_container(container_name, None).await.unwrap_or_default().state { - return state.running.unwrap_or(false); + return state.running.unwrap_or(false); } false From 660c1466b007929e4e15fc79fa2ec6b0183c62cf Mon Sep 17 00:00:00 2001 From: Anshal Shukla Date: Mon, 29 Jan 2024 01:43:22 +0530 Subject: [PATCH 7/7] fix --- src/da/celestia.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/da/celestia.rs b/src/da/celestia.rs index 8eeadb5..6227ec5 100644 --- a/src/da/celestia.rs +++ b/src/da/celestia.rs @@ -60,7 +60,7 @@ impl DaClient for CelestiaClient { exec_cmd_in_celestia_container(run_cmd).await?; // Waits for docker container to execute the commands and generate the keys loop { - let container_exists = is_container_running(&CELESTIA_CONTAINER_NAME).await; + let container_exists = is_container_running(CELESTIA_CONTAINER_NAME).await; if !container_exists { break; // Container has exited }