1use std::path::Path;
7
8use fjall::{Database, Keyspace, KeyspaceCreateOptions, PersistMode};
9use polydigest::{AlgorithmMetas, Storage};
10
11const LOG_META_KEY: [u8; 9] = *b"_logmeta_";
16
17fn checkpoint_root_key(alg_id: u64) -> [u8; 10] {
22 let mut key = [0u8; 10];
23 key[0] = b'r';
24 key[1] = b'o';
25 key[2..10].copy_from_slice(&alg_id.to_be_bytes());
26 key
27}
28
29#[derive(Debug, thiserror::Error)]
31pub enum FjallStorageError {
32 #[error("Database error: {0}")]
34 Database(String),
35
36 #[error("I/O error: {0}")]
38 Io(#[from] std::io::Error),
39
40 #[error("Epoch metadata corruption: {0}")]
42 MetadataCorruption(String),
43
44 #[error("invalid keyspace prefix: {0:?}")]
46 InvalidPrefix(String),
47}
48
49pub struct FjallStorage {
54 #[allow(dead_code)]
55 db: Database,
56 leaves: Keyspace,
57 nodes: Keyspace,
58 metadata: Keyspace,
59}
60
61impl FjallStorage {
62 pub fn open(path: &Path) -> Result<Self, FjallStorageError> {
69 let db = Database::builder(path)
70 .open()
71 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
72 Self::with_database(db)
73 }
74
75 pub fn with_database(db: Database) -> Result<Self, FjallStorageError> {
82 let leaves = db
83 .keyspace("eml_leaves", KeyspaceCreateOptions::default)
84 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
85 let nodes = db
86 .keyspace("eml_nodes", KeyspaceCreateOptions::default)
87 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
88 let metadata = db
89 .keyspace("eml_metadata", KeyspaceCreateOptions::default)
90 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
91
92 Ok(Self {
93 db,
94 leaves,
95 nodes,
96 metadata,
97 })
98 }
99
100 pub fn with_database_scoped(db: Database, prefix: &str) -> Result<Self, FjallStorageError> {
123 if prefix.is_empty()
124 || !prefix
125 .chars()
126 .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '.' | '#' | '$'))
127 {
128 return Err(FjallStorageError::InvalidPrefix(prefix.to_string()));
129 }
130
131 let leaves_name = format!("{prefix}_eml_leaves");
132 let nodes_name = format!("{prefix}_eml_nodes");
133 let metadata_name = format!("{prefix}_eml_metadata");
134
135 if metadata_name.len() > 255 {
138 return Err(FjallStorageError::InvalidPrefix(prefix.to_string()));
139 }
140
141 let leaves = db
142 .keyspace(&leaves_name, KeyspaceCreateOptions::default)
143 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
144 let nodes = db
145 .keyspace(&nodes_name, KeyspaceCreateOptions::default)
146 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
147 let metadata = db
148 .keyspace(&metadata_name, KeyspaceCreateOptions::default)
149 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
150
151 Ok(Self {
152 db,
153 leaves,
154 nodes,
155 metadata,
156 })
157 }
158}
159
160impl Storage for FjallStorage {
161 type Error = FjallStorageError;
162
163 async fn store_leaf(&mut self, index: u64, data: &[u8]) -> Result<(), Self::Error> {
164 let key = index.to_be_bytes();
165 self.leaves
166 .insert(key, data)
167 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
168 Ok(())
169 }
170
171 async fn get_leaf(&self, index: u64) -> Result<Vec<u8>, Self::Error> {
172 let key = index.to_be_bytes();
173 let value = self
174 .leaves
175 .get(key)
176 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
177 match value {
178 Some(bytes) => Ok(bytes.to_vec()),
179 None => Err(FjallStorageError::Io(std::io::Error::new(
180 std::io::ErrorKind::NotFound,
181 format!("leaf index {index} not found"),
182 ))),
183 }
184 }
185
186 async fn len(&self) -> Result<u64, Self::Error> {
187 match self.leaves.iter().next_back() {
188 None => Ok(0),
189 Some(guard) => {
190 let key = guard
191 .key()
192 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
193 let arr: [u8; 8] = key.as_ref().try_into().map_err(|_| {
194 FjallStorageError::MetadataCorruption("leaf key has wrong length".to_string())
195 })?;
196 Ok(u64::from_be_bytes(arr) + 1)
197 },
198 }
199 }
200
201 async fn store_node(
202 &mut self,
203 alg_id: u64,
204 left: u64,
205 height: u32,
206 hash: &[u8],
207 ) -> Result<(), Self::Error> {
208 let mut key = [0u8; 20];
209 key[0..8].copy_from_slice(&alg_id.to_be_bytes());
210 key[8..16].copy_from_slice(&left.to_be_bytes());
211 key[16..20].copy_from_slice(&height.to_be_bytes());
212
213 self.nodes
214 .insert(key, hash)
215 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
216 Ok(())
217 }
218
219 async fn get_node(
220 &self,
221 alg_id: u64,
222 left: u64,
223 height: u32,
224 ) -> Result<Option<Vec<u8>>, Self::Error> {
225 let mut key = [0u8; 20];
226 key[0..8].copy_from_slice(&alg_id.to_be_bytes());
227 key[8..16].copy_from_slice(&left.to_be_bytes());
228 key[16..20].copy_from_slice(&height.to_be_bytes());
229
230 let value = self
231 .nodes
232 .get(key)
233 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
234 Ok(value.map(|bytes| bytes.to_vec()))
235 }
236
237 async fn store_algorithm_meta(
238 &mut self,
239 alg_id: u64,
240 epochs: &[(u64, u64)],
241 ) -> Result<(), Self::Error> {
242 let key = alg_id.to_be_bytes();
243 let mut bytes = Vec::with_capacity(epochs.len() * 16);
244 for &(start, end) in epochs {
245 bytes.extend_from_slice(&start.to_be_bytes());
246 bytes.extend_from_slice(&end.to_be_bytes());
247 }
248 self.metadata
249 .insert(key, bytes)
250 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
251 Ok(())
252 }
253
254 async fn load_algorithm_metas(&self) -> Result<AlgorithmMetas, Self::Error> {
255 let mut metas = Vec::new();
256 for item in self.metadata.iter() {
257 let (key_bytes, val_bytes) = item
258 .into_inner()
259 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
260 let key_ref = key_bytes.as_ref();
262 if key_ref == LOG_META_KEY
263 || (key_ref.len() == 10 && key_ref[0] == b'r' && key_ref[1] == b'o')
264 {
265 continue;
266 }
267 let alg_id = {
268 let arr: [u8; 8] = key_ref.try_into().map_err(|_| {
269 FjallStorageError::MetadataCorruption("invalid key length".to_string())
270 })?;
271 u64::from_be_bytes(arr)
272 };
273
274 if val_bytes.len() % 16 != 0 {
275 return Err(FjallStorageError::MetadataCorruption(
276 "metadata value length is not a multiple of 16".to_string(),
277 ));
278 }
279
280 let mut epochs = Vec::with_capacity(val_bytes.len() / 16);
281 for chunk in val_bytes.chunks_exact(16) {
282 let start_bytes =
283 chunk[0..8]
284 .try_into()
285 .map_err(|e: std::array::TryFromSliceError| {
286 FjallStorageError::MetadataCorruption(e.to_string())
287 })?;
288 let end_bytes =
289 chunk[8..16]
290 .try_into()
291 .map_err(|e: std::array::TryFromSliceError| {
292 FjallStorageError::MetadataCorruption(e.to_string())
293 })?;
294 let start = u64::from_be_bytes(start_bytes);
295 let end = u64::from_be_bytes(end_bytes);
296 epochs.push((start, end));
297 }
298 metas.push((alg_id, epochs));
299 }
300 Ok(metas)
301 }
302
303 async fn load_log_meta(&self) -> Result<Option<(u64, u8)>, Self::Error> {
304 let value = self
305 .metadata
306 .get(LOG_META_KEY)
307 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
308 match value {
309 None => Ok(None),
310 Some(bytes) => {
311 if bytes.len() != 9 {
312 return Err(FjallStorageError::MetadataCorruption(
313 "log_meta value must be 9 bytes".to_string(),
314 ));
315 }
316 let count = u64::from_be_bytes(bytes[0..8].try_into().unwrap());
317 let kind = bytes[8];
318 Ok(Some((count, kind)))
319 },
320 }
321 }
322
323 async fn load_checkpoint_roots(&self) -> Result<Vec<(u64, Vec<u8>)>, Self::Error> {
324 let mut roots = Vec::new();
325 for item in self.metadata.iter() {
326 let (key_bytes, val_bytes) = item
327 .into_inner()
328 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
329 let key_ref = key_bytes.as_ref();
330 if key_ref.len() == 10 && key_ref[0] == b'r' && key_ref[1] == b'o' {
331 let arr: [u8; 8] = key_ref[2..10].try_into().map_err(|_| {
332 FjallStorageError::MetadataCorruption(
333 "checkpoint root key has wrong alg_id length".to_string(),
334 )
335 })?;
336 let alg_id = u64::from_be_bytes(arr);
337 roots.push((alg_id, val_bytes.to_vec()));
338 }
339 }
340 Ok(roots)
341 }
342
343 async fn write_batch(
344 &mut self,
345 leaves: &[(u64, &[u8])],
346 nodes: &[(u64, u64, u32, &[u8])],
347 algorithm_metas: &[(u64, &[(u64, u64)])],
348 log_meta: Option<(u64, u8)>,
349 checkpoint_roots: &[(u64, &[u8])],
350 ) -> Result<(), Self::Error> {
351 let mut batch = self.db.batch();
352
353 for &(index, data) in leaves {
354 let key = index.to_be_bytes();
355 batch.insert(&self.leaves, key, data);
356 }
357
358 for &(alg_id, left, height, hash) in nodes {
359 let mut key = [0u8; 20];
360 key[0..8].copy_from_slice(&alg_id.to_be_bytes());
361 key[8..16].copy_from_slice(&left.to_be_bytes());
362 key[16..20].copy_from_slice(&height.to_be_bytes());
363 batch.insert(&self.nodes, key, hash);
364 }
365
366 for &(alg_id, epochs) in algorithm_metas {
367 let key = alg_id.to_be_bytes();
368 let mut bytes = Vec::with_capacity(epochs.len() * 16);
369 for &(start, end) in epochs {
370 bytes.extend_from_slice(&start.to_be_bytes());
371 bytes.extend_from_slice(&end.to_be_bytes());
372 }
373 batch.insert(&self.metadata, key, bytes);
374 }
375
376 if let Some((count, kind)) = log_meta {
377 let mut value = [0u8; 9];
378 value[0..8].copy_from_slice(&count.to_be_bytes());
379 value[8] = kind;
380 batch.insert(&self.metadata, LOG_META_KEY, value);
381 }
382
383 for &(alg_id, root) in checkpoint_roots {
384 let key = checkpoint_root_key(alg_id);
385 batch.insert(&self.metadata, key, root);
386 }
387
388 batch
389 .durability(Some(PersistMode::SyncAll))
390 .commit()
391 .map_err(|e| FjallStorageError::Database(e.to_string()))?;
392
393 Ok(())
394 }
395}