reth_ethereum_engine_primitives/
payload.rs1use alloc::{sync::Arc, vec::Vec};
4use alloy_eips::{
5 eip4844::BlobTransactionSidecar,
6 eip4895::Withdrawals,
7 eip7594::{BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant},
8 eip7685::Requests,
9};
10use alloy_primitives::{Address, B256, U256};
11use alloy_rlp::Encodable;
12use alloy_rpc_types_engine::{
13 BlobsBundleV1, BlobsBundleV2, ExecutionPayloadEnvelopeV2, ExecutionPayloadEnvelopeV3,
14 ExecutionPayloadEnvelopeV4, ExecutionPayloadEnvelopeV5, ExecutionPayloadFieldV2,
15 ExecutionPayloadV1, ExecutionPayloadV3, PayloadAttributes, PayloadId,
16};
17use core::convert::Infallible;
18use reth_ethereum_primitives::EthPrimitives;
19use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes};
20use reth_primitives_traits::{NodePrimitives, SealedBlock};
21
22use crate::BuiltPayloadConversionError;
23
24#[derive(Debug, Clone)]
30pub struct EthBuiltPayload<N: NodePrimitives = EthPrimitives> {
31 pub(crate) id: PayloadId,
33 pub(crate) block: Arc<SealedBlock<N::Block>>,
35 pub(crate) fees: U256,
37 pub(crate) sidecars: BlobSidecars,
40 pub(crate) requests: Option<Requests>,
42}
43
44impl<N: NodePrimitives> EthBuiltPayload<N> {
47 pub const fn new(
51 id: PayloadId,
52 block: Arc<SealedBlock<N::Block>>,
53 fees: U256,
54 requests: Option<Requests>,
55 ) -> Self {
56 Self { id, block, fees, requests, sidecars: BlobSidecars::Empty }
57 }
58
59 pub const fn id(&self) -> PayloadId {
61 self.id
62 }
63
64 pub fn block(&self) -> &SealedBlock<N::Block> {
66 &self.block
67 }
68
69 pub const fn fees(&self) -> U256 {
71 self.fees
72 }
73
74 pub const fn sidecars(&self) -> &BlobSidecars {
76 &self.sidecars
77 }
78
79 pub fn with_sidecars(mut self, sidecars: impl Into<BlobSidecars>) -> Self {
81 self.sidecars = sidecars.into();
82 self
83 }
84}
85
86impl EthBuiltPayload {
87 pub fn try_into_v3(self) -> Result<ExecutionPayloadEnvelopeV3, BuiltPayloadConversionError> {
91 let Self { block, fees, sidecars, .. } = self;
92
93 let blobs_bundle = match sidecars {
94 BlobSidecars::Empty => BlobsBundleV1::empty(),
95 BlobSidecars::Eip4844(sidecars) => BlobsBundleV1::from(sidecars),
96 BlobSidecars::Eip7594(_) => {
97 return Err(BuiltPayloadConversionError::UnexpectedEip7594Sidecars)
98 }
99 };
100
101 Ok(ExecutionPayloadEnvelopeV3 {
102 execution_payload: ExecutionPayloadV3::from_block_unchecked(
103 block.hash(),
104 &Arc::unwrap_or_clone(block).into_block(),
105 ),
106 block_value: fees,
107 should_override_builder: false,
116 blobs_bundle,
117 })
118 }
119
120 pub fn try_into_v4(self) -> Result<ExecutionPayloadEnvelopeV4, BuiltPayloadConversionError> {
124 Ok(ExecutionPayloadEnvelopeV4 {
125 execution_requests: self.requests.clone().unwrap_or_default(),
126 envelope_inner: self.try_into()?,
127 })
128 }
129
130 pub fn try_into_v5(self) -> Result<ExecutionPayloadEnvelopeV5, BuiltPayloadConversionError> {
132 let Self { block, fees, sidecars, requests, .. } = self;
133
134 let blobs_bundle = match sidecars {
135 BlobSidecars::Empty => BlobsBundleV2::empty(),
136 BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
137 BlobSidecars::Eip4844(_) => {
138 return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
139 }
140 };
141
142 Ok(ExecutionPayloadEnvelopeV5 {
143 execution_payload: ExecutionPayloadV3::from_block_unchecked(
144 block.hash(),
145 &Arc::unwrap_or_clone(block).into_block(),
146 ),
147 block_value: fees,
148 should_override_builder: false,
157 blobs_bundle,
158 execution_requests: requests.unwrap_or_default(),
159 })
160 }
161}
162
163impl<N: NodePrimitives> BuiltPayload for EthBuiltPayload<N> {
164 type Primitives = N;
165
166 fn block(&self) -> &SealedBlock<N::Block> {
167 &self.block
168 }
169
170 fn fees(&self) -> U256 {
171 self.fees
172 }
173
174 fn requests(&self) -> Option<Requests> {
175 self.requests.clone()
176 }
177}
178
179impl From<EthBuiltPayload> for ExecutionPayloadV1 {
181 fn from(value: EthBuiltPayload) -> Self {
182 Self::from_block_unchecked(
183 value.block().hash(),
184 &Arc::unwrap_or_clone(value.block).into_block(),
185 )
186 }
187}
188
189impl From<EthBuiltPayload> for ExecutionPayloadEnvelopeV2 {
191 fn from(value: EthBuiltPayload) -> Self {
192 let EthBuiltPayload { block, fees, .. } = value;
193
194 Self {
195 block_value: fees,
196 execution_payload: ExecutionPayloadFieldV2::from_block_unchecked(
197 block.hash(),
198 &Arc::unwrap_or_clone(block).into_block(),
199 ),
200 }
201 }
202}
203
204impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV3 {
205 type Error = BuiltPayloadConversionError;
206
207 fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
208 value.try_into_v3()
209 }
210}
211
212impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV4 {
213 type Error = BuiltPayloadConversionError;
214
215 fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
216 value.try_into_v4()
217 }
218}
219
220impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV5 {
221 type Error = BuiltPayloadConversionError;
222
223 fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
224 value.try_into_v5()
225 }
226}
227
228#[derive(Clone, Default, Debug)]
230pub enum BlobSidecars {
231 #[default]
233 Empty,
234 Eip4844(Vec<BlobTransactionSidecar>),
236 Eip7594(Vec<BlobTransactionSidecarEip7594>),
238}
239
240impl BlobSidecars {
241 pub const fn eip4844(sidecars: Vec<BlobTransactionSidecar>) -> Self {
243 Self::Eip4844(sidecars)
244 }
245
246 pub const fn eip7594(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
248 Self::Eip7594(sidecars)
249 }
250
251 pub fn push_eip4844_sidecar(&mut self, sidecar: BlobTransactionSidecar) {
253 match self {
254 Self::Empty => {
255 *self = Self::Eip4844(Vec::from([sidecar]));
256 }
257 Self::Eip4844(sidecars) => {
258 sidecars.push(sidecar);
259 }
260 Self::Eip7594(_) => {}
261 }
262 }
263
264 pub fn push_eip7594_sidecar(&mut self, sidecar: BlobTransactionSidecarEip7594) {
266 match self {
267 Self::Empty => {
268 *self = Self::Eip7594(Vec::from([sidecar]));
269 }
270 Self::Eip7594(sidecars) => {
271 sidecars.push(sidecar);
272 }
273 Self::Eip4844(_) => {}
274 }
275 }
276
277 pub fn push_sidecar_variant(&mut self, sidecar: BlobTransactionSidecarVariant) {
280 match sidecar {
281 BlobTransactionSidecarVariant::Eip4844(sidecar) => {
282 self.push_eip4844_sidecar(sidecar);
283 }
284 BlobTransactionSidecarVariant::Eip7594(sidecar) => {
285 self.push_eip7594_sidecar(sidecar);
286 }
287 }
288 }
289}
290
291impl From<Vec<BlobTransactionSidecar>> for BlobSidecars {
292 fn from(value: Vec<BlobTransactionSidecar>) -> Self {
293 Self::eip4844(value)
294 }
295}
296
297impl From<Vec<BlobTransactionSidecarEip7594>> for BlobSidecars {
298 fn from(value: Vec<BlobTransactionSidecarEip7594>) -> Self {
299 Self::eip7594(value)
300 }
301}
302
303impl From<alloc::vec::IntoIter<BlobTransactionSidecar>> for BlobSidecars {
304 fn from(value: alloc::vec::IntoIter<BlobTransactionSidecar>) -> Self {
305 value.collect::<Vec<_>>().into()
306 }
307}
308
309impl From<alloc::vec::IntoIter<BlobTransactionSidecarEip7594>> for BlobSidecars {
310 fn from(value: alloc::vec::IntoIter<BlobTransactionSidecarEip7594>) -> Self {
311 value.collect::<Vec<_>>().into()
312 }
313}
314
315#[derive(Debug, Clone, PartialEq, Eq, Default)]
317pub struct EthPayloadBuilderAttributes {
318 pub id: PayloadId,
320 pub parent: B256,
322 pub timestamp: u64,
326 pub suggested_fee_recipient: Address,
328 pub prev_randao: B256,
330 pub withdrawals: Withdrawals,
332 pub parent_beacon_block_root: Option<B256>,
334}
335
336impl EthPayloadBuilderAttributes {
339 pub const fn payload_id(&self) -> PayloadId {
341 self.id
342 }
343
344 pub fn new(parent: B256, attributes: PayloadAttributes) -> Self {
348 let id = payload_id(&parent, &attributes);
349
350 Self {
351 id,
352 parent,
353 timestamp: attributes.timestamp,
354 suggested_fee_recipient: attributes.suggested_fee_recipient,
355 prev_randao: attributes.prev_randao,
356 withdrawals: attributes.withdrawals.unwrap_or_default().into(),
357 parent_beacon_block_root: attributes.parent_beacon_block_root,
358 }
359 }
360}
361
362impl PayloadBuilderAttributes for EthPayloadBuilderAttributes {
363 type RpcPayloadAttributes = PayloadAttributes;
364 type Error = Infallible;
365
366 fn try_new(
370 parent: B256,
371 attributes: PayloadAttributes,
372 _version: u8,
373 ) -> Result<Self, Infallible> {
374 Ok(Self::new(parent, attributes))
375 }
376
377 fn payload_id(&self) -> PayloadId {
378 self.id
379 }
380
381 fn parent(&self) -> B256 {
382 self.parent
383 }
384
385 fn timestamp(&self) -> u64 {
386 self.timestamp
387 }
388
389 fn parent_beacon_block_root(&self) -> Option<B256> {
390 self.parent_beacon_block_root
391 }
392
393 fn suggested_fee_recipient(&self) -> Address {
394 self.suggested_fee_recipient
395 }
396
397 fn prev_randao(&self) -> B256 {
398 self.prev_randao
399 }
400
401 fn withdrawals(&self) -> &Withdrawals {
402 &self.withdrawals
403 }
404}
405
406pub fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId {
410 use sha2::Digest;
411 let mut hasher = sha2::Sha256::new();
412 hasher.update(parent.as_slice());
413 hasher.update(&attributes.timestamp.to_be_bytes()[..]);
414 hasher.update(attributes.prev_randao.as_slice());
415 hasher.update(attributes.suggested_fee_recipient.as_slice());
416 if let Some(withdrawals) = &attributes.withdrawals {
417 let mut buf = Vec::new();
418 withdrawals.encode(&mut buf);
419 hasher.update(buf);
420 }
421
422 if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
423 hasher.update(parent_beacon_block);
424 }
425
426 let out = hasher.finalize();
427
428 #[allow(deprecated)] PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435 use alloy_eips::eip4895::Withdrawal;
436 use alloy_primitives::B64;
437 use core::str::FromStr;
438
439 #[test]
440 fn attributes_serde() {
441 let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
442 let _attributes: PayloadAttributes = serde_json::from_str(attributes).unwrap();
443 }
444
445 #[test]
446 fn test_payload_id_basic() {
447 let parent =
449 B256::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a")
450 .unwrap();
451 let attributes = PayloadAttributes {
452 timestamp: 0x5,
453 prev_randao: B256::from_str(
454 "0x0000000000000000000000000000000000000000000000000000000000000000",
455 )
456 .unwrap(),
457 suggested_fee_recipient: Address::from_str(
458 "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
459 )
460 .unwrap(),
461 withdrawals: None,
462 parent_beacon_block_root: None,
463 };
464
465 assert_eq!(
467 payload_id(&parent, &attributes),
468 PayloadId(B64::from_str("0xa247243752eb10b4").unwrap())
469 );
470 }
471
472 #[test]
473 fn test_payload_id_with_withdrawals() {
474 let parent =
476 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
477 .unwrap();
478 let attributes = PayloadAttributes {
479 timestamp: 1622553200,
480 prev_randao: B256::from_slice(&[1; 32]),
481 suggested_fee_recipient: Address::from_str(
482 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
483 )
484 .unwrap(),
485 withdrawals: Some(vec![
486 Withdrawal {
487 index: 1,
488 validator_index: 123,
489 address: Address::from([0xAA; 20]),
490 amount: 10,
491 },
492 Withdrawal {
493 index: 2,
494 validator_index: 456,
495 address: Address::from([0xBB; 20]),
496 amount: 20,
497 },
498 ]),
499 parent_beacon_block_root: None,
500 };
501
502 assert_eq!(
504 payload_id(&parent, &attributes),
505 PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
506 );
507 }
508
509 #[test]
510 fn test_payload_id_with_parent_beacon_block_root() {
511 let parent =
513 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
514 .unwrap();
515 let attributes = PayloadAttributes {
516 timestamp: 1622553200,
517 prev_randao: B256::from_str(
518 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
519 )
520 .unwrap(),
521 suggested_fee_recipient: Address::from_str(
522 "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
523 )
524 .unwrap(),
525 withdrawals: None,
526 parent_beacon_block_root: Some(
527 B256::from_str(
528 "0x2222222222222222222222222222222222222222222222222222222222222222",
529 )
530 .unwrap(),
531 ),
532 };
533
534 assert_eq!(
536 payload_id(&parent, &attributes),
537 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
538 );
539 }
540}