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::{Block, EthPrimitives};
19use reth_payload_primitives::{BuiltPayload, PayloadBuilderAttributes};
20use reth_primitives_traits::SealedBlock;
21use reth_seismic_primitives::SeismicPrimitives;
22
23use crate::BuiltPayloadConversionError;
24
25use reth_primitives_traits::NodePrimitives;
27
28#[derive(Debug, Clone)]
34pub struct EthBuiltPayload<N: NodePrimitives = EthPrimitives> {
35 pub(crate) id: PayloadId,
37 pub(crate) block: Arc<SealedBlock<N::Block>>,
39 pub(crate) fees: U256,
41 pub(crate) sidecars: BlobSidecars,
44 pub(crate) requests: Option<Requests>,
46}
47
48impl<N> EthBuiltPayload<N>
51where
52 N: NodePrimitives,
53 N::Block: Into<alloy_consensus::Block<N::SignedTx>>,
54{
55 pub const fn new(
59 id: PayloadId,
60 block: Arc<SealedBlock<N::Block>>,
61 fees: U256,
62 requests: Option<Requests>,
63 ) -> Self {
64 Self { id, block, fees, requests, sidecars: BlobSidecars::Empty }
65 }
66
67 pub const fn id(&self) -> PayloadId {
69 self.id
70 }
71
72 pub fn block(&self) -> &SealedBlock<N::Block> {
74 &self.block
75 }
76
77 pub const fn fees(&self) -> U256 {
79 self.fees
80 }
81
82 pub const fn sidecars(&self) -> &BlobSidecars {
84 &self.sidecars
85 }
86
87 pub fn with_sidecars(mut self, sidecars: impl Into<BlobSidecars>) -> Self {
89 self.sidecars = sidecars.into();
90 self
91 }
92
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().into(),
111 ),
112 block_value: fees,
113 should_override_builder: false,
122 blobs_bundle,
123 })
124 }
125
126 pub fn try_into_v4(self) -> Result<ExecutionPayloadEnvelopeV4, BuiltPayloadConversionError> {
130 Ok(ExecutionPayloadEnvelopeV4 {
131 execution_requests: self.requests.clone().unwrap_or_default(),
132 envelope_inner: self.try_into_v3()?,
133 })
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().into(),
152 ),
153 block_value: fees,
154 should_override_builder: false,
163 blobs_bundle,
164 execution_requests: requests.unwrap_or_default(),
165 })
166 }
167}
168
169impl BuiltPayload for EthBuiltPayload {
170 type Primitives = EthPrimitives;
171
172 fn block(&self) -> &SealedBlock<Block> {
173 &self.block
174 }
175
176 fn fees(&self) -> U256 {
177 self.fees
178 }
179
180 fn requests(&self) -> Option<Requests> {
181 self.requests.clone()
182 }
183}
184
185type SeismicBuiltPayload = EthBuiltPayload<SeismicPrimitives>;
186
187impl SeismicBuiltPayload {
188 pub fn new_seismic_payload(
190 id: PayloadId,
191 block: Arc<SealedBlock<reth_seismic_primitives::SeismicBlock>>,
192 fees: U256,
193 sidecars: BlobSidecars,
194 requests: Option<Requests>,
195 ) -> Self {
196 Self { id, block, fees, sidecars, requests }
197 }
198}
199
200impl BuiltPayload for SeismicBuiltPayload {
201 type Primitives = reth_seismic_primitives::SeismicPrimitives;
202
203 fn block(&self) -> &SealedBlock<reth_seismic_primitives::SeismicBlock> {
204 &self.block
205 }
206
207 fn fees(&self) -> U256 {
208 self.fees
209 }
210
211 fn requests(&self) -> Option<Requests> {
212 self.requests.clone()
213 }
214}
215
216impl<N> From<EthBuiltPayload<N>> for ExecutionPayloadV1
218where
219 N: NodePrimitives,
220 N::Block: Into<alloy_consensus::Block<N::SignedTx>>,
221{
222 fn from(value: EthBuiltPayload<N>) -> Self {
223 Self::from_block_unchecked(
224 value.block().hash(),
225 &Arc::unwrap_or_clone(value.clone().block).into_block().into(),
226 )
227 }
228}
229
230impl<N> From<EthBuiltPayload<N>> for ExecutionPayloadEnvelopeV2
232where
233 N: NodePrimitives,
234 N::Block: Into<alloy_consensus::Block<N::SignedTx>>,
235{
236 fn from(value: EthBuiltPayload<N>) -> Self {
237 let EthBuiltPayload { block, fees, .. } = value;
238
239 Self {
240 block_value: fees,
241 execution_payload: ExecutionPayloadFieldV2::from_block_unchecked(
242 block.hash(),
243 &Arc::unwrap_or_clone(block).into_block().into(),
244 ),
245 }
246 }
247}
248
249impl<N> TryFrom<EthBuiltPayload<N>> for ExecutionPayloadEnvelopeV3
250where
251 N: NodePrimitives,
252 N::Block: Into<alloy_consensus::Block<N::SignedTx>>,
253{
254 type Error = BuiltPayloadConversionError;
255
256 fn try_from(value: EthBuiltPayload<N>) -> Result<Self, Self::Error> {
257 value.try_into_v3()
258 }
259}
260
261impl<N> TryFrom<EthBuiltPayload<N>> for ExecutionPayloadEnvelopeV4
262where
263 N: NodePrimitives,
264 N::Block: Into<alloy_consensus::Block<N::SignedTx>>,
265{
266 type Error = BuiltPayloadConversionError;
267
268 fn try_from(value: EthBuiltPayload<N>) -> Result<Self, Self::Error> {
269 value.try_into_v4()
270 }
271}
272
273impl<N> TryFrom<EthBuiltPayload<N>> for ExecutionPayloadEnvelopeV5
274where
275 N: NodePrimitives,
276 N::Block: Into<alloy_consensus::Block<N::SignedTx>>,
277{
278 type Error = BuiltPayloadConversionError;
279
280 fn try_from(value: EthBuiltPayload<N>) -> Result<Self, Self::Error> {
281 value.try_into_v5()
282 }
283}
284
285#[derive(Clone, Default, Debug)]
287pub enum BlobSidecars {
288 #[default]
290 Empty,
291 Eip4844(Vec<BlobTransactionSidecar>),
293 Eip7594(Vec<BlobTransactionSidecarEip7594>),
295}
296
297impl BlobSidecars {
298 pub const fn eip4844(sidecars: Vec<BlobTransactionSidecar>) -> Self {
300 Self::Eip4844(sidecars)
301 }
302
303 pub const fn eip7594(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
305 Self::Eip7594(sidecars)
306 }
307
308 pub fn push_eip4844_sidecar(&mut self, sidecar: BlobTransactionSidecar) {
310 match self {
311 Self::Empty => {
312 *self = Self::Eip4844(Vec::from([sidecar]));
313 }
314 Self::Eip4844(sidecars) => {
315 sidecars.push(sidecar);
316 }
317 Self::Eip7594(_) => {}
318 }
319 }
320
321 pub fn push_eip7594_sidecar(&mut self, sidecar: BlobTransactionSidecarEip7594) {
323 match self {
324 Self::Empty => {
325 *self = Self::Eip7594(Vec::from([sidecar]));
326 }
327 Self::Eip7594(sidecars) => {
328 sidecars.push(sidecar);
329 }
330 Self::Eip4844(_) => {}
331 }
332 }
333
334 pub fn push_sidecar_variant(&mut self, sidecar: BlobTransactionSidecarVariant) {
337 match sidecar {
338 BlobTransactionSidecarVariant::Eip4844(sidecar) => {
339 self.push_eip4844_sidecar(sidecar);
340 }
341 BlobTransactionSidecarVariant::Eip7594(sidecar) => {
342 self.push_eip7594_sidecar(sidecar);
343 }
344 }
345 }
346}
347
348impl From<Vec<BlobTransactionSidecar>> for BlobSidecars {
349 fn from(value: Vec<BlobTransactionSidecar>) -> Self {
350 Self::eip4844(value)
351 }
352}
353
354impl From<Vec<BlobTransactionSidecarEip7594>> for BlobSidecars {
355 fn from(value: Vec<BlobTransactionSidecarEip7594>) -> Self {
356 Self::eip7594(value)
357 }
358}
359
360impl From<alloc::vec::IntoIter<BlobTransactionSidecar>> for BlobSidecars {
361 fn from(value: alloc::vec::IntoIter<BlobTransactionSidecar>) -> Self {
362 value.collect::<Vec<_>>().into()
363 }
364}
365
366impl From<alloc::vec::IntoIter<BlobTransactionSidecarEip7594>> for BlobSidecars {
367 fn from(value: alloc::vec::IntoIter<BlobTransactionSidecarEip7594>) -> Self {
368 value.collect::<Vec<_>>().into()
369 }
370}
371
372#[derive(Debug, Clone, PartialEq, Eq, Default)]
374pub struct EthPayloadBuilderAttributes {
375 pub id: PayloadId,
377 pub parent: B256,
379 pub timestamp: u64,
383 pub suggested_fee_recipient: Address,
385 pub prev_randao: B256,
387 pub withdrawals: Withdrawals,
389 pub parent_beacon_block_root: Option<B256>,
391}
392
393impl EthPayloadBuilderAttributes {
396 pub const fn payload_id(&self) -> PayloadId {
398 self.id
399 }
400
401 pub fn new(parent: B256, attributes: PayloadAttributes) -> Self {
405 let id = payload_id(&parent, &attributes);
406
407 Self {
408 id,
409 parent,
410 timestamp: attributes.timestamp,
411 suggested_fee_recipient: attributes.suggested_fee_recipient,
412 prev_randao: attributes.prev_randao,
413 withdrawals: attributes.withdrawals.unwrap_or_default().into(),
414 parent_beacon_block_root: attributes.parent_beacon_block_root,
415 }
416 }
417}
418
419impl PayloadBuilderAttributes for EthPayloadBuilderAttributes {
420 type RpcPayloadAttributes = PayloadAttributes;
421 type Error = Infallible;
422
423 fn try_new(
427 parent: B256,
428 attributes: PayloadAttributes,
429 _version: u8,
430 ) -> Result<Self, Infallible> {
431 Ok(Self::new(parent, attributes))
432 }
433
434 fn payload_id(&self) -> PayloadId {
435 self.id
436 }
437
438 fn parent(&self) -> B256 {
439 self.parent
440 }
441
442 fn timestamp(&self) -> u64 {
443 self.timestamp
444 }
445
446 fn parent_beacon_block_root(&self) -> Option<B256> {
447 self.parent_beacon_block_root
448 }
449
450 fn suggested_fee_recipient(&self) -> Address {
451 self.suggested_fee_recipient
452 }
453
454 fn prev_randao(&self) -> B256 {
455 self.prev_randao
456 }
457
458 fn withdrawals(&self) -> &Withdrawals {
459 &self.withdrawals
460 }
461}
462
463pub(crate) fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId {
467 use sha2::Digest;
468 let mut hasher = sha2::Sha256::new();
469 hasher.update(parent.as_slice());
470 hasher.update(&attributes.timestamp.to_be_bytes()[..]);
471 hasher.update(attributes.prev_randao.as_slice());
472 hasher.update(attributes.suggested_fee_recipient.as_slice());
473 if let Some(withdrawals) = &attributes.withdrawals {
474 let mut buf = Vec::new();
475 withdrawals.encode(&mut buf);
476 hasher.update(buf);
477 }
478
479 if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
480 hasher.update(parent_beacon_block);
481 }
482
483 let out = hasher.finalize();
484 PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
485}
486
487#[cfg(test)]
488mod tests {
489 use super::*;
490 use alloy_eips::eip4895::Withdrawal;
491 use alloy_primitives::B64;
492 use core::str::FromStr;
493
494 #[test]
495 fn attributes_serde() {
496 let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
497 let _attributes: PayloadAttributes = serde_json::from_str(attributes).unwrap();
498 }
499
500 #[test]
501 fn test_payload_id_basic() {
502 let parent =
504 B256::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a")
505 .unwrap();
506 let attributes = PayloadAttributes {
507 timestamp: 0x5,
508 prev_randao: B256::from_str(
509 "0x0000000000000000000000000000000000000000000000000000000000000000",
510 )
511 .unwrap(),
512 suggested_fee_recipient: Address::from_str(
513 "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
514 )
515 .unwrap(),
516 withdrawals: None,
517 parent_beacon_block_root: None,
518 };
519
520 assert_eq!(
522 payload_id(&parent, &attributes),
523 PayloadId(B64::from_str("0xa247243752eb10b4").unwrap())
524 );
525 }
526
527 #[test]
528 fn test_payload_id_with_withdrawals() {
529 let parent =
531 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
532 .unwrap();
533 let attributes = PayloadAttributes {
534 timestamp: 1622553200,
535 prev_randao: B256::from_slice(&[1; 32]),
536 suggested_fee_recipient: Address::from_str(
537 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
538 )
539 .unwrap(),
540 withdrawals: Some(vec![
541 Withdrawal {
542 index: 1,
543 validator_index: 123,
544 address: Address::from([0xAA; 20]),
545 amount: 10,
546 },
547 Withdrawal {
548 index: 2,
549 validator_index: 456,
550 address: Address::from([0xBB; 20]),
551 amount: 20,
552 },
553 ]),
554 parent_beacon_block_root: None,
555 };
556
557 assert_eq!(
559 payload_id(&parent, &attributes),
560 PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
561 );
562 }
563
564 #[test]
565 fn test_payload_id_with_parent_beacon_block_root() {
566 let parent =
568 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
569 .unwrap();
570 let attributes = PayloadAttributes {
571 timestamp: 1622553200,
572 prev_randao: B256::from_str(
573 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
574 )
575 .unwrap(),
576 suggested_fee_recipient: Address::from_str(
577 "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
578 )
579 .unwrap(),
580 withdrawals: None,
581 parent_beacon_block_root: Some(
582 B256::from_str(
583 "0x2222222222222222222222222222222222222222222222222222222222222222",
584 )
585 .unwrap(),
586 ),
587 };
588
589 assert_eq!(
591 payload_id(&parent, &attributes),
592 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
593 );
594 }
595}