Skip to main content

reth_trie_db/
trie_cursor.rs

1use alloy_primitives::B256;
2use reth_db_api::{
3    cursor::{DbCursorRO, DbCursorRW, DbDupCursorRO, DbDupCursorRW},
4    table::{DupSort, Key, Table, Value},
5    tables::{self, PackedAccountsTrie, PackedStoragesTrie},
6    transaction::DbTx,
7    DatabaseError,
8};
9use reth_trie::{
10    trie_cursor::{TrieCursor, TrieCursorFactory, TrieStorageCursor},
11    updates::StorageTrieUpdatesSorted,
12    BranchNodeCompact, Nibbles, PackedStorageTrieEntry, PackedStoredNibbles,
13    PackedStoredNibblesSubKey, StorageTrieEntry, StoredNibbles, StoredNibblesSubKey,
14};
15use std::marker::PhantomData;
16
17/// Trait abstracting nibble encoding for trie keys.
18///
19/// Allows the same cursor implementation to work with both legacy (65-byte) and
20/// packed (33-byte) nibble encodings. The underlying cursor types are monomorphized per
21/// adapter, while [`DatabaseTrieCursorFactory`] selects the encoding at runtime.
22pub trait TrieKeyAdapter: Clone + Send + Sync + 'static {
23    /// The key type for account trie lookups (e.g., `StoredNibbles` or `PackedStoredNibbles`).
24    type AccountKey: Key + From<Nibbles> + Clone;
25
26    /// The subkey type for storage trie `DupSort` lookups
27    /// (e.g., `StoredNibblesSubKey` or `PackedStoredNibblesSubKey`).
28    type StorageSubKey: Key + From<Nibbles> + Clone + PartialEq;
29
30    /// The storage trie entry type that pairs a subkey with a `BranchNodeCompact`.
31    type StorageValue: Value + StorageTrieEntryLike<SubKey = Self::StorageSubKey>;
32
33    /// Convert an account key back to `Nibbles`.
34    fn account_key_to_nibbles(key: &Self::AccountKey) -> Nibbles;
35
36    /// Convert a storage subkey back to `Nibbles`.
37    fn subkey_to_nibbles(subkey: &Self::StorageSubKey) -> Nibbles;
38}
39
40/// Trait for storage trie entry types that carry a subkey and node.
41///
42/// Needed because [`StorageTrieEntry`] and [`PackedStorageTrieEntry`] are separate structs
43/// with different field types, but `DatabaseStorageTrieCursor` must access `.nibbles()` and
44/// `.node()` generically through `A::StorageValue`.
45pub trait StorageTrieEntryLike: Sized {
46    /// The subkey type.
47    type SubKey: Clone;
48
49    /// Returns a reference to the nibbles subkey.
50    fn nibbles(&self) -> &Self::SubKey;
51
52    /// Returns a reference to the branch node.
53    fn node(&self) -> &BranchNodeCompact;
54
55    /// Decompose this value into owned parts.
56    fn into_parts(self) -> (Self::SubKey, BranchNodeCompact);
57
58    /// Construct a new entry from a subkey and node.
59    fn new(nibbles: Self::SubKey, node: BranchNodeCompact) -> Self;
60}
61
62impl StorageTrieEntryLike for StorageTrieEntry {
63    type SubKey = StoredNibblesSubKey;
64
65    fn nibbles(&self) -> &Self::SubKey {
66        &self.nibbles
67    }
68
69    fn node(&self) -> &BranchNodeCompact {
70        &self.node
71    }
72
73    fn into_parts(self) -> (Self::SubKey, BranchNodeCompact) {
74        (self.nibbles, self.node)
75    }
76
77    fn new(nibbles: Self::SubKey, node: BranchNodeCompact) -> Self {
78        Self { nibbles, node }
79    }
80}
81
82/// Legacy (v1) nibble encoding: 1 nibble per byte, 65-byte subkeys.
83#[derive(Debug, Clone)]
84pub struct LegacyKeyAdapter;
85
86impl TrieKeyAdapter for LegacyKeyAdapter {
87    type AccountKey = StoredNibbles;
88    type StorageSubKey = StoredNibblesSubKey;
89    type StorageValue = StorageTrieEntry;
90
91    fn account_key_to_nibbles(key: &Self::AccountKey) -> Nibbles {
92        key.0
93    }
94
95    fn subkey_to_nibbles(subkey: &Self::StorageSubKey) -> Nibbles {
96        subkey.0
97    }
98}
99
100impl StorageTrieEntryLike for PackedStorageTrieEntry {
101    type SubKey = PackedStoredNibblesSubKey;
102
103    fn nibbles(&self) -> &Self::SubKey {
104        &self.nibbles
105    }
106
107    fn node(&self) -> &BranchNodeCompact {
108        &self.node
109    }
110
111    fn into_parts(self) -> (Self::SubKey, BranchNodeCompact) {
112        (self.nibbles, self.node)
113    }
114
115    fn new(nibbles: Self::SubKey, node: BranchNodeCompact) -> Self {
116        Self { nibbles, node }
117    }
118}
119
120/// Packed (v2) nibble encoding: 2 nibbles per byte, 33-byte subkeys.
121#[derive(Debug, Clone)]
122pub struct PackedKeyAdapter;
123
124impl TrieKeyAdapter for PackedKeyAdapter {
125    type AccountKey = PackedStoredNibbles;
126    type StorageSubKey = PackedStoredNibblesSubKey;
127    type StorageValue = PackedStorageTrieEntry;
128
129    fn account_key_to_nibbles(key: &Self::AccountKey) -> Nibbles {
130        key.0
131    }
132
133    fn subkey_to_nibbles(subkey: &Self::StorageSubKey) -> Nibbles {
134        subkey.0
135    }
136}
137
138/// Helper trait to map a [`TrieKeyAdapter`] to the correct table types.
139///
140/// This indirection is needed because the `tables!` macro generates non-generic
141/// table types, so we use separate "view" types for packed encoding that share
142/// the same MDBX table name.
143pub trait TrieTableAdapter: TrieKeyAdapter {
144    /// The account trie table type.
145    type AccountTrieTable: Table<Key = Self::AccountKey, Value = BranchNodeCompact>;
146    /// The storage trie table type.
147    type StorageTrieTable: Table<Key = B256, Value = Self::StorageValue>
148        + DupSort<SubKey = Self::StorageSubKey>;
149}
150
151impl TrieTableAdapter for LegacyKeyAdapter {
152    type AccountTrieTable = tables::AccountsTrie;
153    type StorageTrieTable = tables::StoragesTrie;
154}
155
156impl TrieTableAdapter for PackedKeyAdapter {
157    type AccountTrieTable = PackedAccountsTrie;
158    type StorageTrieTable = PackedStoragesTrie;
159}
160
161/// Wrapper struct for database transaction implementing trie cursor factory trait.
162#[derive(Debug, Clone)]
163pub struct DatabaseTrieCursorFactory<T, A: TrieKeyAdapter> {
164    tx: T,
165    _adapter: PhantomData<A>,
166}
167
168impl<T, A: TrieKeyAdapter> DatabaseTrieCursorFactory<T, A> {
169    /// Create new [`DatabaseTrieCursorFactory`].
170    pub const fn new(tx: T) -> Self {
171        Self { tx, _adapter: PhantomData }
172    }
173}
174
175impl<TX, A> TrieCursorFactory for DatabaseTrieCursorFactory<&TX, A>
176where
177    TX: DbTx,
178    A: TrieTableAdapter,
179{
180    type AccountTrieCursor<'a>
181        = DatabaseAccountTrieCursor<<TX as DbTx>::Cursor<A::AccountTrieTable>, A>
182    where
183        Self: 'a;
184
185    type StorageTrieCursor<'a>
186        = DatabaseStorageTrieCursor<<TX as DbTx>::DupCursor<A::StorageTrieTable>, A>
187    where
188        Self: 'a;
189
190    fn account_trie_cursor(&self) -> Result<Self::AccountTrieCursor<'_>, DatabaseError> {
191        Ok(DatabaseAccountTrieCursor::new(self.tx.cursor_read::<A::AccountTrieTable>()?))
192    }
193
194    fn storage_trie_cursor(
195        &self,
196        hashed_address: B256,
197    ) -> Result<Self::StorageTrieCursor<'_>, DatabaseError> {
198        Ok(DatabaseStorageTrieCursor::new(
199            self.tx.cursor_dup_read::<A::StorageTrieTable>()?,
200            hashed_address,
201        ))
202    }
203}
204
205/// A cursor over the account trie.
206#[derive(Debug)]
207pub struct DatabaseAccountTrieCursor<C, A: TrieKeyAdapter>(C, PhantomData<A>);
208
209impl<C, A: TrieKeyAdapter> DatabaseAccountTrieCursor<C, A> {
210    /// Create a new account trie cursor.
211    pub const fn new(cursor: C) -> Self {
212        Self(cursor, PhantomData)
213    }
214}
215
216impl<C, A> TrieCursor for DatabaseAccountTrieCursor<C, A>
217where
218    A: TrieTableAdapter,
219    C: DbCursorRO<A::AccountTrieTable> + Send,
220{
221    fn seek_exact(
222        &mut self,
223        key: Nibbles,
224    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
225        Ok(self
226            .0
227            .seek_exact(A::AccountKey::from(key))?
228            .map(|value| (A::account_key_to_nibbles(&value.0), value.1)))
229    }
230
231    fn seek(
232        &mut self,
233        key: Nibbles,
234    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
235        Ok(self
236            .0
237            .seek(A::AccountKey::from(key))?
238            .map(|value| (A::account_key_to_nibbles(&value.0), value.1)))
239    }
240
241    fn next(&mut self) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
242        Ok(self.0.next()?.map(|value| (A::account_key_to_nibbles(&value.0), value.1)))
243    }
244
245    fn current(&mut self) -> Result<Option<Nibbles>, DatabaseError> {
246        Ok(self.0.current()?.map(|(k, _)| A::account_key_to_nibbles(&k)))
247    }
248
249    fn reset(&mut self) {
250        // No-op for database cursors
251    }
252}
253
254/// A cursor over the storage tries stored in the database.
255#[derive(Debug)]
256pub struct DatabaseStorageTrieCursor<C, A: TrieKeyAdapter> {
257    /// The underlying cursor.
258    pub cursor: C,
259    /// Hashed address used for cursor positioning.
260    hashed_address: B256,
261    _adapter: PhantomData<A>,
262}
263
264impl<C, A: TrieKeyAdapter> DatabaseStorageTrieCursor<C, A> {
265    /// Create a new storage trie cursor.
266    pub const fn new(cursor: C, hashed_address: B256) -> Self {
267        Self { cursor, hashed_address, _adapter: PhantomData }
268    }
269}
270
271impl<C, A> DatabaseStorageTrieCursor<C, A>
272where
273    A: TrieTableAdapter,
274    C: DbCursorRO<A::StorageTrieTable>
275        + DbCursorRW<A::StorageTrieTable>
276        + DbDupCursorRO<A::StorageTrieTable>
277        + DbDupCursorRW<A::StorageTrieTable>,
278{
279    /// Writes storage updates that are already sorted
280    pub fn write_storage_trie_updates_sorted(
281        &mut self,
282        updates: &StorageTrieUpdatesSorted,
283    ) -> Result<usize, DatabaseError> {
284        let mut num_entries = 0;
285        for (nibbles, maybe_updated) in updates.storage_nodes.iter().filter(|(n, _)| !n.is_empty())
286        {
287            num_entries += 1;
288            let nibbles = A::StorageSubKey::from(*nibbles);
289            // Delete the old entry if it exists.
290            if self
291                .cursor
292                .seek_by_key_subkey(self.hashed_address, nibbles.clone())?
293                .as_ref()
294                .is_some_and(|e| *e.nibbles() == nibbles)
295            {
296                self.cursor.delete_current()?;
297            }
298
299            // There is an updated version of this node, insert new entry.
300            if let Some(node) = maybe_updated {
301                self.cursor
302                    .upsert(self.hashed_address, &A::StorageValue::new(nibbles, node.clone()))?;
303            }
304        }
305
306        Ok(num_entries)
307    }
308}
309
310impl<C, A> TrieCursor for DatabaseStorageTrieCursor<C, A>
311where
312    A: TrieTableAdapter,
313    C: DbCursorRO<A::StorageTrieTable> + DbDupCursorRO<A::StorageTrieTable> + Send,
314{
315    fn seek_exact(
316        &mut self,
317        key: Nibbles,
318    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
319        let subkey = A::StorageSubKey::from(key);
320        Ok(self
321            .cursor
322            .seek_by_key_subkey(self.hashed_address, subkey.clone())?
323            .filter(|e| *e.nibbles() == subkey)
324            .map(|value| {
325                let (subkey, node) = value.into_parts();
326                (A::subkey_to_nibbles(&subkey), node)
327            }))
328    }
329
330    fn seek(
331        &mut self,
332        key: Nibbles,
333    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
334        Ok(self.cursor.seek_by_key_subkey(self.hashed_address, A::StorageSubKey::from(key))?.map(
335            |value| {
336                let (subkey, node) = value.into_parts();
337                (A::subkey_to_nibbles(&subkey), node)
338            },
339        ))
340    }
341
342    fn next(&mut self) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
343        Ok(self.cursor.next_dup()?.map(|(_, value)| {
344            let (subkey, node) = value.into_parts();
345            (A::subkey_to_nibbles(&subkey), node)
346        }))
347    }
348
349    fn current(&mut self) -> Result<Option<Nibbles>, DatabaseError> {
350        Ok(self.cursor.current()?.map(|(_, v)| A::subkey_to_nibbles(v.nibbles())))
351    }
352
353    fn reset(&mut self) {
354        // No-op for database cursors
355    }
356}
357
358impl<C, A> TrieStorageCursor for DatabaseStorageTrieCursor<C, A>
359where
360    A: TrieTableAdapter,
361    C: DbCursorRO<A::StorageTrieTable> + DbDupCursorRO<A::StorageTrieTable> + Send,
362{
363    fn set_hashed_address(&mut self, hashed_address: B256) {
364        self.hashed_address = hashed_address;
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371    use alloy_primitives::hex_literal::hex;
372    use reth_db_api::{cursor::DbCursorRW, transaction::DbTxMut};
373    use reth_provider::test_utils::create_test_provider_factory;
374
375    #[test]
376    fn test_account_trie_order() {
377        let factory = create_test_provider_factory();
378        let provider = factory.provider_rw().unwrap();
379        let mut cursor = provider.tx_ref().cursor_write::<tables::AccountsTrie>().unwrap();
380
381        let data = vec![
382            hex!("0303040e").to_vec(),
383            hex!("030305").to_vec(),
384            hex!("03030500").to_vec(),
385            hex!("0303050a").to_vec(),
386        ];
387
388        for key in data.clone() {
389            cursor
390                .upsert(
391                    key.into(),
392                    &BranchNodeCompact::new(
393                        0b0000_0010_0000_0001,
394                        0b0000_0010_0000_0001,
395                        0,
396                        Vec::default(),
397                        None,
398                    ),
399                )
400                .unwrap();
401        }
402
403        let db_data = cursor.walk_range(..).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
404        assert_eq!(db_data[0].0 .0.to_vec(), data[0]);
405        assert_eq!(db_data[1].0 .0.to_vec(), data[1]);
406        assert_eq!(db_data[2].0 .0.to_vec(), data[2]);
407        assert_eq!(db_data[3].0 .0.to_vec(), data[3]);
408
409        assert_eq!(
410            cursor.seek(hex!("0303040f").to_vec().into()).unwrap().map(|(k, _)| k.0.to_vec()),
411            Some(data[1].clone())
412        );
413    }
414
415    // tests that upsert and seek match on the storage trie cursor
416    #[test]
417    fn test_storage_cursor_abstraction() {
418        use reth_storage_api::StorageSettingsCache;
419        use reth_trie::trie_cursor::{TrieCursor, TrieCursorFactory};
420
421        let factory = create_test_provider_factory();
422        let provider = factory.provider_rw().unwrap();
423        let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
424
425        let hashed_address = B256::random();
426        let key = StoredNibblesSubKey::from(vec![0x2, 0x3]);
427        let value = BranchNodeCompact::new(1, 1, 1, vec![B256::random()], None);
428
429        cursor
430            .upsert(hashed_address, &StorageTrieEntry { nibbles: key.clone(), node: value.clone() })
431            .unwrap();
432
433        crate::with_adapter!(provider, |A| {
434            let trie_factory = DatabaseTrieCursorFactory::<_, A>::new(provider.tx_ref());
435            let mut cursor = trie_factory.storage_trie_cursor(hashed_address).unwrap();
436            assert_eq!(cursor.seek(key.into()).unwrap().unwrap().1, value);
437        });
438    }
439}