Skip to main content

signet_hot/db/
read.rs

1use crate::{db::HistoryError, model::HotKvRead, tables};
2use alloy::primitives::{Address, B256, U256};
3use signet_storage_types::{Account, BlockNumberList, SealedHeader, ShardedKey};
4use trevm::revm::bytecode::Bytecode;
5
6/// Trait for database read operations on standard hot tables.
7///
8/// This is a high-level trait that provides convenient methods for reading
9/// common data types from predefined hot storage tables. It builds upon the
10/// lower-level [`HotKvRead`] trait, which provides raw key-value access.
11///
12/// Users should prefer this trait unless customizations are needed to the
13/// table set.
14pub trait HotDbRead: HotKvRead + super::sealed::Sealed {
15    /// Read a block header by its number.
16    fn get_header(&self, number: u64) -> Result<Option<SealedHeader>, Self::Error> {
17        self.get::<tables::Headers>(&number)
18    }
19
20    /// Read a block number by its hash.
21    fn get_header_number(&self, hash: &B256) -> Result<Option<u64>, Self::Error> {
22        self.get::<tables::HeaderNumbers>(hash)
23    }
24
25    /// Read contract Bytecode by its hash.
26    fn get_bytecode(&self, code_hash: &B256) -> Result<Option<Bytecode>, Self::Error> {
27        self.get::<tables::Bytecodes>(code_hash)
28    }
29
30    /// Read an account by its address.
31    fn get_account(&self, address: &Address) -> Result<Option<Account>, Self::Error> {
32        self.get::<tables::PlainAccountState>(address)
33    }
34
35    /// Read a storage slot by its address and key.
36    fn get_storage(&self, address: &Address, key: &U256) -> Result<Option<U256>, Self::Error> {
37        self.get_dual::<tables::PlainStorageState>(address, key)
38    }
39
40    /// Read a block header by its hash.
41    fn header_by_hash(&self, hash: &B256) -> Result<Option<SealedHeader>, Self::Error> {
42        let Some(number) = self.get_header_number(hash)? else {
43            return Ok(None);
44        };
45        self.get_header(number)
46    }
47}
48
49impl<T> HotDbRead for T where T: HotKvRead {}
50
51/// Trait for history read operations.
52///
53/// These tables maintain historical information about accounts and storage
54/// changes, and their contents can be used to reconstruct past states or
55/// roll back changes.
56///
57/// This is a high-level trait that provides convenient methods for reading
58/// common data types from predefined hot storage history tables. It builds
59/// upon the lower-level [`HotDbRead`] trait, which provides raw key-value
60/// access.
61///
62/// Users should prefer this trait unless customizations are needed to the
63/// table set.
64pub trait HistoryRead: HotDbRead {
65    /// Get the list of block numbers where an account was touched.
66    /// Get the list of block numbers where an account was touched.
67    fn get_account_history(
68        &self,
69        address: &Address,
70        latest_height: u64,
71    ) -> Result<Option<BlockNumberList>, Self::Error> {
72        self.get_dual::<tables::AccountsHistory>(address, &latest_height)
73    }
74
75    /// Get the last (highest) account history entry for an address.
76    fn last_account_history(
77        &self,
78        address: Address,
79    ) -> Result<Option<(u64, BlockNumberList)>, Self::Error> {
80        let mut cursor = self.traverse_dual::<tables::AccountsHistory>()?;
81
82        // Move the cursor to the last entry for the given address
83        let Some(res) = cursor.last_of_k1(&address)? else {
84            return Ok(None);
85        };
86
87        Ok(Some((res.1, res.2)))
88    }
89
90    /// Get the account change (pre-state) for an account at a specific block.
91    ///
92    /// If the return value is `None`, the account was not changed in that
93    /// block.
94    fn get_account_change(
95        &self,
96        block_number: u64,
97        address: &Address,
98    ) -> Result<Option<Account>, Self::Error> {
99        self.get_dual::<tables::AccountChangeSets>(&block_number, address)
100    }
101
102    /// Get the storage history for an account and storage slot. The returned
103    /// list will contain block numbers where the storage slot was changed.
104    fn get_storage_history(
105        &self,
106        address: &Address,
107        slot: U256,
108        highest_block_number: u64,
109    ) -> Result<Option<BlockNumberList>, Self::Error> {
110        let sharded_key = ShardedKey::new(slot, highest_block_number);
111        self.get_dual::<tables::StorageHistory>(address, &sharded_key)
112    }
113
114    /// Get the last (highest) storage history entry for an address and slot.
115    fn last_storage_history(
116        &self,
117        address: &Address,
118        slot: &U256,
119    ) -> Result<Option<(ShardedKey<U256>, BlockNumberList)>, Self::Error> {
120        let mut cursor = self.traverse_dual::<tables::StorageHistory>()?;
121
122        // Seek to the highest possible key for this (address, slot) combination.
123        // ShardedKey encodes as slot || highest_block_number, so seeking to
124        // (address, ShardedKey::new(slot, u64::MAX)) positions us at or after
125        // the last shard for this slot.
126        let target = ShardedKey::new(*slot, u64::MAX);
127        let result = cursor.next_dual_above(address, &target)?;
128
129        // Check if we found an exact match for this address and slot
130        if let Some((addr, sharded_key, list)) = result
131            && addr == *address
132            && sharded_key.key == *slot
133        {
134            return Ok(Some((sharded_key, list)));
135        }
136
137        // The cursor is positioned at or after our target. Go backwards to find
138        // the last entry for this (address, slot).
139        let Some((addr, sharded_key, list)) = cursor.previous_k2()? else {
140            return Ok(None);
141        };
142
143        if addr == *address && sharded_key.key == *slot {
144            Ok(Some((sharded_key, list)))
145        } else {
146            Ok(None)
147        }
148    }
149
150    /// Get the storage change (before state) for a specific storage slot at a
151    /// specific block.
152    ///
153    /// If the return value is `None`, the storage slot was not changed in that
154    /// block. If the return value is `Some(value)`, the value is the pre-state
155    /// of the storage slot before the change in that block. If the value is
156    /// `U256::ZERO`, that indicates that the storage slot was not set before
157    /// the change.
158    fn get_storage_change(
159        &self,
160        block_number: u64,
161        address: &Address,
162        slot: &U256,
163    ) -> Result<Option<U256>, Self::Error> {
164        let block_number_address = (block_number, *address);
165        self.get_dual::<tables::StorageChangeSets>(&block_number_address, slot)
166    }
167
168    /// Get the last (highest) header in the database.
169    /// Returns None if the database is empty.
170    fn last_header(&self) -> Result<Option<SealedHeader>, Self::Error> {
171        let mut cursor = self.traverse::<tables::Headers>()?;
172        Ok(cursor.last()?.map(|(_, header)| header))
173    }
174
175    /// Get the last (highest) block number in the database.
176    /// Returns None if the database is empty.
177    fn last_block_number(&self) -> Result<Option<u64>, Self::Error> {
178        let mut cursor = self.traverse::<tables::Headers>()?;
179        Ok(cursor.last()?.map(|(number, _)| number))
180    }
181
182    /// Get the first (lowest) header in the database.
183    /// Returns None if the database is empty.
184    fn first_header(&self) -> Result<Option<SealedHeader>, Self::Error> {
185        let mut cursor = self.traverse::<tables::Headers>()?;
186        Ok(cursor.first()?.map(|(_, header)| header))
187    }
188
189    /// Get the current chain tip (highest block number and hash).
190    /// Returns None if the database is empty.
191    fn get_chain_tip(&self) -> Result<Option<(u64, B256)>, Self::Error> {
192        let mut cursor = self.traverse::<tables::Headers>()?;
193        let Some((number, header)) = cursor.last()? else {
194            return Ok(None);
195        };
196        let hash = header.hash();
197        Ok(Some((number, hash)))
198    }
199
200    /// Get the execution range (first and last block numbers with headers).
201    /// Returns None if the database is empty.
202    fn get_execution_range(&self) -> Result<Option<(u64, u64)>, Self::Error> {
203        let mut cursor = self.traverse::<tables::Headers>()?;
204        let Some((first, _)) = cursor.first()? else {
205            return Ok(None);
206        };
207        let Some((last, _)) = cursor.last()? else {
208            return Ok(None);
209        };
210        Ok(Some((first, last)))
211    }
212
213    /// Get account state, optionally at a specific historical block height.
214    ///
215    /// When `height` is `Some`, reconstructs the account state as it was at
216    /// that block height by consulting history and change set tables. When
217    /// `None`, returns the current value from `PlainAccountState`.
218    ///
219    /// If no changes exist after the given height, the current value is
220    /// returned (the account has not been modified since that height).
221    ///
222    /// # Note
223    ///
224    /// This method does **not** validate `height` against the stored block
225    /// range. Heights past the chain tip silently return current state, and
226    /// heights before the first block return the pre-state of the earliest
227    /// change. Use [`Self::get_account_at_height_checked`] or
228    /// [`HotKv::revm_reader_at_height`] for validated access.
229    ///
230    /// [`HotKv::revm_reader_at_height`]: crate::model::HotKv::revm_reader_at_height
231    fn get_account_at_height(
232        &self,
233        address: &Address,
234        height: Option<u64>,
235    ) -> Result<Option<Account>, Self::Error> {
236        let Some(height) = height else {
237            return self.get_account(address);
238        };
239
240        let mut cursor = self.traverse_dual::<tables::AccountsHistory>()?;
241
242        // Seek to the first shard with key2 >= height + 1
243        let result = cursor.next_dual_above(address, &(height + 1))?;
244
245        // Verify address matches; seek could overshoot to the next address
246        let Some((_, _, list)) = result.filter(|(addr, _, _)| *addr == *address) else {
247            // No history after height — account unchanged, use current value
248            return self.get_account(address);
249        };
250
251        // rank(height) = count of values <= height; select(rank) = first value > height
252        let rank = list.rank(height);
253        let Some(first_change) = list.select(rank) else {
254            // Defensive: shard key2 > height and is in the list, so this
255            // should not happen. Fall back to current value.
256            return self.get_account(address);
257        };
258
259        self.get_account_change(first_change, address)
260    }
261
262    /// Get storage slot value, optionally at a specific historical block
263    /// height.
264    ///
265    /// When `height` is `Some`, reconstructs the storage value as it was at
266    /// that block height by consulting history and change set tables. When
267    /// `None`, returns the current value from `PlainStorageState`.
268    ///
269    /// If no changes exist after the given height, the current value is
270    /// returned (the slot has not been modified since that height).
271    ///
272    /// # Note
273    ///
274    /// This method does **not** validate `height` against the stored block
275    /// range. Heights past the chain tip silently return current state, and
276    /// heights before the first block return the pre-state of the earliest
277    /// change. Use [`Self::get_storage_at_height_checked`] or
278    /// [`HotKv::revm_reader_at_height`] for validated access.
279    ///
280    /// [`HotKv::revm_reader_at_height`]: crate::model::HotKv::revm_reader_at_height
281    fn get_storage_at_height(
282        &self,
283        address: &Address,
284        slot: &U256,
285        height: Option<u64>,
286    ) -> Result<Option<U256>, Self::Error> {
287        let Some(height) = height else {
288            return self.get_storage(address, slot);
289        };
290
291        let mut cursor = self.traverse_dual::<tables::StorageHistory>()?;
292
293        // Seek to first shard with (address, ShardedKey { slot, block >= height+1 })
294        let target = ShardedKey::new(*slot, height + 1);
295        let result = cursor.next_dual_above(address, &target)?;
296
297        // Verify address AND slot match; seek could land on a different slot
298        let Some((_, _, list)) =
299            result.filter(|(addr, sk, _)| *addr == *address && sk.key == *slot)
300        else {
301            // No history after height — slot unchanged, use current value
302            return self.get_storage(address, slot);
303        };
304
305        let rank = list.rank(height);
306        let Some(first_change) = list.select(rank) else {
307            return self.get_storage(address, slot);
308        };
309
310        self.get_storage_change(first_change, address, slot)
311    }
312
313    /// Validate that `height` is within the stored block range.
314    ///
315    /// Returns `Ok(())` if `height` is `None` (current state) or within the
316    /// range of stored blocks. Returns an error if the database has no
317    /// blocks or if the height is out of range.
318    fn check_height(&self, height: Option<u64>) -> Result<(), HistoryError<Self::Error>> {
319        let Some(height) = height else { return Ok(()) };
320        let Some((first, last)) = self.get_execution_range().map_err(HistoryError::Db)? else {
321            return Err(HistoryError::NoBlocks);
322        };
323        if height < first || height > last {
324            return Err(HistoryError::HeightOutOfRange { height, first, last });
325        }
326        Ok(())
327    }
328
329    /// Get account state at a height, with range validation.
330    ///
331    /// Validates that `height` is within the stored block range before
332    /// delegating to [`Self::get_account_at_height`].
333    fn get_account_at_height_checked(
334        &self,
335        address: &Address,
336        height: Option<u64>,
337    ) -> Result<Option<Account>, HistoryError<Self::Error>> {
338        self.check_height(height)?;
339        self.get_account_at_height(address, height).map_err(HistoryError::Db)
340    }
341
342    /// Get storage slot value at a height, with range validation.
343    ///
344    /// Validates that `height` is within the stored block range before
345    /// delegating to [`Self::get_storage_at_height`].
346    fn get_storage_at_height_checked(
347        &self,
348        address: &Address,
349        slot: &U256,
350        height: Option<u64>,
351    ) -> Result<Option<U256>, HistoryError<Self::Error>> {
352        self.check_height(height)?;
353        self.get_storage_at_height(address, slot, height).map_err(HistoryError::Db)
354    }
355
356    /// Check if a specific block number exists in history.
357    fn has_block(&self, number: u64) -> Result<bool, Self::Error> {
358        self.get_header(number).map(|opt| opt.is_some())
359    }
360
361    /// Get headers in a range (inclusive).
362    fn get_headers_range(&self, start: u64, end: u64) -> Result<Vec<SealedHeader>, Self::Error> {
363        self.traverse::<tables::Headers>()?
364            .iter_from(&start)?
365            .take_while(|r| r.as_ref().is_ok_and(|(num, _)| *num <= end))
366            .map(|r| r.map(|(_, header)| header))
367            .collect()
368    }
369}
370
371impl<T> HistoryRead for T where T: HotDbRead {}