config.rs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /* This file is part of DarkFi (https://dark.fi)
  2. *
  3. * Copyright (C) 2020-2024 Dyne.org foundation
  4. *
  5. * This program is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU Affero General Public License as
  7. * published by the Free Software Foundation, either version 3 of the
  8. * License, or (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Affero General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Affero General Public License
  16. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  17. */
  18. use std::{
  19. fmt,
  20. path::{Path, PathBuf},
  21. str::FromStr,
  22. };
  23. use serde::Deserialize;
  24. use structopt::StructOpt;
  25. use tracing::{debug, error};
  26. use url::Url;
  27. use darkfi::{rpc::settings::RpcSettingsOpt, util::file::load_file, Error, Result};
  28. /// Represents an explorer configuration
  29. #[derive(Clone, Debug, Deserialize, StructOpt)]
  30. pub struct ExplorerConfig {
  31. /// Current active network
  32. #[allow(dead_code)] // Part of the config file
  33. pub network: String,
  34. /// Supported network configurations
  35. pub network_config: NetworkConfigs,
  36. /// Path to the configuration if read from a file
  37. pub path: Option<String>,
  38. }
  39. impl ExplorerConfig {
  40. /// Creates a new configuration from a given file path.
  41. /// If the file cannot be loaded or parsed, an error is returned.
  42. pub fn new(config_path: String) -> Result<Self> {
  43. // Load the configuration file from the specified path
  44. let config_content = load_file(Path::new(&config_path)).map_err(|err| {
  45. Error::ConfigError(format!(
  46. "Failed to read the configuration file {config_path}: {err:?}"
  47. ))
  48. })?;
  49. // Parse the loaded content into a configuration instance
  50. let mut config = toml::from_str::<Self>(&config_content).map_err(|e| {
  51. error!(target: "explorerd::config", "Failed parsing TOML config: {e}");
  52. Error::ConfigError(format!("Failed to parse the configuration file {config_path}"))
  53. })?;
  54. // Set the configuration path
  55. config.path = Some(config_path);
  56. debug!(target: "explorerd::config", "Successfully loaded configuration: {config:?}");
  57. Ok(config)
  58. }
  59. /// Returns the currently active network configuration.
  60. #[allow(dead_code)] // Test case currently using
  61. pub fn active_network_config(&self) -> Option<ExplorerNetworkConfig> {
  62. self.get_network_config(self.network.as_str())
  63. }
  64. /// Returns the network configuration for specified network.
  65. pub fn get_network_config(&self, network: &str) -> Option<ExplorerNetworkConfig> {
  66. match network {
  67. "localnet" => self.network_config.localnet.clone(),
  68. "testnet" => self.network_config.testnet.clone(),
  69. "mainnet" => self.network_config.mainnet.clone(),
  70. _ => None,
  71. }
  72. }
  73. }
  74. /// Provides a default `ExplorerConfig` configuration using the `testnet` network.
  75. impl Default for ExplorerConfig {
  76. fn default() -> Self {
  77. Self {
  78. network: String::from("testnet"),
  79. network_config: NetworkConfigs::default(),
  80. path: None,
  81. }
  82. }
  83. }
  84. /// Attempts to convert a [`PathBuf`] to an [`ExplorerConfig`] by
  85. /// loading and parsing from specified file path.
  86. impl TryFrom<&PathBuf> for ExplorerConfig {
  87. type Error = Error;
  88. fn try_from(path: &PathBuf) -> Result<Self> {
  89. let path_str = path.to_str().ok_or_else(|| {
  90. Error::ConfigError("Unable to convert PathBuf to a valid UTF-8 path string".to_string())
  91. })?;
  92. // Create configuration and return
  93. ExplorerConfig::new(path_str.to_string())
  94. }
  95. }
  96. /// Deserializes a `&str` containing explorer content in TOML format into an [`ExplorerConfig`] instance.
  97. impl FromStr for ExplorerConfig {
  98. type Err = String;
  99. fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
  100. let config: ExplorerConfig =
  101. toml::from_str(s).map_err(|e| format!("Failed to parse ExplorerdConfig: {e}"))?;
  102. Ok(config)
  103. }
  104. }
  105. /// Represents network configurations for localnet, testnet, and mainnet.
  106. #[derive(Debug, Clone, Deserialize, StructOpt)]
  107. pub struct NetworkConfigs {
  108. /// Local network configuration
  109. pub localnet: Option<ExplorerNetworkConfig>,
  110. /// Testnet network configuration
  111. pub testnet: Option<ExplorerNetworkConfig>,
  112. /// Mainnet network configuration
  113. pub mainnet: Option<ExplorerNetworkConfig>,
  114. }
  115. /// Provides a default `NetworkConfigs` configuration using the `testnet` network.
  116. impl Default for NetworkConfigs {
  117. fn default() -> Self {
  118. NetworkConfigs {
  119. localnet: None,
  120. testnet: Some(ExplorerNetworkConfig::default()),
  121. mainnet: None,
  122. }
  123. }
  124. }
  125. /// Deserializes a `&str` containing network configs content in TOML format into an [`NetworkConfigs`] instance.
  126. impl FromStr for NetworkConfigs {
  127. type Err = String;
  128. fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
  129. let config: NetworkConfigs =
  130. toml::from_str(s).map_err(|e| format!("Failed to parse NetworkConfigs: {e}"))?;
  131. Ok(config)
  132. }
  133. }
  134. /// Struct representing the configuration for an explorer network.
  135. #[derive(Clone, Deserialize, StructOpt)]
  136. #[structopt()]
  137. #[serde(default)]
  138. pub struct ExplorerNetworkConfig {
  139. #[structopt(flatten)]
  140. /// JSON-RPC settings used to set up a server that the explorer listens on for incoming RPC requests.
  141. pub rpc: RpcSettingsOpt,
  142. #[structopt(long, default_value = "~/.local/share/darkfi/explorerd/testnet")]
  143. /// Path to the explorer's database.
  144. pub database: String,
  145. #[structopt(short, long, default_value = "tcp://127.0.0.1:28345")]
  146. /// Endpoint of the DarkFi node JSON-RPC server to sync with.
  147. pub endpoint: Url,
  148. }
  149. /// Attempts to convert a tuple `(PathBuf, &str)` representing a configuration file path
  150. /// and network name into an `ExplorerNetworkConfig`.
  151. impl TryFrom<(&PathBuf, &String)> for ExplorerNetworkConfig {
  152. type Error = Error;
  153. fn try_from(path_and_network: (&PathBuf, &String)) -> Result<Self> {
  154. // Load the ExplorerConfig from the given file path
  155. let config: ExplorerConfig = path_and_network.0.try_into()?;
  156. // Retrieve the network configuration for the specified network
  157. match config.get_network_config(path_and_network.1) {
  158. Some(config) => Ok(config),
  159. None => Err(Error::ConfigError(format!(
  160. "Failed to retrieve network configuration for network: {}",
  161. path_and_network.1
  162. ))),
  163. }
  164. }
  165. }
  166. /// Provides a default `ExplorerNetworkConfig` instance using `structopt` default values defined
  167. /// in the `ExplorerNetworkConfig` struct.
  168. impl Default for ExplorerNetworkConfig {
  169. fn default() -> Self {
  170. Self::from_iter(&[""])
  171. }
  172. }
  173. /// Provides a user-friendly debug view of the `ExplorerdNetworkConfig` configuration.
  174. impl fmt::Debug for ExplorerNetworkConfig {
  175. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  176. let mut debug_struct = f.debug_struct("ExplorerdConfig");
  177. debug_struct
  178. .field("rpc_listen", &self.rpc.rpc_listen.to_string().trim_end_matches('/'))
  179. .field("db_path", &self.database)
  180. .field("endpoint", &self.endpoint.to_string().trim_end_matches('/'));
  181. debug_struct.finish()
  182. }
  183. }
  184. /// Deserializes a `&str` containing network config content in TOML format into an [`ExplorerNetworkConfig`] instance.
  185. impl FromStr for ExplorerNetworkConfig {
  186. type Err = String;
  187. fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
  188. let config: ExplorerNetworkConfig = toml::from_str(s)
  189. .map_err(|e| format!("Failed to parse ExplorerdNetworkConfig: {e}"))?;
  190. Ok(config)
  191. }
  192. }
  193. #[cfg(test)]
  194. /// Contains test cases for validating the functionality and correctness of the `ExplorerConfig`
  195. /// and related components using a configuration loaded from a TOML file.
  196. mod tests {
  197. use std::path::Path;
  198. use darkfi::util::logger::{setup_test_logger, Level};
  199. use tracing::warn;
  200. use super::*;
  201. /// Validates the functionality of initializing and interacting with `ExplorerConfig`
  202. /// loaded from a TOML file, ensuring correctness of the network-specific configurations.
  203. #[test]
  204. fn test_explorerd_config_from_file() {
  205. // Constants for expected configurations
  206. const CONFIG_PATH: &str = "explorerd_config.toml";
  207. const ACTIVE_NETWORK: &str = "testnet";
  208. const NETWORK_CONFIGS: &[(&str, &str, &str, &str)] = &[
  209. (
  210. "localnet",
  211. "~/.local/share/darkfi/explorerd/localnet",
  212. "tcp://127.0.0.1:28345/",
  213. "tcp://127.0.0.1:14567/",
  214. ),
  215. (
  216. "testnet",
  217. "~/.local/share/darkfi/explorerd/testnet",
  218. "tcp://127.0.0.1:18345/",
  219. "tcp://127.0.0.1:14667/",
  220. ),
  221. (
  222. "mainnet",
  223. "~/.local/share/darkfi/explorerd/mainnet",
  224. "tcp://127.0.0.1:8345/",
  225. "tcp://127.0.0.1:14767/",
  226. ),
  227. ];
  228. if setup_test_logger(
  229. &["sled", "runtime", "net"],
  230. false,
  231. Level::Info,
  232. //Level::Verbose,
  233. //Level::Debug,
  234. //Level::Trace,
  235. )
  236. .is_err()
  237. {
  238. warn!("Logger already initialized");
  239. }
  240. // Ensure the configuration file exists
  241. assert!(Path::new(CONFIG_PATH).exists());
  242. // Load the configuration
  243. let config = ExplorerConfig::new(CONFIG_PATH.to_string())
  244. .expect("Failed to load configuration from file");
  245. // Validate the expected network
  246. assert_eq!(config.network, ACTIVE_NETWORK);
  247. // Validate the path is correctly set
  248. assert_eq!(config.path.as_deref(), Some(CONFIG_PATH));
  249. // Validate that `active_network_config` correctly retrieves the testnet configuration
  250. let active_config = config.active_network_config();
  251. assert!(active_config.is_some(), "Active network configuration should not be None.");
  252. let active_config = active_config.unwrap();
  253. assert_eq!(active_config.database, NETWORK_CONFIGS[1].1); // Testnet database
  254. assert_eq!(active_config.endpoint.to_string(), NETWORK_CONFIGS[1].2);
  255. assert_eq!(&active_config.rpc.rpc_listen.to_string(), NETWORK_CONFIGS[1].3);
  256. // Validate all network configurations values (localnet, testnet, mainnet)
  257. for &(network, expected_db, expected_endpoint, expected_rpc) in NETWORK_CONFIGS {
  258. let network_config = config.get_network_config(network);
  259. if let Some(config) = network_config {
  260. assert_eq!(config.database, expected_db);
  261. assert_eq!(config.endpoint.to_string(), expected_endpoint);
  262. assert_eq!(config.rpc.rpc_listen.to_string(), expected_rpc);
  263. } else {
  264. assert!(network_config.is_none(), "{network} configuration is missing");
  265. }
  266. }
  267. // Validate (path, network).try_into()
  268. let config_path_buf = &PathBuf::from(CONFIG_PATH);
  269. let mainnet_string = &String::from("mainnet");
  270. let mainnet_config: ExplorerNetworkConfig = (config_path_buf, mainnet_string)
  271. .try_into()
  272. .expect("Failed to load explorer network config");
  273. assert_eq!(mainnet_config.database, NETWORK_CONFIGS[2].1); // Mainnet database
  274. assert_eq!(mainnet_config.endpoint.to_string(), NETWORK_CONFIGS[2].2);
  275. assert_eq!(&mainnet_config.rpc.rpc_listen.to_string(), NETWORK_CONFIGS[2].3);
  276. }
  277. }