diff --git a/Cargo.lock b/Cargo.lock index 6d6cc200942..16b7d3f13e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6115,7 +6115,11 @@ dependencies = [ "clap", "eyre", "futures-util", + "reth-chainspec", "reth-cli-util", + "reth-db", + "reth-db-api", + "reth-node-builder", "reth-optimism-chainspec", "reth-optimism-cli", "reth-optimism-consensus", @@ -9535,6 +9539,7 @@ dependencies = [ "alloy-rpc-types-debug", "alloy-rpc-types-engine", "alloy-rpc-types-eth", + "alloy-serde", "alloy-transport", "alloy-transport-http", "async-trait", @@ -9564,8 +9569,10 @@ dependencies = [ "reth-optimism-forks", "reth-optimism-payload-builder", "reth-optimism-primitives", + "reth-optimism-trie", "reth-optimism-txpool", "reth-primitives-traits", + "reth-provider", "reth-rpc", "reth-rpc-api", "reth-rpc-convert", diff --git a/crates/optimism/bin/Cargo.toml b/crates/optimism/bin/Cargo.toml index 9f6ffcedb56..ac3e42247ca 100644 --- a/crates/optimism/bin/Cargo.toml +++ b/crates/optimism/bin/Cargo.toml @@ -21,6 +21,10 @@ reth-optimism-primitives.workspace = true reth-optimism-forks.workspace = true reth-optimism-exex.workspace = true reth-optimism-trie.workspace = true +reth-node-builder.workspace = true +reth-db-api.workspace = true +reth-chainspec.workspace = true +reth-db.workspace = true clap = { workspace = true, features = ["derive", "env"] } tracing.workspace = true diff --git a/crates/optimism/bin/src/main.rs b/crates/optimism/bin/src/main.rs index 8847d70af34..5feb5e59b6e 100644 --- a/crates/optimism/bin/src/main.rs +++ b/crates/optimism/bin/src/main.rs @@ -1,11 +1,16 @@ #![allow(missing_docs, rustdoc::missing_crate_level_docs)] use clap::{builder::ArgPredicate, Parser}; +use eyre::ErrReport; use futures_util::FutureExt; +use reth_db::DatabaseEnv; +use reth_node_builder::{NodeBuilder, WithLaunchContext}; +use reth_optimism_chainspec::OpChainSpec; use reth_optimism_cli::{chainspec::OpChainSpecParser, Cli}; use reth_optimism_exex::OpProofsExEx; use reth_optimism_node::{args::RollupArgs, OpNode}; -use reth_optimism_trie::{db::MdbxProofsStorage, InMemoryProofsStorage}; +use reth_optimism_rpc::eth::proofs::{EthApiExt, EthApiOverrideServer}; +use reth_optimism_trie::{db::MdbxProofsStorage, InMemoryProofsStorage, OpProofsStorage}; use tracing::info; use std::{path::PathBuf, sync::Arc}; @@ -60,6 +65,33 @@ struct Args { pub proofs_history_window: u64, } +async fn launch_node_with_storage( + builder: WithLaunchContext, OpChainSpec>>, + args: Args, + storage: S, +) -> eyre::Result<(), ErrReport> +where + S: OpProofsStorage + Clone + 'static, +{ + let storage_clone = storage.clone(); + let proofs_history_enabled = args.proofs_history; + let handle = builder + .node(OpNode::new(args.rollup_args)) + .install_exex_if(proofs_history_enabled, "proofs-history", async move |exex_context| { + Ok(OpProofsExEx::new(exex_context, storage, args.proofs_history_window).run().boxed()) + }) + .extend_rpc_modules(move |ctx| { + if proofs_history_enabled { + let api_ext = EthApiExt::new(ctx.registry.eth_api().clone(), storage_clone); + ctx.modules.replace_configured(api_ext.into_rpc())?; + } + Ok(()) + }) + .launch_with_debug_capabilities() + .await?; + handle.node_exit_future.await +} + fn main() { reth_cli_util::sigsegv_handler::install(); @@ -73,42 +105,24 @@ fn main() { if let Err(err) = Cli::::parse().run(async move |builder, args| { info!(target: "reth::cli", "Launching node"); - let rollup_args = args.rollup_args; + if args.proofs_history_storage_in_mem { + let storage = Arc::new(InMemoryProofsStorage::new()); + launch_node_with_storage(builder, args.clone(), storage).await?; + } else { + let path = args + .proofs_history_storage_path + .clone() + .expect("Path must be provided if not using in-memory storage"); + info!(target: "reth::cli", "Using on-disk storage for proofs history"); - let handle = builder - .node(OpNode::new(rollup_args)) - .install_exex_if(args.proofs_history, "proofs-history", async move |exex_context| { - if args.proofs_history_storage_in_mem { - info!(target: "reth::cli", "Using in-memory storage for proofs history"); - - let storage = InMemoryProofsStorage::new(); - Ok(OpProofsExEx::new( - exex_context, - Arc::new(storage), - args.proofs_history_window, - ) - .run() - .boxed()) - } else { - let path = args - .proofs_history_storage_path - .expect("Path must be provided if not using in-memory storage"); - info!(target: "reth::cli", "Using on-disk storage for proofs history"); + let storage = Arc::new( + MdbxProofsStorage::new(&path) + .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?, + ); + launch_node_with_storage(builder, args.clone(), storage).await?; + } - let storage = MdbxProofsStorage::new(&path) - .map_err(|e| eyre::eyre!("Failed to create MdbxProofsStorage: {e}"))?; - Ok(OpProofsExEx::new( - exex_context, - Arc::new(storage), - args.proofs_history_window, - ) - .run() - .boxed()) - } - }) - .launch_with_debug_capabilities() - .await?; - handle.node_exit_future.await + Ok(()) }) { eprintln!("Error: {err:?}"); std::process::exit(1); diff --git a/crates/optimism/exex/src/lib.rs b/crates/optimism/exex/src/lib.rs index edab8542075..7ac051722b8 100644 --- a/crates/optimism/exex/src/lib.rs +++ b/crates/optimism/exex/src/lib.rs @@ -8,7 +8,6 @@ use reth_node_api::{FullNodeComponents, NodePrimitives}; use reth_node_types::NodeTypes; use reth_optimism_trie::{BackfillJob, OpProofsStorage}; use reth_provider::{BlockNumReader, DBProvider, DatabaseProviderFactory}; -use std::sync::Arc; /// OP Proofs ExEx - processes blocks and tracks state changes within fault proof window. /// @@ -19,13 +18,13 @@ use std::sync::Arc; pub struct OpProofsExEx where Node: FullNodeComponents, - S: OpProofsStorage, + S: OpProofsStorage + Clone, { /// The ExEx context containing the node related utilities e.g. provider, notifications, /// events. ctx: ExExContext, /// The type of storage DB. - storage: Arc, + storage: S, /// The window to span blocks for proofs history. Value is the number of blocks, received as /// cli arg. #[expect(dead_code)] @@ -36,7 +35,7 @@ impl OpProofsExEx where Node: FullNodeComponents>, Primitives: NodePrimitives, - S: OpProofsStorage, + S: OpProofsStorage + Clone, { /// Main execution loop for the ExEx pub async fn run(mut self) -> eyre::Result<()> { diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index acbc491f648..8f08050e543 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -29,6 +29,7 @@ reth-chainspec.workspace = true reth-chain-state.workspace = true reth-rpc-engine-api.workspace = true reth-rpc-convert.workspace = true +reth-provider.workspace = true # op-reth reth-optimism-evm.workspace = true @@ -38,6 +39,7 @@ reth-optimism-txpool.workspace = true # TODO remove node-builder import reth-optimism-primitives = { workspace = true, features = ["reth-codec", "serde-bincode-compat", "serde"] } reth-optimism-forks.workspace = true +reth-optimism-trie.workspace = true # ethereum alloy-eips.workspace = true @@ -46,6 +48,7 @@ alloy-primitives.workspace = true alloy-rpc-client.workspace = true alloy-rpc-types-eth.workspace = true alloy-rpc-types-debug.workspace = true +alloy-serde.workspace = true alloy-transport.workspace = true alloy-transport-http.workspace = true alloy-consensus.workspace = true diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index 04887d98f4c..2837111bc11 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -1,6 +1,7 @@ //! OP-Reth `eth_` endpoint implementation. pub mod ext; +pub mod proofs; pub mod receipt; pub mod transaction; diff --git a/crates/optimism/rpc/src/eth/proofs.rs b/crates/optimism/rpc/src/eth/proofs.rs new file mode 100644 index 00000000000..a5c79d61862 --- /dev/null +++ b/crates/optimism/rpc/src/eth/proofs.rs @@ -0,0 +1,105 @@ +//! Historical proofs RPC server implementation. + +use alloy_eips::BlockId; +use alloy_primitives::Address; +use alloy_rpc_types_eth::EIP1186AccountProofResponse; +use alloy_serde::JsonStorageKey; +use async_trait::async_trait; +use derive_more::Constructor; +use jsonrpsee::proc_macros::rpc; +use jsonrpsee_core::RpcResult; +use jsonrpsee_types::error::ErrorObject; +use reth_optimism_trie::{provider::OpProofsStateProviderRef, OpProofsStorage}; +use reth_provider::{BlockIdReader, ProviderError, ProviderResult, StateProofProvider}; +use reth_rpc_api::eth::helpers::FullEthApi; +use reth_rpc_eth_types::EthApiError; + +#[cfg_attr(not(test), rpc(server, namespace = "eth"))] +#[cfg_attr(test, rpc(server, client, namespace = "eth"))] +pub trait EthApiOverride { + /// Returns the account and storage values of the specified account including the Merkle-proof. + /// This call can be used to verify that the data you are pulling from is not tampered with. + #[method(name = "getProof")] + async fn get_proof( + &self, + address: Address, + keys: Vec, + block_number: Option, + ) -> RpcResult; +} + +#[derive(Debug, Constructor)] +/// Overrides applied to the `eth_` namespace of the RPC API for historical proofs ExEx. +pub struct EthApiExt { + eth_api: Eth, + preimage_store: P, +} + +impl EthApiExt +where + Eth: FullEthApi + Send + Sync + 'static, + ErrorObject<'static>: From, + P: OpProofsStorage + Clone + 'static, +{ + async fn state_provider( + &self, + block_id: Option, + ) -> ProviderResult { + let block_id = block_id.unwrap_or_default(); + // Check whether the distance to the block exceeds the maximum configured window. + let block_number = self + .eth_api + .provider() + .block_number_for_id(block_id)? + .ok_or(EthApiError::HeaderNotFound(block_id)) + .map_err(ProviderError::other)?; + + let historical_provider = + self.eth_api.state_at_block_id(block_id).await.map_err(ProviderError::other)?; + + let (Some((latest_block_number, _)), Some((earliest_block_number, _))) = ( + self.preimage_store + .get_latest_block_number() + .await + .map_err(|e| ProviderError::Database(e.into()))?, + self.preimage_store + .get_earliest_block_number() + .await + .map_err(|e| ProviderError::Database(e.into()))?, + ) else { + // if no earliest block, db is empty - use historical provider + return Ok(historical_provider as Box); + }; + + if block_number < earliest_block_number || block_number > latest_block_number { + return Ok(historical_provider as Box); + } + + let external_overlay_provider = + OpProofsStateProviderRef::new(historical_provider, &self.preimage_store, block_number); + + Ok(Box::new(external_overlay_provider)) + } +} + +#[async_trait] +impl EthApiOverrideServer for EthApiExt +where + Eth: FullEthApi + Send + Sync + 'static, + ErrorObject<'static>: From, + P: OpProofsStorage + Clone + 'static, +{ + async fn get_proof( + &self, + address: Address, + keys: Vec, + block_number: Option, + ) -> RpcResult { + let state = self.state_provider(block_number).await.map_err(Into::into)?; + let storage_keys = keys.iter().map(|key| key.as_b256()).collect::>(); + + let proof = state.proof(Default::default(), address, &storage_keys).map_err(Into::into)?; + + return Ok(proof.into_eip1186_response(keys)); + } +}