Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions crates/optimism/trie/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,14 @@ pub trait OpProofsStorage: Send + Sync + Debug {
Self: 'tx;

/// Cursor for iterating over storage leaves.
type StorageCursor: OpProofsHashedCursor<Value = U256>;
type StorageCursor<'tx>: OpProofsHashedCursor<Value = U256> + 'tx
where
Self: 'tx;

/// Cursor for iterating over account leaves.
type AccountHashedCursor: OpProofsHashedCursor<Value = Account>;
type AccountHashedCursor<'tx>: OpProofsHashedCursor<Value = Account> + 'tx
where
Self: 'tx;

/// Store a batch of account trie branches. Used for saving existing state. For live state
/// capture, use [store_trie_updates](OpProofsStorage::store_trie_updates).
Expand Down Expand Up @@ -160,17 +164,17 @@ pub trait OpProofsStorage: Send + Sync + Debug {
) -> OpProofsStorageResult<Self::AccountTrieCursor<'tx>>;

/// Get a storage cursor for the storage backend
fn storage_hashed_cursor(
fn storage_hashed_cursor<'tx>(
&self,
hashed_address: B256,
max_block_number: u64,
) -> OpProofsStorageResult<Self::StorageCursor>;
) -> OpProofsStorageResult<Self::StorageCursor<'tx>>;

/// Get an account hashed cursor for the storage backend
fn account_hashed_cursor(
fn account_hashed_cursor<'tx>(
&self,
max_block_number: u64,
) -> OpProofsStorageResult<Self::AccountHashedCursor>;
) -> OpProofsStorageResult<Self::AccountHashedCursor<'tx>>;

/// Store a batch of trie updates.
///
Expand Down
232 changes: 218 additions & 14 deletions crates/optimism/trie/src/db/cursor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use std::marker::PhantomData;

use crate::{
db::{AccountTrieHistory, MaybeDeleted, StorageTrieHistory, StorageTrieKey, VersionedValue},
db::{
AccountTrieHistory, HashedAccountHistory, HashedStorageHistory, HashedStorageKey,
MaybeDeleted, StorageTrieHistory, StorageTrieKey, VersionedValue,
},
OpProofsHashedCursor, OpProofsStorageError, OpProofsStorageResult, OpProofsTrieCursor,
};
use alloy_primitives::{B256, U256};
Expand Down Expand Up @@ -260,45 +263,80 @@ where

/// MDBX implementation of `OpProofsHashedCursor` for storage state.
#[derive(Debug)]
pub struct MdbxStorageCursor {}
pub struct MdbxStorageCursor<Cursor> {
inner: BlockNumberVersionedCursor<HashedStorageHistory, Cursor>,
hashed_address: B256,
}

impl OpProofsHashedCursor for MdbxStorageCursor {
impl<Cursor> MdbxStorageCursor<Cursor>
where
Cursor: DbCursorRO<HashedStorageHistory> + DbDupCursorRO<HashedStorageHistory> + Send + Sync,
{
/// Initializes new [`MdbxStorageCursor`]
pub const fn new(cursor: Cursor, block_number: u64, hashed_address: B256) -> Self {
Self { inner: BlockNumberVersionedCursor::new(cursor, block_number), hashed_address }
}
}

impl<Cursor> OpProofsHashedCursor for MdbxStorageCursor<Cursor>
where
Cursor: DbCursorRO<HashedStorageHistory> + DbDupCursorRO<HashedStorageHistory> + Send + Sync,
{
type Value = U256;

fn seek(&mut self, _key: B256) -> OpProofsStorageResult<Option<(B256, Self::Value)>> {
unimplemented!()
fn seek(&mut self, key: B256) -> OpProofsStorageResult<Option<(B256, Self::Value)>> {
let storage_key = HashedStorageKey::new(self.hashed_address, key);
self.inner.seek(storage_key).map(|opt| opt.map(|(k, v)| (k.hashed_storage_key, v.0)))
}

fn next(&mut self) -> OpProofsStorageResult<Option<(B256, Self::Value)>> {
unimplemented!()
self.inner.next().map(|opt| opt.map(|(k, v)| (k.hashed_storage_key, v.0)))
}
}

/// MDBX implementation of `OpProofsHashedCursor` for account state.
#[derive(Debug)]
pub struct MdbxAccountCursor {}
pub struct MdbxAccountCursor<Cursor> {
inner: BlockNumberVersionedCursor<HashedAccountHistory, Cursor>,
}

impl<Cursor> MdbxAccountCursor<Cursor>
where
Cursor: DbCursorRO<HashedAccountHistory> + DbDupCursorRO<HashedAccountHistory> + Send + Sync,
{
/// Initializes new `MdbxAccountCursor`
pub const fn new(cursor: Cursor, block_number: u64) -> Self {
Self { inner: BlockNumberVersionedCursor::new(cursor, block_number) }
}
}

impl OpProofsHashedCursor for MdbxAccountCursor {
impl<Cursor> OpProofsHashedCursor for MdbxAccountCursor<Cursor>
where
Cursor: DbCursorRO<HashedAccountHistory> + DbDupCursorRO<HashedAccountHistory> + Send + Sync,
{
type Value = Account;

fn seek(&mut self, _key: B256) -> OpProofsStorageResult<Option<(B256, Self::Value)>> {
unimplemented!()
fn seek(&mut self, key: B256) -> OpProofsStorageResult<Option<(B256, Self::Value)>> {
self.inner.seek(key)
}

fn next(&mut self) -> OpProofsStorageResult<Option<(B256, Self::Value)>> {
unimplemented!()
self.inner.next()
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::db::models;
use crate::db::{models, StorageValue};
use reth_db::{
cursor::DbDupCursorRW,
mdbx::{init_db_for, DatabaseArguments},
DatabaseEnv,
};
use reth_db_api::{
cursor::DbDupCursorRW,
transaction::{DbTx, DbTxMut},
Database, DatabaseEnv,
Database,
};
use reth_trie::{BranchNodeCompact, Nibbles, StoredNibbles};
use tempfile::TempDir;
Expand Down Expand Up @@ -340,6 +378,30 @@ mod tests {
c.append_dup(key, vv).expect("append dup");
}

fn append_hashed_storage(
wtx: &<DatabaseEnv as Database>::TXMut,
addr: B256,
slot: B256,
block: u64,
val: Option<U256>,
) {
let mut c = wtx.cursor_dup_write::<HashedStorageHistory>().expect("dup write");
let key = HashedStorageKey::new(addr, slot);
let vv = VersionedValue { block_number: block, value: MaybeDeleted(val.map(StorageValue)) };
c.append_dup(key, vv).expect("append dup");
}

fn append_hashed_account(
wtx: &<DatabaseEnv as Database>::TXMut,
key: B256,
block: u64,
val: Option<Account>,
) {
let mut c = wtx.cursor_dup_write::<HashedAccountHistory>().expect("dup write");
let vv = VersionedValue { block_number: block, value: MaybeDeleted(val) };
c.append_dup(key, vv).expect("append dup");
}

// Open a dup-RO cursor and wrap it in a BlockNumberVersionedCursor with a given bound.
fn version_cursor(
tx: &<DatabaseEnv as Database>::TX,
Expand Down Expand Up @@ -368,6 +430,23 @@ mod tests {
MdbxTrieCursor::new(c, max_block, Some(address))
}

fn storage_cursor(
tx: &'_ <DatabaseEnv as Database>::TX,
max_block: u64,
address: B256,
) -> MdbxStorageCursor<Dup<'_, HashedStorageHistory>> {
let c = tx.cursor_dup_read::<HashedStorageHistory>().expect("dup ro cursor");
MdbxStorageCursor::new(c, max_block, address)
}

fn account_cursor(
tx: &'_ <DatabaseEnv as Database>::TX,
max_block: u64,
) -> MdbxAccountCursor<Dup<'_, HashedAccountHistory>> {
let c = tx.cursor_dup_read::<HashedAccountHistory>().expect("dup ro cursor");
MdbxAccountCursor::new(c, max_block)
}

// Assert helper: ensure the chosen VersionedValue has the expected block and deletion flag.
fn assert_block(
got: Option<(StoredNibbles, VersionedValue<BranchNodeCompact>)>,
Expand Down Expand Up @@ -1000,4 +1079,129 @@ mod tests {
let now = OpProofsTrieCursor::current(&mut cur).expect("ok").expect("some");
assert_eq!(now, p);
}

#[test]
fn hashed_storage_seek_maps_slot_and_value() {
let db = setup_db();
let addr = B256::from([0xAA; 32]);
let slot = B256::from([0x10; 32]);

{
let wtx = db.tx_mut().expect("rw");
append_hashed_storage(&wtx, addr, slot, 10, Some(U256::from(7)));
wtx.commit().expect("commit");
}

let tx = db.tx().expect("ro");
let mut cur = storage_cursor(&tx, 100, addr);

let (got_slot, got_val) =
OpProofsHashedCursor::seek(&mut cur, slot).expect("ok").expect("some");
assert_eq!(got_slot, slot);
assert_eq!(got_val, U256::from(7));
}

#[test]
fn hashed_storage_seek_filters_tombstone() {
let db = setup_db();
let addr = B256::from([0xAB; 32]);
let slot = B256::from([0x11; 32]);

{
let wtx = db.tx_mut().expect("rw");
append_hashed_storage(&wtx, addr, slot, 5, Some(U256::from(1)));
append_hashed_storage(&wtx, addr, slot, 9, None); // latest ≤ max is tombstone
wtx.commit().expect("commit");
}

let tx = db.tx().expect("ro");
let mut cur = storage_cursor(&tx, 10, addr);

let out = OpProofsHashedCursor::seek(&mut cur, slot).expect("ok");
assert!(out.is_none(), "wrapper must filter tombstoned latest");
}

#[test]
fn hashed_storage_seek_and_next_roundtrip() {
let db = setup_db();
let addr = B256::from([0xAC; 32]);
let s1 = B256::from([0x01; 32]);
let s2 = B256::from([0x02; 32]);

{
let wtx = db.tx_mut().expect("rw");
append_hashed_storage(&wtx, addr, s1, 10, Some(U256::from(11)));
append_hashed_storage(&wtx, addr, s2, 10, Some(U256::from(22)));
wtx.commit().expect("commit");
}

let tx = db.tx().expect("ro");
let mut cur = storage_cursor(&tx, 100, addr);

let (k1, v1) = OpProofsHashedCursor::seek(&mut cur, s1).expect("ok").expect("some");
assert_eq!((k1, v1), (s1, U256::from(11)));

let (k2, v2) = OpProofsHashedCursor::next(&mut cur).expect("ok").expect("some");
assert_eq!((k2, v2), (s2, U256::from(22)));
}

#[test]
fn hashed_account_seek_maps_key_and_value() {
let db = setup_db();
let key = B256::from([0x20; 32]);

{
let wtx = db.tx_mut().expect("rw");
append_hashed_account(&wtx, key, 10, Some(Account::default()));
wtx.commit().expect("commit");
}

let tx = db.tx().expect("ro");
let mut cur = account_cursor(&tx, 100);

let (got_key, _acc) = OpProofsHashedCursor::seek(&mut cur, key).expect("ok").expect("some");
assert_eq!(got_key, key);
}

#[test]
fn hashed_account_seek_filters_tombstone() {
let db = setup_db();
let key = B256::from([0x21; 32]);

{
let wtx = db.tx_mut().expect("rw");
append_hashed_account(&wtx, key, 5, Some(Account::default()));
append_hashed_account(&wtx, key, 9, None); // latest ≤ max is tombstone
wtx.commit().expect("commit");
}

let tx = db.tx().expect("ro");
let mut cur = account_cursor(&tx, 10);

let out = OpProofsHashedCursor::seek(&mut cur, key).expect("ok");
assert!(out.is_none(), "wrapper must filter tombstoned latest");
}

#[test]
fn hashed_account_seek_and_next_roundtrip() {
let db = setup_db();
let k1 = B256::from([0x01; 32]);
let k2 = B256::from([0x02; 32]);

{
let wtx = db.tx_mut().expect("rw");
append_hashed_account(&wtx, k1, 10, Some(Account::default()));
append_hashed_account(&wtx, k2, 10, Some(Account::default()));
wtx.commit().expect("commit");
}

let tx = db.tx().expect("ro");
let mut cur = account_cursor(&tx, 100);

let (got1, _) = OpProofsHashedCursor::seek(&mut cur, k1).expect("ok").expect("some");
assert_eq!(got1, k1);

let (got2, _) = OpProofsHashedCursor::next(&mut cur).expect("ok").expect("some");
assert_eq!(got2, k2);
}
}
2 changes: 1 addition & 1 deletion crates/optimism/trie/src/db/models/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl Decode for StorageTrieKey {
///
/// Used to efficiently index storage values by both account address and storage key.
/// The encoding ensures lexicographic ordering: first by address, then by storage key.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct HashedStorageKey {
/// Hashed account address
pub hashed_address: B256,
Expand Down
38 changes: 27 additions & 11 deletions crates/optimism/trie/src/db/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,14 @@ impl OpProofsStorage for MdbxProofsStorage {
= MdbxTrieCursor<AccountTrieHistory, Dup<'tx, AccountTrieHistory>>
where
Self: 'tx;
type StorageCursor = MdbxStorageCursor;
type AccountHashedCursor = MdbxAccountCursor;
type StorageCursor<'tx>
= MdbxStorageCursor<Dup<'tx, HashedStorageHistory>>
where
Self: 'tx;
type AccountHashedCursor<'tx>
= MdbxAccountCursor<Dup<'tx, HashedAccountHistory>>
where
Self: 'tx;

async fn store_account_branches(
&self,
Expand Down Expand Up @@ -206,19 +212,29 @@ impl OpProofsStorage for MdbxProofsStorage {
Ok(MdbxTrieCursor::new(cursor, max_block_number, None))
}

fn storage_hashed_cursor(
fn storage_hashed_cursor<'tx>(
&self,
_hashed_address: B256,
_max_block_number: u64,
) -> OpProofsStorageResult<Self::StorageCursor> {
unimplemented!()
hashed_address: B256,
max_block_number: u64,
) -> OpProofsStorageResult<Self::StorageCursor<'tx>> {
let tx = self.env.tx().map_err(|e| OpProofsStorageError::Other(e.into()))?;
let cursor = tx
.cursor_dup_read::<HashedStorageHistory>()
.map_err(|e| OpProofsStorageError::Other(e.into()))?;

Ok(MdbxStorageCursor::new(cursor, max_block_number, hashed_address))
}

fn account_hashed_cursor(
fn account_hashed_cursor<'tx>(
&self,
_max_block_number: u64,
) -> OpProofsStorageResult<Self::AccountHashedCursor> {
unimplemented!()
max_block_number: u64,
) -> OpProofsStorageResult<Self::AccountHashedCursor<'tx>> {
let tx = self.env.tx().map_err(|e| OpProofsStorageError::Other(e.into()))?;
let cursor = tx
.cursor_dup_read::<HashedAccountHistory>()
.map_err(|e| OpProofsStorageError::Other(e.into()))?;

Ok(MdbxAccountCursor::new(cursor, max_block_number))
}

async fn store_trie_updates(
Expand Down
Loading
Loading