arb_rpc/
header.rs

1//! Arbitrum header conversion for RPC responses.
2//!
3//! Extracts Arbitrum-specific fields (sendRoot, sendCount, l1BlockNumber)
4//! from the consensus header's mix_hash and extra_data fields.
5
6use alloy_consensus::{BlockHeader, Header};
7use alloy_primitives::{B256, U256};
8use alloy_rpc_types_eth::Header as RpcHeader;
9use alloy_serde::WithOtherFields;
10use reth_primitives_traits::SealedHeader;
11use reth_rpc_convert::transaction::HeaderConverter;
12use std::convert::Infallible;
13
14/// Extract L1 block number from header mix_hash (bytes 8-15).
15pub fn l1_block_number_from_mix_hash(mix_hash: &B256) -> u64 {
16    u64::from_be_bytes(mix_hash.0[8..16].try_into().unwrap_or_default())
17}
18
19/// Converts consensus headers to RPC headers with Arbitrum extension fields.
20#[derive(Debug, Clone)]
21pub struct ArbHeaderConverter;
22
23impl HeaderConverter<Header, WithOtherFields<RpcHeader<Header>>> for ArbHeaderConverter {
24    type Err = Infallible;
25
26    fn convert_header(
27        &self,
28        header: SealedHeader<Header>,
29        block_size: usize,
30    ) -> Result<WithOtherFields<RpcHeader<Header>>, Self::Err> {
31        let mix = header.mix_hash().unwrap_or_default();
32        let extra = header.extra_data();
33
34        // Extract Arbitrum fields from mix_hash.
35        let send_count = u64::from_be_bytes(mix.0[0..8].try_into().unwrap_or_default());
36        let l1_block_number = u64::from_be_bytes(mix.0[8..16].try_into().unwrap_or_default());
37
38        // Send root is stored in the first 32 bytes of extra_data.
39        let send_root = if extra.len() >= 32 {
40            B256::from_slice(&extra[..32])
41        } else {
42            B256::ZERO
43        };
44
45        let base_header =
46            RpcHeader::from_consensus(header.into(), None, Some(U256::from(block_size)));
47
48        let mut other = std::collections::BTreeMap::new();
49        other.insert(
50            "sendRoot".to_string(),
51            serde_json::to_value(send_root).unwrap_or_default(),
52        );
53        other.insert(
54            "sendCount".to_string(),
55            serde_json::to_value(format!("{send_count:#x}")).unwrap_or_default(),
56        );
57        other.insert(
58            "l1BlockNumber".to_string(),
59            serde_json::to_value(format!("{l1_block_number:#x}")).unwrap_or_default(),
60        );
61
62        Ok(WithOtherFields {
63            inner: base_header,
64            other: alloy_serde::OtherFields::new(other),
65        })
66    }
67}