iota_light_client/
config.rs1use core::str::FromStr;
6use std::path::{Path, PathBuf};
7
8use anyhow::{Result, anyhow, bail};
9use iota_config::object_storage_config::{ObjectStoreConfig, ObjectStoreType};
10use serde::{Deserialize, Serialize};
11use tokio::fs::{create_dir_all, read_to_string};
12use url::Url;
13
14const GENESIS_FILE_NAME: &str = "genesis.blob";
15const CHECKPOINTS_FILE_NAME: &str = "checkpoints.yaml";
16
17#[derive(Clone, Debug, Deserialize, Serialize)]
19pub struct Config {
20 pub rpc_url: Url,
22 pub graphql_url: Option<Url>,
24 pub checkpoints_dir: PathBuf,
26 pub genesis_blob_download_url: Option<Url>,
28 pub sync_before_check: bool,
31 pub checkpoint_store_config: Option<ObjectStoreConfig>,
34 pub archive_store_config: Option<ObjectStoreConfig>,
38}
39
40impl Config {
41 pub fn get_mainnet_config() -> Self {
42 Self::get_config_by_network("mainnet")
43 }
44
45 pub fn get_testnet_config() -> Self {
46 Self::get_config_by_network("testnet")
47 }
48
49 pub fn get_devnet_config() -> Self {
50 Self::get_config_by_network("devnet")
51 }
52
53 fn get_config_by_network(network: &str) -> Self {
54 Self {
55 rpc_url: Url::parse(&format!("https://api.{network}.iota.cafe")).unwrap(),
56 graphql_url: Some(Url::parse(&format!("https://graphql.{network}.iota.cafe")).unwrap()),
57 checkpoints_dir: PathBuf::from_str(&format!("checkpoints_{network}")).unwrap(),
58 genesis_blob_download_url: Some(
59 Url::parse(&format!("https://dbfiles.{network}.iota.cafe/genesis.blob")).unwrap(),
60 ),
61 sync_before_check: false,
62 checkpoint_store_config: Some(ObjectStoreConfig {
63 object_store: Some(ObjectStoreType::S3),
64 object_store_connection_limit: 20,
65 aws_endpoint: Some(format!("https://checkpoints.{network}.iota.cafe")),
66 aws_virtual_hosted_style_request: true,
67 aws_region: Some("weur".to_string()),
68 no_sign_request: true,
69 ..Default::default()
70 }),
71 archive_store_config: Some(ObjectStoreConfig {
72 object_store: Some(ObjectStoreType::S3),
73 object_store_connection_limit: 20,
74 aws_endpoint: Some(format!("https://archive.{network}.iota.cafe")),
75 aws_virtual_hosted_style_request: true,
76 aws_region: Some("weur".to_string()),
77 no_sign_request: true,
78 ..Default::default()
79 }),
80 }
81 }
82}
83
84impl Config {
85 pub async fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
87 let content = read_to_string(path).await?;
88 let config: Config = serde_yaml::from_str(&content)?;
89 config.validate()?;
90 Ok(config)
91 }
92
93 pub async fn setup(&self) -> Result<()> {
96 if !self.checkpoints_dir.is_dir() {
98 create_dir_all(&self.checkpoints_dir).await?;
99 }
100 if !self.genesis_blob_file_path().is_file() {
102 if let Some(url) = &self.genesis_blob_download_url {
103 match url.scheme() {
104 "file" => {
105 let path = url
106 .to_file_path()
107 .map_err(|_| anyhow!("invalid file path '{url}'"))?;
108 tokio::fs::copy(path, self.genesis_blob_file_path()).await?;
109 }
110 _ => {
111 let contents = reqwest::get(url.as_str()).await?.bytes().await?;
112 tokio::fs::write(self.genesis_blob_file_path(), contents).await?;
113 }
114 }
115 }
116 }
117 Ok(())
118 }
119
120 pub fn validate(&self) -> Result<()> {
121 if self.graphql_url.is_none() && self.archive_store_config.is_none() {
122 bail!("Invalid config: either GraphQL URL or archive store config must be provided");
123 }
124 Ok(())
125 }
126
127 pub fn checkpoints_list_file_path(&self) -> PathBuf {
128 self.checkpoints_dir.join(CHECKPOINTS_FILE_NAME)
129 }
130
131 pub fn genesis_blob_file_path(&self) -> PathBuf {
132 self.checkpoints_dir.join(GENESIS_FILE_NAME)
133 }
134
135 pub fn full_checkpoint_file_path<'a>(
136 &self,
137 seq: u64,
138 custom_path: impl Into<Option<&'a str>>,
139 ) -> PathBuf {
140 let mut path = self.checkpoints_dir.clone();
141 if let Some(custom) = custom_path.into() {
142 path.push(custom);
143 }
144 path.push(format!("{seq}.chk"));
145 path
146 }
147
148 pub fn checkpoint_summary_file_path(&self, seq: u64) -> PathBuf {
149 Path::new(&self.checkpoints_dir).join(format!("{seq}.sum"))
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use iota_config::object_storage_config::ObjectStoreType;
156 use tempfile::TempDir;
157
158 use super::*;
159
160 fn create_test_config() -> (Config, TempDir) {
161 let temp_dir = TempDir::new().unwrap();
162 std::fs::File::create(temp_dir.path().join("genesis.blob")).unwrap();
163 let config = Config {
164 rpc_url: "http://localhost:9000".parse().unwrap(),
165 graphql_url: Some("http://localhost:9003".parse().unwrap()),
166 checkpoints_dir: temp_dir.path().to_path_buf(),
167 genesis_blob_download_url: None,
168 sync_before_check: false,
169 checkpoint_store_config: Some(ObjectStoreConfig {
170 object_store: Some(ObjectStoreType::S3),
171 aws_endpoint: Some("http://localhost:9001".to_string()),
172 ..Default::default()
173 }),
174 archive_store_config: Some(ObjectStoreConfig {
175 object_store: Some(ObjectStoreType::File),
176 directory: Some(temp_dir.path().to_path_buf()),
177 ..Default::default()
178 }),
179 };
180 config.validate().expect("invalid");
181 (config, temp_dir)
182 }
183
184 #[test]
185 fn test_config_validation() {
186 let (config, _temp_dir) = create_test_config();
187 assert!(config.validate().is_ok());
188 }
189
190 #[test]
191 fn test_checkpoint_paths() {
192 let (config, _temp_dir) = create_test_config();
193
194 let list_path = config.checkpoints_list_file_path();
195 assert_eq!(list_path.file_name().unwrap(), "checkpoints.yaml");
196
197 let checkpoint_path = config.full_checkpoint_file_path(123, None);
198 assert_eq!(checkpoint_path.file_name().unwrap(), "123.chk");
199
200 let custom_checkpoint_path = config.full_checkpoint_file_path(456, Some("custom"));
201 assert!(custom_checkpoint_path.to_str().unwrap().contains("custom"));
202 assert_eq!(custom_checkpoint_path.file_name().unwrap(), "456.chk");
203 }
204
205 #[test]
206 fn test_genesis_path() {
207 let (config, _temp_dir) = create_test_config();
208 let genesis_path = config.genesis_blob_file_path();
209 assert_eq!(genesis_path.file_name().unwrap(), "genesis.blob");
210 }
211}