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