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