Skip to main content

ethrex_storage/api/
mod.rs

1//! # Storage Backend API
2//!
3//! This module provides a thin, minimal interface for storage backends:
4//!
5//! - Thin: Minimal set of operations that databases must provide
6//! - Simple: Avoids type-system complexity and focuses on core functionality
7//!
8//! Rather than implementing business logic in each database backend, this API
9//! provides low-level primitives that higher-level code can build upon.
10//! This eliminates code duplication and makes adding new database backends trivial.
11//!
12//! The API differentiates between three types of database access:
13//!
14//! - Read views ([`StorageReadView`]): read-only views of the database,
15//!   with no atomicity guarantees between operations.
16//! - Write batches ([`StorageWriteBatch`]): write batch functionality, with
17//!   atomicity guarantees at commit time.
18//! - Locked views ([`StorageLockedView`]): read-only views of a point in time (snapshots), right now it's
19//!   only used during snap-sync.
20
21use crate::error::StoreError;
22use std::{fmt::Debug, path::Path, sync::Arc};
23
24pub mod tables;
25
26/// Type alias for the result of a prefix iterator.
27pub type PrefixResult = Result<(Box<[u8]>, Box<[u8]>), StoreError>;
28
29/// This trait provides a minimal set of operations required from a database backend.
30/// Implementations should focus on providing efficient access to the underlying storage
31/// without implementing business logic.
32pub trait StorageBackend: Debug + Send + Sync {
33    /// Removes all data from the specified table.
34    fn clear_table(&self, table: &'static str) -> Result<(), StoreError>;
35
36    /// Opens a new read view.
37    fn begin_read(&self) -> Result<Arc<dyn StorageReadView>, StoreError>;
38
39    /// Creates a new write batch.
40    fn begin_write(&self) -> Result<Box<dyn StorageWriteBatch + 'static>, StoreError>;
41
42    /// Creates a locked snapshot for a specific table.
43    ///
44    /// This provides a persistent read-only view of a single table, optimized
45    /// for batch read operations. The snapshot remains valid until dropped.
46    fn begin_locked(
47        &self,
48        table_name: &'static str,
49    ) -> Result<Box<dyn StorageLockedView + 'static>, StoreError>;
50
51    // TODO: remove this and provide historic data via diff-layers
52    /// Creates a checkpoint of the current database state at the specified path.
53    fn create_checkpoint(&self, path: &Path) -> Result<(), StoreError>;
54
55    /// Durably persists all buffered writes to disk, so a subsequent process
56    /// start needs no crash recovery. Called on graceful shutdown. Defaults to a
57    /// no-op for backends that are already durable or purely in-memory.
58    fn flush(&self) -> Result<(), StoreError> {
59        Ok(())
60    }
61}
62
63/// Read-only transaction interface.
64/// Provides methods to read data from the database
65pub trait StorageReadView: Send + Sync {
66    /// Retrieves a value by key from the specified table.
67    fn get(&self, table: &'static str, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError>;
68
69    /// Retrieves multiple values by key from the specified table.
70    /// Returns results in the same order as the input keys.
71    /// Backends that support batched reads (e.g. RocksDB `multi_get_cf`)
72    /// should override this for better throughput. Callers should not
73    /// assume `multi_get` is asymptotically faster than `get`; on backends
74    /// without a batched read primitive (e.g. the in-memory backend) the
75    /// default impl below is equivalent to N independent `get` calls.
76    fn multi_get(
77        &self,
78        table: &'static str,
79        keys: &[&[u8]],
80    ) -> Vec<Result<Option<Vec<u8>>, StoreError>> {
81        keys.iter().map(|k| self.get(table, k)).collect()
82    }
83
84    /// Returns an iterator over all key-value pairs with the given prefix.
85    fn prefix_iterator(
86        &self,
87        table: &'static str,
88        prefix: &[u8],
89    ) -> Result<Box<dyn Iterator<Item = PrefixResult> + '_>, StoreError>;
90}
91
92/// Write transaction interface.
93///
94/// Note that this does not provide read access, since we don't currently use that functionality.
95///
96/// Changes are not persisted until [`commit()`](StorageWriteBatch::commit) is called.
97pub trait StorageWriteBatch: Send {
98    /// Stores a key-value pair in the specified table.
99    fn put(&mut self, table: &'static str, key: &[u8], value: &[u8]) -> Result<(), StoreError> {
100        self.put_batch(table, vec![(key.to_vec(), value.to_vec())])
101    }
102
103    /// Stores multiple key-value pairs in the specified table within the transaction.
104    fn put_batch(
105        &mut self,
106        table: &'static str,
107        batch: Vec<(Vec<u8>, Vec<u8>)>,
108    ) -> Result<(), StoreError>;
109
110    /// Removes a key-value pair from the specified table.
111    fn delete(&mut self, table: &'static str, key: &[u8]) -> Result<(), StoreError>;
112
113    /// Appends a merge operand for the given key in the specified table.
114    ///
115    /// The actual combine step is deferred — backends with a registered merge
116    /// operator (RocksDB) apply it at read or compaction time; backends without
117    /// (InMemory) dispatch by table and apply inline.
118    ///
119    /// Currently used for `TRANSACTION_LOCATIONS`. Calling on a table without
120    /// a registered merge function is an error.
121    fn merge(&mut self, table: &'static str, key: &[u8], operand: &[u8]) -> Result<(), StoreError>;
122
123    /// Commits all changes made in this transaction.
124    fn commit(&mut self) -> Result<(), StoreError>;
125}
126
127/// Locked snapshot interface for batch read operations.
128/// Provides read-only access to a specific table with a persistent snapshot.
129/// This is optimized for scenarios where many reads are performed on the same
130/// table, such as trie traversal operations.
131/// This is currently only used in snapsync stage.
132// TODO: Check if we can remove this trait and use [`StorageReadView`] instead.
133pub trait StorageLockedView: Send + Sync {
134    /// Retrieves a value by key from the locked table.
135    fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError>;
136}