Skip to main content

saorsa_node/
config.rs

1//! Configuration for saorsa-node.
2
3use serde::{Deserialize, Serialize};
4use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
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    /// Storage configuration for chunk persistence.
134    #[serde(default)]
135    pub storage: StorageConfig,
136
137    /// Log level.
138    #[serde(default = "default_log_level")]
139    pub log_level: String,
140}
141
142/// Auto-upgrade configuration.
143#[derive(Debug, Clone, Serialize, Deserialize)]
144pub struct UpgradeConfig {
145    /// Enable automatic upgrades.
146    #[serde(default)]
147    pub enabled: bool,
148
149    /// Release channel.
150    #[serde(default)]
151    pub channel: UpgradeChannel,
152
153    /// Check interval in hours.
154    #[serde(default = "default_check_interval")]
155    pub check_interval_hours: u64,
156
157    /// GitHub repository in "owner/repo" format for release monitoring.
158    #[serde(default = "default_github_repo")]
159    pub github_repo: String,
160
161    /// Staged rollout window in hours.
162    ///
163    /// When a new version is detected, each node waits a deterministic delay
164    /// based on its node ID before applying the upgrade. This prevents mass
165    /// restarts and ensures network stability during upgrades.
166    ///
167    /// Set to 0 to disable staged rollout (apply upgrades immediately).
168    #[serde(default = "default_staged_rollout_hours")]
169    pub staged_rollout_hours: u64,
170}
171
172/// EVM network for payment processing.
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
174#[serde(rename_all = "kebab-case")]
175pub enum EvmNetworkConfig {
176    /// Arbitrum One mainnet.
177    #[default]
178    ArbitrumOne,
179    /// Arbitrum Sepolia testnet.
180    ArbitrumSepolia,
181}
182
183/// Payment verification configuration.
184///
185/// All new data requires EVM payment on Arbitrum. The cache stores
186/// previously verified payments to avoid redundant lookups.
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct PaymentConfig {
189    /// Enable payment verification.
190    #[serde(default = "default_payment_enabled")]
191    pub enabled: bool,
192
193    /// Cache capacity for verified `XorNames`.
194    #[serde(default = "default_cache_capacity")]
195    pub cache_capacity: usize,
196
197    /// EVM wallet address for receiving payments (e.g., "0x...").
198    /// If not set, the node will not be able to receive payments.
199    #[serde(default)]
200    pub rewards_address: Option<String>,
201
202    /// EVM network for payment processing.
203    #[serde(default)]
204    pub evm_network: EvmNetworkConfig,
205
206    /// Metrics port for Prometheus scraping.
207    /// Set to 0 to disable metrics endpoint.
208    #[serde(default = "default_metrics_port")]
209    pub metrics_port: u16,
210}
211
212impl Default for PaymentConfig {
213    fn default() -> Self {
214        Self {
215            enabled: default_payment_enabled(),
216            cache_capacity: default_cache_capacity(),
217            rewards_address: None,
218            evm_network: EvmNetworkConfig::default(),
219            metrics_port: default_metrics_port(),
220        }
221    }
222}
223
224const fn default_metrics_port() -> u16 {
225    9100
226}
227
228// ============================================================================
229// Attestation Configuration
230// ============================================================================
231
232/// Attestation enforcement mode.
233///
234/// Controls how the node responds to attestation verification failures.
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
236#[serde(rename_all = "lowercase")]
237pub enum AttestationMode {
238    /// Attestation is completely disabled (default).
239    /// No verification is performed.
240    #[default]
241    Off,
242    /// Soft enforcement: log warnings but don't reject connections.
243    /// Useful for testing and gradual rollout.
244    Soft,
245    /// Hard enforcement: reject peers with invalid attestations.
246    /// Requires `zkvm-prover` or `zkvm-verifier-groth16` feature for security.
247    Hard,
248}
249
250/// Attestation configuration for software integrity verification.
251///
252/// # Security Warning
253///
254/// **Without the `zkvm-prover` feature enabled, attestation proofs are accepted
255/// without cryptographic verification (mock prover).** This provides **NO SECURITY**.
256///
257/// For production deployments:
258/// - Enable `zkvm-prover` feature for post-quantum secure STARK verification
259/// - Or enable `zkvm-verifier-groth16` for Groth16 verification (NOT post-quantum secure)
260///
261/// The node will **block startup** if attestation is enabled without a verification feature.
262///
263/// # Example Configuration
264///
265/// ```toml
266/// [attestation]
267/// enabled = true
268/// mode = "hard"
269/// require_pq_secure = true
270/// allowed_binary_hashes = ["a1b2c3..."]
271/// ```
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct AttestationNodeConfig {
274    /// Enable attestation verification.
275    /// Default: false (disabled for backward compatibility)
276    #[serde(default)]
277    pub enabled: bool,
278
279    /// Enforcement mode for attestation verification.
280    /// Default: off
281    #[serde(default)]
282    pub mode: AttestationMode,
283
284    /// Require post-quantum secure verification.
285    /// If true, only STARK proofs (via `zkvm-prover`) are accepted.
286    /// If false, Groth16 proofs are also accepted.
287    /// Default: true
288    #[serde(default = "default_require_pq_secure")]
289    pub require_pq_secure: bool,
290
291    /// Allowed binary hashes (hex-encoded, 64 characters each).
292    /// Empty list = permissive mode (all binaries allowed).
293    /// In production, set specific hashes of authorized binaries.
294    #[serde(default)]
295    pub allowed_binary_hashes: Vec<String>,
296
297    /// Grace period in days after sunset before hard rejection.
298    /// During the grace period, nodes can still connect but with warnings.
299    /// Default: 30
300    #[serde(default = "default_sunset_grace_days")]
301    pub sunset_grace_days: u32,
302}
303
304impl Default for AttestationNodeConfig {
305    fn default() -> Self {
306        Self {
307            enabled: false,
308            mode: AttestationMode::Off,
309            require_pq_secure: default_require_pq_secure(),
310            allowed_binary_hashes: Vec::new(),
311            sunset_grace_days: default_sunset_grace_days(),
312        }
313    }
314}
315
316impl AttestationNodeConfig {
317    /// Development configuration: soft enforcement, all binaries allowed.
318    #[must_use]
319    pub fn development() -> Self {
320        Self {
321            enabled: true,
322            mode: AttestationMode::Soft,
323            require_pq_secure: false,
324            allowed_binary_hashes: Vec::new(),
325            sunset_grace_days: 365,
326        }
327    }
328
329    /// Production configuration: hard enforcement with specific allowed binaries.
330    #[must_use]
331    pub fn production(allowed_binary_hashes: Vec<String>) -> Self {
332        Self {
333            enabled: true,
334            mode: AttestationMode::Hard,
335            require_pq_secure: true,
336            allowed_binary_hashes,
337            sunset_grace_days: 30,
338        }
339    }
340}
341
342const fn default_require_pq_secure() -> bool {
343    true
344}
345
346const fn default_sunset_grace_days() -> u32 {
347    30
348}
349
350const fn default_payment_enabled() -> bool {
351    true
352}
353
354const fn default_cache_capacity() -> usize {
355    100_000
356}
357
358impl Default for NodeConfig {
359    fn default() -> Self {
360        Self {
361            root_dir: default_root_dir(),
362            port: 0,
363            ip_version: IpVersion::default(),
364            bootstrap: Vec::new(),
365            network_mode: NetworkMode::default(),
366            testnet: TestnetConfig::default(),
367            upgrade: UpgradeConfig::default(),
368            payment: PaymentConfig::default(),
369            attestation: AttestationNodeConfig::default(),
370            bootstrap_cache: BootstrapCacheConfig::default(),
371            storage: StorageConfig::default(),
372            log_level: default_log_level(),
373        }
374    }
375}
376
377impl NodeConfig {
378    /// Create a testnet configuration preset.
379    ///
380    /// This is a convenience method for setting up a testnet node with
381    /// relaxed anti-Sybil protection, suitable for single-provider deployments.
382    /// Includes default bootstrap nodes for the Saorsa testnet.
383    #[must_use]
384    pub fn testnet() -> Self {
385        Self {
386            network_mode: NetworkMode::Testnet,
387            testnet: TestnetConfig::default(),
388            bootstrap: default_testnet_bootstrap(),
389            ..Self::default()
390        }
391    }
392
393    /// Create a development configuration preset.
394    ///
395    /// This has minimal restrictions and is only suitable for local testing.
396    #[must_use]
397    pub fn development() -> Self {
398        Self {
399            network_mode: NetworkMode::Development,
400            testnet: TestnetConfig {
401                max_nodes_per_asn: usize::MAX,
402                max_nodes_per_64: usize::MAX,
403                enforce_age_requirements: false,
404                enable_geo_checks: false,
405            },
406            ..Self::default()
407        }
408    }
409
410    /// Check if this configuration is using relaxed security settings.
411    #[must_use]
412    pub fn is_relaxed(&self) -> bool {
413        !matches!(self.network_mode, NetworkMode::Production)
414    }
415
416    /// Load configuration from a TOML file.
417    ///
418    /// # Errors
419    ///
420    /// Returns an error if the file cannot be read or parsed.
421    pub fn from_file(path: &std::path::Path) -> crate::Result<Self> {
422        let content = std::fs::read_to_string(path)?;
423        toml::from_str(&content).map_err(|e| crate::Error::Config(e.to_string()))
424    }
425
426    /// Save configuration to a TOML file.
427    ///
428    /// # Errors
429    ///
430    /// Returns an error if the file cannot be written.
431    pub fn to_file(&self, path: &std::path::Path) -> crate::Result<()> {
432        let content =
433            toml::to_string_pretty(self).map_err(|e| crate::Error::Config(e.to_string()))?;
434        std::fs::write(path, content)?;
435        Ok(())
436    }
437}
438
439impl Default for UpgradeConfig {
440    fn default() -> Self {
441        Self {
442            enabled: false,
443            channel: UpgradeChannel::default(),
444            check_interval_hours: default_check_interval(),
445            github_repo: default_github_repo(),
446            staged_rollout_hours: default_staged_rollout_hours(),
447        }
448    }
449}
450
451fn default_github_repo() -> String {
452    "dirvine/saorsa-node".to_string()
453}
454
455fn default_root_dir() -> PathBuf {
456    directories::ProjectDirs::from("", "", "saorsa").map_or_else(
457        || PathBuf::from(".saorsa"),
458        |dirs| dirs.data_dir().to_path_buf(),
459    )
460}
461
462fn default_log_level() -> String {
463    "info".to_string()
464}
465
466const fn default_check_interval() -> u64 {
467    1 // 1 hour
468}
469
470const fn default_staged_rollout_hours() -> u64 {
471    1 // 1 hour window for staged rollout (testing)
472}
473
474// ============================================================================
475// Bootstrap Cache Configuration
476// ============================================================================
477
478/// Bootstrap cache configuration for persistent peer storage.
479///
480/// The bootstrap cache stores discovered peers across node restarts,
481/// ranking them by quality metrics (success rate, latency, recency).
482/// This reduces dependency on hardcoded bootstrap nodes and enables
483/// faster network reconnection after restarts.
484#[derive(Debug, Clone, Serialize, Deserialize)]
485pub struct BootstrapCacheConfig {
486    /// Enable persistent bootstrap cache.
487    /// Default: true
488    #[serde(default = "default_bootstrap_cache_enabled")]
489    pub enabled: bool,
490
491    /// Directory for cache files.
492    /// Default: `{root_dir}/bootstrap_cache/`
493    #[serde(default)]
494    pub cache_dir: Option<PathBuf>,
495
496    /// Maximum contacts to store in the cache.
497    /// Default: 10,000
498    #[serde(default = "default_bootstrap_max_contacts")]
499    pub max_contacts: usize,
500
501    /// Stale contact threshold in days.
502    /// Contacts older than this are removed during cleanup.
503    /// Default: 7 days
504    #[serde(default = "default_bootstrap_stale_days")]
505    pub stale_threshold_days: u64,
506}
507
508impl Default for BootstrapCacheConfig {
509    fn default() -> Self {
510        Self {
511            enabled: default_bootstrap_cache_enabled(),
512            cache_dir: None,
513            max_contacts: default_bootstrap_max_contacts(),
514            stale_threshold_days: default_bootstrap_stale_days(),
515        }
516    }
517}
518
519const fn default_bootstrap_cache_enabled() -> bool {
520    true
521}
522
523const fn default_bootstrap_max_contacts() -> usize {
524    10_000
525}
526
527const fn default_bootstrap_stale_days() -> u64 {
528    7
529}
530
531// ============================================================================
532// Storage Configuration
533// ============================================================================
534
535/// Storage configuration for chunk persistence.
536///
537/// Controls how chunks are stored on disk, including:
538/// - Whether storage is enabled
539/// - Maximum chunks to store (for capacity management)
540/// - Content verification on read
541#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct StorageConfig {
543    /// Enable chunk storage.
544    /// Default: true
545    #[serde(default = "default_storage_enabled")]
546    pub enabled: bool,
547
548    /// Maximum number of chunks to store (0 = unlimited).
549    /// Default: 0 (unlimited)
550    #[serde(default)]
551    pub max_chunks: usize,
552
553    /// Verify content hash matches address on read.
554    /// Default: true
555    #[serde(default = "default_storage_verify_on_read")]
556    pub verify_on_read: bool,
557}
558
559impl Default for StorageConfig {
560    fn default() -> Self {
561        Self {
562            enabled: default_storage_enabled(),
563            max_chunks: 0,
564            verify_on_read: default_storage_verify_on_read(),
565        }
566    }
567}
568
569const fn default_storage_enabled() -> bool {
570    true
571}
572
573const fn default_storage_verify_on_read() -> bool {
574    true
575}
576
577/// Default testnet bootstrap nodes.
578///
579/// These are well-known bootstrap nodes for the Saorsa testnet.
580/// - saorsa-bootstrap-1 (NYC): 165.22.4.178:12000
581/// - saorsa-bootstrap-2 (SFO): 164.92.111.156:12000
582fn default_testnet_bootstrap() -> Vec<SocketAddr> {
583    vec![
584        // saorsa-bootstrap-1 (Digital Ocean NYC1)
585        SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(165, 22, 4, 178), 12000)),
586        // saorsa-bootstrap-2 (Digital Ocean SFO3)
587        SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(164, 92, 111, 156), 12000)),
588    ]
589}