1use std::path::PathBuf;
22
23#[derive(Debug, Clone)]
25pub struct StorageSettings {
26 pub base_path: PathBuf,
28 pub max_bytes: u64,
30 pub min_free_bytes: u64,
32 pub enable_tiering: bool,
34 pub ssd_path: Option<PathBuf>,
36 pub hdd_path: Option<PathBuf>,
38}
39
40impl StorageSettings {
41 #[inline]
43 #[must_use]
44 pub fn new(base_path: PathBuf, max_bytes: u64) -> Self {
45 Self {
46 base_path,
47 max_bytes,
48 min_free_bytes: 1024 * 1024 * 1024, enable_tiering: false,
50 ssd_path: None,
51 hdd_path: None,
52 }
53 }
54
55 #[inline]
57 #[must_use]
58 pub const fn max_bytes_gb(&self) -> f64 {
59 self.max_bytes as f64 / (1024.0 * 1024.0 * 1024.0)
60 }
61
62 pub fn validate(&self) -> Result<(), ConfigError> {
64 if self.max_bytes == 0 {
65 return Err(ConfigError::InvalidValue(
66 "max_bytes must be greater than 0".into(),
67 ));
68 }
69
70 if self.min_free_bytes >= self.max_bytes {
71 return Err(ConfigError::InvalidValue(
72 "min_free_bytes must be less than max_bytes".into(),
73 ));
74 }
75
76 if self.enable_tiering && (self.ssd_path.is_none() || self.hdd_path.is_none()) {
77 return Err(ConfigError::InvalidValue(
78 "Tiering enabled but SSD/HDD paths not specified".into(),
79 ));
80 }
81
82 Ok(())
83 }
84}
85
86impl Default for StorageSettings {
87 #[inline]
88 fn default() -> Self {
89 Self::new(
90 PathBuf::from("./chie-data"),
91 50 * 1024 * 1024 * 1024, )
93 }
94}
95
96#[derive(Debug, Clone)]
98pub struct NetworkSettings {
99 pub max_bandwidth_bps: u64,
101 pub max_connections: usize,
103 pub connection_timeout_secs: u64,
105 pub request_timeout_secs: u64,
107 pub enable_rate_limiting: bool,
109 pub rate_limit_rps: f64,
111}
112
113impl NetworkSettings {
114 #[inline]
116 #[must_use]
117 pub fn new(max_bandwidth_bps: u64) -> Self {
118 Self {
119 max_bandwidth_bps,
120 max_connections: 100,
121 connection_timeout_secs: 10,
122 request_timeout_secs: 30,
123 enable_rate_limiting: true,
124 rate_limit_rps: 100.0,
125 }
126 }
127
128 #[inline]
130 #[must_use]
131 pub const fn max_bandwidth_mbps(&self) -> f64 {
132 (self.max_bandwidth_bps * 8) as f64 / (1024.0 * 1024.0)
133 }
134
135 pub fn validate(&self) -> Result<(), ConfigError> {
137 if self.max_bandwidth_bps == 0 {
138 return Err(ConfigError::InvalidValue(
139 "max_bandwidth_bps must be greater than 0".into(),
140 ));
141 }
142
143 if self.max_connections == 0 {
144 return Err(ConfigError::InvalidValue(
145 "max_connections must be greater than 0".into(),
146 ));
147 }
148
149 if self.rate_limit_rps < 0.0 {
150 return Err(ConfigError::InvalidValue(
151 "rate_limit_rps must be non-negative".into(),
152 ));
153 }
154
155 Ok(())
156 }
157}
158
159impl Default for NetworkSettings {
160 #[inline]
161 fn default() -> Self {
162 Self::new(100 * 1024 * 1024 / 8) }
164}
165
166#[derive(Debug, Clone)]
168pub struct CoordinatorSettings {
169 pub url: String,
171 pub api_key: Option<String>,
173 pub proof_submit_interval_secs: u64,
175 pub proof_batch_size: usize,
177 pub auto_submit: bool,
179}
180
181impl CoordinatorSettings {
182 #[inline]
184 #[must_use]
185 pub fn new(url: String) -> Self {
186 Self {
187 url,
188 api_key: None,
189 proof_submit_interval_secs: 60,
190 proof_batch_size: 10,
191 auto_submit: true,
192 }
193 }
194
195 pub fn validate(&self) -> Result<(), ConfigError> {
197 if self.url.is_empty() {
198 return Err(ConfigError::InvalidValue(
199 "Coordinator URL cannot be empty".into(),
200 ));
201 }
202
203 if !self.url.starts_with("http://") && !self.url.starts_with("https://") {
204 return Err(ConfigError::InvalidValue(
205 "Coordinator URL must start with http:// or https://".into(),
206 ));
207 }
208
209 if self.proof_batch_size == 0 {
210 return Err(ConfigError::InvalidValue(
211 "proof_batch_size must be greater than 0".into(),
212 ));
213 }
214
215 Ok(())
216 }
217}
218
219impl Default for CoordinatorSettings {
220 #[inline]
221 fn default() -> Self {
222 Self::new("https://coordinator.chie.network".to_string())
223 }
224}
225
226#[derive(Debug, Clone)]
228pub struct PerformanceSettings {
229 pub enable_prefetch: bool,
231 pub prefetch_cache_size: usize,
233 pub enable_compression: bool,
235 pub enable_deduplication: bool,
237 pub gc_interval_secs: u64,
239 pub enable_profiling: bool,
241}
242
243impl PerformanceSettings {
244 pub fn validate(&self) -> Result<(), ConfigError> {
246 if self.prefetch_cache_size == 0 {
247 return Err(ConfigError::InvalidValue(
248 "prefetch_cache_size must be greater than 0".into(),
249 ));
250 }
251
252 Ok(())
253 }
254}
255
256impl Default for PerformanceSettings {
257 #[inline]
258 fn default() -> Self {
259 Self {
260 enable_prefetch: true,
261 prefetch_cache_size: 100,
262 enable_compression: true,
263 enable_deduplication: true,
264 gc_interval_secs: 3600, enable_profiling: false,
266 }
267 }
268}
269
270#[derive(Debug, Clone, Default)]
272pub struct NodeSettings {
273 pub storage: StorageSettings,
275 pub network: NetworkSettings,
277 pub coordinator: CoordinatorSettings,
279 pub performance: PerformanceSettings,
281}
282
283impl NodeSettings {
284 #[inline]
286 #[must_use]
287 pub fn builder() -> NodeSettingsBuilder {
288 NodeSettingsBuilder::default()
289 }
290
291 pub fn validate(&self) -> Result<(), ConfigError> {
293 self.storage.validate()?;
294 self.network.validate()?;
295 self.coordinator.validate()?;
296 self.performance.validate()?;
297 Ok(())
298 }
299
300 pub fn from_env() -> Result<Self, ConfigError> {
302 let mut settings = Self::default();
303
304 if let Ok(path) = std::env::var("CHIE_STORAGE_PATH") {
306 settings.storage.base_path = PathBuf::from(path);
307 }
308 if let Ok(max_bytes) = std::env::var("CHIE_STORAGE_MAX_BYTES") {
309 settings.storage.max_bytes = max_bytes
310 .parse()
311 .map_err(|_| ConfigError::InvalidValue("Invalid CHIE_STORAGE_MAX_BYTES".into()))?;
312 }
313
314 if let Ok(bandwidth) = std::env::var("CHIE_MAX_BANDWIDTH_BPS") {
316 settings.network.max_bandwidth_bps = bandwidth
317 .parse()
318 .map_err(|_| ConfigError::InvalidValue("Invalid CHIE_MAX_BANDWIDTH_BPS".into()))?;
319 }
320
321 if let Ok(url) = std::env::var("CHIE_COORDINATOR_URL") {
323 settings.coordinator.url = url;
324 }
325 if let Ok(api_key) = std::env::var("CHIE_API_KEY") {
326 settings.coordinator.api_key = Some(api_key);
327 }
328
329 settings.validate()?;
330 Ok(settings)
331 }
332}
333
334#[derive(Debug, Default)]
336pub struct NodeSettingsBuilder {
337 storage: Option<StorageSettings>,
338 network: Option<NetworkSettings>,
339 coordinator: Option<CoordinatorSettings>,
340 performance: Option<PerformanceSettings>,
341}
342
343impl NodeSettingsBuilder {
344 #[inline]
346 #[must_use]
347 pub fn storage(mut self, storage: StorageSettings) -> Self {
348 self.storage = Some(storage);
349 self
350 }
351
352 #[inline]
354 #[must_use]
355 pub fn network(mut self, network: NetworkSettings) -> Self {
356 self.network = Some(network);
357 self
358 }
359
360 #[inline]
362 #[must_use]
363 pub fn coordinator(mut self, coordinator: CoordinatorSettings) -> Self {
364 self.coordinator = Some(coordinator);
365 self
366 }
367
368 #[inline]
370 #[must_use]
371 pub fn performance(mut self, performance: PerformanceSettings) -> Self {
372 self.performance = Some(performance);
373 self
374 }
375
376 pub fn build(self) -> Result<NodeSettings, ConfigError> {
378 let settings = NodeSettings {
379 storage: self.storage.unwrap_or_default(),
380 network: self.network.unwrap_or_default(),
381 coordinator: self.coordinator.unwrap_or_default(),
382 performance: self.performance.unwrap_or_default(),
383 };
384
385 settings.validate()?;
386 Ok(settings)
387 }
388}
389
390#[derive(Debug, Clone, PartialEq, Eq)]
392pub enum ConfigError {
393 InvalidValue(String),
395 MissingField(String),
397}
398
399impl std::fmt::Display for ConfigError {
400 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
401 match self {
402 Self::InvalidValue(msg) => write!(f, "Invalid configuration value: {}", msg),
403 Self::MissingField(field) => write!(f, "Missing required field: {}", field),
404 }
405 }
406}
407
408impl std::error::Error for ConfigError {}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn test_storage_settings_default() {
416 let settings = StorageSettings::default();
417 assert_eq!(settings.max_bytes, 50 * 1024 * 1024 * 1024);
418 assert!(settings.validate().is_ok());
419 }
420
421 #[test]
422 fn test_storage_settings_validation() {
423 let settings = StorageSettings {
424 max_bytes: 0,
425 ..Default::default()
426 };
427 assert!(settings.validate().is_err());
428
429 let settings = StorageSettings {
430 min_free_bytes: 50 * 1024 * 1024 * 1024 + 1,
431 ..Default::default()
432 };
433 assert!(settings.validate().is_err());
434 }
435
436 #[test]
437 fn test_network_settings_default() {
438 let settings = NetworkSettings::default();
439 assert_eq!(settings.max_connections, 100);
440 assert!(settings.validate().is_ok());
441 }
442
443 #[test]
444 fn test_network_settings_mbps() {
445 let settings = NetworkSettings::new(100 * 1024 * 1024 / 8);
446 assert_eq!(settings.max_bandwidth_mbps(), 100.0);
447 }
448
449 #[test]
450 fn test_coordinator_settings_validation() {
451 let settings = CoordinatorSettings {
452 url: String::new(),
453 ..Default::default()
454 };
455 assert!(settings.validate().is_err());
456
457 let settings = CoordinatorSettings {
458 url: "invalid-url".to_string(),
459 ..Default::default()
460 };
461 assert!(settings.validate().is_err());
462 }
463
464 #[test]
465 fn test_node_settings_builder() {
466 let settings = NodeSettings::builder()
467 .storage(StorageSettings::default())
468 .network(NetworkSettings::default())
469 .build();
470
471 assert!(settings.is_ok());
472 }
473
474 #[test]
475 fn test_node_settings_default() {
476 let settings = NodeSettings::default();
477 assert!(settings.validate().is_ok());
478 }
479
480 #[test]
481 fn test_performance_settings_default() {
482 let settings = PerformanceSettings::default();
483 assert!(settings.enable_prefetch);
484 assert!(settings.validate().is_ok());
485 }
486
487 #[test]
488 fn test_config_error_display() {
489 let err = ConfigError::InvalidValue("test".to_string());
490 assert_eq!(err.to_string(), "Invalid configuration value: test");
491
492 let err = ConfigError::MissingField("field1".to_string());
493 assert_eq!(err.to_string(), "Missing required field: field1");
494 }
495
496 #[test]
497 fn test_storage_tiering_validation() {
498 let settings = StorageSettings {
499 enable_tiering: true,
500 ..Default::default()
501 };
502 assert!(settings.validate().is_err());
503
504 let mut settings = settings;
505 settings.ssd_path = Some(PathBuf::from("/ssd"));
506 settings.hdd_path = Some(PathBuf::from("/hdd"));
507 assert!(settings.validate().is_ok());
508 }
509}