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 = Vec<u8>;
1387
1388pub type WitnessCodeV1 = Vec<u8>;
1390
1391pub type WitnessHeaderV1 = Vec<u8>;
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)]
1419pub struct PayloadStatusWithWitness {
1420 pub payload_status: PayloadStatus,
1422 pub witness: Optional<ExecutionWitnessV1>,
1424}
1425
1426impl PayloadStatusWithWitness {
1427 pub fn new(payload_status: PayloadStatus, witness: Option<ExecutionWitnessV1>) -> Self {
1429 let witness = match &payload_status.status {
1430 PayloadStatusEnum::Valid => witness.into(),
1431 _ => Optional::none(),
1432 };
1433 Self { payload_status, witness }
1434 }
1435}
1436
1437pub type NewPayloadWithWitnessResponseV1 = PayloadStatusWithWitness;
1439
1440impl ssz::Decode for PayloadStatusWithWitness {
1441 fn is_ssz_fixed_len() -> bool {
1442 false
1443 }
1444
1445 fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
1446 let mut builder = ssz::SszDecoderBuilder::new(bytes);
1447 builder.register_type::<PayloadStatus>()?;
1448 builder.register_type::<Optional<ExecutionWitnessV1>>()?;
1449 let mut decoder = builder.build()?;
1450 let response =
1451 Self { payload_status: decoder.decode_next()?, witness: decoder.decode_next()? };
1452 if response.witness.is_some() &&
1453 !matches!(response.payload_status.status, PayloadStatusEnum::Valid)
1454 {
1455 return Err(ssz::DecodeError::BytesInvalid(
1456 "execution witness is only valid for VALID payload status".into(),
1457 ))
1458 }
1459 Ok(response)
1460 }
1461}
1462
1463#[cfg(test)]
1464mod tests {
1465 use super::*;
1466 use alloy_eips::eip4895::Withdrawal;
1467 use alloy_primitives::{Address, Bloom, Bytes};
1468 use ssz::{Decode, Encode};
1469
1470 fn payload_v1() -> ExecutionPayloadV1 {
1471 ExecutionPayloadV1 {
1472 parent_hash: B256::repeat_byte(1),
1473 fee_recipient: Address::repeat_byte(2),
1474 state_root: B256::repeat_byte(3),
1475 receipts_root: B256::repeat_byte(4),
1476 logs_bloom: Bloom::repeat_byte(5),
1477 prev_randao: B256::repeat_byte(6),
1478 block_number: 7,
1479 gas_limit: 8,
1480 gas_used: 9,
1481 timestamp: 10,
1482 extra_data: Bytes::from_static(&[11, 12]),
1483 base_fee_per_gas: U256::from(13),
1484 block_hash: B256::repeat_byte(14),
1485 transactions: vec![Bytes::from_static(&[15, 16])],
1486 }
1487 }
1488
1489 fn payload_v2() -> ExecutionPayloadV2 {
1490 ExecutionPayloadV2 { payload_inner: payload_v1(), withdrawals: vec![Withdrawal::default()] }
1491 }
1492
1493 fn payload_v3() -> ExecutionPayloadV3 {
1494 ExecutionPayloadV3 { payload_inner: payload_v2(), blob_gas_used: 17, excess_blob_gas: 18 }
1495 }
1496
1497 fn payload_v4() -> ExecutionPayloadV4 {
1498 ExecutionPayloadV4 {
1499 payload_inner: payload_v3(),
1500 block_access_list: Bytes::from_static(&[19, 20]),
1501 slot_number: 21,
1502 }
1503 }
1504
1505 fn attributes_cancun() -> PayloadAttributesCancun {
1506 PayloadAttributesCancun {
1507 timestamp: 1,
1508 prev_randao: B256::repeat_byte(2),
1509 suggested_fee_recipient: Address::repeat_byte(3),
1510 withdrawals: vec![Withdrawal::default()],
1511 parent_beacon_block_root: B256::repeat_byte(4),
1512 }
1513 }
1514
1515 fn state() -> ForkchoiceState {
1516 ForkchoiceState {
1517 head_block_hash: B256::repeat_byte(1),
1518 safe_block_hash: B256::repeat_byte(2),
1519 finalized_block_hash: B256::repeat_byte(3),
1520 }
1521 }
1522
1523 fn assert_roundtrip<T>(value: &T)
1524 where
1525 T: Encode + Decode + PartialEq + core::fmt::Debug,
1526 {
1527 assert_eq!(T::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), *value);
1528 }
1529
1530 #[test]
1531 fn execution_payload_envelopes_roundtrip() {
1532 assert_roundtrip(&ExecutionPayloadEnvelopeParis { payload: payload_v1() });
1533 assert_roundtrip(&ExecutionPayloadEnvelopeShanghai { payload: payload_v2() });
1534 assert_roundtrip(&ExecutionPayloadEnvelopeCancun {
1535 payload: payload_v3(),
1536 parent_beacon_block_root: B256::repeat_byte(1),
1537 });
1538 assert_roundtrip(&ExecutionPayloadEnvelopePrague {
1539 payload: payload_v3(),
1540 parent_beacon_block_root: B256::repeat_byte(1),
1541 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1542 });
1543 assert_roundtrip(&ExecutionPayloadEnvelopeOsaka {
1544 payload: payload_v3(),
1545 parent_beacon_block_root: B256::repeat_byte(1),
1546 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1547 });
1548 assert_roundtrip(&ExecutionPayloadEnvelopeAmsterdam {
1549 payload: payload_v4(),
1550 parent_beacon_block_root: B256::repeat_byte(1),
1551 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1552 });
1553 }
1554
1555 #[test]
1556 fn paris_submission_is_a_single_field_container() {
1557 let payload = payload_v1();
1558 let payload_bytes = payload.as_ssz_bytes();
1559 let envelope = ExecutionPayloadEnvelopeParis { payload };
1560 let encoded = envelope.as_ssz_bytes();
1561 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1562 assert_eq!(&encoded[4..], payload_bytes);
1563 }
1564
1565 #[test]
1566 fn built_payloads_roundtrip() {
1567 assert_roundtrip(&BuiltPayloadParis { payload: payload_v1(), block_value: U256::from(1) });
1568 assert_roundtrip(&BuiltPayloadShanghai {
1569 payload: payload_v2(),
1570 block_value: U256::from(1),
1571 });
1572 assert_roundtrip(&BuiltPayloadCancun {
1573 execution_payload: payload_v3(),
1574 block_value: U256::from(1),
1575 blobs_bundle: BlobsBundleV1::empty(),
1576 should_override_builder: true,
1577 });
1578 assert_roundtrip(&BuiltPayloadPrague {
1579 payload: payload_v3(),
1580 block_value: U256::from(1),
1581 blobs_bundle: BlobsBundleV1::empty(),
1582 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1583 should_override_builder: true,
1584 });
1585 assert_roundtrip(&BuiltPayloadOsaka {
1586 payload: payload_v3(),
1587 block_value: U256::from(1),
1588 blobs_bundle: BlobsBundleV2::empty(),
1589 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1590 should_override_builder: true,
1591 });
1592 assert_roundtrip(&BuiltPayloadAmsterdam {
1593 payload: payload_v4(),
1594 block_value: U256::from(1),
1595 blobs_bundle: BlobsBundleV2::empty(),
1596 execution_requests: Requests::from_requests([Bytes::from_static(&[2, 3])]),
1597 should_override_builder: true,
1598 });
1599 }
1600
1601 #[test]
1602 fn shanghai_built_payload_has_no_builder_override() {
1603 let payload = payload_v2();
1604 let payload_len = payload.ssz_bytes_len();
1605 let value = BuiltPayloadShanghai { payload, block_value: U256::from(1) };
1606 let encoded = value.as_ssz_bytes();
1607
1608 assert_eq!(&encoded[..4], &36u32.to_le_bytes());
1609 assert_eq!(encoded.len(), 36 + payload_len);
1610 }
1611
1612 #[test]
1613 fn legacy_built_payload_conversions_preserve_fields() {
1614 let shanghai = BuiltPayloadShanghai { payload: payload_v2(), block_value: U256::from(1) };
1615 let legacy = LegacyBuiltPayloadShanghai::from(shanghai.clone());
1616 assert_eq!(BuiltPayloadShanghai::try_from(legacy).unwrap(), shanghai);
1617
1618 let prague = BuiltPayloadPrague {
1619 payload: payload_v3(),
1620 block_value: U256::from(2),
1621 blobs_bundle: BlobsBundleV1::empty(),
1622 execution_requests: Requests::from_requests([Bytes::from_static(&[3, 4])]),
1623 should_override_builder: true,
1624 };
1625 let legacy = LegacyBuiltPayloadPrague::from(prague.clone());
1626 assert_eq!(BuiltPayloadPrague::from(legacy), prague);
1627
1628 let osaka = BuiltPayloadOsaka {
1629 payload: payload_v3(),
1630 block_value: U256::from(5),
1631 blobs_bundle: BlobsBundleV2::empty(),
1632 execution_requests: Requests::from_requests([Bytes::from_static(&[6, 7])]),
1633 should_override_builder: true,
1634 };
1635 let legacy = LegacyBuiltPayloadOsaka::from(osaka.clone());
1636 assert_eq!(BuiltPayloadOsaka::from(legacy), osaka);
1637
1638 let amsterdam = BuiltPayloadAmsterdam {
1639 payload: payload_v4(),
1640 block_value: U256::from(8),
1641 blobs_bundle: BlobsBundleV2::empty(),
1642 execution_requests: Requests::from_requests([Bytes::from_static(&[9, 10])]),
1643 should_override_builder: true,
1644 };
1645 let legacy = LegacyBuiltPayloadAmsterdam::from(amsterdam.clone());
1646 assert_eq!(BuiltPayloadAmsterdam::from(legacy), amsterdam);
1647 }
1648
1649 #[test]
1650 fn legacy_shanghai_built_payload_rejects_paris_payload() {
1651 let legacy = LegacyBuiltPayloadShanghai {
1652 execution_payload: ExecutionPayloadFieldV2::V1(payload_v1()),
1653 block_value: U256::from(1),
1654 };
1655 assert_eq!(
1656 BuiltPayloadShanghai::try_from(legacy),
1657 Err(BuiltPayloadConversionError::UnexpectedPayloadFork("Paris"))
1658 );
1659 }
1660
1661 #[test]
1662 fn prague_requests_precede_should_override_builder() {
1663 let value = BuiltPayloadPrague {
1664 payload: payload_v3(),
1665 block_value: U256::from(1),
1666 blobs_bundle: BlobsBundleV1::empty(),
1667 execution_requests: Requests::from_requests([Bytes::from_static(&[0xaa, 0xbb])]),
1668 should_override_builder: true,
1669 };
1670 let encoded = value.as_ssz_bytes();
1671 assert_eq!(encoded[48], 1);
1672 assert_eq!(&encoded[encoded.len() - 2..], &[0xaa, 0xbb]);
1673 }
1674
1675 #[test]
1676 fn forkchoice_updates_roundtrip() {
1677 let paris = PayloadAttributesParis {
1678 timestamp: 1,
1679 prev_randao: B256::repeat_byte(2),
1680 suggested_fee_recipient: Address::repeat_byte(3),
1681 };
1682 let shanghai = PayloadAttributesShanghai {
1683 timestamp: 1,
1684 prev_randao: B256::repeat_byte(2),
1685 suggested_fee_recipient: Address::repeat_byte(3),
1686 withdrawals: vec![Withdrawal::default()],
1687 };
1688 let amsterdam = PayloadAttributesAmsterdam {
1689 timestamp: 1,
1690 prev_randao: B256::repeat_byte(2),
1691 suggested_fee_recipient: Address::repeat_byte(3),
1692 withdrawals: vec![Withdrawal::default()],
1693 parent_beacon_block_root: B256::repeat_byte(4),
1694 slot_number: 5,
1695 target_gas_limit: 6,
1696 };
1697 assert_roundtrip(&ForkchoiceUpdateParis {
1698 forkchoice_state: state(),
1699 payload_attributes: Optional::some(paris),
1700 });
1701 assert_roundtrip(&ForkchoiceUpdateShanghai {
1702 forkchoice_state: state(),
1703 payload_attributes: Optional::some(shanghai),
1704 });
1705 assert_roundtrip(&ForkchoiceUpdateCancun {
1706 forkchoice_state: state(),
1707 payload_attributes: Optional::some(attributes_cancun()),
1708 });
1709 assert_roundtrip(&ForkchoiceUpdatePrague {
1710 forkchoice_state: state(),
1711 payload_attributes: Optional::some(attributes_cancun()),
1712 });
1713 assert_roundtrip(&ForkchoiceUpdateOsaka {
1714 forkchoice_state: state(),
1715 payload_attributes: Optional::some(attributes_cancun()),
1716 });
1717 assert_roundtrip(&ForkchoiceUpdateAmsterdam {
1718 forkchoice_state: state(),
1719 payload_attributes: Optional::some(amsterdam),
1720 custody_columns: Optional::some(B128::repeat_byte(0xa5)),
1721 });
1722 }
1723
1724 #[test]
1725 fn payload_attributes_legacy_conversions_preserve_fork_shape() {
1726 let cancun = attributes_cancun();
1727 let legacy = LegacyPayloadAttributes::from(cancun.clone());
1728 assert_eq!(PayloadAttributesCancun::try_from(legacy).unwrap(), cancun);
1729
1730 let amsterdam = PayloadAttributesAmsterdam {
1731 timestamp: 1,
1732 prev_randao: B256::repeat_byte(2),
1733 suggested_fee_recipient: Address::repeat_byte(3),
1734 withdrawals: vec![Withdrawal::default()],
1735 parent_beacon_block_root: B256::repeat_byte(4),
1736 slot_number: 5,
1737 target_gas_limit: 6,
1738 };
1739 let legacy = LegacyPayloadAttributes::from(amsterdam.clone());
1740 assert_eq!(PayloadAttributesAmsterdam::try_from(legacy).unwrap(), amsterdam);
1741 }
1742
1743 #[test]
1744 fn payload_attributes_legacy_conversions_reject_loss() {
1745 let mut legacy = LegacyPayloadAttributes::default();
1746 assert_eq!(
1747 PayloadAttributesShanghai::try_from(legacy.clone()),
1748 Err(PayloadAttributesConversionError::MissingField("withdrawals"))
1749 );
1750
1751 legacy.withdrawals = Some(vec![]);
1752 legacy.parent_beacon_block_root = Some(B256::ZERO);
1753 assert_eq!(
1754 PayloadAttributesShanghai::try_from(legacy),
1755 Err(PayloadAttributesConversionError::UnexpectedField("parent_beacon_block_root"))
1756 );
1757 }
1758
1759 #[test]
1760 fn every_payload_status_roundtrips() {
1761 for status in [
1762 PayloadStatusEnum::Valid,
1763 PayloadStatusEnum::Invalid { validation_error: "invalid".into() },
1764 PayloadStatusEnum::Syncing,
1765 PayloadStatusEnum::Accepted,
1766 ] {
1767 let validation_error = legacy_validation_error(&status).unwrap();
1768 let value = PayloadStatus {
1769 status,
1770 latest_valid_hash: Optional::some(B256::ZERO),
1771 validation_error,
1772 };
1773 assert_eq!(PayloadStatus::from_ssz_bytes(&value.as_ssz_bytes()).unwrap(), value);
1774 }
1775 }
1776
1777 #[test]
1778 fn payload_status_preserves_absent_invalid_validation_error() {
1779 let mut bytes = Vec::new();
1780 let mut encoder = ssz::SszEncoder::container(&mut bytes, 9);
1781 encoder.append(&1u8);
1782 encoder.append(&Optional::<B256>::none());
1783 encoder.append(&Optional::<ErrorBytes>::none());
1784 encoder.finalize();
1785
1786 let decoded = PayloadStatus::from_ssz_bytes(&bytes).unwrap();
1787 assert!(decoded.validation_error.is_none());
1788 assert_eq!(decoded.as_ssz_bytes(), bytes);
1789 }
1790
1791 #[test]
1792 fn payload_status_rejects_non_invalid_validation_error() {
1793 let mut bytes = Vec::new();
1794 let mut encoder = ssz::SszEncoder::container(&mut bytes, 9);
1795 encoder.append(&0u8);
1796 encoder.append(&Optional::<B256>::none());
1797 encoder.append(&Optional::some(Vec::<u8>::new()));
1798 encoder.finalize();
1799 assert!(PayloadStatus::from_ssz_bytes(&bytes).is_err());
1800 }
1801
1802 #[test]
1803 fn payload_status_legacy_conversion_rejects_oversized_error() {
1804 assert!(PayloadStatus::try_from(LegacyPayloadStatus {
1805 status: PayloadStatusEnum::Invalid { validation_error: "x".repeat(1025) },
1806 latest_valid_hash: None,
1807 })
1808 .is_err());
1809 }
1810
1811 #[test]
1812 fn forkchoice_response_distinguishes_absent_and_zero_payload_id() {
1813 let status = PayloadStatus {
1814 status: PayloadStatusEnum::Valid,
1815 latest_valid_hash: Optional::none(),
1816 validation_error: Optional::none(),
1817 };
1818 let none = ForkchoiceUpdateResponse {
1819 payload_status: status.clone(),
1820 payload_id: Optional::none(),
1821 };
1822 let zero = ForkchoiceUpdateResponse {
1823 payload_status: status,
1824 payload_id: Optional::some(PayloadId::default()),
1825 };
1826
1827 assert_ne!(none.as_ssz_bytes(), zero.as_ssz_bytes());
1828 assert_roundtrip(&none);
1829 assert_roundtrip(&zero);
1830 }
1831
1832 #[test]
1833 fn forkchoice_conversion_rejects_accepted() {
1834 let legacy = LegacyForkchoice::from_status(PayloadStatusEnum::Accepted);
1835 assert_eq!(
1836 ForkchoiceUpdateResponse::try_from(legacy),
1837 Err(ConversionError::AcceptedForkchoice)
1838 );
1839 }
1840
1841 fn blob_v2(byte: u8) -> BlobAndProofV2 {
1842 BlobAndProofV2 {
1843 blob: Box::new(Blob::repeat_byte(byte)),
1844 proofs: vec![Bytes48::repeat_byte(byte)],
1845 }
1846 }
1847
1848 #[test]
1849 fn blob_requests_are_single_field_containers() {
1850 let request = BlobsV1Request { versioned_hashes: vec![B256::repeat_byte(0x42)] };
1851 let encoded = request.as_ssz_bytes();
1852
1853 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1854 assert_eq!(&encoded[4..], B256::repeat_byte(0x42).as_slice());
1855 assert_eq!(BlobsV1Request::from_ssz_bytes(&encoded).unwrap(), request);
1856
1857 let _: BlobsV2Request = BlobsV2Request::from_ssz_bytes(&encoded).unwrap();
1858 let _: BlobsV3Request = BlobsV3Request::from_ssz_bytes(&encoded).unwrap();
1859 }
1860
1861 #[test]
1862 fn blob_v4_request_roundtrips_bitvector() {
1863 let request = BlobsV4Request {
1864 versioned_hashes: vec![B256::repeat_byte(0x11)],
1865 indices_bitarray: B128::repeat_byte(0xa5),
1866 };
1867
1868 assert_roundtrip(&request);
1869 }
1870
1871 #[test]
1872 fn blob_response_conversions_preserve_availability_and_order() {
1873 let v1 = BlobsV1Response::try_from(vec![None]).unwrap();
1874 assert!(!v1.entries[0].available);
1875 assert_eq!(v1.entries[0].contents, zero_blob_v1());
1876
1877 let v2 = BlobsV2Response::try_from(vec![blob_v2(1), blob_v2(2)]).unwrap();
1878 assert!(v2.entries.iter().all(|entry| entry.available));
1879
1880 let v3 = BlobsV3Response::try_from(vec![Some(blob_v2(1)), None, Some(blob_v2(3))]).unwrap();
1881 assert_eq!(
1882 v3.entries.iter().map(|entry| entry.available).collect::<Vec<_>>(),
1883 [true, false, true]
1884 );
1885 assert_eq!(v3.entries[2].contents.blob.as_slice(), Blob::repeat_byte(3).as_slice());
1886
1887 let legacy_partial = BlobCellsAndProofsV1 {
1888 blob_cells: vec![Some(Cell::repeat_byte(1)), None],
1889 proofs: vec![Some(Bytes48::repeat_byte(2)), None],
1890 };
1891 let v4 = BlobsV4Response::try_from(vec![None, Some(legacy_partial)]).unwrap();
1892 assert!(!v4.entries[0].available);
1893 assert!(v4.entries[1].available);
1894 assert!(v4.entries[1].contents.blob_cells[0].is_some());
1895 assert!(v4.entries[1].contents.proofs[1].is_none());
1896 }
1897
1898 #[test]
1899 fn blob_cells_and_proofs_uses_rest_optional() {
1900 let value = BlobCellsAndProofs {
1901 blob_cells: vec![Optional::some(Cell::repeat_byte(1))],
1902 proofs: vec![Optional::some(Bytes48::repeat_byte(2))],
1903 };
1904 let encoded = value.as_ssz_bytes();
1905
1906 assert_eq!(BlobCellsAndProofs::from_ssz_bytes(&encoded).unwrap(), value);
1907 assert!(!encoded[8..].starts_with(&[1, 0, 0, 0]));
1908 }
1909
1910 #[test]
1911 fn payload_body_requests_are_single_field_containers() {
1912 let request = BodiesByHashRequest { block_hashes: vec![B256::repeat_byte(0x33)] };
1913 let encoded = request.as_ssz_bytes();
1914
1915 assert_eq!(&encoded[..4], &4u32.to_le_bytes());
1916 assert_eq!(&encoded[4..], B256::repeat_byte(0x33).as_slice());
1917 assert_eq!(BodiesByHashRequest::from_ssz_bytes(&encoded).unwrap(), request);
1918 }
1919
1920 #[test]
1921 fn payload_body_responses_preserve_availability() {
1922 let legacy = LegacyExecutionPayloadBodyV1 {
1923 transactions: vec![Bytes::from_static(&[1, 2, 3])],
1924 withdrawals: Some(vec![Withdrawal::default()]),
1925 };
1926 let response =
1927 BodiesResponseShanghai::from_optional_bodies(vec![Some(legacy), None], |body| {
1928 ExecutionPayloadBodyShanghai::try_from(body).ok()
1929 });
1930
1931 assert!(response.entries[0].available);
1932 assert!(!response.entries[1].available);
1933 assert_roundtrip(&response);
1934 }
1935
1936 #[test]
1937 fn witness_response_roundtrips_when_status_is_valid() {
1938 let payload_status = PayloadStatus {
1939 status: PayloadStatusEnum::Valid,
1940 latest_valid_hash: Optional::none(),
1941 validation_error: Optional::none(),
1942 };
1943 let witness = ExecutionWitnessV1 {
1944 state: vec![vec![1, 2, 3]],
1945 codes: vec![vec![4, 5]],
1946 headers: vec![vec![6]],
1947 };
1948 let response = PayloadStatusWithWitness::new(payload_status, Some(witness));
1949
1950 assert_roundtrip(&response);
1951 }
1952
1953 #[test]
1954 fn witness_response_omits_witness_for_non_valid_status() {
1955 let payload_status = PayloadStatus {
1956 status: PayloadStatusEnum::Syncing,
1957 latest_valid_hash: Optional::none(),
1958 validation_error: Optional::none(),
1959 };
1960 let response =
1961 PayloadStatusWithWitness::new(payload_status, Some(ExecutionWitnessV1::default()));
1962
1963 assert!(response.witness.is_none());
1964 assert_roundtrip(&response);
1965 }
1966
1967 #[test]
1968 fn optional_rejects_more_than_one_value() {
1969 assert!(Optional::<B256>::from_ssz_bytes(&[0; 64]).is_err());
1970 }
1971}