1use serde::{Deserialize, Serialize};
4use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
5use std::path::PathBuf;
6
7pub const NODE_IDENTITY_FILENAME: &str = "node_identity.key";
9
10pub const NODES_SUBDIR: &str = "nodes";
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum IpVersion {
17 Ipv4,
19 Ipv6,
21 #[default]
23 Dual,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
28#[serde(rename_all = "lowercase")]
29pub enum UpgradeChannel {
30 #[default]
32 Stable,
33 Beta,
35}
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
39#[serde(rename_all = "lowercase")]
40pub enum NetworkMode {
41 #[default]
43 Production,
44 Testnet,
47 Development,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct TestnetConfig {
55 #[serde(default = "default_testnet_max_per_asn")]
58 pub max_nodes_per_asn: usize,
59
60 #[serde(default = "default_testnet_max_per_64")]
63 pub max_nodes_per_64: usize,
64
65 #[serde(default)]
68 pub enforce_age_requirements: bool,
69
70 #[serde(default)]
73 pub enable_geo_checks: bool,
74}
75
76impl Default for TestnetConfig {
77 fn default() -> Self {
78 Self {
79 max_nodes_per_asn: default_testnet_max_per_asn(),
80 max_nodes_per_64: default_testnet_max_per_64(),
81 enforce_age_requirements: false,
82 enable_geo_checks: false,
83 }
84 }
85}
86
87const fn default_testnet_max_per_asn() -> usize {
88 5000
89}
90
91const fn default_testnet_max_per_64() -> usize {
92 100
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct NodeConfig {
98 #[serde(default = "default_root_dir")]
100 pub root_dir: PathBuf,
101
102 #[serde(default)]
104 pub port: u16,
105
106 #[serde(default)]
108 pub ip_version: IpVersion,
109
110 #[serde(default)]
112 pub bootstrap: Vec<SocketAddr>,
113
114 #[serde(default)]
116 pub network_mode: NetworkMode,
117
118 #[serde(default)]
121 pub testnet: TestnetConfig,
122
123 #[serde(default)]
125 pub upgrade: UpgradeConfig,
126
127 #[serde(default)]
129 pub payment: PaymentConfig,
130
131 #[serde(default)]
133 pub bootstrap_cache: BootstrapCacheConfig,
134
135 #[serde(default)]
137 pub storage: StorageConfig,
138
139 #[serde(default = "default_max_message_size")]
146 pub max_message_size: usize,
147
148 #[serde(default = "default_log_level")]
150 pub log_level: String,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct UpgradeConfig {
156 #[serde(default)]
158 pub enabled: bool,
159
160 #[serde(default)]
162 pub channel: UpgradeChannel,
163
164 #[serde(default = "default_check_interval")]
166 pub check_interval_hours: u64,
167
168 #[serde(default = "default_github_repo")]
170 pub github_repo: String,
171
172 #[serde(default = "default_staged_rollout_hours")]
180 pub staged_rollout_hours: u64,
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
185#[serde(rename_all = "kebab-case")]
186pub enum EvmNetworkConfig {
187 #[default]
189 ArbitrumOne,
190 ArbitrumSepolia,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
200pub struct PaymentConfig {
201 #[serde(default = "default_cache_capacity")]
203 pub cache_capacity: usize,
204
205 #[serde(default)]
208 pub rewards_address: Option<String>,
209
210 #[serde(default)]
212 pub evm_network: EvmNetworkConfig,
213
214 #[serde(default = "default_metrics_port")]
217 pub metrics_port: u16,
218}
219
220impl Default for PaymentConfig {
221 fn default() -> Self {
222 Self {
223 cache_capacity: default_cache_capacity(),
224 rewards_address: None,
225 evm_network: EvmNetworkConfig::default(),
226 metrics_port: default_metrics_port(),
227 }
228 }
229}
230
231const fn default_metrics_port() -> u16 {
232 9100
233}
234
235const fn default_cache_capacity() -> usize {
236 100_000
237}
238
239impl Default for NodeConfig {
240 fn default() -> Self {
241 Self {
242 root_dir: default_root_dir(),
243 port: 0,
244 ip_version: IpVersion::default(),
245 bootstrap: Vec::new(),
246 network_mode: NetworkMode::default(),
247 testnet: TestnetConfig::default(),
248 upgrade: UpgradeConfig::default(),
249 payment: PaymentConfig::default(),
250 bootstrap_cache: BootstrapCacheConfig::default(),
251 storage: StorageConfig::default(),
252 max_message_size: default_max_message_size(),
253 log_level: default_log_level(),
254 }
255 }
256}
257
258impl NodeConfig {
259 #[must_use]
265 pub fn testnet() -> Self {
266 Self {
267 network_mode: NetworkMode::Testnet,
268 testnet: TestnetConfig::default(),
269 bootstrap: default_testnet_bootstrap(),
270 ..Self::default()
271 }
272 }
273
274 #[must_use]
278 pub fn development() -> Self {
279 Self {
280 network_mode: NetworkMode::Development,
281 testnet: TestnetConfig {
282 max_nodes_per_asn: usize::MAX,
283 max_nodes_per_64: usize::MAX,
284 enforce_age_requirements: false,
285 enable_geo_checks: false,
286 },
287 ..Self::default()
288 }
289 }
290
291 #[must_use]
293 pub fn is_relaxed(&self) -> bool {
294 !matches!(self.network_mode, NetworkMode::Production)
295 }
296
297 pub fn from_file(path: &std::path::Path) -> crate::Result<Self> {
303 let content = std::fs::read_to_string(path)?;
304 toml::from_str(&content).map_err(|e| crate::Error::Config(e.to_string()))
305 }
306
307 pub fn to_file(&self, path: &std::path::Path) -> crate::Result<()> {
313 let content =
314 toml::to_string_pretty(self).map_err(|e| crate::Error::Config(e.to_string()))?;
315 std::fs::write(path, content)?;
316 Ok(())
317 }
318}
319
320impl Default for UpgradeConfig {
321 fn default() -> Self {
322 Self {
323 enabled: false,
324 channel: UpgradeChannel::default(),
325 check_interval_hours: default_check_interval(),
326 github_repo: default_github_repo(),
327 staged_rollout_hours: default_staged_rollout_hours(),
328 }
329 }
330}
331
332fn default_github_repo() -> String {
333 "dirvine/saorsa-node".to_string()
334}
335
336#[must_use]
338pub fn default_root_dir() -> PathBuf {
339 directories::ProjectDirs::from("", "", "saorsa").map_or_else(
340 || PathBuf::from(".saorsa"),
341 |dirs| dirs.data_dir().to_path_buf(),
342 )
343}
344
345#[must_use]
350pub fn default_nodes_dir() -> PathBuf {
351 default_root_dir().join(NODES_SUBDIR)
352}
353
354fn default_max_message_size() -> usize {
355 crate::ant_protocol::MAX_WIRE_MESSAGE_SIZE
356}
357
358fn default_log_level() -> String {
359 "info".to_string()
360}
361
362const fn default_check_interval() -> u64 {
363 1 }
365
366const fn default_staged_rollout_hours() -> u64 {
367 1 }
369
370#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct BootstrapCacheConfig {
382 #[serde(default = "default_bootstrap_cache_enabled")]
385 pub enabled: bool,
386
387 #[serde(default)]
390 pub cache_dir: Option<PathBuf>,
391
392 #[serde(default = "default_bootstrap_max_contacts")]
395 pub max_contacts: usize,
396
397 #[serde(default = "default_bootstrap_stale_days")]
401 pub stale_threshold_days: u64,
402}
403
404impl Default for BootstrapCacheConfig {
405 fn default() -> Self {
406 Self {
407 enabled: default_bootstrap_cache_enabled(),
408 cache_dir: None,
409 max_contacts: default_bootstrap_max_contacts(),
410 stale_threshold_days: default_bootstrap_stale_days(),
411 }
412 }
413}
414
415const fn default_bootstrap_cache_enabled() -> bool {
416 true
417}
418
419const fn default_bootstrap_max_contacts() -> usize {
420 10_000
421}
422
423const fn default_bootstrap_stale_days() -> u64 {
424 7
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
438pub struct StorageConfig {
439 #[serde(default = "default_storage_enabled")]
442 pub enabled: bool,
443
444 #[serde(default)]
447 pub max_chunks: usize,
448
449 #[serde(default = "default_storage_verify_on_read")]
452 pub verify_on_read: bool,
453
454 #[serde(default)]
458 pub db_size_gb: usize,
459}
460
461impl Default for StorageConfig {
462 fn default() -> Self {
463 Self {
464 enabled: default_storage_enabled(),
465 max_chunks: 0,
466 verify_on_read: default_storage_verify_on_read(),
467 db_size_gb: 0,
468 }
469 }
470}
471
472const fn default_storage_enabled() -> bool {
473 true
474}
475
476const fn default_storage_verify_on_read() -> bool {
477 true
478}
479
480fn default_testnet_bootstrap() -> Vec<SocketAddr> {
486 vec![
487 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(165, 22, 4, 178), 12000)),
489 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(164, 92, 111, 156), 12000)),
491 ]
492}
493
494#[cfg(test)]
495mod tests {
496 use super::*;
497
498 #[test]
499 fn test_default_config_has_cache_capacity() {
500 let config = PaymentConfig::default();
501 assert!(config.cache_capacity > 0, "Cache capacity must be positive");
502 }
503
504 #[test]
505 fn test_default_evm_network() {
506 use crate::payment::EvmVerifierConfig;
507 let _config = EvmVerifierConfig::default();
508 }
510}