iota_client/
builder.rs

1// Copyright 2021 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! Builder of the Client Instance
5use crate::{client::*, error::*};
6
7#[cfg(not(feature = "wasm"))]
8use tokio::{runtime::Runtime, sync::broadcast::channel};
9
10#[cfg(not(feature = "wasm"))]
11use std::collections::HashSet;
12
13use std::{
14    collections::HashMap,
15    sync::{Arc, RwLock},
16    time::Duration,
17};
18
19const DEFAULT_REMOTE_POW_TIMEOUT: Duration = Duration::from_secs(100);
20pub(crate) const GET_API_TIMEOUT: Duration = Duration::from_secs(15);
21#[cfg(not(feature = "wasm"))]
22const NODE_SYNC_INTERVAL: Duration = Duration::from_secs(60);
23/// Interval in seconds when new tips will be requested during PoW
24pub const TIPS_INTERVAL: u64 = 15;
25const DEFAULT_MIN_POW: f64 = 4000f64;
26const DEFAULT_BECH32_HRP: &str = "iota";
27
28/// Struct containing network and PoW related information
29#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
30pub struct NetworkInfo {
31    /// Network
32    pub network: Option<String>,
33    /// Network ID
34    #[serde(rename = "networkId")]
35    pub network_id: Option<u64>,
36    /// Bech32 HRP
37    #[serde(rename = "bech32HRP")]
38    pub bech32_hrp: String,
39    /// Mininum proof of work score
40    #[serde(rename = "minPoWScore")]
41    pub min_pow_score: f64,
42    /// Local proof of work
43    #[serde(rename = "localPow")]
44    pub local_pow: bool,
45    /// Fallback to local proof of work if the node doesn't support remote PoW
46    #[serde(rename = "fallbackToLocalPow")]
47    pub fallback_to_local_pow: bool,
48    /// Tips request interval during PoW in seconds
49    #[serde(rename = "tipsInterval")]
50    pub tips_interval: u64,
51}
52
53/// Builder to construct client instance with sensible default values
54#[derive(Clone)]
55pub struct ClientBuilder {
56    node_manager_builder: crate::node_manager::NodeManagerBuilder,
57    #[cfg(not(feature = "wasm"))]
58    node_sync_interval: Duration,
59    #[cfg(not(feature = "wasm"))]
60    node_sync_enabled: bool,
61    #[cfg(feature = "mqtt")]
62    broker_options: BrokerOptions,
63    pub(crate) network_info: NetworkInfo,
64    request_timeout: Duration,
65    api_timeout: HashMap<Api, Duration>,
66    offline: bool,
67}
68
69impl Default for NetworkInfo {
70    fn default() -> Self {
71        Self {
72            network: None,
73            network_id: None,
74            min_pow_score: DEFAULT_MIN_POW,
75            #[cfg(not(feature = "wasm"))]
76            local_pow: true,
77            #[cfg(feature = "wasm")]
78            local_pow: false,
79            fallback_to_local_pow: true,
80            bech32_hrp: DEFAULT_BECH32_HRP.into(),
81            tips_interval: TIPS_INTERVAL,
82        }
83    }
84}
85
86impl Default for ClientBuilder {
87    fn default() -> Self {
88        Self {
89            node_manager_builder: crate::node_manager::NodeManager::builder(),
90            #[cfg(not(feature = "wasm"))]
91            node_sync_interval: NODE_SYNC_INTERVAL,
92            #[cfg(not(feature = "wasm"))]
93            node_sync_enabled: true,
94            #[cfg(feature = "mqtt")]
95            broker_options: Default::default(),
96            network_info: NetworkInfo::default(),
97            request_timeout: GET_API_TIMEOUT,
98            api_timeout: Default::default(),
99            offline: false,
100        }
101    }
102}
103
104impl ClientBuilder {
105    /// Creates an IOTA client builder.
106    pub fn new() -> Self {
107        Default::default()
108    }
109
110    /// Adds an IOTA node by its URL.
111    pub fn with_node(mut self, url: &str) -> Result<Self> {
112        self.node_manager_builder = self.node_manager_builder.with_node(url)?;
113        Ok(self)
114    }
115
116    /// Adds an IOTA node by its URL to be used as primary node, with optional jwt and or basic authentication
117    pub fn with_primary_node(
118        mut self,
119        url: &str,
120        jwt: Option<String>,
121        basic_auth_name_pwd: Option<(&str, &str)>,
122    ) -> Result<Self> {
123        self.node_manager_builder = self
124            .node_manager_builder
125            .with_primary_node(url, jwt, basic_auth_name_pwd)?;
126        Ok(self)
127    }
128
129    /// Adds an IOTA node by its URL to be used as primary PoW node (for remote PoW), with optional jwt and or basic
130    /// authentication
131    pub fn with_primary_pow_node(
132        mut self,
133        url: &str,
134        jwt: Option<String>,
135        basic_auth_name_pwd: Option<(&str, &str)>,
136    ) -> Result<Self> {
137        self.node_manager_builder = self
138            .node_manager_builder
139            .with_primary_pow_node(url, jwt, basic_auth_name_pwd)?;
140        Ok(self)
141    }
142
143    /// Adds a permanode by its URL, with optional jwt and or basic authentication
144    pub fn with_permanode(
145        mut self,
146        url: &str,
147        jwt: Option<String>,
148        basic_auth_name_pwd: Option<(&str, &str)>,
149    ) -> Result<Self> {
150        self.node_manager_builder = self
151            .node_manager_builder
152            .with_permanode(url, jwt, basic_auth_name_pwd)?;
153        Ok(self)
154    }
155
156    /// Adds an IOTA node by its URL with optional jwt and or basic authentication
157    pub fn with_node_auth(
158        mut self,
159        url: &str,
160        jwt: Option<String>,
161        basic_auth_name_pwd: Option<(&str, &str)>,
162    ) -> Result<Self> {
163        self.node_manager_builder = self
164            .node_manager_builder
165            .with_node_auth(url, jwt, basic_auth_name_pwd)?;
166        Ok(self)
167    }
168
169    /// Adds a list of IOTA nodes by their URLs.
170    pub fn with_nodes(mut self, urls: &[&str]) -> Result<Self> {
171        self.node_manager_builder = self.node_manager_builder.with_nodes(urls)?;
172        Ok(self)
173    }
174
175    /// Set the node sync interval
176    pub fn with_node_sync_interval(mut self, node_sync_interval: Duration) -> Self {
177        self.node_manager_builder = self.node_manager_builder.with_node_sync_interval(node_sync_interval);
178        self
179    }
180
181    /// Disables the node syncing process.
182    /// Every node will be considered healthy and ready to use.
183    pub fn with_node_sync_disabled(mut self) -> Self {
184        self.node_manager_builder = self.node_manager_builder.with_node_sync_disabled();
185        self
186    }
187
188    /// Allows creating the client without nodes for offline address generation or signing
189    pub fn with_offline_mode(mut self) -> Self {
190        self.offline = true;
191        self
192    }
193
194    /// Get node list from the node_pool_urls
195    pub async fn with_node_pool_urls(mut self, node_pool_urls: &[String]) -> Result<Self> {
196        self.node_manager_builder = self.node_manager_builder.with_node_pool_urls(node_pool_urls).await?;
197        Ok(self)
198    }
199
200    /// Set if quroum should be used or not
201    pub fn with_quorum(mut self, quorum: bool) -> Self {
202        self.node_manager_builder = self.node_manager_builder.with_quorum(quorum);
203        self
204    }
205
206    /// Set amount of nodes which should be used for quorum
207    pub fn with_quorum_size(mut self, quorum_size: usize) -> Self {
208        self.node_manager_builder = self.node_manager_builder.with_quorum_size(quorum_size);
209        self
210    }
211
212    /// Set quorum_threshold
213    pub fn with_quorum_threshold(mut self, threshold: usize) -> Self {
214        let threshold = if threshold > 100 { 100 } else { threshold };
215        self.node_manager_builder = self.node_manager_builder.with_quorum_threshold(threshold);
216        self
217    }
218
219    /// Selects the type of network to get default nodes for it, only "testnet" is supported at the moment.
220    /// Nodes that don't belong to this network are ignored. The &str must match a part or all of the networkId returned
221    /// in the nodeinfo from a node. For example, if the networkId is `"private-tangle"`, `"tangle"` can be used.
222    /// Default nodes are only used when no other nodes are provided.
223    pub fn with_network(mut self, network: &str) -> Self {
224        self.network_info.network.replace(network.into());
225        self
226    }
227
228    /// Sets the MQTT broker options.
229    #[cfg(feature = "mqtt")]
230    pub fn with_mqtt_broker_options(mut self, options: BrokerOptions) -> Self {
231        self.broker_options = options;
232        self
233    }
234
235    /// Sets whether the PoW should be done locally or remotely.
236    pub fn with_local_pow(mut self, local: bool) -> Self {
237        self.network_info.local_pow = local;
238        self
239    }
240
241    /// Sets whether the PoW should be done locally in case a node doesn't support remote PoW.
242    pub fn with_fallback_to_local_pow(mut self, fallback_to_local_pow: bool) -> Self {
243        self.network_info.fallback_to_local_pow = fallback_to_local_pow;
244        self
245    }
246
247    /// Sets after how many seconds new tips will be requested during PoW
248    pub fn with_tips_interval(mut self, tips_interval: u64) -> Self {
249        self.network_info.tips_interval = tips_interval;
250        self
251    }
252
253    /// Sets the default request timeout.
254    pub fn with_request_timeout(mut self, timeout: Duration) -> Self {
255        self.request_timeout = timeout;
256        self
257    }
258
259    /// Sets the request timeout for a specific API usage.
260    pub fn with_api_timeout(mut self, api: Api, timeout: Duration) -> Self {
261        self.api_timeout.insert(api, timeout);
262        self
263    }
264
265    /// Build the Client instance.
266    pub async fn finish(mut self) -> Result<Client> {
267        // Add default nodes
268        if !self.offline {
269            self.node_manager_builder = self.node_manager_builder.add_default_nodes(&self.network_info).await?;
270            // Return error if we don't have a node
271            if self.node_manager_builder.nodes.is_empty() && self.node_manager_builder.primary_node.is_none() {
272                return Err(Error::MissingParameter("Node"));
273            }
274        }
275        let network_info = Arc::new(RwLock::new(self.network_info));
276        let nodes = self.node_manager_builder.nodes.clone();
277        #[cfg(not(feature = "wasm"))]
278        let node_sync_interval = self.node_sync_interval;
279        #[cfg(feature = "wasm")]
280        let (sync, network_info) = (Arc::new(RwLock::new(nodes)), network_info);
281        #[cfg(not(feature = "wasm"))]
282        let (runtime, sync, sync_kill_sender, network_info) = if self.node_sync_enabled {
283            let sync = Arc::new(RwLock::new(HashSet::new()));
284            let sync_ = sync.clone();
285            let network_info_ = network_info.clone();
286            let (sync_kill_sender, sync_kill_receiver) = channel(1);
287            let runtime = std::thread::spawn(move || {
288                let runtime = Runtime::new().expect("Failed to create Tokio runtime");
289                runtime.block_on(Client::sync_nodes(&sync_, &nodes, &network_info_));
290                Client::start_sync_process(
291                    &runtime,
292                    sync_,
293                    nodes,
294                    node_sync_interval,
295                    network_info_,
296                    sync_kill_receiver,
297                );
298                runtime
299            })
300            .join()
301            .expect("failed to init node syncing process");
302            (Some(runtime), sync, Some(sync_kill_sender), network_info)
303        } else {
304            (None, Arc::new(RwLock::new(nodes)), None, network_info)
305        };
306
307        let mut api_timeout = HashMap::new();
308        api_timeout.insert(
309            Api::GetInfo,
310            self.api_timeout.remove(&Api::GetInfo).unwrap_or(self.request_timeout),
311        );
312        api_timeout.insert(
313            Api::GetPeers,
314            self.api_timeout.remove(&Api::GetPeers).unwrap_or(self.request_timeout),
315        );
316        api_timeout.insert(
317            Api::GetHealth,
318            self.api_timeout.remove(&Api::GetHealth).unwrap_or(self.request_timeout),
319        );
320        api_timeout.insert(
321            Api::GetMilestone,
322            self.api_timeout
323                .remove(&Api::GetMilestone)
324                .unwrap_or(self.request_timeout),
325        );
326        api_timeout.insert(
327            Api::GetBalance,
328            self.api_timeout
329                .remove(&Api::GetBalance)
330                .unwrap_or(self.request_timeout),
331        );
332        api_timeout.insert(
333            Api::GetMessage,
334            self.api_timeout
335                .remove(&Api::GetMessage)
336                .unwrap_or(self.request_timeout),
337        );
338        api_timeout.insert(
339            Api::GetTips,
340            self.api_timeout.remove(&Api::GetTips).unwrap_or(self.request_timeout),
341        );
342        api_timeout.insert(
343            Api::PostMessage,
344            self.api_timeout
345                .remove(&Api::PostMessage)
346                .unwrap_or(self.request_timeout),
347        );
348        api_timeout.insert(
349            Api::PostMessageWithRemotePow,
350            self.api_timeout
351                .remove(&Api::PostMessageWithRemotePow)
352                .unwrap_or(DEFAULT_REMOTE_POW_TIMEOUT),
353        );
354        api_timeout.insert(
355            Api::GetOutput,
356            self.api_timeout.remove(&Api::GetOutput).unwrap_or(self.request_timeout),
357        );
358
359        #[cfg(feature = "mqtt")]
360        let (mqtt_event_tx, mqtt_event_rx) = tokio::sync::watch::channel(MqttEvent::Connected);
361        let client = Client {
362            node_manager: self.node_manager_builder.build(sync),
363            #[cfg(not(feature = "wasm"))]
364            runtime,
365            #[cfg(not(feature = "wasm"))]
366            sync_kill_sender: sync_kill_sender.map(Arc::new),
367            #[cfg(feature = "mqtt")]
368            mqtt_client: None,
369            #[cfg(feature = "mqtt")]
370            mqtt_topic_handlers: Default::default(),
371            #[cfg(feature = "mqtt")]
372            broker_options: self.broker_options,
373            #[cfg(feature = "mqtt")]
374            mqtt_event_channel: (Arc::new(mqtt_event_tx), mqtt_event_rx),
375            network_info,
376            request_timeout: self.request_timeout,
377            api_timeout,
378        };
379        Ok(client)
380    }
381}