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 LoadBlock, LoadFee, LoadPendingBlock, LoadReceipt, LoadState, LoadTransaction,
25 SpawnBlocking, Trace, estimate::EstimateCall, pending_block::PendingEnvBuilder,
26 },
27};
28use reth_rpc_eth_types::{
29 EthApiError, EthStateCache, FeeHistoryCache, GasPriceOracle, PendingBlock,
30 builder::config::PendingBlockKind,
31};
32use reth_storage_api::{ProviderHeader, StateProviderFactory, TransactionsProvider};
33use reth_tasks::{
34 Runtime,
35 pool::{BlockingTaskGuard, BlockingTaskPool},
36};
37use reth_transaction_pool::{
38 AddedTransactionOutcome, PoolPooledTx, PoolTransaction, TransactionOrigin, TransactionPool,
39};
40use tracing::trace;
41
42type SignersForRpc<Provider, Rpc> = parking_lot::RwLock<
44 Vec<Box<dyn EthSigner<<Provider as TransactionsProvider>::Transaction, RpcTxReq<Rpc>>>>,
45>;
46
47use arbos::{
48 l1_pricing::{
49 PRICE_PER_UNIT_OFFSET as L1_PRICE_PER_UNIT, UNITS_SINCE_OFFSET as L1_UNITS_SINCE_UPDATE,
50 },
51 l2_pricing::{BASE_FEE_WEI_OFFSET as L2_BASE_FEE, MIN_BASE_FEE_WEI_OFFSET as L2_MIN_BASE_FEE},
52};
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
81pub struct ArbEthApi<N: RpcNodeCore, Rpc: RpcConvert> {
85 inner: Arc<EthApiInner<N, Rpc>>,
86}
87
88impl<N: RpcNodeCore, Rpc: RpcConvert> Clone for ArbEthApi<N, Rpc> {
89 fn clone(&self) -> Self {
90 Self {
91 inner: self.inner.clone(),
92 }
93 }
94}
95
96impl<N: RpcNodeCore, Rpc: RpcConvert> std::fmt::Debug for ArbEthApi<N, Rpc> {
97 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
98 f.debug_struct("ArbEthApi").finish_non_exhaustive()
99 }
100}
101
102impl<N: RpcNodeCore, Rpc: RpcConvert> ArbEthApi<N, Rpc> {
103 pub fn new(inner: EthApiInner<N, Rpc>) -> Self {
105 Self {
106 inner: Arc::new(inner),
107 }
108 }
109}
110
111impl<N, Rpc> ArbEthApi<N, Rpc>
112where
113 N: RpcNodeCore<Provider: StateProviderFactory>,
114 Rpc: RpcConvert,
115{
116 fn l1_posting_gas(&self, calldata_len: usize, at: BlockId) -> Result<u64, EthApiError> {
121 if calldata_len == 0 {
122 return Ok(0);
123 }
124
125 let state = self
126 .inner
127 .provider()
128 .state_by_block_id(at)
129 .map_err(|e| EthApiError::Internal(e.into()))?;
130
131 let l1_price_slot = subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT);
132 let l1_price = state
133 .storage(
134 ARBOS_STATE_ADDRESS,
135 StorageKey::from(B256::from(l1_price_slot.to_be_bytes::<32>())),
136 )
137 .map_err(|e| EthApiError::Internal(e.into()))?
138 .unwrap_or_default();
139
140 let basefee_slot = subspace_slot(L2_PRICING_SUBSPACE, L2_BASE_FEE);
141 let basefee = state
142 .storage(
143 ARBOS_STATE_ADDRESS,
144 StorageKey::from(B256::from(basefee_slot.to_be_bytes::<32>())),
145 )
146 .map_err(|e| EthApiError::Internal(e.into()))?
147 .unwrap_or_default();
148
149 if l1_price.is_zero() || basefee.is_zero() {
150 return Ok(0);
151 }
152
153 let l1_fee = l1_price
155 .saturating_mul(U256::from(TX_DATA_NON_ZERO_GAS))
156 .saturating_mul(U256::from(calldata_len));
157
158 let padded = l1_fee.saturating_mul(U256::from(GAS_ESTIMATION_L1_PRICE_PADDING))
160 / U256::from(10000u64);
161
162 let adjusted_basefee = basefee.saturating_mul(U256::from(7)) / U256::from(8);
164 let adjusted_basefee = if adjusted_basefee.is_zero() {
165 U256::from(1)
166 } else {
167 adjusted_basefee
168 };
169
170 let gas = padded / adjusted_basefee;
172 Ok(gas.try_into().unwrap_or(u64::MAX))
173 }
174}
175
176impl<N, Rpc> ArbEthApi<N, Rpc>
177where
178 N: RpcNodeCore<Provider: StateProviderFactory>,
179 EthApiError: FromEvmError<N::Evm>,
180 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
181 RpcTxReq<<Rpc as RpcConvert>::Network>: AsRef<alloy_rpc_types_eth::TransactionRequest>
182 + AsMut<alloy_rpc_types_eth::TransactionRequest>
183 + Clone
184 + Default,
185{
186 fn compute_eth_call_units_since_update(
187 &self,
188 request: RpcTxReq<<Rpc as RpcConvert>::Network>,
189 at: BlockId,
190 ) -> Result<alloy_primitives::Bytes, EthApiError> {
191 let inner = request.as_ref();
192 let (to, contract_creation) = match inner.to {
193 Some(alloy_primitives::TxKind::Call(addr)) => (addr, false),
194 Some(alloy_primitives::TxKind::Create) => (Address::ZERO, true),
195 None => (Address::ZERO, false),
196 };
197 let value = inner.value.unwrap_or(U256::ZERO);
198 let data: alloy_primitives::Bytes = inner.input.input().cloned().unwrap_or_default();
199
200 let state = self
201 .inner
202 .provider()
203 .state_by_block_id(at)
204 .map_err(|e| EthApiError::Internal(e.into()))?;
205 let read = |slot: U256| -> Result<U256, EthApiError> {
206 Ok(state
207 .storage(
208 ARBOS_STATE_ADDRESS,
209 StorageKey::from(B256::from(slot.to_be_bytes::<32>())),
210 )
211 .map_err(|e| EthApiError::Internal(e.into()))?
212 .unwrap_or_default())
213 };
214 let stored = read(subspace_slot(L1_PRICING_SUBSPACE, L1_UNITS_SINCE_UPDATE))?;
215 let chain_id_u: u64 = read(root_slot(CHAIN_ID_OFFSET))?.try_into().unwrap_or(0);
216 let brotli_level: u64 = read(root_slot(BROTLI_COMPRESSION_LEVEL_OFFSET))?
217 .try_into()
218 .unwrap_or(0);
219
220 let tx_bytes =
221 arb_precompiles::build_fake_tx_bytes(chain_id_u, to, contract_creation, value, data);
222 let raw_units = arbos::l1_pricing::poster_units_from_bytes(&tx_bytes, brotli_level);
223 let padded_units = raw_units
224 .saturating_add(arbos::l1_pricing::ESTIMATION_PADDING_UNITS)
225 .saturating_mul(10_000 + arbos::l1_pricing::ESTIMATION_PADDING_BASIS_POINTS)
226 / 10_000;
227 let total = stored.saturating_add(U256::from(padded_units));
228
229 Ok(alloy_primitives::Bytes::from(
230 total.to_be_bytes::<32>().to_vec(),
231 ))
232 }
233
234 fn compute_eth_call_current_tx_l1_fees(
235 &self,
236 request: RpcTxReq<<Rpc as RpcConvert>::Network>,
237 at: BlockId,
238 ) -> Result<alloy_primitives::Bytes, EthApiError> {
239 let inner = request.as_ref();
240 let (to, contract_creation) = match inner.to {
241 Some(alloy_primitives::TxKind::Call(addr)) => (addr, false),
242 Some(alloy_primitives::TxKind::Create) => (Address::ZERO, true),
243 None => (Address::ZERO, false),
244 };
245 let value = inner.value.unwrap_or(U256::ZERO);
246 let data: alloy_primitives::Bytes = inner.input.input().cloned().unwrap_or_default();
247
248 let state = self
249 .inner
250 .provider()
251 .state_by_block_id(at)
252 .map_err(|e| EthApiError::Internal(e.into()))?;
253 let read = |slot: U256| -> Result<U256, EthApiError> {
254 Ok(state
255 .storage(
256 ARBOS_STATE_ADDRESS,
257 StorageKey::from(B256::from(slot.to_be_bytes::<32>())),
258 )
259 .map_err(|e| EthApiError::Internal(e.into()))?
260 .unwrap_or_default())
261 };
262 let l1_price = read(subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT))?;
263 if l1_price.is_zero() {
264 return Ok(alloy_primitives::Bytes::from(vec![0u8; 32]));
265 }
266 let chain_id_u: u64 = read(root_slot(CHAIN_ID_OFFSET))?.try_into().unwrap_or(0);
267 let brotli_level: u64 = read(root_slot(BROTLI_COMPRESSION_LEVEL_OFFSET))?
268 .try_into()
269 .unwrap_or(0);
270
271 let tx_bytes =
272 arb_precompiles::build_fake_tx_bytes(chain_id_u, to, contract_creation, value, data);
273 let raw_units = arbos::l1_pricing::poster_units_from_bytes(&tx_bytes, brotli_level);
274 let padded_units = raw_units
275 .saturating_add(arbos::l1_pricing::ESTIMATION_PADDING_UNITS)
276 .saturating_mul(10_000 + arbos::l1_pricing::ESTIMATION_PADDING_BASIS_POINTS)
277 / 10_000;
278 let poster_fee = l1_price.saturating_mul(U256::from(padded_units));
279
280 Ok(alloy_primitives::Bytes::from(
281 poster_fee.to_be_bytes::<32>().to_vec(),
282 ))
283 }
284
285 async fn estimate_arb_combined_gas(
291 &self,
292 inner_req: RpcTxReq<<Rpc as RpcConvert>::Network>,
293 gas_for_l1: u64,
294 at: BlockId,
295 state_override: Option<StateOverride>,
296 ) -> Result<u64, EthApiError>
297 where
298 RpcTxReq<<Rpc as RpcConvert>::Network>: From<alloy_rpc_types_eth::TransactionRequest>,
299 {
300 use alloy_rpc_types_eth::state::EvmOverrides;
301
302 const CALL_STIPEND: u64 = 2300;
303 const ERROR_RATIO: f64 = 0.015;
304
305 let rpc_gas_cap = self.call_gas_limit();
306
307 let simulate = async |req_gas: u64| -> Result<(u64, bool), EthApiError> {
308 let mut req = inner_req.clone();
309 req.as_mut().gas = Some(req_gas);
310 let _permit = self.acquire_owned_blocking_io().await;
311 let res = self
312 .transact_call_at(req, at, EvmOverrides::state(state_override.clone()))
313 .await?;
314 Ok((res.result.gas_used(), res.result.is_success()))
315 };
316
317 let compute_cap = rpc_gas_cap.saturating_sub(gas_for_l1).max(1);
318 let (used_compute, success_first) = simulate(compute_cap).await?;
319 if !success_first {
320 return Ok(rpc_gas_cap);
321 }
322
323 let used_total = used_compute.saturating_add(gas_for_l1);
324 let mut lo = used_total.saturating_sub(1);
325 let mut hi = rpc_gas_cap.max(used_total);
326
327 let optimistic = used_total.saturating_add(CALL_STIPEND).saturating_mul(64) / 63;
328 if optimistic < hi {
329 let compute_limit = optimistic.saturating_sub(gas_for_l1);
330 let (_, ok) = simulate(compute_limit).await?;
331 if ok {
332 hi = optimistic;
333 } else {
334 lo = optimistic;
335 }
336 }
337
338 while lo + 1 < hi {
339 let ratio = (hi - lo) as f64 / hi as f64;
340 if ratio < ERROR_RATIO {
341 break;
342 }
343 let mut mid = lo + (hi - lo) / 2;
344 let two_lo = lo.saturating_mul(2);
345 if mid > two_lo {
346 mid = two_lo;
347 }
348 let compute_limit = mid.saturating_sub(gas_for_l1);
349 let (_, ok) = simulate(compute_limit).await?;
350 if ok {
351 hi = mid;
352 } else {
353 lo = mid;
354 }
355 }
356
357 Ok(hi)
358 }
359
360 async fn estimate_retryable_ticket_gas(
363 &self,
364 input: &alloy_primitives::Bytes,
365 at: BlockId,
366 state_override: Option<StateOverride>,
367 ) -> Result<U256, EthApiError>
368 where
369 RpcTxReq<<Rpc as RpcConvert>::Network>: From<alloy_rpc_types_eth::TransactionRequest>,
370 {
371 use alloy_primitives::{Bytes, TxKind};
372 use alloy_rpc_types_eth::TransactionRequest;
373
374 const HEAD_LEN: usize = 4 + 32 * 7;
378 if input.len() < HEAD_LEN {
379 return Err(EthApiError::InvalidParams(
380 "estimateRetryableTicket: calldata too short".into(),
381 ));
382 }
383 let sender = Address::from_slice(&input[4 + 12..4 + 32]);
384 let _deposit = U256::from_be_slice(&input[36..68]);
385 let to_word = &input[68..100];
386 let to = Address::from_slice(&to_word[12..32]);
387 let l2_call_value = U256::from_be_slice(&input[100..132]);
388 let _excess_fee_refund = Address::from_slice(&input[132 + 12..132 + 32]);
389 let _call_value_refund = Address::from_slice(&input[164 + 12..164 + 32]);
390 let data_offset: usize =
391 U256::from_be_slice(&input[196..228])
392 .try_into()
393 .map_err(|_| {
394 EthApiError::InvalidParams(
395 "estimateRetryableTicket: invalid data offset".into(),
396 )
397 })?;
398 let abi_body = &input[4..];
399 let data: Bytes = if data_offset + 32 <= abi_body.len() {
400 let len: usize = U256::from_be_slice(&abi_body[data_offset..data_offset + 32])
401 .try_into()
402 .map_err(|_| {
403 EthApiError::InvalidParams(
404 "estimateRetryableTicket: data length too large".into(),
405 )
406 })?;
407 if data_offset + 32 + len > abi_body.len() {
408 return Err(EthApiError::InvalidParams(
409 "estimateRetryableTicket: data out of bounds".into(),
410 ));
411 }
412 Bytes::copy_from_slice(&abi_body[data_offset + 32..data_offset + 32 + len])
413 } else {
414 Bytes::new()
415 };
416
417 let kind = if to == Address::ZERO {
420 TxKind::Create
421 } else {
422 TxKind::Call(to)
423 };
424 let data_ref = data.clone();
425 let equivalent = TransactionRequest {
426 from: Some(sender),
427 to: Some(kind),
428 value: Some(l2_call_value),
429 input: data.into(),
430 ..Default::default()
431 };
432 let equivalent_req: RpcTxReq<<Rpc as RpcConvert>::Network> = equivalent.into();
433
434 let redeem_gas =
439 EstimateCall::estimate_gas_at(self, equivalent_req, at, state_override).await?;
440
441 let (zeros, non_zeros) =
448 data_ref.iter().fold(
449 (0u64, 0u64),
450 |(z, nz), &b| if b == 0 { (z + 1, nz) } else { (z, nz + 1) },
451 );
452 let calldata_gas = zeros
453 .saturating_mul(4)
454 .saturating_add(non_zeros.saturating_mul(16));
455 let submit_intrinsic = 21_000u64.saturating_add(calldata_gas);
456
457 Ok(redeem_gas.saturating_add(U256::from(submit_intrinsic)))
458 }
459
460 async fn simulate_retryable_ticket_call(
464 &self,
465 input: &alloy_primitives::Bytes,
466 at: BlockId,
467 _overrides: alloy_rpc_types_eth::state::EvmOverrides,
468 ) -> Result<alloy_primitives::Bytes, EthApiError>
469 where
470 RpcTxReq<<Rpc as RpcConvert>::Network>: From<alloy_rpc_types_eth::TransactionRequest>,
471 {
472 use alloy_primitives::{Bytes, keccak256};
473 use arb_alloy_consensus::{ArbTxEnvelope, tx::ArbSubmitRetryableTx};
474
475 const HEAD_LEN: usize = 4 + 32 * 7;
476 if input.len() < HEAD_LEN {
477 return Err(EthApiError::InvalidParams(
478 "estimateRetryableTicket: calldata too short".into(),
479 ));
480 }
481 let sender = Address::from_slice(&input[4 + 12..4 + 32]);
482 let deposit = U256::from_be_slice(&input[36..68]);
483 let to = Address::from_slice(&input[68 + 12..100]);
484 let l2_call_value = U256::from_be_slice(&input[100..132]);
485 let excess_fee_refund = Address::from_slice(&input[132 + 12..164]);
486 let call_value_refund = Address::from_slice(&input[164 + 12..196]);
487 let data_offset: usize =
488 U256::from_be_slice(&input[196..228])
489 .try_into()
490 .map_err(|_| {
491 EthApiError::InvalidParams(
492 "estimateRetryableTicket: invalid data offset".into(),
493 )
494 })?;
495 let abi_body = &input[4..];
496 let data: Bytes = if data_offset + 32 <= abi_body.len() {
497 let len: usize = U256::from_be_slice(&abi_body[data_offset..data_offset + 32])
498 .try_into()
499 .map_err(|_| {
500 EthApiError::InvalidParams(
501 "estimateRetryableTicket: data length too large".into(),
502 )
503 })?;
504 if data_offset + 32 + len > abi_body.len() {
505 return Err(EthApiError::InvalidParams(
506 "estimateRetryableTicket: data out of bounds".into(),
507 ));
508 }
509 Bytes::copy_from_slice(&abi_body[data_offset + 32..data_offset + 32 + len])
510 } else {
511 Bytes::new()
512 };
513
514 let l1_base_fee = {
515 let state = self
516 .inner
517 .provider()
518 .state_by_block_id(at)
519 .map_err(|e| EthApiError::Internal(e.into()))?;
520 let slot = subspace_slot(L1_PRICING_SUBSPACE, L1_PRICE_PER_UNIT);
521 state
522 .storage(
523 ARBOS_STATE_ADDRESS,
524 StorageKey::from(B256::from(slot.to_be_bytes::<32>())),
525 )
526 .map_err(|e| EthApiError::Internal(e.into()))?
527 .unwrap_or_default()
528 };
529
530 let aliased_from = apply_l1_to_l2_alias(sender);
531 let max_submission_fee =
532 arbos::retryables::retryable_submission_fee(data.len(), l1_base_fee);
533 let retry_to = if to == Address::ZERO { None } else { Some(to) };
534
535 let gas_cap = self.inner.gas_cap();
536 let gas = gas_cap;
537
538 let tx = ArbSubmitRetryableTx {
539 chain_id: U256::ZERO,
540 request_id: B256::ZERO,
541 from: aliased_from,
542 l1_base_fee,
543 deposit_value: deposit,
544 gas_fee_cap: U256::ZERO,
545 gas,
546 retry_to,
547 retry_value: l2_call_value,
548 beneficiary: call_value_refund,
549 max_submission_fee,
550 fee_refund_addr: excess_fee_refund,
551 retry_data: data,
552 };
553 let envelope = ArbTxEnvelope::SubmitRetryable(tx);
554 let encoded = envelope.encode_typed();
555 let hash = keccak256(&encoded);
556 Ok(alloy_primitives::Bytes::from(hash.0.to_vec()))
557 }
558
559 async fn get_retryable_abi(
564 &self,
565 input: &alloy_primitives::Bytes,
566 at: BlockId,
567 ) -> Result<alloy_primitives::Bytes, EthApiError> {
568 use arb_storage::layout::{ROOT_STORAGE_KEY, derive_subspace_key, map_slot};
569 use arbos::retryables::{
570 BENEFICIARY_OFFSET, CALLDATA_KEY, CALLVALUE_OFFSET, FROM_OFFSET, NUM_TRIES_OFFSET,
571 TIMEOUT_OFFSET, TO_OFFSET,
572 };
573
574 if input.len() < 4 + 32 {
575 return Err(EthApiError::InvalidParams(
576 "getRetryable: expected bytes32 ticket".into(),
577 ));
578 }
579 let ticket = B256::from_slice(&input[4..36]);
580
581 let state = self
582 .inner
583 .provider()
584 .state_by_block_id(at)
585 .map_err(|e| EthApiError::Internal(e.into()))?;
586 let load = |slot: U256| -> Result<U256, EthApiError> {
587 let k = StorageKey::from(B256::from(slot.to_be_bytes::<32>()));
588 Ok(state
589 .storage(ARBOS_STATE_ADDRESS, k)
590 .map_err(|e| EthApiError::Internal(e.into()))?
591 .unwrap_or(U256::ZERO))
592 };
593
594 let retryables_key =
595 derive_subspace_key(ROOT_STORAGE_KEY, arb_storage::layout::RETRYABLES_SUBSPACE);
596 let r_key = derive_subspace_key(retryables_key.as_slice(), ticket.as_slice());
597
598 let timeout: u64 = load(map_slot(r_key.as_slice(), TIMEOUT_OFFSET))?
599 .try_into()
600 .unwrap_or(0);
601 if timeout == 0 {
602 return Err(EthApiError::InvalidParams(format!(
603 "no retryable with id 0x{ticket:x}"
604 )));
605 }
606 let from_word = load(map_slot(r_key.as_slice(), FROM_OFFSET))?;
607 let from = Address::from_slice(&from_word.to_be_bytes::<32>()[12..]);
608 let to_word = load(map_slot(r_key.as_slice(), TO_OFFSET))?;
609 let to_bytes: [u8; 32] = to_word.to_be_bytes();
610 let to = Address::from_slice(&to_bytes[12..]);
614 let value = load(map_slot(r_key.as_slice(), CALLVALUE_OFFSET))?;
615 let beneficiary_word = load(map_slot(r_key.as_slice(), BENEFICIARY_OFFSET))?;
616 let beneficiary = Address::from_slice(&beneficiary_word.to_be_bytes::<32>()[12..]);
617 let tries: u64 = load(map_slot(r_key.as_slice(), NUM_TRIES_OFFSET))?
618 .try_into()
619 .unwrap_or(0);
620
621 let cd_key = derive_subspace_key(r_key.as_slice(), CALLDATA_KEY);
625 let size: usize = load(map_slot(cd_key.as_slice(), 0))?
626 .try_into()
627 .unwrap_or(0);
628 let chunks = size.div_ceil(32);
629 let mut data = Vec::with_capacity(size);
630 for i in 0..chunks {
631 let chunk = load(map_slot(cd_key.as_slice(), 1 + i as u64))?;
632 data.extend_from_slice(&chunk.to_be_bytes::<32>());
633 }
634 data.truncate(size);
635
636 let mut out = vec![0u8; 7 * 32];
641 U256::from(timeout)
642 .to_be_bytes::<32>()
643 .iter()
644 .enumerate()
645 .for_each(|(i, b)| out[i] = *b);
646 out[32 + 12..32 + 32].copy_from_slice(from.as_slice());
647 out[64 + 12..64 + 32].copy_from_slice(to.as_slice());
648 out[96..128].copy_from_slice(&value.to_be_bytes::<32>());
649 out[128 + 12..128 + 32].copy_from_slice(beneficiary.as_slice());
650 U256::from(tries)
651 .to_be_bytes::<32>()
652 .iter()
653 .enumerate()
654 .for_each(|(i, b)| out[160 + i] = *b);
655 U256::from(7u64 * 32)
657 .to_be_bytes::<32>()
658 .iter()
659 .enumerate()
660 .for_each(|(i, b)| out[192 + i] = *b);
661 let padded_len = size.div_ceil(32) * 32;
663 let mut tail = vec![0u8; 32 + padded_len];
664 U256::from(size as u64)
665 .to_be_bytes::<32>()
666 .iter()
667 .enumerate()
668 .for_each(|(i, b)| tail[i] = *b);
669 tail[32..32 + size].copy_from_slice(&data);
670 out.extend_from_slice(&tail);
671 Ok(alloy_primitives::Bytes::from(out))
672 }
673
674 async fn construct_outbox_proof(
680 &self,
681 input: &alloy_primitives::Bytes,
682 at: BlockId,
683 ) -> Result<alloy_primitives::Bytes, EthApiError>
684 where
685 N: RpcNodeCore<
686 Provider: reth_provider::BlockReaderIdExt + reth_storage_api::ReceiptProvider,
687 >,
688 {
689 use std::collections::HashMap;
690
691 use alloy_consensus::TxReceipt;
692 use arb_precompiles::arbsys::{
693 ARBSYS_ADDRESS, l2_to_l1_tx_topic, send_merkle_update_topic,
694 };
695 use reth_provider::{BlockNumReader, ReceiptProvider};
696
697 use crate::outbox_proof::{LevelAndLeaf, encode_outbox_proof, finalize_proof, plan_proof};
698
699 if input.len() < 4 + 64 {
700 return Err(EthApiError::InvalidParams(
701 "constructOutboxProof: expected (uint64 size, uint64 leaf)".into(),
702 ));
703 }
704 let size: u64 = U256::from_be_slice(&input[4..36])
705 .try_into()
706 .unwrap_or(u64::MAX);
707 let leaf: u64 = U256::from_be_slice(&input[36..68])
708 .try_into()
709 .unwrap_or(u64::MAX);
710
711 let plan = plan_proof(size, leaf).ok_or_else(|| {
712 EthApiError::InvalidParams(format!("constructOutboxProof: leaf {leaf} ≥ size {size}"))
713 })?;
714
715 let provider = self.inner.provider();
718 let tip = provider
719 .best_block_number()
720 .map_err(|e| EthApiError::Internal(e.into()))?;
721 let upper = match at {
722 BlockId::Number(alloy_rpc_types_eth::BlockNumberOrTag::Number(n)) => n.min(tip),
723 _ => tip,
724 };
725
726 let merkle_topic = send_merkle_update_topic();
731 let l2tol1_topic = l2_to_l1_tx_topic();
732
733 let mut positions: HashMap<[u8; 32], B256> = HashMap::new();
735
736 let receipts_per_block = provider
737 .receipts_by_block_range(0..=upper)
738 .map_err(|e| EthApiError::Internal(e.into()))?;
739
740 for block_receipts in receipts_per_block {
741 for receipt in block_receipts {
742 for log in receipt.logs() {
743 if log.address != ARBSYS_ADDRESS {
744 continue;
745 }
746 let topics = log.data.topics();
747 if topics.len() < 4 {
748 continue;
749 }
750 let kind = topics[0];
751 let is_merkle = kind == merkle_topic;
752 let is_l2tol1 = kind == l2tol1_topic;
753 if !is_merkle && !is_l2tol1 {
754 continue;
755 }
756 let pos: [u8; 32] = topics[3].0;
759 let hash: B256 = topics[2];
760 positions.insert(pos, hash);
761 }
762 }
763 }
764
765 let lookup = |p: LevelAndLeaf| -> Option<B256> {
766 let topic = p.as_topic();
767 positions.get(&topic.0).copied()
768 };
769
770 let (send, root, proof) = finalize_proof(&plan, leaf, lookup)
771 .map_err(|e| EthApiError::InvalidParams(format!("constructOutboxProof: {e}")))?;
772
773 Ok(encode_outbox_proof(send, root, &proof))
774 }
775}
776
777impl<N, Rpc> EthApiTypes for ArbEthApi<N, Rpc>
780where
781 N: RpcNodeCore,
782 Rpc: RpcConvert<Error = EthApiError>,
783{
784 type Error = EthApiError;
785 type NetworkTypes = Rpc::Network;
786 type RpcConvert = Rpc;
787
788 fn converter(&self) -> &Self::RpcConvert {
789 self.inner.converter()
790 }
791}
792
793impl<N, Rpc> RpcNodeCore for ArbEthApi<N, Rpc>
794where
795 N: RpcNodeCore,
796 Rpc: RpcConvert,
797{
798 type Primitives = N::Primitives;
799 type Provider = N::Provider;
800 type Pool = N::Pool;
801 type Evm = N::Evm;
802 type Network = N::Network;
803
804 #[inline]
805 fn pool(&self) -> &Self::Pool {
806 self.inner.pool()
807 }
808
809 #[inline]
810 fn evm_config(&self) -> &Self::Evm {
811 self.inner.evm_config()
812 }
813
814 #[inline]
815 fn network(&self) -> &Self::Network {
816 self.inner.network()
817 }
818
819 #[inline]
820 fn provider(&self) -> &Self::Provider {
821 self.inner.provider()
822 }
823}
824
825impl<N, Rpc> RpcNodeCoreExt for ArbEthApi<N, Rpc>
826where
827 N: RpcNodeCore,
828 Rpc: RpcConvert,
829{
830 #[inline]
831 fn cache(&self) -> &EthStateCache<N::Primitives> {
832 self.inner.cache()
833 }
834}
835
836impl<N, Rpc> EthApiSpec for ArbEthApi<N, Rpc>
837where
838 N: RpcNodeCore,
839 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
840{
841 fn starting_block(&self) -> U256 {
842 self.inner.starting_block()
843 }
844}
845
846impl<N, Rpc> SpawnBlocking for ArbEthApi<N, Rpc>
847where
848 N: RpcNodeCore,
849 Rpc: RpcConvert<Error = EthApiError>,
850{
851 #[inline]
852 fn io_task_spawner(&self) -> &Runtime {
853 self.inner.task_spawner()
854 }
855
856 #[inline]
857 fn tracing_task_pool(&self) -> &BlockingTaskPool {
858 self.inner.blocking_task_pool()
859 }
860
861 #[inline]
862 fn tracing_task_guard(&self) -> &BlockingTaskGuard {
863 self.inner.blocking_task_guard()
864 }
865
866 #[inline]
867 fn blocking_io_task_guard(&self) -> &Arc<tokio::sync::Semaphore> {
868 self.inner.blocking_io_request_semaphore()
869 }
870}
871
872impl<N, Rpc> LoadFee for ArbEthApi<N, Rpc>
873where
874 N: RpcNodeCore,
875 EthApiError: FromEvmError<N::Evm>,
876 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
877{
878 fn gas_oracle(&self) -> &GasPriceOracle<Self::Provider> {
879 self.inner.gas_oracle()
880 }
881
882 fn fee_history_cache(&self) -> &FeeHistoryCache<ProviderHeader<N::Provider>> {
883 self.inner.fee_history_cache()
884 }
885}
886
887impl<N, Rpc> LoadState for ArbEthApi<N, Rpc>
888where
889 N: RpcNodeCore,
890 Rpc: RpcConvert<Primitives = N::Primitives>,
891 Self: LoadPendingBlock,
892{
893}
894
895impl<N, Rpc> EthState for ArbEthApi<N, Rpc>
896where
897 N: RpcNodeCore,
898 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
899 Self: LoadPendingBlock,
900{
901 fn max_proof_window(&self) -> u64 {
902 self.inner.eth_proof_window()
903 }
904}
905
906impl<N, Rpc> EthFees for ArbEthApi<N, Rpc>
907where
908 N: RpcNodeCore,
909 EthApiError: FromEvmError<N::Evm>,
910 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
911{
912 fn gas_price(&self) -> impl std::future::Future<Output = Result<U256, Self::Error>> + Send
915 where
916 Self: reth_rpc_eth_api::helpers::LoadBlock,
917 {
918 use alloy_consensus::BlockHeader;
919 use reth_storage_api::{BlockNumReader, HeaderProvider};
920 async move {
921 let best = self
922 .provider()
923 .best_block_number()
924 .map_err(|e| EthApiError::Internal(e.into()))?;
925 let header_opt = HeaderProvider::sealed_header(self.provider(), best)
926 .map_err(|e| EthApiError::Internal(e.into()))?;
927 let base_fee = match header_opt {
928 Some(sealed) => sealed.header().base_fee_per_gas().unwrap_or_default(),
929 None => 0,
930 };
931 Ok(U256::from(base_fee))
932 }
933 }
934
935 #[allow(clippy::manual_async_fn)]
936 fn suggested_priority_fee(
937 &self,
938 ) -> impl std::future::Future<Output = Result<U256, Self::Error>> + Send
939 where
940 Self: 'static,
941 {
942 async move { Ok(U256::ZERO) }
943 }
944}
945
946impl<N, Rpc> Trace for ArbEthApi<N, Rpc>
947where
948 N: RpcNodeCore,
949 EthApiError: FromEvmError<N::Evm>,
950 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
951{
952}
953
954impl<N, Rpc> LoadPendingBlock for ArbEthApi<N, Rpc>
955where
956 N: RpcNodeCore,
957 EthApiError: FromEvmError<N::Evm>,
958 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
959{
960 fn pending_block(&self) -> &tokio::sync::Mutex<Option<PendingBlock<N::Primitives>>> {
961 self.inner.pending_block()
962 }
963
964 fn pending_env_builder(&self) -> &dyn PendingEnvBuilder<N::Evm> {
965 self.inner.pending_env_builder()
966 }
967
968 fn pending_block_kind(&self) -> PendingBlockKind {
969 self.inner.pending_block_kind()
970 }
971}
972
973impl<N, Rpc> LoadBlock for ArbEthApi<N, Rpc>
974where
975 Self: LoadPendingBlock,
976 N: RpcNodeCore,
977 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
978{
979}
980
981impl<N, Rpc> LoadTransaction for ArbEthApi<N, Rpc>
982where
983 N: RpcNodeCore,
984 EthApiError: FromEvmError<N::Evm>,
985 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
986{
987}
988
989impl<N, Rpc> EthBlocks for ArbEthApi<N, Rpc>
990where
991 N: RpcNodeCore,
992 EthApiError: FromEvmError<N::Evm>,
993 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
994{
995}
996
997impl<N, Rpc> EthTransactions for ArbEthApi<N, Rpc>
998where
999 N: RpcNodeCore,
1000 EthApiError: FromEvmError<N::Evm>,
1001 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
1002{
1003 fn signers(&self) -> &SignersForRpc<Self::Provider, Self::NetworkTypes> {
1004 self.inner.signers()
1005 }
1006
1007 fn send_raw_transaction_sync_timeout(&self) -> Duration {
1008 self.inner.send_raw_transaction_sync_timeout()
1009 }
1010
1011 async fn send_transaction(
1012 &self,
1013 origin: TransactionOrigin,
1014 tx: WithEncoded<Recovered<PoolPooledTx<Self::Pool>>>,
1015 ) -> Result<B256, Self::Error> {
1016 let (_tx_bytes, recovered) = tx.split();
1017 let pool_transaction = <Self::Pool as TransactionPool>::Transaction::from_pooled(recovered);
1018
1019 let AddedTransactionOutcome { hash, .. } = self
1020 .inner
1021 .add_pool_transaction(origin, pool_transaction)
1022 .await?;
1023
1024 Ok(hash)
1025 }
1026}
1027
1028impl<N, Rpc> LoadReceipt for ArbEthApi<N, Rpc>
1029where
1030 N: RpcNodeCore<Primitives = arb_primitives::ArbPrimitives>,
1031 EthApiError: FromEvmError<N::Evm>,
1032 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError>,
1033 Self::Error: reth_rpc_eth_types::error::FromEthApiError,
1034{
1035 fn build_transaction_receipt(
1044 &self,
1045 tx: reth_storage_api::ProviderTx<Self::Provider>,
1046 meta: alloy_consensus::transaction::TransactionMeta,
1047 receipt: reth_storage_api::ProviderReceipt<Self::Provider>,
1048 ) -> impl std::future::Future<
1049 Output = Result<reth_rpc_eth_api::RpcReceipt<Self::NetworkTypes>, Self::Error>,
1050 > + Send {
1051 use alloy_consensus::TxReceipt;
1052 use reth_primitives_traits::SignerRecoverable;
1053 use reth_rpc_convert::transaction::ConvertReceiptInput;
1054 use reth_rpc_eth_api::RpcNodeCoreExt;
1055 use reth_rpc_eth_types::{
1056 EthApiError, error::FromEthApiError, utils::calculate_gas_used_and_next_log_index,
1057 };
1058 async move {
1059 let hash = meta.block_hash;
1060 let all_receipts = self
1061 .cache()
1062 .get_receipts(hash)
1063 .await
1064 .map_err(<Self::Error as FromEthApiError>::from_eth_err)?
1065 .ok_or_else(|| {
1066 <Self::Error as FromEthApiError>::from_eth_err(EthApiError::HeaderNotFound(
1067 hash.into(),
1068 ))
1069 })?;
1070
1071 let (gas_used, next_log_index) =
1072 calculate_gas_used_and_next_log_index(meta.index, &all_receipts);
1073
1074 let block = self
1075 .cache()
1076 .get_recovered_block(hash)
1077 .await
1078 .map_err(<Self::Error as FromEthApiError>::from_eth_err)?;
1079
1080 let tx_recovered = tx
1081 .try_into_recovered_unchecked()
1082 .map_err(<Self::Error as FromEthApiError>::from_eth_err)?;
1083
1084 let input = ConvertReceiptInput {
1085 tx: tx_recovered.as_recovered_ref(),
1086 gas_used: receipt.cumulative_gas_used() - gas_used,
1087 receipt,
1088 next_log_index,
1089 meta,
1090 };
1091
1092 let result = match block {
1093 Some(sealed_block_with_senders) => self.converter().convert_receipts_with_block(
1094 vec![input],
1095 sealed_block_with_senders.sealed_block(),
1096 )?,
1097 None => self.converter().convert_receipts(vec![input])?,
1098 };
1099 Ok(result.into_iter().next().expect("one receipt in, one out"))
1100 }
1101 }
1102}
1103
1104impl<N, Rpc> Call for ArbEthApi<N, Rpc>
1107where
1108 N: RpcNodeCore,
1109 EthApiError: FromEvmError<N::Evm>,
1110 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
1111{
1112 #[inline]
1113 fn call_gas_limit(&self) -> u64 {
1114 self.inner.gas_cap()
1115 }
1116
1117 #[inline]
1118 fn max_simulate_blocks(&self) -> u64 {
1119 self.inner.max_simulate_blocks()
1120 }
1121
1122 #[inline]
1123 fn evm_memory_limit(&self) -> u64 {
1124 self.inner.evm_memory_limit()
1125 }
1126}
1127
1128impl<N, Rpc> EstimateCall for ArbEthApi<N, Rpc>
1129where
1130 N: RpcNodeCore,
1131 EthApiError: FromEvmError<N::Evm>,
1132 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
1133{
1134 }
1136
1137impl<N, Rpc> EthCall for ArbEthApi<N, Rpc>
1138where
1139 N: RpcNodeCore<
1140 Provider: StateProviderFactory + reth_provider::BlockReaderIdExt + Clone,
1141 Primitives = arb_primitives::ArbPrimitives,
1142 >,
1143 EthApiError: FromEvmError<N::Evm>,
1144 Rpc: RpcConvert<Primitives = N::Primitives, Error = EthApiError, Evm = N::Evm>,
1145 RpcTxReq<<Rpc as RpcConvert>::Network>: AsRef<alloy_rpc_types_eth::TransactionRequest>
1146 + AsMut<alloy_rpc_types_eth::TransactionRequest>
1147 + Clone
1148 + Default
1149 + From<alloy_rpc_types_eth::TransactionRequest>,
1150{
1151 #[allow(clippy::manual_async_fn)]
1161 fn estimate_gas_at(
1162 &self,
1163 request: RpcTxReq<<Self::RpcConvert as RpcConvert>::Network>,
1164 at: BlockId,
1165 state_override: Option<StateOverride>,
1166 ) -> impl std::future::Future<Output = Result<U256, Self::Error>> + Send {
1167 async move {
1168 use alloy_primitives::TxKind;
1169
1170 use crate::nodeinterface_rpc::NODE_INTERFACE_ADDRESS;
1171
1172 let inner = request.as_ref();
1173 let target: Option<Address> = match inner.to {
1174 Some(TxKind::Call(addr)) => Some(addr),
1175 _ => None,
1176 };
1177 let input_bytes: Option<alloy_primitives::Bytes> = inner.input.input().cloned();
1178
1179 if target == Some(NODE_INTERFACE_ADDRESS)
1188 && let Some(ref buf) = input_bytes
1189 && buf.len() >= 4
1190 && buf[..4] == [0xc3, 0xdc, 0x58, 0x79]
1191 {
1192 return self
1193 .estimate_retryable_ticket_gas(buf, at, state_override)
1194 .await;
1195 }
1196
1197 let calldata_len = input_bytes.as_ref().map(|b| b.len()).unwrap_or(0);
1199
1200 let compute_gas =
1202 EstimateCall::estimate_gas_at(self, request, at, state_override).await?;
1203
1204 let l1_gas = self.l1_posting_gas(calldata_len, at)?;
1206
1207 if l1_gas > 0 {
1208 trace!(target: "rpc::eth::estimate", %compute_gas, l1_gas, "Adding L1 posting gas to estimate");
1209 }
1210
1211 Ok(compute_gas.saturating_add(U256::from(l1_gas)))
1212 }
1213 }
1214
1215 #[allow(clippy::manual_async_fn)]
1220 fn call(
1221 &self,
1222 request: RpcTxReq<<Self::RpcConvert as RpcConvert>::Network>,
1223 block_number: Option<BlockId>,
1224 overrides: alloy_rpc_types_eth::state::EvmOverrides,
1225 ) -> impl std::future::Future<Output = Result<alloy_primitives::Bytes, Self::Error>> + Send
1226 {
1227 async move {
1228 use alloy_primitives::{Address, TxKind};
1229
1230 use crate::nodeinterface_rpc::{
1231 NODE_INTERFACE_ADDRESS, SEL_BLOCK_L1_NUM, SEL_FIND_BATCH_CONTAINING_BLOCK,
1232 SEL_GAS_ESTIMATE_COMPONENTS, SEL_GAS_ESTIMATE_L1_COMPONENT,
1233 SEL_GET_L1_CONFIRMATIONS, SEL_L2_BLOCK_RANGE_FOR_L1,
1234 SEL_LEGACY_LOOKUP_MESSAGE_BATCH_PROOF, SEL_NITRO_GENESIS_BLOCK,
1235 encode_gas_estimate_components, encode_l2_block_range, encode_legacy_lookup_empty,
1236 encode_u64_word, unpack_mix_hash,
1237 };
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 SEL_NITRO_GENESIS_BLOCK => {
1438 let state = self
1439 .inner
1440 .provider()
1441 .state_by_block_id(at)
1442 .map_err(|e| EthApiError::Internal(e.into()))?;
1443 let genesis: u64 = state
1444 .storage(
1445 ARBOS_STATE_ADDRESS,
1446 StorageKey::from(B256::from(
1447 root_slot(GENESIS_BLOCK_NUM_OFFSET).to_be_bytes::<32>(),
1448 )),
1449 )
1450 .map_err(|e| EthApiError::Internal(e.into()))?
1451 .unwrap_or_default()
1452 .try_into()
1453 .unwrap_or(0);
1454 Ok(encode_u64_word(genesis))
1455 }
1456
1457 SEL_BLOCK_L1_NUM => {
1458 use alloy_consensus::BlockHeader;
1459 use reth_provider::BlockReaderIdExt;
1460 if input_bytes.len() < 4 + 32 {
1461 return Err(EthApiError::InvalidParams(
1462 "blockL1Num: missing uint64 arg".into(),
1463 ));
1464 }
1465 let l2_block: u64 = U256::from_be_slice(&input_bytes[4..36])
1466 .try_into()
1467 .unwrap_or(u64::MAX);
1468 let l1_block = self
1469 .inner
1470 .provider()
1471 .sealed_header_by_number_or_tag(
1472 alloy_rpc_types_eth::BlockNumberOrTag::Number(l2_block),
1473 )
1474 .ok()
1475 .flatten()
1476 .and_then(|h| h.header().mix_hash())
1477 .map(|mix| unpack_mix_hash(mix).1)
1478 .unwrap_or(0);
1479 Ok(encode_u64_word(l1_block))
1480 }
1481
1482 SEL_GET_L1_CONFIRMATIONS | SEL_FIND_BATCH_CONTAINING_BLOCK => {
1483 Ok(encode_u64_word(0))
1484 }
1485
1486 SEL_LEGACY_LOOKUP_MESSAGE_BATCH_PROOF => Ok(encode_legacy_lookup_empty()),
1487
1488 _ => {
1489 let _permit = self.acquire_owned_blocking_io().await;
1491 let res = self.transact_call_at(request, at, overrides).await?;
1492 <Self::Error as reth_rpc_eth_types::error::api::FromEvmError<N::Evm>>::ensure_success(res.result)
1493 }
1494 }
1495 }
1496 }
1497}