ant_bootstrap/
config.rs

1// Copyright 2024 MaidSafe.net limited.
2//
3// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3.
4// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed
5// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
6// KIND, either express or implied. Please review the Licences for the specific language governing
7// permissions and limitations relating to use of the SAFE Network Software.
8
9use crate::error::{Error, Result};
10use clap::Args;
11use libp2p::Multiaddr;
12use serde::{Deserialize, Serialize};
13use std::{
14    env,
15    path::{Path, PathBuf},
16    time::Duration,
17};
18
19/// Maximum peers to store
20const MAX_CACHED_PEERS: usize = 1500;
21
22/// Maximum number of addresses to store for a Peer
23const MAX_ADDRS_PER_CACHED_PEER: usize = 3;
24
25// Min time until we save the bootstrap cache to disk. 30 secs
26const MIN_BOOTSTRAP_CACHE_SAVE_INTERVAL: Duration = Duration::from_secs(30);
27
28// Max time until we save the bootstrap cache to disk. 3 hours
29const MAX_BOOTSTRAP_CACHE_SAVE_INTERVAL: Duration = Duration::from_secs(3 * 60 * 60);
30
31/// The max number of concurrent dials to be made during the initial bootstrap process.
32const CONCURRENT_DIALS: usize = 5;
33
34/// The max number of peers to be added before stopping the initial bootstrap process.
35const MAX_PEERS_BEFORE_TERMINATION: usize = 5;
36
37/// Configurations to fetch the initial peers which is used to bootstrap the network.
38/// This could optionally also be used as a command line argument struct.
39#[derive(Args, Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
40pub struct InitialPeersConfig {
41    /// Set to indicate this is the first node in a new network
42    ///
43    /// If this argument is used, any others will be ignored because they do not apply to the first
44    /// node.
45    #[clap(long, default_value = "false")]
46    pub first: bool,
47    /// Addr(s) to use for bootstrap, in a 'multiaddr' format containing the peer ID.
48    ///
49    /// A multiaddr looks like
50    /// '/ip4/1.2.3.4/tcp/1200/tcp/p2p/12D3KooWRi6wF7yxWLuPSNskXc6kQ5cJ6eaymeMbCRdTnMesPgFx' where
51    /// `1.2.3.4` is the IP, `1200` is the port and the (optional) last part is the peer ID.
52    ///
53    /// This argument can be provided multiple times to connect to multiple peers.
54    ///
55    /// Alternatively, the `ANT_PEERS` environment variable can provide a comma-separated peer
56    /// list.
57    #[clap(
58        long = "peer",
59        value_name = "multiaddr",
60        value_delimiter = ',',
61        conflicts_with = "first"
62    )]
63    pub addrs: Vec<Multiaddr>,
64    /// Specify the URL to fetch the network contacts from.
65    ///
66    /// The URL can point to a text file containing Multiaddresses separated by newline character, or
67    /// a bootstrap cache JSON file.
68    #[clap(long, conflicts_with = "first", value_delimiter = ',')]
69    pub network_contacts_url: Vec<String>,
70    /// Set to indicate this is a local network.
71    #[clap(long, conflicts_with = "network_contacts_url", default_value = "false")]
72    pub local: bool,
73    /// Set to not load the bootstrap addresses from the local cache.
74    #[clap(long, default_value = "false")]
75    pub ignore_cache: bool,
76    /// The directory to load and store the bootstrap cache. If not provided, the default path will be used.
77    ///
78    /// The JSON filename will be derived automatically from the network ID
79    ///
80    /// The default location is platform specific:
81    ///  - Linux: $HOME/.local/share/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
82    ///  - macOS: $HOME/Library/Application Support/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
83    ///  - Windows: C:\Users\<username>\AppData\Roaming\autonomi\bootstrap_cache\bootstrap_cache_<network_id>.json
84    ///
85    /// We fallback to $HOME dir and then to current working directory if the platform specific directory cannot be
86    /// determined.
87    #[clap(long)]
88    pub bootstrap_cache_dir: Option<PathBuf>,
89}
90
91/// Configuration for Bootstrapping
92///
93/// If you have `InitialPeersConfig`, you can convert it to `BootstrapConfig` using `TryFrom` trait.
94#[derive(Clone, Debug)]
95pub struct BootstrapConfig {
96    /// Enable backwards compatibility while writing the cache file.
97    /// This will write the cache file in all versions of the cache file format.
98    pub backwards_compatible_writes: bool,
99    /// The directory to load and store the bootstrap cache. If not provided, the default path will be used.
100    ///
101    /// The JSON filename will be derived automatically from the network ID
102    ///
103    /// The default location is platform specific:
104    ///  - Linux: $HOME/.local/share/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
105    ///  - macOS: $HOME/Library/Application Support/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
106    ///  - Windows: C:\Users\<username>\AppData\Roaming\autonomi\bootstrap_cache\bootstrap_cache_<network_id>.json
107    pub cache_dir: PathBuf,
108    /// The cache save scaling factor. We start with the min_cache_save_duration and scale it up to the max_cache_save_duration.
109    pub cache_save_scaling_factor: u32,
110    /// Flag to disable writing to the cache file
111    pub disable_cache_writing: bool,
112    /// Flag to disable reading from the cache file
113    pub disable_cache_reading: bool,
114    /// Flag to disable reading peers from the ANT_PEERS environment variable
115    pub disable_env_peers: bool,
116    /// Indicate that this is the first node in a new network.
117    pub first: bool,
118    /// The initial peers that are used to bootstrap/connect the network.
119    pub initial_peers: Vec<Multiaddr>,
120    /// If set to true, the cache filename will be suffixed with "_local"
121    pub local: bool,
122    /// The max time duration until we save the bootstrap cache to disk.
123    pub max_cache_save_duration: Duration,
124    /// The max number of concurrent dials to be made during the initial bootstrap process.
125    ///
126    /// This is the number of peers we will try to dial in parallel.
127    /// Default is 5.
128    pub max_concurrent_dials: usize,
129    /// The max number of peers to be added to RT / connected before stopping the initial bootstrap process.
130    /// Default is 5.
131    pub max_contacted_peers_before_termination: usize,
132    /// Maximum number of peers to store inside the bootstrap cache
133    ///
134    /// When the number of cached peers exceeds this value, the least recently seen peers will be removed.
135    /// Default is 1500.
136    pub max_cached_peers: usize,
137    /// Maximum number of addresses stored per peer inside the bootstrap cache.
138    /// Default is 3.
139    pub max_addrs_per_cached_peer: usize,
140    /// The min time duration until we save the bootstrap cache to disk.
141    pub min_cache_save_duration: Duration,
142    /// Specify the URL to fetch the network contacts from.
143    ///
144    /// The URL can point to a text file containing Multiaddresses separated by newline character, or
145    /// a bootstrap cache JSON file.
146    pub network_contacts_url: Vec<String>,
147}
148
149impl Default for BootstrapConfig {
150    fn default() -> Self {
151        Self {
152            backwards_compatible_writes: false,
153            cache_dir: default_cache_dir(),
154            cache_save_scaling_factor: 2,
155            disable_cache_writing: false,
156            disable_cache_reading: false,
157            disable_env_peers: false,
158            first: false,
159            initial_peers: vec![],
160            local: false,
161            max_concurrent_dials: CONCURRENT_DIALS,
162            max_contacted_peers_before_termination: MAX_PEERS_BEFORE_TERMINATION,
163            max_cached_peers: MAX_CACHED_PEERS,
164            max_addrs_per_cached_peer: MAX_ADDRS_PER_CACHED_PEER,
165            min_cache_save_duration: MIN_BOOTSTRAP_CACHE_SAVE_INTERVAL,
166            max_cache_save_duration: MAX_BOOTSTRAP_CACHE_SAVE_INTERVAL,
167            network_contacts_url: vec![],
168        }
169    }
170}
171
172impl TryFrom<&InitialPeersConfig> for BootstrapConfig {
173    type Error = Error;
174    fn try_from(config: &InitialPeersConfig) -> Result<Self> {
175        let cache_dir = if let Some(cache_dir) = &config.bootstrap_cache_dir {
176            cache_dir.clone()
177        } else {
178            default_cache_dir()
179        };
180
181        let bootstrap_config = Self {
182            cache_dir,
183            disable_cache_reading: config.ignore_cache,
184            first: config.first,
185            initial_peers: config.addrs.clone(),
186            local: config.local,
187            network_contacts_url: config.network_contacts_url.clone(),
188            ..Self::default()
189        };
190        Ok(bootstrap_config)
191    }
192}
193
194impl BootstrapConfig {
195    /// Creates a new BootstrapConfig with default settings
196    pub fn new(local: bool) -> Self {
197        Self {
198            local,
199            cache_dir: default_cache_dir(),
200            ..Self::default()
201        }
202    }
203
204    /// Set backwards compatible writes
205    pub fn with_backwards_compatible_writes(mut self, enable: bool) -> Self {
206        self.backwards_compatible_writes = enable;
207        self
208    }
209
210    /// Set the local flag
211    pub fn with_local(mut self, enable: bool) -> Self {
212        self.local = enable;
213        self
214    }
215
216    /// Update the config with a custom cache file path
217    pub fn with_cache_dir<P: AsRef<Path>>(mut self, path: P) -> Self {
218        self.cache_dir = path.as_ref().to_path_buf();
219        self
220    }
221
222    /// Sets the maximum number of concurrent dials to be made during the initial bootstrap process
223    /// Default is 5.
224    pub fn with_max_concurrent_dials(mut self, max_dials: usize) -> Self {
225        self.max_concurrent_dials = max_dials;
226        self
227    }
228
229    /// Sets the maximum number of peers to be added / contacted before stopping the initial bootstrap process
230    /// Default is 5.
231    pub fn with_max_contacted_peers_before_termination(mut self, max_peers: usize) -> Self {
232        self.max_contacted_peers_before_termination = max_peers;
233        self
234    }
235
236    /// Sets the maximum number of cached peers
237    pub fn with_max_cached_peers(mut self, max_peers: usize) -> Self {
238        self.max_cached_peers = max_peers;
239        self
240    }
241
242    /// Sets the maximum number of addresses for a single peer in the bootstrap cache
243    pub fn with_max_addrs_per_cached_peer(mut self, max_addrs: usize) -> Self {
244        self.max_addrs_per_cached_peer = max_addrs;
245        self
246    }
247
248    /// Sets the flag to disable writing to the cache file
249    pub fn with_disable_cache_writing(mut self, disable: bool) -> Self {
250        self.disable_cache_writing = disable;
251        self
252    }
253
254    /// Sets the flag to disable reading from the cache file
255    pub fn with_disable_cache_reading(mut self, disable: bool) -> Self {
256        self.disable_cache_reading = disable;
257        self
258    }
259
260    /// Sets the flag to disable reading peers from the ANT_PEERS environment variable
261    pub fn with_disable_env_peers(mut self, disable: bool) -> Self {
262        self.disable_env_peers = disable;
263        self
264    }
265
266    /// Sets whether this config represents the first node in the network
267    pub fn with_first(mut self, first: bool) -> Self {
268        self.first = first;
269        self
270    }
271
272    /// Sets the initial peers that should be used for bootstrapping
273    pub fn with_initial_peers(mut self, peers: Vec<Multiaddr>) -> Self {
274        self.initial_peers = peers;
275        self
276    }
277
278    /// Sets the cache save scaling factor
279    pub fn with_cache_save_scaling_factor(mut self, factor: u32) -> Self {
280        self.cache_save_scaling_factor = factor;
281        self
282    }
283
284    /// Sets the maximum duration between cache saves
285    pub fn with_max_cache_save_duration(mut self, duration: Duration) -> Self {
286        self.max_cache_save_duration = duration;
287        self
288    }
289
290    /// Sets the minimum duration between cache saves
291    pub fn with_min_cache_save_duration(mut self, duration: Duration) -> Self {
292        self.min_cache_save_duration = duration;
293        self
294    }
295
296    /// Sets the list of network contact URLs
297    pub fn with_network_contacts_url(mut self, urls: Vec<String>) -> Self {
298        self.network_contacts_url = urls;
299        self
300    }
301}
302
303/// Returns the default dir that should contain the bootstrap cache file
304///
305/// The default location is platform specific:
306///  - Linux: $HOME/.local/share/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
307///  - macOS: $HOME/Library/Application Support/autonomi/bootstrap_cache/bootstrap_cache_<network_id>.json
308///  - Windows: C:\Users\<username>\AppData\Roaming\autonomi\bootstrap_cache\bootstrap_cache_<network_id>.json
309///
310/// We fallback to $HOME dir and then to current working directory if the platform specific directory cannot be
311/// determined.
312fn default_cache_dir() -> PathBuf {
313    let base_dir = if let Some(dir) = dirs_next::data_dir() {
314        dir
315    } else if let Some(home) = dirs_next::home_dir() {
316        warn!("Failed to obtain platform data directory, falling back to home directory");
317        home
318    } else {
319        let cwd = env::current_dir().unwrap_or_else(|err| {
320            error!("Failed to obtain current working directory: {err}. Using current process directory '.'");
321            PathBuf::from(".")
322        });
323        warn!("Falling back to current working directory for bootstrap cache");
324        cwd
325    };
326
327    base_dir.join("autonomi").join("bootstrap_cache")
328}