hopr_lib/
config.rs

1use hopr_primitive_types::prelude::*;
2pub use hopr_strategy::StrategyConfig;
3use hopr_transport::config::SessionGlobalConfig;
4pub use hopr_transport::config::{
5    HostConfig, HostType, NetworkConfig, ProbeConfig, ProtocolConfig, TransportConfig, validate_external_host,
6};
7use serde::{Deserialize, Serialize};
8use serde_with::{DisplayFromStr, serde_as};
9use validator::{Validate, ValidationError};
10
11pub const DEFAULT_SAFE_TRANSACTION_SERVICE_PROVIDER: &str = "https://safe-transaction.prod.hoprtech.net/";
12pub const DEFAULT_HOST: &str = "0.0.0.0";
13pub const DEFAULT_PORT: u16 = 9091;
14
15fn validate_announced(v: &bool) -> Result<(), ValidationError> {
16    if *v {
17        Ok(())
18    } else {
19        Err(ValidationError::new(
20            "Announce option should be turned ON in 2.*, only public nodes are supported",
21        ))
22    }
23}
24
25fn validate_logs_snapshot_url(url: &&String) -> Result<(), ValidationError> {
26    if url.is_empty() {
27        return Err(ValidationError::new("Logs snapshot URL must not be empty"));
28    }
29
30    // Basic URL validation (allow file:// for testing)
31    if !url.starts_with("http://") && !url.starts_with("https://") && !url.starts_with("file://") {
32        return Err(ValidationError::new(
33            "Logs snapshot URL must be a valid HTTP, HTTPS, or file:// URL",
34        ));
35    }
36
37    // Check if URL ends with .tar.xz
38    if !url.ends_with(".tar.xz") {
39        return Err(ValidationError::new("Logs snapshot URL must point to a .tar.xz file"));
40    }
41
42    Ok(())
43}
44
45#[inline]
46fn default_network() -> String {
47    "anvil-localhost".to_owned()
48}
49
50#[inline]
51fn just_true() -> bool {
52    true
53}
54
55#[inline]
56fn just_false() -> bool {
57    false
58}
59
60#[derive(Debug, Clone, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, Validate)]
61#[serde(deny_unknown_fields)]
62pub struct Chain {
63    #[validate(custom(function = "validate_announced"))]
64    #[serde(default = "just_true")]
65    #[default = true]
66    pub announce: bool,
67    #[serde(default = "default_network")]
68    #[default(default_network())]
69    pub network: String,
70    #[serde(default)]
71    pub provider: Option<String>,
72    #[serde(default)]
73    pub max_rpc_requests_per_sec: Option<u32>,
74    #[serde(default)]
75    pub protocols: hopr_chain_api::config::ProtocolsConfig,
76    #[serde(default = "just_true")]
77    #[default = true]
78    pub keep_logs: bool,
79    #[serde(default = "just_true")]
80    #[default = true]
81    pub fast_sync: bool,
82    #[serde(default = "just_false")]
83    #[default = false]
84    pub enable_logs_snapshot: bool,
85    #[validate(custom(function = "validate_logs_snapshot_url"))]
86    #[serde(default)]
87    pub logs_snapshot_url: Option<String>,
88}
89
90#[inline]
91fn default_invalid_address() -> Address {
92    Address::default()
93}
94
95#[inline]
96fn default_safe_transaction_service_provider() -> String {
97    DEFAULT_SAFE_TRANSACTION_SERVICE_PROVIDER.to_owned()
98}
99
100#[serde_as]
101#[derive(Debug, Clone, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, Validate)]
102#[serde(deny_unknown_fields)]
103pub struct SafeModule {
104    #[validate(url)]
105    #[serde(default = "default_safe_transaction_service_provider")]
106    #[default(default_safe_transaction_service_provider())]
107    pub safe_transaction_service_provider: String,
108    #[serde_as(as = "DisplayFromStr")]
109    #[serde(default = "default_invalid_address")]
110    #[default(default_invalid_address())]
111    pub safe_address: Address,
112    #[serde_as(as = "DisplayFromStr")]
113    #[serde(default = "default_invalid_address")]
114    #[default(default_invalid_address())]
115    pub module_address: Address,
116}
117
118#[allow(dead_code)]
119fn validate_directory_exists(s: &str) -> Result<(), ValidationError> {
120    if std::path::Path::new(s).is_dir() {
121        Ok(())
122    } else {
123        Err(ValidationError::new("Invalid directory path specified"))
124    }
125}
126
127#[derive(Debug, Clone, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, Validate)]
128#[serde(deny_unknown_fields)]
129pub struct Db {
130    /// Path to the directory containing the database
131    #[serde(default)]
132    pub data: String,
133    #[serde(default = "just_true")]
134    #[default = true]
135    pub initialize: bool,
136    #[serde(default)]
137    pub force_initialize: bool,
138}
139
140#[derive(Debug, Clone, PartialEq, smart_default::SmartDefault, Serialize, Deserialize, Validate)]
141pub struct HoprLibConfig {
142    /// Configuration related to host specifics
143    #[validate(nested)]
144    #[validate(custom(function = "validate_external_host"))]
145    #[serde(default = "default_host")]
146    #[default(default_host())]
147    pub host: HostConfig,
148    /// Configuration of the underlying database engine
149    #[validate(nested)]
150    #[serde(default)]
151    pub db: Db,
152    /// Configuration of underlying node behavior in the form strategies
153    ///
154    /// Strategies represent automatically executable behavior performed by
155    /// the node given pre-configured triggers.
156    #[validate(nested)]
157    #[serde(default = "hopr_strategy::hopr_default_strategies")]
158    #[default(hopr_strategy::hopr_default_strategies())]
159    pub strategy: StrategyConfig,
160    /// Configuration of the protocol heartbeat mechanism
161    #[validate(nested)]
162    #[serde(default)]
163    pub probe: ProbeConfig,
164    /// Configuration of network properties
165    #[validate(nested)]
166    #[serde(default)]
167    pub network_options: NetworkConfig,
168    /// Configuration specific to transport mechanics
169    #[validate(nested)]
170    #[serde(default)]
171    pub transport: TransportConfig,
172    /// Configuration specific to protocol execution on the p2p layer
173    #[validate(nested)]
174    #[serde(default)]
175    pub protocol: ProtocolConfig,
176    /// Configuration specific to Session management.
177    #[validate(nested)]
178    #[serde(default)]
179    pub session: SessionGlobalConfig,
180    /// Blockchain-specific configuration
181    #[validate(nested)]
182    #[serde(default)]
183    pub chain: Chain,
184    /// Configuration of the `Safe` mechanism
185    #[validate(nested)]
186    #[serde(default)]
187    pub safe_module: SafeModule,
188}
189
190// NOTE: this intentionally does not validate (0.0.0.0) to force user to specify
191// their external IP.
192#[inline]
193fn default_host() -> HostConfig {
194    HostConfig {
195        address: HostType::IPv4(DEFAULT_HOST.to_owned()),
196        port: DEFAULT_PORT,
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_config_should_be_serializable_using_serde() -> Result<(), Box<dyn std::error::Error>> {
206        let cfg = HoprLibConfig::default();
207
208        let yaml = serde_yaml::to_string(&cfg)?;
209        let cfg_after_serde: HoprLibConfig = serde_yaml::from_str(&yaml)?;
210        assert_eq!(cfg, cfg_after_serde);
211
212        Ok(())
213    }
214}