1use crate::Transaction;
4use alloc::{collections::BTreeMap, vec::Vec};
5use alloy_consensus::{error::ValueError, BlockBody, BlockHeader, Sealed, TxEnvelope};
6use alloy_eips::{eip4895::Withdrawals, eip7840::BlobParams, Encodable2718};
7use alloy_network_primitives::{
8 BlockResponse, BlockTransactions, HeaderResponse, TransactionResponse,
9};
10use alloy_primitives::{Address, BlockHash, Bloom, Bytes, Sealable, B256, B64, U256};
11use alloy_rlp::Encodable;
12use core::ops::{Deref, DerefMut};
13
14pub use alloy_eips::{
15 calc_blob_gasprice, calc_excess_blob_gas, BlockHashOrNumber, BlockId, BlockNumHash,
16 BlockNumberOrTag, ForkBlock, RpcBlockHash,
17};
18
19#[derive(Clone, Debug, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
23pub struct Block<T = Transaction<TxEnvelope>, H = Header> {
24 #[cfg_attr(feature = "serde", serde(flatten))]
26 pub header: H,
27 #[cfg_attr(feature = "serde", serde(default))]
29 pub uncles: Vec<B256>,
30 #[cfg_attr(
33 feature = "serde",
34 serde(
35 default = "BlockTransactions::uncle",
36 skip_serializing_if = "BlockTransactions::is_uncle"
37 )
38 )]
39 pub transactions: BlockTransactions<T>,
40 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
42 pub withdrawals: Option<Withdrawals>,
43}
44
45impl<T, H: Default> Default for Block<T, H> {
47 fn default() -> Self {
48 Self {
49 header: Default::default(),
50 uncles: Default::default(),
51 transactions: Default::default(),
52 withdrawals: Default::default(),
53 }
54 }
55}
56
57impl<T, H> Block<T, H> {
58 pub const fn empty(header: H) -> Self {
60 Self::new(header, BlockTransactions::Full(vec![]))
61 }
62
63 pub const fn new(header: H, transactions: BlockTransactions<T>) -> Self {
78 Self { header, uncles: vec![], transactions, withdrawals: None }
79 }
80
81 pub fn apply<F>(self, f: F) -> Self
83 where
84 F: FnOnce(Self) -> Self,
85 {
86 f(self)
87 }
88
89 pub fn with_transactions(mut self, transactions: BlockTransactions<T>) -> Self {
91 self.transactions = transactions;
92 self
93 }
94
95 pub fn with_withdrawals(mut self, withdrawals: Option<Withdrawals>) -> Self {
97 self.withdrawals = withdrawals;
98 self
99 }
100
101 pub fn with_uncles(mut self, uncles: Vec<B256>) -> Self {
103 self.uncles = uncles;
104 self
105 }
106
107 pub fn try_into_transactions(self) -> Result<Vec<T>, ValueError<BlockTransactions<T>>> {
111 self.transactions.try_into_transactions()
112 }
113
114 pub fn into_transactions_vec(self) -> Vec<T> {
118 self.transactions.into_transactions_vec()
119 }
120
121 pub fn try_into_block_body(self) -> Result<BlockBody<T, H>, ValueError<Self>> {
125 if !self.uncles.is_empty() {
126 return Err(ValueError::new_static(self, "uncles not empty"));
127 }
128 if !self.transactions.is_full() {
129 return Err(ValueError::new_static(self, "transactions not full"));
130 }
131
132 Ok(self.into_block_body_unchecked())
133 }
134
135 pub fn into_block_body_unchecked(self) -> BlockBody<T, H> {
141 BlockBody {
142 transactions: self.transactions.into_transactions_vec(),
143 ommers: Default::default(),
144 withdrawals: self.withdrawals,
145 }
146 }
147
148 pub fn map_header<U>(self, f: impl FnOnce(H) -> U) -> Block<T, U> {
150 Block {
151 header: f(self.header),
152 uncles: self.uncles,
153 transactions: self.transactions,
154 withdrawals: self.withdrawals,
155 }
156 }
157
158 pub fn into_header(self) -> H {
162 self.header
163 }
164
165 pub fn try_map_header<U, E>(self, f: impl FnOnce(H) -> Result<U, E>) -> Result<Block<T, U>, E> {
167 Ok(Block {
168 header: f(self.header)?,
169 uncles: self.uncles,
170 transactions: self.transactions,
171 withdrawals: self.withdrawals,
172 })
173 }
174
175 pub fn convert_transactions<U>(self) -> Block<U, H>
177 where
178 U: From<T>,
179 {
180 self.map_transactions(U::from)
181 }
182
183 pub fn try_convert_transactions<U>(self) -> Result<Block<U, H>, U::Error>
187 where
188 U: TryFrom<T>,
189 {
190 self.try_map_transactions(U::try_from)
191 }
192
193 pub fn map_transactions<U>(self, f: impl FnMut(T) -> U) -> Block<U, H> {
197 Block {
198 header: self.header,
199 uncles: self.uncles,
200 transactions: self.transactions.map(f),
201 withdrawals: self.withdrawals,
202 }
203 }
204
205 pub fn try_map_transactions<U, E>(
210 self,
211 f: impl FnMut(T) -> Result<U, E>,
212 ) -> Result<Block<U, H>, E> {
213 Ok(Block {
214 header: self.header,
215 uncles: self.uncles,
216 transactions: self.transactions.try_map(f)?,
217 withdrawals: self.withdrawals,
218 })
219 }
220
221 pub fn calculate_transactions_root(&self) -> Option<B256>
225 where
226 T: Encodable2718,
227 {
228 self.transactions.calculate_transactions_root()
229 }
230}
231
232impl<T: TransactionResponse, H> Block<T, H> {
233 pub fn into_full_block(self, txs: Vec<T>) -> Self {
235 Self { transactions: txs.into(), ..self }
236 }
237}
238
239impl<T, H: Sealable + Encodable> Block<T, Header<H>> {
240 pub fn uncle_from_header(header: H) -> Self {
245 let block = alloy_consensus::Block::<TxEnvelope, H>::uncle(header);
246 let size = U256::from(block.length());
247 Self {
248 uncles: vec![],
249 header: Header::from_consensus(block.header.seal_slow(), None, Some(size)),
250 transactions: BlockTransactions::Uncle,
251 withdrawals: None,
252 }
253 }
254}
255
256impl<T> Block<T> {
257 pub fn into_sealed_header(self) -> Sealed<alloy_consensus::Header> {
259 self.header.into_sealed()
260 }
261
262 pub fn into_consensus_header(self) -> alloy_consensus::Header {
265 self.header.into_consensus()
266 }
267
268 pub fn from_consensus(block: alloy_consensus::Block<T>, total_difficulty: Option<U256>) -> Self
270 where
271 T: Encodable,
272 {
273 let size = U256::from(block.length());
274 let alloy_consensus::Block {
275 header,
276 body: alloy_consensus::BlockBody { transactions, ommers, withdrawals },
277 } = block;
278
279 Self {
280 header: Header::from_consensus(header.seal_slow(), total_difficulty, Some(size)),
281 uncles: ommers.into_iter().map(|h| h.hash_slow()).collect(),
282 transactions: BlockTransactions::Full(transactions),
283 withdrawals,
284 }
285 }
286
287 pub fn into_consensus(self) -> alloy_consensus::Block<T> {
294 let Self { header, transactions, withdrawals, .. } = self;
295 alloy_consensus::BlockBody {
296 transactions: transactions.into_transactions_vec(),
297 ommers: vec![],
298 withdrawals,
299 }
300 .into_block(header.into_consensus())
301 }
302}
303
304#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
308#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
309#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
310#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
311pub struct Header<H = alloy_consensus::Header> {
312 pub hash: BlockHash,
314 #[cfg_attr(feature = "serde", serde(flatten))]
316 pub inner: H,
317 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
321 pub total_difficulty: Option<U256>,
322 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
324 pub size: Option<U256>,
325}
326
327impl<H> Header<H> {
328 pub fn new(inner: H) -> Self
332 where
333 H: Sealable,
334 {
335 Self::from_sealed(Sealed::new(inner))
336 }
337
338 pub fn from_sealed(header: Sealed<H>) -> Self {
342 let (inner, hash) = header.into_parts();
343 Self { hash, inner, total_difficulty: None, size: None }
344 }
345
346 pub fn into_sealed(self) -> Sealed<H> {
348 Sealed::new_unchecked(self.inner, self.hash)
349 }
350
351 pub fn into_consensus(self) -> H {
353 self.inner
354 }
355
356 pub fn from_consensus(
358 header: Sealed<H>,
359 total_difficulty: Option<U256>,
360 size: Option<U256>,
361 ) -> Self {
362 let (inner, hash) = header.into_parts();
363 Self { hash, inner, total_difficulty, size }
364 }
365
366 pub const fn with_total_difficulty(mut self, total_difficulty: Option<U256>) -> Self {
368 self.total_difficulty = total_difficulty;
369 self
370 }
371
372 pub const fn with_size(mut self, size: Option<U256>) -> Self {
374 self.size = size;
375 self
376 }
377
378 #[expect(clippy::use_self)]
380 pub fn map<H1>(self, f: impl FnOnce(H) -> H1) -> Header<H1> {
381 let Header { hash, inner, total_difficulty, size } = self;
382
383 Header { hash, inner: f(inner), total_difficulty, size }
384 }
385
386 #[expect(clippy::use_self)]
388 pub fn try_map<H1, E>(self, f: impl FnOnce(H) -> Result<H1, E>) -> Result<Header<H1>, E> {
389 let Header { hash, inner, total_difficulty, size } = self;
390
391 Ok(Header { hash, inner: f(inner)?, total_difficulty, size })
392 }
393}
394
395impl<H> Deref for Header<H> {
396 type Target = H;
397
398 fn deref(&self) -> &Self::Target {
399 &self.inner
400 }
401}
402
403impl<H> DerefMut for Header<H> {
404 fn deref_mut(&mut self) -> &mut Self::Target {
405 &mut self.inner
406 }
407}
408
409impl<H> AsRef<H> for Header<H> {
410 fn as_ref(&self) -> &H {
411 &self.inner
412 }
413}
414
415impl<H: BlockHeader> Header<H> {
416 pub fn blob_fee(&self) -> Option<u128> {
420 self.inner.excess_blob_gas().map(calc_blob_gasprice)
421 }
422
423 pub fn next_block_blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
429 self.inner.next_block_blob_fee(blob_params)
430 }
431
432 pub fn next_block_excess_blob_gas(&self, blob_params: BlobParams) -> Option<u64> {
437 self.inner.next_block_excess_blob_gas(blob_params)
438 }
439}
440
441impl<H: BlockHeader> BlockHeader for Header<H> {
442 fn parent_hash(&self) -> B256 {
443 self.inner.parent_hash()
444 }
445
446 fn ommers_hash(&self) -> B256 {
447 self.inner.ommers_hash()
448 }
449
450 fn beneficiary(&self) -> Address {
451 self.inner.beneficiary()
452 }
453
454 fn state_root(&self) -> B256 {
455 self.inner.state_root()
456 }
457
458 fn transactions_root(&self) -> B256 {
459 self.inner.transactions_root()
460 }
461
462 fn receipts_root(&self) -> B256 {
463 self.inner.receipts_root()
464 }
465
466 fn withdrawals_root(&self) -> Option<B256> {
467 self.inner.withdrawals_root()
468 }
469
470 fn logs_bloom(&self) -> Bloom {
471 self.inner.logs_bloom()
472 }
473
474 fn difficulty(&self) -> U256 {
475 self.inner.difficulty()
476 }
477
478 fn number(&self) -> u64 {
479 self.inner.number()
480 }
481
482 fn gas_limit(&self) -> u64 {
483 self.inner.gas_limit()
484 }
485
486 fn gas_used(&self) -> u64 {
487 self.inner.gas_used()
488 }
489
490 fn timestamp(&self) -> u64 {
491 self.inner.timestamp()
492 }
493
494 fn mix_hash(&self) -> Option<B256> {
495 self.inner.mix_hash()
496 }
497
498 fn nonce(&self) -> Option<B64> {
499 self.inner.nonce()
500 }
501
502 fn base_fee_per_gas(&self) -> Option<u64> {
503 self.inner.base_fee_per_gas()
504 }
505
506 fn blob_gas_used(&self) -> Option<u64> {
507 self.inner.blob_gas_used()
508 }
509
510 fn excess_blob_gas(&self) -> Option<u64> {
511 self.inner.excess_blob_gas()
512 }
513
514 fn parent_beacon_block_root(&self) -> Option<B256> {
515 self.inner.parent_beacon_block_root()
516 }
517
518 fn requests_hash(&self) -> Option<B256> {
519 self.inner.requests_hash()
520 }
521
522 fn extra_data(&self) -> &Bytes {
523 self.inner.extra_data()
524 }
525}
526
527impl<H: BlockHeader> HeaderResponse for Header<H> {
528 fn hash(&self) -> BlockHash {
529 self.hash
530 }
531}
532
533impl From<Header> for alloy_consensus::Header {
534 fn from(header: Header) -> Self {
535 header.into_consensus()
536 }
537}
538
539impl<H> From<Header<H>> for Sealed<H> {
540 fn from(value: Header<H>) -> Self {
541 value.into_sealed()
542 }
543}
544
545#[derive(Clone, Copy, Debug, thiserror::Error)]
547pub enum BlockError {
548 #[error("transaction failed sender recovery")]
550 InvalidSignature,
551 #[error("failed to decode raw block {0}")]
553 RlpDecodeRawBlock(alloy_rlp::Error),
554}
555
556#[cfg(feature = "serde")]
557impl From<Block> for alloy_serde::WithOtherFields<Block> {
558 fn from(inner: Block) -> Self {
559 Self { inner, other: Default::default() }
560 }
561}
562
563#[cfg(feature = "serde")]
564impl From<Header> for alloy_serde::WithOtherFields<Header> {
565 fn from(inner: Header) -> Self {
566 Self { inner, other: Default::default() }
567 }
568}
569
570#[derive(Clone, Debug, Default, PartialEq, Eq)]
572#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
573#[cfg_attr(feature = "serde", serde(default, rename_all = "camelCase", deny_unknown_fields))]
574pub struct BlockOverrides {
575 #[cfg_attr(
581 feature = "serde",
582 serde(default, skip_serializing_if = "Option::is_none", alias = "blockNumber")
583 )]
584 pub number: Option<U256>,
585 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
587 pub difficulty: Option<U256>,
588 #[cfg_attr(
591 feature = "serde",
592 serde(
593 default,
594 skip_serializing_if = "Option::is_none",
595 alias = "timestamp",
596 with = "alloy_serde::quantity::opt"
597 )
598 )]
599 pub time: Option<u64>,
600 #[cfg_attr(
602 feature = "serde",
603 serde(
604 default,
605 skip_serializing_if = "Option::is_none",
606 with = "alloy_serde::quantity::opt"
607 )
608 )]
609 pub gas_limit: Option<u64>,
610 #[cfg_attr(
612 feature = "serde",
613 serde(default, skip_serializing_if = "Option::is_none", alias = "feeRecipient")
614 )]
615 pub coinbase: Option<Address>,
616 #[cfg_attr(
618 feature = "serde",
619 serde(default, skip_serializing_if = "Option::is_none", alias = "prevRandao")
620 )]
621 pub random: Option<B256>,
622 #[cfg_attr(
624 feature = "serde",
625 serde(default, skip_serializing_if = "Option::is_none", alias = "baseFeePerGas")
626 )]
627 pub base_fee: Option<U256>,
628 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
631 pub block_hash: Option<BTreeMap<u64, B256>>,
632}
633
634impl<T: TransactionResponse, H> BlockResponse for Block<T, H> {
635 type Header = H;
636 type Transaction = T;
637
638 fn header(&self) -> &Self::Header {
639 &self.header
640 }
641
642 fn transactions(&self) -> &BlockTransactions<T> {
643 &self.transactions
644 }
645
646 fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
647 &mut self.transactions
648 }
649}
650
651#[derive(Clone, Debug, Default, PartialEq, Eq)]
653#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
654#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
655pub struct BadBlock {
656 block: Block,
658 hash: BlockHash,
660 rlp: Bytes,
662}
663
664#[cfg(test)]
665mod tests {
666 use super::*;
667 use alloy_primitives::{hex, keccak256, Bloom, B64};
668 use arbitrary::Arbitrary;
669 use rand::Rng;
670 use similar_asserts::assert_eq;
671
672 #[test]
673 fn arbitrary_header() {
674 let mut bytes = [0u8; 1024];
675 rand::thread_rng().fill(bytes.as_mut_slice());
676 let _: Header = Header::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
677 }
678
679 #[test]
680 #[cfg(all(feature = "jsonrpsee-types", feature = "serde"))]
681 fn serde_json_header() {
682 use jsonrpsee_types::SubscriptionResponse;
683 let resp = r#"{"jsonrpc":"2.0","method":"eth_subscribe","params":{"subscription":"0x7eef37ff35d471f8825b1c8f67a5d3c0","result":{"hash":"0x7a7ada12e140961a32395059597764416499f4178daf1917193fad7bd2cc6386","parentHash":"0xdedbd831f496e705e7f2ec3c8dcb79051040a360bf1455dbd7eb8ea6ad03b751","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","miner":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","number":"0x8","gasUsed":"0x0","gasLimit":"0x1c9c380","extraData":"0x","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","timestamp":"0x642aa48f","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000"}}}"#;
684 let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap();
685
686 let resp = r#"{"jsonrpc":"2.0","method":"eth_subscription","params":{"subscription":"0x1a14b6bdcf4542fabf71c4abee244e47","result":{"author":"0x000000568b9b5a365eaa767d42e74ed88915c204","difficulty":"0x1","extraData":"0x4e65746865726d696e6420312e392e32322d302d6463373666616366612d32308639ad8ff3d850a261f3b26bc2a55e0f3a718de0dd040a19a4ce37e7b473f2d7481448a1e1fd8fb69260825377c0478393e6055f471a5cf839467ce919a6ad2700","gasLimit":"0x7a1200","gasUsed":"0x0","hash":"0xa4856602944fdfd18c528ef93cc52a681b38d766a7e39c27a47488c8461adcb0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","number":"0x434822","parentHash":"0x1a9bdc31fc785f8a95efeeb7ae58f40f6366b8e805f47447a52335c95f4ceb49","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x261","stateRoot":"0xf38c4bf2958e541ec6df148e54ce073dc6b610f8613147ede568cb7b5c2d81ee","totalDifficulty":"0x633ebd","timestamp":"0x604726b0","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[]}}}"#;
687 let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap();
688 }
689
690 #[test]
691 #[cfg(feature = "serde")]
692 fn serde_block() {
693 use alloy_primitives::B64;
694
695 let block = Block {
696 header: Header {
697 hash: B256::with_last_byte(1),
698 inner: alloy_consensus::Header {
699 parent_hash: B256::with_last_byte(2),
700 ommers_hash: B256::with_last_byte(3),
701 beneficiary: Address::with_last_byte(4),
702 state_root: B256::with_last_byte(5),
703 transactions_root: B256::with_last_byte(6),
704 receipts_root: B256::with_last_byte(7),
705 withdrawals_root: Some(B256::with_last_byte(8)),
706 number: 9,
707 gas_used: 10,
708 gas_limit: 11,
709 extra_data: vec![1, 2, 3].into(),
710 logs_bloom: Default::default(),
711 timestamp: 12,
712 difficulty: U256::from(13),
713 mix_hash: B256::with_last_byte(14),
714 nonce: B64::with_last_byte(15),
715 base_fee_per_gas: Some(20),
716 blob_gas_used: None,
717 excess_blob_gas: None,
718 parent_beacon_block_root: None,
719 requests_hash: None,
720 },
721 total_difficulty: Some(U256::from(100000)),
722 size: None,
723 },
724 uncles: vec![B256::with_last_byte(17)],
725 transactions: vec![B256::with_last_byte(18)].into(),
726 withdrawals: Some(Default::default()),
727 };
728 let serialized = serde_json::to_string(&block).unwrap();
729 similar_asserts::assert_eq!(
730 serialized,
731 r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","baseFeePerGas":"0x14","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","totalDifficulty":"0x186a0","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"withdrawals":[]}"#
732 );
733 let deserialized: Block = serde_json::from_str(&serialized).unwrap();
734 similar_asserts::assert_eq!(block, deserialized);
735 }
736
737 #[test]
738 #[cfg(feature = "serde")]
739 fn serde_uncle_block() {
740 use alloy_primitives::B64;
741
742 let block = Block {
743 header: Header {
744 hash: B256::with_last_byte(1),
745 inner: alloy_consensus::Header {
746 parent_hash: B256::with_last_byte(2),
747 ommers_hash: B256::with_last_byte(3),
748 beneficiary: Address::with_last_byte(4),
749 state_root: B256::with_last_byte(5),
750 transactions_root: B256::with_last_byte(6),
751 receipts_root: B256::with_last_byte(7),
752 withdrawals_root: Some(B256::with_last_byte(8)),
753 number: 9,
754 gas_used: 10,
755 gas_limit: 11,
756 extra_data: vec![1, 2, 3].into(),
757 logs_bloom: Default::default(),
758 timestamp: 12,
759 difficulty: U256::from(13),
760 mix_hash: B256::with_last_byte(14),
761 nonce: B64::with_last_byte(15),
762 base_fee_per_gas: Some(20),
763 blob_gas_used: None,
764 excess_blob_gas: None,
765 parent_beacon_block_root: None,
766 requests_hash: None,
767 },
768 size: None,
769 total_difficulty: Some(U256::from(100000)),
770 },
771 uncles: vec![],
772 transactions: BlockTransactions::Uncle,
773 withdrawals: None,
774 };
775 let serialized = serde_json::to_string(&block).unwrap();
776 assert_eq!(
777 serialized,
778 r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","baseFeePerGas":"0x14","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","totalDifficulty":"0x186a0","uncles":[]}"#
779 );
780 let deserialized: Block = serde_json::from_str(&serialized).unwrap();
781 assert_eq!(block, deserialized);
782 }
783
784 #[test]
785 #[cfg(feature = "serde")]
786 fn serde_block_with_withdrawals_set_as_none() {
787 let block = Block {
788 header: Header {
789 hash: B256::with_last_byte(1),
790 inner: alloy_consensus::Header {
791 parent_hash: B256::with_last_byte(2),
792 ommers_hash: B256::with_last_byte(3),
793 beneficiary: Address::with_last_byte(4),
794 state_root: B256::with_last_byte(5),
795 transactions_root: B256::with_last_byte(6),
796 receipts_root: B256::with_last_byte(7),
797 withdrawals_root: None,
798 number: 9,
799 gas_used: 10,
800 gas_limit: 11,
801 extra_data: vec![1, 2, 3].into(),
802 logs_bloom: Bloom::default(),
803 timestamp: 12,
804 difficulty: U256::from(13),
805 mix_hash: B256::with_last_byte(14),
806 nonce: B64::with_last_byte(15),
807 base_fee_per_gas: Some(20),
808 blob_gas_used: None,
809 excess_blob_gas: None,
810 parent_beacon_block_root: None,
811 requests_hash: None,
812 },
813 total_difficulty: Some(U256::from(100000)),
814 size: None,
815 },
816 uncles: vec![B256::with_last_byte(17)],
817 transactions: vec![B256::with_last_byte(18)].into(),
818 withdrawals: None,
819 };
820 let serialized = serde_json::to_string(&block).unwrap();
821 assert_eq!(
822 serialized,
823 r#"{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","baseFeePerGas":"0x14","totalDifficulty":"0x186a0","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"]}"#
824 );
825 let deserialized: Block = serde_json::from_str(&serialized).unwrap();
826 assert_eq!(block, deserialized);
827 }
828
829 #[test]
830 #[cfg(feature = "serde")]
831 fn block_overrides() {
832 let s = r#"{"blockNumber": "0xe39dd0"}"#;
833 let _overrides = serde_json::from_str::<BlockOverrides>(s).unwrap();
834 }
835
836 #[test]
837 #[cfg(feature = "serde")]
838 fn serde_rich_block() {
839 let s = r#"{
840 "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
841 "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
842 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
843 "miner": "0x829bd824b016326a401d083b33d092293333a830",
844 "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
845 "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
846 "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
847 "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
848 "difficulty": "0xc40faff9c737d",
849 "number": "0xa9a230",
850 "gasLimit": "0xbe5a66",
851 "gasUsed": "0xbe0fcc",
852 "timestamp": "0x5f93b749",
853 "totalDifficulty": "0x3dc957fd8167fb2684a",
854 "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
855 "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
856 "nonce": "0x4722f2acd35abe0f",
857 "uncles": [],
858 "transactions": [
859 "0xf435a26acc2a9ef73ac0b73632e32e29bd0e28d5c4f46a7e18ed545c93315916"
860 ],
861 "size": "0xaeb6"
862}"#;
863
864 let block = serde_json::from_str::<alloy_serde::WithOtherFields<Block>>(s).unwrap();
865 let serialized = serde_json::to_string(&block).unwrap();
866 let block2 =
867 serde_json::from_str::<alloy_serde::WithOtherFields<Block>>(&serialized).unwrap();
868 assert_eq!(block, block2);
869 }
870
871 #[test]
872 #[cfg(feature = "serde")]
873 fn serde_missing_uncles_block() {
874 let s = r#"{
875 "baseFeePerGas":"0x886b221ad",
876 "blobGasUsed":"0x0",
877 "difficulty":"0x0",
878 "excessBlobGas":"0x0",
879 "extraData":"0x6265617665726275696c642e6f7267",
880 "gasLimit":"0x1c9c380",
881 "gasUsed":"0xb0033c",
882 "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac",
883 "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427",
884 "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
885 "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5",
886 "nonce":"0x0000000000000000",
887 "number":"0x128c6df",
888 "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc",
889 "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717",
890 "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90",
891 "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
892 "size":"0xdcc3",
893 "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404",
894 "timestamp":"0x65f5f4c3",
895 "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780",
896 "withdrawals":[
897 {
898 "index":"0x24d80e6",
899 "validatorIndex":"0x8b2b6",
900 "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff",
901 "amount":"0x1161f10"
902 }
903 ],
904 "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7"
905 }"#;
906
907 let block = serde_json::from_str::<Block>(s).unwrap();
908 let serialized = serde_json::to_string(&block).unwrap();
909 let block2 = serde_json::from_str::<Block>(&serialized).unwrap();
910 assert_eq!(block, block2);
911 }
912
913 #[test]
914 #[cfg(feature = "serde")]
915 fn serde_block_containing_uncles() {
916 let s = r#"{
917 "baseFeePerGas":"0x886b221ad",
918 "blobGasUsed":"0x0",
919 "difficulty":"0x0",
920 "excessBlobGas":"0x0",
921 "extraData":"0x6265617665726275696c642e6f7267",
922 "gasLimit":"0x1c9c380",
923 "gasUsed":"0xb0033c",
924 "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac",
925 "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427",
926 "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
927 "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5",
928 "nonce":"0x0000000000000000",
929 "number":"0x128c6df",
930 "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc",
931 "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717",
932 "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90",
933 "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
934 "size":"0xdcc3",
935 "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404",
936 "timestamp":"0x65f5f4c3",
937 "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780",
938 "uncles": ["0x123a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780", "0x489a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780"],
939 "withdrawals":[
940 {
941 "index":"0x24d80e6",
942 "validatorIndex":"0x8b2b6",
943 "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff",
944 "amount":"0x1161f10"
945 }
946 ],
947 "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7"
948 }"#;
949
950 let block = serde_json::from_str::<Block>(s).unwrap();
951 assert_eq!(block.uncles.len(), 2);
952 let serialized = serde_json::to_string(&block).unwrap();
953 let block2 = serde_json::from_str::<Block>(&serialized).unwrap();
954 assert_eq!(block, block2);
955 }
956
957 #[test]
958 #[cfg(feature = "serde")]
959 fn serde_empty_block() {
960 let s = r#"{
961 "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
962 "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
963 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
964 "miner": "0x829bd824b016326a401d083b33d092293333a830",
965 "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
966 "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
967 "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
968 "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
969 "difficulty": "0xc40faff9c737d",
970 "number": "0xa9a230",
971 "gasLimit": "0xbe5a66",
972 "gasUsed": "0xbe0fcc",
973 "timestamp": "0x5f93b749",
974 "totalDifficulty": "0x3dc957fd8167fb2684a",
975 "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
976 "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
977 "nonce": "0x4722f2acd35abe0f",
978 "uncles": [],
979 "transactions": [],
980 "size": "0xaeb6"
981}"#;
982
983 let block = serde_json::from_str::<Block>(s).unwrap();
984 assert!(block.transactions.is_empty());
985 assert!(block.transactions.as_transactions().is_some());
986 }
987
988 #[test]
989 #[cfg(feature = "serde")]
990 fn recompute_block_hash() {
991 let s = r#"{
992 "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
993 "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
994 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
995 "miner": "0x829bd824b016326a401d083b33d092293333a830",
996 "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
997 "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
998 "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
999 "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
1000 "difficulty": "0xc40faff9c737d",
1001 "number": "0xa9a230",
1002 "gasLimit": "0xbe5a66",
1003 "gasUsed": "0xbe0fcc",
1004 "timestamp": "0x5f93b749",
1005 "totalDifficulty": "0x3dc957fd8167fb2684a",
1006 "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
1007 "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
1008 "nonce": "0x4722f2acd35abe0f",
1009 "uncles": [],
1010 "transactions": [],
1011 "size": "0xaeb6"
1012}"#;
1013 let block = serde_json::from_str::<Block>(s).unwrap();
1014 let header = block.clone().header.inner;
1015 let recomputed_hash = keccak256(alloy_rlp::encode(&header));
1016 assert_eq!(recomputed_hash, block.header.hash);
1017
1018 let s2 = r#"{
1019 "baseFeePerGas":"0x886b221ad",
1020 "blobGasUsed":"0x0",
1021 "difficulty":"0x0",
1022 "excessBlobGas":"0x0",
1023 "extraData":"0x6265617665726275696c642e6f7267",
1024 "gasLimit":"0x1c9c380",
1025 "gasUsed":"0xb0033c",
1026 "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac",
1027 "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427",
1028 "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
1029 "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5",
1030 "nonce":"0x0000000000000000",
1031 "number":"0x128c6df",
1032 "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc",
1033 "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717",
1034 "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90",
1035 "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1036 "size":"0xdcc3",
1037 "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404",
1038 "timestamp":"0x65f5f4c3",
1039 "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780",
1040 "withdrawals":[
1041 {
1042 "index":"0x24d80e6",
1043 "validatorIndex":"0x8b2b6",
1044 "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff",
1045 "amount":"0x1161f10"
1046 }
1047 ],
1048 "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7"
1049 }"#;
1050 let block2 = serde_json::from_str::<Block>(s2).unwrap();
1051 let header = block2.clone().header.inner;
1052 let recomputed_hash = keccak256(alloy_rlp::encode(&header));
1053 assert_eq!(recomputed_hash, block2.header.hash);
1054 }
1055
1056 #[test]
1057 fn header_roundtrip_conversion() {
1058 let rpc_header = Header {
1060 hash: B256::with_last_byte(1),
1061 inner: alloy_consensus::Header {
1062 parent_hash: B256::with_last_byte(2),
1063 ommers_hash: B256::with_last_byte(3),
1064 beneficiary: Address::with_last_byte(4),
1065 state_root: B256::with_last_byte(5),
1066 transactions_root: B256::with_last_byte(6),
1067 receipts_root: B256::with_last_byte(7),
1068 withdrawals_root: None,
1069 number: 9,
1070 gas_used: 10,
1071 gas_limit: 11,
1072 extra_data: vec![1, 2, 3].into(),
1073 logs_bloom: Bloom::default(),
1074 timestamp: 12,
1075 difficulty: U256::from(13),
1076 mix_hash: B256::with_last_byte(14),
1077 nonce: B64::with_last_byte(15),
1078 base_fee_per_gas: Some(20),
1079 blob_gas_used: None,
1080 excess_blob_gas: None,
1081 parent_beacon_block_root: None,
1082 requests_hash: None,
1083 },
1084 size: None,
1085 total_difficulty: None,
1086 };
1087
1088 let primitive_header = rpc_header.clone().inner;
1090
1091 let sealed_header: Sealed<alloy_consensus::Header> =
1093 primitive_header.seal(B256::with_last_byte(1));
1094
1095 let roundtrip_rpc_header = Header::from_consensus(sealed_header, None, None);
1097
1098 assert_eq!(rpc_header, roundtrip_rpc_header);
1100 }
1101
1102 #[test]
1103 fn test_consensus_header_to_rpc_block() {
1104 let header = Header {
1106 hash: B256::with_last_byte(1),
1107 inner: alloy_consensus::Header {
1108 parent_hash: B256::with_last_byte(2),
1109 ommers_hash: B256::with_last_byte(3),
1110 beneficiary: Address::with_last_byte(4),
1111 state_root: B256::with_last_byte(5),
1112 transactions_root: B256::with_last_byte(6),
1113 receipts_root: B256::with_last_byte(7),
1114 withdrawals_root: None,
1115 number: 9,
1116 gas_used: 10,
1117 gas_limit: 11,
1118 extra_data: vec![1, 2, 3].into(),
1119 logs_bloom: Bloom::default(),
1120 timestamp: 12,
1121 difficulty: U256::from(13),
1122 mix_hash: B256::with_last_byte(14),
1123 nonce: B64::with_last_byte(15),
1124 base_fee_per_gas: Some(20),
1125 blob_gas_used: None,
1126 excess_blob_gas: None,
1127 parent_beacon_block_root: None,
1128 requests_hash: None,
1129 },
1130 total_difficulty: None,
1131 size: Some(U256::from(505)),
1132 };
1133
1134 let primitive_header = header.clone().inner;
1136
1137 let block: Block<Transaction> = Block::uncle_from_header(primitive_header);
1139
1140 assert_eq!(
1142 block,
1143 Block {
1144 header: Header {
1145 hash: B256::from(hex!(
1146 "379bd1414cf69a9b86fb4e0e6b05a2e4b14cb3d5af057e13ccdc2192cb9780b2"
1147 )),
1148 ..header
1149 },
1150 uncles: vec![],
1151 transactions: BlockTransactions::Uncle,
1152 withdrawals: None,
1153 }
1154 );
1155 }
1156
1157 #[test]
1158 #[cfg(feature = "serde")]
1159 fn serde_bad_block() {
1160 use alloy_primitives::B64;
1161
1162 let block = Block {
1163 header: Header {
1164 hash: B256::with_last_byte(1),
1165 inner: alloy_consensus::Header {
1166 parent_hash: B256::with_last_byte(2),
1167 ommers_hash: B256::with_last_byte(3),
1168 beneficiary: Address::with_last_byte(4),
1169 state_root: B256::with_last_byte(5),
1170 transactions_root: B256::with_last_byte(6),
1171 receipts_root: B256::with_last_byte(7),
1172 withdrawals_root: Some(B256::with_last_byte(8)),
1173 number: 9,
1174 gas_used: 10,
1175 gas_limit: 11,
1176 extra_data: vec![1, 2, 3].into(),
1177 logs_bloom: Default::default(),
1178 timestamp: 12,
1179 difficulty: U256::from(13),
1180 mix_hash: B256::with_last_byte(14),
1181 nonce: B64::with_last_byte(15),
1182 base_fee_per_gas: Some(20),
1183 blob_gas_used: None,
1184 excess_blob_gas: None,
1185 parent_beacon_block_root: None,
1186 requests_hash: None,
1187 },
1188 total_difficulty: Some(U256::from(100000)),
1189 size: Some(U256::from(19)),
1190 },
1191 uncles: vec![B256::with_last_byte(17)],
1192 transactions: vec![B256::with_last_byte(18)].into(),
1193 withdrawals: Some(Default::default()),
1194 };
1195 let hash = block.header.hash;
1196 let rlp = Bytes::from("header");
1197
1198 let bad_block = BadBlock { block, hash, rlp };
1199
1200 let serialized = serde_json::to_string(&bad_block).unwrap();
1201 assert_eq!(
1202 serialized,
1203 r#"{"block":{"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","parentHash":"0x0000000000000000000000000000000000000000000000000000000000000002","sha3Uncles":"0x0000000000000000000000000000000000000000000000000000000000000003","miner":"0x0000000000000000000000000000000000000004","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000005","transactionsRoot":"0x0000000000000000000000000000000000000000000000000000000000000006","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000007","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","difficulty":"0xd","number":"0x9","gasLimit":"0xb","gasUsed":"0xa","timestamp":"0xc","extraData":"0x010203","mixHash":"0x000000000000000000000000000000000000000000000000000000000000000e","nonce":"0x000000000000000f","baseFeePerGas":"0x14","withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000008","totalDifficulty":"0x186a0","size":"0x13","uncles":["0x0000000000000000000000000000000000000000000000000000000000000011"],"transactions":["0x0000000000000000000000000000000000000000000000000000000000000012"],"withdrawals":[]},"hash":"0x0000000000000000000000000000000000000000000000000000000000000001","rlp":"0x686561646572"}"#
1204 );
1205
1206 let deserialized: BadBlock = serde_json::from_str(&serialized).unwrap();
1207 similar_asserts::assert_eq!(bad_block, deserialized);
1208 }
1209
1210 #[test]
1212 #[cfg(feature = "serde")]
1213 fn deserde_tenderly_block() {
1214 let s = include_str!("../testdata/tenderly.sepolia.json");
1215 let _block: Block = serde_json::from_str(s).unwrap();
1216 }
1217}