1use crate::{ExecutionPayloadSidecar, PayloadError};
4use alloc::{
5 string::{String, ToString},
6 vec::Vec,
7};
8use alloy_consensus::{
9 constants::MAXIMUM_EXTRA_DATA_SIZE, Blob, Block, BlockBody, BlockHeader, Bytes48, Header,
10 HeaderInfo, Transaction, EMPTY_OMMER_ROOT_HASH,
11};
12use alloy_eips::{
13 calc_next_block_base_fee,
14 eip1559::BaseFeeParams,
15 eip2718::{Decodable2718, Eip2718Result, Encodable2718, WithEncoded},
16 eip4844::BlobTransactionSidecar,
17 eip4895::{Withdrawal, Withdrawals},
18 eip7594::{BlobTransactionSidecarEip7594, CELLS_PER_EXT_BLOB},
19 eip7685::Requests,
20 eip7840::BlobParams,
21 BlockNumHash,
22};
23use alloy_primitives::{Address, Bloom, Bytes, Sealable, B256, B64, U256};
24use core::iter::{FromIterator, IntoIterator};
25
26pub type ExecutionPayloadBodiesV1 = Vec<Option<ExecutionPayloadBodyV1>>;
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
33pub struct PayloadId(pub B64);
34
35impl PayloadId {
38 pub fn new(id: [u8; 8]) -> Self {
40 Self(B64::from(id))
41 }
42}
43
44impl core::fmt::Display for PayloadId {
45 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46 self.0.fmt(f)
47 }
48}
49
50impl From<B64> for PayloadId {
51 fn from(value: B64) -> Self {
52 Self(value)
53 }
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
69#[cfg_attr(feature = "serde", serde(untagged))]
70#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
71pub enum ExecutionPayloadFieldV2 {
72 V1(ExecutionPayloadV1),
74 V2(ExecutionPayloadV2),
76}
77
78impl ExecutionPayloadFieldV2 {
79 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
89 where
90 T: Encodable2718,
91 H: BlockHeader + Sealable,
92 {
93 Self::from_block_unchecked(block.hash_slow(), block)
94 }
95
96 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
105 where
106 T: Encodable2718,
107 H: BlockHeader,
108 {
109 if block.body.withdrawals.is_some() {
110 Self::V2(ExecutionPayloadV2::from_block_unchecked(block_hash, block))
111 } else {
112 Self::V1(ExecutionPayloadV1::from_block_unchecked(block_hash, block))
113 }
114 }
115
116 pub fn into_v1_payload(self) -> ExecutionPayloadV1 {
118 match self {
119 Self::V1(payload) => payload,
120 Self::V2(payload) => payload.payload_inner,
121 }
122 }
123
124 pub fn into_payload(self) -> ExecutionPayload {
126 match self {
127 Self::V1(payload) => ExecutionPayload::V1(payload),
128 Self::V2(payload) => ExecutionPayload::V2(payload),
129 }
130 }
131}
132
133#[derive(Clone, Debug, PartialEq, Eq)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[cfg_attr(feature = "serde", serde(rename_all = "camelCase", deny_unknown_fields))]
137#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
138pub struct ExecutionPayloadInputV2 {
139 #[cfg_attr(feature = "serde", serde(flatten))]
141 pub execution_payload: ExecutionPayloadV1,
142 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
144 pub withdrawals: Option<Vec<Withdrawal>>,
145}
146
147impl ExecutionPayloadInputV2 {
148 pub fn into_payload(self) -> ExecutionPayload {
150 match self.withdrawals {
151 Some(withdrawals) => ExecutionPayload::V2(ExecutionPayloadV2 {
152 payload_inner: self.execution_payload,
153 withdrawals,
154 }),
155 None => ExecutionPayload::V1(self.execution_payload),
156 }
157 }
158}
159
160impl From<ExecutionPayloadInputV2> for ExecutionPayload {
161 fn from(input: ExecutionPayloadInputV2) -> Self {
162 input.into_payload()
163 }
164}
165
166#[derive(Clone, Debug, PartialEq, Eq)]
172#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
173#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
174#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
175pub struct ExecutionPayloadEnvelopeV2 {
176 pub execution_payload: ExecutionPayloadFieldV2,
184 pub block_value: U256,
186}
187
188impl ExecutionPayloadEnvelopeV2 {
189 pub fn into_v1_payload(self) -> ExecutionPayloadV1 {
191 self.execution_payload.into_v1_payload()
192 }
193}
194
195#[derive(Clone, Debug, PartialEq, Eq)]
201#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
202#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
203#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
204pub struct ExecutionPayloadEnvelopeV3 {
205 pub execution_payload: ExecutionPayloadV3,
207 pub block_value: U256,
209 pub blobs_bundle: BlobsBundleV1,
211 pub should_override_builder: bool,
214}
215
216#[derive(Clone, Debug, PartialEq, Eq, derive_more::Deref, derive_more::DerefMut)]
222#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
223#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
224#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
225pub struct ExecutionPayloadEnvelopeV4 {
226 #[deref]
228 #[deref_mut]
229 #[cfg_attr(feature = "serde", serde(flatten))]
230 pub envelope_inner: ExecutionPayloadEnvelopeV3,
231
232 pub execution_requests: Requests,
236}
237
238#[derive(Clone, Debug, PartialEq, Eq)]
244#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
245#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
246#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
247pub struct ExecutionPayloadEnvelopeV5 {
248 pub execution_payload: ExecutionPayloadV3,
250 pub block_value: U256,
252 pub blobs_bundle: BlobsBundleV2,
255 pub should_override_builder: bool,
258 pub execution_requests: Requests,
262}
263
264#[derive(Clone, Debug, PartialEq, Eq)]
268#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
269#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
270#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
271#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
272pub struct ExecutionPayloadV1 {
273 pub parent_hash: B256,
275 pub fee_recipient: Address,
277 pub state_root: B256,
279 pub receipts_root: B256,
281 pub logs_bloom: Bloom,
283 pub prev_randao: B256,
285 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
287 pub block_number: u64,
288 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
290 pub gas_limit: u64,
291 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
293 pub gas_used: u64,
294 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
296 pub timestamp: u64,
297 pub extra_data: Bytes,
299 pub base_fee_per_gas: U256,
301 pub block_hash: B256,
303 pub transactions: Vec<Bytes>,
305}
306
307impl ExecutionPayloadV1 {
308 pub const fn block_num_hash(&self) -> BlockNumHash {
310 BlockNumHash::new(self.block_number, self.block_hash)
311 }
312
313 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
315 self.try_into_block_with(|tx| {
316 T::decode_2718_exact(tx.as_ref())
317 .map_err(alloy_rlp::Error::from)
318 .map_err(PayloadError::from)
319 })
320 }
321
322 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
324 where
325 F: FnMut(Bytes) -> Result<T, E>,
326 E: Into<PayloadError>,
327 {
328 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
329 }
330
331 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
336 if self.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
337 return Err(PayloadError::ExtraData(self.extra_data));
338 }
339
340 let transactions_root =
342 alloy_consensus::proofs::ordered_trie_root_encoded(&self.transactions);
343
344 let header = Header {
345 parent_hash: self.parent_hash,
346 beneficiary: self.fee_recipient,
347 state_root: self.state_root,
348 transactions_root,
349 receipts_root: self.receipts_root,
350 withdrawals_root: None,
351 logs_bloom: self.logs_bloom,
352 number: self.block_number,
353 gas_limit: self.gas_limit,
354 gas_used: self.gas_used,
355 timestamp: self.timestamp,
356 mix_hash: self.prev_randao,
357 base_fee_per_gas: Some(
362 self.base_fee_per_gas
363 .try_into()
364 .map_err(|_| PayloadError::BaseFee(self.base_fee_per_gas))?,
365 ),
366 blob_gas_used: None,
367 excess_blob_gas: None,
368 parent_beacon_block_root: None,
369 requests_hash: None,
370 extra_data: self.extra_data,
371 ommers_hash: EMPTY_OMMER_ROOT_HASH,
373 difficulty: Default::default(),
374 nonce: Default::default(),
375 };
376
377 Ok(Block {
378 header,
379 body: BlockBody { transactions: self.transactions, ommers: vec![], withdrawals: None },
380 })
381 }
382
383 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
387 where
388 T: Encodable2718,
389 H: BlockHeader + Sealable,
390 {
391 Self::from_block_unchecked(block.header.hash_slow(), block)
392 }
393
394 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
396 where
397 T: Encodable2718,
398 H: BlockHeader,
399 {
400 let transactions =
401 block.body.transactions().map(|tx| tx.encoded_2718().into()).collect::<Vec<_>>();
402 Self {
403 parent_hash: block.parent_hash(),
404 fee_recipient: block.beneficiary(),
405 state_root: block.state_root(),
406 receipts_root: block.receipts_root(),
407 logs_bloom: block.logs_bloom(),
408 prev_randao: block.mix_hash().unwrap_or_default(),
409 block_number: block.number(),
410 gas_limit: block.gas_limit(),
411 gas_used: block.gas_used(),
412 timestamp: block.timestamp(),
413 base_fee_per_gas: U256::from(block.base_fee_per_gas().unwrap_or_default()),
414 extra_data: block.header.extra_data().clone(),
415 block_hash,
416 transactions,
417 }
418 }
419
420 pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option<u64> {
424 Some(calc_next_block_base_fee(
425 self.gas_used,
426 self.gas_limit,
427 self.base_fee_per_gas.try_into().ok()?,
428 base_fee_params,
429 ))
430 }
431}
432
433impl<T: Decodable2718> TryFrom<ExecutionPayloadV1> for Block<T> {
434 type Error = PayloadError;
435
436 fn try_from(value: ExecutionPayloadV1) -> Result<Self, Self::Error> {
437 value.try_into_block()
438 }
439}
440
441#[derive(Clone, Debug, PartialEq, Eq)]
445#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
446#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
447#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
448pub struct ExecutionPayloadV2 {
449 #[cfg_attr(feature = "serde", serde(flatten))]
451 pub payload_inner: ExecutionPayloadV1,
452
453 pub withdrawals: Vec<Withdrawal>,
456}
457
458impl ExecutionPayloadV2 {
459 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
467 where
468 T: Encodable2718,
469 H: BlockHeader + Sealable,
470 {
471 Self::from_block_unchecked(block.header.hash_slow(), block)
472 }
473
474 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
480 where
481 T: Encodable2718,
482 H: BlockHeader,
483 {
484 Self {
485 withdrawals: block
486 .body
487 .withdrawals
488 .clone()
489 .map(Withdrawals::into_inner)
490 .unwrap_or_default(),
491 payload_inner: ExecutionPayloadV1::from_block_unchecked(block_hash, block),
492 }
493 }
494
495 pub const fn timestamp(&self) -> u64 {
497 self.payload_inner.timestamp
498 }
499
500 pub fn into_payload_input_v2(self, is_shanghai_active: bool) -> ExecutionPayloadInputV2 {
508 ExecutionPayloadInputV2 {
509 execution_payload: self.payload_inner,
510 withdrawals: is_shanghai_active.then_some(self.withdrawals),
511 }
512 }
513
514 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
521 self.try_into_block_with(|tx| {
522 T::decode_2718_exact(tx.as_ref())
523 .map_err(alloy_rlp::Error::from)
524 .map_err(PayloadError::from)
525 })
526 }
527
528 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
532 where
533 F: FnMut(Bytes) -> Result<T, E>,
534 E: Into<PayloadError>,
535 {
536 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
537 }
538
539 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
544 let mut base_sealed_block = self.payload_inner.into_block_raw()?;
545 let withdrawals_root =
546 alloy_consensus::proofs::calculate_withdrawals_root(&self.withdrawals);
547 base_sealed_block.body.withdrawals = Some(self.withdrawals.into());
548 base_sealed_block.header.withdrawals_root = Some(withdrawals_root);
549 Ok(base_sealed_block)
550 }
551}
552
553impl<T: Decodable2718> TryFrom<ExecutionPayloadV2> for Block<T> {
554 type Error = PayloadError;
555
556 fn try_from(value: ExecutionPayloadV2) -> Result<Self, Self::Error> {
557 value.try_into_block()
558 }
559}
560
561#[cfg(feature = "ssz")]
562impl ssz::Decode for ExecutionPayloadV2 {
563 fn is_ssz_fixed_len() -> bool {
564 false
565 }
566
567 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
568 let mut builder = ssz::SszDecoderBuilder::new(bytes);
569
570 builder.register_type::<B256>()?;
571 builder.register_type::<Address>()?;
572 builder.register_type::<B256>()?;
573 builder.register_type::<B256>()?;
574 builder.register_type::<Bloom>()?;
575 builder.register_type::<B256>()?;
576 builder.register_type::<u64>()?;
577 builder.register_type::<u64>()?;
578 builder.register_type::<u64>()?;
579 builder.register_type::<u64>()?;
580 builder.register_type::<Bytes>()?;
581 builder.register_type::<U256>()?;
582 builder.register_type::<B256>()?;
583 builder.register_type::<Vec<Bytes>>()?;
584 builder.register_type::<Vec<Withdrawal>>()?;
585
586 let mut decoder = builder.build()?;
587
588 Ok(Self {
589 payload_inner: ExecutionPayloadV1 {
590 parent_hash: decoder.decode_next()?,
591 fee_recipient: decoder.decode_next()?,
592 state_root: decoder.decode_next()?,
593 receipts_root: decoder.decode_next()?,
594 logs_bloom: decoder.decode_next()?,
595 prev_randao: decoder.decode_next()?,
596 block_number: decoder.decode_next()?,
597 gas_limit: decoder.decode_next()?,
598 gas_used: decoder.decode_next()?,
599 timestamp: decoder.decode_next()?,
600 extra_data: decoder.decode_next()?,
601 base_fee_per_gas: decoder.decode_next()?,
602 block_hash: decoder.decode_next()?,
603 transactions: decoder.decode_next()?,
604 },
605 withdrawals: decoder.decode_next()?,
606 })
607 }
608}
609
610#[cfg(feature = "ssz")]
611impl ssz::Encode for ExecutionPayloadV2 {
612 fn is_ssz_fixed_len() -> bool {
613 false
614 }
615
616 fn ssz_append(&self, buf: &mut Vec<u8>) {
617 let offset = <B256 as ssz::Encode>::ssz_fixed_len() * 5
618 + <Address as ssz::Encode>::ssz_fixed_len()
619 + <Bloom as ssz::Encode>::ssz_fixed_len()
620 + <u64 as ssz::Encode>::ssz_fixed_len() * 4
621 + <U256 as ssz::Encode>::ssz_fixed_len()
622 + ssz::BYTES_PER_LENGTH_OFFSET * 3;
623
624 let mut encoder = ssz::SszEncoder::container(buf, offset);
625
626 encoder.append(&self.payload_inner.parent_hash);
627 encoder.append(&self.payload_inner.fee_recipient);
628 encoder.append(&self.payload_inner.state_root);
629 encoder.append(&self.payload_inner.receipts_root);
630 encoder.append(&self.payload_inner.logs_bloom);
631 encoder.append(&self.payload_inner.prev_randao);
632 encoder.append(&self.payload_inner.block_number);
633 encoder.append(&self.payload_inner.gas_limit);
634 encoder.append(&self.payload_inner.gas_used);
635 encoder.append(&self.payload_inner.timestamp);
636 encoder.append(&self.payload_inner.extra_data);
637 encoder.append(&self.payload_inner.base_fee_per_gas);
638 encoder.append(&self.payload_inner.block_hash);
639 encoder.append(&self.payload_inner.transactions);
640 encoder.append(&self.withdrawals);
641
642 encoder.finalize();
643 }
644
645 fn ssz_bytes_len(&self) -> usize {
646 <ExecutionPayloadV1 as ssz::Encode>::ssz_bytes_len(&self.payload_inner)
647 + ssz::BYTES_PER_LENGTH_OFFSET
648 + self.withdrawals.ssz_bytes_len()
649 }
650}
651
652#[derive(Clone, Debug, PartialEq, Eq)]
656#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
657#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
658#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
659pub struct ExecutionPayloadV3 {
660 #[cfg_attr(feature = "serde", serde(flatten))]
662 pub payload_inner: ExecutionPayloadV2,
663
664 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
667 pub blob_gas_used: u64,
668 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
671 pub excess_blob_gas: u64,
672}
673
674impl ExecutionPayloadV3 {
675 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
681 where
682 T: Encodable2718,
683 H: BlockHeader + Sealable,
684 {
685 Self::from_block_unchecked(block.hash_slow(), block)
686 }
687
688 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
692 where
693 T: Encodable2718,
694 H: BlockHeader,
695 {
696 Self {
697 blob_gas_used: block.blob_gas_used().unwrap_or_default(),
698 excess_blob_gas: block.excess_blob_gas().unwrap_or_default(),
699 payload_inner: ExecutionPayloadV2::from_block_unchecked(block_hash, block),
700 }
701 }
702
703 pub const fn withdrawals(&self) -> &Vec<Withdrawal> {
705 &self.payload_inner.withdrawals
706 }
707
708 pub const fn timestamp(&self) -> u64 {
710 self.payload_inner.payload_inner.timestamp
711 }
712
713 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
720 self.try_into_block_with(|tx| {
721 T::decode_2718_exact(tx.as_ref())
722 .map_err(alloy_rlp::Error::from)
723 .map_err(PayloadError::from)
724 })
725 }
726
727 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
731 where
732 F: FnMut(Bytes) -> Result<T, E>,
733 E: Into<PayloadError>,
734 {
735 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
736 }
737
738 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
743 let mut base_block = self.payload_inner.into_block_raw()?;
744
745 base_block.header.blob_gas_used = Some(self.blob_gas_used);
746 base_block.header.excess_blob_gas = Some(self.excess_blob_gas);
747
748 Ok(base_block)
749 }
750}
751
752impl<T: Decodable2718> TryFrom<ExecutionPayloadV3> for Block<T> {
753 type Error = PayloadError;
754
755 fn try_from(value: ExecutionPayloadV3) -> Result<Self, Self::Error> {
756 value.try_into_block()
757 }
758}
759
760#[cfg(feature = "ssz")]
761impl ssz::Decode for ExecutionPayloadV3 {
762 fn is_ssz_fixed_len() -> bool {
763 false
764 }
765
766 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
767 let mut builder = ssz::SszDecoderBuilder::new(bytes);
768
769 builder.register_type::<B256>()?;
770 builder.register_type::<Address>()?;
771 builder.register_type::<B256>()?;
772 builder.register_type::<B256>()?;
773 builder.register_type::<Bloom>()?;
774 builder.register_type::<B256>()?;
775 builder.register_type::<u64>()?;
776 builder.register_type::<u64>()?;
777 builder.register_type::<u64>()?;
778 builder.register_type::<u64>()?;
779 builder.register_type::<Bytes>()?;
780 builder.register_type::<U256>()?;
781 builder.register_type::<B256>()?;
782 builder.register_type::<Vec<Bytes>>()?;
783 builder.register_type::<Vec<Withdrawal>>()?;
784 builder.register_type::<u64>()?;
785 builder.register_type::<u64>()?;
786
787 let mut decoder = builder.build()?;
788
789 Ok(Self {
790 payload_inner: ExecutionPayloadV2 {
791 payload_inner: ExecutionPayloadV1 {
792 parent_hash: decoder.decode_next()?,
793 fee_recipient: decoder.decode_next()?,
794 state_root: decoder.decode_next()?,
795 receipts_root: decoder.decode_next()?,
796 logs_bloom: decoder.decode_next()?,
797 prev_randao: decoder.decode_next()?,
798 block_number: decoder.decode_next()?,
799 gas_limit: decoder.decode_next()?,
800 gas_used: decoder.decode_next()?,
801 timestamp: decoder.decode_next()?,
802 extra_data: decoder.decode_next()?,
803 base_fee_per_gas: decoder.decode_next()?,
804 block_hash: decoder.decode_next()?,
805 transactions: decoder.decode_next()?,
806 },
807 withdrawals: decoder.decode_next()?,
808 },
809 blob_gas_used: decoder.decode_next()?,
810 excess_blob_gas: decoder.decode_next()?,
811 })
812 }
813}
814
815#[cfg(feature = "ssz")]
816impl ssz::Encode for ExecutionPayloadV3 {
817 fn is_ssz_fixed_len() -> bool {
818 false
819 }
820
821 fn ssz_append(&self, buf: &mut Vec<u8>) {
822 let offset = <B256 as ssz::Encode>::ssz_fixed_len() * 5
823 + <Address as ssz::Encode>::ssz_fixed_len()
824 + <Bloom as ssz::Encode>::ssz_fixed_len()
825 + <u64 as ssz::Encode>::ssz_fixed_len() * 6
826 + <U256 as ssz::Encode>::ssz_fixed_len()
827 + ssz::BYTES_PER_LENGTH_OFFSET * 3;
828
829 let mut encoder = ssz::SszEncoder::container(buf, offset);
830
831 encoder.append(&self.payload_inner.payload_inner.parent_hash);
832 encoder.append(&self.payload_inner.payload_inner.fee_recipient);
833 encoder.append(&self.payload_inner.payload_inner.state_root);
834 encoder.append(&self.payload_inner.payload_inner.receipts_root);
835 encoder.append(&self.payload_inner.payload_inner.logs_bloom);
836 encoder.append(&self.payload_inner.payload_inner.prev_randao);
837 encoder.append(&self.payload_inner.payload_inner.block_number);
838 encoder.append(&self.payload_inner.payload_inner.gas_limit);
839 encoder.append(&self.payload_inner.payload_inner.gas_used);
840 encoder.append(&self.payload_inner.payload_inner.timestamp);
841 encoder.append(&self.payload_inner.payload_inner.extra_data);
842 encoder.append(&self.payload_inner.payload_inner.base_fee_per_gas);
843 encoder.append(&self.payload_inner.payload_inner.block_hash);
844 encoder.append(&self.payload_inner.payload_inner.transactions);
845 encoder.append(&self.payload_inner.withdrawals);
846 encoder.append(&self.blob_gas_used);
847 encoder.append(&self.excess_blob_gas);
848
849 encoder.finalize();
850 }
851
852 fn ssz_bytes_len(&self) -> usize {
853 <ExecutionPayloadV2 as ssz::Encode>::ssz_bytes_len(&self.payload_inner)
854 + <u64 as ssz::Encode>::ssz_fixed_len() * 2
855 }
856}
857
858#[derive(Clone, Debug, Default, PartialEq, Eq)]
860#[cfg_attr(feature = "serde", derive(serde::Serialize))]
861#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
862#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
863pub struct BlobsBundleV1 {
864 pub commitments: Vec<alloy_consensus::Bytes48>,
866 pub proofs: Vec<alloy_consensus::Bytes48>,
868 pub blobs: Vec<alloy_consensus::Blob>,
870}
871
872#[cfg(feature = "serde")]
873impl<'de> serde::Deserialize<'de> for BlobsBundleV1 {
874 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
875 where
876 D: serde::Deserializer<'de>,
877 {
878 #[derive(serde::Deserialize)]
879 struct BlobsBundleRaw {
880 commitments: Vec<alloy_consensus::Bytes48>,
881 proofs: Vec<alloy_consensus::Bytes48>,
882 blobs: Vec<alloy_consensus::Blob>,
883 }
884 let raw = BlobsBundleRaw::deserialize(deserializer)?;
885
886 if raw.proofs.len() == raw.commitments.len() && raw.proofs.len() == raw.blobs.len() {
887 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
888 } else {
889 Err(serde::de::Error::invalid_length(
890 raw.proofs.len(),
891 &format!("{}", raw.commitments.len()).as_str(),
892 ))
893 }
894 }
895}
896
897impl BlobsBundleV1 {
898 pub fn new(sidecars: impl IntoIterator<Item = BlobTransactionSidecar>) -> Self {
902 let (commitments, proofs, blobs) = sidecars.into_iter().fold(
903 (Vec::new(), Vec::new(), Vec::new()),
904 |(mut commitments, mut proofs, mut blobs), sidecar| {
905 commitments.extend(sidecar.commitments);
906 proofs.extend(sidecar.proofs);
907 blobs.extend(sidecar.blobs);
908 (commitments, proofs, blobs)
909 },
910 );
911 Self { commitments, proofs, blobs }
912 }
913
914 pub fn empty() -> Self {
919 Self::default()
920 }
921
922 pub fn take(&mut self, len: usize) -> (Vec<Bytes48>, Vec<Bytes48>, Vec<Blob>) {
928 (
929 self.commitments.drain(0..len).collect(),
930 self.proofs.drain(0..len).collect(),
931 self.blobs.drain(0..len).collect(),
932 )
933 }
934
935 pub fn pop_sidecar(&mut self, len: usize) -> BlobTransactionSidecar {
941 let (commitments, proofs, blobs) = self.take(len);
942 BlobTransactionSidecar { commitments, proofs, blobs }
943 }
944
945 #[cfg(feature = "kzg")]
952 pub fn try_into_sidecar(
953 self,
954 ) -> Result<BlobTransactionSidecar, alloy_consensus::error::ValueError<Self>> {
955 if self.commitments.len() != self.proofs.len() || self.commitments.len() != self.blobs.len()
956 {
957 return Err(alloy_consensus::error::ValueError::new(self, "length mismatch"));
958 }
959
960 let Self { commitments, proofs, blobs } = self;
961 Ok(BlobTransactionSidecar { blobs, commitments, proofs })
962 }
963}
964
965impl From<Vec<BlobTransactionSidecar>> for BlobsBundleV1 {
966 fn from(sidecars: Vec<BlobTransactionSidecar>) -> Self {
967 Self::new(sidecars)
968 }
969}
970
971impl FromIterator<BlobTransactionSidecar> for BlobsBundleV1 {
972 fn from_iter<T: IntoIterator<Item = BlobTransactionSidecar>>(iter: T) -> Self {
973 Self::new(iter)
974 }
975}
976
977#[cfg(feature = "kzg")]
978impl TryFrom<BlobsBundleV1> for BlobTransactionSidecar {
979 type Error = alloy_consensus::error::ValueError<BlobsBundleV1>;
980
981 fn try_from(value: BlobsBundleV1) -> Result<Self, Self::Error> {
982 value.try_into_sidecar()
983 }
984}
985
986#[derive(Clone, Debug, Default, PartialEq, Eq)]
988#[cfg_attr(feature = "serde", derive(serde::Serialize))]
989#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode))]
990#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
991pub struct BlobsBundleV2 {
992 pub commitments: Vec<alloy_consensus::Bytes48>,
994 pub proofs: Vec<alloy_consensus::Bytes48>,
996 pub blobs: Vec<alloy_consensus::Blob>,
998}
999
1000#[cfg(feature = "serde")]
1001impl<'de> serde::Deserialize<'de> for BlobsBundleV2 {
1002 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1003 where
1004 D: serde::Deserializer<'de>,
1005 {
1006 #[derive(serde::Deserialize)]
1007 struct BlobsBundleRaw {
1008 commitments: Vec<alloy_consensus::Bytes48>,
1009 proofs: Vec<alloy_consensus::Bytes48>,
1010 blobs: Vec<alloy_consensus::Blob>,
1011 }
1012 let raw = BlobsBundleRaw::deserialize(deserializer)?;
1013
1014 if raw.proofs.len() == raw.blobs.len() * CELLS_PER_EXT_BLOB
1015 && raw.commitments.len() == raw.blobs.len()
1016 {
1017 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
1018 } else {
1019 Err(serde::de::Error::invalid_length(
1020 raw.proofs.len(),
1021 &format!("{}", raw.commitments.len() * CELLS_PER_EXT_BLOB).as_str(),
1022 ))
1023 }
1024 }
1025}
1026
1027#[cfg(feature = "ssz")]
1028impl ssz::Decode for BlobsBundleV2 {
1029 fn is_ssz_fixed_len() -> bool {
1030 false
1031 }
1032
1033 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
1034 #[derive(ssz_derive::Decode)]
1035 struct BlobsBundleRaw {
1036 commitments: Vec<alloy_consensus::Bytes48>,
1037 proofs: Vec<alloy_consensus::Bytes48>,
1038 blobs: Vec<alloy_consensus::Blob>,
1039 }
1040
1041 let raw = BlobsBundleRaw::from_ssz_bytes(bytes)?;
1042
1043 if raw.proofs.len() == raw.blobs.len() * CELLS_PER_EXT_BLOB
1044 && raw.commitments.len() == raw.blobs.len()
1045 {
1046 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
1047 } else {
1048 Err(ssz::DecodeError::BytesInvalid(
1049 format!(
1050 "Invalid BlobsBundleV2: expected {} proofs and {} commitments for {} blobs, got {} proofs and {} commitments",
1051 raw.blobs.len() * CELLS_PER_EXT_BLOB,
1052 raw.blobs.len(),
1053 raw.blobs.len(),
1054 raw.proofs.len(),
1055 raw.commitments.len()
1056 )
1057 ))
1058 }
1059 }
1060}
1061
1062impl BlobsBundleV2 {
1063 pub fn new(sidecars: impl IntoIterator<Item = BlobTransactionSidecarEip7594>) -> Self {
1067 let (commitments, proofs, blobs) = sidecars.into_iter().fold(
1068 (Vec::new(), Vec::new(), Vec::new()),
1069 |(mut commitments, mut proofs, mut blobs), sidecar| {
1070 commitments.extend(sidecar.commitments);
1071 proofs.extend(sidecar.cell_proofs);
1072 blobs.extend(sidecar.blobs);
1073 (commitments, proofs, blobs)
1074 },
1075 );
1076 Self { commitments, proofs, blobs }
1077 }
1078
1079 pub fn empty() -> Self {
1084 Self::default()
1085 }
1086
1087 pub fn take(&mut self, len: usize) -> (Vec<Bytes48>, Vec<Bytes48>, Vec<Blob>) {
1095 (
1096 self.commitments.drain(0..len).collect(),
1097 self.proofs.drain(0..len * CELLS_PER_EXT_BLOB).collect(),
1098 self.blobs.drain(0..len).collect(),
1099 )
1100 }
1101
1102 pub fn pop_sidecar(&mut self, len: usize) -> BlobTransactionSidecarEip7594 {
1108 let (commitments, cell_proofs, blobs) = self.take(len);
1109 BlobTransactionSidecarEip7594 { commitments, cell_proofs, blobs }
1110 }
1111
1112 #[cfg(feature = "kzg")]
1120 pub fn try_into_sidecar(
1121 self,
1122 ) -> Result<BlobTransactionSidecarEip7594, alloy_consensus::error::ValueError<Self>> {
1123 let expected_cell_proofs_len = self.blobs.len() * CELLS_PER_EXT_BLOB;
1124 if self.proofs.len() != expected_cell_proofs_len {
1125 let msg = format!(
1126 "cell proofs length mismatch, expected {expected_cell_proofs_len}, has {}",
1127 self.proofs.len()
1128 );
1129 return Err(alloy_consensus::error::ValueError::new(self, msg));
1130 }
1131
1132 if self.commitments.len() != self.blobs.len() {
1133 let msg = format!(
1134 "commitments length ({}) mismatch, expected blob length ({})",
1135 self.commitments.len(),
1136 self.blobs.len()
1137 );
1138 return Err(alloy_consensus::error::ValueError::new(self, msg));
1139 }
1140
1141 let Self { commitments, proofs, blobs } = self;
1142 Ok(BlobTransactionSidecarEip7594 { blobs, commitments, cell_proofs: proofs })
1143 }
1144}
1145
1146impl From<Vec<BlobTransactionSidecarEip7594>> for BlobsBundleV2 {
1147 fn from(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
1148 Self::new(sidecars)
1149 }
1150}
1151
1152impl FromIterator<BlobTransactionSidecarEip7594> for BlobsBundleV2 {
1153 fn from_iter<T: IntoIterator<Item = BlobTransactionSidecarEip7594>>(iter: T) -> Self {
1154 Self::new(iter)
1155 }
1156}
1157
1158#[cfg(feature = "kzg")]
1159impl TryFrom<BlobsBundleV2> for BlobTransactionSidecarEip7594 {
1160 type Error = alloy_consensus::error::ValueError<BlobsBundleV2>;
1161
1162 fn try_from(value: BlobsBundleV2) -> Result<Self, Self::Error> {
1163 value.try_into_sidecar()
1164 }
1165}
1166
1167#[derive(Clone, Debug, PartialEq, Eq)]
1170#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1171#[cfg_attr(feature = "serde", serde(untagged))]
1172#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1173pub enum ExecutionPayload {
1174 V1(ExecutionPayloadV1),
1176 V2(ExecutionPayloadV2),
1178 V3(ExecutionPayloadV3),
1180}
1181
1182impl ExecutionPayload {
1183 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> (Self, ExecutionPayloadSidecar)
1191 where
1192 T: Encodable2718 + Transaction,
1193 H: BlockHeader + Sealable,
1194 {
1195 Self::from_block_unchecked(block.hash_slow(), block)
1196 }
1197
1198 pub fn from_block_unchecked<T, H>(
1204 block_hash: B256,
1205 block: &Block<T, H>,
1206 ) -> (Self, ExecutionPayloadSidecar)
1207 where
1208 T: Encodable2718 + Transaction,
1209 H: BlockHeader,
1210 {
1211 let sidecar = ExecutionPayloadSidecar::from_block(block);
1212
1213 let execution_payload = if block.header.parent_beacon_block_root().is_some() {
1214 Self::V3(ExecutionPayloadV3::from_block_unchecked(block_hash, block))
1216 } else if block.body.withdrawals.is_some() {
1217 Self::V2(ExecutionPayloadV2::from_block_unchecked(block_hash, block))
1219 } else {
1220 Self::V1(ExecutionPayloadV1::from_block_unchecked(block_hash, block))
1222 };
1223
1224 (execution_payload, sidecar)
1225 }
1226
1227 pub fn try_into_block_with_sidecar<T: Decodable2718>(
1237 self,
1238 sidecar: &ExecutionPayloadSidecar,
1239 ) -> Result<Block<T>, PayloadError> {
1240 self.try_into_block_with_sidecar_with(sidecar, |tx| {
1241 T::decode_2718_exact(tx.as_ref())
1242 .map_err(alloy_rlp::Error::from)
1243 .map_err(PayloadError::from)
1244 })
1245 }
1246
1247 pub fn try_into_block_with_sidecar_with<T, F, E>(
1253 self,
1254 sidecar: &ExecutionPayloadSidecar,
1255 f: F,
1256 ) -> Result<Block<T>, PayloadError>
1257 where
1258 F: FnMut(Bytes) -> Result<T, E>,
1259 E: Into<PayloadError>,
1260 {
1261 self.into_block_with_sidecar_raw(sidecar)?.try_map_transactions(f).map_err(Into::into)
1262 }
1263
1264 pub fn into_block_with_sidecar_raw(
1269 self,
1270 sidecar: &ExecutionPayloadSidecar,
1271 ) -> Result<Block<Bytes>, PayloadError> {
1272 let mut base_block = self.into_block_raw()?;
1273 base_block.header.parent_beacon_block_root = sidecar.parent_beacon_block_root();
1274 base_block.header.requests_hash = sidecar.requests_hash();
1275 Ok(base_block)
1276 }
1277
1278 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
1287 self.try_into_block_with(|tx| {
1288 T::decode_2718_exact(tx.as_ref())
1289 .map_err(alloy_rlp::Error::from)
1290 .map_err(PayloadError::from)
1291 })
1292 }
1293
1294 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
1303 where
1304 F: FnMut(Bytes) -> Result<T, E>,
1305 E: Into<PayloadError>,
1306 {
1307 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
1308 }
1309
1310 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
1315 match self {
1316 Self::V1(payload) => payload.into_block_raw(),
1317 Self::V2(payload) => payload.into_block_raw(),
1318 Self::V3(payload) => payload.into_block_raw(),
1319 }
1320 }
1321
1322 pub const fn as_v1(&self) -> &ExecutionPayloadV1 {
1324 match self {
1325 Self::V1(payload) => payload,
1326 Self::V2(payload) => &payload.payload_inner,
1327 Self::V3(payload) => &payload.payload_inner.payload_inner,
1328 }
1329 }
1330
1331 pub const fn as_v1_mut(&mut self) -> &mut ExecutionPayloadV1 {
1333 match self {
1334 Self::V1(payload) => payload,
1335 Self::V2(payload) => &mut payload.payload_inner,
1336 Self::V3(payload) => &mut payload.payload_inner.payload_inner,
1337 }
1338 }
1339
1340 pub fn into_v1(self) -> ExecutionPayloadV1 {
1342 match self {
1343 Self::V1(payload) => payload,
1344 Self::V2(payload) => payload.payload_inner,
1345 Self::V3(payload) => payload.payload_inner.payload_inner,
1346 }
1347 }
1348
1349 pub const fn as_v2(&self) -> Option<&ExecutionPayloadV2> {
1351 match self {
1352 Self::V1(_) => None,
1353 Self::V2(payload) => Some(payload),
1354 Self::V3(payload) => Some(&payload.payload_inner),
1355 }
1356 }
1357
1358 pub const fn as_v2_mut(&mut self) -> Option<&mut ExecutionPayloadV2> {
1360 match self {
1361 Self::V1(_) => None,
1362 Self::V2(payload) => Some(payload),
1363 Self::V3(payload) => Some(&mut payload.payload_inner),
1364 }
1365 }
1366
1367 pub const fn as_v3(&self) -> Option<&ExecutionPayloadV3> {
1369 match self {
1370 Self::V1(_) | Self::V2(_) => None,
1371 Self::V3(payload) => Some(payload),
1372 }
1373 }
1374
1375 pub const fn as_v3_mut(&mut self) -> Option<&mut ExecutionPayloadV3> {
1377 match self {
1378 Self::V1(_) | Self::V2(_) => None,
1379 Self::V3(payload) => Some(payload),
1380 }
1381 }
1382
1383 pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
1385 match self.as_v2() {
1386 Some(payload) => Some(&payload.withdrawals),
1387 None => None,
1388 }
1389 }
1390
1391 pub const fn transactions(&self) -> &Vec<Bytes> {
1393 &self.as_v1().transactions
1394 }
1395
1396 pub const fn transactions_mut(&mut self) -> &mut Vec<Bytes> {
1398 &mut self.as_v1_mut().transactions
1399 }
1400
1401 pub fn header_info(&self) -> HeaderInfo {
1403 HeaderInfo {
1404 number: self.block_number(),
1405 beneficiary: self.fee_recipient(),
1406 timestamp: self.timestamp(),
1407 gas_limit: self.gas_limit(),
1408 base_fee_per_gas: Some(self.saturated_base_fee_per_gas()),
1409 excess_blob_gas: self.excess_blob_gas(),
1410 blob_gas_used: self.blob_gas_used(),
1411 difficulty: U256::ZERO,
1412 mix_hash: Some(self.prev_randao()),
1413 }
1414 }
1415
1416 pub fn saturated_base_fee_per_gas(&self) -> u64 {
1420 self.as_v1().base_fee_per_gas.saturating_to()
1421 }
1422
1423 pub fn blob_gas_used(&self) -> Option<u64> {
1425 self.as_v3().map(|payload| payload.blob_gas_used)
1426 }
1427
1428 pub fn excess_blob_gas(&self) -> Option<u64> {
1430 self.as_v3().map(|payload| payload.excess_blob_gas)
1431 }
1432
1433 pub const fn gas_limit(&self) -> u64 {
1435 self.as_v1().gas_limit
1436 }
1437
1438 pub const fn fee_recipient(&self) -> Address {
1440 self.as_v1().fee_recipient
1441 }
1442
1443 pub const fn timestamp(&self) -> u64 {
1445 self.as_v1().timestamp
1446 }
1447
1448 pub const fn parent_hash(&self) -> B256 {
1450 self.as_v1().parent_hash
1451 }
1452
1453 pub const fn block_hash(&self) -> B256 {
1455 self.as_v1().block_hash
1456 }
1457
1458 pub const fn block_number(&self) -> u64 {
1460 self.as_v1().block_number
1461 }
1462
1463 pub const fn block_num_hash(&self) -> BlockNumHash {
1465 self.as_v1().block_num_hash()
1466 }
1467
1468 pub const fn prev_randao(&self) -> B256 {
1470 self.as_v1().prev_randao
1471 }
1472
1473 pub fn blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
1477 Some(blob_params.calc_blob_fee(self.excess_blob_gas()?))
1478 }
1479
1480 pub fn next_block_blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
1486 Some(blob_params.calc_blob_fee(self.next_block_excess_blob_gas(blob_params)?))
1487 }
1488
1489 pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option<u64> {
1493 self.as_v1().next_block_base_fee(base_fee_params)
1494 }
1495
1496 pub fn next_block_excess_blob_gas(&self, blob_params: BlobParams) -> Option<u64> {
1501 Some(blob_params.next_block_excess_blob_gas_osaka(
1502 self.excess_blob_gas()?,
1503 self.blob_gas_used()?,
1504 self.as_v1().base_fee_per_gas.to(),
1505 ))
1506 }
1507
1508 pub fn maybe_next_block_excess_blob_gas(&self, blob_params: Option<BlobParams>) -> Option<u64> {
1513 self.next_block_excess_blob_gas(blob_params?)
1514 }
1515
1516 pub fn decoded_transactions<T: Decodable2718>(
1520 &self,
1521 ) -> impl Iterator<Item = Eip2718Result<T>> + '_ {
1522 self.transactions().iter().map(|tx_bytes| T::decode_2718_exact(tx_bytes.as_ref()))
1523 }
1524
1525 pub fn decoded_transactions_with_encoded<T: Decodable2718>(
1529 &self,
1530 ) -> impl Iterator<Item = Eip2718Result<WithEncoded<T>>> + '_ {
1531 self.transactions().iter().map(|tx_bytes| {
1532 T::decode_2718_exact(tx_bytes.as_ref()).map(|tx| WithEncoded::new(tx_bytes.clone(), tx))
1533 })
1534 }
1535
1536 pub fn recovered_transactions<T>(
1540 &self,
1541 ) -> impl Iterator<
1542 Item = Result<
1543 alloy_consensus::transaction::Recovered<T>,
1544 alloy_consensus::crypto::RecoveryError,
1545 >,
1546 > + '_
1547 where
1548 T: Decodable2718 + alloy_consensus::transaction::SignerRecoverable,
1549 {
1550 self.decoded_transactions::<T>().map(|res| {
1551 res.map_err(alloy_consensus::crypto::RecoveryError::from_source)
1552 .and_then(|tx| tx.try_into_recovered())
1553 })
1554 }
1555
1556 pub fn recovered_transactions_with_encoded<T>(
1562 &self,
1563 ) -> impl Iterator<
1564 Item = Result<
1565 WithEncoded<alloy_consensus::transaction::Recovered<T>>,
1566 alloy_consensus::crypto::RecoveryError,
1567 >,
1568 > + '_
1569 where
1570 T: Decodable2718 + alloy_consensus::transaction::SignerRecoverable,
1571 {
1572 self.transactions().iter().map(|tx_bytes| {
1573 T::decode_2718_exact(tx_bytes.as_ref())
1574 .map_err(alloy_consensus::crypto::RecoveryError::from_source)
1575 .and_then(|tx| {
1576 tx.try_into_recovered()
1577 .map(|recovered| WithEncoded::new(tx_bytes.clone(), recovered))
1578 })
1579 })
1580 }
1581}
1582
1583impl From<ExecutionPayloadV1> for ExecutionPayload {
1584 fn from(payload: ExecutionPayloadV1) -> Self {
1585 Self::V1(payload)
1586 }
1587}
1588
1589impl From<ExecutionPayloadV2> for ExecutionPayload {
1590 fn from(payload: ExecutionPayloadV2) -> Self {
1591 Self::V2(payload)
1592 }
1593}
1594
1595impl From<ExecutionPayloadFieldV2> for ExecutionPayload {
1596 fn from(payload: ExecutionPayloadFieldV2) -> Self {
1597 payload.into_payload()
1598 }
1599}
1600
1601impl From<ExecutionPayloadV3> for ExecutionPayload {
1602 fn from(payload: ExecutionPayloadV3) -> Self {
1603 Self::V3(payload)
1604 }
1605}
1606
1607impl<T: Decodable2718> TryFrom<ExecutionPayload> for Block<T> {
1608 type Error = PayloadError;
1609
1610 fn try_from(value: ExecutionPayload) -> Result<Self, Self::Error> {
1611 value.try_into_block()
1612 }
1613}
1614
1615#[cfg(feature = "serde")]
1617impl<'de> serde::Deserialize<'de> for ExecutionPayload {
1618 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1619 where
1620 D: serde::Deserializer<'de>,
1621 {
1622 use alloy_primitives::U64;
1623
1624 struct ExecutionPayloadVisitor;
1625
1626 impl<'de> serde::de::Visitor<'de> for ExecutionPayloadVisitor {
1627 type Value = ExecutionPayload;
1628
1629 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1630 formatter.write_str("a valid ExecutionPayload object")
1631 }
1632
1633 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1634 where
1635 A: serde::de::MapAccess<'de>,
1636 {
1637 #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
1639 #[cfg_attr(feature = "serde", serde(field_identifier, rename_all = "camelCase"))]
1640 enum Fields {
1641 ParentHash,
1642 FeeRecipient,
1643 StateRoot,
1644 ReceiptsRoot,
1645 LogsBloom,
1646 PrevRandao,
1647 BlockNumber,
1648 GasLimit,
1649 GasUsed,
1650 Timestamp,
1651 ExtraData,
1652 BaseFeePerGas,
1653 BlockHash,
1654 Transactions,
1655 Withdrawals,
1657 BlobGasUsed,
1659 ExcessBlobGas,
1660 }
1661
1662 let mut parent_hash = None;
1663 let mut fee_recipient = None;
1664 let mut state_root = None;
1665 let mut receipts_root = None;
1666 let mut logs_bloom = None;
1667 let mut prev_randao = None;
1668 let mut block_number = None;
1669 let mut gas_limit = None;
1670 let mut gas_used = None;
1671 let mut timestamp = None;
1672 let mut extra_data = None;
1673 let mut base_fee_per_gas = None;
1674 let mut block_hash = None;
1675 let mut transactions = None;
1676 let mut withdrawals = None;
1677 let mut blob_gas_used = None;
1678 let mut excess_blob_gas = None;
1679
1680 while let Some(key) = map.next_key()? {
1681 match key {
1682 Fields::ParentHash => parent_hash = Some(map.next_value()?),
1683 Fields::FeeRecipient => fee_recipient = Some(map.next_value()?),
1684 Fields::StateRoot => state_root = Some(map.next_value()?),
1685 Fields::ReceiptsRoot => receipts_root = Some(map.next_value()?),
1686 Fields::LogsBloom => logs_bloom = Some(map.next_value()?),
1687 Fields::PrevRandao => prev_randao = Some(map.next_value()?),
1688 Fields::BlockNumber => {
1689 let raw = map.next_value::<U64>()?;
1690 block_number = Some(raw.to());
1691 }
1692 Fields::GasLimit => {
1693 let raw = map.next_value::<U64>()?;
1694 gas_limit = Some(raw.to());
1695 }
1696 Fields::GasUsed => {
1697 let raw = map.next_value::<U64>()?;
1698 gas_used = Some(raw.to());
1699 }
1700 Fields::Timestamp => {
1701 let raw = map.next_value::<U64>()?;
1702 timestamp = Some(raw.to());
1703 }
1704 Fields::ExtraData => extra_data = Some(map.next_value()?),
1705 Fields::BaseFeePerGas => base_fee_per_gas = Some(map.next_value()?),
1706 Fields::BlockHash => block_hash = Some(map.next_value()?),
1707 Fields::Transactions => transactions = Some(map.next_value()?),
1708 Fields::Withdrawals => withdrawals = Some(map.next_value()?),
1709 Fields::BlobGasUsed => {
1710 let raw = map.next_value::<U64>()?;
1711 blob_gas_used = Some(raw.to());
1712 }
1713 Fields::ExcessBlobGas => {
1714 let raw = map.next_value::<U64>()?;
1715 excess_blob_gas = Some(raw.to());
1716 }
1717 }
1718 }
1719
1720 let parent_hash =
1721 parent_hash.ok_or_else(|| serde::de::Error::missing_field("parentHash"))?;
1722 let fee_recipient =
1723 fee_recipient.ok_or_else(|| serde::de::Error::missing_field("feeRecipient"))?;
1724 let state_root =
1725 state_root.ok_or_else(|| serde::de::Error::missing_field("stateRoot"))?;
1726 let receipts_root =
1727 receipts_root.ok_or_else(|| serde::de::Error::missing_field("receiptsRoot"))?;
1728 let logs_bloom =
1729 logs_bloom.ok_or_else(|| serde::de::Error::missing_field("logsBloom"))?;
1730 let prev_randao =
1731 prev_randao.ok_or_else(|| serde::de::Error::missing_field("prevRandao"))?;
1732 let block_number =
1733 block_number.ok_or_else(|| serde::de::Error::missing_field("blockNumber"))?;
1734 let gas_limit =
1735 gas_limit.ok_or_else(|| serde::de::Error::missing_field("gasLimit"))?;
1736 let gas_used =
1737 gas_used.ok_or_else(|| serde::de::Error::missing_field("gasUsed"))?;
1738 let timestamp =
1739 timestamp.ok_or_else(|| serde::de::Error::missing_field("timestamp"))?;
1740 let extra_data =
1741 extra_data.ok_or_else(|| serde::de::Error::missing_field("extraData"))?;
1742 let base_fee_per_gas = base_fee_per_gas
1743 .ok_or_else(|| serde::de::Error::missing_field("baseFeePerGas"))?;
1744 let block_hash =
1745 block_hash.ok_or_else(|| serde::de::Error::missing_field("blockHash"))?;
1746 let transactions =
1747 transactions.ok_or_else(|| serde::de::Error::missing_field("transactions"))?;
1748
1749 let v1 = ExecutionPayloadV1 {
1750 parent_hash,
1751 fee_recipient,
1752 state_root,
1753 receipts_root,
1754 logs_bloom,
1755 prev_randao,
1756 block_number,
1757 gas_limit,
1758 gas_used,
1759 timestamp,
1760 extra_data,
1761 base_fee_per_gas,
1762 block_hash,
1763 transactions,
1764 };
1765
1766 let Some(withdrawals) = withdrawals else {
1767 return if blob_gas_used.is_none() && excess_blob_gas.is_none() {
1768 Ok(ExecutionPayload::V1(v1))
1769 } else {
1770 Err(serde::de::Error::custom("invalid enum variant"))
1771 };
1772 };
1773
1774 if let (Some(blob_gas_used), Some(excess_blob_gas)) =
1775 (blob_gas_used, excess_blob_gas)
1776 {
1777 return Ok(ExecutionPayload::V3(ExecutionPayloadV3 {
1778 payload_inner: ExecutionPayloadV2 { payload_inner: v1, withdrawals },
1779 blob_gas_used,
1780 excess_blob_gas,
1781 }));
1782 }
1783
1784 if blob_gas_used.is_some() || excess_blob_gas.is_some() {
1786 return Err(serde::de::Error::custom("invalid enum variant"));
1787 }
1788
1789 Ok(ExecutionPayload::V2(ExecutionPayloadV2 { payload_inner: v1, withdrawals }))
1790 }
1791 }
1792
1793 const FIELDS: &[&str] = &[
1794 "parentHash",
1795 "feeRecipient",
1796 "stateRoot",
1797 "receiptsRoot",
1798 "logsBloom",
1799 "prevRandao",
1800 "blockNumber",
1801 "gasLimit",
1802 "gasUsed",
1803 "timestamp",
1804 "extraData",
1805 "baseFeePerGas",
1806 "blockHash",
1807 "transactions",
1808 "withdrawals",
1809 "blobGasUsed",
1810 "excessBlobGas",
1811 ];
1812 deserializer.deserialize_struct("ExecutionPayload", FIELDS, ExecutionPayloadVisitor)
1813 }
1814}
1815
1816#[derive(Clone, Debug, PartialEq, Eq)]
1820#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1821#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1822pub struct ExecutionPayloadBodyV1 {
1823 pub transactions: Vec<Bytes>,
1825 pub withdrawals: Option<Vec<Withdrawal>>,
1829}
1830
1831impl ExecutionPayloadBodyV1 {
1832 pub fn new<'a, T>(
1834 withdrawals: Option<Withdrawals>,
1835 transactions: impl IntoIterator<Item = &'a T>,
1836 ) -> Self
1837 where
1838 T: Encodable2718 + 'a,
1839 {
1840 Self {
1841 transactions: transactions.into_iter().map(|tx| tx.encoded_2718().into()).collect(),
1842 withdrawals: withdrawals.map(Withdrawals::into_inner),
1843 }
1844 }
1845
1846 pub fn from_block<T: Encodable2718, H>(block: Block<T, H>) -> Self {
1848 Self::new(block.body.withdrawals.clone(), block.body.transactions())
1849 }
1850}
1851
1852impl<T: Encodable2718, H> From<Block<T, H>> for ExecutionPayloadBodyV1 {
1853 fn from(value: Block<T, H>) -> Self {
1854 Self::from_block(value)
1855 }
1856}
1857
1858#[derive(Clone, Debug, Default, PartialEq, Eq)]
1861#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1862#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1863#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1864pub struct PayloadAttributes {
1865 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
1867 pub timestamp: u64,
1868 pub prev_randao: B256,
1870 pub suggested_fee_recipient: Address,
1872 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1875 pub withdrawals: Option<Vec<Withdrawal>>,
1876 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
1880 pub parent_beacon_block_root: Option<B256>,
1881}
1882
1883#[derive(Clone, Debug, PartialEq, Eq)]
1885#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
1886#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
1887#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1888pub struct PayloadStatus {
1889 #[cfg_attr(feature = "serde", serde(flatten))]
1891 pub status: PayloadStatusEnum,
1892 pub latest_valid_hash: Option<B256>,
1894}
1895
1896impl PayloadStatus {
1897 pub const fn new(status: PayloadStatusEnum, latest_valid_hash: Option<B256>) -> Self {
1899 Self { status, latest_valid_hash }
1900 }
1901
1902 pub const fn from_status(status: PayloadStatusEnum) -> Self {
1904 Self { status, latest_valid_hash: None }
1905 }
1906
1907 pub const fn with_latest_valid_hash(mut self, latest_valid_hash: B256) -> Self {
1909 self.latest_valid_hash = Some(latest_valid_hash);
1910 self
1911 }
1912
1913 pub const fn maybe_latest_valid_hash(mut self, latest_valid_hash: Option<B256>) -> Self {
1915 self.latest_valid_hash = latest_valid_hash;
1916 self
1917 }
1918
1919 pub const fn is_syncing(&self) -> bool {
1921 self.status.is_syncing()
1922 }
1923
1924 pub const fn is_valid(&self) -> bool {
1926 self.status.is_valid()
1927 }
1928
1929 pub const fn is_invalid(&self) -> bool {
1931 self.status.is_invalid()
1932 }
1933}
1934
1935impl core::fmt::Display for PayloadStatus {
1936 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1937 write!(
1938 f,
1939 "PayloadStatus {{ status: {}, latestValidHash: {:?} }}",
1940 self.status, self.latest_valid_hash
1941 )
1942 }
1943}
1944
1945#[cfg(feature = "serde")]
1946impl serde::Serialize for PayloadStatus {
1947 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1948 where
1949 S: serde::Serializer,
1950 {
1951 use serde::ser::SerializeMap;
1952 let mut map = serializer.serialize_map(Some(3))?;
1953 map.serialize_entry("status", self.status.as_str())?;
1954 map.serialize_entry("latestValidHash", &self.latest_valid_hash)?;
1955 map.serialize_entry("validationError", &self.status.validation_error())?;
1956 map.end()
1957 }
1958}
1959
1960impl From<PayloadError> for PayloadStatusEnum {
1961 fn from(error: PayloadError) -> Self {
1962 Self::Invalid { validation_error: error.to_string() }
1963 }
1964}
1965
1966#[derive(Clone, Debug, PartialEq, Eq)]
1968#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1969#[cfg_attr(feature = "serde", serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE"))]
1970#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1971pub enum PayloadStatusEnum {
1972 Valid,
1976
1977 Invalid {
1981 #[cfg_attr(feature = "serde", serde(rename = "validationError"))]
1983 validation_error: String,
1984 },
1985
1986 Syncing,
1990
1991 Accepted,
1994}
1995
1996impl PayloadStatusEnum {
1997 pub const fn as_str(&self) -> &'static str {
1999 match self {
2000 Self::Valid => "VALID",
2001 Self::Invalid { .. } => "INVALID",
2002 Self::Syncing => "SYNCING",
2003 Self::Accepted => "ACCEPTED",
2004 }
2005 }
2006
2007 pub fn validation_error(&self) -> Option<&str> {
2009 match self {
2010 Self::Invalid { validation_error } => Some(validation_error),
2011 _ => None,
2012 }
2013 }
2014
2015 pub const fn is_syncing(&self) -> bool {
2017 matches!(self, Self::Syncing)
2018 }
2019
2020 pub const fn is_valid(&self) -> bool {
2022 matches!(self, Self::Valid)
2023 }
2024
2025 pub const fn is_invalid(&self) -> bool {
2027 matches!(self, Self::Invalid { .. })
2028 }
2029}
2030
2031impl core::fmt::Display for PayloadStatusEnum {
2032 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2033 match self {
2034 Self::Invalid { validation_error } => {
2035 f.write_str(self.as_str())?;
2036 f.write_str(": ")?;
2037 f.write_str(validation_error.as_str())
2038 }
2039 _ => f.write_str(self.as_str()),
2040 }
2041 }
2042}
2043
2044#[derive(Debug, Clone)]
2047#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2048#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2049pub struct ExecutionData {
2050 pub payload: ExecutionPayload,
2052 pub sidecar: ExecutionPayloadSidecar,
2054}
2055
2056impl ExecutionData {
2057 pub const fn new(payload: ExecutionPayload, sidecar: ExecutionPayloadSidecar) -> Self {
2059 Self { payload, sidecar }
2060 }
2061
2062 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
2071 where
2072 T: Encodable2718 + Transaction,
2073 H: BlockHeader,
2074 {
2075 let (payload, sidecar) = ExecutionPayload::from_block_unchecked(block_hash, block);
2076 Self::new(payload, sidecar)
2077 }
2078
2079 pub const fn parent_hash(&self) -> B256 {
2081 self.payload.parent_hash()
2082 }
2083
2084 pub const fn block_hash(&self) -> B256 {
2086 self.payload.block_hash()
2087 }
2088
2089 pub const fn block_number(&self) -> u64 {
2091 self.payload.block_number()
2092 }
2093
2094 pub fn parent_beacon_block_root(&self) -> Option<B256> {
2096 self.sidecar.parent_beacon_block_root()
2097 }
2098
2099 pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
2101 self.payload.withdrawals()
2102 }
2103
2104 pub fn try_into_block<T: Decodable2718>(
2114 self,
2115 ) -> Result<alloy_consensus::Block<T>, PayloadError> {
2116 self.try_into_block_with(|tx| {
2117 T::decode_2718_exact(tx.as_ref())
2118 .map_err(alloy_rlp::Error::from)
2119 .map_err(PayloadError::from)
2120 })
2121 }
2122
2123 pub fn try_into_block_with<T, F, E>(
2134 self,
2135 f: F,
2136 ) -> Result<alloy_consensus::Block<T>, PayloadError>
2137 where
2138 F: FnMut(Bytes) -> Result<T, E>,
2139 E: Into<PayloadError>,
2140 {
2141 self.payload.try_into_block_with_sidecar_with(&self.sidecar, f)
2142 }
2143
2144 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
2149 let mut base_block = self.payload.into_block_raw()?;
2150 base_block.header.parent_beacon_block_root = self.sidecar.parent_beacon_block_root();
2151 base_block.header.requests_hash = self.sidecar.requests_hash();
2152 Ok(base_block)
2153 }
2154}
2155
2156#[cfg(test)]
2157mod tests {
2158 use super::*;
2159 use crate::{CancunPayloadFields, PayloadValidationError};
2160 use alloc::vec;
2161 use alloy_consensus::TxEnvelope;
2162 use alloy_primitives::{b256, hex};
2163 use similar_asserts::assert_eq;
2164
2165 #[test]
2166 #[cfg(feature = "kzg")]
2167 fn convert_empty_bundle() {
2168 let bundle = BlobsBundleV1::default();
2169 let _sidecar = bundle.try_into_sidecar().unwrap();
2170 }
2171
2172 #[test]
2173 #[cfg(feature = "serde")]
2174 fn serde_blobsbundlev1_empty() {
2175 let blobs_bundle_v1 = BlobsBundleV1::empty();
2176
2177 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
2178 let deserialized: BlobsBundleV1 = serde_json::from_str(&serialized).unwrap();
2179 assert_eq!(deserialized, blobs_bundle_v1);
2180 }
2181
2182 #[test]
2183 #[cfg(feature = "serde")]
2184 #[cfg(not(debug_assertions))]
2185 fn serde_blobsbundlev1_not_empty_pass() {
2186 let blobs_bundle_v1 = BlobsBundleV1 {
2187 proofs: vec![Bytes48::default()],
2188 commitments: vec![Bytes48::default()],
2189 blobs: vec![Blob::default()],
2190 };
2191
2192 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
2193 let deserialized: BlobsBundleV1 = serde_json::from_str(&serialized).unwrap();
2194 assert_eq!(deserialized, blobs_bundle_v1);
2195 }
2196
2197 #[test]
2198 #[cfg(feature = "serde")]
2199 #[cfg(not(debug_assertions))]
2200 fn serde_blobsbundlev1_not_empty_fail() {
2201 let blobs_bundle_v1 = BlobsBundleV1 {
2202 proofs: vec![Bytes48::default(), Bytes48::default()],
2203 commitments: vec![Bytes48::default()],
2204 blobs: vec![Blob::default()],
2205 };
2206
2207 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
2208 let deserialized: Result<BlobsBundleV1, serde_json::Error> =
2209 serde_json::from_str(&serialized);
2210 assert!(deserialized.is_err(), "invalid length 2, expected commitments.len()");
2211 }
2212
2213 #[test]
2214 #[cfg(feature = "serde")]
2215 #[cfg(not(debug_assertions))]
2216 fn serde_blobsbundlev2_not_empty_pass() {
2217 let commitments = vec![Bytes48::default()];
2218
2219 let blobs_bundle_v2 = BlobsBundleV2 {
2220 proofs: vec![Bytes48::default(); commitments.len() * CELLS_PER_EXT_BLOB],
2221 commitments,
2222 blobs: vec![Blob::default()],
2223 };
2224
2225 let serialized = serde_json::to_string(&blobs_bundle_v2).unwrap();
2226 let deserialized: BlobsBundleV2 = serde_json::from_str(&serialized).unwrap();
2227 assert_eq!(deserialized, blobs_bundle_v2);
2228 }
2229
2230 #[test]
2231 #[cfg(feature = "serde")]
2232 #[cfg(not(debug_assertions))]
2233 fn serde_blobsbundlev2_not_empty_fail() {
2234 let blobs_bundle_v2 = BlobsBundleV2 {
2235 proofs: vec![Bytes48::default()],
2236 commitments: vec![Bytes48::default()],
2237 blobs: vec![],
2238 };
2239
2240 let serialized = serde_json::to_string(&blobs_bundle_v2).unwrap();
2241 let deserialized: Result<BlobsBundleV2, serde_json::Error> =
2242 serde_json::from_str(&serialized);
2243 assert!(deserialized.is_err());
2244 }
2245
2246 #[test]
2247 #[cfg(feature = "ssz")]
2248 #[cfg(not(debug_assertions))]
2249 fn ssz_blobsbundlev2_roundtrip() {
2250 let commitments = vec![Bytes48::default(), Bytes48::default()];
2251 let num_blobs = commitments.len();
2252
2253 let blobs_bundle_v2 = BlobsBundleV2 {
2254 commitments,
2255 proofs: vec![Bytes48::default(); num_blobs * CELLS_PER_EXT_BLOB],
2256 blobs: vec![Blob::default(); num_blobs],
2257 };
2258
2259 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
2260 let decoded: BlobsBundleV2 = ssz::Decode::from_ssz_bytes(&encoded).unwrap();
2261
2262 assert_eq!(decoded, blobs_bundle_v2);
2263 }
2264
2265 #[test]
2266 #[cfg(feature = "ssz")]
2267 #[cfg(not(debug_assertions))]
2268 fn ssz_blobsbundlev2_invalid_proofs_length() {
2269 let commitments = vec![Bytes48::default()];
2270
2271 let blobs_bundle_v2 = BlobsBundleV2 {
2272 commitments,
2273 proofs: vec![Bytes48::default(); 2],
2274 blobs: vec![Blob::default()],
2275 };
2276
2277 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
2278
2279 let result: Result<BlobsBundleV2, _> = ssz::Decode::from_ssz_bytes(&encoded);
2281 assert!(result.is_err());
2282 }
2283
2284 #[test]
2285 #[cfg(feature = "ssz")]
2286 #[cfg(not(debug_assertions))]
2287 fn ssz_blobsbundlev2_mismatched_commitments_blobs() {
2288 let blobs_bundle_v2 = BlobsBundleV2 {
2289 commitments: vec![Bytes48::default(), Bytes48::default()],
2290 proofs: vec![Bytes48::default(); CELLS_PER_EXT_BLOB],
2291 blobs: vec![Blob::default()],
2292 };
2293
2294 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
2295
2296 let result: Result<BlobsBundleV2, _> = ssz::Decode::from_ssz_bytes(&encoded);
2298 assert!(result.is_err());
2299 }
2300
2301 #[test]
2302 #[cfg(feature = "ssz")]
2303 fn ssz_blobsbundlev2_empty() {
2304 let blobs_bundle_v2 = BlobsBundleV2 { commitments: vec![], proofs: vec![], blobs: vec![] };
2305
2306 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
2307
2308 let decoded: BlobsBundleV2 = ssz::Decode::from_ssz_bytes(&encoded).unwrap();
2310 assert_eq!(decoded, blobs_bundle_v2);
2311 }
2312
2313 #[test]
2314 #[cfg(feature = "serde")]
2315 fn serde_payload_status() {
2316 let s = r#"{"status":"SYNCING","latestValidHash":null,"validationError":null}"#;
2317 let status: PayloadStatus = serde_json::from_str(s).unwrap();
2318 assert_eq!(status.status, PayloadStatusEnum::Syncing);
2319 assert!(status.latest_valid_hash.is_none());
2320 assert!(status.status.validation_error().is_none());
2321 assert_eq!(serde_json::to_string(&status).unwrap(), s);
2322
2323 let full = s;
2324 let s = r#"{"status":"SYNCING","latestValidHash":null}"#;
2325 let status: PayloadStatus = serde_json::from_str(s).unwrap();
2326 assert_eq!(status.status, PayloadStatusEnum::Syncing);
2327 assert!(status.latest_valid_hash.is_none());
2328 assert!(status.status.validation_error().is_none());
2329 assert_eq!(serde_json::to_string(&status).unwrap(), full);
2330 }
2331
2332 #[test]
2333 #[cfg(feature = "serde")]
2334 fn serde_payload_status_error_deserialize() {
2335 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"Failed to decode block"}"#;
2336 let q = PayloadStatus {
2337 latest_valid_hash: None,
2338 status: PayloadStatusEnum::Invalid {
2339 validation_error: "Failed to decode block".to_string(),
2340 },
2341 };
2342 assert_eq!(q, serde_json::from_str(s).unwrap());
2343
2344 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"links to previously rejected block"}"#;
2345 let q = PayloadStatus {
2346 latest_valid_hash: None,
2347 status: PayloadStatusEnum::Invalid {
2348 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2349 },
2350 };
2351 assert_eq!(q, serde_json::from_str(s).unwrap());
2352
2353 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"invalid block number"}"#;
2354 let q = PayloadStatus {
2355 latest_valid_hash: None,
2356 status: PayloadStatusEnum::Invalid {
2357 validation_error: PayloadValidationError::InvalidBlockNumber.to_string(),
2358 },
2359 };
2360 assert_eq!(q, serde_json::from_str(s).unwrap());
2361
2362 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":
2363 "invalid merkle root: (remote: 0x3f77fb29ce67436532fee970e1add8f5cc80e8878c79b967af53b1fd92a0cab7 local: 0x603b9628dabdaadb442a3bb3d7e0360efc110e1948472909230909f1690fed17)"}"#;
2364 let q = PayloadStatus {
2365 latest_valid_hash: None,
2366 status: PayloadStatusEnum::Invalid {
2367 validation_error: PayloadValidationError::InvalidStateRoot {
2368 remote: "0x3f77fb29ce67436532fee970e1add8f5cc80e8878c79b967af53b1fd92a0cab7"
2369 .parse()
2370 .unwrap(),
2371 local: "0x603b9628dabdaadb442a3bb3d7e0360efc110e1948472909230909f1690fed17"
2372 .parse()
2373 .unwrap(),
2374 }
2375 .to_string(),
2376 },
2377 };
2378 assert_eq!(q, serde_json::from_str(s).unwrap());
2379 }
2380
2381 #[test]
2382 #[cfg(feature = "serde")]
2383 fn serde_roundtrip_legacy_txs_payload_v1() {
2384 let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x44bb4b98c59dbb726f96ffceb5ee028dcbe35b9bba4f9ffd56aeebf8d1e4db62","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0x5655011482546f16b2312ef18e9fad03d6a52b1be95401aea884b222477f9e64","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"]}"#;
2386 let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap();
2387 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
2388
2389 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
2390 assert_eq!(any_payload, payload.into());
2391 }
2392
2393 #[test]
2394 #[cfg(feature = "serde")]
2395 fn serde_roundtrip_legacy_txs_payload_v3() {
2396 let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x44bb4b98c59dbb726f96ffceb5ee028dcbe35b9bba4f9ffd56aeebf8d1e4db62","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0x5655011482546f16b2312ef18e9fad03d6a52b1be95401aea884b222477f9e64","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"],"withdrawals":[],"blobGasUsed":"0xb10b","excessBlobGas":"0xb10b"}"#;
2398 let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap();
2399 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
2400
2401 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
2402 assert_eq!(any_payload, payload.into());
2403 }
2404
2405 #[test]
2406 #[cfg(feature = "serde")]
2407 fn serde_roundtrip_enveloped_txs_payload_v1() {
2408 let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x76a03cbcb7adce07fd284c61e4fa31e5e786175cefac54a29e46ec8efa28ea41","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x028111cb7d25918386a69656b3d17b2febe95fd0f11572c1a55c14f99fdfe3df","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0xa6f40ed042e61e88e76125dede8fff8026751ea14454b68fb534cea99f2b2a77","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"]}"#;
2410 let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap();
2411 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
2412
2413 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
2414 assert_eq!(any_payload, payload.into());
2415 }
2416
2417 #[test]
2418 #[cfg(feature = "serde")]
2419 fn serde_roundtrip_enveloped_txs_payload_v3() {
2420 let s = r#"{"parentHash":"0x67ead97eb79b47a1638659942384143f36ed44275d4182799875ab5a87324055","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x76a03cbcb7adce07fd284c61e4fa31e5e786175cefac54a29e46ec8efa28ea41","receiptsRoot":"0x4e3c608a9f2e129fccb91a1dae7472e78013b8e654bccc8d224ce3d63ae17006","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0x028111cb7d25918386a69656b3d17b2febe95fd0f11572c1a55c14f99fdfe3df","blockNumber":"0x1","gasLimit":"0x2fefd8","gasUsed":"0xa860","timestamp":"0x1235","extraData":"0x8b726574682f76302e312e30","baseFeePerGas":"0x342770c0","blockHash":"0xa6f40ed042e61e88e76125dede8fff8026751ea14454b68fb534cea99f2b2a77","transactions":["0xf865808506fc23ac00830124f8940000000000000000000000000000000000000316018032a044b25a8b9b247d01586b3d59c71728ff49c9b84928d9e7fa3377ead3b5570b5da03ceac696601ff7ee6f5fe8864e2998db9babdf5eeba1a0cd5b4d44b3fcbd181b"],"withdrawals":[],"blobGasUsed":"0xb10b","excessBlobGas":"0xb10b"}"#;
2422 let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap();
2423 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
2424
2425 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
2426 assert_eq!(any_payload, payload.into());
2427 }
2428
2429 #[test]
2430 #[cfg(feature = "serde")]
2431 fn serde_roundtrip_execution_payload_envelope_v3() {
2432 let response = r#"{"executionPayload":{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]},"shouldOverrideBuilder":false}"#;
2434 let envelope: ExecutionPayloadEnvelopeV3 = serde_json::from_str(response).unwrap();
2435 assert_eq!(serde_json::to_string(&envelope).unwrap(), response);
2436 }
2437
2438 #[test]
2439 #[cfg(feature = "serde")]
2440 fn serde_payload_input_enum_v3() {
2441 let response_v3 = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0"}"#;
2442
2443 let payload: ExecutionPayload = serde_json::from_str(response_v3).unwrap();
2444 assert!(payload.as_v3().is_some());
2445 assert_eq!(serde_json::to_string(&payload).unwrap(), response_v3);
2446
2447 let payload_v3: ExecutionPayloadV3 = serde_json::from_str(response_v3).unwrap();
2448 assert_eq!(payload.as_v3().unwrap(), &payload_v3);
2449 }
2450
2451 #[test]
2452 #[cfg(feature = "serde")]
2453 fn serde_payload_input_enum_v2() {
2454 let response_v2 = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[]}"#;
2455
2456 let payload: ExecutionPayload = serde_json::from_str(response_v2).unwrap();
2457 assert!(payload.as_v3().is_none());
2458 assert!(payload.as_v2().is_some());
2459 assert_eq!(serde_json::to_string(&payload).unwrap(), response_v2);
2460
2461 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(response_v2).unwrap();
2462 assert_eq!(payload.as_v2().unwrap(), &payload_v2);
2463 }
2464
2465 #[test]
2466 #[cfg(feature = "serde")]
2467 fn serde_payload_input_enum_faulty_v2() {
2468 let response_faulty = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[], "blobGasUsed": "0x0"}"#;
2470
2471 let payload: Result<ExecutionPayload, serde_json::Error> =
2472 serde_json::from_str(response_faulty);
2473 assert!(payload.is_err());
2474 }
2475
2476 #[test]
2477 #[cfg(feature = "serde")]
2478 fn serde_payload_input_enum_faulty_v1() {
2479 let response_faulty = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"blobGasUsed": "0x0"}"#;
2481
2482 let payload: Result<ExecutionPayload, serde_json::Error> =
2483 serde_json::from_str(response_faulty);
2484 assert!(payload.is_err());
2485 }
2486
2487 #[test]
2488 #[cfg(feature = "serde")]
2489 fn serde_faulty_roundtrip_payload_input_v3() {
2490 let response_v3 = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0"}"#;
2494
2495 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(response_v3).unwrap();
2496 assert_ne!(response_v3, serde_json::to_string(&payload_v2).unwrap());
2497
2498 let payload_v1: ExecutionPayloadV1 = serde_json::from_str(response_v3).unwrap();
2499 assert_ne!(response_v3, serde_json::to_string(&payload_v1).unwrap());
2500 }
2501
2502 #[test]
2503 #[cfg(feature = "serde")]
2504 fn serde_faulty_roundtrip_payload_input_v2() {
2505 let response_v2 = r#"{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[]}"#;
2509
2510 let payload: ExecutionPayloadV1 = serde_json::from_str(response_v2).unwrap();
2511 assert_ne!(response_v2, serde_json::to_string(&payload).unwrap());
2512 }
2513
2514 #[test]
2515 #[cfg(feature = "serde")]
2516 fn serde_deserialize_execution_payload_input_v2() {
2517 let response = r#"
2518{
2519 "baseFeePerGas": "0x173b30b3",
2520 "blockHash": "0x99d486755fd046ad0bbb60457bac93d4856aa42fa00629cc7e4a28b65b5f8164",
2521 "blockNumber": "0xb",
2522 "extraData": "0xd883010d01846765746888676f312e32302e33856c696e7578",
2523 "feeRecipient": "0x0000000000000000000000000000000000000000",
2524 "gasLimit": "0x405829",
2525 "gasUsed": "0x3f0ca0",
2526 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
2527 "parentHash": "0xfe34aaa2b869c66a727783ee5ad3e3983b6ef22baf24a1e502add94e7bcac67a",
2528 "prevRandao": "0x74132c32fe3ab9a470a8352544514d21b6969e7749f97742b53c18a1b22b396c",
2529 "receiptsRoot": "0x6a5c41dc55a1bd3e74e7f6accc799efb08b00c36c15265058433fcea6323e95f",
2530 "stateRoot": "0xde3b357f5f099e4c33d0343c9e9d204d663d7bd9c65020a38e5d0b2a9ace78a2",
2531 "timestamp": "0x6507d6b4",
2532 "transactions": [
2533 "0xf86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53",
2534 "0xf86d0b8458b20efd82520894687704db07e902e9a8b3754031d168d46e3d586e8806f3e8d87878800080820a95a0e3108f710902be662d5c978af16109961ffaf2ac4f88522407d40949a9574276a0205719ed21889b42ab5c1026d40b759a507c12d92db0d100fa69e1ac79137caa",
2535 "0xf86d0c8458b20efd8252089415e6a5a2e131dd5467fa1ff3acd104f45ee5940b8806f3e8d87878800080820a96a0af556ba9cda1d686239e08c24e169dece7afa7b85e0948eaa8d457c0561277fca029da03d3af0978322e54ac7e8e654da23934e0dd839804cb0430f8aaafd732dc",
2536 "0xf8521784565adcb7830186a0808080820a96a0ec782872a673a9fe4eff028a5bdb30d6b8b7711f58a187bf55d3aec9757cb18ea001796d373da76f2b0aeda72183cce0ad070a4f03aa3e6fee4c757a9444245206",
2537 "0xf8521284565adcb7830186a0808080820a95a08a0ea89028eff02596b385a10e0bd6ae098f3b281be2c95a9feb1685065d7384a06239d48a72e4be767bd12f317dd54202f5623a33e71e25a87cb25dd781aa2fc8",
2538 "0xf8521384565adcb7830186a0808080820a95a0784dbd311a82f822184a46f1677a428cbe3a2b88a798fb8ad1370cdbc06429e8a07a7f6a0efd428e3d822d1de9a050b8a883938b632185c254944dd3e40180eb79"
2539 ],
2540 "withdrawals": []
2541}
2542 "#;
2543 let payload: ExecutionPayloadInputV2 = serde_json::from_str(response).unwrap();
2544 assert_eq!(payload.withdrawals, Some(vec![]));
2545
2546 let response = r#"
2547{
2548 "baseFeePerGas": "0x173b30b3",
2549 "blockHash": "0x99d486755fd046ad0bbb60457bac93d4856aa42fa00629cc7e4a28b65b5f8164",
2550 "blockNumber": "0xb",
2551 "extraData": "0xd883010d01846765746888676f312e32302e33856c696e7578",
2552 "feeRecipient": "0x0000000000000000000000000000000000000000",
2553 "gasLimit": "0x405829",
2554 "gasUsed": "0x3f0ca0",
2555 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
2556 "parentHash": "0xfe34aaa2b869c66a727783ee5ad3e3983b6ef22baf24a1e502add94e7bcac67a",
2557 "prevRandao": "0x74132c32fe3ab9a470a8352544514d21b6969e7749f97742b53c18a1b22b396c",
2558 "receiptsRoot": "0x6a5c41dc55a1bd3e74e7f6accc799efb08b00c36c15265058433fcea6323e95f",
2559 "stateRoot": "0xde3b357f5f099e4c33d0343c9e9d204d663d7bd9c65020a38e5d0b2a9ace78a2",
2560 "timestamp": "0x6507d6b4",
2561 "transactions": [
2562 "0xf86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53",
2563 "0xf86d0b8458b20efd82520894687704db07e902e9a8b3754031d168d46e3d586e8806f3e8d87878800080820a95a0e3108f710902be662d5c978af16109961ffaf2ac4f88522407d40949a9574276a0205719ed21889b42ab5c1026d40b759a507c12d92db0d100fa69e1ac79137caa",
2564 "0xf86d0c8458b20efd8252089415e6a5a2e131dd5467fa1ff3acd104f45ee5940b8806f3e8d87878800080820a96a0af556ba9cda1d686239e08c24e169dece7afa7b85e0948eaa8d457c0561277fca029da03d3af0978322e54ac7e8e654da23934e0dd839804cb0430f8aaafd732dc",
2565 "0xf8521784565adcb7830186a0808080820a96a0ec782872a673a9fe4eff028a5bdb30d6b8b7711f58a187bf55d3aec9757cb18ea001796d373da76f2b0aeda72183cce0ad070a4f03aa3e6fee4c757a9444245206",
2566 "0xf8521284565adcb7830186a0808080820a95a08a0ea89028eff02596b385a10e0bd6ae098f3b281be2c95a9feb1685065d7384a06239d48a72e4be767bd12f317dd54202f5623a33e71e25a87cb25dd781aa2fc8",
2567 "0xf8521384565adcb7830186a0808080820a95a0784dbd311a82f822184a46f1677a428cbe3a2b88a798fb8ad1370cdbc06429e8a07a7f6a0efd428e3d822d1de9a050b8a883938b632185c254944dd3e40180eb79"
2568 ]
2569}
2570 "#;
2571 let payload: ExecutionPayloadInputV2 = serde_json::from_str(response).unwrap();
2572 assert_eq!(payload.withdrawals, None);
2573 }
2574
2575 #[test]
2576 #[cfg(feature = "serde")]
2577 fn serde_deserialize_v2_input_with_blob_fields() {
2578 let input = r#"
2579{
2580 "parentHash": "0xaaa4c5b574f37e1537c78931d1bca24a4d17d4f29f1ee97e1cd48b704909de1f",
2581 "feeRecipient": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
2582 "stateRoot": "0x308ee9c5c6fab5e3d08763a3b5fe0be8ada891fa5010a49a3390e018dd436810",
2583 "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
2584 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
2585 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
2586 "blockNumber": "0xf",
2587 "gasLimit": "0x16345785d8a0000",
2588 "gasUsed": "0x0",
2589 "timestamp": "0x3a97",
2590 "extraData": "0x",
2591 "baseFeePerGas": "0x7",
2592 "blockHash": "0x38bb6ba645c7e6bd970f9c7d492fafe1e04d85349054cb48d16c9d2c3e3cd0bf",
2593 "transactions": [],
2594 "withdrawals": [],
2595 "excessBlobGas": "0x0",
2596 "blobGasUsed": "0x0"
2597}
2598 "#;
2599
2600 let payload_res: Result<ExecutionPayloadInputV2, serde_json::Error> =
2602 serde_json::from_str(input);
2603 assert!(payload_res.is_err());
2604 }
2605
2606 #[test]
2608 #[cfg(feature = "serde")]
2609 fn deserialize_op_base_payload() {
2610 let payload = r#"{"parentHash":"0x24e8df372a61cdcdb1a163b52aaa1785e0c869d28c3b742ac09e826bbb524723","feeRecipient":"0x4200000000000000000000000000000000000011","stateRoot":"0x9a5db45897f1ff1e620a6c14b0a6f1b3bcdbed59f2adc516a34c9a9d6baafa71","receiptsRoot":"0x8af6f74835d47835deb5628ca941d00e0c9fd75585f26dabdcb280ec7122e6af","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xf37b24eeff594848072a05f74c8600001706c83e489a9132e55bf43a236e42ec","blockNumber":"0xe3d5d8","gasLimit":"0x17d7840","gasUsed":"0xb705","timestamp":"0x65a118c0","extraData":"0x","baseFeePerGas":"0x7a0ff32","blockHash":"0xf5c147b2d60a519b72434f0a8e082e18599021294dd9085d7597b0ffa638f1c0","withdrawals":[],"transactions":["0x7ef90159a05ba0034ffdcb246703298224564720b66964a6a69d0d7e9ffd970c546f7c048094deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b90104015d8eb900000000000000000000000000000000000000000000000000000000009e1c4a0000000000000000000000000000000000000000000000000000000065a11748000000000000000000000000000000000000000000000000000000000000000a4b479e5fa8d52dd20a8a66e468b56e993bdbffcccf729223aabff06299ab36db000000000000000000000000000000000000000000000000000000000000000400000000000000000000000073b4168cc87f35cc239200a20eb841cded23493b000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240"]}"#;
2611 let _payload = serde_json::from_str::<ExecutionPayloadInputV2>(payload).unwrap();
2612 }
2613
2614 #[test]
2615 fn roundtrip_payload_to_block() {
2616 let first_transaction_raw = Bytes::from_static(&hex!("02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]);
2617 let second_transaction_raw = Bytes::from_static(&hex!("03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]);
2618
2619 let new_payload = ExecutionPayloadV3 {
2620 payload_inner: ExecutionPayloadV2 {
2621 payload_inner: ExecutionPayloadV1 {
2622 base_fee_per_gas: U256::from(7u64),
2623 block_number: 0xa946u64,
2624 block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(),
2625 logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(),
2626 extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(),
2627 gas_limit: 0x1c9c380,
2628 gas_used: 0x1f4a9,
2629 timestamp: 0x651f35b8,
2630 fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(),
2631 parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(),
2632 prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(),
2633 receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(),
2634 state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(),
2635 transactions: vec![first_transaction_raw, second_transaction_raw],
2636 },
2637 withdrawals: vec![],
2638 },
2639 blob_gas_used: 0xc0000,
2640 excess_blob_gas: 0x580000,
2641 };
2642
2643 let mut block: Block<TxEnvelope> = new_payload.clone().try_into_block().unwrap();
2644
2645 let parent_beacon_block_root =
2648 b256!("531cd53b8e68deef0ea65edfa3cda927a846c307b0907657af34bc3f313b5871");
2649 block.header.parent_beacon_block_root = Some(parent_beacon_block_root);
2650
2651 let converted_payload = ExecutionPayloadV3::from_block_unchecked(block.hash_slow(), &block);
2652
2653 assert_eq!(new_payload, converted_payload);
2655 }
2656
2657 #[test]
2658 fn payload_to_block_rejects_network_encoded_tx() {
2659 let first_transaction_raw = Bytes::from_static(&hex!("b9017e02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]);
2660 let second_transaction_raw = Bytes::from_static(&hex!("b9013c03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]);
2661
2662 let new_payload = ExecutionPayloadV3 {
2663 payload_inner: ExecutionPayloadV2 {
2664 payload_inner: ExecutionPayloadV1 {
2665 base_fee_per_gas: U256::from(7u64),
2666 block_number: 0xa946u64,
2667 block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(),
2668 logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(),
2669 extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(),
2670 gas_limit: 0x1c9c380,
2671 gas_used: 0x1f4a9,
2672 timestamp: 0x651f35b8,
2673 fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(),
2674 parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(),
2675 prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(),
2676 receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(),
2677 state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(),
2678 transactions: vec![first_transaction_raw, second_transaction_raw],
2679 },
2680 withdrawals: vec![],
2681 },
2682 blob_gas_used: 0xc0000,
2683 excess_blob_gas: 0x580000,
2684 };
2685
2686 let _block = new_payload
2687 .try_into_block::<TxEnvelope>()
2688 .expect_err("execution payload conversion requires typed txs without a rlp header");
2689 }
2690
2691 #[test]
2692 fn devnet_invalid_block_hash_repro() {
2693 let deser_block = r#"
2694 {
2695 "parentHash": "0xae8315ee86002e6269a17dd1e9516a6cf13223e9d4544d0c32daff826fb31acc",
2696 "feeRecipient": "0xf97e180c050e5ab072211ad2c213eb5aee4df134",
2697 "stateRoot": "0x03787f1579efbaa4a8234e72465eb4e29ef7e62f61242d6454661932e1a282a1",
2698 "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
2699 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
2700 "prevRandao": "0x918e86b497dc15de7d606457c36ca583e24d9b0a110a814de46e33d5bb824a66",
2701 "blockNumber": "0x6a784",
2702 "gasLimit": "0x1c9c380",
2703 "gasUsed": "0x0",
2704 "timestamp": "0x65bc1d60",
2705 "extraData": "0x9a726574682f76302e312e302d616c7068612e31362f6c696e7578",
2706 "baseFeePerGas": "0x8",
2707 "blobGasUsed": "0x0",
2708 "excessBlobGas": "0x0",
2709 "blockHash": "0x340c157eca9fd206b87c17f0ecbe8d411219de7188a0a240b635c88a96fe91c5",
2710 "transactions": [],
2711 "withdrawals": [
2712 {
2713 "index": "0x5ab202",
2714 "validatorIndex": "0xb1b",
2715 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2716 "amount": "0x19b3d"
2717 },
2718 {
2719 "index": "0x5ab203",
2720 "validatorIndex": "0xb1c",
2721 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2722 "amount": "0x15892"
2723 },
2724 {
2725 "index": "0x5ab204",
2726 "validatorIndex": "0xb1d",
2727 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2728 "amount": "0x19b3d"
2729 },
2730 {
2731 "index": "0x5ab205",
2732 "validatorIndex": "0xb1e",
2733 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2734 "amount": "0x19b3d"
2735 },
2736 {
2737 "index": "0x5ab206",
2738 "validatorIndex": "0xb1f",
2739 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2740 "amount": "0x19b3d"
2741 },
2742 {
2743 "index": "0x5ab207",
2744 "validatorIndex": "0xb20",
2745 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2746 "amount": "0x19b3d"
2747 },
2748 {
2749 "index": "0x5ab208",
2750 "validatorIndex": "0xb21",
2751 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2752 "amount": "0x15892"
2753 },
2754 {
2755 "index": "0x5ab209",
2756 "validatorIndex": "0xb22",
2757 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2758 "amount": "0x19b3d"
2759 },
2760 {
2761 "index": "0x5ab20a",
2762 "validatorIndex": "0xb23",
2763 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2764 "amount": "0x19b3d"
2765 },
2766 {
2767 "index": "0x5ab20b",
2768 "validatorIndex": "0xb24",
2769 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2770 "amount": "0x17db2"
2771 },
2772 {
2773 "index": "0x5ab20c",
2774 "validatorIndex": "0xb25",
2775 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2776 "amount": "0x19b3d"
2777 },
2778 {
2779 "index": "0x5ab20d",
2780 "validatorIndex": "0xb26",
2781 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2782 "amount": "0x19b3d"
2783 },
2784 {
2785 "index": "0x5ab20e",
2786 "validatorIndex": "0xa91",
2787 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2788 "amount": "0x15892"
2789 },
2790 {
2791 "index": "0x5ab20f",
2792 "validatorIndex": "0xa92",
2793 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2794 "amount": "0x1c05d"
2795 },
2796 {
2797 "index": "0x5ab210",
2798 "validatorIndex": "0xa93",
2799 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2800 "amount": "0x15892"
2801 },
2802 {
2803 "index": "0x5ab211",
2804 "validatorIndex": "0xa94",
2805 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2806 "amount": "0x19b3d"
2807 }
2808 ]
2809 }
2810 "#;
2811
2812 let payload: ExecutionPayload =
2814 serde_json::from_str::<ExecutionPayloadV3>(deser_block).unwrap().into();
2815
2816 let block_hash_with_blob_fee_fields =
2820 b256!("a7cdd5f9e54147b53a15833a8c45dffccbaed534d7fdc23458f45102a4bf71b0");
2821
2822 let versioned_hashes = vec![];
2823 let parent_beacon_block_root =
2824 b256!("1162de8a0f4d20d86b9ad6e0a2575ab60f00a433dc70d9318c8abc9041fddf54");
2825
2826 let cancun_fields = CancunPayloadFields { parent_beacon_block_root, versioned_hashes };
2828
2829 let block = payload
2831 .try_into_block_with_sidecar::<TxEnvelope>(&ExecutionPayloadSidecar::v3(cancun_fields))
2832 .unwrap();
2833
2834 assert_eq!(block_hash_with_blob_fee_fields, block.header.hash_slow());
2836 }
2837
2838 #[test]
2839 fn test_payload_to_block_with_sidecar_raw() {
2840 use std::path::PathBuf;
2841
2842 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/payload");
2843 let dir = std::fs::read_dir(path).expect("Unable to read payload folder");
2844
2845 for entry in dir {
2846 let entry = entry.expect("Unable to read entry");
2847 let path = entry.path();
2848
2849 if path.extension().and_then(|s| s.to_str()) != Some("json") {
2850 continue;
2851 }
2852
2853 let contents = std::fs::read_to_string(&path).expect("Unable to read file");
2854 let value: serde_json::Value = serde_json::from_str(&contents)
2855 .unwrap_or_else(|e| panic!("Failed to parse JSON from {path:?}: {e}"));
2856
2857 let new_payload = &value["newPayload"];
2859 let payload_value = &new_payload["payload"];
2860 let sidecar_value = &new_payload["sidecar"];
2861
2862 let payload: ExecutionPayload = serde_json::from_value(payload_value.clone())
2863 .unwrap_or_else(|e| panic!("Failed to deserialize payload from {path:?}: {e}"));
2864
2865 let sidecar: ExecutionPayloadSidecar = serde_json::from_value(sidecar_value.clone())
2867 .unwrap_or_else(|e| panic!("Failed to deserialize sidecar from {path:?}: {e}"));
2868
2869 let block = payload.clone().into_block_with_sidecar_raw(&sidecar).unwrap_or_else(|e| {
2871 panic!("Failed to convert payload to block from {path:?}: {e}")
2872 });
2873
2874 if let Some(tx_count) = payload_value["transactions"].as_array().map(|a| a.len()) {
2876 assert_eq!(
2877 block.body.transactions.len(),
2878 tx_count,
2879 "Transaction count mismatch in {:?}",
2880 path
2881 );
2882 }
2883
2884 assert_eq!(
2886 block.header.parent_beacon_block_root,
2887 sidecar.parent_beacon_block_root(),
2888 "Parent beacon block root mismatch in {:?}",
2889 path
2890 );
2891 assert_eq!(
2892 block.header.requests_hash,
2893 sidecar.requests_hash(),
2894 "Requests hash mismatch in {:?}",
2895 path
2896 );
2897
2898 let expected_hash = payload_value["blockHash"]
2900 .as_str()
2901 .unwrap()
2902 .parse::<B256>()
2903 .unwrap_or_else(|e| panic!("Failed to parse block hash from {path:?}: {e}"));
2904 let actual_hash = block.header.hash_slow();
2905 assert_eq!(
2906 actual_hash, expected_hash,
2907 "Block hash mismatch in {:?}: expected {}, got {}",
2908 path, expected_hash, actual_hash
2909 );
2910
2911 let block =
2912 payload.try_into_block_with_sidecar::<TxEnvelope>(&sidecar).unwrap_or_else(|e| {
2913 panic!("Failed to convert payload to block from {path:?}: {e}")
2914 });
2915 let actual_hash = block.header.hash_slow();
2916 assert_eq!(
2917 actual_hash, expected_hash,
2918 "Block hash mismatch in {:?}: expected {}, got {}",
2919 path, expected_hash, actual_hash
2920 );
2921 }
2922 }
2923
2924 #[test]
2925 fn test_decoded_transactions() {
2926 let transaction = Bytes::from_static(&hex!("f86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53"));
2927
2928 let payload = ExecutionPayload::V1(ExecutionPayloadV1 {
2929 parent_hash: B256::default(),
2930 fee_recipient: Address::default(),
2931 state_root: B256::default(),
2932 receipts_root: B256::default(),
2933 logs_bloom: Bloom::default(),
2934 prev_randao: B256::default(),
2935 block_number: 0,
2936 gas_limit: 0,
2937 gas_used: 0,
2938 timestamp: 0,
2939 extra_data: Bytes::default(),
2940 base_fee_per_gas: U256::default(),
2941 block_hash: B256::default(),
2942 transactions: vec![transaction.clone()],
2943 });
2944
2945 let decoded: Vec<_> = payload.decoded_transactions::<TxEnvelope>().collect();
2947 assert_eq!(decoded.len(), 1);
2948 assert!(decoded[0].is_ok(), "Failed to decode transaction: {:?}", decoded[0]);
2949
2950 let decoded_with_encoded: Vec<_> =
2952 payload.decoded_transactions_with_encoded::<TxEnvelope>().collect();
2953 assert_eq!(decoded_with_encoded.len(), 1);
2954 assert!(decoded_with_encoded[0].is_ok());
2955 if let Ok(with_encoded) = &decoded_with_encoded[0] {
2956 assert_eq!(with_encoded.encoded_bytes(), &transaction);
2957 }
2958 }
2959}