From 2bb5a36e761541bdac9ddd00690d0565b8c746a6 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Tue, 14 Oct 2025 13:39:14 -0700 Subject: [PATCH 1/3] chore: separate storage and account cursors --- crates/optimism/trie/src/api.rs | 17 ++++-- crates/optimism/trie/src/backfill.rs | 10 ++-- crates/optimism/trie/src/db/store.rs | 16 +++-- crates/optimism/trie/src/in_memory.rs | 22 +++++-- crates/optimism/trie/tests/in_memory.rs | 80 ++++++++++++------------- 5 files changed, 87 insertions(+), 58 deletions(-) diff --git a/crates/optimism/trie/src/api.rs b/crates/optimism/trie/src/api.rs index 8dbfbc2ac69..b0392f6e3f2 100644 --- a/crates/optimism/trie/src/api.rs +++ b/crates/optimism/trie/src/api.rs @@ -70,7 +70,10 @@ pub struct BlockStateDiff { #[auto_impl(Arc)] pub trait OpProofsStorage: Send + Sync + Debug { /// Cursor for iterating over trie branches. - type TrieCursor: OpProofsTrieCursor; + type StorageTrieCursor: OpProofsTrieCursor; + + /// Cursor for iterating over account trie branches. + type AccountTrieCursor: OpProofsTrieCursor; /// Cursor for iterating over storage leaves. type StorageCursor: OpProofsHashedCursor; @@ -123,11 +126,17 @@ pub trait OpProofsStorage: Send + Sync + Debug { ) -> impl Future>> + Send; /// Get a trie cursor for the storage backend - fn trie_cursor( + fn storage_trie_cursor( + &self, + hashed_address: B256, + max_block_number: u64, + ) -> OpProofsStorageResult; + + /// Get a trie cursor for the account backend + fn account_trie_cursor( &self, - hashed_address: Option, max_block_number: u64, - ) -> OpProofsStorageResult; + ) -> OpProofsStorageResult; /// Get a storage cursor for the storage backend fn storage_hashed_cursor( diff --git a/crates/optimism/trie/src/backfill.rs b/crates/optimism/trie/src/backfill.rs index 5c1f98049c3..dfc58c2fefc 100644 --- a/crates/optimism/trie/src/backfill.rs +++ b/crates/optimism/trie/src/backfill.rs @@ -510,7 +510,7 @@ mod tests { job.backfill_accounts_trie().await.unwrap(); // Verify data was stored - let mut trie_cursor = storage.trie_cursor(None, 100).unwrap(); + let mut trie_cursor = storage.account_trie_cursor(100).unwrap(); let mut count = 0; while let Some((path, _node)) = trie_cursor.next().unwrap() { assert_eq!(path, nodes[count].0 .0); @@ -568,7 +568,7 @@ mod tests { job.backfill_storages_trie().await.unwrap(); // Verify data was stored for addr1 - let mut trie_cursor = storage.trie_cursor(Some(addr1), 100).unwrap(); + let mut trie_cursor = storage.storage_trie_cursor(addr1, 100).unwrap(); let mut found = vec![]; while let Some((path, _node)) = trie_cursor.next().unwrap() { found.push(path); @@ -578,7 +578,7 @@ mod tests { assert_eq!(found[1], nodes[1].1.nibbles.0); // Verify data was stored for addr2 - let mut trie_cursor = storage.trie_cursor(Some(addr2), 100).unwrap(); + let mut trie_cursor = storage.storage_trie_cursor(addr2, 100).unwrap(); let mut found = vec![]; while let Some((path, _node)) = trie_cursor.next().unwrap() { found.push(path); @@ -662,10 +662,10 @@ mod tests { let mut storage_cursor = storage.storage_hashed_cursor(addr, 100).unwrap(); assert!(storage_cursor.next().unwrap().is_some()); - let mut trie_cursor = storage.trie_cursor(None, 100).unwrap(); + let mut trie_cursor = storage.account_trie_cursor(100).unwrap(); assert!(trie_cursor.next().unwrap().is_some()); - let mut storage_trie_cursor = storage.trie_cursor(Some(addr), 100).unwrap(); + let mut storage_trie_cursor = storage.storage_trie_cursor(addr, 100).unwrap(); assert!(storage_trie_cursor.next().unwrap().is_some()); } diff --git a/crates/optimism/trie/src/db/store.rs b/crates/optimism/trie/src/db/store.rs index 57d821de982..980c58e861d 100644 --- a/crates/optimism/trie/src/db/store.rs +++ b/crates/optimism/trie/src/db/store.rs @@ -27,7 +27,8 @@ impl MdbxProofsStorage { } impl OpProofsStorage for MdbxProofsStorage { - type TrieCursor = MdbxTrieCursor; + type StorageTrieCursor = MdbxTrieCursor; + type AccountTrieCursor = MdbxTrieCursor; type StorageCursor = MdbxStorageCursor; type AccountHashedCursor = MdbxAccountCursor; @@ -73,11 +74,18 @@ impl OpProofsStorage for MdbxProofsStorage { unimplemented!() } - fn trie_cursor( + fn storage_trie_cursor( + &self, + _hashed_address: B256, + _max_block_number: u64, + ) -> OpProofsStorageResult { + unimplemented!() + } + + fn account_trie_cursor( &self, - _hashed_address: Option, _max_block_number: u64, - ) -> OpProofsStorageResult { + ) -> OpProofsStorageResult { unimplemented!() } diff --git a/crates/optimism/trie/src/in_memory.rs b/crates/optimism/trie/src/in_memory.rs index 4f2b689a2bd..13b22b19fe6 100644 --- a/crates/optimism/trie/src/in_memory.rs +++ b/crates/optimism/trie/src/in_memory.rs @@ -380,7 +380,8 @@ impl OpProofsHashedCursor for InMemoryAccountCursor { } impl OpProofsStorage for InMemoryProofsStorage { - type TrieCursor = InMemoryTrieCursor; + type StorageTrieCursor = InMemoryTrieCursor; + type AccountTrieCursor = InMemoryTrieCursor; type StorageCursor = InMemoryStorageCursor; type AccountHashedCursor = InMemoryAccountCursor; @@ -459,17 +460,28 @@ impl OpProofsStorage for InMemoryProofsStorage { } } - fn trie_cursor( + fn storage_trie_cursor( &self, - hashed_address: Option, + hashed_address: B256, max_block_number: u64, - ) -> OpProofsStorageResult { + ) -> OpProofsStorageResult { // For synchronous methods, we need to try_read() and handle potential blocking let inner = self .inner .try_read() .map_err(|_| OpProofsStorageError::Other(eyre::eyre!("Failed to acquire read lock")))?; - Ok(InMemoryTrieCursor::new(&inner, hashed_address, max_block_number)) + Ok(InMemoryTrieCursor::new(&inner, Some(hashed_address), max_block_number)) + } + + fn account_trie_cursor( + &self, + max_block_number: u64, + ) -> OpProofsStorageResult { + let inner = self + .inner + .try_read() + .map_err(|_| OpProofsStorageError::Other(eyre::eyre!("Failed to acquire read lock")))?; + Ok(InMemoryTrieCursor::new(&inner, None, max_block_number)) } fn storage_hashed_cursor( diff --git a/crates/optimism/trie/tests/in_memory.rs b/crates/optimism/trie/tests/in_memory.rs index 903275b2c9a..3ae4c317d8c 100644 --- a/crates/optimism/trie/tests/in_memory.rs +++ b/crates/optimism/trie/tests/in_memory.rs @@ -117,7 +117,7 @@ async fn test_trie_updates_operations( async fn test_cursor_empty_trie( storage: S, ) -> Result<(), OpProofsStorageError> { - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // All operations should return None on empty trie assert!(cursor.seek_exact(Nibbles::default())?.is_none()); @@ -140,7 +140,7 @@ async fn test_cursor_single_entry( // Store single entry storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // Test seek_exact let result = cursor.seek_exact(path)?.unwrap(); @@ -174,7 +174,7 @@ async fn test_cursor_multiple_entries( storage.store_account_branches(50, vec![(*path, Some(branch.clone()))]).await?; } - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // Test that we can iterate through all entries let mut found_paths = Vec::new(); @@ -206,7 +206,7 @@ async fn test_seek_exact_existing_path( storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; let result = cursor.seek_exact(path)?.unwrap(); assert_eq!(result.0, path); @@ -224,7 +224,7 @@ async fn test_seek_exact_non_existing_path( storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; let non_existing = nibbles_from(vec![4, 5, 6]); assert!(cursor.seek_exact(non_existing)?.is_none()); @@ -242,7 +242,7 @@ async fn test_seek_exact_empty_path( storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; let result = cursor.seek_exact(Nibbles::default())?.unwrap(); assert_eq!(result.0, Nibbles::default()); @@ -260,7 +260,7 @@ async fn test_seek_to_existing_path( storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; let result = cursor.seek(path)?.unwrap(); assert_eq!(result.0, path); @@ -280,7 +280,7 @@ async fn test_seek_between_existing_nodes( storage.store_account_branches(50, vec![(path1, Some(branch.clone()))]).await?; storage.store_account_branches(50, vec![(path2, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // Seek to path between 1 and 3, should return path 3 let seek_path = nibbles_from(vec![2]); let result = cursor.seek(seek_path)?.unwrap(); @@ -300,7 +300,7 @@ async fn test_seek_after_all_nodes( storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // Seek to path after all nodes let seek_path = nibbles_from(vec![9]); assert!(cursor.seek(seek_path)?.is_none()); @@ -319,7 +319,7 @@ async fn test_seek_before_all_nodes( storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // Seek to path before all nodes, should return first node let seek_path = nibbles_from(vec![1]); let result = cursor.seek(seek_path)?.unwrap(); @@ -343,7 +343,7 @@ async fn test_next_without_prior_seek( storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // next() without prior seek should start from beginning let result = cursor.next()?.unwrap(); assert_eq!(result.0, path); @@ -362,7 +362,7 @@ async fn test_next_after_seek(storage: S) -> Result<(), OpPr storage.store_account_branches(50, vec![(path1, Some(branch.clone()))]).await?; storage.store_account_branches(50, vec![(path2, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; cursor.seek(path1)?; // next() should return second node @@ -383,7 +383,7 @@ async fn test_next_at_end_of_trie( storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; cursor.seek(path)?; // next() at end should return None @@ -405,7 +405,7 @@ async fn test_multiple_consecutive_next( storage.store_account_branches(50, vec![(*path, Some(branch.clone()))]).await?; } - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // Iterate through all with consecutive next() calls for expected_path in &paths { @@ -432,7 +432,7 @@ async fn test_current_after_operations( storage.store_account_branches(50, vec![(path1, Some(branch.clone()))]).await?; storage.store_account_branches(50, vec![(path2, Some(branch.clone()))]).await?; - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // Current should be None initially assert!(cursor.current()?.is_none()); @@ -454,7 +454,7 @@ async fn test_current_after_operations( async fn test_current_no_prior_operations( storage: S, ) -> Result<(), OpProofsStorageError> { - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // Current should be None when no operations performed assert!(cursor.current()?.is_none()); @@ -481,12 +481,12 @@ async fn test_same_path_different_blocks( storage.store_account_branches(100, vec![(path, Some(branch2.clone()))]).await?; // Cursor with max_block_number=75 should see only block 50 data - let mut cursor75 = storage.trie_cursor(None, 75)?; + let mut cursor75 = storage.account_trie_cursor(75)?; let result75 = cursor75.seek_exact(path)?.unwrap(); assert_eq!(result75.0, path); // Cursor with max_block_number=150 should see block 100 data (latest) - let mut cursor150 = storage.trie_cursor(None, 150)?; + let mut cursor150 = storage.account_trie_cursor(150)?; let result150 = cursor150.seek_exact(path)?.unwrap(); assert_eq!(result150.0, path); @@ -507,11 +507,11 @@ async fn test_deleted_branch_nodes( storage.store_account_branches(100, vec![(path, None)]).await?; // Cursor before deletion should see the node - let mut cursor75 = storage.trie_cursor(None, 75)?; + let mut cursor75 = storage.account_trie_cursor(75)?; assert!(cursor75.seek_exact(path)?.is_some()); // Cursor after deletion should not see the node - let mut cursor150 = storage.trie_cursor(None, 150)?; + let mut cursor150 = storage.account_trie_cursor(150)?; assert!(cursor150.seek_exact(path)?.is_none()); Ok(()) @@ -537,17 +537,17 @@ async fn test_account_specific_cursor( storage.store_storage_branches(50, addr2, vec![(path, Some(branch.clone()))]).await?; // Cursor for addr1 should only see addr1 data - let mut cursor1 = storage.trie_cursor(Some(addr1), 100)?; + let mut cursor1 = storage.storage_trie_cursor(addr1, 100)?; let result1 = cursor1.seek_exact(path)?.unwrap(); assert_eq!(result1.0, path); // Cursor for addr2 should only see addr2 data - let mut cursor2 = storage.trie_cursor(Some(addr2), 100)?; + let mut cursor2 = storage.storage_trie_cursor(addr2, 100)?; let result2 = cursor2.seek_exact(path)?.unwrap(); assert_eq!(result2.0, path); // Cursor for addr1 should not see addr2 data when iterating - let mut cursor1_iter = storage.trie_cursor(Some(addr1), 100)?; + let mut cursor1_iter = storage.storage_trie_cursor(addr1, 100)?; let mut found_count = 0; while cursor1_iter.next()?.is_some() { found_count += 1; @@ -572,12 +572,12 @@ async fn test_state_trie_cursor( storage.store_account_branches(50, vec![(path, Some(branch.clone()))]).await?; // State trie cursor (None address) should only see state trie data - let mut state_cursor = storage.trie_cursor(None, 100)?; + let mut state_cursor = storage.account_trie_cursor(100)?; let result = state_cursor.seek_exact(path)?.unwrap(); assert_eq!(result.0, path); // Verify state cursor doesn't see account data when iterating - let mut state_cursor_iter = storage.trie_cursor(None, 100)?; + let mut state_cursor_iter = storage.account_trie_cursor(100)?; let mut found_count = 0; while state_cursor_iter.next()?.is_some() { found_count += 1; @@ -604,7 +604,7 @@ async fn test_mixed_account_state_data( storage.store_account_branches(50, vec![(path2, Some(branch.clone()))]).await?; // Account cursor should only see account data - let mut account_cursor = storage.trie_cursor(Some(addr), 100)?; + let mut account_cursor = storage.storage_trie_cursor(addr, 100)?; let mut account_paths = Vec::new(); while let Some((path, _)) = account_cursor.next()? { account_paths.push(path); @@ -613,7 +613,7 @@ async fn test_mixed_account_state_data( assert_eq!(account_paths[0], path1); // State cursor should only see state data - let mut state_cursor = storage.trie_cursor(None, 100)?; + let mut state_cursor = storage.account_trie_cursor(100)?; let mut state_paths = Vec::new(); while let Some((path, _)) = state_cursor.next()? { state_paths.push(path); @@ -647,7 +647,7 @@ async fn test_lexicographic_ordering( storage.store_account_branches(50, vec![(*path, Some(branch.clone()))]).await?; } - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; let mut found_paths = Vec::new(); while let Some((path, _)) = cursor.next()? { found_paths.push(path); @@ -683,7 +683,7 @@ async fn test_path_prefix_scenarios( storage.store_account_branches(50, vec![(*path, Some(branch.clone()))]).await?; } - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; // Seek to prefix should find exact match let result = cursor.seek_exact(paths[0])?.unwrap(); @@ -719,7 +719,7 @@ async fn test_complex_nibble_combinations( storage.store_account_branches(50, vec![(*path, Some(branch.clone()))]).await?; } - let mut cursor = storage.trie_cursor(None, 100)?; + let mut cursor = storage.account_trie_cursor(100)?; let mut found_paths = Vec::new(); while let Some((path, _)) = cursor.next()? { found_paths.push(path); @@ -1267,7 +1267,7 @@ async fn test_store_trie_updates_comprehensive( storage.store_trie_updates(block_number, block_state_diff).await?; // ========== Verify Account Branch Nodes ========== - let mut account_trie_cursor = storage.trie_cursor(None, block_number + 10)?; + let mut account_trie_cursor = storage.account_trie_cursor(block_number + 10)?; // Should find the added branches let result1 = account_trie_cursor.seek_exact(account_path1)?; @@ -1283,7 +1283,7 @@ async fn test_store_trie_updates_comprehensive( assert!(removed_result.is_none(), "Removed account node should not be found"); // ========== Verify Storage Branch Nodes ========== - let mut storage_trie_cursor = storage.trie_cursor(Some(hashed_address), block_number + 10)?; + let mut storage_trie_cursor = storage.storage_trie_cursor(hashed_address, block_number + 10)?; let storage_result1 = storage_trie_cursor.seek_exact(storage_path1)?; assert!(storage_result1.is_some(), "Storage branch node 1 should be found"); @@ -1427,14 +1427,14 @@ async fn test_replace_updates_applies_all_updates( // ========== Verify initial state exists ========== // Verify block 50 data exists - let mut cursor_initial = storage.trie_cursor(None, 75)?; + let mut cursor_initial = storage.account_trie_cursor(75)?; assert!( cursor_initial.seek_exact(initial_branch_path)?.is_some(), "Initial branch should exist before replace" ); // Verify block 101 old data exists - let mut cursor_old = storage.trie_cursor(None, 150)?; + let mut cursor_old = storage.account_trie_cursor(150)?; assert!( cursor_old.seek_exact(old_branch_path)?.is_some(), "Old branch at block 101 should exist before replace" @@ -1499,13 +1499,13 @@ async fn test_replace_updates_applies_all_updates( storage.replace_updates(100, blocks_to_add).await?; // ========== Verify that data up to block 100 still exists ========== - let mut cursor_50 = storage.trie_cursor(None, 75)?; + let mut cursor_50 = storage.account_trie_cursor(75)?; assert!( cursor_50.seek_exact(initial_branch_path)?.is_some(), "Block 50 branch should still exist after replace" ); - let mut cursor_100 = storage.trie_cursor(None, 100)?; + let mut cursor_100 = storage.account_trie_cursor(100)?; assert!( cursor_100.seek_exact(common_branch_path)?.is_some(), "Block 100 branch should still exist after replace" @@ -1521,7 +1521,7 @@ async fn test_replace_updates_applies_all_updates( ); // ========== Verify that old data after block 100 is gone ========== - let mut cursor_old_gone = storage.trie_cursor(None, 150)?; + let mut cursor_old_gone = storage.account_trie_cursor(150)?; assert!( cursor_old_gone.seek_exact(old_branch_path)?.is_none(), "Old branch at block 101 should be removed after replace" @@ -1537,13 +1537,13 @@ async fn test_replace_updates_applies_all_updates( // ========== Verify new data is properly accessible via cursors ========== // Verify new account branch nodes - let mut trie_cursor = storage.trie_cursor(None, 150)?; + let mut trie_cursor = storage.account_trie_cursor(150)?; let branch_result = trie_cursor.seek_exact(new_branch_path)?; assert!(branch_result.is_some(), "New account branch should be accessible via cursor"); assert_eq!(branch_result.unwrap().0, new_branch_path); // Verify new storage branch nodes - let mut storage_trie_cursor = storage.trie_cursor(Some(storage_hashed_addr), 150)?; + let mut storage_trie_cursor = storage.storage_trie_cursor(storage_hashed_addr, 150)?; let storage_branch_result = storage_trie_cursor.seek_exact(storage_branch_path)?; assert!(storage_branch_result.is_some(), "New storage branch should be accessible via cursor"); assert_eq!(storage_branch_result.unwrap().0, storage_branch_path); @@ -1565,7 +1565,7 @@ async fn test_replace_updates_applies_all_updates( assert_eq!(storage_result.as_ref().unwrap().1, new_storage_value); // Verify block 102 data - let mut trie_cursor_102 = storage.trie_cursor(None, 150)?; + let mut trie_cursor_102 = storage.account_trie_cursor(150)?; let branch_result_102 = trie_cursor_102.seek_exact(block_102_branch_path)?; assert!(branch_result_102.is_some(), "Block 102 branch should be accessible"); assert_eq!(branch_result_102.unwrap().0, block_102_branch_path); From b918de455edf331ef4ee84e9e5af0d9a44d25c1b Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Wed, 15 Oct 2025 09:44:20 -0700 Subject: [PATCH 2/3] fix: optimism primitives should include serde-bincode-compat --- Cargo.lock | 1 + crates/optimism/exex/Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 083825c1c40..65825e254e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9350,6 +9350,7 @@ dependencies = [ "reth-exex", "reth-node-api", "reth-node-types", + "reth-optimism-primitives", "reth-optimism-trie", "reth-provider", "serde", diff --git a/crates/optimism/exex/Cargo.toml b/crates/optimism/exex/Cargo.toml index 576aec406c6..63c1fee0c1c 100644 --- a/crates/optimism/exex/Cargo.toml +++ b/crates/optimism/exex/Cargo.toml @@ -18,6 +18,7 @@ reth-node-types.workspace = true reth-node-api.workspace = true reth-provider.workspace = true reth-chainspec.workspace = true +reth-optimism-primitives = { workspace = true, features = ["reth-codec", "serde-bincode-compat", "serde"] } # ethereum alloy-primitives.workspace = true From 6f3d8623a1602d9013e4ad6026045895ccf1d557 Mon Sep 17 00:00:00 2001 From: Julian Meyer Date: Wed, 15 Oct 2025 09:50:31 -0700 Subject: [PATCH 3/3] chore: add explanation to weird import --- crates/optimism/exex/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/optimism/exex/Cargo.toml b/crates/optimism/exex/Cargo.toml index 63c1fee0c1c..bbcefba8953 100644 --- a/crates/optimism/exex/Cargo.toml +++ b/crates/optimism/exex/Cargo.toml @@ -18,6 +18,9 @@ reth-node-types.workspace = true reth-node-api.workspace = true reth-provider.workspace = true reth-chainspec.workspace = true + +# for ethereum types, `serde-bincode-compat` is added by `reth-storage-api`, however this does not work with `op` until +# `reth-storage-api` is updated to support `op`, so we add it here. reth-optimism-primitives = { workspace = true, features = ["reth-codec", "serde-bincode-compat", "serde"] } # ethereum