1use std::{sync::Arc, time::Duration};
7
8use alloy_primitives::{Address, 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 root_slot, subspace_slot, ARBOS_STATE_ADDRESS, BROTLI_COMPRESSION_LEVEL_OFFSET,
37 CHAIN_ID_OFFSET, L1_PRICING_SUBSPACE, L2_PRICING_SUBSPACE,
38};
39
40type SignersForRpc<Provider, Rpc> = parking_lot::RwLock<
42 Vec<Box<dyn EthSigner<<Provider as TransactionsProvider>::Transaction, RpcTxReq<Rpc>>>>,
43>;
44
45const L1_PRICE_PER_UNIT: u64 = 7;
47
48const L2_BASE_FEE: u64 = 2;
50
51const L2_MIN_BASE_FEE: u64 = 3;
53
54const TX_DATA_NON_ZERO_GAS: u64 = 16;
56
57const GAS_ESTIMATION_L1_PRICE_PADDING: u64 = 11000;
59
60const SEL_GET_CURRENT_TX_L1_FEES: &[u8] = &[0xc6, 0xf7, 0xde, 0x0e];
62
63fn apply_l1_to_l2_alias(addr: Address) -> Address {
66 let mut offset = [0u8; 32];
67 offset[12] = 0x11;
68 offset[13] = 0x11;
69 offset[30] = 0x11;
70 offset[31] = 0x11;
71 let lhs = U256::from_be_slice(addr.as_slice());
72 let rhs = U256::from_be_bytes(offset);
73 let sum = lhs.wrapping_add(rhs);
74 let bytes = sum.to_be_bytes::<32>();
75 Address::from_slice(&bytes[12..32])
76}
77
78const SEL_GET_L1_PRICING_UNITS_SINCE_UPDATE: &[u8] = &[0xef, 0xf0, 0x13, 0x06];
80
81const L1_UNITS_SINCE_UPDATE: u64 = 6;
83
84pub struct ArbEthApi<N: RpcNodeCore, Rpc: RpcConvert> {
88 inner: Arc<EthApiInner<N, Rpc>>,
89}
90
91impl<N: RpcNodeCore, Rpc: RpcConvert> Clone for ArbEthApi<N, Rpc> {
92 fn clone(&self) -> Self {
93 Self {
94 inner: self.inner.clone(),
95 }
96 }
97}
98
99impl<N: RpcNodeCore, Rpc: RpcConvert> std::fmt::Debug for ArbEthApi<N, Rpc> {
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 f.debug_struct("ArbEthApi").finish_non_exhaustive()
102 }
103}
104
105impl<N: RpcNodeCore, Rpc: RpcConvert> ArbEthApi<N, Rpc> {
106 pub fn new(inner: EthApiInner<N, Rpc>) -> Self {
108 Self {
109 inner: Arc::new(inner),
110 }
111 }
112}
113
114impl<N, Rpc> ArbEthApi<N, Rpc>
115where
116 N: RpcNodeCore<Provider: StateProviderFactory>,
117 Rpc: RpcConvert,
118{
119 fn l1_posting_gas(&self, calldata_len: usize, at: BlockId) -> Result<u64, EthApiError> {
124 if calldata_len == 0 {
125 return Ok(0);
126 }
127
128 let state = self
129 .inner
130 .provider()
131 .state_by_block_id(at)
132 .map_err(|e| EthApiError::Internal(e.into()))?;
133
134 let l1_price_slot = subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT);
135 let l1_price = state
136 .storage(
137 ARBOS_STATE_ADDRESS,
138 StorageKey::from(B256::from(l1_price_slot.to_be_bytes::<32>())),
139 )
140 .map_err(|e| EthApiError::Internal(e.into()))?
141 .unwrap_or_default();
142
143 let basefee_slot = subspace_slot(L2_PRICING_SUBSPACE, L2_BASE_FEE);
144 let basefee = state
145 .storage(
146 ARBOS_STATE_ADDRESS,
147 StorageKey::from(B256::from(basefee_slot.to_be_bytes::<32>())),
148 )
149 .map_err(|e| EthApiError::Internal(e.into()))?
150 .unwrap_or_default();
151
152 if l1_price.is_zero() || basefee.is_zero() {
153 return Ok(0);
154 }
155
156 let l1_fee = l1_price
158 .saturating_mul(U256::from(TX_DATA_NON_ZERO_GAS))
159 .saturating_mul(U256::from(calldata_len));
160
161 let padded = l1_fee.saturating_mul(U256::from(GAS_ESTIMATION_L1_PRICE_PADDING))
163 / U256::from(10000u64);
164
165 let adjusted_basefee = basefee.saturating_mul(U256::from(7)) / U256::from(8);
167 let adjusted_basefee = if adjusted_basefee.is_zero() {
168 U256::from(1)
169 } else {
170 adjusted_basefee
171 };
172
173 let gas = padded / adjusted_basefee;
175 Ok(gas.try_into().unwrap_or(u64::MAX))
176 }
177}
178
179impl<N, Rpc> ArbEthApi<N, Rpc>
180where
181 N: RpcNodeCore<Provider: StateProviderFactory>,
182 EthApiError: FromEvmError<N::Evm>,
183 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
184 RpcTxReq<<Rpc as RpcConvert>::Network>: AsRef<alloy_rpc_types_eth::TransactionRequest>
185 + AsMut<alloy_rpc_types_eth::TransactionRequest>
186 + Clone
187 + Default,
188{
189 fn compute_eth_call_units_since_update(
190 &self,
191 request: RpcTxReq<<Rpc as RpcConvert>::Network>,
192 at: BlockId,
193 ) -> Result<alloy_primitives::Bytes, EthApiError> {
194 let inner = request.as_ref();
195 let (to, contract_creation) = match inner.to {
196 Some(alloy_primitives::TxKind::Call(addr)) => (addr, false),
197 Some(alloy_primitives::TxKind::Create) => (Address::ZERO, true),
198 None => (Address::ZERO, false),
199 };
200 let value = inner.value.unwrap_or(U256::ZERO);
201 let data: alloy_primitives::Bytes = inner.input.input().cloned().unwrap_or_default();
202
203 let state = self
204 .inner
205 .provider()
206 .state_by_block_id(at)
207 .map_err(|e| EthApiError::Internal(e.into()))?;
208 let read = |slot: U256| -> Result<U256, EthApiError> {
209 Ok(state
210 .storage(
211 ARBOS_STATE_ADDRESS,
212 StorageKey::from(B256::from(slot.to_be_bytes::<32>())),
213 )
214 .map_err(|e| EthApiError::Internal(e.into()))?
215 .unwrap_or_default())
216 };
217 let stored = read(subspace_slot(L1_PRICING_SUBSPACE, L1_UNITS_SINCE_UPDATE))?;
218 let chain_id_u: u64 = read(root_slot(CHAIN_ID_OFFSET))?.try_into().unwrap_or(0);
219 let brotli_level: u64 = read(root_slot(BROTLI_COMPRESSION_LEVEL_OFFSET))?
220 .try_into()
221 .unwrap_or(0);
222
223 let tx_bytes =
224 arb_precompiles::build_fake_tx_bytes(chain_id_u, to, contract_creation, value, data);
225 let raw_units = arbos::l1_pricing::poster_units_from_bytes(&tx_bytes, brotli_level);
226 let padded_units = raw_units
227 .saturating_add(arbos::l1_pricing::ESTIMATION_PADDING_UNITS)
228 .saturating_mul(10_000 + arbos::l1_pricing::ESTIMATION_PADDING_BASIS_POINTS)
229 / 10_000;
230 let total = stored.saturating_add(U256::from(padded_units));
231
232 Ok(alloy_primitives::Bytes::from(
233 total.to_be_bytes::<32>().to_vec(),
234 ))
235 }
236
237 fn compute_eth_call_current_tx_l1_fees(
238 &self,
239 request: RpcTxReq<<Rpc as RpcConvert>::Network>,
240 at: BlockId,
241 ) -> Result<alloy_primitives::Bytes, EthApiError> {
242 let inner = request.as_ref();
243 let (to, contract_creation) = match inner.to {
244 Some(alloy_primitives::TxKind::Call(addr)) => (addr, false),
245 Some(alloy_primitives::TxKind::Create) => (Address::ZERO, true),
246 None => (Address::ZERO, false),
247 };
248 let value = inner.value.unwrap_or(U256::ZERO);
249 let data: alloy_primitives::Bytes = inner.input.input().cloned().unwrap_or_default();
250
251 let state = self
252 .inner
253 .provider()
254 .state_by_block_id(at)
255 .map_err(|e| EthApiError::Internal(e.into()))?;
256 let read = |slot: U256| -> Result<U256, EthApiError> {
257 Ok(state
258 .storage(
259 ARBOS_STATE_ADDRESS,
260 StorageKey::from(B256::from(slot.to_be_bytes::<32>())),
261 )
262 .map_err(|e| EthApiError::Internal(e.into()))?
263 .unwrap_or_default())
264 };
265 let l1_price = read(subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT))?;
266 if l1_price.is_zero() {
267 return Ok(alloy_primitives::Bytes::from(vec![0u8; 32]));
268 }
269 let chain_id_u: u64 = read(root_slot(CHAIN_ID_OFFSET))?.try_into().unwrap_or(0);
270 let brotli_level: u64 = read(root_slot(BROTLI_COMPRESSION_LEVEL_OFFSET))?
271 .try_into()
272 .unwrap_or(0);
273
274 let tx_bytes =
275 arb_precompiles::build_fake_tx_bytes(chain_id_u, to, contract_creation, value, data);
276 let raw_units = arbos::l1_pricing::poster_units_from_bytes(&tx_bytes, brotli_level);
277 let padded_units = raw_units
278 .saturating_add(arbos::l1_pricing::ESTIMATION_PADDING_UNITS)
279 .saturating_mul(10_000 + arbos::l1_pricing::ESTIMATION_PADDING_BASIS_POINTS)
280 / 10_000;
281 let poster_fee = l1_price.saturating_mul(U256::from(padded_units));
282
283 Ok(alloy_primitives::Bytes::from(
284 poster_fee.to_be_bytes::<32>().to_vec(),
285 ))
286 }
287
288 async fn estimate_arb_combined_gas(
294 &self,
295 inner_req: RpcTxReq<<Rpc as RpcConvert>::Network>,
296 gas_for_l1: u64,
297 at: BlockId,
298 state_override: Option<StateOverride>,
299 ) -> Result<u64, EthApiError>
300 where
301 RpcTxReq<<Rpc as RpcConvert>::Network>: From<alloy_rpc_types_eth::TransactionRequest>,
302 {
303 use alloy_rpc_types_eth::state::EvmOverrides;
304
305 const CALL_STIPEND: u64 = 2300;
306 const ERROR_RATIO: f64 = 0.015;
307
308 let rpc_gas_cap = self.call_gas_limit();
309
310 let simulate = async |req_gas: u64| -> Result<(u64, bool), EthApiError> {
311 let mut req = inner_req.clone();
312 req.as_mut().gas = Some(req_gas);
313 let _permit = self.acquire_owned_blocking_io().await;
314 let res = self
315 .transact_call_at(req, at, EvmOverrides::state(state_override.clone()))
316 .await?;
317 Ok((res.result.gas_used(), res.result.is_success()))
318 };
319
320 let compute_cap = rpc_gas_cap.saturating_sub(gas_for_l1).max(1);
321 let (used_compute, success_first) = simulate(compute_cap).await?;
322 if !success_first {
323 return Ok(rpc_gas_cap);
324 }
325
326 let used_total = used_compute.saturating_add(gas_for_l1);
327 let mut lo = used_total.saturating_sub(1);
328 let mut hi = rpc_gas_cap.max(used_total);
329
330 let optimistic = used_total.saturating_add(CALL_STIPEND).saturating_mul(64) / 63;
331 if optimistic < hi {
332 let compute_limit = optimistic.saturating_sub(gas_for_l1);
333 let (_, ok) = simulate(compute_limit).await?;
334 if ok {
335 hi = optimistic;
336 } else {
337 lo = optimistic;
338 }
339 }
340
341 while lo + 1 < hi {
342 let ratio = (hi - lo) as f64 / hi as f64;
343 if ratio < ERROR_RATIO {
344 break;
345 }
346 let mut mid = lo + (hi - lo) / 2;
347 let two_lo = lo.saturating_mul(2);
348 if mid > two_lo {
349 mid = two_lo;
350 }
351 let compute_limit = mid.saturating_sub(gas_for_l1);
352 let (_, ok) = simulate(compute_limit).await?;
353 if ok {
354 hi = mid;
355 } else {
356 lo = mid;
357 }
358 }
359
360 Ok(hi)
361 }
362
363 async fn estimate_retryable_ticket_gas(
366 &self,
367 input: &alloy_primitives::Bytes,
368 at: BlockId,
369 state_override: Option<StateOverride>,
370 ) -> Result<U256, EthApiError>
371 where
372 RpcTxReq<<Rpc as RpcConvert>::Network>: From<alloy_rpc_types_eth::TransactionRequest>,
373 {
374 use alloy_primitives::{Bytes, TxKind};
375 use alloy_rpc_types_eth::TransactionRequest;
376
377 const HEAD_LEN: usize = 4 + 32 * 7;
381 if input.len() < HEAD_LEN {
382 return Err(EthApiError::InvalidParams(
383 "estimateRetryableTicket: calldata too short".into(),
384 ));
385 }
386 let sender = Address::from_slice(&input[4 + 12..4 + 32]);
387 let _deposit = U256::from_be_slice(&input[36..68]);
388 let to_word = &input[68..100];
389 let to = Address::from_slice(&to_word[12..32]);
390 let l2_call_value = U256::from_be_slice(&input[100..132]);
391 let _excess_fee_refund = Address::from_slice(&input[132 + 12..132 + 32]);
392 let _call_value_refund = Address::from_slice(&input[164 + 12..164 + 32]);
393 let data_offset: usize =
394 U256::from_be_slice(&input[196..228])
395 .try_into()
396 .map_err(|_| {
397 EthApiError::InvalidParams(
398 "estimateRetryableTicket: invalid data offset".into(),
399 )
400 })?;
401 let abi_body = &input[4..];
402 let data: Bytes = if data_offset + 32 <= abi_body.len() {
403 let len: usize = U256::from_be_slice(&abi_body[data_offset..data_offset + 32])
404 .try_into()
405 .map_err(|_| {
406 EthApiError::InvalidParams(
407 "estimateRetryableTicket: data length too large".into(),
408 )
409 })?;
410 if data_offset + 32 + len > abi_body.len() {
411 return Err(EthApiError::InvalidParams(
412 "estimateRetryableTicket: data out of bounds".into(),
413 ));
414 }
415 Bytes::copy_from_slice(&abi_body[data_offset + 32..data_offset + 32 + len])
416 } else {
417 Bytes::new()
418 };
419
420 let kind = if to == Address::ZERO {
423 TxKind::Create
424 } else {
425 TxKind::Call(to)
426 };
427 let data_ref = data.clone();
428 let equivalent = TransactionRequest {
429 from: Some(sender),
430 to: Some(kind),
431 value: Some(l2_call_value),
432 input: data.into(),
433 ..Default::default()
434 };
435 let equivalent_req: RpcTxReq<<Rpc as RpcConvert>::Network> = equivalent.into();
436
437 let redeem_gas =
442 EstimateCall::estimate_gas_at(self, equivalent_req, at, state_override).await?;
443
444 let (zeros, non_zeros) =
451 data_ref.iter().fold(
452 (0u64, 0u64),
453 |(z, nz), &b| if b == 0 { (z + 1, nz) } else { (z, nz + 1) },
454 );
455 let calldata_gas = zeros
456 .saturating_mul(4)
457 .saturating_add(non_zeros.saturating_mul(16));
458 let submit_intrinsic = 21_000u64.saturating_add(calldata_gas);
459
460 Ok(redeem_gas.saturating_add(U256::from(submit_intrinsic)))
461 }
462
463 async fn simulate_retryable_ticket_call(
467 &self,
468 input: &alloy_primitives::Bytes,
469 at: BlockId,
470 _overrides: alloy_rpc_types_eth::state::EvmOverrides,
471 ) -> Result<alloy_primitives::Bytes, EthApiError>
472 where
473 RpcTxReq<<Rpc as RpcConvert>::Network>: From<alloy_rpc_types_eth::TransactionRequest>,
474 {
475 use alloy_primitives::{keccak256, Bytes};
476 use arb_alloy_consensus::{tx::ArbSubmitRetryableTx, ArbTxEnvelope};
477
478 const HEAD_LEN: usize = 4 + 32 * 7;
479 if input.len() < HEAD_LEN {
480 return Err(EthApiError::InvalidParams(
481 "estimateRetryableTicket: calldata too short".into(),
482 ));
483 }
484 let sender = Address::from_slice(&input[4 + 12..4 + 32]);
485 let deposit = U256::from_be_slice(&input[36..68]);
486 let to = Address::from_slice(&input[68 + 12..100]);
487 let l2_call_value = U256::from_be_slice(&input[100..132]);
488 let excess_fee_refund = Address::from_slice(&input[132 + 12..164]);
489 let call_value_refund = Address::from_slice(&input[164 + 12..196]);
490 let data_offset: usize =
491 U256::from_be_slice(&input[196..228])
492 .try_into()
493 .map_err(|_| {
494 EthApiError::InvalidParams(
495 "estimateRetryableTicket: invalid data offset".into(),
496 )
497 })?;
498 let abi_body = &input[4..];
499 let data: Bytes = if data_offset + 32 <= abi_body.len() {
500 let len: usize = U256::from_be_slice(&abi_body[data_offset..data_offset + 32])
501 .try_into()
502 .map_err(|_| {
503 EthApiError::InvalidParams(
504 "estimateRetryableTicket: data length too large".into(),
505 )
506 })?;
507 if data_offset + 32 + len > abi_body.len() {
508 return Err(EthApiError::InvalidParams(
509 "estimateRetryableTicket: data out of bounds".into(),
510 ));
511 }
512 Bytes::copy_from_slice(&abi_body[data_offset + 32..data_offset + 32 + len])
513 } else {
514 Bytes::new()
515 };
516
517 let l1_base_fee = {
518 let state = self
519 .inner
520 .provider()
521 .state_by_block_id(at)
522 .map_err(|e| EthApiError::Internal(e.into()))?;
523 let slot = subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT);
524 state
525 .storage(
526 ARBOS_STATE_ADDRESS,
527 StorageKey::from(B256::from(slot.to_be_bytes::<32>())),
528 )
529 .map_err(|e| EthApiError::Internal(e.into()))?
530 .unwrap_or_default()
531 };
532
533 let aliased_from = apply_l1_to_l2_alias(sender);
534 let max_submission_fee =
535 arbos::retryables::retryable_submission_fee(data.len(), l1_base_fee);
536 let retry_to = if to == Address::ZERO { None } else { Some(to) };
537
538 let gas_cap = self.inner.gas_cap();
539 let gas = gas_cap;
540
541 let tx = ArbSubmitRetryableTx {
542 chain_id: U256::ZERO,
543 request_id: B256::ZERO,
544 from: aliased_from,
545 l1_base_fee,
546 deposit_value: deposit,
547 gas_fee_cap: U256::ZERO,
548 gas,
549 retry_to,
550 retry_value: l2_call_value,
551 beneficiary: call_value_refund,
552 max_submission_fee,
553 fee_refund_addr: excess_fee_refund,
554 retry_data: data,
555 };
556 let envelope = ArbTxEnvelope::SubmitRetryable(tx);
557 let encoded = envelope.encode_typed();
558 let hash = keccak256(&encoded);
559 Ok(alloy_primitives::Bytes::from(hash.0.to_vec()))
560 }
561
562 async fn get_retryable_abi(
567 &self,
568 input: &alloy_primitives::Bytes,
569 at: BlockId,
570 ) -> Result<alloy_primitives::Bytes, EthApiError> {
571 use arb_precompiles::storage_slot::{derive_subspace_key, map_slot, ROOT_STORAGE_KEY};
572 use arbos::retryables::{
573 BENEFICIARY_OFFSET, CALLDATA_KEY, CALLVALUE_OFFSET, FROM_OFFSET, NUM_TRIES_OFFSET,
574 TIMEOUT_OFFSET, TO_OFFSET,
575 };
576
577 if input.len() < 4 + 32 {
578 return Err(EthApiError::InvalidParams(
579 "getRetryable: expected bytes32 ticket".into(),
580 ));
581 }
582 let ticket = B256::from_slice(&input[4..36]);
583
584 let state = self
585 .inner
586 .provider()
587 .state_by_block_id(at)
588 .map_err(|e| EthApiError::Internal(e.into()))?;
589 let load = |slot: U256| -> Result<U256, EthApiError> {
590 let k = StorageKey::from(B256::from(slot.to_be_bytes::<32>()));
591 Ok(state
592 .storage(ARBOS_STATE_ADDRESS, k)
593 .map_err(|e| EthApiError::Internal(e.into()))?
594 .unwrap_or(U256::ZERO))
595 };
596
597 let retryables_key = derive_subspace_key(
598 ROOT_STORAGE_KEY,
599 arb_precompiles::storage_slot::RETRYABLES_SUBSPACE,
600 );
601 let r_key = derive_subspace_key(retryables_key.as_slice(), ticket.as_slice());
602
603 let timeout: u64 = load(map_slot(r_key.as_slice(), TIMEOUT_OFFSET))?
604 .try_into()
605 .unwrap_or(0);
606 if timeout == 0 {
607 return Err(EthApiError::InvalidParams(format!(
608 "no retryable with id 0x{ticket:x}"
609 )));
610 }
611 let from_word = load(map_slot(r_key.as_slice(), FROM_OFFSET))?;
612 let from = Address::from_slice(&from_word.to_be_bytes::<32>()[12..]);
613 let to_word = load(map_slot(r_key.as_slice(), TO_OFFSET))?;
614 let to_bytes: [u8; 32] = to_word.to_be_bytes();
615 let to = Address::from_slice(&to_bytes[12..]);
619 let value = load(map_slot(r_key.as_slice(), CALLVALUE_OFFSET))?;
620 let beneficiary_word = load(map_slot(r_key.as_slice(), BENEFICIARY_OFFSET))?;
621 let beneficiary = Address::from_slice(&beneficiary_word.to_be_bytes::<32>()[12..]);
622 let tries: u64 = load(map_slot(r_key.as_slice(), NUM_TRIES_OFFSET))?
623 .try_into()
624 .unwrap_or(0);
625
626 let cd_key = derive_subspace_key(r_key.as_slice(), CALLDATA_KEY);
630 let size: usize = load(map_slot(cd_key.as_slice(), 0))?
631 .try_into()
632 .unwrap_or(0);
633 let chunks = size.div_ceil(32);
634 let mut data = Vec::with_capacity(size);
635 for i in 0..chunks {
636 let chunk = load(map_slot(cd_key.as_slice(), 1 + i as u64))?;
637 data.extend_from_slice(&chunk.to_be_bytes::<32>());
638 }
639 data.truncate(size);
640
641 let mut out = vec![0u8; 7 * 32];
646 U256::from(timeout)
647 .to_be_bytes::<32>()
648 .iter()
649 .enumerate()
650 .for_each(|(i, b)| out[i] = *b);
651 out[32 + 12..32 + 32].copy_from_slice(from.as_slice());
652 out[64 + 12..64 + 32].copy_from_slice(to.as_slice());
653 out[96..128].copy_from_slice(&value.to_be_bytes::<32>());
654 out[128 + 12..128 + 32].copy_from_slice(beneficiary.as_slice());
655 U256::from(tries)
656 .to_be_bytes::<32>()
657 .iter()
658 .enumerate()
659 .for_each(|(i, b)| out[160 + i] = *b);
660 U256::from(7u64 * 32)
662 .to_be_bytes::<32>()
663 .iter()
664 .enumerate()
665 .for_each(|(i, b)| out[192 + i] = *b);
666 let padded_len = size.div_ceil(32) * 32;
668 let mut tail = vec![0u8; 32 + padded_len];
669 U256::from(size as u64)
670 .to_be_bytes::<32>()
671 .iter()
672 .enumerate()
673 .for_each(|(i, b)| tail[i] = *b);
674 tail[32..32 + size].copy_from_slice(&data);
675 out.extend_from_slice(&tail);
676 Ok(alloy_primitives::Bytes::from(out))
677 }
678
679 async fn construct_outbox_proof(
685 &self,
686 input: &alloy_primitives::Bytes,
687 at: BlockId,
688 ) -> Result<alloy_primitives::Bytes, EthApiError>
689 where
690 N: RpcNodeCore<
691 Provider: reth_provider::BlockReaderIdExt + reth_storage_api::ReceiptProvider,
692 >,
693 {
694 use std::collections::HashMap;
695
696 use alloy_consensus::TxReceipt;
697 use arb_precompiles::arbsys::{
698 l2_to_l1_tx_topic, send_merkle_update_topic, ARBSYS_ADDRESS,
699 };
700 use reth_provider::{BlockNumReader, ReceiptProvider};
701
702 use crate::outbox_proof::{encode_outbox_proof, finalize_proof, plan_proof, LevelAndLeaf};
703
704 if input.len() < 4 + 64 {
705 return Err(EthApiError::InvalidParams(
706 "constructOutboxProof: expected (uint64 size, uint64 leaf)".into(),
707 ));
708 }
709 let size: u64 = U256::from_be_slice(&input[4..36])
710 .try_into()
711 .unwrap_or(u64::MAX);
712 let leaf: u64 = U256::from_be_slice(&input[36..68])
713 .try_into()
714 .unwrap_or(u64::MAX);
715
716 let plan = plan_proof(size, leaf).ok_or_else(|| {
717 EthApiError::InvalidParams(format!("constructOutboxProof: leaf {leaf} ≥ size {size}"))
718 })?;
719
720 let provider = self.inner.provider();
723 let tip = provider
724 .best_block_number()
725 .map_err(|e| EthApiError::Internal(e.into()))?;
726 let upper = match at {
727 BlockId::Number(alloy_rpc_types_eth::BlockNumberOrTag::Number(n)) => n.min(tip),
728 _ => tip,
729 };
730
731 let merkle_topic = send_merkle_update_topic();
736 let l2tol1_topic = l2_to_l1_tx_topic();
737
738 let mut positions: HashMap<[u8; 32], B256> = HashMap::new();
740
741 let receipts_per_block = provider
742 .receipts_by_block_range(0..=upper)
743 .map_err(|e| EthApiError::Internal(e.into()))?;
744
745 for block_receipts in receipts_per_block {
746 for receipt in block_receipts {
747 for log in receipt.logs() {
748 if log.address != ARBSYS_ADDRESS {
749 continue;
750 }
751 let topics = log.data.topics();
752 if topics.len() < 4 {
753 continue;
754 }
755 let kind = topics[0];
756 let is_merkle = kind == merkle_topic;
757 let is_l2tol1 = kind == l2tol1_topic;
758 if !is_merkle && !is_l2tol1 {
759 continue;
760 }
761 let pos: [u8; 32] = topics[3].0;
764 let hash: B256 = topics[2];
765 positions.insert(pos, hash);
766 }
767 }
768 }
769
770 let lookup = |p: LevelAndLeaf| -> Option<B256> {
771 let topic = p.as_topic();
772 positions.get(&topic.0).copied()
773 };
774
775 let (send, root, proof) = finalize_proof(&plan, leaf, lookup)
776 .map_err(|e| EthApiError::InvalidParams(format!("constructOutboxProof: {e}")))?;
777
778 Ok(encode_outbox_proof(send, root, &proof))
779 }
780}
781
782impl<N, Rpc> EthApiTypes for ArbEthApi<N, Rpc>
785where
786 N: RpcNodeCore,
787 Rpc: RpcConvert<Error = EthApiError>,
788{
789 type Error = EthApiError;
790 type NetworkTypes = Rpc::Network;
791 type RpcConvert = Rpc;
792
793 fn converter(&self) -> &Self::RpcConvert {
794 self.inner.converter()
795 }
796}
797
798impl<N, Rpc> RpcNodeCore for ArbEthApi<N, Rpc>
799where
800 N: RpcNodeCore,
801 Rpc: RpcConvert,
802{
803 type Primitives = N::Primitives;
804 type Provider = N::Provider;
805 type Pool = N::Pool;
806 type Evm = N::Evm;
807 type Network = N::Network;
808
809 #[inline]
810 fn pool(&self) -> &Self::Pool {
811 self.inner.pool()
812 }
813
814 #[inline]
815 fn evm_config(&self) -> &Self::Evm {
816 self.inner.evm_config()
817 }
818
819 #[inline]
820 fn network(&self) -> &Self::Network {
821 self.inner.network()
822 }
823
824 #[inline]
825 fn provider(&self) -> &Self::Provider {
826 self.inner.provider()
827 }
828}
829
830impl<N, Rpc> RpcNodeCoreExt for ArbEthApi<N, Rpc>
831where
832 N: RpcNodeCore,
833 Rpc: RpcConvert,
834{
835 #[inline]
836 fn cache(&self) -> &EthStateCache<N::Primitives> {
837 self.inner.cache()
838 }
839}
840
841impl<N, Rpc> EthApiSpec for ArbEthApi<N, Rpc>
842where
843 N: RpcNodeCore,
844 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
845{
846 fn starting_block(&self) -> U256 {
847 self.inner.starting_block()
848 }
849}
850
851impl<N, Rpc> SpawnBlocking for ArbEthApi<N, Rpc>
852where
853 N: RpcNodeCore,
854 Rpc: RpcConvert<Error = EthApiError>,
855{
856 #[inline]
857 fn io_task_spawner(&self) -> &Runtime {
858 self.inner.task_spawner()
859 }
860
861 #[inline]
862 fn tracing_task_pool(&self) -> &BlockingTaskPool {
863 self.inner.blocking_task_pool()
864 }
865
866 #[inline]
867 fn tracing_task_guard(&self) -> &BlockingTaskGuard {
868 self.inner.blocking_task_guard()
869 }
870
871 #[inline]
872 fn blocking_io_task_guard(&self) -> &Arc<tokio::sync::Semaphore> {
873 self.inner.blocking_io_request_semaphore()
874 }
875}
876
877impl<N, Rpc> LoadFee for ArbEthApi<N, Rpc>
878where
879 N: RpcNodeCore,
880 EthApiError: FromEvmError<N::Evm>,
881 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
882{
883 fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> {
884 self.inner.gas_oracle()
885 }
886
887 fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> {
888 self.inner.fee_history_cache()
889 }
890}
891
892impl<N, Rpc> LoadState for ArbEthApi<N, Rpc>
893where
894 N: RpcNodeCore,
895 Rpc: RpcConvert<Primitives = N::Primitives>,
896 Self: LoadPendingBlock,
897{
898}
899
900impl<N, Rpc> EthState for ArbEthApi<N, Rpc>
901where
902 N: RpcNodeCore,
903 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
904 Self: LoadPendingBlock,
905{
906 fn max_proof_window(&self) -> u64 {
907 self.inner.eth_proof_window()
908 }
909}
910
911impl<N, Rpc> EthFees for ArbEthApi<N, Rpc>
912where
913 N: RpcNodeCore,
914 EthApiError: FromEvmError<N::Evm>,
915 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
916{
917 fn gas_price(&self) -> impl std::future::Future<Output = Result<U256, Self::Error>> + Send
920 where
921 Self: reth_rpc_eth_api::helpers::LoadBlock,
922 {
923 use alloy_consensus::BlockHeader;
924 use reth_storage_api::{BlockNumReader, HeaderProvider};
925 async move {
926 let best = self
927 .provider()
928 .best_block_number()
929 .map_err(|e| EthApiError::Internal(e.into()))?;
930 let header_opt = HeaderProvider::sealed_header(self.provider(), best)
931 .map_err(|e| EthApiError::Internal(e.into()))?;
932 let base_fee = match header_opt {
933 Some(sealed) => sealed.header().base_fee_per_gas().unwrap_or_default(),
934 None => 0,
935 };
936 Ok(U256::from(base_fee))
937 }
938 }
939
940 #[allow(clippy::manual_async_fn)]
941 fn suggested_priority_fee(
942 &self,
943 ) -> impl std::future::Future<Output = Result<U256, Self::Error>> + Send
944 where
945 Self: 'static,
946 {
947 async move { Ok(U256::ZERO) }
948 }
949}
950
951impl<N, Rpc> Trace for ArbEthApi<N, Rpc>
952where
953 N: RpcNodeCore,
954 EthApiError: FromEvmError<N::Evm>,
955 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
956{
957}
958
959impl<N, Rpc> LoadPendingBlock for ArbEthApi<N, Rpc>
960where
961 N: RpcNodeCore,
962 EthApiError: FromEvmError<N::Evm>,
963 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
964{
965 fn pending_block(&self) -> &tokio::sync::Mutex<Option<PendingBlock<N::Primitives>>> {
966 self.inner.pending_block()
967 }
968
969 fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<N::Evm> {
970 self.inner.pending_env_builder()
971 }
972
973 fn pending_block_kind(&self) -> PendingBlockKind {
974 self.inner.pending_block_kind()
975 }
976}
977
978impl<N, Rpc> LoadBlock for ArbEthApi<N, Rpc>
979where
980 Self: LoadPendingBlock,
981 N: RpcNodeCore,
982 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
983{
984}
985
986impl<N, Rpc> LoadTransaction for ArbEthApi<N, Rpc>
987where
988 N: RpcNodeCore,
989 EthApiError: FromEvmError<N::Evm>,
990 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
991{
992}
993
994impl<N, Rpc> EthBlocks for ArbEthApi<N, Rpc>
995where
996 N: RpcNodeCore,
997 EthApiError: FromEvmError<N::Evm>,
998 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
999{
1000}
1001
1002impl<N, Rpc> EthTransactions for ArbEthApi<N, Rpc>
1003where
1004 N: RpcNodeCore,
1005 EthApiError: FromEvmError<N::Evm>,
1006 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
1007{
1008 fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes> {
1009 self.inner.signers()
1010 }
1011
1012 fn send_raw_transaction_sync_timeout(&self) -> Duration {
1013 self.inner.send_raw_transaction_sync_timeout()
1014 }
1015
1016 async fn send_transaction(
1017 &self,
1018 origin: TransactionOrigin,
1019 tx: WithEncoded<Recovered<PoolPooledTx<Self::Pool>>>,
1020 ) -> Result<B256, Self::Error> {
1021 let (_tx_bytes, recovered) = tx.split();
1022 let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
1023
1024 let AddedTransactionOutcome { hash, .. } = self
1025 .inner
1026 .add_pool_transaction(origin, pool_transaction)
1027 .await?;
1028
1029 Ok(hash)
1030 }
1031}
1032
1033impl<N, Rpc> LoadReceipt for ArbEthApi<N, Rpc>
1034where
1035 N: RpcNodeCore<Primitives = arb_primitives::ArbPrimitives>,
1036 EthApiError: FromEvmError<N::Evm>,
1037 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
1038 Self::Error: reth_rpc_eth_types::error::FromEthApiError,
1039{
1040 fn build_transaction_receipt(
1049 &self,
1050 tx: reth_storage_api::ProviderTx<Self::Provider>,
1051 meta: alloy_consensus::transaction::TransactionMeta,
1052 receipt: reth_storage_api::ProviderReceipt<Self::Provider>,
1053 ) -> impl std::future::Future<
1054 Output = Result<reth_rpc_eth_api::RpcReceipt<Self::NetworkTypes>, Self::Error>,
1055 > + Send {
1056 use alloy_consensus::TxReceipt;
1057 use reth_primitives_traits::SignerRecoverable;
1058 use reth_rpc_convert::transaction::ConvertReceiptInput;
1059 use reth_rpc_eth_api::RpcNodeCoreExt;
1060 use reth_rpc_eth_types::{
1061 error::FromEthApiError, utils::calculate_gas_used_and_next_log_index, EthApiError,
1062 };
1063 async move {
1064 let hash = meta.block_hash;
1065 let all_receipts = self
1066 .cache()
1067 .get_receipts(hash)
1068 .await
1069 .map_err(<Self::Error as FromEthApiError>::from_eth_err)?
1070 .ok_or_else(|| {
1071 <Self::Error as FromEthApiError>::from_eth_err(EthApiError::HeaderNotFound(
1072 hash.into(),
1073 ))
1074 })?;
1075
1076 let (gas_used, next_log_index) =
1077 calculate_gas_used_and_next_log_index(meta.index, &all_receipts);
1078
1079 let block = self
1080 .cache()
1081 .get_recovered_block(hash)
1082 .await
1083 .map_err(<Self::Error as FromEthApiError>::from_eth_err)?;
1084
1085 let tx_recovered = tx
1086 .try_into_recovered_unchecked()
1087 .map_err(<Self::Error as FromEthApiError>::from_eth_err)?;
1088
1089 let input = ConvertReceiptInput {
1090 tx: tx_recovered.as_recovered_ref(),
1091 gas_used: receipt.cumulative_gas_used() - gas_used,
1092 receipt,
1093 next_log_index,
1094 meta,
1095 };
1096
1097 let result = match block {
1098 Some(sealed_block_with_senders) => self.converter().convert_receipts_with_block(
1099 vec![input],
1100 sealed_block_with_senders.sealed_block(),
1101 )?,
1102 None => self.converter().convert_receipts(vec![input])?,
1103 };
1104 Ok(result.into_iter().next().expect("one receipt in, one out"))
1105 }
1106 }
1107}
1108
1109impl<N, Rpc> Call for ArbEthApi<N, Rpc>
1112where
1113 N: RpcNodeCore,
1114 EthApiError: FromEvmError<N::Evm>,
1115 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
1116{
1117 #[inline]
1118 fn call_gas_limit(&self) -> u64 {
1119 self.inner.gas_cap()
1120 }
1121
1122 #[inline]
1123 fn max_simulate_blocks(&self) -> u64 {
1124 self.inner.max_simulate_blocks()
1125 }
1126
1127 #[inline]
1128 fn evm_memory_limit(&self) -> u64 {
1129 self.inner.evm_memory_limit()
1130 }
1131}
1132
1133impl<N, Rpc> EstimateCall for ArbEthApi<N, Rpc>
1134where
1135 N: RpcNodeCore,
1136 EthApiError: FromEvmError<N::Evm>,
1137 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
1138{
1139 }
1141
1142impl<N, Rpc> EthCall for ArbEthApi<N, Rpc>
1143where
1144 N: RpcNodeCore<
1145 Provider: StateProviderFactory + reth_provider::BlockReaderIdExt + Clone,
1146 Primitives = arb_primitives::ArbPrimitives,
1147 >,
1148 EthApiError: FromEvmError<N::Evm>,
1149 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
1150 RpcTxReq<<Rpc as RpcConvert>::Network>: AsRef<alloy_rpc_types_eth::TransactionRequest>
1151 + AsMut<alloy_rpc_types_eth::TransactionRequest>
1152 + Clone
1153 + Default
1154 + From<alloy_rpc_types_eth::TransactionRequest>,
1155{
1156 #[allow(clippy::manual_async_fn)]
1166 fn estimate_gas_at(
1167 &self,
1168 request: RpcTxReq<<Self::RpcConvert as RpcConvert>::Network>,
1169 at: BlockId,
1170 state_override: Option<StateOverride>,
1171 ) -> impl std::future::Future<Output = Result<U256, Self::Error>> + Send {
1172 async move {
1173 use crate::nodeinterface_rpc::NODE_INTERFACE_ADDRESS;
1174 use alloy_primitives::TxKind;
1175
1176 let inner = request.as_ref();
1177 let target: Option<Address> = match inner.to {
1178 Some(TxKind::Call(addr)) => Some(addr),
1179 _ => None,
1180 };
1181 let input_bytes: Option<alloy_primitives::Bytes> = inner.input.input().cloned();
1182
1183 if target == Some(NODE_INTERFACE_ADDRESS) {
1192 if let Some(ref buf) = input_bytes {
1193 if buf.len() >= 4 && buf[..4] == [0xc3, 0xdc, 0x58, 0x79] {
1194 return self
1195 .estimate_retryable_ticket_gas(buf, at, state_override)
1196 .await;
1197 }
1198 }
1199 }
1200
1201 let calldata_len = input_bytes.as_ref().map(|b| b.len()).unwrap_or(0);
1203
1204 let compute_gas =
1206 EstimateCall::estimate_gas_at(self, request, at, state_override).await?;
1207
1208 let l1_gas = self.l1_posting_gas(calldata_len, at)?;
1210
1211 if l1_gas > 0 {
1212 trace!(target: "rpc::eth::estimate", %compute_gas, l1_gas, "Adding L1 posting gas to estimate");
1213 }
1214
1215 Ok(compute_gas.saturating_add(U256::from(l1_gas)))
1216 }
1217 }
1218
1219 #[allow(clippy::manual_async_fn)]
1224 fn call(
1225 &self,
1226 request: RpcTxReq<<Self::RpcConvert as RpcConvert>::Network>,
1227 block_number: Option<BlockId>,
1228 overrides: alloy_rpc_types_eth::state::EvmOverrides,
1229 ) -> impl std::future::Future<Output = Result<alloy_primitives::Bytes, Self::Error>> + Send
1230 {
1231 async move {
1232 use crate::nodeinterface_rpc::{
1233 encode_gas_estimate_components, encode_l2_block_range, NODE_INTERFACE_ADDRESS,
1234 SEL_GAS_ESTIMATE_COMPONENTS, SEL_GAS_ESTIMATE_L1_COMPONENT,
1235 SEL_L2_BLOCK_RANGE_FOR_L1,
1236 };
1237 use alloy_primitives::{Address, TxKind};
1238
1239 let target: Option<Address> = match request.as_ref().to {
1242 Some(TxKind::Call(addr)) => Some(addr),
1243 _ => None,
1244 };
1245 let is_ni = target == Some(NODE_INTERFACE_ADDRESS);
1246 let is_ni_debug = target == Some(arb_precompiles::NODE_INTERFACE_DEBUG_ADDRESS);
1247
1248 if target == Some(arb_precompiles::ARBGASINFO_ADDRESS) {
1253 let input_bytes = request.as_ref().input.input().cloned().unwrap_or_default();
1254 if input_bytes.len() == 4 && input_bytes.as_ref() == SEL_GET_CURRENT_TX_L1_FEES {
1255 return self.compute_eth_call_current_tx_l1_fees(
1256 request,
1257 block_number.unwrap_or_default(),
1258 );
1259 }
1260 if input_bytes.len() == 4
1261 && input_bytes.as_ref() == SEL_GET_L1_PRICING_UNITS_SINCE_UPDATE
1262 {
1263 return self.compute_eth_call_units_since_update(
1264 request,
1265 block_number.unwrap_or_default(),
1266 );
1267 }
1268 }
1269
1270 if !is_ni && !is_ni_debug {
1271 let _permit = self.acquire_owned_blocking_io().await;
1272 let res = self
1273 .transact_call_at(request, block_number.unwrap_or_default(), overrides)
1274 .await?;
1275 return <Self::Error as reth_rpc_eth_types::error::api::FromEvmError<N::Evm>>::ensure_success(res.result);
1276 }
1277
1278 if is_ni_debug {
1280 let at = block_number.unwrap_or_default();
1281 let data: alloy_primitives::Bytes =
1282 request.as_ref().input.input().cloned().unwrap_or_default();
1283 return self.get_retryable_abi(&data, at).await;
1284 }
1285
1286 let input_bytes = request.as_ref().input.input().cloned().unwrap_or_default();
1288 if input_bytes.len() < 4 {
1289 let _permit = self.acquire_owned_blocking_io().await;
1291 let res = self
1292 .transact_call_at(request, block_number.unwrap_or_default(), overrides)
1293 .await?;
1294 return <Self::Error as reth_rpc_eth_types::error::api::FromEvmError<N::Evm>>::ensure_success(res.result);
1295 }
1296 let selector: [u8; 4] = [
1297 input_bytes[0],
1298 input_bytes[1],
1299 input_bytes[2],
1300 input_bytes[3],
1301 ];
1302 let at = block_number.unwrap_or_default();
1303
1304 match selector {
1305 SEL_GAS_ESTIMATE_COMPONENTS | SEL_GAS_ESTIMATE_L1_COMPONENT => {
1306 use alloy_rpc_types_eth::TransactionRequest;
1307
1308 let (inner_to, inner_creation, inner_data) =
1309 arb_precompiles::decode_estimate_args(&input_bytes).ok_or_else(|| {
1310 EthApiError::InvalidParams(
1311 "gasEstimateComponents: malformed calldata".into(),
1312 )
1313 })?;
1314
1315 let (l1_price, basefee, min_basefee, chain_id_u, brotli_level) = {
1316 let state = self
1317 .inner
1318 .provider()
1319 .state_by_block_id(at)
1320 .map_err(|e| EthApiError::Internal(e.into()))?;
1321 let read = |slot: U256| -> Result<U256, EthApiError> {
1322 Ok(state
1323 .storage(
1324 ARBOS_STATE_ADDRESS,
1325 StorageKey::from(B256::from(slot.to_be_bytes::<32>())),
1326 )
1327 .map_err(|e| EthApiError::Internal(e.into()))?
1328 .unwrap_or_default())
1329 };
1330 let l1_price = read(subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT))?;
1331 let basefee = read(subspace_slot(L2_PRICING_SUBSPACE, L2_BASE_FEE))?;
1332 let min_basefee =
1333 read(subspace_slot(L2_PRICING_SUBSPACE, L2_MIN_BASE_FEE))?;
1334 let chain_id_u: u64 =
1335 read(root_slot(CHAIN_ID_OFFSET))?.try_into().unwrap_or(0);
1336 let brotli_level: u64 = read(root_slot(BROTLI_COMPRESSION_LEVEL_OFFSET))?
1337 .try_into()
1338 .unwrap_or(0);
1339 (l1_price, basefee, min_basefee, chain_id_u, brotli_level)
1340 };
1341
1342 let gas_for_l1 = arb_precompiles::compute_l1_gas_for_estimate(
1343 chain_id_u,
1344 inner_to,
1345 inner_creation,
1346 U256::ZERO,
1347 inner_data.clone(),
1348 l1_price,
1349 basefee,
1350 min_basefee,
1351 brotli_level,
1352 );
1353
1354 if selector == SEL_GAS_ESTIMATE_L1_COMPONENT {
1355 let mut out = vec![0u8; 96];
1356 out[24..32].copy_from_slice(&gas_for_l1.to_be_bytes());
1357 out[32..64].copy_from_slice(&basefee.to_be_bytes::<32>());
1358 out[64..96].copy_from_slice(&l1_price.to_be_bytes::<32>());
1359 return Ok(alloy_primitives::Bytes::from(out));
1360 }
1361
1362 let kind = if inner_creation {
1363 TxKind::Create
1364 } else {
1365 TxKind::Call(inner_to)
1366 };
1367 let from = request.as_ref().from.unwrap_or(Address::ZERO);
1368 let inner_request = TransactionRequest {
1369 from: Some(from),
1370 to: Some(kind),
1371 value: Some(U256::ZERO),
1372 input: inner_data.into(),
1373 ..Default::default()
1374 };
1375 let inner_req: RpcTxReq<<Rpc as RpcConvert>::Network> = inner_request.into();
1376
1377 let total = self
1378 .estimate_arb_combined_gas(inner_req, gas_for_l1, at, overrides.state)
1379 .await?;
1380
1381 Ok(encode_gas_estimate_components(
1382 total, gas_for_l1, basefee, l1_price,
1383 ))
1384 }
1385
1386 SEL_L2_BLOCK_RANGE_FOR_L1 => {
1387 use reth_provider::{BlockNumReader, BlockReaderIdExt};
1388
1389 if input_bytes.len() < 4 + 32 {
1390 return Err(EthApiError::InvalidParams(
1391 "l2BlockRangeForL1: missing uint64 arg".into(),
1392 ));
1393 }
1394 let target_l1: u64 = U256::from_be_slice(&input_bytes[4..36])
1395 .try_into()
1396 .unwrap_or(u64::MAX);
1397
1398 let provider = self.inner.provider().clone();
1399 let best = provider
1400 .best_block_number()
1401 .map_err(|e| EthApiError::Internal(e.into()))?;
1402
1403 let mix_hash_of = move |n: u64| -> Option<B256> {
1404 use alloy_consensus::BlockHeader;
1405 provider
1406 .sealed_header_by_number_or_tag(
1407 alloy_rpc_types_eth::BlockNumberOrTag::Number(n),
1408 )
1409 .ok()
1410 .flatten()
1411 .and_then(|h| h.header().mix_hash())
1412 };
1413
1414 match crate::nodeinterface_rpc::find_l2_block_range(
1415 target_l1,
1416 best,
1417 mix_hash_of,
1418 ) {
1419 Some((first, last)) => Ok(encode_l2_block_range(first, last)),
1420 None => Err(EthApiError::InvalidParams(format!(
1421 "l2BlockRangeForL1: no L2 blocks found for L1 block {target_l1}"
1422 ))),
1423 }
1424 }
1425
1426 [0xc3, 0xdc, 0x58, 0x79] => {
1428 self.simulate_retryable_ticket_call(&input_bytes, at, overrides)
1429 .await
1430 }
1431
1432 [0x42, 0x69, 0x63, 0x50] => self.construct_outbox_proof(&input_bytes, at).await,
1436
1437 _ => {
1438 let _permit = self.acquire_owned_blocking_io().await;
1440 let res = self.transact_call_at(request, at, overrides).await?;
1441 <Self::Error as reth_rpc_eth_types::error::api::FromEvmError<N::Evm>>::ensure_success(res.result)
1442 }
1443 }
1444 }
1445 }
1446}