reth_ethereum_engine_primitives/
payload.rs1use 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#[derive(Debug, Clone)]
31pub struct EthBuiltPayload<N: NodePrimitives = EthPrimitives> {
32 pub(crate) block: Arc<RecoveredBlock<N::Block>>,
34 pub(crate) fees: U256,
36 pub(crate) sidecars: BlobSidecars,
39 pub(crate) requests: Option<Requests>,
41 pub(crate) block_access_list: Option<Bytes>,
43}
44
45impl<N: NodePrimitives> EthBuiltPayload<N> {
48 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 pub fn block(&self) -> &SealedBlock<N::Block> {
62 self.block.sealed_block()
63 }
64
65 pub fn recovered_block(&self) -> &RecoveredBlock<N::Block> {
67 &self.block
68 }
69
70 pub const fn block_arc(&self) -> &Arc<RecoveredBlock<N::Block>> {
72 &self.block
73 }
74
75 pub const fn fees(&self) -> U256 {
77 self.fees
78 }
79
80 pub const fn sidecars(&self) -> &BlobSidecars {
82 &self.sidecars
83 }
84
85 pub fn with_sidecars(mut self, sidecars: impl Into<BlobSidecars>) -> Self {
87 self.sidecars = sidecars.into();
88 self
89 }
90}
91
92impl EthBuiltPayload {
93 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 should_override_builder: false,
122 blobs_bundle,
123 })
124 }
125
126 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 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 should_override_builder: false,
163 blobs_bundle,
164 execution_requests: requests.unwrap_or_default(),
165 })
166 }
167
168 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 should_override_builder: false,
200 blobs_bundle,
201 execution_requests: requests.unwrap_or_default(),
202 })
203 }
204
205 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
259impl 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
269impl 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#[derive(Clone, Default, Debug)]
331pub enum BlobSidecars {
332 #[default]
334 Empty,
335 Eip4844(Vec<BlobTransactionSidecar>),
337 Eip7594(Vec<BlobTransactionSidecarEip7594>),
339}
340
341impl BlobSidecars {
342 pub const fn eip4844(sidecars: Vec<BlobTransactionSidecar>) -> Self {
344 Self::Eip4844(sidecars)
345 }
346
347 pub const fn eip7594(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
349 Self::Eip7594(sidecars)
350 }
351
352 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 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 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}