diff --git a/Cargo.lock b/Cargo.lock index dc7365dec..4cd54a99d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1857,18 +1857,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "confy" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45b1f4c00870f07dc34adcac82bb6a72cc5aabca8536ba1797e01df51d2ce9a0" -dependencies = [ - "directories", - "serde", - "thiserror", - "toml", -] - [[package]] name = "console" version = "0.15.8" @@ -2416,15 +2404,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "directories" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a49173b84e034382284f27f1af4dcbbd231ffa358c0fe316541a7337f376a35" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs" version = "5.0.1" @@ -6226,7 +6205,6 @@ dependencies = [ "aquamarine", "backon", "clap", - "confy", "discv5", "eyre", "fdlimit", @@ -6551,7 +6529,6 @@ dependencies = [ "backon", "clap", "comfy-table", - "confy", "crossterm", "eyre", "fdlimit", @@ -6658,7 +6635,7 @@ dependencies = [ name = "reth-config" version = "1.0.5" dependencies = [ - "confy", + "eyre", "humantime-serde", "reth-network-peers", "reth-network-types", @@ -7693,7 +7670,6 @@ name = "reth-node-builder" version = "1.0.5" dependencies = [ "aquamarine", - "confy", "eyre", "fdlimit", "futures", @@ -7788,9 +7764,12 @@ dependencies = [ "reth-tracing", "reth-transaction-pool", "secp256k1", + "serde", "serde_json", "shellexpand", + "tempfile", "tokio", + "toml", "tracing", "vergen", ] diff --git a/Cargo.toml b/Cargo.toml index 4e8a30e04..9abb32a0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -527,7 +527,6 @@ secp256k1 = { version = "0.29", default-features = false, features = [ c-kzg = "1.0.0" # config -confy = "0.6" toml = "0.8" # misc-testing diff --git a/bin/reth/Cargo.toml b/bin/reth/Cargo.toml index b9c65365a..2c164530f 100644 --- a/bin/reth/Cargo.toml +++ b/bin/reth/Cargo.toml @@ -83,7 +83,6 @@ tracing.workspace = true fdlimit.workspace = true serde.workspace = true serde_json.workspace = true -confy.workspace = true toml = { workspace = true, features = ["display"] } # metrics diff --git a/crates/cli/commands/Cargo.toml b/crates/cli/commands/Cargo.toml index 673cd1799..b0e4bb750 100644 --- a/crates/cli/commands/Cargo.toml +++ b/crates/cli/commands/Cargo.toml @@ -58,7 +58,6 @@ secp256k1 = { workspace = true, features = ["global-context", "rand-std", "recov # io fdlimit.workspace = true -confy.workspace = true toml = { workspace = true, features = ["display"] } # tui diff --git a/crates/cli/commands/src/common.rs b/crates/cli/commands/src/common.rs index a303b8934..55e4b79f2 100644 --- a/crates/cli/commands/src/common.rs +++ b/crates/cli/commands/src/common.rs @@ -65,7 +65,8 @@ impl EnvironmentArgs { } let config_path = self.config.clone().unwrap_or_else(|| data_dir.config()); - let mut config: Config = confy::load_path(config_path) + + let mut config = Config::from_path(config_path) .inspect_err( |err| warn!(target: "reth::cli", %err, "Failed to load config file, using default"), ) diff --git a/crates/cli/commands/src/config_cmd.rs b/crates/cli/commands/src/config_cmd.rs index 890a00611..85af7384e 100644 --- a/crates/cli/commands/src/config_cmd.rs +++ b/crates/cli/commands/src/config_cmd.rs @@ -25,11 +25,12 @@ impl Command { Config::default() } else { let path = self.config.clone().unwrap_or_default(); - // confy will create the file if it doesn't exist; we don't want this + // Check if the file exists if !path.exists() { bail!("Config file does not exist: {}", path.display()); } - confy::load_path::(&path) + // Read the configuration file + Config::from_path(&path) .wrap_err_with(|| format!("Could not load config file: {}", path.display()))? }; println!("{}", toml::to_string_pretty(&config)?); diff --git a/crates/cli/commands/src/p2p/mod.rs b/crates/cli/commands/src/p2p/mod.rs index 9c1b45eb2..a3e37abc3 100644 --- a/crates/cli/commands/src/p2p/mod.rs +++ b/crates/cli/commands/src/p2p/mod.rs @@ -74,13 +74,15 @@ pub enum Subcommands { // RLPx utilities Rlpx(rlpx::Command), } + impl Command { /// Execute `p2p` command pub async fn execute(self) -> eyre::Result<()> { let data_dir = self.datadir.clone().resolve_datadir(self.chain.chain); let config_path = self.config.clone().unwrap_or_else(|| data_dir.config()); - let mut config: Config = confy::load_path(&config_path).unwrap_or_default(); + // Load configuration + let mut config = Config::from_path(&config_path).unwrap_or_default(); config.peers.trusted_nodes.extend(self.network.trusted_peers.clone()); diff --git a/crates/config/Cargo.toml b/crates/config/Cargo.toml index 7a81bcb4d..f30056f1e 100644 --- a/crates/config/Cargo.toml +++ b/crates/config/Cargo.toml @@ -21,9 +21,9 @@ serde.workspace = true humantime-serde.workspace = true # toml -confy.workspace = true +toml.workspace = true +eyre.workspace = true [dev-dependencies] tempfile.workspace = true -toml.workspace = true reth-network-peers.workspace = true diff --git a/crates/config/src/config.rs b/crates/config/src/config.rs index 2f4daeff7..33076afff 100644 --- a/crates/config/src/config.rs +++ b/crates/config/src/config.rs @@ -1,11 +1,13 @@ //! Configuration files. +use eyre::eyre; use reth_network_types::{PeersConfig, SessionsConfig}; use reth_prune_types::PruneModes; use reth_stages_types::ExecutionStageThresholds; use serde::{Deserialize, Deserializer, Serialize}; use std::{ ffi::OsStr, + fs, path::{Path, PathBuf}, time::Duration, }; @@ -29,6 +31,31 @@ pub struct Config { } impl Config { + /// Load a [`Config`] from a specified path. + /// + /// A new configuration file is created with default values if none + /// exists. + pub fn from_path(path: impl AsRef) -> eyre::Result { + let path = path.as_ref(); + match fs::read_to_string(path) { + Ok(cfg_string) => { + toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}")) + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|e| eyre!("Failed to create directory: {e}"))?; + } + let cfg = Self::default(); + let s = toml::to_string_pretty(&cfg) + .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?; + fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?; + Ok(cfg) + } + Err(e) => Err(eyre!("Failed to load configuration: {e}")), + } + } + /// Returns the [`PeersConfig`] for the node. /// /// If a peers file is provided, the basic nodes from the file are added to the configuration. @@ -48,9 +75,14 @@ impl Config { return Err(std::io::Error::new( std::io::ErrorKind::InvalidInput, format!("reth config file extension must be '{EXTENSION}'"), - )) + )); } - confy::store_path(path, self).map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e)) + + std::fs::write( + path, + toml::to_string(self) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?, + ) } /// Sets the pruning configuration. @@ -384,7 +416,7 @@ where mod tests { use super::{Config, EXTENSION}; use reth_network_peers::TrustedPeer; - use std::{str::FromStr, time::Duration}; + use std::{path::Path, str::FromStr, time::Duration}; fn with_tempdir(filename: &str, proc: fn(&std::path::Path)) { let temp_dir = tempfile::tempdir().unwrap(); @@ -395,11 +427,91 @@ mod tests { temp_dir.close().unwrap() } + /// Run a test function with a temporary config path as fixture. + fn with_config_path(test_fn: fn(&Path)) { + // Create a temporary directory for the config file + let config_dir = tempfile::tempdir().expect("creating test fixture failed"); + // Create the config file path + let config_path = + config_dir.path().join("example-app").join("example-config").with_extension("toml"); + // Run the test function with the config path + test_fn(&config_path); + config_dir.close().expect("removing test fixture failed"); + } + + #[test] + fn test_load_path_works() { + with_config_path(|path| { + let config = Config::from_path(path).expect("load_path failed"); + assert_eq!(config, Config::default()); + }) + } + + #[test] + fn test_load_path_reads_existing_config() { + with_config_path(|path| { + let config = Config::default(); + + // Create the parent directory if it doesn't exist + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).expect("Failed to create directories"); + } + + // Write the config to the file + std::fs::write(path, toml::to_string(&config).unwrap()) + .expect("Failed to write config"); + + // Load the config from the file and compare it + let loaded = Config::from_path(path).expect("load_path failed"); + assert_eq!(config, loaded); + }) + } + + #[test] + fn test_load_path_fails_on_invalid_toml() { + with_config_path(|path| { + let invalid_toml = "invalid toml data"; + + // Create the parent directory if it doesn't exist + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent).expect("Failed to create directories"); + } + + // Write invalid TOML data to the file + std::fs::write(path, invalid_toml).expect("Failed to write invalid TOML"); + + // Attempt to load the config should fail + let result = Config::from_path(path); + assert!(result.is_err()); + }) + } + + #[test] + fn test_load_path_creates_directory_if_not_exists() { + with_config_path(|path| { + // Ensure the directory does not exist + let parent = path.parent().unwrap(); + assert!(!parent.exists()); + + // Load the configuration, which should create the directory and a default config file + let config = Config::from_path(path).expect("load_path failed"); + assert_eq!(config, Config::default()); + + // The directory and file should now exist + assert!(parent.exists()); + assert!(path.exists()); + }); + } + #[test] fn test_store_config() { with_tempdir("config-store-test", |config_path| { let config = Config::default(); - confy::store_path(config_path, config).expect("Failed to store config"); + std::fs::write( + config_path, + toml::to_string(&config).expect("Failed to serialize config"), + ) + .expect("Failed to write config file"); }) } @@ -415,9 +527,18 @@ mod tests { fn test_load_config() { with_tempdir("config-load-test", |config_path| { let config = Config::default(); - confy::store_path(config_path, &config).unwrap(); - let loaded_config: Config = confy::load_path(config_path).unwrap(); + // Write the config to a file + std::fs::write( + config_path, + toml::to_string(&config).expect("Failed to serialize config"), + ) + .expect("Failed to write config file"); + + // Load the config from the file + let loaded_config = Config::from_path(config_path).unwrap(); + + // Compare the loaded config with the original config assert_eq!(config, loaded_config); }) } @@ -427,9 +548,18 @@ mod tests { with_tempdir("config-load-test", |config_path| { let mut config = Config::default(); config.stages.execution.max_duration = Some(Duration::from_secs(10 * 60)); - confy::store_path(config_path, &config).unwrap(); - let loaded_config: Config = confy::load_path(config_path).unwrap(); + // Write the config to a file + std::fs::write( + config_path, + toml::to_string(&config).expect("Failed to serialize config"), + ) + .expect("Failed to write config file"); + + // Load the config from the file + let loaded_config = Config::from_path(config_path).unwrap(); + + // Compare the loaded config with the original config assert_eq!(config, loaded_config); }) } diff --git a/crates/node/builder/Cargo.toml b/crates/node/builder/Cargo.toml index 4e7d5ea61..106337d17 100644 --- a/crates/node/builder/Cargo.toml +++ b/crates/node/builder/Cargo.toml @@ -76,7 +76,6 @@ secp256k1 = { workspace = true, features = [ aquamarine.workspace = true eyre.workspace = true fdlimit.workspace = true -confy.workspace = true rayon.workspace = true # tracing diff --git a/crates/node/builder/src/launch/common.rs b/crates/node/builder/src/launch/common.rs index f96fed209..1b2cfee81 100644 --- a/crates/node/builder/src/launch/common.rs +++ b/crates/node/builder/src/launch/common.rs @@ -117,7 +117,7 @@ impl LaunchContext { pub fn load_toml_config(&self, config: &NodeConfig) -> eyre::Result { let config_path = config.config.clone().unwrap_or_else(|| self.data_dir.config()); - let mut toml_config = confy::load_path::(&config_path) + let mut toml_config = reth_config::Config::from_path(&config_path) .wrap_err_with(|| format!("Could not load config file {config_path:?}"))?; Self::save_pruning_config_if_full_node(&mut toml_config, config, &config_path)?; @@ -970,12 +970,8 @@ mod tests { ) .unwrap(); - assert_eq!( - reth_config.prune.as_ref().map(|p| p.block_interval), - node_config.prune_config().map(|p| p.block_interval) - ); + let loaded_config = Config::from_path(config_path).unwrap(); - let loaded_config: Config = confy::load_path(config_path).unwrap(); assert_eq!(reth_config, loaded_config); }) } diff --git a/crates/node/core/Cargo.toml b/crates/node/core/Cargo.toml index ef3bdd703..585f4a8ea 100644 --- a/crates/node/core/Cargo.toml +++ b/crates/node/core/Cargo.toml @@ -51,6 +51,8 @@ humantime.workspace = true const_format.workspace = true rand.workspace = true derive_more.workspace = true +toml.workspace = true +serde.workspace = true # io dirs-next = "2.0.0" @@ -77,6 +79,7 @@ futures.workspace = true # test vectors generation proptest.workspace = true tokio.workspace = true +tempfile.workspace = true [features] optimism = [ diff --git a/crates/node/core/src/node_config.rs b/crates/node/core/src/node_config.rs index 82ed8b660..176d43e26 100644 --- a/crates/node/core/src/node_config.rs +++ b/crates/node/core/src/node_config.rs @@ -8,10 +8,13 @@ use crate::{ dirs::{ChainPath, DataDirPath}, utils::get_single_header, }; +use eyre::eyre; use reth_chainspec::{ChainSpec, MAINNET}; use reth_config::config::PruneConfig; use reth_db_api::database::Database; use reth_network_p2p::headers::client::HeadersClient; +use serde::{de::DeserializeOwned, Serialize}; +use std::{fs, path::Path}; use reth_primitives::{ revm_primitives::EnvKzgSettings, BlockHashOrNumber, BlockNumber, Head, SealedHeader, B256, @@ -365,6 +368,33 @@ impl NodeConfig { pub fn datadir(&self) -> ChainPath { self.datadir.clone().resolve_datadir(self.chain.chain) } + + /// Load an application configuration from a specified path. + /// + /// A new configuration file is created with default values if none + /// exists. + pub fn load_path( + path: impl AsRef, + ) -> eyre::Result { + let path = path.as_ref(); + match fs::read_to_string(path) { + Ok(cfg_string) => { + toml::from_str(&cfg_string).map_err(|e| eyre!("Failed to parse TOML: {e}")) + } + Err(e) if e.kind() == std::io::ErrorKind::NotFound => { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent) + .map_err(|e| eyre!("Failed to create directory: {e}"))?; + } + let cfg = T::default(); + let s = toml::to_string_pretty(&cfg) + .map_err(|e| eyre!("Failed to serialize to TOML: {e}"))?; + fs::write(path, s).map_err(|e| eyre!("Failed to write configuration file: {e}"))?; + Ok(cfg) + } + Err(e) => Err(eyre!("Failed to load configuration: {e}")), + } + } } impl Default for NodeConfig {