1use std::{sync::Arc, time::Duration};
7
8use alloy_primitives::{StorageKey, B256, U256};
9use alloy_rpc_types_eth::{state::StateOverride, BlockId};
10use reth_primitives_traits::{Recovered, WithEncoded};
11use reth_rpc::eth::core::EthApiInner;
12use reth_rpc_convert::{RpcConvert, RpcTxReq};
13use reth_rpc_eth_api::{
14 helpers::{
15 estimate::EstimateCall, pending_block::PendingEnvBuilder, Call, EthApiSpec, EthBlocks,
16 EthCall, EthFees, EthSigner, EthState, EthTransactions, LoadBlock, LoadFee,
17 LoadPendingBlock, LoadReceipt, LoadState, LoadTransaction, SpawnBlocking, Trace,
18 },
19 EthApiTypes, FromEvmError, RpcNodeCore, RpcNodeCoreExt,
20};
21use reth_rpc_eth_types::{
22 builder::config::PendingBlockKind, EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle,
23 PendingBlock,
24};
25use reth_storage_api::{ProviderHeader, StateProviderFactory, TransactionsProvider};
26use reth_tasks::{
27 pool::{BlockingTaskGuard, BlockingTaskPool},
28 Runtime,
29};
30use reth_transaction_pool::{
31 AddedTransactionOutcome, PoolPooledTx, PoolTransaction, TransactionOrigin, TransactionPool,
32};
33use tracing::trace;
34
35use arb_precompiles::storage_slot::{
36 subspace_slot, ARBOS_STATE_ADDRESS, L1_PRICING_SUBSPACE, L2_PRICING_SUBSPACE,
37};
38
39type SignersForRpc<Provider, Rpc> = parking_lot::RwLock<
41 Vec<Box<dyn EthSigner<<Provider as TransactionsProvider>::Transaction, RpcTxReq<Rpc>>>>,
42>;
43
44const L1_PRICE_PER_UNIT: u64 = 7;
46
47const L2_BASE_FEE: u64 = 2;
49
50const TX_DATA_NON_ZERO_GAS: u64 = 16;
52
53const GAS_ESTIMATION_L1_PRICE_PADDING: u64 = 11000;
55
56pub struct ArbEthApi<N: RpcNodeCore, Rpc: RpcConvert> {
60 inner: Arc<EthApiInner<N, Rpc>>,
61}
62
63impl<N: RpcNodeCore, Rpc: RpcConvert> Clone for ArbEthApi<N, Rpc> {
64 fn clone(&self) -> Self {
65 Self {
66 inner: self.inner.clone(),
67 }
68 }
69}
70
71impl<N: RpcNodeCore, Rpc: RpcConvert> std::fmt::Debug for ArbEthApi<N, Rpc> {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 f.debug_struct("ArbEthApi").finish_non_exhaustive()
74 }
75}
76
77impl<N: RpcNodeCore, Rpc: RpcConvert> ArbEthApi<N, Rpc> {
78 pub fn new(inner: EthApiInner<N, Rpc>) -> Self {
80 Self {
81 inner: Arc::new(inner),
82 }
83 }
84}
85
86impl<N, Rpc> ArbEthApi<N, Rpc>
87where
88 N: RpcNodeCore<Provider: StateProviderFactory>,
89 Rpc: RpcConvert,
90{
91 fn l1_posting_gas(&self, calldata_len: usize, at: BlockId) -> Result<u64, EthApiError> {
96 if calldata_len == 0 {
97 return Ok(0);
98 }
99
100 let state = self
101 .inner
102 .provider()
103 .state_by_block_id(at)
104 .map_err(|e| EthApiError::Internal(e.into()))?;
105
106 let l1_price_slot = subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT);
107 let l1_price = state
108 .storage(
109 ARBOS_STATE_ADDRESS,
110 StorageKey::from(B256::from(l1_price_slot.to_be_bytes::<32>())),
111 )
112 .map_err(|e| EthApiError::Internal(e.into()))?
113 .unwrap_or_default();
114
115 let basefee_slot = subspace_slot(L2_PRICING_SUBSPACE, L2_BASE_FEE);
116 let basefee = state
117 .storage(
118 ARBOS_STATE_ADDRESS,
119 StorageKey::from(B256::from(basefee_slot.to_be_bytes::<32>())),
120 )
121 .map_err(|e| EthApiError::Internal(e.into()))?
122 .unwrap_or_default();
123
124 if l1_price.is_zero() || basefee.is_zero() {
125 return Ok(0);
126 }
127
128 let l1_fee = l1_price
130 .saturating_mul(U256::from(TX_DATA_NON_ZERO_GAS))
131 .saturating_mul(U256::from(calldata_len));
132
133 let padded = l1_fee.saturating_mul(U256::from(GAS_ESTIMATION_L1_PRICE_PADDING))
135 / U256::from(10000u64);
136
137 let adjusted_basefee = basefee.saturating_mul(U256::from(7)) / U256::from(8);
139 let adjusted_basefee = if adjusted_basefee.is_zero() {
140 U256::from(1)
141 } else {
142 adjusted_basefee
143 };
144
145 let gas = padded / adjusted_basefee;
147 Ok(gas.try_into().unwrap_or(u64::MAX))
148 }
149}
150
151impl<N, Rpc> EthApiTypes for ArbEthApi<N, Rpc>
154where
155 N: RpcNodeCore,
156 Rpc: RpcConvert<Error = EthApiError>,
157{
158 type Error = EthApiError;
159 type NetworkTypes = Rpc::Network;
160 type RpcConvert = Rpc;
161
162 fn converter(&self) -> &Self::RpcConvert {
163 self.inner.converter()
164 }
165}
166
167impl<N, Rpc> RpcNodeCore for ArbEthApi<N, Rpc>
168where
169 N: RpcNodeCore,
170 Rpc: RpcConvert,
171{
172 type Primitives = N::Primitives;
173 type Provider = N::Provider;
174 type Pool = N::Pool;
175 type Evm = N::Evm;
176 type Network = N::Network;
177
178 #[inline]
179 fn pool(&self) -> &Self::Pool {
180 self.inner.pool()
181 }
182
183 #[inline]
184 fn evm_config(&self) -> &Self::Evm {
185 self.inner.evm_config()
186 }
187
188 #[inline]
189 fn network(&self) -> &Self::Network {
190 self.inner.network()
191 }
192
193 #[inline]
194 fn provider(&self) -> &Self::Provider {
195 self.inner.provider()
196 }
197}
198
199impl<N, Rpc> RpcNodeCoreExt for ArbEthApi<N, Rpc>
200where
201 N: RpcNodeCore,
202 Rpc: RpcConvert,
203{
204 #[inline]
205 fn cache(&self) -> &EthStateCache<N::Primitives> {
206 self.inner.cache()
207 }
208}
209
210impl<N, Rpc> EthApiSpec for ArbEthApi<N, Rpc>
211where
212 N: RpcNodeCore,
213 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
214{
215 fn starting_block(&self) -> U256 {
216 self.inner.starting_block()
217 }
218}
219
220impl<N, Rpc> SpawnBlocking for ArbEthApi<N, Rpc>
221where
222 N: RpcNodeCore,
223 Rpc: RpcConvert<Error = EthApiError>,
224{
225 #[inline]
226 fn io_task_spawner(&self) -> &Runtime {
227 self.inner.task_spawner()
228 }
229
230 #[inline]
231 fn tracing_task_pool(&self) -> &BlockingTaskPool {
232 self.inner.blocking_task_pool()
233 }
234
235 #[inline]
236 fn tracing_task_guard(&self) -> &BlockingTaskGuard {
237 self.inner.blocking_task_guard()
238 }
239
240 #[inline]
241 fn blocking_io_task_guard(&self) -> &Arc<tokio::sync::Semaphore> {
242 self.inner.blocking_io_request_semaphore()
243 }
244}
245
246impl<N, Rpc> LoadFee for ArbEthApi<N, Rpc>
247where
248 N: RpcNodeCore,
249 EthApiError: FromEvmError<N::Evm>,
250 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
251{
252 fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> {
253 self.inner.gas_oracle()
254 }
255
256 fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> {
257 self.inner.fee_history_cache()
258 }
259}
260
261impl<N, Rpc> LoadState for ArbEthApi<N, Rpc>
262where
263 N: RpcNodeCore,
264 Rpc: RpcConvert<Primitives = N::Primitives>,
265 Self: LoadPendingBlock,
266{
267}
268
269impl<N, Rpc> EthState for ArbEthApi<N, Rpc>
270where
271 N: RpcNodeCore,
272 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
273 Self: LoadPendingBlock,
274{
275 fn max_proof_window(&self) -> u64 {
276 self.inner.eth_proof_window()
277 }
278}
279
280impl<N, Rpc> EthFees for ArbEthApi<N, Rpc>
281where
282 N: RpcNodeCore,
283 EthApiError: FromEvmError<N::Evm>,
284 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
285{
286}
287
288impl<N, Rpc> Trace for ArbEthApi<N, Rpc>
289where
290 N: RpcNodeCore,
291 EthApiError: FromEvmError<N::Evm>,
292 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
293{
294}
295
296impl<N, Rpc> LoadPendingBlock for ArbEthApi<N, Rpc>
297where
298 N: RpcNodeCore,
299 EthApiError: FromEvmError<N::Evm>,
300 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
301{
302 fn pending_block(&self) -> &tokio::sync::Mutex<Option<PendingBlock<N::Primitives>>> {
303 self.inner.pending_block()
304 }
305
306 fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<N::Evm> {
307 self.inner.pending_env_builder()
308 }
309
310 fn pending_block_kind(&self) -> PendingBlockKind {
311 self.inner.pending_block_kind()
312 }
313}
314
315impl<N, Rpc> LoadBlock for ArbEthApi<N, Rpc>
316where
317 Self: LoadPendingBlock,
318 N: RpcNodeCore,
319 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
320{
321}
322
323impl<N, Rpc> LoadTransaction for ArbEthApi<N, Rpc>
324where
325 N: RpcNodeCore,
326 EthApiError: FromEvmError<N::Evm>,
327 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
328{
329}
330
331impl<N, Rpc> EthBlocks for ArbEthApi<N, Rpc>
332where
333 N: RpcNodeCore,
334 EthApiError: FromEvmError<N::Evm>,
335 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
336{
337}
338
339impl<N, Rpc> EthTransactions for ArbEthApi<N, Rpc>
340where
341 N: RpcNodeCore,
342 EthApiError: FromEvmError<N::Evm>,
343 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
344{
345 fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes> {
346 self.inner.signers()
347 }
348
349 fn send_raw_transaction_sync_timeout(&self) -> Duration {
350 self.inner.send_raw_transaction_sync_timeout()
351 }
352
353 async fn send_transaction(
354 &self,
355 origin: TransactionOrigin,
356 tx: WithEncoded<Recovered<PoolPooledTx<Self::Pool>>>,
357 ) -> Result<B256, Self::Error> {
358 let (_tx_bytes, recovered) = tx.split();
359 let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
360
361 let AddedTransactionOutcome { hash, .. } = self
362 .inner
363 .add_pool_transaction(origin, pool_transaction)
364 .await?;
365
366 Ok(hash)
367 }
368}
369
370impl<N, Rpc> LoadReceipt for ArbEthApi<N, Rpc>
371where
372 N: RpcNodeCore,
373 EthApiError: FromEvmError<N::Evm>,
374 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
375{
376}
377
378impl<N, Rpc> Call for ArbEthApi<N, Rpc>
381where
382 N: RpcNodeCore,
383 EthApiError: FromEvmError<N::Evm>,
384 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
385{
386 #[inline]
387 fn call_gas_limit(&self) -> u64 {
388 self.inner.gas_cap()
389 }
390
391 #[inline]
392 fn max_simulate_blocks(&self) -> u64 {
393 self.inner.max_simulate_blocks()
394 }
395
396 #[inline]
397 fn evm_memory_limit(&self) -> u64 {
398 self.inner.evm_memory_limit()
399 }
400}
401
402impl<N, Rpc> EstimateCall for ArbEthApi<N, Rpc>
403where
404 N: RpcNodeCore,
405 EthApiError: FromEvmError<N::Evm>,
406 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
407{
408 }
410
411impl<N, Rpc> EthCall for ArbEthApi<N, Rpc>
412where
413 N: RpcNodeCore<Provider: StateProviderFactory>,
414 EthApiError: FromEvmError<N::Evm>,
415 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
416{
417 #[allow(clippy::manual_async_fn)]
419 fn estimate_gas_at(
420 &self,
421 request: RpcTxReq<<Self::RpcConvert as RpcConvert>::Network>,
422 at: BlockId,
423 state_override: Option<StateOverride>,
424 ) -> impl std::future::Future<Output = Result<U256, Self::Error>> + Send {
425 async move {
426 let calldata_len = request.as_ref().input.input().map(|b| b.len()).unwrap_or(0);
428
429 let compute_gas =
431 EstimateCall::estimate_gas_at(self, request, at, state_override).await?;
432
433 let l1_gas = self.l1_posting_gas(calldata_len, at)?;
435
436 if l1_gas > 0 {
437 trace!(target: "rpc::eth::estimate", %compute_gas, l1_gas, "Adding L1 posting gas to estimate");
438 }
439
440 Ok(compute_gas.saturating_add(U256::from(l1_gas)))
441 }
442 }
443}