diff --git a/.github/assets/check_wasm.sh b/.github/assets/check_wasm.sh index 5504cb17e6..c5639c710d 100755 --- a/.github/assets/check_wasm.sh +++ b/.github/assets/check_wasm.sh @@ -58,6 +58,7 @@ exclude_crates=( reth-ress-provider # The following are not supposed to be working reth # all of the crates below + reth-alloy-provider reth-invalid-block-hooks # reth-provider reth-libmdbx # mdbx reth-mdbx-sys # mdbx diff --git a/Cargo.lock b/Cargo.lock index 81678ab067..d6791b9088 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7142,6 +7142,34 @@ dependencies = [ "tracing", ] +[[package]] +name = "reth-alloy-provider" +version = "1.4.8" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-rpc-types-engine", + "reth-chainspec", + "reth-db-api", + "reth-errors", + "reth-execution-types", + "reth-node-types", + "reth-primitives", + "reth-provider", + "reth-prune-types", + "reth-stages-types", + "reth-storage-api", + "reth-trie", + "revm", + "revm-primitives", + "tokio", + "tracing", +] + [[package]] name = "reth-basic-payload-builder" version = "1.4.8" diff --git a/Cargo.toml b/Cargo.toml index c51099f5b7..89086c7027 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ exclude = [".github/"] members = [ "bin/reth-bench/", "bin/reth/", + "crates/alloy-provider/", "crates/chain-state/", "crates/chainspec/", "crates/cli/cli/", @@ -319,6 +320,7 @@ codegen-units = 1 # reth op-reth = { path = "crates/optimism/bin" } reth = { path = "bin/reth" } +reth-alloy-provider = { path = "crates/alloy-provider" } reth-basic-payload-builder = { path = "crates/payload/basic" } reth-bench = { path = "bin/reth-bench" } reth-chain-state = { path = "crates/chain-state" } diff --git a/crates/alloy-provider/Cargo.toml b/crates/alloy-provider/Cargo.toml new file mode 100644 index 0000000000..6eb47e1f4d --- /dev/null +++ b/crates/alloy-provider/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "reth-alloy-provider" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +homepage.workspace = true +repository.workspace = true +description = "Alloy provider implementation for reth that fetches state via RPC" + +[lints] +workspace = true + +[dependencies] +# reth +reth-storage-api.workspace = true +reth-chainspec.workspace = true +reth-primitives.workspace = true +reth-provider.workspace = true +reth-errors.workspace = true +reth-execution-types.workspace = true +reth-prune-types.workspace = true +reth-node-types.workspace = true +reth-trie.workspace = true +reth-stages-types.workspace = true +reth-db-api.workspace = true + +# alloy +alloy-provider.workspace = true +alloy-network.workspace = true +alloy-primitives.workspace = true +alloy-consensus.workspace = true +alloy-rpc-types.workspace = true +alloy-rpc-types-engine.workspace = true +alloy-eips.workspace = true + +# async +tokio = { workspace = true, features = ["sync", "macros", "rt-multi-thread"] } + +# other +tracing.workspace = true + +# revm +revm.workspace = true +revm-primitives.workspace = true + +[dev-dependencies] +tokio = { workspace = true, features = ["rt", "macros"] } diff --git a/crates/alloy-provider/README.md b/crates/alloy-provider/README.md new file mode 100644 index 0000000000..37a75f1b32 --- /dev/null +++ b/crates/alloy-provider/README.md @@ -0,0 +1,60 @@ +# Alloy Provider for Reth + +This crate provides an implementation of reth's `StateProviderFactory` and related traits that fetches state data via RPC instead of from a local database. + +Originally created by [cakevm](https://github.com/cakevm/alloy-reth-provider). + +## Features + +- Implements `StateProviderFactory` for remote RPC state access +- Supports Ethereum networks +- Useful for testing without requiring a full database +- Can be used with reth ExEx (Execution Extensions) for testing + +## Usage + +```rust +use alloy_provider::ProviderBuilder; +use reth_alloy_provider::AlloyRethProvider; +use reth_ethereum_node::EthereumNode; + +// Initialize provider +let provider = ProviderBuilder::new() + .builtin("https://eth.merkle.io") + .await + .unwrap(); + +// Create database provider with NodeTypes +let db_provider = AlloyRethProvider::new(provider, EthereumNode); + +// Get state at specific block +let state = db_provider.state_by_block_id(BlockId::number(16148323)).unwrap(); +``` + +## Configuration + +The provider can be configured with custom settings: + +```rust +use reth_alloy_provider::{AlloyRethProvider, AlloyRethProviderConfig}; +use reth_ethereum_node::EthereumNode; + +let config = AlloyRethProviderConfig { + compute_state_root: true, // Enable state root computation +}; + +let db_provider = AlloyRethProvider::new_with_config(provider, EthereumNode, config); +``` + +## Technical Details + +The provider uses `alloy_network::AnyNetwork` for network operations, providing compatibility with various Ethereum-based networks while maintaining the expected block structure with headers. + +## License + +Licensed under either of: + +- Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) + +at your option. \ No newline at end of file diff --git a/crates/alloy-provider/src/lib.rs b/crates/alloy-provider/src/lib.rs new file mode 100644 index 0000000000..318ecec8b2 --- /dev/null +++ b/crates/alloy-provider/src/lib.rs @@ -0,0 +1,1460 @@ +//! # Alloy Provider for Reth +//! +//! This crate provides an implementation of reth's `StateProviderFactory` and related traits +//! that fetches state data via RPC instead of from a local database. +//! +//! Originally created by [cakevm](https://github.com/cakevm/alloy-reth-provider). +//! +//! ## Features +//! +//! - Implements `StateProviderFactory` for remote RPC state access +//! - Supports Ethereum and Optimism network +//! - Useful for testing without requiring a full database +//! - Can be used with reth ExEx (Execution Extensions) for testing + +#![doc( + html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png", + html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256", + issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" +)] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] + +use alloy_consensus::BlockHeader; +use alloy_network::{primitives::HeaderResponse, BlockResponse}; +use alloy_primitives::{Address, BlockHash, BlockNumber, StorageKey, TxNumber, B256, U256}; +use alloy_provider::{network::Network, Provider}; +use alloy_rpc_types::BlockId; +use alloy_rpc_types_engine::ForkchoiceState; +use reth_chainspec::{ChainInfo, ChainSpecProvider}; +use reth_db_api::mock::{DatabaseMock, TxMock}; +use reth_errors::ProviderError; +use reth_node_types::{BlockTy, HeaderTy, NodeTypes, PrimitivesTy, ReceiptTy, TxTy}; +use reth_primitives::{ + Account, Bytecode, RecoveredBlock, SealedBlock, SealedHeader, TransactionMeta, +}; +use reth_provider::{ + AccountReader, BlockHashReader, BlockIdReader, BlockNumReader, BlockReader, CanonChainTracker, + CanonStateNotification, CanonStateNotifications, CanonStateSubscriptions, + ChainStateBlockReader, ChainStateBlockWriter, ChangeSetReader, DatabaseProviderFactory, + HeaderProvider, PruneCheckpointReader, ReceiptProvider, StageCheckpointReader, StateProvider, + StateProviderBox, StateProviderFactory, StateReader, StateRootProvider, StorageReader, + TransactionVariant, TransactionsProvider, +}; +use reth_prune_types::{PruneCheckpoint, PruneSegment}; +use reth_stages_types::{StageCheckpoint, StageId}; +use reth_storage_api::{BlockBodyIndicesProvider, DBProvider, NodePrimitivesProvider, StatsReader}; +use reth_trie::{updates::TrieUpdates, AccountProof, HashedPostState, MultiProof, TrieInput}; +use std::{ + collections::BTreeMap, + future::Future, + ops::{RangeBounds, RangeInclusive}, + sync::Arc, +}; +use tokio::{runtime::Handle, sync::broadcast}; +use tracing::trace; + +/// Configuration for `AlloyRethProvider` +#[derive(Debug, Clone, Default)] +pub struct AlloyRethProviderConfig { + /// Whether to compute state root when creating execution outcomes + pub compute_state_root: bool, +} + +impl AlloyRethProviderConfig { + /// Sets whether to compute state root when creating execution outcomes + pub const fn with_compute_state_root(mut self, compute: bool) -> Self { + self.compute_state_root = compute; + self + } +} + +/// A provider implementation that uses Alloy RPC to fetch state data +/// +/// This provider implements reth's `StateProviderFactory` and related traits, +/// allowing it to be used as a drop-in replacement for database-backed providers +/// in scenarios where RPC access is preferred (e.g., testing). +/// +/// The provider type is generic over the network type N (defaulting to `AnyNetwork`), +/// but the current implementation is specialized for `alloy_network::AnyNetwork` +/// as it needs to access block header fields directly. +#[derive(Clone)] +pub struct AlloyRethProvider +where + Node: NodeTypes, +{ + /// The underlying Alloy provider + provider: P, + /// Node types marker + node_types: std::marker::PhantomData, + /// Network marker + network: std::marker::PhantomData, + /// Broadcast channel for canon state notifications + canon_state_notification: broadcast::Sender>>, + /// Configuration for the provider + config: AlloyRethProviderConfig, + /// Cached chain spec + chain_spec: Arc, +} + +impl std::fmt::Debug for AlloyRethProvider { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AlloyRethProvider").field("config", &self.config).finish() + } +} + +impl AlloyRethProvider { + /// Creates a new `AlloyRethProvider` with default configuration + pub fn new(provider: P) -> Self + where + Node::ChainSpec: Default, + { + Self::new_with_config(provider, AlloyRethProviderConfig::default()) + } + + /// Creates a new `AlloyRethProvider` with custom configuration + pub fn new_with_config(provider: P, config: AlloyRethProviderConfig) -> Self + where + Node::ChainSpec: Default, + { + let (canon_state_notification, _) = broadcast::channel(1); + Self { + provider, + node_types: std::marker::PhantomData, + network: std::marker::PhantomData, + canon_state_notification, + config, + chain_spec: Arc::new(Node::ChainSpec::default()), + } + } + + /// Helper function to execute async operations in a blocking context + fn block_on_async(&self, fut: F) -> T + where + F: Future, + { + tokio::task::block_in_place(move || Handle::current().block_on(fut)) + } +} + +impl AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + /// Helper function to create a state provider for a given block ID + fn create_state_provider(&self, block_id: BlockId) -> AlloyRethStateProvider { + AlloyRethStateProvider::with_chain_spec( + self.provider.clone(), + block_id, + self.chain_spec.clone(), + ) + } + + /// Helper function to get state provider by block number + fn state_by_block_number( + &self, + block_number: BlockNumber, + ) -> Result { + Ok(Box::new(self.create_state_provider(BlockId::number(block_number)))) + } +} + +// Implementation note: While the types are generic over Network N, the trait implementations +// are specialized for AnyNetwork because they need to access block header fields. +// This allows the types to be instantiated with any network while the actual functionality +// requires AnyNetwork. Future improvements could add trait bounds for networks with +// compatible block structures. +impl BlockHashReader for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_hash(&self, number: BlockNumber) -> Result, ProviderError> { + let block = self.block_on_async(async { + self.provider.get_block_by_number(number.into()).await.map_err(ProviderError::other) + })?; + Ok(block.map(|b| b.header().hash())) + } + + fn canonical_hashes_range( + &self, + _start: BlockNumber, + _end: BlockNumber, + ) -> Result, ProviderError> { + // Would need to make multiple RPC calls + Err(ProviderError::UnsupportedProvider) + } +} + +impl BlockNumReader for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn chain_info(&self) -> Result { + // For RPC provider, we can't get full chain info + Err(ProviderError::UnsupportedProvider) + } + + fn best_block_number(&self) -> Result { + self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + }) + } + + fn last_block_number(&self) -> Result { + self.best_block_number() + } + + fn block_number(&self, hash: B256) -> Result, ProviderError> { + let block = self.block_on_async(async { + self.provider.get_block_by_hash(hash).await.map_err(ProviderError::other) + })?; + Ok(block.map(|b| b.header().number())) + } +} + +impl BlockIdReader for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_number_for_id(&self, block_id: BlockId) -> Result, ProviderError> { + match block_id { + BlockId::Hash(hash) => { + let block = self.block_on_async(async { + self.provider + .get_block_by_hash(hash.block_hash) + .await + .map_err(ProviderError::other) + })?; + Ok(block.map(|b| b.header().number())) + } + BlockId::Number(number_or_tag) => match number_or_tag { + alloy_rpc_types::BlockNumberOrTag::Number(num) => Ok(Some(num)), + alloy_rpc_types::BlockNumberOrTag::Latest => self.block_on_async(async { + self.provider.get_block_number().await.map(Some).map_err(ProviderError::other) + }), + _ => Ok(None), + }, + } + } + + fn pending_block_num_hash(&self) -> Result, ProviderError> { + // RPC doesn't provide pending block number and hash together + Err(ProviderError::UnsupportedProvider) + } + + fn safe_block_num_hash(&self) -> Result, ProviderError> { + // RPC doesn't provide safe block number and hash + Err(ProviderError::UnsupportedProvider) + } + + fn finalized_block_num_hash(&self) -> Result, ProviderError> { + // RPC doesn't provide finalized block number and hash + Err(ProviderError::UnsupportedProvider) + } +} + +impl StateProviderFactory for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn latest(&self) -> Result { + trace!(target: "alloy-provider", "Getting latest state provider"); + + let block_number = self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + })?; + + self.state_by_block_number(block_number) + } + + fn state_by_block_id(&self, block_id: BlockId) -> Result { + Ok(Box::new(self.create_state_provider(block_id))) + } + + fn state_by_block_number_or_tag( + &self, + number_or_tag: alloy_rpc_types::BlockNumberOrTag, + ) -> Result { + match number_or_tag { + alloy_rpc_types::BlockNumberOrTag::Latest => self.latest(), + alloy_rpc_types::BlockNumberOrTag::Pending => self.pending(), + alloy_rpc_types::BlockNumberOrTag::Number(num) => self.state_by_block_number(num), + _ => Err(ProviderError::UnsupportedProvider), + } + } + + fn history_by_block_number( + &self, + block_number: BlockNumber, + ) -> Result { + self.state_by_block_number(block_number) + } + + fn history_by_block_hash( + &self, + block_hash: BlockHash, + ) -> Result { + self.state_by_block_hash(block_hash) + } + + fn state_by_block_hash( + &self, + block_hash: BlockHash, + ) -> Result { + trace!(target: "alloy-provider", ?block_hash, "Getting state provider by block hash"); + + let block = self.block_on_async(async { + self.provider + .get_block_by_hash(block_hash) + .await + .map_err(ProviderError::other)? + .ok_or(ProviderError::BlockHashNotFound(block_hash)) + })?; + + let block_number = block.header().number(); + Ok(Box::new(self.create_state_provider(BlockId::number(block_number)))) + } + + fn pending(&self) -> Result { + trace!(target: "alloy-provider", "Getting pending state provider"); + self.latest() + } + + fn pending_state_by_hash( + &self, + _block_hash: B256, + ) -> Result, ProviderError> { + // RPC provider doesn't support pending state by hash + Err(ProviderError::UnsupportedProvider) + } +} + +impl DatabaseProviderFactory for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type DB = DatabaseMock; + type ProviderRW = AlloyRethStateProvider; + type Provider = AlloyRethStateProvider; + + fn database_provider_ro(&self) -> Result { + // RPC provider returns a new state provider + let block_number = self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + })?; + + Ok(self.create_state_provider(BlockId::number(block_number))) + } + + fn database_provider_rw(&self) -> Result { + // RPC provider returns a new state provider + let block_number = self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + })?; + + Ok(self.create_state_provider(BlockId::number(block_number))) + } +} + +impl CanonChainTracker for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Header = alloy_consensus::Header; + fn on_forkchoice_update_received(&self, _update: &ForkchoiceState) { + // No-op for RPC provider + } + + fn last_received_update_timestamp(&self) -> Option { + None + } + + fn set_canonical_head(&self, _header: SealedHeader) { + // No-op for RPC provider + } + + fn set_safe(&self, _header: SealedHeader) { + // No-op for RPC provider + } + + fn set_finalized(&self, _header: SealedHeader) { + // No-op for RPC provider + } +} + +impl NodePrimitivesProvider for AlloyRethProvider +where + P: Send + Sync, + N: Send + Sync, + Node: NodeTypes, +{ + type Primitives = PrimitivesTy; +} + +impl CanonStateSubscriptions for AlloyRethProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn subscribe_to_canonical_state(&self) -> CanonStateNotifications> { + trace!(target: "alloy-provider", "Subscribing to canonical state notifications"); + self.canon_state_notification.subscribe() + } +} + +impl ChainSpecProvider for AlloyRethProvider +where + P: Send + Sync, + N: Send + Sync, + Node: NodeTypes, + Node::ChainSpec: Default, +{ + type ChainSpec = Node::ChainSpec; + + fn chain_spec(&self) -> Arc { + self.chain_spec.clone() + } +} + +/// State provider implementation that fetches state via RPC +#[derive(Clone)] +pub struct AlloyRethStateProvider +where + Node: NodeTypes, +{ + /// The underlying Alloy provider + provider: P, + /// The block ID to fetch state at + block_id: BlockId, + /// Node types marker + node_types: std::marker::PhantomData, + /// Network marker + network: std::marker::PhantomData, + /// Cached chain spec (shared with parent provider) + chain_spec: Option>, +} + +impl std::fmt::Debug + for AlloyRethStateProvider +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AlloyRethStateProvider") + .field("provider", &self.provider) + .field("block_id", &self.block_id) + .finish() + } +} + +impl AlloyRethStateProvider { + /// Creates a new state provider for the given block + pub const fn new( + provider: P, + block_id: BlockId, + _primitives: std::marker::PhantomData, + ) -> Self { + Self { + provider, + block_id, + node_types: std::marker::PhantomData, + network: std::marker::PhantomData, + chain_spec: None, + } + } + + /// Creates a new state provider with a cached chain spec + pub const fn with_chain_spec( + provider: P, + block_id: BlockId, + chain_spec: Arc, + ) -> Self { + Self { + provider, + block_id, + node_types: std::marker::PhantomData, + network: std::marker::PhantomData, + chain_spec: Some(chain_spec), + } + } + + /// Helper function to execute async operations in a blocking context + fn block_on_async(&self, fut: F) -> T + where + F: Future, + { + tokio::task::block_in_place(move || Handle::current().block_on(fut)) + } + + /// Helper function to create a new state provider with a different block ID + fn with_block_id(&self, block_id: BlockId) -> Self { + Self { + provider: self.provider.clone(), + block_id, + node_types: self.node_types, + network: self.network, + chain_spec: self.chain_spec.clone(), + } + } + + /// Get account information from RPC + fn get_account(&self, address: Address) -> Result, ProviderError> + where + P: Provider + Clone + 'static, + N: Network, + { + self.block_on_async(async { + // Get account info in a single RPC call + let account_info = self + .provider + .get_account_info(address) + .block_id(self.block_id) + .await + .map_err(ProviderError::other)?; + + // Only return account if it exists (has balance, nonce, or code) + if account_info.balance.is_zero() && + account_info.nonce == 0 && + account_info.code.is_empty() + { + Ok(None) + } else { + let bytecode = if account_info.code.is_empty() { + None + } else { + Some(Bytecode::new_raw(account_info.code)) + }; + + Ok(Some(Account { + balance: account_info.balance, + nonce: account_info.nonce, + bytecode_hash: bytecode.as_ref().map(|b| b.hash_slow()), + })) + } + }) + } +} + +impl StateProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn storage( + &self, + address: Address, + storage_key: StorageKey, + ) -> Result, ProviderError> { + self.block_on_async(async { + let value = self + .provider + .get_storage_at(address, storage_key.into()) + .block_id(self.block_id) + .await + .map_err(ProviderError::other)?; + + if value.is_zero() { + Ok(None) + } else { + Ok(Some(value)) + } + }) + } + + fn bytecode_by_hash(&self, _code_hash: &B256) -> Result, ProviderError> { + // Cannot fetch bytecode by hash via RPC + Err(ProviderError::UnsupportedProvider) + } + + fn account_code(&self, addr: &Address) -> Result, ProviderError> { + self.block_on_async(async { + let code = self + .provider + .get_code_at(*addr) + .block_id(self.block_id) + .await + .map_err(ProviderError::other)?; + + if code.is_empty() { + Ok(None) + } else { + Ok(Some(Bytecode::new_raw(code))) + } + }) + } + + fn account_balance(&self, addr: &Address) -> Result, ProviderError> { + self.get_account(*addr).map(|acc| acc.map(|a| a.balance)) + } + + fn account_nonce(&self, addr: &Address) -> Result, ProviderError> { + self.get_account(*addr).map(|acc| acc.map(|a| a.nonce)) + } +} + +impl AccountReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn basic_account(&self, address: &Address) -> Result, ProviderError> { + self.get_account(*address) + } +} + +impl StateRootProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn state_root(&self, _state: HashedPostState) -> Result { + // Return the state root from the block + self.block_on_async(async { + let block = self + .provider + .get_block(self.block_id) + .await + .map_err(ProviderError::other)? + .ok_or(ProviderError::HeaderNotFound(0.into()))?; + + Ok(block.header().state_root()) + }) + } + + fn state_root_from_nodes(&self, _input: TrieInput) -> Result { + Err(ProviderError::UnsupportedProvider) + } + + fn state_root_with_updates( + &self, + _state: HashedPostState, + ) -> Result<(B256, TrieUpdates), ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn state_root_from_nodes_with_updates( + &self, + _input: TrieInput, + ) -> Result<(B256, TrieUpdates), ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl StorageReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn plain_state_storages( + &self, + addresses_with_keys: impl IntoIterator)>, + ) -> Result)>, ProviderError> { + let mut results = Vec::new(); + + for (address, keys) in addresses_with_keys { + let mut values = Vec::new(); + for key in keys { + let value = self.storage(address, key)?.unwrap_or_default(); + values.push(reth_primitives::StorageEntry::new(key, value)); + } + results.push((address, values)); + } + + Ok(results) + } + + fn changed_storages_with_range( + &self, + _range: RangeInclusive, + ) -> Result>, ProviderError> { + Ok(BTreeMap::new()) + } + + fn changed_storages_and_blocks_with_range( + &self, + _range: RangeInclusive, + ) -> Result>, ProviderError> { + Ok(BTreeMap::new()) + } +} + +impl reth_storage_api::StorageRootProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn storage_root( + &self, + _address: Address, + _hashed_storage: reth_trie::HashedStorage, + ) -> Result { + // RPC doesn't provide storage root computation + Err(ProviderError::UnsupportedProvider) + } + + fn storage_proof( + &self, + _address: Address, + _slot: B256, + _hashed_storage: reth_trie::HashedStorage, + ) -> Result { + Err(ProviderError::UnsupportedProvider) + } + + fn storage_multiproof( + &self, + _address: Address, + _slots: &[B256], + _hashed_storage: reth_trie::HashedStorage, + ) -> Result { + Err(ProviderError::UnsupportedProvider) + } +} + +impl reth_storage_api::StateProofProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn proof( + &self, + _input: TrieInput, + _address: Address, + _slots: &[B256], + ) -> Result { + Err(ProviderError::UnsupportedProvider) + } + + fn multiproof( + &self, + _input: TrieInput, + _targets: reth_trie::MultiProofTargets, + ) -> Result { + Err(ProviderError::UnsupportedProvider) + } + + fn witness( + &self, + _input: TrieInput, + _target: HashedPostState, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl reth_storage_api::HashedPostStateProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn hashed_post_state(&self, _bundle_state: &revm::database::BundleState) -> HashedPostState { + // Return empty hashed post state for RPC provider + HashedPostState::default() + } +} + +impl StateReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Receipt = ReceiptTy; + + fn get_state( + &self, + _block: BlockNumber, + ) -> Result>, ProviderError> { + // RPC doesn't provide execution outcomes + Err(ProviderError::UnsupportedProvider) + } +} + +impl DBProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Tx = TxMock; + + fn tx_ref(&self) -> &Self::Tx { + // We can't use a static here since TxMock doesn't allow direct construction + // This is fine since we're just returning a mock transaction + unimplemented!("tx_ref not supported for RPC provider") + } + + fn tx_mut(&mut self) -> &mut Self::Tx { + unimplemented!("tx_mut not supported for RPC provider") + } + + fn into_tx(self) -> Self::Tx { + TxMock::default() + } + + fn prune_modes_ref(&self) -> &reth_prune_types::PruneModes { + unimplemented!("prune modes not supported for RPC provider") + } + + fn disable_long_read_transaction_safety(self) -> Self { + // No-op for RPC provider + self + } +} + +impl BlockNumReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn chain_info(&self) -> Result { + self.block_on_async(async { + let block = self + .provider + .get_block(self.block_id) + .await + .map_err(ProviderError::other)? + .ok_or(ProviderError::HeaderNotFound(0.into()))?; + + Ok(ChainInfo { best_hash: block.header().hash(), best_number: block.header().number() }) + }) + } + + fn best_block_number(&self) -> Result { + self.block_on_async(async { + self.provider.get_block_number().await.map_err(ProviderError::other) + }) + } + + fn last_block_number(&self) -> Result { + self.best_block_number() + } + + fn block_number(&self, hash: B256) -> Result, ProviderError> { + self.block_on_async(async { + let block = + self.provider.get_block_by_hash(hash).await.map_err(ProviderError::other)?; + + Ok(block.map(|b| b.header().number())) + }) + } +} + +impl BlockHashReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_hash(&self, number: u64) -> Result, ProviderError> { + self.block_on_async(async { + let block = self + .provider + .get_block_by_number(number.into()) + .await + .map_err(ProviderError::other)?; + + Ok(block.map(|b| b.header().hash())) + }) + } + + fn canonical_hashes_range( + &self, + _start: BlockNumber, + _end: BlockNumber, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl BlockIdReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_number_for_id( + &self, + _block_id: BlockId, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn pending_block_num_hash(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn safe_block_num_hash(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn finalized_block_num_hash(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl BlockReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Block = BlockTy; + + fn find_block_by_hash( + &self, + _hash: B256, + _source: reth_provider::BlockSource, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn block( + &self, + _id: alloy_rpc_types::BlockHashOrNumber, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn pending_block(&self) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn pending_block_and_receipts( + &self, + ) -> Result, Vec)>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn recovered_block( + &self, + _id: alloy_rpc_types::BlockHashOrNumber, + _transaction_kind: TransactionVariant, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_block_with_senders( + &self, + _id: alloy_rpc_types::BlockHashOrNumber, + _transaction_kind: TransactionVariant, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn block_range( + &self, + _range: RangeInclusive, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn block_with_senders_range( + &self, + _range: RangeInclusive, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn recovered_block_range( + &self, + _range: RangeInclusive, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl TransactionsProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Transaction = TxTy; + + fn transaction_id(&self, _tx_hash: B256) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_id(&self, _id: TxNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_id_unhashed( + &self, + _id: TxNumber, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_hash(&self, _hash: B256) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_by_hash_with_meta( + &self, + _hash: B256, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_block(&self, _id: TxNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transactions_by_block( + &self, + _block: alloy_rpc_types::BlockHashOrNumber, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transactions_by_block_range( + &self, + _range: impl RangeBounds, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transactions_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn senders_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn transaction_sender(&self, _id: TxNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl ReceiptProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Receipt = ReceiptTy; + + fn receipt(&self, _id: TxNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipt_by_hash(&self, _hash: B256) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipts_by_block( + &self, + _block: alloy_rpc_types::BlockHashOrNumber, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipts_by_tx_range( + &self, + _range: impl RangeBounds, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn receipts_by_block_range( + &self, + _range: RangeInclusive, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl HeaderProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + type Header = HeaderTy; + + fn header(&self, _block_hash: &BlockHash) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_by_number(&self, _num: BlockNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_td(&self, _hash: &BlockHash) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn header_td_by_number(&self, _number: BlockNumber) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn headers_range( + &self, + _range: impl RangeBounds, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_header( + &self, + _number: BlockNumber, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_headers_range( + &self, + _range: impl RangeBounds, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn sealed_headers_while( + &self, + _range: impl RangeBounds, + _predicate: impl FnMut(&SealedHeader>) -> bool, + ) -> Result>>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl PruneCheckpointReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn get_prune_checkpoint( + &self, + _segment: PruneSegment, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn get_prune_checkpoints(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl StageCheckpointReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn get_stage_checkpoint(&self, _id: StageId) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn get_stage_checkpoint_progress( + &self, + _id: StageId, + ) -> Result>, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn get_all_checkpoints(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl ChangeSetReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn account_block_changeset( + &self, + _block_number: BlockNumber, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl StateProviderFactory for AlloyRethStateProvider +where + P: Provider + Clone + 'static + Send + Sync, + Node: NodeTypes + 'static, + Node::ChainSpec: Send + Sync, + N: Network, + Self: Clone + 'static, +{ + fn latest(&self) -> Result { + Ok(Box::new(self.clone()) as StateProviderBox) + } + + fn state_by_block_id(&self, block_id: BlockId) -> Result { + Ok(Box::new(self.with_block_id(block_id))) + } + + fn state_by_block_number_or_tag( + &self, + number_or_tag: alloy_rpc_types::BlockNumberOrTag, + ) -> Result { + match number_or_tag { + alloy_rpc_types::BlockNumberOrTag::Latest => self.latest(), + alloy_rpc_types::BlockNumberOrTag::Pending => self.pending(), + alloy_rpc_types::BlockNumberOrTag::Number(num) => self.history_by_block_number(num), + _ => Err(ProviderError::UnsupportedProvider), + } + } + + fn history_by_block_number( + &self, + block_number: BlockNumber, + ) -> Result { + Ok(Box::new(Self::new( + self.provider.clone(), + BlockId::number(block_number), + self.node_types, + ))) + } + + fn history_by_block_hash( + &self, + block_hash: BlockHash, + ) -> Result { + Ok(Box::new(self.with_block_id(BlockId::hash(block_hash)))) + } + + fn state_by_block_hash( + &self, + block_hash: BlockHash, + ) -> Result { + self.history_by_block_hash(block_hash) + } + + fn pending(&self) -> Result { + Ok(Box::new(self.clone())) + } + + fn pending_state_by_hash( + &self, + _block_hash: B256, + ) -> Result, ProviderError> { + // RPC provider doesn't support pending state by hash + Err(ProviderError::UnsupportedProvider) + } +} + +impl ChainSpecProvider for AlloyRethStateProvider +where + P: Send + Sync + std::fmt::Debug, + N: Send + Sync, + Node: NodeTypes, + Node::ChainSpec: Default, +{ + type ChainSpec = Node::ChainSpec; + + fn chain_spec(&self) -> Arc { + if let Some(chain_spec) = &self.chain_spec { + chain_spec.clone() + } else { + // Fallback for when chain_spec is not provided + Arc::new(Node::ChainSpec::default()) + } + } +} + +// Note: FullExecutionDataProvider is already implemented via the blanket implementation +// for types that implement both ExecutionDataProvider and BlockExecutionForkProvider + +impl StatsReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn count_entries(&self) -> Result { + Ok(0) + } +} + +impl BlockBodyIndicesProvider for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn block_body_indices( + &self, + _num: u64, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn block_body_indices_range( + &self, + _range: RangeInclusive, + ) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl NodePrimitivesProvider for AlloyRethStateProvider +where + P: Send + Sync + std::fmt::Debug, + N: Send + Sync, + Node: NodeTypes, +{ + type Primitives = PrimitivesTy; +} + +impl ChainStateBlockReader for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn last_finalized_block_number(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn last_safe_block_number(&self) -> Result, ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +impl ChainStateBlockWriter for AlloyRethStateProvider +where + P: Provider + Clone + 'static, + N: Network, + Node: NodeTypes, +{ + fn save_finalized_block_number(&self, _block_number: BlockNumber) -> Result<(), ProviderError> { + Err(ProviderError::UnsupportedProvider) + } + + fn save_safe_block_number(&self, _block_number: BlockNumber) -> Result<(), ProviderError> { + Err(ProviderError::UnsupportedProvider) + } +} + +// Async database wrapper for revm compatibility +#[allow(dead_code)] +#[derive(Debug, Clone)] +struct AsyncDbWrapper { + provider: P, + block_id: BlockId, + network: std::marker::PhantomData, +} + +#[allow(dead_code)] +impl AsyncDbWrapper { + const fn new(provider: P, block_id: BlockId) -> Self { + Self { provider, block_id, network: std::marker::PhantomData } + } + + /// Helper function to execute async operations in a blocking context + fn block_on_async(&self, fut: F) -> T + where + F: Future, + { + tokio::task::block_in_place(move || Handle::current().block_on(fut)) + } +} + +impl revm::Database for AsyncDbWrapper +where + P: Provider + Clone + 'static, + N: Network, +{ + type Error = ProviderError; + + fn basic(&mut self, address: Address) -> Result, Self::Error> { + self.block_on_async(async { + let account_info = self + .provider + .get_account_info(address) + .block_id(self.block_id) + .await + .map_err(ProviderError::other)?; + + // Only return account if it exists + if account_info.balance.is_zero() && + account_info.nonce == 0 && + account_info.code.is_empty() + { + Ok(None) + } else { + let code_hash = if account_info.code.is_empty() { + revm_primitives::KECCAK_EMPTY + } else { + revm_primitives::keccak256(&account_info.code) + }; + + Ok(Some(revm::state::AccountInfo { + balance: account_info.balance, + nonce: account_info.nonce, + code_hash, + code: if account_info.code.is_empty() { + None + } else { + Some(revm::bytecode::Bytecode::new_raw(account_info.code)) + }, + })) + } + }) + } + + fn code_by_hash(&mut self, _code_hash: B256) -> Result { + // Cannot fetch bytecode by hash via RPC + Ok(revm::bytecode::Bytecode::default()) + } + + fn storage(&mut self, address: Address, index: U256) -> Result { + let index = B256::from(index); + + self.block_on_async(async { + self.provider + .get_storage_at(address, index.into()) + .block_id(self.block_id) + .await + .map_err(ProviderError::other) + }) + } + + fn block_hash(&mut self, number: u64) -> Result { + self.block_on_async(async { + let block = self + .provider + .get_block_by_number(number.into()) + .await + .map_err(ProviderError::other)? + .ok_or(ProviderError::HeaderNotFound(number.into()))?; + + Ok(block.header().hash()) + }) + } +}