reth_optimism_rpc/eth/
mod.rs

1//! OP-Reth `eth_` endpoint implementation.
2
3pub mod ext;
4pub mod receipt;
5pub mod transaction;
6
7mod block;
8mod call;
9mod pending_block;
10
11use crate::{
12    eth::{receipt::OpReceiptConverter, transaction::OpTxInfoMapper},
13    OpEthApiError, SequencerClient,
14};
15use alloy_consensus::BlockHeader;
16use alloy_primitives::U256;
17use eyre::WrapErr;
18use op_alloy_network::Optimism;
19pub use receipt::{OpReceiptBuilder, OpReceiptFieldsBuilder};
20use reqwest::Url;
21use reth_evm::ConfigureEvm;
22use reth_node_api::{FullNodeComponents, FullNodeTypes, HeaderTy};
23use reth_node_builder::rpc::{EthApiBuilder, EthApiCtx};
24use reth_optimism_flashblocks::{
25    launch_wss_flashblocks_service, ExecutionPayloadBaseV1, FlashBlockRx,
26};
27use reth_rpc::eth::{core::EthApiInner, DevSigner};
28use reth_rpc_eth_api::{
29    helpers::{
30        pending_block::BuildPendingEnv, spec::SignersForApi, AddDevSigners, EthApiSpec, EthFees,
31        EthState, LoadFee, LoadPendingBlock, LoadState, SpawnBlocking, Trace,
32    },
33    EthApiTypes, FromEvmError, FullEthApiServer, RpcConvert, RpcConverter, RpcNodeCore,
34    RpcNodeCoreExt, RpcTypes, SignableTxRequest,
35};
36use reth_rpc_eth_types::{
37    pending_block::PendingBlockAndReceipts, EthStateCache, FeeHistoryCache, GasPriceOracle,
38    PendingBlockEnvOrigin,
39};
40use reth_storage_api::{ProviderHeader, ProviderTx};
41use reth_tasks::{
42    pool::{BlockingTaskGuard, BlockingTaskPool},
43    TaskSpawner,
44};
45use std::{fmt, fmt::Formatter, marker::PhantomData, sync::Arc, time::Instant};
46
47/// Adapter for [`EthApiInner`], which holds all the data required to serve core `eth_` API.
48pub type EthApiNodeBackend<N, Rpc> = EthApiInner<N, Rpc>;
49
50/// OP-Reth `Eth` API implementation.
51///
52/// This type provides the functionality for handling `eth_` related requests.
53///
54/// This wraps a default `Eth` implementation, and provides additional functionality where the
55/// optimism spec deviates from the default (ethereum) spec, e.g. transaction forwarding to the
56/// sequencer, receipts, additional RPC fields for transaction receipts.
57///
58/// This type implements the [`FullEthApi`](reth_rpc_eth_api::helpers::FullEthApi) by implemented
59/// all the `Eth` helper traits and prerequisite traits.
60pub struct OpEthApi<N: RpcNodeCore, Rpc: RpcConvert> {
61    /// Gateway to node's core components.
62    inner: Arc<OpEthApiInner<N, Rpc>>,
63}
64
65impl<N: RpcNodeCore, Rpc: RpcConvert> Clone for OpEthApi<N, Rpc> {
66    fn clone(&self) -> Self {
67        Self { inner: self.inner.clone() }
68    }
69}
70
71impl<N: RpcNodeCore, Rpc: RpcConvert> OpEthApi<N, Rpc> {
72    /// Creates a new `OpEthApi`.
73    pub fn new(
74        eth_api: EthApiNodeBackend<N, Rpc>,
75        sequencer_client: Option<SequencerClient>,
76        min_suggested_priority_fee: U256,
77        flashblocks_rx: Option<FlashBlockRx<N::Primitives>>,
78    ) -> Self {
79        let inner = Arc::new(OpEthApiInner {
80            eth_api,
81            sequencer_client,
82            min_suggested_priority_fee,
83            flashblocks_rx,
84        });
85        Self { inner }
86    }
87
88    /// Returns a reference to the [`EthApiNodeBackend`].
89    pub fn eth_api(&self) -> &EthApiNodeBackend<N, Rpc> {
90        self.inner.eth_api()
91    }
92    /// Returns the configured sequencer client, if any.
93    pub fn sequencer_client(&self) -> Option<&SequencerClient> {
94        self.inner.sequencer_client()
95    }
96
97    /// Returns a cloned Flashblocks receiver, if any.
98    pub fn flashblocks_rx(&self) -> Option<FlashBlockRx<N::Primitives>> {
99        self.inner.flashblocks_rx.clone()
100    }
101
102    /// Build a [`OpEthApi`] using [`OpEthApiBuilder`].
103    pub const fn builder() -> OpEthApiBuilder<Rpc> {
104        OpEthApiBuilder::new()
105    }
106
107    /// Returns a [`PendingBlockAndReceipts`] that is built out of flashblocks.
108    ///
109    /// If flashblocks receiver is not set, then it always returns `None`.
110    pub fn pending_flashblock(&self) -> eyre::Result<Option<PendingBlockAndReceipts<N::Primitives>>>
111    where
112        Self: LoadPendingBlock,
113    {
114        let pending = self.pending_block_env_and_cfg()?;
115        let parent = match pending.origin {
116            PendingBlockEnvOrigin::ActualPending(..) => return Ok(None),
117            PendingBlockEnvOrigin::DerivedFromLatest(parent) => parent,
118        };
119
120        let Some(rx) = self.inner.flashblocks_rx.as_ref() else { return Ok(None) };
121        let pending_block = rx.borrow();
122        let Some(pending_block) = pending_block.as_ref() else { return Ok(None) };
123
124        let now = Instant::now();
125
126        // Is the pending block not expired and latest is its parent?
127        if pending.evm_env.block_env.number == U256::from(pending_block.block().number()) &&
128            parent.hash() == pending_block.block().parent_hash() &&
129            now <= pending_block.expires_at
130        {
131            return Ok(Some(pending_block.to_block_and_receipts()));
132        }
133
134        Ok(None)
135    }
136}
137
138impl<N, Rpc> EthApiTypes for OpEthApi<N, Rpc>
139where
140    N: RpcNodeCore,
141    Rpc: RpcConvert<Primitives = N::Primitives>,
142{
143    type Error = OpEthApiError;
144    type NetworkTypes = Rpc::Network;
145    type RpcConvert = Rpc;
146
147    fn tx_resp_builder(&self) -> &Self::RpcConvert {
148        self.inner.eth_api.tx_resp_builder()
149    }
150}
151
152impl<N, Rpc> RpcNodeCore for OpEthApi<N, Rpc>
153where
154    N: RpcNodeCore,
155    Rpc: RpcConvert<Primitives = N::Primitives>,
156{
157    type Primitives = N::Primitives;
158    type Provider = N::Provider;
159    type Pool = N::Pool;
160    type Evm = N::Evm;
161    type Network = N::Network;
162
163    #[inline]
164    fn pool(&self) -> &Self::Pool {
165        self.inner.eth_api.pool()
166    }
167
168    #[inline]
169    fn evm_config(&self) -> &Self::Evm {
170        self.inner.eth_api.evm_config()
171    }
172
173    #[inline]
174    fn network(&self) -> &Self::Network {
175        self.inner.eth_api.network()
176    }
177
178    #[inline]
179    fn provider(&self) -> &Self::Provider {
180        self.inner.eth_api.provider()
181    }
182}
183
184impl<N, Rpc> RpcNodeCoreExt for OpEthApi<N, Rpc>
185where
186    N: RpcNodeCore,
187    Rpc: RpcConvert<Primitives = N::Primitives>,
188{
189    #[inline]
190    fn cache(&self) -> &EthStateCache<N::Primitives> {
191        self.inner.eth_api.cache()
192    }
193}
194
195impl<N, Rpc> EthApiSpec for OpEthApi<N, Rpc>
196where
197    N: RpcNodeCore,
198    Rpc: RpcConvert<Primitives = N::Primitives>,
199{
200    type Transaction = ProviderTx<Self::Provider>;
201    type Rpc = Rpc::Network;
202
203    #[inline]
204    fn starting_block(&self) -> U256 {
205        self.inner.eth_api.starting_block()
206    }
207
208    #[inline]
209    fn signers(&self) -> &SignersForApi<Self> {
210        self.inner.eth_api.signers()
211    }
212}
213
214impl<N, Rpc> SpawnBlocking for OpEthApi<N, Rpc>
215where
216    N: RpcNodeCore,
217    Rpc: RpcConvert<Primitives = N::Primitives>,
218{
219    #[inline]
220    fn io_task_spawner(&self) -> impl TaskSpawner {
221        self.inner.eth_api.task_spawner()
222    }
223
224    #[inline]
225    fn tracing_task_pool(&self) -> &BlockingTaskPool {
226        self.inner.eth_api.blocking_task_pool()
227    }
228
229    #[inline]
230    fn tracing_task_guard(&self) -> &BlockingTaskGuard {
231        self.inner.eth_api.blocking_task_guard()
232    }
233}
234
235impl<N, Rpc> LoadFee for OpEthApi<N, Rpc>
236where
237    N: RpcNodeCore,
238    OpEthApiError: FromEvmError<N::Evm>,
239    Rpc: RpcConvert<Primitives = N::Primitives, Error = OpEthApiError>,
240{
241    #[inline]
242    fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> {
243        self.inner.eth_api.gas_oracle()
244    }
245
246    #[inline]
247    fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> {
248        self.inner.eth_api.fee_history_cache()
249    }
250
251    async fn suggested_priority_fee(&self) -> Result<U256, Self::Error> {
252        let min_tip = U256::from(self.inner.min_suggested_priority_fee);
253        self.inner.eth_api.gas_oracle().op_suggest_tip_cap(min_tip).await.map_err(Into::into)
254    }
255}
256
257impl<N, Rpc> LoadState for OpEthApi<N, Rpc>
258where
259    N: RpcNodeCore,
260    Rpc: RpcConvert<Primitives = N::Primitives>,
261    Self: LoadPendingBlock,
262{
263}
264
265impl<N, Rpc> EthState for OpEthApi<N, Rpc>
266where
267    N: RpcNodeCore,
268    Rpc: RpcConvert<Primitives = N::Primitives>,
269    Self: LoadPendingBlock,
270{
271    #[inline]
272    fn max_proof_window(&self) -> u64 {
273        self.inner.eth_api.eth_proof_window()
274    }
275}
276
277impl<N, Rpc> EthFees for OpEthApi<N, Rpc>
278where
279    N: RpcNodeCore,
280    OpEthApiError: FromEvmError<N::Evm>,
281    Rpc: RpcConvert<Primitives = N::Primitives, Error = OpEthApiError>,
282{
283}
284
285impl<N, Rpc> Trace for OpEthApi<N, Rpc>
286where
287    N: RpcNodeCore,
288    OpEthApiError: FromEvmError<N::Evm>,
289    Rpc: RpcConvert<Primitives = N::Primitives>,
290{
291}
292
293impl<N, Rpc> AddDevSigners for OpEthApi<N, Rpc>
294where
295    N: RpcNodeCore,
296    Rpc: RpcConvert<
297        Network: RpcTypes<TransactionRequest: SignableTxRequest<ProviderTx<N::Provider>>>,
298    >,
299{
300    fn with_dev_accounts(&self) {
301        *self.inner.eth_api.signers().write() = DevSigner::random_signers(20)
302    }
303}
304
305impl<N: RpcNodeCore, Rpc: RpcConvert> fmt::Debug for OpEthApi<N, Rpc> {
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        f.debug_struct("OpEthApi").finish_non_exhaustive()
308    }
309}
310
311/// Container type `OpEthApi`
312pub struct OpEthApiInner<N: RpcNodeCore, Rpc: RpcConvert> {
313    /// Gateway to node's core components.
314    eth_api: EthApiNodeBackend<N, Rpc>,
315    /// Sequencer client, configured to forward submitted transactions to sequencer of given OP
316    /// network.
317    sequencer_client: Option<SequencerClient>,
318    /// Minimum priority fee enforced by OP-specific logic.
319    ///
320    /// See also <https://github.com/ethereum-optimism/op-geth/blob/d4e0fe9bb0c2075a9bff269fb975464dd8498f75/eth/gasprice/optimism-gasprice.go#L38-L38>
321    min_suggested_priority_fee: U256,
322    /// Flashblocks receiver.
323    ///
324    /// If set, then it provides current pending block based on received Flashblocks.
325    flashblocks_rx: Option<FlashBlockRx<N::Primitives>>,
326}
327
328impl<N: RpcNodeCore, Rpc: RpcConvert> fmt::Debug for OpEthApiInner<N, Rpc> {
329    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
330        f.debug_struct("OpEthApiInner").finish()
331    }
332}
333
334impl<N: RpcNodeCore, Rpc: RpcConvert> OpEthApiInner<N, Rpc> {
335    /// Returns a reference to the [`EthApiNodeBackend`].
336    const fn eth_api(&self) -> &EthApiNodeBackend<N, Rpc> {
337        &self.eth_api
338    }
339
340    /// Returns the configured sequencer client, if any.
341    const fn sequencer_client(&self) -> Option<&SequencerClient> {
342        self.sequencer_client.as_ref()
343    }
344}
345
346/// Converter for OP RPC types.
347pub type OpRpcConvert<N, NetworkT> = RpcConverter<
348    NetworkT,
349    <N as FullNodeComponents>::Evm,
350    OpReceiptConverter<<N as FullNodeTypes>::Provider>,
351    (),
352    OpTxInfoMapper<<N as FullNodeTypes>::Provider>,
353>;
354
355/// Builds [`OpEthApi`] for Optimism.
356#[derive(Debug)]
357pub struct OpEthApiBuilder<NetworkT = Optimism> {
358    /// Sequencer client, configured to forward submitted transactions to sequencer of given OP
359    /// network.
360    sequencer_url: Option<String>,
361    /// Headers to use for the sequencer client requests.
362    sequencer_headers: Vec<String>,
363    /// Minimum suggested priority fee (tip)
364    min_suggested_priority_fee: u64,
365    /// A URL pointing to a secure websocket connection (wss) that streams out [flashblocks].
366    ///
367    /// [flashblocks]: reth_optimism_flashblocks
368    flashblocks_url: Option<Url>,
369    /// Marker for network types.
370    _nt: PhantomData<NetworkT>,
371}
372
373impl<NetworkT> Default for OpEthApiBuilder<NetworkT> {
374    fn default() -> Self {
375        Self {
376            sequencer_url: None,
377            sequencer_headers: Vec::new(),
378            min_suggested_priority_fee: 1_000_000,
379            flashblocks_url: None,
380            _nt: PhantomData,
381        }
382    }
383}
384
385impl<NetworkT> OpEthApiBuilder<NetworkT> {
386    /// Creates a [`OpEthApiBuilder`] instance from core components.
387    pub const fn new() -> Self {
388        Self {
389            sequencer_url: None,
390            sequencer_headers: Vec::new(),
391            min_suggested_priority_fee: 1_000_000,
392            flashblocks_url: None,
393            _nt: PhantomData,
394        }
395    }
396
397    /// With a [`SequencerClient`].
398    pub fn with_sequencer(mut self, sequencer_url: Option<String>) -> Self {
399        self.sequencer_url = sequencer_url;
400        self
401    }
402
403    /// With headers to use for the sequencer client requests.
404    pub fn with_sequencer_headers(mut self, sequencer_headers: Vec<String>) -> Self {
405        self.sequencer_headers = sequencer_headers;
406        self
407    }
408
409    /// With minimum suggested priority fee (tip).
410    pub const fn with_min_suggested_priority_fee(mut self, min: u64) -> Self {
411        self.min_suggested_priority_fee = min;
412        self
413    }
414
415    /// With a subscription to flashblocks secure websocket connection.
416    pub fn with_flashblocks(mut self, flashblocks_url: Option<Url>) -> Self {
417        self.flashblocks_url = flashblocks_url;
418        self
419    }
420}
421
422impl<N, NetworkT> EthApiBuilder<N> for OpEthApiBuilder<NetworkT>
423where
424    N: FullNodeComponents<
425        Evm: ConfigureEvm<
426            NextBlockEnvCtx: BuildPendingEnv<HeaderTy<N::Types>>
427                                 + From<ExecutionPayloadBaseV1>
428                                 + Unpin,
429        >,
430    >,
431    NetworkT: RpcTypes,
432    OpRpcConvert<N, NetworkT>: RpcConvert<Network = NetworkT>,
433    OpEthApi<N, OpRpcConvert<N, NetworkT>>:
434        FullEthApiServer<Provider = N::Provider, Pool = N::Pool> + AddDevSigners,
435{
436    type EthApi = OpEthApi<N, OpRpcConvert<N, NetworkT>>;
437
438    async fn build_eth_api(self, ctx: EthApiCtx<'_, N>) -> eyre::Result<Self::EthApi> {
439        let Self {
440            sequencer_url,
441            sequencer_headers,
442            min_suggested_priority_fee,
443            flashblocks_url,
444            ..
445        } = self;
446        let rpc_converter =
447            RpcConverter::new(OpReceiptConverter::new(ctx.components.provider().clone()))
448                .with_mapper(OpTxInfoMapper::new(ctx.components.provider().clone()));
449
450        let sequencer_client = if let Some(url) = sequencer_url {
451            Some(
452                SequencerClient::new_with_headers(&url, sequencer_headers)
453                    .await
454                    .wrap_err_with(|| format!("Failed to init sequencer client with: {url}"))?,
455            )
456        } else {
457            None
458        };
459
460        let flashblocks_rx = flashblocks_url.map(|ws_url| {
461            launch_wss_flashblocks_service(
462                ws_url,
463                ctx.components.evm_config().clone(),
464                ctx.components.provider().clone(),
465            )
466        });
467
468        let eth_api = ctx.eth_api_builder().with_rpc_converter(rpc_converter).build_inner();
469
470        Ok(OpEthApi::new(
471            eth_api,
472            sequencer_client,
473            U256::from(min_suggested_priority_fee),
474            flashblocks_rx,
475        ))
476    }
477}