Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

celestia #13

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -32,3 +33,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"
time = "0.3.31"
5 changes: 3 additions & 2 deletions src/cli/explorer.rs
Original file line number Diff line number Diff line change
@@ -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() {
Expand Down Expand Up @@ -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");
Expand Down
8 changes: 4 additions & 4 deletions src/cli/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -44,7 +44,7 @@ pub fn init() {
log::info!("✅ New app chain initialised.");
}

fn generate_config() -> Result<AppChainConfig, InitError> {
async fn generate_config() -> Result<AppChainConfig, InitError> {
let app_chain = get_text_input("Enter you app chain name:", Some("madara"))?;

let app_chains_home = get_app_chains_home()?;
Expand All @@ -67,7 +67,7 @@ fn generate_config() -> Result<AppChainConfig, InitError> {
config_version,
};

match DAFactory::new_da(&da_layer).setup_and_generate_keypair(&config) {
match DAFactory::new_da(&da_layer).generate_da_config(&config).await {
Ok(_) => (),
Err(err) => {
log::error!("Failed to generate keypair: {}", err);
Expand Down
12 changes: 6 additions & 6 deletions src/da/avail.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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> {
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) = <sr25519::Pair as Pair>::generate_with_phrase(None);
Expand All @@ -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(())
}
Expand Down Expand Up @@ -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(),
Expand Down
189 changes: 189 additions & 0 deletions src/da/celestia.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
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;
use std::thread;
use std::time::Duration;

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 {
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 &&\
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how will the computer recognise he celestia command?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh ok, so we are pulling the image during init only

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yup, we can keep on recreating the container and have same state as we are using a volume, I've in mind to prune the volume in case we detected that the setup was not proper

celestia light auth admin --p2p.network=mocha > /home/celestia/auth.txt"
];
run_celestia_light_node(run_cmd).await.unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unwrap, we can use eyre errors as an easy way for handling error, I used it in Ethereum.rs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, why do we need to run the light node here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't run it, I'll refactor it

// Waits for docker container to execute the commands and generate the keys
thread::sleep(Duration::from_secs(5));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there no other way to handle this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if we don't wait for docker to complete the command execution that we called on line 52 the keys.txt and auth.txt files aren't properly formed and is empty when we try to build da_conf.json. But yeah I think we can add some check to await if the container has exited or not because execution time will be system dependent and this is bad code, I was just hacking it to make it work, will update.

}

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 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("") {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is_empty should work ig

return Err(DaError::CelestiaError(CelestiaError::SetupError));
}

write_config(file_path_str.as_str(), auth_token.trim(), 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<()> {
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 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");

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",
// ];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's remove if not needed


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(run_cmd),
)
.await;
log::info!("🧭 Command ran on Celestia light client\n");

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(())
}
14 changes: 9 additions & 5 deletions src/da/da_layers.rs
Original file line number Diff line number Diff line change
@@ -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,
}
Expand All @@ -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")]
Expand All @@ -44,7 +47,7 @@ pub enum DaError {

#[async_trait]
pub trait DaClient {
fn setup_and_generate_keypair(&self, config: &AppChainConfig) -> Result<(), DaError>;
async fn generate_da_config(&self, config: &AppChainConfig) -> Result<(), DaError>;

fn confirm_minimum_balance(&self, config: &AppChainConfig) -> Result<(), DaError>;

Expand All @@ -61,6 +64,7 @@ impl DAFactory {
pub fn new_da(da: &DALayer) -> Box<dyn DaClient> {
match da {
DALayer::Avail => Box::new(AvailClient {}),
DALayer::Celestia => Box::new(CelestiaClient {}),
DALayer::Ethereum => Box::new(EthereumClient {}),
_ => Box::new(NoDAConfig {}),
}
Expand Down
Loading
Loading