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. [`DatabaseTrieCursorFactory`] fixes the encoding through
21/// its adapter type parameter. Callers must choose an adapter matching the database's storage
22/// settings; [`crate::with_adapter!`] provides runtime dispatch between the adapter types.
23pub trait TrieKeyAdapter: Clone + Send + Sync + 'static {
24    /// The key type for account trie lookups (e.g., `StoredNibbles` or `PackedStoredNibbles`).
25    type AccountKey: Key + From<Nibbles>;
26
27    /// The subkey type for storage trie `DupSort` lookups
28    /// (e.g., `StoredNibblesSubKey` or `PackedStoredNibblesSubKey`).
29    type StorageSubKey: Key + From<Nibbles>;
30
31    /// The storage trie entry type that pairs a subkey with a `BranchNodeCompact`.
32    type StorageValue: Value + StorageTrieEntryLike<SubKey = Self::StorageSubKey>;
33
34    /// Convert an account key back to `Nibbles`.
35    fn account_key_to_nibbles(key: &Self::AccountKey) -> Nibbles;
36
37    /// Convert a storage subkey back to `Nibbles`.
38    fn subkey_to_nibbles(subkey: &Self::StorageSubKey) -> Nibbles;
39}
40
41/// Trait for storage trie entry types that carry a subkey and node.
42///
43/// Provides access to the encoded subkey and branch node, as well as construction and
44/// decomposition into owned parts, independently of the subkey encoding.
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/// Maps a [`TrieKeyAdapter`] to account and storage trie tables using its key and value types.
139///
140/// The selected table types must match the database's stored nibble encoding.
141pub trait TrieTableAdapter: TrieKeyAdapter {
142    /// The account trie table type.
143    type AccountTrieTable: Table<Key = Self::AccountKey, Value = BranchNodeCompact>;
144    /// The storage trie table type.
145    type StorageTrieTable: Table<Key = B256, Value = Self::StorageValue>
146        + DupSort<SubKey = Self::StorageSubKey>;
147}
148
149impl TrieTableAdapter for LegacyKeyAdapter {
150    type AccountTrieTable = tables::AccountsTrie;
151    type StorageTrieTable = tables::StoragesTrie;
152}
153
154impl TrieTableAdapter for PackedKeyAdapter {
155    type AccountTrieTable = PackedAccountsTrie;
156    type StorageTrieTable = PackedStoragesTrie;
157}
158
159/// Wrapper struct for database transaction implementing trie cursor factory trait.
160#[derive(Debug, Clone)]
161pub struct DatabaseTrieCursorFactory<T, A: TrieKeyAdapter> {
162    tx: T,
163    _adapter: PhantomData<A>,
164}
165
166impl<T, A: TrieKeyAdapter> DatabaseTrieCursorFactory<T, A> {
167    /// Create new [`DatabaseTrieCursorFactory`].
168    pub const fn new(tx: T) -> Self {
169        Self { tx, _adapter: PhantomData }
170    }
171}
172
173impl<TX, A> TrieCursorFactory for DatabaseTrieCursorFactory<&TX, A>
174where
175    TX: DbTx,
176    A: TrieTableAdapter,
177{
178    type AccountTrieCursor<'a>
179        = DatabaseAccountTrieCursor<<TX as DbTx>::Cursor<A::AccountTrieTable>, A>
180    where
181        Self: 'a;
182
183    type StorageTrieCursor<'a>
184        = DatabaseStorageTrieCursor<<TX as DbTx>::DupCursor<A::StorageTrieTable>, A>
185    where
186        Self: 'a;
187
188    fn account_trie_cursor(&self) -> Result<Self::AccountTrieCursor<'_>, DatabaseError> {
189        Ok(DatabaseAccountTrieCursor::new(self.tx.cursor_read::<A::AccountTrieTable>()?))
190    }
191
192    fn storage_trie_cursor(
193        &self,
194        hashed_address: B256,
195    ) -> Result<Self::StorageTrieCursor<'_>, DatabaseError> {
196        Ok(DatabaseStorageTrieCursor::new(
197            self.tx.cursor_dup_read::<A::StorageTrieTable>()?,
198            hashed_address,
199        ))
200    }
201}
202
203/// A cursor over the account trie.
204#[derive(Debug)]
205pub struct DatabaseAccountTrieCursor<C, A: TrieKeyAdapter>(C, PhantomData<A>);
206
207impl<C, A: TrieKeyAdapter> DatabaseAccountTrieCursor<C, A> {
208    /// Create a new account trie cursor.
209    pub const fn new(cursor: C) -> Self {
210        Self(cursor, PhantomData)
211    }
212}
213
214impl<C, A> TrieCursor for DatabaseAccountTrieCursor<C, A>
215where
216    A: TrieTableAdapter,
217    C: DbCursorRO<A::AccountTrieTable> + Send,
218{
219    fn seek_exact(
220        &mut self,
221        key: Nibbles,
222    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
223        Ok(self
224            .0
225            .seek_exact(A::AccountKey::from(key))?
226            .map(|value| (A::account_key_to_nibbles(&value.0), value.1)))
227    }
228
229    fn seek(
230        &mut self,
231        key: Nibbles,
232    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
233        Ok(self
234            .0
235            .seek(A::AccountKey::from(key))?
236            .map(|value| (A::account_key_to_nibbles(&value.0), value.1)))
237    }
238
239    fn next(&mut self) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
240        Ok(self.0.next()?.map(|value| (A::account_key_to_nibbles(&value.0), value.1)))
241    }
242
243    fn current(&mut self) -> Result<Option<Nibbles>, DatabaseError> {
244        Ok(self.0.current()?.map(|(k, _)| A::account_key_to_nibbles(&k)))
245    }
246
247    fn reset(&mut self) {
248        // No-op for database cursors
249    }
250}
251
252/// A cursor over the storage tries stored in the database.
253#[derive(Debug)]
254pub struct DatabaseStorageTrieCursor<C, A: TrieKeyAdapter> {
255    /// The underlying cursor.
256    pub cursor: C,
257    /// Hashed address used for cursor positioning.
258    hashed_address: B256,
259    _adapter: PhantomData<A>,
260}
261
262impl<C, A: TrieKeyAdapter> DatabaseStorageTrieCursor<C, A> {
263    /// Create a new storage trie cursor.
264    pub const fn new(cursor: C, hashed_address: B256) -> Self {
265        Self { cursor, hashed_address, _adapter: PhantomData }
266    }
267}
268
269impl<C, A> DatabaseStorageTrieCursor<C, A>
270where
271    A: TrieTableAdapter,
272    C: DbCursorRO<A::StorageTrieTable>
273        + DbCursorRW<A::StorageTrieTable>
274        + DbDupCursorRO<A::StorageTrieTable>
275        + DbDupCursorRW<A::StorageTrieTable>,
276{
277    /// Writes storage updates that are already sorted
278    pub fn write_storage_trie_updates_sorted(
279        &mut self,
280        updates: &StorageTrieUpdatesSorted,
281    ) -> Result<usize, DatabaseError> {
282        let mut num_entries = 0;
283        for (nibbles, maybe_updated) in updates.storage_nodes.iter().filter(|(n, _)| !n.is_empty())
284        {
285            num_entries += 1;
286            let nibbles = A::StorageSubKey::from(*nibbles);
287            // Delete the old entry if it exists.
288            if self
289                .cursor
290                .seek_by_key_subkey(self.hashed_address, nibbles.clone())?
291                .as_ref()
292                .is_some_and(|e| *e.nibbles() == nibbles)
293            {
294                self.cursor.delete_current()?;
295            }
296
297            // There is an updated version of this node, insert new entry.
298            if let Some(node) = maybe_updated {
299                self.cursor
300                    .upsert(self.hashed_address, &A::StorageValue::new(nibbles, node.clone()))?;
301            }
302        }
303
304        Ok(num_entries)
305    }
306}
307
308impl<C, A> TrieCursor for DatabaseStorageTrieCursor<C, A>
309where
310    A: TrieTableAdapter,
311    C: DbCursorRO<A::StorageTrieTable> + DbDupCursorRO<A::StorageTrieTable> + Send,
312{
313    fn seek_exact(
314        &mut self,
315        key: Nibbles,
316    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
317        let subkey = A::StorageSubKey::from(key);
318        Ok(self
319            .cursor
320            .seek_by_key_subkey(self.hashed_address, subkey.clone())?
321            .filter(|e| *e.nibbles() == subkey)
322            .map(|value| {
323                let (subkey, node) = value.into_parts();
324                (A::subkey_to_nibbles(&subkey), node)
325            }))
326    }
327
328    fn seek(
329        &mut self,
330        key: Nibbles,
331    ) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
332        Ok(self.cursor.seek_by_key_subkey(self.hashed_address, A::StorageSubKey::from(key))?.map(
333            |value| {
334                let (subkey, node) = value.into_parts();
335                (A::subkey_to_nibbles(&subkey), node)
336            },
337        ))
338    }
339
340    fn next(&mut self) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
341        Ok(self.cursor.next_dup()?.map(|(_, value)| {
342            let (subkey, node) = value.into_parts();
343            (A::subkey_to_nibbles(&subkey), node)
344        }))
345    }
346
347    fn current(&mut self) -> Result<Option<Nibbles>, DatabaseError> {
348        Ok(self.cursor.current()?.map(|(_, v)| A::subkey_to_nibbles(v.nibbles())))
349    }
350
351    fn reset(&mut self) {
352        // No-op for database cursors
353    }
354}
355
356impl<C, A> TrieStorageCursor for DatabaseStorageTrieCursor<C, A>
357where
358    A: TrieTableAdapter,
359    C: DbCursorRO<A::StorageTrieTable> + DbDupCursorRO<A::StorageTrieTable> + Send,
360{
361    fn set_hashed_address(&mut self, hashed_address: B256) {
362        self.hashed_address = hashed_address;
363    }
364}
365
366#[cfg(test)]
367mod tests {
368    use super::*;
369    use alloy_primitives::hex_literal::hex;
370    use reth_db_api::{cursor::DbCursorRW, transaction::DbTxMut};
371    use reth_provider::test_utils::create_test_provider_factory;
372
373    #[test]
374    fn test_account_trie_order() {
375        let factory = create_test_provider_factory();
376        let provider = factory.provider_rw().unwrap();
377        let mut cursor = provider.tx_ref().cursor_write::<tables::AccountsTrie>().unwrap();
378
379        let data = vec![
380            hex!("0303040e").to_vec(),
381            hex!("030305").to_vec(),
382            hex!("03030500").to_vec(),
383            hex!("0303050a").to_vec(),
384        ];
385
386        for key in data.clone() {
387            cursor
388                .upsert(
389                    key.into(),
390                    &BranchNodeCompact::new(
391                        0b0000_0010_0000_0001,
392                        0b0000_0010_0000_0001,
393                        0,
394                        Vec::default(),
395                        None,
396                    ),
397                )
398                .unwrap();
399        }
400
401        let db_data = cursor.walk_range(..).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
402        assert_eq!(db_data[0].0 .0.to_vec(), data[0]);
403        assert_eq!(db_data[1].0 .0.to_vec(), data[1]);
404        assert_eq!(db_data[2].0 .0.to_vec(), data[2]);
405        assert_eq!(db_data[3].0 .0.to_vec(), data[3]);
406
407        assert_eq!(
408            cursor.seek(hex!("0303040f").to_vec().into()).unwrap().map(|(k, _)| k.0.to_vec()),
409            Some(data[1].clone())
410        );
411    }
412
413    // tests that upsert and seek match on the storage trie cursor
414    #[test]
415    fn test_storage_cursor_abstraction() {
416        use reth_storage_api::StorageSettingsCache;
417        use reth_trie::trie_cursor::{TrieCursor, TrieCursorFactory};
418
419        let factory = create_test_provider_factory();
420        let provider = factory.provider_rw().unwrap();
421        let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
422
423        let hashed_address = B256::random();
424        let key = StoredNibblesSubKey::from(vec![0x2, 0x3]);
425        let value = BranchNodeCompact::new(1, 1, 1, vec![B256::random()], None);
426
427        cursor
428            .upsert(hashed_address, &StorageTrieEntry { nibbles: key.clone(), node: value.clone() })
429            .unwrap();
430
431        crate::with_adapter!(provider, |A| {
432            let trie_factory = DatabaseTrieCursorFactory::<_, A>::new(provider.tx_ref());
433            let mut cursor = trie_factory.storage_trie_cursor(hashed_address).unwrap();
434            assert_eq!(cursor.seek(key.into()).unwrap().unwrap().1, value);
435        });
436    }
437}