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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/optimism/trie/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,5 @@ reth-codecs = { workspace = true, features = ["test-utils"] }
tokio = { workspace = true, features = ["test-util", "rt-multi-thread", "macros"] }
test-case.workspace = true
reth-db = { workspace = true, features = ["test-utils"] }
reth-db-api = { workspace = true, features = ["test-utils"] }
reth-trie = { workspace = true, features = ["test-utils"] }
tempfile.workspace = true
5 changes: 5 additions & 0 deletions crates/optimism/trie/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use alloy_primitives::{map::HashMap, B256, U256};
use auto_impl::auto_impl;
use reth_db_api::DatabaseError;
use reth_primitives_traits::Account;
use reth_trie::{updates::TrieUpdates, BranchNodeCompact, HashedPostState, Nibbles};
use std::fmt::Debug;
Expand All @@ -11,6 +12,10 @@ use thiserror::Error;
#[derive(Debug, Error)]
pub enum OpProofsStorageError {
// TODO: add more errors once we know what they are
/// Error occurred while interacting with the database.
#[error(transparent)]
DatabaseError(#[from] DatabaseError),

/// Other error
#[error("Other error: {0}")]
Other(eyre::Error),
Expand Down
8 changes: 4 additions & 4 deletions crates/optimism/trie/src/db/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ tables! {
/// Each entry is identified by a composite key combining the account’s hashed address and the
/// compact-encoded trie path. Versions are tracked using block numbers as subkeys.
table StorageTrieHistory {
type Key = StorageTrieSubKey;
type Key = StorageTrieKey;
type Value = VersionedValue<BranchNodeCompact>;
type SubKey = u64; // block number
}
Expand All @@ -48,7 +48,7 @@ tables! {
/// code hash, storage root).
table HashedAccountHistory {
type Key = B256;
type Value = VersionedValue<MaybeDeleted<Account>>;
type Value = VersionedValue<Account>;
type SubKey = u64; // block number
}

Expand All @@ -57,8 +57,8 @@ tables! {
/// Each entry maps a composite key of (hashed address, storage key) to its stored value.
/// Used for reconstructing contract storage at any historical block height.
table HashedStorageHistory {
type Key = HashedStorageSubKey;
type Value = VersionedValue<B256>;
type Key = HashedStorageKey;
type Value = VersionedValue<StorageValue>;
type SubKey = u64; // block number
}

Expand Down
66 changes: 45 additions & 21 deletions crates/optimism/trie/src/db/models/storage.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use alloy_primitives::B256;
use alloy_primitives::{B256, U256};
use derive_more::{Constructor, From, Into};
use reth_db::{
table::{Decode, Encode},
table::{Compress, Decode, Decompress, Encode},
DatabaseError,
};
use reth_trie::StoredNibbles;
Expand All @@ -11,21 +12,21 @@ use serde::{Deserialize, Serialize};
/// Used to efficiently index storage branches by both account address and trie path.
/// The encoding ensures lexicographic ordering: first by address, then by path.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct StorageTrieSubKey {
pub struct StorageTrieKey {
/// Hashed account address
pub hashed_address: B256,
/// Trie path as nibbles
pub path: StoredNibbles,
}

impl StorageTrieSubKey {
impl StorageTrieKey {
/// Create a new storage branch key
pub const fn new(hashed_address: B256, path: StoredNibbles) -> Self {
Self { hashed_address, path }
}
}

impl Encode for StorageTrieSubKey {
impl Encode for StorageTrieKey {
type Encoded = Vec<u8>;

fn encode(self) -> Self::Encoded {
Expand All @@ -38,7 +39,7 @@ impl Encode for StorageTrieSubKey {
}
}

impl Decode for StorageTrieSubKey {
impl Decode for StorageTrieKey {
fn decode(value: &[u8]) -> Result<Self, DatabaseError> {
if value.len() < 32 {
return Err(DatabaseError::Decode);
Expand All @@ -59,21 +60,21 @@ impl Decode for StorageTrieSubKey {
/// 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)]
pub struct HashedStorageSubKey {
pub struct HashedStorageKey {
/// Hashed account address
pub hashed_address: B256,
/// Hashed storage key
pub hashed_storage_key: B256,
}

impl HashedStorageSubKey {
impl HashedStorageKey {
/// Create a new hashed storage key
pub const fn new(hashed_address: B256, hashed_storage_key: B256) -> Self {
Self { hashed_address, hashed_storage_key }
}
}

impl Encode for HashedStorageSubKey {
impl Encode for HashedStorageKey {
type Encoded = [u8; 64];

fn encode(self) -> Self::Encoded {
Expand All @@ -86,7 +87,7 @@ impl Encode for HashedStorageSubKey {
}
}

impl Decode for HashedStorageSubKey {
impl Decode for HashedStorageKey {
fn decode(value: &[u8]) -> Result<Self, DatabaseError> {
if value.len() != 64 {
return Err(DatabaseError::Decode);
Expand All @@ -99,6 +100,29 @@ impl Decode for HashedStorageSubKey {
}
}

/// Storage value wrapper for U256 values
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, From, Into, Constructor)]
pub struct StorageValue(pub U256);

impl Compress for StorageValue {
type Compressed = Vec<u8>;

fn compress_to_buf<B: bytes::BufMut + AsMut<[u8]>>(&self, buf: &mut B) {
let be: [u8; 32] = self.0.to_be_bytes::<32>();
buf.put_slice(&be);
}
}

impl Decompress for StorageValue {
fn decompress(value: &[u8]) -> Result<Self, DatabaseError> {
if value.len() != 32 {
return Err(DatabaseError::Decode);
}
let bytes: [u8; 32] = value.try_into().map_err(|_| DatabaseError::Decode)?;
Ok(Self(U256::from_be_bytes(bytes)))
}
}

/// Proof Window key for tracking active proof window bounds
///
/// Used to store earliest and latest block numbers in the external storage.
Expand Down Expand Up @@ -138,10 +162,10 @@ mod tests {
fn test_storage_branch_subkey_encode_decode() {
let addr = B256::from([1u8; 32]);
let path = StoredNibbles(Nibbles::from_nibbles_unchecked([1, 2, 3, 4]));
let key = StorageTrieSubKey::new(addr, path.clone());
let key = StorageTrieKey::new(addr, path.clone());

let encoded = key.clone().encode();
let decoded = StorageTrieSubKey::decode(&encoded).unwrap();
let decoded = StorageTrieKey::decode(&encoded).unwrap();

assert_eq!(key, decoded);
assert_eq!(decoded.hashed_address, addr);
Expand All @@ -155,9 +179,9 @@ mod tests {
let path1 = StoredNibbles(Nibbles::from_nibbles_unchecked([1, 2]));
let path2 = StoredNibbles(Nibbles::from_nibbles_unchecked([1, 3]));

let key1 = StorageTrieSubKey::new(addr1, path1.clone());
let key2 = StorageTrieSubKey::new(addr1, path2);
let key3 = StorageTrieSubKey::new(addr2, path1);
let key1 = StorageTrieKey::new(addr1, path1.clone());
let key2 = StorageTrieKey::new(addr1, path2);
let key3 = StorageTrieKey::new(addr2, path1);

// Encoded bytes should be sortable: first by address, then by path
let enc1 = key1.encode();
Expand All @@ -173,10 +197,10 @@ mod tests {
fn test_hashed_storage_subkey_encode_decode() {
let addr = B256::from([1u8; 32]);
let storage_key = B256::from([2u8; 32]);
let key = HashedStorageSubKey::new(addr, storage_key);
let key = HashedStorageKey::new(addr, storage_key);

let encoded = key.clone().encode();
let decoded = HashedStorageSubKey::decode(&encoded).unwrap();
let decoded = HashedStorageKey::decode(&encoded).unwrap();

assert_eq!(key, decoded);
assert_eq!(decoded.hashed_address, addr);
Expand All @@ -190,9 +214,9 @@ mod tests {
let storage1 = B256::from([10u8; 32]);
let storage2 = B256::from([20u8; 32]);

let key1 = HashedStorageSubKey::new(addr1, storage1);
let key2 = HashedStorageSubKey::new(addr1, storage2);
let key3 = HashedStorageSubKey::new(addr2, storage1);
let key1 = HashedStorageKey::new(addr1, storage1);
let key2 = HashedStorageKey::new(addr1, storage2);
let key3 = HashedStorageKey::new(addr2, storage1);

// Encoded bytes should be sortable: first by address, then by storage key
let enc1 = key1.encode();
Expand All @@ -208,7 +232,7 @@ mod tests {
fn test_hashed_storage_subkey_size() {
let addr = B256::from([1u8; 32]);
let storage_key = B256::from([2u8; 32]);
let key = HashedStorageSubKey::new(addr, storage_key);
let key = HashedStorageKey::new(addr, storage_key);

let encoded = key.encode();
assert_eq!(encoded.len(), 64, "Encoded size should be exactly 64 bytes");
Expand Down
Loading
Loading