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 number(&self) -> u64
83 where
84 H: BlockHeader,
85 {
86 self.header.number()
87 }
88
89 pub fn apply<F>(self, f: F) -> Self
91 where
92 F: FnOnce(Self) -> Self,
93 {
94 f(self)
95 }
96
97 pub fn with_transactions(mut self, transactions: BlockTransactions<T>) -> Self {
99 self.transactions = transactions;
100 self
101 }
102
103 pub fn with_withdrawals(mut self, withdrawals: Option<Withdrawals>) -> Self {
105 self.withdrawals = withdrawals;
106 self
107 }
108
109 pub fn with_uncles(mut self, uncles: Vec<B256>) -> Self {
111 self.uncles = uncles;
112 self
113 }
114
115 pub fn try_into_transactions(self) -> Result<Vec<T>, ValueError<BlockTransactions<T>>> {
119 self.transactions.try_into_transactions()
120 }
121
122 pub fn into_transactions_vec(self) -> Vec<T> {
126 self.transactions.into_transactions_vec()
127 }
128
129 pub fn into_hashes_vec(self) -> Vec<B256>
133 where
134 T: TransactionResponse,
135 {
136 self.transactions.into_hashes_vec()
137 }
138
139 pub fn try_into_block_body(self) -> Result<BlockBody<T, H>, ValueError<Self>> {
143 if !self.uncles.is_empty() {
144 return Err(ValueError::new_static(self, "uncles not empty"));
145 }
146 if !self.transactions.is_full() {
147 return Err(ValueError::new_static(self, "transactions not full"));
148 }
149
150 Ok(self.into_block_body_unchecked())
151 }
152
153 pub fn into_block_body_unchecked(self) -> BlockBody<T, H> {
159 BlockBody {
160 transactions: self.transactions.into_transactions_vec(),
161 ommers: Default::default(),
162 withdrawals: self.withdrawals,
163 }
164 }
165
166 pub fn into_consensus_block(self) -> alloy_consensus::Block<T, H> {
177 alloy_consensus::BlockBody {
178 transactions: self.transactions.into_transactions_vec(),
179 ommers: vec![],
180 withdrawals: self.withdrawals,
181 }
182 .into_block(self.header)
183 }
184
185 pub fn map_header<U>(self, f: impl FnOnce(H) -> U) -> Block<T, U> {
187 Block {
188 header: f(self.header),
189 uncles: self.uncles,
190 transactions: self.transactions,
191 withdrawals: self.withdrawals,
192 }
193 }
194
195 pub fn into_header(self) -> H {
199 self.header
200 }
201
202 pub fn try_convert_header<U>(self) -> Result<Block<T, U>, U::Error>
204 where
205 U: TryFrom<H>,
206 {
207 self.try_map_header(U::try_from)
208 }
209
210 pub fn try_map_header<U, E>(self, f: impl FnOnce(H) -> Result<U, E>) -> Result<Block<T, U>, E> {
212 Ok(Block {
213 header: f(self.header)?,
214 uncles: self.uncles,
215 transactions: self.transactions,
216 withdrawals: self.withdrawals,
217 })
218 }
219
220 pub fn convert_transactions<U>(self) -> Block<U, H>
222 where
223 U: From<T>,
224 {
225 self.map_transactions(U::from)
226 }
227
228 pub fn try_convert_transactions<U>(self) -> Result<Block<U, H>, U::Error>
232 where
233 U: TryFrom<T>,
234 {
235 self.try_map_transactions(U::try_from)
236 }
237
238 pub fn map_transactions<U>(self, f: impl FnMut(T) -> U) -> Block<U, H> {
242 Block {
243 header: self.header,
244 uncles: self.uncles,
245 transactions: self.transactions.map(f),
246 withdrawals: self.withdrawals,
247 }
248 }
249
250 pub fn try_map_transactions<U, E>(
255 self,
256 f: impl FnMut(T) -> Result<U, E>,
257 ) -> Result<Block<U, H>, E> {
258 Ok(Block {
259 header: self.header,
260 uncles: self.uncles,
261 transactions: self.transactions.try_map(f)?,
262 withdrawals: self.withdrawals,
263 })
264 }
265
266 pub fn calculate_transactions_root(&self) -> Option<B256>
270 where
271 T: Encodable2718,
272 {
273 self.transactions.calculate_transactions_root()
274 }
275}
276
277impl<T: TransactionResponse, H> Block<T, H> {
278 pub fn into_full_block(self, txs: Vec<T>) -> Self {
283 Self { transactions: txs.into(), ..self }
284 }
285}
286
287impl<T, H: Sealable + Encodable> Block<T, Header<H>> {
288 pub fn uncle_from_header(header: H) -> Self {
293 let block = alloy_consensus::Block::<TxEnvelope, H>::uncle(header);
294 let size = U256::from(block.length());
295 Self {
296 uncles: vec![],
297 header: Header::from_consensus(block.header.seal_slow(), None, Some(size)),
298 transactions: BlockTransactions::Uncle,
299 withdrawals: None,
300 }
301 }
302}
303
304impl<T> Block<T> {
305 pub const fn hash(&self) -> B256 {
307 self.header.hash
308 }
309
310 pub const fn sealed_header(&self) -> Sealed<&alloy_consensus::Header> {
312 Sealed::new_unchecked(&self.header.inner, self.header.hash)
313 }
314
315 pub fn into_sealed_header(self) -> Sealed<alloy_consensus::Header> {
317 self.header.into_sealed()
318 }
319
320 pub fn into_consensus_header(self) -> alloy_consensus::Header {
323 self.header.into_consensus()
324 }
325
326 pub fn from_consensus(block: alloy_consensus::Block<T>, total_difficulty: Option<U256>) -> Self
328 where
329 T: Encodable,
330 {
331 let size = U256::from(block.length());
332 let alloy_consensus::Block {
333 header,
334 body: alloy_consensus::BlockBody { transactions, ommers, withdrawals },
335 } = block;
336
337 Self {
338 header: Header::from_consensus(header.seal_slow(), total_difficulty, Some(size)),
339 uncles: ommers.into_iter().map(|h| h.hash_slow()).collect(),
340 transactions: BlockTransactions::Full(transactions),
341 withdrawals,
342 }
343 }
344
345 pub fn into_consensus(self) -> alloy_consensus::Block<T> {
353 let Self { header, transactions, withdrawals, .. } = self;
354 alloy_consensus::BlockBody {
355 transactions: transactions.into_transactions_vec(),
356 ommers: vec![],
357 withdrawals,
358 }
359 .into_block(header.into_consensus())
360 }
361
362 pub fn into_consensus_sealed(self) -> Sealed<alloy_consensus::Block<T>> {
365 let hash = self.header.hash;
366 Sealed::new_unchecked(self.into_consensus(), hash)
367 }
368}
369
370impl<T, S> From<Block<T>> for alloy_consensus::Block<S>
371where
372 S: From<T>,
373{
374 fn from(block: Block<T>) -> Self {
375 block.into_consensus().convert_transactions()
376 }
377}
378
379#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
387#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
388#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
389#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
390pub struct Header<H = alloy_consensus::Header> {
391 pub hash: BlockHash,
393 #[cfg_attr(feature = "serde", serde(flatten))]
395 pub inner: H,
396 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
400 pub total_difficulty: Option<U256>,
401 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
403 pub size: Option<U256>,
404}
405
406impl<H> Header<H> {
407 pub fn new(inner: H) -> Self
411 where
412 H: Sealable,
413 {
414 Self::from_sealed(Sealed::new(inner))
415 }
416
417 pub fn from_sealed(header: Sealed<H>) -> Self {
421 let (inner, hash) = header.into_parts();
422 Self { hash, inner, total_difficulty: None, size: None }
423 }
424
425 pub fn into_sealed(self) -> Sealed<H> {
429 Sealed::new_unchecked(self.inner, self.hash)
430 }
431
432 pub fn into_consensus(self) -> H {
434 self.inner
435 }
436
437 pub fn from_consensus(
439 header: Sealed<H>,
440 total_difficulty: Option<U256>,
441 size: Option<U256>,
442 ) -> Self {
443 let (inner, hash) = header.into_parts();
444 Self { hash, inner, total_difficulty, size }
445 }
446
447 pub const fn with_total_difficulty(mut self, total_difficulty: Option<U256>) -> Self {
449 self.total_difficulty = total_difficulty;
450 self
451 }
452
453 pub const fn with_size(mut self, size: Option<U256>) -> Self {
455 self.size = size;
456 self
457 }
458
459 #[expect(clippy::use_self)]
463 pub fn map<H1>(self, f: impl FnOnce(H) -> H1) -> Header<H1> {
464 let Header { hash, inner, total_difficulty, size } = self;
465
466 Header { hash, inner: f(inner), total_difficulty, size }
467 }
468
469 #[expect(clippy::use_self)]
473 pub fn try_map<H1, E>(self, f: impl FnOnce(H) -> Result<H1, E>) -> Result<Header<H1>, E> {
474 let Header { hash, inner, total_difficulty, size } = self;
475
476 Ok(Header { hash, inner: f(inner)?, total_difficulty, size })
477 }
478}
479
480impl<H> Deref for Header<H> {
481 type Target = H;
482
483 fn deref(&self) -> &Self::Target {
484 &self.inner
485 }
486}
487
488impl<H> DerefMut for Header<H> {
489 fn deref_mut(&mut self) -> &mut Self::Target {
490 &mut self.inner
491 }
492}
493
494impl<H> AsRef<H> for Header<H> {
495 fn as_ref(&self) -> &H {
496 &self.inner
497 }
498}
499
500impl<H: BlockHeader> Header<H> {
501 pub fn blob_fee(&self) -> Option<u128> {
505 self.inner.excess_blob_gas().map(calc_blob_gasprice)
506 }
507
508 pub fn next_block_blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
514 self.inner.next_block_blob_fee(blob_params)
515 }
516
517 pub fn next_block_excess_blob_gas(&self, blob_params: BlobParams) -> Option<u64> {
522 self.inner.next_block_excess_blob_gas(blob_params)
523 }
524}
525
526impl<H: BlockHeader> BlockHeader for Header<H> {
527 fn parent_hash(&self) -> B256 {
528 self.inner.parent_hash()
529 }
530
531 fn ommers_hash(&self) -> B256 {
532 self.inner.ommers_hash()
533 }
534
535 fn beneficiary(&self) -> Address {
536 self.inner.beneficiary()
537 }
538
539 fn state_root(&self) -> B256 {
540 self.inner.state_root()
541 }
542
543 fn transactions_root(&self) -> B256 {
544 self.inner.transactions_root()
545 }
546
547 fn receipts_root(&self) -> B256 {
548 self.inner.receipts_root()
549 }
550
551 fn withdrawals_root(&self) -> Option<B256> {
552 self.inner.withdrawals_root()
553 }
554
555 fn logs_bloom(&self) -> Bloom {
556 self.inner.logs_bloom()
557 }
558
559 fn difficulty(&self) -> U256 {
560 self.inner.difficulty()
561 }
562
563 fn number(&self) -> u64 {
564 self.inner.number()
565 }
566
567 fn gas_limit(&self) -> u64 {
568 self.inner.gas_limit()
569 }
570
571 fn gas_used(&self) -> u64 {
572 self.inner.gas_used()
573 }
574
575 fn timestamp(&self) -> u64 {
576 self.inner.timestamp()
577 }
578
579 fn mix_hash(&self) -> Option<B256> {
580 self.inner.mix_hash()
581 }
582
583 fn nonce(&self) -> Option<B64> {
584 self.inner.nonce()
585 }
586
587 fn base_fee_per_gas(&self) -> Option<u64> {
588 self.inner.base_fee_per_gas()
589 }
590
591 fn blob_gas_used(&self) -> Option<u64> {
592 self.inner.blob_gas_used()
593 }
594
595 fn excess_blob_gas(&self) -> Option<u64> {
596 self.inner.excess_blob_gas()
597 }
598
599 fn parent_beacon_block_root(&self) -> Option<B256> {
600 self.inner.parent_beacon_block_root()
601 }
602
603 fn requests_hash(&self) -> Option<B256> {
604 self.inner.requests_hash()
605 }
606
607 fn block_access_list_hash(&self) -> Option<B256> {
608 self.inner.block_access_list_hash()
609 }
610
611 fn slot_number(&self) -> Option<u64> {
612 self.inner.slot_number()
613 }
614
615 fn extra_data(&self) -> &Bytes {
616 self.inner.extra_data()
617 }
618}
619
620impl<H: BlockHeader> HeaderResponse for Header<H> {
621 fn hash(&self) -> BlockHash {
622 self.hash
623 }
624}
625
626impl From<Header> for alloy_consensus::Header {
627 fn from(header: Header) -> Self {
628 header.into_consensus()
629 }
630}
631
632impl<H> From<Header<H>> for Sealed<H> {
633 fn from(value: Header<H>) -> Self {
634 value.into_sealed()
635 }
636}
637
638#[derive(Clone, Copy, Debug, thiserror::Error)]
640pub enum BlockError {
641 #[error("transaction failed sender recovery")]
643 InvalidSignature,
644 #[error("failed to decode raw block {0}")]
646 RlpDecodeRawBlock(alloy_rlp::Error),
647}
648
649#[cfg(feature = "serde")]
650impl<T, H> From<Block<T, H>> for alloy_serde::WithOtherFields<Block<T, H>> {
651 fn from(inner: Block<T, H>) -> Self {
652 Self { inner, other: Default::default() }
653 }
654}
655
656#[cfg(feature = "serde")]
657impl From<Header> for alloy_serde::WithOtherFields<Header> {
658 fn from(inner: Header) -> Self {
659 Self { inner, other: Default::default() }
660 }
661}
662
663#[derive(Clone, Debug, Default, PartialEq, Eq)]
665#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
666#[cfg_attr(feature = "serde", serde(default, rename_all = "camelCase"))]
667pub struct BlockOverrides {
668 #[cfg_attr(
674 feature = "serde",
675 serde(default, skip_serializing_if = "Option::is_none", alias = "blockNumber")
676 )]
677 pub number: Option<U256>,
678 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
680 pub difficulty: Option<U256>,
681 #[cfg_attr(
684 feature = "serde",
685 serde(
686 default,
687 skip_serializing_if = "Option::is_none",
688 alias = "timestamp",
689 with = "alloy_serde::quantity::opt"
690 )
691 )]
692 pub time: Option<u64>,
693 #[cfg_attr(
695 feature = "serde",
696 serde(
697 default,
698 skip_serializing_if = "Option::is_none",
699 with = "alloy_serde::quantity::opt"
700 )
701 )]
702 pub gas_limit: Option<u64>,
703 #[cfg_attr(
705 feature = "serde",
706 serde(default, skip_serializing_if = "Option::is_none", alias = "feeRecipient")
707 )]
708 pub coinbase: Option<Address>,
709 #[cfg_attr(
711 feature = "serde",
712 serde(default, skip_serializing_if = "Option::is_none", alias = "prevRandao")
713 )]
714 pub random: Option<B256>,
715 #[cfg_attr(
717 feature = "serde",
718 serde(default, skip_serializing_if = "Option::is_none", alias = "baseFeePerGas")
719 )]
720 pub base_fee: Option<U256>,
721 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
723 pub blob_base_fee: Option<U256>,
724 #[cfg_attr(
728 feature = "serde",
729 serde(default, skip_serializing_if = "Option::is_none", alias = "parentBeaconBlockRoot")
730 )]
731 pub beacon_root: Option<B256>,
732 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
735 pub block_hash: Option<BTreeMap<u64, B256>>,
736}
737
738impl BlockOverrides {
739 pub const fn is_empty(&self) -> bool {
741 self.number.is_none()
742 && self.difficulty.is_none()
743 && self.time.is_none()
744 && self.gas_limit.is_none()
745 && self.coinbase.is_none()
746 && self.random.is_none()
747 && self.base_fee.is_none()
748 && self.blob_base_fee.is_none()
749 && self.beacon_root.is_none()
750 && self.block_hash.is_none()
751 }
752
753 pub const fn with_number(mut self, number: U256) -> Self {
755 self.number = Some(number);
756 self
757 }
758
759 pub const fn with_difficulty(mut self, difficulty: U256) -> Self {
761 self.difficulty = Some(difficulty);
762 self
763 }
764
765 pub const fn with_time(mut self, time: u64) -> Self {
767 self.time = Some(time);
768 self
769 }
770
771 pub const fn with_gas_limit(mut self, gas_limit: u64) -> Self {
773 self.gas_limit = Some(gas_limit);
774 self
775 }
776
777 pub const fn with_coinbase(mut self, coinbase: Address) -> Self {
779 self.coinbase = Some(coinbase);
780 self
781 }
782
783 pub const fn with_random(mut self, random: B256) -> Self {
785 self.random = Some(random);
786 self
787 }
788
789 pub const fn with_base_fee(mut self, base_fee: U256) -> Self {
791 self.base_fee = Some(base_fee);
792 self
793 }
794
795 pub const fn with_blob_base_fee(mut self, blob_base_fee: U256) -> Self {
797 self.blob_base_fee = Some(blob_base_fee);
798 self
799 }
800
801 pub const fn with_beacon_root(mut self, beacon_root: B256) -> Self {
803 self.beacon_root = Some(beacon_root);
804 self
805 }
806
807 pub fn append_block_hash(mut self, block_number: u64, hash: B256) -> Self {
809 let hash_map = self.block_hash.get_or_insert_with(Default::default);
810 hash_map.insert(block_number, hash);
811 self
812 }
813
814 pub fn with_block_hash_overrides<I>(mut self, hashes: I) -> Self
816 where
817 I: IntoIterator<Item = (u64, B256)>,
818 {
819 let map = self.block_hash.get_or_insert_with(Default::default);
820 map.extend(hashes);
821 self
822 }
823}
824
825impl<T: TransactionResponse, H> BlockResponse for Block<T, H> {
826 type Header = H;
827 type Transaction = T;
828
829 fn header(&self) -> &Self::Header {
830 &self.header
831 }
832
833 fn transactions(&self) -> &BlockTransactions<T> {
834 &self.transactions
835 }
836
837 fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
838 &mut self.transactions
839 }
840}
841
842#[derive(Clone, Debug, Default, PartialEq, Eq)]
844#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
845#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
846pub struct BadBlock<B = Block> {
847 pub block: B,
849 pub hash: BlockHash,
851 pub rlp: Bytes,
853}
854
855#[cfg(test)]
856mod tests {
857 use super::*;
858 use alloy_primitives::{hex, keccak256, Bloom, B64};
859 use arbitrary::Arbitrary;
860 use rand::Rng;
861 use similar_asserts::assert_eq;
862
863 #[test]
864 fn arbitrary_header() {
865 let mut bytes = [0u8; 1024];
866 rand::thread_rng().fill(bytes.as_mut_slice());
867 let _: Header = Header::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
868 }
869
870 #[test]
871 fn header_response_num_hash() {
872 let number = 42;
873 let hash = B256::with_last_byte(1);
874 let header = Header {
875 hash,
876 inner: alloy_consensus::Header { number, ..Default::default() },
877 ..Default::default()
878 };
879
880 assert_eq!(header.num_hash(), BlockNumHash::new(number, hash));
881 }
882
883 #[test]
884 #[cfg(feature = "serde")]
885 fn serde_json_header() {
886 #[derive(serde::Deserialize)]
887 #[allow(dead_code)]
888 struct SubParams<T> {
889 result: T,
890 }
891 #[derive(serde::Deserialize)]
892 #[allow(dead_code)]
893 struct SubNotification<T> {
894 params: SubParams<T>,
895 }
896
897 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"}}}"#;
898 let _header: SubNotification<Header> = serde_json::from_str(resp).unwrap();
899
900 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":[]}}}"#;
901 let _header: SubNotification<Header> = serde_json::from_str(resp).unwrap();
902 }
903
904 #[test]
905 #[cfg(feature = "serde")]
906 fn serde_block() {
907 use alloy_primitives::B64;
908
909 let block = Block {
910 header: Header {
911 hash: B256::with_last_byte(1),
912 inner: alloy_consensus::Header {
913 parent_hash: B256::with_last_byte(2),
914 ommers_hash: B256::with_last_byte(3),
915 beneficiary: Address::with_last_byte(4),
916 state_root: B256::with_last_byte(5),
917 transactions_root: B256::with_last_byte(6),
918 receipts_root: B256::with_last_byte(7),
919 withdrawals_root: Some(B256::with_last_byte(8)),
920 number: 9,
921 gas_used: 10,
922 gas_limit: 11,
923 extra_data: vec![1, 2, 3].into(),
924 logs_bloom: Default::default(),
925 timestamp: 12,
926 difficulty: U256::from(13),
927 mix_hash: B256::with_last_byte(14),
928 nonce: B64::with_last_byte(15),
929 base_fee_per_gas: Some(20),
930 blob_gas_used: None,
931 excess_blob_gas: None,
932 parent_beacon_block_root: None,
933 requests_hash: None,
934 block_access_list_hash: None,
935 slot_number: None,
936 },
937 total_difficulty: Some(U256::from(100000)),
938 size: None,
939 },
940 uncles: vec![B256::with_last_byte(17)],
941 transactions: vec![B256::with_last_byte(18)].into(),
942 withdrawals: Some(Default::default()),
943 };
944 let serialized = serde_json::to_string(&block).unwrap();
945 similar_asserts::assert_eq!(
946 serialized,
947 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":[]}"#
948 );
949 let deserialized: Block = serde_json::from_str(&serialized).unwrap();
950 similar_asserts::assert_eq!(block, deserialized);
951 }
952
953 #[test]
954 #[cfg(feature = "serde")]
955 fn serde_uncle_block() {
956 use alloy_primitives::B64;
957
958 let block = Block {
959 header: Header {
960 hash: B256::with_last_byte(1),
961 inner: alloy_consensus::Header {
962 parent_hash: B256::with_last_byte(2),
963 ommers_hash: B256::with_last_byte(3),
964 beneficiary: Address::with_last_byte(4),
965 state_root: B256::with_last_byte(5),
966 transactions_root: B256::with_last_byte(6),
967 receipts_root: B256::with_last_byte(7),
968 withdrawals_root: Some(B256::with_last_byte(8)),
969 number: 9,
970 gas_used: 10,
971 gas_limit: 11,
972 extra_data: vec![1, 2, 3].into(),
973 logs_bloom: Default::default(),
974 timestamp: 12,
975 difficulty: U256::from(13),
976 mix_hash: B256::with_last_byte(14),
977 nonce: B64::with_last_byte(15),
978 base_fee_per_gas: Some(20),
979 blob_gas_used: None,
980 excess_blob_gas: None,
981 parent_beacon_block_root: None,
982 requests_hash: None,
983 block_access_list_hash: None,
984 slot_number: None,
985 },
986 size: None,
987 total_difficulty: Some(U256::from(100000)),
988 },
989 uncles: vec![],
990 transactions: BlockTransactions::Uncle,
991 withdrawals: None,
992 };
993 let serialized = serde_json::to_string(&block).unwrap();
994 assert_eq!(
995 serialized,
996 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":[]}"#
997 );
998 let deserialized: Block = serde_json::from_str(&serialized).unwrap();
999 assert_eq!(block, deserialized);
1000 }
1001
1002 #[test]
1003 #[cfg(feature = "serde")]
1004 fn serde_block_with_withdrawals_set_as_none() {
1005 let block = Block {
1006 header: Header {
1007 hash: B256::with_last_byte(1),
1008 inner: alloy_consensus::Header {
1009 parent_hash: B256::with_last_byte(2),
1010 ommers_hash: B256::with_last_byte(3),
1011 beneficiary: Address::with_last_byte(4),
1012 state_root: B256::with_last_byte(5),
1013 transactions_root: B256::with_last_byte(6),
1014 receipts_root: B256::with_last_byte(7),
1015 withdrawals_root: None,
1016 number: 9,
1017 gas_used: 10,
1018 gas_limit: 11,
1019 extra_data: vec![1, 2, 3].into(),
1020 logs_bloom: Bloom::default(),
1021 timestamp: 12,
1022 difficulty: U256::from(13),
1023 mix_hash: B256::with_last_byte(14),
1024 nonce: B64::with_last_byte(15),
1025 base_fee_per_gas: Some(20),
1026 blob_gas_used: None,
1027 excess_blob_gas: None,
1028 parent_beacon_block_root: None,
1029 requests_hash: None,
1030 block_access_list_hash: None,
1031 slot_number: None,
1032 },
1033 total_difficulty: Some(U256::from(100000)),
1034 size: None,
1035 },
1036 uncles: vec![B256::with_last_byte(17)],
1037 transactions: vec![B256::with_last_byte(18)].into(),
1038 withdrawals: None,
1039 };
1040 let serialized = serde_json::to_string(&block).unwrap();
1041 assert_eq!(
1042 serialized,
1043 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"]}"#
1044 );
1045 let deserialized: Block = serde_json::from_str(&serialized).unwrap();
1046 assert_eq!(block, deserialized);
1047 }
1048
1049 #[test]
1050 #[cfg(feature = "serde")]
1051 fn block_overrides() {
1052 let s = r#"{"blockNumber": "0xe39dd0"}"#;
1053 let _overrides = serde_json::from_str::<BlockOverrides>(s).unwrap();
1054 }
1055
1056 #[test]
1057 fn block_overrides_is_empty() {
1058 let default_overrides = BlockOverrides::default();
1060 assert!(default_overrides.is_empty());
1061
1062 let overrides_with_number = BlockOverrides::default().with_number(U256::from(42));
1064 assert!(!overrides_with_number.is_empty());
1065
1066 let overrides_with_difficulty = BlockOverrides::default().with_difficulty(U256::from(100));
1067 assert!(!overrides_with_difficulty.is_empty());
1068
1069 let overrides_with_time = BlockOverrides::default().with_time(12345);
1070 assert!(!overrides_with_time.is_empty());
1071
1072 let overrides_with_gas_limit = BlockOverrides::default().with_gas_limit(21000);
1073 assert!(!overrides_with_gas_limit.is_empty());
1074
1075 let overrides_with_coinbase =
1076 BlockOverrides::default().with_coinbase(Address::with_last_byte(1));
1077 assert!(!overrides_with_coinbase.is_empty());
1078
1079 let overrides_with_random = BlockOverrides::default().with_random(B256::with_last_byte(1));
1080 assert!(!overrides_with_random.is_empty());
1081
1082 let overrides_with_base_fee = BlockOverrides::default().with_base_fee(U256::from(20));
1083 assert!(!overrides_with_base_fee.is_empty());
1084
1085 let overrides_with_block_hash =
1086 BlockOverrides::default().append_block_hash(1, B256::with_last_byte(1));
1087 assert!(!overrides_with_block_hash.is_empty());
1088
1089 let overrides_with_beacon_root =
1090 BlockOverrides::default().with_beacon_root(B256::with_last_byte(1));
1091 assert!(!overrides_with_beacon_root.is_empty());
1092 }
1093
1094 #[test]
1095 #[cfg(feature = "serde")]
1096 fn serde_rich_block() {
1097 let s = r#"{
1098 "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
1099 "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
1100 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1101 "miner": "0x829bd824b016326a401d083b33d092293333a830",
1102 "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
1103 "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
1104 "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
1105 "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
1106 "difficulty": "0xc40faff9c737d",
1107 "number": "0xa9a230",
1108 "gasLimit": "0xbe5a66",
1109 "gasUsed": "0xbe0fcc",
1110 "timestamp": "0x5f93b749",
1111 "totalDifficulty": "0x3dc957fd8167fb2684a",
1112 "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
1113 "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
1114 "nonce": "0x4722f2acd35abe0f",
1115 "uncles": [],
1116 "transactions": [
1117 "0xf435a26acc2a9ef73ac0b73632e32e29bd0e28d5c4f46a7e18ed545c93315916"
1118 ],
1119 "size": "0xaeb6"
1120}"#;
1121
1122 let block = serde_json::from_str::<alloy_serde::WithOtherFields<Block>>(s).unwrap();
1123 let serialized = serde_json::to_string(&block).unwrap();
1124 let block2 =
1125 serde_json::from_str::<alloy_serde::WithOtherFields<Block>>(&serialized).unwrap();
1126 assert_eq!(block, block2);
1127 }
1128
1129 #[test]
1130 #[cfg(feature = "serde")]
1131 fn serde_missing_uncles_block() {
1132 let s = r#"{
1133 "baseFeePerGas":"0x886b221ad",
1134 "blobGasUsed":"0x0",
1135 "difficulty":"0x0",
1136 "excessBlobGas":"0x0",
1137 "extraData":"0x6265617665726275696c642e6f7267",
1138 "gasLimit":"0x1c9c380",
1139 "gasUsed":"0xb0033c",
1140 "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac",
1141 "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427",
1142 "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
1143 "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5",
1144 "nonce":"0x0000000000000000",
1145 "number":"0x128c6df",
1146 "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc",
1147 "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717",
1148 "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90",
1149 "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1150 "size":"0xdcc3",
1151 "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404",
1152 "timestamp":"0x65f5f4c3",
1153 "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780",
1154 "withdrawals":[
1155 {
1156 "index":"0x24d80e6",
1157 "validatorIndex":"0x8b2b6",
1158 "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff",
1159 "amount":"0x1161f10"
1160 }
1161 ],
1162 "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7"
1163 }"#;
1164
1165 let block = serde_json::from_str::<Block>(s).unwrap();
1166 let serialized = serde_json::to_string(&block).unwrap();
1167 let block2 = serde_json::from_str::<Block>(&serialized).unwrap();
1168 assert_eq!(block, block2);
1169 }
1170
1171 #[test]
1172 #[cfg(feature = "serde")]
1173 fn serde_block_containing_uncles() {
1174 let s = r#"{
1175 "baseFeePerGas":"0x886b221ad",
1176 "blobGasUsed":"0x0",
1177 "difficulty":"0x0",
1178 "excessBlobGas":"0x0",
1179 "extraData":"0x6265617665726275696c642e6f7267",
1180 "gasLimit":"0x1c9c380",
1181 "gasUsed":"0xb0033c",
1182 "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac",
1183 "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427",
1184 "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
1185 "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5",
1186 "nonce":"0x0000000000000000",
1187 "number":"0x128c6df",
1188 "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc",
1189 "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717",
1190 "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90",
1191 "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1192 "size":"0xdcc3",
1193 "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404",
1194 "timestamp":"0x65f5f4c3",
1195 "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780",
1196 "uncles": ["0x123a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780", "0x489a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780"],
1197 "withdrawals":[
1198 {
1199 "index":"0x24d80e6",
1200 "validatorIndex":"0x8b2b6",
1201 "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff",
1202 "amount":"0x1161f10"
1203 }
1204 ],
1205 "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7"
1206 }"#;
1207
1208 let block = serde_json::from_str::<Block>(s).unwrap();
1209 assert_eq!(block.uncles.len(), 2);
1210 let serialized = serde_json::to_string(&block).unwrap();
1211 let block2 = serde_json::from_str::<Block>(&serialized).unwrap();
1212 assert_eq!(block, block2);
1213 }
1214
1215 #[test]
1216 #[cfg(feature = "serde")]
1217 fn serde_empty_block() {
1218 let s = r#"{
1219 "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
1220 "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
1221 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1222 "miner": "0x829bd824b016326a401d083b33d092293333a830",
1223 "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
1224 "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
1225 "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
1226 "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
1227 "difficulty": "0xc40faff9c737d",
1228 "number": "0xa9a230",
1229 "gasLimit": "0xbe5a66",
1230 "gasUsed": "0xbe0fcc",
1231 "timestamp": "0x5f93b749",
1232 "totalDifficulty": "0x3dc957fd8167fb2684a",
1233 "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
1234 "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
1235 "nonce": "0x4722f2acd35abe0f",
1236 "uncles": [],
1237 "transactions": [],
1238 "size": "0xaeb6"
1239}"#;
1240
1241 let block = serde_json::from_str::<Block>(s).unwrap();
1242 assert!(block.transactions.is_empty());
1243 assert!(block.transactions.as_transactions().is_some());
1244 }
1245
1246 #[test]
1247 #[cfg(feature = "serde")]
1248 fn recompute_block_hash() {
1249 let s = r#"{
1250 "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
1251 "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
1252 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1253 "miner": "0x829bd824b016326a401d083b33d092293333a830",
1254 "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
1255 "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
1256 "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
1257 "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
1258 "difficulty": "0xc40faff9c737d",
1259 "number": "0xa9a230",
1260 "gasLimit": "0xbe5a66",
1261 "gasUsed": "0xbe0fcc",
1262 "timestamp": "0x5f93b749",
1263 "totalDifficulty": "0x3dc957fd8167fb2684a",
1264 "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
1265 "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
1266 "nonce": "0x4722f2acd35abe0f",
1267 "uncles": [],
1268 "transactions": [],
1269 "size": "0xaeb6"
1270}"#;
1271 let block = serde_json::from_str::<Block>(s).unwrap();
1272 let recomputed_hash = keccak256(alloy_rlp::encode(&block.header.inner));
1273 assert_eq!(recomputed_hash, block.header.hash);
1274
1275 let s2 = r#"{
1276 "baseFeePerGas":"0x886b221ad",
1277 "blobGasUsed":"0x0",
1278 "difficulty":"0x0",
1279 "excessBlobGas":"0x0",
1280 "extraData":"0x6265617665726275696c642e6f7267",
1281 "gasLimit":"0x1c9c380",
1282 "gasUsed":"0xb0033c",
1283 "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac",
1284 "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427",
1285 "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
1286 "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5",
1287 "nonce":"0x0000000000000000",
1288 "number":"0x128c6df",
1289 "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc",
1290 "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717",
1291 "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90",
1292 "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1293 "size":"0xdcc3",
1294 "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404",
1295 "timestamp":"0x65f5f4c3",
1296 "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780",
1297 "withdrawals":[
1298 {
1299 "index":"0x24d80e6",
1300 "validatorIndex":"0x8b2b6",
1301 "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff",
1302 "amount":"0x1161f10"
1303 }
1304 ],
1305 "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7"
1306 }"#;
1307 let block2 = serde_json::from_str::<Block>(s2).unwrap();
1308 let recomputed_hash = keccak256(alloy_rlp::encode(&block2.header.inner));
1309 assert_eq!(recomputed_hash, block2.header.hash);
1310 }
1311
1312 #[test]
1313 fn header_roundtrip_conversion() {
1314 let rpc_header = Header {
1316 hash: B256::with_last_byte(1),
1317 inner: alloy_consensus::Header {
1318 parent_hash: B256::with_last_byte(2),
1319 ommers_hash: B256::with_last_byte(3),
1320 beneficiary: Address::with_last_byte(4),
1321 state_root: B256::with_last_byte(5),
1322 transactions_root: B256::with_last_byte(6),
1323 receipts_root: B256::with_last_byte(7),
1324 withdrawals_root: None,
1325 number: 9,
1326 gas_used: 10,
1327 gas_limit: 11,
1328 extra_data: vec![1, 2, 3].into(),
1329 logs_bloom: Bloom::default(),
1330 timestamp: 12,
1331 difficulty: U256::from(13),
1332 mix_hash: B256::with_last_byte(14),
1333 nonce: B64::with_last_byte(15),
1334 base_fee_per_gas: Some(20),
1335 blob_gas_used: None,
1336 excess_blob_gas: None,
1337 parent_beacon_block_root: None,
1338 requests_hash: None,
1339 block_access_list_hash: None,
1340 slot_number: None,
1341 },
1342 size: None,
1343 total_difficulty: None,
1344 };
1345
1346 let primitive_header = rpc_header.inner.clone();
1348
1349 let sealed_header: Sealed<alloy_consensus::Header> =
1351 primitive_header.seal(B256::with_last_byte(1));
1352
1353 let roundtrip_rpc_header = Header::from_consensus(sealed_header, None, None);
1355
1356 assert_eq!(rpc_header, roundtrip_rpc_header);
1358 }
1359
1360 #[test]
1361 fn test_consensus_header_to_rpc_block() {
1362 let header = Header {
1364 hash: B256::with_last_byte(1),
1365 inner: alloy_consensus::Header {
1366 parent_hash: B256::with_last_byte(2),
1367 ommers_hash: B256::with_last_byte(3),
1368 beneficiary: Address::with_last_byte(4),
1369 state_root: B256::with_last_byte(5),
1370 transactions_root: B256::with_last_byte(6),
1371 receipts_root: B256::with_last_byte(7),
1372 withdrawals_root: None,
1373 number: 9,
1374 gas_used: 10,
1375 gas_limit: 11,
1376 extra_data: vec![1, 2, 3].into(),
1377 logs_bloom: Bloom::default(),
1378 timestamp: 12,
1379 difficulty: U256::from(13),
1380 mix_hash: B256::with_last_byte(14),
1381 nonce: B64::with_last_byte(15),
1382 base_fee_per_gas: Some(20),
1383 blob_gas_used: None,
1384 excess_blob_gas: None,
1385 parent_beacon_block_root: None,
1386 requests_hash: None,
1387 block_access_list_hash: None,
1388 slot_number: None,
1389 },
1390 total_difficulty: None,
1391 size: Some(U256::from(505)),
1392 };
1393
1394 let primitive_header = header.inner.clone();
1396
1397 let block: Block<Transaction> = Block::uncle_from_header(primitive_header);
1399
1400 assert_eq!(
1402 block,
1403 Block {
1404 header: Header {
1405 hash: B256::from(hex!(
1406 "379bd1414cf69a9b86fb4e0e6b05a2e4b14cb3d5af057e13ccdc2192cb9780b2"
1407 )),
1408 ..header
1409 },
1410 uncles: vec![],
1411 transactions: BlockTransactions::Uncle,
1412 withdrawals: None,
1413 }
1414 );
1415 }
1416
1417 #[test]
1418 #[cfg(feature = "serde")]
1419 fn serde_bad_block() {
1420 use alloy_primitives::B64;
1421
1422 let block = Block {
1423 header: Header {
1424 hash: B256::with_last_byte(1),
1425 inner: alloy_consensus::Header {
1426 parent_hash: B256::with_last_byte(2),
1427 ommers_hash: B256::with_last_byte(3),
1428 beneficiary: Address::with_last_byte(4),
1429 state_root: B256::with_last_byte(5),
1430 transactions_root: B256::with_last_byte(6),
1431 receipts_root: B256::with_last_byte(7),
1432 withdrawals_root: Some(B256::with_last_byte(8)),
1433 number: 9,
1434 gas_used: 10,
1435 gas_limit: 11,
1436 extra_data: vec![1, 2, 3].into(),
1437 logs_bloom: Default::default(),
1438 timestamp: 12,
1439 difficulty: U256::from(13),
1440 mix_hash: B256::with_last_byte(14),
1441 nonce: B64::with_last_byte(15),
1442 base_fee_per_gas: Some(20),
1443 blob_gas_used: None,
1444 excess_blob_gas: None,
1445 parent_beacon_block_root: None,
1446 requests_hash: None,
1447 block_access_list_hash: None,
1448 slot_number: None,
1449 },
1450 total_difficulty: Some(U256::from(100000)),
1451 size: Some(U256::from(19)),
1452 },
1453 uncles: vec![B256::with_last_byte(17)],
1454 transactions: vec![B256::with_last_byte(18)].into(),
1455 withdrawals: Some(Default::default()),
1456 };
1457 let hash = block.header.hash;
1458 let rlp = Bytes::from("header");
1459
1460 let bad_block = BadBlock { block, hash, rlp };
1461
1462 let serialized = serde_json::to_string(&bad_block).unwrap();
1463 assert_eq!(
1464 serialized,
1465 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"}"#
1466 );
1467
1468 let deserialized: BadBlock = serde_json::from_str(&serialized).unwrap();
1469 similar_asserts::assert_eq!(bad_block, deserialized);
1470 }
1471
1472 #[test]
1474 #[cfg(feature = "serde")]
1475 fn deserde_tenderly_block() {
1476 let s = include_str!("../testdata/tenderly.sepolia.json");
1477 let _block: Block = serde_json::from_str(s).unwrap();
1478 }
1479}