1use rocksdb::{ColumnFamilyDescriptor, DBCompressionType, Options, WriteBatch, DB};
2use solana_sdk::pubkey::Pubkey;
3use std::env;
4use std::path::Path;
5use tape_api::SEGMENT_SIZE;
6use thiserror::Error;
7
8#[derive(Error, Debug)]
9pub enum StoreError {
10 #[error("RocksDB error: {0}")]
11 RocksDB(#[from] rocksdb::Error),
12 #[error("IO error: {0}")]
13 IoError(#[from] std::io::Error),
14
15 #[error("Health column family not found")]
16 HealthCfNotFound,
17
18 #[error("Tapes column family not found")]
19 TapesCfNotFound,
20 #[error("Segments column family not found")]
21 SegmentsCfNotFound,
22 #[error("Tape not found: number {0}")]
23 TapeNotFound(u64),
24 #[error("Segment not found for tape address {0}, segment {1}")]
25 SegmentNotFound(String, u64),
26 #[error("Tape not found for address: {0}")]
27 TapeNotFoundForAddress(String),
28 #[error("Invalid pubkey: {0}")]
29 InvalidPubkey(String),
30 #[error("Segment data exceeds maximum size of {0} bytes")]
31 SegmentSizeExceeded(usize),
32 #[error("Invalid segment key format")]
33 InvalidSegmentKey,
34 #[error("Invalid path")]
35 InvalidPath,
36}
37
38pub struct TapeStore {
39 db: DB,
40}
41
42impl TapeStore {
43 pub fn new<P: AsRef<Path>>(path: P) -> Result<Self, StoreError> {
44 let path = path.as_ref();
45 let mut cf_opts = Options::default();
46 cf_opts.create_if_missing(true);
47 cf_opts.set_compression_type(DBCompressionType::Lz4);
48
49 let cf_tapes = ColumnFamilyDescriptor::new("tapes", cf_opts.clone());
50 let cf_segments = ColumnFamilyDescriptor::new("segments", cf_opts.clone());
51 let cf_health = ColumnFamilyDescriptor::new("health", cf_opts.clone());
52
53 let mut db_opts = Options::default();
54 db_opts.create_if_missing(true);
55 db_opts.create_missing_column_families(true);
56 db_opts.set_write_buffer_size(128 * 1024 * 1024);
57 db_opts.set_max_write_buffer_number(4);
58 db_opts.create_missing_column_families(true);
59
60 let db = DB::open_cf_descriptors(
61 &db_opts,
62 path,
63 vec![cf_tapes, cf_segments, cf_health],
64 )?;
65
66 Ok(Self { db })
67 }
68
69 pub fn new_secondary<P: AsRef<Path>>(
70 primary_path: P,
71 secondary_path: P,
72 ) -> Result<Self, StoreError> {
73 let primary_path = primary_path.as_ref();
74 let secondary_path = secondary_path.as_ref();
75 let mut cf_opts = Options::default();
76 cf_opts.set_compression_type(DBCompressionType::Lz4);
77
78 let cf_tapes = ColumnFamilyDescriptor::new("tapes", cf_opts.clone());
79 let cf_segments = ColumnFamilyDescriptor::new("segments", cf_opts.clone());
80 let cf_health = ColumnFamilyDescriptor::new("health", cf_opts.clone());
81
82 let mut db_opts = Options::default();
83 db_opts.set_compression_type(DBCompressionType::Lz4);
84
85 let db = DB::open_cf_descriptors_as_secondary(
86 &db_opts,
87 primary_path,
88 secondary_path,
89 vec![cf_tapes, cf_segments, cf_health],
90 )?;
91 Ok(Self { db })
92 }
93
94 pub fn catch_up_with_primary(&self) -> Result<(), StoreError> {
95 self.db.try_catch_up_with_primary()?;
96 Ok(())
97 }
98
99 pub fn update_health(&self, last_processed_slot: u64, drift: u64) -> Result<(), StoreError> {
101 let cf = self
102 .db
103 .cf_handle("health")
104 .ok_or(StoreError::HealthCfNotFound)?;
105
106 let mut batch = WriteBatch::default();
107 batch.put_cf(cf, b"last_processed_slot", &last_processed_slot.to_be_bytes());
108 batch.put_cf(cf, b"drift", &drift.to_be_bytes());
109
110 self.db.write(batch)?;
111
112 Ok(())
113 }
114
115 pub fn get_health(&self) -> Result<(u64, u64), StoreError> {
117 let cf = self
118 .db
119 .cf_handle("health")
120 .ok_or(StoreError::HealthCfNotFound)?;
121
122 let bh = self
123 .db
124 .get_cf(cf, b"last_processed_slot")?
125 .ok_or(StoreError::HealthCfNotFound)?;
126
127 let dr = self
128 .db
129 .get_cf(cf, b"drift")?
130 .ok_or(StoreError::HealthCfNotFound)?;
131
132 let height = u64::from_be_bytes(bh[..].try_into().unwrap());
133 let drift = u64::from_be_bytes(dr[..].try_into().unwrap());
134
135 Ok((height, drift))
136 }
137
138
139 pub fn add_tape(&self, tape_number: u64, address: &Pubkey) -> Result<(), StoreError> {
140 let cf_tapes = self
141 .db
142 .cf_handle("tapes")
143 .ok_or(StoreError::TapesCfNotFound)?;
144
145 let tape_number_key = tape_number.to_be_bytes().to_vec();
146 let address_key = address.to_bytes().to_vec();
147
148 let mut batch = WriteBatch::default();
149 batch.put_cf(cf_tapes, tape_number_key, address.to_bytes());
151 batch.put_cf(cf_tapes, address_key, tape_number.to_be_bytes());
153 self.db.write(batch)?;
154
155 Ok(())
156 }
157
158 pub fn add_segment(
159 &self,
160 tape_address: &Pubkey,
161 segment_number: u64,
162 data: Vec<u8>,
163 ) -> Result<(), StoreError> {
164 if data.len() > SEGMENT_SIZE {
165 return Err(StoreError::SegmentSizeExceeded(SEGMENT_SIZE));
166 }
167
168 let cf_segments = self
169 .db
170 .cf_handle("segments")
171 .ok_or(StoreError::SegmentsCfNotFound)?;
172
173 let mut key = Vec::with_capacity(40); key.extend_from_slice(&tape_address.to_bytes());
176 key.extend_from_slice(&segment_number.to_be_bytes());
177
178 let mut batch = WriteBatch::default();
179 batch.put_cf(cf_segments, &key, data);
180 self.db.write(batch)?;
181
182 Ok(())
183 }
184
185 pub fn get_tape_number(&self, address: &Pubkey) -> Result<u64, StoreError> {
186 let cf_tapes = self
187 .db
188 .cf_handle("tapes")
189 .ok_or(StoreError::TapesCfNotFound)?;
190
191 let address_key = address.to_bytes().to_vec();
192 let tape_number_bytes = self
193 .db
194 .get_cf(cf_tapes, &address_key)?
195 .ok_or_else(|| StoreError::TapeNotFoundForAddress(address.to_string()))?;
196
197 Ok(u64::from_be_bytes(
198 tape_number_bytes
199 .try_into()
200 .map_err(|_| StoreError::InvalidSegmentKey)?,
201 ))
202 }
203
204 pub fn get_tape_address(&self, tape_number: u64) -> Result<Pubkey, StoreError> {
205 let cf_tapes = self
206 .db
207 .cf_handle("tapes")
208 .ok_or(StoreError::TapesCfNotFound)?;
209
210 let tape_number_key = tape_number.to_be_bytes().to_vec();
211 let address_bytes = self
212 .db
213 .get_cf(cf_tapes, &tape_number_key)?
214 .ok_or(StoreError::TapeNotFound(tape_number))?;
215
216 Pubkey::try_from(address_bytes.as_slice())
217 .map_err(|e| StoreError::InvalidPubkey(e.to_string()))
218 }
219
220 pub fn get_tape_segments(
221 &self,
222 tape_address: &Pubkey,
223 ) -> Result<Vec<(u64, Vec<u8>)>, StoreError> {
224 let cf_segments = self
225 .db
226 .cf_handle("segments")
227 .ok_or(StoreError::SegmentsCfNotFound)?;
228
229 let mut segments = Vec::new();
230 let prefix = tape_address.to_bytes().to_vec();
231
232 let iter = self.db.prefix_iterator_cf(cf_segments, &prefix);
233 for item in iter {
234 let (key, value) = item?;
235 if key.len() != 40 {
236 continue;
238 }
239 if !key.starts_with(&prefix) {
240 continue;
241 }
242
243 let segment_number = u64::from_be_bytes(
244 key[32..40]
245 .try_into()
246 .map_err(|_| StoreError::InvalidSegmentKey)?,
247 );
248
249 segments.push((segment_number, value.to_vec()));
250 }
251
252 segments.sort_by(|a, b| a.0.cmp(&b.0));
254 Ok(segments)
255 }
256
257 pub fn get_segment(
258 &self,
259 tape_address: &Pubkey,
260 segment_number: u64,
261 ) -> Result<Vec<u8>, StoreError> {
262 let cf_segments = self
263 .db
264 .cf_handle("segments")
265 .ok_or(StoreError::SegmentsCfNotFound)?;
266
267 let mut key = Vec::with_capacity(40); key.extend_from_slice(&tape_address.to_bytes());
269 key.extend_from_slice(&segment_number.to_be_bytes());
270
271 let segment_data = self
272 .db
273 .get_cf(cf_segments, &key)?
274 .ok_or_else(|| StoreError::SegmentNotFound(tape_address.to_string(), segment_number))?;
275
276 Ok(segment_data.to_vec())
277 }
278}
279
280impl Drop for TapeStore {
281 fn drop(&mut self) {
282 }
284}
285
286pub fn primary() -> Result<TapeStore, StoreError> {
287 let current_dir = env::current_dir().map_err(|e| StoreError::IoError(e))?;
288 let db_primary = current_dir.join("db_tapestore");
289 std::fs::create_dir_all(&db_primary).map_err(|e| StoreError::IoError(e))?;
290 TapeStore::new(&db_primary)
291}
292
293pub fn secondary() -> Result<TapeStore, StoreError> {
294 let current_dir = env::current_dir().map_err(|e| StoreError::IoError(e))?;
295 let db_primary = current_dir.join("db_tapestore");
296 let db_secondary = current_dir.join("db_tapestore_read");
297 std::fs::create_dir_all(&db_secondary).map_err(|e| StoreError::IoError(e))?;
298 TapeStore::new_secondary(&db_primary, &db_secondary)
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use solana_sdk::pubkey::Pubkey;
305 use tempdir::TempDir;
306
307 fn setup_store() -> Result<(TapeStore, TempDir), StoreError> {
308 let temp_dir = TempDir::new("rocksdb_test").map_err(StoreError::IoError)?;
309 let store = TapeStore::new(temp_dir.path())?;
310 Ok((store, temp_dir))
311 }
312
313 #[test]
314 fn test_add_and_get_tape() -> Result<(), StoreError> {
315 let (store, _temp_dir) = setup_store()?;
316 let tape_number = 1;
317 let address = Pubkey::new_unique();
318
319 store.add_tape(tape_number, &address)?;
320 let retrieved_number = store.get_tape_number(&address)?;
321 assert_eq!(retrieved_number, tape_number);
322 let retrieved_address = store.get_tape_address(tape_number)?;
323 assert_eq!(retrieved_address, address);
324
325 Ok(())
326 }
327
328 #[test]
329 fn test_add_and_get_segments() -> Result<(), StoreError> {
330 let (store, _temp_dir) = setup_store()?;
331 let tape_number = 1;
332 let address = Pubkey::new_unique();
333
334 store.add_tape(tape_number, &address)?;
335 let segment_data_1 = vec![1, 2, 3];
336 let segment_data_2 = vec![4, 5, 6];
337 store.add_segment(&address, 0, segment_data_1.clone())?;
338 store.add_segment(&address, 1, segment_data_2.clone())?;
339
340 let segments = store.get_tape_segments(&address)?;
341 assert_eq!(segments.len(), 2);
342 assert_eq!(segments[0], (0, segment_data_1));
343 assert_eq!(segments[1], (1, segment_data_2));
344
345 let segments = store.get_tape_segments(&Pubkey::new_unique())?;
346 assert_eq!(segments.len(), 0);
347
348 Ok(())
349 }
350
351 #[test]
352 fn test_segment_size_limit() -> Result<(), StoreError> {
353 let (store, _temp_dir) = setup_store()?;
354 let address = Pubkey::new_unique();
355
356 let oversized_data = vec![0; SEGMENT_SIZE + 1];
357 let result = store.add_segment(&address, 0, oversized_data);
358 assert!(matches!(result, Err(StoreError::SegmentSizeExceeded(_))));
359
360 Ok(())
361 }
362
363 #[test]
364 fn test_error_cases() -> Result<(), StoreError> {
365 let (store, _temp_dir) = setup_store()?;
366 let address = Pubkey::new_unique();
367
368 let result = store.get_tape_number(&address);
369 assert!(matches!(result, Err(StoreError::TapeNotFoundForAddress(_))));
370
371 let result = store.get_tape_address(1);
372 assert!(matches!(result, Err(StoreError::TapeNotFound(1))));
373
374 Ok(())
375 }
376
377 #[test]
378 fn test_multiple_tapes() -> Result<(), StoreError> {
379 let (store, _temp_dir) = setup_store()?;
380
381 let tape1_number = 1;
382 let tape1_address = Pubkey::new_unique();
383 let tape2_number = 2;
384 let tape2_address = Pubkey::new_unique();
385
386 store.add_tape(tape1_number, &tape1_address)?;
387 store.add_tape(tape2_number, &tape2_address)?;
388 store.add_segment(&tape1_address, 0, vec![1, 2, 3])?;
389 store.add_segment(&tape2_address, 0, vec![4, 5, 6])?;
390
391 assert_eq!(store.get_tape_number(&tape1_address)?, tape1_number);
392 assert_eq!(store.get_tape_address(tape1_number)?, tape1_address);
393 let tape1_segments = store.get_tape_segments(&tape1_address)?;
394 assert_eq!(tape1_segments.len(), 1);
395 assert_eq!(tape1_segments[0], (0, vec![1, 2, 3]));
396
397 assert_eq!(store.get_tape_number(&tape2_address)?, tape2_number);
398 assert_eq!(store.get_tape_address(tape2_number)?, tape2_address);
399 let tape2_segments = store.get_tape_segments(&tape2_address)?;
400 assert_eq!(tape2_segments.len(), 1);
401 assert_eq!(tape2_segments[0], (0, vec![4, 5, 6]));
402
403 Ok(())
404 }
405
406 #[test]
407 fn test_get_segment() -> Result<(), StoreError> {
408 let (store, _temp_dir) = setup_store()?;
409 let tape_number = 1;
410 let segment_number = 0;
411 let address = Pubkey::new_unique();
412 let segment_data = vec![1, 2, 3];
413
414 store.add_tape(tape_number, &address)?;
415 store.add_segment(&address, segment_number, segment_data.clone())?;
416
417 let retrieved_data = store.get_segment(&address, segment_number)?;
418 assert_eq!(retrieved_data, segment_data);
419
420 Ok(())
421 }
422
423 #[test]
424 fn test_get_segment_non_existent_segment() -> Result<(), StoreError> {
425 let (store, _temp_dir) = setup_store()?;
426 let address = Pubkey::new_unique();
427 let segment_number = 0;
428
429 let result = store.get_segment(&address, segment_number);
430 assert!(matches!(result, Err(StoreError::SegmentNotFound(_, s)) if s == segment_number));
431
432 Ok(())
433 }
434
435 #[test]
436 fn test_get_multiple_segments() -> Result<(), StoreError> {
437 let (store, _temp_dir) = setup_store()?;
438 let tape_number = 1;
439 let address = Pubkey::new_unique();
440 let segment_data_1 = vec![1, 2, 3];
441 let segment_data_2 = vec![4, 5, 6];
442
443 store.add_tape(tape_number, &address)?;
444 store.add_segment(&address, 0, segment_data_1.clone())?;
445 store.add_segment(&address, 1, segment_data_2.clone())?;
446
447 let retrieved_data_1 = store.get_segment(&address, 0)?;
448 assert_eq!(retrieved_data_1, segment_data_1);
449
450 let retrieved_data_2 = store.get_segment(&address, 1)?;
451 assert_eq!(retrieved_data_2, segment_data_2);
452
453 Ok(())
454 }
455}