hopr_chain_api/
config.rs

1use std::str::FromStr;
2
3use hopr_chain_types::ContractAddresses;
4use hopr_primitive_types::primitives::Address;
5use semver::{Version, VersionReq};
6use serde::{Deserialize, Serialize};
7use serde_with::{DisplayFromStr, serde_as};
8use validator::Validate;
9
10use crate::errors::HoprChainError;
11
12/// Types of HOPR network environments.
13#[derive(Debug, Copy, Clone, Deserialize, Serialize, Eq, PartialEq, strum::Display, strum::EnumString)]
14#[serde(rename_all(deserialize = "lowercase"))]
15#[strum(serialize_all = "lowercase")]
16pub enum EnvironmentType {
17    Production,
18    Staging,
19    Development,
20    Local,
21}
22
23/// Holds all information we need about the blockchain network
24/// the client is going to use
25#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
26#[serde(deny_unknown_fields)]
27pub struct ChainOptions {
28    pub description: String,
29    /// >= 0
30    pub chain_id: u32,
31    pub live: bool,
32    /// a valid HTTP url pointing at a RPC endpoint
33    pub default_provider: String,
34    /// a valid HTTP url pointing at a RPC endpoint
35    pub etherscan_api_url: Option<String>,
36    /// The absolute maximum you are willing to pay per unit of gas to get your transaction included in a block, e.g.
37    /// '10 gwei'
38    pub max_fee_per_gas: String,
39    /// Tips paid directly to miners, e.g. '2 gwei'
40    pub max_priority_fee_per_gas: String,
41    pub native_token_name: String,
42    pub hopr_token_name: String,
43    /// expected block time on the chain in milliseconds
44    pub block_time: u64,
45    /// optional maximum number of RPC requests per second for this chain provider
46    pub max_rpc_requests_per_sec: Option<u32>,
47    pub tags: Option<Vec<String>>,
48}
49
50/// Holds all information about the protocol network
51/// to be used by the client
52#[serde_as]
53#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
54#[serde(deny_unknown_fields)]
55pub struct Network {
56    /// must match one of the Network.id
57    pub chain: String,
58    #[serde_as(as = "DisplayFromStr")]
59    pub environment_type: EnvironmentType,
60    /// Node.js-fashioned semver string
61    pub version_range: String,
62    /// block number to start the indexer from
63    pub indexer_start_block_number: u32,
64    pub tags: Vec<String>,
65    /// contract addresses used by the network
66    pub addresses: Addresses,
67    /// number of follow-on blocks required until a block is considered confirmed on-chain
68    pub confirmations: u32,
69    /// milliseconds between polling the RPC for new transactions
70    pub tx_polling_interval: u64,
71    /// maximum block range to fetch while indexing logs
72    pub max_block_range: u64,
73}
74
75#[serde_as]
76#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
77#[serde(deny_unknown_fields)]
78pub struct Addresses {
79    /// address of contract that manages authorization to access the Hopr network
80    #[serde_as(as = "DisplayFromStr")]
81    pub network_registry: Address,
82    /// address of contract that maps to the requirements that need to be fulfilled
83    /// in order to access the network, upgradeable
84    #[serde_as(as = "DisplayFromStr")]
85    pub network_registry_proxy: Address,
86    /// HoprChannels contract address, implementation of mixnet incentives
87    #[serde_as(as = "DisplayFromStr")]
88    pub channels: Address,
89    /// Hopr token contract address
90    #[serde_as(as = "DisplayFromStr")]
91    pub token: Address,
92    /// contract address of Safe capability module implementation
93    #[serde_as(as = "DisplayFromStr")]
94    pub module_implementation: Address,
95    /// address of contract that maps between Safe instances and node addresses
96    #[serde_as(as = "DisplayFromStr")]
97    pub node_safe_registry: Address,
98    /// address of contract that allows Hopr Association to dictate price per packet in Hopr
99    #[serde_as(as = "DisplayFromStr")]
100    pub ticket_price_oracle: Address,
101    /// address of contract that allows Hopr Association to dictate the minimum ticket winning probability in Hopr
102    #[serde_as(as = "DisplayFromStr")]
103    pub winning_probability_oracle: Address,
104    /// address of contract that manages transport announcements in the hopr network
105    #[serde_as(as = "DisplayFromStr")]
106    pub announcements: Address,
107    /// factory contract to produce Safe instances
108    #[serde_as(as = "DisplayFromStr")]
109    pub node_stake_v2_factory: Address,
110}
111
112#[serde_as]
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ChainNetworkConfig {
115    /// the network identifier, e.g. monte_rosa
116    pub id: String,
117    pub chain: ChainOptions,
118    pub environment_type: EnvironmentType,
119    pub channel_contract_deploy_block: u32,
120    /// address of contract that manages authorization to access the Hopr network
121    #[serde_as(as = "DisplayFromStr")]
122    pub network_registry: Address,
123    /// address of contract that maps to the requirements that need to be fulfilled
124    /// in order to access the network, upgradeable
125    #[serde_as(as = "DisplayFromStr")]
126    pub network_registry_proxy: Address,
127    /// HoprChannels contract address, implementation of mixnet incentives
128    #[serde_as(as = "DisplayFromStr")]
129    pub channels: Address,
130    /// Hopr token contract address
131    #[serde_as(as = "DisplayFromStr")]
132    pub token: Address,
133    /// contract address of Safe capability module implementation
134    #[serde_as(as = "DisplayFromStr")]
135    pub module_implementation: Address,
136    /// address of contract that maps between Safe instances and node addresses
137    #[serde_as(as = "DisplayFromStr")]
138    pub node_safe_registry: Address,
139    /// address of contract that allows Hopr Association to dictate price per packet in Hopr
140    #[serde_as(as = "DisplayFromStr")]
141    pub ticket_price_oracle: Address,
142    /// address of contract that allows Hopr Association to dictate the minimum ticket winning probability in Hopr
143    #[serde_as(as = "DisplayFromStr")]
144    pub winning_probability_oracle: Address,
145    /// address of contract that manages transport announcements in the hopr network
146    #[serde_as(as = "DisplayFromStr")]
147    pub announcements: Address,
148    /// factory contract to produce Safe instances
149    #[serde_as(as = "DisplayFromStr")]
150    pub node_stake_v2_factory: Address,
151    /// number of follow-on blocks required until a block is considered confirmed on-chain
152    pub confirmations: u32,
153    /// milliseconds between polling the RPC for new transactions
154    pub tx_polling_interval: u64,
155    /// maximum block range to fetch when indexing logs
156    pub max_block_range: u64,
157    /// maximum number of RPC requests per second
158    pub max_requests_per_sec: Option<u32>,
159}
160
161/// Check whether the version is allowed
162fn satisfies(version: &str, allowed_versions: &str) -> crate::errors::Result<bool> {
163    let allowed_versions = VersionReq::parse(allowed_versions)
164        .map_err(|e| HoprChainError::Configuration(format!("failed to deserialize allowed version string: {e}")))?;
165
166    let version = Version::from_str(version)
167        .map_err(|e| HoprChainError::Configuration(format!("failed to deserialize current lib version string: {e}")))?;
168
169    Ok(allowed_versions.matches(&version))
170}
171
172impl ChainNetworkConfig {
173    /// Returns the network details, returns an error if network is not supported
174    pub fn new(
175        id: &str,
176        version: &str,
177        maybe_custom_provider: Option<&str>,
178        max_rpc_requests_per_sec: Option<u32>,
179        protocol_config: &mut ProtocolsConfig,
180    ) -> Result<Self, String> {
181        let network = protocol_config
182            .networks
183            .get_mut(id)
184            .ok_or(format!("Could not find network {id} in protocol config"))?;
185
186        let chain = protocol_config
187            .chains
188            .get_mut(&network.chain)
189            .ok_or(format!("Invalid chain {} for network {id}", network.chain))?;
190
191        if let Some(custom_provider) = maybe_custom_provider {
192            chain.default_provider = custom_provider.into();
193        }
194
195        match satisfies(version, network.version_range.as_str()) {
196            Ok(true) => Ok(ChainNetworkConfig {
197                announcements: network.addresses.announcements.to_owned(),
198                chain: chain.to_owned(),
199                channel_contract_deploy_block: network.indexer_start_block_number,
200                channels: network.addresses.channels.to_owned(),
201                confirmations: network.confirmations,
202                environment_type: network.environment_type,
203                id: network.chain.to_owned(),
204                module_implementation: network.addresses.module_implementation.to_owned(),
205                network_registry: network.addresses.network_registry.to_owned(),
206                network_registry_proxy: network.addresses.network_registry_proxy.to_owned(),
207                node_safe_registry: network.addresses.node_safe_registry.to_owned(),
208                node_stake_v2_factory: network.addresses.node_stake_v2_factory.to_owned(),
209                ticket_price_oracle: network.addresses.ticket_price_oracle.to_owned(),
210                winning_probability_oracle: network.addresses.winning_probability_oracle.to_owned(),
211                token: network.addresses.token.to_owned(),
212                tx_polling_interval: network.tx_polling_interval,
213                max_block_range: network.max_block_range,
214                max_requests_per_sec: max_rpc_requests_per_sec.or(chain.max_rpc_requests_per_sec),
215            }),
216            Ok(false) => Err(format!(
217                "network {id} is not supported, supported networks {:?}",
218                protocol_config.supported_networks(version).join(", ")
219            )),
220            Err(e) => Err(e.to_string()),
221        }
222    }
223}
224
225impl From<&ChainNetworkConfig> for ContractAddresses {
226    fn from(network: &ChainNetworkConfig) -> Self {
227        Self {
228            token: network.token,
229            channels: network.channels,
230            announcements: network.announcements,
231            network_registry: network.network_registry,
232            network_registry_proxy: network.network_registry_proxy,
233            safe_registry: network.node_safe_registry,
234            price_oracle: network.ticket_price_oracle,
235            win_prob_oracle: network.winning_probability_oracle,
236            stake_factory: network.node_stake_v2_factory,
237            module_implementation: network.module_implementation,
238        }
239    }
240}
241
242/// The entire on-chain protocol configuration containing the information about
243/// usable networks and chains.
244#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
245#[serde(deny_unknown_fields)]
246pub struct ProtocolsConfig {
247    pub networks: std::collections::BTreeMap<String, Network>,
248    pub chains: std::collections::BTreeMap<String, ChainOptions>,
249}
250
251impl Default for ProtocolsConfig {
252    fn default() -> Self {
253        Self::from_str(include_str!("../../../hopr/hopr-lib/data/protocol-config.json"))
254            .expect("bundled protocol config should be always valid")
255    }
256}
257
258impl FromStr for ProtocolsConfig {
259    type Err = String;
260
261    /// Reads the protocol config JSON file and returns it
262    fn from_str(data: &str) -> Result<Self, Self::Err> {
263        serde_json::from_str::<ProtocolsConfig>(data).map_err(|e| e.to_string())
264    }
265}
266
267impl std::cmp::PartialEq for ProtocolsConfig {
268    fn eq(&self, other: &Self) -> bool {
269        Vec::from_iter(self.networks.clone()) == Vec::from_iter(other.networks.clone())
270            && Vec::from_iter(self.chains.clone()) == Vec::from_iter(self.chains.clone())
271    }
272}
273
274impl ProtocolsConfig {
275    /// Returns a list of environments which the node is able to work with
276    /// TODO: crate::constants::APP_VERSION_COERCED
277    pub fn supported_networks(&self, version: &str) -> Vec<String> {
278        let mut allowed = vec![];
279
280        for (name, env) in self.networks.iter() {
281            let range = env.version_range.to_owned();
282
283            if let Ok(true) = satisfies(version, range.as_str()) {
284                allowed.push(name.clone())
285            }
286        }
287
288        allowed
289    }
290}
291#[cfg(test)]
292mod test {
293    use super::*;
294
295    #[test]
296    fn test_default_protocol_config_can_be_deserialized() {
297        let _ = ProtocolsConfig::default();
298    }
299
300    #[test]
301    fn test_version_is_satisfied_should_work_on_ranges() {
302        let actual = satisfies("1.90.0", ">=1.89, <1.93");
303        assert!(actual.is_ok());
304        assert!(actual.expect("should be contained"))
305    }
306
307    #[test]
308    fn test_version_is_satisfied_should_work_for_glob() {
309        let actual = satisfies("1.2.3", "*");
310        assert!(actual.is_ok());
311        assert!(actual.expect("should be contained"))
312    }
313}