arbos/
header.rs

1use alloy_primitives::{Address, B256, U256, keccak256};
2
3/// ArbOS state storage address.
4pub const ARBOS_STATE_ADDRESS: Address = {
5    let mut bytes = [0u8; 20];
6    bytes[0] = 0xA4;
7    bytes[1] = 0xB0;
8    bytes[2] = 0x5F;
9    bytes[3] = 0xFF;
10    bytes[4] = 0xFF;
11    bytes[5] = 0xFF;
12    bytes[6] = 0xFF;
13    bytes[7] = 0xFF;
14    bytes[8] = 0xFF;
15    bytes[9] = 0xFF;
16    bytes[10] = 0xFF;
17    bytes[11] = 0xFF;
18    bytes[12] = 0xFF;
19    bytes[13] = 0xFF;
20    bytes[14] = 0xFF;
21    bytes[15] = 0xFF;
22    bytes[16] = 0xFF;
23    bytes[17] = 0xFF;
24    bytes[18] = 0xFF;
25    bytes[19] = 0xFF;
26    Address::new(bytes)
27};
28
29#[derive(Debug, Clone, Default)]
30pub struct ArbHeaderInfo {
31    pub send_root: B256,
32    pub send_count: u64,
33    pub l1_block_number: u64,
34    pub arbos_format_version: u64,
35    pub collect_tips: bool,
36}
37
38impl ArbHeaderInfo {
39    pub fn compute_mix_hash(&self) -> B256 {
40        compute_arbos_mixhash(
41            self.send_count,
42            self.l1_block_number,
43            self.arbos_format_version,
44            self.collect_tips,
45        )
46    }
47}
48
49pub fn compute_arbos_mixhash(
50    send_count: u64,
51    l1_block_number: u64,
52    arbos_version: u64,
53    collect_tips: bool,
54) -> B256 {
55    let mut mix = [0u8; 32];
56    mix[0..8].copy_from_slice(&send_count.to_be_bytes());
57    mix[8..16].copy_from_slice(&l1_block_number.to_be_bytes());
58    mix[16..24].copy_from_slice(&arbos_version.to_be_bytes());
59    if collect_tips && arbos_version != arb_chainspec::arbos_version::ARBOS_VERSION_COLLECT_TIPS_OLD
60    {
61        mix[25] = 1;
62    }
63    B256::from(mix)
64}
65
66pub fn extract_collect_tips_from_mix_hash(mix_hash: B256, arbos_version: u64) -> bool {
67    if arbos_version == arb_chainspec::arbos_version::ARBOS_VERSION_COLLECT_TIPS_OLD {
68        return true;
69    }
70    mix_hash.0[25] & 0x1 == 1
71}
72
73/// Extract the send root from the first 32 bytes of header extra_data.
74pub fn extract_send_root_from_header_extra(extra: &[u8]) -> B256 {
75    if extra.len() >= 32 {
76        B256::from_slice(&extra[..32])
77    } else {
78        B256::ZERO
79    }
80}
81
82/// Extract ArbOS version from header mix_hash (bytes 16-23).
83pub fn extract_arbos_version_from_mix_hash(mix_hash: B256) -> u64 {
84    let mut buf = [0u8; 8];
85    buf.copy_from_slice(&mix_hash.0[16..24]);
86    u64::from_be_bytes(buf)
87}
88
89/// Extract send count from header mix_hash (bytes 0-7).
90pub fn extract_send_count_from_mix_hash(mix_hash: B256) -> u64 {
91    let mut buf = [0u8; 8];
92    buf.copy_from_slice(&mix_hash.0[0..8]);
93    u64::from_be_bytes(buf)
94}
95
96/// Extract L1 block number from header mix_hash (bytes 8-15).
97pub fn extract_l1_block_number_from_mix_hash(mix_hash: B256) -> u64 {
98    let mut buf = [0u8; 8];
99    buf.copy_from_slice(&mix_hash.0[8..16]);
100    u64::from_be_bytes(buf)
101}
102
103/// Convert a u64 to a left-padded B256 (big-endian in last 8 bytes).
104fn uint_to_hash_u64_be(k: u64) -> B256 {
105    let mut out = [0u8; 32];
106    out[24..32].copy_from_slice(&k.to_be_bytes());
107    B256::from(out)
108}
109
110/// Map a storage key + sub-key to a derived storage slot.
111fn storage_key_map(storage_key: &[u8], key: B256) -> B256 {
112    let boundary = 31usize;
113    let mut data = Vec::with_capacity(storage_key.len() + boundary);
114    data.extend_from_slice(storage_key);
115    data.extend_from_slice(&key.0[..boundary]);
116    let h = keccak256(&data);
117    let mut mapped = [0u8; 32];
118    mapped[..boundary].copy_from_slice(&h.0[..boundary]);
119    mapped[boundary] = key.0[boundary];
120    B256::from(mapped)
121}
122
123/// Derive a subspace key from parent + id.
124fn subspace(parent: &[u8], id: &[u8]) -> [u8; 32] {
125    let mut data = Vec::with_capacity(parent.len() + id.len());
126    data.extend_from_slice(parent);
127    data.extend_from_slice(id);
128    keccak256(&data).0
129}
130
131/// Calculate the number of partials in the Merkle accumulator.
132fn calc_num_partials(size: u64) -> u64 {
133    if size == 0 {
134        return 0;
135    }
136    64 - size.leading_zeros() as u64
137}
138
139/// Read a u64 from storage at a given slot (big-endian in last 8 bytes).
140///
141/// `read_slot` returns `Ok(None)` for an absent (zero) slot and `Err` for a
142/// backing-store failure; the error propagates so a failed read never silently
143/// reads as zero.
144pub fn read_storage_u64_be<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
145    read_slot: &F,
146    addr: Address,
147    slot: B256,
148) -> Result<Option<u64>, E> {
149    let Some(val) = read_slot(addr, slot)? else {
150        return Ok(None);
151    };
152    let bytes: [u8; 32] = val.to_be_bytes::<32>();
153    let mut buf = [0u8; 8];
154    buf.copy_from_slice(&bytes[24..32]);
155    Ok(Some(u64::from_be_bytes(buf)))
156}
157
158/// Read a B256 hash from storage at a given slot.
159pub fn read_storage_hash<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
160    read_slot: &F,
161    addr: Address,
162    slot: B256,
163) -> Result<Option<B256>, E> {
164    let Some(val) = read_slot(addr, slot)? else {
165        return Ok(None);
166    };
167    Ok(Some(B256::from(val.to_be_bytes::<32>())))
168}
169
170/// Compute the Merkle root from partials stored in state.
171pub fn merkle_root_from_partials<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
172    read_slot: &F,
173    addr: Address,
174    send_merkle_storage_key: &[u8],
175    size: u64,
176) -> Result<Option<B256>, E> {
177    if size == 0 {
178        return Ok(Some(B256::ZERO));
179    }
180    let mut hash_so_far: Option<B256> = None;
181    let mut capacity_in_hash: u64 = 0;
182    let mut capacity = 1u64;
183    let num_partials = calc_num_partials(size);
184    for level in 0..num_partials {
185        let key = uint_to_hash_u64_be(2 + level);
186        let slot = storage_key_map(send_merkle_storage_key, key);
187        let partial = read_storage_hash(read_slot, addr, slot)?.unwrap_or(B256::ZERO);
188        if partial != B256::ZERO {
189            if let Some(mut h) = hash_so_far {
190                while capacity_in_hash < capacity {
191                    let combined = [h.0.as_slice(), &[0u8; 32]].concat();
192                    h = keccak256(&combined);
193                    capacity_in_hash *= 2;
194                }
195                let combined = [partial.0.as_slice(), h.0.as_slice()].concat();
196                hash_so_far = Some(keccak256(&combined));
197                capacity_in_hash = 2 * capacity;
198            } else {
199                hash_so_far = Some(partial);
200                capacity_in_hash = capacity;
201            }
202        }
203        capacity = capacity.saturating_mul(2);
204    }
205    Ok(hash_so_far)
206}
207
208/// Derive ArbHeaderInfo from storage reads.
209///
210/// Returns `Ok(None)` when the ArbOS version slot is absent (pre-genesis state
211/// that cannot be derived) and `Err` when a backing-store read fails.
212pub fn derive_arb_header_info<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
213    read_slot: &F,
214    coinbase: Address,
215) -> Result<Option<ArbHeaderInfo>, E> {
216    let addr = ARBOS_STATE_ADDRESS;
217    let root_storage_key: &[u8] = &[];
218
219    let version_slot = storage_key_map(root_storage_key, uint_to_hash_u64_be(0));
220    let Some(arbos_version) = read_storage_u64_be(read_slot, addr, version_slot)? else {
221        return Ok(None);
222    };
223
224    let send_merkle_sub = subspace(root_storage_key, &[5u8]);
225    let blockhashes_sub = subspace(root_storage_key, &[6u8]);
226
227    let send_count_slot = storage_key_map(&send_merkle_sub, uint_to_hash_u64_be(0));
228    let send_count = read_storage_u64_be(read_slot, addr, send_count_slot)?.unwrap_or(0);
229
230    let send_root = merkle_root_from_partials(read_slot, addr, &send_merkle_sub, send_count)?
231        .unwrap_or(B256::ZERO);
232
233    let l1_block_num_slot = storage_key_map(&blockhashes_sub, uint_to_hash_u64_be(0));
234    let l1_block_number = read_storage_u64_be(read_slot, addr, l1_block_num_slot)?.unwrap_or(0);
235
236    // Tip collection is a block-level property: the flag only applies to blocks
237    // produced by the batch poster, not to delayed-message blocks.
238    let collect_tips_slot = storage_key_map(root_storage_key, uint_to_hash_u64_be(11));
239    let collect_tips = read_storage_u64_be(read_slot, addr, collect_tips_slot)?.unwrap_or(0) != 0
240        && coinbase == crate::l1_pricing::BATCH_POSTER_ADDRESS;
241
242    Ok(Some(ArbHeaderInfo {
243        send_root,
244        send_count,
245        l1_block_number,
246        arbos_format_version: arbos_version,
247        collect_tips,
248    }))
249}
250
251/// Get the storage address and slot for the ArbOS L1 block number.
252pub fn arbos_l1_block_number_slot() -> (Address, B256) {
253    let addr = ARBOS_STATE_ADDRESS;
254    let root_storage_key: &[u8] = &[];
255    let blockhashes_sub = subspace(root_storage_key, &[6u8]);
256    let l1_block_num_slot = storage_key_map(&blockhashes_sub, uint_to_hash_u64_be(0));
257    (addr, l1_block_num_slot)
258}
259
260/// Read ArbOS version from storage.
261pub fn read_arbos_version<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
262    read_slot: &F,
263) -> Result<Option<u64>, E> {
264    let addr = ARBOS_STATE_ADDRESS;
265    let root_storage_key: &[u8] = &[];
266    let version_slot = storage_key_map(root_storage_key, uint_to_hash_u64_be(0));
267    read_storage_u64_be(read_slot, addr, version_slot)
268}
269
270/// Read the L2 per-block gas limit from storage.
271pub fn read_l2_per_block_gas_limit<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
272    read_slot: &F,
273) -> Result<Option<u64>, E> {
274    let addr = ARBOS_STATE_ADDRESS;
275    let root_storage_key: &[u8] = &[];
276    let l2_pricing_subspace = subspace(root_storage_key, &[1u8]);
277    let per_block_gas_limit_slot = storage_key_map(&l2_pricing_subspace, uint_to_hash_u64_be(1));
278    read_storage_u64_be(read_slot, addr, per_block_gas_limit_slot)
279}
280
281/// Read the L2 base fee from storage.
282pub fn read_l2_base_fee<E, F: Fn(Address, B256) -> Result<Option<U256>, E>>(
283    read_slot: &F,
284) -> Result<Option<u64>, E> {
285    let addr = ARBOS_STATE_ADDRESS;
286    let root_storage_key: &[u8] = &[];
287    let l2_pricing_subspace = subspace(root_storage_key, &[1u8]);
288    let price_per_unit_slot = storage_key_map(&l2_pricing_subspace, uint_to_hash_u64_be(2));
289    read_storage_u64_be(read_slot, addr, price_per_unit_slot)
290}