1use alloy_eips::{
9 eip4844::{Blob, BlobAndProofV1, BlobAndProofV2, BlobCellsAndProofsV1, Bytes48},
10 eip4895::Withdrawal,
11 eip7594::Cell,
12 eip7685::Requests,
13};
14use alloy_primitives::{Address, Bytes, B128, B256, U256};
15use alloy_rpc_types_engine::{
16 BlobsBundleV1, BlobsBundleV2, ExecutionPayloadBodyV1 as LegacyExecutionPayloadBodyV1,
17 ExecutionPayloadBodyV2 as LegacyExecutionPayloadBodyV2,
18 ExecutionPayloadEnvelopeV2 as LegacyBuiltPayloadShanghai,
19 ExecutionPayloadEnvelopeV4 as LegacyBuiltPayloadPrague,
20 ExecutionPayloadEnvelopeV5 as LegacyBuiltPayloadOsaka,
21 ExecutionPayloadEnvelopeV6 as LegacyBuiltPayloadAmsterdam, ExecutionPayloadFieldV2,
22 ExecutionPayloadV1, ExecutionPayloadV2, ExecutionPayloadV3, ExecutionPayloadV4,
23 ForkchoiceState, ForkchoiceUpdated as LegacyForkchoice,
24 PayloadAttributes as LegacyPayloadAttributes, PayloadId, PayloadStatus as LegacyPayloadStatus,
25 PayloadStatusEnum,
26};
27
28type ErrorBytes = Vec<u8>;
29
30pub const MAX_BLOBS_REQUEST: usize = 128;
32
33pub const MAX_BODIES_REQUEST: usize = 32;
35
36#[derive(Clone, Debug, Default, PartialEq, Eq)]
40pub struct Optional<T>(Vec<T>);
41
42impl<T> Optional<T> {
43 pub const fn none() -> Self {
45 Self(Vec::new())
46 }
47
48 pub fn some(value: T) -> Self {
50 Self(vec![value])
51 }
52
53 pub fn as_ref(&self) -> Option<&T> {
55 self.0.first()
56 }
57
58 pub const fn is_none(&self) -> bool {
60 self.0.is_empty()
61 }
62
63 pub const fn is_some(&self) -> bool {
65 !self.is_none()
66 }
67
68 pub fn into_option(mut self) -> Option<T> {
70 self.0.pop()
71 }
72}
73
74impl<T> From<Option<T>> for Optional<T> {
75 fn from(value: Option<T>) -> Self {
76 value.map_or_else(Self::none, Self::some)
77 }
78}
79
80impl<T> From<Optional<T>> for Option<T> {
81 fn from(value: Optional<T>) -> Self {
82 value.into_option()
83 }
84}
85
86impl<T: ssz::Encode> ssz::Encode for Optional<T> {
87 fn is_ssz_fixed_len() -> bool {
88 false
89 }
90
91 fn ssz_bytes_len(&self) -> usize {
92 self.0.ssz_bytes_len()
93 }
94
95 fn ssz_append(&self, buf: &mut Vec<u8>) {
96 self.0.ssz_append(buf);
97 }
98}
99
100impl<T: ssz::Decode> ssz::Decode for Optional<T> {
101 fn is_ssz_fixed_len() -> bool {
102 false
103 }
104
105 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
106 let values = Vec::<T>::from_ssz_bytes(bytes)?;
107 if values.len() > 1 {
108 return Err(ssz::DecodeError::BytesInvalid("optional has more than one value".into()))
109 }
110 Ok(Self(values))
111 }
112}
113
114#[derive(Clone, Debug, PartialEq, Eq)]
119pub struct PayloadStatus {
120 pub status: PayloadStatusEnum,
122 pub latest_valid_hash: Optional<B256>,
124 pub validation_error: Optional<ErrorBytes>,
126}
127
128const MAX_ERROR_BYTES: usize = 1024;
129
130const fn status_code(status: &PayloadStatusEnum) -> u8 {
131 match status {
132 PayloadStatusEnum::Valid => 0,
133 PayloadStatusEnum::Invalid { .. } => 1,
134 PayloadStatusEnum::Syncing => 2,
135 PayloadStatusEnum::Accepted => 3,
136 }
137}
138
139fn status_from_code(code: u8) -> Result<PayloadStatusEnum, ssz::DecodeError> {
140 match code {
141 0 => Ok(PayloadStatusEnum::Valid),
142 1 => Ok(PayloadStatusEnum::Invalid { validation_error: String::new() }),
143 2 => Ok(PayloadStatusEnum::Syncing),
144 3 => Ok(PayloadStatusEnum::Accepted),
145 _ => Err(ssz::DecodeError::BytesInvalid("unknown payload status code".into())),
146 }
147}
148
149fn legacy_validation_error(
150 status: &PayloadStatusEnum,
151) -> Result<Optional<ErrorBytes>, ConversionError> {
152 match status {
153 PayloadStatusEnum::Invalid { validation_error } => {
154 let bytes = validation_error.as_bytes().to_vec();
155 if bytes.len() > MAX_ERROR_BYTES {
156 return Err(ConversionError::ErrorBytesTooLong)
157 }
158 Ok(Optional::some(bytes))
159 }
160 _ => Ok(Optional::none()),
161 }
162}
163
164impl ssz::Encode for PayloadStatus {
165 fn is_ssz_fixed_len() -> bool {
166 false
167 }
168
169 fn ssz_bytes_len(&self) -> usize {
170 1 + ssz::BYTES_PER_LENGTH_OFFSET * 2 +
171 self.latest_valid_hash.ssz_bytes_len() +
172 self.validation_error.ssz_bytes_len()
173 }
174
175 fn ssz_append(&self, buf: &mut Vec<u8>) {
176 let mut encoder = ssz::SszEncoder::container(buf, 1 + ssz::BYTES_PER_LENGTH_OFFSET * 2);
177 encoder.append(&status_code(&self.status));
178 encoder.append(&self.latest_valid_hash);
179 encoder.append(&self.validation_error);
180 encoder.finalize();
181 }
182}
183
184impl ssz::Decode for PayloadStatus {
185 fn is_ssz_fixed_len() -> bool {
186 false
187 }
188
189 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
190 let mut builder = ssz::SszDecoderBuilder::new(bytes);
191 builder.register_type::<u8>()?;
192 builder.register_type::<Optional<B256>>()?;
193 builder.register_type::<Optional<ErrorBytes>>()?;
194 let mut decoder = builder.build()?;
195 let mut status = status_from_code(decoder.decode_next()?)?;
196 let latest_valid_hash = decoder.decode_next()?;
197 let validation_error: Optional<ErrorBytes> = decoder.decode_next()?;
198 if let PayloadStatusEnum::Invalid { validation_error: error } = &mut status {
199 *error = match validation_error.as_ref() {
200 Some(error) => String::from_utf8(error.clone())
201 .map_err(|err| ssz::DecodeError::BytesInvalid(err.to_string()))?,
202 None => String::new(),
203 };
204 if validation_error.as_ref().is_some_and(|error| error.len() > MAX_ERROR_BYTES) {
205 return Err(ssz::DecodeError::BytesInvalid(
206 "payload validation error is too long".into(),
207 ))
208 }
209 } else if validation_error.is_some() {
210 return Err(ssz::DecodeError::BytesInvalid(
211 "validation error is only valid for INVALID status".into(),
212 ));
213 }
214 Ok(Self { status, latest_valid_hash, validation_error })
215 }
216}
217
218#[derive(Clone, Debug, PartialEq, Eq)]
220pub enum ConversionError {
221 ErrorBytesTooLong,
223 AcceptedForkchoice,
225 TooManyItems {
227 field: &'static str,
229 max: usize,
231 actual: usize,
233 },
234}
235
236impl core::fmt::Display for ConversionError {
237 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
238 match self {
239 Self::ErrorBytesTooLong => f.write_str("payload validation error is too long"),
240 Self::AcceptedForkchoice => {
241 f.write_str("ACCEPTED is not valid in a forkchoice response")
242 }
243 Self::TooManyItems { field, max, actual } => {
244 write!(f, "too many {field}: expected at most {max}, got {actual}")
245 }
246 }
247 }
248}
249
250impl core::error::Error for ConversionError {}
251
252impl TryFrom<LegacyPayloadStatus> for PayloadStatus {
253 type Error = ConversionError;
254
255 fn try_from(value: LegacyPayloadStatus) -> Result<Self, Self::Error> {
256 let validation_error = legacy_validation_error(&value.status)?;
257 Ok(Self {
258 status: value.status,
259 latest_valid_hash: value.latest_valid_hash.into(),
260 validation_error,
261 })
262 }
263}
264
265impl From<PayloadStatus> for LegacyPayloadStatus {
266 fn from(value: PayloadStatus) -> Self {
267 let status = match value.status {
268 PayloadStatusEnum::Invalid { .. } => PayloadStatusEnum::Invalid {
269 validation_error: value
270 .validation_error
271 .as_ref()
272 .and_then(|error| String::from_utf8(error.clone()).ok())
273 .unwrap_or_default(),
274 },
275 status => status,
276 };
277 Self { status, latest_valid_hash: value.latest_valid_hash.into() }
278 }
279}
280
281#[derive(Clone, Debug, PartialEq, Eq)]
285pub struct ForkchoiceUpdateResponse {
286 pub payload_status: PayloadStatus,
288 pub payload_id: Optional<PayloadId>,
290}
291
292impl ssz::Encode for ForkchoiceUpdateResponse {
293 fn is_ssz_fixed_len() -> bool {
294 false
295 }
296
297 fn ssz_bytes_len(&self) -> usize {
298 ssz::BYTES_PER_LENGTH_OFFSET * 2 +
299 self.payload_status.ssz_bytes_len() +
300 self.payload_id.ssz_bytes_len()
301 }
302
303 fn ssz_append(&self, buf: &mut Vec<u8>) {
304 let mut encoder = ssz::SszEncoder::container(buf, ssz::BYTES_PER_LENGTH_OFFSET * 2);
305 encoder.append(&self.payload_status);
306 encoder.append(&self.payload_id);
307 encoder.finalize();
308 }
309}
310
311impl ssz::Decode for ForkchoiceUpdateResponse {
312 fn is_ssz_fixed_len() -> bool {
313 false
314 }
315
316 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
317 let mut builder = ssz::SszDecoderBuilder::new(bytes);
318 builder.register_type::<PayloadStatus>()?;
319 builder.register_type::<Optional<PayloadId>>()?;
320 let mut decoder = builder.build()?;
321 let response =
322 Self { payload_status: decoder.decode_next()?, payload_id: decoder.decode_next()? };
323 if matches!(response.payload_status.status, PayloadStatusEnum::Accepted) {
324 return Err(ssz::DecodeError::BytesInvalid(
325 "ACCEPTED is not valid in a forkchoice response".into(),
326 ));
327 }
328 Ok(response)
329 }
330}
331
332impl TryFrom<LegacyForkchoice> for ForkchoiceUpdateResponse {
333 type Error = ConversionError;
334
335 fn try_from(value: LegacyForkchoice) -> Result<Self, Self::Error> {
336 let payload_status = PayloadStatus::try_from(value.payload_status)?;
337 if matches!(payload_status.status, PayloadStatusEnum::Accepted) {
338 return Err(ConversionError::AcceptedForkchoice)
339 }
340 Ok(Self { payload_status, payload_id: value.payload_id.into() })
341 }
342}
343
344impl From<ForkchoiceUpdateResponse> for LegacyForkchoice {
345 fn from(value: ForkchoiceUpdateResponse) -> Self {
346 Self { payload_status: value.payload_status.into(), payload_id: value.payload_id.into() }
347 }
348}
349
350pub type ExecutionPayloadParis = ExecutionPayloadV1;
352
353pub type ExecutionPayloadShanghai = ExecutionPayloadV2;
355
356pub type ExecutionPayloadCancun = ExecutionPayloadV3;
358
359pub type ExecutionPayloadPrague = ExecutionPayloadV3;
361
362pub type ExecutionPayloadOsaka = ExecutionPayloadV3;
364
365pub type ExecutionPayloadAmsterdam = ExecutionPayloadV4;
367
368#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
373pub struct PayloadAttributesParis {
374 pub timestamp: u64,
376 pub prev_randao: B256,
378 pub suggested_fee_recipient: Address,
380}
381
382#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
387pub struct PayloadAttributesShanghai {
388 pub timestamp: u64,
390 pub prev_randao: B256,
392 pub suggested_fee_recipient: Address,
394 pub withdrawals: Vec<Withdrawal>,
396}
397
398#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
403pub struct PayloadAttributesCancun {
404 pub timestamp: u64,
406 pub prev_randao: B256,
408 pub suggested_fee_recipient: Address,
410 pub withdrawals: Vec<Withdrawal>,
412 pub parent_beacon_block_root: B256,
414}
415
416pub type PayloadAttributesPrague = PayloadAttributesCancun;
418
419pub type PayloadAttributesOsaka = PayloadAttributesCancun;
421
422#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
426pub struct PayloadAttributesAmsterdam {
427 pub timestamp: u64,
429 pub prev_randao: B256,
431 pub suggested_fee_recipient: Address,
433 pub withdrawals: Vec<Withdrawal>,
435 pub parent_beacon_block_root: B256,
437 pub slot_number: u64,
439 pub target_gas_limit: u64,
441}
442
443#[derive(Clone, Copy, Debug, PartialEq, Eq)]
445pub enum PayloadAttributesConversionError {
446 MissingField(&'static str),
448 UnexpectedField(&'static str),
450}
451
452impl core::fmt::Display for PayloadAttributesConversionError {
453 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
454 match self {
455 Self::MissingField(field) => {
456 write!(f, "missing required payload attributes field: {field}")
457 }
458 Self::UnexpectedField(field) => {
459 write!(f, "unexpected later-fork payload attributes field: {field}")
460 }
461 }
462 }
463}
464
465impl core::error::Error for PayloadAttributesConversionError {}
466
467const fn ensure_absent<T>(
468 value: &Option<T>,
469 field: &'static str,
470) -> Result<(), PayloadAttributesConversionError> {
471 if value.is_some() {
472 Err(PayloadAttributesConversionError::UnexpectedField(field))
473 } else {
474 Ok(())
475 }
476}
477
478fn require<T>(
479 value: Option<T>,
480 field: &'static str,
481) -> Result<T, PayloadAttributesConversionError> {
482 value.ok_or(PayloadAttributesConversionError::MissingField(field))
483}
484
485impl From<PayloadAttributesParis> for LegacyPayloadAttributes {
486 fn from(value: PayloadAttributesParis) -> Self {
487 Self {
488 timestamp: value.timestamp,
489 prev_randao: value.prev_randao,
490 suggested_fee_recipient: value.suggested_fee_recipient,
491 withdrawals: None,
492 parent_beacon_block_root: None,
493 slot_number: None,
494 ..Default::default()
495 }
496 }
497}
498
499impl TryFrom<LegacyPayloadAttributes> for PayloadAttributesParis {
500 type Error = PayloadAttributesConversionError;
501
502 fn try_from(value: LegacyPayloadAttributes) -> Result<Self, Self::Error> {
503 ensure_absent(&value.withdrawals, "withdrawals")?;
504 ensure_absent(&value.parent_beacon_block_root, "parent_beacon_block_root")?;
505 ensure_absent(&value.slot_number, "slot_number")?;
506 ensure_absent(&value.target_gas_limit, "target_gas_limit")?;
507 Ok(Self {
508 timestamp: value.timestamp,
509 prev_randao: value.prev_randao,
510 suggested_fee_recipient: value.suggested_fee_recipient,
511 })
512 }
513}
514
515impl From<PayloadAttributesShanghai> for LegacyPayloadAttributes {
516 fn from(value: PayloadAttributesShanghai) -> Self {
517 Self {
518 timestamp: value.timestamp,
519 prev_randao: value.prev_randao,
520 suggested_fee_recipient: value.suggested_fee_recipient,
521 withdrawals: Some(value.withdrawals),
522 parent_beacon_block_root: None,
523 slot_number: None,
524 ..Default::default()
525 }
526 }
527}
528
529impl TryFrom<LegacyPayloadAttributes> for PayloadAttributesShanghai {
530 type Error = PayloadAttributesConversionError;
531
532 fn try_from(value: LegacyPayloadAttributes) -> Result<Self, Self::Error> {
533 ensure_absent(&value.parent_beacon_block_root, "parent_beacon_block_root")?;
534 ensure_absent(&value.slot_number, "slot_number")?;
535 ensure_absent(&value.target_gas_limit, "target_gas_limit")?;
536 Ok(Self {
537 timestamp: value.timestamp,
538 prev_randao: value.prev_randao,
539 suggested_fee_recipient: value.suggested_fee_recipient,
540 withdrawals: require(value.withdrawals, "withdrawals")?,
541 })
542 }
543}
544
545impl From<PayloadAttributesCancun> for LegacyPayloadAttributes {
546 fn from(value: PayloadAttributesCancun) -> Self {
547 Self {
548 timestamp: value.timestamp,
549 prev_randao: value.prev_randao,
550 suggested_fee_recipient: value.suggested_fee_recipient,
551 withdrawals: Some(value.withdrawals),
552 parent_beacon_block_root: Some(value.parent_beacon_block_root),
553 slot_number: None,
554 ..Default::default()
555 }
556 }
557}
558
559impl TryFrom<LegacyPayloadAttributes> for PayloadAttributesCancun {
560 type Error = PayloadAttributesConversionError;
561
562 fn try_from(value: LegacyPayloadAttributes) -> Result<Self, Self::Error> {
563 ensure_absent(&value.slot_number, "slot_number")?;
564 ensure_absent(&value.target_gas_limit, "target_gas_limit")?;
565 Ok(Self {
566 timestamp: value.timestamp,
567 prev_randao: value.prev_randao,
568 suggested_fee_recipient: value.suggested_fee_recipient,
569 withdrawals: require(value.withdrawals, "withdrawals")?,
570 parent_beacon_block_root: require(
571 value.parent_beacon_block_root,
572 "parent_beacon_block_root",
573 )?,
574 })
575 }
576}
577
578impl From<PayloadAttributesAmsterdam> for LegacyPayloadAttributes {
579 #[allow(clippy::needless_update)]
580 fn from(value: PayloadAttributesAmsterdam) -> Self {
581 Self {
582 timestamp: value.timestamp,
583 prev_randao: value.prev_randao,
584 suggested_fee_recipient: value.suggested_fee_recipient,
585 withdrawals: Some(value.withdrawals),
586 parent_beacon_block_root: Some(value.parent_beacon_block_root),
587 slot_number: Some(value.slot_number),
588 target_gas_limit: Some(value.target_gas_limit),
589 ..Default::default()
590 }
591 }
592}
593
594impl TryFrom<LegacyPayloadAttributes> for PayloadAttributesAmsterdam {
595 type Error = PayloadAttributesConversionError;
596
597 fn try_from(value: LegacyPayloadAttributes) -> Result<Self, Self::Error> {
598 Ok(Self {
599 timestamp: value.timestamp,
600 prev_randao: value.prev_randao,
601 suggested_fee_recipient: value.suggested_fee_recipient,
602 withdrawals: require(value.withdrawals, "withdrawals")?,
603 parent_beacon_block_root: require(
604 value.parent_beacon_block_root,
605 "parent_beacon_block_root",
606 )?,
607 slot_number: require(value.slot_number, "slot_number")?,
608 target_gas_limit: require(value.target_gas_limit, "target_gas_limit")?,
609 })
610 }
611}
612
613#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
617pub struct BuiltPayloadParis {
618 pub payload: ExecutionPayloadParis,
620 pub block_value: U256,
622}
623
624#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
630pub struct BuiltPayloadShanghai {
631 pub payload: ExecutionPayloadShanghai,
633 pub block_value: U256,
635}
636
637pub type BuiltPayloadCancun = alloy_rpc_types_engine::ExecutionPayloadEnvelopeV3;
641
642#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
647pub struct BuiltPayloadPrague {
648 pub payload: ExecutionPayloadPrague,
650 pub block_value: U256,
652 pub blobs_bundle: BlobsBundleV1,
654 pub execution_requests: Requests,
656 pub should_override_builder: bool,
659}
660
661#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
666pub struct BuiltPayloadOsaka {
667 pub payload: ExecutionPayloadOsaka,
669 pub block_value: U256,
671 pub blobs_bundle: BlobsBundleV2,
673 pub execution_requests: Requests,
675 pub should_override_builder: bool,
678}
679
680#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
685pub struct BuiltPayloadAmsterdam {
686 pub payload: ExecutionPayloadAmsterdam,
688 pub block_value: U256,
690 pub blobs_bundle: BlobsBundleV2,
692 pub execution_requests: Requests,
694 pub should_override_builder: bool,
697}
698
699#[derive(Clone, Copy, Debug, PartialEq, Eq)]
701pub enum BuiltPayloadConversionError {
702 UnexpectedPayloadFork(&'static str),
704}
705
706impl core::fmt::Display for BuiltPayloadConversionError {
707 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
708 match self {
709 Self::UnexpectedPayloadFork(fork) => {
710 write!(f, "unexpected execution payload fork: {fork}")
711 }
712 }
713 }
714}
715
716impl core::error::Error for BuiltPayloadConversionError {}
717
718impl From<BuiltPayloadShanghai> for LegacyBuiltPayloadShanghai {
719 fn from(value: BuiltPayloadShanghai) -> Self {
720 Self {
721 execution_payload: ExecutionPayloadFieldV2::V2(value.payload),
722 block_value: value.block_value,
723 }
724 }
725}
726
727impl TryFrom<LegacyBuiltPayloadShanghai> for BuiltPayloadShanghai {
728 type Error = BuiltPayloadConversionError;
729
730 fn try_from(value: LegacyBuiltPayloadShanghai) -> Result<Self, Self::Error> {
731 match value.execution_payload {
732 ExecutionPayloadFieldV2::V2(payload) => {
733 Ok(Self { payload, block_value: value.block_value })
734 }
735 ExecutionPayloadFieldV2::V1(_) => {
736 Err(BuiltPayloadConversionError::UnexpectedPayloadFork("Paris"))
737 }
738 }
739 }
740}
741
742impl From<LegacyBuiltPayloadPrague> for BuiltPayloadPrague {
743 fn from(value: LegacyBuiltPayloadPrague) -> Self {
744 Self {
745 payload: value.envelope_inner.execution_payload,
746 block_value: value.envelope_inner.block_value,
747 blobs_bundle: value.envelope_inner.blobs_bundle,
748 execution_requests: value.execution_requests,
749 should_override_builder: value.envelope_inner.should_override_builder,
750 }
751 }
752}
753
754impl From<BuiltPayloadPrague> for LegacyBuiltPayloadPrague {
755 fn from(value: BuiltPayloadPrague) -> Self {
756 Self {
757 envelope_inner: alloy_rpc_types_engine::ExecutionPayloadEnvelopeV3 {
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 },
763 execution_requests: value.execution_requests,
764 }
765 }
766}
767
768impl From<LegacyBuiltPayloadOsaka> for BuiltPayloadOsaka {
769 fn from(value: LegacyBuiltPayloadOsaka) -> Self {
770 Self {
771 payload: value.execution_payload,
772 block_value: value.block_value,
773 blobs_bundle: value.blobs_bundle,
774 execution_requests: value.execution_requests,
775 should_override_builder: value.should_override_builder,
776 }
777 }
778}
779
780impl From<BuiltPayloadOsaka> for LegacyBuiltPayloadOsaka {
781 fn from(value: BuiltPayloadOsaka) -> Self {
782 Self {
783 execution_payload: value.payload,
784 block_value: value.block_value,
785 blobs_bundle: value.blobs_bundle,
786 should_override_builder: value.should_override_builder,
787 execution_requests: value.execution_requests,
788 }
789 }
790}
791
792impl From<LegacyBuiltPayloadAmsterdam> for BuiltPayloadAmsterdam {
793 fn from(value: LegacyBuiltPayloadAmsterdam) -> Self {
794 Self {
795 payload: value.execution_payload,
796 block_value: value.block_value,
797 blobs_bundle: value.blobs_bundle,
798 execution_requests: value.execution_requests,
799 should_override_builder: value.should_override_builder,
800 }
801 }
802}
803
804impl From<BuiltPayloadAmsterdam> for LegacyBuiltPayloadAmsterdam {
805 fn from(value: BuiltPayloadAmsterdam) -> Self {
806 Self {
807 execution_payload: value.payload,
808 block_value: value.block_value,
809 blobs_bundle: value.blobs_bundle,
810 should_override_builder: value.should_override_builder,
811 execution_requests: value.execution_requests,
812 }
813 }
814}
815
816#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
828pub struct ExecutionPayloadEnvelopeParis {
829 pub payload: ExecutionPayloadParis,
831}
832
833#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
837pub struct ExecutionPayloadEnvelopeShanghai {
838 pub payload: ExecutionPayloadShanghai,
840}
841
842#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
846pub struct ExecutionPayloadEnvelopeCancun {
847 pub payload: ExecutionPayloadCancun,
849 pub parent_beacon_block_root: B256,
851}
852
853#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
857pub struct ExecutionPayloadEnvelopePrague {
858 pub payload: ExecutionPayloadPrague,
860 pub parent_beacon_block_root: B256,
862 pub execution_requests: Requests,
864}
865
866#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
870pub struct ExecutionPayloadEnvelopeOsaka {
871 pub payload: ExecutionPayloadOsaka,
873 pub parent_beacon_block_root: B256,
875 pub execution_requests: Requests,
877}
878
879#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
883pub struct ExecutionPayloadEnvelopeAmsterdam {
884 pub payload: ExecutionPayloadAmsterdam,
886 pub parent_beacon_block_root: B256,
888 pub execution_requests: Requests,
890}
891
892impl From<ExecutionPayloadParis> for ExecutionPayloadEnvelopeParis {
893 fn from(payload: ExecutionPayloadParis) -> Self {
894 Self { payload }
895 }
896}
897
898impl From<ExecutionPayloadShanghai> for ExecutionPayloadEnvelopeShanghai {
899 fn from(payload: ExecutionPayloadShanghai) -> Self {
900 Self { payload }
901 }
902}
903
904impl From<(ExecutionPayloadCancun, B256)> for ExecutionPayloadEnvelopeCancun {
905 fn from((payload, parent_beacon_block_root): (ExecutionPayloadCancun, B256)) -> Self {
906 Self { payload, parent_beacon_block_root }
907 }
908}
909
910impl From<(ExecutionPayloadPrague, B256, Requests)> for ExecutionPayloadEnvelopePrague {
911 fn from(
912 (payload, parent_beacon_block_root, execution_requests): (
913 ExecutionPayloadPrague,
914 B256,
915 Requests,
916 ),
917 ) -> Self {
918 Self { payload, parent_beacon_block_root, execution_requests }
919 }
920}
921
922impl From<(ExecutionPayloadOsaka, B256, Requests)> for ExecutionPayloadEnvelopeOsaka {
923 fn from(
924 (payload, parent_beacon_block_root, execution_requests): (
925 ExecutionPayloadOsaka,
926 B256,
927 Requests,
928 ),
929 ) -> Self {
930 Self { payload, parent_beacon_block_root, execution_requests }
931 }
932}
933
934impl From<(ExecutionPayloadAmsterdam, B256, Requests)> for ExecutionPayloadEnvelopeAmsterdam {
935 fn from(
936 (payload, parent_beacon_block_root, execution_requests): (
937 ExecutionPayloadAmsterdam,
938 B256,
939 Requests,
940 ),
941 ) -> Self {
942 Self { payload, parent_beacon_block_root, execution_requests }
943 }
944}
945
946#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
951pub struct ForkchoiceUpdateParis {
952 pub forkchoice_state: ForkchoiceState,
954 pub payload_attributes: Optional<PayloadAttributesParis>,
956}
957
958#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
960pub struct ForkchoiceUpdateShanghai {
961 pub forkchoice_state: ForkchoiceState,
963 pub payload_attributes: Optional<PayloadAttributesShanghai>,
965}
966
967#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
969pub struct ForkchoiceUpdateCancun {
970 pub forkchoice_state: ForkchoiceState,
972 pub payload_attributes: Optional<PayloadAttributesCancun>,
974}
975
976#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
978pub struct ForkchoiceUpdatePrague {
979 pub forkchoice_state: ForkchoiceState,
981 pub payload_attributes: Optional<PayloadAttributesPrague>,
983}
984
985#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
987pub struct ForkchoiceUpdateOsaka {
988 pub forkchoice_state: ForkchoiceState,
990 pub payload_attributes: Optional<PayloadAttributesOsaka>,
992}
993
994#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
996pub struct ForkchoiceUpdateAmsterdam {
997 pub forkchoice_state: ForkchoiceState,
999 pub payload_attributes: Optional<PayloadAttributesAmsterdam>,
1001 pub custody_columns: Optional<B128>,
1003}
1004
1005#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1009pub struct ExecutionPayloadBodyParis {
1010 pub transactions: Vec<Bytes>,
1012}
1013
1014#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1018pub struct ExecutionPayloadBodyShanghai {
1019 pub transactions: Vec<Bytes>,
1021 pub withdrawals: Vec<Withdrawal>,
1023}
1024
1025pub type ExecutionPayloadBodyCancun = ExecutionPayloadBodyShanghai;
1027
1028pub type ExecutionPayloadBodyPrague = ExecutionPayloadBodyShanghai;
1030
1031pub type ExecutionPayloadBodyOsaka = ExecutionPayloadBodyShanghai;
1033
1034#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1038pub struct ExecutionPayloadBodyAmsterdam {
1039 pub transactions: Vec<Bytes>,
1041 pub withdrawals: Vec<Withdrawal>,
1043 pub block_access_list: Bytes,
1045}
1046
1047#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1049pub enum ExecutionPayloadBodyConversionError {
1050 MissingField(&'static str),
1052 UnexpectedField(&'static str),
1054}
1055
1056impl core::fmt::Display for ExecutionPayloadBodyConversionError {
1057 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1058 match self {
1059 Self::MissingField(field) => {
1060 write!(f, "missing required execution payload body field: {field}")
1061 }
1062 Self::UnexpectedField(field) => {
1063 write!(f, "unexpected later-fork execution payload body field: {field}")
1064 }
1065 }
1066 }
1067}
1068
1069impl core::error::Error for ExecutionPayloadBodyConversionError {}
1070
1071impl From<ExecutionPayloadBodyParis> for LegacyExecutionPayloadBodyV1 {
1072 fn from(value: ExecutionPayloadBodyParis) -> Self {
1073 Self { transactions: value.transactions, withdrawals: None }
1074 }
1075}
1076
1077impl TryFrom<LegacyExecutionPayloadBodyV1> for ExecutionPayloadBodyParis {
1078 type Error = ExecutionPayloadBodyConversionError;
1079
1080 fn try_from(value: LegacyExecutionPayloadBodyV1) -> Result<Self, Self::Error> {
1081 if value.withdrawals.is_some() {
1082 return Err(ExecutionPayloadBodyConversionError::UnexpectedField("withdrawals"))
1083 }
1084 Ok(Self { transactions: value.transactions })
1085 }
1086}
1087
1088impl From<ExecutionPayloadBodyShanghai> for LegacyExecutionPayloadBodyV1 {
1089 fn from(value: ExecutionPayloadBodyShanghai) -> Self {
1090 Self { transactions: value.transactions, withdrawals: Some(value.withdrawals) }
1091 }
1092}
1093
1094impl TryFrom<LegacyExecutionPayloadBodyV1> for ExecutionPayloadBodyShanghai {
1095 type Error = ExecutionPayloadBodyConversionError;
1096
1097 fn try_from(value: LegacyExecutionPayloadBodyV1) -> Result<Self, Self::Error> {
1098 Ok(Self {
1099 transactions: value.transactions,
1100 withdrawals: value
1101 .withdrawals
1102 .ok_or(ExecutionPayloadBodyConversionError::MissingField("withdrawals"))?,
1103 })
1104 }
1105}
1106
1107impl From<ExecutionPayloadBodyAmsterdam> for LegacyExecutionPayloadBodyV2 {
1108 fn from(value: ExecutionPayloadBodyAmsterdam) -> Self {
1109 Self {
1110 transactions: value.transactions,
1111 withdrawals: Some(value.withdrawals),
1112 block_access_list: Some(value.block_access_list),
1113 }
1114 }
1115}
1116
1117impl TryFrom<LegacyExecutionPayloadBodyV2> for ExecutionPayloadBodyAmsterdam {
1118 type Error = ExecutionPayloadBodyConversionError;
1119
1120 fn try_from(value: LegacyExecutionPayloadBodyV2) -> Result<Self, Self::Error> {
1121 Ok(Self {
1122 transactions: value.transactions,
1123 withdrawals: value
1124 .withdrawals
1125 .ok_or(ExecutionPayloadBodyConversionError::MissingField("withdrawals"))?,
1126 block_access_list: value
1127 .block_access_list
1128 .ok_or(ExecutionPayloadBodyConversionError::MissingField("block_access_list"))?,
1129 })
1130 }
1131}
1132
1133#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1137pub struct BodiesByHashRequest {
1138 pub block_hashes: Vec<B256>,
1140}
1141
1142#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1146pub struct BodyEntry<T: ssz::Encode + ssz::Decode> {
1147 pub available: bool,
1149 pub body: T,
1151}
1152
1153impl<T: ssz::Encode + ssz::Decode> BodyEntry<T> {
1154 pub const fn available(body: T) -> Self {
1156 Self { available: true, body }
1157 }
1158}
1159
1160impl<T: ssz::Encode + ssz::Decode + Default> BodyEntry<T> {
1161 pub fn unavailable() -> Self {
1163 Self { available: false, body: T::default() }
1164 }
1165}
1166
1167#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1171pub struct BodiesResponse<T: ssz::Encode + ssz::Decode> {
1172 pub entries: Vec<BodyEntry<T>>,
1174}
1175
1176impl<T: ssz::Encode + ssz::Decode + Default> BodiesResponse<T> {
1177 pub fn from_optional_bodies<LegacyBody>(
1182 bodies: Vec<Option<LegacyBody>>,
1183 convert: impl Fn(LegacyBody) -> Option<T>,
1184 ) -> Self {
1185 let entries = bodies
1186 .into_iter()
1187 .map(|body| match body.and_then(&convert) {
1188 Some(body) => BodyEntry::available(body),
1189 None => BodyEntry::unavailable(),
1190 })
1191 .collect();
1192
1193 Self { entries }
1194 }
1195}
1196
1197pub type BodiesResponseParis = BodiesResponse<ExecutionPayloadBodyParis>;
1199
1200pub type BodiesResponseShanghai = BodiesResponse<ExecutionPayloadBodyShanghai>;
1202
1203pub type BodiesResponseCancun = BodiesResponse<ExecutionPayloadBodyCancun>;
1205
1206pub type BodiesResponsePrague = BodiesResponse<ExecutionPayloadBodyPrague>;
1208
1209pub type BodiesResponseOsaka = BodiesResponse<ExecutionPayloadBodyOsaka>;
1211
1212pub type BodiesResponseAmsterdam = BodiesResponse<ExecutionPayloadBodyAmsterdam>;
1214
1215#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1220pub struct BlobsV1Request {
1221 pub versioned_hashes: Vec<B256>,
1223}
1224
1225pub type BlobsV2Request = BlobsV1Request;
1227
1228pub type BlobsV3Request = BlobsV1Request;
1230
1231#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1233pub struct BlobsV4Request {
1234 pub versioned_hashes: Vec<B256>,
1236 pub indices_bitarray: B128,
1238}
1239
1240#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1244pub struct BlobEntry<T: ssz::Encode + ssz::Decode> {
1245 pub available: bool,
1247 pub contents: T,
1249}
1250
1251#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1255pub struct BlobsResponse<T: ssz::Encode + ssz::Decode> {
1256 pub entries: Vec<BlobEntry<T>>,
1258}
1259
1260pub type BlobsV1Response = BlobsResponse<BlobAndProofV1>;
1262
1263pub type BlobsV2Response = BlobsResponse<BlobAndProofV2>;
1265
1266pub type BlobsV3Response = BlobsResponse<BlobAndProofV2>;
1268
1269pub type BlobsV4Response = BlobsResponse<BlobCellsAndProofs>;
1271
1272#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1277pub struct BlobCellsAndProofs {
1278 pub blob_cells: Vec<Optional<Cell>>,
1280 pub proofs: Vec<Optional<Bytes48>>,
1282}
1283
1284fn zero_blob_v1() -> BlobAndProofV1 {
1285 BlobAndProofV1 { blob: Box::new(Blob::ZERO), proof: Bytes48::ZERO }
1286}
1287
1288fn zero_blob_v2() -> BlobAndProofV2 {
1289 BlobAndProofV2 { blob: Box::new(Blob::ZERO), proofs: Vec::new() }
1290}
1291
1292impl TryFrom<Vec<Option<BlobAndProofV1>>> for BlobsV1Response {
1293 type Error = ConversionError;
1294
1295 fn try_from(value: Vec<Option<BlobAndProofV1>>) -> Result<Self, Self::Error> {
1296 if value.len() > MAX_BLOBS_REQUEST {
1297 return Err(ConversionError::TooManyItems {
1298 field: "blobs",
1299 max: MAX_BLOBS_REQUEST,
1300 actual: value.len(),
1301 })
1302 }
1303
1304 let entries = value
1305 .into_iter()
1306 .map(|value| match value {
1307 Some(contents) => BlobEntry { available: true, contents },
1308 None => BlobEntry { available: false, contents: zero_blob_v1() },
1309 })
1310 .collect();
1311 Ok(Self { entries })
1312 }
1313}
1314
1315impl TryFrom<Vec<BlobAndProofV2>> for BlobsV2Response {
1316 type Error = ConversionError;
1317
1318 fn try_from(value: Vec<BlobAndProofV2>) -> Result<Self, Self::Error> {
1319 if value.len() > MAX_BLOBS_REQUEST {
1320 return Err(ConversionError::TooManyItems {
1321 field: "blobs",
1322 max: MAX_BLOBS_REQUEST,
1323 actual: value.len(),
1324 })
1325 }
1326
1327 let entries =
1328 value.into_iter().map(|contents| BlobEntry { available: true, contents }).collect();
1329 Ok(Self { entries })
1330 }
1331}
1332
1333impl TryFrom<Vec<Option<BlobAndProofV2>>> for BlobsV3Response {
1334 type Error = ConversionError;
1335
1336 fn try_from(value: Vec<Option<BlobAndProofV2>>) -> Result<Self, Self::Error> {
1337 if value.len() > MAX_BLOBS_REQUEST {
1338 return Err(ConversionError::TooManyItems {
1339 field: "blobs",
1340 max: MAX_BLOBS_REQUEST,
1341 actual: value.len(),
1342 })
1343 }
1344
1345 let entries = value
1346 .into_iter()
1347 .map(|value| match value {
1348 Some(contents) => BlobEntry { available: true, contents },
1349 None => BlobEntry { available: false, contents: zero_blob_v2() },
1350 })
1351 .collect();
1352 Ok(Self { entries })
1353 }
1354}
1355
1356impl TryFrom<Vec<Option<BlobCellsAndProofsV1>>> for BlobsV4Response {
1357 type Error = ConversionError;
1358
1359 fn try_from(value: Vec<Option<BlobCellsAndProofsV1>>) -> Result<Self, Self::Error> {
1360 if value.len() > MAX_BLOBS_REQUEST {
1361 return Err(ConversionError::TooManyItems {
1362 field: "blobs",
1363 max: MAX_BLOBS_REQUEST,
1364 actual: value.len(),
1365 })
1366 }
1367
1368 let entries = value
1369 .into_iter()
1370 .map(|value| match value {
1371 Some(contents) => BlobEntry {
1372 available: true,
1373 contents: BlobCellsAndProofs {
1374 blob_cells: contents.blob_cells.into_iter().map(Optional::from).collect(),
1375 proofs: contents.proofs.into_iter().map(Optional::from).collect(),
1376 },
1377 },
1378 None => BlobEntry { available: false, contents: BlobCellsAndProofs::default() },
1379 })
1380 .collect();
1381 Ok(Self { entries })
1382 }
1383}
1384
1385pub type WitnessNodeV1 = Bytes;
1387
1388pub type WitnessCodeV1 = Bytes;
1390
1391pub type WitnessHeaderV1 = Bytes;
1393
1394#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1402pub struct ExecutionWitnessV1 {
1403 pub state: Vec<WitnessNodeV1>,
1405 pub codes: Vec<WitnessCodeV1>,
1407 pub headers: Vec<WitnessHeaderV1>,
1409}
1410
1411pub type ExecutionWitness = ExecutionWitnessV1;
1413
1414#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode)]
1421pub struct PayloadStatusWithWitness {
1422 pub payload_status: PayloadStatus,
1424 pub witness: Optional<ExecutionWitnessV1>,
1426}
1427
1428impl PayloadStatusWithWitness {
1429 pub fn new(payload_status: PayloadStatus, witness: Option<ExecutionWitnessV1>) -> Self {
1431 let witness = match &payload_status.status {
1432 PayloadStatusEnum::Valid => witness.into(),
1433 _ => Optional::none(),
1434 };
1435 Self { payload_status, witness }
1436 }
1437}
1438
1439pub type NewPayloadWithWitnessResponseV1 = PayloadStatusWithWitness;
1441
1442impl ssz::Decode for PayloadStatusWithWitness {
1443 fn is_ssz_fixed_len() -> bool {
1444 false
1445 }
1446
1447 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
1448 let mut builder = ssz::SszDecoderBuilder::new(bytes);
1449 builder.register_type::<PayloadStatus>()?;
1450 builder.register_type::<Optional<ExecutionWitnessV1>>()?;
1451 let mut decoder = builder.build()?;
1452 let response =
1453 Self { payload_status: decoder.decode_next()?, witness: decoder.decode_next()? };
1454 if response.witness.is_some() &&
1455 !matches!(response.payload_status.status, PayloadStatusEnum::Valid)
1456 {
1457 return Err(ssz::DecodeError::BytesInvalid(
1458 "execution witness is only valid for VALID payload status".into(),
1459 ))
1460 }
1461 Ok(response)
1462 }
1463}
1464
1465#[cfg(test)]
1466mod tests {
1467 use super::*;
1468 use alloy_eips::eip4895::Withdrawal;
1469 use alloy_primitives::{Address, Bloom, Bytes};
1470 use ssz::{Decode, Encode};
1471
1472 fn payload_v1() -> ExecutionPayloadV1 {
1473 ExecutionPayloadV1 {
1474 parent_hash: B256::repeat_byte(1),
1475 fee_recipient: Address::repeat_byte(2),
1476 state_root: B256::repeat_byte(3),
1477 receipts_root: B256::repeat_byte(4),
1478 logs_bloom: Bloom::repeat_byte(5),
1479 prev_randao: B256::repeat_byte(6),
1480 block_number: 7,
1481 gas_limit: 8,
1482 gas_used: 9,
1483 timestamp: 10,
1484 extra_data: Bytes::from_static(&[11, 12]),
1485 base_fee_per_gas: U256::from(13),
1486 block_hash: B256::repeat_byte(14),
1487 transactions: vec![Bytes::from_static(&[15, 16])],
1488 }
1489 }
1490
1491 fn payload_v2() -> ExecutionPayloadV2 {
1492 ExecutionPayloadV2 { payload_inner: payload_v1(), withdrawals: vec![Withdrawal::default()] }
1493 }
1494
1495 fn payload_v3() -> ExecutionPayloadV3 {
1496 ExecutionPayloadV3 { payload_inner: payload_v2(), blob_gas_used: 17, excess_blob_gas: 18 }
1497 }
1498
1499 fn payload_v4() -> ExecutionPayloadV4 {
1500 ExecutionPayloadV4 {
1501 payload_inner: payload_v3(),
1502 block_access_list: Bytes::from_static(&[19, 20]),
1503 slot_number: 21,
1504 }
1505 }
1506
1507 fn attributes_cancun() -> PayloadAttributesCancun {
1508 PayloadAttributesCancun {
1509 timestamp: 1,
1510 prev_randao: B256::repeat_byte(2),
1511 suggested_fee_recipient: Address::repeat_byte(3),
1512 withdrawals: vec![Withdrawal::default()],
1513 parent_beacon_block_root: B256::repeat_byte(4),
1514 }
1515 }
1516
1517 fn state() -> ForkchoiceState {
1518 ForkchoiceState {
1519 head_block_hash: B256::repeat_byte(1),
1520 safe_block_hash: B256::repeat_byte(2),
1521 finalized_block_hash: B256::repeat_byte(3),
1522 }
1523 }
1524
1525 fn assert_roundtrip<T>(value: &T)
1526 where
1527 T: Encode + Decode + PartialEq + core::fmt::Debug,
1528 {
1529 assert_eq!(T::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), *value);
1530 }
1531
1532 #[test]
1533 fn execution_payload_envelopes_roundtrip() {
1534 assert_roundtrip(&ExecutionPayloadEnvelopeParis { payload: payload_v1() });
1535 assert_roundtrip(&ExecutionPayloadEnvelopeShanghai { payload: payload_v2() });
1536 assert_roundtrip(&ExecutionPayloadEnvelopeCancun {
1537 payload: payload_v3(),
1538 parent_beacon_block_root: B256::repeat_byte(1),
1539 });
1540 assert_roundtrip(&ExecutionPayloadEnvelopePrague {
1541 payload: payload_v3(),
1542 parent_beacon_block_root: B256::repeat_byte(1),
1543 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1544 });
1545 assert_roundtrip(&ExecutionPayloadEnvelopeOsaka {
1546 payload: payload_v3(),
1547 parent_beacon_block_root: B256::repeat_byte(1),
1548 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1549 });
1550 assert_roundtrip(&ExecutionPayloadEnvelopeAmsterdam {
1551 payload: payload_v4(),
1552 parent_beacon_block_root: B256::repeat_byte(1),
1553 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1554 });
1555 }
1556
1557 #[test]
1558 fn paris_submission_is_a_single_field_container() {
1559 let payload = payload_v1();
1560 let payload_bytes = payload.as_ssz_bytes();
1561 let envelope = ExecutionPayloadEnvelopeParis { payload };
1562 let encoded = envelope.as_ssz_bytes();
1563 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1564 assert_eq!(&encoded[4..], payload_bytes);
1565 }
1566
1567 #[test]
1568 fn built_payloads_roundtrip() {
1569 assert_roundtrip(&BuiltPayloadParis { payload: payload_v1(), block_value: U256::from(1) });
1570 assert_roundtrip(&BuiltPayloadShanghai {
1571 payload: payload_v2(),
1572 block_value: U256::from(1),
1573 });
1574 assert_roundtrip(&BuiltPayloadCancun {
1575 execution_payload: payload_v3(),
1576 block_value: U256::from(1),
1577 blobs_bundle: BlobsBundleV1::empty(),
1578 should_override_builder: true,
1579 });
1580 assert_roundtrip(&BuiltPayloadPrague {
1581 payload: payload_v3(),
1582 block_value: U256::from(1),
1583 blobs_bundle: BlobsBundleV1::empty(),
1584 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1585 should_override_builder: true,
1586 });
1587 assert_roundtrip(&BuiltPayloadOsaka {
1588 payload: payload_v3(),
1589 block_value: U256::from(1),
1590 blobs_bundle: BlobsBundleV2::empty(),
1591 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1592 should_override_builder: true,
1593 });
1594 assert_roundtrip(&BuiltPayloadAmsterdam {
1595 payload: payload_v4(),
1596 block_value: U256::from(1),
1597 blobs_bundle: BlobsBundleV2::empty(),
1598 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1599 should_override_builder: true,
1600 });
1601 }
1602
1603 #[test]
1604 fn shanghai_built_payload_has_no_builder_override() {
1605 let payload = payload_v2();
1606 let payload_len = payload.ssz_bytes_len();
1607 let value = BuiltPayloadShanghai { payload, block_value: U256::from(1) };
1608 let encoded = value.as_ssz_bytes();
1609
1610 assert_eq!(&encoded[..4], &36u32.to_le_bytes());
1611 assert_eq!(encoded.len(), 36 + payload_len);
1612 }
1613
1614 #[test]
1615 fn legacy_built_payload_conversions_preserve_fields() {
1616 let shanghai = BuiltPayloadShanghai { payload: payload_v2(), block_value: U256::from(1) };
1617 let legacy = LegacyBuiltPayloadShanghai::from(shanghai.clone());
1618 assert_eq!(BuiltPayloadShanghai::try_from(legacy).unwrap(), shanghai);
1619
1620 let prague = BuiltPayloadPrague {
1621 payload: payload_v3(),
1622 block_value: U256::from(2),
1623 blobs_bundle: BlobsBundleV1::empty(),
1624 execution_requests: Requests::from_requests([Bytes::from_static(&[3, 4])]),
1625 should_override_builder: true,
1626 };
1627 let legacy = LegacyBuiltPayloadPrague::from(prague.clone());
1628 assert_eq!(BuiltPayloadPrague::from(legacy), prague);
1629
1630 let osaka = BuiltPayloadOsaka {
1631 payload: payload_v3(),
1632 block_value: U256::from(5),
1633 blobs_bundle: BlobsBundleV2::empty(),
1634 execution_requests: Requests::from_requests([Bytes::from_static(&[6, 7])]),
1635 should_override_builder: true,
1636 };
1637 let legacy = LegacyBuiltPayloadOsaka::from(osaka.clone());
1638 assert_eq!(BuiltPayloadOsaka::from(legacy), osaka);
1639
1640 let amsterdam = BuiltPayloadAmsterdam {
1641 payload: payload_v4(),
1642 block_value: U256::from(8),
1643 blobs_bundle: BlobsBundleV2::empty(),
1644 execution_requests: Requests::from_requests([Bytes::from_static(&[9, 10])]),
1645 should_override_builder: true,
1646 };
1647 let legacy = LegacyBuiltPayloadAmsterdam::from(amsterdam.clone());
1648 assert_eq!(BuiltPayloadAmsterdam::from(legacy), amsterdam);
1649 }
1650
1651 #[test]
1652 fn legacy_shanghai_built_payload_rejects_paris_payload() {
1653 let legacy = LegacyBuiltPayloadShanghai {
1654 execution_payload: ExecutionPayloadFieldV2::V1(payload_v1()),
1655 block_value: U256::from(1),
1656 };
1657 assert_eq!(
1658 BuiltPayloadShanghai::try_from(legacy),
1659 Err(BuiltPayloadConversionError::UnexpectedPayloadFork("Paris"))
1660 );
1661 }
1662
1663 #[test]
1664 fn prague_requests_precede_should_override_builder() {
1665 let value = BuiltPayloadPrague {
1666 payload: payload_v3(),
1667 block_value: U256::from(1),
1668 blobs_bundle: BlobsBundleV1::empty(),
1669 execution_requests: Requests::from_requests([Bytes::from_static(&[0xaa, 0xbb])]),
1670 should_override_builder: true,
1671 };
1672 let encoded = value.as_ssz_bytes();
1673 assert_eq!(encoded[48], 1);
1674 assert_eq!(&encoded[encoded.len() - 2..], &[0xaa, 0xbb]);
1675 }
1676
1677 #[test]
1678 fn forkchoice_updates_roundtrip() {
1679 let paris = PayloadAttributesParis {
1680 timestamp: 1,
1681 prev_randao: B256::repeat_byte(2),
1682 suggested_fee_recipient: Address::repeat_byte(3),
1683 };
1684 let shanghai = PayloadAttributesShanghai {
1685 timestamp: 1,
1686 prev_randao: B256::repeat_byte(2),
1687 suggested_fee_recipient: Address::repeat_byte(3),
1688 withdrawals: vec![Withdrawal::default()],
1689 };
1690 let amsterdam = PayloadAttributesAmsterdam {
1691 timestamp: 1,
1692 prev_randao: B256::repeat_byte(2),
1693 suggested_fee_recipient: Address::repeat_byte(3),
1694 withdrawals: vec![Withdrawal::default()],
1695 parent_beacon_block_root: B256::repeat_byte(4),
1696 slot_number: 5,
1697 target_gas_limit: 6,
1698 };
1699 assert_roundtrip(&ForkchoiceUpdateParis {
1700 forkchoice_state: state(),
1701 payload_attributes: Optional::some(paris),
1702 });
1703 assert_roundtrip(&ForkchoiceUpdateShanghai {
1704 forkchoice_state: state(),
1705 payload_attributes: Optional::some(shanghai),
1706 });
1707 assert_roundtrip(&ForkchoiceUpdateCancun {
1708 forkchoice_state: state(),
1709 payload_attributes: Optional::some(attributes_cancun()),
1710 });
1711 assert_roundtrip(&ForkchoiceUpdatePrague {
1712 forkchoice_state: state(),
1713 payload_attributes: Optional::some(attributes_cancun()),
1714 });
1715 assert_roundtrip(&ForkchoiceUpdateOsaka {
1716 forkchoice_state: state(),
1717 payload_attributes: Optional::some(attributes_cancun()),
1718 });
1719 assert_roundtrip(&ForkchoiceUpdateAmsterdam {
1720 forkchoice_state: state(),
1721 payload_attributes: Optional::some(amsterdam),
1722 custody_columns: Optional::some(B128::repeat_byte(0xa5)),
1723 });
1724 }
1725
1726 #[test]
1727 fn payload_attributes_legacy_conversions_preserve_fork_shape() {
1728 let cancun = attributes_cancun();
1729 let legacy = LegacyPayloadAttributes::from(cancun.clone());
1730 assert_eq!(PayloadAttributesCancun::try_from(legacy).unwrap(), cancun);
1731
1732 let amsterdam = PayloadAttributesAmsterdam {
1733 timestamp: 1,
1734 prev_randao: B256::repeat_byte(2),
1735 suggested_fee_recipient: Address::repeat_byte(3),
1736 withdrawals: vec![Withdrawal::default()],
1737 parent_beacon_block_root: B256::repeat_byte(4),
1738 slot_number: 5,
1739 target_gas_limit: 6,
1740 };
1741 let legacy = LegacyPayloadAttributes::from(amsterdam.clone());
1742 assert_eq!(PayloadAttributesAmsterdam::try_from(legacy).unwrap(), amsterdam);
1743 }
1744
1745 #[test]
1746 fn payload_attributes_legacy_conversions_reject_loss() {
1747 let mut legacy = LegacyPayloadAttributes::default();
1748 assert_eq!(
1749 PayloadAttributesShanghai::try_from(legacy.clone()),
1750 Err(PayloadAttributesConversionError::MissingField("withdrawals"))
1751 );
1752
1753 legacy.withdrawals = Some(vec![]);
1754 legacy.parent_beacon_block_root = Some(B256::ZERO);
1755 assert_eq!(
1756 PayloadAttributesShanghai::try_from(legacy),
1757 Err(PayloadAttributesConversionError::UnexpectedField("parent_beacon_block_root"))
1758 );
1759 }
1760
1761 #[test]
1762 fn every_payload_status_roundtrips() {
1763 for status in [
1764 PayloadStatusEnum::Valid,
1765 PayloadStatusEnum::Invalid { validation_error: "invalid".into() },
1766 PayloadStatusEnum::Syncing,
1767 PayloadStatusEnum::Accepted,
1768 ] {
1769 let validation_error = legacy_validation_error(&status).unwrap();
1770 let value = PayloadStatus {
1771 status,
1772 latest_valid_hash: Optional::some(B256::ZERO),
1773 validation_error,
1774 };
1775 assert_eq!(PayloadStatus::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), value);
1776 }
1777 }
1778
1779 #[test]
1780 fn payload_status_preserves_absent_invalid_validation_error() {
1781 let mut bytes = Vec::new();
1782 let mut encoder = ssz::SszEncoder::container(&mut bytes, 9);
1783 encoder.append(&1u8);
1784 encoder.append(&Optional::<B256>::none());
1785 encoder.append(&Optional::<ErrorBytes>::none());
1786 encoder.finalize();
1787
1788 let decoded = PayloadStatus::from_ssz_bytes(&bytes).unwrap();
1789 assert!(decoded.validation_error.is_none());
1790 assert_eq!(decoded.as_ssz_bytes(), bytes);
1791 }
1792
1793 #[test]
1794 fn payload_status_rejects_non_invalid_validation_error() {
1795 let mut bytes = Vec::new();
1796 let mut encoder = ssz::SszEncoder::container(&mut bytes, 9);
1797 encoder.append(&0u8);
1798 encoder.append(&Optional::<B256>::none());
1799 encoder.append(&Optional::some(Vec::<u8>::new()));
1800 encoder.finalize();
1801 assert!(PayloadStatus::from_ssz_bytes(&bytes).is_err());
1802 }
1803
1804 #[test]
1805 fn payload_status_legacy_conversion_rejects_oversized_error() {
1806 assert!(PayloadStatus::try_from(LegacyPayloadStatus {
1807 status: PayloadStatusEnum::Invalid { validation_error: "x".repeat(1025) },
1808 latest_valid_hash: None,
1809 })
1810 .is_err());
1811 }
1812
1813 #[test]
1814 fn forkchoice_response_distinguishes_absent_and_zero_payload_id() {
1815 let status = PayloadStatus {
1816 status: PayloadStatusEnum::Valid,
1817 latest_valid_hash: Optional::none(),
1818 validation_error: Optional::none(),
1819 };
1820 let none = ForkchoiceUpdateResponse {
1821 payload_status: status.clone(),
1822 payload_id: Optional::none(),
1823 };
1824 let zero = ForkchoiceUpdateResponse {
1825 payload_status: status,
1826 payload_id: Optional::some(PayloadId::default()),
1827 };
1828
1829 assert_ne!(none.as_ssz_bytes(), zero.as_ssz_bytes());
1830 assert_roundtrip(&none);
1831 assert_roundtrip(&zero);
1832 }
1833
1834 #[test]
1835 fn forkchoice_conversion_rejects_accepted() {
1836 let legacy = LegacyForkchoice::from_status(PayloadStatusEnum::Accepted);
1837 assert_eq!(
1838 ForkchoiceUpdateResponse::try_from(legacy),
1839 Err(ConversionError::AcceptedForkchoice)
1840 );
1841 }
1842
1843 fn blob_v2(byte: u8) -> BlobAndProofV2 {
1844 BlobAndProofV2 {
1845 blob: Box::new(Blob::repeat_byte(byte)),
1846 proofs: vec![Bytes48::repeat_byte(byte)],
1847 }
1848 }
1849
1850 #[test]
1851 fn blob_requests_are_single_field_containers() {
1852 let request = BlobsV1Request { versioned_hashes: vec![B256::repeat_byte(0x42)] };
1853 let encoded = request.as_ssz_bytes();
1854
1855 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1856 assert_eq!(&encoded[4..], B256::repeat_byte(0x42).as_slice());
1857 assert_eq!(BlobsV1Request::from_ssz_bytes(&encoded).unwrap(), request);
1858
1859 let _: BlobsV2Request = BlobsV2Request::from_ssz_bytes(&encoded).unwrap();
1860 let _: BlobsV3Request = BlobsV3Request::from_ssz_bytes(&encoded).unwrap();
1861 }
1862
1863 #[test]
1864 fn blob_v4_request_roundtrips_bitvector() {
1865 let request = BlobsV4Request {
1866 versioned_hashes: vec![B256::repeat_byte(0x11)],
1867 indices_bitarray: B128::repeat_byte(0xa5),
1868 };
1869
1870 assert_roundtrip(&request);
1871 }
1872
1873 #[test]
1874 fn blob_response_conversions_preserve_availability_and_order() {
1875 let v1 = BlobsV1Response::try_from(vec![None]).unwrap();
1876 assert!(!v1.entries[0].available);
1877 assert_eq!(v1.entries[0].contents, zero_blob_v1());
1878
1879 let v2 = BlobsV2Response::try_from(vec![blob_v2(1), blob_v2(2)]).unwrap();
1880 assert!(v2.entries.iter().all(|entry| entry.available));
1881
1882 let v3 = BlobsV3Response::try_from(vec![Some(blob_v2(1)), None, Some(blob_v2(3))]).unwrap();
1883 assert_eq!(
1884 v3.entries.iter().map(|entry| entry.available).collect::<Vec<_>>(),
1885 [true, false, true]
1886 );
1887 assert_eq!(v3.entries[2].contents.blob.as_slice(), Blob::repeat_byte(3).as_slice());
1888
1889 let legacy_partial = BlobCellsAndProofsV1 {
1890 blob_cells: vec![Some(Cell::repeat_byte(1)), None],
1891 proofs: vec![Some(Bytes48::repeat_byte(2)), None],
1892 };
1893 let v4 = BlobsV4Response::try_from(vec![None, Some(legacy_partial)]).unwrap();
1894 assert!(!v4.entries[0].available);
1895 assert!(v4.entries[1].available);
1896 assert!(v4.entries[1].contents.blob_cells[0].is_some());
1897 assert!(v4.entries[1].contents.proofs[1].is_none());
1898 }
1899
1900 #[test]
1901 fn blob_cells_and_proofs_uses_rest_optional() {
1902 let value = BlobCellsAndProofs {
1903 blob_cells: vec![Optional::some(Cell::repeat_byte(1))],
1904 proofs: vec![Optional::some(Bytes48::repeat_byte(2))],
1905 };
1906 let encoded = value.as_ssz_bytes();
1907
1908 assert_eq!(BlobCellsAndProofs::from_ssz_bytes(&encoded).unwrap(), value);
1909 assert!(!encoded[8..].starts_with(&[1, 0, 0, 0]));
1910 }
1911
1912 #[test]
1913 fn payload_body_requests_are_single_field_containers() {
1914 let request = BodiesByHashRequest { block_hashes: vec![B256::repeat_byte(0x33)] };
1915 let encoded = request.as_ssz_bytes();
1916
1917 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1918 assert_eq!(&encoded[4..], B256::repeat_byte(0x33).as_slice());
1919 assert_eq!(BodiesByHashRequest::from_ssz_bytes(&encoded).unwrap(), request);
1920 }
1921
1922 #[test]
1923 fn payload_body_responses_preserve_availability() {
1924 let legacy = LegacyExecutionPayloadBodyV1 {
1925 transactions: vec![Bytes::from_static(&[1, 2, 3])],
1926 withdrawals: Some(vec![Withdrawal::default()]),
1927 };
1928 let response =
1929 BodiesResponseShanghai::from_optional_bodies(vec![Some(legacy), None], |body| {
1930 ExecutionPayloadBodyShanghai::try_from(body).ok()
1931 });
1932
1933 assert!(response.entries[0].available);
1934 assert!(!response.entries[1].available);
1935 assert_roundtrip(&response);
1936 }
1937
1938 #[test]
1939 fn witness_response_roundtrips_when_status_is_valid() {
1940 let payload_status = PayloadStatus {
1941 status: PayloadStatusEnum::Valid,
1942 latest_valid_hash: Optional::none(),
1943 validation_error: Optional::none(),
1944 };
1945 let witness = ExecutionWitnessV1 {
1946 state: vec![vec![1, 2, 3].into()],
1947 codes: vec![vec![4, 5].into()],
1948 headers: vec![vec![6].into()],
1949 };
1950 let response = PayloadStatusWithWitness::new(payload_status, Some(witness));
1951
1952 assert_roundtrip(&response);
1953 }
1954
1955 #[test]
1956 fn witness_response_omits_witness_for_non_valid_status() {
1957 let payload_status = PayloadStatus {
1958 status: PayloadStatusEnum::Syncing,
1959 latest_valid_hash: Optional::none(),
1960 validation_error: Optional::none(),
1961 };
1962 let response =
1963 PayloadStatusWithWitness::new(payload_status, Some(ExecutionWitnessV1::default()));
1964
1965 assert!(response.witness.is_none());
1966 assert_roundtrip(&response);
1967 }
1968
1969 #[test]
1970 fn optional_rejects_more_than_one_value() {
1971 assert!(Optional::<B256>::from_ssz_bytes(&[0; 64]).is_err());
1972 }
1973}