Skip to main content

ant_node/
config.rs

1//! Configuration for ant-node.
2
3use serde::{Deserialize, Serialize};
4use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
5use std::path::PathBuf;
6
7/// Filename for the persisted node identity keypair.
8pub const NODE_IDENTITY_FILENAME: &str = "node_identity.key";
9
10/// Subdirectory under the root dir that contains per-node data directories.
11pub const NODES_SUBDIR: &str = "nodes";
12
13/// IP version configuration.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
15#[serde(rename_all = "lowercase")]
16pub enum IpVersion {
17    /// IPv4 only.
18    Ipv4,
19    /// IPv6 only.
20    Ipv6,
21    /// Dual-stack (both IPv4 and IPv6).
22    #[default]
23    Dual,
24}
25
26/// Upgrade channel for auto-updates.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
28#[serde(rename_all = "lowercase")]
29pub enum UpgradeChannel {
30    /// Stable releases only.
31    #[default]
32    Stable,
33    /// Beta releases (includes stable).
34    Beta,
35}
36
37/// Network mode for different deployment scenarios.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
39#[serde(rename_all = "lowercase")]
40pub enum NetworkMode {
41    /// Production mode with full anti-Sybil protection.
42    #[default]
43    Production,
44    /// Testnet mode with relaxed diversity requirements.
45    /// Suitable for single-provider deployments (e.g., Digital Ocean).
46    Testnet,
47    /// Development mode with minimal restrictions.
48    /// Only use for local testing.
49    Development,
50}
51
52/// Testnet-specific configuration for relaxed anti-Sybil protection.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct TestnetConfig {
55    /// Maximum nodes allowed per ASN.
56    /// Default: 5000 (compared to 20 in production).
57    #[serde(default = "default_testnet_max_per_asn")]
58    pub max_nodes_per_asn: usize,
59
60    /// Maximum nodes allowed per /64 subnet.
61    /// Default: 100 (compared to 1 in production).
62    #[serde(default = "default_testnet_max_per_64")]
63    pub max_nodes_per_64: usize,
64
65    /// Whether to enforce node age requirements.
66    /// Default: false (compared to true in production).
67    #[serde(default)]
68    pub enforce_age_requirements: bool,
69
70    /// Enable geographic diversity checks.
71    /// Default: false (compared to true in production).
72    #[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/// Node configuration.
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct NodeConfig {
98    /// Root directory for node data.
99    #[serde(default = "default_root_dir")]
100    pub root_dir: PathBuf,
101
102    /// Listening port (0 for auto-select).
103    #[serde(default)]
104    pub port: u16,
105
106    /// IP version to use.
107    #[serde(default)]
108    pub ip_version: IpVersion,
109
110    /// Bootstrap peer addresses.
111    #[serde(default)]
112    pub bootstrap: Vec<SocketAddr>,
113
114    /// Network mode (production, testnet, or development).
115    #[serde(default)]
116    pub network_mode: NetworkMode,
117
118    /// Testnet-specific configuration.
119    /// Only used when `network_mode` is `Testnet`.
120    #[serde(default)]
121    pub testnet: TestnetConfig,
122
123    /// Upgrade configuration.
124    #[serde(default)]
125    pub upgrade: UpgradeConfig,
126
127    /// Payment verification configuration.
128    #[serde(default)]
129    pub payment: PaymentConfig,
130
131    /// Bootstrap cache configuration for persistent peer storage.
132    #[serde(default)]
133    pub bootstrap_cache: BootstrapCacheConfig,
134
135    /// Storage configuration for chunk persistence.
136    #[serde(default)]
137    pub storage: StorageConfig,
138
139    /// Maximum application-layer message size in bytes.
140    ///
141    /// Tunes the QUIC stream receive window and per-stream read buffer.
142    /// Default: [`MAX_WIRE_MESSAGE_SIZE`](crate::ant_protocol::MAX_WIRE_MESSAGE_SIZE)
143    /// (5 MiB — sufficient for 4 MiB data chunks plus serialization
144    /// envelope overhead).
145    #[serde(default = "default_max_message_size")]
146    pub max_message_size: usize,
147
148    /// Log level.
149    #[serde(default = "default_log_level")]
150    pub log_level: String,
151}
152
153/// Auto-upgrade configuration.
154#[derive(Debug, Clone, Serialize, Deserialize)]
155pub struct UpgradeConfig {
156    /// Release channel.
157    #[serde(default)]
158    pub channel: UpgradeChannel,
159
160    /// Check interval in hours.
161    #[serde(default = "default_check_interval")]
162    pub check_interval_hours: u64,
163
164    /// GitHub repository in "owner/repo" format for release monitoring.
165    #[serde(default = "default_github_repo")]
166    pub github_repo: String,
167
168    /// Staged rollout window in hours.
169    ///
170    /// When a new version is detected, each node waits a deterministic delay
171    /// based on its node ID before applying the upgrade. This prevents mass
172    /// restarts and ensures network stability during upgrades.
173    ///
174    /// Set to 0 to disable staged rollout (apply upgrades immediately).
175    #[serde(default = "default_staged_rollout_hours")]
176    pub staged_rollout_hours: u64,
177
178    /// Exit cleanly on upgrade instead of spawning a new process.
179    ///
180    /// When true, the node exits after applying an upgrade and relies on
181    /// an external service manager (systemd, launchd, Windows Service) to
182    /// restart it. When false (default), the node spawns the new binary
183    /// as a child process before exiting.
184    #[serde(default)]
185    pub stop_on_upgrade: bool,
186}
187
188/// EVM network for payment processing.
189#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
190#[serde(rename_all = "kebab-case")]
191pub enum EvmNetworkConfig {
192    /// Arbitrum One mainnet.
193    #[default]
194    ArbitrumOne,
195    /// Arbitrum Sepolia testnet.
196    ArbitrumSepolia,
197}
198
199/// Payment verification configuration.
200///
201/// All new data requires EVM payment on Arbitrum — there is no way to
202/// disable payment verification. The cache stores previously verified
203/// payments to avoid redundant on-chain lookups.
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct PaymentConfig {
206    /// Cache capacity for verified `XorNames`.
207    #[serde(default = "default_cache_capacity")]
208    pub cache_capacity: usize,
209
210    /// EVM wallet address for receiving payments (e.g., "0x...").
211    /// If not set, the node will not be able to receive payments.
212    #[serde(default)]
213    pub rewards_address: Option<String>,
214
215    /// EVM network for payment processing.
216    #[serde(default)]
217    pub evm_network: EvmNetworkConfig,
218
219    /// Metrics port for Prometheus scraping.
220    /// Set to 0 to disable metrics endpoint.
221    #[serde(default = "default_metrics_port")]
222    pub metrics_port: u16,
223}
224
225impl Default for PaymentConfig {
226    fn default() -> Self {
227        Self {
228            cache_capacity: default_cache_capacity(),
229            rewards_address: None,
230            evm_network: EvmNetworkConfig::default(),
231            metrics_port: default_metrics_port(),
232        }
233    }
234}
235
236const fn default_metrics_port() -> u16 {
237    9100
238}
239
240const fn default_cache_capacity() -> usize {
241    100_000
242}
243
244impl Default for NodeConfig {
245    fn default() -> Self {
246        Self {
247            root_dir: default_root_dir(),
248            port: 0,
249            ip_version: IpVersion::default(),
250            bootstrap: Vec::new(),
251            network_mode: NetworkMode::default(),
252            testnet: TestnetConfig::default(),
253            upgrade: UpgradeConfig::default(),
254            payment: PaymentConfig::default(),
255            bootstrap_cache: BootstrapCacheConfig::default(),
256            storage: StorageConfig::default(),
257            max_message_size: default_max_message_size(),
258            log_level: default_log_level(),
259        }
260    }
261}
262
263impl NodeConfig {
264    /// Create a testnet configuration preset.
265    ///
266    /// This is a convenience method for setting up a testnet node with
267    /// relaxed anti-Sybil protection, suitable for single-provider deployments.
268    /// Includes default bootstrap nodes for the Autonomi testnet.
269    #[must_use]
270    pub fn testnet() -> Self {
271        Self {
272            network_mode: NetworkMode::Testnet,
273            testnet: TestnetConfig::default(),
274            bootstrap: default_testnet_bootstrap(),
275            ..Self::default()
276        }
277    }
278
279    /// Create a development configuration preset.
280    ///
281    /// This has minimal restrictions and is only suitable for local testing.
282    #[must_use]
283    pub fn development() -> Self {
284        Self {
285            network_mode: NetworkMode::Development,
286            testnet: TestnetConfig {
287                max_nodes_per_asn: usize::MAX,
288                max_nodes_per_64: usize::MAX,
289                enforce_age_requirements: false,
290                enable_geo_checks: false,
291            },
292            ..Self::default()
293        }
294    }
295
296    /// Check if this configuration is using relaxed security settings.
297    #[must_use]
298    pub fn is_relaxed(&self) -> bool {
299        !matches!(self.network_mode, NetworkMode::Production)
300    }
301
302    /// Load configuration from a TOML file.
303    ///
304    /// # Errors
305    ///
306    /// Returns an error if the file cannot be read or parsed.
307    pub fn from_file(path: &std::path::Path) -> crate::Result<Self> {
308        let content = std::fs::read_to_string(path)?;
309        toml::from_str(&content).map_err(|e| crate::Error::Config(e.to_string()))
310    }
311
312    /// Save configuration to a TOML file.
313    ///
314    /// # Errors
315    ///
316    /// Returns an error if the file cannot be written.
317    pub fn to_file(&self, path: &std::path::Path) -> crate::Result<()> {
318        let content =
319            toml::to_string_pretty(self).map_err(|e| crate::Error::Config(e.to_string()))?;
320        std::fs::write(path, content)?;
321        Ok(())
322    }
323}
324
325impl Default for UpgradeConfig {
326    fn default() -> Self {
327        Self {
328            channel: UpgradeChannel::default(),
329            check_interval_hours: default_check_interval(),
330            github_repo: default_github_repo(),
331            staged_rollout_hours: default_staged_rollout_hours(),
332            stop_on_upgrade: false,
333        }
334    }
335}
336
337fn default_github_repo() -> String {
338    "WithAutonomi/ant-node".to_string()
339}
340
341/// Default base directory for node data (platform data dir for "ant").
342#[must_use]
343pub fn default_root_dir() -> PathBuf {
344    directories::ProjectDirs::from("", "", "ant").map_or_else(
345        || PathBuf::from(".ant"),
346        |dirs| dirs.data_dir().to_path_buf(),
347    )
348}
349
350/// Default directory containing per-node data subdirectories.
351///
352/// Each node gets `{default_root_dir}/nodes/{peer_id}/` where `peer_id` is the
353/// full 64-character hex-encoded node ID.
354#[must_use]
355pub fn default_nodes_dir() -> PathBuf {
356    default_root_dir().join(NODES_SUBDIR)
357}
358
359fn default_max_message_size() -> usize {
360    crate::ant_protocol::MAX_WIRE_MESSAGE_SIZE
361}
362
363fn default_log_level() -> String {
364    "info".to_string()
365}
366
367const fn default_check_interval() -> u64 {
368    1 // 1 hour
369}
370
371const fn default_staged_rollout_hours() -> u64 {
372    1 // 1 hour window for staged rollout (testing)
373}
374
375// ============================================================================
376// Bootstrap Cache Configuration
377// ============================================================================
378
379/// Bootstrap cache configuration for persistent peer storage.
380///
381/// The bootstrap cache stores discovered peers across node restarts,
382/// ranking them by quality metrics (success rate, latency, recency).
383/// This reduces dependency on hardcoded bootstrap nodes and enables
384/// faster network reconnection after restarts.
385#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct BootstrapCacheConfig {
387    /// Enable persistent bootstrap cache.
388    /// Default: true
389    #[serde(default = "default_bootstrap_cache_enabled")]
390    pub enabled: bool,
391
392    /// Directory for cache files.
393    /// Default: `{root_dir}/bootstrap_cache/`
394    #[serde(default)]
395    pub cache_dir: Option<PathBuf>,
396
397    /// Maximum contacts to store in the cache.
398    /// Default: 10,000
399    #[serde(default = "default_bootstrap_max_contacts")]
400    pub max_contacts: usize,
401
402    /// Stale contact threshold in days.
403    /// Contacts older than this are removed during cleanup.
404    /// Default: 7 days
405    #[serde(default = "default_bootstrap_stale_days")]
406    pub stale_threshold_days: u64,
407}
408
409impl Default for BootstrapCacheConfig {
410    fn default() -> Self {
411        Self {
412            enabled: default_bootstrap_cache_enabled(),
413            cache_dir: None,
414            max_contacts: default_bootstrap_max_contacts(),
415            stale_threshold_days: default_bootstrap_stale_days(),
416        }
417    }
418}
419
420const fn default_bootstrap_cache_enabled() -> bool {
421    true
422}
423
424const fn default_bootstrap_max_contacts() -> usize {
425    10_000
426}
427
428const fn default_bootstrap_stale_days() -> u64 {
429    7
430}
431
432// ============================================================================
433// Storage Configuration
434// ============================================================================
435
436/// Storage configuration for chunk persistence.
437///
438/// Controls how chunks are stored, including:
439/// - Whether storage is enabled
440/// - Maximum chunks to store (for capacity management)
441/// - Content verification on read
442#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct StorageConfig {
444    /// Enable chunk storage.
445    /// Default: true
446    #[serde(default = "default_storage_enabled")]
447    pub enabled: bool,
448
449    /// Maximum number of chunks to store (0 = unlimited).
450    /// Default: 0 (unlimited)
451    #[serde(default)]
452    pub max_chunks: usize,
453
454    /// Verify content hash matches address on read.
455    /// Default: true
456    #[serde(default = "default_storage_verify_on_read")]
457    pub verify_on_read: bool,
458
459    /// Maximum LMDB database size in GiB (0 = use default of 32 GiB).
460    /// On Unix the mmap is a lazy reservation and costs nothing until pages
461    /// are faulted in.
462    #[serde(default)]
463    pub db_size_gb: usize,
464}
465
466impl Default for StorageConfig {
467    fn default() -> Self {
468        Self {
469            enabled: default_storage_enabled(),
470            max_chunks: 0,
471            verify_on_read: default_storage_verify_on_read(),
472            db_size_gb: 0,
473        }
474    }
475}
476
477const fn default_storage_enabled() -> bool {
478    true
479}
480
481const fn default_storage_verify_on_read() -> bool {
482    true
483}
484
485/// Default testnet bootstrap nodes.
486///
487/// These are well-known bootstrap nodes for the Autonomi testnet.
488/// - ant-bootstrap-1 (NYC): 165.22.4.178:12000
489/// - ant-bootstrap-2 (SFO): 164.92.111.156:12000
490fn default_testnet_bootstrap() -> Vec<SocketAddr> {
491    vec![
492        // ant-bootstrap-1 (Digital Ocean NYC1)
493        SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(165, 22, 4, 178), 12000)),
494        // ant-bootstrap-2 (Digital Ocean SFO3)
495        SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::new(164, 92, 111, 156), 12000)),
496    ]
497}
498
499#[cfg(test)]
500mod tests {
501    use super::*;
502
503    #[test]
504    fn test_default_config_has_cache_capacity() {
505        let config = PaymentConfig::default();
506        assert!(config.cache_capacity > 0, "Cache capacity must be positive");
507    }
508
509    #[test]
510    fn test_default_evm_network() {
511        use crate::payment::EvmVerifierConfig;
512        let _config = EvmVerifierConfig::default();
513        // EVM verification is always on — no enabled field
514    }
515}