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}