1pub 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
47pub type EthApiNodeBackend<N, Rpc> = EthApiInner<N, Rpc>;
49
50pub struct OpEthApi<N: RpcNodeCore, Rpc: RpcConvert> {
61 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 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 pub fn eth_api(&self) -> &EthApiNodeBackend<N, Rpc> {
90 self.inner.eth_api()
91 }
92 pub fn sequencer_client(&self) -> Option<&SequencerClient> {
94 self.inner.sequencer_client()
95 }
96
97 pub fn flashblocks_rx(&self) -> Option<FlashBlockRx<N::Primitives>> {
99 self.inner.flashblocks_rx.clone()
100 }
101
102 pub const fn builder() -> OpEthApiBuilder<Rpc> {
104 OpEthApiBuilder::new()
105 }
106
107 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 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
311pub struct OpEthApiInner<N: RpcNodeCore, Rpc: RpcConvert> {
313 eth_api: EthApiNodeBackend<N, Rpc>,
315 sequencer_client: Option<SequencerClient>,
318 min_suggested_priority_fee: U256,
322 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 const fn eth_api(&self) -> &EthApiNodeBackend<N, Rpc> {
337 &self.eth_api
338 }
339
340 const fn sequencer_client(&self) -> Option<&SequencerClient> {
342 self.sequencer_client.as_ref()
343 }
344}
345
346pub 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#[derive(Debug)]
357pub struct OpEthApiBuilder<NetworkT = Optimism> {
358 sequencer_url: Option<String>,
361 sequencer_headers: Vec<String>,
363 min_suggested_priority_fee: u64,
365 flashblocks_url: Option<Url>,
369 _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 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 pub fn with_sequencer(mut self, sequencer_url: Option<String>) -> Self {
399 self.sequencer_url = sequencer_url;
400 self
401 }
402
403 pub fn with_sequencer_headers(mut self, sequencer_headers: Vec<String>) -> Self {
405 self.sequencer_headers = sequencer_headers;
406 self
407 }
408
409 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 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}