Skip to main content

hopr_lib/
config.rs

1use std::time::Duration;
2
3use hopr_api::types::primitive::prelude::*;
4pub use hopr_transport::{
5    TagAllocatorConfig,
6    config::{
7        HoprPacketPipelineConfig, HoprProtocolConfig, HostConfig, HostType, MixerConfig, ProbeConfig,
8        SessionGlobalConfig, TransitLatencyConfig, TransportConfig, looks_like_domain,
9    },
10};
11use validator::{Validate, ValidationError};
12
13pub const DEFAULT_HOST: &str = "0.0.0.0";
14pub const DEFAULT_PORT: u16 = 9091;
15
16#[inline]
17fn default_invalid_address() -> Address {
18    Address::default()
19}
20
21#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_with::serde_as)]
22#[derive(Debug, Clone, PartialEq, smart_default::SmartDefault, Validate)]
23#[cfg_attr(
24    feature = "serde",
25    derive(serde::Serialize, serde::Deserialize),
26    serde(deny_unknown_fields)
27)]
28pub struct SafeModule {
29    #[cfg_attr(
30        feature = "serde",
31        serde_as(as = "serde_with::DisplayFromStr"),
32        serde(default = "default_invalid_address")
33    )]
34    #[default(default_invalid_address())]
35    pub safe_address: Address,
36    #[cfg_attr(
37        feature = "serde",
38        serde_as(as = "serde_with::DisplayFromStr"),
39        serde(default = "default_invalid_address")
40    )]
41    #[default(default_invalid_address())]
42    pub module_address: Address,
43}
44
45#[cfg(feature = "session-server")]
46#[inline]
47fn default_incoming_session_capacity() -> usize {
48    256
49}
50
51#[allow(dead_code)]
52fn validate_directory_exists(s: &str) -> Result<(), ValidationError> {
53    if std::path::Path::new(s).is_dir() {
54        Ok(())
55    } else {
56        Err(ValidationError::new("Invalid directory path specified"))
57    }
58}
59
60#[derive(Debug, Clone, PartialEq, smart_default::SmartDefault, Validate)]
61#[cfg_attr(
62    feature = "serde",
63    derive(serde::Serialize, serde::Deserialize),
64    serde(deny_unknown_fields)
65)]
66pub struct HoprLibConfig {
67    /// Configuration related to host specifics
68    #[validate(nested)]
69    #[default(default_host())]
70    #[cfg_attr(feature = "serde", serde(default = "default_host"))]
71    pub host: HostConfig,
72    /// Determines whether the node should be advertised publicly on-chain.
73    #[cfg_attr(feature = "serde", serde(default))]
74    pub publish: bool,
75    /// Configuration of the HOPR protocol.
76    #[validate(nested)]
77    #[cfg_attr(feature = "serde", serde(default))]
78    pub protocol: HoprProtocolConfig,
79    /// Configuration of the node Safe and Module.
80    #[validate(nested)]
81    #[cfg_attr(feature = "serde", serde(default))]
82    pub safe_module: SafeModule,
83    /// Defines how often the outgoing ticket indices be saved to the persistent storage.
84    ///
85    /// If synchronization to a persistent storage does not happen and the node restarts,
86    /// the node will start from the current on-chain channel index and could as a result
87    /// be creating invalid outgoing tickets.
88    ///
89    /// Default is 15 seconds, minimum is 1 second.
90    #[default(default_out_index_sync_period())]
91    #[validate(custom(function = "validate_out_index_sync_period"))]
92    #[cfg_attr(
93        feature = "serde",
94        serde(default = "default_out_index_sync_period", with = "humantime_serde")
95    )]
96    pub out_index_sync_period: Duration,
97    /// Capacity of the incoming session channel (number of buffered sessions).
98    ///
99    /// Only relevant when the `session-server` feature is enabled. Default is 256.
100    #[cfg(feature = "session-server")]
101    #[default(default_incoming_session_capacity())]
102    #[cfg_attr(feature = "serde", serde(default = "default_incoming_session_capacity"))]
103    pub incoming_session_capacity: usize,
104    /// Disables win-probability and ticket-price protocol safety checks.
105    ///
106    /// Only available in debug builds. Never set in production.
107    #[cfg(debug_assertions)]
108    #[cfg_attr(feature = "serde", serde(default))]
109    pub disable_protocol_checks: bool,
110}
111
112const MINIMUM_OUT_SYNC_PERIOD: Duration = Duration::from_secs(1);
113
114fn validate_out_index_sync_period(lifetime: &Duration) -> Result<(), ValidationError> {
115    if lifetime < &MINIMUM_OUT_SYNC_PERIOD {
116        Err(ValidationError::new("out_index_sync_period is too low"))
117    } else {
118        Ok(())
119    }
120}
121
122fn default_out_index_sync_period() -> Duration {
123    Duration::from_secs(15)
124}
125
126// NOTE: this intentionally does not validate (0.0.0.0) to force user to specify
127// their external IP.
128#[inline]
129fn default_host() -> HostConfig {
130    HostConfig {
131        address: HostType::IPv4(DEFAULT_HOST.to_owned()),
132        port: DEFAULT_PORT,
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    #[cfg(feature = "serde")]
139    #[test]
140    fn test_config_should_be_serializable_using_serde() -> Result<(), Box<dyn std::error::Error>> {
141        let cfg = super::HoprLibConfig::default();
142
143        let yaml = serde_saphyr::to_string(&cfg)?;
144        let cfg_after_serde: super::HoprLibConfig = serde_saphyr::from_str(&yaml)?;
145        assert_eq!(cfg, cfg_after_serde);
146
147        Ok(())
148    }
149
150    #[cfg(feature = "serde")]
151    #[test]
152    fn explicit_mixer_section_round_trips() -> anyhow::Result<()> {
153        use std::time::Duration;
154
155        let mut cfg = super::HoprLibConfig::default();
156        cfg.protocol.mixer = super::MixerConfig {
157            min_delay: Duration::from_millis(5),
158            delay_range: Duration::from_millis(50),
159            capacity: 1_000,
160            ..Default::default()
161        };
162        insta::assert_yaml_snapshot!(cfg.protocol.mixer);
163
164        let yaml = serde_saphyr::to_string(&cfg.protocol.mixer)?;
165        let parsed: super::MixerConfig = serde_saphyr::from_str(&yaml)?;
166        assert_eq!(cfg.protocol.mixer, parsed);
167
168        Ok(())
169    }
170}