1use alloy_eips::{eip4895::Withdrawal, eip7685::Requests};
10use alloy_primitives::{Address, B128, B256, U256};
11use alloy_rpc_types_engine::{
12 BlobsBundleV1, BlobsBundleV2, ExecutionPayloadEnvelopeV2 as LegacyBuiltPayloadShanghai,
13 ExecutionPayloadEnvelopeV4 as LegacyBuiltPayloadPrague,
14 ExecutionPayloadEnvelopeV5 as LegacyBuiltPayloadOsaka,
15 ExecutionPayloadEnvelopeV6 as LegacyBuiltPayloadAmsterdam, ExecutionPayloadFieldV2,
16 ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, ExecutionPayloadV4,
17 ForkchoiceState, ForkchoiceUpdated as LegacyForkchoice,
18 PayloadAttributes as LegacyPayloadAttributes, PayloadId, PayloadStatus as LegacyPayloadStatus,
19 PayloadStatusEnum,
20};
21
22type ErrorBytes = Vec<u8>;
23
24#[derive(Clone, Debug, Default, PartialEq, Eq)]
28pub struct Optional<T>(Vec<T>);
29
30impl<T> Optional<T> {
31 pub const fn none() -> Self {
33 Self(Vec::new())
34 }
35
36 pub fn some(value: T) -> Self {
38 Self(vec![value])
39 }
40
41 pub fn as_ref(&self) -> Option<&T> {
43 self.0.first()
44 }
45
46 pub const fn is_none(&self) -> bool {
48 self.0.is_empty()
49 }
50
51 pub const fn is_some(&self) -> bool {
53 !self.is_none()
54 }
55
56 pub fn into_option(mut self) -> Option<T> {
58 self.0.pop()
59 }
60}
61
62impl<T> From<Option<T>> for Optional<T> {
63 fn from(value: Option<T>) -> Self {
64 value.map_or_else(Self::none, Self::some)
65 }
66}
67
68impl<T> From<Optional<T>> for Option<T> {
69 fn from(value: Optional<T>) -> Self {
70 value.into_option()
71 }
72}
73
74impl<T: ssz::Encode> ssz::Encode for Optional<T> {
75 fn is_ssz_fixed_len() -> bool {
76 false
77 }
78
79 fn ssz_bytes_len(&self) -> usize {
80 self.0.ssz_bytes_len()
81 }
82
83 fn ssz_append(&self, buf: &mut Vec<u8>) {
84 self.0.ssz_append(buf);
85 }
86}
87
88impl<T: ssz::Decode> ssz::Decode for Optional<T> {
89 fn is_ssz_fixed_len() -> bool {
90 false
91 }
92
93 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
94 let values = Vec::<T>::from_ssz_bytes(bytes)?;
95 if values.len() > 1 {
96 return Err(ssz::DecodeError::BytesInvalid("optional has more than one value".into()))
97 }
98 Ok(Self(values))
99 }
100}
101
102#[derive(Clone, Debug, PartialEq, Eq)]
104pub struct PayloadStatus {
105 pub status: PayloadStatusEnum,
107 pub latest_valid_hash: Optional<B256>,
109 pub validation_error: Optional<ErrorBytes>,
111}
112
113const MAX_ERROR_BYTES: usize = 1024;
114
115const fn status_code(status: &PayloadStatusEnum) -> u8 {
116 match status {
117 PayloadStatusEnum::Valid => 0,
118 PayloadStatusEnum::Invalid { .. } => 1,
119 PayloadStatusEnum::Syncing => 2,
120 PayloadStatusEnum::Accepted => 3,
121 }
122}
123
124fn status_from_code(code: u8) -> Result<PayloadStatusEnum, ssz::DecodeError> {
125 match code {
126 0 => Ok(PayloadStatusEnum::Valid),
127 1 => Ok(PayloadStatusEnum::Invalid { validation_error: String::new() }),
128 2 => Ok(PayloadStatusEnum::Syncing),
129 3 => Ok(PayloadStatusEnum::Accepted),
130 _ => Err(ssz::DecodeError::BytesInvalid("unknown payload status code".into())),
131 }
132}
133
134fn legacy_validation_error(
135 status: &PayloadStatusEnum,
136) -> Result<Optional<ErrorBytes>, ConversionError> {
137 match status {
138 PayloadStatusEnum::Invalid { validation_error } => {
139 let bytes = validation_error.as_bytes().to_vec();
140 if bytes.len() > MAX_ERROR_BYTES {
141 return Err(ConversionError::ErrorBytesTooLong)
142 }
143 Ok(Optional::some(bytes))
144 }
145 _ => Ok(Optional::none()),
146 }
147}
148
149impl ssz::Encode for PayloadStatus {
150 fn is_ssz_fixed_len() -> bool {
151 false
152 }
153
154 fn ssz_bytes_len(&self) -> usize {
155 1 + ssz::BYTES_PER_LENGTH_OFFSET * 2 +
156 self.latest_valid_hash.ssz_bytes_len() +
157 self.validation_error.ssz_bytes_len()
158 }
159
160 fn ssz_append(&self, buf: &mut Vec<u8>) {
161 let mut encoder = ssz::SszEncoder::container(buf, 1 + ssz::BYTES_PER_LENGTH_OFFSET * 2);
162 encoder.append(&status_code(&self.status));
163 encoder.append(&self.latest_valid_hash);
164 encoder.append(&self.validation_error);
165 encoder.finalize();
166 }
167}
168
169impl ssz::Decode for PayloadStatus {
170 fn is_ssz_fixed_len() -> bool {
171 false
172 }
173
174 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
175 let mut builder = ssz::SszDecoderBuilder::new(bytes);
176 builder.register_type::<u8>()?;
177 builder.register_type::<Optional<B256>>()?;
178 builder.register_type::<Optional<ErrorBytes>>()?;
179 let mut decoder = builder.build()?;
180 let mut status = status_from_code(decoder.decode_next()?)?;
181 let latest_valid_hash = decoder.decode_next()?;
182 let validation_error: Optional<ErrorBytes> = decoder.decode_next()?;
183 if let PayloadStatusEnum::Invalid { validation_error: error } = &mut status {
184 *error = match validation_error.as_ref() {
185 Some(error) => String::from_utf8(error.clone())
186 .map_err(|err| ssz::DecodeError::BytesInvalid(err.to_string()))?,
187 None => String::new(),
188 };
189 if validation_error.as_ref().is_some_and(|error| error.len() > MAX_ERROR_BYTES) {
190 return Err(ssz::DecodeError::BytesInvalid(
191 "payload validation error is too long".into(),
192 ))
193 }
194 } else if validation_error.is_some() {
195 return Err(ssz::DecodeError::BytesInvalid(
196 "validation error is only valid for INVALID status".into(),
197 ));
198 }
199 Ok(Self { status, latest_valid_hash, validation_error })
200 }
201}
202
203#[derive(Clone, Debug, PartialEq, Eq)]
205pub enum ConversionError {
206 ErrorBytesTooLong,
208 AcceptedForkchoice,
210}
211
212impl core::fmt::Display for ConversionError {
213 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
214 match self {
215 Self::ErrorBytesTooLong => f.write_str("payload validation error is too long"),
216 Self::AcceptedForkchoice => {
217 f.write_str("ACCEPTED is not valid in a forkchoice response")
218 }
219 }
220 }
221}
222
223impl core::error::Error for ConversionError {}
224
225impl TryFrom<LegacyPayloadStatus> for PayloadStatus {
226 type Error = ConversionError;
227
228 fn try_from(value: LegacyPayloadStatus) -> Result<Self, Self::Error> {
229 let validation_error = legacy_validation_error(&value.status)?;
230 Ok(Self {
231 status: value.status,
232 latest_valid_hash: value.latest_valid_hash.into(),
233 validation_error,
234 })
235 }
236}
237
238impl From<PayloadStatus> for LegacyPayloadStatus {
239 fn from(value: PayloadStatus) -> Self {
240 let status = match value.status {
241 PayloadStatusEnum::Invalid { .. } => PayloadStatusEnum::Invalid {
242 validation_error: value
243 .validation_error
244 .as_ref()
245 .and_then(|error| String::from_utf8(error.clone()).ok())
246 .unwrap_or_default(),
247 },
248 status => status,
249 };
250 Self { status, latest_valid_hash: value.latest_valid_hash.into() }
251 }
252}
253
254#[derive(Clone, Debug, PartialEq, Eq)]
256pub struct ForkchoiceUpdateResponse {
257 pub payload_status: PayloadStatus,
259 pub payload_id: Optional<PayloadId>,
261}
262
263impl ssz::Encode for ForkchoiceUpdateResponse {
264 fn is_ssz_fixed_len() -> bool {
265 false
266 }
267
268 fn ssz_bytes_len(&self) -> usize {
269 ssz::BYTES_PER_LENGTH_OFFSET * 2 +
270 self.payload_status.ssz_bytes_len() +
271 self.payload_id.ssz_bytes_len()
272 }
273
274 fn ssz_append(&self, buf: &mut Vec<u8>) {
275 let mut encoder = ssz::SszEncoder::container(buf, ssz::BYTES_PER_LENGTH_OFFSET * 2);
276 encoder.append(&self.payload_status);
277 encoder.append(&self.payload_id);
278 encoder.finalize();
279 }
280}
281
282impl ssz::Decode for ForkchoiceUpdateResponse {
283 fn is_ssz_fixed_len() -> bool {
284 false
285 }
286
287 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
288 let mut builder = ssz::SszDecoderBuilder::new(bytes);
289 builder.register_type::<PayloadStatus>()?;
290 builder.register_type::<Optional<PayloadId>>()?;
291 let mut decoder = builder.build()?;
292 let response =
293 Self { payload_status: decoder.decode_next()?, payload_id: decoder.decode_next()? };
294 if matches!(response.payload_status.status, PayloadStatusEnum::Accepted) {
295 return Err(ssz::DecodeError::BytesInvalid(
296 "ACCEPTED is not valid in a forkchoice response".into(),
297 ));
298 }
299 Ok(response)
300 }
301}
302
303impl TryFrom<LegacyForkchoice> for ForkchoiceUpdateResponse {
304 type Error = ConversionError;
305
306 fn try_from(value: LegacyForkchoice) -> Result<Self, Self::Error> {
307 let payload_status = PayloadStatus::try_from(value.payload_status)?;
308 if matches!(payload_status.status, PayloadStatusEnum::Accepted) {
309 return Err(ConversionError::AcceptedForkchoice)
310 }
311 Ok(Self { payload_status, payload_id: value.payload_id.into() })
312 }
313}
314
315impl From<ForkchoiceUpdateResponse> for LegacyForkchoice {
316 fn from(value: ForkchoiceUpdateResponse) -> Self {
317 Self { payload_status: value.payload_status.into(), payload_id: value.payload_id.into() }
318 }
319}
320
321pub type ExecutionPayloadParis = ExecutionPayloadV1;
323
324pub type ExecutionPayloadShanghai = ExecutionPayloadV2;
326
327pub type ExecutionPayloadCancun = ExecutionPayloadV3;
329
330pub type ExecutionPayloadPrague = ExecutionPayloadV3;
332
333pub type ExecutionPayloadOsaka = ExecutionPayloadV3;
335
336pub type ExecutionPayloadAmsterdam = ExecutionPayloadV4;
338
339#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
341pub struct PayloadAttributesParis {
342 pub timestamp: u64,
344 pub prev_randao: B256,
346 pub suggested_fee_recipient: Address,
348}
349
350#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
352pub struct PayloadAttributesShanghai {
353 pub timestamp: u64,
355 pub prev_randao: B256,
357 pub suggested_fee_recipient: Address,
359 pub withdrawals: Vec<Withdrawal>,
361}
362
363#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
365pub struct PayloadAttributesCancun {
366 pub timestamp: u64,
368 pub prev_randao: B256,
370 pub suggested_fee_recipient: Address,
372 pub withdrawals: Vec<Withdrawal>,
374 pub parent_beacon_block_root: B256,
376}
377
378pub type PayloadAttributesPrague = PayloadAttributesCancun;
380
381pub type PayloadAttributesOsaka = PayloadAttributesCancun;
383
384#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
386pub struct PayloadAttributesAmsterdam {
387 pub timestamp: u64,
389 pub prev_randao: B256,
391 pub suggested_fee_recipient: Address,
393 pub withdrawals: Vec<Withdrawal>,
395 pub parent_beacon_block_root: B256,
397 pub slot_number: u64,
399 pub target_gas_limit: u64,
401}
402
403#[derive(Clone, Copy, Debug, PartialEq, Eq)]
405pub enum PayloadAttributesConversionError {
406 MissingField(&'static str),
408 UnexpectedField(&'static str),
410}
411
412impl core::fmt::Display for PayloadAttributesConversionError {
413 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
414 match self {
415 Self::MissingField(field) => {
416 write!(f, "missing required payload attributes field: {field}")
417 }
418 Self::UnexpectedField(field) => {
419 write!(f, "unexpected later-fork payload attributes field: {field}")
420 }
421 }
422 }
423}
424
425impl core::error::Error for PayloadAttributesConversionError {}
426
427const fn ensure_absent<T>(
428 value: &Option<T>,
429 field: &'static str,
430) -> Result<(), PayloadAttributesConversionError> {
431 if value.is_some() {
432 Err(PayloadAttributesConversionError::UnexpectedField(field))
433 } else {
434 Ok(())
435 }
436}
437
438fn require<T>(
439 value: Option<T>,
440 field: &'static str,
441) -> Result<T, PayloadAttributesConversionError> {
442 value.ok_or(PayloadAttributesConversionError::MissingField(field))
443}
444
445impl From<PayloadAttributesParis> for LegacyPayloadAttributes {
446 fn from(value: PayloadAttributesParis) -> Self {
447 Self {
448 timestamp: value.timestamp,
449 prev_randao: value.prev_randao,
450 suggested_fee_recipient: value.suggested_fee_recipient,
451 withdrawals: None,
452 parent_beacon_block_root: None,
453 slot_number: None,
454 target_gas_limit: None,
455 }
456 }
457}
458
459impl TryFrom<LegacyPayloadAttributes> for PayloadAttributesParis {
460 type Error = PayloadAttributesConversionError;
461
462 fn try_from(value: LegacyPayloadAttributes) -> Result<Self, Self::Error> {
463 ensure_absent(&value.withdrawals, "withdrawals")?;
464 ensure_absent(&value.parent_beacon_block_root, "parent_beacon_block_root")?;
465 ensure_absent(&value.slot_number, "slot_number")?;
466 ensure_absent(&value.target_gas_limit, "target_gas_limit")?;
467 Ok(Self {
468 timestamp: value.timestamp,
469 prev_randao: value.prev_randao,
470 suggested_fee_recipient: value.suggested_fee_recipient,
471 })
472 }
473}
474
475impl From<PayloadAttributesShanghai> for LegacyPayloadAttributes {
476 fn from(value: PayloadAttributesShanghai) -> Self {
477 Self {
478 timestamp: value.timestamp,
479 prev_randao: value.prev_randao,
480 suggested_fee_recipient: value.suggested_fee_recipient,
481 withdrawals: Some(value.withdrawals),
482 parent_beacon_block_root: None,
483 slot_number: None,
484 target_gas_limit: None,
485 }
486 }
487}
488
489impl TryFrom<LegacyPayloadAttributes> for PayloadAttributesShanghai {
490 type Error = PayloadAttributesConversionError;
491
492 fn try_from(value: LegacyPayloadAttributes) -> Result<Self, Self::Error> {
493 ensure_absent(&value.parent_beacon_block_root, "parent_beacon_block_root")?;
494 ensure_absent(&value.slot_number, "slot_number")?;
495 ensure_absent(&value.target_gas_limit, "target_gas_limit")?;
496 Ok(Self {
497 timestamp: value.timestamp,
498 prev_randao: value.prev_randao,
499 suggested_fee_recipient: value.suggested_fee_recipient,
500 withdrawals: require(value.withdrawals, "withdrawals")?,
501 })
502 }
503}
504
505impl From<PayloadAttributesCancun> for LegacyPayloadAttributes {
506 fn from(value: PayloadAttributesCancun) -> Self {
507 Self {
508 timestamp: value.timestamp,
509 prev_randao: value.prev_randao,
510 suggested_fee_recipient: value.suggested_fee_recipient,
511 withdrawals: Some(value.withdrawals),
512 parent_beacon_block_root: Some(value.parent_beacon_block_root),
513 slot_number: None,
514 target_gas_limit: None,
515 }
516 }
517}
518
519impl TryFrom<LegacyPayloadAttributes> for PayloadAttributesCancun {
520 type Error = PayloadAttributesConversionError;
521
522 fn try_from(value: LegacyPayloadAttributes) -> Result<Self, Self::Error> {
523 ensure_absent(&value.slot_number, "slot_number")?;
524 ensure_absent(&value.target_gas_limit, "target_gas_limit")?;
525 Ok(Self {
526 timestamp: value.timestamp,
527 prev_randao: value.prev_randao,
528 suggested_fee_recipient: value.suggested_fee_recipient,
529 withdrawals: require(value.withdrawals, "withdrawals")?,
530 parent_beacon_block_root: require(
531 value.parent_beacon_block_root,
532 "parent_beacon_block_root",
533 )?,
534 })
535 }
536}
537
538impl From<PayloadAttributesAmsterdam> for LegacyPayloadAttributes {
539 fn from(value: PayloadAttributesAmsterdam) -> Self {
540 Self {
541 timestamp: value.timestamp,
542 prev_randao: value.prev_randao,
543 suggested_fee_recipient: value.suggested_fee_recipient,
544 withdrawals: Some(value.withdrawals),
545 parent_beacon_block_root: Some(value.parent_beacon_block_root),
546 slot_number: Some(value.slot_number),
547 target_gas_limit: Some(value.target_gas_limit),
548 }
549 }
550}
551
552impl TryFrom<LegacyPayloadAttributes> for PayloadAttributesAmsterdam {
553 type Error = PayloadAttributesConversionError;
554
555 fn try_from(value: LegacyPayloadAttributes) -> Result<Self, Self::Error> {
556 Ok(Self {
557 timestamp: value.timestamp,
558 prev_randao: value.prev_randao,
559 suggested_fee_recipient: value.suggested_fee_recipient,
560 withdrawals: require(value.withdrawals, "withdrawals")?,
561 parent_beacon_block_root: require(
562 value.parent_beacon_block_root,
563 "parent_beacon_block_root",
564 )?,
565 slot_number: require(value.slot_number, "slot_number")?,
566 target_gas_limit: require(value.target_gas_limit, "target_gas_limit")?,
567 })
568 }
569}
570
571#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
575pub struct BuiltPayloadParis {
576 pub payload: ExecutionPayloadParis,
578 pub block_value: U256,
580}
581
582#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
587pub struct BuiltPayloadShanghai {
588 pub payload: ExecutionPayloadShanghai,
590 pub block_value: U256,
592}
593
594pub type BuiltPayloadCancun = alloy_rpc_types_engine::ExecutionPayloadEnvelopeV3;
598
599#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
604pub struct BuiltPayloadPrague {
605 pub payload: ExecutionPayloadPrague,
607 pub block_value: U256,
609 pub blobs_bundle: BlobsBundleV1,
611 pub execution_requests: Requests,
613 pub should_override_builder: bool,
616}
617
618#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
620pub struct BuiltPayloadOsaka {
621 pub payload: ExecutionPayloadOsaka,
623 pub block_value: U256,
625 pub blobs_bundle: BlobsBundleV2,
627 pub execution_requests: Requests,
629 pub should_override_builder: bool,
632}
633
634#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
636pub struct BuiltPayloadAmsterdam {
637 pub payload: ExecutionPayloadAmsterdam,
639 pub block_value: U256,
641 pub blobs_bundle: BlobsBundleV2,
643 pub execution_requests: Requests,
645 pub should_override_builder: bool,
648}
649
650#[derive(Clone, Copy, Debug, PartialEq, Eq)]
652pub enum BuiltPayloadConversionError {
653 UnexpectedPayloadFork(&'static str),
655}
656
657impl core::fmt::Display for BuiltPayloadConversionError {
658 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
659 match self {
660 Self::UnexpectedPayloadFork(fork) => {
661 write!(f, "unexpected execution payload fork: {fork}")
662 }
663 }
664 }
665}
666
667impl core::error::Error for BuiltPayloadConversionError {}
668
669impl From<BuiltPayloadShanghai> for LegacyBuiltPayloadShanghai {
670 fn from(value: BuiltPayloadShanghai) -> Self {
671 Self {
672 execution_payload: ExecutionPayloadFieldV2::V2(value.payload),
673 block_value: value.block_value,
674 }
675 }
676}
677
678impl TryFrom<LegacyBuiltPayloadShanghai> for BuiltPayloadShanghai {
679 type Error = BuiltPayloadConversionError;
680
681 fn try_from(value: LegacyBuiltPayloadShanghai) -> Result<Self, Self::Error> {
682 match value.execution_payload {
683 ExecutionPayloadFieldV2::V2(payload) => {
684 Ok(Self { payload, block_value: value.block_value })
685 }
686 ExecutionPayloadFieldV2::V1(_) => {
687 Err(BuiltPayloadConversionError::UnexpectedPayloadFork("Paris"))
688 }
689 }
690 }
691}
692
693impl From<LegacyBuiltPayloadPrague> for BuiltPayloadPrague {
694 fn from(value: LegacyBuiltPayloadPrague) -> Self {
695 Self {
696 payload: value.envelope_inner.execution_payload,
697 block_value: value.envelope_inner.block_value,
698 blobs_bundle: value.envelope_inner.blobs_bundle,
699 execution_requests: value.execution_requests,
700 should_override_builder: value.envelope_inner.should_override_builder,
701 }
702 }
703}
704
705impl From<BuiltPayloadPrague> for LegacyBuiltPayloadPrague {
706 fn from(value: BuiltPayloadPrague) -> Self {
707 Self {
708 envelope_inner: alloy_rpc_types_engine::ExecutionPayloadEnvelopeV3 {
709 execution_payload: value.payload,
710 block_value: value.block_value,
711 blobs_bundle: value.blobs_bundle,
712 should_override_builder: value.should_override_builder,
713 },
714 execution_requests: value.execution_requests,
715 }
716 }
717}
718
719impl From<LegacyBuiltPayloadOsaka> for BuiltPayloadOsaka {
720 fn from(value: LegacyBuiltPayloadOsaka) -> Self {
721 Self {
722 payload: value.execution_payload,
723 block_value: value.block_value,
724 blobs_bundle: value.blobs_bundle,
725 execution_requests: value.execution_requests,
726 should_override_builder: value.should_override_builder,
727 }
728 }
729}
730
731impl From<BuiltPayloadOsaka> for LegacyBuiltPayloadOsaka {
732 fn from(value: BuiltPayloadOsaka) -> Self {
733 Self {
734 execution_payload: value.payload,
735 block_value: value.block_value,
736 blobs_bundle: value.blobs_bundle,
737 should_override_builder: value.should_override_builder,
738 execution_requests: value.execution_requests,
739 }
740 }
741}
742
743impl From<LegacyBuiltPayloadAmsterdam> for BuiltPayloadAmsterdam {
744 fn from(value: LegacyBuiltPayloadAmsterdam) -> Self {
745 Self {
746 payload: value.execution_payload,
747 block_value: value.block_value,
748 blobs_bundle: value.blobs_bundle,
749 execution_requests: value.execution_requests,
750 should_override_builder: value.should_override_builder,
751 }
752 }
753}
754
755impl From<BuiltPayloadAmsterdam> for LegacyBuiltPayloadAmsterdam {
756 fn from(value: BuiltPayloadAmsterdam) -> Self {
757 Self {
758 execution_payload: value.payload,
759 block_value: value.block_value,
760 blobs_bundle: value.blobs_bundle,
761 should_override_builder: value.should_override_builder,
762 execution_requests: value.execution_requests,
763 }
764 }
765}
766
767#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
776pub struct ExecutionPayloadEnvelopeParis {
777 pub payload: ExecutionPayloadParis,
779}
780
781#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
783pub struct ExecutionPayloadEnvelopeShanghai {
784 pub payload: ExecutionPayloadShanghai,
786}
787
788#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
790pub struct ExecutionPayloadEnvelopeCancun {
791 pub payload: ExecutionPayloadCancun,
793 pub parent_beacon_block_root: B256,
795}
796
797#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
799pub struct ExecutionPayloadEnvelopePrague {
800 pub payload: ExecutionPayloadPrague,
802 pub parent_beacon_block_root: B256,
804 pub execution_requests: Requests,
806}
807
808#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
810pub struct ExecutionPayloadEnvelopeOsaka {
811 pub payload: ExecutionPayloadOsaka,
813 pub parent_beacon_block_root: B256,
815 pub execution_requests: Requests,
817}
818
819#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
821pub struct ExecutionPayloadEnvelopeAmsterdam {
822 pub payload: ExecutionPayloadAmsterdam,
824 pub parent_beacon_block_root: B256,
826 pub execution_requests: Requests,
828}
829
830impl From<ExecutionPayloadParis> for ExecutionPayloadEnvelopeParis {
831 fn from(payload: ExecutionPayloadParis) -> Self {
832 Self { payload }
833 }
834}
835
836impl From<ExecutionPayloadShanghai> for ExecutionPayloadEnvelopeShanghai {
837 fn from(payload: ExecutionPayloadShanghai) -> Self {
838 Self { payload }
839 }
840}
841
842impl From<(ExecutionPayloadCancun, B256)> for ExecutionPayloadEnvelopeCancun {
843 fn from((payload, parent_beacon_block_root): (ExecutionPayloadCancun, B256)) -> Self {
844 Self { payload, parent_beacon_block_root }
845 }
846}
847
848impl From<(ExecutionPayloadPrague, B256, Requests)> for ExecutionPayloadEnvelopePrague {
849 fn from(
850 (payload, parent_beacon_block_root, execution_requests): (
851 ExecutionPayloadPrague,
852 B256,
853 Requests,
854 ),
855 ) -> Self {
856 Self { payload, parent_beacon_block_root, execution_requests }
857 }
858}
859
860impl From<(ExecutionPayloadOsaka, B256, Requests)> for ExecutionPayloadEnvelopeOsaka {
861 fn from(
862 (payload, parent_beacon_block_root, execution_requests): (
863 ExecutionPayloadOsaka,
864 B256,
865 Requests,
866 ),
867 ) -> Self {
868 Self { payload, parent_beacon_block_root, execution_requests }
869 }
870}
871
872impl From<(ExecutionPayloadAmsterdam, B256, Requests)> for ExecutionPayloadEnvelopeAmsterdam {
873 fn from(
874 (payload, parent_beacon_block_root, execution_requests): (
875 ExecutionPayloadAmsterdam,
876 B256,
877 Requests,
878 ),
879 ) -> Self {
880 Self { payload, parent_beacon_block_root, execution_requests }
881 }
882}
883
884#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
886pub struct ForkchoiceUpdateParis {
887 pub forkchoice_state: ForkchoiceState,
889 pub payload_attributes: Optional<PayloadAttributesParis>,
891}
892
893#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
895pub struct ForkchoiceUpdateShanghai {
896 pub forkchoice_state: ForkchoiceState,
898 pub payload_attributes: Optional<PayloadAttributesShanghai>,
900}
901
902#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
904pub struct ForkchoiceUpdateCancun {
905 pub forkchoice_state: ForkchoiceState,
907 pub payload_attributes: Optional<PayloadAttributesCancun>,
909}
910
911#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
913pub struct ForkchoiceUpdatePrague {
914 pub forkchoice_state: ForkchoiceState,
916 pub payload_attributes: Optional<PayloadAttributesPrague>,
918}
919
920#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
922pub struct ForkchoiceUpdateOsaka {
923 pub forkchoice_state: ForkchoiceState,
925 pub payload_attributes: Optional<PayloadAttributesOsaka>,
927}
928
929#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
931pub struct ForkchoiceUpdateAmsterdam {
932 pub forkchoice_state: ForkchoiceState,
934 pub payload_attributes: Optional<PayloadAttributesAmsterdam>,
936 pub custody_columns: Optional<B128>,
938}
939
940#[cfg(test)]
941mod tests {
942 use super::*;
943 use alloy_eips::eip4895::Withdrawal;
944 use alloy_primitives::{Address, Bloom, Bytes};
945 use ssz::{Decode, Encode};
946
947 fn payload_v1() -> ExecutionPayloadV1 {
948 ExecutionPayloadV1 {
949 parent_hash: B256::repeat_byte(1),
950 fee_recipient: Address::repeat_byte(2),
951 state_root: B256::repeat_byte(3),
952 receipts_root: B256::repeat_byte(4),
953 logs_bloom: Bloom::repeat_byte(5),
954 prev_randao: B256::repeat_byte(6),
955 block_number: 7,
956 gas_limit: 8,
957 gas_used: 9,
958 timestamp: 10,
959 extra_data: Bytes::from_static(&[11, 12]),
960 base_fee_per_gas: U256::from(13),
961 block_hash: B256::repeat_byte(14),
962 transactions: vec![Bytes::from_static(&[15, 16])],
963 }
964 }
965
966 fn payload_v2() -> ExecutionPayloadV2 {
967 ExecutionPayloadV2 { payload_inner: payload_v1(), withdrawals: vec![Withdrawal::default()] }
968 }
969
970 fn payload_v3() -> ExecutionPayloadV3 {
971 ExecutionPayloadV3 { payload_inner: payload_v2(), blob_gas_used: 17, excess_blob_gas: 18 }
972 }
973
974 fn payload_v4() -> ExecutionPayloadV4 {
975 ExecutionPayloadV4 {
976 payload_inner: payload_v3(),
977 block_access_list: Bytes::from_static(&[19, 20]),
978 slot_number: 21,
979 }
980 }
981
982 fn attributes_cancun() -> PayloadAttributesCancun {
983 PayloadAttributesCancun {
984 timestamp: 1,
985 prev_randao: B256::repeat_byte(2),
986 suggested_fee_recipient: Address::repeat_byte(3),
987 withdrawals: vec![Withdrawal::default()],
988 parent_beacon_block_root: B256::repeat_byte(4),
989 }
990 }
991
992 fn state() -> ForkchoiceState {
993 ForkchoiceState {
994 head_block_hash: B256::repeat_byte(1),
995 safe_block_hash: B256::repeat_byte(2),
996 finalized_block_hash: B256::repeat_byte(3),
997 }
998 }
999
1000 fn assert_roundtrip<T>(value: &T)
1001 where
1002 T: Encode + Decode + PartialEq + core::fmt::Debug,
1003 {
1004 assert_eq!(T::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), *value);
1005 }
1006
1007 #[test]
1008 fn execution_payload_envelopes_roundtrip() {
1009 assert_roundtrip(&ExecutionPayloadEnvelopeParis { payload: payload_v1() });
1010 assert_roundtrip(&ExecutionPayloadEnvelopeShanghai { payload: payload_v2() });
1011 assert_roundtrip(&ExecutionPayloadEnvelopeCancun {
1012 payload: payload_v3(),
1013 parent_beacon_block_root: B256::repeat_byte(1),
1014 });
1015 assert_roundtrip(&ExecutionPayloadEnvelopePrague {
1016 payload: payload_v3(),
1017 parent_beacon_block_root: B256::repeat_byte(1),
1018 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1019 });
1020 assert_roundtrip(&ExecutionPayloadEnvelopeOsaka {
1021 payload: payload_v3(),
1022 parent_beacon_block_root: B256::repeat_byte(1),
1023 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1024 });
1025 assert_roundtrip(&ExecutionPayloadEnvelopeAmsterdam {
1026 payload: payload_v4(),
1027 parent_beacon_block_root: B256::repeat_byte(1),
1028 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1029 });
1030 }
1031
1032 #[test]
1033 fn paris_submission_is_a_single_field_container() {
1034 let payload = payload_v1();
1035 let payload_bytes = payload.as_ssz_bytes();
1036 let envelope = ExecutionPayloadEnvelopeParis { payload };
1037 let encoded = envelope.as_ssz_bytes();
1038 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1039 assert_eq!(&encoded[4..], payload_bytes);
1040 }
1041
1042 #[test]
1043 fn built_payloads_roundtrip() {
1044 assert_roundtrip(&BuiltPayloadParis { payload: payload_v1(), block_value: U256::from(1) });
1045 assert_roundtrip(&BuiltPayloadShanghai {
1046 payload: payload_v2(),
1047 block_value: U256::from(1),
1048 });
1049 assert_roundtrip(&BuiltPayloadCancun {
1050 execution_payload: payload_v3(),
1051 block_value: U256::from(1),
1052 blobs_bundle: BlobsBundleV1::empty(),
1053 should_override_builder: true,
1054 });
1055 assert_roundtrip(&BuiltPayloadPrague {
1056 payload: payload_v3(),
1057 block_value: U256::from(1),
1058 blobs_bundle: BlobsBundleV1::empty(),
1059 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1060 should_override_builder: true,
1061 });
1062 assert_roundtrip(&BuiltPayloadOsaka {
1063 payload: payload_v3(),
1064 block_value: U256::from(1),
1065 blobs_bundle: BlobsBundleV2::empty(),
1066 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1067 should_override_builder: true,
1068 });
1069 assert_roundtrip(&BuiltPayloadAmsterdam {
1070 payload: payload_v4(),
1071 block_value: U256::from(1),
1072 blobs_bundle: BlobsBundleV2::empty(),
1073 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1074 should_override_builder: true,
1075 });
1076 }
1077
1078 #[test]
1079 fn shanghai_built_payload_has_no_builder_override() {
1080 let payload = payload_v2();
1081 let payload_len = payload.ssz_bytes_len();
1082 let value = BuiltPayloadShanghai { payload, block_value: U256::from(1) };
1083 let encoded = value.as_ssz_bytes();
1084
1085 assert_eq!(&encoded[..4], &36u32.to_le_bytes());
1086 assert_eq!(encoded.len(), 36 + payload_len);
1087 }
1088
1089 #[test]
1090 fn legacy_built_payload_conversions_preserve_fields() {
1091 let shanghai = BuiltPayloadShanghai { payload: payload_v2(), block_value: U256::from(1) };
1092 let legacy = LegacyBuiltPayloadShanghai::from(shanghai.clone());
1093 assert_eq!(BuiltPayloadShanghai::try_from(legacy).unwrap(), shanghai);
1094
1095 let prague = BuiltPayloadPrague {
1096 payload: payload_v3(),
1097 block_value: U256::from(2),
1098 blobs_bundle: BlobsBundleV1::empty(),
1099 execution_requests: Requests::from_requests([Bytes::from_static(&[3, 4])]),
1100 should_override_builder: true,
1101 };
1102 let legacy = LegacyBuiltPayloadPrague::from(prague.clone());
1103 assert_eq!(BuiltPayloadPrague::from(legacy), prague);
1104
1105 let osaka = BuiltPayloadOsaka {
1106 payload: payload_v3(),
1107 block_value: U256::from(5),
1108 blobs_bundle: BlobsBundleV2::empty(),
1109 execution_requests: Requests::from_requests([Bytes::from_static(&[6, 7])]),
1110 should_override_builder: true,
1111 };
1112 let legacy = LegacyBuiltPayloadOsaka::from(osaka.clone());
1113 assert_eq!(BuiltPayloadOsaka::from(legacy), osaka);
1114
1115 let amsterdam = BuiltPayloadAmsterdam {
1116 payload: payload_v4(),
1117 block_value: U256::from(8),
1118 blobs_bundle: BlobsBundleV2::empty(),
1119 execution_requests: Requests::from_requests([Bytes::from_static(&[9, 10])]),
1120 should_override_builder: true,
1121 };
1122 let legacy = LegacyBuiltPayloadAmsterdam::from(amsterdam.clone());
1123 assert_eq!(BuiltPayloadAmsterdam::from(legacy), amsterdam);
1124 }
1125
1126 #[test]
1127 fn legacy_shanghai_built_payload_rejects_paris_payload() {
1128 let legacy = LegacyBuiltPayloadShanghai {
1129 execution_payload: ExecutionPayloadFieldV2::V1(payload_v1()),
1130 block_value: U256::from(1),
1131 };
1132 assert_eq!(
1133 BuiltPayloadShanghai::try_from(legacy),
1134 Err(BuiltPayloadConversionError::UnexpectedPayloadFork("Paris"))
1135 );
1136 }
1137
1138 #[test]
1139 fn prague_requests_precede_should_override_builder() {
1140 let value = BuiltPayloadPrague {
1141 payload: payload_v3(),
1142 block_value: U256::from(1),
1143 blobs_bundle: BlobsBundleV1::empty(),
1144 execution_requests: Requests::from_requests([Bytes::from_static(&[0xaa, 0xbb])]),
1145 should_override_builder: true,
1146 };
1147 let encoded = value.as_ssz_bytes();
1148 assert_eq!(encoded[48], 1);
1149 assert_eq!(&encoded[encoded.len() - 2..], &[0xaa, 0xbb]);
1150 }
1151
1152 #[test]
1153 fn forkchoice_updates_roundtrip() {
1154 let paris = PayloadAttributesParis {
1155 timestamp: 1,
1156 prev_randao: B256::repeat_byte(2),
1157 suggested_fee_recipient: Address::repeat_byte(3),
1158 };
1159 let shanghai = PayloadAttributesShanghai {
1160 timestamp: 1,
1161 prev_randao: B256::repeat_byte(2),
1162 suggested_fee_recipient: Address::repeat_byte(3),
1163 withdrawals: vec![Withdrawal::default()],
1164 };
1165 let amsterdam = PayloadAttributesAmsterdam {
1166 timestamp: 1,
1167 prev_randao: B256::repeat_byte(2),
1168 suggested_fee_recipient: Address::repeat_byte(3),
1169 withdrawals: vec![Withdrawal::default()],
1170 parent_beacon_block_root: B256::repeat_byte(4),
1171 slot_number: 5,
1172 target_gas_limit: 6,
1173 };
1174 assert_roundtrip(&ForkchoiceUpdateParis {
1175 forkchoice_state: state(),
1176 payload_attributes: Optional::some(paris),
1177 });
1178 assert_roundtrip(&ForkchoiceUpdateShanghai {
1179 forkchoice_state: state(),
1180 payload_attributes: Optional::some(shanghai),
1181 });
1182 assert_roundtrip(&ForkchoiceUpdateCancun {
1183 forkchoice_state: state(),
1184 payload_attributes: Optional::some(attributes_cancun()),
1185 });
1186 assert_roundtrip(&ForkchoiceUpdatePrague {
1187 forkchoice_state: state(),
1188 payload_attributes: Optional::some(attributes_cancun()),
1189 });
1190 assert_roundtrip(&ForkchoiceUpdateOsaka {
1191 forkchoice_state: state(),
1192 payload_attributes: Optional::some(attributes_cancun()),
1193 });
1194 assert_roundtrip(&ForkchoiceUpdateAmsterdam {
1195 forkchoice_state: state(),
1196 payload_attributes: Optional::some(amsterdam),
1197 custody_columns: Optional::some(B128::repeat_byte(0xa5)),
1198 });
1199 }
1200
1201 #[test]
1202 fn payload_attributes_legacy_conversions_preserve_fork_shape() {
1203 let cancun = attributes_cancun();
1204 let legacy = LegacyPayloadAttributes::from(cancun.clone());
1205 assert_eq!(PayloadAttributesCancun::try_from(legacy).unwrap(), cancun);
1206
1207 let amsterdam = PayloadAttributesAmsterdam {
1208 timestamp: 1,
1209 prev_randao: B256::repeat_byte(2),
1210 suggested_fee_recipient: Address::repeat_byte(3),
1211 withdrawals: vec![Withdrawal::default()],
1212 parent_beacon_block_root: B256::repeat_byte(4),
1213 slot_number: 5,
1214 target_gas_limit: 6,
1215 };
1216 let legacy = LegacyPayloadAttributes::from(amsterdam.clone());
1217 assert_eq!(PayloadAttributesAmsterdam::try_from(legacy).unwrap(), amsterdam);
1218 }
1219
1220 #[test]
1221 fn payload_attributes_legacy_conversions_reject_loss() {
1222 let mut legacy = LegacyPayloadAttributes::default();
1223 assert_eq!(
1224 PayloadAttributesShanghai::try_from(legacy.clone()),
1225 Err(PayloadAttributesConversionError::MissingField("withdrawals"))
1226 );
1227
1228 legacy.withdrawals = Some(vec![]);
1229 legacy.parent_beacon_block_root = Some(B256::ZERO);
1230 assert_eq!(
1231 PayloadAttributesShanghai::try_from(legacy),
1232 Err(PayloadAttributesConversionError::UnexpectedField("parent_beacon_block_root"))
1233 );
1234 }
1235
1236 #[test]
1237 fn every_payload_status_roundtrips() {
1238 for status in [
1239 PayloadStatusEnum::Valid,
1240 PayloadStatusEnum::Invalid { validation_error: "invalid".into() },
1241 PayloadStatusEnum::Syncing,
1242 PayloadStatusEnum::Accepted,
1243 ] {
1244 let validation_error = legacy_validation_error(&status).unwrap();
1245 let value = PayloadStatus {
1246 status,
1247 latest_valid_hash: Optional::some(B256::ZERO),
1248 validation_error,
1249 };
1250 assert_eq!(PayloadStatus::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), value);
1251 }
1252 }
1253
1254 #[test]
1255 fn payload_status_preserves_absent_invalid_validation_error() {
1256 let mut bytes = Vec::new();
1257 let mut encoder = ssz::SszEncoder::container(&mut bytes, 9);
1258 encoder.append(&1u8);
1259 encoder.append(&Optional::<B256>::none());
1260 encoder.append(&Optional::<ErrorBytes>::none());
1261 encoder.finalize();
1262
1263 let decoded = PayloadStatus::from_ssz_bytes(&bytes).unwrap();
1264 assert!(decoded.validation_error.is_none());
1265 assert_eq!(decoded.as_ssz_bytes(), bytes);
1266 }
1267
1268 #[test]
1269 fn payload_status_rejects_non_invalid_validation_error() {
1270 let mut bytes = Vec::new();
1271 let mut encoder = ssz::SszEncoder::container(&mut bytes, 9);
1272 encoder.append(&0u8);
1273 encoder.append(&Optional::<B256>::none());
1274 encoder.append(&Optional::some(Vec::<u8>::new()));
1275 encoder.finalize();
1276 assert!(PayloadStatus::from_ssz_bytes(&bytes).is_err());
1277 }
1278
1279 #[test]
1280 fn payload_status_legacy_conversion_rejects_oversized_error() {
1281 assert!(PayloadStatus::try_from(LegacyPayloadStatus {
1282 status: PayloadStatusEnum::Invalid { validation_error: "x".repeat(1025) },
1283 latest_valid_hash: None,
1284 })
1285 .is_err());
1286 }
1287
1288 #[test]
1289 fn forkchoice_response_distinguishes_absent_and_zero_payload_id() {
1290 let status = PayloadStatus {
1291 status: PayloadStatusEnum::Valid,
1292 latest_valid_hash: Optional::none(),
1293 validation_error: Optional::none(),
1294 };
1295 let none = ForkchoiceUpdateResponse {
1296 payload_status: status.clone(),
1297 payload_id: Optional::none(),
1298 };
1299 let zero = ForkchoiceUpdateResponse {
1300 payload_status: status,
1301 payload_id: Optional::some(PayloadId::default()),
1302 };
1303
1304 assert_ne!(none.as_ssz_bytes(), zero.as_ssz_bytes());
1305 assert_roundtrip(&none);
1306 assert_roundtrip(&zero);
1307 }
1308
1309 #[test]
1310 fn forkchoice_conversion_rejects_accepted() {
1311 let legacy = LegacyForkchoice::from_status(PayloadStatusEnum::Accepted);
1312 assert_eq!(
1313 ForkchoiceUpdateResponse::try_from(legacy),
1314 Err(ConversionError::AcceptedForkchoice)
1315 );
1316 }
1317
1318 #[test]
1319 fn optional_rejects_more_than_one_value() {
1320 assert!(Optional::<B256>::from_ssz_bytes(&[0; 64]).is_err());
1321 }
1322}