diff --git a/Cargo.lock b/Cargo.lock index b70c91641..1eb2d96ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -717,67 +717,18 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "config" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" -dependencies = [ - "async-trait", - "convert_case", - "json5", - "lazy_static", - "nom", - "pathdiff", - "ron", - "rust-ini", - "serde", - "serde_json", - "toml 0.8.12", - "yaml-rust", -] - [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "const-random" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" -dependencies = [ - "const-random-macro", -] - -[[package]] -name = "const-random-macro" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" -dependencies = [ - "getrandom", - "once_cell", - "tiny-keccak", -] - [[package]] name = "constant_time_eq" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "cookie" version = "0.18.1" @@ -989,12 +940,6 @@ version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - [[package]] name = "crypto-common" version = "0.1.6" @@ -1262,15 +1207,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "dlv-list" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" -dependencies = [ - "const-random", -] - [[package]] name = "document-features" version = "0.2.8" @@ -1581,7 +1517,6 @@ dependencies = [ "chacha20poly1305", "chrono", "clap", - "config", "cookie", "crossbeam", "ctrlc", @@ -1603,6 +1538,7 @@ dependencies = [ "parking_lot", "pav_regression", "pico-args", + "pkcs1", "rand", "redb", "reqwest", @@ -1620,6 +1556,7 @@ dependencies = [ "time", "tokio", "tokio-tungstenite", + "toml 0.8.12", "tower-http", "tracing", "tracing-opentelemetry", @@ -1929,12 +1866,6 @@ dependencies = [ "ahash 0.7.8", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" - [[package]] name = "hashbrown" version = "0.14.5" @@ -2359,17 +2290,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json5" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" -dependencies = [ - "pest", - "pest_derive", - "serde", -] - [[package]] name = "kqueue" version = "1.0.8" @@ -2450,12 +2370,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" version = "0.4.13" @@ -3014,16 +2928,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ordered-multimap" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" -dependencies = [ - "dlv-list", - "hashbrown 0.13.2", -] - [[package]] name = "overload" version = "0.1.1" @@ -3065,12 +2969,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - [[package]] name = "pav_regression" version = "0.4.0" @@ -3096,51 +2994,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560131c633294438da9f7c4b08189194b20946c8274c6b9e38881a7874dc8ee8" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26293c9193fbca7b1a3bf9b79dc1e388e927e6cacaa78b4a3ab705a1d3d41459" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ec22af7d3fb470a85dd2ca96b7c577a1eb4ef6f1683a9fe9a8c16e136c04687" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.63", -] - -[[package]] -name = "pest_meta" -version = "2.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a240022f37c361ec1878d646fc5b7d7c4d28d5946e1a80ad5a7a4f4ca0bdcd" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - [[package]] name = "pico-args" version = "0.5.0" @@ -3646,18 +3499,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "ron" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" -dependencies = [ - "base64 0.21.7", - "bitflags 2.5.0", - "serde", - "serde_derive", -] - [[package]] name = "rsa" version = "0.9.6" @@ -3679,16 +3520,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rust-ini" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" -dependencies = [ - "cfg-if", - "ordered-multimap", -] - [[package]] name = "rustc-demangle" version = "0.1.24" @@ -4639,15 +4470,6 @@ dependencies = [ "time-core", ] -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - [[package]] name = "tinyvec" version = "1.6.0" @@ -5024,12 +4846,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - [[package]] name = "ulid" version = "1.1.2" @@ -5856,15 +5672,6 @@ dependencies = [ "lzma-sys", ] -[[package]] -name = "yaml-rust" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" -dependencies = [ - "linked-hash-map", -] - [[package]] name = "zerocopy" version = "0.7.34" diff --git a/crates/core/Cargo.toml b/crates/core/Cargo.toml index 16ad68193..4fe7b0f2b 100644 --- a/crates/core/Cargo.toml +++ b/crates/core/Cargo.toml @@ -27,7 +27,6 @@ cache-padded = "1.3" chacha20poly1305 = { workspace = true } chrono = { workspace = true } clap = { features = ["derive", "env"], workspace = true } -config = { features = ["toml"], version = "0.14" } cookie = "0.18" crossbeam = { workspace = true } ctrlc = { features = ["termination"], workspace = true } @@ -49,6 +48,7 @@ rand = { features = ["small_rng"], workspace = true } redb = { optional = true, version = "2" } serde = { features = ["derive", "rc"], workspace = true } serde_json = { workspace = true } +toml = "0.8" serde_with = { workspace = true } sqlx = { features = ["runtime-tokio-rustls", "sqlite"], optional = true, version = "0.7" } stretto = { features = ["async", "sync"], version = "0.8" } @@ -63,7 +63,8 @@ unsigned-varint = { version = "0.8", features = ["codec", "asynchronous_codec"] wasmer = { features = ["sys"], workspace = true } xz2 = { version = "0.1" } reqwest = { version = "0.12", features = ["json"] } -rsa = { version = "0.9.6", features = ["serde"] } +rsa = { version = "0.9", features = ["serde", "pem"] } +pkcs1 = { version = "0.7", features = ["std", "pem"] } # Tracing deps opentelemetry = "0.22" diff --git a/crates/core/src/bin/freenet.rs b/crates/core/src/bin/freenet.rs index 64a672775..006b7609e 100644 --- a/crates/core/src/bin/freenet.rs +++ b/crates/core/src/bin/freenet.rs @@ -1,39 +1,37 @@ use clap::Parser; use freenet::{ - local_node::{Executor, OperationMode, PeerCliConfig}, + config::{Config, ConfigArgs}, + local_node::{Executor, OperationMode}, server::{local_node::run_local_node, network_node::run_network_node}, }; use std::net::SocketAddr; type DynError = Box; -async fn run(config: PeerCliConfig) -> Result<(), DynError> { +async fn run(config: Config) -> Result<(), DynError> { match config.mode { OperationMode::Local => run_local(config).await, OperationMode::Network => run_network(config).await, } } -async fn run_local(config: PeerCliConfig) -> Result<(), DynError> { - let port = config.port; - let ip = config.address; - freenet::config::Config::set_op_mode(OperationMode::Local); - let executor = Executor::from_config(config, None).await?; +async fn run_local(config: Config) -> Result<(), DynError> { + let port = config.http_gateway.port; + let ip = config.http_gateway.address; + let executor = Executor::from_config(&config, None).await?; let socket: SocketAddr = (ip, port).into(); run_local_node(executor, socket).await } -async fn run_network(config: PeerCliConfig) -> Result<(), DynError> { - let port = config.port; - let ip = config.address; - freenet::config::Config::set_op_mode(OperationMode::Network); - +async fn run_network(config: Config) -> Result<(), DynError> { + let port = config.http_gateway.port; + let ip = config.http_gateway.address; run_network_node(config, (ip, port).into()).await } fn main() -> Result<(), DynError> { freenet::config::set_logger(None); - let config = PeerCliConfig::parse(); + let config = ConfigArgs::parse().build()?; let rt = tokio::runtime::Builder::new_multi_thread() .worker_threads(4) .enable_all() diff --git a/crates/core/src/config.rs b/crates/core/src/config.rs index 6bc13a9da..10bf25618 100644 --- a/crates/core/src/config.rs +++ b/crates/core/src/config.rs @@ -1,16 +1,17 @@ use std::{ fs::{self, File}, future::Future, - io::Read, - net::{IpAddr, Ipv4Addr, SocketAddr}, + io::{Read, Write}, + net::{IpAddr, Ipv4Addr}, path::PathBuf, - str::FromStr, sync::atomic::AtomicBool, time::Duration, }; use directories::ProjectDirs; use once_cell::sync::Lazy; +use pkcs1::DecodeRsaPrivateKey; +use serde::{Deserialize, Serialize}; use tokio::runtime::Runtime; use crate::{local_node::OperationMode, transport::TransportKeypair}; @@ -29,10 +30,6 @@ pub const DEFAULT_RANDOM_PEER_CONN_THRESHOLD: usize = 7; /// Default maximum number of hops to live for any operation /// (if it applies, e.g. connect requests). pub const DEFAULT_MAX_HOPS_TO_LIVE: usize = 10; -const DEFAULT_WEBSOCKET_API_PORT: u16 = 55008; - -static CONFIG: std::sync::OnceLock = std::sync::OnceLock::new(); - pub(crate) const OPERATION_TTL: Duration = Duration::from_secs(60); // Initialize the executor once. @@ -42,62 +39,375 @@ const QUALIFIER: &str = ""; const ORGANIZATION: &str = "The Freenet Project Inc"; const APPLICATION: &str = "Freenet"; +#[derive(clap::Parser, Debug, Serialize, Deserialize)] +pub struct ConfigArgs { + /// Node operation mode. Default is network mode. + #[clap(value_enum, env = "MODE")] + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, + + #[clap(flatten)] + #[serde(flatten)] + pub http_gateway: GatewayArgs, + #[clap(value_parser, env = "TRANSPORT_KEYPAIR")] + pub transport_keypair: Option, + #[serde( + with = "serde_option_log_level_filter", + skip_serializing_if = "Option::is_none" + )] + #[clap(long, env = "LOG_LEVEL")] + pub log_level: Option, + #[clap(flatten)] + #[serde(flatten)] + config_paths: ConfigPathsArgs, +} + +impl Default for ConfigArgs { + fn default() -> Self { + Self { + mode: Some(OperationMode::Network), + http_gateway: GatewayArgs { + address: Some(default_gateway_address()), + port: Some(default_gateway_port()), + }, + transport_keypair: None, + log_level: Some(tracing::log::LevelFilter::Info), + config_paths: Default::default(), + } + } +} + +impl ConfigArgs { + fn read_config(dir: &PathBuf) -> std::io::Result> { + if dir.exists() { + let mut dir = std::fs::read_dir(dir)?; + let config_args = dir.find_map(|f| { + if let Ok(f) = f { + let filename = f.file_name().to_string_lossy().into_owned(); + let ext = filename.rsplit('.').next().map(|s| s.to_owned()); + + if let Some(ext) = ext { + if filename.starts_with("config") { + match ext.as_str() { + "toml" => { + return Some((filename, ext)); + } + "json" => { + return Some((filename, ext)); + } + _ => {} + } + } + } + } + + None + }); + match config_args { + Some((filename, ext)) => match ext.as_str() { + "toml" => { + let mut file = File::open(&*filename)?; + let mut content = String::new(); + file.read_to_string(&mut content)?; + Ok(Some(toml::from_str::(&content).map_err(|e| { + std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()) + })?)) + } + "json" => { + let mut file = File::open(&*filename)?; + Ok(Some(serde_json::from_reader::<_, Self>(&mut file)?)) + } + ext => Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + format!("Invalid configuration file extension: {}", ext), + )), + }, + None => Ok(None), + } + } else { + Ok(None) + } + } + + /// Parse the command line arguments and return the configuration. + pub fn build(mut self) -> std::io::Result { + let cfg = if let Some(path) = self.config_paths.config_dir.as_ref() { + if !path.exists() { + return Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Configuration directory not found", + )); + } + + Self::read_config(path)? + } else { + // find default application dir to see if there is a config file + let dir = ConfigPathsArgs::config_dir()?; + Self::read_config(&dir)? + }; + + let should_persist = cfg.is_none(); + + // merge the configuration from the file with the command line arguments + if let Some(cfg) = cfg { + if self.transport_keypair.is_none() { + self.transport_keypair = cfg.transport_keypair; + } + + if self.mode.is_none() { + self.mode = cfg.mode; + } + + if self.http_gateway.address.is_none() { + self.http_gateway.address = cfg.http_gateway.address; + } + + if self.http_gateway.port.is_none() { + self.http_gateway.port = cfg.http_gateway.port; + } + + if self.log_level.is_none() { + self.log_level = cfg.log_level; + } + + self.config_paths.merge(cfg.config_paths); + } + + let mode = self.mode.unwrap_or(OperationMode::Network); + + let this = Config { + mode, + http_gateway: GatewayConfig { + address: self.http_gateway.address.unwrap_or_else(|| match mode { + OperationMode::Local => default_local_gateway_address(), + OperationMode::Network => default_gateway_address(), + }), + port: self.http_gateway.port.unwrap_or(default_gateway_port()), + }, + transport_keypair: match self.transport_keypair { + Some(path_to_key) => { + let mut key_file = File::open(&path_to_key).map_err(|e| { + std::io::Error::new( + e.kind(), + format!("Failed to open key file {}: {e}", path_to_key.display()), + ) + })?; + let mut buf = String::new(); + key_file.read_to_string(&mut buf).map_err(|e| { + std::io::Error::new( + e.kind(), + format!("Failed to read key file {}: {e}", path_to_key.display()), + ) + })?; + + let pk = rsa::RsaPrivateKey::from_pkcs1_pem(&buf).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Failed to read key file {}: {e}", path_to_key.display()), + ) + })?; + + TransportKeypair::from_private_key(pk) + } + None => TransportKeypair::new(), + }, + log_level: self.log_level.unwrap_or(tracing::log::LevelFilter::Info), + config_paths: self.config_paths.build()?, + }; + + if should_persist { + let mut file = File::create(this.config_paths.config_dir.join("config.toml"))?; + file.write_all( + toml::to_string(&this) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))? + .as_bytes(), + )?; + } + + Ok(this) + } +} + +mod serde_log_level_filter { + use serde::{Deserialize, Deserializer, Serializer}; + use tracing::log::LevelFilter; + + pub fn parse_log_level_str<'a, D>(level: &str) -> Result + where + D: serde::Deserializer<'a>, + { + Ok(match level.trim() { + "off" | "Off" | "OFF" => LevelFilter::Off, + "error" | "Error" | "ERROR" => LevelFilter::Error, + "warn" | "Warn" | "WARN" => LevelFilter::Warn, + "info" | "Info" | "INFO" => LevelFilter::Info, + "debug" | "Debug" | "DEBUG" => LevelFilter::Debug, + "trace" | "Trace" | "TRACE" => LevelFilter::Trace, + s => return Err(serde::de::Error::custom(format!("unknown log level: {s}"))), + }) + } + + pub fn serialize(level: &LevelFilter, serializer: S) -> Result + where + S: Serializer, + { + let level = match level { + LevelFilter::Off => "off", + LevelFilter::Error => "error", + LevelFilter::Warn => "warn", + LevelFilter::Info => "info", + LevelFilter::Debug => "debug", + LevelFilter::Trace => "trace", + }; + serializer.serialize_str(level) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let level = <&str>::deserialize(deserializer)?; + parse_log_level_str::(level) + } +} + +mod serde_option_log_level_filter { + use serde::{Deserialize, Deserializer, Serializer}; + use tracing::log::LevelFilter; + + pub fn serialize(level: &Option, serializer: S) -> Result + where + S: Serializer, + { + if let Some(level) = level { + super::serde_log_level_filter::serialize(level, serializer) + } else { + serializer.serialize_none() + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let level = >::deserialize(deserializer)?; + + match level { + Some(level) => Ok(Some( + super::serde_log_level_filter::parse_log_level_str::(level)?, + )), + None => Ok(None), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] pub struct Config { + /// Node operation mode. + pub mode: OperationMode, + + #[serde(flatten)] + pub http_gateway: GatewayConfig, pub transport_keypair: TransportKeypair, + #[serde(with = "serde_log_level_filter")] pub log_level: tracing::log::LevelFilter, + #[serde(flatten)] config_paths: ConfigPaths, - local_mode: AtomicBool, +} - #[cfg(feature = "websocket")] - #[allow(unused)] - pub(crate) ws: WebSocketApiConfig, +impl Config { + pub fn transport_keypair(&self) -> &TransportKeypair { + &self.transport_keypair + } } -#[cfg(feature = "websocket")] -#[derive(Debug, Copy, Clone)] -pub(crate) struct WebSocketApiConfig { - ip: IpAddr, - port: u16, +#[derive(clap::Parser, Debug, Default, Copy, Clone, Serialize, Deserialize)] +pub struct GatewayArgs { + /// Address to bind to, default is 0.0.0.0 + #[arg(long = "gateway-address", env = "GATEWAY_ADDRESS")] + #[serde(rename = "gateway-address", skip_serializing_if = "Option::is_none")] + pub address: Option, + + /// Port to expose api on, default is 50509 + #[arg(long = "gateway-port", env = "GATEWAY_PORT")] + #[serde(rename = "gateway-port", skip_serializing_if = "Option::is_none")] + pub port: Option, } -#[cfg(feature = "websocket")] -impl From for SocketAddr { - fn from(val: WebSocketApiConfig) -> Self { - (val.ip, val.port).into() - } +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub struct GatewayConfig { + /// Address to bind to + #[serde(default = "default_gateway_address", rename = "gateway-address")] + pub address: IpAddr, + + /// Port to expose api on + #[serde(default = "default_gateway_port", rename = "gateway-port")] + pub port: u16, } -#[cfg(feature = "websocket")] -impl WebSocketApiConfig { - fn from_config(config: &config::Config) -> Self { - WebSocketApiConfig { - ip: IpAddr::from_str( - &config - .get_string("websocket_api_ip") - .unwrap_or_else(|_| format!("{}", Ipv4Addr::LOCALHOST)), - ) - .map_err(|_err| std::io::ErrorKind::InvalidInput) - .unwrap(), - port: config - .get_int("websocket_api_port") - .map(u16::try_from) - .unwrap_or(Ok(DEFAULT_WEBSOCKET_API_PORT)) - .map_err(|_err| std::io::ErrorKind::InvalidInput) - .unwrap(), +impl Default for GatewayConfig { + #[inline] + fn default() -> Self { + Self { + address: default_gateway_address(), + port: default_gateway_port(), } } } -#[derive(Debug)] -pub struct ConfigPaths { - contracts_dir: PathBuf, - delegates_dir: PathBuf, - secrets_dir: PathBuf, - db_dir: PathBuf, - event_log: PathBuf, +#[inline] +const fn default_gateway_address() -> IpAddr { + IpAddr::V4(Ipv4Addr::UNSPECIFIED) } -impl ConfigPaths { +#[inline] +const fn default_local_gateway_address() -> IpAddr { + IpAddr::V4(Ipv4Addr::LOCALHOST) +} + +#[inline] +const fn default_gateway_port() -> u16 { + 50509 +} + +#[derive(clap::Parser, Default, Debug, Clone, Serialize, Deserialize)] +pub struct ConfigPathsArgs { + config_dir: Option, + contracts_dir: Option, + delegates_dir: Option, + secrets_dir: Option, + db_dir: Option, + event_log: Option, + data_dir: Option, +} + +impl ConfigPathsArgs { + fn merge(&mut self, other: Self) { + if self.contracts_dir.is_none() { + self.contracts_dir = other.contracts_dir; + } + + if self.delegates_dir.is_none() { + self.delegates_dir = other.delegates_dir; + } + + if self.secrets_dir.is_none() { + self.secrets_dir = other.secrets_dir; + } + + if self.db_dir.is_none() { + self.db_dir = other.db_dir; + } + + if self.event_log.is_none() { + self.event_log = other.event_log; + } + + if self.data_dir.is_none() { + self.data_dir = other.data_dir; + } + } + pub fn app_data_dir() -> std::io::Result { let project_dir = ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION) .ok_or(std::io::ErrorKind::NotFound)?; @@ -109,12 +419,29 @@ impl ConfigPaths { Ok(app_data_dir) } - fn new(data_dir: Option) -> std::io::Result { - let app_data_dir = data_dir.map(Ok).unwrap_or_else(Self::app_data_dir)?; - let contracts_dir = app_data_dir.join("contracts"); - let delegates_dir = app_data_dir.join("delegates"); - let secrets_dir = app_data_dir.join("secrets"); - let db_dir = app_data_dir.join("db"); + pub fn config_dir() -> std::io::Result { + let project_dir = ProjectDirs::from(QUALIFIER, ORGANIZATION, APPLICATION) + .ok_or(std::io::ErrorKind::NotFound)?; + let config_data_dir: PathBuf = if cfg!(any(test, debug_assertions)) { + std::env::temp_dir().join("freenet").join("config") + } else { + project_dir.config_dir().into() + }; + Ok(config_data_dir) + } + + pub fn build(self) -> std::io::Result { + let app_data_dir = self.data_dir.map(Ok).unwrap_or_else(Self::app_data_dir)?; + let contracts_dir = self + .contracts_dir + .unwrap_or_else(|| app_data_dir.join("contracts")); + let delegates_dir = self + .delegates_dir + .unwrap_or_else(|| app_data_dir.join("delegates")); + let secrets_dir = self + .secrets_dir + .unwrap_or_else(|| app_data_dir.join("secrets")); + let db_dir = self.db_dir.unwrap_or_else(|| app_data_dir.join("db")); if !contracts_dir.exists() { fs::create_dir_all(&contracts_dir)?; @@ -144,119 +471,125 @@ impl ConfigPaths { fs::write(local_file, [])?; } - Ok(Self { + Ok(ConfigPaths { contracts_dir, delegates_dir, secrets_dir, db_dir, + data_dir: app_data_dir, event_log, + config_dir: match self.config_dir { + Some(dir) => dir, + None => Self::config_dir()?, + }, }) } } -impl Config { - pub fn set_op_mode(mode: OperationMode) { - let local_mode = matches!(mode, OperationMode::Local); - Self::conf() - .local_mode - .store(local_mode, std::sync::atomic::Ordering::SeqCst); - } +#[derive(Debug, Serialize, Deserialize)] +pub struct ConfigPaths { + contracts_dir: PathBuf, + delegates_dir: PathBuf, + secrets_dir: PathBuf, + db_dir: PathBuf, + event_log: PathBuf, + data_dir: PathBuf, + config_dir: PathBuf, +} - pub fn db_dir(&self) -> PathBuf { - if self.local_mode.load(std::sync::atomic::Ordering::SeqCst) { - self.config_paths.db_dir.join("local") - } else { - self.config_paths.db_dir.to_owned() +impl ConfigPaths { + pub fn db_dir(&self, mode: OperationMode) -> PathBuf { + match mode { + OperationMode::Local => self.db_dir.join("local"), + OperationMode::Network => self.db_dir.to_owned(), } } - pub fn contracts_dir(&self) -> PathBuf { - if self.local_mode.load(std::sync::atomic::Ordering::SeqCst) { - self.config_paths.contracts_dir.join("local") - } else { - self.config_paths.contracts_dir.to_owned() - } + pub fn with_db_dir(mut self, db_dir: PathBuf) -> Self { + self.db_dir = db_dir; + self } - pub fn delegates_dir(&self) -> PathBuf { - if self.local_mode.load(std::sync::atomic::Ordering::SeqCst) { - self.config_paths.delegates_dir.join("local") - } else { - self.config_paths.delegates_dir.to_owned() + pub fn contracts_dir(&self, mode: OperationMode) -> PathBuf { + match mode { + OperationMode::Local => self.contracts_dir.join("local"), + OperationMode::Network => self.contracts_dir.to_owned(), } } - pub fn secrets_dir(&self) -> PathBuf { - if self.local_mode.load(std::sync::atomic::Ordering::SeqCst) { - self.config_paths.secrets_dir.join("local") - } else { - self.config_paths.delegates_dir.to_owned() + pub fn with_contract_dir(mut self, contracts_dir: PathBuf) -> Self { + self.contracts_dir = contracts_dir; + self + } + + pub fn delegates_dir(&self, mode: OperationMode) -> PathBuf { + match mode { + OperationMode::Local => self.delegates_dir.join("local"), + OperationMode::Network => self.delegates_dir.to_owned(), } } - pub fn event_log(&self) -> PathBuf { - if self.local_mode.load(std::sync::atomic::Ordering::SeqCst) { - let mut local_file = self.config_paths.event_log.clone(); - local_file.set_file_name("_EVENT_LOG_LOCAL"); - local_file - } else { - self.config_paths.event_log.to_owned() + pub fn with_delegates_dir(mut self, delegates_dir: PathBuf) -> Self { + self.delegates_dir = delegates_dir; + self + } + + pub fn config_dir(&self) -> PathBuf { + self.config_dir.clone() + } + + pub fn secrets_dir(&self, mode: OperationMode) -> PathBuf { + match mode { + OperationMode::Local => self.secrets_dir.join("local"), + OperationMode::Network => self.secrets_dir.to_owned(), } } - pub fn conf() -> &'static Config { - CONFIG.get_or_init(|| match Config::load_conf() { - Ok(config) => config, - Err(err) => { - tracing::error!("failed while loading configuration: {err}"); - panic!("Failed while loading configuration") + pub fn with_secrets_dir(mut self, secrets_dir: PathBuf) -> Self { + self.secrets_dir = secrets_dir; + self + } + + pub fn event_log(&self, mode: OperationMode) -> PathBuf { + match mode { + OperationMode::Local => { + let mut local_file = self.event_log.clone(); + local_file.set_file_name("_EVENT_LOG_LOCAL"); + local_file } - }) + OperationMode::Network => self.event_log.to_owned(), + } } - fn load_conf() -> anyhow::Result { - let settings: config::Config = config::Config::builder() - .add_source(config::Environment::with_prefix("FREENET")) - .build() - .unwrap(); + pub fn with_event_log(mut self, event_log: PathBuf) -> Self { + self.event_log = event_log; + self + } +} - let transport_keypair: Option = if let Ok(path_to_key) = settings - .get_string("local_peer_key_file") - .map(PathBuf::from) - { - let mut key_file = File::open(&path_to_key).unwrap_or_else(|_| { - panic!( - "Failed to open key file: {}", - &path_to_key.to_str().unwrap() - ) - }); - let mut buf = Vec::new(); - key_file.read_to_end(&mut buf).unwrap(); - todo!("get an rsa private key from the file and create a TransportKeypair") - } else { - None - }; +impl Config { + pub fn db_dir(&self) -> PathBuf { + self.config_paths.db_dir(self.mode) + } - let log_level = settings - .get_string("log") - .map(|lvl| lvl.parse().ok()) - .ok() - .flatten() - .unwrap_or(tracing::log::LevelFilter::Info); - - let data_dir = settings.get_string("data_dir").ok().map(PathBuf::from); - let config_paths = ConfigPaths::new(data_dir)?; - - let local_mode = settings.get_string("network_mode").is_err(); - - Ok(Config { - transport_keypair: transport_keypair.unwrap_or_default(), - log_level, - config_paths, - local_mode: AtomicBool::new(local_mode), - #[cfg(feature = "websocket")] - ws: WebSocketApiConfig::from_config(&settings), - }) + pub fn contracts_dir(&self) -> PathBuf { + self.config_paths.contracts_dir(self.mode) + } + + pub fn delegates_dir(&self) -> PathBuf { + self.config_paths.delegates_dir(self.mode) + } + + pub fn secrets_dir(&self) -> PathBuf { + self.config_paths.secrets_dir(self.mode) + } + + pub fn event_log(&self) -> PathBuf { + self.config_paths.event_log(self.mode) + } + + pub fn config_dir(&self) -> PathBuf { + self.config_paths.config_dir() } } diff --git a/crates/core/src/contract/executor.rs b/crates/core/src/contract/executor.rs index ab617c544..eb21f2227 100644 --- a/crates/core/src/contract/executor.rs +++ b/crates/core/src/contract/executor.rs @@ -16,8 +16,10 @@ use freenet_stdlib::client_api::{ RequestError, }; use freenet_stdlib::prelude::*; +use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::{self}; +use crate::config::Config; use crate::message::Transaction; use crate::node::OpManager; #[cfg(any( @@ -33,7 +35,6 @@ use crate::wasm_runtime::{ }; use crate::{ client_events::{ClientId, HostResult}, - node::PeerCliConfig, operations::{self, Operation}, DynError, }; @@ -161,7 +162,8 @@ impl From> for ExecutorError { type Response = Result; -#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq)] +#[derive(clap::ValueEnum, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] pub enum OperationMode { /// Run the node in local-only mode. Useful for development purposes. Local, @@ -484,7 +486,7 @@ impl Executor { } async fn get_stores( - config: &PeerCliConfig, + config: &Config, ) -> Result< ( ContractStore, @@ -496,31 +498,14 @@ impl Executor { > { const MAX_SIZE: i64 = 10 * 1024 * 1024; const MAX_MEM_CACHE: u32 = 10_000_000; - let static_conf = crate::config::Config::conf(); - - let db_path = crate::config::Config::conf().db_dir(); - let state_store = StateStore::new(Storage::new(&db_path).await?, MAX_MEM_CACHE).unwrap(); - - let contract_dir = config - .node_data_dir - .as_ref() - .map(|d| d.join("contracts")) - .unwrap_or_else(|| static_conf.contracts_dir()); - let contract_store = ContractStore::new(contract_dir, MAX_SIZE)?; - - let delegate_dir = config - .node_data_dir - .as_ref() - .map(|d| d.join("delegates")) - .unwrap_or_else(|| static_conf.delegates_dir()); - let delegate_store = DelegateStore::new(delegate_dir, MAX_SIZE)?; - - let secrets_dir = config - .node_data_dir - .as_ref() - .map(|d| d.join("secrets")) - .unwrap_or_else(|| static_conf.secrets_dir()); - let secret_store = SecretsStore::new(secrets_dir)?; + + let state_store = + StateStore::new(Storage::new(&config.db_dir()).await?, MAX_MEM_CACHE).unwrap(); + let contract_store = ContractStore::new(config.contracts_dir(), MAX_SIZE)?; + + let delegate_store = DelegateStore::new(config.delegates_dir(), MAX_SIZE)?; + + let secret_store = SecretsStore::new(config.secrets_dir())?; Ok((contract_store, delegate_store, secret_store, state_store)) } diff --git a/crates/core/src/contract/executor/runtime.rs b/crates/core/src/contract/executor/runtime.rs index 2ca0b03bc..9ab0836c1 100644 --- a/crates/core/src/contract/executor/runtime.rs +++ b/crates/core/src/contract/executor/runtime.rs @@ -161,11 +161,11 @@ impl ContractExecutor for Executor { impl Executor { pub async fn from_config( - config: PeerCliConfig, + config: &Config, event_loop_channel: Option>, ) -> Result { let (contract_store, delegate_store, secret_store, state_store) = - Self::get_stores(&config).await?; + Self::get_stores(config).await?; let rt = Runtime::build(contract_store, delegate_store, secret_store, false).unwrap(); Executor::new( state_store, diff --git a/crates/core/src/contract/handler.rs b/crates/core/src/contract/handler.rs index e4869a3dc..dd500741a 100644 --- a/crates/core/src/contract/handler.rs +++ b/crates/core/src/contract/handler.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::future::Future; use std::hash::Hash; use std::sync::atomic::{AtomicU64, Ordering::SeqCst}; +use std::sync::Arc; use std::time::Duration; use freenet_stdlib::client_api::{ClientError, ClientRequest, HostResponse}; @@ -16,8 +17,9 @@ use super::{ ContractError, }; use crate::client_events::HostResult; +use crate::config::Config; use crate::message::Transaction; -use crate::{client_events::ClientId, node::PeerCliConfig, wasm_runtime::Runtime, DynError}; +use crate::{client_events::ClientId, wasm_runtime::Runtime, DynError}; pub(crate) struct ClientResponsesReceiver(UnboundedReceiver<(ClientId, HostResult)>); @@ -83,7 +85,7 @@ pub(crate) struct NetworkContractHandler { } impl ContractHandler for NetworkContractHandler { - type Builder = PeerCliConfig; + type Builder = Arc; type ContractExecutor = Executor; async fn build( @@ -94,7 +96,7 @@ impl ContractHandler for NetworkContractHandler { where Self: Sized + 'static, { - let executor = Executor::from_config(config, Some(executor_request_sender)).await?; + let executor = Executor::from_config(&config, Some(executor_request_sender)).await?; Ok(Self { executor, channel }) } @@ -414,8 +416,6 @@ impl std::fmt::Display for ContractHandlerEvent { #[cfg(test)] pub mod test { - use std::sync::Arc; - use freenet_stdlib::prelude::*; use super::*; diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 623309fe9..17fe0a6c7 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -36,7 +36,6 @@ pub mod local_node { use super::*; pub use contract::Executor; pub use contract::OperationMode; - pub use node::PeerCliConfig; } /// Exports for the dev tool. @@ -54,7 +53,7 @@ pub mod dev_tool { testing_impl::{ EventChain, NetworkPeer, NodeLabel, PeerMessage, PeerStatus, SimNetwork, SimPeer, }, - InitPeerNode, InterProcessConnManager, NodeConfig, PeerCliConfig, PeerId, + InitPeerNode, InterProcessConnManager, NodeConfig, PeerId, }; pub use ring::Location; pub use transport::TransportKeypair; diff --git a/crates/core/src/node.rs b/crates/core/src/node.rs index 909c86d2e..2cdad7ba2 100644 --- a/crates/core/src/node.rs +++ b/crates/core/src/node.rs @@ -8,12 +8,10 @@ //! - in-memory: a simplifying node used for emulation purposes mainly. //! - inter-process: similar to in-memory, but can be rana cross multiple processes, closer to the real p2p impl -use std::net::SocketAddr; use std::{ fmt::Display, io::Write, - net::{IpAddr, Ipv4Addr}, - path::PathBuf, + net::{IpAddr, SocketAddr}, sync::Arc, time::Duration, }; @@ -28,13 +26,12 @@ use serde::{Deserialize, Serialize}; use tracing::Instrument; use self::p2p_impl::NodeP2P; -use crate::message::{MessageStats, NetMessageV1}; use crate::{ client_events::{BoxedClient, ClientEventsProxy, ClientId, OpenRequest}, config::GlobalExecutor, contract::{ Callback, ClientResponsesReceiver, ClientResponsesSender, ContractError, - ExecutorToEventLoopChannel, NetworkContractHandler, OperationMode, + ExecutorToEventLoopChannel, NetworkContractHandler, }, message::{NetMessage, NodeEvent, Transaction, TransactionType}, operations::{ @@ -46,6 +43,10 @@ use crate::{ tracing::{EventRegister, NetEventLog, NetEventRegister}, DynError, }; +use crate::{ + config::Config, + message::{MessageStats, NetMessageV1}, +}; use crate::operations::handle_op_request; pub use network_bridge::inter_process::InterProcessConnManager; @@ -60,23 +61,6 @@ mod op_state_manager; mod p2p_impl; pub(crate) mod testing_impl; -#[derive(clap::Parser, Clone, Debug)] -pub struct PeerCliConfig { - /// Node operation mode. - #[clap(value_enum, default_value_t=OperationMode::Local)] - pub mode: OperationMode, - /// Overrides the default data directory where Freenet contract files are stored. - pub node_data_dir: Option, - - /// Address to bind to - #[arg(long, short, default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] - pub address: IpAddr, - - /// Port to expose the websocket API on - #[arg(long, short, default_value_t = 50509)] - pub port: u16, -} - pub struct Node(NodeP2P); impl Node { @@ -109,7 +93,8 @@ pub struct NodeConfig { pub local_ip: Option, /// socket port to bind to the network listener. pub local_port: Option, - /// IP dialers should connect to, only set on gateways + pub(crate) config: Arc, + /// IP dialers should connect to pub(crate) public_ip: Option, /// socket port dialers should connect to, only set on gateways pub(crate) public_port: Option, @@ -127,12 +112,13 @@ pub struct NodeConfig { } impl NodeConfig { - pub fn new() -> NodeConfig { + pub fn new(config: Config) -> NodeConfig { NodeConfig { should_connect: true, is_gateway: false, key_pair: None, remote_nodes: Vec::with_capacity(1), + config: Arc::new(config), local_ip: None, local_port: None, public_ip: None, @@ -147,6 +133,10 @@ impl NodeConfig { } } + pub fn config(&self) -> &Config { + &self.config + } + pub fn is_gateway(&mut self) -> &mut Self { self.is_gateway = true; self @@ -210,7 +200,6 @@ impl NodeConfig { /// Builds a node using the default backend connection manager. pub async fn build( self, - config: PeerCliConfig, clients: [BoxedClient; CLIENTS], private_key: TransportKeypair, ) -> Result { @@ -219,23 +208,22 @@ impl NodeConfig { { use super::tracing::{CombinedRegister, OTEventRegister}; CombinedRegister::new([ - Box::new(EventRegister::new( - crate::config::Config::conf().event_log(), - )), + Box::new(EventRegister::new(self.config.event_log())), Box::new(OTEventRegister::new()), ]) } #[cfg(not(feature = "trace-ot"))] { - EventRegister::new(crate::config::Config::conf().event_log()) + EventRegister::new(self.config.event_log()) } }; + let cfg = self.config.clone(); let node = NodeP2P::build::( self, private_key, clients, event_register, - config, + cfg, ) .await?; Ok(Node(node)) @@ -244,7 +232,7 @@ impl NodeConfig { pub fn get_peer_id(&self) -> Option { match (self.key_pair.as_ref(), self.local_ip, self.local_port) { (Some(kp), Some(ip), Some(port)) => { - Some(PeerId::new(SocketAddr::new(ip, port), kp.public.clone())) + Some(PeerId::new(SocketAddr::new(ip, port), kp.public().clone())) } _ => None, } @@ -272,12 +260,6 @@ impl NodeConfig { } } -impl Default for NodeConfig { - fn default() -> Self { - Self::new() - } -} - /// Gateway node to use for joining the network. #[derive(Clone, Serialize, Deserialize, Debug)] pub struct InitPeerNode { @@ -899,7 +881,7 @@ impl PeerId { impl<'a> arbitrary::Arbitrary<'a> for PeerId { fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { let addr: ([u8; 4], u16) = u.arbitrary()?; - let pub_key = TransportKeypair::new().public; // FIXME: impl arbitrary for TransportPublicKey + let pub_key = TransportKeypair::new().public().clone(); // FIXME: impl arbitrary for TransportPublicKey Ok(Self { addr: addr.into(), pub_key, @@ -913,7 +895,7 @@ impl PeerId { let mut addr = [0; 4]; rand::thread_rng().fill(&mut addr[..]); let port = crate::util::get_free_port().unwrap(); - let pub_key = TransportKeypair::new().public; + let pub_key = TransportKeypair::new().public().clone(); Self { addr: (addr, port).into(), pub_key, diff --git a/crates/core/src/node/p2p_impl.rs b/crates/core/src/node/p2p_impl.rs index 492b4e590..4cc7d3aa6 100644 --- a/crates/core/src/node/p2p_impl.rs +++ b/crates/core/src/node/p2p_impl.rs @@ -74,7 +74,7 @@ impl NodeP2P { { let keypair = config.key_pair.clone().unwrap_or_default(); // FIXME: pass downn this keypair to the network listener - let peer_pub_key = keypair.public.clone(); + let peer_pub_key = keypair.public().clone(); let (notification_channel, notification_tx) = event_loop_notification_channel(); let (ch_outbound, ch_inbound, wait_for_event) = contract::contract_handler_channel(); diff --git a/crates/core/src/node/testing_impl.rs b/crates/core/src/node/testing_impl.rs index 183763575..51b181244 100644 --- a/crates/core/src/node/testing_impl.rs +++ b/crates/core/src/node/testing_impl.rs @@ -20,7 +20,7 @@ use tracing::{info, Instrument}; use crate::tracing::CombinedRegister; use crate::{ client_events::test::{MemoryEventsGen, RandomEventGenerator}, - config::GlobalExecutor, + config::{ConfigArgs, GlobalExecutor}, contract::{ self, ContractHandlerChannel, ExecutorToEventLoopChannel, NetworkEventListenerHalve, WaitingResolution, @@ -375,10 +375,10 @@ impl SimNetwork { let label = NodeLabel::gateway(node_no); let port = crate::util::get_free_port().unwrap(); let keypair = crate::transport::TransportKeypair::new(); - let id = PeerId::new((Ipv6Addr::LOCALHOST, port).into(), keypair.public.clone()); + let id = PeerId::new((Ipv6Addr::LOCALHOST, port).into(), keypair.public().clone()); let location = Location::random(); - let mut config = NodeConfig::new(); + let mut config = NodeConfig::new(ConfigArgs::default().build().unwrap()); config .with_key_pair(keypair) .with_ip(Ipv6Addr::LOCALHOST) @@ -449,7 +449,7 @@ impl SimNetwork { let keypair: crate::dev_tool::TransportKeypair = crate::transport::TransportKeypair::new(); - let mut config = NodeConfig::new(); + let mut config = NodeConfig::new(ConfigArgs::default().build().unwrap()); for GatewayConfig { id, location, .. } in &gateways { config.add_gateway(InitPeerNode::new(id.clone(), *location)); } diff --git a/crates/core/src/node/testing_impl/inter_process.rs b/crates/core/src/node/testing_impl/inter_process.rs index 308a733a0..48d8958bf 100644 --- a/crates/core/src/node/testing_impl/inter_process.rs +++ b/crates/core/src/node/testing_impl/inter_process.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use tracing::Instrument; use crate::{ - config::GlobalExecutor, + config::{Config, GlobalExecutor}, contract::{self, ContractHandler, MemoryContractHandler}, dev_tool::{ClientEventsProxy, NodeConfig}, node::{ @@ -21,7 +21,11 @@ pub struct SimPeer { } impl SimPeer { - pub async fn start_child(self, event_generator: UsrEv) -> Result<(), anyhow::Error> + pub async fn start_child( + self, + config: &Config, + event_generator: UsrEv, + ) -> Result<(), anyhow::Error> where UsrEv: ClientEventsProxy + Send + 'static, { @@ -30,15 +34,13 @@ impl SimPeer { { use crate::tracing::{CombinedRegister, OTEventRegister}; CombinedRegister::new([ - Box::new(EventRegister::new( - crate::config::Config::conf().event_log(), - )), + Box::new(EventRegister::new(config.event_log())), Box::new(OTEventRegister::new()), ]) } #[cfg(not(feature = "trace-ot"))] { - EventRegister::new(crate::config::Config::conf().event_log()) + EventRegister::new(config.event_log()) } }; self.run_node(event_generator, event_register).await diff --git a/crates/core/src/node/testing_impl/network.rs b/crates/core/src/node/testing_impl/network.rs index 61b51bb7e..187baf715 100644 --- a/crates/core/src/node/testing_impl/network.rs +++ b/crates/core/src/node/testing_impl/network.rs @@ -74,15 +74,13 @@ impl NetworkPeer { { use crate::tracing::OTEventRegister; crate::tracing::CombinedRegister::new([ - Box::new(EventRegister::new( - crate::config::Config::conf().event_log(), - )), + Box::new(EventRegister::new(self.config.config.event_log())), Box::new(OTEventRegister::new()), ]) } #[cfg(not(feature = "trace-ot"))] { - EventRegister::new(crate::config::Config::conf().event_log()) + EventRegister::new(self.config.config.event_log()) } }; let node = NodeP2P::build::( diff --git a/crates/core/src/ring.rs b/crates/core/src/ring.rs index 8cd2c8b59..2dd20a764 100644 --- a/crates/core/src/ring.rs +++ b/crates/core/src/ring.rs @@ -260,7 +260,7 @@ impl Ring { let peer_pub_key = config .key_pair .as_ref() - .map(|kp| kp.public.clone()) + .map(|kp| kp.public().clone()) .ok_or(anyhow::anyhow!("Key pair should be set at this point"))?; let peer_key = config.get_peer_id(); diff --git a/crates/core/src/server.rs b/crates/core/src/server.rs index dd61522c6..0151bc4b9 100644 --- a/crates/core/src/server.rs +++ b/crates/core/src/server.rs @@ -185,28 +185,22 @@ pub mod network_node { use tower_http::trace::TraceLayer; use crate::{ - client_events::websocket::WebSocketProxy, dev_tool::NodeConfig, local_node::PeerCliConfig, - transport::TransportKeypair, DynError, + client_events::websocket::WebSocketProxy, config::Config, dev_tool::NodeConfig, DynError, }; use super::{http_gateway::HttpGateway, serve}; - pub async fn run_network_node( - config: PeerCliConfig, - socket: SocketAddr, - ) -> Result<(), DynError> { + pub async fn run_network_node(config: Config, socket: SocketAddr) -> Result<(), DynError> { let (gw, gw_router) = HttpGateway::as_router(&socket); let (ws_proxy, ws_router) = WebSocketProxy::as_router(gw_router); serve(socket, ws_router.layer(TraceLayer::new_for_http())); - let mut node_config = NodeConfig::new(); + let mut node_config = NodeConfig::new(config); node_config.with_ip(socket.ip()).with_port(socket.port()); - - let private_key = TransportKeypair::new(); - + let private_key = node_config.key_pair.clone().unwrap_or_default(); let is_gateway = node_config.is_gateway; let node = node_config - .build(config, [Box::new(gw), Box::new(ws_proxy)], private_key) + .build([Box::new(gw), Box::new(ws_proxy)], private_key) .await?; match node.run().await { diff --git a/crates/core/src/transport/crypto.rs b/crates/core/src/transport/crypto.rs index c4e6abea0..290be8831 100644 --- a/crates/core/src/transport/crypto.rs +++ b/crates/core/src/transport/crypto.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct TransportKeypair { - pub(crate) public: TransportPublicKey, - pub(crate) secret: TransportSecretKey, + pub(super) public: TransportPublicKey, + pub(super) secret: TransportSecretKey, } impl Default for TransportKeypair { @@ -27,6 +27,17 @@ impl TransportKeypair { secret: TransportSecretKey(priv_key), } } + + pub fn from_private_key(priv_key: RsaPrivateKey) -> Self { + TransportKeypair { + public: TransportPublicKey(RsaPublicKey::from(&priv_key)), + secret: TransportSecretKey(priv_key), + } + } + + pub fn public(&self) -> &TransportPublicKey { + &self.public + } } #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, Hash)] diff --git a/crates/core/src/util.rs b/crates/core/src/util.rs index 8fa8a260e..07969dcfb 100644 --- a/crates/core/src/util.rs +++ b/crates/core/src/util.rs @@ -17,7 +17,7 @@ pub fn set_cleanup_on_exit() -> Result<(), ctrlc::Error> { ctrlc::set_handler(move || { tracing::info!("Received Ctrl+C. Cleaning up..."); - let Ok(path) = crate::config::ConfigPaths::app_data_dir() else { + let Ok(path) = crate::config::ConfigPathsArgs::app_data_dir() else { std::process::exit(0); }; tracing::info!("Removing content stored at {path:?}"); diff --git a/crates/fdev/Cargo.toml b/crates/fdev/Cargo.toml index 6fcd9067c..f2d753bd5 100644 --- a/crates/fdev/Cargo.toml +++ b/crates/fdev/Cargo.toml @@ -28,7 +28,7 @@ semver = { workspace = true } tar = "0.4" thiserror = "1" tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros", "signal", "parking_lot", "process"] } -tokio-tungstenite = "0.21.0" +tokio-tungstenite = "0.21" toml = { version = "0.8", features = ["default", "preserve_order"] } tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } diff --git a/crates/fdev/src/commands.rs b/crates/fdev/src/commands.rs index ada6c306f..8afc9fa1e 100644 --- a/crates/fdev/src/commands.rs +++ b/crates/fdev/src/commands.rs @@ -1,24 +1,18 @@ -use std::{fs::File, io::Read, path::PathBuf}; - -use freenet::dev_tool::{ - ClientId, Config, ContractStore, DelegateStore, Executor, OperationMode, SecretsStore, - StateStore, Storage, +use std::{ + fs::File, + io::Read, + net::{IpAddr, SocketAddr}, + path::PathBuf, }; + +use freenet::dev_tool::OperationMode; use freenet_stdlib::{ - client_api::{ClientRequest, ContractRequest, DelegateRequest}, + client_api::{ClientRequest, ContractRequest, DelegateRequest, WebApi}, prelude::*, }; -// use freenet_runtime::{ -// ContractContainer, ContractInstanceId, ContractStore, DelegateContainer, DelegateStore, -// Parameters, SecretsStore, StateStore, -// }; use crate::config::{BaseConfig, PutConfig, UpdateConfig}; -const MAX_MEM_CACHE: u32 = 10_000_000; -const DEFAULT_MAX_CONTRACT_SIZE: i64 = 50 * 1024 * 1024; -const DEFAULT_MAX_DELEGATE_SIZE: i64 = 50 * 1024 * 1024; - #[derive(Debug, Clone, clap::Subcommand)] pub(crate) enum PutType { /// Puts a new contract @@ -34,7 +28,7 @@ pub(crate) struct PutContract { pub(crate) related_contracts: Option, /// A path to the initial state for the contract being published. #[arg(long)] - pub(crate) state: PathBuf, + pub(crate) state: Option, } #[derive(clap::Parser, Clone, Debug)] @@ -71,10 +65,13 @@ async fn put_contract( params: Parameters<'static>, ) -> Result<(), anyhow::Error> { let contract = ContractContainer::try_from((config.code.as_path(), params))?; - let state = { + let state = if let Some(ref state_path) = contract_config.state { let mut buf = vec![]; - File::open(&contract_config.state)?.read_to_end(&mut buf)?; + File::open(state_path)?.read_to_end(&mut buf)?; buf.into() + } else { + tracing::warn!("no state provided for contract, if your contract cannot handle empty state correctly, this will always cause an error."); + vec![].into() }; let related_contracts = if let Some(_related) = &contract_config.related_contracts { todo!("use `related` contracts") @@ -89,7 +86,7 @@ async fn put_contract( related_contracts, } .into(); - execute_command(request, other).await + execute_command(request, other, config.address, config.port).await } async fn put_delegate( @@ -128,7 +125,7 @@ For additional hardening is recommended to use a different cipher and nonce to e nonce, } .into(); - execute_command(request, other).await + execute_command(request, other, config.address, config.port).await } pub async fn update(config: UpdateConfig, other: BaseConfig) -> Result<(), anyhow::Error> { @@ -143,42 +140,41 @@ pub async fn update(config: UpdateConfig, other: BaseConfig) -> Result<(), anyho StateDelta::from(buf).into() }; let request = ContractRequest::Update { key, data }.into(); - execute_command(request, other).await + execute_command(request, other, config.address, config.port).await } async fn execute_command( request: ClientRequest<'static>, other: BaseConfig, + address: IpAddr, + port: u16, ) -> Result<(), anyhow::Error> { - let contracts_data_path = other - .contract_data_dir - .unwrap_or_else(|| Config::conf().contracts_dir()); - let delegates_data_path = other - .delegate_data_dir - .unwrap_or_else(|| Config::conf().delegates_dir()); - let secrets_data_path = other - .secret_data_dir - .unwrap_or_else(|| Config::conf().secrets_dir()); - let database_path = other - .database_dir - .unwrap_or_else(|| Config::conf().db_dir()); - let contract_store = ContractStore::new(contracts_data_path, DEFAULT_MAX_CONTRACT_SIZE)?; - let delegate_store = DelegateStore::new(delegates_data_path, DEFAULT_MAX_DELEGATE_SIZE)?; - let secret_store = SecretsStore::new(secrets_data_path)?; - let state_store = StateStore::new(Storage::new(&database_path).await?, MAX_MEM_CACHE)?; - let rt = - freenet::dev_tool::Runtime::build(contract_store, delegate_store, secret_store, false)?; - let mut executor = Executor::new(state_store, || Ok(()), OperationMode::Local, rt, None) - .await - .map_err(|err| anyhow::anyhow!(err))?; + let mode = other.mode; + + let target = match mode { + OperationMode::Local => { + if !address.is_loopback() { + return Err(anyhow::anyhow!( + "invalid ip: {address}, expecting a loopback ip address in local mode" + )); + } + SocketAddr::new(address, port) + } + OperationMode::Network => SocketAddr::new(address, port), + }; - executor - .handle_request(ClientId::FIRST, request, None) + let (stream, _) = tokio_tungstenite::connect_async(&format!( + "ws://{}/contract/command?encodingProtocol=native", + target + )) + .await + .map_err(|e| { + tracing::error!(err=%e); + anyhow::anyhow!(format!("fail to connect to the host({target}): {e}")) + })?; + + WebApi::start(stream) + .send(request) .await - .map_err(|err| { - tracing::error!("{err}"); - err - })?; - - Ok(()) + .map_err(Into::into) } diff --git a/crates/fdev/src/config.rs b/crates/fdev/src/config.rs index d17588965..6877ee64b 100644 --- a/crates/fdev/src/config.rs +++ b/crates/fdev/src/config.rs @@ -1,8 +1,12 @@ -use std::{fmt::Display, path::PathBuf}; +use std::{ + fmt::Display, + net::{IpAddr, Ipv4Addr}, + path::PathBuf, +}; use crate::{commands::PutType, wasm_runtime::ExecutorConfig}; use clap::ValueEnum; -use freenet::dev_tool::OperationMode; +use freenet::{config::ConfigPathsArgs, dev_tool::OperationMode}; use semver::Version; #[derive(clap::Parser, Clone)] @@ -18,20 +22,10 @@ pub struct Config { #[derive(clap::Parser, Clone)] pub struct BaseConfig { - /// Overrides the default data directory where Freenet contract files are stored. - #[arg(long)] - pub(crate) contract_data_dir: Option, - /// Overrides the default data directory where Freenet delegate files are stored. - #[arg(long)] - pub(crate) delegate_data_dir: Option, - /// Overrides the default data directory where Freenet secret files are stored. - #[arg(long)] - pub(crate) secret_data_dir: Option, - /// Overrides the default data directory where Freenet database files are stored. - #[arg(long)] - pub(crate) database_dir: Option, + #[clap(flatten)] + pub(crate) paths: ConfigPathsArgs, /// Node operation mode. - #[arg(value_enum, default_value_t=OperationMode::Local)] + #[arg(value_enum, default_value_t=OperationMode::Local, env = "MODE")] pub mode: OperationMode, } @@ -86,6 +80,13 @@ pub enum NodeCommand { pub struct UpdateConfig { /// Contract id of the contract being updated in Base58 format. pub(crate) key: String, + /// The ip address of freenet node to update the contract to. If the node is running in local mode, + /// The default value is `127.0.0.1` + #[arg(short, long, default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + pub(crate) address: IpAddr, + /// The port of the running local freenet node. + #[arg(short, long, default_value = "50509")] + pub(crate) port: u16, /// A path to the update/delta being pushed to the contract. pub(crate) delta: PathBuf, /// Whether this contract will be updated in the network or is just a dry run @@ -101,6 +102,16 @@ pub struct PutConfig { /// (built using the `fdev` tool). Not an arbitrary WASM file. #[arg(long)] pub(crate) code: PathBuf, + + /// The ip address of freenet node to publish the contract to. If the node is running in local mode, + /// The default value is `127.0.0.1`. + #[arg(short, long, default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + pub(crate) address: IpAddr, + + /// The port of the running local freenet node. + #[arg(short, long, default_value = "50509")] + pub(crate) port: u16, + /// A path to the file parameters for the contract/delegate. If not specified, will be published /// with empty parameters. #[arg(long)] diff --git a/crates/fdev/src/main.rs b/crates/fdev/src/main.rs index 9b2aa24b6..7ff6b42ef 100644 --- a/crates/fdev/src/main.rs +++ b/crates/fdev/src/main.rs @@ -38,7 +38,6 @@ fn main() -> Result<(), anyhow::Error> { .enable_all() .build()?; let config = Config::parse(); - freenet::config::Config::set_op_mode(config.additional.mode); if !config.sub_command.is_child() { freenet::config::set_logger(None); } diff --git a/crates/fdev/src/testing/multiple_process.rs b/crates/fdev/src/testing/multiple_process.rs index c9317fd21..abb73ca49 100644 --- a/crates/fdev/src/testing/multiple_process.rs +++ b/crates/fdev/src/testing/multiple_process.rs @@ -437,12 +437,14 @@ async fn child( .unwrap_or(test_config.nodes * 10), test_config.events as usize, ); - let config = SimPeer::from(node_config); + let config = SimPeer::from(node_config.clone()); if let Some(backoff) = test_config.peer_start_backoff_ms { tokio::time::sleep(Duration::from_millis(backoff)).await; } tokio::task::spawn(this_child.event_loop()); - config.start_child(event_generator).await?; + config + .start_child(node_config.config(), event_generator) + .await?; Ok(()) } diff --git a/crates/fdev/src/wasm_runtime.rs b/crates/fdev/src/wasm_runtime.rs index ab35f1006..206b4590d 100644 --- a/crates/fdev/src/wasm_runtime.rs +++ b/crates/fdev/src/wasm_runtime.rs @@ -1,6 +1,10 @@ -use std::path::PathBuf; +use std::{ + net::{IpAddr, Ipv4Addr}, + path::PathBuf, +}; use clap::ArgGroup; +use freenet::{config::ConfigPathsArgs, dev_tool::OperationMode}; mod commands; mod state; @@ -52,8 +56,18 @@ pub struct ExecutorConfig { #[clap(long, requires = "fmt")] pub(crate) clean_exit: bool, /// Path to the contract to be loaded. - #[clap(value_parser)] - pub(crate) contract: PathBuf, + #[clap(flatten)] + pub(crate) paths: ConfigPathsArgs, + /// The ip address of freenet node to update the contract to. If the node is running in local mode, + /// The default value is `127.0.0.1` + #[arg(short, long, default_value_t = IpAddr::V4(Ipv4Addr::LOCALHOST))] + pub(crate) address: IpAddr, + /// The port of the running local freenet node. + #[arg(short, long, default_value = "50509")] + pub(crate) port: u16, + /// Node operation mode. + #[clap(value_enum, default_value_t = OperationMode::Local, env = "MODE")] + pub(crate) mode: OperationMode, /// Path to the file containing the parameters for this contract. If not set the default parameters will be empty. #[clap(long = "parameters", value_parser)] pub(crate) params: Option, diff --git a/crates/fdev/src/wasm_runtime/commands.rs b/crates/fdev/src/wasm_runtime/commands.rs index c99d5c63b..9b70b30dc 100644 --- a/crates/fdev/src/wasm_runtime/commands.rs +++ b/crates/fdev/src/wasm_runtime/commands.rs @@ -1,5 +1,4 @@ -use freenet::dev_tool::ClientId; -use freenet_stdlib::client_api::{ClientRequest, ContractRequest, ContractResponse, HostResponse}; +use freenet_stdlib::client_api::ClientRequest; use crate::CommandReceiver; @@ -30,68 +29,13 @@ async fn execute_command( ) -> Result { let node = &mut *app.local_node.write().await; match req { - ClientRequest::ContractOp(op) => match op { - req @ ContractRequest::Put { .. } => { - match node.handle_request(ClientId::FIRST, req.into(), None).await { - Ok(HostResponse::ContractResponse(ContractResponse::PutResponse { key })) => { - println!("valid put for {key}"); - } - Err(err) => { - println!("error: {err}"); - } - _ => unreachable!(), - } - } - req @ ContractRequest::Update { .. } => { - match node.handle_request(ClientId::FIRST, req.into(), None).await { - Ok(HostResponse::ContractResponse(ContractResponse::UpdateResponse { - key, - summary, - })) => { - println!("valid update for {key}, state summary:"); - app.printout_deser(summary.as_ref())?; - } - Err(err) => { - println!("error: {err}"); - } - _ => unreachable!(), - } - } - ContractRequest::Get { - key, - fetch_contract: contract, - } => { - match node - .handle_request( - ClientId::FIRST, - ContractRequest::Get { - key: key.clone(), - fetch_contract: contract, - } - .into(), - None, - ) - .await - { - Ok(HostResponse::ContractResponse(ContractResponse::GetResponse { - state, - .. - })) => { - println!("current state for {key}:"); - app.printout_deser(state.as_ref())?; - } - Err(err) => { - println!("error: {err}"); - } - _ => unreachable!(), - } - } - _ => unreachable!(), - }, + ClientRequest::ContractOp(_) => { + node.send(req).await?; + Ok(false) + } _ => { tracing::error!("op not supported"); anyhow::bail!("op not support"); } } - Ok(false) } diff --git a/crates/fdev/src/wasm_runtime/state.rs b/crates/fdev/src/wasm_runtime/state.rs index 6d454114c..20081960e 100644 --- a/crates/fdev/src/wasm_runtime/state.rs +++ b/crates/fdev/src/wasm_runtime/state.rs @@ -1,11 +1,6 @@ -use std::{fs::File, io::Write, sync::Arc}; +use std::{fs::File, io::Write, net::SocketAddr, sync::Arc}; -use freenet::dev_tool::{ - Config, ContractStore, DelegateStore, Executor, OperationMode, SecretsStore, StateStore, - Storage, -}; -use freenet_stdlib::prelude::*; -use futures::TryFutureExt; +use freenet_stdlib::{client_api::WebApi, prelude::*}; use tokio::sync::RwLock; use crate::wasm_runtime::DeserializationFmt; @@ -14,47 +9,25 @@ use super::ExecutorConfig; #[derive(Clone)] pub(super) struct AppState { - pub(crate) local_node: Arc>, + pub(crate) local_node: Arc>, config: ExecutorConfig, } impl AppState { - const MAX_MEM_CACHE: u32 = 10_000_000; - const DEFAULT_MAX_DELEGATE_SIZE: i64 = 10 * 1024 * 1024; - pub async fn new(config: &ExecutorConfig) -> Result { - let contract_store = - ContractStore::new(Config::conf().contracts_dir(), config.max_contract_size)?; - let delegate_store = DelegateStore::new( - Config::conf().delegates_dir(), - Self::DEFAULT_MAX_DELEGATE_SIZE, - )?; - let secrets_store = SecretsStore::new(Config::conf().secrets_dir())?; - let state_store = StateStore::new( - Storage::new(&Config::conf().db_dir()).await?, - Self::MAX_MEM_CACHE, - )?; - let rt = freenet::dev_tool::Runtime::build( - contract_store, - delegate_store, - secrets_store, - false, - )?; + let target: SocketAddr = (config.address, config.port).into(); + let (stream, _) = tokio_tungstenite::connect_async(&format!( + "ws://{}/contract/command?encodingProtocol=native", + target + )) + .await + .map_err(|e| { + tracing::error!(err=%e); + anyhow::anyhow!(format!("fail to connect to the host({target}): {e}")) + })?; + Ok(AppState { - local_node: Arc::new(RwLock::new( - Executor::new( - state_store, - || { - freenet::util::set_cleanup_on_exit()?; - Ok(()) - }, - OperationMode::Local, - rt, - None, - ) - .map_err(|err| anyhow::anyhow!(err)) - .await?, - )), + local_node: Arc::new(RwLock::new(WebApi::start(stream))), config: config.clone(), }) } diff --git a/crates/fdev/src/wasm_runtime/user_events.rs b/crates/fdev/src/wasm_runtime/user_events.rs index bebea671c..6cab1711b 100644 --- a/crates/fdev/src/wasm_runtime/user_events.rs +++ b/crates/fdev/src/wasm_runtime/user_events.rs @@ -9,7 +9,9 @@ use std::{ use either::Either; use freenet::dev_tool::{ClientEventsProxy, ClientId, OpenRequest}; use freenet_stdlib::{ - client_api::{ClientError, ClientRequest, ContractRequest, ErrorKind, HostResponse}, + client_api::{ + ClientError, ClientRequest, ContractRequest, ContractResponse, ErrorKind, HostResponse, + }, prelude::*, }; use futures::future::BoxFuture; @@ -62,6 +64,7 @@ struct StdInput { impl StdInput { fn new(config: ExecutorConfig, app_state: AppState) -> Result { + let paths = config.paths.clone().build()?; let params = config .params .as_ref() @@ -73,7 +76,8 @@ impl StdInput { }) .transpose()? .unwrap_or_default(); - let (contract_code, _ver) = ContractCode::load_versioned_from_path(&config.contract)?; + let (contract_code, _ver) = + ContractCode::load_versioned_from_path(&paths.contracts_dir(config.mode))?; let contract = ContractContainer::Wasm(ContractWasmAPIVersion::V1(WrappedContract::new( Arc::new(contract_code), params.into(), @@ -309,25 +313,40 @@ impl ClientEventsProxy for StdInput { )); } Ok(Command::GetParams) => { - let node = &*self.app_state.local_node.read().await; + let node = &mut *self.app_state.local_node.write().await; let key = self.contract.key(); - let p = node - .state_store - .get_params(&key) - .await - .map_err(|e| { - ClientError::from(ErrorKind::Unhandled { - cause: format!("{e}").into(), - }) - })? - .ok_or_else(|| { - ClientError::from(ErrorKind::Unhandled { - cause: format!("missing contract parameters: {key}",) - .into(), - }) - })?; - if let Err(e) = self.app_state.printout_deser(&p) { - tracing::error!("error printing params: {e}"); + node.send(ClientRequest::ContractOp(ContractRequest::Get { + key, + fetch_contract: true, + })) + .await + .map_err(|e| { + ClientError::from(ErrorKind::Unhandled { + cause: format!("{e}").into(), + }) + })?; + let resp = node.recv().await.map_err(|e| { + ClientError::from(ErrorKind::Unhandled { + cause: format!("{e}").into(), + }) + })?; + + if let HostResponse::ContractResponse(ContractResponse::GetResponse { + contract, + .. + }) = resp + { + if let Some(contract) = contract { + if let Err(e) = + self.app_state.printout_deser(&contract.params()) + { + tracing::error!("error printing params: {e}"); + } + } else { + return Err(ClientError::from(ErrorKind::Unhandled { + cause: "missing contract container".into(), + })); + } } } Ok(cmd) => {