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        // The storage trie for this account has to be deleted.
285        if updates.is_deleted() && self.cursor.seek_exact(self.hashed_address)?.is_some() {
286            self.cursor.delete_current_duplicates()?;
287        }
288
289        let mut num_entries = 0;
290        for (nibbles, maybe_updated) in updates.storage_nodes.iter().filter(|(n, _)| !n.is_empty())
291        {
292            num_entries += 1;
293            let nibbles = A::StorageSubKey::from(*nibbles);
294            // Delete the old entry if it exists.
295            if self
296                .cursor
297                .seek_by_key_subkey(self.hashed_address, nibbles.clone())?
298                .as_ref()
299                .is_some_and(|e| *e.nibbles() == nibbles)
300            {
301                self.cursor.delete_current()?;
302            }
303
304            // There is an updated version of this node, insert new entry.
305            if let Some(node) = maybe_updated {
306                self.cursor
307                    .upsert(self.hashed_address, &A::StorageValue::new(nibbles, node.clone()))?;
308            }
309        }
310
311        Ok(num_entries)
312    }
313}
314
315impl<C, A> TrieCursor for DatabaseStorageTrieCursor<C, A>
316where
317    A: TrieTableAdapter,
318    C: DbCursorRO<A::StorageTrieTable> + DbDupCursorRO<A::StorageTrieTable> + Send,
319{
320    fn seek_exact(
321        &mut self,
322        key: Nibbles,
323    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
324        let subkey = A::StorageSubKey::from(key);
325        Ok(self
326            .cursor
327            .seek_by_key_subkey(self.hashed_address, subkey.clone())?
328            .filter(|e| *e.nibbles() == subkey)
329            .map(|value| {
330                let (subkey, node) = value.into_parts();
331                (A::subkey_to_nibbles(&subkey), node)
332            }))
333    }
334
335    fn seek(
336        &mut self,
337        key: Nibbles,
338    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
339        Ok(self.cursor.seek_by_key_subkey(self.hashed_address, A::StorageSubKey::from(key))?.map(
340            |value| {
341                let (subkey, node) = value.into_parts();
342                (A::subkey_to_nibbles(&subkey), node)
343            },
344        ))
345    }
346
347    fn next(&mut self) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
348        Ok(self.cursor.next_dup()?.map(|(_, value)| {
349            let (subkey, node) = value.into_parts();
350            (A::subkey_to_nibbles(&subkey), node)
351        }))
352    }
353
354    fn current(&mut self) -> Result<Option<Nibbles>, DatabaseError> {
355        Ok(self.cursor.current()?.map(|(_, v)| A::subkey_to_nibbles(v.nibbles())))
356    }
357
358    fn reset(&mut self) {
359        // No-op for database cursors
360    }
361}
362
363impl<C, A> TrieStorageCursor for DatabaseStorageTrieCursor<C, A>
364where
365    A: TrieTableAdapter,
366    C: DbCursorRO<A::StorageTrieTable> + DbDupCursorRO<A::StorageTrieTable> + Send,
367{
368    fn set_hashed_address(&mut self, hashed_address: B256) {
369        self.hashed_address = hashed_address;
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376    use alloy_primitives::hex_literal::hex;
377    use reth_db_api::{cursor::DbCursorRW, transaction::DbTxMut};
378    use reth_provider::test_utils::create_test_provider_factory;
379
380    #[test]
381    fn test_account_trie_order() {
382        let factory = create_test_provider_factory();
383        let provider = factory.provider_rw().unwrap();
384        let mut cursor = provider.tx_ref().cursor_write::<tables::AccountsTrie>().unwrap();
385
386        let data = vec![
387            hex!("0303040e").to_vec(),
388            hex!("030305").to_vec(),
389            hex!("03030500").to_vec(),
390            hex!("0303050a").to_vec(),
391        ];
392
393        for key in data.clone() {
394            cursor
395                .upsert(
396                    key.into(),
397                    &BranchNodeCompact::new(
398                        0b0000_0010_0000_0001,
399                        0b0000_0010_0000_0001,
400                        0,
401                        Vec::default(),
402                        None,
403                    ),
404                )
405                .unwrap();
406        }
407
408        let db_data = cursor.walk_range(..).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
409        assert_eq!(db_data[0].0 .0.to_vec(), data[0]);
410        assert_eq!(db_data[1].0 .0.to_vec(), data[1]);
411        assert_eq!(db_data[2].0 .0.to_vec(), data[2]);
412        assert_eq!(db_data[3].0 .0.to_vec(), data[3]);
413
414        assert_eq!(
415            cursor.seek(hex!("0303040f").to_vec().into()).unwrap().map(|(k, _)| k.0.to_vec()),
416            Some(data[1].clone())
417        );
418    }
419
420    // tests that upsert and seek match on the storage trie cursor
421    #[test]
422    fn test_storage_cursor_abstraction() {
423        use reth_storage_api::StorageSettingsCache;
424        use reth_trie::trie_cursor::{TrieCursor, TrieCursorFactory};
425
426        let factory = create_test_provider_factory();
427        let provider = factory.provider_rw().unwrap();
428        let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
429
430        let hashed_address = B256::random();
431        let key = StoredNibblesSubKey::from(vec![0x2, 0x3]);
432        let value = BranchNodeCompact::new(1, 1, 1, vec![B256::random()], None);
433
434        cursor
435            .upsert(hashed_address, &StorageTrieEntry { nibbles: key.clone(), node: value.clone() })
436            .unwrap();
437
438        crate::with_adapter!(provider, |A| {
439            let trie_factory = DatabaseTrieCursorFactory::<_, A>::new(provider.tx_ref());
440            let mut cursor = trie_factory.storage_trie_cursor(hashed_address).unwrap();
441            assert_eq!(cursor.seek(key.into()).unwrap().unwrap().1, value);
442        });
443    }
444}