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