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, ExecutionPayloadEnvelopeV6,
15 ExecutionPayloadFieldV2, 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)]
32pub struct EthBuiltPayload<N: NodePrimitives = EthPrimitives> {
33 pub(crate) id: PayloadId,
35 pub(crate) block: Arc<SealedBlock<N::Block>>,
37 pub(crate) fees: U256,
39 pub(crate) sidecars: BlobSidecars,
42 pub(crate) requests: Option<Requests>,
44}
45
46impl<N: NodePrimitives> EthBuiltPayload<N> {
49 pub const fn new(
53 id: PayloadId,
54 block: Arc<SealedBlock<N::Block>>,
55 fees: U256,
56 requests: Option<Requests>,
57 ) -> Self {
58 Self { id, block, fees, requests, sidecars: BlobSidecars::Empty }
59 }
60
61 pub const fn id(&self) -> PayloadId {
63 self.id
64 }
65
66 pub fn block(&self) -> &SealedBlock<N::Block> {
68 &self.block
69 }
70
71 pub const fn fees(&self) -> U256 {
73 self.fees
74 }
75
76 pub const fn sidecars(&self) -> &BlobSidecars {
78 &self.sidecars
79 }
80
81 pub fn with_sidecars(mut self, sidecars: impl Into<BlobSidecars>) -> Self {
83 self.sidecars = sidecars.into();
84 self
85 }
86}
87
88impl EthBuiltPayload {
89 pub fn try_into_v3(self) -> Result<ExecutionPayloadEnvelopeV3, BuiltPayloadConversionError> {
93 let Self { block, fees, sidecars, .. } = self;
94
95 let blobs_bundle = match sidecars {
96 BlobSidecars::Empty => BlobsBundleV1::empty(),
97 BlobSidecars::Eip4844(sidecars) => BlobsBundleV1::from(sidecars),
98 BlobSidecars::Eip7594(_) => {
99 return Err(BuiltPayloadConversionError::UnexpectedEip7594Sidecars)
100 }
101 };
102
103 Ok(ExecutionPayloadEnvelopeV3 {
104 execution_payload: ExecutionPayloadV3::from_block_unchecked(
105 block.hash(),
106 &Arc::unwrap_or_clone(block).into_block(),
107 ),
108 block_value: fees,
109 should_override_builder: false,
118 blobs_bundle,
119 })
120 }
121
122 pub fn try_into_v4(
126 mut self,
127 ) -> Result<ExecutionPayloadEnvelopeV4, BuiltPayloadConversionError> {
128 let execution_requests = self.requests.take().unwrap_or_default();
129 Ok(ExecutionPayloadEnvelopeV4 { execution_requests, envelope_inner: self.try_into()? })
130 }
131
132 pub fn try_into_v5(self) -> Result<ExecutionPayloadEnvelopeV5, BuiltPayloadConversionError> {
134 let Self { block, fees, sidecars, requests, .. } = self;
135
136 let blobs_bundle = match sidecars {
137 BlobSidecars::Empty => BlobsBundleV2::empty(),
138 BlobSidecars::Eip7594(sidecars) => BlobsBundleV2::from(sidecars),
139 BlobSidecars::Eip4844(_) => {
140 return Err(BuiltPayloadConversionError::UnexpectedEip4844Sidecars)
141 }
142 };
143
144 Ok(ExecutionPayloadEnvelopeV5 {
145 execution_payload: ExecutionPayloadV3::from_block_unchecked(
146 block.hash(),
147 &Arc::unwrap_or_clone(block).into_block(),
148 ),
149 block_value: fees,
150 should_override_builder: false,
159 blobs_bundle,
160 execution_requests: requests.unwrap_or_default(),
161 })
162 }
163
164 pub fn try_into_v6(self) -> Result<ExecutionPayloadEnvelopeV6, BuiltPayloadConversionError> {
168 unimplemented!("ExecutionPayloadEnvelopeV6 not yet supported")
169 }
170}
171
172impl<N: NodePrimitives> BuiltPayload for EthBuiltPayload<N> {
173 type Primitives = N;
174
175 fn block(&self) -> &SealedBlock<N::Block> {
176 &self.block
177 }
178
179 fn fees(&self) -> U256 {
180 self.fees
181 }
182
183 fn requests(&self) -> Option<Requests> {
184 self.requests.clone()
185 }
186}
187
188impl From<EthBuiltPayload> for ExecutionPayloadV1 {
190 fn from(value: EthBuiltPayload) -> Self {
191 Self::from_block_unchecked(
192 value.block().hash(),
193 &Arc::unwrap_or_clone(value.block).into_block(),
194 )
195 }
196}
197
198impl From<EthBuiltPayload> for ExecutionPayloadEnvelopeV2 {
200 fn from(value: EthBuiltPayload) -> Self {
201 let EthBuiltPayload { block, fees, .. } = value;
202
203 Self {
204 block_value: fees,
205 execution_payload: ExecutionPayloadFieldV2::from_block_unchecked(
206 block.hash(),
207 &Arc::unwrap_or_clone(block).into_block(),
208 ),
209 }
210 }
211}
212
213impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV3 {
214 type Error = BuiltPayloadConversionError;
215
216 fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
217 value.try_into_v3()
218 }
219}
220
221impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV4 {
222 type Error = BuiltPayloadConversionError;
223
224 fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
225 value.try_into_v4()
226 }
227}
228
229impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV5 {
230 type Error = BuiltPayloadConversionError;
231
232 fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
233 value.try_into_v5()
234 }
235}
236
237impl TryFrom<EthBuiltPayload> for ExecutionPayloadEnvelopeV6 {
238 type Error = BuiltPayloadConversionError;
239
240 fn try_from(value: EthBuiltPayload) -> Result<Self, Self::Error> {
241 value.try_into_v6()
242 }
243}
244
245#[derive(Clone, Default, Debug)]
247pub enum BlobSidecars {
248 #[default]
250 Empty,
251 Eip4844(Vec<BlobTransactionSidecar>),
253 Eip7594(Vec<BlobTransactionSidecarEip7594>),
255}
256
257impl BlobSidecars {
258 pub const fn eip4844(sidecars: Vec<BlobTransactionSidecar>) -> Self {
260 Self::Eip4844(sidecars)
261 }
262
263 pub const fn eip7594(sidecars: Vec<BlobTransactionSidecarEip7594>) -> Self {
265 Self::Eip7594(sidecars)
266 }
267
268 pub fn push_eip4844_sidecar(&mut self, sidecar: BlobTransactionSidecar) {
270 match self {
271 Self::Empty => {
272 *self = Self::Eip4844(Vec::from([sidecar]));
273 }
274 Self::Eip4844(sidecars) => {
275 sidecars.push(sidecar);
276 }
277 Self::Eip7594(_) => {}
278 }
279 }
280
281 pub fn push_eip7594_sidecar(&mut self, sidecar: BlobTransactionSidecarEip7594) {
283 match self {
284 Self::Empty => {
285 *self = Self::Eip7594(Vec::from([sidecar]));
286 }
287 Self::Eip7594(sidecars) => {
288 sidecars.push(sidecar);
289 }
290 Self::Eip4844(_) => {}
291 }
292 }
293
294 pub fn push_sidecar_variant(&mut self, sidecar: BlobTransactionSidecarVariant) {
297 match sidecar {
298 BlobTransactionSidecarVariant::Eip4844(sidecar) => {
299 self.push_eip4844_sidecar(sidecar);
300 }
301 BlobTransactionSidecarVariant::Eip7594(sidecar) => {
302 self.push_eip7594_sidecar(sidecar);
303 }
304 }
305 }
306}
307
308impl From<Vec<BlobTransactionSidecar>> for BlobSidecars {
309 fn from(value: Vec<BlobTransactionSidecar>) -> Self {
310 Self::eip4844(value)
311 }
312}
313
314impl From<Vec<BlobTransactionSidecarEip7594>> for BlobSidecars {
315 fn from(value: Vec<BlobTransactionSidecarEip7594>) -> Self {
316 Self::eip7594(value)
317 }
318}
319
320impl From<alloc::vec::IntoIter<BlobTransactionSidecar>> for BlobSidecars {
321 fn from(value: alloc::vec::IntoIter<BlobTransactionSidecar>) -> Self {
322 value.collect::<Vec<_>>().into()
323 }
324}
325
326impl From<alloc::vec::IntoIter<BlobTransactionSidecarEip7594>> for BlobSidecars {
327 fn from(value: alloc::vec::IntoIter<BlobTransactionSidecarEip7594>) -> Self {
328 value.collect::<Vec<_>>().into()
329 }
330}
331
332#[derive(Debug, Clone, PartialEq, Eq, Default)]
334pub struct EthPayloadBuilderAttributes {
335 pub id: PayloadId,
337 pub parent: B256,
339 pub timestamp: u64,
343 pub suggested_fee_recipient: Address,
345 pub prev_randao: B256,
347 pub withdrawals: Withdrawals,
349 pub parent_beacon_block_root: Option<B256>,
351}
352
353impl EthPayloadBuilderAttributes {
356 pub const fn payload_id(&self) -> PayloadId {
358 self.id
359 }
360
361 pub fn new(parent: B256, attributes: PayloadAttributes) -> Self {
365 let id = payload_id(&parent, &attributes);
366
367 Self {
368 id,
369 parent,
370 timestamp: attributes.timestamp,
371 suggested_fee_recipient: attributes.suggested_fee_recipient,
372 prev_randao: attributes.prev_randao,
373 withdrawals: attributes.withdrawals.unwrap_or_default().into(),
374 parent_beacon_block_root: attributes.parent_beacon_block_root,
375 }
376 }
377}
378
379impl PayloadBuilderAttributes for EthPayloadBuilderAttributes {
380 type RpcPayloadAttributes = PayloadAttributes;
381 type Error = Infallible;
382
383 fn try_new(
387 parent: B256,
388 attributes: PayloadAttributes,
389 _version: u8,
390 ) -> Result<Self, Infallible> {
391 Ok(Self::new(parent, attributes))
392 }
393
394 fn payload_id(&self) -> PayloadId {
395 self.id
396 }
397
398 fn parent(&self) -> B256 {
399 self.parent
400 }
401
402 fn timestamp(&self) -> u64 {
403 self.timestamp
404 }
405
406 fn parent_beacon_block_root(&self) -> Option<B256> {
407 self.parent_beacon_block_root
408 }
409
410 fn suggested_fee_recipient(&self) -> Address {
411 self.suggested_fee_recipient
412 }
413
414 fn prev_randao(&self) -> B256 {
415 self.prev_randao
416 }
417
418 fn withdrawals(&self) -> &Withdrawals {
419 &self.withdrawals
420 }
421}
422
423pub fn payload_id(parent: &B256, attributes: &PayloadAttributes) -> PayloadId {
427 use sha2::Digest;
428 let mut hasher = sha2::Sha256::new();
429 hasher.update(parent.as_slice());
430 hasher.update(&attributes.timestamp.to_be_bytes()[..]);
431 hasher.update(attributes.prev_randao.as_slice());
432 hasher.update(attributes.suggested_fee_recipient.as_slice());
433 if let Some(withdrawals) = &attributes.withdrawals {
434 let mut buf = Vec::new();
435 withdrawals.encode(&mut buf);
436 hasher.update(buf);
437 }
438
439 if let Some(parent_beacon_block) = attributes.parent_beacon_block_root {
440 hasher.update(parent_beacon_block);
441 }
442
443 let out = hasher.finalize();
444
445 #[allow(deprecated)] PayloadId::new(out.as_slice()[..8].try_into().expect("sufficient length"))
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use alloy_eips::eip4895::Withdrawal;
453 use alloy_primitives::B64;
454 use core::str::FromStr;
455
456 #[test]
457 fn attributes_serde() {
458 let attributes = r#"{"timestamp":"0x1235","prevRandao":"0xf343b00e02dc34ec0124241f74f32191be28fb370bb48060f5fa4df99bda774c","suggestedFeeRecipient":"0x0000000000000000000000000000000000000000","withdrawals":null,"parentBeaconBlockRoot":null}"#;
459 let _attributes: PayloadAttributes = serde_json::from_str(attributes).unwrap();
460 }
461
462 #[test]
463 fn test_payload_id_basic() {
464 let parent =
466 B256::from_str("0x3b8fb240d288781d4aac94d3fd16809ee413bc99294a085798a589dae51ddd4a")
467 .unwrap();
468 let attributes = PayloadAttributes {
469 timestamp: 0x5,
470 prev_randao: B256::from_str(
471 "0x0000000000000000000000000000000000000000000000000000000000000000",
472 )
473 .unwrap(),
474 suggested_fee_recipient: Address::from_str(
475 "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
476 )
477 .unwrap(),
478 withdrawals: None,
479 parent_beacon_block_root: None,
480 };
481
482 assert_eq!(
484 payload_id(&parent, &attributes),
485 PayloadId(B64::from_str("0xa247243752eb10b4").unwrap())
486 );
487 }
488
489 #[test]
490 fn test_payload_id_with_withdrawals() {
491 let parent =
493 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
494 .unwrap();
495 let attributes = PayloadAttributes {
496 timestamp: 1622553200,
497 prev_randao: B256::from_slice(&[1; 32]),
498 suggested_fee_recipient: Address::from_str(
499 "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
500 )
501 .unwrap(),
502 withdrawals: Some(vec![
503 Withdrawal {
504 index: 1,
505 validator_index: 123,
506 address: Address::from([0xAA; 20]),
507 amount: 10,
508 },
509 Withdrawal {
510 index: 2,
511 validator_index: 456,
512 address: Address::from([0xBB; 20]),
513 amount: 20,
514 },
515 ]),
516 parent_beacon_block_root: None,
517 };
518
519 assert_eq!(
521 payload_id(&parent, &attributes),
522 PayloadId(B64::from_str("0xedddc2f84ba59865").unwrap())
523 );
524 }
525
526 #[test]
527 fn test_payload_id_with_parent_beacon_block_root() {
528 let parent =
530 B256::from_str("0x9876543210abcdef9876543210abcdef9876543210abcdef9876543210abcdef")
531 .unwrap();
532 let attributes = PayloadAttributes {
533 timestamp: 1622553200,
534 prev_randao: B256::from_str(
535 "0x123456789abcdef123456789abcdef123456789abcdef123456789abcdef1234",
536 )
537 .unwrap(),
538 suggested_fee_recipient: Address::from_str(
539 "0xc94f5374fce5edbc8e2a8697c15331677e6ebf0b",
540 )
541 .unwrap(),
542 withdrawals: None,
543 parent_beacon_block_root: Some(
544 B256::from_str(
545 "0x2222222222222222222222222222222222222222222222222222222222222222",
546 )
547 .unwrap(),
548 ),
549 };
550
551 assert_eq!(
553 payload_id(&parent, &attributes),
554 PayloadId(B64::from_str("0x0fc49cd532094cce").unwrap())
555 );
556 }
557}