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, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3,
12    ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadEnvelopeV6,
13    ExecutionPayloadFieldV2, ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4,
14};
15use reth_ethereum_primitives::EthPrimitives;
16use reth_payload_primitives::BuiltPayload;
17use reth_primitives_traits::{NodePrimitives, SealedBlock};
18
19use crate::BuiltPayloadConversionError;
20
21/// Contains the built payload.
22///
23/// 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.
24///
25/// This struct represents a single built block at a point in time. The payload building process
26/// creates a sequence of these payloads, starting with an empty block and progressively including
27/// more transactions.
28#[derive(Debug, Clone)]
29pub struct EthBuiltPayload<N: NodePrimitives = EthPrimitives> {
30    /// The built block
31    pub(crate) block: Arc<SealedBlock<N::Block>>,
32    /// The fees of the block
33    pub(crate) fees: U256,
34    /// The blobs, proofs, and commitments in the block. If the block is pre-cancun, this will be
35    /// empty.
36    pub(crate) sidecars: BlobSidecars,
37    /// The requests of the payload
38    pub(crate) requests: Option<Requests>,
39    /// The block access list of the payload
40    pub(crate) block_access_list: Option<Bytes>,
41}
42
43// === impl BuiltPayload ===
44
45impl<N: NodePrimitives> EthBuiltPayload<N> {
46    /// Initializes the payload with the given initial block
47    ///
48    /// Caution: This does not set any [`BlobSidecars`].
49    pub const fn new(
50        block: Arc<SealedBlock<N::Block>>,
51        fees: U256,
52        requests: Option<Requests>,
53        block_access_list: Option<Bytes>,
54    ) -> Self {
55        Self { block, fees, requests, sidecars: BlobSidecars::Empty, block_access_list }
56    }
57
58    /// Returns the built block(sealed)
59    pub fn block(&self) -> &SealedBlock<N::Block> {
60        &self.block
61    }
62
63    /// Fees of the block
64    pub const fn fees(&self) -> U256 {
65        self.fees
66    }
67
68    /// Returns the blob sidecars.
69    pub const fn sidecars(&self) -> &BlobSidecars {
70        &self.sidecars
71    }
72
73    /// Sets blob transactions sidecars on the payload.
74    pub fn with_sidecars(mut self, sidecars: impl Into<BlobSidecars>) -> Self {
75        self.sidecars = sidecars.into();
76        self
77    }
78}
79
80impl EthBuiltPayload {
81    /// Try converting built payload into [`ExecutionPayloadEnvelopeV3`].
82    ///
83    /// Returns an error if the payload contains non EIP-4844 sidecar.
84    pub fn try_into_v3(self) -> Result<ExecutionPayloadEnvelopeV3, BuiltPayloadConversionError> {
85        let Self { block, fees, sidecars, .. } = self;
86
87        let blobs_bundle = match sidecars {
88            BlobSidecars::Empty => BlobsBundleV1::empty(),
89            BlobSidecars::Eip4844(sidecars) => BlobsBundleV1::from(sidecars),
90            BlobSidecars::Eip7594(_) => {
91                return Err(BuiltPayloadConversionError::UnexpectedEip7594Sidecars)
92            }
93        };
94
95        Ok(ExecutionPayloadEnvelopeV3 {
96            execution_payload: ExecutionPayloadV3::from_block_unchecked(
97                block.hash(),
98                &Arc::unwrap_or_clone(block).into_block(),
99            ),
100            block_value: fees,
101            // From the engine API spec:
102            //
103            // > Client software **MAY** use any heuristics to decide whether to set
104            // `shouldOverrideBuilder` flag or not. If client software does not implement any
105            // heuristic this flag **SHOULD** be set to `false`.
106            //
107            // Spec:
108            // <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
109            should_override_builder: false,
110            blobs_bundle,
111        })
112    }
113
114    /// Try converting built payload into [`ExecutionPayloadEnvelopeV4`].
115    ///
116    /// Returns an error if the payload contains non EIP-4844 sidecar.
117    pub fn try_into_v4(
118        mut self,
119    ) -> Result<ExecutionPayloadEnvelopeV4, BuiltPayloadConversionError> {
120        let execution_requests = self.requests.take().unwrap_or_default();
121        Ok(ExecutionPayloadEnvelopeV4 { execution_requests, envelope_inner: self.try_into()? })
122    }
123
124    /// Try converting built payload into [`ExecutionPayloadEnvelopeV5`].
125    pub fn try_into_v5(self) -> Result<ExecutionPayloadEnvelopeV5, BuiltPayloadConversionError> {
126        let Self { block, fees, sidecars, requests, .. } = self;
127
128        let blobs_bundle = match sidecars {
129            BlobSidecars::Empty => BlobsBundleV2::empty(),
130            BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
131            BlobSidecars::Eip4844(_) => {
132                return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
133            }
134        };
135
136        Ok(ExecutionPayloadEnvelopeV5 {
137            execution_payload: ExecutionPayloadV3::from_block_unchecked(
138                block.hash(),
139                &Arc::unwrap_or_clone(block).into_block(),
140            ),
141            block_value: fees,
142            // From the engine API spec:
143            //
144            // > Client software **MAY** use any heuristics to decide whether to set
145            // `shouldOverrideBuilder` flag or not. If client software does not implement any
146            // heuristic this flag **SHOULD** be set to `false`.
147            //
148            // Spec:
149            // <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
150            should_override_builder: false,
151            blobs_bundle,
152            execution_requests: requests.unwrap_or_default(),
153        })
154    }
155
156    /// Try converting built payload into [`ExecutionPayloadEnvelopeV6`].
157    ///
158    /// Returns an error if the block access list is missing, as it's required for V6 envelopes.
159    pub fn try_into_v6(self) -> Result<ExecutionPayloadEnvelopeV6, BuiltPayloadConversionError> {
160        let Self { block, fees, sidecars, requests, block_access_list, .. } = self;
161
162        let block_access_list =
163            block_access_list.ok_or(BuiltPayloadConversionError::MissingBlockAccessList)?;
164
165        let blobs_bundle = match sidecars {
166            BlobSidecars::Empty => BlobsBundleV2::empty(),
167            BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
168            BlobSidecars::Eip4844(_) => {
169                return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
170            }
171        };
172        Ok(ExecutionPayloadEnvelopeV6 {
173            execution_payload: ExecutionPayloadV4::from_block_unchecked_with_bal(
174                block.hash(),
175                &Arc::unwrap_or_clone(block).into_block(),
176                block_access_list,
177            ),
178            block_value: fees,
179            // From the engine API spec:
180            //
181            // > Client software **MAY** use any heuristics to decide whether to set
182            // `shouldOverrideBuilder` flag or not. If client software does not implement any
183            // heuristic this flag **SHOULD** be set to `false`.
184            //
185            // Spec:
186            // <https://github.com/ethereum/execution-apis/blob/fe8e13c288c592ec154ce25c534e26cb7ce0530d/src/engine/cancun.md#specification-2>
187            should_override_builder: false,
188            blobs_bundle,
189            execution_requests: requests.unwrap_or_default(),
190        })
191    }
192}
193
194impl<N: NodePrimitives> BuiltPayload for EthBuiltPayload<N> {
195    type Primitives = N;
196
197    fn block(&self) -> &SealedBlock<N::Block> {
198        &self.block
199    }
200
201    fn fees(&self) -> U256 {
202        self.fees
203    }
204
205    fn requests(&self) -> Option<Requests> {
206        self.requests.clone()
207    }
208}
209
210// V1 engine_getPayloadV1 response
211impl From<EthBuiltPayload> for ExecutionPayloadV1 {
212    fn from(value: EthBuiltPayload) -> Self {
213        Self::from_block_unchecked(
214            value.block().hash(),
215            &Arc::unwrap_or_clone(value.block).into_block(),
216        )
217    }
218}
219
220// V2 engine_getPayloadV2 response
221impl From<EthBuiltPayload> for ExecutionPayloadEnvelopeV2 {
222    fn from(value: EthBuiltPayload) -> Self {
223        let EthBuiltPayload { block, fees, .. } = value;
224
225        Self {
226            block_value: fees,
227            execution_payload: ExecutionPayloadFieldV2::from_block_unchecked(
228                block.hash(),
229                &Arc::unwrap_or_clone(block).into_block(),
230            ),
231        }
232    }
233}
234
235impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV3 {
236    type Error = BuiltPayloadConversionError;
237
238    fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
239        value.try_into_v3()
240    }
241}
242
243impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV4 {
244    type Error = BuiltPayloadConversionError;
245
246    fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
247        value.try_into_v4()
248    }
249}
250
251impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV5 {
252    type Error = BuiltPayloadConversionError;
253
254    fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
255        value.try_into_v5()
256    }
257}
258
259impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV6 {
260    type Error = BuiltPayloadConversionError;
261
262    fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
263        value.try_into_v6()
264    }
265}
266
267/// An enum representing blob transaction sidecars belonging to [`EthBuiltPayload`].
268#[derive(Clone, Default, Debug)]
269pub enum BlobSidecars {
270    /// No sidecars (default).
271    #[default]
272    Empty,
273    /// EIP-4844 style sidecars.
274    Eip4844(Vec<BlobTransactionSidecar>),
275    /// EIP-7594 style sidecars.
276    Eip7594(Vec<BlobTransactionSidecarEip7594>),
277}
278
279impl BlobSidecars {
280    /// Create new EIP-4844 style sidecars.
281    pub const fn eip4844(sidecars: Vec<BlobTransactionSidecar>) -> Self {
282        Self::Eip4844(sidecars)
283    }
284
285    /// Create new EIP-7594 style sidecars.
286    pub const fn eip7594(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
287        Self::Eip7594(sidecars)
288    }
289
290    /// Push EIP-4844 blob sidecar. Ignores the item if sidecars already contain EIP-7594 sidecars.
291    pub fn push_eip4844_sidecar(&mut self, sidecar: BlobTransactionSidecar) {
292        match self {
293            Self::Empty => {
294                *self = Self::Eip4844(Vec::from([sidecar]));
295            }
296            Self::Eip4844(sidecars) => {
297                sidecars.push(sidecar);
298            }
299            Self::Eip7594(_) => {}
300        }
301    }
302
303    /// Push EIP-7594 blob sidecar. Ignores the item if sidecars already contain EIP-4844 sidecars.
304    pub fn push_eip7594_sidecar(&mut self, sidecar: BlobTransactionSidecarEip7594) {
305        match self {
306            Self::Empty => {
307                *self = Self::Eip7594(Vec::from([sidecar]));
308            }
309            Self::Eip7594(sidecars) => {
310                sidecars.push(sidecar);
311            }
312            Self::Eip4844(_) => {}
313        }
314    }
315
316    /// Push a [`BlobTransactionSidecarVariant`]. Ignores the item if sidecars already contain the
317    /// opposite type.
318    pub fn push_sidecar_variant(&mut self, sidecar: BlobTransactionSidecarVariant) {
319        match sidecar {
320            BlobTransactionSidecarVariant::Eip4844(sidecar) => {
321                self.push_eip4844_sidecar(sidecar);
322            }
323            BlobTransactionSidecarVariant::Eip7594(sidecar) => {
324                self.push_eip7594_sidecar(sidecar);
325            }
326        }
327    }
328}
329
330impl From<Vec<BlobTransactionSidecar>> for BlobSidecars {
331    fn from(value: Vec<BlobTransactionSidecar>) -> Self {
332        Self::eip4844(value)
333    }
334}
335
336impl From<Vec<BlobTransactionSidecarEip7594>> for BlobSidecars {
337    fn from(value: Vec<BlobTransactionSidecarEip7594>) -> Self {
338        Self::eip7594(value)
339    }
340}
341
342impl From<alloc::vec::IntoIter<BlobTransactionSidecar>> for BlobSidecars {
343    fn from(value: alloc::vec::IntoIter<BlobTransactionSidecar>) -> Self {
344        value.collect::<Vec<_>>().into()
345    }
346}
347
348impl From<alloc::vec::IntoIter<BlobTransactionSidecarEip7594>> for BlobSidecars {
349    fn from(value: alloc::vec::IntoIter<BlobTransactionSidecarEip7594>) -> Self {
350        value.collect::<Vec<_>>().into()
351    }
352}