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