Skip to main content

tape_network/
store.rs

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    /// Update the health values in the database.
100    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    /// Load the last‐written health values.
116    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        // Store tape_number -> address
150        batch.put_cf(cf_tapes, tape_number_key, address.to_bytes());
151        // Store address -> tape_number
152        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        // Create key: [<tape_address><segment_number>]
174        let mut key = Vec::with_capacity(40); // 32 bytes for pubkey + 8 bytes for segment_number
175        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                // 32 bytes for pubkey + 8 bytes for segment_number
237                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        // Sort by segment_number
253        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); // 32 bytes for pubkey + 8 bytes for segment_number
268        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        // RocksDB handles cleanup automatically
283    }
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}