Skip to main content

ave_core/
config.rs

1//! # Configuration module
2
3use std::{
4    collections::{BTreeMap, BTreeSet},
5    fmt::{self, Display},
6    path::PathBuf,
7};
8
9use ave_common::identity::{HashAlgorithm, KeyPairAlgorithm};
10use network::Config as NetworkConfig;
11use serde::{Deserialize, Deserializer, Serialize};
12
13use crate::{helpers::sink::TokenResponse, subject::sinkdata::SinkTypes};
14
15/// Node configuration.
16#[derive(Clone, Debug, Deserialize, Serialize)]
17#[serde(default)]
18#[serde(rename_all = "snake_case")]
19pub struct Config {
20    /// Key derivator.
21    pub keypair_algorithm: KeyPairAlgorithm,
22    /// Digest derivator.
23    pub hash_algorithm: HashAlgorithm,
24    /// Database configuration.
25    pub internal_db: AveInternalDBConfig,
26    /// External database configuration.
27    pub external_db: AveExternalDBConfig,
28    /// Network configuration.
29    pub network: NetworkConfig,
30    /// Contract dir.
31    pub contracts_path: PathBuf,
32    /// Approval mode.
33    pub always_accept: bool,
34    /// Safe mode disables mutating operations while allowing queries.
35    pub safe_mode: bool,
36    /// Tracking lru cache size
37    pub tracking_size: usize,
38    /// Is a service node
39    pub is_service: bool,
40    /// Sync protocol configuration.
41    pub sync: SyncConfig,
42    /// Wasmtime execution environment sizing.
43    /// `None` machine spec → auto-detect RAM and CPU from the host.
44    pub spec: Option<MachineSpec>,
45}
46
47impl Default for Config {
48    fn default() -> Self {
49        Self {
50            keypair_algorithm: KeyPairAlgorithm::Ed25519,
51            hash_algorithm: HashAlgorithm::Blake3,
52            internal_db: Default::default(),
53            external_db: Default::default(),
54            network: Default::default(),
55            contracts_path: PathBuf::new(),
56            always_accept: Default::default(),
57            safe_mode: false,
58            tracking_size: 100,
59            is_service: false,
60            sync: Default::default(),
61            spec: None,
62        }
63    }
64}
65
66#[derive(Clone, Debug, Default, Deserialize, Serialize)]
67#[serde(default)]
68#[serde(rename_all = "snake_case")]
69pub struct SyncConfig {
70    pub governance: GovernanceSyncConfig,
71    pub tracker: TrackerSyncConfig,
72}
73
74#[derive(Clone, Debug, Deserialize, Serialize)]
75#[serde(default)]
76#[serde(rename_all = "snake_case")]
77pub struct GovernanceSyncConfig {
78    /// Seconds between version sync rounds for governance service nodes.
79    pub interval_secs: u64,
80    /// Number of peers sampled on each version sync round.
81    pub sample_size: usize,
82    /// Seconds to wait for responses during a version sync round.
83    pub response_timeout_secs: u64,
84}
85
86impl Default for GovernanceSyncConfig {
87    fn default() -> Self {
88        Self {
89            interval_secs: 60,
90            sample_size: 3,
91            response_timeout_secs: 10,
92        }
93    }
94}
95
96#[derive(Clone, Debug, Deserialize, Serialize)]
97#[serde(default)]
98#[serde(rename_all = "snake_case")]
99pub struct TrackerSyncConfig {
100    /// Seconds between tracker sync rounds for service nodes.
101    pub interval_secs: u64,
102    /// Number of tracker subjects returned per remote page.
103    pub page_size: usize,
104    /// Seconds to wait for a tracker sync page response.
105    pub response_timeout_secs: u64,
106    /// Number of tracker updates launched per local batch.
107    pub update_batch_size: usize,
108    /// Seconds between tracker update progress checks.
109    pub update_timeout_secs: u64,
110}
111
112impl Default for TrackerSyncConfig {
113    fn default() -> Self {
114        Self {
115            interval_secs: 30,
116            page_size: 50,
117            response_timeout_secs: 10,
118            update_batch_size: 2,
119            update_timeout_secs: 10,
120        }
121    }
122}
123
124// ── Machine specification ─────────────────────────────────────────────────────
125
126/// How to size the contract execution environment.
127///
128/// - `Profile` — use a predefined instance type.
129/// - `Custom`  — supply exact RAM (MB) and vCPU count manually.
130/// - Absent (`None` in `WasmConfig`) — auto-detect from the running host.
131#[derive(Serialize, Deserialize, Debug, Clone)]
132#[serde(rename_all = "snake_case")]
133pub enum MachineSpec {
134    /// Use a predefined profile.
135    Profile(MachineProfile),
136    /// Supply exact machine dimensions.
137    Custom {
138        /// Total RAM in megabytes.
139        ram_mb: u64,
140        /// Available CPU cores.
141        cpu_cores: usize,
142    },
143}
144
145/// Predefined instance profiles with fixed vCPU and RAM.
146/// They only exist to provide convenient default values — the actual
147/// wasmtime tuning is derived from the resolved `ram_mb` and `cpu_cores`.
148///
149/// | Profile  | vCPU | RAM    |
150/// |----------|------|--------|
151/// | Nano     | 2    | 512 MB |
152/// | Micro    | 2    | 1 GB   |
153/// | Small    | 2    | 2 GB   |
154/// | Medium   | 2    | 4 GB   |
155/// | Large    | 2    | 8 GB   |
156/// | XLarge   | 4    | 16 GB  |
157/// | XXLarge  | 8    | 32 GB  |
158#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
159#[serde(rename_all = "snake_case")]
160pub enum MachineProfile {
161    /// 2 vCPU, 512 MB RAM.
162    Nano,
163    /// 2 vCPU, 1 GB RAM.
164    Micro,
165    /// 2 vCPU, 2 GB RAM.
166    Small,
167    /// 2 vCPU, 4 GB RAM.
168    Medium,
169    /// 2 vCPU, 8 GB RAM.
170    Large,
171    /// 4 vCPU, 16 GB RAM.
172    XLarge,
173    /// 8 vCPU, 32 GB RAM.
174    #[serde(rename = "2xlarge")]
175    XXLarge,
176}
177
178impl MachineProfile {
179    /// Canonical RAM for this profile in megabytes.
180    pub const fn ram_mb(self) -> u64 {
181        match self {
182            Self::Nano => 512,
183            Self::Micro => 1_024,
184            Self::Small => 2_048,
185            Self::Medium => 4_096,
186            Self::Large => 8_192,
187            Self::XLarge => 16_384,
188            Self::XXLarge => 32_768,
189        }
190    }
191
192    /// vCPU count for this profile.
193    pub const fn cpu_cores(self) -> usize {
194        match self {
195            Self::Nano => 2,
196            Self::Micro => 2,
197            Self::Small => 2,
198            Self::Medium => 2,
199            Self::Large => 2,
200            Self::XLarge => 4,
201            Self::XXLarge => 8,
202        }
203    }
204}
205
206impl Display for MachineProfile {
207    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208        match self {
209            Self::Nano => write!(f, "nano"),
210            Self::Micro => write!(f, "micro"),
211            Self::Small => write!(f, "small"),
212            Self::Medium => write!(f, "medium"),
213            Self::Large => write!(f, "large"),
214            Self::XLarge => write!(f, "xlarge"),
215            Self::XXLarge => write!(f, "2xlarge"),
216        }
217    }
218}
219
220// ── Spec resolution ───────────────────────────────────────────────────────────
221
222/// Resolved machine parameters ready to be consumed by any tuned subsystem.
223pub struct ResolvedSpec {
224    /// Total RAM in megabytes.
225    pub ram_mb: u64,
226    /// Available CPU cores.
227    pub cpu_cores: usize,
228}
229
230/// Resolve the final sizing parameters from a [`MachineSpec`]:
231///
232/// - `Profile(p)` → use the profile's canonical RAM and vCPU.
233/// - `Custom { ram_mb, cpu_cores }` → use the supplied values directly.
234/// - `None` → auto-detect total RAM and available CPU cores from the host.
235pub fn resolve_spec(spec: Option<&MachineSpec>) -> ResolvedSpec {
236    match spec {
237        Some(MachineSpec::Profile(p)) => ResolvedSpec {
238            ram_mb: p.ram_mb(),
239            cpu_cores: p.cpu_cores(),
240        },
241        Some(MachineSpec::Custom { ram_mb, cpu_cores }) => ResolvedSpec {
242            ram_mb: *ram_mb,
243            cpu_cores: *cpu_cores,
244        },
245        None => ResolvedSpec {
246            ram_mb: detect_ram_mb(),
247            cpu_cores: detect_cpu_cores(),
248        },
249    }
250}
251
252pub(crate) fn detect_ram_mb() -> u64 {
253    #[cfg(target_os = "linux")]
254    {
255        if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
256            for line in meminfo.lines() {
257                if let Some(rest) = line.strip_prefix("MemTotal:")
258                    && let Some(kb_str) = rest.split_whitespace().next()
259                    && let Ok(kb) = kb_str.parse::<u64>()
260                {
261                    return kb / 1024;
262                }
263            }
264        }
265    }
266    4_096
267}
268
269pub(crate) fn detect_cpu_cores() -> usize {
270    std::thread::available_parallelism()
271        .map(|n| n.get())
272        .unwrap_or(2)
273}
274
275// ── Conversions to peer-crate MachineSpec types ───────────────────────────────
276
277impl From<MachineProfile> for network::MachineProfile {
278    fn from(p: MachineProfile) -> Self {
279        match p {
280            MachineProfile::Nano => Self::Nano,
281            MachineProfile::Micro => Self::Micro,
282            MachineProfile::Small => Self::Small,
283            MachineProfile::Medium => Self::Medium,
284            MachineProfile::Large => Self::Large,
285            MachineProfile::XLarge => Self::XLarge,
286            MachineProfile::XXLarge => Self::XXLarge,
287        }
288    }
289}
290
291impl From<MachineSpec> for network::MachineSpec {
292    fn from(spec: MachineSpec) -> Self {
293        match spec {
294            MachineSpec::Profile(p) => Self::Profile(p.into()),
295            MachineSpec::Custom { ram_mb, cpu_cores } => {
296                Self::Custom { ram_mb, cpu_cores }
297            }
298        }
299    }
300}
301
302impl From<MachineProfile> for ave_actors::MachineProfile {
303    fn from(p: MachineProfile) -> Self {
304        match p {
305            MachineProfile::Nano => Self::Nano,
306            MachineProfile::Micro => Self::Micro,
307            MachineProfile::Small => Self::Small,
308            MachineProfile::Medium => Self::Medium,
309            MachineProfile::Large => Self::Large,
310            MachineProfile::XLarge => Self::XLarge,
311            MachineProfile::XXLarge => Self::XXLarge,
312        }
313    }
314}
315
316impl From<MachineSpec> for ave_actors::MachineSpec {
317    fn from(spec: MachineSpec) -> Self {
318        match spec {
319            MachineSpec::Profile(p) => Self::Profile(p.into()),
320            MachineSpec::Custom { ram_mb, cpu_cores } => {
321                Self::Custom { ram_mb, cpu_cores }
322            }
323        }
324    }
325}
326
327#[derive(Debug, Clone, Deserialize, Serialize, Default)]
328#[serde(default)]
329pub struct AveInternalDBConfig {
330    #[serde(deserialize_with = "AveInternalDBFeatureConfig::deserialize_db")]
331    pub db: AveInternalDBFeatureConfig,
332    pub durability: bool,
333}
334
335/// Database configuration.
336#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
337pub enum AveInternalDBFeatureConfig {
338    /// Rocksdb database.
339    #[cfg(feature = "rocksdb")]
340    Rocksdb {
341        /// Path to the database.
342        path: PathBuf,
343    },
344    /// Sqlite database.
345    #[cfg(feature = "sqlite")]
346    Sqlite {
347        /// Path to the database.
348        path: PathBuf,
349    },
350}
351
352impl Default for AveInternalDBFeatureConfig {
353    fn default() -> Self {
354        #[cfg(feature = "rocksdb")]
355        return AveInternalDBFeatureConfig::Rocksdb {
356            path: PathBuf::from("db").join("local").join("rocksdb"),
357        };
358        #[cfg(feature = "sqlite")]
359        return Self::Sqlite {
360            path: PathBuf::from("db").join("local").join("sqlite"),
361        };
362    }
363}
364
365impl AveInternalDBFeatureConfig {
366    pub fn build(path: &PathBuf) -> Self {
367        #[cfg(feature = "rocksdb")]
368        return AveInternalDBFeatureConfig::Rocksdb {
369            path: path.to_owned(),
370        };
371        #[cfg(feature = "sqlite")]
372        return Self::Sqlite {
373            path: path.to_owned(),
374        };
375    }
376
377    pub fn deserialize_db<'de, D>(deserializer: D) -> Result<Self, D::Error>
378    where
379        D: Deserializer<'de>,
380    {
381        let path: String = String::deserialize(deserializer)?;
382        #[cfg(feature = "rocksdb")]
383        return Ok(AveInternalDBFeatureConfig::Rocksdb {
384            path: PathBuf::from(path),
385        });
386        #[cfg(feature = "sqlite")]
387        return Ok(Self::Sqlite {
388            path: PathBuf::from(path),
389        });
390    }
391}
392
393impl fmt::Display for AveInternalDBFeatureConfig {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        match self {
396            #[cfg(feature = "rocksdb")]
397            AveInternalDBFeatureConfig::Rocksdb { .. } => write!(f, "Rocksdb"),
398            #[cfg(feature = "sqlite")]
399            Self::Sqlite { .. } => write!(f, "Sqlite"),
400        }
401    }
402}
403
404#[derive(Debug, Clone, Deserialize, Serialize, Default)]
405#[serde(default)]
406pub struct AveExternalDBConfig {
407    #[serde(deserialize_with = "AveExternalDBFeatureConfig::deserialize_db")]
408    pub db: AveExternalDBFeatureConfig,
409    pub durability: bool,
410}
411
412/// Database configuration.
413#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
414pub enum AveExternalDBFeatureConfig {
415    /// Sqlite database.
416    #[cfg(feature = "ext-sqlite")]
417    Sqlite {
418        /// Path to the database.
419        path: PathBuf,
420    },
421}
422
423impl Default for AveExternalDBFeatureConfig {
424    fn default() -> Self {
425        #[cfg(feature = "ext-sqlite")]
426        return Self::Sqlite {
427            path: PathBuf::from("db").join("ext").join("sqlite"),
428        };
429    }
430}
431
432impl AveExternalDBFeatureConfig {
433    pub fn build(path: &PathBuf) -> Self {
434        #[cfg(feature = "ext-sqlite")]
435        return Self::Sqlite {
436            path: path.to_owned(),
437        };
438    }
439
440    pub fn deserialize_db<'de, D>(deserializer: D) -> Result<Self, D::Error>
441    where
442        D: Deserializer<'de>,
443    {
444        let path: String = String::deserialize(deserializer)?;
445        #[cfg(feature = "ext-sqlite")]
446        return Ok(Self::Sqlite {
447            path: PathBuf::from(path),
448        });
449    }
450}
451
452impl fmt::Display for AveExternalDBFeatureConfig {
453    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
454        write!(f, "Sqlite")
455    }
456}
457
458#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
459pub struct LoggingOutput {
460    pub stdout: bool,
461    pub file: bool,
462    pub api: bool,
463}
464
465impl Default for LoggingOutput {
466    fn default() -> Self {
467        Self {
468            stdout: true,
469            file: Default::default(),
470            api: Default::default(),
471        }
472    }
473}
474
475#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Default, Serialize)]
476#[serde(rename_all = "lowercase")]
477pub enum LoggingRotation {
478    #[default]
479    Size,
480    Hourly,
481    Daily,
482    Weekly,
483    Monthly,
484    Yearly,
485    Never,
486}
487
488impl Display for LoggingRotation {
489    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490        match self {
491            Self::Size => write!(f, "size"),
492            Self::Hourly => write!(f, "hourly"),
493            Self::Daily => write!(f, "daily"),
494            Self::Weekly => write!(f, "weekly"),
495            Self::Monthly => write!(f, "monthly"),
496            Self::Yearly => write!(f, "yearly"),
497            Self::Never => write!(f, "never"),
498        }
499    }
500}
501
502#[derive(Clone, Debug, Deserialize, Serialize)]
503#[serde(default)]
504pub struct LoggingConfig {
505    pub output: LoggingOutput,
506    pub api_url: Option<String>,
507    pub file_path: PathBuf, // ruta base de logs
508    pub rotation: LoggingRotation,
509    pub max_size: usize,  // bytes
510    pub max_files: usize, // copias a conservar
511    /// Log level filter. Accepts tracing/RUST_LOG syntax: "info", "debug",
512    /// "warn", "error", "trace", or per-crate directives like "info,ave=debug".
513    /// The RUST_LOG environment variable takes priority over this field.
514    pub level: String,
515}
516
517impl Default for LoggingConfig {
518    fn default() -> Self {
519        Self {
520            output: LoggingOutput::default(),
521            api_url: None,
522            file_path: PathBuf::from("logs"),
523            rotation: LoggingRotation::default(),
524            max_size: 100 * 1024 * 1024,
525            max_files: 3,
526            level: "info".to_string(),
527        }
528    }
529}
530
531impl LoggingConfig {
532    pub const fn logs(&self) -> bool {
533        self.output.api || self.output.file || self.output.stdout
534    }
535}
536
537#[derive(Clone, Debug, Deserialize, Default, Eq, PartialEq, Serialize)]
538#[serde(default)]
539pub struct SinkServer {
540    pub server: String,
541    pub events: BTreeSet<SinkTypes>,
542    pub url: String,
543    pub auth: bool,
544    #[serde(default = "default_sink_concurrency")]
545    pub concurrency: usize,
546    #[serde(default = "default_sink_queue_capacity")]
547    pub queue_capacity: usize,
548    #[serde(default)]
549    pub queue_policy: SinkQueuePolicy,
550    #[serde(default)]
551    pub routing_strategy: SinkRoutingStrategy,
552    #[serde(default = "default_sink_connect_timeout_ms")]
553    pub connect_timeout_ms: u64,
554    #[serde(default = "default_sink_request_timeout_ms")]
555    pub request_timeout_ms: u64,
556    #[serde(default = "default_sink_max_retries")]
557    pub max_retries: usize,
558}
559
560#[derive(Default)]
561pub struct SinkAuth {
562    pub sink: SinkConfig,
563    pub token: Option<TokenResponse>,
564    pub password: String,
565    pub api_key: String,
566}
567
568#[derive(Clone, Debug, Deserialize, Default, Serialize)]
569#[serde(default)]
570pub struct SinkConfig {
571    pub sinks: BTreeMap<String, Vec<SinkServer>>,
572    pub auth: String,
573    pub username: String,
574}
575
576#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
577#[serde(rename_all = "snake_case")]
578pub enum SinkQueuePolicy {
579    DropOldest,
580    #[default]
581    DropNewest,
582}
583
584#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
585#[serde(rename_all = "snake_case")]
586pub enum SinkRoutingStrategy {
587    #[default]
588    OrderedBySubject,
589    UnorderedRoundRobin,
590}
591
592const fn default_sink_concurrency() -> usize {
593    2
594}
595
596const fn default_sink_queue_capacity() -> usize {
597    1024
598}
599
600const fn default_sink_connect_timeout_ms() -> u64 {
601    2_000
602}
603
604const fn default_sink_request_timeout_ms() -> u64 {
605    5_000
606}
607
608const fn default_sink_max_retries() -> usize {
609    2
610}