Skip to main content

signet_hot/model/
traits.rs

1use crate::{
2    db::HistoryRead,
3    model::{
4        DualKeyTraverse, DualKeyTraverseMut, DualTableCursor, HotKvError, HotKvReadError,
5        KvTraverse, KvTraverseMut, TableCursor,
6        revm::{RevmRead, RevmWrite},
7    },
8    ser::{KeySer, MAX_KEY_SIZE, ValSer},
9    tables::{DualKey, SingleKey, Table},
10};
11use std::borrow::Cow;
12
13/// Trait for hot storage. This is a KV store with read/write transactions.
14///
15/// This is the top-level trait for hot storage backends, providing
16/// transactional access through read-only and read-write transactions.
17///
18/// We recommend using [`HistoryRead`] and [`HistoryWrite`] for most use cases,
19/// as they provide higher-level abstractions over predefined tables.
20///
21/// When implementing this trait, consult the [`model`] module documentation for
22/// details on the associated types and their requirements.
23///
24/// [`HistoryRead`]: crate::db::HistoryRead
25/// [`HistoryWrite`]: crate::db::HistoryWrite
26/// [`model`]: crate::model
27#[auto_impl::auto_impl(&, Arc, Box)]
28pub trait HotKv {
29    /// The read-only transaction type.
30    type RoTx: HotKvRead;
31
32    /// The read-write transaction type.
33    type RwTx: HotKvWrite;
34
35    /// Create a read-only transaction.
36    fn reader(&self) -> Result<Self::RoTx, HotKvError>;
37
38    /// Create a read-only transaction, and wrap it in an adapter for the
39    /// revm [`DatabaseRef`] trait. The resulting reader can be used directly
40    /// with [`trevm`] and [`revm`].
41    ///
42    /// [`revm`]: trevm::revm
43    /// [`DatabaseRef`]: trevm::revm::database::DatabaseRef
44    fn revm_reader(&self) -> Result<RevmRead<Self::RoTx>, HotKvError> {
45        self.reader().map(RevmRead::new)
46    }
47
48    /// Create a read-only transaction that reads state at a specific block
49    /// height, and wrap it in an adapter for the revm [`DatabaseRef`] trait.
50    ///
51    /// The returned reader uses history and change set tables to
52    /// reconstruct state as it was at `height`. Bytecodes are
53    /// content-addressed and always read from the current table.
54    ///
55    /// # Errors
56    ///
57    /// - [`HotKvError::NoBlocks`] if the database has no blocks.
58    /// - [`HotKvError::HeightOutOfRange`] if `height` is outside the
59    ///   stored block range.
60    ///
61    /// [`DatabaseRef`]: trevm::revm::database::DatabaseRef
62    fn revm_reader_at_height(&self, height: u64) -> Result<RevmRead<Self::RoTx>, HotKvError> {
63        let reader = self.reader()?;
64        let Some((first, last)) =
65            reader.get_execution_range().map_err(|e| e.into_hot_kv_error())?
66        else {
67            return Err(HotKvError::NoBlocks);
68        };
69        if height < first || height > last {
70            return Err(HotKvError::HeightOutOfRange { height, first, last });
71        }
72        Ok(RevmRead::at_height(reader, height))
73    }
74
75    /// Create a read-write transaction.
76    ///
77    /// This is allowed to fail with [`Err(HotKvError::WriteLocked)`] if
78    /// multiple write transactions are not supported concurrently.
79    ///
80    /// # Returns
81    ///
82    /// - `Ok(Some(tx))` if the write transaction was created successfully.
83    /// - [`Err(HotKvError::WriteLocked)`] if there is already a write
84    ///   transaction in progress.
85    /// - [`Err(HotKvError::Inner)`] if there was an error creating the
86    ///   transaction.
87    ///
88    /// [`Err(HotKvError::Inner)`]: HotKvError::Inner
89    /// [`Err(HotKvError::WriteLocked)`]: HotKvError::WriteLocked
90    fn writer(&self) -> Result<Self::RwTx, HotKvError>;
91
92    /// Create a read-write transaction, and wrap it in an adapter for the
93    /// revm [`TryDatabaseCommit`] trait. The resulting writer can be used
94    /// directly with [`trevm`] and [`revm`].
95    ///
96    ///
97    /// [`revm`]: trevm::revm
98    /// [`TryDatabaseCommit`]: trevm::revm::database::TryDatabaseCommit
99    fn revm_writer(&self) -> Result<RevmWrite<Self::RwTx>, HotKvError> {
100        self.writer().map(RevmWrite::new)
101    }
102}
103
104/// Trait for hot storage read transactions.
105///
106/// This trait provides read-only access to hot storage tables. It should only
107/// be imported if accessing custom tables, or when implementing new hot storage
108/// backends.
109#[auto_impl::auto_impl(&, Arc, Box)]
110pub trait HotKvRead {
111    /// Error type for read operations.
112    type Error: HotKvReadError;
113
114    /// The cursor type for traversing key-value pairs.
115    type Traverse<'a>: KvTraverse<Self::Error> + DualKeyTraverse<Self::Error>
116    where
117        Self: 'a;
118
119    /// Get a raw cursor to traverse the database.
120    fn raw_traverse<'a>(&'a self, table: &'static str) -> Result<Self::Traverse<'a>, Self::Error>;
121
122    /// Get a raw value from a specific table.
123    ///
124    /// The `key` buf must be <= [`MAX_KEY_SIZE`] bytes. Implementations are
125    /// allowed to panic if this is not the case.
126    ///
127    /// If the table is dual-keyed, the output MAY be implementation-defined.
128    fn raw_get<'a>(
129        &'a self,
130        table: &'static str,
131        key: &[u8],
132    ) -> Result<Option<Cow<'a, [u8]>>, Self::Error>;
133
134    /// Get a raw value from a specific table with dual keys.
135    ///
136    /// If `key1` is present, but `key2` is not in the table, the output is
137    /// implementation-defined. For sorted databases, it SHOULD return the value
138    /// of the NEXT populated key. It MAY also return `None`, even if other
139    /// subkeys are populated.
140    ///
141    /// If the table is not dual-keyed, the output MAY be
142    /// implementation-defined.
143    fn raw_get_dual<'a>(
144        &'a self,
145        table: &'static str,
146        key1: &[u8],
147        key2: &[u8],
148    ) -> Result<Option<Cow<'a, [u8]>>, Self::Error>;
149
150    /// Traverse a specific table. Returns a typed cursor wrapper.
151    fn traverse<'a, T: SingleKey>(
152        &'a self,
153    ) -> Result<TableCursor<Self::Traverse<'a>, T, Self::Error>, Self::Error> {
154        let cursor = self.raw_traverse(T::NAME)?;
155        Ok(TableCursor::new(cursor))
156    }
157
158    /// Traverse a specific dual-keyed table. Returns a typed dual-keyed
159    /// cursor wrapper.
160    fn traverse_dual<'a, T: DualKey>(
161        &'a self,
162    ) -> Result<DualTableCursor<Self::Traverse<'a>, T, Self::Error>, Self::Error> {
163        let cursor = self.raw_traverse(T::NAME)?;
164        Ok(DualTableCursor::new(cursor))
165    }
166
167    /// Get a value from a specific table.
168    fn get<T: SingleKey>(&self, key: &T::Key) -> Result<Option<T::Value>, Self::Error> {
169        let mut key_buf = [0u8; MAX_KEY_SIZE];
170        let key_bytes = key.encode_key(&mut key_buf);
171        debug_assert!(
172            key_bytes.len() == T::Key::SIZE,
173            "Encoded key length does not match expected size"
174        );
175
176        let Some(value_bytes) = self.raw_get(T::NAME, key_bytes)? else {
177            return Ok(None);
178        };
179        T::Value::decode_value(&value_bytes).map(Some).map_err(Into::into)
180    }
181
182    /// Get a value from a specific dual-keyed table.
183    ///
184    /// If `key1` is present, but `key2` is not in the table, the output is
185    /// implementation-defined. For sorted databases, it SHOULD return the value
186    /// of the NEXT populated key. It MAY also return `None`, even if other
187    /// subkeys are populated.
188    ///
189    /// If the table is not dual-keyed, the output MAY be
190    /// implementation-defined.
191    fn get_dual<T: DualKey>(
192        &self,
193        key1: &T::Key,
194        key2: &T::Key2,
195    ) -> Result<Option<T::Value>, Self::Error> {
196        let mut key1_buf = [0u8; MAX_KEY_SIZE];
197        let mut key2_buf = [0u8; MAX_KEY_SIZE];
198
199        let key1_bytes = key1.encode_key(&mut key1_buf);
200        let key2_bytes = key2.encode_key(&mut key2_buf);
201
202        let Some(value_bytes) = self.raw_get_dual(T::NAME, key1_bytes, key2_bytes)? else {
203            return Ok(None);
204        };
205        T::Value::decode_value(&value_bytes).map(Some).map_err(Into::into)
206    }
207}
208
209/// Trait for hot storage write transactions.
210///
211/// This extends the [`HotKvRead`] trait with write capabilities.
212pub trait HotKvWrite: HotKvRead {
213    /// The mutable cursor type for traversing key-value pairs.
214    type TraverseMut<'a>: KvTraverseMut<Self::Error> + DualKeyTraverseMut<Self::Error>
215    where
216        Self: 'a;
217
218    /// Get a raw mutable cursor to traverse the database.
219    fn raw_traverse_mut<'a>(
220        &'a self,
221        table: &'static str,
222    ) -> Result<Self::TraverseMut<'a>, Self::Error>;
223
224    /// Queue a raw put operation.
225    ///
226    /// The `key` buf must be <= [`MAX_KEY_SIZE`] bytes. Implementations are
227    /// allowed to panic if this is not the case.
228    fn queue_raw_put(
229        &self,
230        table: &'static str,
231        key: &[u8],
232        value: &[u8],
233    ) -> Result<(), Self::Error>;
234
235    /// Queue a raw put operation for a dual-keyed table.
236    ////
237    /// The `key1` and `key2` buf must be <= [`MAX_KEY_SIZE`] bytes.
238    /// Implementations are allowed to panic if this is not the case.
239    fn queue_raw_put_dual(
240        &self,
241        table: &'static str,
242        key1: &[u8],
243        key2: &[u8],
244        value: &[u8],
245    ) -> Result<(), Self::Error>;
246
247    /// Queue a raw delete operation.
248    ///
249    /// The `key` buf must be <= [`MAX_KEY_SIZE`] bytes. Implementations are
250    /// allowed to panic if this is not the case.
251    fn queue_raw_delete(&self, table: &'static str, key: &[u8]) -> Result<(), Self::Error>;
252
253    /// Queue a raw delete operation for a dual-keyed table.
254    ///
255    /// The `key1` and `key2` buf must be <= [`MAX_KEY_SIZE`] bytes.
256    /// Implementations are allowed to panic if this is not the case.
257    fn queue_raw_delete_dual(
258        &self,
259        table: &'static str,
260        key1: &[u8],
261        key2: &[u8],
262    ) -> Result<(), Self::Error>;
263
264    /// Queue a raw clear operation for a specific table.
265    fn queue_raw_clear(&self, table: &'static str) -> Result<(), Self::Error>;
266
267    /// Queue a raw create operation for a specific table.
268    ///
269    /// Implementations MUST be idempotent: if the table already exists,
270    /// this should be a no-op.
271    ///
272    /// This abstraction supports table specializations:
273    /// 1. `dual_key_size` - whether the table is dual-keyed (i.e.,
274    ///    `DUPSORT` in LMDB/MDBX). If so, the argument MUST be the
275    ///    encoded size of the second key. If not, it MUST be `None`.
276    /// 2. `fixed_val_size`: whether the table has fixed-size values.
277    ///    If so, the argument MUST be the size of the fixed value.
278    ///    If not, it MUST be `None`.
279    ///
280    /// Database implementations can use this information for optimizations.
281    fn queue_raw_create(
282        &self,
283        table: &'static str,
284        dual_key_size: Option<usize>,
285        fixed_val_size: Option<usize>,
286    ) -> Result<(), Self::Error>;
287
288    /// Traverse a specific table. Returns a mutable typed cursor wrapper.
289    /// If invoked for a dual-keyed table, it will traverse the primary keys
290    /// only, and the return value may be implementation-defined.
291    fn traverse_mut<'a, T: SingleKey>(
292        &'a self,
293    ) -> Result<TableCursor<Self::TraverseMut<'a>, T, Self::Error>, Self::Error> {
294        let cursor = self.raw_traverse_mut(T::NAME)?;
295        Ok(TableCursor::new(cursor))
296    }
297
298    /// Traverse a specific dual-keyed table. Returns a mutable typed
299    /// dual-keyed cursor wrapper.
300    fn traverse_dual_mut<'a, T: DualKey>(
301        &'a self,
302    ) -> Result<DualTableCursor<Self::TraverseMut<'a>, T, Self::Error>, Self::Error> {
303        let cursor = self.raw_traverse_mut(T::NAME)?;
304        Ok(DualTableCursor::new(cursor))
305    }
306
307    /// Queue a put operation for a specific table.
308    fn queue_put<T: SingleKey>(&self, key: &T::Key, value: &T::Value) -> Result<(), Self::Error> {
309        let mut key_buf = [0u8; MAX_KEY_SIZE];
310        let key_bytes = key.encode_key(&mut key_buf);
311        let value_bytes = value.encoded();
312
313        self.queue_raw_put(T::NAME, key_bytes, &value_bytes)
314    }
315
316    /// Append a key-value pair. Key must be > all existing keys.
317    ///
318    /// Default implementation falls back to `queue_put`. Backends that support
319    /// optimized append (like MDBX) should override via cursor.append().
320    fn queue_append<T: SingleKey>(
321        &self,
322        key: &T::Key,
323        value: &T::Value,
324    ) -> Result<(), Self::Error> {
325        self.queue_put::<T>(key, value)
326    }
327
328    /// Queue a put operation for a specific dual-keyed table.
329    fn queue_put_dual<T: DualKey>(
330        &self,
331        key1: &T::Key,
332        key2: &T::Key2,
333        value: &T::Value,
334    ) -> Result<(), Self::Error> {
335        let mut key1_buf = [0u8; MAX_KEY_SIZE];
336        let mut key2_buf = [0u8; MAX_KEY_SIZE];
337        let key1_bytes = key1.encode_key(&mut key1_buf);
338        let key2_bytes = key2.encode_key(&mut key2_buf);
339        let value_bytes = value.encoded();
340
341        self.queue_raw_put_dual(T::NAME, key1_bytes, key2_bytes, &value_bytes)
342    }
343
344    /// Append a dual-key entry. k2 must be > all existing k2s for k1.
345    ///
346    /// Default implementation falls back to `queue_put_dual`. Backends that support
347    /// optimized append (like MDBX) should override via cursor.append_dual().
348    fn queue_append_dual<T: DualKey>(
349        &self,
350        k1: &T::Key,
351        k2: &T::Key2,
352        value: &T::Value,
353    ) -> Result<(), Self::Error> {
354        self.queue_put_dual::<T>(k1, k2, value)
355    }
356
357    /// Queue a delete operation for a specific table.
358    fn queue_delete<T: SingleKey>(&self, key: &T::Key) -> Result<(), Self::Error> {
359        let mut key_buf = [0u8; MAX_KEY_SIZE];
360        let key_bytes = key.encode_key(&mut key_buf);
361
362        self.queue_raw_delete(T::NAME, key_bytes)
363    }
364
365    /// Queue a delete operation for a specific dual-keyed table.
366    fn queue_delete_dual<T: DualKey>(
367        &self,
368        key1: &T::Key,
369        key2: &T::Key2,
370    ) -> Result<(), Self::Error> {
371        let mut key1_buf = [0u8; MAX_KEY_SIZE];
372        let mut key2_buf = [0u8; MAX_KEY_SIZE];
373        let key1_bytes = key1.encode_key(&mut key1_buf);
374        let key2_bytes = key2.encode_key(&mut key2_buf);
375
376        self.queue_raw_delete_dual(T::NAME, key1_bytes, key2_bytes)
377    }
378
379    /// Queue creation of a specific table.
380    fn queue_create<T>(&self) -> Result<(), Self::Error>
381    where
382        T: Table,
383    {
384        self.queue_raw_create(T::NAME, T::DUAL_KEY_SIZE, T::FIXED_VAL_SIZE)
385    }
386
387    /// Queue clearing all entries in a specific table.
388    fn queue_clear<T>(&self) -> Result<(), Self::Error>
389    where
390        T: Table,
391    {
392        self.queue_raw_clear(T::NAME)
393    }
394
395    /// Queue creation of all standard hot storage tables.
396    ///
397    /// This creates the 9 predefined tables used by the history and state
398    /// subsystems: [`Headers`], [`HeaderNumbers`], [`Bytecodes`],
399    /// [`PlainAccountState`], [`PlainStorageState`], [`AccountsHistory`],
400    /// [`AccountChangeSets`], [`StorageHistory`], and
401    /// [`StorageChangeSets`].
402    ///
403    /// This is expected to be a no-op if the tables already exist, as
404    /// [`queue_raw_create`] is required to be idempotent.
405    ///
406    /// This does not commit the transaction. The caller must call
407    /// [`raw_commit`] after this method to persist the tables.
408    ///
409    /// [`queue_raw_create`]: Self::queue_raw_create
410    ///
411    /// [`raw_commit`]: Self::raw_commit
412    /// [`Headers`]: crate::tables::Headers
413    /// [`HeaderNumbers`]: crate::tables::HeaderNumbers
414    /// [`Bytecodes`]: crate::tables::Bytecodes
415    /// [`PlainAccountState`]: crate::tables::PlainAccountState
416    /// [`PlainStorageState`]: crate::tables::PlainStorageState
417    /// [`AccountsHistory`]: crate::tables::AccountsHistory
418    /// [`AccountChangeSets`]: crate::tables::AccountChangeSets
419    /// [`StorageHistory`]: crate::tables::StorageHistory
420    /// [`StorageChangeSets`]: crate::tables::StorageChangeSets
421    fn queue_db_init(&self) -> Result<(), Self::Error> {
422        use crate::tables;
423        self.queue_create::<tables::Headers>()?;
424        self.queue_create::<tables::HeaderNumbers>()?;
425        self.queue_create::<tables::Bytecodes>()?;
426        self.queue_create::<tables::PlainAccountState>()?;
427        self.queue_create::<tables::PlainStorageState>()?;
428        self.queue_create::<tables::AccountsHistory>()?;
429        self.queue_create::<tables::AccountChangeSets>()?;
430        self.queue_create::<tables::StorageHistory>()?;
431        self.queue_create::<tables::StorageChangeSets>()?;
432        Ok(())
433    }
434
435    /// Clear all K2 entries for a specific K1 in a dual-keyed table.
436    fn clear_k1_for<T: DualKey>(&self, key1: &T::Key) -> Result<(), Self::Error> {
437        let mut cursor = self.traverse_dual_mut::<T>()?;
438        cursor.clear_k1(key1)
439    }
440
441    /// Commit the queued operations.
442    fn raw_commit(self) -> Result<(), Self::Error>;
443}