1use serde::{Deserialize, Serialize};
4use std::net::SocketAddr;
5use std::path::PathBuf;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum IpVersion {
11 Ipv4,
13 Ipv6,
15 #[default]
17 Dual,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23pub enum UpgradeChannel {
24 #[default]
26 Stable,
27 Beta,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
33#[serde(rename_all = "lowercase")]
34pub enum NetworkMode {
35 #[default]
37 Production,
38 Testnet,
41 Development,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct TestnetConfig {
49 #[serde(default = "default_testnet_max_per_asn")]
52 pub max_nodes_per_asn: usize,
53
54 #[serde(default = "default_testnet_max_per_64")]
57 pub max_nodes_per_64: usize,
58
59 #[serde(default)]
62 pub enforce_age_requirements: bool,
63
64 #[serde(default)]
67 pub enable_geo_checks: bool,
68}
69
70impl Default for TestnetConfig {
71 fn default() -> Self {
72 Self {
73 max_nodes_per_asn: default_testnet_max_per_asn(),
74 max_nodes_per_64: default_testnet_max_per_64(),
75 enforce_age_requirements: false,
76 enable_geo_checks: false,
77 }
78 }
79}
80
81const fn default_testnet_max_per_asn() -> usize {
82 5000
83}
84
85const fn default_testnet_max_per_64() -> usize {
86 100
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct NodeConfig {
92 #[serde(default = "default_root_dir")]
94 pub root_dir: PathBuf,
95
96 #[serde(default)]
98 pub port: u16,
99
100 #[serde(default)]
102 pub ip_version: IpVersion,
103
104 #[serde(default)]
106 pub bootstrap: Vec<SocketAddr>,
107
108 #[serde(default)]
110 pub network_mode: NetworkMode,
111
112 #[serde(default)]
115 pub testnet: TestnetConfig,
116
117 #[serde(default)]
119 pub upgrade: UpgradeConfig,
120
121 #[serde(default)]
123 pub payment: PaymentConfig,
124
125 #[serde(default)]
127 pub attestation: AttestationNodeConfig,
128
129 #[serde(default)]
131 pub bootstrap_cache: BootstrapCacheConfig,
132
133 #[serde(default = "default_log_level")]
135 pub log_level: String,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct UpgradeConfig {
141 #[serde(default)]
143 pub enabled: bool,
144
145 #[serde(default)]
147 pub channel: UpgradeChannel,
148
149 #[serde(default = "default_check_interval")]
151 pub check_interval_hours: u64,
152
153 #[serde(default = "default_github_repo")]
155 pub github_repo: String,
156
157 #[serde(default = "default_staged_rollout_hours")]
165 pub staged_rollout_hours: u64,
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
170#[serde(rename_all = "kebab-case")]
171pub enum EvmNetworkConfig {
172 #[default]
174 ArbitrumOne,
175 ArbitrumSepolia,
177}
178
179#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct PaymentConfig {
185 #[serde(default = "default_payment_enabled")]
187 pub enabled: bool,
188
189 #[serde(default = "default_cache_capacity")]
191 pub cache_capacity: usize,
192
193 #[serde(default)]
196 pub rewards_address: Option<String>,
197
198 #[serde(default)]
200 pub evm_network: EvmNetworkConfig,
201
202 #[serde(default = "default_metrics_port")]
205 pub metrics_port: u16,
206}
207
208impl Default for PaymentConfig {
209 fn default() -> Self {
210 Self {
211 enabled: default_payment_enabled(),
212 cache_capacity: default_cache_capacity(),
213 rewards_address: None,
214 evm_network: EvmNetworkConfig::default(),
215 metrics_port: default_metrics_port(),
216 }
217 }
218}
219
220const fn default_metrics_port() -> u16 {
221 9100
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
232#[serde(rename_all = "lowercase")]
233pub enum AttestationMode {
234 #[default]
237 Off,
238 Soft,
241 Hard,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct AttestationNodeConfig {
270 #[serde(default)]
273 pub enabled: bool,
274
275 #[serde(default)]
278 pub mode: AttestationMode,
279
280 #[serde(default = "default_require_pq_secure")]
285 pub require_pq_secure: bool,
286
287 #[serde(default)]
291 pub allowed_binary_hashes: Vec<String>,
292
293 #[serde(default = "default_sunset_grace_days")]
297 pub sunset_grace_days: u32,
298}
299
300impl Default for AttestationNodeConfig {
301 fn default() -> Self {
302 Self {
303 enabled: false,
304 mode: AttestationMode::Off,
305 require_pq_secure: default_require_pq_secure(),
306 allowed_binary_hashes: Vec::new(),
307 sunset_grace_days: default_sunset_grace_days(),
308 }
309 }
310}
311
312impl AttestationNodeConfig {
313 #[must_use]
315 pub fn development() -> Self {
316 Self {
317 enabled: true,
318 mode: AttestationMode::Soft,
319 require_pq_secure: false,
320 allowed_binary_hashes: Vec::new(),
321 sunset_grace_days: 365,
322 }
323 }
324
325 #[must_use]
327 pub fn production(allowed_binary_hashes: Vec<String>) -> Self {
328 Self {
329 enabled: true,
330 mode: AttestationMode::Hard,
331 require_pq_secure: true,
332 allowed_binary_hashes,
333 sunset_grace_days: 30,
334 }
335 }
336}
337
338const fn default_require_pq_secure() -> bool {
339 true
340}
341
342const fn default_sunset_grace_days() -> u32 {
343 30
344}
345
346const fn default_payment_enabled() -> bool {
347 true
348}
349
350const fn default_cache_capacity() -> usize {
351 100_000
352}
353
354impl Default for NodeConfig {
355 fn default() -> Self {
356 Self {
357 root_dir: default_root_dir(),
358 port: 0,
359 ip_version: IpVersion::default(),
360 bootstrap: Vec::new(),
361 network_mode: NetworkMode::default(),
362 testnet: TestnetConfig::default(),
363 upgrade: UpgradeConfig::default(),
364 payment: PaymentConfig::default(),
365 attestation: AttestationNodeConfig::default(),
366 bootstrap_cache: BootstrapCacheConfig::default(),
367 log_level: default_log_level(),
368 }
369 }
370}
371
372impl NodeConfig {
373 #[must_use]
379 pub fn testnet() -> Self {
380 Self {
381 network_mode: NetworkMode::Testnet,
382 testnet: TestnetConfig::default(),
383 bootstrap: default_testnet_bootstrap(),
384 ..Self::default()
385 }
386 }
387
388 #[must_use]
392 pub fn development() -> Self {
393 Self {
394 network_mode: NetworkMode::Development,
395 testnet: TestnetConfig {
396 max_nodes_per_asn: usize::MAX,
397 max_nodes_per_64: usize::MAX,
398 enforce_age_requirements: false,
399 enable_geo_checks: false,
400 },
401 ..Self::default()
402 }
403 }
404
405 #[must_use]
407 pub fn is_relaxed(&self) -> bool {
408 !matches!(self.network_mode, NetworkMode::Production)
409 }
410
411 pub fn from_file(path: &std::path::Path) -> crate::Result<Self> {
417 let content = std::fs::read_to_string(path)?;
418 toml::from_str(&content).map_err(|e| crate::Error::Config(e.to_string()))
419 }
420
421 pub fn to_file(&self, path: &std::path::Path) -> crate::Result<()> {
427 let content =
428 toml::to_string_pretty(self).map_err(|e| crate::Error::Config(e.to_string()))?;
429 std::fs::write(path, content)?;
430 Ok(())
431 }
432}
433
434impl Default for UpgradeConfig {
435 fn default() -> Self {
436 Self {
437 enabled: false,
438 channel: UpgradeChannel::default(),
439 check_interval_hours: default_check_interval(),
440 github_repo: default_github_repo(),
441 staged_rollout_hours: default_staged_rollout_hours(),
442 }
443 }
444}
445
446fn default_github_repo() -> String {
447 "dirvine/saorsa-node".to_string()
448}
449
450fn default_root_dir() -> PathBuf {
451 directories::ProjectDirs::from("", "", "saorsa").map_or_else(
452 || PathBuf::from(".saorsa"),
453 |dirs| dirs.data_dir().to_path_buf(),
454 )
455}
456
457fn default_log_level() -> String {
458 "info".to_string()
459}
460
461const fn default_check_interval() -> u64 {
462 1 }
464
465const fn default_staged_rollout_hours() -> u64 {
466 1 }
468
469#[derive(Debug, Clone, Serialize, Deserialize)]
480pub struct BootstrapCacheConfig {
481 #[serde(default = "default_bootstrap_cache_enabled")]
484 pub enabled: bool,
485
486 #[serde(default)]
489 pub cache_dir: Option<PathBuf>,
490
491 #[serde(default = "default_bootstrap_max_contacts")]
494 pub max_contacts: usize,
495
496 #[serde(default = "default_bootstrap_stale_days")]
500 pub stale_threshold_days: u64,
501}
502
503impl Default for BootstrapCacheConfig {
504 fn default() -> Self {
505 Self {
506 enabled: default_bootstrap_cache_enabled(),
507 cache_dir: None,
508 max_contacts: default_bootstrap_max_contacts(),
509 stale_threshold_days: default_bootstrap_stale_days(),
510 }
511 }
512}
513
514const fn default_bootstrap_cache_enabled() -> bool {
515 true
516}
517
518const fn default_bootstrap_max_contacts() -> usize {
519 10_000
520}
521
522const fn default_bootstrap_stale_days() -> u64 {
523 7
524}
525
526fn default_testnet_bootstrap() -> Vec<SocketAddr> {
532 use std::net::{Ipv4Addr, SocketAddrV4};
533
534 vec![
535 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(165, 22, 4, 178), 12000)),
537 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(164, 92, 111, 156), 12000)),
539 ]
540}