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