saorsa_node/
config.rs

1//! Configuration for saorsa-node.
2
3use serde::{Deserialize, Serialize};
4use std::net::SocketAddr;
5use std::path::PathBuf;
6
7/// IP version configuration.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
9#[serde(rename_all = "lowercase")]
10pub enum IpVersion {
11    /// IPv4 only.
12    Ipv4,
13    /// IPv6 only.
14    Ipv6,
15    /// Dual-stack (both IPv4 and IPv6).
16    #[default]
17    Dual,
18}
19
20/// Upgrade channel for auto-updates.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23pub enum UpgradeChannel {
24    /// Stable releases only.
25    #[default]
26    Stable,
27    /// Beta releases (includes stable).
28    Beta,
29}
30
31/// Network mode for different deployment scenarios.
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
33#[serde(rename_all = "lowercase")]
34pub enum NetworkMode {
35    /// Production mode with full anti-Sybil protection.
36    #[default]
37    Production,
38    /// Testnet mode with relaxed diversity requirements.
39    /// Suitable for single-provider deployments (e.g., Digital Ocean).
40    Testnet,
41    /// Development mode with minimal restrictions.
42    /// Only use for local testing.
43    Development,
44}
45
46/// Testnet-specific configuration for relaxed anti-Sybil protection.
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct TestnetConfig {
49    /// Maximum nodes allowed per ASN.
50    /// Default: 5000 (compared to 20 in production).
51    #[serde(default = "default_testnet_max_per_asn")]
52    pub max_nodes_per_asn: usize,
53
54    /// Maximum nodes allowed per /64 subnet.
55    /// Default: 100 (compared to 1 in production).
56    #[serde(default = "default_testnet_max_per_64")]
57    pub max_nodes_per_64: usize,
58
59    /// Whether to enforce node age requirements.
60    /// Default: false (compared to true in production).
61    #[serde(default)]
62    pub enforce_age_requirements: bool,
63
64    /// Enable geographic diversity checks.
65    /// Default: false (compared to true in production).
66    #[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/// Node configuration.
90#[derive(Debug, Clone, Serialize, Deserialize)]
91pub struct NodeConfig {
92    /// Root directory for node data.
93    #[serde(default = "default_root_dir")]
94    pub root_dir: PathBuf,
95
96    /// Listening port (0 for auto-select).
97    #[serde(default)]
98    pub port: u16,
99
100    /// IP version to use.
101    #[serde(default)]
102    pub ip_version: IpVersion,
103
104    /// Bootstrap peer addresses.
105    #[serde(default)]
106    pub bootstrap: Vec<SocketAddr>,
107
108    /// Network mode (production, testnet, or development).
109    #[serde(default)]
110    pub network_mode: NetworkMode,
111
112    /// Testnet-specific configuration.
113    /// Only used when `network_mode` is `Testnet`.
114    #[serde(default)]
115    pub testnet: TestnetConfig,
116
117    /// Upgrade configuration.
118    #[serde(default)]
119    pub upgrade: UpgradeConfig,
120
121    /// Payment verification configuration.
122    #[serde(default)]
123    pub payment: PaymentConfig,
124
125    /// Attestation configuration for software integrity verification.
126    #[serde(default)]
127    pub attestation: AttestationNodeConfig,
128
129    /// Bootstrap cache configuration for persistent peer storage.
130    #[serde(default)]
131    pub bootstrap_cache: BootstrapCacheConfig,
132
133    /// Log level.
134    #[serde(default = "default_log_level")]
135    pub log_level: String,
136}
137
138/// Auto-upgrade configuration.
139#[derive(Debug, Clone, Serialize, Deserialize)]
140pub struct UpgradeConfig {
141    /// Enable automatic upgrades.
142    #[serde(default)]
143    pub enabled: bool,
144
145    /// Release channel.
146    #[serde(default)]
147    pub channel: UpgradeChannel,
148
149    /// Check interval in hours.
150    #[serde(default = "default_check_interval")]
151    pub check_interval_hours: u64,
152
153    /// GitHub repository in "owner/repo" format for release monitoring.
154    #[serde(default = "default_github_repo")]
155    pub github_repo: String,
156
157    /// Staged rollout window in hours.
158    ///
159    /// When a new version is detected, each node waits a deterministic delay
160    /// based on its node ID before applying the upgrade. This prevents mass
161    /// restarts and ensures network stability during upgrades.
162    ///
163    /// Set to 0 to disable staged rollout (apply upgrades immediately).
164    #[serde(default = "default_staged_rollout_hours")]
165    pub staged_rollout_hours: u64,
166}
167
168/// EVM network for payment processing.
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
170#[serde(rename_all = "kebab-case")]
171pub enum EvmNetworkConfig {
172    /// Arbitrum One mainnet.
173    #[default]
174    ArbitrumOne,
175    /// Arbitrum Sepolia testnet.
176    ArbitrumSepolia,
177}
178
179/// Payment verification configuration.
180///
181/// All new data requires EVM payment on Arbitrum. The cache stores
182/// previously verified payments to avoid redundant lookups.
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct PaymentConfig {
185    /// Enable payment verification.
186    #[serde(default = "default_payment_enabled")]
187    pub enabled: bool,
188
189    /// Cache capacity for verified `XorNames`.
190    #[serde(default = "default_cache_capacity")]
191    pub cache_capacity: usize,
192
193    /// EVM wallet address for receiving payments (e.g., "0x...").
194    /// If not set, the node will not be able to receive payments.
195    #[serde(default)]
196    pub rewards_address: Option<String>,
197
198    /// EVM network for payment processing.
199    #[serde(default)]
200    pub evm_network: EvmNetworkConfig,
201
202    /// Metrics port for Prometheus scraping.
203    /// Set to 0 to disable metrics endpoint.
204    #[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// ============================================================================
225// Attestation Configuration
226// ============================================================================
227
228/// Attestation enforcement mode.
229///
230/// Controls how the node responds to attestation verification failures.
231#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
232#[serde(rename_all = "lowercase")]
233pub enum AttestationMode {
234    /// Attestation is completely disabled (default).
235    /// No verification is performed.
236    #[default]
237    Off,
238    /// Soft enforcement: log warnings but don't reject connections.
239    /// Useful for testing and gradual rollout.
240    Soft,
241    /// Hard enforcement: reject peers with invalid attestations.
242    /// Requires `zkvm-prover` or `zkvm-verifier-groth16` feature for security.
243    Hard,
244}
245
246/// Attestation configuration for software integrity verification.
247///
248/// # Security Warning
249///
250/// **Without the `zkvm-prover` feature enabled, attestation proofs are accepted
251/// without cryptographic verification (mock prover).** This provides **NO SECURITY**.
252///
253/// For production deployments:
254/// - Enable `zkvm-prover` feature for post-quantum secure STARK verification
255/// - Or enable `zkvm-verifier-groth16` for Groth16 verification (NOT post-quantum secure)
256///
257/// The node will **block startup** if attestation is enabled without a verification feature.
258///
259/// # Example Configuration
260///
261/// ```toml
262/// [attestation]
263/// enabled = true
264/// mode = "hard"
265/// require_pq_secure = true
266/// allowed_binary_hashes = ["a1b2c3..."]
267/// ```
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct AttestationNodeConfig {
270    /// Enable attestation verification.
271    /// Default: false (disabled for backward compatibility)
272    #[serde(default)]
273    pub enabled: bool,
274
275    /// Enforcement mode for attestation verification.
276    /// Default: off
277    #[serde(default)]
278    pub mode: AttestationMode,
279
280    /// Require post-quantum secure verification.
281    /// If true, only STARK proofs (via `zkvm-prover`) are accepted.
282    /// If false, Groth16 proofs are also accepted.
283    /// Default: true
284    #[serde(default = "default_require_pq_secure")]
285    pub require_pq_secure: bool,
286
287    /// Allowed binary hashes (hex-encoded, 64 characters each).
288    /// Empty list = permissive mode (all binaries allowed).
289    /// In production, set specific hashes of authorized binaries.
290    #[serde(default)]
291    pub allowed_binary_hashes: Vec<String>,
292
293    /// Grace period in days after sunset before hard rejection.
294    /// During the grace period, nodes can still connect but with warnings.
295    /// Default: 30
296    #[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    /// Development configuration: soft enforcement, all binaries allowed.
314    #[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    /// Production configuration: hard enforcement with specific allowed binaries.
326    #[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    /// Create a testnet configuration preset.
374    ///
375    /// This is a convenience method for setting up a testnet node with
376    /// relaxed anti-Sybil protection, suitable for single-provider deployments.
377    /// Includes default bootstrap nodes for the Saorsa testnet.
378    #[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    /// Create a development configuration preset.
389    ///
390    /// This has minimal restrictions and is only suitable for local testing.
391    #[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    /// Check if this configuration is using relaxed security settings.
406    #[must_use]
407    pub fn is_relaxed(&self) -> bool {
408        !matches!(self.network_mode, NetworkMode::Production)
409    }
410
411    /// Load configuration from a TOML file.
412    ///
413    /// # Errors
414    ///
415    /// Returns an error if the file cannot be read or parsed.
416    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    /// Save configuration to a TOML file.
422    ///
423    /// # Errors
424    ///
425    /// Returns an error if the file cannot be written.
426    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 // 1 hour
463}
464
465const fn default_staged_rollout_hours() -> u64 {
466    1 // 1 hour window for staged rollout (testing)
467}
468
469// ============================================================================
470// Bootstrap Cache Configuration
471// ============================================================================
472
473/// Bootstrap cache configuration for persistent peer storage.
474///
475/// The bootstrap cache stores discovered peers across node restarts,
476/// ranking them by quality metrics (success rate, latency, recency).
477/// This reduces dependency on hardcoded bootstrap nodes and enables
478/// faster network reconnection after restarts.
479#[derive(Debug, Clone, Serialize, Deserialize)]
480pub struct BootstrapCacheConfig {
481    /// Enable persistent bootstrap cache.
482    /// Default: true
483    #[serde(default = "default_bootstrap_cache_enabled")]
484    pub enabled: bool,
485
486    /// Directory for cache files.
487    /// Default: `{root_dir}/bootstrap_cache/`
488    #[serde(default)]
489    pub cache_dir: Option<PathBuf>,
490
491    /// Maximum contacts to store in the cache.
492    /// Default: 10,000
493    #[serde(default = "default_bootstrap_max_contacts")]
494    pub max_contacts: usize,
495
496    /// Stale contact threshold in days.
497    /// Contacts older than this are removed during cleanup.
498    /// Default: 7 days
499    #[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
526/// Default testnet bootstrap nodes.
527///
528/// These are well-known bootstrap nodes for the Saorsa testnet.
529/// - saorsa-bootstrap-1 (NYC): 165.22.4.178:12000
530/// - saorsa-bootstrap-2 (SFO): 164.92.111.156:12000
531fn default_testnet_bootstrap() -> Vec<SocketAddr> {
532    use std::net::{Ipv4Addr, SocketAddrV4};
533
534    vec![
535        // saorsa-bootstrap-1 (Digital Ocean NYC1)
536        SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(165, 22, 4, 178), 12000)),
537        // saorsa-bootstrap-2 (Digital Ocean SFO3)
538        SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(164, 92, 111, 156), 12000)),
539    ]
540}