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 PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
428}
429
430#[cfg(test)]
431mod tests {
432 use super::*;
433 use alloy_eips::eip4895::Withdrawal;
434 use alloy_primitives::B64;
435 use core::str::FromStr;
436
437 #[test]
438 fn attributes_serde() {
439 let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
440 let _attributes: PayloadAttributes = serde_json::from_str(attributes).unwrap();
441 }
442
443 #[test]
444 fn test_payload_id_basic() {
445 let parent =
447 B256::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a")
448 .unwrap();
449 let attributes = PayloadAttributes {
450 timestamp: 0x5,
451 prev_randao: B256::from_str(
452 "0x0000000000000000000000000000000000000000000000000000000000000000",
453 )
454 .unwrap(),
455 suggested_fee_recipient: Address::from_str(
456 "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
457 )
458 .unwrap(),
459 withdrawals: None,
460 parent_beacon_block_root: None,
461 };
462
463 assert_eq!(
465 payload_id(&parent, &attributes),
466 PayloadId(B64::from_str("0xa247243752eb10b4").unwrap())
467 );
468 }
469
470 #[test]
471 fn test_payload_id_with_withdrawals() {
472 let parent =
474 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
475 .unwrap();
476 let attributes = PayloadAttributes {
477 timestamp: 1622553200,
478 prev_randao: B256::from_slice(&[1; 32]),
479 suggested_fee_recipient: Address::from_str(
480 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
481 )
482 .unwrap(),
483 withdrawals: Some(vec![
484 Withdrawal {
485 index: 1,
486 validator_index: 123,
487 address: Address::from([0xAA; 20]),
488 amount: 10,
489 },
490 Withdrawal {
491 index: 2,
492 validator_index: 456,
493 address: Address::from([0xBB; 20]),
494 amount: 20,
495 },
496 ]),
497 parent_beacon_block_root: None,
498 };
499
500 assert_eq!(
502 payload_id(&parent, &attributes),
503 PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
504 );
505 }
506
507 #[test]
508 fn test_payload_id_with_parent_beacon_block_root() {
509 let parent =
511 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
512 .unwrap();
513 let attributes = PayloadAttributes {
514 timestamp: 1622553200,
515 prev_randao: B256::from_str(
516 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
517 )
518 .unwrap(),
519 suggested_fee_recipient: Address::from_str(
520 "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
521 )
522 .unwrap(),
523 withdrawals: None,
524 parent_beacon_block_root: Some(
525 B256::from_str(
526 "0x2222222222222222222222222222222222222222222222222222222222222222",
527 )
528 .unwrap(),
529 ),
530 };
531
532 assert_eq!(
534 payload_id(&parent, &attributes),
535 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
536 );
537 }
538}