hopr_chain_api/
config.rs

1use std::str::FromStr;
2
3use semver::{Version, VersionReq};
4use serde::{Deserialize, Serialize};
5use serde_with::{serde_as, DisplayFromStr};
6use validator::Validate;
7
8use hopr_chain_types::ContractAddresses;
9use hopr_primitive_types::primitives::Address;
10
11use crate::errors::HoprChainError;
12
13/// Types of HOPR network environments.
14#[derive(Debug, Copy, Clone, Deserialize, Serialize, Eq, PartialEq, strum::Display, strum::EnumString)]
15#[serde(rename_all(deserialize = "lowercase"))]
16#[strum(serialize_all = "lowercase")]
17pub enum EnvironmentType {
18    Production,
19    Staging,
20    Development,
21    Local,
22}
23
24/// Holds all information we need about the blockchain network
25/// the client is going to use
26#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)]
27#[serde(deny_unknown_fields)]
28pub struct ChainOptions {
29    pub description: String,
30    /// >= 0
31    pub chain_id: u32,
32    pub live: bool,
33    /// a valid HTTP url pointing at a RPC endpoint
34    pub default_provider: String,
35    /// a valid HTTP url pointing at a RPC endpoint
36    pub etherscan_api_url: Option<String>,
37    /// The absolute maximum you are willing to pay per unit of gas to get your transaction included in a block, e.g. '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).map_err(|e| {
167        HoprChainError::Configuration(format!("failed to deserialize current lib version string: {}", e))
168    })?;
169
170    Ok(allowed_versions.matches(&version))
171}
172
173impl ChainNetworkConfig {
174    /// Returns the network details, returns an error if network is not supported
175    pub fn new(
176        id: &str,
177        version: &str,
178        maybe_custom_provider: Option<&str>,
179        max_rpc_requests_per_sec: Option<u32>,
180        protocol_config: &mut ProtocolsConfig,
181    ) -> Result<Self, String> {
182        let network = protocol_config
183            .networks
184            .get_mut(id)
185            .ok_or(format!("Could not find network {id} in protocol config"))?;
186
187        let chain = protocol_config
188            .chains
189            .get_mut(&network.chain)
190            .ok_or(format!("Invalid chain {} for network {id}", network.chain))?;
191
192        if let Some(custom_provider) = maybe_custom_provider {
193            chain.default_provider = custom_provider.into();
194        }
195
196        match satisfies(version, network.version_range.as_str()) {
197            Ok(true) => Ok(ChainNetworkConfig {
198                announcements: network.addresses.announcements.to_owned(),
199                chain: chain.to_owned(),
200                channel_contract_deploy_block: network.indexer_start_block_number,
201                channels: network.addresses.channels.to_owned(),
202                confirmations: network.confirmations,
203                environment_type: network.environment_type,
204                id: network.chain.to_owned(),
205                module_implementation: network.addresses.module_implementation.to_owned(),
206                network_registry: network.addresses.network_registry.to_owned(),
207                network_registry_proxy: network.addresses.network_registry_proxy.to_owned(),
208                node_safe_registry: network.addresses.node_safe_registry.to_owned(),
209                node_stake_v2_factory: network.addresses.node_stake_v2_factory.to_owned(),
210                ticket_price_oracle: network.addresses.ticket_price_oracle.to_owned(),
211                winning_probability_oracle: network.addresses.winning_probability_oracle.to_owned(),
212                token: network.addresses.token.to_owned(),
213                tx_polling_interval: network.tx_polling_interval,
214                max_block_range: network.max_block_range,
215                max_requests_per_sec: max_rpc_requests_per_sec.or(chain.max_rpc_requests_per_sec),
216            }),
217            Ok(false) => Err(format!(
218                "network {id} is not supported, supported networks {:?}",
219                protocol_config.supported_networks(version).join(", ")
220            )),
221            Err(e) => Err(e.to_string()),
222        }
223    }
224}
225
226impl From<&ChainNetworkConfig> for ContractAddresses {
227    fn from(network: &ChainNetworkConfig) -> Self {
228        Self {
229            token: network.token,
230            channels: network.channels,
231            announcements: network.announcements,
232            network_registry: network.network_registry,
233            network_registry_proxy: network.network_registry_proxy,
234            safe_registry: network.node_safe_registry,
235            price_oracle: network.ticket_price_oracle,
236            win_prob_oracle: network.winning_probability_oracle,
237            stake_factory: network.node_stake_v2_factory,
238            module_implementation: network.module_implementation,
239        }
240    }
241}
242
243/// The entire on-chain protocol configuration containing the information about
244/// usable networks and chains.
245#[derive(Debug, Clone, Serialize, Deserialize, Validate)]
246#[serde(deny_unknown_fields)]
247pub struct ProtocolsConfig {
248    pub networks: std::collections::BTreeMap<String, Network>,
249    pub chains: std::collections::BTreeMap<String, ChainOptions>,
250}
251
252impl Default for ProtocolsConfig {
253    fn default() -> Self {
254        Self::from_str(include_str!("../../../hopr/hopr-lib/data/protocol-config.json"))
255            .expect("bundled protocol config should be always valid")
256    }
257}
258
259impl FromStr for ProtocolsConfig {
260    type Err = String;
261
262    /// Reads the protocol config JSON file and returns it
263    fn from_str(data: &str) -> Result<Self, Self::Err> {
264        serde_json::from_str::<ProtocolsConfig>(data).map_err(|e| e.to_string())
265    }
266}
267
268impl std::cmp::PartialEq for ProtocolsConfig {
269    fn eq(&self, other: &Self) -> bool {
270        Vec::from_iter(self.networks.clone()) == Vec::from_iter(other.networks.clone())
271            && Vec::from_iter(self.chains.clone()) == Vec::from_iter(self.chains.clone())
272    }
273}
274
275impl ProtocolsConfig {
276    /// Returns a list of environments which the node is able to work with
277    /// TODO: crate::constants::APP_VERSION_COERCED
278    pub fn supported_networks(&self, version: &str) -> Vec<String> {
279        let mut allowed = vec![];
280
281        for (name, env) in self.networks.iter() {
282            let range = env.version_range.to_owned();
283
284            if let Ok(true) = satisfies(version, range.as_str()) {
285                allowed.push(name.clone())
286            }
287        }
288
289        allowed
290    }
291}
292#[cfg(test)]
293mod test {
294    use super::*;
295
296    #[test]
297    fn test_default_protocol_config_can_be_deserialized() {
298        let _ = ProtocolsConfig::default();
299    }
300
301    #[test]
302    fn test_version_is_satisfied_should_work_on_ranges() {
303        let actual = satisfies("1.90.0", ">=1.89, <1.93");
304        assert!(actual.is_ok());
305        assert!(actual.expect("should be contained"))
306    }
307
308    #[test]
309    fn test_version_is_satisfied_should_work_for_glob() {
310        let actual = satisfies("1.2.3", "*");
311        assert!(actual.is_ok());
312        assert!(actual.expect("should be contained"))
313    }
314}