alloy_consensus_any/block/
header.rs

1use alloy_consensus::{error::ValueError, BlockHeader, Header};
2use alloy_primitives::{Address, BlockNumber, Bloom, Bytes, Sealed, B256, B64, U256};
3
4/// Block header representation with certain fields made optional to account for possible
5/// differences in network implementations.
6#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
7#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
9#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
10pub struct AnyHeader {
11    /// Hash of the parent
12    pub parent_hash: B256,
13    /// Hash of the uncles
14    #[cfg_attr(feature = "serde", serde(rename = "sha3Uncles"))]
15    pub ommers_hash: B256,
16    /// Alias of `author`
17    #[cfg_attr(feature = "serde", serde(rename = "miner"))]
18    pub beneficiary: Address,
19    /// State root hash
20    #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_state_root"))]
21    pub state_root: B256,
22    /// Transactions root hash
23    pub transactions_root: B256,
24    /// Transactions receipts root hash
25    pub receipts_root: B256,
26    /// Logs bloom
27    pub logs_bloom: Bloom,
28    /// Difficulty
29    pub difficulty: U256,
30    /// Block number
31    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
32    pub number: u64,
33    /// Gas Limit
34    #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))]
35    pub gas_limit: u64,
36    /// Gas Used
37    #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))]
38    pub gas_used: u64,
39    /// Timestamp
40    #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))]
41    pub timestamp: u64,
42    /// Extra data
43    pub extra_data: Bytes,
44    /// Mix Hash
45    ///
46    /// Before the merge this proves, combined with the nonce, that a sufficient amount of
47    /// computation has been carried out on this block: the Proof-of-Work (PoW).
48    ///
49    /// After the merge this is `prevRandao`: Randomness value for the generated payload.
50    ///
51    /// This is an Option because it is not always set by non-ethereum networks.
52    ///
53    /// See also <https://eips.ethereum.org/EIPS/eip-4399>
54    /// And <https://github.com/ethereum/execution-apis/issues/328>
55    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
56    pub mix_hash: Option<B256>,
57    /// Nonce
58    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
59    pub nonce: Option<B64>,
60    /// Base fee per unit of gas (if past London)
61    #[cfg_attr(
62        feature = "serde",
63        serde(
64            default,
65            skip_serializing_if = "Option::is_none",
66            with = "alloy_serde::quantity::opt"
67        )
68    )]
69    pub base_fee_per_gas: Option<u64>,
70    /// Withdrawals root hash added by EIP-4895 and is ignored in legacy headers.
71    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
72    pub withdrawals_root: Option<B256>,
73    /// Blob gas used
74    #[cfg_attr(
75        feature = "serde",
76        serde(
77            default,
78            skip_serializing_if = "Option::is_none",
79            with = "alloy_serde::quantity::opt"
80        )
81    )]
82    pub blob_gas_used: Option<u64>,
83    /// Excess blob gas
84    #[cfg_attr(
85        feature = "serde",
86        serde(
87            default,
88            skip_serializing_if = "Option::is_none",
89            with = "alloy_serde::quantity::opt"
90        )
91    )]
92    pub excess_blob_gas: Option<u64>,
93    /// EIP-4788 parent beacon block root
94    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
95    pub parent_beacon_block_root: Option<B256>,
96    /// EIP-7685 requests hash.
97    #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
98    pub requests_hash: Option<B256>,
99}
100
101impl AnyHeader {
102    /// Seal the header with a known hash.
103    ///
104    /// WARNING: This method does not perform validation whether the hash is correct.
105    #[inline]
106    pub const fn seal(self, hash: B256) -> Sealed<Self> {
107        Sealed::new_unchecked(self, hash)
108    }
109
110    /// Attempts to convert this header into a `Header`.
111    ///
112    /// This can fail if the header is missing required fields:
113    /// - nonce
114    /// - mix_hash
115    ///
116    /// If the conversion fails, the original [`AnyHeader`] is returned.
117    pub fn try_into_header(self) -> Result<Header, ValueError<Self>> {
118        if self.nonce.is_none() {
119            return Err(ValueError::new(self, "missing nonce field"));
120        }
121        if self.mix_hash.is_none() {
122            return Err(ValueError::new(self, "missing mix hash field"));
123        }
124
125        let Self {
126            parent_hash,
127            ommers_hash,
128            beneficiary,
129            state_root,
130            transactions_root,
131            receipts_root,
132            logs_bloom,
133            difficulty,
134            number,
135            gas_limit,
136            gas_used,
137            timestamp,
138            extra_data,
139            mix_hash,
140            nonce,
141            base_fee_per_gas,
142            withdrawals_root,
143            blob_gas_used,
144            excess_blob_gas,
145            parent_beacon_block_root,
146            requests_hash,
147        } = self;
148
149        Ok(Header {
150            parent_hash,
151            ommers_hash,
152            beneficiary,
153            state_root,
154            transactions_root,
155            receipts_root,
156            logs_bloom,
157            difficulty,
158            number,
159            gas_limit,
160            gas_used,
161            timestamp,
162            extra_data,
163            mix_hash: mix_hash.unwrap(),
164            nonce: nonce.unwrap(),
165            base_fee_per_gas,
166            withdrawals_root,
167            blob_gas_used,
168            excess_blob_gas,
169            parent_beacon_block_root,
170            requests_hash,
171        })
172    }
173
174    /// Converts this header into a [`Header`] with default values for missing mandatory fields:
175    /// - mix_hash
176    /// - nonce
177    pub fn into_header_with_defaults(self) -> Header {
178        let Self {
179            parent_hash,
180            ommers_hash,
181            beneficiary,
182            state_root,
183            transactions_root,
184            receipts_root,
185            logs_bloom,
186            difficulty,
187            number,
188            gas_limit,
189            gas_used,
190            timestamp,
191            extra_data,
192            mix_hash,
193            nonce,
194            base_fee_per_gas,
195            withdrawals_root,
196            blob_gas_used,
197            excess_blob_gas,
198            parent_beacon_block_root,
199            requests_hash,
200        } = self;
201
202        Header {
203            parent_hash,
204            ommers_hash,
205            beneficiary,
206            state_root,
207            transactions_root,
208            receipts_root,
209            logs_bloom,
210            difficulty,
211            number,
212            gas_limit,
213            gas_used,
214            timestamp,
215            extra_data,
216            mix_hash: mix_hash.unwrap_or_default(),
217            nonce: nonce.unwrap_or_default(),
218            base_fee_per_gas,
219            withdrawals_root,
220            blob_gas_used,
221            excess_blob_gas,
222            parent_beacon_block_root,
223            requests_hash,
224        }
225    }
226}
227
228impl BlockHeader for AnyHeader {
229    fn parent_hash(&self) -> B256 {
230        self.parent_hash
231    }
232
233    fn ommers_hash(&self) -> B256 {
234        self.ommers_hash
235    }
236
237    fn beneficiary(&self) -> Address {
238        self.beneficiary
239    }
240
241    fn state_root(&self) -> B256 {
242        self.state_root
243    }
244
245    fn transactions_root(&self) -> B256 {
246        self.transactions_root
247    }
248
249    fn receipts_root(&self) -> B256 {
250        self.receipts_root
251    }
252
253    fn withdrawals_root(&self) -> Option<B256> {
254        self.withdrawals_root
255    }
256
257    fn logs_bloom(&self) -> Bloom {
258        self.logs_bloom
259    }
260
261    fn difficulty(&self) -> U256 {
262        self.difficulty
263    }
264
265    fn number(&self) -> BlockNumber {
266        self.number
267    }
268
269    fn gas_limit(&self) -> u64 {
270        self.gas_limit
271    }
272
273    fn gas_used(&self) -> u64 {
274        self.gas_used
275    }
276
277    fn timestamp(&self) -> u64 {
278        self.timestamp
279    }
280
281    fn mix_hash(&self) -> Option<B256> {
282        self.mix_hash
283    }
284
285    fn nonce(&self) -> Option<B64> {
286        self.nonce
287    }
288
289    fn base_fee_per_gas(&self) -> Option<u64> {
290        self.base_fee_per_gas
291    }
292
293    fn blob_gas_used(&self) -> Option<u64> {
294        self.blob_gas_used
295    }
296
297    fn excess_blob_gas(&self) -> Option<u64> {
298        self.excess_blob_gas
299    }
300
301    fn parent_beacon_block_root(&self) -> Option<B256> {
302        self.parent_beacon_block_root
303    }
304
305    fn requests_hash(&self) -> Option<B256> {
306        self.requests_hash
307    }
308
309    fn extra_data(&self) -> &Bytes {
310        &self.extra_data
311    }
312}
313
314impl From<Header> for AnyHeader {
315    fn from(value: Header) -> Self {
316        let Header {
317            parent_hash,
318            ommers_hash,
319            beneficiary,
320            state_root,
321            transactions_root,
322            receipts_root,
323            logs_bloom,
324            difficulty,
325            number,
326            gas_limit,
327            gas_used,
328            timestamp,
329            extra_data,
330            mix_hash,
331            nonce,
332            base_fee_per_gas,
333            withdrawals_root,
334            blob_gas_used,
335            excess_blob_gas,
336            parent_beacon_block_root,
337            requests_hash,
338        } = value;
339
340        Self {
341            parent_hash,
342            ommers_hash,
343            beneficiary,
344            state_root,
345            transactions_root,
346            receipts_root,
347            logs_bloom,
348            difficulty,
349            number,
350            gas_limit,
351            gas_used,
352            timestamp,
353            extra_data,
354            mix_hash: Some(mix_hash),
355            nonce: Some(nonce),
356            base_fee_per_gas,
357            withdrawals_root,
358            blob_gas_used,
359            excess_blob_gas,
360            parent_beacon_block_root,
361            requests_hash,
362        }
363    }
364}
365
366impl TryFrom<AnyHeader> for Header {
367    type Error = ValueError<AnyHeader>;
368
369    fn try_from(value: AnyHeader) -> Result<Self, Self::Error> {
370        value.try_into_header()
371    }
372}
373
374/// Custom deserializer for `state_root` that treats `"0x"` or empty as `B256::ZERO`
375///
376/// This exists because some networks (like Tron) may serialize the state root as `"0x"`
377#[cfg(feature = "serde")]
378fn lenient_state_root<'de, D>(deserializer: D) -> Result<B256, D::Error>
379where
380    D: serde::de::Deserializer<'de>,
381{
382    use alloc::string::String;
383    use core::str::FromStr;
384    use serde::de::Error;
385
386    let s: String = serde::de::Deserialize::deserialize(deserializer)?;
387    let s = s.trim();
388
389    if s == "0x" || s.is_empty() {
390        return Ok(B256::ZERO);
391    }
392
393    B256::from_str(s).map_err(D::Error::custom)
394}
395
396#[cfg(test)]
397mod tests {
398
399    // <https://github.com/alloy-rs/alloy/issues/2494>
400    #[test]
401    #[cfg(feature = "serde")]
402    fn deserializes_tron_state_root_in_header() {
403        use super::*;
404        use alloy_primitives::B256;
405
406        let s = r#"{
407  "baseFeePerGas": "0x0",
408  "difficulty": "0x0",
409  "extraData": "0x",
410  "gasLimit": "0x160227b88",
411  "gasUsed": "0x360d92",
412  "hash": "0x00000000040a0687e0fc7194aabd024a4786ce94ad63855774f8d48896d8750b",
413  "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
414  "miner": "0x9a96c8003a1e3a6866c08acff9f629e2a6ef062b",
415  "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
416  "nonce": "0x0000000000000000",
417  "number": "0x40a0687",
418  "parentHash": "0x00000000040a068652c581a982a0d17976201ad44aa28eb4e24881e82f99ee04",
419  "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
420  "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
421  "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
422  "size": "0xba05",
423  "stateRoot": "0x",
424  "timestamp": "0x6759f2f1",
425  "totalDifficulty": "0x0"
426}"#;
427
428        let header: AnyHeader = serde_json::from_str(s).unwrap();
429        assert_eq!(header.state_root, B256::ZERO);
430    }
431}