diff --git a/Cargo.lock b/Cargo.lock index cf63ded94358f..3ee1267751e1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8970,6 +8970,17 @@ dependencies = [ "revm", ] +[[package]] +name = "reth-scroll-storage" +version = "1.1.0" +dependencies = [ + "alloy-primitives", + "reth-primitives-traits", + "reth-revm", + "reth-scroll-revm", + "reth-storage-errors", +] + [[package]] name = "reth-stages" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 294d6b4e5577c..38c3095ac057d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -100,6 +100,7 @@ members = [ "crates/rpc/rpc-types-compat/", "crates/rpc/rpc/", "crates/scroll/revm", + "crates/scroll/storage", "crates/stages/api/", "crates/stages/stages/", "crates/stages/types/", @@ -402,6 +403,7 @@ reth-rpc-layer = { path = "crates/rpc/rpc-layer" } reth-rpc-server-types = { path = "crates/rpc/rpc-server-types" } reth-rpc-types-compat = { path = "crates/rpc/rpc-types-compat" } reth-scroll-revm = { path = "crates/scroll/revm" } +reth-scroll-storage = { path = "crates/scroll/storage" } reth-stages = { path = "crates/stages/stages" } reth-stages-api = { path = "crates/stages/api" } reth-stages-types = { path = "crates/stages/types" } diff --git a/crates/scroll/storage/Cargo.toml b/crates/scroll/storage/Cargo.toml new file mode 100644 index 0000000000000..5a5d1ac4936d8 --- /dev/null +++ b/crates/scroll/storage/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "reth-scroll-storage" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +exclude.workspace = true + +[lints] +workspace = true + +[dependencies] +# alloy +alloy-primitives.workspace = true + +# reth +reth-primitives-traits.workspace = true +reth-revm.workspace = true +reth-storage-errors.workspace = true + +# scroll +reth-scroll-revm.workspace = true diff --git a/crates/scroll/storage/src/lib.rs b/crates/scroll/storage/src/lib.rs new file mode 100644 index 0000000000000..3e9038fbf3aa0 --- /dev/null +++ b/crates/scroll/storage/src/lib.rs @@ -0,0 +1,76 @@ +//! Scroll storage implementation. + +use alloy_primitives::{map::Entry, Address, B256, U256}; +use reth_revm::{ + database::EvmStateProvider, + primitives::{AccountInfo, Bytecode}, + Database, +}; +use reth_scroll_revm::primitives::{poseidon, ScrollPostExecutionContext, POSEIDON_EMPTY}; +use reth_storage_errors::provider::ProviderError; + +/// A similar construct as `StateProviderDatabase` which captures additional Scroll context derived +/// from bytecode during execution. +#[derive(Clone, Debug)] +pub struct ScrollStateProviderDatabase { + /// Scroll post execution context. + post_execution_context: ScrollPostExecutionContext, + /// The database. + pub db: DB, +} + +impl ScrollStateProviderDatabase { + /// Creates a [`ScrollStateProviderDatabase`] from the provided DB. + pub fn new(db: DB) -> Self { + Self { db, post_execution_context: Default::default() } + } + + /// Consumes the provider and returns the post execution context. + pub fn post_execution_context(self) -> ScrollPostExecutionContext { + self.post_execution_context + } +} + +impl Database for ScrollStateProviderDatabase { + type Error = ProviderError; + + /// Retrieves basic account information for a given address. + /// + /// Returns `Ok` with `Some(AccountInfo)` if the account exists, + /// `None` if it doesn't, or an error if encountered. + fn basic(&mut self, address: Address) -> Result, Self::Error> { + let Some(account) = self.db.basic_account(address)? else { return Ok(None) }; + let Some(code_hash) = account.bytecode_hash else { return Ok(Some(account.into())) }; + + if let Entry::Vacant(entry) = self.post_execution_context.entry(code_hash) { + let code = self.db.bytecode_by_hash(code_hash)?.unwrap_or_default(); + let poseidon_hash = + if code.is_empty() { POSEIDON_EMPTY } else { poseidon(code.bytecode()) }; + entry.insert((code.len(), poseidon_hash)); + } + + Ok(Some(account.into())) + } + + /// Retrieves the bytecode associated with a given code hash. + /// + /// Returns `Ok` with the bytecode if found, or the default bytecode otherwise. + fn code_by_hash(&mut self, code_hash: B256) -> Result { + Ok(self.db.bytecode_by_hash(code_hash)?.unwrap_or_default().0) + } + + /// Retrieves the storage value at a specific index for a given address. + /// + /// Returns `Ok` with the storage value, or the default value if not found. + fn storage(&mut self, address: Address, index: U256) -> Result { + Ok(self.db.storage(address, B256::new(index.to_be_bytes()))?.unwrap_or_default()) + } + + /// Retrieves the block hash for a given block number. + /// + /// Returns `Ok` with the block hash if found, or the default hash otherwise. + /// Note: It safely casts the `number` to `u64`. + fn block_hash(&mut self, number: u64) -> Result { + Ok(self.db.block_hash(number)?.unwrap_or_default()) + } +}