Skip to content

Commit

Permalink
katana init output path
Browse files Browse the repository at this point in the history
  • Loading branch information
kariy committed Feb 11, 2025
1 parent da76156 commit 9504dc5
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 49 deletions.
7 changes: 3 additions & 4 deletions bin/katana/src/cli/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use anyhow::Result;
use clap::Args;
use katana_chain_spec::rollup::file::ChainConfigDir;
use katana_chain_spec::rollup::LocalChainConfigDir;
use katana_primitives::chain::ChainId;
use starknet::core::utils::parse_cairo_short_string;

Expand All @@ -15,14 +15,13 @@ impl ConfigArgs {
pub fn execute(self) -> Result<()> {
match self.chain {
Some(chain) => {
let cs = ChainConfigDir::open(&chain)?;
let path = cs.config_path();
let path = LocalChainConfigDir::open(&chain)?.config_path();
let config = std::fs::read_to_string(&path)?;
println!("File: {}\n\n{config}", path.display());
}

None => {
let chains = katana_chain_spec::rollup::file::list()?;
let chains = katana_chain_spec::rollup::list()?;
for chain in chains {
// TODO:
// We can't just assume that the id is a valid (and readable) ascii string
Expand Down
18 changes: 15 additions & 3 deletions bin/katana/src/cli/init/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;

use anyhow::Context;
use clap::Args;
use katana_chain_spec::rollup::FeeContract;
use katana_chain_spec::rollup::{ChainConfigDir, FeeContract};
use katana_chain_spec::{rollup, SettlementLayer};
use katana_primitives::chain::ChainId;
use katana_primitives::genesis::allocation::DevAllocationsGenerator;
Expand Down Expand Up @@ -43,6 +44,10 @@ pub struct InitArgs {
#[arg(long = "settlement-contract")]
#[arg(requires_all = ["id", "settlement_chain", "settlement_account", "settlement_account_private_key"])]
settlement_contract: Option<ContractAddress>,

/// Specify the path of the directory where the configuration files will be stored at.
#[arg(long)]
output_path: Option<PathBuf>,
}

impl InitArgs {
Expand All @@ -66,9 +71,16 @@ impl InitArgs {
let genesis = GENESIS.clone();
// At the moment, the fee token is limited to a predefined token.
let fee_contract = FeeContract::default();

let chain_spec = rollup::ChainSpec { id, genesis, settlement, fee_contract };
rollup::file::write(&chain_spec).context("failed to write chain spec file")?;

if let Some(path) = self.output_path {
let dir = ChainConfigDir::create(path)?;
rollup::write(&dir, &chain_spec).context("failed to write chain spec file")?;
} else {
// Write to the local chain config directory by default if user
// doesn't specify the output path
rollup::write_local(&chain_spec).context("failed to write chain spec file")?;
}

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion bin/katana/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Cli {
#[derive(Subcommand)]
enum Commands {
#[command(about = "Initialize chain")]
Init(init::InitArgs),
Init(Box<init::InitArgs>),

#[command(about = "Chain configuration utilities")]
Config(config::ConfigArgs),
Expand Down
151 changes: 120 additions & 31 deletions crates/katana/chain-spec/src/rollup/file.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::fs::File;
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter};
use std::path::{Path, PathBuf};

Expand All @@ -16,8 +16,11 @@ pub enum Error {
#[error("OS not supported")]
UnsupportedOS,

#[error("config directory not found for chain `{id}`")]
DirectoryNotFound { id: String },
#[error("no local config directory found for chain `{id}`")]
LocalConfigDirectoryNotFound { id: String },

#[error("chain config path must be a directory")]
MustBeADirectory,

#[error("failed to read config file: {0}")]
ConfigReadError(#[from] toml::ser::Error),
Expand All @@ -32,12 +35,14 @@ pub enum Error {
GenesisJson(#[from] katana_primitives::genesis::json::GenesisJsonError),
}

pub fn read(id: &ChainId) -> Result<ChainSpec, Error> {
read_at(local_dir()?, id)
/// Read the [`ChainSpec`] of the given `id` from the local config directory.
pub fn read_local(id: &ChainId) -> Result<ChainSpec, Error> {
read(&ChainConfigDir::open_local(id)?)
}

pub fn write(chain_spec: &ChainSpec) -> Result<(), Error> {
write_at(local_dir()?, chain_spec)
/// Write the given [`ChainSpec`] at the local config directory based on it's id.
pub fn write_local(chain_spec: &ChainSpec) -> Result<(), Error> {
write(&ChainConfigDir::create_local(&chain_spec.id)?, chain_spec)
}

/// List all of the available chain configurations.
Expand All @@ -48,9 +53,7 @@ pub fn list() -> Result<Vec<ChainId>, Error> {
list_at(local_dir()?)
}

fn read_at<P: AsRef<Path>>(dir: P, id: &ChainId) -> Result<ChainSpec, Error> {
let dir = ChainConfigDir::open_at(dir, id)?;

pub fn read(dir: &ChainConfigDir) -> Result<ChainSpec, Error> {
let chain_spec: ChainSpecFile = {
let content = std::fs::read_to_string(dir.config_path())?;
toml::from_str(&content)?
Expand All @@ -70,9 +73,7 @@ fn read_at<P: AsRef<Path>>(dir: P, id: &ChainId) -> Result<ChainSpec, Error> {
})
}

fn write_at<P: AsRef<Path>>(dir: P, chain_spec: &ChainSpec) -> Result<(), Error> {
let dir = ChainConfigDir::create_at(dir, &chain_spec.id)?;

pub fn write(dir: &ChainConfigDir, chain_spec: &ChainSpec) -> Result<(), Error> {
{
let cfg = ChainSpecFile {
id: chain_spec.id,
Expand Down Expand Up @@ -109,7 +110,7 @@ fn list_at<P: AsRef<Path>>(dir: P) -> Result<Vec<ChainId>, Error> {
if entry.file_type()?.is_dir() {
if let Some(name) = entry.file_name().to_str() {
if let Ok(chain_id) = ChainId::parse(name) {
let cs = ChainConfigDir::open_at(dir, &chain_id).expect("must exist");
let cs = LocalChainConfigDir::open_at(dir, &chain_id).expect("must exist");
if cs.config_path().exists() {
chains.push(chain_id);
}
Expand All @@ -133,11 +134,61 @@ struct ChainSpecFile {
/// The local directory name where the chain configuration files are stored.
const KATANA_LOCAL_DIR: &str = "katana";

// > LOCAL_DIR/$chain_id/
#[derive(Debug, Clone)]
pub struct ChainConfigDir(PathBuf);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChainConfigDir {
Absolute(PathBuf),
Local(LocalChainConfigDir),
}

impl ChainConfigDir {
pub fn create_local(id: &ChainId) -> Result<Self, Error> {
Ok(Self::Local(LocalChainConfigDir::create(id)?))
}

pub fn open_local(id: &ChainId) -> Result<Self, Error> {
Ok(Self::Local(LocalChainConfigDir::open(id)?))
}

pub fn create<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let path = path.as_ref();

if !path.exists() {
std::fs::create_dir_all(path)?;
}

Ok(ChainConfigDir::Absolute(path.to_path_buf()))
}

pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let path = fs::canonicalize(path)?;

if !path.is_dir() {
return Err(Error::MustBeADirectory);
}

Ok(Self::Absolute(path.to_path_buf()))
}

pub fn config_path(&self) -> PathBuf {
match self {
Self::Absolute(path) => path.join("config").with_extension("toml"),
Self::Local(local) => local.config_path(),
}
}

pub fn genesis_path(&self) -> PathBuf {
match self {
Self::Absolute(path) => path.join("genesis").with_extension("json"),
Self::Local(local) => local.genesis_path(),
}
}
}

// > LOCAL_DIR/$chain_id/
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LocalChainConfigDir(PathBuf);

impl LocalChainConfigDir {
/// Creates a new config directory for the given chain ID.
///
/// The directory will be created at `$LOCAL_DIR/<id>`, where `$LOCAL_DIR` is the path returned
Expand All @@ -160,9 +211,10 @@ impl ChainConfigDir {
Self::open_at(local_dir()?, id)
}

pub fn create_at<P: AsRef<Path>>(dir: P, id: &ChainId) -> Result<Self, Error> {
/// Same like [`Self::create`] but at a specific base path instead of `$LOCAL_DIR`.
pub fn create_at<P: AsRef<Path>>(base: P, id: &ChainId) -> Result<Self, Error> {
let id = id.to_string();
let path = dir.as_ref().join(id);
let path = base.as_ref().join(id);

if !path.exists() {
std::fs::create_dir_all(&path)?;
Expand All @@ -171,12 +223,13 @@ impl ChainConfigDir {
Ok(Self(path))
}

pub fn open_at<P: AsRef<Path>>(dir: P, id: &ChainId) -> Result<Self, Error> {
/// Same like [`Self::open`] but at a specific base path instead of `$LOCAL_DIR`.
pub fn open_at<P: AsRef<Path>>(base: P, id: &ChainId) -> Result<Self, Error> {
let id = id.to_string();
let path = dir.as_ref().join(&id);
let path = base.as_ref().join(&id);

if !path.exists() {
return Err(Error::DirectoryNotFound { id: id.clone() });
return Err(Error::LocalConfigDirectoryNotFound { id: id.clone() });
}

Ok(Self(path))
Expand Down Expand Up @@ -212,6 +265,7 @@ pub fn local_dir() -> Result<PathBuf, Error> {

#[cfg(test)]
mod tests {
use std::fs;
use std::path::Path;
use std::sync::OnceLock;

Expand All @@ -222,7 +276,7 @@ mod tests {
use url::Url;

use super::Error;
use crate::rollup::file::{local_dir, ChainConfigDir, KATANA_LOCAL_DIR};
use crate::rollup::file::{local_dir, ChainConfigDir, LocalChainConfigDir, KATANA_LOCAL_DIR};
use crate::rollup::{ChainSpec, FeeContract};
use crate::SettlementLayer;

Expand All @@ -234,15 +288,21 @@ mod tests {

/// Test version of [`super::read`].
fn read(id: &ChainId) -> Result<ChainSpec, Error> {
with_temp_dir(|dir| super::read_at(dir, id))
with_temp_dir(|dir| {
let dir = LocalChainConfigDir::open_at(dir, id)?;
super::read(&ChainConfigDir::Local(dir))
})
}

/// Test version of [`super::write`].
fn write(chain_spec: &ChainSpec) -> Result<(), Error> {
with_temp_dir(|dir| super::write_at(dir, chain_spec))
with_temp_dir(|dir| {
let dir = LocalChainConfigDir::create_at(dir, &chain_spec.id)?;
super::write(&ChainConfigDir::Local(dir), chain_spec)
})
}

impl ChainConfigDir {
impl LocalChainConfigDir {
fn open_tmp(id: &ChainId) -> Result<Self, Error> {
with_temp_dir(|dir| Self::open_at(dir, id))
}
Expand Down Expand Up @@ -284,16 +344,19 @@ mod tests {
let chain_id = ChainId::parse("test").unwrap();

// Test creation
let config_dir = ChainConfigDir::create_tmp(&chain_id).unwrap();
let config_dir = LocalChainConfigDir::create_tmp(&chain_id).unwrap();
assert!(config_dir.0.exists());

// Test opening existing dir
let opened_dir = ChainConfigDir::open_tmp(&chain_id).unwrap();
let opened_dir = LocalChainConfigDir::open_tmp(&chain_id).unwrap();
assert_eq!(config_dir.0, opened_dir.0);

// Test opening non-existent dir
let bad_id = ChainId::parse("nonexistent").unwrap();
assert!(matches!(ChainConfigDir::open_tmp(&bad_id), Err(Error::DirectoryNotFound { .. })));
assert!(matches!(
LocalChainConfigDir::open_tmp(&bad_id),
Err(Error::LocalConfigDirectoryNotFound { .. })
));
}

#[test]
Expand All @@ -305,7 +368,7 @@ mod tests {
#[test]
fn test_config_paths() {
let chain_id = ChainId::parse("test").unwrap();
let config_dir = ChainConfigDir::create_tmp(&chain_id).unwrap();
let config_dir = LocalChainConfigDir::create_tmp(&chain_id).unwrap();

assert!(config_dir.config_path().ends_with("config.toml"));
assert!(config_dir.genesis_path().ends_with("genesis.json"));
Expand All @@ -329,10 +392,36 @@ mod tests {

// Write them to disk
for spec in &chain_specs {
super::write_at(&dir, spec).unwrap();
let id = &spec.id;
let dir = LocalChainConfigDir::create_at(&dir, id).unwrap();
super::write(&ChainConfigDir::Local(dir), spec).unwrap();
}

let listed_chains = super::list_at(&dir).unwrap();
assert_eq!(listed_chains.len(), chain_specs.len());
}

#[test]
fn test_absolute_chain_config_dir() {
let temp_dir = tempfile::tempdir().unwrap();
let path = temp_dir.path();

// Test creating absolute dir
let chain_dir = ChainConfigDir::create(path).unwrap();
match &chain_dir {
ChainConfigDir::Absolute(p) => assert_eq!(p, &path),
_ => panic!("Expected Absolute variant"),
}

// Test opening existing absolute dir
let opened_dir = ChainConfigDir::open(path).unwrap();
match opened_dir {
ChainConfigDir::Absolute(p) => assert_eq!(p, fs::canonicalize(path).unwrap()),
_ => panic!("Expected Absolute variant"),
}

// Test error on non-existent dir
let bad_path = path.join("nonexistent");
assert!(matches!(ChainConfigDir::open(&bad_path), Err(Error::IO(..))));
}
}
3 changes: 2 additions & 1 deletion crates/katana/chain-spec/src/rollup/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use katana_primitives::genesis::Genesis;
use katana_primitives::version::CURRENT_STARKNET_VERSION;
use serde::{Deserialize, Serialize};

pub mod file;
mod file;
mod utils;

pub use file::*;
pub use utils::DEFAULT_APPCHAIN_FEE_TOKEN_ADDRESS;

use crate::SettlementLayer;
Expand Down
Loading

0 comments on commit 9504dc5

Please sign in to comment.