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 try_into_block_body(self) -> Result<BlockBody<T, H>, ValueError<Self>> {
133 if !self.uncles.is_empty() {
134 return Err(ValueError::new_static(self, "uncles not empty"));
135 }
136 if !self.transactions.is_full() {
137 return Err(ValueError::new_static(self, "transactions not full"));
138 }
139
140 Ok(self.into_block_body_unchecked())
141 }
142
143 pub fn into_block_body_unchecked(self) -> BlockBody<T, H> {
149 BlockBody {
150 transactions: self.transactions.into_transactions_vec(),
151 ommers: Default::default(),
152 withdrawals: self.withdrawals,
153 }
154 }
155
156 pub fn into_consensus_block(self) -> alloy_consensus::Block<T, H> {
167 alloy_consensus::BlockBody {
168 transactions: self.transactions.into_transactions_vec(),
169 ommers: vec![],
170 withdrawals: self.withdrawals,
171 }
172 .into_block(self.header)
173 }
174
175 pub fn map_header<U>(self, f: impl FnOnce(H) -> U) -> Block<T, U> {
177 Block {
178 header: f(self.header),
179 uncles: self.uncles,
180 transactions: self.transactions,
181 withdrawals: self.withdrawals,
182 }
183 }
184
185 pub fn into_header(self) -> H {
189 self.header
190 }
191
192 pub fn try_convert_header<U>(self) -> Result<Block<T, U>, U::Error>
194 where
195 U: TryFrom<H>,
196 {
197 self.try_map_header(U::try_from)
198 }
199
200 pub fn try_map_header<U, E>(self, f: impl FnOnce(H) -> Result<U, E>) -> Result<Block<T, U>, E> {
202 Ok(Block {
203 header: f(self.header)?,
204 uncles: self.uncles,
205 transactions: self.transactions,
206 withdrawals: self.withdrawals,
207 })
208 }
209
210 pub fn convert_transactions<U>(self) -> Block<U, H>
212 where
213 U: From<T>,
214 {
215 self.map_transactions(U::from)
216 }
217
218 pub fn try_convert_transactions<U>(self) -> Result<Block<U, H>, U::Error>
222 where
223 U: TryFrom<T>,
224 {
225 self.try_map_transactions(U::try_from)
226 }
227
228 pub fn map_transactions<U>(self, f: impl FnMut(T) -> U) -> Block<U, H> {
232 Block {
233 header: self.header,
234 uncles: self.uncles,
235 transactions: self.transactions.map(f),
236 withdrawals: self.withdrawals,
237 }
238 }
239
240 pub fn try_map_transactions<U, E>(
245 self,
246 f: impl FnMut(T) -> Result<U, E>,
247 ) -> Result<Block<U, H>, E> {
248 Ok(Block {
249 header: self.header,
250 uncles: self.uncles,
251 transactions: self.transactions.try_map(f)?,
252 withdrawals: self.withdrawals,
253 })
254 }
255
256 pub fn calculate_transactions_root(&self) -> Option<B256>
260 where
261 T: Encodable2718,
262 {
263 self.transactions.calculate_transactions_root()
264 }
265}
266
267impl<T: TransactionResponse, H> Block<T, H> {
268 pub fn into_full_block(self, txs: Vec<T>) -> Self {
270 Self { transactions: txs.into(), ..self }
271 }
272}
273
274impl<T, H: Sealable + Encodable> Block<T, Header<H>> {
275 pub fn uncle_from_header(header: H) -> Self {
280 let block = alloy_consensus::Block::<TxEnvelope, H>::uncle(header);
281 let size = U256::from(block.length());
282 Self {
283 uncles: vec![],
284 header: Header::from_consensus(block.header.seal_slow(), None, Some(size)),
285 transactions: BlockTransactions::Uncle,
286 withdrawals: None,
287 }
288 }
289}
290
291impl<T> Block<T> {
292 pub const fn hash(&self) -> B256 {
294 self.header.hash
295 }
296
297 pub const fn sealed_header(&self) -> Sealed<&alloy_consensus::Header> {
299 Sealed::new_unchecked(&self.header.inner, self.header.hash)
300 }
301
302 pub fn into_sealed_header(self) -> Sealed<alloy_consensus::Header> {
304 self.header.into_sealed()
305 }
306
307 pub fn into_consensus_header(self) -> alloy_consensus::Header {
310 self.header.into_consensus()
311 }
312
313 pub fn from_consensus(block: alloy_consensus::Block<T>, total_difficulty: Option<U256>) -> Self
315 where
316 T: Encodable,
317 {
318 let size = U256::from(block.length());
319 let alloy_consensus::Block {
320 header,
321 body: alloy_consensus::BlockBody { transactions, ommers, withdrawals },
322 } = block;
323
324 Self {
325 header: Header::from_consensus(header.seal_slow(), total_difficulty, Some(size)),
326 uncles: ommers.into_iter().map(|h| h.hash_slow()).collect(),
327 transactions: BlockTransactions::Full(transactions),
328 withdrawals,
329 }
330 }
331
332 pub fn into_consensus(self) -> alloy_consensus::Block<T> {
340 let Self { header, transactions, withdrawals, .. } = self;
341 alloy_consensus::BlockBody {
342 transactions: transactions.into_transactions_vec(),
343 ommers: vec![],
344 withdrawals,
345 }
346 .into_block(header.into_consensus())
347 }
348
349 pub fn into_consensus_sealed(self) -> Sealed<alloy_consensus::Block<T>> {
351 let hash = self.header.hash;
352 Sealed::new_unchecked(self.into_consensus(), hash)
353 }
354}
355
356impl<T, S> From<Block<T>> for alloy_consensus::Block<S>
357where
358 S: From<T>,
359{
360 fn from(block: Block<T>) -> Self {
361 block.into_consensus().convert_transactions()
362 }
363}
364
365#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
369#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
370#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
371#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
372pub struct Header<H = alloy_consensus::Header> {
373 pub hash: BlockHash,
375 #[cfg_attr(feature = "serde", serde(flatten))]
377 pub inner: H,
378 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
382 pub total_difficulty: Option<U256>,
383 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
385 pub size: Option<U256>,
386}
387
388impl<H> Header<H> {
389 pub fn new(inner: H) -> Self
393 where
394 H: Sealable,
395 {
396 Self::from_sealed(Sealed::new(inner))
397 }
398
399 pub fn from_sealed(header: Sealed<H>) -> Self {
403 let (inner, hash) = header.into_parts();
404 Self { hash, inner, total_difficulty: None, size: None }
405 }
406
407 pub fn into_sealed(self) -> Sealed<H> {
409 Sealed::new_unchecked(self.inner, self.hash)
410 }
411
412 pub fn into_consensus(self) -> H {
414 self.inner
415 }
416
417 pub fn from_consensus(
419 header: Sealed<H>,
420 total_difficulty: Option<U256>,
421 size: Option<U256>,
422 ) -> Self {
423 let (inner, hash) = header.into_parts();
424 Self { hash, inner, total_difficulty, size }
425 }
426
427 pub const fn with_total_difficulty(mut self, total_difficulty: Option<U256>) -> Self {
429 self.total_difficulty = total_difficulty;
430 self
431 }
432
433 pub const fn with_size(mut self, size: Option<U256>) -> Self {
435 self.size = size;
436 self
437 }
438
439 #[expect(clippy::use_self)]
441 pub fn map<H1>(self, f: impl FnOnce(H) -> H1) -> Header<H1> {
442 let Header { hash, inner, total_difficulty, size } = self;
443
444 Header { hash, inner: f(inner), total_difficulty, size }
445 }
446
447 #[expect(clippy::use_self)]
449 pub fn try_map<H1, E>(self, f: impl FnOnce(H) -> Result<H1, E>) -> Result<Header<H1>, E> {
450 let Header { hash, inner, total_difficulty, size } = self;
451
452 Ok(Header { hash, inner: f(inner)?, total_difficulty, size })
453 }
454}
455
456impl<H> Deref for Header<H> {
457 type Target = H;
458
459 fn deref(&self) -> &Self::Target {
460 &self.inner
461 }
462}
463
464impl<H> DerefMut for Header<H> {
465 fn deref_mut(&mut self) -> &mut Self::Target {
466 &mut self.inner
467 }
468}
469
470impl<H> AsRef<H> for Header<H> {
471 fn as_ref(&self) -> &H {
472 &self.inner
473 }
474}
475
476impl<H: BlockHeader> Header<H> {
477 pub fn blob_fee(&self) -> Option<u128> {
481 self.inner.excess_blob_gas().map(calc_blob_gasprice)
482 }
483
484 pub fn next_block_blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
490 self.inner.next_block_blob_fee(blob_params)
491 }
492
493 pub fn next_block_excess_blob_gas(&self, blob_params: BlobParams) -> Option<u64> {
498 self.inner.next_block_excess_blob_gas(blob_params)
499 }
500}
501
502impl<H: BlockHeader> BlockHeader for Header<H> {
503 fn parent_hash(&self) -> B256 {
504 self.inner.parent_hash()
505 }
506
507 fn ommers_hash(&self) -> B256 {
508 self.inner.ommers_hash()
509 }
510
511 fn beneficiary(&self) -> Address {
512 self.inner.beneficiary()
513 }
514
515 fn state_root(&self) -> B256 {
516 self.inner.state_root()
517 }
518
519 fn transactions_root(&self) -> B256 {
520 self.inner.transactions_root()
521 }
522
523 fn receipts_root(&self) -> B256 {
524 self.inner.receipts_root()
525 }
526
527 fn withdrawals_root(&self) -> Option<B256> {
528 self.inner.withdrawals_root()
529 }
530
531 fn logs_bloom(&self) -> Bloom {
532 self.inner.logs_bloom()
533 }
534
535 fn difficulty(&self) -> U256 {
536 self.inner.difficulty()
537 }
538
539 fn number(&self) -> u64 {
540 self.inner.number()
541 }
542
543 fn gas_limit(&self) -> u64 {
544 self.inner.gas_limit()
545 }
546
547 fn gas_used(&self) -> u64 {
548 self.inner.gas_used()
549 }
550
551 fn timestamp(&self) -> u64 {
552 self.inner.timestamp()
553 }
554
555 fn mix_hash(&self) -> Option<B256> {
556 self.inner.mix_hash()
557 }
558
559 fn nonce(&self) -> Option<B64> {
560 self.inner.nonce()
561 }
562
563 fn base_fee_per_gas(&self) -> Option<u64> {
564 self.inner.base_fee_per_gas()
565 }
566
567 fn blob_gas_used(&self) -> Option<u64> {
568 self.inner.blob_gas_used()
569 }
570
571 fn excess_blob_gas(&self) -> Option<u64> {
572 self.inner.excess_blob_gas()
573 }
574
575 fn parent_beacon_block_root(&self) -> Option<B256> {
576 self.inner.parent_beacon_block_root()
577 }
578
579 fn requests_hash(&self) -> Option<B256> {
580 self.inner.requests_hash()
581 }
582
583 fn extra_data(&self) -> &Bytes {
584 self.inner.extra_data()
585 }
586}
587
588impl<H: BlockHeader> HeaderResponse for Header<H> {
589 fn hash(&self) -> BlockHash {
590 self.hash
591 }
592}
593
594impl From<Header> for alloy_consensus::Header {
595 fn from(header: Header) -> Self {
596 header.into_consensus()
597 }
598}
599
600impl<H> From<Header<H>> for Sealed<H> {
601 fn from(value: Header<H>) -> Self {
602 value.into_sealed()
603 }
604}
605
606#[derive(Clone, Copy, Debug, thiserror::Error)]
608pub enum BlockError {
609 #[error("transaction failed sender recovery")]
611 InvalidSignature,
612 #[error("failed to decode raw block {0}")]
614 RlpDecodeRawBlock(alloy_rlp::Error),
615}
616
617#[cfg(feature = "serde")]
618impl<T, H> From<Block<T, H>> for alloy_serde::WithOtherFields<Block<T, H>> {
619 fn from(inner: Block<T, H>) -> Self {
620 Self { inner, other: Default::default() }
621 }
622}
623
624#[cfg(feature = "serde")]
625impl From<Header> for alloy_serde::WithOtherFields<Header> {
626 fn from(inner: Header) -> Self {
627 Self { inner, other: Default::default() }
628 }
629}
630
631#[derive(Clone, Debug, Default, PartialEq, Eq)]
633#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
634#[cfg_attr(feature = "serde", serde(default, rename_all = "camelCase", deny_unknown_fields))]
635pub struct BlockOverrides {
636 #[cfg_attr(
642 feature = "serde",
643 serde(default, skip_serializing_if = "Option::is_none", alias = "blockNumber")
644 )]
645 pub number: Option<U256>,
646 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
648 pub difficulty: Option<U256>,
649 #[cfg_attr(
652 feature = "serde",
653 serde(
654 default,
655 skip_serializing_if = "Option::is_none",
656 alias = "timestamp",
657 with = "alloy_serde::quantity::opt"
658 )
659 )]
660 pub time: Option<u64>,
661 #[cfg_attr(
663 feature = "serde",
664 serde(
665 default,
666 skip_serializing_if = "Option::is_none",
667 with = "alloy_serde::quantity::opt"
668 )
669 )]
670 pub gas_limit: Option<u64>,
671 #[cfg_attr(
673 feature = "serde",
674 serde(default, skip_serializing_if = "Option::is_none", alias = "feeRecipient")
675 )]
676 pub coinbase: Option<Address>,
677 #[cfg_attr(
679 feature = "serde",
680 serde(default, skip_serializing_if = "Option::is_none", alias = "prevRandao")
681 )]
682 pub random: Option<B256>,
683 #[cfg_attr(
685 feature = "serde",
686 serde(default, skip_serializing_if = "Option::is_none", alias = "baseFeePerGas")
687 )]
688 pub base_fee: Option<U256>,
689 #[cfg_attr(feature = "serde", serde(default, skip_serializing_if = "Option::is_none"))]
692 pub block_hash: Option<BTreeMap<u64, B256>>,
693}
694
695impl BlockOverrides {
696 pub const fn is_empty(&self) -> bool {
698 self.number.is_none()
699 && self.difficulty.is_none()
700 && self.time.is_none()
701 && self.gas_limit.is_none()
702 && self.coinbase.is_none()
703 && self.random.is_none()
704 && self.base_fee.is_none()
705 && self.block_hash.is_none()
706 }
707
708 pub const fn with_number(mut self, number: U256) -> Self {
710 self.number = Some(number);
711 self
712 }
713
714 pub const fn with_difficulty(mut self, difficulty: U256) -> Self {
716 self.difficulty = Some(difficulty);
717 self
718 }
719
720 pub const fn with_time(mut self, time: u64) -> Self {
722 self.time = Some(time);
723 self
724 }
725
726 pub const fn with_gas_limit(mut self, gas_limit: u64) -> Self {
728 self.gas_limit = Some(gas_limit);
729 self
730 }
731
732 pub const fn with_coinbase(mut self, coinbase: Address) -> Self {
734 self.coinbase = Some(coinbase);
735 self
736 }
737
738 pub const fn with_random(mut self, random: B256) -> Self {
740 self.random = Some(random);
741 self
742 }
743
744 pub const fn with_base_fee(mut self, base_fee: U256) -> Self {
746 self.base_fee = Some(base_fee);
747 self
748 }
749
750 pub fn append_block_hash(mut self, block_number: u64, hash: B256) -> Self {
752 let hash_map = self.block_hash.get_or_insert_with(Default::default);
753 hash_map.insert(block_number, hash);
754 self
755 }
756
757 pub fn with_block_hash_overrides<I>(mut self, hashes: I) -> Self
759 where
760 I: IntoIterator<Item = (u64, B256)>,
761 {
762 let map = self.block_hash.get_or_insert_with(Default::default);
763 map.extend(hashes);
764 self
765 }
766}
767
768impl<T: TransactionResponse, H> BlockResponse for Block<T, H> {
769 type Header = H;
770 type Transaction = T;
771
772 fn header(&self) -> &Self::Header {
773 &self.header
774 }
775
776 fn transactions(&self) -> &BlockTransactions<T> {
777 &self.transactions
778 }
779
780 fn transactions_mut(&mut self) -> &mut BlockTransactions<Self::Transaction> {
781 &mut self.transactions
782 }
783}
784
785#[derive(Clone, Debug, Default, PartialEq, Eq)]
787#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
788#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
789pub struct BadBlock {
790 block: Block,
792 hash: BlockHash,
794 rlp: Bytes,
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use alloy_primitives::{hex, keccak256, Bloom, B64};
802 use arbitrary::Arbitrary;
803 use rand::Rng;
804 use similar_asserts::assert_eq;
805
806 #[test]
807 fn arbitrary_header() {
808 let mut bytes = [0u8; 1024];
809 rand::thread_rng().fill(bytes.as_mut_slice());
810 let _: Header = Header::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap();
811 }
812
813 #[test]
814 #[cfg(all(feature = "jsonrpsee-types", feature = "serde"))]
815 fn serde_json_header() {
816 use jsonrpsee_types::SubscriptionResponse;
817 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"}}}"#;
818 let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap();
819
820 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":[]}}}"#;
821 let _header: SubscriptionResponse<'_, Header> = serde_json::from_str(resp).unwrap();
822 }
823
824 #[test]
825 #[cfg(feature = "serde")]
826 fn serde_block() {
827 use alloy_primitives::B64;
828
829 let block = Block {
830 header: Header {
831 hash: B256::with_last_byte(1),
832 inner: alloy_consensus::Header {
833 parent_hash: B256::with_last_byte(2),
834 ommers_hash: B256::with_last_byte(3),
835 beneficiary: Address::with_last_byte(4),
836 state_root: B256::with_last_byte(5),
837 transactions_root: B256::with_last_byte(6),
838 receipts_root: B256::with_last_byte(7),
839 withdrawals_root: Some(B256::with_last_byte(8)),
840 number: 9,
841 gas_used: 10,
842 gas_limit: 11,
843 extra_data: vec![1, 2, 3].into(),
844 logs_bloom: Default::default(),
845 timestamp: 12,
846 difficulty: U256::from(13),
847 mix_hash: B256::with_last_byte(14),
848 nonce: B64::with_last_byte(15),
849 base_fee_per_gas: Some(20),
850 blob_gas_used: None,
851 excess_blob_gas: None,
852 parent_beacon_block_root: None,
853 requests_hash: None,
854 },
855 total_difficulty: Some(U256::from(100000)),
856 size: None,
857 },
858 uncles: vec![B256::with_last_byte(17)],
859 transactions: vec![B256::with_last_byte(18)].into(),
860 withdrawals: Some(Default::default()),
861 };
862 let serialized = serde_json::to_string(&block).unwrap();
863 similar_asserts::assert_eq!(
864 serialized,
865 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":[]}"#
866 );
867 let deserialized: Block = serde_json::from_str(&serialized).unwrap();
868 similar_asserts::assert_eq!(block, deserialized);
869 }
870
871 #[test]
872 #[cfg(feature = "serde")]
873 fn serde_uncle_block() {
874 use alloy_primitives::B64;
875
876 let block = Block {
877 header: Header {
878 hash: B256::with_last_byte(1),
879 inner: alloy_consensus::Header {
880 parent_hash: B256::with_last_byte(2),
881 ommers_hash: B256::with_last_byte(3),
882 beneficiary: Address::with_last_byte(4),
883 state_root: B256::with_last_byte(5),
884 transactions_root: B256::with_last_byte(6),
885 receipts_root: B256::with_last_byte(7),
886 withdrawals_root: Some(B256::with_last_byte(8)),
887 number: 9,
888 gas_used: 10,
889 gas_limit: 11,
890 extra_data: vec![1, 2, 3].into(),
891 logs_bloom: Default::default(),
892 timestamp: 12,
893 difficulty: U256::from(13),
894 mix_hash: B256::with_last_byte(14),
895 nonce: B64::with_last_byte(15),
896 base_fee_per_gas: Some(20),
897 blob_gas_used: None,
898 excess_blob_gas: None,
899 parent_beacon_block_root: None,
900 requests_hash: None,
901 },
902 size: None,
903 total_difficulty: Some(U256::from(100000)),
904 },
905 uncles: vec![],
906 transactions: BlockTransactions::Uncle,
907 withdrawals: None,
908 };
909 let serialized = serde_json::to_string(&block).unwrap();
910 assert_eq!(
911 serialized,
912 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":[]}"#
913 );
914 let deserialized: Block = serde_json::from_str(&serialized).unwrap();
915 assert_eq!(block, deserialized);
916 }
917
918 #[test]
919 #[cfg(feature = "serde")]
920 fn serde_block_with_withdrawals_set_as_none() {
921 let block = Block {
922 header: Header {
923 hash: B256::with_last_byte(1),
924 inner: alloy_consensus::Header {
925 parent_hash: B256::with_last_byte(2),
926 ommers_hash: B256::with_last_byte(3),
927 beneficiary: Address::with_last_byte(4),
928 state_root: B256::with_last_byte(5),
929 transactions_root: B256::with_last_byte(6),
930 receipts_root: B256::with_last_byte(7),
931 withdrawals_root: None,
932 number: 9,
933 gas_used: 10,
934 gas_limit: 11,
935 extra_data: vec![1, 2, 3].into(),
936 logs_bloom: Bloom::default(),
937 timestamp: 12,
938 difficulty: U256::from(13),
939 mix_hash: B256::with_last_byte(14),
940 nonce: B64::with_last_byte(15),
941 base_fee_per_gas: Some(20),
942 blob_gas_used: None,
943 excess_blob_gas: None,
944 parent_beacon_block_root: None,
945 requests_hash: None,
946 },
947 total_difficulty: Some(U256::from(100000)),
948 size: None,
949 },
950 uncles: vec![B256::with_last_byte(17)],
951 transactions: vec![B256::with_last_byte(18)].into(),
952 withdrawals: None,
953 };
954 let serialized = serde_json::to_string(&block).unwrap();
955 assert_eq!(
956 serialized,
957 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"]}"#
958 );
959 let deserialized: Block = serde_json::from_str(&serialized).unwrap();
960 assert_eq!(block, deserialized);
961 }
962
963 #[test]
964 #[cfg(feature = "serde")]
965 fn block_overrides() {
966 let s = r#"{"blockNumber": "0xe39dd0"}"#;
967 let _overrides = serde_json::from_str::<BlockOverrides>(s).unwrap();
968 }
969
970 #[test]
971 fn block_overrides_is_empty() {
972 let default_overrides = BlockOverrides::default();
974 assert!(default_overrides.is_empty());
975
976 let overrides_with_number = BlockOverrides::default().with_number(U256::from(42));
978 assert!(!overrides_with_number.is_empty());
979
980 let overrides_with_difficulty = BlockOverrides::default().with_difficulty(U256::from(100));
981 assert!(!overrides_with_difficulty.is_empty());
982
983 let overrides_with_time = BlockOverrides::default().with_time(12345);
984 assert!(!overrides_with_time.is_empty());
985
986 let overrides_with_gas_limit = BlockOverrides::default().with_gas_limit(21000);
987 assert!(!overrides_with_gas_limit.is_empty());
988
989 let overrides_with_coinbase =
990 BlockOverrides::default().with_coinbase(Address::with_last_byte(1));
991 assert!(!overrides_with_coinbase.is_empty());
992
993 let overrides_with_random = BlockOverrides::default().with_random(B256::with_last_byte(1));
994 assert!(!overrides_with_random.is_empty());
995
996 let overrides_with_base_fee = BlockOverrides::default().with_base_fee(U256::from(20));
997 assert!(!overrides_with_base_fee.is_empty());
998
999 let overrides_with_block_hash =
1000 BlockOverrides::default().append_block_hash(1, B256::with_last_byte(1));
1001 assert!(!overrides_with_block_hash.is_empty());
1002 }
1003
1004 #[test]
1005 #[cfg(feature = "serde")]
1006 fn serde_rich_block() {
1007 let s = r#"{
1008 "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
1009 "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
1010 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1011 "miner": "0x829bd824b016326a401d083b33d092293333a830",
1012 "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
1013 "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
1014 "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
1015 "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
1016 "difficulty": "0xc40faff9c737d",
1017 "number": "0xa9a230",
1018 "gasLimit": "0xbe5a66",
1019 "gasUsed": "0xbe0fcc",
1020 "timestamp": "0x5f93b749",
1021 "totalDifficulty": "0x3dc957fd8167fb2684a",
1022 "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
1023 "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
1024 "nonce": "0x4722f2acd35abe0f",
1025 "uncles": [],
1026 "transactions": [
1027 "0xf435a26acc2a9ef73ac0b73632e32e29bd0e28d5c4f46a7e18ed545c93315916"
1028 ],
1029 "size": "0xaeb6"
1030}"#;
1031
1032 let block = serde_json::from_str::<alloy_serde::WithOtherFields<Block>>(s).unwrap();
1033 let serialized = serde_json::to_string(&block).unwrap();
1034 let block2 =
1035 serde_json::from_str::<alloy_serde::WithOtherFields<Block>>(&serialized).unwrap();
1036 assert_eq!(block, block2);
1037 }
1038
1039 #[test]
1040 #[cfg(feature = "serde")]
1041 fn serde_missing_uncles_block() {
1042 let s = r#"{
1043 "baseFeePerGas":"0x886b221ad",
1044 "blobGasUsed":"0x0",
1045 "difficulty":"0x0",
1046 "excessBlobGas":"0x0",
1047 "extraData":"0x6265617665726275696c642e6f7267",
1048 "gasLimit":"0x1c9c380",
1049 "gasUsed":"0xb0033c",
1050 "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac",
1051 "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427",
1052 "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
1053 "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5",
1054 "nonce":"0x0000000000000000",
1055 "number":"0x128c6df",
1056 "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc",
1057 "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717",
1058 "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90",
1059 "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1060 "size":"0xdcc3",
1061 "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404",
1062 "timestamp":"0x65f5f4c3",
1063 "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780",
1064 "withdrawals":[
1065 {
1066 "index":"0x24d80e6",
1067 "validatorIndex":"0x8b2b6",
1068 "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff",
1069 "amount":"0x1161f10"
1070 }
1071 ],
1072 "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7"
1073 }"#;
1074
1075 let block = serde_json::from_str::<Block>(s).unwrap();
1076 let serialized = serde_json::to_string(&block).unwrap();
1077 let block2 = serde_json::from_str::<Block>(&serialized).unwrap();
1078 assert_eq!(block, block2);
1079 }
1080
1081 #[test]
1082 #[cfg(feature = "serde")]
1083 fn serde_block_containing_uncles() {
1084 let s = r#"{
1085 "baseFeePerGas":"0x886b221ad",
1086 "blobGasUsed":"0x0",
1087 "difficulty":"0x0",
1088 "excessBlobGas":"0x0",
1089 "extraData":"0x6265617665726275696c642e6f7267",
1090 "gasLimit":"0x1c9c380",
1091 "gasUsed":"0xb0033c",
1092 "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac",
1093 "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427",
1094 "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
1095 "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5",
1096 "nonce":"0x0000000000000000",
1097 "number":"0x128c6df",
1098 "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc",
1099 "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717",
1100 "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90",
1101 "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1102 "size":"0xdcc3",
1103 "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404",
1104 "timestamp":"0x65f5f4c3",
1105 "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780",
1106 "uncles": ["0x123a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780", "0x489a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780"],
1107 "withdrawals":[
1108 {
1109 "index":"0x24d80e6",
1110 "validatorIndex":"0x8b2b6",
1111 "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff",
1112 "amount":"0x1161f10"
1113 }
1114 ],
1115 "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7"
1116 }"#;
1117
1118 let block = serde_json::from_str::<Block>(s).unwrap();
1119 assert_eq!(block.uncles.len(), 2);
1120 let serialized = serde_json::to_string(&block).unwrap();
1121 let block2 = serde_json::from_str::<Block>(&serialized).unwrap();
1122 assert_eq!(block, block2);
1123 }
1124
1125 #[test]
1126 #[cfg(feature = "serde")]
1127 fn serde_empty_block() {
1128 let s = r#"{
1129 "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
1130 "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
1131 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1132 "miner": "0x829bd824b016326a401d083b33d092293333a830",
1133 "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
1134 "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
1135 "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
1136 "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
1137 "difficulty": "0xc40faff9c737d",
1138 "number": "0xa9a230",
1139 "gasLimit": "0xbe5a66",
1140 "gasUsed": "0xbe0fcc",
1141 "timestamp": "0x5f93b749",
1142 "totalDifficulty": "0x3dc957fd8167fb2684a",
1143 "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
1144 "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
1145 "nonce": "0x4722f2acd35abe0f",
1146 "uncles": [],
1147 "transactions": [],
1148 "size": "0xaeb6"
1149}"#;
1150
1151 let block = serde_json::from_str::<Block>(s).unwrap();
1152 assert!(block.transactions.is_empty());
1153 assert!(block.transactions.as_transactions().is_some());
1154 }
1155
1156 #[test]
1157 #[cfg(feature = "serde")]
1158 fn recompute_block_hash() {
1159 let s = r#"{
1160 "hash": "0xb25d0e54ca0104e3ebfb5a1dcdf9528140854d609886a300946fd6750dcb19f4",
1161 "parentHash": "0x9400ec9ef59689c157ac89eeed906f15ddd768f94e1575e0e27d37c241439a5d",
1162 "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1163 "miner": "0x829bd824b016326a401d083b33d092293333a830",
1164 "stateRoot": "0x546e330050c66d02923e7f1f3e925efaf64e4384eeecf2288f40088714a77a84",
1165 "transactionsRoot": "0xd5eb3ad6d7c7a4798cc5fb14a6820073f44a941107c5d79dac60bd16325631fe",
1166 "receiptsRoot": "0xb21c41cbb3439c5af25304e1405524c885e733b16203221900cb7f4b387b62f0",
1167 "logsBloom": "0x1f304e641097eafae088627298685d20202004a4a59e4d8900914724e2402b028c9d596660581f361240816e82d00fa14250c9ca89840887a381efa600288283d170010ab0b2a0694c81842c2482457e0eb77c2c02554614007f42aaf3b4dc15d006a83522c86a240c06d241013258d90540c3008888d576a02c10120808520a2221110f4805200302624d22092b2c0e94e849b1e1aa80bc4cc3206f00b249d0a603ee4310216850e47c8997a20aa81fe95040a49ca5a420464600e008351d161dc00d620970b6a801535c218d0b4116099292000c08001943a225d6485528828110645b8244625a182c1a88a41087e6d039b000a180d04300d0680700a15794",
1168 "difficulty": "0xc40faff9c737d",
1169 "number": "0xa9a230",
1170 "gasLimit": "0xbe5a66",
1171 "gasUsed": "0xbe0fcc",
1172 "timestamp": "0x5f93b749",
1173 "totalDifficulty": "0x3dc957fd8167fb2684a",
1174 "extraData": "0x7070796520e4b883e5bda9e7a59ee4bb99e9b1bc0103",
1175 "mixHash": "0xd5e2b7b71fbe4ddfe552fb2377bf7cddb16bbb7e185806036cee86994c6e97fc",
1176 "nonce": "0x4722f2acd35abe0f",
1177 "uncles": [],
1178 "transactions": [],
1179 "size": "0xaeb6"
1180}"#;
1181 let block = serde_json::from_str::<Block>(s).unwrap();
1182 let header = block.clone().header.inner;
1183 let recomputed_hash = keccak256(alloy_rlp::encode(&header));
1184 assert_eq!(recomputed_hash, block.header.hash);
1185
1186 let s2 = r#"{
1187 "baseFeePerGas":"0x886b221ad",
1188 "blobGasUsed":"0x0",
1189 "difficulty":"0x0",
1190 "excessBlobGas":"0x0",
1191 "extraData":"0x6265617665726275696c642e6f7267",
1192 "gasLimit":"0x1c9c380",
1193 "gasUsed":"0xb0033c",
1194 "hash":"0x85cdcbe36217fd57bf2c33731d8460657a7ce512401f49c9f6392c82a7ccf7ac",
1195 "logsBloom":"0xc36919406572730518285284f2293101104140c0d42c4a786c892467868a8806f40159d29988002870403902413a1d04321320308da2e845438429e0012a00b419d8ccc8584a1c28f82a415d04eab8a5ae75c00d07761acf233414c08b6d9b571c06156086c70ea5186e9b989b0c2d55c0213c936805cd2ab331589c90194d070c00867549b1e1be14cb24500b0386cd901197c1ef5a00da453234fa48f3003dcaa894e3111c22b80e17f7d4388385a10720cda1140c0400f9e084ca34fc4870fb16b472340a2a6a63115a82522f506c06c2675080508834828c63defd06bc2331b4aa708906a06a560457b114248041e40179ebc05c6846c1e922125982f427",
1196 "miner":"0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5",
1197 "mixHash":"0x4c068e902990f21f92a2456fc75c59bec8be03b7f13682b6ebd27da56269beb5",
1198 "nonce":"0x0000000000000000",
1199 "number":"0x128c6df",
1200 "parentBeaconBlockRoot":"0x2843cb9f7d001bd58816a915e685ed96a555c9aeec1217736bd83a96ebd409cc",
1201 "parentHash":"0x90926e0298d418181bd20c23b332451e35fd7d696b5dcdc5a3a0a6b715f4c717",
1202 "receiptsRoot":"0xd43aa19ecb03571d1b86d89d9bb980139d32f2f2ba59646cd5c1de9e80c68c90",
1203 "sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
1204 "size":"0xdcc3",
1205 "stateRoot":"0x707875120a7103621fb4131df59904cda39de948dfda9084a1e3da44594d5404",
1206 "timestamp":"0x65f5f4c3",
1207 "transactionsRoot":"0x889a1c26dc42ba829dab552b779620feac231cde8a6c79af022bdc605c23a780",
1208 "withdrawals":[
1209 {
1210 "index":"0x24d80e6",
1211 "validatorIndex":"0x8b2b6",
1212 "address":"0x7cd1122e8e118b12ece8d25480dfeef230da17ff",
1213 "amount":"0x1161f10"
1214 }
1215 ],
1216 "withdrawalsRoot":"0x360c33f20eeed5efbc7d08be46e58f8440af5db503e40908ef3d1eb314856ef7"
1217 }"#;
1218 let block2 = serde_json::from_str::<Block>(s2).unwrap();
1219 let header = block2.clone().header.inner;
1220 let recomputed_hash = keccak256(alloy_rlp::encode(&header));
1221 assert_eq!(recomputed_hash, block2.header.hash);
1222 }
1223
1224 #[test]
1225 fn header_roundtrip_conversion() {
1226 let rpc_header = Header {
1228 hash: B256::with_last_byte(1),
1229 inner: alloy_consensus::Header {
1230 parent_hash: B256::with_last_byte(2),
1231 ommers_hash: B256::with_last_byte(3),
1232 beneficiary: Address::with_last_byte(4),
1233 state_root: B256::with_last_byte(5),
1234 transactions_root: B256::with_last_byte(6),
1235 receipts_root: B256::with_last_byte(7),
1236 withdrawals_root: None,
1237 number: 9,
1238 gas_used: 10,
1239 gas_limit: 11,
1240 extra_data: vec![1, 2, 3].into(),
1241 logs_bloom: Bloom::default(),
1242 timestamp: 12,
1243 difficulty: U256::from(13),
1244 mix_hash: B256::with_last_byte(14),
1245 nonce: B64::with_last_byte(15),
1246 base_fee_per_gas: Some(20),
1247 blob_gas_used: None,
1248 excess_blob_gas: None,
1249 parent_beacon_block_root: None,
1250 requests_hash: None,
1251 },
1252 size: None,
1253 total_difficulty: None,
1254 };
1255
1256 let primitive_header = rpc_header.clone().inner;
1258
1259 let sealed_header: Sealed<alloy_consensus::Header> =
1261 primitive_header.seal(B256::with_last_byte(1));
1262
1263 let roundtrip_rpc_header = Header::from_consensus(sealed_header, None, None);
1265
1266 assert_eq!(rpc_header, roundtrip_rpc_header);
1268 }
1269
1270 #[test]
1271 fn test_consensus_header_to_rpc_block() {
1272 let header = Header {
1274 hash: B256::with_last_byte(1),
1275 inner: alloy_consensus::Header {
1276 parent_hash: B256::with_last_byte(2),
1277 ommers_hash: B256::with_last_byte(3),
1278 beneficiary: Address::with_last_byte(4),
1279 state_root: B256::with_last_byte(5),
1280 transactions_root: B256::with_last_byte(6),
1281 receipts_root: B256::with_last_byte(7),
1282 withdrawals_root: None,
1283 number: 9,
1284 gas_used: 10,
1285 gas_limit: 11,
1286 extra_data: vec![1, 2, 3].into(),
1287 logs_bloom: Bloom::default(),
1288 timestamp: 12,
1289 difficulty: U256::from(13),
1290 mix_hash: B256::with_last_byte(14),
1291 nonce: B64::with_last_byte(15),
1292 base_fee_per_gas: Some(20),
1293 blob_gas_used: None,
1294 excess_blob_gas: None,
1295 parent_beacon_block_root: None,
1296 requests_hash: None,
1297 },
1298 total_difficulty: None,
1299 size: Some(U256::from(505)),
1300 };
1301
1302 let primitive_header = header.clone().inner;
1304
1305 let block: Block<Transaction> = Block::uncle_from_header(primitive_header);
1307
1308 assert_eq!(
1310 block,
1311 Block {
1312 header: Header {
1313 hash: B256::from(hex!(
1314 "379bd1414cf69a9b86fb4e0e6b05a2e4b14cb3d5af057e13ccdc2192cb9780b2"
1315 )),
1316 ..header
1317 },
1318 uncles: vec![],
1319 transactions: BlockTransactions::Uncle,
1320 withdrawals: None,
1321 }
1322 );
1323 }
1324
1325 #[test]
1326 #[cfg(feature = "serde")]
1327 fn serde_bad_block() {
1328 use alloy_primitives::B64;
1329
1330 let block = Block {
1331 header: Header {
1332 hash: B256::with_last_byte(1),
1333 inner: alloy_consensus::Header {
1334 parent_hash: B256::with_last_byte(2),
1335 ommers_hash: B256::with_last_byte(3),
1336 beneficiary: Address::with_last_byte(4),
1337 state_root: B256::with_last_byte(5),
1338 transactions_root: B256::with_last_byte(6),
1339 receipts_root: B256::with_last_byte(7),
1340 withdrawals_root: Some(B256::with_last_byte(8)),
1341 number: 9,
1342 gas_used: 10,
1343 gas_limit: 11,
1344 extra_data: vec![1, 2, 3].into(),
1345 logs_bloom: Default::default(),
1346 timestamp: 12,
1347 difficulty: U256::from(13),
1348 mix_hash: B256::with_last_byte(14),
1349 nonce: B64::with_last_byte(15),
1350 base_fee_per_gas: Some(20),
1351 blob_gas_used: None,
1352 excess_blob_gas: None,
1353 parent_beacon_block_root: None,
1354 requests_hash: None,
1355 },
1356 total_difficulty: Some(U256::from(100000)),
1357 size: Some(U256::from(19)),
1358 },
1359 uncles: vec![B256::with_last_byte(17)],
1360 transactions: vec![B256::with_last_byte(18)].into(),
1361 withdrawals: Some(Default::default()),
1362 };
1363 let hash = block.header.hash;
1364 let rlp = Bytes::from("header");
1365
1366 let bad_block = BadBlock { block, hash, rlp };
1367
1368 let serialized = serde_json::to_string(&bad_block).unwrap();
1369 assert_eq!(
1370 serialized,
1371 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"}"#
1372 );
1373
1374 let deserialized: BadBlock = serde_json::from_str(&serialized).unwrap();
1375 similar_asserts::assert_eq!(bad_block, deserialized);
1376 }
1377
1378 #[test]
1380 #[cfg(feature = "serde")]
1381 fn deserde_tenderly_block() {
1382 let s = include_str!("../testdata/tenderly.sepolia.json");
1383 let _block: Block = serde_json::from_str(s).unwrap();
1384 }
1385}