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