1use alloy_consensus::{error::ValueError, BlockHeader, Header};
2use alloy_primitives::{Address, BlockNumber, Bloom, Bytes, Sealed, B256, B64, U256};
3
4#[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 pub parent_hash: B256,
13 #[cfg_attr(feature = "serde", serde(rename = "sha3Uncles"))]
15 pub ommers_hash: B256,
16 #[cfg_attr(feature = "serde", serde(rename = "miner"))]
18 pub beneficiary: Address,
19 #[cfg_attr(feature = "serde", serde(deserialize_with = "lenient_state_root"))]
21 pub state_root: B256,
22 pub transactions_root: B256,
24 pub receipts_root: B256,
26 pub logs_bloom: Bloom,
28 pub difficulty: U256,
30 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
32 pub number: u64,
33 #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))]
35 pub gas_limit: u64,
36 #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))]
38 pub gas_used: u64,
39 #[cfg_attr(feature = "serde", serde(default, with = "alloy_serde::quantity"))]
41 pub timestamp: u64,
42 pub extra_data: Bytes,
44 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
56 pub mix_hash: Option<B256>,
57 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
59 pub nonce: Option<B64>,
60 #[cfg_attr(
72 feature = "serde",
73 serde(
74 default,
75 skip_serializing_if = "Option::is_none",
76 with = "saturating_base_fee_per_gas"
77 )
78 )]
79 pub base_fee_per_gas: Option<u64>,
80 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
82 pub withdrawals_root: Option<B256>,
83 #[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 blob_gas_used: Option<u64>,
93 #[cfg_attr(
95 feature = "serde",
96 serde(
97 default,
98 skip_serializing_if = "Option::is_none",
99 with = "alloy_serde::quantity::opt"
100 )
101 )]
102 pub excess_blob_gas: Option<u64>,
103 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
105 pub parent_beacon_block_root: Option<B256>,
106 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
108 pub requests_hash: Option<B256>,
109 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
111 pub block_access_list_hash: Option<B256>,
112 #[cfg_attr(
114 feature = "serde",
115 serde(
116 default,
117 skip_serializing_if = "Option::is_none",
118 with = "alloy_serde::quantity::opt"
119 )
120 )]
121 pub slot_number: Option<u64>,
122}
123
124impl AnyHeader {
125 #[inline]
129 pub const fn seal(self, hash: B256) -> Sealed<Self> {
130 Sealed::new_unchecked(self, hash)
131 }
132
133 pub fn try_into_header(self) -> Result<Header, ValueError<Self>> {
141 if self.nonce.is_none() {
142 return Err(ValueError::new(self, "missing nonce field"));
143 }
144 if self.mix_hash.is_none() {
145 return Err(ValueError::new(self, "missing mix hash field"));
146 }
147
148 let Self {
149 parent_hash,
150 ommers_hash,
151 beneficiary,
152 state_root,
153 transactions_root,
154 receipts_root,
155 logs_bloom,
156 difficulty,
157 number,
158 gas_limit,
159 gas_used,
160 timestamp,
161 extra_data,
162 mix_hash,
163 nonce,
164 base_fee_per_gas,
165 withdrawals_root,
166 blob_gas_used,
167 excess_blob_gas,
168 parent_beacon_block_root,
169 requests_hash,
170 block_access_list_hash,
171 slot_number,
172 } = self;
173
174 Ok(Header {
175 parent_hash,
176 ommers_hash,
177 beneficiary,
178 state_root,
179 transactions_root,
180 receipts_root,
181 logs_bloom,
182 difficulty,
183 number,
184 gas_limit,
185 gas_used,
186 timestamp,
187 extra_data,
188 mix_hash: mix_hash.unwrap(),
189 nonce: nonce.unwrap(),
190 base_fee_per_gas,
191 withdrawals_root,
192 blob_gas_used,
193 excess_blob_gas,
194 parent_beacon_block_root,
195 requests_hash,
196 block_access_list_hash,
197 slot_number,
198 })
199 }
200
201 pub fn into_header_with_defaults(self) -> Header {
205 let Self {
206 parent_hash,
207 ommers_hash,
208 beneficiary,
209 state_root,
210 transactions_root,
211 receipts_root,
212 logs_bloom,
213 difficulty,
214 number,
215 gas_limit,
216 gas_used,
217 timestamp,
218 extra_data,
219 mix_hash,
220 nonce,
221 base_fee_per_gas,
222 withdrawals_root,
223 blob_gas_used,
224 excess_blob_gas,
225 parent_beacon_block_root,
226 requests_hash,
227 block_access_list_hash,
228 slot_number,
229 } = self;
230
231 Header {
232 parent_hash,
233 ommers_hash,
234 beneficiary,
235 state_root,
236 transactions_root,
237 receipts_root,
238 logs_bloom,
239 difficulty,
240 number,
241 gas_limit,
242 gas_used,
243 timestamp,
244 extra_data,
245 mix_hash: mix_hash.unwrap_or_default(),
246 nonce: nonce.unwrap_or_default(),
247 base_fee_per_gas,
248 withdrawals_root,
249 blob_gas_used,
250 excess_blob_gas,
251 parent_beacon_block_root,
252 requests_hash,
253 block_access_list_hash,
254 slot_number,
255 }
256 }
257}
258
259impl BlockHeader for AnyHeader {
260 fn parent_hash(&self) -> B256 {
261 self.parent_hash
262 }
263
264 fn ommers_hash(&self) -> B256 {
265 self.ommers_hash
266 }
267
268 fn beneficiary(&self) -> Address {
269 self.beneficiary
270 }
271
272 fn state_root(&self) -> B256 {
273 self.state_root
274 }
275
276 fn transactions_root(&self) -> B256 {
277 self.transactions_root
278 }
279
280 fn receipts_root(&self) -> B256 {
281 self.receipts_root
282 }
283
284 fn withdrawals_root(&self) -> Option<B256> {
285 self.withdrawals_root
286 }
287
288 fn logs_bloom(&self) -> Bloom {
289 self.logs_bloom
290 }
291
292 fn difficulty(&self) -> U256 {
293 self.difficulty
294 }
295
296 fn number(&self) -> BlockNumber {
297 self.number
298 }
299
300 fn gas_limit(&self) -> u64 {
301 self.gas_limit
302 }
303
304 fn gas_used(&self) -> u64 {
305 self.gas_used
306 }
307
308 fn timestamp(&self) -> u64 {
309 self.timestamp
310 }
311
312 fn mix_hash(&self) -> Option<B256> {
313 self.mix_hash
314 }
315
316 fn nonce(&self) -> Option<B64> {
317 self.nonce
318 }
319
320 fn base_fee_per_gas(&self) -> Option<u64> {
321 self.base_fee_per_gas
322 }
323
324 fn blob_gas_used(&self) -> Option<u64> {
325 self.blob_gas_used
326 }
327
328 fn excess_blob_gas(&self) -> Option<u64> {
329 self.excess_blob_gas
330 }
331
332 fn parent_beacon_block_root(&self) -> Option<B256> {
333 self.parent_beacon_block_root
334 }
335
336 fn requests_hash(&self) -> Option<B256> {
337 self.requests_hash
338 }
339
340 fn block_access_list_hash(&self) -> Option<B256> {
341 self.block_access_list_hash
342 }
343
344 fn slot_number(&self) -> Option<u64> {
345 self.slot_number
346 }
347
348 fn extra_data(&self) -> &Bytes {
349 &self.extra_data
350 }
351}
352
353impl From<Header> for AnyHeader {
354 fn from(value: Header) -> Self {
355 let Header {
356 parent_hash,
357 ommers_hash,
358 beneficiary,
359 state_root,
360 transactions_root,
361 receipts_root,
362 logs_bloom,
363 difficulty,
364 number,
365 gas_limit,
366 gas_used,
367 timestamp,
368 extra_data,
369 mix_hash,
370 nonce,
371 base_fee_per_gas,
372 withdrawals_root,
373 blob_gas_used,
374 excess_blob_gas,
375 parent_beacon_block_root,
376 requests_hash,
377 block_access_list_hash,
378 slot_number,
379 } = value;
380
381 Self {
382 parent_hash,
383 ommers_hash,
384 beneficiary,
385 state_root,
386 transactions_root,
387 receipts_root,
388 logs_bloom,
389 difficulty,
390 number,
391 gas_limit,
392 gas_used,
393 timestamp,
394 extra_data,
395 mix_hash: Some(mix_hash),
396 nonce: Some(nonce),
397 base_fee_per_gas,
398 withdrawals_root,
399 blob_gas_used,
400 excess_blob_gas,
401 parent_beacon_block_root,
402 requests_hash,
403 block_access_list_hash,
404 slot_number,
405 }
406 }
407}
408
409impl TryFrom<AnyHeader> for Header {
410 type Error = ValueError<AnyHeader>;
411
412 fn try_from(value: AnyHeader) -> Result<Self, Self::Error> {
413 value.try_into_header()
414 }
415}
416
417#[cfg(feature = "serde")]
423mod saturating_base_fee_per_gas {
424 use alloy_primitives::U256;
425 use serde::{Deserialize, Deserializer, Serializer};
426
427 pub(super) fn serialize<S>(value: &Option<u64>, serializer: S) -> Result<S::Ok, S::Error>
428 where
429 S: Serializer,
430 {
431 alloy_serde::quantity::opt::serialize(value, serializer)
432 }
433
434 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
435 where
436 D: Deserializer<'de>,
437 {
438 if deserializer.is_human_readable() {
439 let opt: Option<U256> = Option::deserialize(deserializer)?;
440 Ok(opt.map(|v| v.try_into().unwrap_or(u64::MAX)))
441 } else {
442 alloy_serde::quantity::opt::deserialize(deserializer)
443 }
444 }
445}
446
447#[cfg(feature = "serde")]
451fn lenient_state_root<'de, D>(deserializer: D) -> Result<B256, D::Error>
452where
453 D: serde::de::Deserializer<'de>,
454{
455 use alloc::string::String;
456 use core::str::FromStr;
457 use serde::de::Error;
458
459 let s: String = serde::de::Deserialize::deserialize(deserializer)?;
460 let s = s.trim();
461
462 if s == "0x" || s.is_empty() {
463 return Ok(B256::ZERO);
464 }
465
466 B256::from_str(s).map_err(D::Error::custom)
467}
468
469#[cfg(test)]
470mod tests {
471
472 #[test]
474 #[cfg(feature = "serde")]
475 fn deserializes_tron_state_root_in_header() {
476 use super::*;
477 use alloy_primitives::B256;
478
479 let s = r#"{
480 "baseFeePerGas": "0x0",
481 "difficulty": "0x0",
482 "extraData": "0x",
483 "gasLimit": "0x160227b88",
484 "gasUsed": "0x360d92",
485 "hash": "0x00000000040a0687e0fc7194aabd024a4786ce94ad63855774f8d48896d8750b",
486 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
487 "miner": "0x9a96c8003a1e3a6866c08acff9f629e2a6ef062b",
488 "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
489 "nonce": "0x0000000000000000",
490 "number": "0x40a0687",
491 "parentHash": "0x00000000040a068652c581a982a0d17976201ad44aa28eb4e24881e82f99ee04",
492 "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
493 "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
494 "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
495 "size": "0xba05",
496 "stateRoot": "0x",
497 "timestamp": "0x6759f2f1",
498 "totalDifficulty": "0x0"
499}"#;
500
501 let header: AnyHeader = serde_json::from_str(s).unwrap();
502 assert_eq!(header.state_root, B256::ZERO);
503 }
504
505 #[test]
507 #[cfg(feature = "serde")]
508 fn deserializes_base_fee_within_u64() {
509 use super::*;
510
511 let s = r#"{
513 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
514 "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
515 "miner": "0x0000000000000000000000000000000000000000",
516 "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
517 "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
518 "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
519 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
520 "difficulty": "0x0",
521 "number": "0x1",
522 "gasLimit": "0x1c9c380",
523 "gasUsed": "0x5208",
524 "timestamp": "0x65",
525 "extraData": "0x",
526 "baseFeePerGas": "0x3b9aca00"
527}"#;
528 let header: AnyHeader = serde_json::from_str(s).unwrap();
529 assert_eq!(header.base_fee_per_gas, Some(1_000_000_000));
530 }
531
532 #[test]
538 #[cfg(feature = "serde")]
539 fn deserializes_base_fee_saturates_above_u64() {
540 use super::*;
541
542 let s = r#"{
544 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
545 "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
546 "miner": "0x0000000000000000000000000000000000000000",
547 "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
548 "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
549 "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
550 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
551 "difficulty": "0x0",
552 "number": "0x1",
553 "gasLimit": "0x1c9c380",
554 "gasUsed": "0x5208",
555 "timestamp": "0x65",
556 "extraData": "0x",
557 "baseFeePerGas": "0x3635c9adc5dea00000"
558}"#;
559 let header: AnyHeader = serde_json::from_str(s).unwrap();
560 assert_eq!(header.base_fee_per_gas, Some(u64::MAX));
561 }
562
563 #[test]
565 #[cfg(feature = "serde")]
566 fn deserializes_base_fee_exact_u64_max() {
567 use super::*;
568
569 let s = r#"{
571 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
572 "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
573 "miner": "0x0000000000000000000000000000000000000000",
574 "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
575 "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
576 "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
577 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
578 "difficulty": "0x0",
579 "number": "0x1",
580 "gasLimit": "0x1c9c380",
581 "gasUsed": "0x5208",
582 "timestamp": "0x65",
583 "extraData": "0x",
584 "baseFeePerGas": "0xffffffffffffffff"
585}"#;
586 let header: AnyHeader = serde_json::from_str(s).unwrap();
587 assert_eq!(header.base_fee_per_gas, Some(u64::MAX));
588 }
589
590 #[test]
593 #[cfg(feature = "serde")]
594 fn binary_roundtrip_preserves_base_fee() {
595 #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
596 struct BaseFee {
597 #[serde(with = "super::saturating_base_fee_per_gas")]
598 base_fee_per_gas: Option<u64>,
599 }
600
601 let header = BaseFee { base_fee_per_gas: Some(1_000_000_000) };
602 let encoded = bincode::serde::encode_to_vec(&header, bincode::config::legacy()).unwrap();
603 let (decoded, _) =
604 bincode::serde::decode_from_slice::<BaseFee, _>(&encoded, bincode::config::legacy())
605 .unwrap();
606
607 assert_eq!(decoded, header);
608 }
609
610 #[test]
612 #[cfg(feature = "serde")]
613 fn deserializes_base_fee_absent() {
614 use super::*;
615
616 let s = r#"{
617 "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
618 "sha3Uncles": "0x0000000000000000000000000000000000000000000000000000000000000000",
619 "miner": "0x0000000000000000000000000000000000000000",
620 "stateRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
621 "transactionsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
622 "receiptsRoot": "0x0000000000000000000000000000000000000000000000000000000000000000",
623 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
624 "difficulty": "0x0",
625 "number": "0x1",
626 "gasLimit": "0x1c9c380",
627 "gasUsed": "0x5208",
628 "timestamp": "0x65",
629 "extraData": "0x"
630}"#;
631 let header: AnyHeader = serde_json::from_str(s).unwrap();
632 assert_eq!(header.base_fee_per_gas, None);
633 }
634}