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
264impl ExecutionPayloadEnvelopeV4 {
265 #[cfg(feature = "kzg")]
275 pub fn try_into_v5(
276 self,
277 ) -> Result<ExecutionPayloadEnvelopeV5, alloy_eips::eip4844::c_kzg::Error> {
278 self.try_into_v5_with_settings(
279 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
280 )
281 }
282
283 #[cfg(feature = "kzg")]
290 pub fn try_into_v5_with_settings(
291 self,
292 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
293 ) -> Result<ExecutionPayloadEnvelopeV5, alloy_eips::eip4844::c_kzg::Error> {
294 let blobs_bundle = self.envelope_inner.blobs_bundle.try_into_v2_with_settings(settings)?;
295 Ok(ExecutionPayloadEnvelopeV5 {
296 execution_payload: self.envelope_inner.execution_payload,
297 block_value: self.envelope_inner.block_value,
298 blobs_bundle,
299 should_override_builder: self.envelope_inner.should_override_builder,
300 execution_requests: self.execution_requests,
301 })
302 }
303}
304
305#[cfg(feature = "kzg")]
306impl TryFrom<ExecutionPayloadEnvelopeV4> for ExecutionPayloadEnvelopeV5 {
307 type Error = alloy_eips::eip4844::c_kzg::Error;
308
309 fn try_from(value: ExecutionPayloadEnvelopeV4) -> Result<Self, Self::Error> {
310 value.try_into_v5()
311 }
312}
313
314impl ExecutionPayloadEnvelopeV5 {
315 #[cfg(feature = "kzg")]
325 pub fn try_into_v4(
326 self,
327 ) -> Result<ExecutionPayloadEnvelopeV4, alloy_eips::eip4844::c_kzg::Error> {
328 self.try_into_v4_with_settings(
329 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
330 )
331 }
332
333 #[cfg(feature = "kzg")]
340 pub fn try_into_v4_with_settings(
341 self,
342 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
343 ) -> Result<ExecutionPayloadEnvelopeV4, alloy_eips::eip4844::c_kzg::Error> {
344 let blobs_bundle = self.blobs_bundle.try_into_v1_with_settings(settings)?;
345 Ok(ExecutionPayloadEnvelopeV4 {
346 envelope_inner: ExecutionPayloadEnvelopeV3 {
347 execution_payload: self.execution_payload,
348 block_value: self.block_value,
349 blobs_bundle,
350 should_override_builder: self.should_override_builder,
351 },
352 execution_requests: self.execution_requests,
353 })
354 }
355}
356
357#[cfg(feature = "kzg")]
358impl TryFrom<ExecutionPayloadEnvelopeV5> for ExecutionPayloadEnvelopeV4 {
359 type Error = alloy_eips::eip4844::c_kzg::Error;
360
361 fn try_from(value: ExecutionPayloadEnvelopeV5) -> Result<Self, Self::Error> {
362 value.try_into_v4()
363 }
364}
365
366#[derive(Clone, Debug, PartialEq, Eq)]
370#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
371#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
372#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
373#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
374pub struct ExecutionPayloadV1 {
375 pub parent_hash: B256,
377 pub fee_recipient: Address,
379 pub state_root: B256,
381 pub receipts_root: B256,
383 pub logs_bloom: Bloom,
385 pub prev_randao: B256,
387 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
389 pub block_number: u64,
390 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
392 pub gas_limit: u64,
393 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
395 pub gas_used: u64,
396 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
398 pub timestamp: u64,
399 pub extra_data: Bytes,
401 pub base_fee_per_gas: U256,
403 pub block_hash: B256,
405 pub transactions: Vec<Bytes>,
407}
408
409impl ExecutionPayloadV1 {
410 pub const fn block_num_hash(&self) -> BlockNumHash {
412 BlockNumHash::new(self.block_number, self.block_hash)
413 }
414
415 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
417 self.try_into_block_with(|tx| {
418 T::decode_2718_exact(tx.as_ref())
419 .map_err(alloy_rlp::Error::from)
420 .map_err(PayloadError::from)
421 })
422 }
423
424 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
426 where
427 F: FnMut(Bytes) -> Result<T, E>,
428 E: Into<PayloadError>,
429 {
430 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
431 }
432
433 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
438 if self.extra_data.len() > MAXIMUM_EXTRA_DATA_SIZE {
439 return Err(PayloadError::ExtraData(self.extra_data));
440 }
441
442 let transactions_root =
444 alloy_consensus::proofs::ordered_trie_root_encoded(&self.transactions);
445
446 let header = Header {
447 parent_hash: self.parent_hash,
448 beneficiary: self.fee_recipient,
449 state_root: self.state_root,
450 transactions_root,
451 receipts_root: self.receipts_root,
452 withdrawals_root: None,
453 logs_bloom: self.logs_bloom,
454 number: self.block_number,
455 gas_limit: self.gas_limit,
456 gas_used: self.gas_used,
457 timestamp: self.timestamp,
458 mix_hash: self.prev_randao,
459 base_fee_per_gas: Some(
464 self.base_fee_per_gas
465 .try_into()
466 .map_err(|_| PayloadError::BaseFee(self.base_fee_per_gas))?,
467 ),
468 blob_gas_used: None,
469 excess_blob_gas: None,
470 parent_beacon_block_root: None,
471 requests_hash: None,
472 extra_data: self.extra_data,
473 ommers_hash: EMPTY_OMMER_ROOT_HASH,
475 difficulty: Default::default(),
476 nonce: Default::default(),
477 };
478
479 Ok(Block {
480 header,
481 body: BlockBody { transactions: self.transactions, ommers: vec![], withdrawals: None },
482 })
483 }
484
485 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
489 where
490 T: Encodable2718,
491 H: BlockHeader + Sealable,
492 {
493 Self::from_block_unchecked(block.header.hash_slow(), block)
494 }
495
496 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
498 where
499 T: Encodable2718,
500 H: BlockHeader,
501 {
502 let transactions =
503 block.body.transactions().map(|tx| tx.encoded_2718().into()).collect::<Vec<_>>();
504 Self {
505 parent_hash: block.parent_hash(),
506 fee_recipient: block.beneficiary(),
507 state_root: block.state_root(),
508 receipts_root: block.receipts_root(),
509 logs_bloom: block.logs_bloom(),
510 prev_randao: block.mix_hash().unwrap_or_default(),
511 block_number: block.number(),
512 gas_limit: block.gas_limit(),
513 gas_used: block.gas_used(),
514 timestamp: block.timestamp(),
515 base_fee_per_gas: U256::from(block.base_fee_per_gas().unwrap_or_default()),
516 extra_data: block.header.extra_data().clone(),
517 block_hash,
518 transactions,
519 }
520 }
521
522 pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option<u64> {
526 Some(calc_next_block_base_fee(
527 self.gas_used,
528 self.gas_limit,
529 self.base_fee_per_gas.try_into().ok()?,
530 base_fee_params,
531 ))
532 }
533}
534
535impl<T: Decodable2718> TryFrom<ExecutionPayloadV1> for Block<T> {
536 type Error = PayloadError;
537
538 fn try_from(value: ExecutionPayloadV1) -> Result<Self, Self::Error> {
539 value.try_into_block()
540 }
541}
542
543#[derive(Clone, Debug, PartialEq, Eq)]
547#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
548#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
549#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
550pub struct ExecutionPayloadV2 {
551 #[cfg_attr(feature = "serde", serde(flatten))]
553 pub payload_inner: ExecutionPayloadV1,
554
555 pub withdrawals: Vec<Withdrawal>,
558}
559
560impl ExecutionPayloadV2 {
561 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
569 where
570 T: Encodable2718,
571 H: BlockHeader + Sealable,
572 {
573 Self::from_block_unchecked(block.header.hash_slow(), block)
574 }
575
576 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
582 where
583 T: Encodable2718,
584 H: BlockHeader,
585 {
586 Self {
587 withdrawals: block
588 .body
589 .withdrawals
590 .clone()
591 .map(Withdrawals::into_inner)
592 .unwrap_or_default(),
593 payload_inner: ExecutionPayloadV1::from_block_unchecked(block_hash, block),
594 }
595 }
596
597 pub const fn timestamp(&self) -> u64 {
599 self.payload_inner.timestamp
600 }
601
602 pub fn into_payload_input_v2(self, is_shanghai_active: bool) -> ExecutionPayloadInputV2 {
610 ExecutionPayloadInputV2 {
611 execution_payload: self.payload_inner,
612 withdrawals: is_shanghai_active.then_some(self.withdrawals),
613 }
614 }
615
616 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
623 self.try_into_block_with(|tx| {
624 T::decode_2718_exact(tx.as_ref())
625 .map_err(alloy_rlp::Error::from)
626 .map_err(PayloadError::from)
627 })
628 }
629
630 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
634 where
635 F: FnMut(Bytes) -> Result<T, E>,
636 E: Into<PayloadError>,
637 {
638 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
639 }
640
641 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
646 let mut base_sealed_block = self.payload_inner.into_block_raw()?;
647 let withdrawals_root =
648 alloy_consensus::proofs::calculate_withdrawals_root(&self.withdrawals);
649 base_sealed_block.body.withdrawals = Some(self.withdrawals.into());
650 base_sealed_block.header.withdrawals_root = Some(withdrawals_root);
651 Ok(base_sealed_block)
652 }
653}
654
655impl<T: Decodable2718> TryFrom<ExecutionPayloadV2> for Block<T> {
656 type Error = PayloadError;
657
658 fn try_from(value: ExecutionPayloadV2) -> Result<Self, Self::Error> {
659 value.try_into_block()
660 }
661}
662
663#[cfg(feature = "ssz")]
664impl ssz::Decode for ExecutionPayloadV2 {
665 fn is_ssz_fixed_len() -> bool {
666 false
667 }
668
669 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
670 let mut builder = ssz::SszDecoderBuilder::new(bytes);
671
672 builder.register_type::<B256>()?;
673 builder.register_type::<Address>()?;
674 builder.register_type::<B256>()?;
675 builder.register_type::<B256>()?;
676 builder.register_type::<Bloom>()?;
677 builder.register_type::<B256>()?;
678 builder.register_type::<u64>()?;
679 builder.register_type::<u64>()?;
680 builder.register_type::<u64>()?;
681 builder.register_type::<u64>()?;
682 builder.register_type::<Bytes>()?;
683 builder.register_type::<U256>()?;
684 builder.register_type::<B256>()?;
685 builder.register_type::<Vec<Bytes>>()?;
686 builder.register_type::<Vec<Withdrawal>>()?;
687
688 let mut decoder = builder.build()?;
689
690 Ok(Self {
691 payload_inner: ExecutionPayloadV1 {
692 parent_hash: decoder.decode_next()?,
693 fee_recipient: decoder.decode_next()?,
694 state_root: decoder.decode_next()?,
695 receipts_root: decoder.decode_next()?,
696 logs_bloom: decoder.decode_next()?,
697 prev_randao: decoder.decode_next()?,
698 block_number: decoder.decode_next()?,
699 gas_limit: decoder.decode_next()?,
700 gas_used: decoder.decode_next()?,
701 timestamp: decoder.decode_next()?,
702 extra_data: decoder.decode_next()?,
703 base_fee_per_gas: decoder.decode_next()?,
704 block_hash: decoder.decode_next()?,
705 transactions: decoder.decode_next()?,
706 },
707 withdrawals: decoder.decode_next()?,
708 })
709 }
710}
711
712#[cfg(feature = "ssz")]
713impl ssz::Encode for ExecutionPayloadV2 {
714 fn is_ssz_fixed_len() -> bool {
715 false
716 }
717
718 fn ssz_append(&self, buf: &mut Vec<u8>) {
719 let offset = <B256 as ssz::Encode>::ssz_fixed_len() * 5
720 + <Address as ssz::Encode>::ssz_fixed_len()
721 + <Bloom as ssz::Encode>::ssz_fixed_len()
722 + <u64 as ssz::Encode>::ssz_fixed_len() * 4
723 + <U256 as ssz::Encode>::ssz_fixed_len()
724 + ssz::BYTES_PER_LENGTH_OFFSET * 3;
725
726 let mut encoder = ssz::SszEncoder::container(buf, offset);
727
728 encoder.append(&self.payload_inner.parent_hash);
729 encoder.append(&self.payload_inner.fee_recipient);
730 encoder.append(&self.payload_inner.state_root);
731 encoder.append(&self.payload_inner.receipts_root);
732 encoder.append(&self.payload_inner.logs_bloom);
733 encoder.append(&self.payload_inner.prev_randao);
734 encoder.append(&self.payload_inner.block_number);
735 encoder.append(&self.payload_inner.gas_limit);
736 encoder.append(&self.payload_inner.gas_used);
737 encoder.append(&self.payload_inner.timestamp);
738 encoder.append(&self.payload_inner.extra_data);
739 encoder.append(&self.payload_inner.base_fee_per_gas);
740 encoder.append(&self.payload_inner.block_hash);
741 encoder.append(&self.payload_inner.transactions);
742 encoder.append(&self.withdrawals);
743
744 encoder.finalize();
745 }
746
747 fn ssz_bytes_len(&self) -> usize {
748 <ExecutionPayloadV1 as ssz::Encode>::ssz_bytes_len(&self.payload_inner)
749 + ssz::BYTES_PER_LENGTH_OFFSET
750 + self.withdrawals.ssz_bytes_len()
751 }
752}
753
754#[derive(Clone, Debug, PartialEq, Eq)]
758#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
759#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
760#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
761pub struct ExecutionPayloadV3 {
762 #[cfg_attr(feature = "serde", serde(flatten))]
764 pub payload_inner: ExecutionPayloadV2,
765
766 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
769 pub blob_gas_used: u64,
770 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
773 pub excess_blob_gas: u64,
774}
775
776impl ExecutionPayloadV3 {
777 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> Self
783 where
784 T: Encodable2718,
785 H: BlockHeader + Sealable,
786 {
787 Self::from_block_unchecked(block.hash_slow(), block)
788 }
789
790 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
794 where
795 T: Encodable2718,
796 H: BlockHeader,
797 {
798 Self {
799 blob_gas_used: block.blob_gas_used().unwrap_or_default(),
800 excess_blob_gas: block.excess_blob_gas().unwrap_or_default(),
801 payload_inner: ExecutionPayloadV2::from_block_unchecked(block_hash, block),
802 }
803 }
804
805 pub const fn withdrawals(&self) -> &Vec<Withdrawal> {
807 &self.payload_inner.withdrawals
808 }
809
810 pub const fn timestamp(&self) -> u64 {
812 self.payload_inner.payload_inner.timestamp
813 }
814
815 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
822 self.try_into_block_with(|tx| {
823 T::decode_2718_exact(tx.as_ref())
824 .map_err(alloy_rlp::Error::from)
825 .map_err(PayloadError::from)
826 })
827 }
828
829 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
833 where
834 F: FnMut(Bytes) -> Result<T, E>,
835 E: Into<PayloadError>,
836 {
837 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
838 }
839
840 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
845 let mut base_block = self.payload_inner.into_block_raw()?;
846
847 base_block.header.blob_gas_used = Some(self.blob_gas_used);
848 base_block.header.excess_blob_gas = Some(self.excess_blob_gas);
849
850 Ok(base_block)
851 }
852}
853
854impl<T: Decodable2718> TryFrom<ExecutionPayloadV3> for Block<T> {
855 type Error = PayloadError;
856
857 fn try_from(value: ExecutionPayloadV3) -> Result<Self, Self::Error> {
858 value.try_into_block()
859 }
860}
861
862#[cfg(feature = "ssz")]
863impl ssz::Decode for ExecutionPayloadV3 {
864 fn is_ssz_fixed_len() -> bool {
865 false
866 }
867
868 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
869 let mut builder = ssz::SszDecoderBuilder::new(bytes);
870
871 builder.register_type::<B256>()?;
872 builder.register_type::<Address>()?;
873 builder.register_type::<B256>()?;
874 builder.register_type::<B256>()?;
875 builder.register_type::<Bloom>()?;
876 builder.register_type::<B256>()?;
877 builder.register_type::<u64>()?;
878 builder.register_type::<u64>()?;
879 builder.register_type::<u64>()?;
880 builder.register_type::<u64>()?;
881 builder.register_type::<Bytes>()?;
882 builder.register_type::<U256>()?;
883 builder.register_type::<B256>()?;
884 builder.register_type::<Vec<Bytes>>()?;
885 builder.register_type::<Vec<Withdrawal>>()?;
886 builder.register_type::<u64>()?;
887 builder.register_type::<u64>()?;
888
889 let mut decoder = builder.build()?;
890
891 Ok(Self {
892 payload_inner: ExecutionPayloadV2 {
893 payload_inner: ExecutionPayloadV1 {
894 parent_hash: decoder.decode_next()?,
895 fee_recipient: decoder.decode_next()?,
896 state_root: decoder.decode_next()?,
897 receipts_root: decoder.decode_next()?,
898 logs_bloom: decoder.decode_next()?,
899 prev_randao: decoder.decode_next()?,
900 block_number: decoder.decode_next()?,
901 gas_limit: decoder.decode_next()?,
902 gas_used: decoder.decode_next()?,
903 timestamp: decoder.decode_next()?,
904 extra_data: decoder.decode_next()?,
905 base_fee_per_gas: decoder.decode_next()?,
906 block_hash: decoder.decode_next()?,
907 transactions: decoder.decode_next()?,
908 },
909 withdrawals: decoder.decode_next()?,
910 },
911 blob_gas_used: decoder.decode_next()?,
912 excess_blob_gas: decoder.decode_next()?,
913 })
914 }
915}
916
917#[cfg(feature = "ssz")]
918impl ssz::Encode for ExecutionPayloadV3 {
919 fn is_ssz_fixed_len() -> bool {
920 false
921 }
922
923 fn ssz_append(&self, buf: &mut Vec<u8>) {
924 let offset = <B256 as ssz::Encode>::ssz_fixed_len() * 5
925 + <Address as ssz::Encode>::ssz_fixed_len()
926 + <Bloom as ssz::Encode>::ssz_fixed_len()
927 + <u64 as ssz::Encode>::ssz_fixed_len() * 6
928 + <U256 as ssz::Encode>::ssz_fixed_len()
929 + ssz::BYTES_PER_LENGTH_OFFSET * 3;
930
931 let mut encoder = ssz::SszEncoder::container(buf, offset);
932
933 encoder.append(&self.payload_inner.payload_inner.parent_hash);
934 encoder.append(&self.payload_inner.payload_inner.fee_recipient);
935 encoder.append(&self.payload_inner.payload_inner.state_root);
936 encoder.append(&self.payload_inner.payload_inner.receipts_root);
937 encoder.append(&self.payload_inner.payload_inner.logs_bloom);
938 encoder.append(&self.payload_inner.payload_inner.prev_randao);
939 encoder.append(&self.payload_inner.payload_inner.block_number);
940 encoder.append(&self.payload_inner.payload_inner.gas_limit);
941 encoder.append(&self.payload_inner.payload_inner.gas_used);
942 encoder.append(&self.payload_inner.payload_inner.timestamp);
943 encoder.append(&self.payload_inner.payload_inner.extra_data);
944 encoder.append(&self.payload_inner.payload_inner.base_fee_per_gas);
945 encoder.append(&self.payload_inner.payload_inner.block_hash);
946 encoder.append(&self.payload_inner.payload_inner.transactions);
947 encoder.append(&self.payload_inner.withdrawals);
948 encoder.append(&self.blob_gas_used);
949 encoder.append(&self.excess_blob_gas);
950
951 encoder.finalize();
952 }
953
954 fn ssz_bytes_len(&self) -> usize {
955 <ExecutionPayloadV2 as ssz::Encode>::ssz_bytes_len(&self.payload_inner)
956 + <u64 as ssz::Encode>::ssz_fixed_len() * 2
957 }
958}
959
960#[derive(Clone, Debug, Default, PartialEq, Eq)]
962#[cfg_attr(feature = "serde", derive(serde::Serialize))]
963#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode, ssz_derive::Decode))]
964#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
965pub struct BlobsBundleV1 {
966 pub commitments: Vec<alloy_consensus::Bytes48>,
968 pub proofs: Vec<alloy_consensus::Bytes48>,
970 pub blobs: Vec<alloy_consensus::Blob>,
972}
973
974#[cfg(feature = "serde")]
975impl<'de> serde::Deserialize<'de> for BlobsBundleV1 {
976 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
977 where
978 D: serde::Deserializer<'de>,
979 {
980 #[derive(serde::Deserialize)]
981 struct BlobsBundleRaw {
982 commitments: Vec<alloy_consensus::Bytes48>,
983 proofs: Vec<alloy_consensus::Bytes48>,
984 blobs: Vec<alloy_consensus::Blob>,
985 }
986 let raw = BlobsBundleRaw::deserialize(deserializer)?;
987
988 if raw.proofs.len() == raw.commitments.len() && raw.proofs.len() == raw.blobs.len() {
989 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
990 } else {
991 Err(serde::de::Error::invalid_length(
992 raw.proofs.len(),
993 &format!("{}", raw.commitments.len()).as_str(),
994 ))
995 }
996 }
997}
998
999impl BlobsBundleV1 {
1000 pub fn new(sidecars: impl IntoIterator<Item = BlobTransactionSidecar>) -> Self {
1004 let (commitments, proofs, blobs) = sidecars.into_iter().fold(
1005 (Vec::new(), Vec::new(), Vec::new()),
1006 |(mut commitments, mut proofs, mut blobs), sidecar| {
1007 commitments.extend(sidecar.commitments);
1008 proofs.extend(sidecar.proofs);
1009 blobs.extend(sidecar.blobs);
1010 (commitments, proofs, blobs)
1011 },
1012 );
1013 Self { commitments, proofs, blobs }
1014 }
1015
1016 pub fn empty() -> Self {
1021 Self::default()
1022 }
1023
1024 pub fn take(&mut self, len: usize) -> (Vec<Bytes48>, Vec<Bytes48>, Vec<Blob>) {
1030 (
1031 self.commitments.drain(0..len).collect(),
1032 self.proofs.drain(0..len).collect(),
1033 self.blobs.drain(0..len).collect(),
1034 )
1035 }
1036
1037 pub fn pop_sidecar(&mut self, len: usize) -> BlobTransactionSidecar {
1043 let (commitments, proofs, blobs) = self.take(len);
1044 BlobTransactionSidecar { commitments, proofs, blobs }
1045 }
1046
1047 #[cfg(feature = "kzg")]
1054 pub fn try_into_sidecar(
1055 self,
1056 ) -> Result<BlobTransactionSidecar, alloy_consensus::error::ValueError<Self>> {
1057 if self.commitments.len() != self.proofs.len() || self.commitments.len() != self.blobs.len()
1058 {
1059 return Err(alloy_consensus::error::ValueError::new(self, "length mismatch"));
1060 }
1061
1062 let Self { commitments, proofs, blobs } = self;
1063 Ok(BlobTransactionSidecar { blobs, commitments, proofs })
1064 }
1065
1066 #[cfg(feature = "kzg")]
1075 pub fn try_into_v2(self) -> Result<BlobsBundleV2, alloy_eips::eip4844::c_kzg::Error> {
1076 self.try_into_v2_with_settings(
1077 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
1078 )
1079 }
1080
1081 #[cfg(feature = "kzg")]
1088 pub fn try_into_v2_with_settings(
1089 self,
1090 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
1091 ) -> Result<BlobsBundleV2, alloy_eips::eip4844::c_kzg::Error> {
1092 use alloy_eips::eip7594::CELLS_PER_EXT_BLOB;
1093
1094 let mut cell_proofs = Vec::with_capacity(self.blobs.len() * CELLS_PER_EXT_BLOB);
1095
1096 for blob in self.blobs.iter() {
1097 let blob_kzg =
1099 unsafe { core::mem::transmute::<&Blob, &alloy_eips::eip4844::c_kzg::Blob>(blob) };
1100
1101 let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob_kzg)?;
1103
1104 unsafe {
1106 for kzg_proof in kzg_proofs.iter() {
1107 cell_proofs.push(core::mem::transmute::<
1108 alloy_eips::eip4844::c_kzg::Bytes48,
1109 Bytes48,
1110 >(kzg_proof.to_bytes()));
1111 }
1112 }
1113 }
1114
1115 Ok(BlobsBundleV2 { commitments: self.commitments, proofs: cell_proofs, blobs: self.blobs })
1116 }
1117}
1118
1119impl From<Vec<BlobTransactionSidecar>> for BlobsBundleV1 {
1120 fn from(sidecars: Vec<BlobTransactionSidecar>) -> Self {
1121 Self::new(sidecars)
1122 }
1123}
1124
1125impl FromIterator<BlobTransactionSidecar> for BlobsBundleV1 {
1126 fn from_iter<T: IntoIterator<Item = BlobTransactionSidecar>>(iter: T) -> Self {
1127 Self::new(iter)
1128 }
1129}
1130
1131#[cfg(feature = "kzg")]
1132impl TryFrom<BlobsBundleV1> for BlobTransactionSidecar {
1133 type Error = alloy_consensus::error::ValueError<BlobsBundleV1>;
1134
1135 fn try_from(value: BlobsBundleV1) -> Result<Self, Self::Error> {
1136 value.try_into_sidecar()
1137 }
1138}
1139
1140#[cfg(feature = "kzg")]
1141impl TryFrom<BlobsBundleV1> for BlobsBundleV2 {
1142 type Error = alloy_eips::eip4844::c_kzg::Error;
1143
1144 fn try_from(value: BlobsBundleV1) -> Result<Self, Self::Error> {
1145 value.try_into_v2()
1146 }
1147}
1148
1149#[derive(Clone, Debug, Default, PartialEq, Eq)]
1151#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1152#[cfg_attr(feature = "ssz", derive(ssz_derive::Encode))]
1153#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1154pub struct BlobsBundleV2 {
1155 pub commitments: Vec<alloy_consensus::Bytes48>,
1157 pub proofs: Vec<alloy_consensus::Bytes48>,
1159 pub blobs: Vec<alloy_consensus::Blob>,
1161}
1162
1163#[cfg(feature = "serde")]
1164impl<'de> serde::Deserialize<'de> for BlobsBundleV2 {
1165 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1166 where
1167 D: serde::Deserializer<'de>,
1168 {
1169 #[derive(serde::Deserialize)]
1170 struct BlobsBundleRaw {
1171 commitments: Vec<alloy_consensus::Bytes48>,
1172 proofs: Vec<alloy_consensus::Bytes48>,
1173 blobs: Vec<alloy_consensus::Blob>,
1174 }
1175 let raw = BlobsBundleRaw::deserialize(deserializer)?;
1176
1177 if raw.proofs.len() == raw.blobs.len() * CELLS_PER_EXT_BLOB
1178 && raw.commitments.len() == raw.blobs.len()
1179 {
1180 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
1181 } else {
1182 Err(serde::de::Error::invalid_length(
1183 raw.proofs.len(),
1184 &format!("{}", raw.commitments.len() * CELLS_PER_EXT_BLOB).as_str(),
1185 ))
1186 }
1187 }
1188}
1189
1190#[cfg(feature = "ssz")]
1191impl ssz::Decode for BlobsBundleV2 {
1192 fn is_ssz_fixed_len() -> bool {
1193 false
1194 }
1195
1196 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
1197 #[derive(ssz_derive::Decode)]
1198 struct BlobsBundleRaw {
1199 commitments: Vec<alloy_consensus::Bytes48>,
1200 proofs: Vec<alloy_consensus::Bytes48>,
1201 blobs: Vec<alloy_consensus::Blob>,
1202 }
1203
1204 let raw = BlobsBundleRaw::from_ssz_bytes(bytes)?;
1205
1206 if raw.proofs.len() == raw.blobs.len() * CELLS_PER_EXT_BLOB
1207 && raw.commitments.len() == raw.blobs.len()
1208 {
1209 Ok(Self { commitments: raw.commitments, proofs: raw.proofs, blobs: raw.blobs })
1210 } else {
1211 Err(ssz::DecodeError::BytesInvalid(
1212 format!(
1213 "Invalid BlobsBundleV2: expected {} proofs and {} commitments for {} blobs, got {} proofs and {} commitments",
1214 raw.blobs.len() * CELLS_PER_EXT_BLOB,
1215 raw.blobs.len(),
1216 raw.blobs.len(),
1217 raw.proofs.len(),
1218 raw.commitments.len()
1219 )
1220 ))
1221 }
1222 }
1223}
1224
1225impl BlobsBundleV2 {
1226 pub fn new(sidecars: impl IntoIterator<Item = BlobTransactionSidecarEip7594>) -> Self {
1230 let (commitments, proofs, blobs) = sidecars.into_iter().fold(
1231 (Vec::new(), Vec::new(), Vec::new()),
1232 |(mut commitments, mut proofs, mut blobs), sidecar| {
1233 commitments.extend(sidecar.commitments);
1234 proofs.extend(sidecar.cell_proofs);
1235 blobs.extend(sidecar.blobs);
1236 (commitments, proofs, blobs)
1237 },
1238 );
1239 Self { commitments, proofs, blobs }
1240 }
1241
1242 pub fn empty() -> Self {
1247 Self::default()
1248 }
1249
1250 pub fn take(&mut self, len: usize) -> (Vec<Bytes48>, Vec<Bytes48>, Vec<Blob>) {
1258 (
1259 self.commitments.drain(0..len).collect(),
1260 self.proofs.drain(0..len * CELLS_PER_EXT_BLOB).collect(),
1261 self.blobs.drain(0..len).collect(),
1262 )
1263 }
1264
1265 pub fn pop_sidecar(&mut self, len: usize) -> BlobTransactionSidecarEip7594 {
1271 let (commitments, cell_proofs, blobs) = self.take(len);
1272 BlobTransactionSidecarEip7594 { commitments, cell_proofs, blobs }
1273 }
1274
1275 #[cfg(feature = "kzg")]
1283 pub fn try_into_sidecar(
1284 self,
1285 ) -> Result<BlobTransactionSidecarEip7594, alloy_consensus::error::ValueError<Self>> {
1286 let expected_cell_proofs_len = self.blobs.len() * CELLS_PER_EXT_BLOB;
1287 if self.proofs.len() != expected_cell_proofs_len {
1288 let msg = format!(
1289 "cell proofs length mismatch, expected {expected_cell_proofs_len}, has {}",
1290 self.proofs.len()
1291 );
1292 return Err(alloy_consensus::error::ValueError::new(self, msg));
1293 }
1294
1295 if self.commitments.len() != self.blobs.len() {
1296 let msg = format!(
1297 "commitments length ({}) mismatch, expected blob length ({})",
1298 self.commitments.len(),
1299 self.blobs.len()
1300 );
1301 return Err(alloy_consensus::error::ValueError::new(self, msg));
1302 }
1303
1304 let Self { commitments, proofs, blobs } = self;
1305 Ok(BlobTransactionSidecarEip7594 { blobs, commitments, cell_proofs: proofs })
1306 }
1307
1308 #[cfg(feature = "kzg")]
1317 pub fn try_into_v1(self) -> Result<BlobsBundleV1, alloy_eips::eip4844::c_kzg::Error> {
1318 self.try_into_v1_with_settings(
1319 alloy_eips::eip4844::env_settings::EnvKzgSettings::Default.get(),
1320 )
1321 }
1322
1323 #[cfg(feature = "kzg")]
1333 pub fn try_into_v1_with_settings(
1334 self,
1335 settings: &alloy_eips::eip4844::c_kzg::KzgSettings,
1336 ) -> Result<BlobsBundleV1, alloy_eips::eip4844::c_kzg::Error> {
1337 let mut proofs = Vec::with_capacity(self.blobs.len());
1338
1339 for (blob, commitment) in self.blobs.iter().zip(self.commitments.iter()) {
1340 let blob_kzg =
1342 unsafe { core::mem::transmute::<&Blob, &alloy_eips::eip4844::c_kzg::Blob>(blob) };
1343 let commitment_kzg = unsafe {
1344 core::mem::transmute::<&Bytes48, &alloy_eips::eip4844::c_kzg::Bytes48>(commitment)
1345 };
1346
1347 let proof = settings.compute_blob_kzg_proof(blob_kzg, commitment_kzg)?;
1349
1350 unsafe {
1352 proofs.push(core::mem::transmute::<alloy_eips::eip4844::c_kzg::Bytes48, Bytes48>(
1353 proof.to_bytes(),
1354 ));
1355 }
1356 }
1357
1358 Ok(BlobsBundleV1 { commitments: self.commitments, proofs, blobs: self.blobs })
1359 }
1360}
1361
1362impl From<Vec<BlobTransactionSidecarEip7594>> for BlobsBundleV2 {
1363 fn from(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
1364 Self::new(sidecars)
1365 }
1366}
1367
1368impl FromIterator<BlobTransactionSidecarEip7594> for BlobsBundleV2 {
1369 fn from_iter<T: IntoIterator<Item = BlobTransactionSidecarEip7594>>(iter: T) -> Self {
1370 Self::new(iter)
1371 }
1372}
1373
1374#[cfg(feature = "kzg")]
1375impl TryFrom<BlobsBundleV2> for BlobTransactionSidecarEip7594 {
1376 type Error = alloy_consensus::error::ValueError<BlobsBundleV2>;
1377
1378 fn try_from(value: BlobsBundleV2) -> Result<Self, Self::Error> {
1379 value.try_into_sidecar()
1380 }
1381}
1382
1383#[cfg(feature = "kzg")]
1384impl TryFrom<BlobsBundleV2> for BlobsBundleV1 {
1385 type Error = alloy_eips::eip4844::c_kzg::Error;
1386
1387 fn try_from(value: BlobsBundleV2) -> Result<Self, Self::Error> {
1388 value.try_into_v1()
1389 }
1390}
1391
1392#[derive(Clone, Debug, PartialEq, Eq)]
1395#[cfg_attr(feature = "serde", derive(serde::Serialize))]
1396#[cfg_attr(feature = "serde", serde(untagged))]
1397#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
1398pub enum ExecutionPayload {
1399 V1(ExecutionPayloadV1),
1401 V2(ExecutionPayloadV2),
1403 V3(ExecutionPayloadV3),
1405}
1406
1407impl ExecutionPayload {
1408 pub fn from_block_slow<T, H>(block: &Block<T, H>) -> (Self, ExecutionPayloadSidecar)
1416 where
1417 T: Encodable2718 + Transaction,
1418 H: BlockHeader + Sealable,
1419 {
1420 Self::from_block_unchecked(block.hash_slow(), block)
1421 }
1422
1423 pub fn from_block_unchecked<T, H>(
1429 block_hash: B256,
1430 block: &Block<T, H>,
1431 ) -> (Self, ExecutionPayloadSidecar)
1432 where
1433 T: Encodable2718 + Transaction,
1434 H: BlockHeader,
1435 {
1436 let sidecar = ExecutionPayloadSidecar::from_block(block);
1437
1438 let execution_payload = if block.header.parent_beacon_block_root().is_some() {
1439 Self::V3(ExecutionPayloadV3::from_block_unchecked(block_hash, block))
1441 } else if block.body.withdrawals.is_some() {
1442 Self::V2(ExecutionPayloadV2::from_block_unchecked(block_hash, block))
1444 } else {
1445 Self::V1(ExecutionPayloadV1::from_block_unchecked(block_hash, block))
1447 };
1448
1449 (execution_payload, sidecar)
1450 }
1451
1452 pub fn try_into_block_with_sidecar<T: Decodable2718>(
1462 self,
1463 sidecar: &ExecutionPayloadSidecar,
1464 ) -> Result<Block<T>, PayloadError> {
1465 self.try_into_block_with_sidecar_with(sidecar, |tx| {
1466 T::decode_2718_exact(tx.as_ref())
1467 .map_err(alloy_rlp::Error::from)
1468 .map_err(PayloadError::from)
1469 })
1470 }
1471
1472 pub fn try_into_block_with_sidecar_with<T, F, E>(
1478 self,
1479 sidecar: &ExecutionPayloadSidecar,
1480 f: F,
1481 ) -> Result<Block<T>, PayloadError>
1482 where
1483 F: FnMut(Bytes) -> Result<T, E>,
1484 E: Into<PayloadError>,
1485 {
1486 self.into_block_with_sidecar_raw(sidecar)?.try_map_transactions(f).map_err(Into::into)
1487 }
1488
1489 pub fn into_block_with_sidecar_raw(
1494 self,
1495 sidecar: &ExecutionPayloadSidecar,
1496 ) -> Result<Block<Bytes>, PayloadError> {
1497 let mut base_block = self.into_block_raw()?;
1498 base_block.header.parent_beacon_block_root = sidecar.parent_beacon_block_root();
1499 base_block.header.requests_hash = sidecar.requests_hash();
1500 Ok(base_block)
1501 }
1502
1503 pub fn try_into_block<T: Decodable2718>(self) -> Result<Block<T>, PayloadError> {
1512 self.try_into_block_with(|tx| {
1513 T::decode_2718_exact(tx.as_ref())
1514 .map_err(alloy_rlp::Error::from)
1515 .map_err(PayloadError::from)
1516 })
1517 }
1518
1519 pub fn try_into_block_with<T, F, E>(self, f: F) -> Result<Block<T>, PayloadError>
1528 where
1529 F: FnMut(Bytes) -> Result<T, E>,
1530 E: Into<PayloadError>,
1531 {
1532 self.into_block_raw()?.try_map_transactions(f).map_err(Into::into)
1533 }
1534
1535 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
1540 match self {
1541 Self::V1(payload) => payload.into_block_raw(),
1542 Self::V2(payload) => payload.into_block_raw(),
1543 Self::V3(payload) => payload.into_block_raw(),
1544 }
1545 }
1546
1547 pub const fn as_v1(&self) -> &ExecutionPayloadV1 {
1549 match self {
1550 Self::V1(payload) => payload,
1551 Self::V2(payload) => &payload.payload_inner,
1552 Self::V3(payload) => &payload.payload_inner.payload_inner,
1553 }
1554 }
1555
1556 pub const fn as_v1_mut(&mut self) -> &mut ExecutionPayloadV1 {
1558 match self {
1559 Self::V1(payload) => payload,
1560 Self::V2(payload) => &mut payload.payload_inner,
1561 Self::V3(payload) => &mut payload.payload_inner.payload_inner,
1562 }
1563 }
1564
1565 pub fn into_v1(self) -> ExecutionPayloadV1 {
1567 match self {
1568 Self::V1(payload) => payload,
1569 Self::V2(payload) => payload.payload_inner,
1570 Self::V3(payload) => payload.payload_inner.payload_inner,
1571 }
1572 }
1573
1574 pub const fn as_v2(&self) -> Option<&ExecutionPayloadV2> {
1576 match self {
1577 Self::V1(_) => None,
1578 Self::V2(payload) => Some(payload),
1579 Self::V3(payload) => Some(&payload.payload_inner),
1580 }
1581 }
1582
1583 pub const fn as_v2_mut(&mut self) -> Option<&mut ExecutionPayloadV2> {
1585 match self {
1586 Self::V1(_) => None,
1587 Self::V2(payload) => Some(payload),
1588 Self::V3(payload) => Some(&mut payload.payload_inner),
1589 }
1590 }
1591
1592 pub const fn as_v3(&self) -> Option<&ExecutionPayloadV3> {
1594 match self {
1595 Self::V1(_) | Self::V2(_) => None,
1596 Self::V3(payload) => Some(payload),
1597 }
1598 }
1599
1600 pub const fn as_v3_mut(&mut self) -> Option<&mut ExecutionPayloadV3> {
1602 match self {
1603 Self::V1(_) | Self::V2(_) => None,
1604 Self::V3(payload) => Some(payload),
1605 }
1606 }
1607
1608 pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
1610 match self.as_v2() {
1611 Some(payload) => Some(&payload.withdrawals),
1612 None => None,
1613 }
1614 }
1615
1616 pub const fn transactions(&self) -> &Vec<Bytes> {
1618 &self.as_v1().transactions
1619 }
1620
1621 pub const fn transactions_mut(&mut self) -> &mut Vec<Bytes> {
1623 &mut self.as_v1_mut().transactions
1624 }
1625
1626 pub fn header_info(&self) -> HeaderInfo {
1628 HeaderInfo {
1629 number: self.block_number(),
1630 beneficiary: self.fee_recipient(),
1631 timestamp: self.timestamp(),
1632 gas_limit: self.gas_limit(),
1633 base_fee_per_gas: Some(self.saturated_base_fee_per_gas()),
1634 excess_blob_gas: self.excess_blob_gas(),
1635 blob_gas_used: self.blob_gas_used(),
1636 difficulty: U256::ZERO,
1637 mix_hash: Some(self.prev_randao()),
1638 }
1639 }
1640
1641 pub fn saturated_base_fee_per_gas(&self) -> u64 {
1645 self.as_v1().base_fee_per_gas.saturating_to()
1646 }
1647
1648 pub fn blob_gas_used(&self) -> Option<u64> {
1650 self.as_v3().map(|payload| payload.blob_gas_used)
1651 }
1652
1653 pub fn excess_blob_gas(&self) -> Option<u64> {
1655 self.as_v3().map(|payload| payload.excess_blob_gas)
1656 }
1657
1658 pub const fn gas_limit(&self) -> u64 {
1660 self.as_v1().gas_limit
1661 }
1662
1663 pub const fn fee_recipient(&self) -> Address {
1665 self.as_v1().fee_recipient
1666 }
1667
1668 pub const fn timestamp(&self) -> u64 {
1670 self.as_v1().timestamp
1671 }
1672
1673 pub const fn parent_hash(&self) -> B256 {
1675 self.as_v1().parent_hash
1676 }
1677
1678 pub const fn block_hash(&self) -> B256 {
1680 self.as_v1().block_hash
1681 }
1682
1683 pub const fn block_number(&self) -> u64 {
1685 self.as_v1().block_number
1686 }
1687
1688 pub const fn block_num_hash(&self) -> BlockNumHash {
1690 self.as_v1().block_num_hash()
1691 }
1692
1693 pub const fn prev_randao(&self) -> B256 {
1695 self.as_v1().prev_randao
1696 }
1697
1698 pub fn blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
1702 Some(blob_params.calc_blob_fee(self.excess_blob_gas()?))
1703 }
1704
1705 pub fn next_block_blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
1711 Some(blob_params.calc_blob_fee(self.next_block_excess_blob_gas(blob_params)?))
1712 }
1713
1714 pub fn next_block_base_fee(&self, base_fee_params: BaseFeeParams) -> Option<u64> {
1718 self.as_v1().next_block_base_fee(base_fee_params)
1719 }
1720
1721 pub fn next_block_excess_blob_gas(&self, blob_params: BlobParams) -> Option<u64> {
1726 Some(blob_params.next_block_excess_blob_gas_osaka(
1727 self.excess_blob_gas()?,
1728 self.blob_gas_used()?,
1729 self.as_v1().base_fee_per_gas.to(),
1730 ))
1731 }
1732
1733 pub fn maybe_next_block_excess_blob_gas(&self, blob_params: Option<BlobParams>) -> Option<u64> {
1738 self.next_block_excess_blob_gas(blob_params?)
1739 }
1740
1741 pub fn decoded_transactions<T: Decodable2718>(
1745 &self,
1746 ) -> impl Iterator<Item = Eip2718Result<T>> + '_ {
1747 self.transactions().iter().map(|tx_bytes| T::decode_2718_exact(tx_bytes.as_ref()))
1748 }
1749
1750 pub fn decoded_transactions_with_encoded<T: Decodable2718>(
1754 &self,
1755 ) -> impl Iterator<Item = Eip2718Result<WithEncoded<T>>> + '_ {
1756 self.transactions().iter().map(|tx_bytes| {
1757 T::decode_2718_exact(tx_bytes.as_ref()).map(|tx| WithEncoded::new(tx_bytes.clone(), tx))
1758 })
1759 }
1760
1761 pub fn recovered_transactions<T>(
1765 &self,
1766 ) -> impl Iterator<
1767 Item = Result<
1768 alloy_consensus::transaction::Recovered<T>,
1769 alloy_consensus::crypto::RecoveryError,
1770 >,
1771 > + '_
1772 where
1773 T: Decodable2718 + alloy_consensus::transaction::SignerRecoverable,
1774 {
1775 self.decoded_transactions::<T>().map(|res| {
1776 res.map_err(alloy_consensus::crypto::RecoveryError::from_source)
1777 .and_then(|tx| tx.try_into_recovered())
1778 })
1779 }
1780
1781 pub fn recovered_transactions_with_encoded<T>(
1787 &self,
1788 ) -> impl Iterator<
1789 Item = Result<
1790 WithEncoded<alloy_consensus::transaction::Recovered<T>>,
1791 alloy_consensus::crypto::RecoveryError,
1792 >,
1793 > + '_
1794 where
1795 T: Decodable2718 + alloy_consensus::transaction::SignerRecoverable,
1796 {
1797 self.transactions().iter().map(|tx_bytes| {
1798 T::decode_2718_exact(tx_bytes.as_ref())
1799 .map_err(alloy_consensus::crypto::RecoveryError::from_source)
1800 .and_then(|tx| {
1801 tx.try_into_recovered()
1802 .map(|recovered| WithEncoded::new(tx_bytes.clone(), recovered))
1803 })
1804 })
1805 }
1806}
1807
1808impl From<ExecutionPayloadV1> for ExecutionPayload {
1809 fn from(payload: ExecutionPayloadV1) -> Self {
1810 Self::V1(payload)
1811 }
1812}
1813
1814impl From<ExecutionPayloadV2> for ExecutionPayload {
1815 fn from(payload: ExecutionPayloadV2) -> Self {
1816 Self::V2(payload)
1817 }
1818}
1819
1820impl From<ExecutionPayloadFieldV2> for ExecutionPayload {
1821 fn from(payload: ExecutionPayloadFieldV2) -> Self {
1822 payload.into_payload()
1823 }
1824}
1825
1826impl From<ExecutionPayloadV3> for ExecutionPayload {
1827 fn from(payload: ExecutionPayloadV3) -> Self {
1828 Self::V3(payload)
1829 }
1830}
1831
1832impl<T: Decodable2718> TryFrom<ExecutionPayload> for Block<T> {
1833 type Error = PayloadError;
1834
1835 fn try_from(value: ExecutionPayload) -> Result<Self, Self::Error> {
1836 value.try_into_block()
1837 }
1838}
1839
1840#[cfg(feature = "serde")]
1842impl<'de> serde::Deserialize<'de> for ExecutionPayload {
1843 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1844 where
1845 D: serde::Deserializer<'de>,
1846 {
1847 use alloy_primitives::U64;
1848
1849 struct ExecutionPayloadVisitor;
1850
1851 impl<'de> serde::de::Visitor<'de> for ExecutionPayloadVisitor {
1852 type Value = ExecutionPayload;
1853
1854 fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1855 formatter.write_str("a valid ExecutionPayload object")
1856 }
1857
1858 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
1859 where
1860 A: serde::de::MapAccess<'de>,
1861 {
1862 #[cfg_attr(feature = "serde", derive(serde::Deserialize))]
1864 #[cfg_attr(feature = "serde", serde(field_identifier, rename_all = "camelCase"))]
1865 enum Fields {
1866 ParentHash,
1867 FeeRecipient,
1868 StateRoot,
1869 ReceiptsRoot,
1870 LogsBloom,
1871 PrevRandao,
1872 BlockNumber,
1873 GasLimit,
1874 GasUsed,
1875 Timestamp,
1876 ExtraData,
1877 BaseFeePerGas,
1878 BlockHash,
1879 Transactions,
1880 Withdrawals,
1882 BlobGasUsed,
1884 ExcessBlobGas,
1885 }
1886
1887 let mut parent_hash = None;
1888 let mut fee_recipient = None;
1889 let mut state_root = None;
1890 let mut receipts_root = None;
1891 let mut logs_bloom = None;
1892 let mut prev_randao = None;
1893 let mut block_number = None;
1894 let mut gas_limit = None;
1895 let mut gas_used = None;
1896 let mut timestamp = None;
1897 let mut extra_data = None;
1898 let mut base_fee_per_gas = None;
1899 let mut block_hash = None;
1900 let mut transactions = None;
1901 let mut withdrawals = None;
1902 let mut blob_gas_used = None;
1903 let mut excess_blob_gas = None;
1904
1905 while let Some(key) = map.next_key()? {
1906 match key {
1907 Fields::ParentHash => parent_hash = Some(map.next_value()?),
1908 Fields::FeeRecipient => fee_recipient = Some(map.next_value()?),
1909 Fields::StateRoot => state_root = Some(map.next_value()?),
1910 Fields::ReceiptsRoot => receipts_root = Some(map.next_value()?),
1911 Fields::LogsBloom => logs_bloom = Some(map.next_value()?),
1912 Fields::PrevRandao => prev_randao = Some(map.next_value()?),
1913 Fields::BlockNumber => {
1914 let raw = map.next_value::<U64>()?;
1915 block_number = Some(raw.to());
1916 }
1917 Fields::GasLimit => {
1918 let raw = map.next_value::<U64>()?;
1919 gas_limit = Some(raw.to());
1920 }
1921 Fields::GasUsed => {
1922 let raw = map.next_value::<U64>()?;
1923 gas_used = Some(raw.to());
1924 }
1925 Fields::Timestamp => {
1926 let raw = map.next_value::<U64>()?;
1927 timestamp = Some(raw.to());
1928 }
1929 Fields::ExtraData => extra_data = Some(map.next_value()?),
1930 Fields::BaseFeePerGas => base_fee_per_gas = Some(map.next_value()?),
1931 Fields::BlockHash => block_hash = Some(map.next_value()?),
1932 Fields::Transactions => transactions = Some(map.next_value()?),
1933 Fields::Withdrawals => withdrawals = Some(map.next_value()?),
1934 Fields::BlobGasUsed => {
1935 let raw = map.next_value::<U64>()?;
1936 blob_gas_used = Some(raw.to());
1937 }
1938 Fields::ExcessBlobGas => {
1939 let raw = map.next_value::<U64>()?;
1940 excess_blob_gas = Some(raw.to());
1941 }
1942 }
1943 }
1944
1945 let parent_hash =
1946 parent_hash.ok_or_else(|| serde::de::Error::missing_field("parentHash"))?;
1947 let fee_recipient =
1948 fee_recipient.ok_or_else(|| serde::de::Error::missing_field("feeRecipient"))?;
1949 let state_root =
1950 state_root.ok_or_else(|| serde::de::Error::missing_field("stateRoot"))?;
1951 let receipts_root =
1952 receipts_root.ok_or_else(|| serde::de::Error::missing_field("receiptsRoot"))?;
1953 let logs_bloom =
1954 logs_bloom.ok_or_else(|| serde::de::Error::missing_field("logsBloom"))?;
1955 let prev_randao =
1956 prev_randao.ok_or_else(|| serde::de::Error::missing_field("prevRandao"))?;
1957 let block_number =
1958 block_number.ok_or_else(|| serde::de::Error::missing_field("blockNumber"))?;
1959 let gas_limit =
1960 gas_limit.ok_or_else(|| serde::de::Error::missing_field("gasLimit"))?;
1961 let gas_used =
1962 gas_used.ok_or_else(|| serde::de::Error::missing_field("gasUsed"))?;
1963 let timestamp =
1964 timestamp.ok_or_else(|| serde::de::Error::missing_field("timestamp"))?;
1965 let extra_data =
1966 extra_data.ok_or_else(|| serde::de::Error::missing_field("extraData"))?;
1967 let base_fee_per_gas = base_fee_per_gas
1968 .ok_or_else(|| serde::de::Error::missing_field("baseFeePerGas"))?;
1969 let block_hash =
1970 block_hash.ok_or_else(|| serde::de::Error::missing_field("blockHash"))?;
1971 let transactions =
1972 transactions.ok_or_else(|| serde::de::Error::missing_field("transactions"))?;
1973
1974 let v1 = ExecutionPayloadV1 {
1975 parent_hash,
1976 fee_recipient,
1977 state_root,
1978 receipts_root,
1979 logs_bloom,
1980 prev_randao,
1981 block_number,
1982 gas_limit,
1983 gas_used,
1984 timestamp,
1985 extra_data,
1986 base_fee_per_gas,
1987 block_hash,
1988 transactions,
1989 };
1990
1991 let Some(withdrawals) = withdrawals else {
1992 return if blob_gas_used.is_none() && excess_blob_gas.is_none() {
1993 Ok(ExecutionPayload::V1(v1))
1994 } else {
1995 Err(serde::de::Error::custom("invalid enum variant"))
1996 };
1997 };
1998
1999 if let (Some(blob_gas_used), Some(excess_blob_gas)) =
2000 (blob_gas_used, excess_blob_gas)
2001 {
2002 return Ok(ExecutionPayload::V3(ExecutionPayloadV3 {
2003 payload_inner: ExecutionPayloadV2 { payload_inner: v1, withdrawals },
2004 blob_gas_used,
2005 excess_blob_gas,
2006 }));
2007 }
2008
2009 if blob_gas_used.is_some() || excess_blob_gas.is_some() {
2011 return Err(serde::de::Error::custom("invalid enum variant"));
2012 }
2013
2014 Ok(ExecutionPayload::V2(ExecutionPayloadV2 { payload_inner: v1, withdrawals }))
2015 }
2016 }
2017
2018 const FIELDS: &[&str] = &[
2019 "parentHash",
2020 "feeRecipient",
2021 "stateRoot",
2022 "receiptsRoot",
2023 "logsBloom",
2024 "prevRandao",
2025 "blockNumber",
2026 "gasLimit",
2027 "gasUsed",
2028 "timestamp",
2029 "extraData",
2030 "baseFeePerGas",
2031 "blockHash",
2032 "transactions",
2033 "withdrawals",
2034 "blobGasUsed",
2035 "excessBlobGas",
2036 ];
2037 deserializer.deserialize_struct("ExecutionPayload", FIELDS, ExecutionPayloadVisitor)
2038 }
2039}
2040
2041#[derive(Clone, Debug, PartialEq, Eq)]
2045#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2046#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2047pub struct ExecutionPayloadBodyV1 {
2048 pub transactions: Vec<Bytes>,
2050 pub withdrawals: Option<Vec<Withdrawal>>,
2054}
2055
2056impl ExecutionPayloadBodyV1 {
2057 pub fn new<'a, T>(
2059 withdrawals: Option<Withdrawals>,
2060 transactions: impl IntoIterator<Item = &'a T>,
2061 ) -> Self
2062 where
2063 T: Encodable2718 + 'a,
2064 {
2065 Self {
2066 transactions: transactions.into_iter().map(|tx| tx.encoded_2718().into()).collect(),
2067 withdrawals: withdrawals.map(Withdrawals::into_inner),
2068 }
2069 }
2070
2071 pub fn from_block<T: Encodable2718, H>(block: Block<T, H>) -> Self {
2073 Self::new(block.body.withdrawals.clone(), block.body.transactions())
2074 }
2075}
2076
2077impl<T: Encodable2718, H> From<Block<T, H>> for ExecutionPayloadBodyV1 {
2078 fn from(value: Block<T, H>) -> Self {
2079 Self::from_block(value)
2080 }
2081}
2082
2083#[derive(Clone, Debug, Default, PartialEq, Eq)]
2086#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2087#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2088#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2089pub struct PayloadAttributes {
2090 #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
2092 pub timestamp: u64,
2093 pub prev_randao: B256,
2095 pub suggested_fee_recipient: Address,
2097 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2100 pub withdrawals: Option<Vec<Withdrawal>>,
2101 #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2105 pub parent_beacon_block_root: Option<B256>,
2106}
2107
2108#[derive(Clone, Debug, PartialEq, Eq)]
2110#[cfg_attr(feature = "serde", derive(serde::Deserialize))]
2111#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
2112#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2113pub struct PayloadStatus {
2114 #[cfg_attr(feature = "serde", serde(flatten))]
2116 pub status: PayloadStatusEnum,
2117 pub latest_valid_hash: Option<B256>,
2119}
2120
2121impl PayloadStatus {
2122 pub const fn new(status: PayloadStatusEnum, latest_valid_hash: Option<B256>) -> Self {
2124 Self { status, latest_valid_hash }
2125 }
2126
2127 pub const fn from_status(status: PayloadStatusEnum) -> Self {
2129 Self { status, latest_valid_hash: None }
2130 }
2131
2132 pub const fn with_latest_valid_hash(mut self, latest_valid_hash: B256) -> Self {
2134 self.latest_valid_hash = Some(latest_valid_hash);
2135 self
2136 }
2137
2138 pub const fn maybe_latest_valid_hash(mut self, latest_valid_hash: Option<B256>) -> Self {
2140 self.latest_valid_hash = latest_valid_hash;
2141 self
2142 }
2143
2144 pub const fn is_syncing(&self) -> bool {
2146 self.status.is_syncing()
2147 }
2148
2149 pub const fn is_valid(&self) -> bool {
2151 self.status.is_valid()
2152 }
2153
2154 pub const fn is_invalid(&self) -> bool {
2156 self.status.is_invalid()
2157 }
2158}
2159
2160impl core::fmt::Display for PayloadStatus {
2161 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2162 write!(
2163 f,
2164 "PayloadStatus {{ status: {}, latestValidHash: {:?} }}",
2165 self.status, self.latest_valid_hash
2166 )
2167 }
2168}
2169
2170#[cfg(feature = "serde")]
2171impl serde::Serialize for PayloadStatus {
2172 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
2173 where
2174 S: serde::Serializer,
2175 {
2176 use serde::ser::SerializeMap;
2177 let mut map = serializer.serialize_map(Some(3))?;
2178 map.serialize_entry("status", self.status.as_str())?;
2179 map.serialize_entry("latestValidHash", &self.latest_valid_hash)?;
2180 map.serialize_entry("validationError", &self.status.validation_error())?;
2181 map.end()
2182 }
2183}
2184
2185impl From<PayloadError> for PayloadStatusEnum {
2186 fn from(error: PayloadError) -> Self {
2187 Self::Invalid { validation_error: error.to_string() }
2188 }
2189}
2190
2191#[derive(Clone, Debug, PartialEq, Eq)]
2193#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2194#[cfg_attr(feature = "serde", serde(tag = "status", rename_all = "SCREAMING_SNAKE_CASE"))]
2195#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2196pub enum PayloadStatusEnum {
2197 Valid,
2201
2202 Invalid {
2206 #[cfg_attr(feature = "serde", serde(rename = "validationError"))]
2208 validation_error: String,
2209 },
2210
2211 Syncing,
2215
2216 Accepted,
2219}
2220
2221impl PayloadStatusEnum {
2222 pub const fn as_str(&self) -> &'static str {
2224 match self {
2225 Self::Valid => "VALID",
2226 Self::Invalid { .. } => "INVALID",
2227 Self::Syncing => "SYNCING",
2228 Self::Accepted => "ACCEPTED",
2229 }
2230 }
2231
2232 pub fn validation_error(&self) -> Option<&str> {
2234 match self {
2235 Self::Invalid { validation_error } => Some(validation_error),
2236 _ => None,
2237 }
2238 }
2239
2240 pub const fn is_syncing(&self) -> bool {
2242 matches!(self, Self::Syncing)
2243 }
2244
2245 pub const fn is_valid(&self) -> bool {
2247 matches!(self, Self::Valid)
2248 }
2249
2250 pub const fn is_invalid(&self) -> bool {
2252 matches!(self, Self::Invalid { .. })
2253 }
2254}
2255
2256impl core::fmt::Display for PayloadStatusEnum {
2257 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
2258 match self {
2259 Self::Invalid { validation_error } => {
2260 f.write_str(self.as_str())?;
2261 f.write_str(": ")?;
2262 f.write_str(validation_error.as_str())
2263 }
2264 _ => f.write_str(self.as_str()),
2265 }
2266 }
2267}
2268
2269#[derive(Debug, Clone)]
2272#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2273#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))]
2274pub struct ExecutionData {
2275 pub payload: ExecutionPayload,
2277 pub sidecar: ExecutionPayloadSidecar,
2279}
2280
2281impl ExecutionData {
2282 pub const fn new(payload: ExecutionPayload, sidecar: ExecutionPayloadSidecar) -> Self {
2284 Self { payload, sidecar }
2285 }
2286
2287 pub fn from_block_unchecked<T, H>(block_hash: B256, block: &Block<T, H>) -> Self
2296 where
2297 T: Encodable2718 + Transaction,
2298 H: BlockHeader,
2299 {
2300 let (payload, sidecar) = ExecutionPayload::from_block_unchecked(block_hash, block);
2301 Self::new(payload, sidecar)
2302 }
2303
2304 pub const fn parent_hash(&self) -> B256 {
2306 self.payload.parent_hash()
2307 }
2308
2309 pub const fn block_hash(&self) -> B256 {
2311 self.payload.block_hash()
2312 }
2313
2314 pub const fn block_number(&self) -> u64 {
2316 self.payload.block_number()
2317 }
2318
2319 pub fn parent_beacon_block_root(&self) -> Option<B256> {
2321 self.sidecar.parent_beacon_block_root()
2322 }
2323
2324 pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
2326 self.payload.withdrawals()
2327 }
2328
2329 pub fn try_into_block<T: Decodable2718>(
2339 self,
2340 ) -> Result<alloy_consensus::Block<T>, PayloadError> {
2341 self.try_into_block_with(|tx| {
2342 T::decode_2718_exact(tx.as_ref())
2343 .map_err(alloy_rlp::Error::from)
2344 .map_err(PayloadError::from)
2345 })
2346 }
2347
2348 pub fn try_into_block_with<T, F, E>(
2359 self,
2360 f: F,
2361 ) -> Result<alloy_consensus::Block<T>, PayloadError>
2362 where
2363 F: FnMut(Bytes) -> Result<T, E>,
2364 E: Into<PayloadError>,
2365 {
2366 self.payload.try_into_block_with_sidecar_with(&self.sidecar, f)
2367 }
2368
2369 pub fn into_block_raw(self) -> Result<Block<Bytes>, PayloadError> {
2374 let mut base_block = self.payload.into_block_raw()?;
2375 base_block.header.parent_beacon_block_root = self.sidecar.parent_beacon_block_root();
2376 base_block.header.requests_hash = self.sidecar.requests_hash();
2377 Ok(base_block)
2378 }
2379}
2380
2381#[cfg(test)]
2382mod tests {
2383 use super::*;
2384 use crate::{CancunPayloadFields, PayloadValidationError};
2385 use alloc::vec;
2386 use alloy_consensus::TxEnvelope;
2387 use alloy_primitives::{b256, hex};
2388 use similar_asserts::assert_eq;
2389
2390 #[test]
2391 #[cfg(feature = "kzg")]
2392 fn convert_empty_bundle() {
2393 let bundle = BlobsBundleV1::default();
2394 let _sidecar = bundle.try_into_sidecar().unwrap();
2395 }
2396
2397 #[test]
2398 #[cfg(feature = "serde")]
2399 fn serde_blobsbundlev1_empty() {
2400 let blobs_bundle_v1 = BlobsBundleV1::empty();
2401
2402 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
2403 let deserialized: BlobsBundleV1 = serde_json::from_str(&serialized).unwrap();
2404 assert_eq!(deserialized, blobs_bundle_v1);
2405 }
2406
2407 #[test]
2408 #[cfg(feature = "serde")]
2409 #[cfg(not(debug_assertions))]
2410 fn serde_blobsbundlev1_not_empty_pass() {
2411 let blobs_bundle_v1 = BlobsBundleV1 {
2412 proofs: vec![Bytes48::default()],
2413 commitments: vec![Bytes48::default()],
2414 blobs: vec![Blob::default()],
2415 };
2416
2417 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
2418 let deserialized: BlobsBundleV1 = serde_json::from_str(&serialized).unwrap();
2419 assert_eq!(deserialized, blobs_bundle_v1);
2420 }
2421
2422 #[test]
2423 #[cfg(feature = "serde")]
2424 #[cfg(not(debug_assertions))]
2425 fn serde_blobsbundlev1_not_empty_fail() {
2426 let blobs_bundle_v1 = BlobsBundleV1 {
2427 proofs: vec![Bytes48::default(), Bytes48::default()],
2428 commitments: vec![Bytes48::default()],
2429 blobs: vec![Blob::default()],
2430 };
2431
2432 let serialized = serde_json::to_string(&blobs_bundle_v1).unwrap();
2433 let deserialized: Result<BlobsBundleV1, serde_json::Error> =
2434 serde_json::from_str(&serialized);
2435 assert!(deserialized.is_err(), "invalid length 2, expected commitments.len()");
2436 }
2437
2438 #[test]
2439 #[cfg(feature = "serde")]
2440 #[cfg(not(debug_assertions))]
2441 fn serde_blobsbundlev2_not_empty_pass() {
2442 let commitments = vec![Bytes48::default()];
2443
2444 let blobs_bundle_v2 = BlobsBundleV2 {
2445 proofs: vec![Bytes48::default(); commitments.len() * CELLS_PER_EXT_BLOB],
2446 commitments,
2447 blobs: vec![Blob::default()],
2448 };
2449
2450 let serialized = serde_json::to_string(&blobs_bundle_v2).unwrap();
2451 let deserialized: BlobsBundleV2 = serde_json::from_str(&serialized).unwrap();
2452 assert_eq!(deserialized, blobs_bundle_v2);
2453 }
2454
2455 #[test]
2456 #[cfg(feature = "serde")]
2457 #[cfg(not(debug_assertions))]
2458 fn serde_blobsbundlev2_not_empty_fail() {
2459 let blobs_bundle_v2 = BlobsBundleV2 {
2460 proofs: vec![Bytes48::default()],
2461 commitments: vec![Bytes48::default()],
2462 blobs: vec![],
2463 };
2464
2465 let serialized = serde_json::to_string(&blobs_bundle_v2).unwrap();
2466 let deserialized: Result<BlobsBundleV2, serde_json::Error> =
2467 serde_json::from_str(&serialized);
2468 assert!(deserialized.is_err());
2469 }
2470
2471 #[test]
2472 #[cfg(feature = "ssz")]
2473 #[cfg(not(debug_assertions))]
2474 fn ssz_blobsbundlev2_roundtrip() {
2475 let commitments = vec![Bytes48::default(), Bytes48::default()];
2476 let num_blobs = commitments.len();
2477
2478 let blobs_bundle_v2 = BlobsBundleV2 {
2479 commitments,
2480 proofs: vec![Bytes48::default(); num_blobs * CELLS_PER_EXT_BLOB],
2481 blobs: vec![Blob::default(); num_blobs],
2482 };
2483
2484 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
2485 let decoded: BlobsBundleV2 = ssz::Decode::from_ssz_bytes(&encoded).unwrap();
2486
2487 assert_eq!(decoded, blobs_bundle_v2);
2488 }
2489
2490 #[test]
2491 #[cfg(feature = "ssz")]
2492 #[cfg(not(debug_assertions))]
2493 fn ssz_blobsbundlev2_invalid_proofs_length() {
2494 let commitments = vec![Bytes48::default()];
2495
2496 let blobs_bundle_v2 = BlobsBundleV2 {
2497 commitments,
2498 proofs: vec![Bytes48::default(); 2],
2499 blobs: vec![Blob::default()],
2500 };
2501
2502 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
2503
2504 let result: Result<BlobsBundleV2, _> = ssz::Decode::from_ssz_bytes(&encoded);
2506 assert!(result.is_err());
2507 }
2508
2509 #[test]
2510 #[cfg(feature = "ssz")]
2511 #[cfg(not(debug_assertions))]
2512 fn ssz_blobsbundlev2_mismatched_commitments_blobs() {
2513 let blobs_bundle_v2 = BlobsBundleV2 {
2514 commitments: vec![Bytes48::default(), Bytes48::default()],
2515 proofs: vec![Bytes48::default(); CELLS_PER_EXT_BLOB],
2516 blobs: vec![Blob::default()],
2517 };
2518
2519 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
2520
2521 let result: Result<BlobsBundleV2, _> = ssz::Decode::from_ssz_bytes(&encoded);
2523 assert!(result.is_err());
2524 }
2525
2526 #[test]
2527 #[cfg(feature = "ssz")]
2528 fn ssz_blobsbundlev2_empty() {
2529 let blobs_bundle_v2 = BlobsBundleV2 { commitments: vec![], proofs: vec![], blobs: vec![] };
2530
2531 let encoded = ssz::Encode::as_ssz_bytes(&blobs_bundle_v2);
2532
2533 let decoded: BlobsBundleV2 = ssz::Decode::from_ssz_bytes(&encoded).unwrap();
2535 assert_eq!(decoded, blobs_bundle_v2);
2536 }
2537
2538 #[test]
2539 #[cfg(feature = "serde")]
2540 fn serde_payload_status() {
2541 let s = r#"{"status":"SYNCING","latestValidHash":null,"validationError":null}"#;
2542 let status: PayloadStatus = serde_json::from_str(s).unwrap();
2543 assert_eq!(status.status, PayloadStatusEnum::Syncing);
2544 assert!(status.latest_valid_hash.is_none());
2545 assert!(status.status.validation_error().is_none());
2546 assert_eq!(serde_json::to_string(&status).unwrap(), s);
2547
2548 let full = s;
2549 let s = r#"{"status":"SYNCING","latestValidHash":null}"#;
2550 let status: PayloadStatus = serde_json::from_str(s).unwrap();
2551 assert_eq!(status.status, PayloadStatusEnum::Syncing);
2552 assert!(status.latest_valid_hash.is_none());
2553 assert!(status.status.validation_error().is_none());
2554 assert_eq!(serde_json::to_string(&status).unwrap(), full);
2555 }
2556
2557 #[test]
2558 #[cfg(feature = "serde")]
2559 fn serde_payload_status_error_deserialize() {
2560 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"Failed to decode block"}"#;
2561 let q = PayloadStatus {
2562 latest_valid_hash: None,
2563 status: PayloadStatusEnum::Invalid {
2564 validation_error: "Failed to decode block".to_string(),
2565 },
2566 };
2567 assert_eq!(q, serde_json::from_str(s).unwrap());
2568
2569 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"links to previously rejected block"}"#;
2570 let q = PayloadStatus {
2571 latest_valid_hash: None,
2572 status: PayloadStatusEnum::Invalid {
2573 validation_error: PayloadValidationError::LinksToRejectedPayload.to_string(),
2574 },
2575 };
2576 assert_eq!(q, serde_json::from_str(s).unwrap());
2577
2578 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":"invalid block number"}"#;
2579 let q = PayloadStatus {
2580 latest_valid_hash: None,
2581 status: PayloadStatusEnum::Invalid {
2582 validation_error: PayloadValidationError::InvalidBlockNumber.to_string(),
2583 },
2584 };
2585 assert_eq!(q, serde_json::from_str(s).unwrap());
2586
2587 let s = r#"{"status":"INVALID","latestValidHash":null,"validationError":
2588 "invalid merkle root: (remote: 0x3f77fb29ce67436532fee970e1add8f5cc80e8878c79b967af53b1fd92a0cab7 local: 0x603b9628dabdaadb442a3bb3d7e0360efc110e1948472909230909f1690fed17)"}"#;
2589 let q = PayloadStatus {
2590 latest_valid_hash: None,
2591 status: PayloadStatusEnum::Invalid {
2592 validation_error: PayloadValidationError::InvalidStateRoot {
2593 remote: "0x3f77fb29ce67436532fee970e1add8f5cc80e8878c79b967af53b1fd92a0cab7"
2594 .parse()
2595 .unwrap(),
2596 local: "0x603b9628dabdaadb442a3bb3d7e0360efc110e1948472909230909f1690fed17"
2597 .parse()
2598 .unwrap(),
2599 }
2600 .to_string(),
2601 },
2602 };
2603 assert_eq!(q, serde_json::from_str(s).unwrap());
2604 }
2605
2606 #[test]
2607 #[cfg(feature = "serde")]
2608 fn serde_roundtrip_legacy_txs_payload_v1() {
2609 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"]}"#;
2611 let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap();
2612 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
2613
2614 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
2615 assert_eq!(any_payload, payload.into());
2616 }
2617
2618 #[test]
2619 #[cfg(feature = "serde")]
2620 fn serde_roundtrip_legacy_txs_payload_v3() {
2621 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"}"#;
2623 let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap();
2624 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
2625
2626 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
2627 assert_eq!(any_payload, payload.into());
2628 }
2629
2630 #[test]
2631 #[cfg(feature = "serde")]
2632 fn serde_roundtrip_enveloped_txs_payload_v1() {
2633 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"]}"#;
2635 let payload: ExecutionPayloadV1 = serde_json::from_str(s).unwrap();
2636 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
2637
2638 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
2639 assert_eq!(any_payload, payload.into());
2640 }
2641
2642 #[test]
2643 #[cfg(feature = "serde")]
2644 fn serde_roundtrip_enveloped_txs_payload_v3() {
2645 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"}"#;
2647 let payload: ExecutionPayloadV3 = serde_json::from_str(s).unwrap();
2648 assert_eq!(serde_json::to_string(&payload).unwrap(), s);
2649
2650 let any_payload: ExecutionPayload = serde_json::from_str(s).unwrap();
2651 assert_eq!(any_payload, payload.into());
2652 }
2653
2654 #[test]
2655 #[cfg(feature = "serde")]
2656 fn serde_roundtrip_execution_payload_envelope_v3() {
2657 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}"#;
2659 let envelope: ExecutionPayloadEnvelopeV3 = serde_json::from_str(response).unwrap();
2660 assert_eq!(serde_json::to_string(&envelope).unwrap(), response);
2661 }
2662
2663 #[test]
2664 #[cfg(feature = "serde")]
2665 fn serde_payload_input_enum_v3() {
2666 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"}"#;
2667
2668 let payload: ExecutionPayload = serde_json::from_str(response_v3).unwrap();
2669 assert!(payload.as_v3().is_some());
2670 assert_eq!(serde_json::to_string(&payload).unwrap(), response_v3);
2671
2672 let payload_v3: ExecutionPayloadV3 = serde_json::from_str(response_v3).unwrap();
2673 assert_eq!(payload.as_v3().unwrap(), &payload_v3);
2674 }
2675
2676 #[test]
2677 #[cfg(feature = "serde")]
2678 fn serde_payload_input_enum_v2() {
2679 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":[]}"#;
2680
2681 let payload: ExecutionPayload = serde_json::from_str(response_v2).unwrap();
2682 assert!(payload.as_v3().is_none());
2683 assert!(payload.as_v2().is_some());
2684 assert_eq!(serde_json::to_string(&payload).unwrap(), response_v2);
2685
2686 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(response_v2).unwrap();
2687 assert_eq!(payload.as_v2().unwrap(), &payload_v2);
2688 }
2689
2690 #[test]
2691 #[cfg(feature = "serde")]
2692 fn serde_payload_input_enum_faulty_v2() {
2693 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"}"#;
2695
2696 let payload: Result<ExecutionPayload, serde_json::Error> =
2697 serde_json::from_str(response_faulty);
2698 assert!(payload.is_err());
2699 }
2700
2701 #[test]
2702 #[cfg(feature = "serde")]
2703 fn serde_payload_input_enum_faulty_v1() {
2704 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"}"#;
2706
2707 let payload: Result<ExecutionPayload, serde_json::Error> =
2708 serde_json::from_str(response_faulty);
2709 assert!(payload.is_err());
2710 }
2711
2712 #[test]
2713 #[cfg(feature = "serde")]
2714 fn serde_faulty_roundtrip_payload_input_v3() {
2715 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"}"#;
2719
2720 let payload_v2: ExecutionPayloadV2 = serde_json::from_str(response_v3).unwrap();
2721 assert_ne!(response_v3, serde_json::to_string(&payload_v2).unwrap());
2722
2723 let payload_v1: ExecutionPayloadV1 = serde_json::from_str(response_v3).unwrap();
2724 assert_ne!(response_v3, serde_json::to_string(&payload_v1).unwrap());
2725 }
2726
2727 #[test]
2728 #[cfg(feature = "serde")]
2729 fn serde_faulty_roundtrip_payload_input_v2() {
2730 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":[]}"#;
2734
2735 let payload: ExecutionPayloadV1 = serde_json::from_str(response_v2).unwrap();
2736 assert_ne!(response_v2, serde_json::to_string(&payload).unwrap());
2737 }
2738
2739 #[test]
2740 #[cfg(feature = "serde")]
2741 fn serde_deserialize_execution_payload_input_v2() {
2742 let response = r#"
2743{
2744 "baseFeePerGas": "0x173b30b3",
2745 "blockHash": "0x99d486755fd046ad0bbb60457bac93d4856aa42fa00629cc7e4a28b65b5f8164",
2746 "blockNumber": "0xb",
2747 "extraData": "0xd883010d01846765746888676f312e32302e33856c696e7578",
2748 "feeRecipient": "0x0000000000000000000000000000000000000000",
2749 "gasLimit": "0x405829",
2750 "gasUsed": "0x3f0ca0",
2751 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
2752 "parentHash": "0xfe34aaa2b869c66a727783ee5ad3e3983b6ef22baf24a1e502add94e7bcac67a",
2753 "prevRandao": "0x74132c32fe3ab9a470a8352544514d21b6969e7749f97742b53c18a1b22b396c",
2754 "receiptsRoot": "0x6a5c41dc55a1bd3e74e7f6accc799efb08b00c36c15265058433fcea6323e95f",
2755 "stateRoot": "0xde3b357f5f099e4c33d0343c9e9d204d663d7bd9c65020a38e5d0b2a9ace78a2",
2756 "timestamp": "0x6507d6b4",
2757 "transactions": [
2758 "0xf86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53",
2759 "0xf86d0b8458b20efd82520894687704db07e902e9a8b3754031d168d46e3d586e8806f3e8d87878800080820a95a0e3108f710902be662d5c978af16109961ffaf2ac4f88522407d40949a9574276a0205719ed21889b42ab5c1026d40b759a507c12d92db0d100fa69e1ac79137caa",
2760 "0xf86d0c8458b20efd8252089415e6a5a2e131dd5467fa1ff3acd104f45ee5940b8806f3e8d87878800080820a96a0af556ba9cda1d686239e08c24e169dece7afa7b85e0948eaa8d457c0561277fca029da03d3af0978322e54ac7e8e654da23934e0dd839804cb0430f8aaafd732dc",
2761 "0xf8521784565adcb7830186a0808080820a96a0ec782872a673a9fe4eff028a5bdb30d6b8b7711f58a187bf55d3aec9757cb18ea001796d373da76f2b0aeda72183cce0ad070a4f03aa3e6fee4c757a9444245206",
2762 "0xf8521284565adcb7830186a0808080820a95a08a0ea89028eff02596b385a10e0bd6ae098f3b281be2c95a9feb1685065d7384a06239d48a72e4be767bd12f317dd54202f5623a33e71e25a87cb25dd781aa2fc8",
2763 "0xf8521384565adcb7830186a0808080820a95a0784dbd311a82f822184a46f1677a428cbe3a2b88a798fb8ad1370cdbc06429e8a07a7f6a0efd428e3d822d1de9a050b8a883938b632185c254944dd3e40180eb79"
2764 ],
2765 "withdrawals": []
2766}
2767 "#;
2768 let payload: ExecutionPayloadInputV2 = serde_json::from_str(response).unwrap();
2769 assert_eq!(payload.withdrawals, Some(vec![]));
2770
2771 let response = r#"
2772{
2773 "baseFeePerGas": "0x173b30b3",
2774 "blockHash": "0x99d486755fd046ad0bbb60457bac93d4856aa42fa00629cc7e4a28b65b5f8164",
2775 "blockNumber": "0xb",
2776 "extraData": "0xd883010d01846765746888676f312e32302e33856c696e7578",
2777 "feeRecipient": "0x0000000000000000000000000000000000000000",
2778 "gasLimit": "0x405829",
2779 "gasUsed": "0x3f0ca0",
2780 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
2781 "parentHash": "0xfe34aaa2b869c66a727783ee5ad3e3983b6ef22baf24a1e502add94e7bcac67a",
2782 "prevRandao": "0x74132c32fe3ab9a470a8352544514d21b6969e7749f97742b53c18a1b22b396c",
2783 "receiptsRoot": "0x6a5c41dc55a1bd3e74e7f6accc799efb08b00c36c15265058433fcea6323e95f",
2784 "stateRoot": "0xde3b357f5f099e4c33d0343c9e9d204d663d7bd9c65020a38e5d0b2a9ace78a2",
2785 "timestamp": "0x6507d6b4",
2786 "transactions": [
2787 "0xf86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53",
2788 "0xf86d0b8458b20efd82520894687704db07e902e9a8b3754031d168d46e3d586e8806f3e8d87878800080820a95a0e3108f710902be662d5c978af16109961ffaf2ac4f88522407d40949a9574276a0205719ed21889b42ab5c1026d40b759a507c12d92db0d100fa69e1ac79137caa",
2789 "0xf86d0c8458b20efd8252089415e6a5a2e131dd5467fa1ff3acd104f45ee5940b8806f3e8d87878800080820a96a0af556ba9cda1d686239e08c24e169dece7afa7b85e0948eaa8d457c0561277fca029da03d3af0978322e54ac7e8e654da23934e0dd839804cb0430f8aaafd732dc",
2790 "0xf8521784565adcb7830186a0808080820a96a0ec782872a673a9fe4eff028a5bdb30d6b8b7711f58a187bf55d3aec9757cb18ea001796d373da76f2b0aeda72183cce0ad070a4f03aa3e6fee4c757a9444245206",
2791 "0xf8521284565adcb7830186a0808080820a95a08a0ea89028eff02596b385a10e0bd6ae098f3b281be2c95a9feb1685065d7384a06239d48a72e4be767bd12f317dd54202f5623a33e71e25a87cb25dd781aa2fc8",
2792 "0xf8521384565adcb7830186a0808080820a95a0784dbd311a82f822184a46f1677a428cbe3a2b88a798fb8ad1370cdbc06429e8a07a7f6a0efd428e3d822d1de9a050b8a883938b632185c254944dd3e40180eb79"
2793 ]
2794}
2795 "#;
2796 let payload: ExecutionPayloadInputV2 = serde_json::from_str(response).unwrap();
2797 assert_eq!(payload.withdrawals, None);
2798 }
2799
2800 #[test]
2801 #[cfg(feature = "serde")]
2802 fn serde_deserialize_v2_input_with_blob_fields() {
2803 let input = r#"
2804{
2805 "parentHash": "0xaaa4c5b574f37e1537c78931d1bca24a4d17d4f29f1ee97e1cd48b704909de1f",
2806 "feeRecipient": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba",
2807 "stateRoot": "0x308ee9c5c6fab5e3d08763a3b5fe0be8ada891fa5010a49a3390e018dd436810",
2808 "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
2809 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
2810 "prevRandao": "0x0000000000000000000000000000000000000000000000000000000000000000",
2811 "blockNumber": "0xf",
2812 "gasLimit": "0x16345785d8a0000",
2813 "gasUsed": "0x0",
2814 "timestamp": "0x3a97",
2815 "extraData": "0x",
2816 "baseFeePerGas": "0x7",
2817 "blockHash": "0x38bb6ba645c7e6bd970f9c7d492fafe1e04d85349054cb48d16c9d2c3e3cd0bf",
2818 "transactions": [],
2819 "withdrawals": [],
2820 "excessBlobGas": "0x0",
2821 "blobGasUsed": "0x0"
2822}
2823 "#;
2824
2825 let payload_res: Result<ExecutionPayloadInputV2, serde_json::Error> =
2827 serde_json::from_str(input);
2828 assert!(payload_res.is_err());
2829 }
2830
2831 #[test]
2833 #[cfg(feature = "serde")]
2834 fn deserialize_op_base_payload() {
2835 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"]}"#;
2836 let _payload = serde_json::from_str::<ExecutionPayloadInputV2>(payload).unwrap();
2837 }
2838
2839 #[test]
2840 fn roundtrip_payload_to_block() {
2841 let first_transaction_raw = Bytes::from_static(&hex!("02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]);
2842 let second_transaction_raw = Bytes::from_static(&hex!("03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]);
2843
2844 let new_payload = ExecutionPayloadV3 {
2845 payload_inner: ExecutionPayloadV2 {
2846 payload_inner: ExecutionPayloadV1 {
2847 base_fee_per_gas: U256::from(7u64),
2848 block_number: 0xa946u64,
2849 block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(),
2850 logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(),
2851 extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(),
2852 gas_limit: 0x1c9c380,
2853 gas_used: 0x1f4a9,
2854 timestamp: 0x651f35b8,
2855 fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(),
2856 parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(),
2857 prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(),
2858 receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(),
2859 state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(),
2860 transactions: vec![first_transaction_raw, second_transaction_raw],
2861 },
2862 withdrawals: vec![],
2863 },
2864 blob_gas_used: 0xc0000,
2865 excess_blob_gas: 0x580000,
2866 };
2867
2868 let mut block: Block<TxEnvelope> = new_payload.clone().try_into_block().unwrap();
2869
2870 let parent_beacon_block_root =
2873 b256!("531cd53b8e68deef0ea65edfa3cda927a846c307b0907657af34bc3f313b5871");
2874 block.header.parent_beacon_block_root = Some(parent_beacon_block_root);
2875
2876 let converted_payload = ExecutionPayloadV3::from_block_unchecked(block.hash_slow(), &block);
2877
2878 assert_eq!(new_payload, converted_payload);
2880 }
2881
2882 #[test]
2883 fn payload_to_block_rejects_network_encoded_tx() {
2884 let first_transaction_raw = Bytes::from_static(&hex!("b9017e02f9017a8501a1f0ff438211cc85012a05f2008512a05f2000830249f094d5409474fd5a725eab2ac9a8b26ca6fb51af37ef80b901040cc7326300000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000001bdd2ed4b616c800000000000000000000000000001e9ee781dd4b97bdef92e5d1785f73a1f931daa20000000000000000000000007a40026a3b9a41754a95eec8c92c6b99886f440c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000009ae80eb647dd09968488fa1d7e412bf8558a0b7a0000000000000000000000000f9815537d361cb02befd9918c95c97d4d8a4a2bc001a0ba8f1928bb0efc3fcd01524a2039a9a2588fa567cd9a7cc18217e05c615e9d69a0544bfd11425ac7748e76b3795b57a5563e2b0eff47b5428744c62ff19ccfc305")[..]);
2885 let second_transaction_raw = Bytes::from_static(&hex!("b9013c03f901388501a1f0ff430c843b9aca00843b9aca0082520894e7249813d8ccf6fa95a2203f46a64166073d58878080c005f8c6a00195f6dff17753fc89b60eac6477026a805116962c9e412de8015c0484e661c1a001aae314061d4f5bbf158f15d9417a238f9589783f58762cd39d05966b3ba2fba0013f5be9b12e7da06f0dd11a7bdc4e0db8ef33832acc23b183bd0a2c1408a757a0019d9ac55ea1a615d92965e04d960cb3be7bff121a381424f1f22865bd582e09a001def04412e76df26fefe7b0ed5e10580918ae4f355b074c0cfe5d0259157869a0011c11a415db57e43db07aef0de9280b591d65ca0cce36c7002507f8191e5d4a80a0c89b59970b119187d97ad70539f1624bbede92648e2dc007890f9658a88756c5a06fb2e3d4ce2c438c0856c2de34948b7032b1aadc4642a9666228ea8cdc7786b7")[..]);
2886
2887 let new_payload = ExecutionPayloadV3 {
2888 payload_inner: ExecutionPayloadV2 {
2889 payload_inner: ExecutionPayloadV1 {
2890 base_fee_per_gas: U256::from(7u64),
2891 block_number: 0xa946u64,
2892 block_hash: hex!("a5ddd3f286f429458a39cafc13ffe89295a7efa8eb363cf89a1a4887dbcf272b").into(),
2893 logs_bloom: hex!("00200004000000000000000080000000000200000000000000000000000000000000200000000000000000000000000000000000800000000200000000000000000000000000000000000008000000200000000000000000000001000000000000000000000000000000800000000000000000000100000000000030000000000000000040000000000000000000000000000000000800080080404000000000000008000000000008200000000000200000000000000000000000000000000000000002000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000100000000000000000000").into(),
2894 extra_data: hex!("d883010d03846765746888676f312e32312e31856c696e7578").into(),
2895 gas_limit: 0x1c9c380,
2896 gas_used: 0x1f4a9,
2897 timestamp: 0x651f35b8,
2898 fee_recipient: hex!("f97e180c050e5ab072211ad2c213eb5aee4df134").into(),
2899 parent_hash: hex!("d829192799c73ef28a7332313b3c03af1f2d5da2c36f8ecfafe7a83a3bfb8d1e").into(),
2900 prev_randao: hex!("753888cc4adfbeb9e24e01c84233f9d204f4a9e1273f0e29b43c4c148b2b8b7e").into(),
2901 receipts_root: hex!("4cbc48e87389399a0ea0b382b1c46962c4b8e398014bf0cc610f9c672bee3155").into(),
2902 state_root: hex!("017d7fa2b5adb480f5e05b2c95cb4186e12062eed893fc8822798eed134329d1").into(),
2903 transactions: vec![first_transaction_raw, second_transaction_raw],
2904 },
2905 withdrawals: vec![],
2906 },
2907 blob_gas_used: 0xc0000,
2908 excess_blob_gas: 0x580000,
2909 };
2910
2911 let _block = new_payload
2912 .try_into_block::<TxEnvelope>()
2913 .expect_err("execution payload conversion requires typed txs without a rlp header");
2914 }
2915
2916 #[test]
2917 fn devnet_invalid_block_hash_repro() {
2918 let deser_block = r#"
2919 {
2920 "parentHash": "0xae8315ee86002e6269a17dd1e9516a6cf13223e9d4544d0c32daff826fb31acc",
2921 "feeRecipient": "0xf97e180c050e5ab072211ad2c213eb5aee4df134",
2922 "stateRoot": "0x03787f1579efbaa4a8234e72465eb4e29ef7e62f61242d6454661932e1a282a1",
2923 "receiptsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
2924 "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
2925 "prevRandao": "0x918e86b497dc15de7d606457c36ca583e24d9b0a110a814de46e33d5bb824a66",
2926 "blockNumber": "0x6a784",
2927 "gasLimit": "0x1c9c380",
2928 "gasUsed": "0x0",
2929 "timestamp": "0x65bc1d60",
2930 "extraData": "0x9a726574682f76302e312e302d616c7068612e31362f6c696e7578",
2931 "baseFeePerGas": "0x8",
2932 "blobGasUsed": "0x0",
2933 "excessBlobGas": "0x0",
2934 "blockHash": "0x340c157eca9fd206b87c17f0ecbe8d411219de7188a0a240b635c88a96fe91c5",
2935 "transactions": [],
2936 "withdrawals": [
2937 {
2938 "index": "0x5ab202",
2939 "validatorIndex": "0xb1b",
2940 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2941 "amount": "0x19b3d"
2942 },
2943 {
2944 "index": "0x5ab203",
2945 "validatorIndex": "0xb1c",
2946 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2947 "amount": "0x15892"
2948 },
2949 {
2950 "index": "0x5ab204",
2951 "validatorIndex": "0xb1d",
2952 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2953 "amount": "0x19b3d"
2954 },
2955 {
2956 "index": "0x5ab205",
2957 "validatorIndex": "0xb1e",
2958 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2959 "amount": "0x19b3d"
2960 },
2961 {
2962 "index": "0x5ab206",
2963 "validatorIndex": "0xb1f",
2964 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2965 "amount": "0x19b3d"
2966 },
2967 {
2968 "index": "0x5ab207",
2969 "validatorIndex": "0xb20",
2970 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2971 "amount": "0x19b3d"
2972 },
2973 {
2974 "index": "0x5ab208",
2975 "validatorIndex": "0xb21",
2976 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2977 "amount": "0x15892"
2978 },
2979 {
2980 "index": "0x5ab209",
2981 "validatorIndex": "0xb22",
2982 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2983 "amount": "0x19b3d"
2984 },
2985 {
2986 "index": "0x5ab20a",
2987 "validatorIndex": "0xb23",
2988 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2989 "amount": "0x19b3d"
2990 },
2991 {
2992 "index": "0x5ab20b",
2993 "validatorIndex": "0xb24",
2994 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
2995 "amount": "0x17db2"
2996 },
2997 {
2998 "index": "0x5ab20c",
2999 "validatorIndex": "0xb25",
3000 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
3001 "amount": "0x19b3d"
3002 },
3003 {
3004 "index": "0x5ab20d",
3005 "validatorIndex": "0xb26",
3006 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
3007 "amount": "0x19b3d"
3008 },
3009 {
3010 "index": "0x5ab20e",
3011 "validatorIndex": "0xa91",
3012 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
3013 "amount": "0x15892"
3014 },
3015 {
3016 "index": "0x5ab20f",
3017 "validatorIndex": "0xa92",
3018 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
3019 "amount": "0x1c05d"
3020 },
3021 {
3022 "index": "0x5ab210",
3023 "validatorIndex": "0xa93",
3024 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
3025 "amount": "0x15892"
3026 },
3027 {
3028 "index": "0x5ab211",
3029 "validatorIndex": "0xa94",
3030 "address": "0x388ea662ef2c223ec0b047d41bf3c0f362142ad5",
3031 "amount": "0x19b3d"
3032 }
3033 ]
3034 }
3035 "#;
3036
3037 let payload: ExecutionPayload =
3039 serde_json::from_str::<ExecutionPayloadV3>(deser_block).unwrap().into();
3040
3041 let block_hash_with_blob_fee_fields =
3045 b256!("a7cdd5f9e54147b53a15833a8c45dffccbaed534d7fdc23458f45102a4bf71b0");
3046
3047 let versioned_hashes = vec![];
3048 let parent_beacon_block_root =
3049 b256!("1162de8a0f4d20d86b9ad6e0a2575ab60f00a433dc70d9318c8abc9041fddf54");
3050
3051 let cancun_fields = CancunPayloadFields { parent_beacon_block_root, versioned_hashes };
3053
3054 let block = payload
3056 .try_into_block_with_sidecar::<TxEnvelope>(&ExecutionPayloadSidecar::v3(cancun_fields))
3057 .unwrap();
3058
3059 assert_eq!(block_hash_with_blob_fee_fields, block.header.hash_slow());
3061 }
3062
3063 #[test]
3064 fn test_payload_to_block_with_sidecar_raw() {
3065 use std::path::PathBuf;
3066
3067 let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("testdata/payload");
3068 let dir = std::fs::read_dir(path).expect("Unable to read payload folder");
3069
3070 for entry in dir {
3071 let entry = entry.expect("Unable to read entry");
3072 let path = entry.path();
3073
3074 if path.extension().and_then(|s| s.to_str()) != Some("json") {
3075 continue;
3076 }
3077
3078 let contents = std::fs::read_to_string(&path).expect("Unable to read file");
3079 let value: serde_json::Value = serde_json::from_str(&contents)
3080 .unwrap_or_else(|e| panic!("Failed to parse JSON from {path:?}: {e}"));
3081
3082 let new_payload = &value["newPayload"];
3084 let payload_value = &new_payload["payload"];
3085 let sidecar_value = &new_payload["sidecar"];
3086
3087 let payload: ExecutionPayload = serde_json::from_value(payload_value.clone())
3088 .unwrap_or_else(|e| panic!("Failed to deserialize payload from {path:?}: {e}"));
3089
3090 let sidecar: ExecutionPayloadSidecar = serde_json::from_value(sidecar_value.clone())
3092 .unwrap_or_else(|e| panic!("Failed to deserialize sidecar from {path:?}: {e}"));
3093
3094 let block = payload.clone().into_block_with_sidecar_raw(&sidecar).unwrap_or_else(|e| {
3096 panic!("Failed to convert payload to block from {path:?}: {e}")
3097 });
3098
3099 if let Some(tx_count) = payload_value["transactions"].as_array().map(|a| a.len()) {
3101 assert_eq!(
3102 block.body.transactions.len(),
3103 tx_count,
3104 "Transaction count mismatch in {:?}",
3105 path
3106 );
3107 }
3108
3109 assert_eq!(
3111 block.header.parent_beacon_block_root,
3112 sidecar.parent_beacon_block_root(),
3113 "Parent beacon block root mismatch in {:?}",
3114 path
3115 );
3116 assert_eq!(
3117 block.header.requests_hash,
3118 sidecar.requests_hash(),
3119 "Requests hash mismatch in {:?}",
3120 path
3121 );
3122
3123 let expected_hash = payload_value["blockHash"]
3125 .as_str()
3126 .unwrap()
3127 .parse::<B256>()
3128 .unwrap_or_else(|e| panic!("Failed to parse block hash from {path:?}: {e}"));
3129 let actual_hash = block.header.hash_slow();
3130 assert_eq!(
3131 actual_hash, expected_hash,
3132 "Block hash mismatch in {:?}: expected {}, got {}",
3133 path, expected_hash, actual_hash
3134 );
3135
3136 let block =
3137 payload.try_into_block_with_sidecar::<TxEnvelope>(&sidecar).unwrap_or_else(|e| {
3138 panic!("Failed to convert payload to block from {path:?}: {e}")
3139 });
3140 let actual_hash = block.header.hash_slow();
3141 assert_eq!(
3142 actual_hash, expected_hash,
3143 "Block hash mismatch in {:?}: expected {}, got {}",
3144 path, expected_hash, actual_hash
3145 );
3146 }
3147 }
3148
3149 #[test]
3150 fn test_decoded_transactions() {
3151 let transaction = Bytes::from_static(&hex!("f86d0a8458b20efd825208946177843db3138ae69679a54b95cf345ed759450d8806f3e8d87878800080820a95a0f8bddb1dcc4558b532ff747760a6f547dd275afdbe7bdecc90680e71de105757a014f34ba38c180913c0543b0ac2eccfb77cc3f801a535008dc50e533fbe435f53"));
3152
3153 let payload = ExecutionPayload::V1(ExecutionPayloadV1 {
3154 parent_hash: B256::default(),
3155 fee_recipient: Address::default(),
3156 state_root: B256::default(),
3157 receipts_root: B256::default(),
3158 logs_bloom: Bloom::default(),
3159 prev_randao: B256::default(),
3160 block_number: 0,
3161 gas_limit: 0,
3162 gas_used: 0,
3163 timestamp: 0,
3164 extra_data: Bytes::default(),
3165 base_fee_per_gas: U256::default(),
3166 block_hash: B256::default(),
3167 transactions: vec![transaction.clone()],
3168 });
3169
3170 let decoded: Vec<_> = payload.decoded_transactions::<TxEnvelope>().collect();
3172 assert_eq!(decoded.len(), 1);
3173 assert!(decoded[0].is_ok(), "Failed to decode transaction: {:?}", decoded[0]);
3174
3175 let decoded_with_encoded: Vec<_> =
3177 payload.decoded_transactions_with_encoded::<TxEnvelope>().collect();
3178 assert_eq!(decoded_with_encoded.len(), 1);
3179 assert!(decoded_with_encoded[0].is_ok());
3180 if let Ok(with_encoded) = &decoded_with_encoded[0] {
3181 assert_eq!(with_encoded.encoded_bytes(), &transaction);
3182 }
3183 }
3184}