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 all 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,12 +23,15 @@ 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"
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"
1 change: 1 addition & 0 deletions src/cli/explorer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pub async fn explorer(opts: &ExplorerOpts) {
CONTAINER_NAME,
Some(env),
Some(host_config),
None,
)
.await;
log::info!("🧭 Explorer is running on http://localhost:4000");
Expand Down
2 changes: 1 addition & 1 deletion src/cli/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async 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
13 changes: 7 additions & 6 deletions src/da/avail.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
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 eyre::Result as EyreResult;
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 +34,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) -> 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) = <sr25519::Pair as Pair>::generate_with_phrase(None);
Expand All @@ -54,7 +55,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 @@ -83,7 +84,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
190 changes: 190 additions & 0 deletions src/da/celestia.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
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 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 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,
#[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) -> 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() {
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",
];
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;
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 = 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() {
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.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)?;

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"];
exec_cmd_in_celestia_container(run_cmd).await
}
}

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");

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()
};

if container_exists(CELESTIA_CONTAINER_NAME).await {
// TODO: handle error
let _ = kill_container(CELESTIA_CONTAINER_NAME).await;
}

run_docker_image(
"ghcr.io/celestiaorg/celestia-node:v0.12.2",
CELESTIA_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(())
}

pub fn get_celestia_home() -> Result<PathBuf, Error> {
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)
}
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) -> EyreResult<()>;

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
31 changes: 17 additions & 14 deletions src/da/ethereum.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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> {
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();

Expand All @@ -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(()),
Expand Down
Loading
Loading