From 1e6ed14e0508b7cdf6b1551cd918e84044bbc2a9 Mon Sep 17 00:00:00 2001 From: Jonatan Chaverri Date: Sat, 10 Feb 2024 21:57:36 -0600 Subject: [PATCH] feat(saya): add config file arg for saya bin (#1513) * feat(saya): add config file arg for saya bin * fix: minor fixes and valid celestia namespace * fix: adjust config test with new celestia namespace --------- Co-authored-by: Jonatan Chaverri Co-authored-by: glihm --- Cargo.toml | 2 +- bin/saya/src/args/mod.rs | 119 ++++++++++++++---- bin/saya/src/args/test_saya_config_file.json | 11 ++ .../src/data_availability/celestia/mod.rs | 5 +- crates/saya/core/src/data_availability/mod.rs | 3 +- crates/saya/core/src/lib.rs | 11 ++ 6 files changed, 123 insertions(+), 28 deletions(-) create mode 100644 bin/saya/src/args/test_saya_config_file.json diff --git a/Cargo.toml b/Cargo.toml index 434f6a5b1b..f14df97a19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -160,7 +160,7 @@ tokio = { version = "1.32.0", features = [ "full" ] } toml = "0.7.4" tracing = "0.1.34" tracing-subscriber = { version = "0.3.16", features = [ "env-filter", "json" ] } -url = "2.4.0" +url = { version = "2.4.0", features = [ "serde" ] } # server hyper = "0.14.27" diff --git a/bin/saya/src/args/mod.rs b/bin/saya/src/args/mod.rs index 2c3230efbd..149e80c093 100644 --- a/bin/saya/src/args/mod.rs +++ b/bin/saya/src/args/mod.rs @@ -1,4 +1,8 @@ //! Saya binary options. +use std::fs::File; +use std::io::BufReader; +use std::path::PathBuf; + use clap::Parser; use saya_core::data_availability::celestia::CelestiaConfig; use saya_core::data_availability::DataAvailabilityConfig; @@ -19,6 +23,7 @@ pub struct SayaArgs { #[arg(long)] #[arg(value_name = "KATANA URL")] #[arg(help = "The Katana RPC URL to fetch data from.")] + #[arg(default_value = "http://localhost:5050")] pub rpc_url: Url, /// Enable JSON logging. @@ -26,6 +31,13 @@ pub struct SayaArgs { #[arg(help = "Output logs in JSON format.")] pub json_log: bool, + /// Specify a JSON configuration file to use. + #[arg(long)] + #[arg(value_name = "CONFIG FILE")] + #[arg(help = "The path to a JSON configuration file. This takes precedence over other CLI \ + arguments.")] + pub config_file: Option, + /// Specify a block to start fetching data from. #[arg(short, long, default_value = "0")] pub start_block: u64, @@ -54,34 +66,91 @@ impl SayaArgs { } impl TryFrom for SayaConfig { - type Error = &'static str; + type Error = Box; fn try_from(args: SayaArgs) -> Result { - let da_config = match args.data_availability.da_chain { - Some(chain) => Some(match chain { - DataAvailabilityChain::Celestia => { - let conf = args.data_availability.celestia; - - DataAvailabilityConfig::Celestia(CelestiaConfig { - node_url: match conf.celestia_node_url { - Some(v) => v, - None => return Err("Celestia config: Node url is required"), - }, - namespace: match conf.celestia_namespace { - Some(v) => v, - None => return Err("Celestia config: Namespace is required"), - }, - node_auth_token: conf.celestia_node_auth_token, - }) - } - }), - None => None, + if let Some(config_file) = args.config_file { + let file = File::open(config_file).map_err(|_| "Failed to open config file")?; + let reader = BufReader::new(file); + serde_json::from_reader(reader).map_err(|e| e.into()) + } else { + let da_config = match args.data_availability.da_chain { + Some(chain) => Some(match chain { + DataAvailabilityChain::Celestia => { + let conf = args.data_availability.celestia; + + DataAvailabilityConfig::Celestia(CelestiaConfig { + node_url: match conf.celestia_node_url { + Some(v) => v, + None => { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Celestia config: Node url is required", + ))); + } + }, + namespace: match conf.celestia_namespace { + Some(v) => v, + None => { + return Err(Box::new(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Celestia config: Namespace is required", + ))); + } + }, + node_auth_token: conf.celestia_node_auth_token, + }) + } + }), + None => None, + }; + + Ok(SayaConfig { + katana_rpc: args.rpc_url, + start_block: args.start_block, + data_availability: da_config, + }) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::args::data_availability::CelestiaOptions; + + #[test] + fn test_saya_config_deserialization() { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let config_file_path = std::path::Path::new(&manifest_dir) + .join("src") + .join("args") + .join("test_saya_config_file.json"); + + let args = SayaArgs { + config_file: Some(config_file_path.clone()), + rpc_url: Url::parse("http://localhost:5050").unwrap(), + json_log: false, + start_block: 0, + data_availability: DataAvailabilityOptions { + da_chain: None, + celestia: CelestiaOptions { + celestia_node_url: None, + celestia_node_auth_token: None, + celestia_namespace: None, + }, + }, }; + let config: SayaConfig = args.try_into().unwrap(); - Ok(SayaConfig { - katana_rpc: args.rpc_url, - start_block: args.start_block, - data_availability: da_config, - }) + assert_eq!(config.katana_rpc.as_str(), "http://localhost:5050/"); + assert_eq!(config.start_block, 0); + if let Some(DataAvailabilityConfig::Celestia(celestia_config)) = config.data_availability { + assert_eq!(celestia_config.node_url.as_str(), "http://localhost:26657/"); + assert_eq!(celestia_config.node_auth_token, Some("your_auth_token".to_string())); + assert_eq!(celestia_config.namespace, "katana"); + } else { + panic!("Expected Celestia config"); + } } } diff --git a/bin/saya/src/args/test_saya_config_file.json b/bin/saya/src/args/test_saya_config_file.json new file mode 100644 index 0000000000..7fb85a942d --- /dev/null +++ b/bin/saya/src/args/test_saya_config_file.json @@ -0,0 +1,11 @@ +{ + "katana_rpc": "http://localhost:5050", + "start_block": 0, + "data_availability": { + "Celestia": { + "node_url": "http://localhost:26657", + "node_auth_token": "your_auth_token", + "namespace": "katana" + } + } +} diff --git a/crates/saya/core/src/data_availability/celestia/mod.rs b/crates/saya/core/src/data_availability/celestia/mod.rs index efa6f482a7..17bbed633c 100644 --- a/crates/saya/core/src/data_availability/celestia/mod.rs +++ b/crates/saya/core/src/data_availability/celestia/mod.rs @@ -6,14 +6,17 @@ use celestia_rpc::{BlobClient, Client}; use celestia_types::blob::SubmitOptions; use celestia_types::nmt::Namespace; use celestia_types::Blob; +use serde::{Deserialize, Serialize}; use starknet::core::types::FieldElement; use url::Url; use crate::data_availability::error::{DataAvailabilityResult, Error}; use crate::data_availability::{DataAvailabilityClient, DataAvailabilityMode}; +use crate::url_deserializer; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct CelestiaConfig { + #[serde(deserialize_with = "url_deserializer")] pub node_url: Url, pub node_auth_token: Option, pub namespace: String, diff --git a/crates/saya/core/src/data_availability/mod.rs b/crates/saya/core/src/data_availability/mod.rs index 8a4057abbf..356234a4c0 100644 --- a/crates/saya/core/src/data_availability/mod.rs +++ b/crates/saya/core/src/data_availability/mod.rs @@ -6,6 +6,7 @@ use std::fmt::Display; use async_trait::async_trait; +use serde::{Deserialize, Serialize}; use starknet::core::types::FieldElement; pub mod celestia; @@ -15,7 +16,7 @@ pub mod state_diff; use error::DataAvailabilityResult; /// All possible chains configuration for data availability. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub enum DataAvailabilityConfig { Celestia(celestia::CelestiaConfig), } diff --git a/crates/saya/core/src/lib.rs b/crates/saya/core/src/lib.rs index bb5c496db3..ceb58eab2a 100644 --- a/crates/saya/core/src/lib.rs +++ b/crates/saya/core/src/lib.rs @@ -1,6 +1,7 @@ //! Saya core library. use std::sync::Arc; +use serde::{Deserialize, Serialize}; use starknet::core::types::{BlockId, MaybePendingStateUpdate, StateUpdate}; use starknet::providers::jsonrpc::HttpTransport; use starknet::providers::{JsonRpcClient, Provider}; @@ -16,12 +17,22 @@ pub mod prover; pub mod verifier; /// Saya's main configuration. +#[derive(Debug, Deserialize, Serialize)] pub struct SayaConfig { + #[serde(deserialize_with = "url_deserializer")] pub katana_rpc: Url, pub start_block: u64, pub data_availability: Option, } +fn url_deserializer<'de, D>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, +{ + let s = String::deserialize(deserializer)?; + Url::parse(&s).map_err(serde::de::Error::custom) +} + /// Saya. pub struct Saya { /// The main Saya configuration.