Skip to main content

reth_ethereum_engine_primitives/
payload.rs

1//! Contains types required for building a payload.
2
3use alloc::{sync::Arc, vec::Vec};
4use alloy_eips::{
5    eip4844::BlobTransactionSidecar,
6    eip7594::{BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant},
7    eip7685::Requests,
8};
9use alloy_primitives::{Bytes, U256};
10use alloy_rpc_types_engine::{
11    BlobsBundleV1, BlobsBundleV2, CancunPayloadFields, ExecutionData, ExecutionPayload,
12    ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3, ExecutionPayloadEnvelopeV4,
13    ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6, ExecutionPayloadFieldV2,
14    ExecutionPayloadSidecar, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4,
15    PraguePayloadFields,
16};
17use reth_ethereum_primitives::EthPrimitives;
18use reth_payload_primitives::BuiltPayload;
19use reth_primitives_traits::{NodePrimitives, RecoveredBlock, SealedBlock};
20
21use crate::BuiltPayloadConversionError;
22
23/// Contains the built payload.
24///
25/// According to the [engine API specification](https://github.com/ethereum/execution-apis/blob/main/src/engine/README.md) the execution layer should build the initial version of the payload with an empty transaction set and then keep update it in order to maximize the revenue.
26///
27/// This struct represents a single built block at a point in time. The payload building process
28/// creates a sequence of these payloads, starting with an empty block and progressively including
29/// more transactions.
30#[derive(Debug, Clone)]
31pub struct EthBuiltPayload<N: NodePrimitives = EthPrimitives> {
32    /// The built block
33    pub(crate) block: Arc<RecoveredBlock<N::Block>>,
34    /// The fees of the block
35    pub(crate) fees: U256,
36    /// The blobs, proofs, and commitments in the block. If the block is pre-cancun, this will be
37    /// empty.
38    pub(crate) sidecars: BlobSidecars,
39    /// The requests of the payload
40    pub(crate) requests: Option<Requests>,
41    /// The block access list of the payload
42    pub(crate) block_access_list: Option<Bytes>,
43}
44
45// === impl BuiltPayload ===
46
47impl<N: NodePrimitives> EthBuiltPayload<N> {
48    /// Initializes the payload with the given initial block
49    ///
50    /// Caution: This does not set any [`BlobSidecars`].
51    pub const fn new(
52        block: Arc<RecoveredBlock<N::Block>>,
53        fees: U256,
54        requests: Option<Requests>,
55        block_access_list: Option<Bytes>,
56    ) -> Self {
57        Self { block, fees, requests, sidecars: BlobSidecars::Empty, block_access_list }
58    }
59
60    /// Returns the built block(sealed)
61    pub fn block(&self) -> &SealedBlock<N::Block> {
62        self.block.sealed_block()
63    }
64
65    /// Returns the built block with recovered senders.
66    pub fn recovered_block(&self) -> &RecoveredBlock<N::Block> {
67        &self.block
68    }
69
70    /// Returns the built block with shared ownership.
71    pub const fn block_arc(&self) -> &Arc<RecoveredBlock<N::Block>> {
72        &self.block
73    }
74
75    /// Consumes the payload and returns the built block with shared ownership.
76    pub fn into_block_arc(self) -> Arc<RecoveredBlock<N::Block>> {
77        self.block
78    }
79
80    /// Fees of the block
81    pub const fn fees(&self) -> U256 {
82        self.fees
83    }
84
85    /// Returns the blob sidecars.
86    pub const fn sidecars(&self) -> &BlobSidecars {
87        &self.sidecars
88    }
89
90    /// Sets blob transactions sidecars on the payload.
91    pub fn with_sidecars(mut self, sidecars: impl Into<BlobSidecars>) -> Self {
92        self.sidecars = sidecars.into();
93        self
94    }
95}
96
97impl EthBuiltPayload {
98    /// Try converting built payload into [`ExecutionPayloadEnvelopeV3`].
99    ///
100    /// Returns an error if the payload contains non EIP-4844 sidecar.
101    pub fn try_into_v3(self) -> Result<ExecutionPayloadEnvelopeV3, BuiltPayloadConversionError> {
102        let Self { block, fees, sidecars, .. } = self;
103
104        let blobs_bundle = match sidecars {
105            BlobSidecars::Empty => BlobsBundleV1::empty(),
106            BlobSidecars::Eip4844(sidecars) => BlobsBundleV1::from(sidecars),
107            BlobSidecars::Eip7594(_) => {
108                return Err(BuiltPayloadConversionError::UnexpectedEip7594Sidecars)
109            }
110        };
111
112        Ok(ExecutionPayloadEnvelopeV3 {
113            execution_payload: ExecutionPayloadV3::from_block_unchecked(
114                block.hash(),
115                &Arc::unwrap_or_clone(block).into_block(),
116            ),
117            block_value: fees,
118            // From the engine API spec:
119            //
120            // > Client software **MAY** use any heuristics to decide whether to set
121            // `shouldOverrideBuilder` flag or not. If client software does not implement any
122            // heuristic this flag **SHOULD** be set to `false`.
123            //
124            // Spec:
125            // <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
126            should_override_builder: false,
127            blobs_bundle,
128        })
129    }
130
131    /// Try converting built payload into [`ExecutionPayloadEnvelopeV4`].
132    ///
133    /// Returns an error if the payload contains non EIP-4844 sidecar.
134    pub fn try_into_v4(
135        mut self,
136    ) -> Result<ExecutionPayloadEnvelopeV4, BuiltPayloadConversionError> {
137        let execution_requests = self.requests.take().unwrap_or_default();
138        Ok(ExecutionPayloadEnvelopeV4 { execution_requests, envelope_inner: self.try_into()? })
139    }
140
141    /// Try converting built payload into [`ExecutionPayloadEnvelopeV5`].
142    pub fn try_into_v5(self) -> Result<ExecutionPayloadEnvelopeV5, BuiltPayloadConversionError> {
143        let Self { block, fees, sidecars, requests, .. } = self;
144
145        let blobs_bundle = match sidecars {
146            BlobSidecars::Empty => BlobsBundleV2::empty(),
147            BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
148            BlobSidecars::Eip4844(_) => {
149                return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
150            }
151        };
152
153        Ok(ExecutionPayloadEnvelopeV5 {
154            execution_payload: ExecutionPayloadV3::from_block_unchecked(
155                block.hash(),
156                &Arc::unwrap_or_clone(block).into_block(),
157            ),
158            block_value: fees,
159            // From the engine API spec:
160            //
161            // > Client software **MAY** use any heuristics to decide whether to set
162            // `shouldOverrideBuilder` flag or not. If client software does not implement any
163            // heuristic this flag **SHOULD** be set to `false`.
164            //
165            // Spec:
166            // <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
167            should_override_builder: false,
168            blobs_bundle,
169            execution_requests: requests.unwrap_or_default(),
170        })
171    }
172
173    /// Try converting built payload into [`ExecutionPayloadEnvelopeV6`].
174    ///
175    /// Returns an error if the block access list is missing, as it's required for V6 envelopes.
176    pub fn try_into_v6(self) -> Result<ExecutionPayloadEnvelopeV6, BuiltPayloadConversionError> {
177        let Self { block, fees, sidecars, requests, block_access_list, .. } = self;
178
179        let block_access_list =
180            block_access_list.ok_or(BuiltPayloadConversionError::MissingBlockAccessList)?;
181
182        let blobs_bundle = match sidecars {
183            BlobSidecars::Empty => BlobsBundleV2::empty(),
184            BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
185            BlobSidecars::Eip4844(_) => {
186                return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
187            }
188        };
189        Ok(ExecutionPayloadEnvelopeV6 {
190            execution_payload: ExecutionPayloadV4::from_block_unchecked_with_bal(
191                block.hash(),
192                &Arc::unwrap_or_clone(block).into_block(),
193                block_access_list,
194            ),
195            block_value: fees,
196            // From the engine API spec:
197            //
198            // > Client software **MAY** use any heuristics to decide whether to set
199            // `shouldOverrideBuilder` flag or not. If client software does not implement any
200            // heuristic this flag **SHOULD** be set to `false`.
201            //
202            // Spec:
203            // <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
204            should_override_builder: false,
205            blobs_bundle,
206            execution_requests: requests.unwrap_or_default(),
207        })
208    }
209
210    /// Converts built payload into [`ExecutionData`].
211    pub fn into_execution_data(self) -> ExecutionData {
212        let Self { block, requests, block_access_list, .. } = self;
213        let block_hash = block.hash();
214        let block = Arc::unwrap_or_clone(block).into_block();
215
216        let (payload, sidecar) = ExecutionPayload::from_block_unchecked_with_extras(
217            block_hash,
218            &block,
219            block_access_list,
220        );
221
222        let sidecar = if let Some(requests) = requests {
223            block.header.parent_beacon_block_root.map_or(sidecar, |parent_beacon_block_root| {
224                ExecutionPayloadSidecar::v4(
225                    CancunPayloadFields {
226                        parent_beacon_block_root,
227                        versioned_hashes: block
228                            .body
229                            .blob_versioned_hashes_iter()
230                            .copied()
231                            .collect(),
232                    },
233                    PraguePayloadFields::new(requests),
234                )
235            })
236        } else {
237            sidecar
238        };
239
240        ExecutionData::new(payload, sidecar)
241    }
242}
243
244impl<N: NodePrimitives> BuiltPayload for EthBuiltPayload<N> {
245    type Primitives = N;
246
247    fn block(&self) -> &SealedBlock<N::Block> {
248        self.block.sealed_block()
249    }
250
251    fn fees(&self) -> U256 {
252        self.fees
253    }
254
255    fn block_access_list(&self) -> Option<&Bytes> {
256        self.block_access_list.as_ref()
257    }
258
259    fn requests(&self) -> Option<Requests> {
260        self.requests.clone()
261    }
262}
263
264// V1 engine_getPayloadV1 response
265impl From<EthBuiltPayload> for ExecutionPayloadV1 {
266    fn from(value: EthBuiltPayload) -> Self {
267        Self::from_block_unchecked(
268            value.block().hash(),
269            &Arc::unwrap_or_clone(value.block).into_block(),
270        )
271    }
272}
273
274// V2 engine_getPayloadV2 response
275impl From<EthBuiltPayload> for ExecutionPayloadEnvelopeV2 {
276    fn from(value: EthBuiltPayload) -> Self {
277        let EthBuiltPayload { block, fees, .. } = value;
278
279        Self {
280            block_value: fees,
281            execution_payload: ExecutionPayloadFieldV2::from_block_unchecked(
282                block.hash(),
283                &Arc::unwrap_or_clone(block).into_block(),
284            ),
285        }
286    }
287}
288
289impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV3 {
290    type Error = BuiltPayloadConversionError;
291
292    fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
293        value.try_into_v3()
294    }
295}
296
297impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV4 {
298    type Error = BuiltPayloadConversionError;
299
300    fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
301        value.try_into_v4()
302    }
303}
304
305impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV5 {
306    type Error = BuiltPayloadConversionError;
307
308    fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
309        value.try_into_v5()
310    }
311}
312
313impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV6 {
314    type Error = BuiltPayloadConversionError;
315
316    fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
317        value.try_into_v6()
318    }
319}
320
321impl From<EthBuiltPayload> for ExecutionData {
322    fn from(value: EthBuiltPayload) -> Self {
323        value.into_execution_data()
324    }
325}
326
327#[cfg(feature = "std")]
328impl From<EthBuiltPayload> for reth_engine_primitives::BigBlockData<ExecutionData> {
329    fn from(_value: EthBuiltPayload) -> Self {
330        unreachable!("payload building is not supported for big blocks");
331    }
332}
333
334/// An enum representing blob transaction sidecars belonging to [`EthBuiltPayload`].
335#[derive(Clone, Default, Debug)]
336pub enum BlobSidecars {
337    /// No sidecars (default).
338    #[default]
339    Empty,
340    /// EIP-4844 style sidecars.
341    Eip4844(Vec<BlobTransactionSidecar>),
342    /// EIP-7594 style sidecars.
343    Eip7594(Vec<BlobTransactionSidecarEip7594>),
344}
345
346impl BlobSidecars {
347    /// Create new EIP-4844 style sidecars.
348    pub const fn eip4844(sidecars: Vec<BlobTransactionSidecar>) -> Self {
349        Self::Eip4844(sidecars)
350    }
351
352    /// Create new EIP-7594 style sidecars.
353    pub const fn eip7594(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
354        Self::Eip7594(sidecars)
355    }
356
357    /// Push EIP-4844 blob sidecar. Ignores the item if sidecars already contain EIP-7594 sidecars.
358    pub fn push_eip4844_sidecar(&mut self, sidecar: BlobTransactionSidecar) {
359        match self {
360            Self::Empty => {
361                *self = Self::Eip4844(Vec::from([sidecar]));
362            }
363            Self::Eip4844(sidecars) => {
364                sidecars.push(sidecar);
365            }
366            Self::Eip7594(_) => {}
367        }
368    }
369
370    /// Push EIP-7594 blob sidecar. Ignores the item if sidecars already contain EIP-4844 sidecars.
371    pub fn push_eip7594_sidecar(&mut self, sidecar: BlobTransactionSidecarEip7594) {
372        match self {
373            Self::Empty => {
374                *self = Self::Eip7594(Vec::from([sidecar]));
375            }
376            Self::Eip7594(sidecars) => {
377                sidecars.push(sidecar);
378            }
379            Self::Eip4844(_) => {}
380        }
381    }
382
383    /// Push a [`BlobTransactionSidecarVariant`]. Ignores the item if sidecars already contain the
384    /// opposite type.
385    pub fn push_sidecar_variant(&mut self, sidecar: BlobTransactionSidecarVariant) {
386        match sidecar {
387            BlobTransactionSidecarVariant::Eip4844(sidecar) => {
388                self.push_eip4844_sidecar(sidecar);
389            }
390            BlobTransactionSidecarVariant::Eip7594(sidecar) => {
391                self.push_eip7594_sidecar(sidecar);
392            }
393        }
394    }
395}
396
397impl From<Vec<BlobTransactionSidecar>> for BlobSidecars {
398    fn from(value: Vec<BlobTransactionSidecar>) -> Self {
399        Self::eip4844(value)
400    }
401}
402
403impl From<Vec<BlobTransactionSidecarEip7594>> for BlobSidecars {
404    fn from(value: Vec<BlobTransactionSidecarEip7594>) -> Self {
405        Self::eip7594(value)
406    }
407}
408
409impl From<alloc::vec::IntoIter<BlobTransactionSidecar>> for BlobSidecars {
410    fn from(value: alloc::vec::IntoIter<BlobTransactionSidecar>) -> Self {
411        value.collect::<Vec<_>>().into()
412    }
413}
414
415impl From<alloc::vec::IntoIter<BlobTransactionSidecarEip7594>> for BlobSidecars {
416    fn from(value: alloc::vec::IntoIter<BlobTransactionSidecarEip7594>) -> Self {
417        value.collect::<Vec<_>>().into()
418    }
419}
420
421#[cfg(test)]
422mod tests {
423    use super::*;
424    use alloy_primitives::B256;
425    use reth_primitives_traits::{Block as _, RecoveredBlock};
426
427    #[test]
428    fn into_execution_data_preserves_requests() {
429        let requests = Requests::from_requests([Bytes::from_static(&[1, 2])]);
430        let parent_beacon_block_root = B256::with_last_byte(1);
431
432        let mut block = reth_ethereum_primitives::Block::default();
433        block.header.parent_beacon_block_root = Some(parent_beacon_block_root);
434        block.header.requests_hash = Some(requests.requests_hash());
435
436        let payload = EthBuiltPayload::new(
437            Arc::new(RecoveredBlock::new_sealed(block.seal_slow(), vec![])),
438            U256::ZERO,
439            Some(requests.clone()),
440            None,
441        );
442
443        let execution_data: ExecutionData = payload.into();
444
445        assert_eq!(
446            execution_data.sidecar.parent_beacon_block_root(),
447            Some(parent_beacon_block_root)
448        );
449        assert_eq!(execution_data.sidecar.requests(), Some(&requests));
450    }
451
452    #[test]
453    fn into_execution_data_preserves_block_access_list() {
454        let block_access_list = Bytes::from_static(&[0xc0]);
455
456        let mut block = reth_ethereum_primitives::Block::default();
457        block.header.parent_beacon_block_root = Some(B256::ZERO);
458        block.header.block_access_list_hash = Some(B256::ZERO);
459
460        let payload = EthBuiltPayload::new(
461            Arc::new(RecoveredBlock::new_sealed(block.seal_slow(), vec![])),
462            U256::ZERO,
463            None,
464            Some(block_access_list.clone()),
465        );
466
467        let execution_data: ExecutionData = payload.into();
468
469        assert_eq!(execution_data.payload.block_access_list(), Some(&block_access_list));
470    }
471
472    #[test]
473    fn execution_data_has_infallible_try_from_impl() {
474        fn assert_try_from<T: TryFrom<EthBuiltPayload, Error = core::convert::Infallible>>() {}
475
476        assert_try_from::<ExecutionData>();
477    }
478}