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
56/// Read-only transaction interface.
57/// Provides methods to read data from the database
58pub trait StorageReadView: Send + Sync {
59    /// Retrieves a value by key from the specified table.
60    fn get(&self, table: &'static str, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError>;
61
62    /// Returns an iterator over all key-value pairs with the given prefix.
63    fn prefix_iterator(
64        &self,
65        table: &'static str,
66        prefix: &[u8],
67    ) -> Result<Box<dyn Iterator<Item = PrefixResult> + '_>, StoreError>;
68}
69
70/// Write transaction interface.
71///
72/// Note that this does not provide read access, since we don't currently use that functionality.
73///
74/// Changes are not persisted until [`commit()`](StorageWriteBatch::commit) is called.
75pub trait StorageWriteBatch: Send {
76    /// Stores a key-value pair in the specified table.
77    fn put(&mut self, table: &'static str, key: &[u8], value: &[u8]) -> Result<(), StoreError> {
78        self.put_batch(table, vec![(key.to_vec(), value.to_vec())])
79    }
80
81    /// Stores multiple key-value pairs in the specified table within the transaction.
82    fn put_batch(
83        &mut self,
84        table: &'static str,
85        batch: Vec<(Vec<u8>, Vec<u8>)>,
86    ) -> Result<(), StoreError>;
87
88    /// Removes a key-value pair from the specified table.
89    fn delete(&mut self, table: &'static str, key: &[u8]) -> Result<(), StoreError>;
90
91    /// Appends a merge operand for the given key in the specified table.
92    ///
93    /// The actual combine step is deferred — backends with a registered merge
94    /// operator (RocksDB) apply it at read or compaction time; backends without
95    /// (InMemory) dispatch by table and apply inline.
96    ///
97    /// Currently used for `TRANSACTION_LOCATIONS`. Calling on a table without
98    /// a registered merge function is an error.
99    fn merge(&mut self, table: &'static str, key: &[u8], operand: &[u8]) -> Result<(), StoreError>;
100
101    /// Commits all changes made in this transaction.
102    fn commit(&mut self) -> Result<(), StoreError>;
103}
104
105/// Locked snapshot interface for batch read operations.
106/// Provides read-only access to a specific table with a persistent snapshot.
107/// This is optimized for scenarios where many reads are performed on the same
108/// table, such as trie traversal operations.
109/// This is currently only used in snapsync stage.
110// TODO: Check if we can remove this trait and use [`StorageReadView`] instead.
111pub trait StorageLockedView: Send + Sync {
112    /// Retrieves a value by key from the locked table.
113    fn get(&self, key: &[u8]) -> Result<Option<Vec<u8>>, StoreError>;
114}