hdp_primitives/block/
header.rs

1use std::str::FromStr;
2
3use alloy::{
4    hex,
5    primitives::{keccak256, Address, BlockNumber, Bloom, Bytes, B256, B64, U256},
6};
7use alloy_rlp::{length_of_length, BufMut, Decodable, Encodable};
8use serde::{Deserialize, Serialize};
9
10// =============================================================================
11// Header (credit: https://github.com/paradigmxyz/reth/blob/main/crates/primitives/src/header.rs#L133)
12// Orignally had dependnecy on `reth_primitives` crate, but it was removed to publish in crates.io
13// =============================================================================
14#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub struct Header {
16    /// The Keccak 256-bit hash of the parent
17    /// block’s header, in its entirety; formally Hp.
18    pub parent_hash: B256,
19    /// The Keccak 256-bit hash of the ommers list portion of this block; formally Ho.
20    pub ommers_hash: B256,
21    /// The 160-bit address to which all fees collected from the successful mining of this block
22    /// be transferred; formally Hc.
23    pub beneficiary: Address,
24    /// The Keccak 256-bit hash of the root node of the state trie, after all transactions are
25    /// executed and finalisations applied; formally Hr.
26    pub state_root: B256,
27    /// The Keccak 256-bit hash of the root node of the trie structure populated with each
28    /// transaction in the transactions list portion of the block; formally Ht.
29    pub transactions_root: B256,
30    /// The Keccak 256-bit hash of the root node of the trie structure populated with the receipts
31    /// of each transaction in the transactions list portion of the block; formally He.
32    pub receipts_root: B256,
33    /// The Keccak 256-bit hash of the withdrawals list portion of this block.
34    /// <https://eips.ethereum.org/EIPS/eip-4895>
35    pub withdrawals_root: Option<B256>,
36    /// The Bloom filter composed from indexable information (logger address and log topics)
37    /// contained in each log entry from the receipt of each transaction in the transactions list;
38    /// formally Hb.
39    pub logs_bloom: Bloom,
40    /// A scalar value corresponding to the difficulty level of this block. This can be calculated
41    /// from the previous block’s difficulty level and the timestamp; formally Hd.
42    pub difficulty: U256,
43    /// A scalar value equal to the number of ancestor blocks. The genesis block has a number of
44    /// zero; formally Hi.
45    pub number: BlockNumber,
46    /// A scalar value equal to the current limit of gas expenditure per block; formally Hl.
47    pub gas_limit: u64,
48    /// A scalar value equal to the total gas used in transactions in this block; formally Hg.
49    pub gas_used: u64,
50    /// A scalar value equal to the reasonable output of Unix’s time() at this block’s inception;
51    /// formally Hs.
52    pub timestamp: u64,
53    /// A 256-bit hash which, combined with the
54    /// nonce, proves that a sufficient amount of computation has been carried out on this block;
55    /// formally Hm.
56    pub mix_hash: B256,
57    /// A 64-bit value which, combined with the mixhash, proves that a sufficient amount of
58    /// computation has been carried out on this block; formally Hn.
59    pub nonce: u64,
60    /// A scalar representing EIP1559 base fee which can move up or down each block according
61    /// to a formula which is a function of gas used in parent block and gas target
62    /// (block gas limit divided by elasticity multiplier) of parent block.
63    /// The algorithm results in the base fee per gas increasing when blocks are
64    /// above the gas target, and decreasing when blocks are below the gas target. The base fee per
65    /// gas is burned.
66    pub base_fee_per_gas: Option<u64>,
67    /// The total amount of blob gas consumed by the transactions within the block, added in
68    /// EIP-4844.
69    pub blob_gas_used: Option<u64>,
70    /// A running total of blob gas consumed in excess of the target, prior to the block. Blocks
71    /// with above-target blob gas consumption increase this value, blocks with below-target blob
72    /// gas consumption decrease it (bounded at 0). This was added in EIP-4844.
73    pub excess_blob_gas: Option<u64>,
74    /// The hash of the parent beacon block's root is included in execution blocks, as proposed by
75    /// EIP-4788.
76    ///
77    /// This enables trust-minimized access to consensus state, supporting staking pools, bridges,
78    /// and more.
79    ///
80    /// The beacon roots contract handles root storage, enhancing Ethereum's functionalities.
81    pub parent_beacon_block_root: Option<B256>,
82    /// An arbitrary byte array containing data relevant to this block. This must be 32 bytes or
83    /// fewer; formally Hx.
84    pub extra_data: Bytes,
85}
86
87impl Header {
88    fn header_payload_length(&self) -> usize {
89        let mut length = 0;
90        length += self.parent_hash.length(); // Hash of the previous block.
91        length += self.ommers_hash.length(); // Hash of uncle blocks.
92        length += self.beneficiary.length(); // Address that receives rewards.
93        length += self.state_root.length(); // Root hash of the state object.
94        length += self.transactions_root.length(); // Root hash of transactions in the block.
95        length += self.receipts_root.length(); // Hash of transaction receipts.
96        length += self.logs_bloom.length(); // Data structure containing event logs.
97        length += self.difficulty.length(); // Difficulty value of the block.
98        length += U256::from(self.number).length(); // Block number.
99        length += U256::from(self.gas_limit).length(); // Maximum gas allowed.
100        length += U256::from(self.gas_used).length(); // Actual gas used.
101        length += self.timestamp.length(); // Block timestamp.
102        length += self.extra_data.length(); // Additional arbitrary data.
103        length += self.mix_hash.length(); // Hash used for mining.
104        length += B64::new(self.nonce.to_be_bytes()).length(); // Nonce for mining.
105
106        if let Some(base_fee) = self.base_fee_per_gas {
107            // Adding base fee length if it exists.
108            length += U256::from(base_fee).length();
109        }
110
111        if let Some(root) = self.withdrawals_root {
112            // Adding withdrawals_root length if it exists.
113            length += root.length();
114        }
115
116        if let Some(blob_gas_used) = self.blob_gas_used {
117            // Adding blob_gas_used length if it exists.
118            length += U256::from(blob_gas_used).length();
119        }
120
121        if let Some(excess_blob_gas) = self.excess_blob_gas {
122            // Adding excess_blob_gas length if it exists.
123            length += U256::from(excess_blob_gas).length();
124        }
125
126        if let Some(parent_beacon_block_root) = self.parent_beacon_block_root {
127            length += parent_beacon_block_root.length();
128        }
129
130        length
131    }
132
133    /// Heavy function that will calculate hash of data and will *not* save the change to metadata.
134    /// Use [`Header::seal`], [`SealedHeader`] and unlock if you need hash to be persistent.
135    pub fn hash_slow(&self) -> B256 {
136        keccak256(alloy_rlp::encode(self))
137    }
138}
139
140impl Encodable for Header {
141    fn encode(&self, out: &mut dyn BufMut) {
142        // Create a header indicating the encoded content is a list with the payload length computed
143        // from the header's payload calculation function.
144        let list_header = alloy_rlp::Header {
145            list: true,
146            payload_length: self.header_payload_length(),
147        };
148        list_header.encode(out);
149
150        // Encode each header field sequentially
151        self.parent_hash.encode(out); // Encode parent hash.
152        self.ommers_hash.encode(out); // Encode ommer's hash.
153        self.beneficiary.encode(out); // Encode beneficiary.
154        self.state_root.encode(out); // Encode state root.
155        self.transactions_root.encode(out); // Encode transactions root.
156        self.receipts_root.encode(out); // Encode receipts root.
157        self.logs_bloom.encode(out); // Encode logs bloom.
158        self.difficulty.encode(out); // Encode difficulty.
159        U256::from(self.number).encode(out); // Encode block number.
160        U256::from(self.gas_limit).encode(out); // Encode gas limit.
161        U256::from(self.gas_used).encode(out); // Encode gas used.
162        self.timestamp.encode(out); // Encode timestamp.
163        self.extra_data.encode(out); // Encode extra data.
164        self.mix_hash.encode(out); // Encode mix hash.
165        B64::new(self.nonce.to_be_bytes()).encode(out); // Encode nonce.
166
167        // Encode base fee. Put empty list if base fee is missing,
168        // but withdrawals root is present.
169        if let Some(ref base_fee) = self.base_fee_per_gas {
170            U256::from(*base_fee).encode(out);
171        }
172
173        // Encode withdrawals root. Put empty string if withdrawals root is missing,
174        // but blob gas used is present.
175        if let Some(ref root) = self.withdrawals_root {
176            root.encode(out);
177        }
178
179        // Encode blob gas used. Put empty list if blob gas used is missing,
180        // but excess blob gas is present.
181        if let Some(ref blob_gas_used) = self.blob_gas_used {
182            U256::from(*blob_gas_used).encode(out);
183        }
184
185        // Encode excess blob gas. Put empty list if excess blob gas is missing,
186        // but parent beacon block root is present.
187        if let Some(ref excess_blob_gas) = self.excess_blob_gas {
188            U256::from(*excess_blob_gas).encode(out);
189        }
190
191        // Encode parent beacon block root. If new fields are added, the above pattern will need to
192        // be repeated and placeholders added. Otherwise, it's impossible to tell _which_
193        // fields are missing. This is mainly relevant for contrived cases where a header is
194        // created at random, for example:
195        //  * A header is created with a withdrawals root, but no base fee. Shanghai blocks are
196        //    post-London, so this is technically not valid. However, a tool like proptest would
197        //    generate a block like this.
198        if let Some(ref parent_beacon_block_root) = self.parent_beacon_block_root {
199            parent_beacon_block_root.encode(out);
200        }
201    }
202
203    fn length(&self) -> usize {
204        let mut length = 0;
205        length += self.header_payload_length();
206        length += length_of_length(length);
207        length
208    }
209}
210
211impl Decodable for Header {
212    fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
213        let rlp_head = alloy_rlp::Header::decode(buf)?;
214        if !rlp_head.list {
215            return Err(alloy_rlp::Error::UnexpectedString);
216        }
217        let started_len = buf.len();
218        let mut this = Self {
219            parent_hash: Decodable::decode(buf)?,
220            ommers_hash: Decodable::decode(buf)?,
221            beneficiary: Decodable::decode(buf)?,
222            state_root: Decodable::decode(buf)?,
223            transactions_root: Decodable::decode(buf)?,
224            receipts_root: Decodable::decode(buf)?,
225            logs_bloom: Decodable::decode(buf)?,
226            difficulty: Decodable::decode(buf)?,
227            number: u64::decode(buf)?,
228            gas_limit: u64::decode(buf)?,
229            gas_used: u64::decode(buf)?,
230            timestamp: Decodable::decode(buf)?,
231            extra_data: Decodable::decode(buf)?,
232            mix_hash: Decodable::decode(buf)?,
233            nonce: u64::from_be_bytes(B64::decode(buf)?.0),
234            base_fee_per_gas: None,
235            withdrawals_root: None,
236            blob_gas_used: None,
237            excess_blob_gas: None,
238            parent_beacon_block_root: None,
239        };
240        if started_len - buf.len() < rlp_head.payload_length {
241            this.base_fee_per_gas = Some(u64::decode(buf)?);
242        }
243
244        // Withdrawals root for post-shanghai headers
245        if started_len - buf.len() < rlp_head.payload_length {
246            this.withdrawals_root = Some(Decodable::decode(buf)?);
247        }
248
249        // Blob gas used and excess blob gas for post-cancun headers
250        if started_len - buf.len() < rlp_head.payload_length {
251            this.blob_gas_used = Some(u64::decode(buf)?);
252        }
253
254        if started_len - buf.len() < rlp_head.payload_length {
255            this.excess_blob_gas = Some(u64::decode(buf)?);
256        }
257
258        // Decode parent beacon block root. If new fields are added, the above pattern will need to
259        // be repeated and placeholders decoded. Otherwise, it's impossible to tell _which_
260        // fields are missing. This is mainly relevant for contrived cases where a header is
261        // created at random, for example:
262        //  * A header is created with a withdrawals root, but no base fee. Shanghai blocks are
263        //    post-London, so this is technically not valid. However, a tool like proptest would
264        //    generate a block like this.
265        if started_len - buf.len() < rlp_head.payload_length {
266            this.parent_beacon_block_root = Some(B256::decode(buf)?);
267        }
268
269        let consumed = started_len - buf.len();
270        if consumed != rlp_head.payload_length {
271            return Err(alloy_rlp::Error::ListLengthMismatch {
272                expected: rlp_head.payload_length,
273                got: consumed,
274            });
275        }
276        Ok(this)
277    }
278}
279
280// =============================================================================
281
282impl Header {
283    #[allow(clippy::too_many_arguments)]
284    pub fn new(
285        parent_hash: B256,
286        ommers_hash: B256,
287        beneficiary: Address,
288        state_root: B256,
289        transactions_root: B256,
290        receipts_root: B256,
291        logs_bloom: Bloom,
292        difficulty: U256,
293        number: u64,
294        gas_limit: u64,
295        gas_used: u64,
296        timestamp: u64,
297        extra_data: Bytes,
298        mix_hash: B256,
299        nonce: u64,
300        base_fee_per_gas: Option<u64>,
301        withdrawals_root: Option<B256>,
302        blob_gas_used: Option<u64>,
303        excess_blob_gas: Option<u64>,
304        parent_beacon_block_root: Option<B256>,
305    ) -> Self {
306        Header {
307            parent_hash,
308            ommers_hash,
309            beneficiary,
310            state_root,
311            transactions_root,
312            receipts_root,
313            logs_bloom,
314            difficulty,
315            number,
316            gas_limit,
317            gas_used,
318            timestamp,
319            extra_data,
320            mix_hash,
321            nonce,
322            base_fee_per_gas,
323            withdrawals_root,
324            blob_gas_used,
325            excess_blob_gas,
326            parent_beacon_block_root,
327        }
328    }
329
330    pub fn rlp_encode(&self) -> Vec<u8> {
331        let mut buffer = Vec::<u8>::new();
332        self.encode(&mut buffer);
333        buffer
334    }
335
336    pub fn rlp_decode(mut rlp: &[u8]) -> Self {
337        <Header>::decode(&mut rlp).unwrap()
338    }
339
340    pub fn get_block_hash(&self) -> String {
341        self.hash_slow().to_string()
342    }
343}
344
345/// Block header returned from RPC
346/// https://ethereum.org/en/developers/docs/apis/json-rpc#eth_getblockbynumber
347#[derive(Serialize, Deserialize, Debug, Clone)]
348#[serde(rename_all = "camelCase")]
349pub struct BlockHeaderFromRpc {
350    pub base_fee_per_gas: Option<String>,
351    pub blob_gas_used: Option<String>,
352    pub difficulty: String,
353    pub excess_blob_gas: Option<String>,
354    pub extra_data: String,
355    pub gas_limit: String,
356    pub gas_used: String,
357    pub hash: String,
358    pub logs_bloom: String,
359    pub miner: String,
360    pub mix_hash: String,
361    pub nonce: String,
362    pub number: String,
363    pub parent_beacon_block_root: Option<String>,
364    pub parent_hash: String,
365    pub receipts_root: String,
366    pub sha3_uncles: String,
367    pub size: String,
368    pub state_root: String,
369    pub timestamp: String,
370    pub total_difficulty: String,
371    pub transactions_root: String,
372    pub withdrawals_root: Option<String>,
373}
374
375impl BlockHeaderFromRpc {
376    pub fn get_block_hash(&self) -> String {
377        self.hash.clone()
378    }
379}
380
381impl From<&BlockHeaderFromRpc> for Header {
382    fn from(value: &BlockHeaderFromRpc) -> Self {
383        Self {
384            parent_hash: B256::from_str(&value.parent_hash).expect("Invalid hex string"),
385            ommers_hash: B256::from_str(&value.sha3_uncles).expect("Invalid hex string"),
386            beneficiary: Address::from_str(&value.miner).expect("Invalid hex string"),
387            state_root: B256::from_str(&value.state_root).expect("Invalid hex string"),
388            transactions_root: B256::from_str(&value.transactions_root)
389                .expect("Invalid hex string"),
390            receipts_root: B256::from_str(&value.receipts_root).expect("Invalid hex string"),
391            logs_bloom: Bloom::from_str(&value.logs_bloom).expect("Invalid hex string"),
392            difficulty: U256::from_str_radix(&value.difficulty[2..], 16)
393                .expect("Invalid hex string"),
394            number: u64::from_str_radix(&value.number[2..], 16).expect("Invalid hex string"),
395            gas_limit: u64::from_str_radix(&value.gas_limit[2..], 16).expect("Invalid hex string"),
396            gas_used: u64::from_str_radix(&value.gas_used[2..], 16).expect("Invalid hex string"),
397            timestamp: u64::from_str_radix(&value.timestamp[2..], 16).expect("Invalid hex string"),
398            extra_data: Bytes::from_str(&value.extra_data).expect("Invalid hex string"),
399            mix_hash: B256::from_str(&value.mix_hash).expect("Invalid hex string"),
400            nonce: u64::from_str_radix(&value.nonce[2..], 16).expect("Invalid hex string"),
401            base_fee_per_gas: value
402                .base_fee_per_gas
403                .clone()
404                .map(|x| u64::from_str_radix(&x[2..], 16).expect("Invalid hex string")),
405            withdrawals_root: value
406                .withdrawals_root
407                .clone()
408                .map(|x| B256::from_str(&x).expect("Invalid hex string")),
409            blob_gas_used: value
410                .blob_gas_used
411                .clone()
412                .map(|x| u64::from_str_radix(&x[2..], 16).expect("Invalid hex string")),
413            excess_blob_gas: value
414                .excess_blob_gas
415                .clone()
416                .map(|x| u64::from_str_radix(&x[2..], 16).expect("Invalid hex string")),
417            parent_beacon_block_root: value
418                .parent_beacon_block_root
419                .clone()
420                .map(|x| B256::from_str(&x).expect("Invalid hex string")),
421        }
422    }
423}
424
425// THIS ENDPOINT IS DEPRECATED
426/// MMR metadata and proof returned from indexer
427// example https://rs-indexer.api.herodotus.cloud/accumulators/mmr-meta-and-proof?deployed_on_chain=11155111&accumulates_chain=11155111&block_numbers=4952100&block_numbers=4952101&block_numbers=4952102&block_numbers=4952103&hashing_function=poseidon&contract_type=AGGREGATOR
428#[derive(Serialize, Deserialize, Debug, Clone)]
429#[serde(rename_all = "camelCase")]
430pub struct MMRFromIndexer {
431    pub data: Vec<MMRDataFromIndexer>,
432}
433
434#[derive(Serialize, Deserialize, Debug, Clone)]
435pub struct MMRDataFromIndexer {
436    pub meta: MMRMetaFromIndexer,
437    pub proofs: Vec<MMRProofFromIndexer>,
438}
439
440#[derive(Serialize, Deserialize, Debug, Clone)]
441pub struct MMRMetaFromIndexer {
442    pub mmr_id: String,
443    pub mmr_peaks: Vec<String>,
444    pub mmr_root: String,
445    pub mmr_size: u64,
446}
447
448#[derive(Serialize, Deserialize, Debug, Clone)]
449pub struct MMRProofFromIndexer {
450    pub block_number: u64,
451    pub element_hash: String,
452    pub element_index: u64,
453    pub rlp_block_header: String,
454    pub siblings_hashes: Vec<String>,
455}
456
457/// MMR metadata and proof returned from indexer
458// example https://rs-indexer.api.herodotus.cloud/accumulators/proofs
459#[derive(Serialize, Deserialize, Debug, Clone)]
460#[serde(rename_all = "camelCase")]
461pub struct MMRFromNewIndexer {
462    pub data: Vec<MMRDataFromNewIndexer>,
463}
464
465#[derive(Serialize, Deserialize, Debug, Clone)]
466pub struct MMRDataFromNewIndexer {
467    pub meta: MMRMetaFromNewIndexer,
468    pub proofs: Vec<MMRProofFromNewIndexer>,
469}
470
471#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
472pub struct MMRMetaFromNewIndexer {
473    pub mmr_id: String,
474    pub mmr_peaks: Vec<String>,
475    pub mmr_root: String,
476    pub mmr_size: u64,
477}
478
479#[derive(Serialize, Deserialize, Debug, Clone)]
480pub struct RlpBlockHeader {
481    #[serde(rename = "String")]
482    pub value: String,
483}
484
485impl From<RlpBlockHeader> for Bytes {
486    fn from(rlp_block_header: RlpBlockHeader) -> Self {
487        Bytes::from(hex::decode(rlp_block_header.value).expect("Cannot decode RLP block header"))
488    }
489}
490
491#[derive(Serialize, Deserialize, Debug, Clone)]
492pub struct MMRProofFromNewIndexer {
493    pub block_number: u64,
494    pub element_hash: String,
495    pub element_index: u64,
496    #[serde(rename = "rlp_block_header")]
497    pub rlp_block_header: RlpBlockHeader,
498    pub siblings_hashes: Vec<String>,
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504    use std::str::FromStr;
505
506    use alloy::primitives::{hex, Address, Bloom, Bytes, FixedBytes, U256};
507    use alloy_rlp::Decodable;
508
509    #[test]
510    pub fn test_rlp() {
511        let rlp_hex ="f90266a045adb684cb5458019c496206c1383894c360fe969a1028ba44955eadfa585cc5a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d4934794b636a68f834b4d75af9edc5fb0138bb4758ed293a01db2388923f7c78680b4a46bae725637013d74ad787ec5c861d3ade3df882d81a093586eb5f2781ded334a2a03d178f41dc06f271d7f1ff429e4da6ef42d12a773a0361590775fea7857cc048b9324c03e96f287199803ce1440ff1e12c5c6008049b901000420000a200308000025201005a30400008962800402185dc600144280040082221400010101200458002b0d88008028004206808408400402108f0812246200240a204365100109051c082a020081204200001060440090044044448100082100028001060640c011401a802000090331000408243804009402201240802082820403801141050a4a00208283202050000f10058894008000411050512800220a200000042275800280894080000202460040030000408001ce00282400000002a8c24210000200014a30040015020b04800020608800000850440240c06100011002000000200988001800000880128a050400329081c144080a040800000480839eb0f68401c9c380836f9a8e8465aa87809f496c6c756d696e61746520446d6f63726174697a6520447374726962757465a0c653e1c1cee990147f4439776cc3ead6f175e081998c33c93da41653112e89ce8800000000000000000da039db3f9d1fe0756e5aef4e2f0241ad957e999e49c981809c018425d0080f6cd2830400008405320000a0713ce910d12e99ba96492ff2f6411d4e0a3e567ab419e92e60cf5fc4aa74db7a".to_string();
512        let rlp = hex::decode(rlp_hex).unwrap();
513        let decoded = <Header as Decodable>::decode(&mut rlp.as_slice()).unwrap();
514        let expected_header = Header {
515        parent_hash:FixedBytes::from_str( "0x45adb684cb5458019c496206c1383894c360fe969a1028ba44955eadfa585cc5").unwrap(),
516        ommers_hash: FixedBytes::from_str( "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347").unwrap(),
517        beneficiary: Address::from_str("0xb636a68f834b4d75af9edc5fb0138bb4758ed293").unwrap(),
518        state_root:FixedBytes::from_str( "0x1db2388923f7c78680b4a46bae725637013d74ad787ec5c861d3ade3df882d81"
519    ).unwrap(),
520        transactions_root: FixedBytes::from_str("0x93586eb5f2781ded334a2a03d178f41dc06f271d7f1ff429e4da6ef42d12a773"
521).unwrap(),
522        receipts_root: FixedBytes::from_str("0x361590775fea7857cc048b9324c03e96f287199803ce1440ff1e12c5c6008049"
523).unwrap(),
524        logs_bloom:Bloom::from_str("0x0420000a200308000025201005a30400008962800402185dc600144280040082221400010101200458002b0d88008028004206808408400402108f0812246200240a204365100109051c082a020081204200001060440090044044448100082100028001060640c011401a802000090331000408243804009402201240802082820403801141050a4a00208283202050000f10058894008000411050512800220a200000042275800280894080000202460040030000408001ce00282400000002a8c24210000200014a30040015020b04800020608800000850440240c06100011002000000200988001800000880128a050400329081c144080a0408000004").unwrap(),
525        difficulty: U256::from(0x0),
526        number: 0x9eb0f6u64,
527        gas_limit: 0x1c9c380u64,
528        gas_used: 0x6f9a8eu64,
529        timestamp: 0x65aa8780u64,
530        extra_data: Bytes::from_str("0x496c6c756d696e61746520446d6f63726174697a6520447374726962757465").unwrap(),
531        mix_hash: FixedBytes::from_str("0xc653e1c1cee990147f4439776cc3ead6f175e081998c33c93da41653112e89ce").unwrap(),
532        nonce:0x0u64,
533        base_fee_per_gas: Some(13),
534        withdrawals_root: Some(FixedBytes::from_str("0x39db3f9d1fe0756e5aef4e2f0241ad957e999e49c981809c018425d0080f6cd2").unwrap()),
535        blob_gas_used: Some(0x40000u64),
536        excess_blob_gas: Some(0x5320000u64),
537        parent_beacon_block_root: Some(FixedBytes::from_str("0x713ce910d12e99ba96492ff2f6411d4e0a3e567ab419e92e60cf5fc4aa74db7a").unwrap()),
538    };
539
540        assert_eq!(decoded, expected_header);
541    }
542}