//! Node configuration. use std::fs; use std::path::{Path, PathBuf}; use serde::{Deserialize, Serialize}; use tracing::info; /// Complete node configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct NodeConfig { /// Network name (mainnet, testnet, devnet). pub network: String, /// Chain ID. pub chain_id: u64, /// Data directory. pub data_dir: PathBuf, /// Storage configuration. #[serde(default)] pub storage: StorageConfig, /// Network/P2P configuration. #[serde(default)] pub p2p: P2PConfig, /// RPC configuration. #[serde(default)] pub rpc: RpcConfig, /// Mining configuration. #[serde(default)] pub mining: MiningConfig, /// Consensus configuration. #[serde(default)] pub consensus: ConsensusConfig, /// VM configuration. #[serde(default)] pub vm: VmConfig, /// Logging configuration. #[serde(default)] pub logging: LoggingConfig, /// Metrics configuration. #[serde(default)] pub metrics: MetricsConfig, } impl NodeConfig { /// Creates default config for a network. pub fn for_network(network: &str) -> anyhow::Result { // Chain IDs match synor-network convention: // 0 = mainnet, 1 = testnet, 2+ = devnet/local let (chain_id, data_dir_name) = match network { "mainnet" => (0, "synor"), "testnet" => (1, "synor-testnet"), "devnet" => (2, "synor-devnet"), _ => anyhow::bail!("Unknown network: {}", network), }; let data_dir = dirs::data_dir() .unwrap_or_else(|| PathBuf::from(".")) .join(data_dir_name); Ok(NodeConfig { network: network.to_string(), chain_id, data_dir, storage: StorageConfig::default(), p2p: P2PConfig::for_network(network), rpc: RpcConfig::for_network(network), mining: MiningConfig::default(), consensus: ConsensusConfig::for_network(network), vm: VmConfig::default(), logging: LoggingConfig::default(), metrics: MetricsConfig::default(), }) } /// Loads config from file or creates default. pub fn load_or_default(path: &Path, network: &str) -> anyhow::Result { if path.exists() { Self::load(path) } else { info!("Config file not found, using defaults"); Self::for_network(network) } } /// Loads config from file. pub fn load(path: &Path) -> anyhow::Result { let content = fs::read_to_string(path)?; let config: NodeConfig = toml::from_str(&content)?; Ok(config) } /// Saves config to file. pub fn save(&self, path: &Path) -> anyhow::Result<()> { let content = toml::to_string_pretty(self)?; fs::write(path, content)?; Ok(()) } /// Sets data directory. pub fn with_data_dir(mut self, data_dir: Option) -> Self { if let Some(dir) = data_dir { self.data_dir = dir; } self } /// Sets mining configuration. pub fn with_mining( mut self, enabled: bool, coinbase: Option, threads: usize, ) -> Self { if enabled { self.mining.enabled = true; } if let Some(addr) = coinbase { self.mining.coinbase_address = Some(addr); } if threads > 0 { self.mining.threads = threads; } self } /// Sets RPC configuration. pub fn with_rpc(mut self, host: &str, rpc_port: u16, ws_port: u16) -> Self { self.rpc.http_addr = format!("{}:{}", host, rpc_port); self.rpc.ws_addr = format!("{}:{}", host, ws_port); self } /// Sets P2P configuration. pub fn with_p2p(mut self, host: &str, port: u16, seeds: Vec) -> Self { self.p2p.listen_addr = format!("{}:{}", host, port); if !seeds.is_empty() { self.p2p.seeds = seeds; } self } /// Returns paths for various data. pub fn blocks_path(&self) -> PathBuf { self.data_dir.join("blocks") } pub fn chainstate_path(&self) -> PathBuf { self.data_dir.join("chainstate") } pub fn contracts_path(&self) -> PathBuf { self.data_dir.join("contracts") } pub fn keys_path(&self) -> PathBuf { self.data_dir.join("keys") } } /// Storage configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct StorageConfig { /// Database type (rocksdb, sled). pub db_type: String, /// Cache size in MB. pub cache_size_mb: usize, /// Max open files. pub max_open_files: i32, /// Enable compression. pub compression: bool, /// Pruning mode. pub pruning: PruningConfig, } impl Default for StorageConfig { fn default() -> Self { StorageConfig { db_type: "rocksdb".to_string(), cache_size_mb: 512, max_open_files: 1024, compression: true, pruning: PruningConfig::default(), } } } /// Pruning configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct PruningConfig { /// Enable pruning. pub enabled: bool, /// Keep last N blocks. pub keep_blocks: u64, /// Pruning interval in blocks. pub interval: u64, } impl Default for PruningConfig { fn default() -> Self { PruningConfig { enabled: false, keep_blocks: 100_000, interval: 1000, } } } /// P2P network configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct P2PConfig { /// Listen address. pub listen_addr: String, /// External address (for NAT). pub external_addr: Option, /// Seed nodes. pub seeds: Vec, /// Maximum inbound connections. pub max_inbound: usize, /// Maximum outbound connections. pub max_outbound: usize, /// Connection timeout in seconds. pub connection_timeout: u64, /// Enable UPnP. pub upnp: bool, /// Ban duration in seconds. pub ban_duration: u64, } impl Default for P2PConfig { fn default() -> Self { P2PConfig { listen_addr: "0.0.0.0:16100".to_string(), external_addr: None, seeds: vec![], max_inbound: 125, max_outbound: 8, connection_timeout: 30, upnp: true, ban_duration: 86400, // 24 hours } } } impl P2PConfig { /// Creates config for a network. pub fn for_network(network: &str) -> Self { let mut config = P2PConfig::default(); match network { "mainnet" => { config.listen_addr = "/ip4/0.0.0.0/tcp/16511".to_string(); config.seeds = vec![ // Mainnet seeds - geographically distributed // Format: /dns4//tcp//p2p/ // Peer IDs will be populated after seed node deployment "/dns4/seed1.synor.cc/tcp/16511".to_string(), "/dns4/seed2.synor.cc/tcp/16511".to_string(), "/dns4/seed3.synor.cc/tcp/16511".to_string(), ]; } "testnet" => { config.listen_addr = "/ip4/0.0.0.0/tcp/17511".to_string(); config.seeds = vec![ // Testnet seeds - geographically distributed // North America (US-East) "/dns4/testnet-seed1.synor.cc/tcp/17511".to_string(), // Europe (Frankfurt) "/dns4/testnet-seed2.synor.cc/tcp/17511".to_string(), // Asia (Singapore) "/dns4/testnet-seed3.synor.cc/tcp/17511".to_string(), ]; } "devnet" => { config.listen_addr = "/ip4/0.0.0.0/tcp/18511".to_string(); config.seeds = vec![]; } _ => {} }; config } } /// RPC configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RpcConfig { /// Enable HTTP RPC. pub http_enabled: bool, /// HTTP bind address. pub http_addr: String, /// Enable WebSocket RPC. pub ws_enabled: bool, /// WebSocket bind address. pub ws_addr: String, /// Enable CORS. pub cors: bool, /// Allowed origins. pub cors_origins: Vec, /// Maximum batch size. pub max_batch_size: usize, /// Maximum response size. pub max_response_size: usize, /// Rate limit (requests per second, 0 = unlimited). pub rate_limit: u32, /// Maximum connections. pub max_connections: u32, } impl Default for RpcConfig { fn default() -> Self { RpcConfig { http_enabled: true, http_addr: "127.0.0.1:16110".to_string(), ws_enabled: true, ws_addr: "127.0.0.1:16111".to_string(), cors: true, cors_origins: vec!["*".to_string()], max_batch_size: 100, max_response_size: 10 * 1024 * 1024, // 10MB rate_limit: 0, max_connections: 100, } } } impl RpcConfig { /// Creates config for a network. pub fn for_network(network: &str) -> Self { let mut config = RpcConfig::default(); match network { "mainnet" => { config.http_addr = "127.0.0.1:16110".to_string(); config.ws_addr = "127.0.0.1:16111".to_string(); } "testnet" => { config.http_addr = "127.0.0.1:17110".to_string(); config.ws_addr = "127.0.0.1:17111".to_string(); } "devnet" => { config.http_addr = "127.0.0.1:18110".to_string(); config.ws_addr = "127.0.0.1:18111".to_string(); } _ => {} } config } } /// Mining configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MiningConfig { /// Enable mining. pub enabled: bool, /// Coinbase address for rewards. pub coinbase_address: Option, /// Number of mining threads (0 = auto). pub threads: usize, /// Extra data for coinbase. pub extra_data: String, /// Mining intensity (0.0 - 1.0). pub intensity: f32, /// Enable GPU mining. pub gpu_enabled: bool, /// GPU device indices. pub gpu_devices: Vec, } impl Default for MiningConfig { fn default() -> Self { MiningConfig { enabled: false, coinbase_address: None, threads: 0, // Auto-detect extra_data: "synord".to_string(), intensity: 1.0, gpu_enabled: false, gpu_devices: vec![], } } } /// Consensus configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ConsensusConfig { /// GHOSTDAG K parameter. pub ghostdag_k: u8, /// Merge depth. pub merge_depth: u64, /// Finality depth. pub finality_depth: u64, /// Target block time in milliseconds. pub target_time_ms: u64, /// Difficulty adjustment window. pub difficulty_window: u64, /// Max block size. pub max_block_size: usize, /// Max block mass. pub max_block_mass: u64, } impl Default for ConsensusConfig { fn default() -> Self { ConsensusConfig { ghostdag_k: 18, merge_depth: 3600, // ~1 hour finality_depth: 86400, // ~24 hours target_time_ms: 1000, difficulty_window: 2641, max_block_size: 1_000_000, max_block_mass: 500_000, } } } impl ConsensusConfig { /// Creates config for a network. pub fn for_network(network: &str) -> Self { let mut config = ConsensusConfig::default(); match network { "mainnet" => { // Mainnet: 1 second blocks, high finality for security config.target_time_ms = 1000; config.finality_depth = 86400; // ~24 hours at 1 BPS config.merge_depth = 3600; // ~1 hour at 1 BPS } "testnet" => { // Testnet: Fast 100ms blocks for development testing config.target_time_ms = 100; config.finality_depth = 36000; // ~1 hour at 10 BPS config.merge_depth = 360; // ~36 seconds at 10 BPS config.ghostdag_k = 18; } "devnet" => { // Devnet: Very fast for local testing config.target_time_ms = 100; config.finality_depth = 100; config.merge_depth = 36; } _ => {} } config } } /// VM configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct VmConfig { /// Enable smart contracts. pub enabled: bool, /// Maximum gas per block. pub max_gas_per_block: u64, /// Maximum contract size. pub max_contract_size: usize, /// Maximum call depth. pub max_call_depth: u32, /// Maximum memory pages. pub max_memory_pages: u32, /// Execution timeout in milliseconds. pub execution_timeout_ms: u64, } impl Default for VmConfig { fn default() -> Self { VmConfig { enabled: true, max_gas_per_block: 100_000_000, max_contract_size: 24 * 1024, // 24KB max_call_depth: 16, max_memory_pages: 256, // 16MB execution_timeout_ms: 5000, } } } /// Logging configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct LoggingConfig { /// Log level (trace, debug, info, warn, error). pub level: String, /// Enable JSON format. pub json: bool, /// Log file path. pub file: Option, /// Maximum log file size in MB. pub max_size_mb: usize, /// Number of log files to keep. pub max_files: usize, } impl Default for LoggingConfig { fn default() -> Self { LoggingConfig { level: "info".to_string(), json: false, file: None, max_size_mb: 100, max_files: 5, } } } /// Metrics configuration. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct MetricsConfig { /// Enable metrics. pub enabled: bool, /// Metrics bind address. pub addr: String, /// Enable Prometheus endpoint. pub prometheus: bool, } impl Default for MetricsConfig { fn default() -> Self { MetricsConfig { enabled: false, addr: "127.0.0.1:9090".to_string(), prometheus: true, } } } #[cfg(test)] mod tests { use super::*; use tempfile::tempdir; #[test] fn test_config_for_network() { let config = NodeConfig::for_network("mainnet").unwrap(); assert_eq!(config.chain_id, 0); assert_eq!(config.network, "mainnet"); let config = NodeConfig::for_network("testnet").unwrap(); assert_eq!(config.chain_id, 1); assert_eq!(config.consensus.target_time_ms, 100); // Fast testnet let config = NodeConfig::for_network("devnet").unwrap(); assert_eq!(config.chain_id, 2); } #[test] fn test_config_save_load() { let dir = tempdir().unwrap(); let path = dir.path().join("config.toml"); let config = NodeConfig::for_network("mainnet").unwrap(); config.save(&path).unwrap(); let loaded = NodeConfig::load(&path).unwrap(); assert_eq!(loaded.network, config.network); assert_eq!(loaded.chain_id, config.chain_id); } }