agglayer_config/
storage.rs1use std::path::{Component, Path, PathBuf};
2
3use agglayer_types::EpochNumber;
4use backup::BackupConfig;
5use serde::{Deserialize, Serialize};
6
7pub(crate) const STORAGE_DIR: &str = "storage";
8const METADATA_DB_NAME: &str = "metadata";
9const PENDING_DB_NAME: &str = "pending";
10const STATE_DB_NAME: &str = "state";
11const EPOCHS_DB_PATH: &str = "epochs";
12const DEBUG_DB_PATH: &str = "debug";
13
14pub mod backup;
15
16#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
18#[serde(from = "StorageConfigHelper", into = "StorageConfigHelper")]
19#[serde(default)]
20pub struct StorageConfig {
21 pub metadata_db_path: PathBuf,
23 pub pending_db_path: PathBuf,
25 pub state_db_path: PathBuf,
27 pub epochs_db_path: PathBuf,
29 pub debug_db_path: PathBuf,
31 pub backup: BackupConfig,
33}
34
35impl Default for StorageConfig {
36 fn default() -> Self {
37 StorageConfig {
38 metadata_db_path: Path::new("./").join(STORAGE_DIR).join(METADATA_DB_NAME),
39 pending_db_path: Path::new("./").join(STORAGE_DIR).join(PENDING_DB_NAME),
40 state_db_path: Path::new("./").join(STORAGE_DIR).join(STATE_DB_NAME),
41 epochs_db_path: Path::new("./").join(STORAGE_DIR).join(EPOCHS_DB_PATH),
42 debug_db_path: Path::new("./").join(STORAGE_DIR).join(DEBUG_DB_PATH),
43 backup: BackupConfig::default(),
44 }
45 }
46}
47
48impl StorageConfig {
49 pub fn path_contextualized(mut self, base_path: &Path) -> Self {
50 self.metadata_db_path = normalize_path(&base_path.join(&self.metadata_db_path));
51 self.pending_db_path = normalize_path(&base_path.join(&self.pending_db_path));
52 self.state_db_path = normalize_path(&base_path.join(&self.state_db_path));
53 self.epochs_db_path = normalize_path(&base_path.join(&self.epochs_db_path));
54 self.debug_db_path = normalize_path(&base_path.join(&self.debug_db_path));
55
56 self
57 }
58
59 pub fn new_with_default_path() -> Self {
61 Self::new_from_path(&Path::new("./").join(STORAGE_DIR))
62 }
63
64 pub fn new_from_path(value: &Path) -> Self {
66 let db_path = value.join(STORAGE_DIR);
67
68 Self {
69 metadata_db_path: db_path.join(METADATA_DB_NAME),
70 pending_db_path: db_path.join(PENDING_DB_NAME),
71 state_db_path: db_path.join(STATE_DB_NAME),
72 epochs_db_path: db_path.join(EPOCHS_DB_PATH),
73 debug_db_path: db_path.join(DEBUG_DB_PATH),
74 backup: BackupConfig::default(),
75 }
76 }
77
78 pub fn epoch_db_path(&self, epoch_number: EpochNumber) -> PathBuf {
79 self.epochs_db_path.join(format!("{epoch_number}"))
80 }
81}
82
83#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
85#[serde(rename_all = "kebab-case")]
86struct StorageConfigHelper {
87 #[serde(default = "default_db_path")]
88 db_path: PathBuf,
89 pub metadata_db_path: Option<PathBuf>,
91 pub pending_db_path: Option<PathBuf>,
93 pub state_db_path: Option<PathBuf>,
95 pub epochs_db_path: Option<PathBuf>,
97 pub debug_db_path: Option<PathBuf>,
99 #[serde(default, skip_serializing_if = "BackupConfig::is_disabled")]
101 pub backup: BackupConfig,
102}
103
104impl From<StorageConfigHelper> for StorageConfig {
105 fn from(value: StorageConfigHelper) -> Self {
106 StorageConfig {
107 metadata_db_path: value
108 .metadata_db_path
109 .unwrap_or_else(|| value.db_path.join(METADATA_DB_NAME)),
110 pending_db_path: value
111 .pending_db_path
112 .unwrap_or_else(|| value.db_path.join(PENDING_DB_NAME)),
113 state_db_path: value
114 .state_db_path
115 .unwrap_or_else(|| value.db_path.join(STATE_DB_NAME)),
116 epochs_db_path: value
117 .epochs_db_path
118 .unwrap_or_else(|| value.db_path.join(EPOCHS_DB_PATH)),
119 debug_db_path: value
120 .debug_db_path
121 .unwrap_or_else(|| value.db_path.join(DEBUG_DB_PATH)),
122 backup: value.backup,
123 }
124 }
125}
126
127impl From<StorageConfig> for StorageConfigHelper {
128 fn from(value: StorageConfig) -> Self {
129 let db_path = value
130 .state_db_path
131 .parent()
132 .expect("Unable to define a base_path for the storage")
133 .to_path_buf();
134
135 Self {
136 db_path,
137 metadata_db_path: None,
138 pending_db_path: None,
139 state_db_path: None,
140 epochs_db_path: None,
141 debug_db_path: None,
142 backup: value.backup,
143 }
144 }
145}
146
147fn default_db_path() -> PathBuf {
148 PathBuf::new().join(STORAGE_DIR)
149}
150
151pub(crate) fn normalize_path(path: &Path) -> PathBuf {
166 let mut components = path.components().peekable();
167 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
168 components.next();
169 PathBuf::from(c.as_os_str())
170 } else {
171 PathBuf::new()
172 };
173
174 for component in components {
175 match component {
176 Component::Prefix(..) => unreachable!(),
177 Component::RootDir => {
178 ret.push(component.as_os_str());
179 }
180 Component::CurDir => {}
181 Component::ParentDir => {
182 ret.pop();
183 }
184 Component::Normal(c) => {
185 ret.push(c);
186 }
187 }
188 }
189 ret
190}
191#[cfg(test)]
192mod tests {
193 use pretty_assertions::assert_eq;
194
195 use super::*;
196
197 #[test]
198 fn base_path() {
199 let value = toml::toml! {
200 db-path = "/tmp/base"
201 };
202
203 let cfg = toml::to_string(&value).unwrap();
204 let config: StorageConfig = toml::from_str(&cfg).unwrap();
205
206 assert_eq!(config.metadata_db_path, PathBuf::from("/tmp/base/metadata"));
207 assert_eq!(config.pending_db_path, PathBuf::from("/tmp/base/pending"));
208 assert_eq!(config.state_db_path, PathBuf::from("/tmp/base/state"));
209 assert_eq!(config.epochs_db_path, PathBuf::from("/tmp/base/epochs"));
210 assert_eq!(config.debug_db_path, PathBuf::from("/tmp/base/debug"));
211 }
212}