diff --git a/Cargo.lock b/Cargo.lock index fcf98a08d..1494c0d9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7981,16 +7981,22 @@ name = "reth-optimism-rpc" version = "1.0.1" dependencies = [ "alloy-primitives", + "jsonrpsee", "parking_lot 0.12.3", "reth-chainspec", "reth-errors", "reth-evm", + "reth-evm-optimism", + "reth-primitives", "reth-provider", "reth-rpc-eth-api", "reth-rpc-eth-types", + "reth-rpc-server-types", "reth-rpc-types", "reth-tasks", "reth-transaction-pool", + "revm", + "thiserror", "tokio", ] diff --git a/crates/optimism/rpc/Cargo.toml b/crates/optimism/rpc/Cargo.toml index f7599b4a0..71aeefdc3 100644 --- a/crates/optimism/rpc/Cargo.toml +++ b/crates/optimism/rpc/Cargo.toml @@ -6,25 +6,47 @@ rust-version.workspace = true license.workspace = true homepage.workspace = true repository.workspace = true +description = "Ethereum RPC implementation for optimism." [lints] workspace = true [dependencies] # reth +reth-chainspec.workspace = true reth-errors.workspace = true +reth-evm-optimism.workspace = true reth-evm.workspace = true +reth-primitives.workspace = true +reth-provider.workspace = true reth-rpc-eth-api.workspace = true reth-rpc-eth-types.workspace = true +reth-rpc-server-types.workspace = true reth-rpc-types.workspace = true -reth-chainspec.workspace = true -reth-provider.workspace = true reth-tasks = { workspace = true, features = ["rayon"] } reth-transaction-pool.workspace = true # ethereum alloy-primitives.workspace = true +revm.workspace = true # async parking_lot.workspace = true -tokio.workspace = true \ No newline at end of file +tokio.workspace = true + +# rpc +jsonrpsee.workspace = true + +# misc +thiserror.workspace = true + +[features] +optimism = [ + "reth-chainspec/optimism", + "reth-evm-optimism/optimism", + "reth-primitives/optimism", + "reth-provider/optimism", + "reth-rpc-eth-api/optimism", + "reth-rpc-eth-types/optimism", + "revm/optimism" +] \ No newline at end of file diff --git a/crates/optimism/rpc/src/error.rs b/crates/optimism/rpc/src/error.rs new file mode 100644 index 000000000..7b7d3bed9 --- /dev/null +++ b/crates/optimism/rpc/src/error.rs @@ -0,0 +1,31 @@ +//! RPC errors specific to OP. + +use jsonrpsee::types::ErrorObject; +use reth_rpc_eth_types::EthApiError; +use reth_rpc_server_types::result::internal_rpc_err; +use reth_rpc_types::ToRpcError; + +/// Optimism specific errors, that extend [`EthApiError`]. +#[derive(Debug, thiserror::Error)] +pub enum OpEthApiError { + /// Thrown when calculating L1 gas fee. + #[error("failed to calculate l1 gas fee")] + L1BlockFeeError, + /// Thrown when calculating L1 gas used + #[error("failed to calculate l1 gas used")] + L1BlockGasError, +} + +impl ToRpcError for OpEthApiError { + fn to_rpc_error(&self) -> ErrorObject<'static> { + match self { + Self::L1BlockFeeError | Self::L1BlockGasError => internal_rpc_err(self.to_string()), + } + } +} + +impl From for EthApiError { + fn from(err: OpEthApiError) -> Self { + Self::other(err) + } +} diff --git a/crates/optimism/rpc/src/eth/block.rs b/crates/optimism/rpc/src/eth/block.rs new file mode 100644 index 000000000..c48d70907 --- /dev/null +++ b/crates/optimism/rpc/src/eth/block.rs @@ -0,0 +1,75 @@ +//! Loads and formats OP block RPC response. + +use reth_primitives::TransactionMeta; +use reth_provider::{BlockReaderIdExt, HeaderProvider}; +use reth_rpc_eth_api::helpers::{EthApiSpec, EthBlocks, LoadBlock, LoadReceipt, LoadTransaction}; +use reth_rpc_eth_types::{EthResult, EthStateCache, ReceiptBuilder}; +use reth_rpc_types::{AnyTransactionReceipt, BlockId}; + +use crate::{op_receipt_fields, OpEthApi}; + +impl EthBlocks for OpEthApi +where + Eth: EthBlocks + EthApiSpec + LoadTransaction, +{ + fn provider(&self) -> impl HeaderProvider { + EthBlocks::provider(&self.inner) + } + + async fn block_receipts( + &self, + block_id: BlockId, + ) -> EthResult>> + where + Self: LoadReceipt, + { + if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? { + let block_number = block.number; + let base_fee = block.base_fee_per_gas; + let block_hash = block.hash(); + let excess_blob_gas = block.excess_blob_gas; + let timestamp = block.timestamp; + let block = block.unseal(); + + let l1_block_info = reth_evm_optimism::extract_l1_info(&block).ok(); + + let receipts = block + .body + .into_iter() + .zip(receipts.iter()) + .enumerate() + .map(|(idx, (ref tx, receipt))| { + let meta = TransactionMeta { + tx_hash: tx.hash, + index: idx as u64, + block_hash, + block_number, + base_fee, + excess_blob_gas, + timestamp, + }; + + let optimism_tx_meta = + self.build_op_tx_meta(tx, l1_block_info.clone(), timestamp)?; + + ReceiptBuilder::new(tx, meta, receipt, &receipts).map(|builder| { + op_receipt_fields(builder, tx, receipt, optimism_tx_meta).build() + }) + }) + .collect::>>(); + return receipts.map(Some) + } + + Ok(None) + } +} + +impl LoadBlock for OpEthApi { + fn provider(&self) -> impl BlockReaderIdExt { + LoadBlock::provider(&self.inner) + } + + fn cache(&self) -> &EthStateCache { + self.inner.cache() + } +} diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index d580a30e9..bcdc0f954 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -1,26 +1,26 @@ //! OP-Reth `eth_` endpoint implementation. +pub mod receipt; +pub mod transaction; + +mod block; +mod pending_block; + +use std::{future::Future, sync::Arc}; + use alloy_primitives::{Address, U64}; -use reth_chainspec::ChainInfo; +use reth_chainspec::{ChainInfo, ChainSpec}; use reth_errors::RethResult; use reth_evm::ConfigureEvm; -use reth_provider::{ - BlockReaderIdExt, ChainSpecProvider, EvmEnvProvider, HeaderProvider, StateProviderFactory, +use reth_provider::{BlockReaderIdExt, ChainSpecProvider, HeaderProvider, StateProviderFactory}; +use reth_rpc_eth_api::helpers::{ + Call, EthApiSpec, EthCall, EthFees, EthState, LoadFee, LoadState, SpawnBlocking, Trace, }; -use reth_rpc_eth_api::{ - helpers::{ - Call, EthApiSpec, EthBlocks, EthCall, EthFees, EthSigner, EthState, EthTransactions, - LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, LoadState, LoadTransaction, - SpawnBlocking, Trace, - }, - RawTransactionForwarder, -}; -use reth_rpc_eth_types::{EthStateCache, PendingBlock}; +use reth_rpc_eth_types::EthStateCache; use reth_rpc_types::SyncStatus; use reth_tasks::{pool::BlockingTaskPool, TaskSpawner}; use reth_transaction_pool::TransactionPool; -use std::future::Future; -use tokio::sync::{AcquireError, Mutex, OwnedSemaphorePermit}; +use tokio::sync::{AcquireError, OwnedSemaphorePermit}; /// OP-Reth `Eth` API implementation. /// @@ -68,35 +68,9 @@ impl EthApiSpec for OpEthApi { fn sync_status(&self) -> RethResult { self.inner.sync_status() } -} -impl LoadBlock for OpEthApi { - fn provider(&self) -> impl BlockReaderIdExt { - LoadBlock::provider(&self.inner) - } - - fn cache(&self) -> &reth_rpc_eth_types::EthStateCache { - self.inner.cache() - } -} - -impl LoadPendingBlock for OpEthApi { - fn provider( - &self, - ) -> impl BlockReaderIdExt + EvmEnvProvider + ChainSpecProvider + StateProviderFactory { - self.inner.provider() - } - - fn pool(&self) -> impl TransactionPool { - self.inner.pool() - } - - fn pending_block(&self) -> &Mutex> { - self.inner.pending_block() - } - - fn evm_config(&self) -> &impl ConfigureEvm { - self.inner.evm_config() + fn chain_spec(&self) -> Arc { + self.inner.chain_spec() } } @@ -123,12 +97,6 @@ impl SpawnBlocking for OpEthApi { } } -impl LoadReceipt for OpEthApi { - fn cache(&self) -> &EthStateCache { - self.inner.cache() - } -} - impl LoadFee for OpEthApi { fn provider(&self) -> impl reth_provider::BlockIdReader + HeaderProvider + ChainSpecProvider { LoadFee::provider(&self.inner) @@ -171,42 +139,6 @@ impl LoadState for OpEthApi { } } -impl LoadTransaction for OpEthApi { - type Pool = Eth::Pool; - - fn provider(&self) -> impl reth_provider::TransactionsProvider { - LoadTransaction::provider(&self.inner) - } - - fn cache(&self) -> &EthStateCache { - LoadTransaction::cache(&self.inner) - } - - fn pool(&self) -> &Self::Pool { - LoadTransaction::pool(&self.inner) - } -} - -impl EthTransactions for OpEthApi { - fn provider(&self) -> impl BlockReaderIdExt { - EthTransactions::provider(&self.inner) - } - - fn raw_tx_forwarder(&self) -> Option> { - self.inner.raw_tx_forwarder() - } - - fn signers(&self) -> &parking_lot::RwLock>> { - self.inner.signers() - } -} - -impl EthBlocks for OpEthApi { - fn provider(&self) -> impl HeaderProvider { - EthBlocks::provider(&self.inner) - } -} - impl EthState for OpEthApi { fn max_proof_window(&self) -> u64 { self.inner.max_proof_window() diff --git a/crates/optimism/rpc/src/eth/pending_block.rs b/crates/optimism/rpc/src/eth/pending_block.rs new file mode 100644 index 000000000..8c0d6196a --- /dev/null +++ b/crates/optimism/rpc/src/eth/pending_block.rs @@ -0,0 +1,72 @@ +//! Loads OP pending block for a RPC response. + +use reth_primitives::{ + revm_primitives::{BlockEnv, ExecutionResult}, + BlockNumber, Receipt, TransactionSignedEcRecovered, B256, +}; +use reth_provider::{ChainSpecProvider, ExecutionOutcome}; +use reth_rpc_eth_api::helpers::LoadPendingBlock; +use reth_rpc_eth_types::PendingBlock; + +use crate::OpEthApi; + +impl LoadPendingBlock for OpEthApi +where + Eth: LoadPendingBlock, +{ + #[inline] + fn provider( + &self, + ) -> impl reth_provider::BlockReaderIdExt + + reth_provider::EvmEnvProvider + + reth_provider::ChainSpecProvider + + reth_provider::StateProviderFactory { + self.inner.provider() + } + + #[inline] + fn pool(&self) -> impl reth_transaction_pool::TransactionPool { + self.inner.pool() + } + + #[inline] + fn pending_block(&self) -> &tokio::sync::Mutex> { + self.inner.pending_block() + } + + #[inline] + fn evm_config(&self) -> &impl reth_evm::ConfigureEvm { + self.inner.evm_config() + } + + fn assemble_receipt( + &self, + tx: &TransactionSignedEcRecovered, + result: ExecutionResult, + cumulative_gas_used: u64, + ) -> Receipt { + Receipt { + tx_type: tx.tx_type(), + success: result.is_success(), + cumulative_gas_used, + logs: result.into_logs().into_iter().map(Into::into).collect(), + deposit_nonce: None, + deposit_receipt_version: None, + } + } + + fn receipts_root( + &self, + _block_env: &BlockEnv, + execution_outcome: &ExecutionOutcome, + block_number: BlockNumber, + ) -> B256 { + execution_outcome + .optimism_receipts_root_slow( + block_number, + self.provider().chain_spec().as_ref(), + _block_env.timestamp.to::(), + ) + .expect("Block is present") + } +} diff --git a/crates/optimism/rpc/src/eth/receipt.rs b/crates/optimism/rpc/src/eth/receipt.rs new file mode 100644 index 000000000..ec22be71e --- /dev/null +++ b/crates/optimism/rpc/src/eth/receipt.rs @@ -0,0 +1,64 @@ +//! Loads and formats OP receipt RPC response. + +use reth_primitives::{Receipt, TransactionMeta, TransactionSigned}; +use reth_rpc_eth_api::helpers::{EthApiSpec, LoadReceipt, LoadTransaction}; +use reth_rpc_eth_types::{EthApiError, EthResult, EthStateCache, ReceiptBuilder}; +use reth_rpc_types::{AnyTransactionReceipt, OptimismTransactionReceiptFields}; + +use crate::{OpEthApi, OptimismTxMeta}; + +impl LoadReceipt for OpEthApi +where + Eth: LoadReceipt + EthApiSpec + LoadTransaction, +{ + #[inline] + fn cache(&self) -> &EthStateCache { + LoadReceipt::cache(&self.inner) + } + + async fn build_transaction_receipt( + &self, + tx: TransactionSigned, + meta: TransactionMeta, + receipt: Receipt, + ) -> EthResult { + let (block, receipts) = LoadReceipt::cache(self) + .get_block_and_receipts(meta.block_hash) + .await? + .ok_or(EthApiError::UnknownBlockNumber)?; + + let block = block.unseal(); + let l1_block_info = reth_evm_optimism::extract_l1_info(&block).ok(); + let optimism_tx_meta = self.build_op_tx_meta(&tx, l1_block_info, block.timestamp)?; + + let resp_builder = ReceiptBuilder::new(&tx, meta, &receipt, &receipts)?; + let resp_builder = op_receipt_fields(resp_builder, &tx, &receipt, optimism_tx_meta); + + Ok(resp_builder.build()) + } +} + +/// Applies OP specific fields to a receipt. +pub fn op_receipt_fields( + resp_builder: ReceiptBuilder, + tx: &TransactionSigned, + receipt: &Receipt, + optimism_tx_meta: OptimismTxMeta, +) -> ReceiptBuilder { + let mut op_fields = OptimismTransactionReceiptFields::default(); + + if tx.is_deposit() { + op_fields.deposit_nonce = receipt.deposit_nonce.map(reth_primitives::U64::from); + op_fields.deposit_receipt_version = + receipt.deposit_receipt_version.map(reth_primitives::U64::from); + } else if let Some(l1_block_info) = optimism_tx_meta.l1_block_info { + op_fields.l1_fee = optimism_tx_meta.l1_fee; + op_fields.l1_gas_used = optimism_tx_meta.l1_data_gas.map(|dg| { + dg + l1_block_info.l1_fee_overhead.unwrap_or_default().saturating_to::() + }); + op_fields.l1_fee_scalar = Some(f64::from(l1_block_info.l1_base_fee_scalar) / 1_000_000.0); + op_fields.l1_gas_price = Some(l1_block_info.l1_base_fee.saturating_to()); + } + + resp_builder.add_other_fields(op_fields.into()) +} diff --git a/crates/optimism/rpc/src/eth/transaction.rs b/crates/optimism/rpc/src/eth/transaction.rs new file mode 100644 index 000000000..69a16cd3c --- /dev/null +++ b/crates/optimism/rpc/src/eth/transaction.rs @@ -0,0 +1,107 @@ +//! Loads and formats OP transaction RPC response. + +use reth_evm_optimism::RethL1BlockInfo; +use reth_primitives::TransactionSigned; +use reth_provider::BlockReaderIdExt; +use reth_rpc_eth_api::{ + helpers::{EthApiSpec, EthSigner, EthTransactions, LoadTransaction}, + RawTransactionForwarder, +}; +use reth_rpc_eth_types::{EthResult, EthStateCache}; +use revm::L1BlockInfo; + +use crate::{OpEthApi, OpEthApiError}; + +impl EthTransactions for OpEthApi { + fn provider(&self) -> impl BlockReaderIdExt { + EthTransactions::provider(&self.inner) + } + + fn raw_tx_forwarder(&self) -> Option> { + self.inner.raw_tx_forwarder() + } + + fn signers(&self) -> &parking_lot::RwLock>> { + self.inner.signers() + } +} + +impl LoadTransaction for OpEthApi { + type Pool = Eth::Pool; + + fn provider(&self) -> impl reth_provider::TransactionsProvider { + LoadTransaction::provider(&self.inner) + } + + fn cache(&self) -> &EthStateCache { + LoadTransaction::cache(&self.inner) + } + + fn pool(&self) -> &Self::Pool { + LoadTransaction::pool(&self.inner) + } +} + +/// L1 fee and data gas for a transaction, along with the L1 block info. +#[derive(Debug, Default, Clone)] +pub struct OptimismTxMeta { + /// The L1 block info. + pub l1_block_info: Option, + /// The L1 fee for the block. + pub l1_fee: Option, + /// The L1 data gas for the block. + pub l1_data_gas: Option, +} + +impl OptimismTxMeta { + /// Creates a new [`OptimismTxMeta`]. + pub const fn new( + l1_block_info: Option, + l1_fee: Option, + l1_data_gas: Option, + ) -> Self { + Self { l1_block_info, l1_fee, l1_data_gas } + } +} + +impl OpEthApi +where + Eth: EthApiSpec + LoadTransaction, +{ + /// Builds [`OptimismTxMeta`] object using the provided [`TransactionSigned`], L1 block + /// info and block timestamp. The [`L1BlockInfo`] is used to calculate the l1 fee and l1 data + /// gas for the transaction. If the [`L1BlockInfo`] is not provided, the meta info will be + /// empty. + pub fn build_op_tx_meta( + &self, + tx: &TransactionSigned, + l1_block_info: Option, + block_timestamp: u64, + ) -> EthResult { + let Some(l1_block_info) = l1_block_info else { return Ok(OptimismTxMeta::default()) }; + + let (l1_fee, l1_data_gas) = if !tx.is_deposit() { + let envelope_buf = tx.envelope_encoded(); + + let inner_l1_fee = l1_block_info + .l1_tx_data_fee( + &self.inner.chain_spec(), + block_timestamp, + &envelope_buf, + tx.is_deposit(), + ) + .map_err(|_| OpEthApiError::L1BlockFeeError)?; + let inner_l1_data_gas = l1_block_info + .l1_data_gas(&self.inner.chain_spec(), block_timestamp, &envelope_buf) + .map_err(|_| OpEthApiError::L1BlockGasError)?; + ( + Some(inner_l1_fee.saturating_to::()), + Some(inner_l1_data_gas.saturating_to::()), + ) + } else { + (None, None) + }; + + Ok(OptimismTxMeta::new(Some(l1_block_info), l1_fee, l1_data_gas)) + } +} diff --git a/crates/optimism/rpc/src/lib.rs b/crates/optimism/rpc/src/lib.rs index cad90bc42..e70194a89 100644 --- a/crates/optimism/rpc/src/lib.rs +++ b/crates/optimism/rpc/src/lib.rs @@ -5,7 +5,13 @@ 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(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(feature = "optimism")] +pub mod error; pub mod eth; + +pub use error::OpEthApiError; +pub use eth::{receipt::op_receipt_fields, transaction::OptimismTxMeta, OpEthApi}; diff --git a/crates/rpc/rpc-api/src/optimism.rs b/crates/rpc/rpc-api/src/optimism.rs index 783bd1760..48171cf09 100644 --- a/crates/rpc/rpc-api/src/optimism.rs +++ b/crates/rpc/rpc-api/src/optimism.rs @@ -210,7 +210,7 @@ pub struct PeerStats { /// https://github.com/ethereum-optimism/optimism/blob/8dd17a7b114a7c25505cd2e15ce4e3d0f7e3f7c1/op-node/node/api.go#L114 #[cfg_attr(not(feature = "client"), rpc(server, namespace = "optimism"))] #[cfg_attr(feature = "client", rpc(server, client, namespace = "optimism"))] -pub trait OptimismApi { +pub trait OpEthApi { /// Get the output root at a specific block. #[method(name = "outputAtBlock")] async fn optimism_output_at_block( diff --git a/crates/rpc/rpc-eth-api/src/helpers/spec.rs b/crates/rpc/rpc-eth-api/src/helpers/spec.rs index 63722e376..a36868e52 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/spec.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/spec.rs @@ -1,7 +1,9 @@ //! Loads chain metadata. +use std::sync::Arc; + use futures::Future; -use reth_chainspec::ChainInfo; +use reth_chainspec::{ChainInfo, ChainSpec}; use reth_errors::RethResult; use reth_primitives::{Address, U64}; use reth_rpc_types::SyncStatus; @@ -28,4 +30,7 @@ pub trait EthApiSpec: Send + Sync { /// Returns the [`SyncStatus`] of the network fn sync_status(&self) -> RethResult; + + /// Returns the configured [`ChainSpec`]. + fn chain_spec(&self) -> Arc; } diff --git a/crates/rpc/rpc/src/eth/helpers/spec.rs b/crates/rpc/rpc/src/eth/helpers/spec.rs index a93d662ea..58ac1c64d 100644 --- a/crates/rpc/rpc/src/eth/helpers/spec.rs +++ b/crates/rpc/rpc/src/eth/helpers/spec.rs @@ -1,4 +1,6 @@ -use reth_chainspec::ChainInfo; +use std::sync::Arc; + +use reth_chainspec::{ChainInfo, ChainSpec}; use reth_errors::{RethError, RethResult}; use reth_evm::ConfigureEvm; use reth_network_api::NetworkInfo; @@ -62,4 +64,8 @@ where }; Ok(status) } + + fn chain_spec(&self) -> Arc { + self.inner.provider().chain_spec() + } }