alloy_rpc_types_engine/
payload.rs

1//! Payload types.
2
3use 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
26/// The execution payload body response that allows for `null` values.
27pub type ExecutionPayloadBodiesV1 = Vec<Option<ExecutionPayloadBodyV1>>;
28
29/// And 8-byte identifier for an execution payload.
30#[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
35// === impl PayloadId ===
36
37impl PayloadId {
38    /// Creates a new payload id from the given identifier.
39    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/// This represents the `executionPayload` field in the return value of `engine_getPayloadV2`,
57/// specified as:
58///
59/// - `executionPayload`: `ExecutionPayloadV1` | `ExecutionPayloadV2` where:
60///   - `ExecutionPayloadV1` **MUST** be returned if the payload `timestamp` is lower than the
61///     Shanghai timestamp
62///   - `ExecutionPayloadV2` **MUST** be returned if the payload `timestamp` is greater or equal to
63///     the Shanghai timestamp
64///
65/// See:
66/// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/shanghai.md#response>
67#[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 payload
73    V1(ExecutionPayloadV1),
74    /// V2 payload
75    V2(ExecutionPayloadV2),
76}
77
78impl ExecutionPayloadFieldV2 {
79    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayloadFieldV2`].
80    ///
81    /// See also:
82    ///  - [`ExecutionPayloadV1::from_block_unchecked`].
83    ///  - [`ExecutionPayloadV2::from_block_unchecked`].
84    ///
85    /// If the block body contains withdrawals this returns [`ExecutionPayloadFieldV2::V2`].
86    ///
87    /// Note: This re-calculates the block hash.
88    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    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayloadFieldV2`] using the given block
97    /// hash.
98    ///
99    /// See also:
100    ///  - [`ExecutionPayloadV1::from_block_unchecked`].
101    ///  - [`ExecutionPayloadV2::from_block_unchecked`].
102    ///
103    /// If the block body contains withdrawals this returns [`ExecutionPayloadFieldV2::V2`].
104    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    /// Returns the inner [ExecutionPayloadV1]
117    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    /// Converts this payload variant into the corresponding [ExecutionPayload]
125    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/// This is the input to `engine_newPayloadV2`, which may or may not have a withdrawals field.
134#[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    /// The V1 execution payload
140    #[cfg_attr(feature = "serde", serde(flatten))]
141    pub execution_payload: ExecutionPayloadV1,
142    /// The payload withdrawals
143    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
144    pub withdrawals: Option<Vec<Withdrawal>>,
145}
146
147impl ExecutionPayloadInputV2 {
148    /// Converts [`ExecutionPayloadInputV2`] to [`ExecutionPayload`]
149    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/// This structure maps for the return value of `engine_getPayload` of the beacon chain spec, for
167/// V2.
168///
169/// See also:
170/// <https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#engine_getpayloadv2>
171#[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    /// Execution payload, which could be either V1 or V2
177    ///
178    /// V1 (_NO_ withdrawals) MUST be returned if the payload timestamp is lower than the Shanghai
179    /// timestamp
180    ///
181    /// V2 (_WITH_ withdrawals) MUST be returned if the payload timestamp is greater or equal to
182    /// the Shanghai timestamp
183    pub execution_payload: ExecutionPayloadFieldV2,
184    /// The expected value to be received by the feeRecipient in wei
185    pub block_value: U256,
186}
187
188impl ExecutionPayloadEnvelopeV2 {
189    /// Returns the [ExecutionPayload] for the `engine_getPayloadV1` endpoint
190    pub fn into_v1_payload(self) -> ExecutionPayloadV1 {
191        self.execution_payload.into_v1_payload()
192    }
193}
194
195/// This structure maps for the return value of `engine_getPayload` of the beacon chain spec, for
196/// V3.
197///
198/// See also:
199/// <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#response-2>
200#[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    /// Execution payload V3
206    pub execution_payload: ExecutionPayloadV3,
207    /// The expected value to be received by the feeRecipient in wei
208    pub block_value: U256,
209    /// The blobs, commitments, and proofs associated with the executed payload.
210    pub blobs_bundle: BlobsBundleV1,
211    /// Introduced in V3, this represents a suggestion from the execution layer if the payload
212    /// should be used instead of an externally provided one.
213    pub should_override_builder: bool,
214}
215
216/// This structure maps for the return value of `engine_getPayload` of the beacon chain spec, for
217/// V4.
218///
219/// See also:
220/// <https://github.com/ethereum/execution-apis/blob/main/src/engine/prague.md#engine_getpayloadv4>
221#[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    /// Inner [`ExecutionPayloadEnvelopeV3`].
227    #[deref]
228    #[deref_mut]
229    #[cfg_attr(feature = "serde", serde(flatten))]
230    pub envelope_inner: ExecutionPayloadEnvelopeV3,
231
232    /// A list of opaque [EIP-7685][eip7685] requests.
233    ///
234    /// [eip7685]: https://eips.ethereum.org/EIPS/eip-7685
235    pub execution_requests: Requests,
236}
237
238/// This structure maps for the return value of `engine_getPayload` of the beacon chain spec, for
239/// V5.
240///
241/// See also:
242/// <https://github.com/ethereum/execution-apis/blob/a091e7c3b6a5748a8843a1a9130d5fbfc3191a2c/src/engine/osaka.md#engine_getpayloadv5>
243#[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    /// Execution payload V3
249    pub execution_payload: ExecutionPayloadV3,
250    /// The expected value to be received by the feeRecipient in wei
251    pub block_value: U256,
252    /// The blobs, commitments, and EIP-7594 style cell proofs associated with the executed
253    /// payload. See also: <https://github.com/ethereum/execution-apis/blob/a091e7c3b6a5748a8843a1a9130d5fbfc3191a2c/src/engine/osaka.md#BlobsBundleV2>.
254    pub blobs_bundle: BlobsBundleV2,
255    /// Introduced in V3, this represents a suggestion from the execution layer if the payload
256    /// should be used instead of an externally provided one.
257    pub should_override_builder: bool,
258    /// A list of opaque [EIP-7685][eip7685] requests.
259    ///
260    /// [eip7685]: https://eips.ethereum.org/EIPS/eip-7685
261    pub execution_requests: Requests,
262}
263
264impl ExecutionPayloadEnvelopeV4 {
265    /// Converts this V4 envelope into a [`ExecutionPayloadEnvelopeV5`] by computing EIP-7594
266    /// cell proofs for the blobs bundle.
267    ///
268    /// This uses the default KZG settings. See [`Self::try_into_v5_with_settings`] for custom
269    /// settings.
270    ///
271    /// # Errors
272    ///
273    /// Returns an error if KZG proof computation fails.
274    #[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    /// Converts this V4 envelope into a [`ExecutionPayloadEnvelopeV5`] by computing EIP-7594
284    /// cell proofs for the blobs bundle using the provided KZG settings.
285    ///
286    /// # Errors
287    ///
288    /// Returns an error if KZG proof computation fails.
289    #[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    /// Converts this V5 envelope into a [`ExecutionPayloadEnvelopeV4`] by computing EIP-4844
316    /// blob proofs for the blobs bundle.
317    ///
318    /// This uses the default KZG settings. See [`Self::try_into_v4_with_settings`] for custom
319    /// settings.
320    ///
321    /// # Errors
322    ///
323    /// Returns an error if KZG proof computation fails.
324    #[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    /// Converts this V5 envelope into a [`ExecutionPayloadEnvelopeV4`] by computing EIP-4844
334    /// blob proofs for the blobs bundle using the provided KZG settings.
335    ///
336    /// # Errors
337    ///
338    /// Returns an error if KZG proof computation fails.
339    #[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/// This structure maps on the ExecutionPayload structure of the beacon chain spec.
367///
368/// See also: <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/paris.md#executionpayloadv1>
369#[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    /// The parent hash of the block.
376    pub parent_hash: B256,
377    /// The fee recipient of the block.
378    pub fee_recipient: Address,
379    /// The state root of the block.
380    pub state_root: B256,
381    /// The receipts root of the block.
382    pub receipts_root: B256,
383    /// The logs bloom of the block.
384    pub logs_bloom: Bloom,
385    /// The previous randao of the block.
386    pub prev_randao: B256,
387    /// The block number.
388    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
389    pub block_number: u64,
390    /// The gas limit of the block.
391    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
392    pub gas_limit: u64,
393    /// The gas used of the block.
394    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
395    pub gas_used: u64,
396    /// The timestamp of the block.
397    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
398    pub timestamp: u64,
399    /// The extra data of the block.
400    pub extra_data: Bytes,
401    /// The base fee per gas of the block.
402    pub base_fee_per_gas: U256,
403    /// The block hash of the block.
404    pub block_hash: B256,
405    /// The transactions of the block.
406    pub transactions: Vec<Bytes>,
407}
408
409impl ExecutionPayloadV1 {
410    /// Returns the block number and hash as a [`BlockNumHash`].
411    pub const fn block_num_hash(&self) -> BlockNumHash {
412        BlockNumHash::new(self.block_number, self.block_hash)
413    }
414
415    /// Converts [`ExecutionPayloadV1`] to [`Block`]
416    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    /// Converts [`ExecutionPayloadV1`] to [`Block`] with the given closure.
425    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    /// Converts [`ExecutionPayloadV1`] to [`Block`] with raw [`Bytes`] transactions.
434    ///
435    /// This is similar to [`Self::try_into_block_with`] but returns the transactions as raw bytes
436    /// without any conversion.
437    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        // Calculate the transactions root using encoded bytes
443        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            // WARNING: It's allowed for a base fee in EIP1559 to increase unbounded. We assume that
460            // it will fit in an u64. This is not always necessarily true, although it is extremely
461            // unlikely not to be the case, a u64 maximum would have 2^64 which equates to 18 ETH
462            // per gas.
463            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            // Defaults
474            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    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayloadV1`].
486    ///
487    /// Note: This re-calculates the block hash.
488    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    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayloadV1`] using the given block hash.
497    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    /// Calculate base fee for next block according to the EIP-1559 spec.
523    ///
524    /// Returns a `None` if no base fee is set, no EIP-1559 support
525    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/// This structure maps on the ExecutionPayloadV2 structure of the beacon chain spec.
544///
545/// See also: <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2>
546#[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    /// Inner V1 payload
552    #[cfg_attr(feature = "serde", serde(flatten))]
553    pub payload_inner: ExecutionPayloadV1,
554
555    /// Array of [`Withdrawal`] enabled with V2
556    /// See <https://github.com/ethereum/execution-apis/blob/6709c2a795b707202e93c4f2867fa0bf2640a84f/src/engine/shanghai.md#executionpayloadv2>
557    pub withdrawals: Vec<Withdrawal>,
558}
559
560impl ExecutionPayloadV2 {
561    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayloadV2`].
562    ///
563    /// See also [`ExecutionPayloadV1::from_block_unchecked`].
564    ///
565    /// If the block does not have any withdrawals, an empty vector is used.
566    ///
567    /// Note: This re-calculates the block hash.
568    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    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayloadV2`] using the given block hash.
577    ///
578    /// See also [`ExecutionPayloadV1::from_block_unchecked`].
579    ///
580    /// If the block does not have any withdrawals, an empty vector is used.
581    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    /// Returns the timestamp for the execution payload.
598    pub const fn timestamp(&self) -> u64 {
599        self.payload_inner.timestamp
600    }
601
602    /// Converts [`ExecutionPayloadV2`] to [`ExecutionPayloadInputV2`].
603    ///
604    /// An [`ExecutionPayloadInputV2`] should have a [`Some`] withdrawals field if shanghai is
605    /// active, otherwise the withdrawals field should be [`None`], so the `is_shanghai_active`
606    /// argument is provided which will either:
607    /// - include the withdrawals field as [`Some`] if true
608    /// - set the withdrawals field to [`None`] if false
609    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    /// Converts [`ExecutionPayloadV2`] to [`Block`].
617    ///
618    /// This performs the same conversion as the underlying V1 payload, but calculates the
619    /// withdrawals root and adds withdrawals.
620    ///
621    /// See also [`ExecutionPayloadV1::try_into_block`].
622    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    /// Converts [`ExecutionPayloadV2`] to [`Block`] with a custom transaction mapper.
631    ///
632    /// See also [`ExecutionPayloadV1::try_into_block_with`].
633    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    /// Converts [`ExecutionPayloadV2`] to [`Block`] with raw [`Bytes`] transactions.
642    ///
643    /// This is similar to [`Self::try_into_block_with`] but returns the transactions as raw bytes
644    /// without any conversion.
645    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/// This structure maps on the ExecutionPayloadV3 structure of the beacon chain spec.
755///
756/// See also: <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#executionpayloadv3>
757#[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    /// Inner V2 payload
763    #[cfg_attr(feature = "serde", serde(flatten))]
764    pub payload_inner: ExecutionPayloadV2,
765
766    /// Array of hex [`u64`] representing blob gas used, enabled with V3
767    /// See <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#ExecutionPayloadV3>
768    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
769    pub blob_gas_used: u64,
770    /// Array of hex [`u64`] representing excess blob gas, enabled with V3
771    /// See <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#ExecutionPayloadV3>
772    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
773    pub excess_blob_gas: u64,
774}
775
776impl ExecutionPayloadV3 {
777    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayloadV3`].
778    ///
779    /// See also [`ExecutionPayloadV2::from_block_unchecked`].
780    ///
781    /// Note: This re-calculates the block hash.
782    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    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayloadV3`] using the given block hash.
791    ///
792    /// See also [`ExecutionPayloadV2::from_block_unchecked`].
793    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    /// Returns the withdrawals for the payload.
806    pub const fn withdrawals(&self) -> &Vec<Withdrawal> {
807        &self.payload_inner.withdrawals
808    }
809
810    /// Returns the timestamp for the payload.
811    pub const fn timestamp(&self) -> u64 {
812        self.payload_inner.payload_inner.timestamp
813    }
814
815    /// Converts [`ExecutionPayloadV3`] to [`Block`].
816    ///
817    /// This performs the same conversion as the underlying V2 payload, but inserts the blob gas
818    /// used and excess blob gas.
819    ///
820    /// See also [`ExecutionPayloadV2::try_into_block`].
821    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    /// Converts [`ExecutionPayloadV3`] to [`Block`] with a custom transaction mapper.
830    ///
831    /// See also [`ExecutionPayloadV2::try_into_block_with`].
832    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    /// Converts [`ExecutionPayloadV3`] to [`Block`] with raw [`Bytes`] transactions.
841    ///
842    /// This is similar to [`Self::try_into_block_with`] but returns the transactions as raw bytes
843    /// without any conversion.
844    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/// This includes all bundled blob related data of an executed payload.
961#[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    /// All commitments in the bundle.
967    pub commitments: Vec<alloy_consensus::Bytes48>,
968    /// All proofs in the bundle.
969    pub proofs: Vec<alloy_consensus::Bytes48>,
970    /// All blobs in the bundle.
971    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    /// Creates a new blob bundle from the given sidecars.
1001    ///
1002    /// This folds the sidecar fields into single commit, proof, and blob vectors.
1003    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    /// Returns a new empty blobs bundle.
1017    ///
1018    /// This is useful for the opstack engine API that expects an empty bundle as part of the
1019    /// payload for API compatibility reasons.
1020    pub fn empty() -> Self {
1021        Self::default()
1022    }
1023
1024    /// Take `len` blob data from the bundle.
1025    ///
1026    /// # Panics
1027    ///
1028    /// If len is more than the blobs bundle len.
1029    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    /// Returns the sidecar from the bundle
1038    ///
1039    /// # Panics
1040    ///
1041    /// If len is more than the blobs bundle len.
1042    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    /// Converts this bundle into a single [`BlobTransactionSidecar`].
1048    ///
1049    /// Returns an error if the bundle doesn't contain the same number of commitments as blobs and
1050    /// proofs.
1051    ///
1052    /// Returns an empty [`BlobTransactionSidecar`] if the bundle is empty.
1053    #[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    /// Converts this V1 bundle into a [`BlobsBundleV2`] by computing EIP-7594 cell proofs.
1067    ///
1068    /// This uses the default KZG settings. See [`Self::try_into_v2_with_settings`] for custom
1069    /// settings.
1070    ///
1071    /// # Errors
1072    ///
1073    /// Returns an error if the bundle has mismatched lengths or if KZG proof computation fails.
1074    #[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    /// Converts this V1 bundle into a [`BlobsBundleV2`] by computing EIP-7594 cell proofs
1082    /// using the provided KZG settings.
1083    ///
1084    /// # Errors
1085    ///
1086    /// Returns an error if the bundle has mismatched lengths or if KZG proof computation fails.
1087    #[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            // SAFETY: Blob and alloy_eips::eip4844::c_kzg::Blob have the same memory layout
1098            let blob_kzg =
1099                unsafe { core::mem::transmute::<&Blob, &alloy_eips::eip4844::c_kzg::Blob>(blob) };
1100
1101            // Compute cells and their KZG proofs for this blob
1102            let (_cells, kzg_proofs) = settings.compute_cells_and_kzg_proofs(blob_kzg)?;
1103
1104            // SAFETY: same size
1105            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/// This includes all bundled blob related data of an executed payload.
1150#[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    /// All commitments in the bundle.
1156    pub commitments: Vec<alloy_consensus::Bytes48>,
1157    /// All cell proofs in the bundle.
1158    pub proofs: Vec<alloy_consensus::Bytes48>,
1159    /// All blobs in the bundle.
1160    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    /// Creates a new blob bundle from the given sidecars.
1227    ///
1228    /// This folds the sidecar fields into single commit, proof, and blob vectors.
1229    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    /// Returns a new empty blobs bundle.
1243    ///
1244    /// This is useful for the opstack engine API that expects an empty bundle as part of the
1245    /// payload for API compatibility reasons.
1246    pub fn empty() -> Self {
1247        Self::default()
1248    }
1249
1250    /// Take `len` blob data from the bundle.
1251    ///
1252    /// Note this will take `len * CELLS_PER_EXT_BLOB` proofs.
1253    ///
1254    /// # Panics
1255    ///
1256    /// If len is more than the blobs bundle len.
1257    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    /// Returns the sidecar from the bundle
1266    ///
1267    /// # Panics
1268    ///
1269    /// If len is more than the blobs bundle len.
1270    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    /// Converts this bundle into a single [`BlobTransactionSidecarEip7594`].
1276    ///
1277    /// Returns an error if the bundle doesn't contain the correct number of cell proofs
1278    /// (expected blobs.len() * CELLS_PER_EXT_BLOB) or if the commitments length doesn't
1279    /// match the blobs length.
1280    ///
1281    /// Returns an empty [`BlobTransactionSidecarEip7594`] if the bundle is empty.
1282    #[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    /// Converts this V2 bundle into a [`BlobsBundleV1`] by computing EIP-4844 blob proofs.
1309    ///
1310    /// This uses the default KZG settings. See [`Self::try_into_v1_with_settings`] for custom
1311    /// settings.
1312    ///
1313    /// # Errors
1314    ///
1315    /// Returns an error if KZG proof computation fails.
1316    #[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    /// Converts this V2 bundle into a [`BlobsBundleV1`] by computing EIP-4844 blob proofs
1324    /// using the provided KZG settings.
1325    ///
1326    /// This recomputes the blob proofs from the blobs and commitments. The cell proofs from
1327    /// V2 are discarded as they are not used in V1.
1328    ///
1329    /// # Errors
1330    ///
1331    /// Returns an error if KZG proof computation fails.
1332    #[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            // SAFETY: Blob and alloy_eips::eip4844::c_kzg::Blob have the same memory layout
1341            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            // Compute the blob proof
1348            let proof = settings.compute_blob_kzg_proof(blob_kzg, commitment_kzg)?;
1349
1350            // SAFETY: same size
1351            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/// An execution payload, which can be either [ExecutionPayloadV1], [ExecutionPayloadV2], or
1393/// [ExecutionPayloadV3].
1394#[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 payload
1400    V1(ExecutionPayloadV1),
1401    /// V2 payload
1402    V2(ExecutionPayloadV2),
1403    /// V3 payload
1404    V3(ExecutionPayloadV3),
1405}
1406
1407impl ExecutionPayload {
1408    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayload`] and also returns the
1409    /// [`ExecutionPayloadSidecar`] extracted from the block.
1410    ///
1411    /// See also [`ExecutionPayloadV3::from_block_unchecked`].
1412    /// See also [`ExecutionPayloadSidecar::from_block`].
1413    ///
1414    /// Note: This re-calculates the block hash.
1415    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    /// Converts [`alloy_consensus::Block`] to [`ExecutionPayload`] and also returns the
1424    /// [`ExecutionPayloadSidecar`] extracted from the block.
1425    ///
1426    /// See also [`ExecutionPayloadV3::from_block_unchecked`].
1427    /// See also [`ExecutionPayloadSidecar::from_block`].
1428    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            // block with parent beacon block root: V3
1440            Self::V3(ExecutionPayloadV3::from_block_unchecked(block_hash, block))
1441        } else if block.body.withdrawals.is_some() {
1442            // block with withdrawals: V2
1443            Self::V2(ExecutionPayloadV2::from_block_unchecked(block_hash, block))
1444        } else {
1445            // otherwise V1
1446            Self::V1(ExecutionPayloadV1::from_block_unchecked(block_hash, block))
1447        };
1448
1449        (execution_payload, sidecar)
1450    }
1451
1452    /// Tries to create a new unsealed block from the given payload and payload sidecar.
1453    ///
1454    /// Performs additional validation of `extra_data` and `base_fee_per_gas` fields.
1455    ///
1456    /// # Note
1457    ///
1458    /// The log bloom is assumed to be validated during serialization.
1459    ///
1460    /// See <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
1461    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    /// Converts [`ExecutionPayload`] to [`Block`] with sidecar and a custom transaction mapper.
1473    ///
1474    /// The log bloom is assumed to be validated during serialization.
1475    ///
1476    /// See <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
1477    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    /// Converts [`ExecutionPayload`] to [`Block`] with raw [`Bytes`] transactions and sidecar.
1490    ///
1491    /// This is similar to [`Self::try_into_block_with_sidecar_with`] but returns the transactions
1492    /// as raw bytes without any conversion.
1493    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    /// Converts [`ExecutionPayload`] to [`Block`].
1504    ///
1505    /// Caution: This does not set fields that are not part of the payload and only part of the
1506    /// [`ExecutionPayloadSidecar`]:
1507    /// - parent_beacon_block_root
1508    /// - requests_hash
1509    ///
1510    /// See also: [`ExecutionPayload::try_into_block_with_sidecar`]
1511    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    /// Converts [`ExecutionPayload`] to [`Block`] with a custom transaction mapper.
1520    ///
1521    /// Caution: This does not set fields that are not part of the payload and only part of the
1522    /// [`ExecutionPayloadSidecar`]:
1523    /// - parent_beacon_block_root
1524    /// - requests_hash
1525    ///
1526    /// See also: [`ExecutionPayload::try_into_block_with_sidecar`]
1527    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    /// Converts [`ExecutionPayload`] to [`Block`] with raw [`Bytes`] transactions.
1536    ///
1537    /// This is similar to [`Self::try_into_block_with`] but returns the transactions as raw bytes
1538    /// without any conversion.
1539    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    /// Returns a reference to the V1 payload.
1548    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    /// Returns a mutable reference to the V1 payload.
1557    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    /// Consumes the payload and returns the V1 payload.
1566    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    /// Returns a reference to the V2 payload, if any.
1575    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    /// Returns a mutable reference to the V2 payload, if any.
1584    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    /// Returns a reference to the V3 payload, if any.
1593    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    /// Returns a mutable reference to the V3 payload, if any.
1601    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    /// Returns the withdrawals for the payload.
1609    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    /// Returns the transactions for the payload.
1617    pub const fn transactions(&self) -> &Vec<Bytes> {
1618        &self.as_v1().transactions
1619    }
1620
1621    /// Returns a mutable reference to the transactions for the payload.
1622    pub const fn transactions_mut(&mut self) -> &mut Vec<Bytes> {
1623        &mut self.as_v1_mut().transactions
1624    }
1625
1626    /// Extracts essential information into one container type.
1627    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    /// Returns the gas limit for the payload.
1642    ///
1643    /// Note: this returns the u64 saturated base fee, but it is specified as [`U256`].
1644    pub fn saturated_base_fee_per_gas(&self) -> u64 {
1645        self.as_v1().base_fee_per_gas.saturating_to()
1646    }
1647
1648    /// Returns the blob gas used for the payload.
1649    pub fn blob_gas_used(&self) -> Option<u64> {
1650        self.as_v3().map(|payload| payload.blob_gas_used)
1651    }
1652
1653    /// Returns the excess blob gas for the payload.
1654    pub fn excess_blob_gas(&self) -> Option<u64> {
1655        self.as_v3().map(|payload| payload.excess_blob_gas)
1656    }
1657
1658    /// Returns the gas limit for the payload.
1659    pub const fn gas_limit(&self) -> u64 {
1660        self.as_v1().gas_limit
1661    }
1662
1663    /// Returns the fee recipient.
1664    pub const fn fee_recipient(&self) -> Address {
1665        self.as_v1().fee_recipient
1666    }
1667
1668    /// Returns the timestamp for the payload.
1669    pub const fn timestamp(&self) -> u64 {
1670        self.as_v1().timestamp
1671    }
1672
1673    /// Returns the parent hash for the payload.
1674    pub const fn parent_hash(&self) -> B256 {
1675        self.as_v1().parent_hash
1676    }
1677
1678    /// Returns the block hash for the payload.
1679    pub const fn block_hash(&self) -> B256 {
1680        self.as_v1().block_hash
1681    }
1682
1683    /// Returns the block number for this payload.
1684    pub const fn block_number(&self) -> u64 {
1685        self.as_v1().block_number
1686    }
1687
1688    /// Returns the block number for this payload.
1689    pub const fn block_num_hash(&self) -> BlockNumHash {
1690        self.as_v1().block_num_hash()
1691    }
1692
1693    /// Returns the prev randao for this payload.
1694    pub const fn prev_randao(&self) -> B256 {
1695        self.as_v1().prev_randao
1696    }
1697
1698    /// Returns the blob fee for _this_ block according to the EIP-4844 spec.
1699    ///
1700    /// Returns `None` if `excess_blob_gas` is None
1701    pub fn blob_fee(&self, blob_params: BlobParams) -> Option<u128> {
1702        Some(blob_params.calc_blob_fee(self.excess_blob_gas()?))
1703    }
1704
1705    /// Returns the blob fee for the next block according to the EIP-4844 spec.
1706    ///
1707    /// Returns `None` if `excess_blob_gas` is None.
1708    ///
1709    /// See also [Self::next_block_excess_blob_gas]
1710    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    /// Calculate base fee for next block according to the EIP-1559 spec.
1715    ///
1716    /// Returns a `None` if no base fee is set, no EIP-1559 support
1717    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    /// Calculate excess blob gas for the next block according to the EIP-4844
1722    /// spec.
1723    ///
1724    /// Returns a `None` if no excess blob gas is set, no EIP-4844 support
1725    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    /// Convenience function for [`Self::next_block_excess_blob_gas`] with an optional
1734    /// [`BlobParams`] argument.
1735    ///
1736    /// Returns `None` if the `blob_params` are `None`.
1737    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    /// Returns an iterator over the decoded transactions in this payload.
1742    ///
1743    /// This iterator will decode transactions on the fly.
1744    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    /// Returns iterator over decoded transactions with their original encoded bytes.
1751    ///
1752    /// This iterator will decode transactions on the fly and return them with their bytes.
1753    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    /// Returns an iterator over the recovered transactions in this payload.
1762    ///
1763    /// This iterator will decode and recover signer addresses for transactions on the fly.
1764    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    /// Returns an iterator over the recovered transactions in this payload with their
1782    /// original encoded bytes.
1783    ///
1784    /// This iterator will decode and recover signer addresses for transactions on the fly
1785    /// and return them with their bytes.
1786    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// Deserializes untagged ExecutionPayload depending on the available fields
1841#[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                // this currently rejects unknown fields
1863                #[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                    // V2
1881                    Withdrawals,
1882                    // V3
1883                    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                // reject incomplete V3 payloads even if they could construct a valid V2
2010                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/// This structure contains a body of an execution payload.
2042///
2043/// See also: <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/shanghai.md#executionpayloadbodyv1>
2044#[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    /// Enveloped encoded transactions.
2049    pub transactions: Vec<Bytes>,
2050    /// All withdrawals in the block.
2051    ///
2052    /// Will always be `None` if pre shanghai.
2053    pub withdrawals: Option<Vec<Withdrawal>>,
2054}
2055
2056impl ExecutionPayloadBodyV1 {
2057    /// Creates an [`ExecutionPayloadBodyV1`] from the given withdrawals and transactions
2058    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    /// Converts a [`alloy_consensus::Block`] into an execution payload body.
2072    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/// This structure contains the attributes required to initiate a payload build process in the
2084/// context of an `engine_forkchoiceUpdated` call.
2085#[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    /// Value for the `timestamp` field of the new payload
2091    #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))]
2092    pub timestamp: u64,
2093    /// Value for the `prevRandao` field of the new payload
2094    pub prev_randao: B256,
2095    /// Suggested value for the `feeRecipient` field of the new payload
2096    pub suggested_fee_recipient: Address,
2097    /// Array of [`Withdrawal`] enabled with V2
2098    /// See <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/shanghai.md#payloadattributesv2>
2099    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2100    pub withdrawals: Option<Vec<Withdrawal>>,
2101    /// Root of the parent beacon block enabled with V3.
2102    ///
2103    /// See also <https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#payloadattributesv3>
2104    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
2105    pub parent_beacon_block_root: Option<B256>,
2106}
2107
2108/// This structure contains the result of processing a payload or fork choice update.
2109#[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    /// The status of the payload.
2115    #[cfg_attr(feature = "serde", serde(flatten))]
2116    pub status: PayloadStatusEnum,
2117    /// Hash of the most recent valid block in the branch defined by payload and its ancestors
2118    pub latest_valid_hash: Option<B256>,
2119}
2120
2121impl PayloadStatus {
2122    /// Initializes a new payload status.
2123    pub const fn new(status: PayloadStatusEnum, latest_valid_hash: Option<B256>) -> Self {
2124        Self { status, latest_valid_hash }
2125    }
2126
2127    /// Creates a new payload status from the given status.
2128    pub const fn from_status(status: PayloadStatusEnum) -> Self {
2129        Self { status, latest_valid_hash: None }
2130    }
2131
2132    /// Sets the latest valid hash.
2133    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    /// Sets the latest valid hash if it's not None.
2139    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    /// Returns true if the payload status is syncing.
2145    pub const fn is_syncing(&self) -> bool {
2146        self.status.is_syncing()
2147    }
2148
2149    /// Returns true if the payload status is valid.
2150    pub const fn is_valid(&self) -> bool {
2151        self.status.is_valid()
2152    }
2153
2154    /// Returns true if the payload status is invalid.
2155    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/// Represents the status response of a payload.
2192#[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 is returned by the engine API in the following calls:
2198    ///   - newPayload:       if the payload was already known or was just validated and executed
2199    ///   - forkchoiceUpdate: if the chain accepted the reorg (might ignore if it's stale)
2200    Valid,
2201
2202    /// INVALID is returned by the engine API in the following calls:
2203    ///   - newPayload:       if the payload failed to execute on top of the local chain
2204    ///   - forkchoiceUpdate: if the new head is unknown, pre-merge, or reorg to it fails
2205    Invalid {
2206        /// The error message for the invalid payload.
2207        #[cfg_attr(feature = "serde", serde(rename = "validationError"))]
2208        validation_error: String,
2209    },
2210
2211    /// SYNCING is returned by the engine API in the following calls:
2212    ///   - newPayload:       if the payload was accepted on top of an active sync
2213    ///   - forkchoiceUpdate: if the new head was seen before, but not part of the chain
2214    Syncing,
2215
2216    /// ACCEPTED is returned by the engine API in the following calls:
2217    ///   - newPayload: if the payload was accepted, but not processed (side chain)
2218    Accepted,
2219}
2220
2221impl PayloadStatusEnum {
2222    /// Returns the string representation of the payload status.
2223    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    /// Returns the validation error if the payload status is invalid.
2233    pub fn validation_error(&self) -> Option<&str> {
2234        match self {
2235            Self::Invalid { validation_error } => Some(validation_error),
2236            _ => None,
2237        }
2238    }
2239
2240    /// Returns true if the payload status is syncing.
2241    pub const fn is_syncing(&self) -> bool {
2242        matches!(self, Self::Syncing)
2243    }
2244
2245    /// Returns true if the payload status is valid.
2246    pub const fn is_valid(&self) -> bool {
2247        matches!(self, Self::Valid)
2248    }
2249
2250    /// Returns true if the payload status is invalid.
2251    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/// Struct aggregating [`ExecutionPayload`] and [`ExecutionPayloadSidecar`] and encapsulating
2270/// complete payload supplied for execution.
2271#[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    /// Execution payload.
2276    pub payload: ExecutionPayload,
2277    /// Additional fork-specific fields.
2278    pub sidecar: ExecutionPayloadSidecar,
2279}
2280
2281impl ExecutionData {
2282    /// Creates new instance of [`ExecutionData`].
2283    pub const fn new(payload: ExecutionPayload, sidecar: ExecutionPayloadSidecar) -> Self {
2284        Self { payload, sidecar }
2285    }
2286
2287    /// Conversion from [`alloy_consensus::Block`]. Also returns the [`ExecutionPayloadSidecar`]
2288    /// extracted from the block.
2289    ///
2290    /// For the [`ExecutionPayloadSidecar`] this is expected to use just the requests hash, because
2291    /// the [`Requests`] are not part of the block/header. See also
2292    /// [`RequestsOrHash`](alloy_eips::eip7685::RequestsOrHash).
2293    ///
2294    /// See also [`ExecutionPayload::from_block_unchecked`].
2295    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    /// Returns the parent hash of the block.
2305    pub const fn parent_hash(&self) -> B256 {
2306        self.payload.parent_hash()
2307    }
2308
2309    /// Returns the hash of the block.
2310    pub const fn block_hash(&self) -> B256 {
2311        self.payload.block_hash()
2312    }
2313
2314    /// Returns the number of the block.
2315    pub const fn block_number(&self) -> u64 {
2316        self.payload.block_number()
2317    }
2318
2319    /// Returns the parent beacon block root, if any.
2320    pub fn parent_beacon_block_root(&self) -> Option<B256> {
2321        self.sidecar.parent_beacon_block_root()
2322    }
2323
2324    /// Return the withdrawals for the payload or attributes.
2325    pub const fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
2326        self.payload.withdrawals()
2327    }
2328
2329    /// Tries to create a new unsealed block from the given payload and payload sidecar.
2330    ///
2331    /// Performs additional validation of `extra_data` and `base_fee_per_gas` fields.
2332    ///
2333    /// # Note
2334    ///
2335    /// The log bloom is assumed to be validated during serialization.
2336    ///
2337    /// See <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
2338    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    /// Tries to create a new unsealed block from the given payload and payload sidecar with a
2349    /// custom transaction mapper.
2350    ///
2351    /// Performs additional validation of `extra_data` and `base_fee_per_gas` fields.
2352    ///
2353    /// # Note
2354    ///
2355    /// The log bloom is assumed to be validated during serialization.
2356    ///
2357    /// See <https://github.com/ethereum/go-ethereum/blob/79a478bb6176425c2400e949890e668a3d9a3d05/core/beacon/types.go#L145>
2358    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    /// Converts [`ExecutionData`] to [`Block`] with raw [`Bytes`] transactions.
2370    ///
2371    /// This is similar to [`Self::try_into_block_with`] but returns the transactions as raw bytes
2372    /// without any conversion.
2373    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        // Attempt to decode - should fail due to mismatched proofs length
2505        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        // Attempt to decode - should fail due to wrong number of commitments
2522        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        // Decode from SSZ - empty bundle should be valid
2534        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        // pulled from hive tests
2610        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        // pulled from hive tests - modified with 4844 fields
2622        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        // pulled from hive tests
2634        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        // pulled from hive tests - modified with 4844 fields
2646        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        // pulled from a geth response getPayloadV3 in hive tests
2658        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        // incomplete V3 payload should be rejected even if it has all V2 fields
2694        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        // incomplete V3 payload should be rejected even if it has all V1 fields
2705        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        // The deserialization behavior of ExecutionPayload structs is faulty.
2716        // They should not be implicitly deserializable to an earlier version,
2717        // as this breaks round-trip behavior
2718        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        // The deserialization behavior of ExecutionPayload structs is faulty.
2731        // They should not be implicitly deserializable to an earlier version,
2732        // as this breaks round-trip behavior
2733        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        // ensure that deserializing this (it includes blob fields) fails
2826        let payload_res: Result<ExecutionPayloadInputV2, serde_json::Error> =
2827            serde_json::from_str(input);
2828        assert!(payload_res.is_err());
2829    }
2830
2831    // <https://github.com/paradigmxyz/reth/issues/6036>
2832    #[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        // this newPayload came with a parent beacon block root, we need to manually insert it
2871        // before hashing
2872        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        // ensure the payloads are the same
2879        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        // deserialize payload
3038        let payload: ExecutionPayload =
3039            serde_json::from_str::<ExecutionPayloadV3>(deser_block).unwrap().into();
3040
3041        // NOTE: the actual block hash here is incorrect, it is a result of a bug, this was the
3042        // fix:
3043        // <https://github.com/paradigmxyz/reth/pull/6328>
3044        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        // set up cancun payload fields
3052        let cancun_fields = CancunPayloadFields { parent_beacon_block_root, versioned_hashes };
3053
3054        // convert into block
3055        let block = payload
3056            .try_into_block_with_sidecar::<TxEnvelope>(&ExecutionPayloadSidecar::v3(cancun_fields))
3057            .unwrap();
3058
3059        // Ensure the actual hash is calculated if we set the fields to what they should be
3060        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            // Extract the newPayload object
3083            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            // Deserialize the sidecar
3091            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            // Convert to block with raw transactions
3095            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            // Verify the block has raw transactions (Bytes) if there are any
3100            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            // Verify sidecar fields are applied
3110            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            // Verify the block hash matches the one in the payload
3124            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        // Test decoded_transactions
3171        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        // Test decoded_transactions_with_encoded
3176        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}