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