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