Skip to content

Commit

Permalink
Merge branch 'main' into katana/piltover-update-deser
Browse files Browse the repository at this point in the history
  • Loading branch information
kariy authored Feb 3, 2025
2 parents 3a4ffb3 + d250797 commit ae75910
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 40 deletions.
29 changes: 24 additions & 5 deletions bin/katana/src/cli/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,39 @@ use anyhow::Result;
use clap::Args;
use katana_chain_spec::rollup::file::ChainConfigDir;
use katana_primitives::chain::ChainId;
use starknet::core::utils::parse_cairo_short_string;

#[derive(Debug, Args)]
pub struct ConfigArgs {
/// The chain id.
#[arg(value_parser = ChainId::parse)]
chain: ChainId,
chain: Option<ChainId>,
}

impl ConfigArgs {
pub fn execute(self) -> Result<()> {
let cs = ChainConfigDir::open(&self.chain)?;
let path = cs.config_path();
let config = std::fs::read_to_string(&path)?;
println!("File: {}\n\n{config}", path.display());
match self.chain {
Some(chain) => {
let cs = ChainConfigDir::open(&chain)?;
let path = cs.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()?;
for chain in chains {
// TODO:
// We can't just assume that the id is a valid (and readable) ascii string
// as we don' yet handle that elegently in the `ChainId` type itself. The ids
// returned by `list` will be of the `ChainId::Id` variant and thus
// will display in hex form. But for now, it's fine to assume that because we
// only limit valid ASCII string in the `katana init` flow.
let name = parse_cairo_short_string(&chain.id())?;
println!("{name}");
}
}
}
Ok(())
}
}
166 changes: 131 additions & 35 deletions crates/katana/chain-spec/src/rollup/file.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fs::File;
use std::io::{self, BufReader, BufWriter};
use std::path::PathBuf;
use std::path::{Path, PathBuf};

use katana_primitives::chain::ChainId;
use katana_primitives::genesis::json::GenesisJson;
Expand Down Expand Up @@ -33,7 +33,23 @@ pub enum Error {
}

pub fn read(id: &ChainId) -> Result<ChainSpec, Error> {
let dir = ChainConfigDir::open(id)?;
read_at(local_dir()?, id)
}

pub fn write(chain_spec: &ChainSpec) -> Result<(), Error> {
write_at(local_dir()?, chain_spec)
}

/// List all of the available chain configurations.
///
/// This will list only the configurations that are stored in the default local directory. See
/// [`local_dir`].
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)?;

let chain_spec: ChainSpecFile = {
let content = std::fs::read_to_string(dir.config_path())?;
Expand All @@ -54,8 +70,8 @@ pub fn read(id: &ChainId) -> Result<ChainSpec, Error> {
})
}

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

{
let cfg = ChainSpecFile {
Expand All @@ -77,6 +93,35 @@ pub fn write(chain_spec: &ChainSpec) -> Result<(), Error> {
Ok(())
}

fn list_at<P: AsRef<Path>>(dir: P) -> Result<Vec<ChainId>, Error> {
let mut chains = Vec::new();
let dir = dir.as_ref();

if dir.exists() {
for entry in std::fs::read_dir(dir)? {
let entry = entry?;

// Ignore entry that is:-
//
// - not a directory
// - name can't be parse as chain id
// - config file is not found inside the directory
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");
if cs.config_path().exists() {
chains.push(chain_id);
}
}
}
}
}
}

Ok(chains)
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
struct ChainSpecFile {
Expand All @@ -93,12 +138,31 @@ const KATANA_LOCAL_DIR: &str = "katana";
pub struct ChainConfigDir(PathBuf);

impl ChainConfigDir {
/// Create a new config directory for the given chain ID.
/// 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
/// by [`local_dir`].
///
/// This will create the directory if it does not yet exist.
pub fn create(id: &ChainId) -> Result<Self, Error> {
Self::create_at(local_dir()?, id)
}

/// Opens an existing config directory for the given chain ID.
///
/// The path of the directory is expected to be `$LOCAL_DIR/<id>`, where `$LOCAL_DIR` is the
/// path returned by [`local_dir`].
///
/// # Errors
///
/// This function will return an error if no directory exists with the given chain ID.
pub fn open(id: &ChainId) -> Result<Self, Error> {
Self::open_at(local_dir()?, id)
}

pub fn create_at<P: AsRef<Path>>(dir: P, id: &ChainId) -> Result<Self, Error> {
let id = id.to_string();
let path = local_dir()?.join(id);
let path = dir.as_ref().join(id);

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

/// Open an existing config directory for the given chain ID.
///
/// This will return an error if the no config directory exists for the given chain ID.
pub fn open(id: &ChainId) -> Result<Self, Error> {
pub fn open_at<P: AsRef<Path>>(dir: P, id: &ChainId) -> Result<Self, Error> {
let id = id.to_string();
let path = local_dir()?.join(&id);
let path = dir.as_ref().join(&id);

if !path.exists() {
return Err(Error::DirectoryNotFound { id: id.clone() });
Expand Down Expand Up @@ -151,25 +212,43 @@ pub fn local_dir() -> Result<PathBuf, Error> {

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

use katana_primitives::chain::ChainId;
use katana_primitives::genesis::Genesis;
use katana_primitives::ContractAddress;
use tempfile::TempDir;
use url::Url;

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

static TEMPDIR: OnceLock<TempDir> = OnceLock::new();

// To make sure the path returned by `local_dir` is always the same across
// testes and is created inside of a temp dir
fn init() {
let temp_dir = tempfile::TempDir::new().unwrap();
let path = temp_dir.path();
fn with_temp_dir<T>(f: impl FnOnce(&Path) -> T) -> T {
f(TEMPDIR.get_or_init(|| tempfile::TempDir::new().unwrap()).path())
}

#[cfg(target_os = "linux")]
if std::env::var("XDG_CONFIG_HOME").is_err() {
std::env::set_var("XDG_CONFIG_HOME", path);
/// Test version of [`super::read`].
fn read(id: &ChainId) -> Result<ChainSpec, Error> {
with_temp_dir(|dir| super::read_at(dir, id))
}

/// Test version of [`super::write`].
fn write(chain_spec: &ChainSpec) -> Result<(), Error> {
with_temp_dir(|dir| super::write_at(dir, chain_spec))
}

impl ChainConfigDir {
fn open_tmp(id: &ChainId) -> Result<Self, Error> {
with_temp_dir(|dir| Self::open_at(dir, id))
}

#[cfg(target_os = "macos")]
if std::env::var("HOME").is_err() {
std::env::set_var("HOME", path);
fn create_tmp(id: &ChainId) -> Result<Self, Error> {
with_temp_dir(|dir| Self::create_at(dir, id))
}
}

Expand All @@ -189,8 +268,6 @@ mod tests {

#[test]
fn test_read_write_chainspec() {
init();

let chain_spec = chainspec();
let id = chain_spec.id;

Expand All @@ -204,39 +281,58 @@ mod tests {

#[test]
fn test_chain_config_dir() {
init();

let chain_id = ChainId::parse("test").unwrap();

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

// Test opening existing dir
let opened_dir = ChainConfigDir::open(&chain_id).unwrap();
let opened_dir = ChainConfigDir::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(&bad_id), Err(Error::DirectoryNotFound { .. })));
assert!(matches!(ChainConfigDir::open_tmp(&bad_id), Err(Error::DirectoryNotFound { .. })));
}

#[test]
fn test_local_dir() {
init();

let dir = local_dir().unwrap();
assert!(dir.ends_with(KATANA_LOCAL_DIR));
}

#[test]
fn test_config_paths() {
init();

let chain_id = ChainId::parse("test").unwrap();
let config_dir = ChainConfigDir::create(&chain_id).unwrap();
let config_dir = ChainConfigDir::create_tmp(&chain_id).unwrap();

assert!(config_dir.config_path().ends_with("config.toml"));
assert!(config_dir.genesis_path().ends_with("genesis.json"));
}

#[test]
fn test_list_chain_specs() {
let dir = tempfile::TempDir::new().unwrap().into_path();

let listed_chains = super::list_at(&dir).unwrap();
assert_eq!(listed_chains.len(), 0, "Must be empty initially");

// Create some dummy chain specs
let mut chain_specs = Vec::new();
for i in 1..=3 {
let mut spec = chainspec();
// update the chain id to make they're unqiue
spec.id = ChainId::parse(&format!("chain_{i}")).unwrap();
chain_specs.push(spec);
}

// Write them to disk
for spec in &chain_specs {
super::write_at(&dir, spec).unwrap();
}

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

0 comments on commit ae75910

Please sign in to comment.