Compare commits

...

4 Commits

Author SHA1 Message Date
yongkangc
bcb90854c2 fix(trie): remove needless borrow in test helper 2026-01-06 11:13:47 +00:00
yongkangc
6a100d6175 fix(trie): fix let-chain syntax and clippy warnings
- Replace Rust 2024 let-chains with nested if statements
- Add #[allow(clippy::collapsible_if)] to suppress false positive
2026-01-06 04:51:02 +00:00
yongkangc
80082c330d fix(trie): reset last_key in write_storage_trie_updates_sorted and add tests
- Reset last_key when writing storage updates to prevent stale cursor position
- Add comprehensive tests for cursor locality optimization covering:
  - Forward sequential seeks
  - Backward seek fallback
  - Cursor exhaustion
  - Address switching
  - Reset behavior

Amp-Thread-ID: https://ampcode.com/threads/T-019b9193-8ae8-753e-abbd-21b1a58f2c6c
2026-01-06 04:46:11 +00:00
yongkangc
4a2b60aeca perf(trie): add cursor locality optimization for storage cursors
Implement locality optimization for DatabaseStorageTrieCursor and
DatabaseHashedStorageCursor that tracks the last key returned by the
cursor. When seeking forward from the current position, the cursor now
uses next_dup/next_dup_val (O(1)) to walk forward instead of performing
expensive seek_by_key_subkey operations (O(log N)).

This optimization targets the hot path identified in profiling data where
StorageTrie showed 100% seeks (729.5K ops) and HashedStorages showed 76%
seeks (2.5M ops) during state root calculation.

The optimization:
- Tracks last_key in both cursor types
- When seek target > last_key, walks forward using next_dup
- When seek target == last_key, returns cached current position
- Falls back to seek_by_key_subkey for backward seeks or unpositioned cursor
- Resets last_key when switching storage address
2026-01-06 02:17:52 +00:00
2 changed files with 656 additions and 12 deletions

View File

@@ -78,18 +78,25 @@ where
/// The structure wrapping a database cursor for hashed storage and
/// a target hashed address. Implements [`HashedCursor`] and [`HashedStorageCursor`]
/// for iterating over hashed storage.
///
/// This cursor implements locality optimization: when seeking forward from the current position,
/// it uses `next_dup_val` (O(1)) to walk forward instead of `seek_by_key_subkey` (O(log N)) when
/// the target key is close to the current position.
#[derive(Debug)]
pub struct DatabaseHashedStorageCursor<C> {
/// Database hashed storage cursor.
cursor: C,
/// Target hashed address of the account that the storage belongs to.
hashed_address: B256,
/// The last key returned by the cursor, used for locality optimization.
/// When seeking forward from this position, we can use `next_dup_val` instead of re-seeking.
last_key: Option<B256>,
}
impl<C> DatabaseHashedStorageCursor<C> {
/// Create new [`DatabaseHashedStorageCursor`].
pub const fn new(cursor: C, hashed_address: B256) -> Self {
Self { cursor, hashed_address }
Self { cursor, hashed_address, last_key: None }
}
}
@@ -99,16 +106,52 @@ where
{
type Value = U256;
/// Seeks the given key in the hashed storage.
///
/// This method implements a locality optimization: if the cursor is already positioned
/// and the target key is >= the current position, we use `next_dup_val` to walk forward
/// instead of performing an expensive `seek_by_key_subkey` operation.
#[allow(clippy::collapsible_if)]
fn seek(&mut self, subkey: B256) -> Result<Option<(B256, Self::Value)>, DatabaseError> {
Ok(self.cursor.seek_by_key_subkey(self.hashed_address, subkey)?.map(|e| (e.key, e.value)))
// Locality optimization: if cursor is positioned and target is ahead,
// walk forward using next_dup_val instead of seeking
if let Some(last) = self.last_key {
if subkey > last {
// Walk forward using next_dup_val until we find a key >= target
while let Some(entry) = self.cursor.next_dup_val()? {
if entry.key >= subkey {
self.last_key = Some(entry.key);
return Ok(Some((entry.key, entry.value)));
}
}
// Exhausted the duplicates, no match found
self.last_key = None;
return Ok(None);
} else if subkey == last {
// Re-seeking the same key, return current position if still valid
if let Some((_, entry)) = self.cursor.current()? {
if entry.key == subkey {
return Ok(Some((entry.key, entry.value)));
}
}
}
}
// Fall back to seek_by_key_subkey for backward seeks or when cursor is not positioned
let result =
self.cursor.seek_by_key_subkey(self.hashed_address, subkey)?.map(|e| (e.key, e.value));
self.last_key = result.as_ref().map(|(k, _)| *k);
Ok(result)
}
fn next(&mut self) -> Result<Option<(B256, Self::Value)>, DatabaseError> {
Ok(self.cursor.next_dup_val()?.map(|e| (e.key, e.value)))
let result = self.cursor.next_dup_val()?.map(|e| (e.key, e.value));
self.last_key = result.as_ref().map(|(k, _)| *k);
Ok(result)
}
fn reset(&mut self) {
// Database cursors are stateless, no reset needed
self.last_key = None;
}
}
@@ -122,5 +165,256 @@ where
fn set_hashed_address(&mut self, hashed_address: B256) {
self.hashed_address = hashed_address;
// Reset cursor position tracking when switching to a different storage
self.last_key = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use reth_db_api::{cursor::DbCursorRW, transaction::DbTxMut};
use reth_primitives_traits::StorageEntry;
use reth_provider::test_utils::create_test_provider_factory;
fn create_test_keys(count: usize) -> Vec<B256> {
(0..count as u64)
.map(|i| {
let mut bytes = [0u8; 32];
bytes[24..32].copy_from_slice(&i.to_be_bytes());
B256::from(bytes)
})
.collect()
}
#[test]
fn test_forward_sequential_seek_uses_optimization() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_keys(10);
// Insert test data
{
let mut cursor =
provider.tx_ref().cursor_dup_write::<tables::HashedStorages>().unwrap();
for (i, key) in keys.iter().enumerate() {
cursor
.upsert(
hashed_address,
&StorageEntry { key: *key, value: U256::from(i as u64) },
)
.unwrap();
}
}
let mut cursor = DatabaseHashedStorageCursor::new(
provider.tx_ref().cursor_dup_read::<tables::HashedStorages>().unwrap(),
hashed_address,
);
// Forward sequential seeks should all succeed
for (i, key) in keys.iter().enumerate() {
let result = cursor.seek(*key).unwrap();
assert!(result.is_some(), "Should find key at index {i}");
let (found_key, value) = result.unwrap();
assert_eq!(found_key, *key);
assert_eq!(value, U256::from(i as u64));
}
}
#[test]
fn test_backward_seek_falls_back_to_seek_by_key_subkey() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_keys(10);
// Insert test data
{
let mut cursor =
provider.tx_ref().cursor_dup_write::<tables::HashedStorages>().unwrap();
for (i, key) in keys.iter().enumerate() {
cursor
.upsert(
hashed_address,
&StorageEntry { key: *key, value: U256::from(i as u64) },
)
.unwrap();
}
}
let mut cursor = DatabaseHashedStorageCursor::new(
provider.tx_ref().cursor_dup_read::<tables::HashedStorages>().unwrap(),
hashed_address,
);
// Seek to last key first
let result = cursor.seek(keys[9]).unwrap();
assert!(result.is_some());
// Backward seek should still work (falls back to seek_by_key_subkey)
let result = cursor.seek(keys[2]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().0, keys[2]);
}
#[test]
fn test_cursor_exhaustion_then_new_seek() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_keys(5);
// Insert test data
{
let mut cursor =
provider.tx_ref().cursor_dup_write::<tables::HashedStorages>().unwrap();
for (i, key) in keys.iter().enumerate() {
cursor
.upsert(
hashed_address,
&StorageEntry { key: *key, value: U256::from(i as u64) },
)
.unwrap();
}
}
let mut cursor = DatabaseHashedStorageCursor::new(
provider.tx_ref().cursor_dup_read::<tables::HashedStorages>().unwrap(),
hashed_address,
);
// Exhaust cursor by seeking past all keys
let high_key = {
let mut bytes = [0xffu8; 32];
bytes[0] = 0xff;
B256::from(bytes)
};
// First seek to position the cursor
let _ = cursor.seek(keys[0]).unwrap();
// Then seek past all entries
let result = cursor.seek(high_key).unwrap();
assert!(result.is_none(), "Should not find key past all entries");
// Now seek back to an existing key - should work via fallback
let result = cursor.seek(keys[0]).unwrap();
assert!(result.is_some(), "Should find first key after exhaustion");
assert_eq!(result.unwrap().0, keys[0]);
}
#[test]
fn test_address_switch_resets_position() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let address1 = B256::random();
let address2 = B256::random();
let keys = create_test_keys(5);
// Insert test data for both addresses
{
let mut cursor =
provider.tx_ref().cursor_dup_write::<tables::HashedStorages>().unwrap();
for (i, key) in keys.iter().enumerate() {
cursor
.upsert(address1, &StorageEntry { key: *key, value: U256::from(i as u64) })
.unwrap();
cursor
.upsert(
address2,
&StorageEntry { key: *key, value: U256::from((i + 100) as u64) },
)
.unwrap();
}
}
let mut cursor = DatabaseHashedStorageCursor::new(
provider.tx_ref().cursor_dup_read::<tables::HashedStorages>().unwrap(),
address1,
);
// Seek in first address
let result = cursor.seek(keys[2]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().1, U256::from(2));
// Switch address and seek
cursor.set_hashed_address(address2);
let result = cursor.seek(keys[2]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().1, U256::from(102));
}
#[test]
fn test_seek_same_key_returns_current() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_keys(5);
// Insert test data
{
let mut cursor =
provider.tx_ref().cursor_dup_write::<tables::HashedStorages>().unwrap();
for (i, key) in keys.iter().enumerate() {
cursor
.upsert(
hashed_address,
&StorageEntry { key: *key, value: U256::from(i as u64) },
)
.unwrap();
}
}
let mut cursor = DatabaseHashedStorageCursor::new(
provider.tx_ref().cursor_dup_read::<tables::HashedStorages>().unwrap(),
hashed_address,
);
// Seek to a key
let result1 = cursor.seek(keys[2]).unwrap();
assert!(result1.is_some());
// Seek to the same key again - should use cached current position
let result2 = cursor.seek(keys[2]).unwrap();
assert!(result2.is_some());
assert_eq!(result1.unwrap(), result2.unwrap());
}
#[test]
fn test_reset_clears_last_key() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_keys(5);
// Insert test data
{
let mut cursor =
provider.tx_ref().cursor_dup_write::<tables::HashedStorages>().unwrap();
for (i, key) in keys.iter().enumerate() {
cursor
.upsert(
hashed_address,
&StorageEntry { key: *key, value: U256::from(i as u64) },
)
.unwrap();
}
}
let mut cursor = DatabaseHashedStorageCursor::new(
provider.tx_ref().cursor_dup_read::<tables::HashedStorages>().unwrap(),
hashed_address,
);
// Seek to position cursor
let _ = cursor.seek(keys[2]).unwrap();
// Reset and verify we can still seek
cursor.reset();
let result = cursor.seek(keys[0]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().0, keys[0]);
}
}

View File

@@ -98,18 +98,25 @@ where
}
/// A cursor over the storage tries stored in the database.
///
/// This cursor implements locality optimization: when seeking forward from the current position,
/// it uses `next_dup` (O(1)) to walk forward instead of `seek_by_key_subkey` (O(log N)) when
/// the target key is close to the current position.
#[derive(Debug)]
pub struct DatabaseStorageTrieCursor<C> {
/// The underlying cursor.
pub cursor: C,
/// Hashed address used for cursor positioning.
hashed_address: B256,
/// The last key returned by the cursor, used for locality optimization.
/// When seeking forward from this position, we can use `next_dup` instead of re-seeking.
last_key: Option<Nibbles>,
}
impl<C> DatabaseStorageTrieCursor<C> {
/// Create a new storage trie cursor.
pub const fn new(cursor: C, hashed_address: B256) -> Self {
Self { cursor, hashed_address }
Self { cursor, hashed_address, last_key: None }
}
}
@@ -125,6 +132,9 @@ where
&mut self,
updates: &StorageTrieUpdatesSorted,
) -> Result<usize, DatabaseError> {
// Invalidate cursor position tracking: writes move the cursor arbitrarily
self.last_key = None;
// The storage trie for this account has to be deleted.
if updates.is_deleted() && self.cursor.seek_exact(self.hashed_address)?.is_some() {
self.cursor.delete_current_duplicates()?;
@@ -167,27 +177,65 @@ where
&mut self,
key: Nibbles,
) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
Ok(self
let result = self
.cursor
.seek_by_key_subkey(self.hashed_address, StoredNibblesSubKey(key))?
.filter(|e| e.nibbles == StoredNibblesSubKey(key))
.map(|value| (value.nibbles.0, value.node)))
.map(|value| (value.nibbles.0, value.node));
self.last_key = result.as_ref().map(|(k, _)| *k);
Ok(result)
}
/// Seeks the given key in the storage trie.
///
/// This method implements a locality optimization: if the cursor is already positioned
/// and the target key is >= the current position, we use `next_dup` to walk forward
/// instead of performing an expensive `seek_by_key_subkey` operation.
#[allow(clippy::collapsible_if)]
fn seek(
&mut self,
key: Nibbles,
) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
Ok(self
// Locality optimization: if cursor is positioned and target is ahead,
// walk forward using next_dup instead of seeking
if let Some(last) = self.last_key {
if key > last {
// Walk forward using next_dup until we find a key >= target
while let Some((_, entry)) = self.cursor.next_dup()? {
if entry.nibbles.0 >= key {
self.last_key = Some(entry.nibbles.0);
return Ok(Some((entry.nibbles.0, entry.node)));
}
}
// Exhausted the duplicates, no match found
self.last_key = None;
return Ok(None);
} else if key == last {
// Re-seeking the same key, return current position if still valid
if let Some((_, entry)) = self.cursor.current()? {
if entry.nibbles.0 == key {
return Ok(Some((entry.nibbles.0, entry.node)));
}
}
}
}
// Fall back to seek_by_key_subkey for backward seeks or when cursor is not positioned
let result = self
.cursor
.seek_by_key_subkey(self.hashed_address, StoredNibblesSubKey(key))?
.map(|value| (value.nibbles.0, value.node)))
.map(|value| (value.nibbles.0, value.node));
self.last_key = result.as_ref().map(|(k, _)| *k);
Ok(result)
}
/// Move the cursor to the next entry and return it.
fn next(&mut self) -> Result<Option<(Nibbles, BranchNodeCompact)>, DatabaseError> {
Ok(self.cursor.next_dup()?.map(|(_, v)| (v.nibbles.0, v.node)))
let result = self.cursor.next_dup()?.map(|(_, v)| (v.nibbles.0, v.node));
self.last_key = result.as_ref().map(|(k, _)| *k);
Ok(result)
}
/// Retrieves the current value in the storage trie cursor.
@@ -196,7 +244,7 @@ where
}
fn reset(&mut self) {
// No-op for database cursors
self.last_key = None;
}
}
@@ -206,6 +254,8 @@ where
{
fn set_hashed_address(&mut self, hashed_address: B256) {
self.hashed_address = hashed_address;
// Reset cursor position tracking when switching to a different storage trie
self.last_key = None;
}
}
@@ -215,6 +265,21 @@ mod tests {
use alloy_primitives::hex_literal::hex;
use reth_db_api::{cursor::DbCursorRW, transaction::DbTxMut};
use reth_provider::test_utils::create_test_provider_factory;
use reth_trie::trie_cursor::TrieStorageCursor;
fn create_test_nibbles(count: usize) -> Vec<Nibbles> {
(0..count as u64)
.map(|i| {
let mut bytes = [0u8; 8];
bytes.copy_from_slice(&i.to_be_bytes());
Nibbles::unpack(bytes)
})
.collect()
}
fn create_test_node() -> BranchNodeCompact {
BranchNodeCompact::new(0b1111, 0b0011, 0, vec![], None)
}
#[test]
fn test_account_trie_order() {
@@ -256,7 +321,6 @@ mod tests {
);
}
// tests that upsert and seek match on the storage trie cursor
#[test]
fn test_storage_cursor_abstraction() {
let factory = create_test_provider_factory();
@@ -274,4 +338,290 @@ mod tests {
let mut cursor = DatabaseStorageTrieCursor::new(cursor, hashed_address);
assert_eq!(cursor.seek(key.into()).unwrap().unwrap().1, value);
}
#[test]
fn test_storage_trie_forward_sequential_seek() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_nibbles(10);
// Insert test data
{
let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
for key in &keys {
cursor
.upsert(
hashed_address,
&StorageTrieEntry {
nibbles: StoredNibblesSubKey(*key),
node: create_test_node(),
},
)
.unwrap();
}
}
let mut cursor = DatabaseStorageTrieCursor::new(
provider.tx_ref().cursor_dup_read::<tables::StoragesTrie>().unwrap(),
hashed_address,
);
// Forward sequential seeks should all succeed
for (i, key) in keys.iter().enumerate() {
let result = cursor.seek(*key).unwrap();
assert!(result.is_some(), "Should find key at index {i}");
let (found_key, _) = result.unwrap();
assert_eq!(found_key, *key);
}
}
#[test]
fn test_storage_trie_backward_seek_fallback() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_nibbles(10);
// Insert test data
{
let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
for key in &keys {
cursor
.upsert(
hashed_address,
&StorageTrieEntry {
nibbles: StoredNibblesSubKey(*key),
node: create_test_node(),
},
)
.unwrap();
}
}
let mut cursor = DatabaseStorageTrieCursor::new(
provider.tx_ref().cursor_dup_read::<tables::StoragesTrie>().unwrap(),
hashed_address,
);
// Seek to last key first
let result = cursor.seek(keys[9]).unwrap();
assert!(result.is_some());
// Backward seek should still work
let result = cursor.seek(keys[2]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().0, keys[2]);
}
#[test]
fn test_storage_trie_cursor_exhaustion() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_nibbles(5);
// Insert test data
{
let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
for key in &keys {
cursor
.upsert(
hashed_address,
&StorageTrieEntry {
nibbles: StoredNibblesSubKey(*key),
node: create_test_node(),
},
)
.unwrap();
}
}
let mut cursor = DatabaseStorageTrieCursor::new(
provider.tx_ref().cursor_dup_read::<tables::StoragesTrie>().unwrap(),
hashed_address,
);
// Position cursor
let _ = cursor.seek(keys[0]).unwrap();
// Create a very high nibbles key to exhaust
let high_key = Nibbles::from_nibbles([0xf; 64]);
// Seek past all entries
let result = cursor.seek(high_key).unwrap();
assert!(result.is_none());
// Now seek back - should work via fallback
let result = cursor.seek(keys[0]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().0, keys[0]);
}
#[test]
fn test_storage_trie_address_switch() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let address1 = B256::random();
let address2 = B256::random();
let keys = create_test_nibbles(5);
let node1 = BranchNodeCompact::new(0b0001, 0b0001, 0, vec![], None);
let node2 = BranchNodeCompact::new(0b0010, 0b0010, 0, vec![], None);
// Insert test data for both addresses
{
let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
for key in &keys {
cursor
.upsert(
address1,
&StorageTrieEntry {
nibbles: StoredNibblesSubKey(*key),
node: node1.clone(),
},
)
.unwrap();
cursor
.upsert(
address2,
&StorageTrieEntry {
nibbles: StoredNibblesSubKey(*key),
node: node2.clone(),
},
)
.unwrap();
}
}
let mut cursor = DatabaseStorageTrieCursor::new(
provider.tx_ref().cursor_dup_read::<tables::StoragesTrie>().unwrap(),
address1,
);
// Seek in first address
let result = cursor.seek(keys[2]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().1, node1);
// Switch address and seek
cursor.set_hashed_address(address2);
let result = cursor.seek(keys[2]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().1, node2);
}
#[test]
fn test_storage_trie_seek_same_key() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_nibbles(5);
// Insert test data
{
let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
for key in &keys {
cursor
.upsert(
hashed_address,
&StorageTrieEntry {
nibbles: StoredNibblesSubKey(*key),
node: create_test_node(),
},
)
.unwrap();
}
}
let mut cursor = DatabaseStorageTrieCursor::new(
provider.tx_ref().cursor_dup_read::<tables::StoragesTrie>().unwrap(),
hashed_address,
);
// Seek to a key
let result1 = cursor.seek(keys[2]).unwrap();
assert!(result1.is_some());
// Seek to the same key again
let result2 = cursor.seek(keys[2]).unwrap();
assert!(result2.is_some());
assert_eq!(result1.unwrap().0, result2.unwrap().0);
}
#[test]
fn test_storage_trie_reset() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_nibbles(5);
// Insert test data
{
let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
for key in &keys {
cursor
.upsert(
hashed_address,
&StorageTrieEntry {
nibbles: StoredNibblesSubKey(*key),
node: create_test_node(),
},
)
.unwrap();
}
}
let mut cursor = DatabaseStorageTrieCursor::new(
provider.tx_ref().cursor_dup_read::<tables::StoragesTrie>().unwrap(),
hashed_address,
);
// Position cursor
let _ = cursor.seek(keys[2]).unwrap();
// Reset and verify we can still seek
cursor.reset();
let result = cursor.seek(keys[0]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().0, keys[0]);
}
#[test]
fn test_storage_trie_seek_exact_updates_last_key() {
let factory = create_test_provider_factory();
let provider = factory.provider_rw().unwrap();
let hashed_address = B256::random();
let keys = create_test_nibbles(5);
// Insert test data
{
let mut cursor = provider.tx_ref().cursor_dup_write::<tables::StoragesTrie>().unwrap();
for key in &keys {
cursor
.upsert(
hashed_address,
&StorageTrieEntry {
nibbles: StoredNibblesSubKey(*key),
node: create_test_node(),
},
)
.unwrap();
}
}
let mut cursor = DatabaseStorageTrieCursor::new(
provider.tx_ref().cursor_dup_read::<tables::StoragesTrie>().unwrap(),
hashed_address,
);
// seek_exact should update last_key
let result = cursor.seek_exact(keys[2]).unwrap();
assert!(result.is_some());
// Forward seek should use optimization
let result = cursor.seek(keys[3]).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().0, keys[3]);
}
}