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 target_gas_limit: None,
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 target_gas_limit: None,
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 target_gas_limit: None,
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 fn from(value: PayloadAttributesAmsterdam) -> Self {
580 Self {
581 timestamp: value.timestamp,
582 prev_randao: value.prev_randao,
583 suggested_fee_recipient: value.suggested_fee_recipient,
584 withdrawals: Some(value.withdrawals),
585 parent_beacon_block_root: Some(value.parent_beacon_block_root),
586 slot_number: Some(value.slot_number),
587 target_gas_limit: Some(value.target_gas_limit),
588 }
589 }
590}
591
592impl TryFrom<LegacyPayloadAttributes> for PayloadAttributesAmsterdam {
593 type Error = PayloadAttributesConversionError;
594
595 fn try_from(value: LegacyPayloadAttributes) -> Result<Self, Self::Error> {
596 Ok(Self {
597 timestamp: value.timestamp,
598 prev_randao: value.prev_randao,
599 suggested_fee_recipient: value.suggested_fee_recipient,
600 withdrawals: require(value.withdrawals, "withdrawals")?,
601 parent_beacon_block_root: require(
602 value.parent_beacon_block_root,
603 "parent_beacon_block_root",
604 )?,
605 slot_number: require(value.slot_number, "slot_number")?,
606 target_gas_limit: require(value.target_gas_limit, "target_gas_limit")?,
607 })
608 }
609}
610
611#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
615pub struct BuiltPayloadParis {
616 pub payload: ExecutionPayloadParis,
618 pub block_value: U256,
620}
621
622#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
628pub struct BuiltPayloadShanghai {
629 pub payload: ExecutionPayloadShanghai,
631 pub block_value: U256,
633}
634
635pub type BuiltPayloadCancun = alloy_rpc_types_engine::ExecutionPayloadEnvelopeV3;
639
640#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
645pub struct BuiltPayloadPrague {
646 pub payload: ExecutionPayloadPrague,
648 pub block_value: U256,
650 pub blobs_bundle: BlobsBundleV1,
652 pub execution_requests: Requests,
654 pub should_override_builder: bool,
657}
658
659#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
664pub struct BuiltPayloadOsaka {
665 pub payload: ExecutionPayloadOsaka,
667 pub block_value: U256,
669 pub blobs_bundle: BlobsBundleV2,
671 pub execution_requests: Requests,
673 pub should_override_builder: bool,
676}
677
678#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
683pub struct BuiltPayloadAmsterdam {
684 pub payload: ExecutionPayloadAmsterdam,
686 pub block_value: U256,
688 pub blobs_bundle: BlobsBundleV2,
690 pub execution_requests: Requests,
692 pub should_override_builder: bool,
695}
696
697#[derive(Clone, Copy, Debug, PartialEq, Eq)]
699pub enum BuiltPayloadConversionError {
700 UnexpectedPayloadFork(&'static str),
702}
703
704impl core::fmt::Display for BuiltPayloadConversionError {
705 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
706 match self {
707 Self::UnexpectedPayloadFork(fork) => {
708 write!(f, "unexpected execution payload fork: {fork}")
709 }
710 }
711 }
712}
713
714impl core::error::Error for BuiltPayloadConversionError {}
715
716impl From<BuiltPayloadShanghai> for LegacyBuiltPayloadShanghai {
717 fn from(value: BuiltPayloadShanghai) -> Self {
718 Self {
719 execution_payload: ExecutionPayloadFieldV2::V2(value.payload),
720 block_value: value.block_value,
721 }
722 }
723}
724
725impl TryFrom<LegacyBuiltPayloadShanghai> for BuiltPayloadShanghai {
726 type Error = BuiltPayloadConversionError;
727
728 fn try_from(value: LegacyBuiltPayloadShanghai) -> Result<Self, Self::Error> {
729 match value.execution_payload {
730 ExecutionPayloadFieldV2::V2(payload) => {
731 Ok(Self { payload, block_value: value.block_value })
732 }
733 ExecutionPayloadFieldV2::V1(_) => {
734 Err(BuiltPayloadConversionError::UnexpectedPayloadFork("Paris"))
735 }
736 }
737 }
738}
739
740impl From<LegacyBuiltPayloadPrague> for BuiltPayloadPrague {
741 fn from(value: LegacyBuiltPayloadPrague) -> Self {
742 Self {
743 payload: value.envelope_inner.execution_payload,
744 block_value: value.envelope_inner.block_value,
745 blobs_bundle: value.envelope_inner.blobs_bundle,
746 execution_requests: value.execution_requests,
747 should_override_builder: value.envelope_inner.should_override_builder,
748 }
749 }
750}
751
752impl From<BuiltPayloadPrague> for LegacyBuiltPayloadPrague {
753 fn from(value: BuiltPayloadPrague) -> Self {
754 Self {
755 envelope_inner: alloy_rpc_types_engine::ExecutionPayloadEnvelopeV3 {
756 execution_payload: value.payload,
757 block_value: value.block_value,
758 blobs_bundle: value.blobs_bundle,
759 should_override_builder: value.should_override_builder,
760 },
761 execution_requests: value.execution_requests,
762 }
763 }
764}
765
766impl From<LegacyBuiltPayloadOsaka> for BuiltPayloadOsaka {
767 fn from(value: LegacyBuiltPayloadOsaka) -> Self {
768 Self {
769 payload: value.execution_payload,
770 block_value: value.block_value,
771 blobs_bundle: value.blobs_bundle,
772 execution_requests: value.execution_requests,
773 should_override_builder: value.should_override_builder,
774 }
775 }
776}
777
778impl From<BuiltPayloadOsaka> for LegacyBuiltPayloadOsaka {
779 fn from(value: BuiltPayloadOsaka) -> Self {
780 Self {
781 execution_payload: value.payload,
782 block_value: value.block_value,
783 blobs_bundle: value.blobs_bundle,
784 should_override_builder: value.should_override_builder,
785 execution_requests: value.execution_requests,
786 }
787 }
788}
789
790impl From<LegacyBuiltPayloadAmsterdam> for BuiltPayloadAmsterdam {
791 fn from(value: LegacyBuiltPayloadAmsterdam) -> Self {
792 Self {
793 payload: value.execution_payload,
794 block_value: value.block_value,
795 blobs_bundle: value.blobs_bundle,
796 execution_requests: value.execution_requests,
797 should_override_builder: value.should_override_builder,
798 }
799 }
800}
801
802impl From<BuiltPayloadAmsterdam> for LegacyBuiltPayloadAmsterdam {
803 fn from(value: BuiltPayloadAmsterdam) -> Self {
804 Self {
805 execution_payload: value.payload,
806 block_value: value.block_value,
807 blobs_bundle: value.blobs_bundle,
808 should_override_builder: value.should_override_builder,
809 execution_requests: value.execution_requests,
810 }
811 }
812}
813
814#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
826pub struct ExecutionPayloadEnvelopeParis {
827 pub payload: ExecutionPayloadParis,
829}
830
831#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
835pub struct ExecutionPayloadEnvelopeShanghai {
836 pub payload: ExecutionPayloadShanghai,
838}
839
840#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
844pub struct ExecutionPayloadEnvelopeCancun {
845 pub payload: ExecutionPayloadCancun,
847 pub parent_beacon_block_root: B256,
849}
850
851#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
855pub struct ExecutionPayloadEnvelopePrague {
856 pub payload: ExecutionPayloadPrague,
858 pub parent_beacon_block_root: B256,
860 pub execution_requests: Requests,
862}
863
864#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
868pub struct ExecutionPayloadEnvelopeOsaka {
869 pub payload: ExecutionPayloadOsaka,
871 pub parent_beacon_block_root: B256,
873 pub execution_requests: Requests,
875}
876
877#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
881pub struct ExecutionPayloadEnvelopeAmsterdam {
882 pub payload: ExecutionPayloadAmsterdam,
884 pub parent_beacon_block_root: B256,
886 pub execution_requests: Requests,
888}
889
890impl From<ExecutionPayloadParis> for ExecutionPayloadEnvelopeParis {
891 fn from(payload: ExecutionPayloadParis) -> Self {
892 Self { payload }
893 }
894}
895
896impl From<ExecutionPayloadShanghai> for ExecutionPayloadEnvelopeShanghai {
897 fn from(payload: ExecutionPayloadShanghai) -> Self {
898 Self { payload }
899 }
900}
901
902impl From<(ExecutionPayloadCancun, B256)> for ExecutionPayloadEnvelopeCancun {
903 fn from((payload, parent_beacon_block_root): (ExecutionPayloadCancun, B256)) -> Self {
904 Self { payload, parent_beacon_block_root }
905 }
906}
907
908impl From<(ExecutionPayloadPrague, B256, Requests)> for ExecutionPayloadEnvelopePrague {
909 fn from(
910 (payload, parent_beacon_block_root, execution_requests): (
911 ExecutionPayloadPrague,
912 B256,
913 Requests,
914 ),
915 ) -> Self {
916 Self { payload, parent_beacon_block_root, execution_requests }
917 }
918}
919
920impl From<(ExecutionPayloadOsaka, B256, Requests)> for ExecutionPayloadEnvelopeOsaka {
921 fn from(
922 (payload, parent_beacon_block_root, execution_requests): (
923 ExecutionPayloadOsaka,
924 B256,
925 Requests,
926 ),
927 ) -> Self {
928 Self { payload, parent_beacon_block_root, execution_requests }
929 }
930}
931
932impl From<(ExecutionPayloadAmsterdam, B256, Requests)> for ExecutionPayloadEnvelopeAmsterdam {
933 fn from(
934 (payload, parent_beacon_block_root, execution_requests): (
935 ExecutionPayloadAmsterdam,
936 B256,
937 Requests,
938 ),
939 ) -> Self {
940 Self { payload, parent_beacon_block_root, execution_requests }
941 }
942}
943
944#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
949pub struct ForkchoiceUpdateParis {
950 pub forkchoice_state: ForkchoiceState,
952 pub payload_attributes: Optional<PayloadAttributesParis>,
954}
955
956#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
958pub struct ForkchoiceUpdateShanghai {
959 pub forkchoice_state: ForkchoiceState,
961 pub payload_attributes: Optional<PayloadAttributesShanghai>,
963}
964
965#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
967pub struct ForkchoiceUpdateCancun {
968 pub forkchoice_state: ForkchoiceState,
970 pub payload_attributes: Optional<PayloadAttributesCancun>,
972}
973
974#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
976pub struct ForkchoiceUpdatePrague {
977 pub forkchoice_state: ForkchoiceState,
979 pub payload_attributes: Optional<PayloadAttributesPrague>,
981}
982
983#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
985pub struct ForkchoiceUpdateOsaka {
986 pub forkchoice_state: ForkchoiceState,
988 pub payload_attributes: Optional<PayloadAttributesOsaka>,
990}
991
992#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
994pub struct ForkchoiceUpdateAmsterdam {
995 pub forkchoice_state: ForkchoiceState,
997 pub payload_attributes: Optional<PayloadAttributesAmsterdam>,
999 pub custody_columns: Optional<B128>,
1001}
1002
1003#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1007pub struct ExecutionPayloadBodyParis {
1008 pub transactions: Vec<Bytes>,
1010}
1011
1012#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1016pub struct ExecutionPayloadBodyShanghai {
1017 pub transactions: Vec<Bytes>,
1019 pub withdrawals: Vec<Withdrawal>,
1021}
1022
1023pub type ExecutionPayloadBodyCancun = ExecutionPayloadBodyShanghai;
1025
1026pub type ExecutionPayloadBodyPrague = ExecutionPayloadBodyShanghai;
1028
1029pub type ExecutionPayloadBodyOsaka = ExecutionPayloadBodyShanghai;
1031
1032#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1036pub struct ExecutionPayloadBodyAmsterdam {
1037 pub transactions: Vec<Bytes>,
1039 pub withdrawals: Vec<Withdrawal>,
1041 pub block_access_list: Bytes,
1043}
1044
1045#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1047pub enum ExecutionPayloadBodyConversionError {
1048 MissingField(&'static str),
1050 UnexpectedField(&'static str),
1052}
1053
1054impl core::fmt::Display for ExecutionPayloadBodyConversionError {
1055 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
1056 match self {
1057 Self::MissingField(field) => {
1058 write!(f, "missing required execution payload body field: {field}")
1059 }
1060 Self::UnexpectedField(field) => {
1061 write!(f, "unexpected later-fork execution payload body field: {field}")
1062 }
1063 }
1064 }
1065}
1066
1067impl core::error::Error for ExecutionPayloadBodyConversionError {}
1068
1069impl From<ExecutionPayloadBodyParis> for LegacyExecutionPayloadBodyV1 {
1070 fn from(value: ExecutionPayloadBodyParis) -> Self {
1071 Self { transactions: value.transactions, withdrawals: None }
1072 }
1073}
1074
1075impl TryFrom<LegacyExecutionPayloadBodyV1> for ExecutionPayloadBodyParis {
1076 type Error = ExecutionPayloadBodyConversionError;
1077
1078 fn try_from(value: LegacyExecutionPayloadBodyV1) -> Result<Self, Self::Error> {
1079 if value.withdrawals.is_some() {
1080 return Err(ExecutionPayloadBodyConversionError::UnexpectedField("withdrawals"))
1081 }
1082 Ok(Self { transactions: value.transactions })
1083 }
1084}
1085
1086impl From<ExecutionPayloadBodyShanghai> for LegacyExecutionPayloadBodyV1 {
1087 fn from(value: ExecutionPayloadBodyShanghai) -> Self {
1088 Self { transactions: value.transactions, withdrawals: Some(value.withdrawals) }
1089 }
1090}
1091
1092impl TryFrom<LegacyExecutionPayloadBodyV1> for ExecutionPayloadBodyShanghai {
1093 type Error = ExecutionPayloadBodyConversionError;
1094
1095 fn try_from(value: LegacyExecutionPayloadBodyV1) -> Result<Self, Self::Error> {
1096 Ok(Self {
1097 transactions: value.transactions,
1098 withdrawals: value
1099 .withdrawals
1100 .ok_or(ExecutionPayloadBodyConversionError::MissingField("withdrawals"))?,
1101 })
1102 }
1103}
1104
1105impl From<ExecutionPayloadBodyAmsterdam> for LegacyExecutionPayloadBodyV2 {
1106 fn from(value: ExecutionPayloadBodyAmsterdam) -> Self {
1107 Self {
1108 transactions: value.transactions,
1109 withdrawals: Some(value.withdrawals),
1110 block_access_list: Some(value.block_access_list),
1111 }
1112 }
1113}
1114
1115impl TryFrom<LegacyExecutionPayloadBodyV2> for ExecutionPayloadBodyAmsterdam {
1116 type Error = ExecutionPayloadBodyConversionError;
1117
1118 fn try_from(value: LegacyExecutionPayloadBodyV2) -> Result<Self, Self::Error> {
1119 Ok(Self {
1120 transactions: value.transactions,
1121 withdrawals: value
1122 .withdrawals
1123 .ok_or(ExecutionPayloadBodyConversionError::MissingField("withdrawals"))?,
1124 block_access_list: value
1125 .block_access_list
1126 .ok_or(ExecutionPayloadBodyConversionError::MissingField("block_access_list"))?,
1127 })
1128 }
1129}
1130
1131#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1135pub struct BodiesByHashRequest {
1136 pub block_hashes: Vec<B256>,
1138}
1139
1140#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1144pub struct BodyEntry<T: ssz::Encode + ssz::Decode> {
1145 pub available: bool,
1147 pub body: T,
1149}
1150
1151impl<T: ssz::Encode + ssz::Decode> BodyEntry<T> {
1152 pub const fn available(body: T) -> Self {
1154 Self { available: true, body }
1155 }
1156}
1157
1158impl<T: ssz::Encode + ssz::Decode + Default> BodyEntry<T> {
1159 pub fn unavailable() -> Self {
1161 Self { available: false, body: T::default() }
1162 }
1163}
1164
1165#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1169pub struct BodiesResponse<T: ssz::Encode + ssz::Decode> {
1170 pub entries: Vec<BodyEntry<T>>,
1172}
1173
1174impl<T: ssz::Encode + ssz::Decode + Default> BodiesResponse<T> {
1175 pub fn from_optional_bodies<LegacyBody>(
1180 bodies: Vec<Option<LegacyBody>>,
1181 convert: impl Fn(LegacyBody) -> Option<T>,
1182 ) -> Self {
1183 let entries = bodies
1184 .into_iter()
1185 .map(|body| match body.and_then(&convert) {
1186 Some(body) => BodyEntry::available(body),
1187 None => BodyEntry::unavailable(),
1188 })
1189 .collect();
1190
1191 Self { entries }
1192 }
1193}
1194
1195pub type BodiesResponseParis = BodiesResponse<ExecutionPayloadBodyParis>;
1197
1198pub type BodiesResponseShanghai = BodiesResponse<ExecutionPayloadBodyShanghai>;
1200
1201pub type BodiesResponseCancun = BodiesResponse<ExecutionPayloadBodyCancun>;
1203
1204pub type BodiesResponsePrague = BodiesResponse<ExecutionPayloadBodyPrague>;
1206
1207pub type BodiesResponseOsaka = BodiesResponse<ExecutionPayloadBodyOsaka>;
1209
1210pub type BodiesResponseAmsterdam = BodiesResponse<ExecutionPayloadBodyAmsterdam>;
1212
1213#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1218pub struct BlobsV1Request {
1219 pub versioned_hashes: Vec<B256>,
1221}
1222
1223pub type BlobsV2Request = BlobsV1Request;
1225
1226pub type BlobsV3Request = BlobsV1Request;
1228
1229#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1231pub struct BlobsV4Request {
1232 pub versioned_hashes: Vec<B256>,
1234 pub indices_bitarray: B128,
1236}
1237
1238#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1242pub struct BlobEntry<T: ssz::Encode + ssz::Decode> {
1243 pub available: bool,
1245 pub contents: T,
1247}
1248
1249#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1253pub struct BlobsResponse<T: ssz::Encode + ssz::Decode> {
1254 pub entries: Vec<BlobEntry<T>>,
1256}
1257
1258pub type BlobsV1Response = BlobsResponse<BlobAndProofV1>;
1260
1261pub type BlobsV2Response = BlobsResponse<BlobAndProofV2>;
1263
1264pub type BlobsV3Response = BlobsResponse<BlobAndProofV2>;
1266
1267pub type BlobsV4Response = BlobsResponse<BlobCellsAndProofs>;
1269
1270#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1275pub struct BlobCellsAndProofs {
1276 pub blob_cells: Vec<Optional<Cell>>,
1278 pub proofs: Vec<Optional<Bytes48>>,
1280}
1281
1282fn zero_blob_v1() -> BlobAndProofV1 {
1283 BlobAndProofV1 { blob: Box::new(Blob::ZERO), proof: Bytes48::ZERO }
1284}
1285
1286fn zero_blob_v2() -> BlobAndProofV2 {
1287 BlobAndProofV2 { blob: Box::new(Blob::ZERO), proofs: Vec::new() }
1288}
1289
1290impl TryFrom<Vec<Option<BlobAndProofV1>>> for BlobsV1Response {
1291 type Error = ConversionError;
1292
1293 fn try_from(value: Vec<Option<BlobAndProofV1>>) -> Result<Self, Self::Error> {
1294 if value.len() > MAX_BLOBS_REQUEST {
1295 return Err(ConversionError::TooManyItems {
1296 field: "blobs",
1297 max: MAX_BLOBS_REQUEST,
1298 actual: value.len(),
1299 })
1300 }
1301
1302 let entries = value
1303 .into_iter()
1304 .map(|value| match value {
1305 Some(contents) => BlobEntry { available: true, contents },
1306 None => BlobEntry { available: false, contents: zero_blob_v1() },
1307 })
1308 .collect();
1309 Ok(Self { entries })
1310 }
1311}
1312
1313impl TryFrom<Vec<BlobAndProofV2>> for BlobsV2Response {
1314 type Error = ConversionError;
1315
1316 fn try_from(value: Vec<BlobAndProofV2>) -> Result<Self, Self::Error> {
1317 if value.len() > MAX_BLOBS_REQUEST {
1318 return Err(ConversionError::TooManyItems {
1319 field: "blobs",
1320 max: MAX_BLOBS_REQUEST,
1321 actual: value.len(),
1322 })
1323 }
1324
1325 let entries =
1326 value.into_iter().map(|contents| BlobEntry { available: true, contents }).collect();
1327 Ok(Self { entries })
1328 }
1329}
1330
1331impl TryFrom<Vec<Option<BlobAndProofV2>>> for BlobsV3Response {
1332 type Error = ConversionError;
1333
1334 fn try_from(value: Vec<Option<BlobAndProofV2>>) -> Result<Self, Self::Error> {
1335 if value.len() > MAX_BLOBS_REQUEST {
1336 return Err(ConversionError::TooManyItems {
1337 field: "blobs",
1338 max: MAX_BLOBS_REQUEST,
1339 actual: value.len(),
1340 })
1341 }
1342
1343 let entries = value
1344 .into_iter()
1345 .map(|value| match value {
1346 Some(contents) => BlobEntry { available: true, contents },
1347 None => BlobEntry { available: false, contents: zero_blob_v2() },
1348 })
1349 .collect();
1350 Ok(Self { entries })
1351 }
1352}
1353
1354impl TryFrom<Vec<Option<BlobCellsAndProofsV1>>> for BlobsV4Response {
1355 type Error = ConversionError;
1356
1357 fn try_from(value: Vec<Option<BlobCellsAndProofsV1>>) -> Result<Self, Self::Error> {
1358 if value.len() > MAX_BLOBS_REQUEST {
1359 return Err(ConversionError::TooManyItems {
1360 field: "blobs",
1361 max: MAX_BLOBS_REQUEST,
1362 actual: value.len(),
1363 })
1364 }
1365
1366 let entries = value
1367 .into_iter()
1368 .map(|value| match value {
1369 Some(contents) => BlobEntry {
1370 available: true,
1371 contents: BlobCellsAndProofs {
1372 blob_cells: contents.blob_cells.into_iter().map(Optional::from).collect(),
1373 proofs: contents.proofs.into_iter().map(Optional::from).collect(),
1374 },
1375 },
1376 None => BlobEntry { available: false, contents: BlobCellsAndProofs::default() },
1377 })
1378 .collect();
1379 Ok(Self { entries })
1380 }
1381}
1382
1383pub type WitnessNodeV1 = Vec<u8>;
1385
1386pub type WitnessCodeV1 = Vec<u8>;
1388
1389pub type WitnessHeaderV1 = Vec<u8>;
1391
1392#[derive(Clone, Debug, Default, PartialEq, Eq, ssz_derive::Encode, ssz_derive::Decode)]
1400pub struct ExecutionWitnessV1 {
1401 pub state: Vec<WitnessNodeV1>,
1403 pub codes: Vec<WitnessCodeV1>,
1405 pub headers: Vec<WitnessHeaderV1>,
1407}
1408
1409pub type ExecutionWitness = ExecutionWitnessV1;
1411
1412#[derive(Clone, Debug, PartialEq, Eq, ssz_derive::Encode)]
1417pub struct PayloadStatusWithWitness {
1418 pub payload_status: PayloadStatus,
1420 pub witness: Optional<ExecutionWitnessV1>,
1422}
1423
1424impl PayloadStatusWithWitness {
1425 pub fn new(payload_status: PayloadStatus, witness: Option<ExecutionWitnessV1>) -> Self {
1427 let witness = match &payload_status.status {
1428 PayloadStatusEnum::Valid => witness.into(),
1429 _ => Optional::none(),
1430 };
1431 Self { payload_status, witness }
1432 }
1433}
1434
1435pub type NewPayloadWithWitnessResponseV1 = PayloadStatusWithWitness;
1437
1438impl ssz::Decode for PayloadStatusWithWitness {
1439 fn is_ssz_fixed_len() -> bool {
1440 false
1441 }
1442
1443 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
1444 let mut builder = ssz::SszDecoderBuilder::new(bytes);
1445 builder.register_type::<PayloadStatus>()?;
1446 builder.register_type::<Optional<ExecutionWitnessV1>>()?;
1447 let mut decoder = builder.build()?;
1448 let response =
1449 Self { payload_status: decoder.decode_next()?, witness: decoder.decode_next()? };
1450 if response.witness.is_some() &&
1451 !matches!(response.payload_status.status, PayloadStatusEnum::Valid)
1452 {
1453 return Err(ssz::DecodeError::BytesInvalid(
1454 "execution witness is only valid for VALID payload status".into(),
1455 ))
1456 }
1457 Ok(response)
1458 }
1459}
1460
1461#[cfg(test)]
1462mod tests {
1463 use super::*;
1464 use alloy_eips::eip4895::Withdrawal;
1465 use alloy_primitives::{Address, Bloom, Bytes};
1466 use ssz::{Decode, Encode};
1467
1468 fn payload_v1() -> ExecutionPayloadV1 {
1469 ExecutionPayloadV1 {
1470 parent_hash: B256::repeat_byte(1),
1471 fee_recipient: Address::repeat_byte(2),
1472 state_root: B256::repeat_byte(3),
1473 receipts_root: B256::repeat_byte(4),
1474 logs_bloom: Bloom::repeat_byte(5),
1475 prev_randao: B256::repeat_byte(6),
1476 block_number: 7,
1477 gas_limit: 8,
1478 gas_used: 9,
1479 timestamp: 10,
1480 extra_data: Bytes::from_static(&[11, 12]),
1481 base_fee_per_gas: U256::from(13),
1482 block_hash: B256::repeat_byte(14),
1483 transactions: vec![Bytes::from_static(&[15, 16])],
1484 }
1485 }
1486
1487 fn payload_v2() -> ExecutionPayloadV2 {
1488 ExecutionPayloadV2 { payload_inner: payload_v1(), withdrawals: vec![Withdrawal::default()] }
1489 }
1490
1491 fn payload_v3() -> ExecutionPayloadV3 {
1492 ExecutionPayloadV3 { payload_inner: payload_v2(), blob_gas_used: 17, excess_blob_gas: 18 }
1493 }
1494
1495 fn payload_v4() -> ExecutionPayloadV4 {
1496 ExecutionPayloadV4 {
1497 payload_inner: payload_v3(),
1498 block_access_list: Bytes::from_static(&[19, 20]),
1499 slot_number: 21,
1500 }
1501 }
1502
1503 fn attributes_cancun() -> PayloadAttributesCancun {
1504 PayloadAttributesCancun {
1505 timestamp: 1,
1506 prev_randao: B256::repeat_byte(2),
1507 suggested_fee_recipient: Address::repeat_byte(3),
1508 withdrawals: vec![Withdrawal::default()],
1509 parent_beacon_block_root: B256::repeat_byte(4),
1510 }
1511 }
1512
1513 fn state() -> ForkchoiceState {
1514 ForkchoiceState {
1515 head_block_hash: B256::repeat_byte(1),
1516 safe_block_hash: B256::repeat_byte(2),
1517 finalized_block_hash: B256::repeat_byte(3),
1518 }
1519 }
1520
1521 fn assert_roundtrip<T>(value: &T)
1522 where
1523 T: Encode + Decode + PartialEq + core::fmt::Debug,
1524 {
1525 assert_eq!(T::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), *value);
1526 }
1527
1528 #[test]
1529 fn execution_payload_envelopes_roundtrip() {
1530 assert_roundtrip(&ExecutionPayloadEnvelopeParis { payload: payload_v1() });
1531 assert_roundtrip(&ExecutionPayloadEnvelopeShanghai { payload: payload_v2() });
1532 assert_roundtrip(&ExecutionPayloadEnvelopeCancun {
1533 payload: payload_v3(),
1534 parent_beacon_block_root: B256::repeat_byte(1),
1535 });
1536 assert_roundtrip(&ExecutionPayloadEnvelopePrague {
1537 payload: payload_v3(),
1538 parent_beacon_block_root: B256::repeat_byte(1),
1539 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1540 });
1541 assert_roundtrip(&ExecutionPayloadEnvelopeOsaka {
1542 payload: payload_v3(),
1543 parent_beacon_block_root: B256::repeat_byte(1),
1544 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1545 });
1546 assert_roundtrip(&ExecutionPayloadEnvelopeAmsterdam {
1547 payload: payload_v4(),
1548 parent_beacon_block_root: B256::repeat_byte(1),
1549 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1550 });
1551 }
1552
1553 #[test]
1554 fn paris_submission_is_a_single_field_container() {
1555 let payload = payload_v1();
1556 let payload_bytes = payload.as_ssz_bytes();
1557 let envelope = ExecutionPayloadEnvelopeParis { payload };
1558 let encoded = envelope.as_ssz_bytes();
1559 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1560 assert_eq!(&encoded[4..], payload_bytes);
1561 }
1562
1563 #[test]
1564 fn built_payloads_roundtrip() {
1565 assert_roundtrip(&BuiltPayloadParis { payload: payload_v1(), block_value: U256::from(1) });
1566 assert_roundtrip(&BuiltPayloadShanghai {
1567 payload: payload_v2(),
1568 block_value: U256::from(1),
1569 });
1570 assert_roundtrip(&BuiltPayloadCancun {
1571 execution_payload: payload_v3(),
1572 block_value: U256::from(1),
1573 blobs_bundle: BlobsBundleV1::empty(),
1574 should_override_builder: true,
1575 });
1576 assert_roundtrip(&BuiltPayloadPrague {
1577 payload: payload_v3(),
1578 block_value: U256::from(1),
1579 blobs_bundle: BlobsBundleV1::empty(),
1580 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1581 should_override_builder: true,
1582 });
1583 assert_roundtrip(&BuiltPayloadOsaka {
1584 payload: payload_v3(),
1585 block_value: U256::from(1),
1586 blobs_bundle: BlobsBundleV2::empty(),
1587 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1588 should_override_builder: true,
1589 });
1590 assert_roundtrip(&BuiltPayloadAmsterdam {
1591 payload: payload_v4(),
1592 block_value: U256::from(1),
1593 blobs_bundle: BlobsBundleV2::empty(),
1594 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1595 should_override_builder: true,
1596 });
1597 }
1598
1599 #[test]
1600 fn shanghai_built_payload_has_no_builder_override() {
1601 let payload = payload_v2();
1602 let payload_len = payload.ssz_bytes_len();
1603 let value = BuiltPayloadShanghai { payload, block_value: U256::from(1) };
1604 let encoded = value.as_ssz_bytes();
1605
1606 assert_eq!(&encoded[..4], &36u32.to_le_bytes());
1607 assert_eq!(encoded.len(), 36 + payload_len);
1608 }
1609
1610 #[test]
1611 fn legacy_built_payload_conversions_preserve_fields() {
1612 let shanghai = BuiltPayloadShanghai { payload: payload_v2(), block_value: U256::from(1) };
1613 let legacy = LegacyBuiltPayloadShanghai::from(shanghai.clone());
1614 assert_eq!(BuiltPayloadShanghai::try_from(legacy).unwrap(), shanghai);
1615
1616 let prague = BuiltPayloadPrague {
1617 payload: payload_v3(),
1618 block_value: U256::from(2),
1619 blobs_bundle: BlobsBundleV1::empty(),
1620 execution_requests: Requests::from_requests([Bytes::from_static(&[3, 4])]),
1621 should_override_builder: true,
1622 };
1623 let legacy = LegacyBuiltPayloadPrague::from(prague.clone());
1624 assert_eq!(BuiltPayloadPrague::from(legacy), prague);
1625
1626 let osaka = BuiltPayloadOsaka {
1627 payload: payload_v3(),
1628 block_value: U256::from(5),
1629 blobs_bundle: BlobsBundleV2::empty(),
1630 execution_requests: Requests::from_requests([Bytes::from_static(&[6, 7])]),
1631 should_override_builder: true,
1632 };
1633 let legacy = LegacyBuiltPayloadOsaka::from(osaka.clone());
1634 assert_eq!(BuiltPayloadOsaka::from(legacy), osaka);
1635
1636 let amsterdam = BuiltPayloadAmsterdam {
1637 payload: payload_v4(),
1638 block_value: U256::from(8),
1639 blobs_bundle: BlobsBundleV2::empty(),
1640 execution_requests: Requests::from_requests([Bytes::from_static(&[9, 10])]),
1641 should_override_builder: true,
1642 };
1643 let legacy = LegacyBuiltPayloadAmsterdam::from(amsterdam.clone());
1644 assert_eq!(BuiltPayloadAmsterdam::from(legacy), amsterdam);
1645 }
1646
1647 #[test]
1648 fn legacy_shanghai_built_payload_rejects_paris_payload() {
1649 let legacy = LegacyBuiltPayloadShanghai {
1650 execution_payload: ExecutionPayloadFieldV2::V1(payload_v1()),
1651 block_value: U256::from(1),
1652 };
1653 assert_eq!(
1654 BuiltPayloadShanghai::try_from(legacy),
1655 Err(BuiltPayloadConversionError::UnexpectedPayloadFork("Paris"))
1656 );
1657 }
1658
1659 #[test]
1660 fn prague_requests_precede_should_override_builder() {
1661 let value = BuiltPayloadPrague {
1662 payload: payload_v3(),
1663 block_value: U256::from(1),
1664 blobs_bundle: BlobsBundleV1::empty(),
1665 execution_requests: Requests::from_requests([Bytes::from_static(&[0xaa, 0xbb])]),
1666 should_override_builder: true,
1667 };
1668 let encoded = value.as_ssz_bytes();
1669 assert_eq!(encoded[48], 1);
1670 assert_eq!(&encoded[encoded.len() - 2..], &[0xaa, 0xbb]);
1671 }
1672
1673 #[test]
1674 fn forkchoice_updates_roundtrip() {
1675 let paris = PayloadAttributesParis {
1676 timestamp: 1,
1677 prev_randao: B256::repeat_byte(2),
1678 suggested_fee_recipient: Address::repeat_byte(3),
1679 };
1680 let shanghai = PayloadAttributesShanghai {
1681 timestamp: 1,
1682 prev_randao: B256::repeat_byte(2),
1683 suggested_fee_recipient: Address::repeat_byte(3),
1684 withdrawals: vec![Withdrawal::default()],
1685 };
1686 let amsterdam = PayloadAttributesAmsterdam {
1687 timestamp: 1,
1688 prev_randao: B256::repeat_byte(2),
1689 suggested_fee_recipient: Address::repeat_byte(3),
1690 withdrawals: vec![Withdrawal::default()],
1691 parent_beacon_block_root: B256::repeat_byte(4),
1692 slot_number: 5,
1693 target_gas_limit: 6,
1694 };
1695 assert_roundtrip(&ForkchoiceUpdateParis {
1696 forkchoice_state: state(),
1697 payload_attributes: Optional::some(paris),
1698 });
1699 assert_roundtrip(&ForkchoiceUpdateShanghai {
1700 forkchoice_state: state(),
1701 payload_attributes: Optional::some(shanghai),
1702 });
1703 assert_roundtrip(&ForkchoiceUpdateCancun {
1704 forkchoice_state: state(),
1705 payload_attributes: Optional::some(attributes_cancun()),
1706 });
1707 assert_roundtrip(&ForkchoiceUpdatePrague {
1708 forkchoice_state: state(),
1709 payload_attributes: Optional::some(attributes_cancun()),
1710 });
1711 assert_roundtrip(&ForkchoiceUpdateOsaka {
1712 forkchoice_state: state(),
1713 payload_attributes: Optional::some(attributes_cancun()),
1714 });
1715 assert_roundtrip(&ForkchoiceUpdateAmsterdam {
1716 forkchoice_state: state(),
1717 payload_attributes: Optional::some(amsterdam),
1718 custody_columns: Optional::some(B128::repeat_byte(0xa5)),
1719 });
1720 }
1721
1722 #[test]
1723 fn payload_attributes_legacy_conversions_preserve_fork_shape() {
1724 let cancun = attributes_cancun();
1725 let legacy = LegacyPayloadAttributes::from(cancun.clone());
1726 assert_eq!(PayloadAttributesCancun::try_from(legacy).unwrap(), cancun);
1727
1728 let amsterdam = PayloadAttributesAmsterdam {
1729 timestamp: 1,
1730 prev_randao: B256::repeat_byte(2),
1731 suggested_fee_recipient: Address::repeat_byte(3),
1732 withdrawals: vec![Withdrawal::default()],
1733 parent_beacon_block_root: B256::repeat_byte(4),
1734 slot_number: 5,
1735 target_gas_limit: 6,
1736 };
1737 let legacy = LegacyPayloadAttributes::from(amsterdam.clone());
1738 assert_eq!(PayloadAttributesAmsterdam::try_from(legacy).unwrap(), amsterdam);
1739 }
1740
1741 #[test]
1742 fn payload_attributes_legacy_conversions_reject_loss() {
1743 let mut legacy = LegacyPayloadAttributes::default();
1744 assert_eq!(
1745 PayloadAttributesShanghai::try_from(legacy.clone()),
1746 Err(PayloadAttributesConversionError::MissingField("withdrawals"))
1747 );
1748
1749 legacy.withdrawals = Some(vec![]);
1750 legacy.parent_beacon_block_root = Some(B256::ZERO);
1751 assert_eq!(
1752 PayloadAttributesShanghai::try_from(legacy),
1753 Err(PayloadAttributesConversionError::UnexpectedField("parent_beacon_block_root"))
1754 );
1755 }
1756
1757 #[test]
1758 fn every_payload_status_roundtrips() {
1759 for status in [
1760 PayloadStatusEnum::Valid,
1761 PayloadStatusEnum::Invalid { validation_error: "invalid".into() },
1762 PayloadStatusEnum::Syncing,
1763 PayloadStatusEnum::Accepted,
1764 ] {
1765 let validation_error = legacy_validation_error(&status).unwrap();
1766 let value = PayloadStatus {
1767 status,
1768 latest_valid_hash: Optional::some(B256::ZERO),
1769 validation_error,
1770 };
1771 assert_eq!(PayloadStatus::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), value);
1772 }
1773 }
1774
1775 #[test]
1776 fn payload_status_preserves_absent_invalid_validation_error() {
1777 let mut bytes = Vec::new();
1778 let mut encoder = ssz::SszEncoder::container(&mut bytes, 9);
1779 encoder.append(&1u8);
1780 encoder.append(&Optional::<B256>::none());
1781 encoder.append(&Optional::<ErrorBytes>::none());
1782 encoder.finalize();
1783
1784 let decoded = PayloadStatus::from_ssz_bytes(&bytes).unwrap();
1785 assert!(decoded.validation_error.is_none());
1786 assert_eq!(decoded.as_ssz_bytes(), bytes);
1787 }
1788
1789 #[test]
1790 fn payload_status_rejects_non_invalid_validation_error() {
1791 let mut bytes = Vec::new();
1792 let mut encoder = ssz::SszEncoder::container(&mut bytes, 9);
1793 encoder.append(&0u8);
1794 encoder.append(&Optional::<B256>::none());
1795 encoder.append(&Optional::some(Vec::<u8>::new()));
1796 encoder.finalize();
1797 assert!(PayloadStatus::from_ssz_bytes(&bytes).is_err());
1798 }
1799
1800 #[test]
1801 fn payload_status_legacy_conversion_rejects_oversized_error() {
1802 assert!(PayloadStatus::try_from(LegacyPayloadStatus {
1803 status: PayloadStatusEnum::Invalid { validation_error: "x".repeat(1025) },
1804 latest_valid_hash: None,
1805 })
1806 .is_err());
1807 }
1808
1809 #[test]
1810 fn forkchoice_response_distinguishes_absent_and_zero_payload_id() {
1811 let status = PayloadStatus {
1812 status: PayloadStatusEnum::Valid,
1813 latest_valid_hash: Optional::none(),
1814 validation_error: Optional::none(),
1815 };
1816 let none = ForkchoiceUpdateResponse {
1817 payload_status: status.clone(),
1818 payload_id: Optional::none(),
1819 };
1820 let zero = ForkchoiceUpdateResponse {
1821 payload_status: status,
1822 payload_id: Optional::some(PayloadId::default()),
1823 };
1824
1825 assert_ne!(none.as_ssz_bytes(), zero.as_ssz_bytes());
1826 assert_roundtrip(&none);
1827 assert_roundtrip(&zero);
1828 }
1829
1830 #[test]
1831 fn forkchoice_conversion_rejects_accepted() {
1832 let legacy = LegacyForkchoice::from_status(PayloadStatusEnum::Accepted);
1833 assert_eq!(
1834 ForkchoiceUpdateResponse::try_from(legacy),
1835 Err(ConversionError::AcceptedForkchoice)
1836 );
1837 }
1838
1839 fn blob_v2(byte: u8) -> BlobAndProofV2 {
1840 BlobAndProofV2 {
1841 blob: Box::new(Blob::repeat_byte(byte)),
1842 proofs: vec![Bytes48::repeat_byte(byte)],
1843 }
1844 }
1845
1846 #[test]
1847 fn blob_requests_are_single_field_containers() {
1848 let request = BlobsV1Request { versioned_hashes: vec![B256::repeat_byte(0x42)] };
1849 let encoded = request.as_ssz_bytes();
1850
1851 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1852 assert_eq!(&encoded[4..], B256::repeat_byte(0x42).as_slice());
1853 assert_eq!(BlobsV1Request::from_ssz_bytes(&encoded).unwrap(), request);
1854
1855 let _: BlobsV2Request = BlobsV2Request::from_ssz_bytes(&encoded).unwrap();
1856 let _: BlobsV3Request = BlobsV3Request::from_ssz_bytes(&encoded).unwrap();
1857 }
1858
1859 #[test]
1860 fn blob_v4_request_roundtrips_bitvector() {
1861 let request = BlobsV4Request {
1862 versioned_hashes: vec![B256::repeat_byte(0x11)],
1863 indices_bitarray: B128::repeat_byte(0xa5),
1864 };
1865
1866 assert_roundtrip(&request);
1867 }
1868
1869 #[test]
1870 fn blob_response_conversions_preserve_availability_and_order() {
1871 let v1 = BlobsV1Response::try_from(vec![None]).unwrap();
1872 assert!(!v1.entries[0].available);
1873 assert_eq!(v1.entries[0].contents, zero_blob_v1());
1874
1875 let v2 = BlobsV2Response::try_from(vec![blob_v2(1), blob_v2(2)]).unwrap();
1876 assert!(v2.entries.iter().all(|entry| entry.available));
1877
1878 let v3 = BlobsV3Response::try_from(vec![Some(blob_v2(1)), None, Some(blob_v2(3))]).unwrap();
1879 assert_eq!(
1880 v3.entries.iter().map(|entry| entry.available).collect::<Vec<_>>(),
1881 [true, false, true]
1882 );
1883 assert_eq!(v3.entries[2].contents.blob.as_slice(), Blob::repeat_byte(3).as_slice());
1884
1885 let legacy_partial = BlobCellsAndProofsV1 {
1886 blob_cells: vec![Some(Cell::repeat_byte(1)), None],
1887 proofs: vec![Some(Bytes48::repeat_byte(2)), None],
1888 };
1889 let v4 = BlobsV4Response::try_from(vec![None, Some(legacy_partial)]).unwrap();
1890 assert!(!v4.entries[0].available);
1891 assert!(v4.entries[1].available);
1892 assert!(v4.entries[1].contents.blob_cells[0].is_some());
1893 assert!(v4.entries[1].contents.proofs[1].is_none());
1894 }
1895
1896 #[test]
1897 fn blob_cells_and_proofs_uses_rest_optional() {
1898 let value = BlobCellsAndProofs {
1899 blob_cells: vec![Optional::some(Cell::repeat_byte(1))],
1900 proofs: vec![Optional::some(Bytes48::repeat_byte(2))],
1901 };
1902 let encoded = value.as_ssz_bytes();
1903
1904 assert_eq!(BlobCellsAndProofs::from_ssz_bytes(&encoded).unwrap(), value);
1905 assert!(!encoded[8..].starts_with(&[1, 0, 0, 0]));
1906 }
1907
1908 #[test]
1909 fn payload_body_requests_are_single_field_containers() {
1910 let request = BodiesByHashRequest { block_hashes: vec![B256::repeat_byte(0x33)] };
1911 let encoded = request.as_ssz_bytes();
1912
1913 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1914 assert_eq!(&encoded[4..], B256::repeat_byte(0x33).as_slice());
1915 assert_eq!(BodiesByHashRequest::from_ssz_bytes(&encoded).unwrap(), request);
1916 }
1917
1918 #[test]
1919 fn payload_body_responses_preserve_availability() {
1920 let legacy = LegacyExecutionPayloadBodyV1 {
1921 transactions: vec![Bytes::from_static(&[1, 2, 3])],
1922 withdrawals: Some(vec![Withdrawal::default()]),
1923 };
1924 let response =
1925 BodiesResponseShanghai::from_optional_bodies(vec![Some(legacy), None], |body| {
1926 ExecutionPayloadBodyShanghai::try_from(body).ok()
1927 });
1928
1929 assert!(response.entries[0].available);
1930 assert!(!response.entries[1].available);
1931 assert_roundtrip(&response);
1932 }
1933
1934 #[test]
1935 fn witness_response_roundtrips_when_status_is_valid() {
1936 let payload_status = PayloadStatus {
1937 status: PayloadStatusEnum::Valid,
1938 latest_valid_hash: Optional::none(),
1939 validation_error: Optional::none(),
1940 };
1941 let witness = ExecutionWitnessV1 {
1942 state: vec![vec![1, 2, 3]],
1943 codes: vec![vec![4, 5]],
1944 headers: vec![vec![6]],
1945 };
1946 let response = PayloadStatusWithWitness::new(payload_status, Some(witness));
1947
1948 assert_roundtrip(&response);
1949 }
1950
1951 #[test]
1952 fn witness_response_omits_witness_for_non_valid_status() {
1953 let payload_status = PayloadStatus {
1954 status: PayloadStatusEnum::Syncing,
1955 latest_valid_hash: Optional::none(),
1956 validation_error: Optional::none(),
1957 };
1958 let response =
1959 PayloadStatusWithWitness::new(payload_status, Some(ExecutionWitnessV1::default()));
1960
1961 assert!(response.witness.is_none());
1962 assert_roundtrip(&response);
1963 }
1964
1965 #[test]
1966 fn optional_rejects_more_than_one_value() {
1967 assert!(Optional::<B256>::from_ssz_bytes(&[0; 64]).is_err());
1968 }
1969}